/** NSMatrix Matrix class for grouping controls Copyright (C) 1996, 1997, 1999 Free Software Foundation, Inc. Author: Ovidiu Predescu Date: March 1997 A completely rewritten version of the original source by Pascal Forget and Scott Christley. Modified: Felipe A. Rodriguez Date: August 1998 Cell handling rewritten: Richard Frith-Macdonald Date: November 1999 Implementation of Editing: Nicola Pero Date: November 1999 Modified: Mirko Viviani Date: March 2001 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 static NSNotificationCenter *nc; #define STRICT 0 #ifdef MIN # undef MIN #endif #define MIN(A,B) ({ typeof(A) __a = (A); \ typeof(B) __b = (B); \ __a < __b ? __a : __b; }) #ifdef MAX # undef MAX #endif #define MAX(A,B) ({ typeof(A) __a = (A); \ typeof(B) __b = (B); \ __a < __b ? __b : __a; }) #ifdef ABS # undef ABS #endif #define ABS(A) ({ typeof(A) __a = (A); __a < 0 ? -__a : __a; }) #define SIGN(x) \ ({typeof(x) _SIGN_x = (x); \ _SIGN_x > 0 ? 1 : (_SIGN_x == 0 ? 0 : -1); }) #define POINT_FROM_INDEX(index) \ ({MPoint point = { index % _numCols, index / _numCols }; point; }) #define INDEX_FROM_COORDS(x,y) \ (y * _numCols + x) #define INDEX_FROM_POINT(point) \ (point.y * _numCols + point.x) /* Some stuff needed to compute the selection in the list mode. */ typedef struct { int x; int y; } MPoint; typedef struct { int x; int y; int width; int height; } MRect; static inline MPoint MakePoint (int x, int y) { MPoint point = { x, y }; return point; } @interface NSMatrix (PrivateMethods) - (void) _renewRows: (int)row columns: (int)col rowSpace: (int)rowSpace colSpace: (int)colSpace; - (void) _setState: (int)state highlight: (BOOL)highlight startIndex: (int)start endIndex: (int)end; - (BOOL) _selectNextSelectableCellAfterRow: (int)row column: (int)column; - (BOOL) _selectPreviousSelectableCellBeforeRow: (int)row column: (int)column; @end enum { DEFAULT_CELL_HEIGHT = 17, DEFAULT_CELL_WIDTH = 100 }; @implementation NSMatrix /* Class variables */ static Class defaultCellClass = nil; static int mouseDownFlags = 0; static SEL copySel; static SEL initSel; static SEL allocSel; static SEL getSel; + (void) initialize { if (self == [NSMatrix class]) { /* Set the initial version */ [self setVersion: 1]; copySel = @selector(copyWithZone:); initSel = @selector(init); allocSel = @selector(allocWithZone:); getSel = @selector(objectAtIndex:); /* * MacOS-X docs say default cell class is NSActionCell */ defaultCellClass = [NSActionCell class]; // nc = [NSNotificationCenter defaultCenter]; } } + (Class) cellClass { return defaultCellClass; } + (void) setCellClass: (Class)classId { defaultCellClass = classId; if (defaultCellClass == nil) defaultCellClass = [NSActionCell class]; } - (id) init { return [self initWithFrame: NSZeroRect mode: NSRadioModeMatrix cellClass: [isa cellClass] numberOfRows: 0 numberOfColumns: 0]; } - (id) initWithFrame: (NSRect)frameRect { return [self initWithFrame: frameRect mode: NSRadioModeMatrix cellClass: [isa cellClass] numberOfRows: 0 numberOfColumns: 0]; } - (id) _privateFrame: (NSRect)frameRect mode: (int)aMode numberOfRows: (int)rows numberOfColumns: (int)cols { _myZone = [self zone]; [self _renewRows: rows columns: cols rowSpace: 0 colSpace: 0]; _mode = aMode; [self setFrame: frameRect]; if ((_numCols > 0) && (_numRows > 0)) _cellSize = NSMakeSize (rint(frameRect.size.width/_numCols), rint(frameRect.size.height/_numRows)); else _cellSize = NSMakeSize (DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT); _intercell = NSMakeSize(1, 1); _tabKeyTraversesCells = YES; [self setBackgroundColor: [NSColor controlBackgroundColor]]; [self setDrawsBackground: YES]; [self setCellBackgroundColor: [NSColor controlBackgroundColor]]; [self setSelectionByRect: YES]; [self setAutosizesCells: YES]; _dottedRow = _dottedColumn = -1; if (_mode == NSRadioModeMatrix && _numRows > 0 && _numCols > 0) { [self selectCellAtRow: 0 column: 0]; } else { _selectedRow = _selectedColumn = -1; } return self; } - (id) initWithFrame: (NSRect)frameRect mode: (int)aMode cellClass: (Class)class numberOfRows: (int)rowsHigh numberOfColumns: (int)colsWide { self = [super initWithFrame: frameRect]; [self setCellClass: class]; return [self _privateFrame: frameRect mode: aMode numberOfRows: rowsHigh numberOfColumns: colsWide]; } - (id) initWithFrame: (NSRect)frameRect mode: (int)aMode prototype: (NSCell*)prototype numberOfRows: (int)rowsHigh numberOfColumns: (int)colsWide { self = [super initWithFrame: frameRect]; [self setPrototype: prototype]; return [self _privateFrame: frameRect mode: aMode numberOfRows: rowsHigh numberOfColumns: colsWide]; } - (void) dealloc { int i; for (i = 0; i < _maxRows; i++) { int j; for (j = 0; j < _maxCols; j++) { [_cells[i][j] release]; } NSZoneFree(_myZone, _cells[i]); NSZoneFree(GSAtomicMallocZone(), _selectedCells[i]); } NSZoneFree(_myZone, _cells); NSZoneFree(_myZone, _selectedCells); [_cellPrototype release]; [_backgroundColor release]; [_cellBackgroundColor release]; [super dealloc]; } - (void) addColumn { [self insertColumn: _numCols withCells: nil]; } - (void) addColumnWithCells: (NSArray*)cellArray { [self insertColumn: _numCols withCells: cellArray]; } - (void) addRow { [self insertRow: _numRows withCells: nil]; } - (void) addRowWithCells: (NSArray*)cellArray { [self insertRow: _numRows withCells: cellArray]; } - (void) insertColumn: (int)column { [self insertColumn: column withCells: nil]; } - (void) insertColumn: (int)column withCells: (NSArray*)cellArray { int count = [cellArray count]; int i = _numCols + 1; if (column < 0) { column = 0; #if STRICT == 0 NSLog(@"insert negative column (%d) in matrix", column); #else [NSException raise: NSRangeException format: @"insert negative column (%d) in matrix", column]; #endif } if (column >= i) { i = column + 1; } /* * Use _renewRows:columns:rowSpace:colSpace: to grow the matrix as necessary. * MacOS-X docs say that if the matrix is empty, we make it have one column * and enough rows for all the elements. */ if (count > 0 && (_numRows == 0 || _numCols == 0)) { [self _renewRows: count columns: 1 rowSpace: 0 colSpace: count]; } else { [self _renewRows: _numRows ? _numRows : 1 columns: i rowSpace: 0 colSpace: count]; } /* * Rotate the new column to the insertion point if necessary. */ if (_numCols != column) { for (i = 0; i < _numRows; i++) { int j = _numCols; id old = _cells[i][j-1]; while (--j > column) { _cells[i][j] = _cells[i][j-1]; _selectedCells[i][j] = _selectedCells[i][j-1]; } _cells[i][column] = old; _selectedCells[i][column] = NO; } if (_selectedCell && (_selectedColumn >= column)) { _selectedColumn++; } } /* * Now put the new cells from the array into the matrix. */ if (count > 0) { IMP getImp = [cellArray methodForSelector: getSel]; for (i = 0; i < _numRows && i < count; i++) { ASSIGN(_cells[i][column], (*getImp)(cellArray, getSel, i)); } } if (_mode == NSRadioModeMatrix && _allowsEmptySelection == NO && _selectedCell == nil) [self selectCellAtRow: 0 column: 0]; } - (void) insertRow: (int)row { [self insertRow: row withCells: nil]; } - (void) insertRow: (int)row withCells: (NSArray*)cellArray { int count = [cellArray count]; int i = _numRows + 1; if (row < 0) { row = 0; #if STRICT == 0 NSLog(@"insert negative row (%d) in matrix", row); #else [NSException raise: NSRangeException format: @"insert negative row (%d) in matrix", row]; #endif } if (row >= i) { i = row + 1; } /* * Grow the matrix to have the new row. * MacOS-X docs say that if the matrix is empty, we make it have one * row and enough columns for all the elements. */ if (count > 0 && (_numRows == 0 || _numCols == 0)) { [self _renewRows: 1 columns: count rowSpace: count colSpace: 0]; } else { [self _renewRows: i columns: _numCols ? _numCols : 1 rowSpace: count colSpace: 0]; } /* * Rotate the newly created row to the insertion point if necessary. */ if (_numRows != row) { id *oldr = _cells[_numRows - 1]; BOOL *olds = _selectedCells[_numRows - 1]; for (i = _numRows - 1; i > row; i--) { _cells[i] = _cells[i-1]; _selectedCells[i] = _selectedCells[i-1]; } _cells[row] = oldr; _selectedCells[row] = olds; if (_selectedCell && (_selectedRow >= row)) _selectedRow++; if (_dottedRow != -1 && _dottedRow >= row) _dottedRow++; } /* * Put cells from the array into the matrix. */ if (count > 0) { IMP getImp = [cellArray methodForSelector: getSel]; for (i = 0; i < _numCols && i < count; i++) { ASSIGN(_cells[row][i], (*getImp)(cellArray, getSel, i)); } } if (_mode == NSRadioModeMatrix && !_allowsEmptySelection && _selectedCell == nil) { [self selectCellAtRow: 0 column: 0]; } } - (NSCell*) makeCellAtRow: (int)row column: (int)column { NSCell *aCell; if (_cellPrototype != nil) { aCell = (*_cellNew)(_cellPrototype, copySel, _myZone); } else { aCell = (*_cellNew)(_cellClass, allocSel, _myZone); if (aCell != nil) { aCell = (*_cellInit)(aCell, initSel); } } /* * This is only ever called when we are creating a new cell - so we know * we can simply assign a value into the matrix without releasing an old * value. If someone uses this method directly (which the documentation * specifically says they shouldn't) they may produce a memory leak. */ _cells[row][column] = aCell; return aCell; } - (NSRect) cellFrameAtRow: (int)row column: (int)column { NSRect rect; rect.origin.x = column * (_cellSize.width + _intercell.width); if (_rFlags.flipped_view) { rect.origin.y = row * (_cellSize.height + _intercell.height); } else { rect.origin.y = (_numRows - row - 1) * (_cellSize.height + _intercell.height); } rect.size = _cellSize; return rect; } - (void) getNumberOfRows: (int*)rowCount columns: (int*)columnCount { *rowCount = _numRows; *columnCount = _numCols; } - (void) putCell: (NSCell*)newCell atRow: (int)row column: (int)column { if (row < 0 || row >= _numRows || column < 0 || column >= _numCols) { [NSException raise: NSRangeException format: @"attempt to put cell outside matrix bounds"]; } if ((row == _selectedRow) && (column == _selectedColumn) && (_selectedCell != nil)) { _selectedCell = newCell; } ASSIGN(_cells[row][column], newCell); [self setNeedsDisplayInRect: [self cellFrameAtRow: row column: column]]; } - (void) removeColumn: (int)col { if (col >= 0 && col < _numCols) { int i; for (i = 0; i < _maxRows; i++) { int j; AUTORELEASE(_cells[i][col]); for (j = col + 1; j < _maxCols; j++) { _cells[i][j-1] = _cells[i][j]; _selectedCells[i][j-1] = _selectedCells[i][j]; } } _numCols--; _maxCols--; if (col == _selectedColumn) { _selectedCell = nil; [self selectCellAtRow: _selectedRow column: 0]; } if (col == _dottedColumn) { if (_numCols && [_cells[_dottedRow][0] acceptsFirstResponder]) _dottedColumn = 0; else _dottedRow = _dottedColumn = -1; } } else { #if STRICT == 0 NSLog(@"remove non-existent column (%d) from matrix", col); #else [NSException raise: NSRangeException format: @"remove non-existent column (%d) from matrix", col]; #endif } } - (void) removeRow: (int)row { if (row >= 0 && row < _numRows) { int i; #if GS_WITH_GC == 0 for (i = 0; i < _maxCols; i++) { [_cells[row][i] autorelease]; } #endif NSZoneFree(_myZone, _cells[row]); NSZoneFree(GSAtomicMallocZone(), _selectedCells[row]); for (i = row + 1; i < _maxRows; i++) { _cells[i-1] = _cells[i]; _selectedCells[i-1] = _selectedCells[i]; } _maxRows--; _numRows--; if (row == _selectedRow) { _selectedCell = nil; [self selectCellAtRow: 0 column: _selectedColumn]; } if (row == _dottedRow) { if (_numRows && [_cells[0][_dottedColumn] acceptsFirstResponder]) _dottedRow = 0; else _dottedRow = _dottedColumn = -1; } } else { #if STRICT == 0 NSLog(@"remove non-existent row (%d) from matrix", row); #else [NSException raise: NSRangeException format: @"remove non-existent row (%d) from matrix", row]; #endif } } - (void) renewRows: (int)r columns: (int)c { [self _renewRows: r columns: c rowSpace: 0 colSpace: 0]; } - (void) setCellSize: (NSSize)size { _cellSize = size; [self sizeToCells]; } - (void) setIntercellSpacing: (NSSize)size { _intercell = size; [self sizeToCells]; } - (void) sortUsingFunction: (int (*)(id element1, id element2, void *userData))comparator context: (void*)context { NSMutableArray *sorted; IMP add; IMP get; int i, j, index = 0; sorted = [NSMutableArray arrayWithCapacity: _numRows * _numCols]; add = [sorted methodForSelector: @selector(addObject:)]; get = [sorted methodForSelector: @selector(objectAtIndex:)]; for (i = 0; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { (*add)(sorted, @selector(addObject:), _cells[i][j]); } } [sorted sortUsingFunction: comparator context: context]; for (i = 0; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { _cells[i][j] = (*get)(sorted, @selector(objectAtIndex:), index++); } } } - (void) sortUsingSelector: (SEL)comparator { NSMutableArray *sorted; IMP add; IMP get; int i, j, index = 0; sorted = [NSMutableArray arrayWithCapacity: _numRows * _numCols]; add = [sorted methodForSelector: @selector(addObject:)]; get = [sorted methodForSelector: @selector(objectAtIndex:)]; for (i = 0; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { (*add)(sorted, @selector(addObject:), _cells[i][j]); } } [sorted sortUsingSelector: comparator]; for (i = 0; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { _cells[i][j] = (*get)(sorted, @selector(objectAtIndex:), index++); } } } - (BOOL) getRow: (int*)row column: (int*)column forPoint: (NSPoint)point { BOOL betweenRows; BOOL betweenCols; BOOL beyondRows; BOOL beyondCols; int approxRow = point.y / (_cellSize.height + _intercell.height); float approxRowsHeight = approxRow * (_cellSize.height + _intercell.height); int approxCol = point.x / (_cellSize.width + _intercell.width); float approxColsWidth = approxCol * (_cellSize.width + _intercell.width); /* First check the limit cases - is the point outside the matrix */ beyondCols = (point.x > _bounds.size.width || point.x < 0); beyondRows = (point.y > _bounds.size.height || point.y < 0); /* Determine if the point is inside a cell - note: if the point lies on the cell boundaries, we consider it inside the cell. to be outside the cell (that is, in the intercell spacing) it must be completely in the intercell spacing - not on the border */ /* The following is non zero if the point lies between rows (not inside a cell) */ betweenRows = (point.y < approxRowsHeight || point.y > approxRowsHeight + _cellSize.height); betweenCols = (point.x < approxColsWidth || point.x > approxColsWidth + _cellSize.width); if (beyondRows || betweenRows || beyondCols || betweenCols || (_numCols == 0) || (_numRows == 0)) { if (row) { *row = -1; } if (column) { *column = -1; } return NO; } if (row) { if (_rFlags.flipped_view == NO) { approxRow = _numRows - approxRow - 1; } if (approxRow < 0) { approxRow = 0; } else if (approxRow >= _numRows) { approxRow = _numRows - 1; } *row = approxRow; } if (column) { if (approxCol < 0) { approxCol = 0; } else if (approxCol >= _numCols) { approxCol = _numCols - 1; } *column = approxCol; } return YES; } - (BOOL) getRow: (int*)row column: (int*)column ofCell: (NSCell*)aCell { int i; for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { if (_cells[i][j] == aCell) { if(row) *row = i; if(column) *column = j; return YES; } } } if(row) *row = -1; if(column) *column = -1; return NO; } - (void) setState: (int)value atRow: (int)row column: (int)column { NSCell *aCell = [self cellAtRow: row column: column]; if (!aCell) return; if (_mode == NSRadioModeMatrix) { if (value) { if (_selectedRow > -1 && _selectedColumn > -1) { _selectedCells[_selectedRow][_selectedColumn] = NO; } _selectedCell = aCell; _selectedRow = row; _selectedColumn = column; if ([_cells[_dottedRow][_dottedColumn] acceptsFirstResponder]) { _dottedRow = row; _dottedColumn = column; } [_selectedCell setState: value]; _selectedCells[row][column] = YES; } else if (_allowsEmptySelection) { [self deselectSelectedCell]; } } else { [aCell setState: value]; } [self setNeedsDisplayInRect: [self cellFrameAtRow: row column: column]]; } - (void) deselectAllCells { int i; if (!_allowsEmptySelection && _mode == NSRadioModeMatrix) return; for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { if (_selectedCells[i][j]) { NSCell *aCell = _cells[i][j]; BOOL isHighlighted = [aCell isHighlighted]; _selectedCells[i][j] = NO; if ([aCell state] || isHighlighted) { [aCell setState: NSOffState]; if (isHighlighted) [self highlightCell: NO atRow: i column: j]; else [self drawCellAtRow: i column: j]; } } } } _selectedCell = nil; _selectedRow = -1; _selectedColumn = -1; } - (void) deselectSelectedCell { int i,j; if (!_selectedCell || (!_allowsEmptySelection && (_mode == NSRadioModeMatrix))) return; /* * For safety (as in macosx) */ for (i = 0; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { if (_selectedCells[i][j]) { [_cells[i][j] setState: NSOffState]; _selectedCells[i][j] = NO; } } } _selectedCell = nil; _selectedRow = -1; _selectedColumn = -1; } - (void) selectAll: (id)sender { unsigned i, j; _selectedCell = nil; _selectedRow = -1; _selectedColumn = -1; for (i = 0; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { if ([_cells[i][j] isEnabled] == YES && [_cells[i][j] isEditable] == NO) { _selectedCell = _cells[i][j]; [_selectedCell setState: NSOnState]; _selectedCells[i][j] = YES; _selectedRow = i; _selectedColumn = j; } else { _selectedCells[i][j] = NO; [_cells[i][j] setShowsFirstResponder: NO]; } } } [self setNeedsDisplay: YES]; } - (void) _selectCell: (NSCell *)aCell atRow: (int)row column: (int)column { /* * We always deselect the current selection unless the new selection * is the same. (in NSRadioModeMatrix) */ if (_mode == NSRadioModeMatrix && _selectedCell && aCell && _selectedCell != aCell) { _selectedCells[_selectedRow][_selectedColumn] = NO; [_selectedCell setState: NSOffState]; [self setNeedsDisplayInRect: [self cellFrameAtRow: _selectedRow column: _selectedColumn]]; } if (aCell) { NSRect cellFrame; if (_selectedCell && _selectedCell != aCell) { [_selectedCell setShowsFirstResponder: NO]; [self setNeedsDisplayInRect: [self cellFrameAtRow: _selectedRow column: _selectedColumn]]; } _selectedCell = aCell; _selectedRow = row; _selectedColumn = column; _selectedCells[row][column] = YES; cellFrame = [self cellFrameAtRow: row column: column]; if ([_cells[row][column] acceptsFirstResponder]) { int lastRow, lastColumn; lastRow = _dottedRow; lastColumn = _dottedColumn; _dottedRow = row; _dottedColumn = column; if (lastRow != -1 && lastColumn != -1 && (lastRow != row || lastColumn != column) && [self window] != nil) { [self drawCellAtRow: lastRow column: lastColumn]; } } [_selectedCell setState: NSOnState]; if (_mode == NSListModeMatrix) [aCell setCellAttribute: NSCellHighlighted to: 1]; if (_autoscroll) [self scrollRectToVisible: cellFrame]; // Note: we select the cell iff it is 'selectable', not 'editable' // as macosx says. This looks definitely more appropriate. // [This is going to start editing only if the cell is also editable, // otherwise the text gets selected and that's all.] [self selectTextAtRow: row column: column]; [self setNeedsDisplayInRect: cellFrame]; } else { _selectedCell = nil; _selectedRow = _selectedColumn = -1; } } - (void) selectCell: (NSCell *)aCell { int row, column; if ([self getRow: &row column: &column ofCell: aCell] == YES) [self _selectCell: aCell atRow: row column: column]; } - (void) selectCellAtRow: (int)row column: (int)column { NSCell *aCell; if ((row == -1) || (column == -1)) { [self deselectAllCells]; return; } aCell = [self cellAtRow: row column: column]; if (aCell) [self _selectCell: aCell atRow: row column: column]; } - (BOOL) selectCellWithTag: (int)anInt { id aCell; int i = _numRows; while (i-- > 0) { int j = _numCols; while (j-- > 0) { aCell = _cells[i][j]; if ([aCell tag] == anInt) { [self _selectCell: aCell atRow: i column: j]; return YES; } } } return NO; } - (NSArray*) selectedCells { NSMutableArray *array = [NSMutableArray array]; int i; for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { if (_selectedCells[i][j] == YES) { [array addObject: _cells[i][j]]; } } } return array; } - (void) setSelectionFrom: (int)startPos to: (int)endPos anchor: (int)anchorPos highlight: (BOOL)flag { BOOL doSelect = NO; BOOL doUnselect = NO; BOOL drawLast = NO; int selectx = 0; int selecty = 0; int unselectx = 0; int unselecty = 0; int dca = endPos - anchorPos; int dla = startPos - anchorPos; int dca_dla = SIGN(dca) / (SIGN(dla) ? SIGN(dla) : 1); int lastDottedRow, lastDottedColumn; MPoint end = POINT_FROM_INDEX(endPos); if (dca_dla >= 0) { if (ABS(dca) >= ABS(dla)) { doSelect = YES; selectx = MIN(startPos, endPos); selecty = MAX(startPos, endPos); } else { doUnselect = YES; drawLast = YES; if (endPos < startPos) { unselectx = endPos + 1; unselecty = startPos; } else { unselectx = startPos; unselecty = endPos - 1; } } } else { doSelect = YES; if (anchorPos < endPos) { selectx = anchorPos; selecty = endPos; } else { selectx = endPos; selecty = anchorPos; } doUnselect = YES; if (anchorPos < startPos) { unselectx = anchorPos; unselecty = startPos - 1; } else { unselectx = startPos; unselecty = anchorPos - 1; } } if (_dottedRow != -1 && _dottedColumn != -1 && (_dottedRow != end.y || _dottedColumn != end.x)) { lastDottedRow = _dottedRow; lastDottedColumn = _dottedColumn; [self drawCellAtRow: lastDottedRow column: lastDottedColumn]; } _selectedRow = _dottedRow = end.y; _selectedColumn = _dottedColumn = end.x; _selectedCells[_selectedRow][_selectedColumn] = YES; _selectedCell = _cells[_selectedRow][_selectedColumn]; if (doUnselect) { [self _setState: flag ? NSOffState : NSOnState highlight: flag ? NO : YES startIndex: unselectx endIndex: unselecty]; } if (doSelect) { [self _setState: flag ? NSOnState : NSOffState highlight: flag ? YES : NO startIndex: selectx endIndex: selecty]; } if (drawLast) [self drawCellAtRow: _dottedRow column: _dottedColumn]; } - (id) cellAtRow: (int)row column: (int)column { if (row < 0 || row >= _numRows || column < 0 || column >= _numCols) return nil; return _cells[row][column]; } - (id) cellWithTag: (int)anInt { int i = _numRows; while (i-- > 0) { int j = _numCols; while (j-- > 0) { id aCell = _cells[i][j]; if ([aCell tag] == anInt) { return aCell; } } } return nil; } - (NSArray*) cells { NSMutableArray *c; IMP add; int i; c = [NSMutableArray arrayWithCapacity: _numRows * _numCols]; add = [c methodForSelector: @selector(addObject:)]; for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { (*add)(c, @selector(addObject:), _cells[i][j]); } } return c; } - (void) selectText: (id)sender { // Attention, we are *not* doing what MacOS-X does. // But they are *not* doing what the OpenStep specification says. // This is a compromise -- and fully OpenStep compliant. NSSelectionDirection s = NSDirectSelection; if (_window) s = [_window keyViewSelectionDirection]; switch (s) { // _window selecting backwards case NSSelectingPrevious: [self _selectPreviousSelectableCellBeforeRow: _numRows column: _numCols]; break; // _Window selecting forward case NSSelectingNext: [self _selectNextSelectableCellAfterRow: -1 column: -1]; break; case NSDirectSelection: // Someone else -- we have some freedom here if ([_selectedCell isSelectable]) { [self selectTextAtRow: _selectedRow column: _selectedColumn]; } else { if (_keyCell != nil) { BOOL isValid; int row, column; isValid = [self getRow: &row column: &column ofCell: _keyCell]; if (isValid == YES) { [self selectTextAtRow: row column: column]; } } } break; } } - (id) selectTextAtRow: (int)row column: (int)column { if (row < 0 || row >= _numRows || column < 0 || column >= _numCols) return self; // macosx doesn't select the cell if it isn't 'editable'; instead, // we select the cell if and only if it is 'selectable', which looks // more appropriate. This is going to start editing if and only if // the cell is also 'editable'. if ([_cells[row][column] isSelectable] == NO) return nil; if (_textObject) { if (_selectedCell == _cells[row][column]) { [_textObject selectAll: self]; return _selectedCell; } else { [self validateEditing]; [self abortEditing]; } } // Now _textObject == nil { NSText *t = [_window fieldEditor: YES forObject: self]; int length; if ([t superview] != nil) if ([t resignFirstResponder] == NO) return nil; if (_selectedCell && _selectedCell != _cells[row][column]) { [_selectedCell setShowsFirstResponder: NO]; [self setNeedsDisplayInRect: [self cellFrameAtRow: _selectedRow column: _selectedColumn]]; } _selectedCell = _cells[row][column]; _selectedRow = row; _selectedColumn = column; if ([_cells[row][column] acceptsFirstResponder]) { _dottedRow = row; _dottedColumn = column; } /* See comment in NSTextField */ length = [[_selectedCell stringValue] length]; _textObject = [_selectedCell setUpFieldEditorAttributes: t]; [_selectedCell selectWithFrame: [self cellFrameAtRow: _selectedRow column: _selectedColumn] inView: self editor: _textObject delegate: self start: 0 length: length]; return _selectedCell; } } - (id) keyCell { return _keyCell; } - (void) setKeyCell: (NSCell *)aCell { BOOL isValid; int row, column; isValid = [self getRow: &row column: &column ofCell: aCell]; if (isValid == YES) { ASSIGN (_keyCell, aCell); } } - (id) nextText { return _nextKeyView; } - (id) previousText { return _previousKeyView; } - (void) textDidBeginEditing: (NSNotification *)aNotification { NSMutableDictionary *d; d = [[NSMutableDictionary alloc] initWithDictionary: [aNotification userInfo]]; AUTORELEASE (d); [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; [nc postNotificationName: NSControlTextDidBeginEditingNotification object: self userInfo: d]; } - (void) textDidChange: (NSNotification *)aNotification { NSMutableDictionary *d; NSFormatter *formatter; // MacOS-X asks us to inform the cell if possible. if ((_selectedCell != nil) && [_selectedCell respondsToSelector: @selector(textDidChange:)]) [_selectedCell textDidChange: aNotification]; d = [[NSMutableDictionary alloc] initWithDictionary: [aNotification userInfo]]; AUTORELEASE (d); [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; [nc postNotificationName: NSControlTextDidChangeNotification object: self userInfo: d]; formatter = [_cell formatter]; if (formatter != nil) { /* * FIXME: This part needs heavy interaction with the yet to finish * text system. * */ NSString *partialString; NSString *newString = nil; NSString *error = nil; BOOL wasAccepted; partialString = [_textObject string]; wasAccepted = [formatter isPartialStringValid: partialString newEditingString: &newString errorDescription: &error]; if (wasAccepted == NO) { [_delegate control:self didFailToValidatePartialString: partialString errorDescription: error]; } if (newString != nil) { NSLog (@"Unimplemented: should set string to %@", newString); // FIXME ! This would reset editing ! //[_textObject setString: newString]; } else { if (wasAccepted == NO) { // FIXME: Need to delete last typed character (?!) NSLog (@"Unimplemented: should delete last typed character"); } } } } - (void) textDidEndEditing: (NSNotification *)aNotification { NSMutableDictionary *d; id textMovement; [self validateEditing]; d = [[NSMutableDictionary alloc] initWithDictionary: [aNotification userInfo]]; AUTORELEASE (d); [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; [nc postNotificationName: NSControlTextDidEndEditingNotification object: self userInfo: d]; [_selectedCell endEditing: [aNotification object]]; _textObject = nil; textMovement = [[aNotification userInfo] objectForKey: @"NSTextMovement"]; if (textMovement) { switch ([(NSNumber *)textMovement intValue]) { case NSReturnTextMovement: if ([self sendAction] == NO) { NSEvent *event = [_window currentEvent]; if ([self performKeyEquivalent: event] == NO && [_window performKeyEquivalent: event] == NO) [self selectText: self]; } break; case NSTabTextMovement: if (_tabKeyTraversesCells) { if([self _selectNextSelectableCellAfterRow: _selectedRow column: _selectedColumn]) break; } [_window selectKeyViewFollowingView: self]; if ([_window firstResponder] == _window) { if (_tabKeyTraversesCells) { if([self _selectNextSelectableCellAfterRow: -1 column: -1]) break; } [self selectText: self]; } break; case NSBacktabTextMovement: if (_tabKeyTraversesCells) { if([self _selectPreviousSelectableCellBeforeRow: _selectedRow column: _selectedColumn]) break; } [_window selectKeyViewPrecedingView: self]; if ([_window firstResponder] == _window) { if (_tabKeyTraversesCells) { if([self _selectPreviousSelectableCellBeforeRow: _numRows column: _numCols]) break; } [self selectText: self]; } break; } } } - (BOOL) textShouldBeginEditing: (NSText *)textObject { if (_delegate && [_delegate respondsToSelector: @selector(control:textShouldBeginEditing:)]) return [_delegate control: self textShouldBeginEditing: textObject]; else return YES; } - (BOOL) textShouldEndEditing: (NSText *)aTextObject { if ([_selectedCell isEntryAcceptable: [aTextObject text]] == NO) { [self sendAction: _errorAction to: _target]; return NO; } if ([_delegate respondsToSelector: @selector(control:textShouldEndEditing:)]) { if ([_delegate control: self textShouldEndEditing: aTextObject] == NO) { NSBeep (); return NO; } } if ([_delegate respondsToSelector: @selector(control:isValidObject:)] == YES) { NSFormatter *formatter; id newObjectValue; formatter = [_cell formatter]; if ([formatter getObjectValue: &newObjectValue forString: [_textObject text] errorDescription: NULL] == YES) { if ([_delegate control: self isValidObject: newObjectValue] == NO) return NO; } } // In all other cases return YES; } - (BOOL) tabKeyTraversesCells { return _tabKeyTraversesCells; } - (void) setTabKeyTraversesCells: (BOOL)flag { _tabKeyTraversesCells = flag; } - (void) setNextText: (id)anObject { [self setNextKeyView: anObject]; } - (void) setPreviousText: (id)anObject { [self setPreviousKeyView: anObject]; } - (void) setValidateSize: (BOOL)flag { // TODO } - (void) sizeToCells { NSSize newSize; int nc = _numCols; int nr = _numRows; if (!nc) nc = 1; if (!nr) nr = 1; newSize.width = nc * (_cellSize.width + _intercell.width) - _intercell.width; newSize.height = nr * (_cellSize.height + _intercell.height) - _intercell.height; [self setFrameSize: newSize]; } - (void) sizeToFit { /* * A simple explanation of the logic behind this method. * * Example of when you would like to use this method: * you have a matrix containing radio buttons. Say that you have the * following radio buttons - * * * First option * * Second option * * Third option * * No thanks, no option for me * * this method should size the matrix so that it can comfortably * show all the cells it contains. To do it, we must consider that * all the cells should be given the same size, yet some cells need * more space than the others to show their contents, so we need to * choose the cell size as to be enough to display every cell. We * loop on all cells, call cellSize on each (which returns the * *minimum* comfortable size to display that cell), and choose a * final cellSize which is enough big to be bigger than all these * cellSizes. We resize the matrix to have that cellSize, and * that's it. */ NSSize newSize = NSZeroSize; int i, j; for (i = 0; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { NSSize tempSize = [_cells[i][j] cellSize]; tempSize.height = ceil(tempSize.height); tempSize.width = ceil(tempSize.width); if (tempSize.width > newSize.width) { newSize.width = tempSize.width; } if (tempSize.height > newSize.height) { newSize.height = tempSize.height; } } } [self setCellSize: newSize]; } - (void) scrollCellToVisibleAtRow: (int)row column: (int)column { [self scrollRectToVisible: [self cellFrameAtRow: row column: column]]; } - (void) setAutoscroll: (BOOL)flag { _autoscroll = flag; } - (void) setScrollable: (BOOL)flag { int i; for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { [_cells[i][j] setScrollable: flag]; } } [_cellPrototype setScrollable: flag]; } - (void) drawRect: (NSRect)rect { int i, j; int row1, col1; // The cell at the upper left corner int row2, col2; // The cell at the lower right corner if (_drawsBackground) { [_backgroundColor set]; NSRectFill(rect); } if (!_numRows || !_numCols) return; row1 = rect.origin.y / (_cellSize.height + _intercell.height); col1 = rect.origin.x / (_cellSize.width + _intercell.width); row2 = NSMaxY(rect) / (_cellSize.height + _intercell.height); col2 = NSMaxX(rect) / (_cellSize.width + _intercell.width); if (_rFlags.flipped_view == NO) { row1 = _numRows - row1 - 1; row2 = _numRows - row2 - 1; } if (row1 < 0) row1 = 0; else if (row1 >= _numRows) row1 = _numRows - 1; if (col1 < 0) col1 = 0; else if (col1 >= _numCols) col1 = _numCols - 1; if (row2 < 0) row2 = 0; else if (row1 >= _numRows) row2 = _numRows - 1; if (col2 < 0) col2 = 0; else if (col2 >= _numCols) col2 = _numCols - 1; /* Draw the cells within the drawing rectangle. */ for (i = row1; i <= row2 && i < _numRows; i++) for (j = col1; j <= col2 && j < _numCols; j++) { [self drawCellAtRow: i column: j]; } } - (BOOL) isOpaque { return _drawsBackground; } - (void) drawCell: (NSCell *)aCell { int row, column; if ([self getRow: &row column: &column ofCell: aCell] == YES) { [self drawCellAtRow: row column: column]; } } - (void) drawCellAtRow: (int)row column: (int)column { NSCell *aCell = [self cellAtRow: row column: column]; if (aCell) { NSRect cellFrame = [self cellFrameAtRow: row column: column]; if (_drawsCellBackground) { [self lockFocus]; [_cellBackgroundColor set]; NSRectFill(cellFrame); [self unlockFocus]; } if (_dottedRow == row && _dottedColumn == column && [aCell acceptsFirstResponder]) { [aCell setShowsFirstResponder: ([_window isKeyWindow] && [_window firstResponder] == self)]; } else [aCell setShowsFirstResponder: NO]; [aCell drawWithFrame: cellFrame inView: self]; [aCell setShowsFirstResponder: NO]; } } - (void) highlightCell: (BOOL)flag atRow: (int)row column: (int)column { NSCell *aCell = [self cellAtRow: row column: column]; if (aCell) { NSRect cellFrame = [self cellFrameAtRow: row column: column]; if (_drawsCellBackground) { [self lockFocus]; [_cellBackgroundColor set]; NSRectFill(cellFrame); [self unlockFocus]; } if (_dottedRow != -1 && _dottedColumn != -1 && _cells[_dottedRow][_dottedColumn] == aCell && [_cells[_dottedRow][_dottedColumn] acceptsFirstResponder]) { [_cells[_dottedRow][_dottedColumn] setShowsFirstResponder: ([_window isKeyWindow] && [_window firstResponder] == self)]; } [aCell highlight: flag withFrame: cellFrame inView: self]; if (_dottedRow != -1 && _dottedColumn != -1) [_cells[_dottedRow][_dottedColumn] setShowsFirstResponder: NO]; } } - (BOOL) sendAction { if (_selectedCell) { if ([_selectedCell isEnabled] == NO) return NO; return [self sendAction: [_selectedCell action] to: [_selectedCell target]]; } // _selectedCell == nil return [super sendAction: _action to: _target]; } - (BOOL) sendAction: (SEL)theAction to: (id)theTarget { if (theAction) { if (theTarget) { return [super sendAction: theAction to: theTarget]; } else { return [super sendAction: theAction to: _target]; } } else { return [super sendAction: _action to: _target]; } } - (void) sendAction: (SEL)aSelector to: (id)anObject forAllCells: (BOOL)flag { int i; if (flag) { for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { if (![anObject performSelector: aSelector withObject: _cells[i][j]]) { return; } } } } else { for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { if (_selectedCells[i][j]) { if (![anObject performSelector: aSelector withObject: _cells[i][j]]) { return; } } } } } } - (void) sendDoubleAction { if ([_selectedCell isEnabled] == NO) return; if (_doubleAction) [self sendAction: _doubleAction to: _target]; else [self sendAction]; } - (BOOL) acceptsFirstMouse: (NSEvent*)theEvent { if (_mode == NSListModeMatrix) return NO; else return YES; } - (void) _mouseDownNonListMode: (NSEvent *)theEvent { BOOL mouseUpInCell = NO, onCell, scrolling = NO; NSCell *highlightedCell = nil; int highlightedRow = 0; int highlightedColumn = 0; NSCell *mouseCell; int mouseRow; int mouseColumn; NSPoint mouseLocation; NSRect mouseCellFrame; NSEvent *currentEvent = nil; unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDownMask | NSMouseMovedMask | NSLeftMouseDraggedMask; while (!mouseUpInCell && ([theEvent type] != NSLeftMouseUp)) { mouseLocation = [self convertPoint: [theEvent locationInWindow] fromView: nil]; onCell = [self getRow: &mouseRow column: &mouseColumn forPoint: mouseLocation]; if (!onCell) scrolling = NO; if (onCell) { mouseCellFrame = [self cellFrameAtRow: mouseRow column: mouseColumn]; mouseCell = [self cellAtRow: mouseRow column: mouseColumn]; if (_autoscroll) scrolling = [self scrollRectToVisible: mouseCellFrame]; if ([mouseCell isEnabled]) { if ([mouseCell acceptsFirstResponder]) { NSCell *aCell = [self cellAtRow: _dottedRow column: _dottedColumn]; _dottedRow = mouseRow; _dottedColumn = mouseColumn; if (aCell && aCell != mouseCell) [self drawCell: aCell]; } if (_selectedCell) { if (_mode == NSRadioModeMatrix) { [_selectedCell setState: NSOffState]; [self drawCell: _selectedCell]; } _selectedCells[_selectedRow][_selectedColumn] = NO; } _selectedCell = mouseCell; _selectedRow = mouseRow; _selectedColumn = mouseColumn; _selectedCells[_selectedRow][_selectedColumn] = YES; if (_mode != NSTrackModeMatrix && highlightedCell != mouseCell) { highlightedCell = mouseCell; highlightedRow = mouseRow; highlightedColumn = mouseColumn; [self highlightCell: YES atRow: highlightedRow column: highlightedColumn]; [_window flushWindow]; } if (_mode == NSRadioModeMatrix) [mouseCell setState: NSOffState]; mouseUpInCell = [mouseCell trackMouse: theEvent inRect: mouseCellFrame ofView: self untilMouseUp: [[mouseCell class] prefersTrackingUntilMouseUp]]; if (_mode != NSTrackModeMatrix) { if(_mode == NSRadioModeMatrix) { if (!mouseUpInCell) [mouseCell setState: NSOnState]; } highlightedCell = nil; [self highlightCell: NO atRow: highlightedRow column: highlightedColumn]; [_window flushWindow]; } if (!mouseUpInCell) { ASSIGN(currentEvent, [NSApp currentEvent]); if (currentEvent && currentEvent != theEvent && [currentEvent type] != NSPeriodic) { if (!scrolling) { theEvent = currentEvent; continue; } } else DESTROY(currentEvent); } } } else if (_mode != NSRadioModeMatrix) { // mouse is not over a Cell if (_selectedCell != nil) { if ([_selectedCell state]) { [_selectedCell setState: NSOffState]; [self drawCell: _selectedCell]; } _selectedCells[_selectedRow][_selectedColumn] = NO; _selectedCell = nil; _selectedRow = _selectedColumn = -1; } } // if mouse didn't go up, take next event if (!mouseUpInCell) { theEvent = [NSApp nextEventMatchingMask: eventMask untilDate: !scrolling || !currentEvent ? [NSDate distantFuture] : [NSDate dateWithTimeIntervalSinceNow: 0.05] inMode: NSEventTrackingRunLoopMode dequeue: YES]; if (scrolling && !theEvent) theEvent = currentEvent; else DESTROY(currentEvent); } } RELEASE(currentEvent); // the mouse went up. // if it was inside a cell, the cell has already sent the action. // if not, _selectedCell is the last cell that had the mouse, and // it's state is Off. It must be set into a consistent state. // anyway, the action has to be sent if (!mouseUpInCell) { if (_mode != NSRadioModeMatrix) { if (_selectedCell != nil) { _selectedCells[_selectedRow][_selectedColumn] = NO; _selectedCell = nil; _selectedRow = _selectedColumn = -1; } } [self sendAction]; } } - (void) mouseDown: (NSEvent*)theEvent { BOOL onCell; int row, column; unsigned eventMask = NSLeftMouseUpMask | NSLeftMouseDownMask | NSMouseMovedMask | NSLeftMouseDraggedMask | NSPeriodicMask; NSPoint lastLocation = [theEvent locationInWindow]; NSEvent* lastEvent = nil; BOOL done = NO; NSRect rect; id aCell, previousCell = nil; NSRect previousRect; NSApplication *app = [NSApplication sharedApplication]; static MPoint anchor = {0, 0}; int clickCount; /* * Pathological case -- ignore mouse down */ if ((_numRows == 0) || (_numCols == 0)) { [super mouseDown: theEvent]; return; } // Manage multi-click events clickCount = [theEvent clickCount]; if (clickCount > 2) return; if (clickCount == 2 && (_ignoresMultiClick == NO)) { [self sendDoubleAction]; return; } // From now on, code to manage simple-click events lastLocation = [self convertPoint: lastLocation fromView: nil]; // If mouse down was on a selectable cell, start editing/selecting. if ([self getRow: &row column: &column forPoint: lastLocation]) { if ([_cells[row][column] isEnabled]) { if ((_mode == NSRadioModeMatrix) && _selectedCell != nil) { [_selectedCell setState: NSOffState]; [self drawCellAtRow: _selectedRow column: _selectedColumn]; [_window flushWindow]; _selectedCells[_selectedRow][_selectedColumn] = NO; _selectedCell = nil; _selectedRow = _selectedColumn = -1; } if ([_cells[row][column] isSelectable]) { NSText* t = [_window fieldEditor: YES forObject: self]; if ([t superview] != nil) { if ([t resignFirstResponder] == NO) { if ([_window makeFirstResponder: _window] == NO) return; } } // During editing, the selected cell is the cell being edited _selectedCell = _cells[row][column]; _selectedRow = row; _selectedColumn = column; if ([_cells[row][column] acceptsFirstResponder]) { _dottedRow = row; _dottedColumn = column; } _textObject = [_selectedCell setUpFieldEditorAttributes: t]; [_selectedCell editWithFrame: [self cellFrameAtRow: row column: column] inView: self editor: _textObject delegate: self event: theEvent]; return; } } } // Paranoia check -- _textObject should already be nil, since we // accept first responder, so NSWindow should have already given // us first responder status (thus already ending editing with _textObject). if (_textObject) { NSLog (@"Hi, I am a bug."); [self validateEditing]; [self abortEditing]; } mouseDownFlags = [theEvent modifierFlags]; if (_mode != NSListModeMatrix) { [self _mouseDownNonListMode: theEvent]; return; } [NSEvent startPeriodicEventsAfterDelay: 0.05 withPeriod: 0.05]; ASSIGN(lastEvent, theEvent); // selection involves two steps, first // a loop that continues until the left mouse goes up; then a series of // steps which send actions and display the cell as it should appear after // the selection process is complete while (!done) { BOOL shouldProceedEvent = NO; onCell = [self getRow: &row column: &column forPoint: lastLocation]; if (onCell) { aCell = [self cellAtRow: row column: column]; rect = [self cellFrameAtRow: row column: column]; if (_autoscroll) [self scrollRectToVisible: rect]; if (aCell != previousCell && [aCell isEnabled] == YES) { unsigned modifiers = [lastEvent modifierFlags]; // List mode allows multiple cells to be selected // When the user first clicks on a cell // we clear the existing selection // unless the Alternate or Shift keys have been pressed. if (!previousCell) { if (!(modifiers & NSShiftKeyMask) && !(modifiers & NSAlternateKeyMask)) { [self deselectAllCells]; anchor = MakePoint (column, row); } // Consider the selected cell as the // anchor from which to extend the // selection to the current cell if (!(modifiers & NSAlternateKeyMask)) [self _selectCell: aCell atRow: row column: column]; } else { [self setSelectionFrom: INDEX_FROM_COORDS(_selectedColumn, _selectedRow) to: INDEX_FROM_COORDS(column, row) anchor: INDEX_FROM_POINT(anchor) highlight: YES]; } [_window flushWindow]; previousCell = aCell; previousRect = rect; } } // if done break out of selection loop if (done) break; while (!shouldProceedEvent) { theEvent = [app nextEventMatchingMask: eventMask untilDate: [NSDate distantFuture] inMode: NSEventTrackingRunLoopMode dequeue: YES]; switch ([theEvent type]) { case NSPeriodic: NSDebugLog(@"NSMatrix: got NSPeriodic event\n"); shouldProceedEvent = YES; break; case NSLeftMouseUp: done = YES; case NSLeftMouseDown: default: shouldProceedEvent = YES; NSDebugLog(@"NSMatrix: got event of type: %d\n", [theEvent type]); ASSIGN(lastEvent, theEvent); continue; } } lastLocation = [lastEvent locationInWindow]; lastLocation = [self convertPoint: lastLocation fromView: nil]; } [self setNeedsDisplayInRect: rect]; [_window flushWindow]; [NSEvent stopPeriodicEvents]; [self sendAction]; RELEASE(lastEvent); } - (void) updateCell: (NSCell*)aCell { int row, col; NSRect rect; if ([self getRow: &row column: &col ofCell: aCell] == NO) return; // Not a cell in this matrix - we can't update it. rect = [self cellFrameAtRow: row column: col]; [self setNeedsDisplayInRect: rect]; } - (BOOL) performKeyEquivalent: (NSEvent*)theEvent { NSString *key = [theEvent charactersIgnoringModifiers]; int i; for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { NSCell *aCell = _cells[i][j];; if ([aCell isEnabled] && [[aCell keyEquivalent] isEqualToString: key]) { NSCell *oldSelectedCell = _selectedCell; int oldSelectedRow = _selectedRow; int oldSelectedColumn = _selectedColumn; _selectedCell = aCell; [self highlightCell: YES atRow: i column: j]; [aCell setNextState]; [self sendAction]; [self highlightCell: NO atRow: i column: j]; _selectedCell = oldSelectedCell; _selectedRow = oldSelectedRow; _selectedColumn = oldSelectedColumn; return YES; } } } return NO; } - (void) resetCursorRects { int i; for (i = 0; i < _numRows; i++) { int j; for (j = 0; j < _numCols; j++) { NSCell *aCell = _cells[i][j]; [aCell resetCursorRect: [self cellFrameAtRow: i column: j] inView: self]; } } } - (void) encodeWithCoder: (NSCoder*)aCoder { [super encodeWithCoder: aCoder]; [aCoder encodeValueOfObjCType: @encode (int) at: &_mode]; [aCoder encodeValueOfObjCType: @encode (BOOL) at: &_allowsEmptySelection]; [aCoder encodeValueOfObjCType: @encode (BOOL) at: &_selectionByRect]; [aCoder encodeValueOfObjCType: @encode (BOOL) at: &_autosizesCells]; [aCoder encodeValueOfObjCType: @encode (BOOL) at: &_autoscroll]; [aCoder encodeSize: _cellSize]; [aCoder encodeSize: _intercell]; [aCoder encodeObject: _backgroundColor]; [aCoder encodeObject: _cellBackgroundColor]; [aCoder encodeValueOfObjCType: @encode (BOOL) at: &_drawsBackground]; [aCoder encodeValueOfObjCType: @encode (BOOL) at: &_drawsCellBackground]; [aCoder encodeObject: NSStringFromClass (_cellClass)]; [aCoder encodeObject: _cellPrototype]; [aCoder encodeValueOfObjCType: @encode (int) at: &_numRows]; [aCoder encodeValueOfObjCType: @encode (int) at: &_numCols]; /* This is slower, but does not expose NSMatrix internals and will work with subclasses */ [aCoder encodeObject: [self cells]]; [aCoder encodeConditionalObject: _delegate]; [aCoder encodeConditionalObject: _target]; [aCoder encodeValueOfObjCType: @encode (SEL) at: &_action]; [aCoder encodeValueOfObjCType: @encode (SEL) at: &_doubleAction]; [aCoder encodeValueOfObjCType: @encode (SEL) at: &_errorAction]; [aCoder encodeValueOfObjCType: @encode (BOOL) at: &_tabKeyTraversesCells]; [aCoder encodeObject: _keyCell]; /* We do not encode information on selected cells, because this is saved with the cells themselves */ } - (id) initWithCoder: (NSCoder*)aDecoder { Class class; id cell; int rows, columns; NSArray *array; int i, count; [super initWithCoder: aDecoder]; _myZone = [self zone]; [aDecoder decodeValueOfObjCType: @encode (int) at: &_mode]; [aDecoder decodeValueOfObjCType: @encode (BOOL) at: &_allowsEmptySelection]; [aDecoder decodeValueOfObjCType: @encode (BOOL) at: &_selectionByRect]; [aDecoder decodeValueOfObjCType: @encode (BOOL) at: &_autosizesCells]; [aDecoder decodeValueOfObjCType: @encode (BOOL) at: &_autoscroll]; _cellSize = [aDecoder decodeSize]; _intercell = [aDecoder decodeSize]; [aDecoder decodeValueOfObjCType: @encode (id) at: &_backgroundColor]; [aDecoder decodeValueOfObjCType: @encode (id) at: &_cellBackgroundColor]; [aDecoder decodeValueOfObjCType: @encode (BOOL) at: &_drawsBackground]; [aDecoder decodeValueOfObjCType: @encode (BOOL) at: &_drawsCellBackground]; class = NSClassFromString ((NSString *)[aDecoder decodeObject]); if (class != Nil) { [self setCellClass: class]; } cell = [aDecoder decodeObject]; if (cell != nil) { [self setPrototype: cell]; } if (_cellPrototype == nil) { [self setCellClass: [isa cellClass]]; } [aDecoder decodeValueOfObjCType: @encode (int) at: &rows]; [aDecoder decodeValueOfObjCType: @encode (int) at: &columns]; /* NB: This works without changes for NSForm */ array = [aDecoder decodeObject]; [self renewRows: rows columns: columns]; count = [array count]; if (count != rows * columns) { NSLog (@"Trying to decode an invalid NSMatrix: cell number does not fit matrix dimension"); // Quick fix to do what we can if (count > rows * columns) { count = rows * columns; } } _selectedRow = _selectedColumn = 0; for (i = 0; i < count; i++) { int row, column; cell = [array objectAtIndex: i]; row = i / columns; column = i % columns; [self putCell:cell atRow: row column: column]; if ([cell state]) { [self selectCellAtRow: row column: column]; } } [aDecoder decodeValueOfObjCType: @encode (id) at: &_delegate]; [aDecoder decodeValueOfObjCType: @encode (id) at: &_target]; [aDecoder decodeValueOfObjCType: @encode (SEL) at: &_action]; [aDecoder decodeValueOfObjCType: @encode (SEL) at: &_doubleAction]; [aDecoder decodeValueOfObjCType: @encode (SEL) at: &_errorAction]; [aDecoder decodeValueOfObjCType: @encode (BOOL) at: &_tabKeyTraversesCells]; [self setKeyCell: [aDecoder decodeObject]]; return self; } - (void) setMode: (NSMatrixMode)aMode { _mode = aMode; } - (NSMatrixMode) mode { return _mode; } - (void) setCellClass: (Class)class { _cellClass = class; if (_cellClass == nil) { _cellClass = defaultCellClass; } _cellNew = [_cellClass methodForSelector: allocSel]; _cellInit = [_cellClass instanceMethodForSelector: initSel]; DESTROY(_cellPrototype); } - (Class) cellClass { return _cellClass; } - (void) setPrototype: (NSCell*)aCell { ASSIGN(_cellPrototype, aCell); if (_cellPrototype == nil) { [self setCellClass: defaultCellClass]; } else { _cellNew = [_cellPrototype methodForSelector: copySel]; _cellInit = 0; _cellClass = [aCell class]; } } - (id) prototype { return _cellPrototype; } - (NSSize) cellSize { return _cellSize; } - (NSSize) intercellSpacing { return _intercell; } - (void) setBackgroundColor: (NSColor*)c { ASSIGN(_backgroundColor, c); } - (NSColor*) backgroundColor { return _backgroundColor; } - (void) setCellBackgroundColor: (NSColor*)c { ASSIGN(_cellBackgroundColor, c); } - (NSColor*) cellBackgroundColor { return _cellBackgroundColor; } - (void) setDelegate: (id)object { if (_delegate) [nc removeObserver: _delegate name: nil object: self]; _delegate = object; #define SET_DELEGATE_NOTIFICATION(notif_name) \ if ([_delegate respondsToSelector: @selector(controlText##notif_name:)]) \ [nc addObserver: _delegate \ selector: @selector(controlText##notif_name:) \ name: NSControlText##notif_name##Notification object: self] if (_delegate) { SET_DELEGATE_NOTIFICATION(DidBeginEditing); SET_DELEGATE_NOTIFICATION(DidEndEditing); SET_DELEGATE_NOTIFICATION(DidChange); } } - (id) delegate { return _delegate; } - (void) setTarget: anObject { _target = anObject; } - (id) target { return _target; } - (void) setAction: (SEL)sel { _action = sel; } - (SEL) action { return _action; } // NB: In GNUstep the following method does *not* set // ignoresMultiClick to NO as in the MacOS-X spec. // It simply sets the doubleAction, as in OpenStep spec. - (void) setDoubleAction: (SEL)sel { _doubleAction = sel; } - (SEL) doubleAction { return _doubleAction; } - (void) setErrorAction: (SEL)sel { _errorAction = sel; } - (SEL) errorAction { return _errorAction; } - (void) setAllowsEmptySelection: (BOOL)f { _allowsEmptySelection = f; } - (BOOL) allowsEmptySelection { return _allowsEmptySelection; } - (void) setSelectionByRect: (BOOL)flag { _selectionByRect = flag; } - (BOOL) isSelectionByRect { return _selectionByRect; } - (void) setDrawsBackground: (BOOL)flag { _drawsBackground = flag; } - (BOOL) drawsBackground { return _drawsBackground; } - (void) setDrawsCellBackground: (BOOL)f { _drawsCellBackground = f; } - (BOOL) drawsCellBackground { return _drawsCellBackground; } - (void) setAutosizesCells: (BOOL)flag { _autosizesCells = flag; } - (BOOL) autosizesCells { return _autosizesCells; } - (BOOL) isAutoscroll { return _autoscroll; } - (int) numberOfRows { return _numRows; } - (int) numberOfColumns { return _numCols; } - (id) selectedCell { return _selectedCell; } - (int) selectedColumn { return _selectedColumn; } - (int) selectedRow { return _selectedRow; } - (int) mouseDownFlags { return mouseDownFlags; } - (BOOL) isFlipped { return YES; } - (void) resizeWithOldSuperviewSize: (NSSize)oldSize { NSSize oldBoundsSize = _bounds.size; NSSize newBoundsSize; NSSize change; int nc = _numCols; int nr = _numRows; [super resizeWithOldSuperviewSize: oldSize]; newBoundsSize = _bounds.size; change.height = newBoundsSize.height - oldBoundsSize.height; change.width = newBoundsSize.width - oldBoundsSize.width; if (_autosizesCells) { if (change.height != 0) { if (nr <= 0) nr = 1; if (_cellSize.height == 0) { _cellSize.height = oldBoundsSize.height - ((nr - 1) * _intercell.height); _cellSize.height = _cellSize.height / nr; } change.height = change.height / nr; _cellSize.height += change.height; _cellSize.height = rint(_cellSize.height); if (_cellSize.height < 0) _cellSize.height = 0; } if (change.width != 0) { if (nc <= 0) nc = 1; if (_cellSize.width == 0) { _cellSize.width = oldBoundsSize.width - ((nc - 1) * _intercell.width); _cellSize.width = _cellSize.width / nc; } change.width = change.width / nc; _cellSize.width += change.width; _cellSize.width = rint(_cellSize.width); if (_cellSize.width < 0) _cellSize.width = 0; } } else // !autosizesCells { if (change.height != 0) { if (nr > 1) { if (_intercell.height == 0) { _intercell.height = oldBoundsSize.height - (nr * _cellSize.height); _intercell.height = _intercell.height / (nr - 1); } change.height = change.height / (nr - 1); _intercell.height += change.height; if (_intercell.height < 0) _intercell.height = 0; } } if (change.width != 0) { if (nc > 1) { if (_intercell.width == 0) { _intercell.width = oldBoundsSize.width - (nc * _cellSize.width); _intercell.width = _intercell.width / (nc - 1); } change.width = change.width / (nc - 1); _intercell.width += change.width; if (_intercell.width < 0) _intercell.width = 0; } } } [self setNeedsDisplay: YES]; } - (void)_move:(unichar)pos { BOOL selectCell = NO; int h, i, lastDottedRow, lastDottedColumn; if (_mode == NSRadioModeMatrix || _mode == NSListModeMatrix) selectCell = YES; if (_dottedRow == -1 || _dottedColumn == -1) { if (pos == NSUpArrowFunctionKey || pos == NSDownArrowFunctionKey) { for (h = 0; h < _numCols; h++) { for (i = 0; i < _numRows; i++) { if ([_cells[i][h] acceptsFirstResponder]) { _dottedRow = i; _dottedColumn = h; break; } } if (i == _dottedRow) break; } } else { for (i = 0; i < _numRows; i++) { for (h = 0; h < _numCols; h++) { if ([_cells[i][h] acceptsFirstResponder]) { _dottedRow = i; _dottedColumn = h; break; } } if (h == _dottedColumn) break; } } if (_dottedRow == -1 || _dottedColumn == -1) return; if (selectCell) { if (_selectedCell) { if (_mode == NSRadioModeMatrix) { NSCell *aCell = _selectedCell; [aCell setState: NSOffState]; _selectedCells[_selectedRow][_selectedColumn] = NO; _selectedRow = _selectedColumn = -1; _selectedCell = nil; [self drawCell: aCell]; } else [self deselectAllCells]; } [self selectCellAtRow: _dottedRow column: _dottedColumn]; } else [self drawCellAtRow: _dottedRow column: _dottedColumn]; } else { lastDottedRow = _dottedRow; lastDottedColumn = _dottedColumn; if (pos == NSUpArrowFunctionKey) { if (_dottedRow <= 0) return; for (i = _dottedRow-1; i >= 0; i--) { if ([_cells[i][_dottedColumn] acceptsFirstResponder]) { _dottedRow = i; break; } } } else if (pos == NSDownArrowFunctionKey) { if (_dottedRow >= _numRows-1) return; for (i = _dottedRow+1; i < _numRows; i++) { if ([_cells[i][_dottedColumn] acceptsFirstResponder]) { _dottedRow = i; break; } } } else if (pos == NSLeftArrowFunctionKey) { if (_dottedColumn <= 0) return; for (i = _dottedColumn-1; i >= 0; i--) { if ([_cells[_dottedRow][i] acceptsFirstResponder]) { _dottedColumn = i; break; } } } else { if (_dottedColumn >= _numCols-1) return; for (i = _dottedColumn+1; i < _numCols; i++) { if ([_cells[_dottedRow][i] acceptsFirstResponder]) { _dottedColumn = i; break; } } } if ((pos == NSUpArrowFunctionKey || pos == NSDownArrowFunctionKey) && _dottedRow != i) return; if ((pos == NSLeftArrowFunctionKey || pos == NSRightArrowFunctionKey) && _dottedColumn != i) return; if (selectCell) { if (_mode == NSRadioModeMatrix) { NSCell *aCell = _cells[lastDottedRow][lastDottedColumn]; BOOL isHighlighted = [aCell isHighlighted]; if ([aCell state] || isHighlighted) { [aCell setState: NSOffState]; _selectedCells[lastDottedRow][lastDottedColumn] = NO; _selectedRow = _selectedColumn = -1; _selectedCell = nil; if (isHighlighted) [self highlightCell: NO atRow: lastDottedRow column: lastDottedColumn]; else [self drawCell: aCell]; } } else [self deselectAllCells]; [self selectCellAtRow: _dottedRow column: _dottedColumn]; } else { [self drawCell: _cells[lastDottedRow][lastDottedColumn]]; [self drawCell: _cells[_dottedRow][_dottedColumn]]; } } [_window flushWindow]; if (selectCell) { [self performClick: self]; [_window flushWindow]; } } - (void)moveUp:(id)sender { [self _move: NSUpArrowFunctionKey]; } - (void)moveDown:(id)sender { [self _move: NSDownArrowFunctionKey]; } - (void)moveLeft:(id)sender { [self _move: NSLeftArrowFunctionKey]; } - (void)moveRight:(id)sender { [self _move: NSRightArrowFunctionKey]; } - (void) _shiftModifier:(unichar)character { int i, lastDottedRow, lastDottedColumn; lastDottedRow = _dottedRow; lastDottedColumn = _dottedColumn; if (character == NSUpArrowFunctionKey) { if (_dottedRow <= 0) return; for (i = _dottedRow-1; i >= 0; i--) { if ([_cells[i][_dottedColumn] acceptsFirstResponder]) { _dottedRow = i; break; } } if (_dottedRow != i) return; } else if (character == NSDownArrowFunctionKey) { if (_dottedRow < 0 || _dottedRow >= _numRows-1) return; for (i = _dottedRow+1; i < _numRows; i++) { if ([_cells[i][_dottedColumn] acceptsFirstResponder]) { _dottedRow = i; break; } } } else if (character == NSLeftArrowFunctionKey) { if (_dottedColumn <= 0) return; for (i = _dottedColumn-1; i >= 0; i--) { if ([_cells[_dottedRow][i] acceptsFirstResponder]) { _dottedColumn = i; break; } } } else { if (_dottedColumn < 0 || _dottedColumn >= _numCols-1) return; for (i = _dottedColumn+1; i < _numCols; i++) { if ([_cells[_dottedRow][i] acceptsFirstResponder]) { _dottedColumn = i; break; } } } [self drawCell: _cells[lastDottedRow][_dottedColumn]]; [self drawCell: _cells[_dottedRow][_dottedColumn]]; [_window flushWindow]; [self performClick: self]; } - (void) _altModifier:(unichar)character { switch (character) { case NSUpArrowFunctionKey: if (_dottedRow <= 0) return; _dottedRow--; break; case NSDownArrowFunctionKey: if (_dottedRow < 0 || _dottedRow >= _numRows-1) return; _dottedRow++; break; case NSLeftArrowFunctionKey: if (_dottedColumn <= 0) return; _dottedColumn--; break; case NSRightArrowFunctionKey: if (_dottedColumn < 0 || _dottedColumn >= _numCols-1) return; _dottedColumn++; break; } [self setSelectionFrom: INDEX_FROM_COORDS(_selectedRow, _selectedColumn) to: INDEX_FROM_COORDS(_dottedRow, _dottedColumn) anchor: INDEX_FROM_COORDS(_selectedRow, _selectedColumn) highlight: YES]; [self performClick: self]; } - (void) keyDown: (NSEvent *)theEvent { NSString *characters = [theEvent characters]; unsigned modifiers = [theEvent modifierFlags]; unichar character = 0; if ([characters length] > 0) { character = [characters characterAtIndex: 0]; } switch (character) { case NSCarriageReturnCharacter: case NSNewlineCharacter: case NSEnterCharacter: [self selectText: self]; break; case ' ': if (_dottedRow != -1 && _dottedColumn != -1) { if (modifiers & NSAlternateKeyMask) [self _altModifier: character]; else { NSCell *cell; switch (_mode) { case NSTrackModeMatrix: case NSHighlightModeMatrix: cell = _cells[_dottedRow][_dottedColumn]; [cell setNextState]; [self drawCell: cell]; break; case NSListModeMatrix: if (!(modifiers & NSShiftKeyMask)) [self deselectAllCells]; case NSRadioModeMatrix: [self selectCellAtRow: _dottedRow column: _dottedColumn]; break; } [_window flushWindow]; [self performClick: self]; } return; } break; case NSLeftArrowFunctionKey: case NSRightArrowFunctionKey: if (_numCols <= 1) break; case NSUpArrowFunctionKey: case NSDownArrowFunctionKey: if (modifiers & NSShiftKeyMask) [self _shiftModifier: character]; else if (modifiers & NSAlternateKeyMask) [self _altModifier: character]; else { if (character == NSUpArrowFunctionKey) [self moveUp: self]; else if (character == NSDownArrowFunctionKey) [self moveDown: self]; else if (character == NSLeftArrowFunctionKey) [self moveLeft: self]; else [self moveRight: self]; } return; case NSTabCharacter: if (_tabKeyTraversesCells) { if ([theEvent modifierFlags] & NSShiftKeyMask) { if([self _selectNextSelectableCellAfterRow: _selectedRow column: _selectedColumn]) return; } else { if([self _selectPreviousSelectableCellBeforeRow: _selectedRow column: _selectedColumn]) return; } } break; default: break; } [super keyDown: theEvent]; } - (void) performClick: (id)sender { [super sendAction: _action to: _target]; } - (BOOL) acceptsFirstResponder { // We gratefully accept keyboard events. return YES; } - (void) _setNeedsDisplayDottedCell { if (_dottedRow != -1 && _dottedColumn != -1) { [self setNeedsDisplayInRect: [self cellFrameAtRow: _dottedRow column: _dottedColumn]]; } } - (BOOL) becomeFirstResponder { [self _setNeedsDisplayDottedCell]; return YES; } - (BOOL) resignFirstResponder { [self _setNeedsDisplayDottedCell]; return YES; } - (void) becomeKeyWindow { [self _setNeedsDisplayDottedCell]; } - (void) resignKeyWindow { [self _setNeedsDisplayDottedCell]; } - (BOOL) abortEditing { if (_textObject) { [_textObject setString: @""]; [_selectedCell endEditing: _textObject]; _textObject = nil; return YES; } else return NO; } - (NSText *) currentEditor { if (_textObject && ([_window firstResponder] == _textObject)) return _textObject; else return nil; } - (void) validateEditing { if (_textObject) { NSFormatter *formatter; NSString *string; formatter = [_selectedCell formatter]; string = AUTORELEASE ([[_textObject text] copy]); if (formatter == nil) { [_selectedCell setStringValue: string]; } else { id newObjectValue; NSString *error; if ([formatter getObjectValue: &newObjectValue forString: string errorDescription: &error] == YES) { [_selectedCell setObjectValue: newObjectValue]; } else { if ([_delegate control: self didFailToFormatString: string errorDescription: error] == YES) { [_selectedCell setStringValue: string]; } } } } } @end @implementation NSMatrix (PrivateMethods) /* * Renew rows and columns, but when expanding the matrix, refrain from * creating rowSpace items in the last row and colSpace items in the * last column. When inserting the contents of an array into the matrix, * this avoids creation of new cless which would immediately be replaced * by those from the array. * NB. new spaces in the matrix are pre-initialised with nil values so * that replacing them doesn't cause attempts to release random memory. */ - (void) _renewRows: (int)row columns: (int)col rowSpace: (int)rowSpace colSpace: (int)colSpace { int i, j; int oldMaxC; int oldMaxR; SEL mkSel = @selector(makeCellAtRow:column:); IMP mkImp = [self methodForSelector: mkSel]; //NSLog(@"%x - mr: %d mc:%d nr:%d nc:%d r:%d c:%d", (unsigned)self, _maxRows, _maxCols, _numRows, _numCols, row, col); if (row < 0) { #if STRICT == 0 NSLog(@"renew negative row (%d) in matrix", row); #else [NSException raise: NSRangeException format: @"renew negative row (%d) in matrix", row]; #endif row = 0; } if (col < 0) { #if STRICT == 0 NSLog(@"renew negative column (%d) in matrix", col); #else [NSException raise: NSRangeException format: @"renew negative column (%d) in matrix", col]; #endif col = 0; } /* * Update matrix dimension before we actually change it - so that * makeCellAtRow:column: diesn't think we are trying to make a cell * outside the array bounds. * Our implementation doesn't care, but a subclass might use * putCell:atRow:column: to implement it, and that checks bounds. */ oldMaxC = _maxCols; _numCols = col; if (col > _maxCols) _maxCols = col; oldMaxR = _maxRows; _numRows = row; if (row > _maxRows) _maxRows = row; if (col > oldMaxC) { int end = col - 1; for (i = 0; i < oldMaxR; i++) { _cells[i] = NSZoneRealloc(_myZone, _cells[i], col * sizeof(id)); _selectedCells[i] = NSZoneRealloc(GSAtomicMallocZone(), _selectedCells[i], col * sizeof(BOOL)); for (j = oldMaxC; j < col; j++) { _cells[i][j] = nil; _selectedCells[i][j] = NO; if (j == end && colSpace > 0) { colSpace--; } else { (*mkImp)(self, mkSel, i, j); } } } } if (row > oldMaxR) { int end = row - 1; _cells = NSZoneRealloc(_myZone, _cells, row * sizeof(id*)); _selectedCells = NSZoneRealloc(_myZone, _selectedCells, row * sizeof(BOOL*)); /* Allocate the new rows and fill them */ for (i = oldMaxR; i < row; i++) { _cells[i] = NSZoneMalloc(_myZone, col * sizeof(id)); _selectedCells[i] = NSZoneMalloc(GSAtomicMallocZone(), col * sizeof(BOOL)); if (i == end) { for (j = 0; j < col; j++) { _cells[i][j] = nil; _selectedCells[i][j] = NO; if (rowSpace > 0) { rowSpace--; } else { (*mkImp)(self, mkSel, i, j); } } } else { for (j = 0; j < col; j++) { _cells[i][j] = nil; _selectedCells[i][j] = NO; (*mkImp)(self, mkSel, i, j); } } } } [self deselectAllCells]; //NSLog(@"%x - end mr: %d mc:%d nr:%d nc:%d r:%d c:%d", (unsigned)self, _maxRows, _maxCols, _numRows, _numCols, row, col); } - (void) _setState: (int)state highlight: (BOOL)highlight startIndex: (int)start endIndex: (int)end { int i; MPoint startPoint = POINT_FROM_INDEX(start); MPoint endPoint = POINT_FROM_INDEX(end); for (i = startPoint.y; i <= endPoint.y; i++) { int j; int colLimit; if (i == startPoint.y) { j = startPoint.x; } else { j = 0; } if (_selectionByRect || i == endPoint.y) colLimit = endPoint.x; else colLimit = _numCols - 1; for (; j <= colLimit; j++) { NSCell *aCell = _cells[i][j]; [aCell setState: state]; if (state == NSOffState) _selectedCells[i][j] = NO; else _selectedCells[i][j] = YES; [aCell setCellAttribute: NSCellHighlighted to: highlight]; [self drawCell: aCell]; } } [_window flushWindow]; } // Return YES on success; NO if no selectable cell found. -(BOOL) _selectNextSelectableCellAfterRow: (int)row column: (int)column { int i,j; if (row > -1) { // First look for cells in the same row for (j = column + 1; j < _numCols; j++) { if ([_cells[row][j] isEnabled] && [_cells[row][j] isSelectable]) { _selectedCell = [self selectTextAtRow: row column: j]; _selectedRow = row; _selectedColumn = j; return YES; } } } // Otherwise, make the big cycle. for (i = row + 1; i < _numRows; i++) { for (j = 0; j < _numCols; j++) { if ([_cells[i][j] isEnabled] && [_cells[i][j] isSelectable]) { _selectedCell = [self selectTextAtRow: i column: j]; _selectedRow = i; _selectedColumn = j; return YES; } } } return NO; } -(BOOL) _selectPreviousSelectableCellBeforeRow: (int)row column: (int)column { int i,j; if (row < _numRows) { // First look for cells in the same row for (j = column - 1; j > -1; j--) { if ([_cells[row][j] isEnabled] && [_cells[row][j] isSelectable]) { _selectedCell = [self selectTextAtRow: row column: j]; _selectedRow = row; _selectedColumn = j; return YES; } } } // Otherwise, make the big cycle. for (i = row - 1; i > -1; i--) { for (j = _numCols - 1; j > -1; j--) { if ([_cells[i][j] isEnabled] && [_cells[i][j] isSelectable]) { _selectedCell = [self selectTextAtRow: i column: j]; _selectedRow = i; _selectedColumn = j; return YES; } } } return NO; } @end