• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Undo support of NSTextStorage
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Undo support of NSTextStorage


  • Subject: Undo support of NSTextStorage
  • From: Jesse Grosjean <email@hidden>
  • Date: Fri, 2 May 2003 22:37:32 -0400

I wrote a while ago asking if anyone know how to add undo support to NSTextStorage. I didn't get any responses, but now I'm further along and hope someone can explain to me what I'm doing wrong. Simple undo (char by char) is easy, but I would like to be able to merge appropriate edits and that's where I'm having difficulties with the undo stack.

Here is what I have so far. Seems to work for me on paper, but I'm doing something wrong and corupting the undo stack. The basic idea here is to commit partial edits (ones that might be merged with later edits) to a separate HBTextStorageEdit object. This is done so that I have a "handle" on these edits and can remove them from the undo stack when they need to be merged with later edits. When a situation arrises where the edits cannot be merged they are commited to the undo stack. By this "commit" I mean that the edits are placed on the undo stack with the NSTextStorage as the target, they won't be removed or merged with other edits after this commit.

So what do I need to do to get this thing working?

@interface HBTextStorage : NSTextStorage {
NSMutableAttributedString *_textContents;
HBTextStorageEdit *_editInProgress;
}
@end

@implementation HBTextStorage

- ( NSString *)string {
return [_textContents string];
}

- (void)addAttribute:( NSString *)name value:(id)value range:( NSRange )aRange {
[_textContents addAttribute:name value:value range:aRange]; // cover bug listed on cocoadev
}

- ( NSDictionary *)attributesAtIndex:(unsigned)index effectiveRange:(NSRangePointer)aRange {
return [_textContents attributesAtIndex:index effectiveRange:aRange];
}

- (void)setAttributes:( NSDictionary *)attributes range:( NSRange )aRange {
[[self textContents] setAttributes:attributes range:aRange];
[self edited:NSTextStorageEditedAttributes range:aRange changeInLength:0];
}

- (void)replaceCharactersInRange:( NSRange )aRange withString:( NSString *)aString {
NSUndoManager *undoManager = [self undoManager];
NSRange insertedRange = NSMakeRange (aRange.location, [aString length]);
int origLen = [self length];
BOOL commitThisChangeToUndoStack = NO;

if ([undoManager isUndoing] || [undoManager isRedoing]) {
[_editInProgress release];
_editInProgress = nil;
commitThisChangeToUndoStack = YES;
} else {
[undoManager removeAllActionsWithTarget:_editInProgress];

// 1. try to merge changes with edit in progress
if (![_editInProgress merge:aString range:aRange]) {

// 2. commit current edit if can't merge
[_editInProgress commitEditToUndoStack];
[_editInProgress release];
_editInProgress = nil;

BOOL singleInsert = [HBTextStorageEdit isSingleCharInsert:aString range:aRange];
BOOL singleDelete = [HBTextStorageEdit isSingleCharDelete:aString range:aRange];

// 3. start new edit in progress
if (singleInsert || singleDelete) {
_editInProgress = [[HBTextStorageEdit alloc] initWithString:aString range:aRange target:self];
[[undoManager prepareWithInvocationTarget:_editInProgress] undoEdit];

// 4. commit edit
} else {
commitThisChangeToUndoStack = YES;
}
}

if (_editInProgress) {
[[undoManager prepareWithInvocationTarget:_editInProgress] undoEdit];
[undoManager setActionName:NSLocalizedString(@"Typing", @"")];
}
}

if (commitThisChangeToUndoStack) { NSString *replacedString = [[self string] substringWithRange:aRange];
[[undoManager prepareWithInvocationTarget:self] replaceCharactersInRange:insertedRange withString:replacedString];
[undoManager setActionName:NSLocalizedString(@"Typing", @"")];
}

[[self textContents] replaceCharactersInRange:aRange withString:aString];
[self edited:NSTextStorageEditedCharacters range:aRange changeInLength:[self length] - origLen];
}

@end

@interface HBTextStorageEdit : NSObject {
HBTextStorage *_target;
NSRange _editRange;
NSString *_replacedString;
}

+ (BOOL)isSingleCharInsert:( NSString *)edit range:( NSRange )range;
+ (BOOL)isSingleCharDelete:( NSString *)edit range:( NSRange )range;

- (id)initWithString:( NSString *)string range:( NSRange )range target:(HBTextStorage *)target;
- (void)undoEdit;
- (void)commitEditToUndoStack;
- (BOOL)merge:( NSString *)edit range:( NSRange )range;

@end

@implementation HBTextStorageEdit

+ (BOOL)isSingleCharInsert:( NSString *)edit range:( NSRange )range {
return range.length == 0 && [edit length] == 1;
}

+ (BOOL)isSingleCharDelete:( NSString *)edit range:( NSRange )range {
return range.length == 1 && [edit length] == 0;
}

- (id)initWithString:( NSString *)string range:( NSRange )range target:(HBTextStorage *)target {
if (self = [super init]) {
_target = target;
_editRange.location = range.location;

if (range.length == 0) {
_editRange.length = 1;
_replacedString = @"";
} else {
_editRange.length = 0;
_replacedString = [[[target string] substringWithRange:range] retain];
}
}

return self;
}

- (void)dealloc {
[_replacedString release];
[super dealloc];
}

- (void)undoEdit {
[[_target undoManager] removeAllActionsWithTarget:self];
[_target replaceCharactersInRange:_editRange withString:_replacedString];
}

- (void)commitEditToUndoStack {
NSUndoManager *undoManager = [_target undoManager];
[[_target undoManager] removeAllActionsWithTarget:self];
[[undoManager prepareWithInvocationTarget:_target] replaceCharactersInRange:_editRange withString:_replacedString];
[undoManager setActionName:NSLocalizedString(@"Typing", @"")];
}

- (BOOL)merge:( NSString *)edit range:( NSRange )range {
int currentCursor = _editRange.location + _editRange.length;
BOOL singleInsert = range.location == currentCursor && [HBTextStorageEdit isSingleCharInsert:edit range:range];
BOOL singleDelete = range.location == currentCursor - 1 && [HBTextStorageEdit isSingleCharDelete:edit range:range];

if (singleInsert) {
_editRange.length++;
return YES;
} else if (singleDelete) {
if (_editRange.length > 0) {
_editRange.length--;
} else { NSString *temp = [[_target string] substringWithRange:range];
[_replacedString autorelease];
_replacedString = [[temp stringByAppendingString:_replacedString] retain];
_editRange.location--;
}
return YES;
}

return NO;
}

@end

Thanks for any help. Sorry for the long post but I think this code can be useful to a number of people so it's worthwhile to get it working correctly.

----------------------------------------------------------------
Jesse Grosjean / Hog Bay Software, Quality Mac Software

163 Westway RD #204
Greenbelt, MD 20770
email@hidden
http://www.hogbay.com/software
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives: http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.

  • Prev by Date: Right Justify NSTextField?
  • Next by Date: Re: mutating method sent to immutable object
  • Previous by thread: Right Justify NSTextField?
  • Next by thread: Re: Bug/limitation in referencing controls in AS Studio
  • Index(es):
    • Date
    • Thread