/*
**  ExtendedMatrix.m
**
**  Copyright (c) 2003
**
**  Author: Yen-Ju Chen <yjchenx@hotmail.com>
**
**  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 <AppKit/AppKit.h>

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 <NSDraggingInfo>) 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 <NSDraggingInfo>) 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 <NSDraggingInfo>) sender
{
  [self setNeedsDisplay: YES];
}

- (BOOL) performDragOperation: (id<NSDraggingInfo>)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<NSDraggingInfo>)sender
{
  return YES;
}

- (void) concludeDragOperation:(id <NSDraggingInfo>)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
