Kyl*_*yle 3 cocoa selection nstextview osx-mountain-lion
如果a NSTextView
包含以下内容:
SELECT someTable.someColumn FROM someTable
Run Code Online (Sandbox Code Playgroud)
并且用户双击someTable.someColumn
,整个事物被选中(期间的两侧).在这种特定情况下(查询编辑器),对于选择someTable
或者someColumn
选择更有意义.
我试着四处寻找,看看我是否能找到一种方法来自定义选择,但到目前为止我一直无法做到.
目前我正在考虑的是进行子类化NSTextView
并执行以下操作:
- (void)mouseDown:(NSEvent *)theEvent
{
if(theEvent.clickCount == 2)
{
// TODO: Handle double click selection.
}
else
{
[super mouseDown:theEvent];
}
}
Run Code Online (Sandbox Code Playgroud)
有没有人对此有任何想法或替代方案?(我是否还有另一种方法,可能更适合覆盖)?
首先,违背了先前的回答,NSTextView
的selectionRangeForProposedRange:granularity:
方法不重写实现这一目标的正确的位置.在Apple的"Cocoa Text Architecture"文档中(https://developer.apple.com/library/prerelease/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TextEditing/TextEditing.html - 请参阅"Subclassing NSTextView"部分)Apple明确声明"这些机制不适用于改变语言词定义(例如通过双击选择的内容)." 我不确定苹果为什么会这样,但我怀疑是因为selectionRangeForProposedRange:granularity:
没有得到关于建议范围的哪个部分是初始点击点的任何信息,而不是用户拖动的地方的哪个部分; 使双击拖动行为正确可能很难超越此方法.也许还有其他问题,我不知道; 该文档有点神秘.也许苹果计划稍后改变选择机制,以打破这种覆盖.也许还有其他方面来定义什么是"字",这里的覆盖无法解决.谁知道; 但是当他们做出这样的陈述时,通常最好遵循Apple的指示.
奇怪的是,Apple的文档继续说"选择的细节是在文本系统的较低(当前是私有的)级别处理的." 我认为这是过时的,因为实际上必要的支持确实存在:在doubleClickAtIndex:
上法NSAttributedString
(在NSAttributedStringKitAdditions
类别).该方法由Cocoa文本系统(在NSTextStorage
子类中NSAttributedString
)用于确定单词边界.子类化NSTextStorage
有点棘手,因此我将在这里为一个名为的子类提供完整的实现MyTextStorage
.这个子类化代码大部分NSTextStorage
来自Apple的Ali Ozer.
在MyTextStorage .h
:
@interface MyTextStorage : NSTextStorage
- (id)init;
- (id)initWithAttributedString:(NSAttributedString *)attrStr;
@end
Run Code Online (Sandbox Code Playgroud)
在MyTextStorage.m
:
@interface MyTextStorage ()
{
NSMutableAttributedString *contents;
}
@end
@implementation MyTextStorage
- (id)initWithAttributedString:(NSAttributedString *)attrStr
{
if (self = [super init])
{
contents = attrStr ? [attrStr mutableCopy] : [[NSMutableAttributedString alloc] init];
}
return self;
}
- init
{
return [self initWithAttributedString:nil];
}
- (void)dealloc
{
[contents release];
[super dealloc];
}
// The next set of methods are the primitives for attributed and mutable attributed string...
- (NSString *)string
{
return [contents string];
}
- (NSDictionary *)attributesAtIndex:(NSUInteger)location effectiveRange:(NSRange *)range
{
return [contents attributesAtIndex:location effectiveRange:range];
}
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str
{
NSUInteger origLen = [self length];
[contents replaceCharactersInRange:range withString:str];
[self edited:NSTextStorageEditedCharacters range:range changeInLength:[self length] - origLen];
}
- (void)setAttributes:(NSDictionary *)attrs range:(NSRange)range
{
[contents setAttributes:attrs range:range];
[self edited:NSTextStorageEditedAttributes range:range changeInLength:0];
}
// And now the actual reason for this subclass: to provide code-aware word selection behavior
- (NSRange)doubleClickAtIndex:(NSUInteger)location
{
// Start by calling super to get a proposed range. This is documented to raise if location >= [self length]
// or location < 0, so in the code below we can assume that location indicates a valid character position.
NSRange superRange = [super doubleClickAtIndex:location];
NSString *string = [self string];
// If the user has actually double-clicked a period, we want to just return the range of the period.
if ([string characterAtIndex:location] == '.')
return NSMakeRange(location, 1);
// The case where super's behavior is wrong involves the dot operator; x.y should not be considered a word.
// So we check for a period before or after the anchor position, and trim away the periods and everything
// past them on both sides. This will correctly handle longer sequences like foo.bar.baz.is.a.test.
NSRange candidateRangeBeforeLocation = NSMakeRange(superRange.location, location - superRange.location);
NSRange candidateRangeAfterLocation = NSMakeRange(location + 1, NSMaxRange(superRange) - (location + 1));
NSRange periodBeforeRange = [string rangeOfString:@"." options:NSBackwardsSearch range:candidateRangeBeforeLocation];
NSRange periodAfterRange = [string rangeOfString:@"." options:(NSStringCompareOptions)0 range:candidateRangeAfterLocation];
if (periodBeforeRange.location != NSNotFound)
{
// Change superRange to start after the preceding period; fix its length so its end remains unchanged.
superRange.length -= (periodBeforeRange.location + 1 - superRange.location);
superRange.location = periodBeforeRange.location + 1;
}
if (periodAfterRange.location != NSNotFound)
{
// Change superRange to end before the following period
superRange.length -= (NSMaxRange(superRange) - periodAfterRange.location);
}
return superRange;
}
@end
Run Code Online (Sandbox Code Playgroud)
然后最后一部分实际上是在textview中使用自定义子类.如果你有一个NSTextView子类,你可以在它的awakeFromNib方法中执行此操作; 否则,在你的笔尖加载之后,在你有机会的任何地方做这个; 例如,在awakeFromNib调用相关窗口或控制器时,或者只是在调用后加载包含textview的nib.无论如何,你想要这样做(textview是你的NSTextView
对象):
[[textview layoutManager] replaceTextStorage:[[[MyTextStorage alloc] init] autorelease]];
Run Code Online (Sandbox Code Playgroud)
除此之外,你应该好好去,除非我在解释这个错误时犯了错误!
最后,注意,是另一种方法NSAttributedString
,nextWordFromIndex:forward:
即使用由可可的文字系统,当用户将插入点移动到下一个/前一个字.如果你想要那样的东西遵循相同的单词定义,你也需要将它子类化.对于我的应用程序,我没有这样做 - 我希望下一个/上一个单词移动整个abcd序列(或者更准确地说我只是不关心) - 所以我没有在此分享的实现.留给读者练习.