Fix index beyond range in text attachment processing in mouseDown

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/branches/gnustep_testplant_branch@38227 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Marcian Lytwyn 2014-12-03 18:49:58 +00:00
parent 0dc0c821f9
commit fd815172ac

View file

@ -5521,332 +5521,342 @@ other than copy/paste or dragging. */
NSRange chosenRange, proposedRange; NSRange chosenRange, proposedRange;
NSPoint point, startPoint; NSPoint point, startPoint;
NSUInteger startIndex, endIndex; NSUInteger startIndex, endIndex;
/* If non selectable then ignore the mouse down. */ /* If non selectable then ignore the mouse down. */
if (_tf.is_selectable == NO) if (_tf.is_selectable == NO)
{ {
return; return;
} }
if (!_layoutManager) if (!_layoutManager)
return; return;
/* Otherwise, NSWindow has already made us first responder (if /* Otherwise, NSWindow has already made us first responder (if
possible) */ possible) */
startPoint = [self convertPoint: [theEvent locationInWindow] fromView: nil]; startPoint = [self convertPoint: [theEvent locationInWindow] fromView: nil];
startIndex = [self _characterIndexForPoint: startPoint startIndex = [self _characterIndexForPoint: startPoint
respectFraction: [theEvent clickCount] == 1]; respectFraction: [theEvent clickCount] == 1];
if ([theEvent modifierFlags] & NSShiftKeyMask) if ([theEvent modifierFlags] & NSShiftKeyMask)
{ {
/* Shift-click is for extending or shrinking an existing selection using /* Shift-click is for extending or shrinking an existing selection using
the existing granularity */ the existing granularity */
proposedRange = _layoutManager->_selected_range; proposedRange = _layoutManager->_selected_range;
granularity = _layoutManager->_selectionGranularity; granularity = _layoutManager->_selectionGranularity;
/* If the clicked point is closer to the left end of the current selection /* If the clicked point is closer to the left end of the current selection
adjust the left end of the selected range */ adjust the left end of the selected range */
if (startIndex < proposedRange.location + proposedRange.length / 2) if (startIndex < proposedRange.location + proposedRange.length / 2)
{ {
proposedRange = NSMakeRange(startIndex, proposedRange = NSMakeRange(startIndex,
NSMaxRange(proposedRange) - startIndex); NSMaxRange(proposedRange) - startIndex);
proposedRange = [self selectionRangeForProposedRange: proposedRange proposedRange = [self selectionRangeForProposedRange: proposedRange
granularity: granularity]; granularity: granularity];
/* Prepare for shift-dragging. Anchor is at the right end. */ /* Prepare for shift-dragging. Anchor is at the right end. */
startIndex = NSMaxRange(proposedRange); startIndex = NSMaxRange(proposedRange);
} }
/* otherwise, adjust the right end of the selected range */ /* otherwise, adjust the right end of the selected range */
else else
{ {
proposedRange = NSMakeRange(proposedRange.location, proposedRange = NSMakeRange(proposedRange.location,
startIndex - proposedRange.location); startIndex - proposedRange.location);
proposedRange = [self selectionRangeForProposedRange: proposedRange proposedRange = [self selectionRangeForProposedRange: proposedRange
granularity: granularity]; granularity: granularity];
/* Prepare for shift-dragging. Anchor is at the left end. */ /* Prepare for shift-dragging. Anchor is at the left end. */
startIndex = proposedRange.location; startIndex = proposedRange.location;
} }
} }
else /* No shift */ else /* No shift */
{ {
switch ([theEvent clickCount]) switch ([theEvent clickCount])
{ {
case 1: granularity = NSSelectByCharacter; case 1: granularity = NSSelectByCharacter;
break; break;
case 2: granularity = NSSelectByWord; case 2: granularity = NSSelectByWord;
break; break;
case 3: granularity = NSSelectByParagraph; case 3: granularity = NSSelectByParagraph;
break; break;
} }
/* A single click into the selected range can start a drag operation */ /* A single click into the selected range can start a drag operation */
canDrag = granularity == NSSelectByCharacter canDrag = granularity == NSSelectByCharacter && NSLocationInRange(startIndex, _layoutManager->_selected_range);
&& NSLocationInRange(startIndex, _layoutManager->_selected_range); proposedRange = NSMakeRange (startIndex, 0);
proposedRange = NSMakeRange (startIndex, 0);
/* We manage clicks on attachments and links only on the first
/* We manage clicks on attachments and links only on the first click, so that if you double-click on them, only the first
click, so that if you double-click on them, only the first click gets sent to them; the other clicks select by
click gets sent to them; the other clicks select by word/paragraph as usual. */
word/paragraph as usual. */ if (granularity == NSSelectByCharacter)
if (granularity == NSSelectByCharacter) {
{ NSTextAttachment *attachment;
NSTextAttachment *attachment;
/* Check if the click was on an attachment cell. */
/* Check if the click was on an attachment cell. */ attachment = [_textStorage attribute: NSAttachmentAttributeName
attachment = [_textStorage attribute: NSAttachmentAttributeName atIndex: startIndex
atIndex: startIndex effectiveRange: NULL];
effectiveRange: NULL];
if (attachment != nil)
if (attachment != nil) {
{ id <NSTextAttachmentCell> cell = [attachment attachmentCell];
id <NSTextAttachmentCell> cell = [attachment attachmentCell];
if (cell != nil)
if (cell != nil) {
{ NSRect cellFrame = NSZeroRect;
NSRect cellFrame;
NSRect lfRect;
NSUInteger glyphIndex;
glyphIndex =
[_layoutManager
glyphRangeForCharacterRange: NSMakeRange(startIndex, 1)
actualCharacterRange: NULL].location;
lfRect =
[_layoutManager
lineFragmentRectForGlyphAtIndex: glyphIndex
effectiveRange: NULL];
cellFrame.origin =
[_layoutManager
locationForGlyphAtIndex: glyphIndex];
cellFrame.size =
[_layoutManager
attachmentSizeForGlyphAtIndex: glyphIndex];
cellFrame.origin.y -= cellFrame.size.height;
cellFrame.origin.x += lfRect.origin.x;
cellFrame.origin.y += lfRect.origin.y;
/* TODO: What about the insertion point ? */
if ([cell wantsToTrackMouseForEvent: theEvent
inRect: cellFrame
ofView: self
atCharacterIndex: startIndex]
&& [cell trackMouse: theEvent
inRect: cellFrame
ofView: self
atCharacterIndex: startIndex
untilMouseUp: NO])
{
return;
}
}
}
/* This is the code for handling click event on a link (a link
is some chars with the NSLinkAttributeName set to something
which is not-null, a NSURL object usually). */
{
/* What exactly is this link object, it's up to the
programmer who is using the NSTextView and who
originally created the link object and saved it under
the NSLinkAttributeName in the text. Normally, a NSURL
object is used. */
/* TODO: should call -clickedOnLink:atIndex: instead */
id link = [_textStorage attribute: NSLinkAttributeName
atIndex: startIndex
effectiveRange: NULL];
if (link != nil && _delegate != nil)
{
SEL selector = @selector(textView:clickedOnLink:atIndex:);
if ([_delegate respondsToSelector: selector])
{
/* Move the insertion point over the link. */
chosenRange = [self selectionRangeForProposedRange:
proposedRange
granularity: granularity];
[self setSelectedRange: chosenRange affinity: affinity
stillSelecting: NO];
[self displayIfNeeded];
/* Now 'activate' the link. The _delegate returns
YES if it handles the click, NO if it doesn't
-- and if it doesn't, we need to pass the click
to the next responder. */
if ([_delegate textView: self
clickedOnLink: link
atIndex: startIndex])
{
return;
}
else
{
[super mouseDown: theEvent];
return;
}
}
}
}
}
}
if (startIndex >= [_textStorage length])
{
NSUInteger glyphIndex = [_textStorage length]-1;
NSRect lfRect = [_layoutManager lineFragmentRectForGlyphAtIndex: glyphIndex
effectiveRange: NULL];
cellFrame.origin = [_layoutManager locationForGlyphAtIndex: glyphIndex];
cellFrame.size = [_layoutManager attachmentSizeForGlyphAtIndex: glyphIndex];
cellFrame.origin.y -= cellFrame.size.height;
cellFrame.origin.x += lfRect.origin.x;
cellFrame.origin.y += lfRect.origin.y;
}
else
{
NSRect lfRect;
NSUInteger glyphIndex;
glyphIndex = [_layoutManager glyphRangeForCharacterRange: NSMakeRange(startIndex, 1)
actualCharacterRange: NULL].location;
lfRect = [_layoutManager lineFragmentRectForGlyphAtIndex: glyphIndex
effectiveRange: NULL];
cellFrame.origin = [_layoutManager locationForGlyphAtIndex: glyphIndex];
cellFrame.size = [_layoutManager attachmentSizeForGlyphAtIndex: glyphIndex];
cellFrame.origin.y -= cellFrame.size.height;
cellFrame.origin.x += lfRect.origin.x;
cellFrame.origin.y += lfRect.origin.y;
}
if (NSEqualRects(NSZeroRect, cellFrame) == NO)
{
/* TODO: What about the insertion point ? */
if ([cell wantsToTrackMouseForEvent: theEvent
inRect: cellFrame
ofView: self
atCharacterIndex: startIndex]
&& [cell trackMouse: theEvent
inRect: cellFrame
ofView: self
atCharacterIndex: startIndex
untilMouseUp: NO])
{
return;
}
}
}
}
/* This is the code for handling click event on a link (a link
is some chars with the NSLinkAttributeName set to something
which is not-null, a NSURL object usually). */
{
/* What exactly is this link object, it's up to the
programmer who is using the NSTextView and who
originally created the link object and saved it under
the NSLinkAttributeName in the text. Normally, a NSURL
object is used. */
/* TODO: should call -clickedOnLink:atIndex: instead */
id link = [_textStorage attribute: NSLinkAttributeName
atIndex: startIndex
effectiveRange: NULL];
if (link != nil && _delegate != nil)
{
SEL selector = @selector(textView:clickedOnLink:atIndex:);
if ([_delegate respondsToSelector: selector])
{
/* Move the insertion point over the link. */
chosenRange = [self selectionRangeForProposedRange: proposedRange
granularity: granularity];
[self setSelectedRange: chosenRange affinity: affinity
stillSelecting: NO];
[self displayIfNeeded];
/* Now 'activate' the link. The _delegate returns
YES if it handles the click, NO if it doesn't
-- and if it doesn't, we need to pass the click
to the next responder. */
if ([_delegate textView: self
clickedOnLink: link
atIndex: startIndex])
{
return;
}
else
{
[super mouseDown: theEvent];
return;
}
}
}
}
}
}
chosenRange = [self selectionRangeForProposedRange: proposedRange chosenRange = [self selectionRangeForProposedRange: proposedRange
granularity: granularity]; granularity: granularity];
if (canDrag) if (canDrag)
{ {
NSUInteger mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask; NSUInteger mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
NSEvent *currentEvent; NSEvent *currentEvent;
currentEvent = [_window nextEventMatchingMask: mask currentEvent = [_window nextEventMatchingMask: mask
untilDate: [NSDate distantFuture] untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode inMode: NSEventTrackingRunLoopMode
dequeue: NO]; dequeue: NO];
if ([currentEvent type] == NSLeftMouseDragged) if ([currentEvent type] == NSLeftMouseDragged)
{ {
if (![self dragSelectionWithEvent: theEvent if (![self dragSelectionWithEvent: theEvent
offset: NSMakeSize(0, 0) offset: NSMakeSize(0, 0)
slideBack: YES]) slideBack: YES])
{ {
NSBeep(); NSBeep();
} }
return; return;
} }
} }
/* Enter modal loop tracking the mouse */ /* Enter modal loop tracking the mouse */
{ {
NSUInteger mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask NSUInteger mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask
| NSPeriodicMask; | NSPeriodicMask;
NSEvent *currentEvent; NSEvent *currentEvent;
NSEvent *lastEvent = nil; /* Last non-periodic event. */ NSEvent *lastEvent = nil; /* Last non-periodic event. */
NSDate *distantPast = [NSDate distantPast]; NSDate *distantPast = [NSDate distantPast];
BOOL gettingPeriodic, gotPeriodic; BOOL gettingPeriodic, gotPeriodic;
[self setSelectedRange: chosenRange affinity: affinity [self setSelectedRange: chosenRange affinity: affinity
stillSelecting: YES]; stillSelecting: YES];
currentEvent = [_window nextEventMatchingMask: mask currentEvent = [_window nextEventMatchingMask: mask
untilDate: [NSDate distantFuture] untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode inMode: NSEventTrackingRunLoopMode
dequeue: YES]; dequeue: YES];
gettingPeriodic = NO; gettingPeriodic = NO;
do do
{ {
gotPeriodic = NO; gotPeriodic = NO;
while (currentEvent && [currentEvent type] != NSLeftMouseUp) while (currentEvent && [currentEvent type] != NSLeftMouseUp)
{ {
if ([currentEvent type] == NSPeriodic) if ([currentEvent type] == NSPeriodic)
{ {
gotPeriodic = YES; gotPeriodic = YES;
} }
else else
lastEvent = currentEvent; {
currentEvent = [_window nextEventMatchingMask: mask lastEvent = currentEvent;
untilDate: distantPast }
inMode: NSEventTrackingRunLoopMode currentEvent = [_window nextEventMatchingMask: mask
dequeue: YES]; untilDate: distantPast
} inMode: NSEventTrackingRunLoopMode
if (currentEvent && [currentEvent type] == NSLeftMouseUp) dequeue: YES];
break; }
if (currentEvent && [currentEvent type] == NSLeftMouseUp)
/* break;
Automatic scrolling.
/*
If we aren't getting periodic events, we check all events. If any of Automatic scrolling.
them causes us to scroll, we scroll and start up the periodic events.
If we aren't getting periodic events, we check all events. If any of
If we are getting periodic events, we only scroll when we receive a them causes us to scroll, we scroll and start up the periodic events.
periodic event (and we use the last non-periodic event to determine
how far). If no scrolling occurred, we stop the periodic events. If we are getting periodic events, we only scroll when we receive a
*/ periodic event (and we use the last non-periodic event to determine
if (!gettingPeriodic) how far). If no scrolling occurred, we stop the periodic events.
{ */
if ([self autoscroll: lastEvent]) if (!gettingPeriodic)
{ {
gettingPeriodic = YES; if ([self autoscroll: lastEvent])
[NSEvent startPeriodicEventsAfterDelay: 0.1 {
withPeriod: 0.1]; gettingPeriodic = YES;
[NSEvent startPeriodicEventsAfterDelay: 0.1
} withPeriod: 0.1];
}
else if (gotPeriodic) }
{ }
if (![self autoscroll: lastEvent]) else if (gotPeriodic)
{ {
gettingPeriodic = NO; if (![self autoscroll: lastEvent])
[NSEvent stopPeriodicEvents]; {
} gettingPeriodic = NO;
} [NSEvent stopPeriodicEvents];
}
}
point = [self convertPoint: [lastEvent locationInWindow]
fromView: nil];
point = [self convertPoint: [lastEvent locationInWindow]
endIndex = [self _characterIndexForPoint: point fromView: nil];
respectFraction: YES];
endIndex = [self _characterIndexForPoint: point
/** respectFraction: YES];
* If the mouse is not inside the receiver, see if it is over another
* text view with the same layout manager. If so, use that text view /**
* to compute endIndex * If the mouse is not inside the receiver, see if it is over another
*/ * text view with the same layout manager. If so, use that text view
if (![self mouse: point inRect: [self bounds]]) * to compute endIndex
{ */
BOOL found = NO; if (![self mouse: point inRect: [self bounds]])
// FIXME: Is there an easier way to find the view under a point? {
NSView *winContentView = [[self window] contentView]; BOOL found = NO;
NSView *view = [winContentView hitTest: // FIXME: Is there an easier way to find the view under a point?
[[winContentView superview] convertPoint: [lastEvent locationInWindow] NSView *winContentView = [[self window] contentView];
fromView: nil]]; NSView *view = [winContentView hitTest:
for (; view != nil; view = [view superview]) [[winContentView superview] convertPoint: [lastEvent locationInWindow]
{ fromView: nil]];
if ([view isKindOfClass: [NSTextView class]] && for (; view != nil; view = [view superview])
[(NSTextView*)view layoutManager] == [self layoutManager]) {
{ if ([view isKindOfClass: [NSTextView class]] &&
found = YES; [(NSTextView*)view layoutManager] == [self layoutManager])
break; {
} found = YES;
} break;
}
if (found) }
{
NSTextView *textview = (NSTextView*)view; if (found)
NSPoint mouse = [textview convertPoint: [lastEvent locationInWindow] {
fromView: nil]; NSTextView *textview = (NSTextView*)view;
NSPoint mouse = [textview convertPoint: [lastEvent locationInWindow]
endIndex = [textview _characterIndexForPoint: mouse fromView: nil];
respectFraction: YES];
} endIndex = [textview _characterIndexForPoint: mouse
} respectFraction: YES];
}
proposedRange = MakeRangeFromAbs(endIndex, }
startIndex);
proposedRange = MakeRangeFromAbs(endIndex,
chosenRange = [self selectionRangeForProposedRange: proposedRange startIndex);
granularity: granularity];
chosenRange = [self selectionRangeForProposedRange: proposedRange
[self setSelectedRange: chosenRange affinity: affinity granularity: granularity];
stillSelecting: YES];
[self setSelectedRange: chosenRange affinity: affinity
currentEvent = [_window nextEventMatchingMask: mask stillSelecting: YES];
untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode currentEvent = [_window nextEventMatchingMask: mask
dequeue: YES]; untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode
dequeue: YES];
} while ([currentEvent type] != NSLeftMouseUp); } while ([currentEvent type] != NSLeftMouseUp);
if (gettingPeriodic) if (gettingPeriodic)
{ {
[NSEvent stopPeriodicEvents]; [NSEvent stopPeriodicEvents];
} }
} }
NSDebugLog(@"chosenRange. location = %d, length = %d\n", NSDebugLog(@"chosenRange. location = %d, length = %d\n",
(int)chosenRange.location, (int)chosenRange.length); (int)chosenRange.location, (int)chosenRange.length);
[self setSelectedRange: chosenRange affinity: affinity [self setSelectedRange: chosenRange affinity: affinity
stillSelecting: NO]; stillSelecting: NO];
/* Remember granularity till a new selection destroys the memory */ /* Remember granularity till a new selection destroys the memory */
[self setSelectionGranularity: granularity]; [self setSelectionGranularity: granularity];
} }