Fixes for outline/table view tooltip support

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/branches/gnustep_testplant_branch@40471 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Marcian Lytwyn 2017-04-13 14:22:52 +00:00
parent a3e28ec077
commit 7c21633785
6 changed files with 324 additions and 155 deletions

View file

@ -31,11 +31,13 @@
@class NSTimer;
@class NSView;
@class NSWindow;
@class GSTTProvider;
@interface GSToolTips : NSObject
{
NSView *view;
NSTrackingRectTag toolTipTag;
NSView *view;
GSTTProvider *_provider;
NSTrackingRectTag toolTipTag;
}
/** Destroy object handling tips for aView.

View file

@ -25,6 +25,7 @@
Boston, MA 02110-1301, USA.
*/
#import <Foundation/NSDebug.h>
#import <Foundation/NSGeometry.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSString.h>
@ -63,13 +64,16 @@
*/
@interface GSTTProvider : NSObject
{
id object;
void *data;
id object;
void *data;
NSInteger _trackingNumber;
}
- (void*) data;
- (id) initWithObject: (id)o userData: (void*)d;
- (id) object;
- (void) setObject: (id)o;
- (NSInteger) trackingNumber;
- (void) setTrackingNumber: (NSInteger)trackingNumber;
@end
@implementation GSTTProvider
@ -115,6 +119,16 @@
object = [[object description] copy];
}
}
- (NSInteger)trackingNumber
{
return _trackingNumber;
}
- (void)setTrackingNumber:(NSInteger)trackingNumber
{
_trackingNumber = trackingNumber;
}
@end
@interface GSTTView : NSView
@ -123,6 +137,7 @@
}
- (void)setText: (NSAttributedString *)text;
- (NSAttributedString*)text;
@end
@implementation GSTTView
@ -144,7 +159,12 @@
[self setNeedsDisplay: YES];
}
}
- (NSAttributedString*)text
{
return _text;
}
- (void) drawRect: (NSRect)dirtyRect
{
if (_text)
@ -204,6 +224,12 @@
- (void) _endDisplay;
- (void) _endDisplay:(NSTrackingRectTag)tag;
- (void) _timedOut: (NSTimer *)timer;
- (GSTTProvider*) provider;
- (void) setProvider: (GSTTProvider*)provider;
- (NSAttributedString*) _attributedStringForString: (NSString*)toolTipString;
- (NSSize) _sizeForToolTipText: (NSAttributedString*)toolTipText;
- (NSString*) _toolTipForProvider: (GSTTProvider*)provider location: (NSPoint)location;
- (void) _setToolTip: (NSString*)toolTipString atLocation: (NSPoint)mouseLocation;
@end
/*
typedef struct NSView_struct
@ -229,13 +255,13 @@ static BOOL restoreMouseMoved;
+ (void) initialize
{
viewsMap = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
NSObjectMapValueCallBacks, 8);
NSObjectMapValueCallBacks, 8);
window = [[GSTTPanel alloc] initWithContentRect: NSMakeRect(0,0,100,25)
styleMask: NSBorderlessWindowMask
backing: NSBackingStoreRetained
defer: YES];
styleMask: NSBorderlessWindowMask
backing: NSBackingStoreRetained
defer: YES];
[window setBackgroundColor:
[NSColor colorWithDeviceRed: 1.0 green: 1.0 blue: 0.90 alpha: 1.0]];
[window setReleasedWhenClosed: NO];
@ -291,8 +317,7 @@ static BOOL restoreMouseMoved;
return -1; // No provider object.
}
provider = [[GSTTProvider alloc] initWithObject: anObject
userData: data];
provider = [[GSTTProvider alloc] initWithObject: anObject userData: data];
tag = [view addTrackingRect: aRect
owner: self
userData: provider
@ -327,6 +352,7 @@ static BOOL restoreMouseMoved;
- (id) initForView: (NSView*)aView
{
view = aView;
_provider = nil;
toolTipTag = -1;
return self;
}
@ -334,7 +360,7 @@ static BOOL restoreMouseMoved;
- (void) mouseEntered: (NSEvent *)theEvent
{
GSTTProvider *provider;
NSString *toolTipString;
NSString *toolTipString;
if (timer != nil)
{
@ -347,31 +373,16 @@ static BOOL restoreMouseMoved;
}
provider = (GSTTProvider*)[theEvent userData];
if ([[provider object] respondsToSelector:
@selector(view:stringForToolTip:point:userData:)] == YES)
{
// From testing on OS X, point is in the view's coordinate system
// The locationInWindow has been converted to this in
// [NSWindow _checkTrackingRectangles:forEvent:]
NSPoint p = [theEvent locationInWindow];
toolTipString = [[provider object] view: view
stringForToolTip: [theEvent trackingNumber]
point: p
userData: [provider data]];
}
else
{
toolTipString = [provider object];
}
[provider setTrackingNumber: [theEvent trackingNumber]];
[self setProvider: provider];
toolTipString = [self _toolTipForProvider: provider
location: [theEvent locationInWindow]];
timer = [NSTimer scheduledTimerWithTimeInterval: 0.5
target: self
selector: @selector(_timedOut:)
userInfo: toolTipString
repeats: YES];
// WHy is this here...as it's essentially a no-op...timer has already been
// scheduled in the RunLoop in the previous line...
[[NSRunLoop currentRunLoop] addTimer: timer forMode: NSModalPanelRunLoopMode];
timedObject = self;
timedTag = [theEvent trackingNumber];
@ -399,19 +410,20 @@ static BOOL restoreMouseMoved;
- (void) mouseMoved: (NSEvent *)theEvent
{
NSPoint mouseLocation;
NSPoint mouseLocation = [NSEvent mouseLocation];
NSPoint locationInWindow = [view convertPoint: [theEvent locationInWindow] fromView:nil];
NSPoint origin;
NSString *tooltipString;
if (window == nil)
if ((window == nil) || (timer != nil))
{
return;
}
mouseLocation = [NSEvent mouseLocation];
origin = NSMakePoint(mouseLocation.x + offset.width,
mouseLocation.y + offset.height);
tooltipString = [self _toolTipForProvider: [self provider] location: locationInWindow];
if ([[[(GSTTView*)([window contentView]) text] string] isEqualToString: tooltipString] == NO)
[self _setToolTip: tooltipString atLocation: mouseLocation];
origin = NSMakePoint(mouseLocation.x + offset.width, mouseLocation.y + offset.height);
[window setFrameOrigin: origin];
}
@ -566,11 +578,8 @@ static BOOL restoreMouseMoved;
timedObject = nil;
timedTag = NSNotFound;
}
if (window != nil)
{
[window setFrame: NSZeroRect display: NO];
[window orderOut:self];
}
[self _setToolTip: nil atLocation: NSZeroPoint];
if (restoreMouseMoved == YES)
{
restoreMouseMoved = NO;
@ -578,35 +587,141 @@ static BOOL restoreMouseMoved;
}
}
- (NSAttributedString*) _attributedStringForString: (NSString*)toolTipString
{
if (toolTipString)
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
CGFloat size = [userDefaults floatForKey: @"NSToolTipsFontSize"];
if (size <= 0)
{
#if defined(__linux__) && defined(__x86_64__)
size = 11.0;
#else
size = 10.0;
#endif
}
[attributes setObject: [NSFont toolTipsFontOfSize: size] forKey: NSFontAttributeName];
return [[NSAttributedString alloc] initWithString: toolTipString attributes: attributes];
}
return nil;
}
- (NSSize) _sizeForToolTipText: (NSAttributedString*)toolTipText
{
NSSize textSize = [toolTipText size];
// TESTPLANT-MAL-03092016: Merged...
if (textSize.width > 300)
{
NSRect rect;
rect = [toolTipText boundingRectWithSize: NSMakeSize(300, 1e7)
options: 0];
textSize = rect.size;
// This extra pixel is needed, otherwise the last line gets cut off.
textSize.height += 1;
}
return textSize;
}
- (void) _setToolTip: (NSString*)toolTipString atLocation: (NSPoint)mouseLocation
{
if (toolTipString == nil)
{
if (window != nil)
{
[window setFrame: NSZeroRect display: NO];
[window orderOut:self];
}
[(GSTTView*)([window contentView]) setText: nil];
}
else
{
NSAttributedString *toolTipText = [self _attributedStringForString: toolTipString];
NSSize textSize = [self _sizeForToolTipText: toolTipText];
NSRect visible;
NSRect rect;
/* Create window just off the current mouse position
* Constrain it to be on screen, shrinking if necessary.
*/
rect = NSMakeRect(mouseLocation.x + 8,
mouseLocation.y - 16 - (textSize.height+3),
textSize.width + 4, textSize.height + 4);
visible = [[NSScreen mainScreen] visibleFrame];
if (NSMaxY(rect) > NSMaxY(visible))
{
rect.origin.y -= (NSMaxY(rect) - NSMaxY(visible));
}
if (NSMinY(rect) < NSMinY(visible))
{
rect.origin.y += (NSMinY(visible) - NSMinY(rect));
}
if (NSMaxY(rect) > NSMaxY(visible))
{
rect.origin.y = visible.origin.y;
rect.size.height = visible.size.height;
}
if (NSMaxX(rect) > NSMaxX(visible))
{
rect.origin.x -= (NSMaxX(rect) - NSMaxX(visible));
}
if (NSMinX(rect) < NSMinX(visible))
{
rect.origin.x += (NSMinX(visible) - NSMinX(rect));
}
if (NSMaxX(rect) > NSMaxX(visible))
{
rect.origin.x = visible.origin.x;
rect.size.width = visible.size.width;
}
offset.height = rect.origin.y - mouseLocation.y;
offset.width = rect.origin.x - mouseLocation.x;
isOpening = YES;
[(GSTTView*)([window contentView]) setText: toolTipText];
[window setFrame: rect display: NO];
[window orderFront: nil];
// Testplant-MAL-2015-06-26: Main branch merge...
// Keeping this testplant fix...
[window display];
isOpening = NO;
}
}
/* The delay timed out -- display the tooltip */
- (void) _timedOut: (NSTimer *)aTimer
{
CGFloat size;
NSString *toolTipString;
NSAttributedString *toolTipText = nil;
CGFloat size;
NSString *toolTipString;
NSSize textSize;
NSPoint mouseLocation = [NSEvent mouseLocation];
NSRect visible;
NSRect rect;
NSMutableDictionary *attributes;
// retain and autorelease the timer's userinfo because we
// may invalidate the timer (which releases the userinfo),
// but need the userinfo object to remain valid for the
// remainder of this method.
toolTipString = [[[aTimer userInfo] retain] autorelease];
if ( (nil == toolTipString) ||
([toolTipString isEqualToString: @""]) )
#if 0
if ( (nil == toolTipString) || ([toolTipString isEqualToString: @""]) )
{
return;
}
#endif
if (timer != nil)
{
if ([timer isValid])
{
[timer invalidate];
}
{
[timer invalidate];
}
timer = nil;
timedObject = nil;
timedTag = NSNotFound;
@ -629,81 +744,36 @@ static BOOL restoreMouseMoved;
[self _endDisplay];
}
size = [[NSUserDefaults standardUserDefaults] floatForKey: @"NSToolTipsFontSize"];
if (size <= 0)
{
#if defined(__linux__) && defined(__x86_64__)
size = 11.0;
#else
size = 10.0;
#endif
}
attributes = [NSMutableDictionary dictionary];
[attributes setObject: [NSFont toolTipsFontOfSize: size] forKey: NSFontAttributeName];
toolTipText = [[NSAttributedString alloc] initWithString: toolTipString attributes: attributes];
textSize = [toolTipText size];
// TESTPLANT-MAL-03092016: Merged...
if (textSize.width > 300)
{
NSRect rect;
rect = [toolTipText boundingRectWithSize: NSMakeSize(300, 1e7)
options: 0];
textSize = rect.size;
// This extra pixel is needed, otherwise the last line gets cut off.
textSize.height += 1;
}
/* Create window just off the current mouse position
* Constrain it to be on screen, shrinking if necessary.
*/
rect = NSMakeRect(mouseLocation.x + 8,
mouseLocation.y - 16 - (textSize.height+3),
textSize.width + 4, textSize.height + 4);
visible = [[NSScreen mainScreen] visibleFrame];
if (NSMaxY(rect) > NSMaxY(visible))
{
rect.origin.y -= (NSMaxY(rect) - NSMaxY(visible));
}
if (NSMinY(rect) < NSMinY(visible))
{
rect.origin.y += (NSMinY(visible) - NSMinY(rect));
}
if (NSMaxY(rect) > NSMaxY(visible))
{
rect.origin.y = visible.origin.y;
rect.size.height = visible.size.height;
}
if (NSMaxX(rect) > NSMaxX(visible))
{
rect.origin.x -= (NSMaxX(rect) - NSMaxX(visible));
}
if (NSMinX(rect) < NSMinX(visible))
{
rect.origin.x += (NSMinX(visible) - NSMinX(rect));
}
if (NSMaxX(rect) > NSMaxX(visible))
{
rect.origin.x = visible.origin.x;
rect.size.width = visible.size.width;
}
offset.height = rect.origin.y - mouseLocation.y;
offset.width = rect.origin.x - mouseLocation.x;
isOpening = YES;
[(GSTTView*)([window contentView]) setText: toolTipText];
[window setFrame: rect display: NO];
[window orderFront: nil];
// Testplant-MAL-2015-06-26: Main branch merge...
// Keeping this testplant fix...
[window display];
isOpening = NO;
RELEASE(toolTipText);
[self _setToolTip: toolTipString atLocation: mouseLocation];
}
- (void)setProvider:(GSTTProvider *)provider
{
_provider = provider;
}
- (GSTTProvider *)provider
{
return _provider;
}
- (NSString*) _toolTipForProvider: (GSTTProvider*)provider location: (NSPoint)location
{
const SEL theSelector = @selector(view:stringForToolTip:point:userData:);
NSString *toolTipString = nil;
if ([[provider object] respondsToSelector: theSelector] == YES)
{
// From testing on OS X, point is in the view's coordinate system
// The locationInWindow has been converted to this in
// [NSWindow _checkTrackingRectangles:forEvent:]
return [[provider object] view: view
stringForToolTip: [provider trackingNumber]
point: location
userData: [provider data]];
}
return [provider object];
}
@end

View file

@ -746,6 +746,11 @@ static NSImage *unexpandable = nil;
/**
* Sets the delegate of the outlineView.
*/
- (SEL) _toolTipSelector
{
return @selector(outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:);
}
- (void) setDelegate: (id)anObject
{
const SEL sel = @selector(outlineView:willDisplayCell:forTableColumn:item:);
@ -770,6 +775,37 @@ static NSImage *unexpandable = nil;
SET_DELEGATE_NOTIFICATION(ItemWillCollapse);
_del_responds = [_delegate respondsToSelector: sel];
// Tooltip support...
[self _setToolTipTracking];
}
- (NSString *)view:(NSView *)view
stringForToolTip:(NSToolTipTag)tag
point:(NSPoint)point
userData:(void *)data
{
NSInteger column = [self columnAtPoint: point];
NSInteger row = [self rowAtPoint: point];
if ((column != -1) && (row != -1))
{
if (_delegate && [_delegate respondsToSelector: [self _toolTipSelector]])
{
NSCell *cell = [self preparedCellAtColumn: column row: row];
NSRect frame = [self frameOfCellAtColumn: column row: row];
frame = [cell drawingRectForBounds: frame];
return [_delegate outlineView: self
toolTipForCell: cell
rect: &frame
tableColumn: [[self tableColumns] objectAtIndex: column]
item: [self itemAtRow: row]
mouseLocation: point];
}
NSWarnMLog(@"attempt to retrieve tooltip without delegate support for %@", NSStringFromSelector([self _toolTipSelector]));
}
return nil;
}
- (void) encodeWithCoder: (NSCoder*)aCoder

View file

@ -1291,11 +1291,11 @@ static NSImage *_pbc_image[5];
[menuItem setTarget: self];
}
else if ([[aDecoder class] coderVersion] > 0)
{
// Fix for XIB 5 parsing - unfortunately...
[menuItem setAction: @selector(_popUpItemAction:)];
[menuItem setTarget: self];
}
{
// Fix for XIB 5 parsing - unfortunately...
[menuItem setAction: @selector(_popUpItemAction:)];
[menuItem setTarget: self];
}
}
}

View file

@ -4709,6 +4709,10 @@ This method is deprecated, use -columnIndexesInRect:. */
}
}
[super setFrame: tmpRect];
// Tooltip support...
if (NSEqualRects(tmpRect, _frame) == NO)
[self _setToolTipTracking];
}
- (void) setFrameSize: (NSSize)frameSize
@ -4860,10 +4864,7 @@ This method is deprecated, use -columnIndexesInRect:. */
remainingWidth = newWidth;
// Do visible columns against the remaining width...
NSMutableArray *columns = [self _visibleColumns];
// Avoid hidden/unresizable columns for resizing...
//columns = AUTORELEASE([[self _resizableColumns] mutableCopy]);
NSArray *columns = [self _visibleColumns];
/*
* We store the minWidth and the maxWidth of every column
@ -5048,17 +5049,17 @@ This method is deprecated, use -columnIndexesInRect:. */
}
// Do visible columns for the remaining width...
NSMutableArray *columns = [self _visibleColumns];
[self _currentColumnWidth: columns remainingWidth: NULL];
NSArray *visColumns = [self _visibleColumns];
[self _currentColumnWidth: visColumns remainingWidth: NULL];
for (i = 0; i < [columns count]; i++)
for (i = 0; i < [visColumns count]; i++)
{
tb = [columns objectAtIndex: i];
remainingWidth -= [[columns objectAtIndex: i] width];
tb = [visColumns objectAtIndex: i];
remainingWidth -= [[visColumns objectAtIndex: i] width];
}
// Avoid hidden/unresizable columns for resizing...
columns = AUTORELEASE([[self _resizableColumns] mutableCopy]);
NSMutableArray *columns = [[self _resizableColumns] mutableCopy];
if (fabs(remainingWidth) > 1.0)
{
@ -5131,7 +5132,8 @@ This method is deprecated, use -columnIndexesInRect:. */
_lastRemainingWidth = NSMaxX([self convertRect: [_super_view bounds] fromView: _super_view]) - remainingWidth;
[self tile];
}
RELEASE(columns);
}
- (void) noteNumberOfRowsChanged
@ -5576,10 +5578,65 @@ This method is deprecated, use -columnIndexesInRect:. */
* Delegate
*/
- (NSString *)view:(NSView *)view
stringForToolTip:(NSToolTipTag)tag
point:(NSPoint)point
userData:(void *)data
{
const SEL theSelector = @selector(tableView:toolTipForCell:rect:tableColumn:row:mouseLocation:);
NSInteger column = [self columnAtPoint: point];
NSInteger row = [self rowAtPoint: point];
if ((column != -1) && (row != -1))
{
if (_delegate && [_delegate respondsToSelector:theSelector])
{
NSCell *cell = [self preparedCellAtColumn: column row: row];
NSRect frame = [self frameOfCellAtColumn: column row: row];
frame = [cell drawingRectForBounds: frame];
return [_delegate tableView: self
toolTipForCell: [self preparedCellAtColumn: column row: row]
rect: &frame
tableColumn: [[self tableColumns] objectAtIndex: column]
row: row
mouseLocation: point];
}
NSWarnMLog(@"attempt to retrieve tooltip without delegate support for %@", NSStringFromSelector([self _toolTipSelector]));
}
return nil;
}
- (NSRect) adjustScroll:(NSRect)newVisible
{
NSRect returnRect = [super adjustScroll:newVisible];
[self _setToolTipTracking: newVisible];
return returnRect;
}
- (SEL) _toolTipSelector
{
return @selector(tableView:toolTipForCell:rect:tableColumn:row:mouseLocation:);
}
- (void) _setToolTipTracking: (NSRect)frame
{
// Tooltip support...
[self removeAllToolTips];
if ([_delegate respondsToSelector: [self _toolTipSelector]])
[self addToolTipRect: frame owner: self userData: nil];
}
- (void) _setToolTipTracking
{
// Tooltip support...
[self _setToolTipTracking: [self visibleRect]];
}
- (void) setDelegate: (id)anObject
{
const SEL sel = @selector(tableView:willDisplayCell:forTableColumn:row:);
if (_delegate)
[nc removeObserver: _delegate name: nil object: self];
_delegate = anObject;
@ -5597,6 +5654,9 @@ This method is deprecated, use -columnIndexesInRect:. */
/* Cache */
_del_responds = [_delegate respondsToSelector: sel];
// Tooltip support...
[self _setToolTipTracking];
}
- (id) delegate

View file

@ -3981,6 +3981,14 @@ checkCursorRectanglesExited(NSView *theView, NSEvent *theEvent, NSPoint lastPoi
case NSOtherMouseDragged:
case NSRightMouseDragged:
case NSMouseMoved:
/*
* We need to go through all of the views, and if there is any with
* a tracking rectangle then we need to determine if we should send
* a NSMouseEntered or NSMouseExited event.
*/
(*ctImp)(self, ctSel, _wv, theEvent);
switch (type)
{
case NSLeftMouseDragged:
@ -4013,20 +4021,13 @@ checkCursorRectanglesExited(NSView *theView, NSEvent *theEvent, NSPoint lastPoi
{
[toolTipVisible mouseMoved: theEvent];
}
else
//else
{
[v mouseMoved: theEvent];
}
}
break;
}
/*
* We need to go through all of the views, and if there is any with
* a tracking rectangle then we need to determine if we should send
* a NSMouseEntered or NSMouseExited event.
*/
(*ctImp)(self, ctSel, _wv, theEvent);
if (_f.is_key)
{