
/* Generated by Interface Builder */

#import "ColumnSelectionHandler.h"
#import "Plot.h"
#import "defs.h"
#import <appkit/Font.h>
#import <appkit/Panel.h>
#import <appkit/Window.h>
#import <appkit/Matrix.h>
#import <appkit/View.h>
#import <appkit/ButtonCell.h>
#import <dpsclient/wraps.h>
#import <appkit/color.h>	/* only for "blanking out" parts of a matrix */
#import <appkit/graphics.h>	/* ditto */

/*
 * The job of this object is to keep the appearance of the Column Selection panel
 * in accord with the state of the program.  We do not update the Column Selection
 * panel every time a file is read in, since it is expected that this option will
 * not be heavily used.
 */

@implementation ColumnSelectionHandler

- init
{
/*
 * This global variable keeps track of whether the column selection window
 * has ever been displayed.  We can avoid updating after the selective
 * file removal if we have never been displayed.
 */ 
  everVisible = NO;
  return self;
}


/*
 * Arrange to blank out the disabled buttons.  The "right" way to do this
 * is probably to have a "RaggedMatrix" class which allows a different
 * number of columns in each row, but there are too many other issues
 * raised by that to deal with right now.  Note that having such a class
 * would allow us to correctly deal with the problem of correctly
 * redisplaying the matrix after a resize operation on the window.
 * (We currently get around that problem by making ourself the window's delegate 
 * and defining windowDidResize:, and in that method calling
 * perform:with:afterDelay:cancelPrevious, but this is highly suboptimal.)
 */
- eraseDisabledCells:sender
{
  int n, j, numrows, numcols, ncurves;
  NXRect r;

  [columnMatrix getNumRows:&numrows numCols:&numcols];
  [columnMatrix lockFocus];
  NXSetColor([columnMatrix backgroundColor]);
/* The NXSetColor call has to come after the lockFocus; otherwise, when
 * this method is called by fixPanel, the color always comes out black
 * even if something else is specifically requested.  The weird part is,
 * when this method is called by fixRow, the color is correct when the
 * NXSetColor call is outside the lockFocus.
 */
  for (n=0; n<[plotParam nFiles]; n++) {
    ncurves = [plotParam nCurves:n];
    if (ncurves < numcols-1) {
      for (j=ncurves; j<numcols; j++) {
        [columnMatrix getCellFrame:&r at:n :j+1]; /* note the j+1 */
        NXRectFill(&r);
      }
    }
  }
  [columnMatrix unlockFocus];
  NXPing();
  return self;
}


- removeAll:sender
/* This method is called by the "remove all files" method of the Plot object. */
{
  int n, j;
  int numrows, numcols;
  NXSize cellsize, intercell;
  NXCoord dy;

  /* Do something only if the window has ever been displayed  */
  if (everVisible) {
    [columnMatrix getNumRows:&numrows numCols:&numcols];
    [columnMatrix getCellSize:&cellsize];
    [columnMatrix getIntercell:&intercell];
    /*
     * Wipe out everything.
     */
    dy = (NXCoord)(numrows - 1) * (cellsize.height + intercell.height);
    [columnMatrix moveBy:0.0 :dy];
    /*
     * Before doing renewRows, set the state of all the cells to 0; this is
     * because the renewRows method just recycles the cells, and later on
     * we may find ourselves using a cell which has state=1 when we don't
     * want such a beast.  Also enable all cells (paranoia strikes).
     */
    for (n=0; n<numrows; n++) {
      for (j=0; j<numcols; j++) {
	[[columnMatrix cellAt:n :j] setState:0];
	[[columnMatrix cellAt:n :j] setEnabled:YES];
      }
    }
    [columnMatrix renewRows:1 cols:1];
    [columnMatrix sizeToCells];
    [[columnMatrix cellAt:0 :0] setEnabled:YES];
    [[columnMatrix cellAt:0 :0] setType:NX_TOGGLE];
    [[columnMatrix cellAt:0 :0] setState:1];
    [filenameMatrix moveBy:0.0 :dy];
    [filenameMatrix renewRows:1 cols:1];
    [[filenameMatrix cellAt:0 :0] setStringValue:"filename"];
    [filenameMatrix sizeToCells];
    [columnText renewRows:1 cols:1];
    [columnText sizeToCells];
    /* Do some drawing now.  Display the view. */
    [[columnMatrix window] display];
    NXPing();			/* needed??? */
  }
  return self;
}


- fixPanel:sender
/*
 * The select column button activates this method; it is also called from
 * windowDidResize, from Plot.m in postludeToReading, and by the update
 * button on the panel.  We have to fix up the column selection panel
 * so that it's correct.
 */
{
  int n, j;
  int nfilestotal = [plotParam nFiles], maxcols = 0, ncurves;
  char title[80];
  int numrows, numcols;
  BOOL one_on;
  NXSize cellsize, intercell;
  NXCoord dy;
  int * turned_on;

  if (everVisible == NO) everVisible = YES;

  [columnMatrix getNumRows:&numrows numCols:&numcols];
  [columnMatrix getCellSize:&cellsize];
  [columnMatrix getIntercell:&intercell];

  /*
   * We have to create some additional rows and perhaps some additional columns.
   */
  // First figure out how many rows and columns the matrices should have
  for (n=0; n<nfilestotal; n++) {
    maxcols = MAX(maxcols, [plotParam nCurves:n]);
  }
  maxcols++;			/* this is the number of columns needed */
  if (numrows != nfilestotal  ||  numcols != maxcols) {
    /* The renewRows:cols: message might have the side effect of setting
     * the state of all the cells of the matrix back to 0; this is undesired,
     * so before changing the number of rows and/or columns in the matrix,
     * make sure we remember which cells in each row were turned on.
     */
    turned_on = (int *)malloc(numrows * sizeof(int)); /* should do error check */
    for (n=0; n<numrows; n++) {
      turned_on[n] = 0;		/* cautious initialization */
      for (j=0; j<numcols; j++) {
	if ([[columnMatrix cellAt:n :j] state] == 1) {
	  turned_on[n] = j;
	  break;
	}
      }
    }
    [columnMatrix renewRows:nfilestotal cols:maxcols];
    for (n=numrows; n<nfilestotal; n++) {
      [[columnMatrix cellAt:n :0] setState:1];
      for (j=1; j<maxcols; j++) {
	[[columnMatrix cellAt:n :j] setState:0];
      }
    }      
    /* And now turn cells back on. */
    for (n=0; n<numrows; n++) {
      for (j=0; j<numcols; j++) {
	[[columnMatrix cellAt:n :j] setState:0];
      }
      [[columnMatrix cellAt:n :turned_on[n]] setState:1];
    }
    free((void *)turned_on);
  }
  [columnMatrix sizeToCells];
  dy = (float)(nfilestotal - numrows) * (cellsize.height + intercell.height);
  [columnMatrix moveBy:0.0 :-dy];
  // Some of the cells may come in with type NULLCELL; fix them here
  for (n=0; n<nfilestotal; n++) {
    for (j=0; j<maxcols; j++) {
      [[columnMatrix cellAt:n :j] setEnabled:YES];
      [[columnMatrix cellAt:n :j] setType:NX_TOGGLE];
    }
  }
  // Make sure exactly one button is turned on in each row
  for (n=0; n<nfilestotal; n++) {
    one_on = NO;
    for (j=0; j<maxcols; j++) {
      if ([[columnMatrix cellAt:n :j] state] == 1) {
	one_on = YES;
	break;
      }
    }
    if (!one_on)
      [[columnMatrix cellAt:n :0] setState:1];
  }
  // We surround the [columnMatrix display] and [self eraseDisabledCells]
  // calls with the disableFlushWindow, reenableFlushWindow, and flushWindow
  // calls to prevent the disabled matrix cells from being visible for a long
  // time while they're drawn and then erased.
  [columnPanel disableFlushWindow];
  [columnMatrix display];
  // Disable cells that shouldn't appear.
  for (n=0; n<nfilestotal; n++) {
    ncurves = [plotParam nCurves:n];
    if (ncurves < maxcols-1) {
      for (j=ncurves; j<maxcols; j++) {
        [[columnMatrix cellAt:n :j+1] setType:NX_NULLCELL]; /* note the j+1 */
        [[columnMatrix cellAt:n :j+1] setEnabled:NO];       /* again j+1    */
      }
    }
  }

  // We have to put the makeKeyAndOrderFront before the eraseDisabledCells
  // method, because eraseDisabledCells does a lockFocus and lockFocus needs
  // a real window to draw into.  We put the makeKeyAndOrderFront here rather
  // than at the beginning of this method because this way the disabled cells
  // of the matrix are visible for a shorter period of time.
  [columnPanel makeKeyAndOrderFront:self];

  // Now erase those disabled cells.
  [self eraseDisabledCells:self];
  [columnPanel reenableFlushWindow];
  [columnPanel flushWindowIfNeeded];
  NXPing();			/* needed? */

  // That finishes with the columnMatrix; now for the filename Matrix.
  [filenameMatrix renewRows:nfilestotal cols:1];
  for (n=0; n<nfilestotal; n++) {
    if (!strncmp([plotParam filename:(unsigned)n], "pasteboard", 10))
      sprintf(title, "pasteboard");
    else
      sprintf(title, strrchr([plotParam filename:(unsigned)n], '/') + 1);
    [[filenameMatrix cellAt:n :0] setStringValue:title];
  }
  [filenameMatrix sizeToCells];
  [filenameMatrix moveBy:0.0 :-dy];
  [filenameMatrix display];

  // And now for the columnText matrix.
  [columnText renewRows:1 cols:maxcols];
  for (j=1; j<=maxcols; j++) {
    sprintf(title, "%d", j);
    [[columnText cellAt:0 :(j-1)] setStringValue:title];
  }
  [columnText sizeToCells];
  [columnText display];

  return self;
}


- fixRow:sender
/* The sender is columnMatrix in the column selection panel; we want to
 * turn off some buttons in the matrix row.
 */
{
  int row, col, i, prev_col=0, numrows, numcols;

  row = [sender selectedRow];
  col = [sender selectedCol];
  [sender getNumRows:&numrows numCols:&numcols];

  // Find out what column was previously highlighted:
  for (i=0; i<numcols; i++) {
    if ( [[sender cellAt:row :i] state] == 1  &&  i != col) {
      prev_col = i;
      break;
    }
  }
  // adjust the matrix
  for (i=0; i<numcols; i++) {
    [ [sender cellAt:row :i] setState:0];
  }
  [ [sender cellAt:row :col] setState:1];

  // The disableFlushWindow, reenableFlushWindow, and flushWindow methods
  // bracket the [sender display] and [self eraseDisabledCells] methods, with
  // the result that the disabled cells are not visibly drawn on the screen.
  // This is the way it should be.
  [columnPanel disableFlushWindow];
  [sender display];
  [self eraseDisabledCells:self];
  [columnPanel reenableFlushWindow];
  [columnPanel flushWindowIfNeeded];

  // swap the pointers as requested
  [plotParam swapColumns:prev_col :col  forFileNumber:row];
  return self;
}

/*
 * We call fixPanel here, rather than eraseDisabledCells (which is more
 * logical), because for some reason eraseDisableCells doesn't seem to work
 * properly!  The method fixPanel: does a lot more work than is really
 * needed.  Sigh... This should all be cleaned up.
 */
- windowDidResize:sender
{
  [self perform:@selector(fixPanel:)
        with:self
        afterDelay:1		/* wait 0.001 second */
        cancelPrevious:YES];
  return self;
}


/*
 * This is called by the selective file removal code in Plot.m.
 */
- update:sender
{
  int n, nfilestotal = [plotParam nFiles], num_removed = 0;
  NXSize cellsize, intercell;
  NXCoord dy;

  /* Do something only if the window has ever been displayed  */
  if (everVisible) {
    [columnMatrix getCellSize:&cellsize];
    [columnMatrix getIntercell:&intercell];
    /*
     * Count down the file removal panel, removing rows as needed.
     */
    for (n = nfilestotal-1; n>=0; n--) {
      if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
	/* This file is to be removed. */
	num_removed++;
	[columnMatrix removeRowAt:n andFree:YES];
	[filenameMatrix removeRowAt:n andFree:YES];
      }
    }
    [columnMatrix sizeToCells];
    [filenameMatrix sizeToCells];
    if (num_removed > 0) {
      dy = (NXCoord)(num_removed) * (cellsize.height + intercell.height);
      [columnMatrix moveBy:0.0 :dy];
      [filenameMatrix moveBy:0.0 :dy];
    }
    /* Do some drawing now.  Display the view. */
    [[columnMatrix window] display];
    NXPing();			/* needed??? */
  }

  return self;
}



@end
