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

@ -5532,7 +5532,7 @@ other than copy/paste or dragging. */
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
@ -5541,155 +5541,163 @@ other than copy/paste or dragging. */
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; NSRect cellFrame = NSZeroRect;
NSRect lfRect;
NSUInteger glyphIndex;
glyphIndex = if (startIndex >= [_textStorage length])
[_layoutManager {
glyphRangeForCharacterRange: NSMakeRange(startIndex, 1) NSUInteger glyphIndex = [_textStorage length]-1;
actualCharacterRange: NULL].location; NSRect lfRect = [_layoutManager lineFragmentRectForGlyphAtIndex: glyphIndex
lfRect = effectiveRange: NULL];
[_layoutManager cellFrame.origin = [_layoutManager locationForGlyphAtIndex: glyphIndex];
lineFragmentRectForGlyphAtIndex: glyphIndex cellFrame.size = [_layoutManager attachmentSizeForGlyphAtIndex: glyphIndex];
effectiveRange: NULL]; cellFrame.origin.y -= cellFrame.size.height;
cellFrame.origin = cellFrame.origin.x += lfRect.origin.x;
[_layoutManager cellFrame.origin.y += lfRect.origin.y;
locationForGlyphAtIndex: glyphIndex]; }
cellFrame.size = else
[_layoutManager {
attachmentSizeForGlyphAtIndex: glyphIndex]; NSRect lfRect;
cellFrame.origin.y -= cellFrame.size.height; NSUInteger glyphIndex;
cellFrame.origin.x += lfRect.origin.x;
cellFrame.origin.y += lfRect.origin.y;
/* TODO: What about the insertion point ? */ glyphIndex = [_layoutManager glyphRangeForCharacterRange: NSMakeRange(startIndex, 1)
if ([cell wantsToTrackMouseForEvent: theEvent actualCharacterRange: NULL].location;
inRect: cellFrame lfRect = [_layoutManager lineFragmentRectForGlyphAtIndex: glyphIndex
ofView: self effectiveRange: NULL];
atCharacterIndex: startIndex] cellFrame.origin = [_layoutManager locationForGlyphAtIndex: glyphIndex];
&& [cell trackMouse: theEvent cellFrame.size = [_layoutManager attachmentSizeForGlyphAtIndex: glyphIndex];
inRect: cellFrame cellFrame.origin.y -= cellFrame.size.height;
ofView: self cellFrame.origin.x += lfRect.origin.x;
atCharacterIndex: startIndex cellFrame.origin.y += lfRect.origin.y;
untilMouseUp: NO]) }
{
return;
}
}
}
/* This is the code for handling click event on a link (a link if (NSEqualRects(NSZeroRect, cellFrame) == NO)
is some chars with the NSLinkAttributeName set to something {
which is not-null, a NSURL object usually). */ /* TODO: What about the insertion point ? */
{ if ([cell wantsToTrackMouseForEvent: theEvent
/* What exactly is this link object, it's up to the inRect: cellFrame
programmer who is using the NSTextView and who ofView: self
originally created the link object and saved it under atCharacterIndex: startIndex]
the NSLinkAttributeName in the text. Normally, a NSURL && [cell trackMouse: theEvent
object is used. */ inRect: cellFrame
/* TODO: should call -clickedOnLink:atIndex: instead */ ofView: self
id link = [_textStorage attribute: NSLinkAttributeName atCharacterIndex: startIndex
atIndex: startIndex untilMouseUp: NO])
effectiveRange: NULL]; {
if (link != nil && _delegate != nil) return;
{ }
SEL selector = @selector(textView:clickedOnLink:atIndex:); }
}
}
if ([_delegate respondsToSelector: selector]) /* This is the code for handling click event on a link (a link
{ is some chars with the NSLinkAttributeName set to something
/* Move the insertion point over the link. */ which is not-null, a NSURL object usually). */
chosenRange = [self selectionRangeForProposedRange: {
proposedRange /* What exactly is this link object, it's up to the
granularity: granularity]; 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:);
[self setSelectedRange: chosenRange affinity: affinity if ([_delegate respondsToSelector: selector])
stillSelecting: NO]; {
/* Move the insertion point over the link. */
chosenRange = [self selectionRangeForProposedRange: proposedRange
granularity: granularity];
[self displayIfNeeded]; [self setSelectedRange: chosenRange affinity: affinity
stillSelecting: NO];
/* Now 'activate' the link. The _delegate returns [self displayIfNeeded];
YES if it handles the click, NO if it doesn't
-- and if it doesn't, we need to pass the click /* Now 'activate' the link. The _delegate returns
to the next responder. */ YES if it handles the click, NO if it doesn't
if ([_delegate textView: self -- and if it doesn't, we need to pass the click
clickedOnLink: link to the next responder. */
atIndex: startIndex]) if ([_delegate textView: self
{ clickedOnLink: link
return; atIndex: startIndex])
} {
else return;
{ }
[super mouseDown: theEvent]; else
return; {
} [super mouseDown: theEvent];
} return;
} }
} }
} }
} }
}
}
chosenRange = [self selectionRangeForProposedRange: proposedRange chosenRange = [self selectionRangeForProposedRange: proposedRange
granularity: granularity]; granularity: granularity];
@ -5699,153 +5707,155 @@ other than copy/paste or dragging. */
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. Automatic scrolling.
If we aren't getting periodic events, we check all events. If any of If we aren't getting periodic events, we check all events. If any of
them causes us to scroll, we scroll and start up the periodic events. them causes us to scroll, we scroll and start up the periodic events.
If we are getting periodic events, we only scroll when we receive a 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 periodic event (and we use the last non-periodic event to determine
how far). If no scrolling occurred, we stop the periodic events. how far). If no scrolling occurred, we stop the periodic events.
*/ */
if (!gettingPeriodic) if (!gettingPeriodic)
{ {
if ([self autoscroll: lastEvent]) if ([self autoscroll: lastEvent])
{ {
gettingPeriodic = YES; gettingPeriodic = YES;
[NSEvent startPeriodicEventsAfterDelay: 0.1 [NSEvent startPeriodicEventsAfterDelay: 0.1
withPeriod: 0.1]; withPeriod: 0.1];
} }
} }
else if (gotPeriodic) else if (gotPeriodic)
{ {
if (![self autoscroll: lastEvent]) if (![self autoscroll: lastEvent])
{ {
gettingPeriodic = NO; gettingPeriodic = NO;
[NSEvent stopPeriodicEvents]; [NSEvent stopPeriodicEvents];
} }
} }
point = [self convertPoint: [lastEvent locationInWindow] point = [self convertPoint: [lastEvent locationInWindow]
fromView: nil]; fromView: nil];
endIndex = [self _characterIndexForPoint: point endIndex = [self _characterIndexForPoint: point
respectFraction: YES]; respectFraction: YES];
/** /**
* If the mouse is not inside the receiver, see if it is over another * 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 * text view with the same layout manager. If so, use that text view
* to compute endIndex * to compute endIndex
*/ */
if (![self mouse: point inRect: [self bounds]]) if (![self mouse: point inRect: [self bounds]])
{ {
BOOL found = NO; BOOL found = NO;
// FIXME: Is there an easier way to find the view under a point? // FIXME: Is there an easier way to find the view under a point?
NSView *winContentView = [[self window] contentView]; NSView *winContentView = [[self window] contentView];
NSView *view = [winContentView hitTest: NSView *view = [winContentView hitTest:
[[winContentView superview] convertPoint: [lastEvent locationInWindow] [[winContentView superview] convertPoint: [lastEvent locationInWindow]
fromView: nil]]; fromView: nil]];
for (; view != nil; view = [view superview]) for (; view != nil; view = [view superview])
{ {
if ([view isKindOfClass: [NSTextView class]] && if ([view isKindOfClass: [NSTextView class]] &&
[(NSTextView*)view layoutManager] == [self layoutManager]) [(NSTextView*)view layoutManager] == [self layoutManager])
{ {
found = YES; found = YES;
break; break;
} }
} }
if (found) if (found)
{ {
NSTextView *textview = (NSTextView*)view; NSTextView *textview = (NSTextView*)view;
NSPoint mouse = [textview convertPoint: [lastEvent locationInWindow] NSPoint mouse = [textview convertPoint: [lastEvent locationInWindow]
fromView: nil]; fromView: nil];
endIndex = [textview _characterIndexForPoint: mouse endIndex = [textview _characterIndexForPoint: mouse
respectFraction: YES]; respectFraction: YES];
} }
} }
proposedRange = MakeRangeFromAbs(endIndex, proposedRange = MakeRangeFromAbs(endIndex,
startIndex); startIndex);
chosenRange = [self selectionRangeForProposedRange: proposedRange chosenRange = [self selectionRangeForProposedRange: proposedRange
granularity: granularity]; granularity: granularity];
[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];
} 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];