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