Skip to content

Instantly share code, notes, and snippets.

@ishikawa
Created November 8, 2008 10:28
Show Gist options
  • Save ishikawa/23049 to your computer and use it in GitHub Desktop.
Save ishikawa/23049 to your computer and use it in GitHub Desktop.
Cocoa custom text view with managing marked text
#import <Cocoa/Cocoa.h>
@interface MyTextView : NSView <NSTextInput, NSTextInputClient> {
NSMutableAttributedString *_text;
NSRange _selectedRange;
NSRange _markedRange;
}
@end
#import "MyTextView.h"
static const NSRange kEmptyRange = {NSNotFound, 0};
@implementation MyTextView
- (id) initWithFrame: (NSRect) frameRect {
if (self = [super initWithFrame:frameRect]) {
_text = [[NSMutableAttributedString alloc] init];
_selectedRange = _markedRange = kEmptyRange;
}
return self;
}
- (void) dealloc {
[_text release];
[super dealloc];
}
- (void) updateWithDefaultTextAttributes {
const NSRange entireRange = NSMakeRange(0, [_text length]);
[_text removeAttribute: NSFontAttributeName
range: entireRange];
[_text addAttribute: NSFontAttributeName
value: [NSFont userFontOfSize: 18.0f]
range: entireRange];
}
- (void) drawRect: (NSRect) theRect {
[[NSColor whiteColor] set];
NSFrameRect(theRect);
NSRectFill(theRect);
[[NSColor grayColor] set];
NSFrameRect(theRect);
[self updateWithDefaultTextAttributes];
[_text drawInRect: NSMakeRect(
theRect.origin.x + 5.0f, theRect.origin.y,
theRect.size.width, theRect.size.height)];
}
// ----------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------
- (void) removeMarkedText {
if (_markedRange.location != NSNotFound) {
if (NSMaxRange(_markedRange) <= [_text length])
[_text deleteCharactersInRange: _markedRange];
_markedRange = _selectedRange = kEmptyRange;
}
}
- (void) appendCharacters: (id) aString {
if ([aString isKindOfClass: [NSAttributedString class]]) {
[_text appendAttributedString: aString];
} else {
[[_text mutableString] appendString: aString];
}
}
- (void) replaceCharactersInRange: (NSRange) aRange
withText: (id) aString
effectiveRange: (NSRangePointer) effectiveRange
{
NSRange replacementRange = aRange;
if (replacementRange.location == NSNotFound) {
replacementRange.location = [_text length];
replacementRange.length = 0;
}
NSAssert2(
NSMaxRange(replacementRange) <= [_text length],
@"Out of bounds: %@ for length %u",
NSStringFromRange(replacementRange),
[_text length]);
if ([aString isKindOfClass: [NSAttributedString class]]) {
[_text replaceCharactersInRange: replacementRange
withAttributedString: aString];
} else {
[_text replaceCharactersInRange: replacementRange
withString: aString];
}
if (effectiveRange != NULL) {
*effectiveRange = NSMakeRange(replacementRange.location, [aString length]);
}
}
/**
* If there is no marked text, the current selection is replaced.
* If there is no selection, the string is inserted at the insertion point.
*
* @param replacementRange The range to replace, computed from
* the beginning of the marked text.
*/
- (NSRange) replacementMarkedRange: (NSRange) replacementRange {
NSRange markedRange = _markedRange;
if (markedRange.location == NSNotFound) markedRange = _selectedRange;
if (replacementRange.location != NSNotFound) {
NSRange newRange = markedRange;
newRange.location += replacementRange.location;
newRange.length += replacementRange.length;
if (NSMaxRange(newRange) <= NSMaxRange(markedRange)) {
markedRange = newRange;
}
}
return markedRange;
}
// ----------------------------------------------------------------
// NSResponder
// ----------------------------------------------------------------
- (BOOL) acceptsFirstResponder {
return YES;
}
- (void) keyDown: (NSEvent *) theEvent {
[self interpretKeyEvents: [NSArray arrayWithObject: theEvent]];
}
- (void) deleteBackward: (id) sender {
const NSUInteger length = [_text length];
if (length > 0) {
[_text deleteCharactersInRange: NSMakeRange(length - 1, 1)];
[self setNeedsDisplay: YES];
}
}
- (void) insertNewline: (id) sender {
[self insertText: @"\n"];
}
- (void) insertTab: (id) sender {
[self insertText: @"\t"];
}
- (void) insertText: (id) aString {
[self appendCharacters: aString];
[self setNeedsDisplay: YES];
}
// ----------------------------------------------------------------
// NSTextInput
// ----------------------------------------------------------------
- (void) doCommandBySelector: (SEL) aSelector {
[super doCommandBySelector: aSelector];
}
- (void) setMarkedText: (id) aString selectedRange: (NSRange) selRange {
[self setMarkedText: aString
selectedRange: selRange
replacementRange: kEmptyRange];
}
- (void) unmarkText {
}
- (BOOL) hasMarkedText {
return _markedRange.location != NSNotFound;
}
- (NSInteger) conversationIdentifier {
return (NSInteger) self;
}
- (NSAttributedString *) attributedSubstringFromRange: (NSRange) theRange {
return [self attributedSubstringForProposedRange:theRange actualRange:NULL];
}
- (NSRange) markedRange {
return _markedRange;
}
- (NSRange) selectedRange {
return _selectedRange;
}
- (NSRect) firstRectForCharacterRange: (NSRange) theRange {
return [self firstRectForCharacterRange:theRange actualRange:NULL];
}
- (NSUInteger) characterIndexForPoint: (NSPoint) thePoint {
return 0;
}
- (NSArray *) validAttributesForMarkedText {
return [NSArray array];
}
// ----------------------------------------------------------------
// NSTextInputClient (Mac OS X 10.5)
// ----------------------------------------------------------------
- (void) insertText: (id) aString
replacementRange: (NSRange) replacementRange
{
[self removeMarkedText];
[self replaceCharactersInRange: replacementRange
withText: aString
effectiveRange: NULL];
[self setNeedsDisplay: YES];
}
- (void) setMarkedText: (id) aString
selectedRange: (NSRange) selectedRange
replacementRange: (NSRange) replacementRange
{
NSRange effectiveRange;
[self replaceCharactersInRange: [self replacementMarkedRange: replacementRange]
withText: aString
effectiveRange: &effectiveRange];
if (selectedRange.location != NSNotFound) selectedRange.location += effectiveRange.location;
_selectedRange = selectedRange;
_markedRange = effectiveRange;
if ([aString length] == 0) [self removeMarkedText];
[self setNeedsDisplay: YES];
}
- (NSAttributedString *) attributedSubstringForProposedRange: (NSRange) aRange
actualRange: (NSRangePointer) actualRange
{
return [[[NSAttributedString alloc] init] autorelease];
}
- (NSRect) firstRectForCharacterRange: (NSRange) aRange
actualRange: (NSRangePointer) actualRange
{
return NSZeroRect;
}
- (NSAttributedString *) attributedString {
return _text;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment