// $Id: sheet.cc,v 1.53 1998/10/20 19:08:48 cthulhu Exp $

#include "sheet.hh"
#include "canvas.hh"
#include "list.hh"

Sheet::Sheet(char *nam,Tcl_Interp *intrp) {
    char *st;
    char command[256];
    int code;
    int i;

    interp = intrp;
    
    print_range.start.col = 0;
    print_range.end.col = 0;
    print_range.start.row = 0;
    print_range.end.row = 0;

    for(i=0; i<MAX_GRAPHS; i++) 
        graphs[i] = NULL;
    graphs_cnt = 0;

    max_row = DEFMAXROW;
    max_col = DEFMAXCOL;
    max_col_used = 0;
    max_row_used = 0;
    min_col_used = 100000;
    min_row_used = 100000;
    display_cell_borders = TRUE;
    page_borders = FALSE;
    borderItemId = -1;

    stack = new Stack_elem[32];

    Tcl_InitHashTable(&cells,2);
    Tcl_InitHashTable(&dirty_cells,2);

    Tcl_InitHashTable(&row_formats,1);
    Tcl_InitHashTable(&col_formats,1);

    default_font.family = DEFAULT_FONT_FAMILY;
    default_font.size = DEFAULT_FONT_SIZE;
    default_font.italics = DEFAULT_FONT_ITALICS;
    default_font.bold = DEFAULT_FONT_BOLD;

    default_font_name = getFont(interp,default_font);
   
    fill_widget_names(nam);

    sprintf(command,"defCellWidth");
    code = Tcl_Eval(interp,command);
    if (code != TCL_OK) 
        internal_error();
    def_cell_width = atoi(interp->result);
    
    sprintf(command,"defCellHeight");
    code = Tcl_Eval(interp,command);
    if (code != TCL_OK) 
        internal_error();
    def_cell_height = atoi(interp->result);
    
        
    canvas_info = new Canvas;
    global_canvas_info = canvas_info;
    canvas_info->cmdPtr = NULL;
    canvas_info->sheet = (void*)this;
    iteration = 0;
    automatic_recalc = 255;
    recalculate_before_saving = TRUE;
    empty = TRUE;

    event_it=NULL;
    undo_on=0;
    redo_on=0;
}

void Sheet::add_graph_deps(Graph *gr) {
  Range_iter rang(gr->range);
  Cell *cell2;
  Ref rf;

  for(rang.first(rf); !rang.last();  rang.next(rf)){
      
      cell2 = cellFind(rf.col,rf.row);
      cell2->addDependence(gr, GRAPH_DEP);
      
  }


}

void Sheet::remove_graph_deps(Graph *gr) {
  Range_iter rang(gr->range);
  Cell *cell2;
  Ref rf;

  for(rang.first(rf); !rang.last();  rang.next(rf)){
      
      cell2 = cellFind(rf.col,rf.row);
      cell2->removeDependence(gr, GRAPH_DEP);
      
  }


}


void Sheet::fill_widget_names(char *nam) {
    char command[256];
    int code;

    name = nam;

    sprintf(command,"canvasFromSheet %s",nam);
    code = Tcl_Eval(interp,command);
    if (code != TCL_OK) 
        internal_error();
    sprintf(canvas,"%s",interp->result);
    
    sprintf(command,"hScrollFromSheet %s",nam);
    code = Tcl_Eval(interp,command);
    if (code != TCL_OK) 
        internal_error();
    sprintf(hscroll,"%s",interp->result);
    
    sprintf(command,"vScrollFromSheet %s",nam);
    code = Tcl_Eval(interp,command);
    if (code != TCL_OK) 
        internal_error();
    sprintf(vscroll,"%s",interp->result);
    
    sprintf(command,"colLabelsFromSheet %s",nam);
    code = Tcl_Eval(interp,command);
    if (code != TCL_OK) 
        internal_error();
    sprintf(col_labels,"%s",interp->result);
    
    sprintf(command,"rowLabelsFromSheet %s",nam);
    code = Tcl_Eval(interp,command);
    if (code != TCL_OK) 
        internal_error();
    sprintf(row_labels,"%s",interp->result);
    

}

Sheet::~Sheet() {
    Tcl_HashSearch pt;
    Tcl_HashEntry *entry;
    Cell *cell;
    Format *fmt;

    for( entry = Tcl_FirstHashEntry(&cells,&pt); 
         entry ;
         entry = Tcl_NextHashEntry(&pt)) {
        cell = (Cell*)Tcl_GetHashValue(entry);
        delete cell;
    }

    for( entry = Tcl_FirstHashEntry(&col_formats,&pt); 
         entry ;
         entry = Tcl_NextHashEntry(&pt)) {
      fmt = (Format*)Tcl_GetHashValue(entry);
      delete fmt;
    }

    for( entry = Tcl_FirstHashEntry(&row_formats,&pt); 
         entry ;
         entry = Tcl_NextHashEntry(&pt)) {
      fmt = (Format*)Tcl_GetHashValue(entry);
      delete fmt;
    }

    delete [] stack;
    Tcl_DeleteHashTable(&cells);

}

void Sheet::update() {
  static char *command="update";

  Tcl_Eval(interp,command);
}

void Sheet::redraw() {
    Tcl_HashSearch pt;
    Tcl_HashEntry *entry;
    Cell *cell;
    int i;
    int cnt=0;
    char command[256];
    int code;

    /* Need to invalidade item names */
    for( entry = Tcl_FirstHashEntry(&(cells),&pt); 
         entry ; entry = Tcl_NextHashEntry(&pt)) {
      cell = (Cell*)Tcl_GetHashValue(entry);
      cell->item = NULL;
      cell->format.bg.item = NO_ITEM;
      cell->border_item = NO_ITEM;
    }


    for( entry = Tcl_FirstHashEntry(&cells,&pt); entry ; entry = Tcl_NextHashEntry(&pt)) {
      cell = (Cell*)Tcl_GetHashValue(entry);
      display(cell,FALSE);
      cnt++;
//      if ((cnt % UPDATE_RATE) == 0) 
//        update();
    }
}

void Sheet::set_col_width(int col, int wid) {
    int diff;
    int i;
    Sheet_format frm;



    frm.type = CODE_COLW1;
    frm.c_val1 = wid / DEFAULT_FONT_WIDTH+1;
    frm.sh_val1 = col;
    formats += frm;

    frm.type = CODE_COLW2;
    frm.c_val1 = wid;
    frm.sh_val1 = col;
    formats += frm;
    
    diff = wid-cell_width(col);
    
    for(i=col+1; i<width()+1; i++) {
        canvas_info->col_origin[i] += diff;
    }
}

void Sheet::set_row_height(int row, int heig) {
    int diff;
    int i;
    Sheet_format frm;


    frm.type = CODE_ROWH1;
    frm.c_val1 = heig;
    frm.sh_val1 = row;

    formats += frm;
    
    //    diff = heig-cell_height(row)+INTERNAL_CELL_BORDER;
    diff = heig-cell_height(row);

    for(i=row+1; i<height()+1; i++) {
        canvas_info->row_origin[i] += diff;
    }
}

int Sheet::recalc(array<Cell *> &cells) {
  Cell *cell2;
  int i;
  Tcl_HashEntry *entry;
  Tcl_HashSearch pt;


  for(i=0; i<cells.cnt; i++) {
    cell2 = cells[i];
    depth_first_search(cell2);
  }
  /* Now, for the dirty cells */
  for( entry = Tcl_FirstHashEntry(&dirty_cells,&pt); entry ;
       entry = Tcl_NextHashEntry(&pt)) {
    cell2 = (Cell*)Tcl_GetHashValue(entry);
    depth_first_search(cell2);
  }


  if (recalculate_list_of_cells() == FALSE) {
    for(i=0; i<cells.cnt; i++) {
      cell2 = cells[i];
      evalCell(cell2);
      display(cell2);
    }
  }

  
}

int Sheet::recalc(Cell *cell) {
  /* Cell cell has changed. Need to recalc all cells that
     depend on this one */
  int i;
  int modified;
  Tcl_HashEntry *entry;
  Tcl_HashSearch pt;
  Cell *cell2;

  depth_first_search(cell);

  //  printf("cell->col:%d\tcell->row:%d\ncell->val:%d\ncell->error_code:%d\ncell->formval:%f\n****************\n",cell->col,cell->row,cell->val,cell->error_code,(cell->type==CODE_FORMULA ? cell->formula->value : 0.1998));

  /* Now, for the dirty cells */
  for( entry = Tcl_FirstHashEntry(&dirty_cells,&pt); entry ;
       entry = Tcl_NextHashEntry(&pt)) {
    cell2 = (Cell*)Tcl_GetHashValue(entry);
    depth_first_search(cell2);
  }

  if (recalculate_list_of_cells() == FALSE) {
      evalCell(cell);
      display(cell);
    
  }

  /*
  cerr << "List : ";
  for (i=0; i<cells_to_eval.cnt; i++) {
    cerr << "(" << cells_to_eval[i].cell->col << "," << cells_to_eval[i].cell->row << ") ";
  }*/

}

int Sheet::recalculate_list_of_cells() {
  int i;
  Cell *cell2;
  Graph *graph2;
  static char command[256];
  int code;
  
/*******************************************************************/
  if (automatic_recalc) {

    for(i=cells_to_eval.cnt-1; i>=0; i--) {
      //    for(i=0;i<=cells_to_eval.cnt-1; i++) {
        if (cells_to_eval[i].type == CELL_DEP) {
            cell2 = (Cell*)cells_to_eval[i].cell;
            evalCell(cell2);
            display(cell2);
        } else if (cells_to_eval[i].type == GRAPH_DEP) {
            graph2 = (Graph*)cells_to_eval[i].cell;
            sprintf(command,"doRedrawGraph %d",graph2->number);
            code = Tcl_Eval(interp,command);
            if (code != TCL_OK)
              internal_error();
        }
    }
    cells_to_eval.cnt = 0;
    iteration++;

    return(TRUE);
  }
  return(FALSE); /* No cells recalculated */
/**********************************************************************/

}

void Sheet::depth_first_search(Cell *cell) {
  int i;
  Cell *cell2;

  cell->tag = 1;

  for(i=0; i<cell->deps.cnt; i++) {
      if (cell->deps[i].type == CELL_DEP) {
          cell2 = (Cell *)cell->deps[i].cell;
          if (cell2->tag) {
              char buf[256];
              int code;
              if (! cell2->dirty) {
                  sprintf((char*)buf,
                          "warnUser {Dependence loop detected for cell %s!\nResults will not be correct for some cells. }",cell_name_rc(cell2->col,cell2->row));
                  code = Tcl_Eval(interp,(char*)buf);
              }
              cell2->dirty = TRUE;
              add_to_dirty(cell2);
          } else {
              
              if (cell2->iter == iteration) 
                  continue;
              
              depth_first_search(cell2);
          }
      }
      else {
          Graph *graph;
          graph = (Graph*)cell->deps[i].cell;
          
          if (graph->iter != iteration)
              cells_to_eval[cells_to_eval.cnt++] = cell->deps[i];
          graph->iter = iteration;
      }
  }

  cell->tag = 0;
  if (cell->iter != iteration) {
    cells_to_eval[cells_to_eval.cnt].cell = cell;
    cells_to_eval[cells_to_eval.cnt++].type = CELL_DEP;
    cell->iter = iteration;
  }

}

void Sheet::global_recalc() {
    Tcl_HashSearch pt;
    Tcl_HashEntry *entry;
    Cell *cell;
    int i;
    
    for( entry = Tcl_FirstHashEntry(&cells,&pt); entry ;
         entry = Tcl_NextHashEntry(&pt)) {
            cell = (Cell*)Tcl_GetHashValue(entry);
            evalCell(cell);
        }
}

void Sheet::display(Cell *cell, int clear) {
    char command[256];
    int x1,x2,x,y1,y2,y,ox1,ox2;
    int code;
    Stack_elem *res;
    char *form;
    char *st;
    Tk_Item *item;
    Cell *cell2;
    short c1,c2 = -1,c;
    int needs_rectangle=FALSE;
    int i,j;
    char *ft;
    int cx1,cx2;
    int dx; /* Has same role as in tkCanvas.c */
    int position;

    ox1 = x1 = x_coord(cell->col,canvas_info);
    ox2 = x2 = x_coord(cell->col+1,canvas_info);
    y1 = y_coord(cell->row,canvas_info);
    y2 = y_coord(cell->row+1,canvas_info);
    y = (y1+y2)/2+1;

    /* Now, draw all cells aligned at right. This will 
       change when cell formats are in */

    if (clear) 
      /* apagar o conteudo antigo da celula */
      {
        if (cell->item) {
	  sprintf(command,"%s delete %d",canvas,cell->item->id);
	  code = Tcl_Eval(interp,command); 
	  cell->item = NULL;
        }
        if (cell->format.bg.item != NO_ITEM) {
	  sprintf(command,"%s delete %d",canvas,cell->format.bg.item);
	  code = Tcl_Eval(interp,command);
	  cell->format.bg.item = NO_ITEM;
        }
        if (cell->border_item != NO_ITEM) {
	  sprintf(command,"%s delete %d",canvas,cell->border_item);
	  code = Tcl_Eval(interp,command); 
	  cell->border_item = NO_ITEM;
        }
      }

    if (cell->type != CODE_BLANK) 
      {
        st = (char *) cell->display();
        if (cell->format.alignment > RIGHT_ALIGNMENT)
          cell->format.alignment = DEFAULT_ALIGNMENT;
        if (cell->format.alignment==LEFT_ALIGNMENT || 
            (cell->format.alignment==DEFAULT_ALIGNMENT && 
             (cell->type==CODE_LABEL ||
              (cell->type==CODE_FORMULA && cell->formula->typ==FORM_STRING)))) 
	  {
	    /* alinhamento a esquerda*/
	    x = x1+INTERNAL_CELL_BORDER+1;
	    dx = x;
	    position = WEST;
	  }
        else if (cell->format.alignment==RIGHT_ALIGNMENT ||
                 cell->format.alignment==DEFAULT_ALIGNMENT) 
	  {
	    /* alinhamento a direita */
            x = x2-INTERNAL_CELL_BORDER;
            dx = x;
            position = EAST;
	  } 
	else if (cell->format.alignment == CENTER_ALIGNMENT) 
	  {
	    /* alinhamento ao centro */
            x = (x1+x2)/2;
            dx = x;
            position = CENTER;
	  } 
	else 
	  {
            x = x2-INTERNAL_CELL_BORDER;
            position = EAST;
	  }

        if (cell->format.font.isdefault())
	  {
	    ft = default_font_name;
	  }
        else 
          ft = getFont(interp,cell->format.font);

        if (cell->type != CODE_BLANK) 
	  {
	    cell->item = draw_text(st,x,y,position,cell->col,cell->row,ft);
	    cx1 = cell->item->x1+dx;
	    cx2 = cell->item->x2+dx;
	    cell->wid = cell->item->x2-cell->item->x1;
	  } 
	else 
	  {
	    cx1 = x_coord(cell->col,canvas_info)+dx;
	    cx2 = x_coord(cell->col+1,canvas_info)+dx;
	    cell->wid = 0;
	  }

        /* First, simple case. Cell needs rectangle because it overflows */

        if (cx1 < x1 || cx2 > x2) 
	  needs_rectangle = TRUE;

        if (cell->type != CODE_LABEL && needs_rectangle &&
            (cell->type != CODE_FORMULA || cell->formula->typ != FORM_STRING))
	  {
	    static char buf[20];

	    needs_rectangle = FALSE;
	    c = (int)((float)(cell_width(cell->col))/(cx2-cx1)*strlen(st)-1);
	    cx1 = x1+1;
	    cx2 = x2-1;
	    for(i=0; i<c; i++)
	      buf[i] = '#';
	    buf[i] = 0;
	    sprintf(command,"%s delete %d",canvas,cell->item->id);
	    code = Tcl_Eval(interp,command);
	    cell->item = draw_text(buf,x,y,position,cell->col,cell->row,ft);
	  }
        
        /* 
	   Second, more complex case. Cell needs rectangle because
           another cell overflows into it
	   Cells to the left. We will arbitrarly scan only 5 cells to our left
	*/
        
        i = findIndex(rows[cell->row],cell->col,1);
        
        for (j=i-1; j>=0 && i-j<8; j--) 
	  {
	    cell2 = rows[cell->row].cells[j];
	    if (cell2->item == NULL) continue;
	    if (cell2->item && cell2->item->x2 - cell2->item->x1 > 
		col_origin(cell->col)-
		col_origin(cell2->col)) 
	      {
		needs_rectangle = TRUE;
		break;
	      }
	  }
        for (j=i+1; j<rows[cell->row].cells.cnt && j-i<8; j++)
	  {
	    int dx1;
            
	    cell2 = rows[cell->row].cells[j];
            if (cell2->item == NULL) 
	      continue;
            if (cell2->item && cell2->item->x1+x2 < 
		x_coord(cell->col+1,canvas_info)) 
	      { /* AML - Probably wrong */
		/*needs_rectangle = TRUE;*/
		break;
	      }
	  }
        if ((needs_rectangle && cell->type != CODE_BLANK) 
            || !cell->format.bg.isdefault() 
            || !cell->format.borders.isdefault()) 
	  { 
	    /* Compute coordinates */
	    cell->has_rectangle = TRUE;
	    for (c1 = cell->col; c1>0 && x_coord(c1,canvas_info) > cx1; )
	      c1--;
	    for(c2=cell->col+1;c2<width() && x_coord(c2,canvas_info)<cx2;)
	      c2++;
	    x1 = x_coord(c1,canvas_info);
	    x2 = x_coord(c2,canvas_info);
	  }
      }
    else 
      {
	c1 = cell->col;
	c2 = c1+1;
      }

    if((needs_rectangle && cell->type!=CODE_BLANK)||
       !cell->format.bg.isdefault()) 
      {
	/* Draw fill area */
        int fl = cell->filllevel();

	if (fl != 100) 
	  sprintf(command,
        "%s create rec %d %d %d %d X %d %d %d %d -fill gray%d -outline gray%d "
		  ,canvas,0,0,0,0,c1,cell->row,c2,cell->row+1,
		  cell->filllevel(),cell->filllevel());
	else
	  sprintf(command,
	  "%s create rec %d %d %d %d X %d %d %d %d -fill white -outline white "
		  ,canvas,0,0,0,0,c1,cell->row,c2,cell->row+1);
	code = Tcl_Eval(interp,command);
	if (code != TCL_OK)
	  internal_error();
	cell->format.bg.item = atoi(interp->result);
	if (display_cell_borders) 
	  {
	    sprintf(command,"%s raise %s %d",canvas,
		    interp->result,borderItemId);
	    code = Tcl_Eval(interp,command);
	  } 
	else 
	  {
	    sprintf(command,"%s lower %s",canvas,
		    interp->result);
	    code = Tcl_Eval(interp,command);
	  }
        if (display_cell_borders && cell->format.bg.item != NO_ITEM) 
	  {
            sprintf(command,
      "%s create rect %d %d %d %d X %d %d %d %d -outline blue -tag cellborders"
                    ,canvas,0,0,0,0,
                    c1,cell->row,c2,cell->row+1);
            code = Tcl_Eval(interp,command);
            if (code != TCL_OK) 
	      internal_error();
            if (cell->format.bg.item != NO_ITEM) {
              sprintf(command,"%s raise %s %d",canvas,
                      interp->result,cell->format.bg.item);
              code = Tcl_Eval(interp,command);
            }
            cell->border_item = atoi(interp->result);
	  } 
	else
	  cell->border_item = NO_ITEM;
      }
    else /* Doesn't need rectangle */
        cell->format.bg.item = NO_ITEM;
    
    if (!cell->format.borders.isdefault()) 
      /* Draw borders */
      draw_borders(cell,x1,y1,x2,y2);
        
    /* And now, redraw the ones to the right, if needed */

    if (cell->type != CODE_BLANK) 
      if (cx1 < ox1 || cx2 > ox2) 
	for (j=i+1; j<rows[cell->row].cells.cnt && j-i<5; j++) 
	  {
	    cell2 = rows[cell->row].cells[j];
	    if (cell2->item == NULL) 
	      continue;
	    if (cx2 > x_coord(cell2->col,canvas_info)) 
	      display(cell2,TRUE);
	    else 
	      break;
	  }

    code = TCL_OK;
    if (code != TCL_OK)
        internal_error();
}

void Sheet::display(array<Cell *> &cells,int clear)
{
  for(int i=0; i<cells.cnt;i++)
    display(cells[i],clear);
}

int Sheet::draw_borders(Cell *cell, int x1, int y1, int x2, int y2) {
    static char command[256];
    int code;

    x1 = y1 = x2 = y2 = 0;

    if (cell->format.borders.top != DEFAULT_BORDER) {
      if (cell->format.borders.top_item != NO_ITEM) {
        sprintf(command,"%s delete %d",
                canvas,cell->format.borders.top_item );
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
          internal_error();
        
        /* Now, the case for the double lines */
        if (cell->format.borders.top_item_double ) {
          sprintf(command,"%s delete %d",       
                  canvas,cell->format.borders.top_item-1);
          code = Tcl_Eval(interp,command);
          if (code != TCL_OK) 
            internal_error();
        }
      }
    }

    if (cell->format.borders.bottom != DEFAULT_BORDER) {
      if (cell->format.borders.bottom_item != NO_ITEM) {
        sprintf(command,"%s delete %d",
                canvas,cell->format.borders.bottom_item );
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
          internal_error();
        
        /* Now, the case for the double lines */
        if (cell->format.borders.bottom_item_double ) {
          sprintf(command,"%s delete %d",       
                  canvas,cell->format.borders.bottom_item-1);
          code = Tcl_Eval(interp,command);
          if (code != TCL_OK) 
            internal_error();
        }
      }
    }

    if (cell->format.borders.left != DEFAULT_BORDER) {
      if (cell->format.borders.left_item != NO_ITEM) {
        sprintf(command,"%s delete %d",
                canvas,cell->format.borders.left_item );
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
          internal_error();
        
        /* Now, the case for the double lines */
        if (cell->format.borders.left_item_double ) {
          sprintf(command,"%s delete %d",       
                  canvas,cell->format.borders.left_item-1);
          code = Tcl_Eval(interp,command);
          if (code != TCL_OK) 
            internal_error();
        }
      }
    }

    if (cell->format.borders.right != DEFAULT_BORDER) {
      if (cell->format.borders.right_item != NO_ITEM) {
        sprintf(command,"%s delete %d",
                canvas,cell->format.borders.right_item );
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
          internal_error();
        
        /* Now, the case for the double lines */
        if (cell->format.borders.right_item_double ) {
          sprintf(command,"%s delete %d",       
                  canvas,cell->format.borders.right_item-1);
          code = Tcl_Eval(interp,command);
          if (code != TCL_OK) 
            internal_error();
        }
      }
    }
    
    if (cell->format.borders.top != DEFAULT_BORDER)
      cell->format.borders.top_item_double = FALSE;
    if (cell->format.borders.bottom != DEFAULT_BORDER)
      cell->format.borders.bottom_item_double = FALSE;
    if (cell->format.borders.left != DEFAULT_BORDER)
      cell->format.borders.left_item_double = FALSE;
    if (cell->format.borders.right != DEFAULT_BORDER)
      cell->format.borders.right_item_double = FALSE;
    

    switch(cell->format.borders.top) {
      case DEFAULT_BORDER:
        break;
      case 0:
        cell->format.borders.top_item = NO_ITEM;
        break;
      case 1:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y1,x2,y1,
                cell->col,cell->row,cell->col+1,cell->row,
                (int)cell->format.borders.top);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.top_item = atoi(interp->result);
        break;
      case 2:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y1+1,x2,y1+1,
                cell->col,cell->row,cell->col+1,cell->row,
                (int)cell->format.borders.top);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.top_item = atoi(interp->result);
        break;
      case 3:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y1+1,x2,y1+1,
                cell->col,cell->row,cell->col+1,cell->row,
                (int)cell->format.borders.top);
                
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.top_item = atoi(interp->result);
        break;
      case 5:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y1,x2,y1,
                cell->col,cell->row,cell->col+1,cell->row,1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y1+2,x2,y1+2,
                cell->col,cell->row,cell->col+1,cell->row,1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.top_item_double = TRUE;
        cell->format.borders.top_item = atoi(interp->result);
        break;
      default:
        cerr << "Unknown border type\n";
    }

    switch(cell->format.borders.bottom) {
      case DEFAULT_BORDER:
        break;
      case 0:
        cell->format.borders.bottom_item = NO_ITEM;
        break;
      case 1:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y2-1,x2,y2-1,
                cell->col,cell->row+1,cell->col+1,cell->row+1,
                (int)cell->format.borders.bottom);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.bottom_item = atoi(interp->result);
        break;
      case 2:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y2-1,x2,y2-1,
                cell->col,cell->row+1,cell->col+1,cell->row+1,
                (int)cell->format.borders.bottom);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.bottom_item = atoi(interp->result);
        break;
      case 3:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y2-2,x2,y2-2,
                cell->col,cell->row+1,cell->col+1,cell->row+1,
                (int)cell->format.borders.bottom);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.bottom_item = atoi(interp->result);
        break;
      case 5:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y2-1,x2,y2-1,
                cell->col,cell->row+1,cell->col+1,cell->row+1,
                1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y2-3,x2,y2-3,
                cell->col,cell->row+1,cell->col+1,cell->row+1,
                1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.bottom_item_double = TRUE;
        cell->format.borders.bottom_item = atoi(interp->result);
        break;
      default:
        cerr << "Unknown border type\n";
    }

    switch(cell->format.borders.left) {
      case DEFAULT_BORDER:
        break;
      case 0:
        cell->format.borders.left_item = NO_ITEM;
        break;
      case 1:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y1,x1,y2,
                cell->col,cell->row,cell->col,cell->row+1,
                (int)cell->format.borders.left);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.left_item = atoi(interp->result);
        break;
      case 2:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1+1,y1,x1+1,y2,
                cell->col,cell->row,cell->col,cell->row+1,
                (int)cell->format.borders.left);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.left_item = atoi(interp->result);
        break;
      case 3:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1+1,y1,x1+1,y2,
                cell->col,cell->row,cell->col,cell->row+1,
                (int)cell->format.borders.left);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.left_item = atoi(interp->result);
        break;
      case 5:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1,y1,x1,y2,
                cell->col,cell->row,cell->col,cell->row+1,1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x1+2,y1,x1+2,y2,
                cell->col,cell->row,cell->col,cell->row+1,1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.left_item_double = TRUE;
        cell->format.borders.left_item = atoi(interp->result);
        break;
      default:
        cerr << "Unknown border type\n";
    }

    switch(cell->format.borders.right) {
      case DEFAULT_BORDER:
        break;
      case 0:
        cell->format.borders.right_item = NO_ITEM;
        break;
      case 1:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x2-1,y1,x2-1,y2,
                cell->col+1,cell->row,cell->col+1,cell->row+1,
                (int)cell->format.borders.right);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.right_item = atoi(interp->result);
        break;
      case 2:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x2-1,y1,x2-1,y2,
                cell->col+1,cell->row,cell->col+1,cell->row+1,
                (int)cell->format.borders.right);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.right_item = atoi(interp->result);
        break;
      case 3:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x2-2,y1,x2-2,y2,
                cell->col+1,cell->row,cell->col+1,cell->row+1,
                (int)cell->format.borders.right);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.right_item = atoi(interp->result);
        break;
      case 5:
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x2-1,y1,x2-1,y2,
                cell->col+1,cell->row,cell->col+1,cell->row+1,1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        sprintf(command,
        "%s create line %d %d %d %d X %d %d %d %d -width %d",
                canvas,x2-3,y1,x2-3,y2,
                cell->col+1,cell->row,cell->col+1,cell->row+1,1);
        code = Tcl_Eval(interp,command);
        if (code != TCL_OK) 
            internal_error();
        cell->format.borders.right_item_double = TRUE;
        cell->format.borders.right_item = atoi(interp->result);
        break;
      default:
        cerr << "Unknown border type\n";
    }


}


int Sheet::evalCell(Cell *cell) {
    Stack_elem *result;
    double prev_value;
    Cell *c;

    cell->error_code = NO_ERROR;
    
    switch(cell->type) {
      case CODE_BLANK:
        cell->value = 0;
        break;
      case CODE_FORMULA:
        prev_value = cell->value;
        result = evaluate(cell);
        if ((cell->error_code = result->contents.error_code) != NO_ERROR) {}
        else
            switch(result->type) {
              case FORM_REF:
                c = cellFind(result->contents.ref_val);
                switch(c->type) {
                  case CODE_LABEL:
                    if (cell->formula->st_value == NULL || 
                        strlen(cell->formula->st_value) < 
                        strlen(c->label)){
                        delete [] cell->formula->st_value;
                        cell->formula->st_value = strsave(c->label+1);
                    }
                    else {
                        strcpy(cell->formula->st_value,c->label+1);
                    }
                    cell->formula->typ = FORM_STRING;
                    
                    break;
                  default:
                    cell->value = cell->formula->value = stack_value(result);
                    if (VALUE_DIFFERENT(prev_value,cell->value) )
                        cell->modified = TRUE;
                    cell->formula->typ = FORM_FP;
                    break;
                }
                break;
              case FORM_INT:
              case FORM_FP:
                cell->value = cell->formula->value = stack_value(result);
                if (VALUE_DIFFERENT(prev_value,cell->value) )
                    cell->modified = TRUE;
                cell->formula->typ = FORM_FP;
                break;
              case FORM_STRING:
                if (cell->formula->st_value == NULL || 
                    strlen(cell->formula->st_value) < 
                    strlen(result->contents.string_val)+1){
                    delete [] cell->formula->st_value;
                    cell->formula->st_value = strsave(result->contents.string_val);
                    cell->value = cell->formula->value = 0;
                }
                else {
                    strcpy(cell->formula->st_value,result->contents.string_val);
                }
                cell->formula->typ = FORM_STRING;
                break;
              default:
                internal_error();
                break;
            }
        break;
      case CODE_NUMBER:
      case CODE_INTEGER:
        break;
      case CODE_LABEL:
        cell->value = 0;
        break;
      default:
        internal_error();
    }
    
    
}

Stack_elem *Sheet::cellRef(short col, short row) {
    Cell *cell;
    static Stack_elem sta;

    sta.contents.ref_val.col = col;
    sta.contents.ref_val.row = row;
    sta.contents.error_code = NO_ERROR;
    sta.type = FORM_REF;

    if (!CHECK_COL_BOUNDS(col) ||
        !CHECK_ROW_BOUNDS(row)) {
      sta.contents.error_code = ERROR_BAD_REF;
    };
      

    return(&sta);
}

void Sheet::add_to_dirty(Cell *cell) {
  int CellCoords[2];
  Tcl_HashEntry *entry;
  int newv;

  CellCoords[0] =  cell->col; 
  CellCoords[1] =  cell->row; 
    
  entry = Tcl_CreateHashEntry(&dirty_cells,(char *)CellCoords,&newv);
  Tcl_SetHashValue(entry,cell);

}

void Sheet::remove_from_dirty(Cell *cell) {
  int CellCoords[2];
  Tcl_HashEntry *entry;
  int newv;


  CellCoords[0] =  cell->col; 
  CellCoords[1] =  cell->row; 
    
  entry = Tcl_FindHashEntry(&dirty_cells,(char *)CellCoords);
  if (entry == NULL) internal_error();

  Tcl_DeleteHashEntry(entry);
}

Cell *Sheet::cellSet(short col, short row, char *form) {
    Cell *cell;

    empty = FALSE;

    cell = cellFind(col,row);
    if (cell->formula){
        manipulateReferences(cell,REMOVE_REFERENCES);
        delete cell->formula;
	cell->formula = NULL;
    }
    cell->set(form,parser);
    if (cell->type == CODE_FORMULA)
        manipulateReferences(cell,INSERT_REFERENCES);
    modified_after_save = TRUE;
    if (cell->dirty)
      remove_from_dirty(cell);
    cell->dirty = FALSE;
    cell->iter = -1;
    return(cell);
}

char *Sheet::stack_string(Stack_elem *p) {
    Cell *c;
    Stack_elem *s;

    switch(p->type) {
      case FORM_STRING:
        return(p->contents.string_val);
        break;
      case FORM_REF:
        c = cellFind(p->contents.ref_val);
        switch(c->type) {
          case CODE_LABEL:
            return(c->label+1);
            break;
          case CODE_FORMULA:
            return(c->formula->st_value);
            break;
          default:
            return("");
            break;
        }
      default:
        break;
    }
}

double Sheet::stack_value(Stack_elem *p) {
    Cell *c;
    Stack_elem *s;

    switch (p->type) {
      case FORM_FP:
        return(p->contents.fp_val);
      case FORM_INT:
        return((double)p->contents.int_val);
      case FORM_REF:
        c = cellFind(p->contents.ref_val);
        s = c->val();
        return(STACK_VALUE(s));
    }
}

Cell *Sheet::cellExists(short col, short row) {
    int CellCoords[2];
    Tcl_HashEntry *entry;
    int newv;
    Cell *cell;

    CellCoords[0] =  col; /* AML */
    CellCoords[1] =  row; /* AML */
    
    
    entry = Tcl_FindHashEntry(&cells,(char *)CellCoords);
    if (entry != NULL) {
        cell = (Cell*)Tcl_GetHashValue(entry);
        return(cell);
    } else {
        return(NULL);
    }
}


Cell *Sheet::cellFind(Ref &ref) {
    return(cellFind(ref.col,ref.row));
}

Cell *Sheet::cellFind(short col, short row) {
    int CellCoords[2];
    Tcl_HashEntry *entry,*format_entry;
    int newv;
    Cell *cell,*aux;
    Format *format=NULL;

    CellCoords[0] =  col; /* AML */
    CellCoords[1] =  row; /* AML */
    
    if (col > max_col) max_col = col;
    if (row > max_row) max_row = row;
    
    if (col > max_col_used) max_col_used = col;
    if (row > max_row_used) max_row_used = row;
    if (col < min_col_used) min_col_used = col;
    if (row < min_row_used) min_row_used = row;

    entry = Tcl_CreateHashEntry(&cells,(char *)CellCoords,&newv);
    if (newv == 1) /* criar entrada nova */
      {
        cell = new Cell(col,row);
	if((format_entry=Tcl_FindHashEntry(&row_formats,(char *) row))!=NULL)
	  cell->mergeformats((Format*) Tcl_GetHashValue(format_entry));
	if((format_entry=Tcl_FindHashEntry(&col_formats,(char *) col))!=NULL)
	  cell->mergeformats((Format*) Tcl_GetHashValue(format_entry));
        Tcl_SetHashValue(entry,cell);
        insertCell(cell);
        modified_after_save = TRUE;
      }
    else /* aceder a entrada ja existente */
      cell = (Cell*)Tcl_GetHashValue(entry);
    return(cell);
}

void Sheet::insertInCellList(Cell *cell) {
    int CellCoords[2];
    Cell *cell2;
    int newv;
    Tcl_HashEntry *entry;

    CellCoords[0] =  cell->col; /* AML */
    CellCoords[1] =  cell->row; /* AML */
    
    if (cell->col > max_col) max_col = cell->col;
    if (cell->row > max_row) max_row = cell->row;
    
    if (cell->col > max_col_used) max_col_used = cell->col;
    if (cell->row > max_row_used) max_row_used = cell->row;
    if (cell->col < min_col_used) min_col_used = cell->col;
    if (cell->row < min_row_used) min_row_used = cell->row;


    entry = Tcl_CreateHashEntry(&cells,(char *)CellCoords,&newv);
    if (newv == 1) {
        Tcl_SetHashValue(entry,cell);
        modified_after_save = TRUE;
    } else {
        cell2 = (Cell*)Tcl_GetHashValue(entry);
        delete cell2;
    }

}

void Sheet::removeCell(Cell *cell) {
  int r,c;
  int i,j;
  Cell *pt;
  short size;

  r = cell->row;
  c = cell->col;

  if (r < 0 || c < 0) 
      internal_error();

  /* Locate position in row */

  i = findIndex(rows[r],c,1);
  for(j=i;j<rows[r].cells.cnt;j++)
    rows[r].cells[j]=rows[r].cells[j+1];
  rows[r].cells.cnt--;

  /* Locate position in col  */

  i = findIndex(cols[c],r,0);
  for(j=i;j<cols[c].cells.cnt;j++)
    cols[c].cells[j]=cols[c].cells[j+1];
  cols[c].cells.cnt--;
}

void Sheet::insertCell(Cell *cell) {
  int r,c;
  int i,j;
  Cell *pt;
  short size;

  /* Locate position in row */
  r = cell->row;
  c = cell->col;

  if (r < 0 || c < 0) 
      internal_error();

  i = findIndex(rows[r],c,1);

  for (j=rows[r].cells.cnt; j>i; j--) {
    rows[r].cells[j] = rows[r].cells[j-1];
  }
  rows[r].cells[i] = cell;
  rows[r].cells.cnt++;

  /* Locate position in col  */

  i = findIndex(cols[c],r,0);
  for (j=cols[c].cells.cnt; j>i; j--) {
    cols[c].cells[j] = cols[c].cells[j-1];
  }
  cols[c].cells[i] = cell;
  cols[c].cells.cnt++;

  /*  
  cerr << "New list of cells for row " << r << " " ;

  for (i=0; i<rows[r].cells.cnt; i++) {
    pt = rows[r].cells[i];
    cerr << "(" << pt->col << "," << pt->row << ") ";
  }

  cerr << "\nNew list of cells for col " << c << " " ;

  for (i=0; i<cols[c].cells.cnt; i++) {
    pt = cols[c].cells[i];
    cerr << "(" << pt->col << "," << pt->row << ") ";
  }
  cerr << "\n";*/

}

void Sheet::copyToBuffer(Cell *cell, Cell_buffer &buffer) {
    
    buffer.cells += cell;

}

void Sheet::removeFromCellList(Cell *cell) {
    int CellCoords[2];
    Cell *cell2;
    Tcl_HashEntry *entry;

    CellCoords[0] =  cell->col; /* AML */
    CellCoords[1] =  cell->row; /* AML */
    
    entry = Tcl_FindHashEntry(&cells,(char *)CellCoords);
    if (entry == NULL) {
        internal_error();
    }
    else
        Tcl_DeleteHashEntry(entry);
    
}

short Sheet::findIndex(Rc_info &arr,short c, int rc /* 0 = row , 1 = col */) {
  short cnt,size,i;
  Cell *pt;
  short x;

  cnt = arr.cells.cnt;
  for (i = cnt/2, size = MAX(cnt/2,1); i != cnt ; ) {
    if (size > 1) size = size/2;
    pt = arr.cells[i];
    x = rc ? pt->col : pt->row;
    if (x == c) return(i);
    if (x < c) {
      i = i+size;
      continue;
    } 
    if (i == 0) break;
    pt = arr.cells[i-1];
    x = rc ? pt->col : pt->row;
    if (x == c) return(i-1);
    if (x > c) {
      i = i-size;
      continue;
    } 
    break;
  }

  return(i);
}

int Sheet::max_text_width(short col) {
  Cell *cell;
  int max = 0;
  int wid,i;

  for (i=0; i<cols[col].cells.cnt ; i++) {
    cell = cols[col].cells[i];
    if (cell->type != CODE_BLANK) 
        wid = cell->wid;
    else 
        wid = 0;
    max = MAX(max,wid);
  }
  return(max);
}

int Sheet::text_width(Cell *cell) {
  if (cell->type != CODE_BLANK) {
    if (cell->item == NULL) 
      internal_error();
    return(cell->item->x2-cell->item->x1);
  } else {
    return(0);
  }
  
}

int Sheet::propagate_error(Stack_elem *ptfrom, Stack_elem *ptto) {

  if (ptfrom->type == FORM_REF) {
    Cell *cell;

    if (CHECK_COL_BOUNDS(ptfrom->contents.ref_val.col) &&
        CHECK_ROW_BOUNDS(ptfrom->contents.ref_val.row)) {
      cell = cellFind(ptfrom->contents.ref_val);
      if (cell->error_code != NO_ERROR) {
        ptto->contents.error_code = cell->error_code ;
        return(TRUE);
      } 
    }
    else {
      ptto->contents.error_code = ERROR_BAD_REF;
      return(TRUE);
    }
  }

  if (ptfrom->contents.error_code != NO_ERROR) {
    ptto->contents.error_code = ptfrom->contents.error_code ;
    return(TRUE);
  } 
  return(FALSE);
}

int Sheet::propagate_error(Cell *cell, Stack_elem *ptto) {
  if (cell->error_code != NO_ERROR) {
    ptto->contents.error_code = cell->error_code ;
    return(TRUE);
  } 
  return(FALSE);
} 

void Sheet::SetUndoRedoGUI(int UndoRedo, int OnOff)
{
  char command[256];
  int code;

  if((UndoRedo==0 ? undo_on : redo_on)==OnOff)
    return;
  sprintf(command,
	  ".%s.fr.header.standard.g3.%s configure -state %s",
	  (char*) name, 
	  UndoRedo==0 ? "undo" : "redo", 
	  OnOff==1 ? "normal" : "disabled");
  code = Tcl_Eval(interp,command);
  if (code != TCL_OK) 
    internal_error();
  sprintf(command,
	  ".%s.fr.menubar.menu.edit.menu entryconfigure %s -state %s",
	  (char*) name, 
	  UndoRedo==0 ? "Undo" : "Redo", 
	  OnOff==1 ? "normal" : "disabled");
  code = Tcl_Eval(interp,command);
  if (code != TCL_OK) 
    internal_error();
  if(UndoRedo==0)
    undo_on=OnOff;
  else
    redo_on=OnOff;
}

void Sheet::RegisterEvent(Event *ev)
{
  Event *aux;

  if(event_l.IsEmpty())
    {
      if((event_it=event_l.AddHead(ev))==NULL)
	internal_error();
      SetUndoRedoGUI(UNDO,ON);
      return;
    }
  if(event_it!=NULL)
    while(!event_it->IsFirst())
      aux=event_l.RemoveHead();
  else
    while(!event_l.IsEmpty())
      aux=event_l.RemoveHead();
  if((event_it=event_l.AddHead(ev))==NULL)
    internal_error();
  if(event_l.GetCount()>UNDO_DEPTH)
    event_l.RemoveTail();
  SetUndoRedoGUI(UNDO,ON);
  SetUndoRedoGUI(REDO,OFF);
}

void Sheet::Undo()
{
  Event *aux;

  if(event_it==NULL)
    return;
  if((aux=event_l.GetNext(&event_it))==NULL)
    return;
  aux->UndoEvent();
  SetUndoRedoGUI(REDO,ON);
  if(event_it==NULL)
    SetUndoRedoGUI(UNDO,OFF);
}

void Sheet::Redo()
{
  Event *aux;

  int i;

  if(event_it==NULL)
    {
      if(event_l.IsEmpty())
	return;
      aux=event_l.GetTail();
      event_it=event_l.GetTailPosition();
    }
  else if(event_it->IsFirst())
    return;
  else
    {
      event_l.GetPrev(&event_it);
      aux=event_l.GetAt(event_it);
    }
  aux->RedoEvent();
  SetUndoRedoGUI(UNDO,ON);
  if(event_it->IsFirst())
    SetUndoRedoGUI(REDO,OFF);
}

void Sheet::RunScript(char *buf)
{
  int code;

  code = Tcl_Eval(interp,buf);
  if (code != TCL_OK) 
    internal_error();
}

int Sheet::RunScriptFromFile(char *filename)
{
  int code;

  return(Tcl_EvalFile(interp,filename));
}

// $Log: sheet.cc,v $
// Revision 1.53  1998/10/20 19:08:48  cthulhu
// Fixed bug when checking col and row bounds inside propagate_error.
//
// Revision 1.52  1998/08/25 20:57:28  cthulhu
// Added RunScriptFromFile to run user-based scripts (macros).
//
// Revision 1.51  1998/08/10 12:19:42  aml
// Fixed evaluation bug.
//
// Revision 1.50  1998/08/06 21:08:47  aml
// Released alpha version of Abacus.
//
// Revision 1.49  1997/02/10 15:11:06  aml
// Print settings now are saved to file.
// Fixed buggy error message when loading sheets.
//
// Revision 1.48  1997/01/07 01:07:48  aml
// Error propagation for formulas fixed.
// Edit operations in place.
//
// Revision 1.47  1997/01/02  16:15:35  aml
// Fixed unsufficient range of colunm width.
// First cut of vlookup and hlookup functions.
// Fixed bug in display routines.
//
// Revision 1.46  1996/12/31 17:38:45  aml
// On-demand calculation improved. Loops are detected.
// Automatic recalculation can now be disabled.
// Printing was improved.
//
// Revision 1.45  1996/12/11 21:40:05  aml
// Sumif implemented.
// Diverse time functions implemented.
// Fixed needtoscroll2 to avoid out of control scroll.
//
// Revision 1.44  1996/11/22 16:29:22  aml
// First cut at transforming canvas into a true cell widget.
// Text, lines and rectangles are now relative to row and colunm numbers.
// It still has a bug with wrong estimation of column widths.
//
// Revision 1.43  1996/10/09  13:55:10  aml
// First cut of full print.
//
// Revision 1.42  1996/10/07 12:35:44  aml
// First cut at error handling.
// Date formats are in.
// Fixed problem with blank cell drawing.
//
// Revision 1.41  1996/09/19  12:17:13  aml
// Created row and column insert.
// Fixed small problem with display of vergrown cells.
//
// Revision 1.40  1996/09/17 15:16:41  aml
// Fixed problems with copying of cells with non-default formats.
// Created printing formats, alignment formats.
// Format toolbar now reflects format of active cell.
//
// Revision 1.39  1996/09/16 18:43:02  aml
// Some performance problems addressed by reducing tag use.
// Several performance problems remain when heavy use is made
// of borders and shading in large spreadsheets.
//
// Revision 1.38  1996/09/15  19:25:00  aml
// Rulling and shading.
// Optionally hide cell borders.
// Works well, but is very slow for large spreadsheets.
//
// Revision 1.37  1996/09/14  23:56:18  aml
// Created cell shading.
//
// Revision 1.36  1996/09/04  14:30:06  aml
// Fixed double redrawing of sheets that was taking place.
// Fixed a item reference problem when the sheet is redrawn.
// Fixed misplacement of row labels.
// Created first version of format toolbar.
//
// Revision 1.35  1996/09/02 10:51:54  aml
// Cell fonts created, loaded and saved.
// Row height created.
//
// Revision 1.34  1996/08/29 12:05:49  aml
// Fixed problem with initialization of formulas.
// Insertion in entry is now done properly.
// Focus is slightly better handled.
// Fixed serious problem when canvas changes name and old
// references exist. Also removed double call to redraw that
// was slowing things a lot.
//
// Revision 1.33  1996/08/28 17:18:00  aml
// Load and save now accept string_value for formula cells.
// This fixes previous thought problem of formula values not
// being stored.
// Functions upper,lower and proper created.
// Function if can now return labels.
// Fixed problem with function count.
// Reasonably stable version, very used to manipulate notas.wk1.
//
// Revision 1.32  1996/08/27 17:19:01  aml
// First version of regressive tests created.
// Changes were made to allow for string functions.
// String function upper created. Raises the problem
// that formulas do NOT have a space for string values,
// and therefore have to be evaluated upon loading.
//
// Revision 1.31  1996/08/26 17:22:32  aml
// Function round fixed.
// Many other functions added, from power to mod.
//
// Revision 1.30  1996/08/26 12:08:55  aml
// Fixed problem with several sheets. Each canvas now has its own
// canvas info.
// Fixed scroll up and down. Implemented max_row and max_col.
// Fairly stable version.
//
// Revision 1.29  1996/08/24  10:16:09  aml
// Scroll almost fixed. We are missing scroll steps.
// Several sheets work again, but need to be fixed right.
//
// Revision 1.28  1996/08/23  16:13:44  aml
// Top window resizing now works well.
// Range selection now uses a filled rectangle with overall good results.
// Intermediate version, does not work well.
//
// Revision 1.27  1996/07/23  14:01:16  aml
// Changed canvas widget to handle special items like cell borders.
// Seems to work well. Spreadsheet size is no more limited now.
//
// Revision 1.26  1996/07/18 10:19:38  aml
// Created formats for cells.
// Load cell now makes copy of old file.
//
// Revision 1.25  1996/04/27 11:12:43  aml
// Inserted check and delete button.
// Font selection widget created.
// Fixed bug canvas_information destructor.
//
// Revision 1.24  1996/04/23  09:43:07  aml
// Data structures for ordered scans of rows and columns are in place.
// Cell overlap is working.
// Forward cell dependences inserted. Automatic recalculation created.
// Uniformizaed label entry procedure.
//
// Revision 1.23  1996/04/21 13:28:21  aml
// Sped up scroll functions, caching keys presses.
// First cut at handling overflowing cells.
// Overflow into ajoining filled cells not solved.
//
// Revision 1.22  1996/04/19  10:46:42  aml
// First cut at speeding up canvas critical functions.
// CanvasWidgetCommand is now called directly from draw_sheet.
// Fixed bug in reading values from datafiles. Also works
// for Suns now.
// Created canvas directory, replacing builtin command canvas.
//
// Revision 1.21  1996/03/29 21:46:03  aml
// Changed key based scrolls to be synchronous. Work fine, but are somewhat slow.
// Fixed abnormaly in state machine after range defition causing canvas scroll.
// Solid, working version.
//
// Revision 1.20  1996/03/11  15:47:52  aml
// Made redraw more efficient by removing at once all cells before redrawing.
// Fixed problem with unsufficient difinition of logical expressions.
// Added >=, <=, <>, @and and @or functions.
//
// Revision 1.19  1996/03/09 08:32:45  aml
// Fixed problem in string_single_arg macro
// Created if function, boolean evaluations and so on.
// Improved print controls.
//
// Revision 1.18  1996/03/08  19:00:36  aml
// Fixed problem in string_single_arg macro
// Created if function, boolean evaluations and so on.
//
// Revision 1.17  1996/03/07 20:33:11  aml
// Created print range ability.
// Set in gray non-working menus.
// Created RangeKill command.
// Created round function and macros for single argument functions.
//
// Revision 1.16  1996/03/06 20:25:29  aml
// Improved user waiting times during redraw and loads by calling update.
//
// Revision 1.15  1996/03/01 13:08:50  aml
// Stack references are now pushed instead of double value.
// Fixed problem with integers too large on formulas.
// Scroll with cursors now works better.
//
// Revision 1.14  1996/02/19 15:47:40  aml
// Fixed abnormality with mouse click.
// Variable width columns implemented, but not yet saved.
// Labels are now a lex element, fixing some aberrant behavior that existed.
//
// Revision 1.13  1996/02/16  23:09:42  aml
// Improved user interf state machine.
// Centralized range definitions.
// Range defined outiside current view work properly.
//
// Revision 1.12  1996/02/16  18:04:13  aml
// Fixed a memory bug in formula copy with purify.
// Fixed a few minor bugs. Functional, stable version.
//
// Revision 1.11  1996/02/13 12:03:59  aml
// Fixed bug with range definition via mouse.
// Fixed bug in range iterators.
//
// Revision 1.10  1996/01/30  16:05:43  aml
// User interface for cell and range copy created.
// Improved user interface state machine when entering and viewing cells.
// Fixed unaligned accesses during formula parsing and io operations.
//
// Revision 1.9  1996/01/11  22:48:56  aml
// Range iterators created.
// Functions sum, max and min now work properly.
// Negative numbers now allowed by flex (oops :-)
//
// Revision 1.8  1996/01/10  18:53:45  aml
// Fixed limited integer range of cells.
// Fixed incorrect code in spreadsheet type.
// Users interface improved. Cursors and mouse clicks
// now work in the most basic modes.
//
// Revision 1.7  1996/01/09  18:34:57  aml
// Load, save, open, close and exit now work properly (hopefuly).
// Sheet utility functions also work : SheetExists, SheetEmpty, SheetModified
//
// Revision 1.6  1996/01/07  09:07:53  aml
// Sheet::save and Sheet::load created.
// Program can now write and read wk1 files.
// Slight changes made to relative references. Bit 14 is now always 0.
//
// Revision 1.5  1996/01/05  23:06:04  aml
// Cell references evaluated.
// Spreadsheet is recalculated at every change, by an arbitrary order.
// Reformulated program structure. Evaluation and reverse parsing
// are member functions of Sheet.
//
// Revision 1.4  1996/01/03  23:07:19  aml
// Absolute and relative references to cells introduced.
// They are parsed and reverse parsed, not yet evaluated.
//
// Revision 1.3  1996/01/02  16:22:13  aml
// Formula compilation, evaluation and decompilation now work.
// Cells can be of type label, numerical formula or numbers.
//
// Revision 1.2  1995/12/27  23:13:07  aml
// First draft of Sheet::display
//
// Revision 1.1  1995/12/13  14:33:20  aml
// Initial revision
//
