libs-gui/Source/NSTableHeaderView.m
Eric Wasylishen 360c974c4e * Source/NSTableHeaderView.m: Use resize cursors
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@33042 72102866-910b-0410-8b05-ffd578937521
2011-05-15 05:48:22 +00:00

825 lines
27 KiB
Objective-C

/** <title>NSTableHeaderView</title>
Copyright (C) 1999 Free Software Foundation, Inc.
Author: Nicola Pero <n.pero@mi.flashnet.it>
Date: December 1999
First actual coding.
Author: Nicola Pero <nicola@brainstorm.co.uk>
Date: August 2000, Semptember 2000
Selection and resizing of Columns.
This file is part of the GNUstep GUI Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; see the file COPYING.LIB.
If not, see <http://www.gnu.org/licenses/> or write to the
Free Software Foundation, 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#import <Foundation/NSArray.h>
#import <Foundation/NSDebug.h>
#import <Foundation/NSRunLoop.h>
#import <Foundation/NSUserDefaults.h>
#import "AppKit/NSTableHeaderCell.h"
#import "AppKit/NSTableHeaderView.h"
#import "AppKit/NSTableColumn.h"
#import "AppKit/NSTableView.h"
#import "AppKit/NSWindow.h"
#import "AppKit/NSEvent.h"
#import "AppKit/NSApplication.h"
#import "AppKit/NSColor.h"
#import "AppKit/NSCursor.h"
#import "AppKit/NSScrollView.h"
#import "AppKit/NSGraphics.h"
#import "GSGuiPrivate.h"
#import "GNUstepGUI/GSTheme.h"
/*
* Number of pixels in either direction that will be counted as a hit
* on the column border and trigger a column resize.
*/
#define mouse_sensitivity 4
@interface NSTableView (GNUstepPrivate)
- (void) _userResizedTableColumn: (int)index
width: (float)width;
- (float *) _columnOrigins;
- (void) _mouseDownInHeaderOfTableColumn: (NSTableColumn *)tc;
- (void) _clickTableColumn: (NSTableColumn *)tc;
@end
@implementation NSTableHeaderView
/*
*
* Class methods
*
*/
+ (void) initialize
{
if (self == [NSTableColumn class])
[self setVersion: 1];
}
/*
*
* Instance methods
*
*/
/*
* Initializes an instance
*/
// TODO: Remove this method, if not really needed
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame: frameRect];
if (self == nil)
return nil;
_tableView = nil;
_resizedColumn = -1;
return self;
}
- (BOOL) isFlipped
{
return YES;
}
/*
* Setting the table view
*/
- (void)setTableView: (NSTableView*)aTableView
{
// We do not RETAIN aTableView but aTableView is supposed
// to RETAIN us.
_tableView = aTableView;
}
- (NSTableView*)tableView
{
return _tableView;
}
/*
* Checking altered columns
*/
- (int) draggedColumn
{
// TODO
return -1;
}
- (float) draggedDistance
{
// TODO
return -1;
}
- (int) resizedColumn
{
return _resizedColumn;
}
/*
* Utility methods
*/
- (int) columnAtPoint: (NSPoint)aPoint
{
if (_tableView == nil)
return -1;
/* Ask to the tableview, which is caching geometry info */
aPoint = [self convertPoint: aPoint toView: _tableView];
aPoint.y = [_tableView bounds].origin.y;
return [_tableView columnAtPoint: aPoint];
}
- (NSRect)headerRectOfColumn: (int)columnIndex
{
NSRect rect;
if (_tableView == nil)
return NSZeroRect;
/* Ask to the tableview, which is caching geometry info */
rect = [_tableView rectOfColumn: columnIndex];
rect = [self convertRect: rect fromView: _tableView];
rect.origin.y = _bounds.origin.y;
rect.size.height = _bounds.size.height;
return rect;
}
/*
* Overidden Methods
*/
- (void)drawRect: (NSRect)aRect
{
[[GSTheme theme] drawTableHeaderRect: aRect
inView: self];
}
- (void) resetCursorRects
{
if ([[self tableView] allowsColumnResizing])
{
const NSRect visibleRect = [self visibleRect];
NSInteger i;
const NSInteger count = [[[self tableView] tableColumns] count];
for (i = 0; i < (count - 1) && (count > 0); i++)
{
NSRect resizeRect = [self headerRectOfColumn: i];
resizeRect.origin.x = NSMaxX(resizeRect) - mouse_sensitivity;
resizeRect.size.width = 2 * mouse_sensitivity;
resizeRect = NSIntersectionRect(resizeRect, visibleRect);
if (!NSEqualRects(NSZeroRect, resizeRect))
{
[self addCursorRect: resizeRect cursor: [NSCursor resizeLeftRightCursor]];
}
}
}
}
/**
* In -mouseDown we intercept the mouse event to handle the
* colum resize and rearrangement. Resizing or moving columns
* will do a live resize/move of the columns by default. Users can revert to
* a "ghost" resize/move indicator by doing:
* defaults write NSGlobalDomain GSUseGhostResize YES
*/
- (void) mouseDown: (NSEvent*)event
{
NSPoint location = [event locationInWindow];
int clickCount;
int columnIndex;
NSTableColumn *currentColumn;
clickCount = [event clickCount];
/*
if (clickCount > 2)
{
return;
}
*/
location = [self convertPoint: location fromView: nil];
columnIndex = [self columnAtPoint: location];
if (columnIndex == -1)
{
return;
}
currentColumn = [[_tableView tableColumns]
objectAtIndex: columnIndex];
if (clickCount == 2)
{
[_tableView _sendDoubleActionForColumn: columnIndex];
// return;
}
// if (clickCount == 1)
{
NSRect rect = [self headerRectOfColumn: columnIndex];
/* Safety check */
if (_resizedColumn != -1)
{
NSLog(@"Bug: starting resizing of column while already resizing!");
_resizedColumn = -1;
}
if ([_tableView allowsColumnResizing])
{
/* Start resizing if the mouse is down on the bounds of a column. */
if (location.x >= NSMaxX(rect) - mouse_sensitivity)
{
if (columnIndex < [_tableView numberOfColumns])
{
_resizedColumn = columnIndex;
}
else
{
NSLog(@"Bug: Trying to resize column past the end of the table.");
}
}
else if (location.x <= NSMinX(rect) + mouse_sensitivity)
{
if (columnIndex > 0)
{
_resizedColumn = columnIndex - 1;
}
}
}
/* Resizing */
if (_resizedColumn != -1)
{
float p;
NSEvent *e;
BOOL lit;
unsigned int eventMask;
BOOL liveResize;
/* Width of the highlighted area. */
const float divWidth = 4;
/* Coordinates of visible part of table */
float minVisCoord = NSMinX([self visibleRect]);
float maxVisCoord = NSMaxX([self visibleRect]);
NSPoint unconverted = [event locationInWindow];
NSArray *columns = [_tableView tableColumns];
/* Column on the left of resizing bound */
NSTableColumn *column = [columns objectAtIndex: _resizedColumn];
const float columnMinX = NSMinX([self headerRectOfColumn: _resizedColumn]);
const float columnMinWidth = [column minWidth];
const float columnMaxWidth = [column maxWidth];
float newColumnWidth = [column width];
float newColumnMaxX;
NSRect oldHighlightRect;
NSRect highlightRect = [self visibleRect];
highlightRect.size.width = divWidth;
/* Mouse position */
/* YES if some highlighting was done and needs to be undone */
lit = NO;
eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask;
liveResize = ![[NSUserDefaults standardUserDefaults] boolForKey: @"GSUseGhostResize"];
if ([column isResizable] == NO)
{
_resizedColumn = -1;
return;
}
/* Do we need to check that we already fit into this area ?
We should */
[[NSCursor resizeLeftRightCursor] push];
if (!liveResize)
{
oldHighlightRect = NSZeroRect;
[self lockFocus];
[[NSColor lightGrayColor] set];
}
[[NSRunLoop currentRunLoop] limitDateForMode: NSEventTrackingRunLoopMode];
[NSEvent startPeriodicEventsAfterDelay: 0.05 withPeriod: 0.05];
e = [NSApp nextEventMatchingMask: eventMask
untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode
dequeue: YES];
while ([e type] != NSLeftMouseUp)
{
if ([e type] != NSPeriodic)
{
unconverted = [e locationInWindow];
}
p = [self convertPoint: unconverted fromView: nil].x;
minVisCoord = NSMinX([self visibleRect]);
maxVisCoord = NSMaxX([self visibleRect]);
/* newColumnWidth is always positive, since we always resize
the column to the left of the mouse pointer */
newColumnWidth = (p - columnMinX);
newColumnWidth = MAX(MIN(newColumnWidth, columnMaxWidth), columnMinWidth);
newColumnMaxX = columnMinX + newColumnWidth;
if (liveResize && [column width] != newColumnWidth)
{
[_tableView _userResizedTableColumn: _resizedColumn
width: newColumnWidth];
}
else if (!liveResize)
{
highlightRect.origin.x = newColumnMaxX;
/* Only draw the divider if it is not the same as the currently
drawn one, and lies within the header view area */
if (!NSEqualRects(oldHighlightRect,highlightRect) &&
highlightRect.origin.x > [self visibleRect].origin.x &&
highlightRect.origin.x + divWidth < [self visibleRect].origin.x + [self visibleRect].size.width)
{
if (lit)
{
NSHighlightRect(oldHighlightRect);
}
NSHighlightRect(highlightRect);
[_window flushWindow];
lit = YES;
oldHighlightRect = highlightRect;
}
}
/* Scroll the tableview, if needed, so the user's desired new
column edge position lies at the edge of the visible part of
the table */
if ((p > maxVisCoord && newColumnMaxX > maxVisCoord)
|| (p < minVisCoord && newColumnMaxX < minVisCoord))
{
NSRect tvRect = [_tableView visibleRect];
if (!liveResize && lit)
{
NSHighlightRect(oldHighlightRect);
lit = NO;
[_window flushWindow];
}
if (p > maxVisCoord) /* resizing to the right */
tvRect.origin.x = newColumnMaxX - tvRect.size.width;
else /* resizing to the left */
tvRect.origin.x = newColumnMaxX;
[_tableView scrollPoint: tvRect.origin];
}
e = [NSApp nextEventMatchingMask: eventMask
untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode
dequeue: YES];
}
[NSEvent stopPeriodicEvents];
[NSCursor pop];
if (!liveResize)
{
if (lit)
{
NSHighlightRect(oldHighlightRect);
[_window flushWindow];
}
[self unlockFocus];
/* The following tiles the table. We use a private method
which avoids tiling the table twice. */
if ([column width] != newColumnWidth)
{
[_tableView _userResizedTableColumn: _resizedColumn
width: newColumnWidth];
}
}
/* Clean up */
_resizedColumn = -1;
return;
}
/* We are not resizing
Let's launch a mouseDownInHeaderOfTableColumn message
*/
{
NSRect rect = [self headerRectOfColumn: columnIndex];
[_tableView _mouseDownInHeaderOfTableColumn:
[[_tableView tableColumns]
objectAtIndex: columnIndex]];
rect.origin.y++;
rect.size.height--;
[[currentColumn headerCell] setHighlighted: YES];
[self lockFocus];
[[currentColumn headerCell]
highlight: YES
withFrame: rect
inView: self];
[self unlockFocus];
[_window flushWindow];
}
/* Dragging */
/* Wait for mouse dragged events.
If mouse is dragged, move the column.
If mouse is not dragged but released, select/deselect the column. */
if ([_tableView allowsColumnReordering])
{
int i = columnIndex;
int j = columnIndex;
float minCoord;
float maxCoord;
float minVisCoord;
float maxVisCoord;
float *_cO;
float *_cO_minus1;
int numberOfColumns = [_tableView numberOfColumns];
unsigned int eventMask = (NSLeftMouseUpMask
| NSLeftMouseDraggedMask
| NSPeriodicMask);
unsigned int modifiers = [event modifierFlags];
NSEvent *e;
NSDate *distantFuture = [NSDate distantFuture];
NSRect visibleRect = [self visibleRect];
NSRect tvRect;
NSRect highlightRect = NSZeroRect, oldRect = NSZeroRect;
BOOL outside = NO;
BOOL lit = NO;
BOOL liveResize = ![[NSUserDefaults standardUserDefaults] boolForKey: @"GSUseGhostResize"];
BOOL mouseDragged = NO;
float p;
NSPoint unconverted;
minVisCoord = NSMinX (visibleRect);
maxVisCoord = NSMaxX (visibleRect);
{
NSRect bounds = [self bounds];
minCoord = NSMinX(bounds);
maxCoord = NSMaxX(bounds);
}
{
float *_c = [_tableView _columnOrigins];
_cO_minus1 = malloc((numberOfColumns + 3) * sizeof(float));
_cO = _cO_minus1 + 1;
memcpy(_cO, _c, numberOfColumns * sizeof(float));
_cO[numberOfColumns] = maxCoord;
_cO[numberOfColumns + 1] = maxCoord;
_cO[-1] = minCoord;
}
highlightRect.size.height = NSHeight (visibleRect);
highlightRect.origin.y = NSMinY (visibleRect);
if (!liveResize)
{
[self lockFocus];
[[NSColor lightGrayColor] set];
}
[NSEvent startPeriodicEventsAfterDelay: 0.05
withPeriod: 0.05];
e = [NSApp nextEventMatchingMask: eventMask
untilDate: distantFuture
inMode: NSEventTrackingRunLoopMode
dequeue: YES];
while ([e type] != NSLeftMouseUp)
{
switch ([e type])
{
case NSLeftMouseDragged:
unconverted = [e locationInWindow];
p = [self convertPoint: unconverted fromView: nil].x;
if (mouseDragged == NO)
{
NSLog(@"TODO: Deselect the column");
}
mouseDragged = YES;
if (p < minVisCoord || p > maxVisCoord)
{
outside = YES;
}
else
{
outside = NO;
i = j;
if (p > (_cO[i] + _cO[i+1]) / 2)
{
while (p > (_cO[i] + _cO[i+1]) / 2)
i++;
}
else if (p < (_cO[i] + _cO[i-1]) / 2)
{
while (p < (_cO[i] + _cO[i-1]) / 2)
i--;
}
if (!liveResize)
{
if (i != columnIndex
&& i != columnIndex + 1)
{
j = i;
highlightRect.size.height = NSHeight (visibleRect);
highlightRect.origin.y = NSMinY (visibleRect);
highlightRect.size.width = 7;
if (i == numberOfColumns)
{
highlightRect.origin.x = _cO[i] - 3;
}
else if (i == 0)
{
highlightRect.origin.x = _cO[i] - 3;
}
else
{
highlightRect.origin.x = _cO[i] - 3;
}
if (!NSEqualRects(highlightRect, oldRect))
{
if (lit)
NSHighlightRect(oldRect);
NSHighlightRect(highlightRect);
[_window flushWindow];
}
else if (!lit)
{
NSHighlightRect(highlightRect);
[_window flushWindow];
}
oldRect = highlightRect;
lit = YES;
}
else
{
i = columnIndex;
highlightRect.size.height = NSHeight (visibleRect);
highlightRect.origin.y = NSMinY (visibleRect);
highlightRect.origin.x = _cO[columnIndex];
highlightRect.size.width =
_cO[columnIndex + 1] - _cO[columnIndex];
if (!NSEqualRects(highlightRect, oldRect))
{
if (lit)
NSHighlightRect(oldRect);
// NSHighlightRect(highlightRect);
[_window flushWindow];
}
else if (!lit)
{
// NSHighlightRect(highlightRect);
// [_window flushWindow];
}
// oldRect = highlightRect;
oldRect = NSZeroRect;
lit = NO; //lit = YES;
}
}
else if (liveResize)
{
if (i > columnIndex)
i--;
if (i != columnIndex)
{
[_tableView moveColumn: columnIndex
toColumn: i];
}
columnIndex = i;
}
}
break;
case NSPeriodic:
if (outside == YES)
{
if (!liveResize)
{
if (lit)
{
NSHighlightRect(oldRect);
[_window flushWindow];
lit = NO;
oldRect = NSZeroRect;
}
}
p = [self convertPoint: unconverted
fromView: nil].x;
tvRect = [_tableView visibleRect];
if (p > maxVisCoord)
{
if (p > maxCoord)
tvRect.origin.x += (p - maxVisCoord)/8;
else
tvRect.origin.x += (p - maxVisCoord)/2;
}
else if (p < minVisCoord)
{
if (p < minCoord)
tvRect.origin.x += (p - minVisCoord)/8;
else
tvRect.origin.x += (p - minVisCoord)/2;
}
else // TODO remove this condition
{
NSLog(@"not outside !");
}
[_tableView scrollPoint: tvRect.origin];
visibleRect = [self visibleRect];
minVisCoord = NSMinX (visibleRect);
maxVisCoord = NSMaxX (visibleRect);
}
break;
default:
break;
}
e = [NSApp nextEventMatchingMask: eventMask
untilDate: distantFuture
inMode: NSEventTrackingRunLoopMode
dequeue: YES];
}
if (!liveResize)
{
if (lit)
{
NSHighlightRect(highlightRect);
[_window flushWindow];
}
[self unlockFocus];
}
[NSEvent stopPeriodicEvents];
if (mouseDragged == NO)
{
[_tableView _selectColumn: columnIndex modifiers: modifiers];
[_tableView _clickTableColumn: currentColumn];
[self setNeedsDisplay: YES];;
}
else // mouseDragged == YES
{
{
NSRect rect = [self headerRectOfColumn: columnIndex];
[_tableView _mouseDownInHeaderOfTableColumn:
[[_tableView tableColumns]
objectAtIndex: columnIndex]];
rect.origin.y++;
rect.size.height--;
[[currentColumn headerCell]
setHighlighted: NO];
[self lockFocus];
[[currentColumn headerCell]
highlight: NO
withFrame: rect
inView: self];
[self unlockFocus];
[_window flushWindow];
}
if (i > columnIndex)
i--;
if (i != columnIndex)
{
[_tableView moveColumn: columnIndex
toColumn: i];
}
}
free(_cO_minus1);
return;
}
else
{
NSRect cellFrame = [self headerRectOfColumn: columnIndex];
NSApplication *theApp = [NSApplication sharedApplication];
unsigned int modifiers = [event modifierFlags];
NSPoint location = [event locationInWindow];
NSPoint point = [self convertPoint: location fromView: nil];
if (![self mouse: point inRect: cellFrame])
{
NSLog(@"not in frame, what's happening ?");
return;
}
event = [theApp nextEventMatchingMask: NSLeftMouseUpMask
untilDate: [NSDate distantFuture]
inMode: NSEventTrackingRunLoopMode
dequeue: NO];
location = [event locationInWindow];
point = [self convertPoint: location fromView: nil];
if (![self mouse: point inRect: cellFrame])
{
NSDebugLLog(@"NSCell",
@"tableheaderview point not in cell frame\n");
{
NSRect rect = [self headerRectOfColumn: columnIndex];
[_tableView _mouseDownInHeaderOfTableColumn:
[[_tableView tableColumns]
objectAtIndex: columnIndex]];
rect.origin.y++;
rect.size.height--;
[[currentColumn headerCell]
setHighlighted: NO];
[self lockFocus];
[[currentColumn headerCell]
highlight: NO
withFrame: rect
inView: self];
[self unlockFocus];
[_window flushWindow];
}
}
else
{
[_tableView _selectColumn: columnIndex modifiers: modifiers];
[_tableView _clickTableColumn: currentColumn];
[self setNeedsDisplay: YES];
/*
if ([_tableView highlightedTableColumn] != currentColumn)
{
NSRect rect = [self headerRectOfColumn: columnIndex];
// [_tableView _mouseDownInHeaderOfTableColumn:
// [[_tableView tableColumns]
// objectAtIndex: columnIndex]];
rect.origin.y++;
rect.size.height--;
NSLog(@"highlight");
[[currentColumn headerCell] setHighlighted: NO];
[[currentColumn headerCell]
highlight: NO
withFrame: rect
inView: self];
[_window flushWindow];
}
*/
}
}
}
}
/*
* Encoding/Decoding
*/
- (void) encodeWithCoder: (NSCoder*)aCoder
{
[super encodeWithCoder: aCoder];
/* Nothing else to encode in NSTableHeaderView:
- _tableView is set by the parent NSTableView
- _resizedColumn is reset on decoding anyway
*/
}
- (id) initWithCoder: (NSCoder*)aDecoder
{
self = [super initWithCoder: aDecoder];
if (self == nil)
return nil;
_tableView = nil;
_resizedColumn = -1;
return self;
}
@end