/* ** ExtendedMatrix.m ** ** Copyright (c) 2003 ** ** Author: Yen-Ju Chen ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "ExtendedMatrix.h" #include static unsigned currentDragOperation; static int dropRow; static int dropColumn; @implementation ExtendedMatrix /* * Editing Cells */ - (BOOL) abortEditing { NSLog(@"abortEditing"); if (_textObject) { [_textObject setString: @""]; [_editedCell endEditing: _textObject]; RELEASE (_editedCell); [self setNeedsDisplayInRect: [self cellFrameAtRow: _editedRow column: _editedColumn]]; _editedRow = -1; _editedColumn = -1; _editedCell = nil; _textObject = nil; return YES; } else return NO; } - (NSText *) currentEditor { if (_textObject && ([_window firstResponder] == _textObject)) return _textObject; else return nil; } - (void) validateEditing { NSLog(@"validateEditing"); if (_textObject) { NSFormatter *formatter; NSString *string; id newObjectValue; BOOL validatedOK = YES; formatter = [_editedCell formatter]; string = AUTORELEASE ([[_textObject text] copy]); if (formatter == nil) { newObjectValue = string; } else { NSString *error; if ([formatter getObjectValue: &newObjectValue forString: string errorDescription: &error] == NO) { if ([_delegate control: self didFailToFormatString: string errorDescription: error] == NO) { validatedOK = NO; } else { newObjectValue = string; } } } if (validatedOK == YES) { [_editedCell setObjectValue: newObjectValue]; /* Don't check editable yet if (_dataSource_editable) { */ [_editorDelegate matrix: self setObjectValue: newObjectValue forRow: _editedRow column: _editedColumn]; // } } } } - (void) editColumn: (int) column row: (int) row withEvent: (NSEvent *) theEvent select: (BOOL) flag { NSText *t; NSRect drawingRect; unsigned length = 0; _editedRow = row; _editedColumn = column; if (row < 0 || row >= _numRows || column < 0 || column >= _numCols) { [NSException raise: NSInvalidArgumentException format: @"Row/column out of index in edit"]; } _editedCell = [self cellAtRow: _editedRow column: _editedColumn]; if (_textObject != nil) { [self validateEditing]; [self abortEditing]; } // Now (_textObject == nil) t = [_window fieldEditor: YES forObject: self]; if ([t superview] != nil) { if ([t resignFirstResponder] == NO) { return; } } [_editedCell setEditable: YES]; // FIXME: Don't know how to select text by default /* [_editedCell setObjectValue: @""]; [_editedCell setObjectValue: [self _objectValueForTableColumn: tb row: rowIndex]]; */ // [_dataSource tableView: self // objectValueForTableColumn: tb // row: rowIndex]]; // We really want the correct background color! if ([_editedCell respondsToSelector: @selector(setBackgroundColor:)]) { // [(NSTextFieldCell *)_editedCell setBackgroundColor: _backgroundColor]; [(NSTextFieldCell *)_editedCell setBackgroundColor: [NSColor yellowColor]]; } else { // [t setBackgroundColor: _backgroundColor]; [t setBackgroundColor: [NSColor yellowColor]]; } // But of course the delegate can mess it up if it wants /* if (_del_responds) { [_delegate tableView: self willDisplayCell: _editedCell forTableColumn: tb row: rowIndex]; } */ /* Please note the important point - calling stringValue normally causes the _editedCell to call the validateEditing method of its control view ... which happens to be this NSTableView object :-) but we don't want any spurious validateEditing to be performed before the actual editing is started (otherwise you easily end up with the table view picking up the string stored in the field editor, which is likely to be the string resulting from the last edit somewhere else ... getting into the bug that when you TAB from one cell to another one, the string is copied!), so we must call stringValue when _textObject is still nil. */ if (flag) { length = [[_editedCell stringValue] length]; } _textObject = [_editedCell setUpFieldEditorAttributes: t]; drawingRect = [self cellFrameAtRow: _editedRow column: _editedColumn]; /* This number, 2, is the same as in ExtendedBrowserCell */ drawingRect.origin.x += 2; drawingRect.origin.y += 1; drawingRect.size.width -= 20; drawingRect.size.height -= 2; if (flag) { [_editedCell selectWithFrame: drawingRect inView: self editor: _textObject delegate: self start: 0 length: length]; } else { [_editedCell editWithFrame: drawingRect inView: self editor: _textObject delegate: self event: theEvent]; } return; } /* * Text delegate methods */ - (void) textDidBeginEditing: (NSNotification *)aNotification { NSMutableDictionary *d; NSLog(@"In matrix: textDidBeginEditing:"); d = [[NSMutableDictionary alloc] initWithDictionary: [aNotification userInfo]]; [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; [[NSNotificationCenter defaultCenter] postNotificationName: NSControlTextDidBeginEditingNotification object: self userInfo: d]; } - (void) textDidChange: (NSNotification *)aNotification { NSMutableDictionary *d; NSLog(@"In matrix: textDidChange:"); /* NSLog(@"_editedCell %@", [_editedCell stringValue]); NSLog(@"%@", [[aNotification object] string]); */ [self setNeedsDisplayInRect: [self cellFrameAtRow: _editedRow column: _editedColumn]]; // MacOS-X asks us to inform the cell if possible. if ((_editedCell != nil) && [_editedCell respondsToSelector: @selector(textDidChange:)]) [_editedCell textDidChange: aNotification]; d = [[NSMutableDictionary alloc] initWithDictionary: [aNotification userInfo]]; [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; [[NSNotificationCenter defaultCenter] postNotificationName: NSControlTextDidChangeNotification object: self userInfo: d]; } - (void) textDidEndEditing: (NSNotification *)aNotification { NSMutableDictionary *d; id textMovement; int row, column; NSLog(@"In matrix: textDidEndEditing:"); [self validateEditing]; [_editedCell endEditing: [aNotification object]]; [self setNeedsDisplayInRect: [self cellFrameAtRow: _editedRow column: _editedColumn]]; _textObject = nil; // FIXME: Not sure to release it // RELEASE (_editedCell); [_editedCell setEditable: NO]; _editedCell = nil; // Save values row = _editedRow; column = _editedColumn; // Only then Reset them _editedColumn = -1; _editedRow = -1; d = [[NSMutableDictionary alloc] initWithDictionary: [aNotification userInfo]]; [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; [[NSNotificationCenter defaultCenter] postNotificationName: NSControlTextDidEndEditingNotification object: self userInfo: d]; textMovement = [[aNotification userInfo] objectForKey: @"NSTextMovement"]; if (textMovement) { switch ([(NSNumber *)textMovement intValue]) { case NSReturnTextMovement: // Send action ? break; /* case NSTabTextMovement: if([self _editNextEditableCellAfterRow: row column: column] == YES) { break; } [_window selectKeyViewFollowingView: self]; break; case NSBacktabTextMovement: if([self _editPreviousEditableCellBeforeRow: row column: column] == YES) { break; } [_window selectKeyViewPrecedingView: self]; break; */ } } } - (BOOL) textShouldBeginEditing: (NSText *)textObject { NSLog(@"In matrix: textShouldBeginEditing:"); if (_delegate && [_delegate respondsToSelector: @selector(control:textShouldBeginEditing:)]) return [_delegate control: self textShouldBeginEditing: textObject]; else return YES; } - (BOOL) textShouldEndEditing: (NSText *)aTextObject { NSLog(@"In matrix: textShouldEndEditing:"); if ([_delegate respondsToSelector: @selector(control:textShouldEndEditing:)]) { if ([_delegate control: self textShouldEndEditing: aTextObject] == NO) { NSBeep (); return NO; } return YES; } 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; } } return [_editedCell isEntryAcceptable: [aTextObject text]]; } /* Prepare for Drag & Drop * Copy from BMatrix of GWorkspace */ - (void)mouseDown:(NSEvent*)theEvent { if (NO == [_editorDelegate respondsToSelector: @selector(matrix:writeRows:inColumn:toPasteboard:)]) { [super mouseDown: theEvent]; return; } else { int clickCount; NSPoint lastLocation; int row, col; if (([self numberOfRows] == 0) || ([self numberOfColumns] == 0)) { [super mouseDown: theEvent]; return; } clickCount = [theEvent clickCount]; if (clickCount > 2) { return; } if (clickCount == 2) { [self sendDoubleAction]; return; } lastLocation = [theEvent locationInWindow]; lastLocation = [self convertPoint: lastLocation fromView: nil]; if ([self getRow: &row column: &col forPoint: lastLocation]) { // FIXME: Seens weird // NSCell *cell = [[self cells] objectAtIndex: row]; NSCell *cell = [self cellAtRow: row column: col]; NSRect rect = [self cellFrameAtRow: row column: col]; if ([cell isEnabled]) { if (NSPointInRect(lastLocation, rect)) { NSEvent *nextEvent; BOOL startdnd = NO; int dragdelay = 0; [self deselectAllCells]; [self selectCellAtRow: row column: col]; [self sendAction]; while (1) { nextEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask]; if ([nextEvent type] == NSLeftMouseUp) { [self mouseUp: nextEvent]; break; } else if ([nextEvent type] == NSLeftMouseDragged) { if (dragdelay < 5) { dragdelay++; } else { startdnd = YES; break; } } } if (startdnd == YES) { [self startExternalDragOnEvent: nextEvent]; return; } } else { [super mouseDown: theEvent]; } } } } } - (void) mouseUp: (NSEvent *) event { [super mouseUp: event]; } /* Drag Source */ - (void) startExternalDragOnEvent: (NSEvent *) event { NSPoint dragPoint; NSPasteboard *pb; NSImage *dragIcon; BOOL canDrag = NO; int selectedRow, selectedCol; dragPoint = [event locationInWindow]; dragPoint = [self convertPoint: dragPoint fromView: nil]; selectedRow = [self selectedRow]; selectedCol = [self selectedColumn]; pb = [NSPasteboard pasteboardWithName: NSDragPboard]; canDrag = [_editorDelegate matrix: self writeRows: selectedRow inColumn: selectedCol toPasteboard: pb]; if (canDrag == NO) return; dragIcon = AUTORELEASE([[NSImage alloc] initWithSize: NSMakeSize(8, 8)]); [self dragImage: dragIcon at: dragPoint offset: NSZeroSize event: event pasteboard: pb source: self slideBack: YES]; } - (NSDragOperation) draggingSourceOperationMaskForLocal:(BOOL) isLocal; { if(isLocal) return NSDragOperationMove; else return NSDragOperationNone; } /* Drag Destination */ - (void) setDropRow: (int) row dropOperation: (ExtendedMatrixDropOperation) operation { if (row < 0) { _newDropRow = 0; } else if (operation == ExtendedMatrixDropOn) { if (row >= [self numberOfRows]) _newDropRow = [self numberOfRows]; } else if (row > [self numberOfRows]) { /* Above */ _newDropRow = [self numberOfRows]; } else { /* On */ _newDropRow = row; } _newDropOperation = operation; } - (NSDragOperation) draggingEntered: (id ) sender { if (([sender draggingSource] == nil) || (![[sender draggingSource] isKindOfClass: [self class]])) { /* Prevent drop from other view or applications */ return NSDragOperationNone; } return [sender draggingSourceOperationMask]; } - (NSDragOperation) draggingUpdated: (id ) sender { BOOL gotCell = NO; dropRow = -1, dropColumn = -1; NSPoint p = [sender draggingLocation]; p = [self convertPoint: p fromView: nil]; gotCell = [self getRow: &dropRow column: &dropColumn forPoint: p]; if (gotCell == YES) { NSRect cellFrame; _newDropRow = dropRow; _newDropOperation = ExtendedMatrixDropOn; if ([_editorDelegate respondsToSelector: @selector(matrix:validateDrop:proposedRow:proposedDropOperation:)]) { currentDragOperation = [_editorDelegate matrix: self validateDrop: sender proposedRow: _newDropRow proposedDropOperation: ExtendedMatrixDropOn]; } if (currentDragOperation == NSDragOperationNone) return NSDragOperationNone; [self lockFocus]; [self setNeedsDisplay: YES]; [self displayIfNeeded]; [[NSColor redColor] set]; cellFrame = [self cellFrameAtRow: _newDropRow column: dropColumn]; if (_newDropOperation == ExtendedMatrixDropAbove) { if (_newDropRow == 0) { cellFrame = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y + 1, cellFrame.size.width, 1); } else if (_newDropRow == [self numberOfRows]) { cellFrame = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y + cellFrame.size.height, cellFrame.size.width, 1); } else { cellFrame = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y, cellFrame.size.width, 1); } } NSFrameRectWithWidth(cellFrame, 2.0); [_window flushWindow]; [self unlockFocus]; return currentDragOperation; } return NSDragOperationNone; } - (void) draggingExited: (id ) sender { [self setNeedsDisplay: YES]; } - (BOOL) performDragOperation: (id)sender { if ([_editorDelegate respondsToSelector: @selector(matrix:acceptDrop:row:proposedDropOperation:)]) { return [_editorDelegate matrix: self acceptDrop: sender row: dropRow proposedDropOperation: _newDropOperation]; } else return NO; } - (BOOL) prepareForDragOperation: (id)sender { return YES; } - (void) concludeDragOperation:(id )sender { [self setNeedsDisplay: YES]; } - (id) initWithFrame: (NSRect) frame { self = [super initWithFrame: frame]; _editorDelegate = nil; _newDropOperation = ExtendedMatrixDropOn; return self; } - (void) dealloc { RELEASE(_editorDelegate); } - (void) setEditorDelegate: (id) delegate { ASSIGN(_editorDelegate, delegate); } - (id) editorDelegate { return _editorDelegate; } @end