/* NSMatrix.m Matrix class for grouping controls Copyright (C) 1996 Free Software Foundation, Inc. Author: Pascal Forget Scott Christley Date: 1996 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. If you are interested in a warranty or support for this source code, contact Scott Christley for more information. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #define GNU_DEFAULT_CELL_HEIGHT 20 // // Class variables // Class NSMATRIX_DEFAULT_CELL_CLASS; @implementation NSMatrix + (void)initialize { if (self == [NSMatrix class]) { NSDebugLog(@"Initialize NSMatrix class\n"); // Set initial version [self setVersion: 1]; // Set the default cell class NSMATRIX_DEFAULT_CELL_CLASS = [NSCell class]; } } // // Initializing the NSMatrix Class // + (Class)cellClass { return NSMATRIX_DEFAULT_CELL_CLASS; } + (void)setCellClass:(Class)classId { NSMATRIX_DEFAULT_CELL_CLASS = classId; } // // Initializing an NSMatrix Object // - (void)createInitialMatrix { NSSize cs; int i, j; id aRow, aFloat; // Determine cell width and height for uniform cell size cs.width = (frame.size.width - (inter_cell.width * (num_cols - 1))) / num_cols; cs.height = (frame.size.height - (inter_cell.height * (num_rows - 1))) / num_rows; // Save cell widths and heights in arrays aFloat = [NSNumber numberWithFloat: cs.height]; for (i = 0;i < num_rows; ++i) [row_heights addObject:aFloat]; aFloat = [NSNumber numberWithFloat: cs.width]; for (i = 0;i < num_cols; ++i) [col_widths addObject:aFloat]; for (i = 0;i < num_rows; ++i) { aRow = [NSMutableArray arrayWithCapacity: num_cols]; [rows addObject: aRow]; for (j = 0;j < num_cols; ++j) { if (cell_prototype != nil) { [(NSMutableArray *)aRow addObject: [cell_prototype copy]]; } else { [(NSMutableArray *)aRow addObject: [[cell_class alloc] init]]; } } } } - (id)initWithFrame:(NSRect)frameRect { return [self initWithFrame: frameRect mode: NSTrackModeMatrix cellClass: NSMATRIX_DEFAULT_CELL_CLASS numberOfRows: 0 numberOfColumns: 0]; } - (id)initWithFrame:(NSRect)frameRect mode:(int)aMode cellClass:(Class)classId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide { NSDebugLog(@"NSMatrix start -initWithFrame: ..cellClass:\n"); [super initWithFrame:frameRect]; if (rowsHigh < 0) { NSLog(@"NSMatrix initWithFrame:mode: rows has to be >= 0.\n"); NSLog(@"Will create matrix with 0 rows.\n"); num_rows = 0; } else { num_rows = rowsHigh; } if (colsWide < 0) { NSLog(@"NSMatrix initWithFrame:mode: columns has to be >= 0.\n"); NSLog(@"Will create matrix with 0 columns.\n"); num_cols = 0; } else { num_cols = colsWide; } rows = [[NSMutableArray alloc] init]; row_heights = [[NSMutableArray alloc] init]; col_widths = [[NSMutableArray alloc] init]; selected_cells = [[NSMutableArray alloc] init]; inter_cell.width = inter_cell.height = 2; allows_empty_selection = YES; cell_prototype = nil; cell_class = classId; mode = aMode; [self createInitialMatrix]; NSDebugLog(@"NSMatrix end -initWithFrame: ..cellClass:\n"); return self; } - (id)initWithFrame:(NSRect)frameRect mode:(int)aMode prototype:(NSCell *)aCell numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide { [super initWithFrame:frameRect]; if (aCell == nil) { NSLog(@"NSMatrix "); NSLog(@"initWithFrame:mode:prototype:numberOfRows:numberOfColumns: "); NSLog(@"prototype can't be nil. "); NSLog(@"Using NSCell as the default class.\n"); cell_prototype = nil; cell_class = [NSCell class]; } else { cell_prototype = [aCell retain]; } return self; } // // Setting the Selection Mode // - (NSMatrixMode)mode { return mode; } - (void)setMode:(NSMatrixMode)aMode { mode = aMode; } // // Configuring the NSMatrix // - (BOOL)allowsEmptySelection { return allows_empty_selection; } - (BOOL)isSelectionByRect { return selection_by_rect; } - (void)setAllowsEmptySelection:(BOOL)flag { allows_empty_selection = flag; } - (void)setSelectionByRect:(BOOL)flag { selection_by_rect = flag; } // // Setting the Cell Class // - (Class)cellClass { return cell_class; } - (id)prototype { return cell_prototype; } - (void)setCellClass:(Class)classId { cell_class = classId; } - (void)setPrototype:(NSCell *)aCell { cell_prototype = [aCell retain]; } // // Laying Out the NSMatrix // - (void)addColumn { int i; NSNumber *anInt; NSMutableArray *aRow; if (num_rows <= 0) { [rows addObject:[[NSMutableArray alloc] init]]; num_rows = 1; [row_heights removeAllObjects]; anInt = [NSNumber numberWithInt: GNU_DEFAULT_CELL_HEIGHT]; [row_heights addObject:anInt]; } for (i=0; i num_rows) { NSDebugLog(@"NSMatrix add more rows %d %d\n", nrows, num_rows); new_rows = nrows - num_rows; for (i = 0; i < new_rows; ++i) { a = [[NSMutableArray alloc] init]; [rows addObject: a]; ++num_rows; } } ++num_cols; NSDebugLog(@"NSMatrix rows %d cols %d\n", num_rows, num_cols); // Determine cell width and height for uniform cell size cs.width = (frame.size.width - (inter_cell.width * (num_cols - 1))) / num_cols; cs.height = (frame.size.height - (inter_cell.height * (num_rows - 1))) / num_rows; // Save cell widths and heights in arrays aFloat = [NSNumber numberWithFloat: cs.height]; for (i = 0;i < nrows; ++i) [row_heights addObject:aFloat]; aFloat = [NSNumber numberWithFloat: cs.width]; [col_widths addObject:aFloat]; NSDebugLog(@"NSMatrix cell size %f %f\n", cs.width, cs.height); e = [cellArray objectEnumerator]; o = [e nextObject]; re = [rows objectEnumerator]; aRow = [re nextObject]; while (o) { [aRow addObject: o]; [o retain]; o = [e nextObject]; aRow = [re nextObject]; } } - (void)addRow { int i; NSNumber *anInt; NSMutableArray *newRow = [[NSMutableArray alloc] init]; if (cell_prototype != nil) { [newRow addObject:[cell_prototype copy]]; } else { [newRow addObject:[[cell_class alloc] init]]; } if (num_cols > 0) { for (i=1; i= num_rows) || (row < 0)) { return r; } if ((column >= num_cols) || (column < 0)) { return r; } #endif /* Compute the x origin */ for (i=0; i 1) { for (i=0; icolumn; j--) { [currentRow replaceObjectAtIndex:j withObject: [currentRow objectAtIndex:i-1]]; } [currentRow replaceObjectAtIndex:column withObject:newCell]; } } } - (void)insertColumn:(int)column withCells:(NSArray *)cellArray { NSCell *newCell; int i, count; [self insertColumn:column]; if (cellArray != nil) { count = [cellArray count]; } else { count = 0; } for (i=0; i 1) { aRow = [rows lastObject]; for (i=num_rows-1; i> row; i--) { [rows replaceObjectAtIndex:i withObject:[rows objectAtIndex:i-1]]; } [rows replaceObjectAtIndex:row withObject:aRow]; } } - (void)insertRow:(int)row withCells:(NSArray *)cellArray { NSCell *newCell; int i, count; id e, o; // No array then forget the insert if (!cellArray) return; [self insertRow:row]; e = [cellArray objectEnumerator]; o = [e nextObject]; i = 0; while (o) { [self putCell: o atRow: row column: i]; o = [e nextObject]; ++i; } } - (NSSize)intercellSpacing { return inter_cell; } - (NSCell *)makeCellAtRow:(int)row column:(int)column { id newCell; if ((row < num_rows) && (row > -1) && (column < num_cols) && (column > -1)) { newCell = [[cell_class alloc] init]; [[rows objectAtIndex:row] replaceObjectAtIndex:column withObject:newCell]; return newCell; } else { return nil; } } - (void)putCell:(NSCell *)newCell atRow:(int)row column:(int)column { NSMutableArray *aRow; if ((row > -1) && (row < num_rows) && (column > -1) && (column < num_cols)) { aRow = (NSMutableArray *)[rows objectAtIndex:row]; [aRow replaceObjectAtIndex:column withObject:newCell]; } } - (void)removeColumn:(int)column { int i; NSMutableArray *aRow; if ((column > -1) && (column < num_cols)) { for (i=0; i -1) && (row < num_rows)) { [rows removeObjectAtIndex:row]; [row_heights removeObjectAtIndex:row]; num_rows--; } } - empty { [rows removeAllObjects]; [selected_cells removeAllObjects]; num_rows = 0; num_cols = 0; return self; } - (void)renewRows:(int)newRows columns:(int)newColumns {} - (void)setCellSize:(NSSize)aSize { NSNumber *aFloat; id old; int i; for (i=0; i -1) && (column < num_cols)) { aFloat = [NSNumber numberWithFloat: width]; old = [col_widths objectAtIndex:column]; [col_widths replaceObjectAtIndex:column withObject: aFloat]; [old release]; } } - (void)setIntercellSpacing:(NSSize)aSize { inter_cell = aSize; } - (void)sortUsingFunction:(int (*)(id element1, id element2, void *userData))comparator context:(void *)context {} - (void)sortUsingSelector:(SEL)comparator {} - (int)numCols; { return num_cols; } - (int)numRows; { return num_rows; } // // Finding Matrix Coordinates // /* * The next function returns NO if the point is located inside * the matrix but also in an intercell gap, which may or may * not be the behavior God intended... */ - (BOOL)getRow:(int *)row column:(int *)column forPoint:(NSPoint)aPoint { NSRect myFrame = [self bounds]; NSRect cFrame; id re, ce; id aRow, aCol; int i, j; /* Trivial rejection if the point is not in the Matrix's frame rect */ if (![self mouse: aPoint inRect: myFrame]) { NSDebugLog(@"NSMatrix point %f %f not in rect %f %f %f %f\n", aPoint.x, aPoint.y, myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height); return NO; } /* Here an optimized algo could be used at the expense of clarity */ re = [rows objectEnumerator]; aRow = [re nextObject]; i = 0; while (aRow) { ce = [aRow objectEnumerator]; aCol = [ce nextObject]; j = 0; while (aCol) { cFrame = [self cellFrameAtRow:i column:j]; if ([self mouse: aPoint inRect: cFrame]) { *row = i; *column = j; return YES; } aCol = [ce nextObject]; ++j; } aRow = [re nextObject]; ++i; } return NO; } - (BOOL)getRow:(int *)row column:(int *)column ofCell:(NSCell *)aCell { id re, ce; id aRow, aCol; int i, j; re = [rows objectEnumerator]; aRow = [re nextObject]; i = 0; while (aRow) { ce = [aRow objectEnumerator]; aCol = [ce nextObject]; j = 0; while (aCol) { if ((NSCell *)[self cellAtRow:i column:j] == aCell) { *row = i; *column = j; return YES; } aCol = [ce nextObject]; ++j; } aRow = [re nextObject]; ++i; } return NO; } // // Modifying Individual Cells // - (void)setState:(int)value atRow:(int)row column:(int)column { NSMutableArray *aRow; NSCell *aCell; if ((row > -1) && (row < num_rows) && (column > -1) && (column < num_cols)) { aRow = (NSMutableArray *)[rows objectAtIndex:row]; aCell = (NSCell *)[aRow objectAtIndex:column]; [aCell setState:value]; } } // // Selecting Cells // - (void)deselectAllCells { if (allows_empty_selection) [selected_cells removeAllObjects]; } - (void)deselectSelectedCell { int cnt = [selected_cells count]; // Is anything even selected? if (cnt > 0) // Only empty array if empty selection is allowed if ((cnt != 1) || (allows_empty_selection)) [selected_cells removeLastObject]; } - (void)selectAll:(id)sender { [selected_cells removeAllObjects]; [selected_cells addObjectsFromArray:[self cells]]; } - (void)selectCellAtRow:(int)row column:(int)column { int index; id aCell = [self cellAtRow:row column:column]; NSDebugLog(@"NSMatrix select cell at %d %d\n", row, column); if (aCell != nil) { // Add to selected cell list if not already index = [selected_cells indexOfObject:aCell]; if ((index < 0) || (index > [selected_cells count])) { [selected_cells addObject:aCell]; } } } - (BOOL)selectCellWithTag:(int)anInt { id aCell = [self cellWithTag:anInt]; int index; if (cell != nil) { index = [selected_cells indexOfObject:aCell]; if ((index < 0) || (index > [selected_cells count])) { [selected_cells addObject:aCell]; } return YES; } else { return NO; } } - (id)selectedCell { return [selected_cells lastObject]; } /* * returns an array of the selected cells */ - (NSArray *)selectedCells { return selected_cells; } - (int)selectedColumn { int row, col; id aCell; if ([selected_cells count]) { aCell = [selected_cells lastObject]; if ([self getRow: &row column: &col ofCell: aCell]) return col; } // Not found return -1; } - (int)selectedRow { int row, col; id aCell; if ([selected_cells count]) { aCell = [selected_cells lastObject]; if ([self getRow: &row column: &col ofCell: aCell]) return row; } // Not found return -1; } - (void)setSelectionFrom:(int)startPos to:(int)endPos anchor:(int)anchorPos highlight:(BOOL)flag {} // // Finding Cells // - (id)cellAtRow:(int)row column:(int)column { id aRow; if ((row >= num_rows) || (row < 0)) { NSLog(@"NSMatrix cellAt:: invalid row (%d)\n", row); return nil; } aRow = [rows objectAtIndex: row]; if ((column >= [aRow count]) || (column < 0)) { NSLog(@"NSMatrix cellAt:: invalid column (%d)\n", column); return nil; } return [(NSArray *)aRow objectAtIndex:column]; } - (id)cellWithTag:(int)anInt { id re, ce; int i, j; NSMutableArray *aRow; NSCell *aCell; // Loop through the rows and columns and find the cell re = [rows objectEnumerator]; aRow = [re nextObject]; while (aRow) { ce = [aRow objectEnumerator]; aCell = (NSCell *)[ce nextObject]; while (aCell) { if ([aCell tag] == anInt) return aCell; aCell = (NSCell *)[ce nextObject]; } } return nil; } - (NSArray *)cells { NSMutableArray *cells; int i; if ((num_cols < 1) || (num_rows < 1)) { return nil; } else { cells = [[NSMutableArray alloc] initWithCapacity:(num_rows * num_cols)]; for (i=0; i 0) && (row < (num_rows-1) )) { newSize.height += inter_cell.height; } } for (col=0; col 0) && (col < (num_cols-1) )) { newSize.width += inter_cell.width; } } #if 1 NSDebugLog(@"NSMatrix size: %4.0f %4.0f\n",newSize.width,newSize.height); #endif [(NSView *)self setFrameSize:newSize]; } - (void)resizeWithOldSuperviewSize:(NSSize)oldSize { NSDebugLog(@"NSMatrix resize %f %f old %f %f\n", bounds.size.width, bounds.size.height, oldSize.width, oldSize.height); } // // Scrolling // - (BOOL)isAutoscroll { return autoscroll; } - (void)scrollCellToVisibleAtRow:(int)row column:(int)column {} - (void)setAutoscroll:(BOOL)flag { autoscroll = flag; } - (void)setScrollable:(BOOL)flag { scrollable = flag; } // // Displaying // - (void)display; { id re, ce; int row, col; NSMutableArray *aRow; id aCol; NSDebugLog(@"NSMatrix display %f %f %f %f\n", bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height); re = [rows objectEnumerator]; aRow = (NSMutableArray *)[re nextObject]; row = 0; while (aRow) { ce = [aRow objectEnumerator]; aCol = [ce nextObject]; col = 0; while (aCol) { [self drawCellAtRow: row column: col]; ++col; aCol = [ce nextObject]; } ++row; aRow = [re nextObject]; } NSDebugLog(@"End NSMatrix display\n"); } - (void)drawCellAtRow:(int)row column:(int)column { NSMutableArray *aRow = [rows objectAtIndex: row]; NSCell *aCell = [aRow objectAtIndex: column]; NSDebugLog(@"NSMatrix draw cell %d %d %d %d\n", row, column, [rows count], [aRow count]); [aCell drawWithFrame: [self cellFrameAtRow:row column:column] inView:self]; } - (void)highlightCell:(BOOL)flag atRow:(int)row column:(int)column { NSCell *aCell = [self cellAtRow:row column:column]; NSRect cellFrame; BOOL did_lock = NO; if (aCell != nil) { cellFrame = [self cellFrameAtRow:row column:column]; [aCell highlight:flag withFrame:cellFrame inView:self]; } } // // Target and Action // - (SEL)doubleAction { return double_action; } - (void)setDoubleAction:(SEL)aSelector { double_action = aSelector; } - (SEL)errorAction { return error_action; } - (BOOL)sendAction { NSCell *aCell = [self selectedCell]; NSDebugLog(@"NSMatrix -sendAction\n"); if (!aCell) return NO; if ([aCell isKindOfClass:[NSActionCell class]]) { NSDebugLog(@"NSMatrix sending action\n"); [self sendAction: [aCell action] to: [aCell target]]; return YES; } NSDebugLog(@"NSMatrix no action\n"); return NO; } - (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag { BOOL (*test)(id, SEL, id); id row, columns, col; id aCell; BOOL result; // If the object doesn't respond to selector then quit if (![anObject respondsToSelector: aSelector]) return; // Obtain function pointer to selector test = (BOOL (*)(id, SEL, id))[anObject methodForSelector: aSelector]; // Enumerate through all the cells row = [rows objectEnumerator]; columns = [row nextObject]; while (columns) { col = [columns objectEnumerator]; aCell = [col nextObject]; while (aCell) { // Call the method result = test(anObject, aSelector, aCell); // If the result is NO // and we shouldn't continue for all cells // then quit if ((!result) && (!flag)) return; aCell = [col nextObject]; } columns = [row nextObject]; } } - (void)sendDoubleAction { NSCell *aCell = [self selectedCell]; NSDebugLog(@"NSMatrix -sendDoubleAction\n"); if (!aCell) return; if (double_action) { NSDebugLog(@"NSMatrix sending double action\n"); [self sendAction: double_action to: [aCell target]]; } else NSDebugLog(@"NSMatrix no double action\n"); } - (void)setErrorAction:(SEL)aSelector { error_action = aSelector; } // // Handling Event and Action Messages // - (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { if (mode == NSListModeMatrix) { return NO; } return YES; } - (void)highlightTrackingMode:(NSEvent *)theEvent { NSPoint location; NSPoint point; NSCell *aCell; int aRow, aCol; NSRect cellFrame; NSApplication *theApp = [NSApplication sharedApplication]; BOOL mouseUp, done; NSEvent *e; unsigned int event_mask = NSLeftMouseDownMask | NSLeftMouseUpMask | NSMouseMovedMask; // Capture mouse [[self window] captureMouse: self]; mouseUp = NO; done = NO; e = theEvent; while (!done) { location = [e locationInWindow]; point = [self convertPoint: location fromView: nil]; // Find out what cell was clicked on if ([self getRow:&aRow column:&aCol forPoint:point]) { NSDebugLog(@"NSMatrix found row/column from point\n"); aCell = [self cellAtRow:aRow column:aCol]; if (!aCell) { // Yikes problem here NSLog(@"NSMatrix no cell at %d %d\n", aRow, aCol); mouseUp = NO; done = YES; continue; } NSDebugLog(@"NSMatrix found cell\n"); cellFrame = [self cellFrameAtRow: aRow column: aCol]; mouseUp = [aCell trackMouse: e inRect: cellFrame ofView:self untilMouseUp:YES]; e = [theApp currentEvent]; } // If mouse went up then we are done if ((mouseUp) || ([e type] == NSLeftMouseUp)) done = YES; else { NSDebugLog(@"NSMatrix process another event\n"); e = [theApp nextEventMatchingMask:event_mask untilDate:nil inMode:nil dequeue:YES]; } } // Release mouse [[self window] releaseMouse: self]; // If the mouse went up in the button if (mouseUp) { NSDebugLog(@"NSMatrix the mouse went up in a button\n"); // Unhighlight the cell [aCell highlight: NO withFrame: cellFrame inView: self]; // Select the cell [self selectCellAtRow: aRow column: aCol]; NSDebugLog(@"NSMatrix send action\n"); // Send action if ([theEvent clickCount] == 1) [self sendAction]; else [self sendDoubleAction]; } } - (void)mouseDown:(NSEvent *)theEvent { NSView *view = [[self window] contentView]; NSPoint location = [theEvent locationInWindow]; NSPoint point = [super_view convertPoint:location fromView:view]; NSDebugLog(@"NSMatrix mouseDown at point %f %f\n", point.x, point.y); // What mode are we in? switch(mode) { case NSTrackModeMatrix: break; case NSHighlightModeMatrix: NSDebugLog(@"NSMatrix NSHighlightModeMatrix\n"); [self highlightTrackingMode: theEvent]; break; case NSRadioModeMatrix: break; case NSListModeMatrix: break; } } - (int)mouseDownFlags { return 0; } - (BOOL)performKeyEquivalent:(NSEvent *)theEvent { return NO; } // // Managing the Cursor // - (void)resetCursorRects {} // // NSCoding protocol // - (void)encodeWithCoder:aCoder { [super encodeWithCoder:aCoder]; } - initWithCoder:aDecoder { [super initWithCoder:aDecoder]; return self; } @end