1/* 2** ExtendedMatrix.m 3** 4** Copyright (c) 2003 5** 6** Author: Yen-Ju Chen <yjchenx@hotmail.com> 7** 8** This program is free software; you can redistribute it and/or modify 9** it under the terms of the GNU General Public License as published by 10** the Free Software Foundation; either version 2 of the License, or 11** (at your option) any later version. 12** 13** This program is distributed in the hope that it will be useful, 14** but WITHOUT ANY WARRANTY; without even the implied warranty of 15** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16** GNU General Public License for more details. 17** 18** You should have received a copy of the GNU General Public License 19** along with this program; if not, write to the Free Software 20** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21*/ 22 23#include "ExtendedMatrix.h" 24#include <AppKit/AppKit.h> 25 26static unsigned currentDragOperation; 27static int dropRow; 28static int dropColumn; 29 30@implementation ExtendedMatrix 31 32/* 33 * Editing Cells 34 */ 35 36- (BOOL) abortEditing 37{ 38 NSLog(@"abortEditing"); 39 if (_textObject) 40 { 41 [_textObject setString: @""]; 42 [_editedCell endEditing: _textObject]; 43 RELEASE (_editedCell); 44 [self setNeedsDisplayInRect: 45 [self cellFrameAtRow: _editedRow column: _editedColumn]]; 46 _editedRow = -1; 47 _editedColumn = -1; 48 _editedCell = nil; 49 _textObject = nil; 50 return YES; 51 } 52 else 53 return NO; 54} 55 56- (NSText *) currentEditor 57{ 58 if (_textObject && ([_window firstResponder] == _textObject)) 59 return _textObject; 60 else 61 return nil; 62} 63 64- (void) validateEditing 65{ 66 NSLog(@"validateEditing"); 67 if (_textObject) 68 { 69 NSFormatter *formatter; 70 NSString *string; 71 id newObjectValue; 72 BOOL validatedOK = YES; 73 74 formatter = [_editedCell formatter]; 75 string = AUTORELEASE ([[_textObject text] copy]); 76 77 if (formatter == nil) 78 { 79 newObjectValue = string; 80 } 81 else 82 { 83 NSString *error; 84 85 if ([formatter getObjectValue: &newObjectValue 86 forString: string 87 errorDescription: &error] == NO) 88 { 89 if ([_delegate control: self 90 didFailToFormatString: string 91 errorDescription: error] == NO) 92 { 93 validatedOK = NO; 94 } 95 else 96 { 97 newObjectValue = string; 98 } 99 } 100 } 101 if (validatedOK == YES) 102 { 103 [_editedCell setObjectValue: newObjectValue]; 104 105/* Don't check editable yet 106 if (_dataSource_editable) 107 { 108*/ 109 [_editorDelegate matrix: self 110 setObjectValue: newObjectValue 111 forRow: _editedRow 112 column: _editedColumn]; 113// } 114 } 115 } 116} 117 118- (void) editColumn: (int) column 119 row: (int) row 120 withEvent: (NSEvent *) theEvent 121 select: (BOOL) flag 122{ 123 124 NSText *t; 125 NSRect drawingRect; 126 unsigned length = 0; 127 128 _editedRow = row; 129 _editedColumn = column; 130 131 if (row < 0 || row >= _numRows 132 || column < 0 || column >= _numCols) 133 { 134 [NSException raise: NSInvalidArgumentException 135 format: @"Row/column out of index in edit"]; 136 } 137 138 _editedCell = [self cellAtRow: _editedRow column: _editedColumn]; 139 140 if (_textObject != nil) 141 { 142 [self validateEditing]; 143 [self abortEditing]; 144 } 145 146 // Now (_textObject == nil) 147 148 t = [_window fieldEditor: YES forObject: self]; 149 150 if ([t superview] != nil) 151 { 152 if ([t resignFirstResponder] == NO) 153 { 154 return; 155 } 156 } 157 158 [_editedCell setEditable: YES]; 159 160// FIXME: Don't know how to select text by default 161/* 162 [_editedCell setObjectValue: @""]; 163 [_editedCell setObjectValue: [self _objectValueForTableColumn: tb 164 row: rowIndex]]; 165*/ 166 // [_dataSource tableView: self 167 // objectValueForTableColumn: tb 168 // row: rowIndex]]; 169 170 // We really want the correct background color! 171 if ([_editedCell respondsToSelector: @selector(setBackgroundColor:)]) 172 { 173// [(NSTextFieldCell *)_editedCell setBackgroundColor: _backgroundColor]; 174 [(NSTextFieldCell *)_editedCell setBackgroundColor: [NSColor yellowColor]]; 175 } 176 else 177 { 178// [t setBackgroundColor: _backgroundColor]; 179 [t setBackgroundColor: [NSColor yellowColor]]; 180 } 181 182 // But of course the delegate can mess it up if it wants 183/* 184 if (_del_responds) 185 { 186 [_delegate tableView: self willDisplayCell: _editedCell 187 forTableColumn: tb row: rowIndex]; 188 } 189*/ 190 191 /* Please note the important point - calling stringValue normally 192 causes the _editedCell to call the validateEditing method of its 193 control view ... which happens to be this NSTableView object :-) 194 but we don't want any spurious validateEditing to be performed 195 before the actual editing is started (otherwise you easily end up 196 with the table view picking up the string stored in the field 197 editor, which is likely to be the string resulting from the last 198 edit somewhere else ... getting into the bug that when you TAB 199 from one cell to another one, the string is copied!), so we must 200 call stringValue when _textObject is still nil. */ 201 if (flag) 202 { 203 length = [[_editedCell stringValue] length]; 204 } 205 206 _textObject = [_editedCell setUpFieldEditorAttributes: t]; 207 208 drawingRect = [self cellFrameAtRow: _editedRow column: _editedColumn]; 209 210 /* This number, 2, is the same as in ExtendedBrowserCell */ 211 drawingRect.origin.x += 2; 212 drawingRect.origin.y += 1; 213 drawingRect.size.width -= 20; 214 drawingRect.size.height -= 2; 215 216 if (flag) 217 { 218 [_editedCell selectWithFrame: drawingRect 219 inView: self 220 editor: _textObject 221 delegate: self 222 start: 0 223 length: length]; 224 } 225 else 226 { 227 [_editedCell editWithFrame: drawingRect 228 inView: self 229 editor: _textObject 230 delegate: self 231 event: theEvent]; 232 } 233 return; 234} 235 236/* 237 * Text delegate methods 238 */ 239 240- (void) textDidBeginEditing: (NSNotification *)aNotification 241{ 242 NSMutableDictionary *d; 243 244 NSLog(@"In matrix: textDidBeginEditing:"); 245 d = [[NSMutableDictionary alloc] initWithDictionary: 246 [aNotification userInfo]]; 247 [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; 248 [[NSNotificationCenter defaultCenter] 249 postNotificationName: NSControlTextDidBeginEditingNotification 250 object: self 251 userInfo: d]; 252} 253 254- (void) textDidChange: (NSNotification *)aNotification 255{ 256 NSMutableDictionary *d; 257 258 NSLog(@"In matrix: textDidChange:"); 259/* 260 NSLog(@"_editedCell %@", [_editedCell stringValue]); 261 NSLog(@"%@", [[aNotification object] string]); 262*/ 263 [self setNeedsDisplayInRect: 264 [self cellFrameAtRow: _editedRow column: _editedColumn]]; 265 266 // MacOS-X asks us to inform the cell if possible. 267 if ((_editedCell != nil) && [_editedCell respondsToSelector: 268 @selector(textDidChange:)]) 269 [_editedCell textDidChange: aNotification]; 270 271 d = [[NSMutableDictionary alloc] initWithDictionary: 272 [aNotification userInfo]]; 273 [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; 274 [[NSNotificationCenter defaultCenter] 275 postNotificationName: NSControlTextDidChangeNotification 276 object: self 277 userInfo: d]; 278} 279 280- (void) textDidEndEditing: (NSNotification *)aNotification 281{ 282 NSMutableDictionary *d; 283 id textMovement; 284 int row, column; 285 286 NSLog(@"In matrix: textDidEndEditing:"); 287 [self validateEditing]; 288 289 [_editedCell endEditing: [aNotification object]]; 290 [self setNeedsDisplayInRect: 291 [self cellFrameAtRow: _editedRow column: _editedColumn]]; 292 _textObject = nil; 293// FIXME: Not sure to release it 294// RELEASE (_editedCell); 295 [_editedCell setEditable: NO]; 296 _editedCell = nil; 297 // Save values 298 row = _editedRow; 299 column = _editedColumn; 300 // Only then Reset them 301 _editedColumn = -1; 302 _editedRow = -1; 303 304 d = [[NSMutableDictionary alloc] initWithDictionary: 305 [aNotification userInfo]]; 306 [d setObject: [aNotification object] forKey: @"NSFieldEditor"]; 307 [[NSNotificationCenter defaultCenter] 308 postNotificationName: NSControlTextDidEndEditingNotification 309 object: self 310 userInfo: d]; 311 312 textMovement = [[aNotification userInfo] objectForKey: @"NSTextMovement"]; 313 if (textMovement) 314 { 315 switch ([(NSNumber *)textMovement intValue]) 316 { 317 case NSReturnTextMovement: 318 // Send action ? 319 break; 320/* 321 case NSTabTextMovement: 322 if([self _editNextEditableCellAfterRow: row column: column] == YES) 323 { 324 break; 325 } 326 [_window selectKeyViewFollowingView: self]; 327 break; 328 case NSBacktabTextMovement: 329 if([self _editPreviousEditableCellBeforeRow: row column: column] == YES) 330 { 331 break; 332 } 333 [_window selectKeyViewPrecedingView: self]; 334 break; 335*/ 336 } 337 } 338} 339 340- (BOOL) textShouldBeginEditing: (NSText *)textObject 341{ 342 NSLog(@"In matrix: textShouldBeginEditing:"); 343 if (_delegate && [_delegate respondsToSelector: 344 @selector(control:textShouldBeginEditing:)]) 345 return [_delegate control: self 346 textShouldBeginEditing: textObject]; 347 else 348 return YES; 349} 350 351- (BOOL) textShouldEndEditing: (NSText *)aTextObject 352{ 353 NSLog(@"In matrix: textShouldEndEditing:"); 354 if ([_delegate respondsToSelector: 355 @selector(control:textShouldEndEditing:)]) 356 { 357 if ([_delegate control: self 358 textShouldEndEditing: aTextObject] == NO) 359 { 360 NSBeep (); 361 return NO; 362 } 363 364 return YES; 365 } 366 367 if ([_delegate respondsToSelector: 368 @selector(control:isValidObject:)] == YES) 369 { 370 NSFormatter *formatter; 371 id newObjectValue; 372 373 formatter = [_cell formatter]; 374 375 if ([formatter getObjectValue: &newObjectValue 376 forString: [_textObject text] 377 errorDescription: NULL] == YES) 378 { 379 if ([_delegate control: self 380 isValidObject: newObjectValue] == NO) 381 return NO; 382 } 383 } 384 385 return [_editedCell isEntryAcceptable: [aTextObject text]]; 386} 387 388/* Prepare for Drag & Drop 389 * Copy from BMatrix of GWorkspace 390 */ 391- (void)mouseDown:(NSEvent*)theEvent 392{ 393 if (NO == [_editorDelegate respondsToSelector: 394 @selector(matrix:writeRows:inColumn:toPasteboard:)]) 395 { 396 [super mouseDown: theEvent]; 397 return; 398 } 399 else 400 { 401 int clickCount; 402 NSPoint lastLocation; 403 int row, col; 404 405 406 if (([self numberOfRows] == 0) || ([self numberOfColumns] == 0)) { 407 [super mouseDown: theEvent]; 408 return; 409 } 410 411 clickCount = [theEvent clickCount]; 412 413 if (clickCount > 2) { 414 return; 415 } 416 417 if (clickCount == 2) { 418 [self sendDoubleAction]; 419 return; 420 } 421 422 lastLocation = [theEvent locationInWindow]; 423 lastLocation = [self convertPoint: lastLocation 424 fromView: nil]; 425 426 if ([self getRow: &row column: &col forPoint: lastLocation]) { 427 // FIXME: Seens weird 428 // NSCell *cell = [[self cells] objectAtIndex: row]; 429 NSCell *cell = [self cellAtRow: row column: col]; 430 NSRect rect = [self cellFrameAtRow: row column: col]; 431 432 if ([cell isEnabled]) { 433 if (NSPointInRect(lastLocation, rect)) { 434 NSEvent *nextEvent; 435 BOOL startdnd = NO; 436 int dragdelay = 0; 437 438 [self deselectAllCells]; 439 [self selectCellAtRow: row column: col]; 440 [self sendAction]; 441 442 while (1) 443 { 444 nextEvent = [[self window] nextEventMatchingMask: 445 NSLeftMouseUpMask | NSLeftMouseDraggedMask]; 446 447 if ([nextEvent type] == NSLeftMouseUp) 448 { 449 [self mouseUp: nextEvent]; 450 break; 451 } 452 else if ([nextEvent type] == NSLeftMouseDragged) 453 { 454 if (dragdelay < 5) 455 { 456 dragdelay++; 457 } 458 else 459 { 460 startdnd = YES; 461 break; 462 } 463 } 464 } 465 466 if (startdnd == YES) 467 { 468 [self startExternalDragOnEvent: nextEvent]; 469 return; 470 } 471 } else 472 { 473 [super mouseDown: theEvent]; 474 } 475 } 476 } 477 } 478} 479 480- (void) mouseUp: (NSEvent *) event 481{ 482 [super mouseUp: event]; 483} 484 485/* Drag Source */ 486- (void) startExternalDragOnEvent: (NSEvent *) event 487{ 488 NSPoint dragPoint; 489 NSPasteboard *pb; 490 NSImage *dragIcon; 491 BOOL canDrag = NO; 492 int selectedRow, selectedCol; 493 494 dragPoint = [event locationInWindow]; 495 dragPoint = [self convertPoint: dragPoint fromView: nil]; 496 497 selectedRow = [self selectedRow]; 498 selectedCol = [self selectedColumn]; 499 500 pb = [NSPasteboard pasteboardWithName: NSDragPboard]; 501 canDrag = [_editorDelegate matrix: self 502 writeRows: selectedRow inColumn: selectedCol 503 toPasteboard: pb]; 504 if (canDrag == NO) 505 return; 506 507 dragIcon = AUTORELEASE([[NSImage alloc] initWithSize: NSMakeSize(8, 8)]); 508 509 [self dragImage: dragIcon 510 at: dragPoint 511 offset: NSZeroSize 512 event: event 513 pasteboard: pb 514 source: self 515 slideBack: YES]; 516} 517 518- (NSDragOperation) draggingSourceOperationMaskForLocal:(BOOL) isLocal; 519{ 520 if(isLocal) 521 return NSDragOperationMove; 522 else 523 return NSDragOperationNone; 524} 525 526/* Drag Destination */ 527- (void) setDropRow: (int) row 528 dropOperation: (ExtendedMatrixDropOperation) operation 529{ 530 if (row < 0) 531 { 532 _newDropRow = 0; 533 } 534 else if (operation == ExtendedMatrixDropOn) 535 { 536 if (row >= [self numberOfRows]) 537 _newDropRow = [self numberOfRows]; 538 } 539 else if (row > [self numberOfRows]) 540 { 541 /* Above */ 542 _newDropRow = [self numberOfRows]; 543 } 544 else 545 { 546 /* On */ 547 _newDropRow = row; 548 } 549 550 _newDropOperation = operation; 551} 552 553- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) sender 554{ 555 if (([sender draggingSource] == nil) || 556 (![[sender draggingSource] isKindOfClass: [self class]])) 557 { 558 /* Prevent drop from other view or applications */ 559 return NSDragOperationNone; 560 } 561 return [sender draggingSourceOperationMask]; 562} 563 564- (NSDragOperation) draggingUpdated: (id <NSDraggingInfo>) sender 565{ 566 BOOL gotCell = NO; 567 dropRow = -1, dropColumn = -1; 568 NSPoint p = [sender draggingLocation]; 569 570 p = [self convertPoint: p fromView: nil]; 571 gotCell = [self getRow: &dropRow column: &dropColumn forPoint: p]; 572 if (gotCell == YES) 573 { 574 NSRect cellFrame; 575 _newDropRow = dropRow; 576 _newDropOperation = ExtendedMatrixDropOn; 577 578 if ([_editorDelegate respondsToSelector: 579 @selector(matrix:validateDrop:proposedRow:proposedDropOperation:)]) 580 { 581 currentDragOperation = [_editorDelegate matrix: self 582 validateDrop: sender 583 proposedRow: _newDropRow 584 proposedDropOperation: ExtendedMatrixDropOn]; 585 } 586 587 if (currentDragOperation == NSDragOperationNone) 588 return NSDragOperationNone; 589 590 [self lockFocus]; 591 [self setNeedsDisplay: YES]; 592 [self displayIfNeeded]; 593 594 [[NSColor redColor] set]; 595 cellFrame = [self cellFrameAtRow: _newDropRow column: dropColumn]; 596 597 if (_newDropOperation == ExtendedMatrixDropAbove) 598 { 599 if (_newDropRow == 0) 600 { 601 cellFrame = NSMakeRect(cellFrame.origin.x, 602 cellFrame.origin.y + 1, 603 cellFrame.size.width, 604 1); 605 } 606 else if (_newDropRow == [self numberOfRows]) 607 { 608 cellFrame = NSMakeRect(cellFrame.origin.x, 609 cellFrame.origin.y + cellFrame.size.height, 610 cellFrame.size.width, 611 1); 612 } 613 else 614 { 615 cellFrame = NSMakeRect(cellFrame.origin.x, 616 cellFrame.origin.y, 617 cellFrame.size.width, 618 1); 619 } 620 } 621 622 NSFrameRectWithWidth(cellFrame, 2.0); 623 624 [_window flushWindow]; 625 [self unlockFocus]; 626 627 return currentDragOperation; 628 } 629 return NSDragOperationNone; 630} 631 632- (void) draggingExited: (id <NSDraggingInfo>) sender 633{ 634 [self setNeedsDisplay: YES]; 635} 636 637- (BOOL) performDragOperation: (id<NSDraggingInfo>)sender 638{ 639 if ([_editorDelegate respondsToSelector: 640 @selector(matrix:acceptDrop:row:proposedDropOperation:)]) 641 { 642 return [_editorDelegate matrix: self 643 acceptDrop: sender 644 row: dropRow 645 proposedDropOperation: _newDropOperation]; 646 } 647 else 648 return NO; 649} 650 651- (BOOL) prepareForDragOperation: (id<NSDraggingInfo>)sender 652{ 653 return YES; 654} 655 656- (void) concludeDragOperation:(id <NSDraggingInfo>)sender 657{ 658 [self setNeedsDisplay: YES]; 659} 660 661- (id) initWithFrame: (NSRect) frame 662{ 663 self = [super initWithFrame: frame]; 664 _editorDelegate = nil; 665 _newDropOperation = ExtendedMatrixDropOn; 666 return self; 667} 668 669- (void) dealloc 670{ 671 RELEASE(_editorDelegate); 672} 673 674- (void) setEditorDelegate: (id) delegate 675{ 676 ASSIGN(_editorDelegate, delegate); 677} 678 679- (id) editorDelegate 680{ 681 return _editorDelegate; 682} 683 684@end 685