Rewrite insertion point movement actions properly.

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@15904 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Alexander Malmberg 2003-02-08 16:55:51 +00:00
parent 5ca050e9ca
commit 39744f3b2f
5 changed files with 578 additions and 394 deletions

View file

@ -1,3 +1,14 @@
2003-02-08 17:51 Alexander Malmberg <alexander@malmberg.org>
* Headers/gnustep/gui/NSLayoutManager.h, Source/NSLayoutManager.m:
Add -characterIndexMoving:fromCharacterIndex:originalCharacterIndex:
distance: method.
* Source/NSTextView.m: Keep track of the selection affinity.
* Source/NSTextView_actions.m: Use the new method and affinity
handling to reimplement the insertion point movement actions.
2003-02-07 17:47 Alexander Malmberg <alexander@malmberg.org>
* Source/GSTextStorage.m (-setAttributes:range:): Fix memory leak.

View file

@ -32,6 +32,18 @@
@class NSParagraphStyle;
/*
GNUstep extension.
*/
typedef enum {
GSInsertionPointMoveLeft,
GSInsertionPointMoveRight,
GSInsertionPointMoveDown,
GSInsertionPointMoveUp,
} GSInsertionPointMovementDirection;
@interface NSLayoutManager : GSLayoutManager
{
/* Public for use only in the associated NSTextViews. Don't access
@ -124,6 +136,36 @@ GNUstep extension.
-(NSRect) insertionPointRectForCharacterIndex: (unsigned int)cindex
inTextContainer: (NSTextContainer *)textContainer;
/*
Insertion point movement primitive. 'from' is the character index moved from,
and 'original' is the character index originally moved from in this sequence
of moves (ie. if the user hits the down key several times, the first call
would have original==from, and subsequent calls would use the same 'original'
and the 'from' returned from the last call).
The returned character index will always be different from 'from' unless
'from' is the "furthest" character index in the text container in the
specified direction.
The distance is the target distance for the move (in the text container's
coordinate system). The move won't be farther than this distance unless
it's impossible to move a shorter distance. Distance 0.0 is treated
specially: the move will be the shortest possible move, and movement will
"make sense" even if the glyph/character mapping is complex at 'from'
(eg. it will move through ligatures in a sensible way).
Note that this method does not work across text containers. 'original' and
'from' should be in the same container, and the returned index will also be
in that container.
GNUstep extension.
*/
-(unsigned int) characterIndexMoving: (GSInsertionPointMovementDirection)direction
fromCharacterIndex: (unsigned int)from
originalCharacterIndex: (unsigned int)original
distance: (float)distance;
@end

View file

@ -79,6 +79,8 @@ http://wiki.gnustep.org/index.php/NominallySpacedGlyphs
i = r.location;
f = [self effectiveFontForGlyphAtIndex: i
range: &r];
/* TODO: this is rather inefficient and doesn't deal with non-shown
glyphs */
for (; i < glyphIndex; i++)
{
if (i == r.location + r.length)
@ -545,8 +547,50 @@ anything visible
}
-(NSRect) insertionPointRectForCharacterIndex: (unsigned int)cindex
inTextContainer: (NSTextContainer *)textContainer
/*
Insertion point positioning and movement.
*/
-(unsigned int) _glyphIndexForCharacterIndex: (unsigned int)cindex
fractionThrough: (float *)fraction
{
if (cindex == [[_textStorage string] length])
{
if (!cindex)
{
*fraction = 0.0;
return (unsigned int)-1;
}
*fraction = 1.0;
return [self numberOfGlyphs] - 1;
}
else
{
NSRange glyphRange, charRange;
unsigned int glyph_index;
float fraction_through;
glyphRange = [self glyphRangeForCharacterRange: NSMakeRange(cindex, 1)
actualCharacterRange: &charRange];
/* Deal with composite characters and ligatures. */
fraction_through = (cindex - charRange.location) / (float)charRange.length;
fraction_through *= glyphRange.length;
glyph_index = glyphRange.location + floor(fraction_through);
fraction_through -= floor(fraction_through);
*fraction = fraction_through;
return glyph_index;
}
}
/*
Note: other methods rely a lot on the fact that the rectangle returned here
has the same y origin and height as the line frag rect it is in.
*/
-(NSRect) _insertionPointRectForCharacterIndex: (unsigned int)cindex
textContainer: (int *)textContainer
{
int i;
textcontainer_t *tc;
@ -558,45 +602,29 @@ anything visible
float fraction_through;
if (cindex == [[_textStorage string] length])
{ /* TODO: use extra line frag, etc. */
if (!cindex)
{
return NSMakeRect(1,1,1,13); /* TODO! */
}
glyph_index = [self numberOfGlyphs] - 1;
fraction_through = 1.0;
}
else
glyph_index = [self _glyphIndexForCharacterIndex: cindex
fractionThrough: &fraction_through];
if (glyph_index == (unsigned int)-1)
{
NSRange glyphRange, charRange;
glyphRange = [self glyphRangeForCharacterRange: NSMakeRange(cindex, 1)
actualCharacterRange: &charRange];
/* Magic to deal with composite characters and ligatures. */
fraction_through = (cindex - charRange.location) / (float)charRange.length;
fraction_through *= glyphRange.length;
glyph_index = glyphRange.location + floor(fraction_through);
fraction_through -= floor(fraction_through);
if (num_textcontainers > 0)
*textContainer = 0;
else
*textContainer = -1;
/* TODO: use extra rect, etc. */
return NSMakeRect(1,1,1,13);
}
for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++)
if (tc->textContainer == textContainer)
if (tc->pos + tc->length > glyph_index)
break;
[self _doLayoutToGlyph: glyph_index - 1];
[self _doLayoutToGlyph: glyph_index];
if (i == num_textcontainers)
{
NSLog(@"%s: invalid text container", __PRETTY_FUNCTION__);
*textContainer = -1;
return NSZeroRect;
}
if (glyph_index < tc->pos || glyph_index >= tc->pos + tc->length)
{
return NSZeroRect;
}
*textContainer = i;
for (lf = tc->linefrags, i = 0; i < tc->num_linefrags; i++, lf++)
if (lf->pos + lf->length > glyph_index)
@ -637,6 +665,20 @@ anything visible
r.origin.x = x0 + (x1 - x0) * fraction_through;
r.size.width = 1;
return r;
}
-(NSRect) insertionPointRectForCharacterIndex: (unsigned int)cindex
inTextContainer: (NSTextContainer *)textContainer
{
int i;
NSRect r;
r = [self _insertionPointRectForCharacterIndex: cindex
textContainer: &i];
if (i == -1 || textcontainers[i].textContainer != textContainer)
return NSZeroRect;
r.origin.y++;
r.size.height -= 2;
@ -644,6 +686,210 @@ anything visible
}
-(unsigned int) characterIndexMoving: (GSInsertionPointMovementDirection)direction
fromCharacterIndex: (unsigned int)from
originalCharacterIndex: (unsigned int)original
distance: (float)distance
{
NSRect from_rect, new_rect;
int from_tc, new_tc;
int i;
unsigned int new;
unsigned int length = [_textStorage length];
/* This call will ensure that layout is built to 'from', and that layout
for the line 'from' is in is built. */
from_rect = [self _insertionPointRectForCharacterIndex: from
textContainer: &from_tc];
if (from_tc == -1)
{
NSLog(@"%s: character index not in any text container",
__PRETTY_FUNCTION__);
return from;
}
if (direction == GSInsertionPointMoveLeft ||
direction == GSInsertionPointMoveRight)
{
float target;
if (distance == 0.0)
{
new = from;
if (direction == GSInsertionPointMoveLeft && new > 0)
new--;
if (direction == GSInsertionPointMoveRight && new < length)
new++;
[self _insertionPointRectForCharacterIndex: new
textContainer: &i];
/* Don't leave the text container. */
if (i == from_tc)
return new;
else
return from;
}
/*
This is probably very inefficient, but it shouldn't be a bottleneck,
and it guarantees that cursor movement matches insertion point
positioning. It also lets us do this by character instead of by glyph.
*/
new = from;
if (direction == GSInsertionPointMoveLeft)
{
target = from_rect.origin.x - distance;
while (new > 0)
{
new_rect = [self _insertionPointRectForCharacterIndex: new - 1
textContainer: &new_tc];
if (new_tc != from_tc)
break;
if (new_rect.origin.y != from_rect.origin.y)
break;
new--;
if (NSMaxX(new_rect) <= target)
break;
}
return new;
}
else
{
target = from_rect.origin.x + distance;
while (new < length)
{
new_rect = [self _insertionPointRectForCharacterIndex: new + 1
textContainer: &new_tc];
if (new_tc != from_tc)
break;
if (new_rect.origin.y != from_rect.origin.y)
break;
new++;
if (NSMinX(new_rect) >= target)
break;
}
return new;
}
}
if (direction == GSInsertionPointMoveUp ||
direction == GSInsertionPointMoveDown)
{
NSRect orig_rect;
int orig_tc;
float target;
textcontainer_t *tc;
linefrag_t *lf;
int i;
orig_rect = [self _insertionPointRectForCharacterIndex: original
textContainer: &orig_tc];
if (orig_tc == from_tc)
target = orig_rect.origin.x;
else
target = from_rect.origin.x;
tc = &textcontainers[from_tc];
/* Find first line frag rect on the from line. */
for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++)
{
if (lf->rect.origin.y == from_rect.origin.y)
break;
}
/* If we don't have a line frag rect that matches the from position,
the from position is probably on the last line, in the extra rect,
and i == tc->num_linefrags. The movement direction specific code
handles this case, as long as tc->num_linefrags > 0. */
if (!tc->num_linefrags)
return from; /* Impossible? Should be, since from_tc!=-1. */
if (direction == GSInsertionPointMoveDown)
{
[self _doLayoutToContainer: from_tc
point: NSMakePoint(target, distance + NSMaxY(from_rect))];
/* Find the target line. Move at least (should be up to?)
distance, and at least one line. */
for (; i < tc->num_linefrags; i++, lf++)
if (NSMaxY(lf->rect) >= distance + NSMaxY(from_rect) &&
NSMinY(lf->rect) != NSMinY(from_rect))
break;
if (i == tc->num_linefrags)
{
/* We can't move as far as we want to. In fact, we might not
have been able to move at all.
TODO: figure out how to handle this
*/
return from;
}
}
else
{
/* Find the target line. Move at least (should be up to?)
distance, and at least one line. */
for (; i >= 0; i--, lf--)
if (NSMinY(lf->rect) <= NSMinY(from_rect) - distance &&
NSMinY(lf->rect) != NSMinY(from_rect))
break;
/* Now we have the last line frag of the target line. Move
backwards to the first one. */
for (; i > 0; i--, lf--)
if (NSMinY(lf->rect) != NSMinY(lf[-1].rect))
break;
if (i == -1)
{
/* We can't move as far as we want to. In fact, we might not
have been able to move at all.
TODO: figure out how to handle this
*/
return from;
}
}
/* Now we have the first line frag of the target line and the
target x position. */
new = [self characterRangeForGlyphRange: NSMakeRange(lf->pos, 1)
actualGlyphRange: NULL].location;
/* The first character index might not actually be in this line
rect, so move forwards to the first character in the target line. */
while (new < length)
{
new_rect = [self _insertionPointRectForCharacterIndex: new + 1
textContainer: &new_tc];
if (new_tc > from_tc)
break;
if (new_rect.origin.y >= lf->rect.origin.y)
break;
new++;
}
/* Now find the target character in the line. */
while (new < length)
{
new_rect = [self _insertionPointRectForCharacterIndex: new + 1
textContainer: &new_tc];
if (new_tc != from_tc)
break;
if (new_rect.origin.y != lf->rect.origin.y)
break;
new++;
if (NSMinX(new_rect) >= target)
break;
}
return new;
}
NSLog(@"(%s): invalid direction %i (distance %g)",
__PRETTY_FUNCTION__, direction, distance);
return from;
}
@end
@ -656,7 +902,7 @@ anything visible
/*
If a range passed to a drawing function isn't contained in the text
container that containts its first glyph, the range is silently clamped.
container that contains its first glyph, the range is silently clamped.
My thought with this is that the requested glyphs might not fit in the
text container (if it's the last text container, or there's only one).
In that case, it isn't really the caller's fault, and drawing as much as

View file

@ -2690,6 +2690,7 @@ afterString in order over charRange.
}
[self setSelectionGranularity: NSSelectByCharacter];
_layoutManager->_selectionAffinity = affinity;
/* TODO: Remove the marking from marked text if the new selection is
greater than the marked region. */
@ -2710,7 +2711,7 @@ afterString in order over charRange.
- (NSSelectionAffinity) selectionAffinity
{
return NSSelectionAffinityDownstream;
return _layoutManager->_selectionAffinity;
}
- (void) setSelectionGranularity: (NSSelectionGranularity)granularity

View file

@ -44,6 +44,7 @@
#include <Foundation/NSValue.h>
#include <AppKit/NSGraphics.h>
#include <AppKit/NSLayoutManager.h>
#include <AppKit/NSScrollView.h>
#include <AppKit/NSTextStorage.h>
#include <AppKit/NSTextView.h>
@ -82,6 +83,9 @@ in the same way that the attributes of the text are changed.
sure this holds.)
TODO: can the selected range's location be NSNotFound? when?
Not all user actions are here. Exceptions:
@ -152,7 +156,9 @@ send -shouldChangeTextInRange:replacementString: or -didChangeText.
&& ([_delegate textShouldEndEditing: self] == NO))
return;
/* TODO: insertion point */
/* TODO: insertion point.
doesn't the -resignFirstResponder take care of that?
*/
number = [NSNumber numberWithInt: textMovement];
uiDictionary = [NSDictionary dictionaryWithObject: number
@ -549,6 +555,7 @@ static NSNumber *float_plus_one(NSNumber *cur)
return;
}
/* TODO */
//[self insertText: @"\t"];
}
@ -639,237 +646,265 @@ static NSNumber *float_plus_one(NSNumber *cur)
}
/*
TODO: find out what affinity is supposed to mean
My current assumption:
Affinity deals with which direction we are selecting in, ie. which end of
the selected range is the moving end, and which is the anchor.
NSSelectionAffinityUpstream means that the minimum index of the selected
range is moving (ie. _selected_range.location).
NSSelectionAffinityDownstream means that the maximum index of the selected
range is moving (ie. _selected_range.location+_selected_range.length).
Thus, when moving and selecting, we use the affinity to find out which end
of the selected range to move, and after moving, we compare the character
index we moved to with the anchor and set the range and affinity.
The affinity is important when making keyboard selection have sensible
behavior. Example:
If, in the string "abcd", the insertion point is between the "c" and the "d"
(selected range is (3,0)), and the user hits shift-left twice, we select
the "c" and "b" (1,2) and set the affinity to NSSelectionAffinityUpstream.
If the user hits shift-right, only the "c" will be selected (2,1).
If the insertion point is between the "a" and the "b" (1,0) and the user hits
shift-right twice, we again select the "b" and "c" (1,2), but the affinity
is NSSelectionAffinityDownstream. If the user hits shift-right, the "d" is
added to the selection (1,3).
*/
-(unsigned int) _movementOrigin
{
if (_layoutManager->_selectionAffinity == NSSelectionAffinityUpstream)
return _layoutManager->_selected_range.location;
else
return NSMaxRange(_layoutManager->_selected_range);
}
-(void) _moveTo: (unsigned int)cindex
select: (BOOL)select
{
if (select)
{
unsigned int anchor;
if (_layoutManager->_selectionAffinity == NSSelectionAffinityDownstream)
anchor = _layoutManager->_selected_range.location;
else
anchor = NSMaxRange(_layoutManager->_selected_range);
if (anchor < cindex)
{
[self setSelectedRange: NSMakeRange(anchor, cindex - anchor)
affinity: NSSelectionAffinityDownstream
stillSelecting: NO];
}
else
{
[self setSelectedRange: NSMakeRange(cindex, anchor - cindex)
affinity: NSSelectionAffinityUpstream
stillSelecting: NO];
}
}
else
{
[self setSelectedRange: NSMakeRange(cindex, 0)];
}
}
-(void) _move: (GSInsertionPointMovementDirection)direction
distance: (float)distance
select: (BOOL)select
{
unsigned int cindex;
cindex = [self _movementOrigin];
cindex = [_layoutManager characterIndexMoving: direction
fromCharacterIndex: cindex
originalCharacterIndex: cindex /* TODO */
distance: distance];
[self _moveTo: cindex
select: select];
}
/*
Insertion point movement actions.
TODO: should implement: (D marks done)
D-(void) moveBackward: (id)sender;
D-(void) moveBackwardAndModifySelection: (id)sender;
D-(void) moveForward: (id)sender;
D-(void) moveForwardAndModifySelection: (id)sender;
D-(void) moveDown: (id)sender;
D-(void) moveDownAndModifySelection: (id)sender;
D-(void) moveUp: (id)sender;
D-(void) moveUpAndModifySelection: (id)sender;
D-(void) moveLeft: (id)sender;
D-(void) moveRight: (id)sender;
D-(void) moveWordBackward: (id)sender;
D-(void) moveWordBackwardAndModifySelection: (id)sender;
D-(void) moveWordForward: (id)sender;
D-(void) moveWordForwardAndModifySelection: (id)sender;
D-(void) moveToBeginningOfDocument: (id)sender;
D-(void) moveToEndOfDocument: (id)sender;
D-(void) moveToBeginningOfLine: (id)sender;
D-(void) moveToEndOfLine: (id)sender;
-(void) moveToBeginningOfParagraph: (id)sender;
-(void) moveToEndOfParagraph: (id)sender;
TODO: think hard about behavior for pageUp: and pageDown:
D-(void) pageDown: (id)sender;
D-(void) pageUp: (id)sender;
TODO: some of these used to do nothing if self is a field editor. should
check if there was a reason for that.
*/
-(void) moveUp: (id)sender
{
/* float originalInsertionPoint;
float savedOriginalInsertionPoint;
float startingY;
unsigned newLocation;*/
if (_tf.is_field_editor) /* TODO: why? */
return;
/* Do nothing if we are at beginning of text */
if (_layoutManager->_selected_range.location == 0)
{
return;
}
#if 0 /* TODO */
/* Read from memory the horizontal position we aim to move the cursor
at on the next line */
savedOriginalInsertionPoint = _originalInsertPoint;
originalInsertionPoint = _originalInsertPoint;
/* Ask the layout manager to compute where to go */
startingY = NSMidY (_insertionPointRect);
/* consider textContainerInset */
startingY -= _textContainerInset.height;
originalInsertionPoint -= _textContainerInset.width;
newLocation = [_layoutManager
_charIndexForInsertionPointMovingFromY: startingY
bestX: originalInsertionPoint
up: YES
textContainer: _textContainer];
/* Move the insertion point */
[self setSelectedRange: NSMakeRange (newLocation, 0)];
/* Restore the _originalInsertPoint (which was changed
by setSelectedRange:) because we don't want it to change between
moveUp:/moveDown: operations. */
_originalInsertPoint = savedOriginalInsertionPoint;
#endif
[self _move: GSInsertionPointMoveUp
distance: 0.0
select: NO];
}
-(void) moveUpAndModifySelection: (id)sender
{
[self _move: GSInsertionPointMoveUp
distance: 0.0
select: YES];
}
-(void) moveDown: (id)sender
{
/* float originalInsertionPoint;
float savedOriginalInsertionPoint;
float startingY;
unsigned newLocation;*/
if (_tf.is_field_editor)
return;
/* Do nothing if we are at end of text */
if (_layoutManager->_selected_range.location == [_textStorage length])
{
return;
}
#if 0 /* TODO */
/* Read from memory the horizontal position we aim to move the cursor
at on the next line */
savedOriginalInsertionPoint = _originalInsertPoint;
originalInsertionPoint = _originalInsertPoint;
/* Ask the layout manager to compute where to go */
startingY = NSMidY (_insertionPointRect);
/* consider textContainerInset */
startingY -= _textContainerInset.height;
originalInsertionPoint -= _textContainerInset.width;
newLocation = [_layoutManager
_charIndexForInsertionPointMovingFromY: startingY
bestX: originalInsertionPoint
up: NO
textContainer: _textContainer];
/* Move the insertion point */
[self setSelectedRange: NSMakeRange (newLocation, 0)];
/* Restore the _originalInsertPoint (which was changed
by setSelectedRange:) because we don't want it to change between
moveUp:/moveDown: operations. */
_originalInsertPoint = savedOriginalInsertionPoint;
#endif
[self _move: GSInsertionPointMoveDown
distance: 0.0
select: NO];
}
-(void) moveDownAndModifySelection: (id)sender
{
[self _move: GSInsertionPointMoveDown
distance: 0.0
select: YES];
}
-(void) moveLeft: (id)sender
{
unsigned newLocation;
/* Do nothing if we are at beginning of text with no selection */
if (_layoutManager->_selected_range.location == 0 && _layoutManager->_selected_range.length == 0)
return;
if (_layoutManager->_selected_range.location == 0)
{
newLocation = 0;
}
else
{
newLocation = _layoutManager->_selected_range.location - 1;
}
[self setSelectedRange: NSMakeRange (newLocation, 0)];
[self _move: GSInsertionPointMoveLeft
distance: 0.0
select: NO];
}
-(void) moveRight: (id)sender
{
unsigned int length = [_textStorage length];
unsigned newLocation;
/* Do nothing if we are at end of text */
if (_layoutManager->_selected_range.location == length)
return;
newLocation = MIN (NSMaxRange (_layoutManager->_selected_range) + 1, length);
[self setSelectedRange: NSMakeRange (newLocation, 0)];
[self _move: GSInsertionPointMoveRight
distance: 0.0
select: NO];
}
-(void) moveBackward: (id)sender
{
unsigned int to = [self _movementOrigin];
if (to == 0)
return;
to--;
[self _moveTo: to
select: NO];
}
-(void) moveBackwardAndModifySelection: (id)sender
{
NSRange newRange;
/* Do nothing if we are at beginning of text. */
if (_layoutManager->_selected_range.location == 0)
{
return;
}
/* Turn to select by character. */
[self setSelectionGranularity: NSSelectByCharacter];
/* Extend the selection on the left. */
newRange = NSMakeRange (_layoutManager->_selected_range.location - 1,
_layoutManager->_selected_range.length + 1);
newRange = [self selectionRangeForProposedRange: newRange
granularity: NSSelectByCharacter];
[self setSelectedRange: newRange];
unsigned int to = [self _movementOrigin];
if (to == 0)
return;
to--;
[self _moveTo: to
select: YES];
}
-(void) moveForward: (id)sender
{
unsigned int to = [self _movementOrigin];
if (to == [_textStorage length])
return;
to++;
[self _moveTo: to
select: NO];
}
-(void) moveForwardAndModifySelection: (id)sender
{
unsigned int length = [_textStorage length];
NSRange newRange;
/* Do nothing if we are at end of text */
if (_layoutManager->_selected_range.location == length)
unsigned int to = [self _movementOrigin];
if (to == [_textStorage length])
return;
/* Turn to select by character. */
[self setSelectionGranularity: NSSelectByCharacter];
/* Extend the selection on the right. */
newRange = NSMakeRange (_layoutManager->_selected_range.location,
_layoutManager->_selected_range.length + 1);
newRange = [self selectionRangeForProposedRange: newRange
granularity: NSSelectByCharacter];
[self setSelectedRange: newRange];
to++;
[self _moveTo: to
select: YES];
}
-(void) moveWordBackward: (id)sender
{
unsigned newLocation;
unsigned int newLocation;
newLocation = [_textStorage nextWordFromIndex: _layoutManager->_selected_range.location
forward: NO];
[self setSelectedRange: NSMakeRange (newLocation, 0)];
[self _moveTo: newLocation
select: NO];
}
-(void) moveWordBackwardAndModifySelection: (id)sender
{
unsigned int newLocation;
newLocation = [_textStorage nextWordFromIndex: _layoutManager->_selected_range.location
forward: NO];
[self _moveTo: newLocation
select: YES];
}
-(void) moveWordForward: (id)sender
{
unsigned newLocation;
newLocation = [_textStorage nextWordFromIndex: _layoutManager->_selected_range.location
forward: YES];
[self setSelectedRange: NSMakeRange (newLocation, 0)];
[self _moveTo: newLocation
select: NO];
}
-(void) moveWordBackwardAndModifySelection: (id)sender
{
unsigned newLocation;
NSRange newRange;
[self setSelectionGranularity: NSSelectByWord];
newLocation = [_textStorage nextWordFromIndex: _layoutManager->_selected_range.location
forward: NO];
newRange = NSMakeRange (newLocation,
NSMaxRange (_layoutManager->_selected_range) - newLocation);
newRange = [self selectionRangeForProposedRange: newRange
granularity: NSSelectByCharacter];
[self setSelectedRange: newRange];
}
-(void) moveWordForwardAndModifySelection: (id)sender
{
unsigned newMaxRange;
NSRange newRange;
[self setSelectionGranularity: NSSelectByWord];
newMaxRange = [_textStorage nextWordFromIndex: NSMaxRange (_layoutManager->_selected_range)
forward: YES];
newRange = NSMakeRange (_layoutManager->_selected_range.location,
newMaxRange - _layoutManager->_selected_range.location);
newRange = [self selectionRangeForProposedRange: newRange
granularity: NSSelectByCharacter];
[self setSelectedRange: newRange];
unsigned newLocation;
newLocation = [_textStorage nextWordFromIndex: _layoutManager->_selected_range.location
forward: NO];
[self _moveTo: newLocation
select: NO];
}
-(void) moveToBeginningOfDocument: (id)sender
{
[self setSelectedRange: NSMakeRange (0, 0)];
[self _moveTo: 0
select: NO];
}
-(void) moveToEndOfDocument: (id)sender
{
[self _moveTo: [_textStorage length]
select: NO];
}
-(void) moveToBeginningOfParagraph: (id)sender
{
@ -879,38 +914,6 @@ static NSNumber *float_plus_one(NSNumber *cur)
[self setSelectedRange: NSMakeRange (aRange.location, 0)];
}
-(void) moveToBeginningOfLine: (id)sender
{
NSRange aRange;
NSRect ignored;
/* We do nothing if we are at the beginning of the text. */
if (_layoutManager->_selected_range.location == 0)
{
return;
}
ignored = [_layoutManager lineFragmentRectForGlyphAtIndex:
_layoutManager->_selected_range.location
effectiveRange: &aRange];
[self setSelectedRange: NSMakeRange (aRange.location, 0)];
}
-(void) moveToEndOfDocument: (id)sender
{
unsigned length = [_textStorage length];
if (length > 0)
{
[self setSelectedRange: NSMakeRange (length, 0)];
}
else
{
[self setSelectedRange: NSMakeRange (0, 0)];
}
}
-(void) moveToEndOfParagraph: (id)sender
{
NSRange aRange;
@ -963,72 +966,23 @@ static NSNumber *float_plus_one(NSNumber *cur)
[self setSelectedRange: NSMakeRange (newLocation, 0) ];
}
/* TODO: this is only the beginning and end of lines if lines are horizontal
and layout is left-to-right */
-(void) moveToBeginningOfLine: (id)sender
{
[self _move: GSInsertionPointMoveLeft
distance: 1e8
select: NO];
}
-(void) moveToEndOfLine: (id)sender
{
NSRect ignored;
NSRange line, glyphs;
unsigned newLocation;
unsigned maxRange;
/* We do nothing if we are at the end of the text. */
if (_layoutManager->_selected_range.location == [_textStorage length])
{
return;
}
ignored = [_layoutManager lineFragmentRectForGlyphAtIndex:
_layoutManager->_selected_range.location
effectiveRange: &glyphs];
line = [_layoutManager characterRangeForGlyphRange: glyphs
actualGlyphRange: NULL];
maxRange = NSMaxRange (line);
if (maxRange == 0)
{
/* Beginning of text is special only for technical reasons -
since maxRange is an unsigned, we can't safely subtract 1
from it if it is 0. */
newLocation = maxRange;
}
else if (maxRange == [_textStorage length])
{
/* End of text is special - we want the insertion point to
appear *after* the last character, which means as if before
the next (virtual) character after the end of text ... unless
the last character is a newline, and we are trying to go to
the end of the line which is displayed as the
one-before-the-last. (Please note that we do not check for
spaces - spaces are ok, we want to move after the last space)
Please note (maxRange - 1) is a valid char since the maxRange
== 0 case has already been eliminated. */
unichar u = [[_textStorage string] characterAtIndex: (maxRange - 1)];
if (u == '\n' || u == '\r')
{
newLocation = maxRange - 1;
}
else
{
newLocation = maxRange;
}
}
else
{
/* Else, we want the insertion point to appear before the last
character in the line range. Normally the last character in
the line range is a space or a newline. */
newLocation = maxRange - 1;
}
if (newLocation < line.location)
{
newLocation = line.location;
}
[self setSelectedRange: NSMakeRange (newLocation, 0) ];
[self _move: GSInsertionPointMoveRight
distance: 1e8
select: NO];
}
/**
* Tries to move the selection/insertion point down one page of the
* visible rect in the receiver while trying to maintain the
@ -1037,23 +991,9 @@ static NSNumber *float_plus_one(NSNumber *cur)
*/
-(void) pageDown: (id)sender
{
#if 0 /* TODO */
float cachedInsertPointX;
float scrollDelta;
float oldOriginY;
float newOriginY;
unsigned glyphIDX;
unsigned charIDX;
NSPoint iPoint;
if (_tf.is_field_editor)
return;
/*
* Save the current horizontal position cache as we will implictly
* change it later.
*/
// cachedInsertPointX = _originalInsertPoint;
/*
* Scroll; also determine how far to move the insertion point.
@ -1070,34 +1010,12 @@ static NSNumber *float_plus_one(NSNumber *cur)
* insertion point to the last line when the user clicks
* 'PageDown' in that case ?
*/
return;
}
/*
* Calculate new insertion point.
*/
iPoint.x = _originalInsertPoint - _textContainerInset.width;
iPoint.y = NSMidY(_insertionPointRect) - _textContainerInset.height;
iPoint.y += scrollDelta;
/*
* Ask the layout manager to compute where to go.
*/
glyphIDX = [_layoutManager glyphIndexForPoint: iPoint
inTextContainer: _textContainer];
charIDX = [_layoutManager characterIndexForGlyphAtIndex: glyphIDX];
/*
* Move the insertion point (implicitly changing
* _originalInsertPoint).
*/
[self setSelectedRange: NSMakeRange(charIDX, 0)];
/*
* Restore the _originalInsertPoint because we do not want it to
* change between moveUp:/moveDown:/pageUp:/pageDown: operations.
*/
_originalInsertPoint = cachedInsertPointX;
#endif
[self _move: GSInsertionPointMoveDown
distance: scrollDelta
select: NO];
}
/**
@ -1108,23 +1026,9 @@ static NSNumber *float_plus_one(NSNumber *cur)
*/
-(void) pageUp: (id)sender
{
#if 0 /* TODO */
float cachedInsertPointX;
float scrollDelta;
float oldOriginY;
float newOriginY;
unsigned glyphIDX;
unsigned charIDX;
NSPoint iPoint;
if (_tf.is_field_editor)
return;
/*
* Save the current horizontal position cache as we will implictly
* change it later.
*/
cachedInsertPointX = _originalInsertPoint;
/*
* Scroll; also determine how far to move the insertion point.
@ -1141,37 +1045,17 @@ static NSNumber *float_plus_one(NSNumber *cur)
* insertion point to the first line when the user clicks
* 'PageUp' in that case ?
*/
return;
}
/*
* Calculate new insertion point.
*/
iPoint.x = _originalInsertPoint - _textContainerInset.width;
iPoint.y = NSMidY(_insertionPointRect) - _textContainerInset.height;
iPoint.y += scrollDelta;
/*
* Ask the layout manager to compute where to go.
*/
glyphIDX = [_layoutManager glyphIndexForPoint: iPoint
inTextContainer: _textContainer];
charIDX = [_layoutManager characterIndexForGlyphAtIndex: glyphIDX];
/*
* Move the insertion point (implicitly changing
* _originalInsertPoint).
*/
[self setSelectedRange: NSMakeRange(charIDX, 0)];
/*
* Restore the _originalInsertPoint because we do not want it to
* change between moveUp:/moveDown:/pageUp:/pageDown: operations.
*/
_originalInsertPoint = cachedInsertPointX;
#endif
[self _move: GSInsertionPointMoveUp
distance: -scrollDelta
select: NO];
}
-(void) scrollLineDown: (id)sender
{
// TODO