/** NSTableHeaderView Copyright (C) 1999 Free Software Foundation, Inc. Author: Nicola Pero Date: December 1999 First actual coding. Author: Nicola Pero 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 Library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include @interface NSTableView (GNUstepPrivate) - (void) _userResizedTableColumn: (int)index width: (float)width; - (float *) _columnOrigins; - (void) _mouseDownInHeaderOfTableColumn: (NSTableColumn *)tc; - (void) _didClickTableColumn: (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 - (NSTableHeaderView*)initWithFrame:(NSRect)frameRect { self = [super initWithFrame: frameRect]; _tableView = nil; _resizedColumn = -1; return self; } /* * 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 { NSArray *columns; int firstColumnToDraw; int lastColumnToDraw; NSRect drawingRect; NSTableColumn *column; NSTableColumn *highlightedTableColumn; float width; int i; NSCell *cell; if (_tableView == nil) return; firstColumnToDraw = [self columnAtPoint: NSMakePoint (aRect.origin.x, aRect.origin.y)]; if (firstColumnToDraw == -1) firstColumnToDraw = 0; lastColumnToDraw = [self columnAtPoint: NSMakePoint (NSMaxX (aRect), aRect.origin.y)]; if (lastColumnToDraw == -1) lastColumnToDraw = [_tableView numberOfColumns] - 1; drawingRect = [self headerRectOfColumn: firstColumnToDraw]; drawingRect.origin.y++; drawingRect.size.height--; columns = [_tableView tableColumns]; highlightedTableColumn = [_tableView highlightedTableColumn]; for (i = firstColumnToDraw; i < lastColumnToDraw; i++) { column = [columns objectAtIndex: i]; width = [column width]; drawingRect.size.width = width; cell = [column headerCell]; if ((column == highlightedTableColumn) || [_tableView isColumnSelected: i]) { /* FIXME/TODO - cell can be any subclass of NSCell (specially a custom subclass), which might not implement setBackgroundColor: (or might not want us to mess up its custom drawing by hardcoding a background color here) - so we should not call it. Instead, NSTableHeaderCell should override setHighlighted: to change the background color. */ [cell setHighlighted: YES]; [cell setBackgroundColor: [NSColor controlColor]]; } else { [cell setHighlighted: NO]; [cell setBackgroundColor: [NSColor controlShadowColor]]; } [cell drawWithFrame: drawingRect inView: self]; drawingRect.origin.x += width; } if (lastColumnToDraw == [_tableView numberOfColumns] - 1) { column = [columns objectAtIndex: lastColumnToDraw]; width = [column width] - 1; drawingRect.size.width = width; cell = [column headerCell]; if ((column == highlightedTableColumn) || [_tableView isColumnSelected: lastColumnToDraw]) { [cell setHighlighted: YES]; [cell setBackgroundColor: [NSColor controlColor]]; } else { [cell setHighlighted: NO]; [cell setBackgroundColor: [NSColor controlShadowColor]]; } [cell drawWithFrame: drawingRect inView: self]; drawingRect.origin.x += width; } else { column = [columns objectAtIndex: lastColumnToDraw]; width = [column width]; drawingRect.size.width = width; cell = [column headerCell]; if ((column == highlightedTableColumn) || [_tableView isColumnSelected: lastColumnToDraw]) { [cell setHighlighted: YES]; [cell setBackgroundColor: [NSColor controlColor]]; } else { [cell setHighlighted: NO]; [cell setBackgroundColor: [NSColor controlShadowColor]]; } [cell drawWithFrame: drawingRect inView: self]; drawingRect.origin.x += width; } { NSGraphicsContext *ctxt = GSCurrentContext(); [self lockFocus]; DPSsetgray(ctxt, NSBlack); DPSrectfill(ctxt,_bounds.origin.x, _bounds.origin.y, _bounds.size.width, 1.); DPSrectfill(ctxt, NSMaxX(_bounds)-1., NSMinY(_bounds), 1., _bounds.size.height); [self unlockFocus]; } } - (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) - 1) { if (columnIndex != ([_tableView numberOfColumns])) { _resizedColumn = columnIndex; } else { NSLog(@"ohoh"); } } else if (location.x <= NSMinX (rect) + 2) { if (columnIndex > 0) { _resizedColumn = columnIndex - 1; } } } /* Resizing */ if (_resizedColumn != -1) { /* Width of the highlighted area. */ const float divWidth = 4; /* Dragging limits */ float minCoord; float maxCoord; float minAbsCoord; float maxAbsCoord; float minVisCoord; float maxVisCoord; NSRect tvRect; NSPoint unconverted; NSArray *columns; /* Column on the left of resizing bound */ NSTableColumn *column; NSRect rectLow = [self headerRectOfColumn: _resizedColumn]; /* Old highlighted rect, used to avoid useless redrawing */ NSRect oldRect = NSZeroRect; /* Current highlighted rect */ NSRect r; /* Mouse position */ float p; float q; BOOL outside = NO; /* YES if some highlighting was done and needs to be undone */ BOOL lit = NO; /* YES if some dragging was actually done - to avoid retiling/redrawing the table if no dragging is done */ BOOL dragged = NO; NSEvent *e; NSDate *farAway = [NSDate distantFuture]; unsigned int eventMask = NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSPeriodicMask; /* Determine dragging limits, constrained to visible rect */ rect = [self visibleRect]; minVisCoord = MAX (NSMinX (rectLow), NSMinX (rect)) + divWidth; maxVisCoord = NSMaxX (rect) - divWidth; /* Then constrain to minimum and maximum column width if any */ columns = [_tableView tableColumns]; /* Column at the left */ column = [columns objectAtIndex: _resizedColumn]; if ([column isResizable] == NO) { _resizedColumn = -1; return; } /* We use p as a temporary variable for a while */ minAbsCoord = NSMinX (rectLow) + [column minWidth]; maxAbsCoord = NSMinX (rectLow) + [column maxWidth]; minCoord = MAX (minAbsCoord, minVisCoord); maxCoord = MIN (maxAbsCoord, maxVisCoord); /* Do we need to check that we already fit into this area ? We should */ [self lockFocus]; [[NSRunLoop currentRunLoop] limitDateForMode: NSEventTrackingRunLoopMode]; [[NSColor lightGrayColor] set]; r.size.width = divWidth; r.size.height = NSHeight (rect); r.origin.y = NSMinY (rect); [NSEvent startPeriodicEventsAfterDelay: 0.05 withPeriod: 0.05]; e = [NSApp nextEventMatchingMask: eventMask untilDate: farAway inMode: NSEventTrackingRunLoopMode dequeue: YES]; /* Safety assignment to make sure p is never left unitialized - should make no difference with current code but anyway */ p = NSMaxX (rectLow); while ([e type] != NSLeftMouseUp) { if ([e type] != NSPeriodic) { dragged = YES; unconverted = [e locationInWindow]; p = [self convertPoint: unconverted fromView: nil].x; q = p; if (p > maxVisCoord || p < minVisCoord) { outside = YES; } else { outside = NO; } if (p < minCoord) { p = minCoord; } else if (p > maxCoord) { p = maxCoord; } r.origin.x = p - (divWidth / 2.); if (!outside && NSEqualRects(r, oldRect) == NO) { if (lit == YES) { NSHighlightRect (oldRect); } NSHighlightRect (r); [_window flushWindow]; lit = YES; oldRect = r; } } else { if (outside) { q = [self convertPoint: unconverted fromView: nil].x; if (lit) { NSHighlightRect (oldRect); [_window flushWindow]; lit = NO; } tvRect = [_tableView visibleRect]; if (q > maxVisCoord) { if (q > maxAbsCoord + 5) q = maxAbsCoord + 5; tvRect.origin.x += (q - maxVisCoord)/2; } else if (q < minVisCoord) { if (q < minAbsCoord - 5) q = minAbsCoord - 5; tvRect.origin.x += (q - minVisCoord)/2; } else // TODO remove this condition { NSLog(@"not outside !"); } [_tableView scrollPoint: tvRect.origin]; rect = [self visibleRect]; minVisCoord = NSMinX (rect) + divWidth; maxVisCoord = NSMaxX (rect) - divWidth; minCoord = MAX (minAbsCoord, minVisCoord); maxCoord = MIN (maxAbsCoord, maxVisCoord); } } e = [NSApp nextEventMatchingMask: eventMask untilDate: farAway inMode: NSEventTrackingRunLoopMode dequeue: YES]; } [NSEvent stopPeriodicEvents]; if (outside) { p = [self convertPoint: [e locationInWindow] fromView: nil].x; if (p > maxAbsCoord) p = maxAbsCoord; else if (p < minAbsCoord) p = minAbsCoord; } if (lit == YES) { NSHighlightRect(oldRect); [_window flushWindow]; } [self unlockFocus]; /* The following tiles the table. We use a private method which avoids tiling the table twice. */ if (dragged == YES) { [_tableView _userResizedTableColumn: _resizedColumn width: (p - NSMinX (rectLow))]; } /* 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] setBackgroundColor: [NSColor controlColor]]; [[currentColumn headerCell] highlight: YES withFrame: rect inView: self]; [_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, oldRect; BOOL outside = NO; BOOL lit = NO; 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); [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 (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; } } break; case NSPeriodic: if (outside == YES) { 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 (lit) { NSHighlightRect(highlightRect); [_window flushWindow]; lit = NO; } [NSEvent stopPeriodicEvents]; [self unlockFocus]; if (mouseDragged == NO) { [_tableView _selectColumn: columnIndex modifiers: modifiers]; [_tableView _didClickTableColumn: 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] setBackgroundColor: [NSColor controlShadowColor]]; [[currentColumn headerCell] highlight: NO withFrame: rect inView: self]; [_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: nil 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] setBackgroundColor: [NSColor controlShadowColor]]; [[currentColumn headerCell] highlight: NO withFrame: rect inView: self]; [_window flushWindow]; } } else { [_tableView _selectColumn: columnIndex modifiers: modifiers]; [_tableView _didClickTableColumn: 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] setBackgroundColor: [NSColor controlShadowColor]]; [[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]; _tableView = nil; _resizedColumn = -1; return self; } @end