mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-04-23 07:00:46 +00:00
Implement coalescing of undo actions for typing events in NSTextView.
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@29164 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
fbf8c3edb5
commit
395f9a293b
3 changed files with 169 additions and 43 deletions
|
@ -1,3 +1,9 @@
|
|||
2009-12-23 Wolfgang Lux <wolfgang.lux@gmail.com>
|
||||
|
||||
* Source/NSTextView.m (-dealloc, -breakUndoCoalescing,
|
||||
-shouldChangeTextInRange:replacementString:): Implement coalescing
|
||||
of undo actions for typing events.
|
||||
|
||||
2009-12-23 Wolfgang Lux <wolfgang.lux@gmail.com>
|
||||
|
||||
* Source/NSTableView.m (-mouseDown:): A single click into a cell
|
||||
|
|
|
@ -228,13 +228,12 @@ therefore be stored in the NSLayoutManager to avoid problems.
|
|||
|
||||
/* Ivar to store the location where text is going to be inserted during
|
||||
* a DnD operation.
|
||||
* Note: This used to be a range attribute with a different meaning; the
|
||||
* _dragTargetUnused attribute is present just to preserve the size of
|
||||
* NSTextView instances.
|
||||
* FIXME: Drop this attribute on the next major release.
|
||||
*/
|
||||
unsigned int _dragTargetLocation;
|
||||
unsigned int _dragTargetUnused;
|
||||
|
||||
/* Ivar used to implement coalescing of undo operations */
|
||||
id _undoObject;
|
||||
|
||||
/*
|
||||
TODO:
|
||||
Still need to figure out what "proper behavior" is when moving between two
|
||||
|
|
|
@ -106,6 +106,21 @@ a new internal method called from NSLayoutManager when text has changed.
|
|||
*/
|
||||
|
||||
|
||||
@interface NSTextViewUndoObject : NSObject
|
||||
{
|
||||
NSRange range;
|
||||
NSAttributedString *string;
|
||||
NSRange selectedRange;
|
||||
}
|
||||
- (id) initWithRange: (NSRange)aRange
|
||||
attributedString: (NSAttributedString *)aString
|
||||
selectedRange: (NSRange)selected;
|
||||
- (NSRange) range;
|
||||
- (void) setRange: (NSRange)aRange;
|
||||
- (void) performUndo: (NSTextStorage *)aTextStorage;
|
||||
- (NSTextView *) bestTextViewForTextStorage: (NSTextStorage *)aTextStorage;
|
||||
@end
|
||||
|
||||
|
||||
/*
|
||||
Interface for a bunch of internal methods that need to be cleaned up.
|
||||
|
@ -129,13 +144,6 @@ Interface for a bunch of internal methods that need to be cleaned up.
|
|||
@end
|
||||
|
||||
|
||||
@interface NSTextStorage(NSTextViewUndoSupport)
|
||||
- (NSTextView *)_bestTextViewForUndo;
|
||||
- (void)_undoReplaceCharactersInRange: (NSRange)undoRange
|
||||
withAttributedString: (NSAttributedString *)undoString
|
||||
selectedRange: (NSRange)selectedRange;
|
||||
@end
|
||||
|
||||
// This class is a helper for keyed unarchiving only
|
||||
@interface NSTextViewSharedData : NSObject
|
||||
{
|
||||
|
@ -278,6 +286,11 @@ Interface for a bunch of internal methods that need to be cleaned up.
|
|||
@end
|
||||
|
||||
|
||||
@interface NSTextStorage(NSTextViewUndoSupport)
|
||||
- (void) _undoTextChange: (NSTextViewUndoObject *)anObject;
|
||||
@end
|
||||
|
||||
|
||||
/**** Misc. helpers and stuff ****/
|
||||
|
||||
static const int currentVersion = 2;
|
||||
|
@ -385,7 +398,7 @@ some cases, so it needs to be safe wrt. that.
|
|||
|
||||
|
||||
/*
|
||||
_syncTextViewsCalling:withFlag: calls a set method on all text
|
||||
_syncTextViewsByCalling:withFlag: calls a set method on all text
|
||||
views sharing the same layout manager as this one. It sets the
|
||||
IS_SYNCHRONIZING_FLAGS flag to YES to prevent recursive calls;
|
||||
calls the specified action on all the textviews (this one included)
|
||||
|
@ -406,7 +419,7 @@ editing is turned on or off.
|
|||
if (IS_SYNCHRONIZING_FLAGS == YES)
|
||||
{
|
||||
[NSException raise: NSGenericException
|
||||
format: @"_syncTextViewsCalling:withFlag: called recursively"];
|
||||
format: @"_syncTextViewsByCalling:withFlag: called recursively"];
|
||||
}
|
||||
|
||||
array = [_layoutManager textContainers];
|
||||
|
@ -518,6 +531,14 @@ this happens when layout has been invalidated, and when we are resized.
|
|||
|
||||
@end
|
||||
|
||||
@interface NSUndoManager(UndoCoalescing)
|
||||
/* Auxiliary method to support coalescing undo actions for typing events
|
||||
in NSTextView. However, the implementation is not restricted to that
|
||||
purpose. */
|
||||
- (BOOL) _canCoalesceUndoWithTarget: (id)target
|
||||
selector: (SEL)aSelector
|
||||
object: (id)anObject;
|
||||
@end
|
||||
|
||||
|
||||
@implementation NSTextView
|
||||
|
@ -1009,6 +1030,7 @@ that makes decoding and encoding compatible with the old code.
|
|||
DESTROY(_backgroundColor);
|
||||
DESTROY(_defaultParagraphStyle);
|
||||
DESTROY(_linkTextAttributes);
|
||||
DESTROY(_undoObject);
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
@ -2507,8 +2529,8 @@ TextDidEndEditing notification _without_ asking the delegate
|
|||
if (_tf.delegate_responds_to_should_change)
|
||||
{
|
||||
result = [_delegate textView: self
|
||||
shouldChangeTextInRange: affectedCharRange
|
||||
replacementString: replacementString];
|
||||
shouldChangeTextInRange: affectedCharRange
|
||||
replacementString: replacementString];
|
||||
}
|
||||
|
||||
if (result && [self allowsUndo])
|
||||
|
@ -2516,9 +2538,62 @@ TextDidEndEditing notification _without_ asking the delegate
|
|||
NSUndoManager *undo;
|
||||
NSRange undoRange;
|
||||
NSAttributedString *undoString;
|
||||
NSTextViewUndoObject *undoObject;
|
||||
BOOL isTyping;
|
||||
NSEvent *event;
|
||||
static BOOL undoManagerCanCoalesce = NO;
|
||||
|
||||
{
|
||||
// FIXME This code (together with undoManagerCanCoalesce) is a
|
||||
// temporary workaround to allow using an out of date version of
|
||||
// base. Removed this upon the next release of base.
|
||||
static BOOL didCheck = NO;
|
||||
if (!didCheck)
|
||||
{
|
||||
undoManagerCanCoalesce =
|
||||
[NSUndoManager instancesRespondToSelector:
|
||||
@selector(_canCoalesceUndoWithTarget:selector:object:)];
|
||||
if (!undoManagerCanCoalesce)
|
||||
{
|
||||
NSLog(@"This version of NSUndoManager does not\n"
|
||||
"support coalescing undo operations. "
|
||||
"Upgrade gnustep-base to r29163 or newer to\n"
|
||||
"get rid of this one-time warning.");
|
||||
}
|
||||
didCheck = YES;
|
||||
}
|
||||
}
|
||||
|
||||
undo = [self undoManager];
|
||||
|
||||
/* Coalesce consecutive typing events into a single undo action using
|
||||
currently private undo manager functionality. An event is considered
|
||||
a typing event if it is a keyboard event and the event's characters
|
||||
match our replacement string.
|
||||
Note: Typing events are coalesced only when the previous action was
|
||||
a typing event too and the current character follows the previous one
|
||||
immediately. We never coalesce actions when the current selection is
|
||||
not empty. */
|
||||
event = [NSApp currentEvent];
|
||||
isTyping = [event type] == NSKeyDown
|
||||
&& [[event characters] isEqualToString: replacementString];
|
||||
if (undoManagerCanCoalesce && _undoObject)
|
||||
{
|
||||
undoRange = [_undoObject range];
|
||||
if (isTyping &&
|
||||
NSMaxRange(undoRange) == affectedCharRange.location &&
|
||||
affectedCharRange.length == 0 &&
|
||||
[undo _canCoalesceUndoWithTarget: _textStorage
|
||||
selector: @selector(_undoTextChange:)
|
||||
object: _undoObject])
|
||||
{
|
||||
undoRange.length += [replacementString length];
|
||||
[_undoObject setRange: undoRange];
|
||||
return result;
|
||||
}
|
||||
DESTROY(_undoObject);
|
||||
}
|
||||
|
||||
// The length of the undoRange is the length of the replacement, if any.
|
||||
if (replacementString != nil)
|
||||
{
|
||||
|
@ -2530,10 +2605,18 @@ TextDidEndEditing notification _without_ asking the delegate
|
|||
undoRange = affectedCharRange;
|
||||
}
|
||||
undoString = [self attributedSubstringFromRange: affectedCharRange];
|
||||
[[undo prepareWithInvocationTarget: [self textStorage]]
|
||||
_undoReplaceCharactersInRange: undoRange
|
||||
withAttributedString: undoString
|
||||
selectedRange: [self selectedRange]];
|
||||
|
||||
undoObject =
|
||||
[[NSTextViewUndoObject alloc] initWithRange: undoRange
|
||||
attributedString: undoString
|
||||
selectedRange: [self selectedRange]];
|
||||
[undo registerUndoWithTarget: _textStorage
|
||||
selector: @selector(_undoTextChange:)
|
||||
object: undoObject];
|
||||
if (isTyping)
|
||||
_undoObject = undoObject;
|
||||
else
|
||||
RELEASE(undoObject);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -5191,7 +5274,7 @@ configuation! */
|
|||
|
||||
- (void) breakUndoCoalescing
|
||||
{
|
||||
// FIXME
|
||||
DESTROY(_undoObject);
|
||||
}
|
||||
|
||||
- (void) complete: (id)sender
|
||||
|
@ -5349,12 +5432,56 @@ configuation! */
|
|||
|
||||
@end
|
||||
|
||||
@implementation NSTextStorage(NSTextViewUndoSupport)
|
||||
/* FIXME: Should this code be moved to NSTextStorage? */
|
||||
- (NSTextView *)_bestTextViewForUndo
|
||||
@implementation NSTextViewUndoObject
|
||||
|
||||
- (id) initWithRange: (NSRange)aRange
|
||||
attributedString: (NSAttributedString *)aString
|
||||
selectedRange: (NSRange)selected
|
||||
{
|
||||
if ((self = [super init]) != nil)
|
||||
{
|
||||
range = aRange;
|
||||
string = RETAIN(aString);
|
||||
selectedRange = selected;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
RELEASE(string);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSRange) range
|
||||
{
|
||||
return range;
|
||||
}
|
||||
|
||||
- (void) setRange: (NSRange)aRange
|
||||
{
|
||||
range = aRange;
|
||||
}
|
||||
|
||||
- (void) performUndo: (NSTextStorage *)aTextStorage
|
||||
{
|
||||
NSTextView *tv = [self bestTextViewForTextStorage: aTextStorage];
|
||||
|
||||
if ([tv shouldChangeTextInRange: range
|
||||
replacementString: (string ? (NSString*)[string string]
|
||||
: (NSString*)@"")])
|
||||
{
|
||||
[tv replaceCharactersInRange: range
|
||||
withAttributedString: string];
|
||||
[tv setSelectedRange: selectedRange];
|
||||
[tv didChangeText];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTextView *) bestTextViewForTextStorage: (NSTextStorage *)aTextStorage
|
||||
{
|
||||
int i, j;
|
||||
NSArray *textContainers;
|
||||
NSArray *layoutManagers, *textContainers;
|
||||
NSTextView *tv, *first = nil, *best = nil;
|
||||
NSWindow *win;
|
||||
|
||||
|
@ -5365,9 +5492,10 @@ configuation! */
|
|||
* by default if a NSTextView is not editable, we consider only editable
|
||||
* views here.
|
||||
*/
|
||||
for (i = 0; i < [_layoutManagers count]; i++)
|
||||
layoutManagers = [aTextStorage layoutManagers];
|
||||
for (i = 0; i < [layoutManagers count]; i++)
|
||||
{
|
||||
textContainers = [[_layoutManagers objectAtIndex: i] textContainers];
|
||||
textContainers = [[layoutManagers objectAtIndex: i] textContainers];
|
||||
for (j = 0; j < [textContainers count]; j++)
|
||||
{
|
||||
tv = [[textContainers objectAtIndex: j] textView];
|
||||
|
@ -5399,20 +5527,13 @@ configuation! */
|
|||
return best != nil ? best : first;
|
||||
}
|
||||
|
||||
- (void)_undoReplaceCharactersInRange: (NSRange)undoRange
|
||||
withAttributedString: (NSAttributedString *)undoString
|
||||
selectedRange: (NSRange)selectedRange
|
||||
{
|
||||
NSTextView *tv = [self _bestTextViewForUndo];
|
||||
|
||||
if ([tv shouldChangeTextInRange: undoRange
|
||||
replacementString: undoString ? (NSString*)[undoString string] :
|
||||
(NSString*)@""])
|
||||
{
|
||||
[tv replaceCharactersInRange: undoRange
|
||||
withAttributedString: undoString];
|
||||
[tv setSelectedRange: selectedRange];
|
||||
[tv didChangeText];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation NSTextStorage(NSTextViewUndoSupport)
|
||||
|
||||
- (void) _undoTextChange: (NSTextViewUndoObject *)anObject
|
||||
{
|
||||
[anObject performUndo: self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in a new issue