/* Implementation of Matrix class
 *
 * Copyright (C)  1993, 1994, 1995  The Board of Trustees of  
 * The Leland Stanford Junior University.  All Rights Reserved.
 *
 * Authors: Scott Francis, Mike Gravina, Fred Harris, Paul Kunz, Tom Pavel,
 *          Imran Qureshi, and Libing Wang
 *	    Mike L. Kienenberger (Alaska)
 *
 * This file is part of an Objective-C class library for a window system
 *
 * Matrix.m,v 1.101 1995/12/13 22:33:04 fedor Exp
 */

/* Implementation notes:
 *
 * When reducing the size of the matrix, NeXTSTEP doesn't necessarily
 * free the unused cells.   This "feature" has not been implemented here
 *
 */
 
#include "Matrix.h"

/* Required for implementation: */
#include "ButtonCell.h"
#include <objc/List.h>
#include <objc/objc-api.h>
#include "stdmacros.h"
#include <stdlib.h>
#include <objc/typedstream2.h>

@interface View(WidgetSet)
- _init;
- _setAdjustedFrame:(const NXRect *)aFrame;
- _sizeTo:(NXCoord)width :(NXCoord)height;
@end

@implementation Matrix

/* Handy macro: */
#define CELL_INDEX(r,c) (r)*numCols+(c)

/* Public methods */

// + initialize;
+ setCellClass:factoryId
{
  return [self notImplemented:_cmd];
}

- initFrame:(const NXRect *)frameRect
{
    [super initFrame:frameRect];
    cellSize.width  = 100;
    cellSize.height = 17;
    intercell.width  = 0;
    intercell.height = 0;

    if (!cellList)
	cellList = [[List alloc] init];
    [self setCellClass:[ActionCell class]];
    selectedCell = nil;
    selectedRow = -1;
    selectedCol = -1;
    return self;
}
   
- initFrame:(const NXRect *)frameRect mode:(int)aMode prototype:aCell
    numRows:(int)rowsHigh numCols:(int)colsWide
{
    [self initFrame:frameRect];
    [self setMode:aMode];
    [self setPrototype:aCell];
    [self renewRows:rowsHigh cols:colsWide];
    return self;
}

- initFrame:(const NXRect *)frameRect mode:(int)aMode cellClass:factoryId 
    numRows:(int)rowsHigh numCols:(int)colsWide
{
    [self initFrame:frameRect];
    [self setMode:aMode];
    [self setCellClass:factoryId];
    [self renewRows:rowsHigh cols:colsWide];
    return self;
}

- free
{
   [cellList freeObjects];
   cellList = [cellList free];
   if ( protoCell ) {
	[protoCell free];
   }
   return [super free];
}

- setCellClass:factoryId
{
   cellClass = factoryId;
   return self;
}

- prototype
{
    return protoCell;
}

- setPrototype:aCell
{
    protoCell = aCell;
  /* give it new controlView */
    [protoCell _setFrame:(NXRect *)0 inView:self];
    return self;
}

- makeCellAt:(int)row :(int)col
{
    ActionCell	*newCell;
    NXRect	cellFrame;

    if ( protoCell ) {
	newCell = [protoCell copy];
    } else {
	newCell = [[cellClass alloc] init];
    	if ([newCell isKindOf:[ButtonCell class]]) {
	    [(ButtonCell *)newCell _setMode:radioBehavior];
	}
    }
    [self getCellFrame:&cellFrame at:row :col];
    [newCell _setFrame:&cellFrame inView:self];
    if ( widgetid ) {
	[newCell _managedBy:self];
    }
    return newCell;
}

- (int)mode
{
  [self notImplemented:_cmd];
  return 0;
}

- setMode:(int)aMode
{
    switch(aMode) {
       case NX_RADIOMODE:
          radioBehavior = YES;
          break;
       case NX_LISTMODE:	
          radioBehavior = NO;
          break;
       default:
          break;
    }
    return self;
}

- setEmptySelectionEnabled:(BOOL)flag
{
    allowEmptySel = flag;
    return self;
}

- (BOOL)isEmptySelectionEnabled
{
    return allowEmptySel;
}

- sendAction:(SEL)aSelector to:anObject forAllCells:(BOOL)flag
{
   id 	*members = NX_ADDRESS(cellList);
   id 	*last = members + [cellList count];
   BOOL cont = YES;
   
   while (cont && members < last) {
      cont = [anObject perform:aSelector with:*members] ? YES : NO;
      members++;
   }
   return self;
}

- cellList
{
   return cellList;
}

- selectedCell
{
   return selectedCell;
}

- getSelectedCells:(List *)aList
/* Implementation of selectedCell is very weak right now since Matrix
 * only allows one cell to be selected at any time 
 */
{
    List	*selList;
    if ( aList ) {
        selList = aList;
    } else {
        selList = [[List alloc] init];
    }
    
    /* currently, only one cell can be selected */
    if (selectedCell != nil)  [selList addObject:selectedCell];
    
    return selList;
}

- (int)selectedRow
{
   return selectedRow;
}

- (int)selectedCol
{
   return selectedCol;
}


- setSelectionByRect:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- (BOOL)isSelectionByRect
{
  [self notImplemented:_cmd];
  return 0;
}

- setSelectionFrom:(int)startPos to:(int)endPos anchor:(int)anchorPos 
              lit:(BOOL)lit
{
  return [self notImplemented:_cmd];
}

- clearSelectedCell
{
   Cell    *temp = selectedCell;

   [selectedCell setState:0];
   selectedCell = nil;
   return temp;
}

- selectCellAt:(int)row :(int)col
{
   if (row < 0 || col < 0) {
      return [self clearSelectedCell];
   }

   selectedRow = row;
   selectedCol = col;
   selectedCell = [self cellAt:row :col];
   [selectedCell setState:1];
   return selectedCell;
}

- selectAll:sender
{
    int		on = 1;
    
    [cellList makeObjectsPerform:@selector(setState) with:(id)&on];
    return self;
}

- selectCell:aCell
{
    Cell	*tempCell;
    int		i;
    
   selectedCell = [self getRow:&selectedRow andCol:&selectedCol ofCell:aCell];
    
    /* Radio buttons:- set all off except the one selected */
    if (selectedCell) {
	if (radioBehavior) {	/* Radio */
    	    i = [cellList count];
    	    while ( i-- ) {
		tempCell = [cellList objectAt:i];
		[tempCell setState:0];
	    }
	[selectedCell setState:1];
    	}
    }
   return selectedCell;
}

- selectCellWithTag:(int)anInt
{
    Cell	*aCell;
    int		i;
    
    i = [cellList count];
    while ( i-- ) {
        aCell = [cellList objectAt:i];
	if ( [cell tag] == anInt ) {
	    [self selectCell:aCell];
	    return self;
	}
    }
    return nil;
}
- getCellSize:(NXSize *)theSize

{
   theSize->width  = cellSize.width;
   theSize->height = cellSize.height;
   return self;
}

- setCellBackgroundGray:(float)value
{
    /* FIXME! */
    return self;
    
}

- setCellSize:(const NXSize *)aSize
{
   cellSize.width  = aSize->width;
   cellSize.height = aSize->height;
   return self;
}

- getIntercell:(NXSize *)theSize
{
   theSize->width  = intercell.width;
   theSize->height = intercell.height;
   return self;
}

- setIntercell:(const NXSize *)aSize
{
   intercell.width  = aSize->width;
   intercell.height = aSize->height;
   return self;
}

- setEnabled:(BOOL)flag
{
    int i, count;
    
    count = [cellList count];
    for (i=0; i < count; i++) {
    	[[cellList objectAt:i] setEnabled:flag];
    }
    return self;
}


- setScrollable:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- font
{
    return font;
}

- setFont:fontObj
{
    font = fontObj;
    return self;
}

- (float)backgroundGray
{
 /*
  * only used in HippoDraw to make text same color as background so to make
  * test disappear.   We can work around not having this. 
  */
    return 0.0;
}


- setBackgroundGray:(float)value
{
  return [self notImplemented:_cmd];
}

- setBackgroundColor:(NXColor)color
{
  return [self notImplemented:_cmd];
}

- (NXColor)backgroundColor
{
  NXColor color;
  [self notImplemented:_cmd];
  return color;
}

- setBackgroundTransparent:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- (BOOL)isBackgroundTransparent
{
  [self notImplemented:_cmd];
  return 0;
}

- setCellBackgroundColor:(NXColor)color
{
  return [self notImplemented:_cmd];
}

- (NXColor)cellBackgroundColor
{
  NXColor color;
  [self notImplemented:_cmd];
  return color;
}

- setCellBackgroundTransparent:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- (BOOL)isCellBackgroundTransparent
{
  [self notImplemented:_cmd];
  return 0;
}

- (float)cellBackgroundGray
{
  [self notImplemented:_cmd];
  return 0;
}

- setState:(int)value at:(int)row :(int)col
{
   return [[self cellAt:row :col] setIntValue:value];
}

- setIcon:(const char *)name at:(int)row :(int)col
{
    return [[self cellAt:row :col] setIcon:name];
}


- setTitle:(const char *)aString at:(int)row :(int)col
{
  return [self notImplemented:_cmd];
}

- (int)cellCount
{
   return [cellList count];
}

- getNumRows:(int *)rowCount numCols:(int *)colCount
{
   *rowCount = numRows;
   *colCount = numCols;
   return self;
}

- cellAt:(int)row :(int)col
{
   return [cellList objectAt:CELL_INDEX(row,col)];
}


- getCellFrame:(NXRect *)theRect at:(int)row :(int)col
{
    theRect->origin.x = col * (cellSize.width  + intercell.width);
    theRect->origin.y = row * (cellSize.height + intercell.height);
    theRect->origin.y = frame.size.height 
                        - theRect->origin.y - cellSize.height;
    theRect->size.width  = cellSize.width;
    theRect->size.height = cellSize.height;
    return self;
}

- getRow:(int *)row andCol:(int *)col ofCell:aCell
{
   int index = [cellList indexOf:aCell];

   if (index < 0) {
      return nil;
   } else {
      *col = index % numCols;
      *row = index / numCols;
      return aCell;
   }
}

- getRow:(int *)row andCol:(int *)col forPoint:(const NXPoint *)aPoint
{
  return [self notImplemented:_cmd];
}

- renewRows:(int)newRows cols:(int)newCols
{
  /* Note: NeXTSTEP does not free unused cells when new size is smaller. */
    int		diff;
    
    diff = newRows - numRows;
	if (diff < 0) {
		selectedCell = NULL;
		selectedRow = -1;
		selectedCol = -1;
	}
	while (diff > 0) {
		[self addRow];
		diff--;
	}
    while (diff < 0) {
	    [self removeRowAt:(numRows-1) andFree:YES];
		diff++;
	}
    diff = newCols - numCols;
	if (diff < 0) {
		selectedCell = NULL;
		selectedRow = -1;
		selectedCol = -1;
	}
	while (diff > 0) {
	    [self addCol];
		diff--;
	}
	while (diff < 0) {
	    [self removeColAt:(numCols-1) andFree:YES];
		diff++;
	}
    [cellList makeObjectsPerform:@selector(setState:) with:(id)&diff];
    return self;
}

- putCell:newCell at:(int)row :(int)col
{
   Cell    *old = [cellList replaceObjectAt:CELL_INDEX(row,col) with:newCell];

   return old;
}

- addRow
{
   [self insertRowAt:numRows];
   return self;
}

- insertRowAt:(int)row
{
   Cell		*aCell;
   int 		i;
   
   numRows++;
   if ( numCols <= 0 ) {
      return self;
   }
   for (i=0; i < numCols; i++) {
      aCell = [self makeCellAt:row :i];
      [cellList insertObject:aCell at:CELL_INDEX(row,i)];
   }
   return self;
}

- addCol
{
   [self insertColAt:numCols];
   return self;
}

- insertColAt:(int)col
{
   Cell		*aCell;
   int 		i;
   
   numCols++;
   if ( numRows <= 0 ) {
       return self;
   }
   for (i=0; i < numRows; i++) {
      aCell = [self makeCellAt:i :col];
      [cellList insertObject:aCell at:CELL_INDEX(i,col)];
   }
   return self;
}

- removeColAt:(int)col andFree:(BOOL)flag
{
    Cell	*aCell;
    int		i;
    
    i = numRows;
    while( i-- ) {
        aCell = [cellList removeObjectAt:CELL_INDEX(i,col)];
	if (flag) 
	    [aCell free];
    }
    numCols--;
    return self;
}

- removeRowAt:(int)row andFree:(BOOL)flag
{
    Cell	*aCell;
    int 	i;

    i = numCols;
    while ( i-- ) {
	aCell = [cellList removeObjectAt:CELL_INDEX(row, i)];
        if (flag)
	    [aCell free];
    }
    numRows--;
    return self;
}

- findCellWithTag:(int)anInt
{
    Cell	*aCell;
    int		i;
    
    i = [cellList count];
    while( i-- ) {
        aCell = [cellList objectAt:i];
	if ( [aCell tag] == anInt ) 
	    return aCell;
    }
    return nil;
}

- setTag:(int)anInt at:(int)row :(int)col
{
   return [[self cellAt:row :col] setTag:anInt];
}

- target
{
    return target;
}

- setTarget:anObject
{
    target = anObject;
    return self;
}

- setTarget:anObject at:(int)row :(int)col
{
   [[self cellAt:row :col] setTarget:anObject];
   return self;
}

- (SEL)action
{
    return action;
}

- setAction:(SEL)aSelector
{
    action = aSelector;
    return self;
}

- (SEL)doubleAction
{
    return doubleAction;
}

- setDoubleAction:(SEL)aSelector
{
    doubleAction = aSelector;
    return self;
}

- (SEL)errorAction
{
    return errorAction;
}

- setErrorAction:(SEL)aSelector
{
    errorAction = aSelector;
    return self;
}

- setAction:(SEL)aSelector at:(int)row :(int)col
{
   [[self cellAt:row :col] setAction:aSelector];
   return self;
}

- setTag:(int)anInt target:anObject action:(SEL)aSelector 
      at:(int)row :(int)col
{
    Cell	*aCell;

    aCell = [self cellAt:row :col];
    [aCell setTag:anInt];
    [aCell setTarget:anObject];
    [aCell setAction:aSelector];
    return self;
}

- setAutosizeCells:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- (BOOL)doesAutosizeCells
{
  [self notImplemented:_cmd];
  return 0;
}

- sizeTo:(float)width :(float)height
{
  return [self notImplemented:_cmd];
}

- sizeToCells
{
    if (numCols)
        frame.size.width = numCols*cellSize.width+
            (numCols-1)*intercell.width;
    else
        frame.size.width = 0;

    if (numRows)
        frame.size.height = numRows*cellSize.height+
            (numRows-1)*intercell.height;
    else
        frame.size.height = 0;
    
    return self;
}

- sizeToFit
{
    int i;
    NXSize theSize,maxSize={0.0,0.0};
    
    i = [cellList count];
    while ( i-- ) {
        [[cellList objectAt:i] calcCellSize:&theSize];
        maxSize.width = MAX(maxSize.width, theSize.width);
        maxSize.height= MAX(maxSize.height,theSize.height);
    }
    cellSize = maxSize;
    return [self sizeToCells];
}

- validateSize:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- calcSize
{
  return [self notImplemented:_cmd];
}

- drawCell:aCell
{
  return [self notImplemented:_cmd];
}

- drawCellInside:aCell
{
  return [self notImplemented:_cmd];
}

- drawCellAt:(int)row :(int)col
{
  return [self notImplemented:_cmd];
}

- highlightCellAt:(int)row :(int)col lit:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
  return self;
}

- display
{
  return self;
}

- setAutoscroll:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- scrollCellToVisible:(int)row :(int)col
{
 /* This is highly desireable frill for phase 2 */
    return self;
}

- setReaction:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- (int)mouseDownFlags
{
  [self notImplemented:_cmd];
  return 0;
}

- mouseDown:(NXEvent *)theEvent
{
  return [self notImplemented:_cmd];
}

- (BOOL)performKeyEquivalent:(NXEvent *)theEvent
{
  [self notImplemented:_cmd];
  return 0;
}

- sendAction:(SEL)theAction to:theTarget
{
    SEL		a = theAction;
    id		t = theTarget;
    
    if (!a) {
        a = action;
    }
    if ( !t ) {
        t = target;
    }
    return [super sendAction:a to:t];
}

- sendAction
{
    if ( selectedCell && [selectedCell action] ) {
        if ( [selectedCell target] ) {
	    [[selectedCell target] perform:[selectedCell action] with:self];
	} else {
	    [target perform:[selectedCell action] with:self];
	}
    } else {
        [target perform:action with:self];
    }

    return self;
}


- sendDoubleAction
{
  return [self notImplemented:_cmd];
}

- textDelegate
{
    return textDelegate;
}

- setTextDelegate:anObject
{
    textDelegate = anObject;
    return self;
}

- (BOOL)textWillEnd:textObject
{
  [self notImplemented:_cmd];
  return 0;
}

- (BOOL)textWillChange:textObject
{
  [self notImplemented:_cmd];
  return 0;
}

- textDidEnd:textObject endChar:(unsigned short)whyEnd
{
  return [self notImplemented:_cmd];
}

- textDidChange:textObject
{
  return [self notImplemented:_cmd];
}

- textDidGetKeys:textObject isEmpty:(BOOL)flag
{
  return [self notImplemented:_cmd];
}

- selectText:sender
{
  return [self notImplemented:_cmd];
}

- selectTextAt:(int)row :(int)col
{
  return [self notImplemented:_cmd];
}

- setPreviousText:anObject
{
  return [self notImplemented:_cmd];
}

- setNextText:anObject
{
  return [self notImplemented:_cmd];
}

- (BOOL)acceptsFirstMouse
{
  [self notImplemented:_cmd];
  return 0;
}

- write:(NXTypedStream *)stream
{
  return self;
}

- read:(TypedStream*)stream
{
	Cell	*aCell;
	char 	*cellClassName;
	int 	i, h, w;

        [super read:stream];
      /* A matrix with one row will have its cell ivar set to the
       * cell that is in the cellList.   We must set it to nil
       * so that Control super class wouldn't attempt to use it.
       */
        if ( cell ) {
	    cell = nil;
        }
        objc_read_object(stream,&cellList);

        objc_read_type(stream,"*",&cellClassName);

        cellClass = objc_get_class(cellClassName);

        objc_read_object(stream, &protoCell);

	objc_read_types(stream, "ii", &w, &h);
	cellSize.width  = w;
	cellSize.height = h;
        objc_read_types(stream,"ii",&numRows,&numCols);
    	objc_read_types(stream, "ii", &selectedRow, &selectedCol);
        objc_read_type(stream,"i",&radioBehavior);

	i = [cellList count];
	while ( i-- ) {
	    aCell = [cellList objectAt:i];
	    if ([aCell isKindOf:[ButtonCell class]]) {
		[(ButtonCell *)aCell _setMode:radioBehavior];
	    }
	}
        return self;
}

- awake
{
    int i;
    [super awake];

    instancename = "Matrix";

    /* replace these later */
    intercell.width  = 0;
    intercell.height = 0;
    
    if (!cellList)
	cellList = [[List alloc] init];
    
    /* maybe the following should be moved to awakeFromNib? */
    i = [cellList count];
    while ( i-- )
    {
	id newCell = [cellList objectAt:i];
	[newCell _setControlView:self];
	if ( widgetid )  [newCell _managedBy:self];
    }

    if ((-1 != selectedRow) && (-1 != selectedCol))
       selectedCell = [self cellAt:selectedRow :selectedCol];
    else selectedCell = nil;

    return self;
}

- resetCursorRects
{
  return [self notImplemented:_cmd];
}

// + newFrame:(const NXRect *)frameRect;
// + newFrame:(const NXRect *)frameRect mode:(int)aMode prototype:aCell 
//    numRows:(int)rowsHigh numCols:(int)colsWide;
// + newFrame:(const NXRect *)frameRect mode:(int)aMode cellClass:factoryId 
//    numRows:(int)rowsHigh numCols:(int)colsWide;


- (int)getMode
{
    return radioBehavior;
}

- _init
{
    [super _init];
    instancename = "Matrix";
    return self;
}

- _calcInterCellSize
{
    intercell.width  = frame.size.width  - (numCols) * cellSize.width;    
    intercell.height = frame.size.height - (numRows) * cellSize.height;
    if ( numCols > 1 ) {
	intercell.width /= (numCols - 1);
    } else {
	intercell.width = 0;
    }
    if ( numRows > 1 ) {
	intercell.height /= (numRows -1);
    } else {
	intercell.height = 0;
    }
    return self;
}

- _managedBy:parent wid:(void*)widget
{
    int                 i, count;
    int			row, col;
    NXRect		cellRect;
    id			myCell;

    [super _managedBy:parent wid:widget];
    [self _calcInterCellSize];
    count = [cellList count];
    for (i = 0; i < count; i++) {
	myCell = [cellList objectAt:i];
	[self getRow:&row andCol:&col ofCell:myCell];
	[self getCellFrame:&cellRect at:row :col];
	[myCell _setFrame:&cellRect inView:self];
	[myCell _managedBy:self];
    }
    return self;
}

- _destroy
{
    Cell	*aCell;
    int		i;
    
    [super _destroy];
    i = [cellList count];
    while ( i-- ) {
        aCell = [cellList objectAt:i];
	[aCell _destroy];
    }

    return self;
}

- _setAdjustedFrame:(const NXRect *)aFrame
{
  /* This method adjusts the position and size of the matrix's
   * frame to accomodate widgets that need a larger frame
   * due to the selection border.   It uses it's super view 
   * (Control) by temporarily setting a cell.
   */

    cell = [cellList objectAt:0];
    [super _setAdjustedFrame:aFrame];
    cell = nil;
    return self;
}

- _sizeTo:(NXCoord)width :(NXCoord)height
{
  /* Temporarily set a cell, so super (Control) can do the work */

    cell = [cellList objectAt:0];
    [super _sizeTo: width :height];
    cell = nil;
    return self;
}

@end
