/*	Copyright (C) 1993 Free Software Foundation, Inc.

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, 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 software; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */


#include <ctype.h>
#include "sysdef.h"
#include "global.h"
#include "basic.h"
#include "cmd.h"
#include "regions.h"
#include "window.h"
#include "io-term.h"
#include "io-generic.h"
#include "io-abstract.h"
#include "io-utils.h"
#include "io-curses.h"
#include "ref.h"
#include "format.h"
#include "lists.h"
#include "io-edit.h"
#include "eval.h"
#include "byte-compile.h"


/* Used by motion commands. */
const int colmagic[] = {0, 0, 1, -1, 1, -1, 1, -1, 0};
const int rowmagic[] = {-1, 1, 0, 0, -1, -1, 1, 1, 0};

char * motion_name[] =  
{
  "up",
  "down",
  "right",
  "left",
  "up right",
  "up left",
  "down right",
  "down left",
  "no motion"
};


/* This table ought to be a user parameter. */
enum motion_magic complementary_motion[] = 
{
  magic_right,
  magic_right,
  magic_down,
  magic_down,
  magic_right,
  magic_right,
  magic_right,
  magic_right,
  magic_no_motion,
};

enum motion_magic opposite_motion[] = 
{
  magic_down,
  magic_up,
  magic_left,
  magic_right,
  magic_down_left,
  magic_down_right,
  magic_up_left,
  magic_up_right,
  magic_no_motion,
};


/* Indexed by MAGIC + 1 */
const int boundrymagic[3] = { MIN_ROW, NON_ROW, MAX_ROW };




/* A very basic command. */

#ifdef __STDC__
void
noop (void)
#else
void
noop ()
#endif
{}



/* Commands that inser/delete rows/columns. */

#ifdef __STDC__
void
insert_row (int repeat)
#else
void
insert_row (repeat)
     int repeat;
#endif
{
  struct rng from;
  struct rng to;
  if ((repeat > (MAX_ROW - curow)) || (repeat < 0))
    {
      io_error_msg ("insert-row: prefix argument out of range.");
      return;
    }
  from.lc = MIN_COL;
  from.hc = MAX_COL;
  from.lr = curow;
  from.hr = MAX_ROW - repeat;
  to.lc = MIN_COL;
  to.hc = MIN_COL;
  to.lr = curow + repeat;
  to.hr = curow + repeat;
  move_region (&from, &to);
}

#ifdef __STDC__
void
insert_col (int repeat)
#else
void
insert_col (repeat)
     int repeat;
#endif
{
  struct rng from;
  struct rng to;
  if ((repeat > (MAX_COL - cucol)) || (repeat < 0))
    {
      io_error_msg ("insert-col: prefix argument out of range.");
      return;
    }
  from.lr = MIN_ROW;
  from.hr = MAX_ROW;
  from.lc = cucol;
  from.hc = MAX_COL - repeat;
  to.lr = MIN_ROW;
  to.hr = MIN_ROW;
  to.lc = cucol + repeat;
  to.hc = cucol + repeat;
  move_region (&from, &to);
}

#ifdef __STDC__
void
delete_row (int repeat)
#else
void
delete_row (repeat)
     int repeat;
#endif
{
  struct rng from;
  struct rng to;
  if ((repeat < 0) || (repeat > (MAX_ROW - curow + 1)))
    {
      io_error_msg ("delete-row: prefix argument out of range.");
      return;
    }
  from.lc = MIN_COL;
  from.hc = MAX_COL;
  from.lr = curow + repeat;
  from.hr = MAX_ROW;
  to.lc = MIN_COL;
  to.hc = MIN_COL;
  to.lr = curow;
  to.hr = curow;
  move_region (&from, &to);
}

#ifdef __STDC__
void
delete_col (int repeat)
#else
void
delete_col (repeat)
     int repeat;
#endif
{
  struct rng from;
  struct rng to;
  if ((repeat < 0) || (repeat > (MAX_COL - cucol + 1)))
    {
      io_error_msg ("delete-col: prefix argument out of range.");
      return;
    }
  from.lr = MIN_ROW;
  from.hr = MAX_ROW;
  from.lc = cucol + repeat;
  from.hc = MAX_COL;
  to.lr = MIN_ROW;
  to.hr = MIN_ROW;
  to.lc = cucol;
  to.hc = cucol;
  move_region (&from, &to);
}



/* Front end to the window functions. */

#ifdef __STDC__
void 
open_window (char *text)
#else
void 
open_window (text)
     char *text;
#endif
{
  int hv;
  int where;

  while (*text == ' ')
    text++;

  if (*text == 'h' || *text == 'H')
    hv = 0;
  else if (*text == 'v' || *text == 'V')
    hv = 1;
  else
    {
      io_error_msg ("Open 'h'orizontal or 'v'ertical window, not '%s'", text);
      return;
    }
  where = atoi (text + 1);
  while (isspace (*text))
    ++text;
  while (isalnum (*text))
    ++text;
  while (isspace (*text))
    ++text;
  if (*text == '%')
    {
      where *= (hv
		? (cwin->numr + (cwin->lh_wid ? label_rows : 0))
		: (cwin->numc + cwin->lh_wid));
      where /= 100;
    }
  io_win_open (hv, where);
}

#ifdef __STDC__
void
hsplit_window (void)
#else
void
hsplit_window ()
#endif
{
  open_window ("h50%");
}


#ifdef __STDC__
void
vsplit_window (void)
#else
void
vsplit_window ()
#endif
{
  open_window ("v50%");
}


#ifdef __STDC__
void 
close_window (char *text)
#else
void 
close_window (text)
     char *text;
#endif
{
  int num;

  num = atoi (text) - 1;

  if (num < 0 || num >= nwin)
    {
      io_error_msg ("Window %num?", text);
      return;
    }
  if (nwin == 1)
    {
      io_error_msg ("You can't close the last window!");
      return;
    }
  io_win_close (&wins[num]);
}

#ifdef __STDC__
void 
delete_window (void)
#else
void 
delete_window ()
#endif
{
  io_win_close (cwin);
}

#ifdef __STDC__
void
delete_other_windows (void)
#else
void
delete_other_windows ()
#endif
{
  if (nwin > 1)
    {
      CELLREF r = curow;
      CELLREF c = cucol;
      while (nwin > 1)
	io_win_close (cwin);
      io_move_cell_cursor (r, c);
    }
}

#ifdef __STDC__
void 
nicely_goto_window (int n)
#else
void 
nicely_goto_window (n)
     int n;
#endif
{
  if (input_active)
    {
      io_cellize_cursor ();
      window_after_input = n;
      input_active = 0;
      the_cmd_frame->top_keymap = map_id ("main");
      return;
    }
  else
    {
      if ((window_after_input >= 0)
	  && ((window_after_input % nwin) == n))
	{
	  io_inputize_cursor ();
	  window_after_input = -1;
	  input_active = 1;
	  the_cmd_frame->top_keymap =
	    map_id (the_cmd_frame->cmd
		    ? the_cmd_arg.style->keymap
		    : "main");
	}
      else
	io_set_cwin (&wins[n]);
    }
}

#ifdef __STDC__
void
goto_minibuffer (void)
#else
void
goto_minibuffer ()
#endif
{
  if (window_after_input < 0)
    {
      if (!input_active)
	io_error_msg ("Minibuffer not active.");
    }
  else
    nicely_goto_window ((window_after_input % nwin));
}


#ifdef __STDC__
void
goto_window (char *text)
#else
void
goto_window (text)
     char *text;
#endif
{
  int n;
  n = atoi (text) - 1;
  if (n < 0 || n > nwin)
    {
      io_error_msg ("Window %s doesn't exist.", text);
      return;
    }
  else
    nicely_goto_window (n);
}


#ifdef __STDC__
void 
other_window (void)
#else
void 
other_window ()
#endif
{
  int n = cwin - wins;
  if (!input_active)
    n = (n + 1) % nwin;
  nicely_goto_window (n);
}

#ifdef __STDC__
int
set_window_option (int set_opt, char *text)
#else
int
set_window_option (set_opt, text)
     int set_opt;
     char *text;
#endif
{
  int n;
  int stat;
  static struct opt
    {
      char *text;
      int bits;
    }
  opts[] =
  {
    {
      "reverse", WIN_EDGE_REV
    }
    ,
    {
      "standout", WIN_EDGE_REV
    }
    ,
    {
      "page", WIN_PAG_HZ | WIN_PAG_VT
    }
    ,
    {
      "pageh", WIN_PAG_HZ
    }
    ,
    {
      "pagev", WIN_PAG_VT
    }
    ,
    {
      "lockh", WIN_LCK_HZ
    }
    ,
    {
      "lockv", WIN_LCK_VT
    }
    ,
    {
      "edges", WIN_EDGES
    }
  };
  if ((stat = (!strincmp (text, "status", 6) && isspace (text[6])))
      || (!strincmp (text, "input", 5) && isspace (text[5])))
    {
      int n = set_opt ? atoi (text + 6 + stat) : 0;	/* A little pun. */
      int new_inp = stat ? user_input : n;
      int new_stat = stat ? n : user_status;
      io_set_input_status (new_inp, new_stat, 1);
    }
  else if (!strincmp (text, "link", 4))
    {
      if (set_opt)
	{
	  n = atoi (text + 4) - 1;
	  if (n < 0 || n > nwin)
	    io_error_msg ("Can't '%s': window # out of range", text);
	  else
	    cwin->link = n;
	}
      else
	cwin->link = -1;
    }
  else if (set_opt && !stricmp (text, "unlink"))
    cwin->link = -1;
  else if (set_opt && !strincmp (text, "row ", 4))
    {
      text += 4;
      curow = astol (&text);
    }
  else if (set_opt && !strincmp (text, "col ", 4))
    {
      text += 4;
      cucol = astol (&text);
    }
  else
    {
      for (n = 0; n < sizeof (opts) / sizeof (struct opt); n++)
	if (!stricmp (text, opts[n].text))
	  {
	    if (set_opt)
	      cwin->flags |= opts[n].bits;
	    else
	      cwin->flags &= ~opts[n].bits;
	    break;
	  }

      if (n == sizeof (opts) / sizeof (struct opt))
	  return 0;
    }
  return 1;
}

#ifdef __STDC__
void
show_window_options (void)
#else
void
show_window_options ()
#endif
{
  int n;

  cwin->win_curow = curow;
  cwin->win_cucol = cucol;
  if (user_status)
    io_text_line ("Status line at %d", user_status);
  else
    io_text_line ("Status line disabled.");
  io_text_line ("");
  for (n = 0; n < nwin; n++)
    {
      int flags = wins[n].flags;
      io_text_line ("Window #%d showing %s, with cursor at %s",
		    n + 1,
		    range_name (&wins[n].screen),
		    cell_name (wins[n].win_curow, wins[n].win_cucol));
      io_text_line ("   Options:  %sedges (%sreverse)%s%s%s%s",
		    flags & WIN_EDGES ? "" : "no",
		    flags & WIN_EDGE_REV ? "" : "no",
		    flags & WIN_PAG_HZ ? ", pageh" : "",
		    flags & WIN_PAG_VT ? ", pagev" : "",
		    flags & WIN_LCK_HZ ? ", lockh" : "",
		    flags & WIN_LCK_VT ? ", lockv" : "");
      if (wins[n].link != -1)
	io_text_line ("Linked to window %d", wins[n].link + 1);
    }
}

#ifdef __STDC__
void
recenter_window (void)
#else
void
recenter_window ()
#endif
{
  io_recenter_cur_win ();
}



/* Trivial front-end commands. */


#ifdef __STDC__
void
suspend_oleo (void)
#else
void
suspend_oleo ()
#endif
{
  if (using_curses)
    {
      stop_curses ();
#ifdef SIGTSTP
      kill (getpid (), SIGTSTP);
#else
#ifdef SIGSTOP
       kill (getpid (), SIGSTOP);
#endif
#endif
      cont_curses ();
    }
}

#ifdef __STDC__
void
recalculate (int all)
#else
void
recalculate (all)
     int all;
#endif
{
  current_cycle++;
  if (all)
    {
      CELLREF row;
      CELLREF col;
      struct rng all;
      all.lr = MIN_ROW;
      all.hr = MAX_ROW;
      all.lc = MIN_COL;
      all.hc = MAX_COL;
      find_cells_in_range (&all);
      while (next_row_col_in_range (&row, &col))
	push_cell (row, col);
    }
  while (eval_next_cell ())
    ;
}


#ifdef __STDC__
void
kill_oleo (void)
#else
void
kill_oleo ()
#endif
{
  io_close_display ();
  exit (0);
}


#ifdef __STDC__
void
kill_all_cmd (void)
#else
void
kill_all_cmd ()
#endif
{
  clear_spreadsheet ();
  io_repaint ();
}

#ifdef __STDC__
void
redraw_screen (void)
#else
void
redraw_screen ()
#endif
{
  io_repaint ();
}



/* Motion commands. */

#ifdef __STDC__
void
shift_cell_cursor (int dir, int repeat)
#else
void
shift_cell_cursor (dir, repeat)
     int dir;
     int repeat;
#endif
{
  io_shift_cell_cursor (dir, repeat);
}


#ifdef __STDC__
void
scroll_cell_cursor (int dir, int repeat)
#else
void
scroll_cell_cursor (dir, repeat)
     int dir;
     int repeat;
#endif
{
  io_scroll_cell_cursor (dir, repeat);
}



#ifdef __STDC__
void
goto_region (struct rng *r)
#else
void
goto_region (r)
     struct rng *r;
#endif
{
  (void) io_move_cell_cursor (r->lr, r->lc);

  if (r->hr != r->lr || r->hc != r->lc)
    {
      mkrow = r->hr;
      mkcol = r->hc;
    }
  else if (mkrow != NON_ROW)
    mkrow = NON_ROW;
  io_update_status ();
}

#ifdef __STDC__
void
goto_cell (struct rng * rng)
#else
void
goto_cell (rng)
     struct rng * rng;
#endif
{
  rng->hr = mkrow;
  rng->hc = mkcol;
  goto_region (rng);
}

#ifdef __STDC__
void
exchange_point_and_mark (int clrmk)
#else
void
exchange_point_and_mark (clrmk)
     int clrmk;
#endif
{
  struct rng rng;
  if (clrmk)
    {
      rng.lr = curow;
      rng.lc = cucol;
      rng.hr = NON_ROW;
      rng.hc = NON_COL;
      goto_region (&rng);
    }
  else if (mkrow != NON_ROW)
    {
      rng.lr = mkrow;
      rng.lc = mkcol;
      rng.hr = curow;
      rng.hc = cucol;
      goto_region (&rng);
    }
}

#ifdef __STDC__
static CELLREF
first_filled_col (CELLREF row)
#else
static CELLREF
first_filled_col (row)
     CELLREF row;
#endif
{
  struct rng rng;
  CELLREF r;
  CELLREF c;
  rng.lr = row;
  rng.hr = row;
  rng.lc = MIN_COL;
  rng.hc = MAX_COL;
  find_cells_in_range (&rng);
  while (1)
    {
      CELL * cp;
      cp = next_row_col_in_range (&r, &c);
      if (!cp)
	break;
      if (GET_TYP(cp))
	{
	  no_more_cells ();
	  return c;
	}
    }
  return NON_COL;
}

#ifdef __STDC__
static CELLREF
last_filled_col (CELLREF row)
#else
static CELLREF
last_filled_col (row)
     CELLREF row;
#endif
{
  struct rng rng;
  CELLREF r;
  CELLREF c;
  CELLREF bestc = MIN_COL;
  rng.lr = row;
  rng.hr = row;
  rng.lc = MIN_COL;
  rng.hc = MAX_COL;
  find_cells_in_range (&rng);
  while (1)
    {
      CELL * cp;
      cp = next_row_col_in_range (&r, &c);
      if (!cp)
	break;
      if (GET_TYP(cp))
	bestc = c;
    }
  return bestc;
}

#ifdef __STDC__
static CELLREF
first_filled_row (CELLREF col)
#else
static CELLREF
first_filled_row (col)
     CELLREF col;
#endif
{
  struct rng rng;
  CELLREF r;
  CELLREF c;
  CELL * cp;
  rng.lr = MIN_ROW;
  rng.hr = MAX_ROW;
  rng.lc = col;
  rng.hc = col;
  find_cells_in_range (&rng);
  while (1)
    {
      cp = next_row_col_in_range (&r, &c);
      if (!cp)
	break;
      if (GET_TYP(cp))
	{
	  no_more_cells ();
	  return r;
	}
    }
  return NON_ROW;
}

#ifdef __STDC__
static CELLREF
last_filled_row (CELLREF col)
#else
static CELLREF
last_filled_row (col)
     CELLREF col;
#endif
{
  struct rng rng;
  CELLREF r;
  CELLREF c;
  CELLREF bestr = MIN_ROW;
  CELL * cp;
  rng.lr = MIN_ROW;
  rng.hr = MAX_ROW;
  rng.lc = col;
  rng.hc = col;
  find_cells_in_range (&rng);
  while (1)
    {
      cp = next_row_col_in_range (&r, &c);
      if (!cp)
	break;
      if (GET_TYP(cp))
	bestr = r;
    }
  return bestr;
}

#ifdef __STDC__
static CELLREF
max_filled_row (void)
#else
static CELLREF
max_filled_row ()
#endif
{
  CELLREF max_r = highest_row ();
  while (max_r != MIN_ROW)
    {
      CELLREF c = first_filled_col (max_r);
      if (c != NON_COL)
	break;
      --max_r;
    }
  return max_r;
}


#ifdef __STDC__
static CELLREF
max_filled_col (void)
#else
static CELLREF
max_filled_col ()
#endif
{
  CELLREF max_c = highest_col ();
  while (max_c != MIN_COL)
    {
      CELLREF r = first_filled_row (max_c);
      if (r != NON_COL)
	break;
      --max_c;
    }
  return max_c;
}

#ifdef __STDC__
static void 
mk_for_extreme (struct rng * rng)
#else
static void 
mk_for_extreme (rng)
     struct rng * rng;
#endif
{
  if (mkrow != NON_ROW)
    {
      rng->hr = mkrow;
      rng->hc = mkcol;
    }
  else
    {
      rng->hc = cucol;
      rng->hr = curow;
    }
}

#ifdef __STDC__
void
upper_left (void)
#else
void
upper_left ()
#endif
{
  struct rng rng;
  rng.lr = MIN_ROW;
  rng.lc = MIN_COL;
  mk_for_extreme (&rng);
  goto_region (&rng);
}

#ifdef __STDC__
void
lower_left (void)
#else
void
lower_left ()
#endif
{
  struct rng rng;
  rng.lr = max_filled_row ();
  rng.lc = MIN_COL;
  mk_for_extreme (&rng);
  goto_region (&rng);
}

#ifdef __STDC__
void
upper_right (void)
#else
void
upper_right ()
#endif
{
  struct rng rng;
  rng.lr = MIN_ROW;
  rng.lc = max_filled_col ();
  mk_for_extreme (&rng);
  goto_region (&rng);
}

#ifdef __STDC__
void
lower_right (void)
#else
void
lower_right ()
#endif
{
  struct rng rng;
  rng.lr = max_filled_row ();
  rng.lc = max_filled_col ();
  mk_for_extreme (&rng);
  goto_region (&rng);
}

#ifdef __STDC__
void
mark_cell_cmd (int popmk)
#else
void
mark_cell_cmd (popmk)
     int popmk;
#endif
{
  if (popmk)
    {
      if (mkrow != NON_ROW)
	{
	  struct rng rng;
	  rng.lr = mkrow;
	  rng.lc = mkcol;
	  rng.hr = NON_ROW;
	  rng.hc = NON_COL;
	  goto_region (&rng);
	}
    }
  else
    {
      mkrow = curow;
      mkcol = cucol;
      io_update_status ();
    }
}

#ifdef __STDC__
void
unmark_cmd (void)
#else
void
unmark_cmd ()
#endif
{
  mkrow = NON_ROW;
  mkcol = NON_COL;
  io_update_status ();
}


/* This is a bit kludgey. Input line editting has its own event loop (grr!),
 * and all of its state is private.  These mouse commands can't entirely
 * handle it when the target is in the input line.  In that case, they
 * save the decoded mouse event where io_get_line can pick it up:
 */
struct mouse_event last_mouse_event;

#ifdef __STDC__
void
do_mouse_goto (void)
#else
void
do_mouse_goto ()
#endif
{
  if (!last_mouse_event.downp)
    return;
  if (last_mouse_event.location >= 0 && last_mouse_event.downp)
    {
      if (input_active)
	{
	  io_cellize_cursor ();
	  window_after_input = last_mouse_event.location;
	  input_active = 0;
	  the_cmd_frame->top_keymap = map_id ("main");
	}
      io_set_cwin (&wins[last_mouse_event.location]);
      io_move_cell_cursor (last_mouse_event.r, last_mouse_event.c);
    }
  else if (last_mouse_event.location == MOUSE_ON_INPUT)
    {
      goto_minibuffer ();
#ifdef HAVE_X11_X_H
      if (using_x)
	goto_char (io_col_to_input_pos (last_mouse_event.col));
#endif
    }
  else
    io_bell ();
}

#ifdef __STDC__
void
do_mouse_mark (void)
#else
void
do_mouse_mark ()
#endif
{
  if (last_mouse_event.location >= 0 && last_mouse_event.downp)
    {
      mkrow = last_mouse_event.r;
      mkcol = last_mouse_event.c;
    }
}


#ifdef __STDC__
void
do_mouse_mark_and_goto (void)
#else
void
do_mouse_mark_and_goto ()
#endif
{
  if (last_mouse_event.location >= 0 && last_mouse_event.downp)
    {
      mkrow = curow;
      mkcol = cucol;
    }
  do_mouse_goto ();
}

#ifdef __STDC__
void
do_mouse_cmd (void (*fn) ())
#else
void
do_mouse_cmd (fn)
     void (*fn) ();
#endif
{
  int seq = real_get_chr ();
  dequeue_mouse_event (&last_mouse_event, seq);
  fn ();
}

#ifdef __STDC__
void
mouse_mark_cmd (void)
#else
void
mouse_mark_cmd ()
#endif
{
  do_mouse_cmd (do_mouse_mark);
}


#ifdef __STDC__
void
mouse_goto_cmd (void)
#else
void
mouse_goto_cmd ()
#endif
{
  do_mouse_cmd (do_mouse_goto);
}

#ifdef __STDC__
void
mouse_mark_and_goto_cmd (void)
#else
void
mouse_mark_and_goto_cmd ()
#endif
{
  do_mouse_cmd (do_mouse_mark_and_goto);
}



/* Commands used to modify cell formulas. */

#ifdef __STDC__
void 
kill_cell_cmd (void)
#else
void 
kill_cell_cmd ()
#endif
{
  CELL *cp;

  cp = find_cell (curow, cucol);
  if (!cp)
    return;
  if ((GET_LCK (cp) == LCK_DEF && default_lock == LCK_LCK) || GET_LCK (cp) == LCK_LCK)
    {
      io_error_msg ("Cell %s is locked", cell_name (curow, cucol));
      return;
    }
  new_value (curow, cucol, "");
  cp->cell_flags = 0;
  cp->cell_font = 0;
  modified = 1;
}




/* A front end to sorting. */

#ifdef __STDC__
void
sort_region_cmd (char *ptr)
#else
void
sort_region_cmd (ptr)
     char *ptr;
#endif
{
  struct rng tmp_rng;

  if (get_abs_rng (&ptr, &sort_rng))
    {
      io_error_msg ("Can't find a range to sort in %s", ptr);
      return;
    }

  cur_row = sort_rng.lr;
  cur_col = sort_rng.lc;

  while (*ptr == ' ')
    ptr++;
  if (!*ptr)
    {
      sort_ele.lr = 0;
      sort_ele.lc = 0;
      sort_ele.hr = 0;
      sort_ele.hc = 0;
    }
  else if (!parse_cell_or_range (&ptr, &sort_ele))
    {
      io_error_msg ("Can't parse elements in %s", ptr);
      return;
    }
  else
    {
      sort_ele.lr -= sort_rng.lr;
      sort_ele.lc -= sort_rng.lc;
      sort_ele.hr -= sort_rng.lr;
      sort_ele.hc -= sort_rng.lc;
    }

  sort_keys_num = 0;
  while (*ptr == ' ')
    ptr++;
  for (; *ptr;)
    {
      if (sort_keys_num == sort_keys_alloc)
	{
	  sort_keys_alloc++;
	  if (sort_keys_alloc > 1)
	    sort_keys = ck_realloc (sort_keys, sort_keys_alloc * sizeof (struct cmp));
	  else
	    sort_keys = ck_malloc (sizeof (struct cmp));
	}
      sort_keys[sort_keys_num].mult = 1;
      if (*ptr == '+')
	ptr++;
      else if (*ptr == '-')
	{
	  sort_keys[sort_keys_num].mult = -1;
	  ptr++;
	}
      if (!*ptr)
	{
	  sort_keys[sort_keys_num].row = 0;
	  sort_keys[sort_keys_num].col = 0;
	  sort_keys_num++;
	  break;
	}
      if (!parse_cell_or_range (&ptr, &tmp_rng) || tmp_rng.lr != tmp_rng.hr || tmp_rng.lc != tmp_rng.hc)
	{
	  io_error_msg ("Can't parse key #%d in %s", sort_keys_num + 1, ptr);
	  sort_keys_num = -1;
	  return;
	}
      sort_keys[sort_keys_num].row = tmp_rng.lr - sort_rng.lr;
      sort_keys[sort_keys_num].col = tmp_rng.lc - sort_rng.lc;
      sort_keys_num++;

      while (*ptr == ' ')
	ptr++;
    }
  if (sort_keys_num == 0)
    {
      if (sort_keys_alloc == 0)
	{
	  sort_keys_alloc++;
	  sort_keys = ck_malloc (sizeof (struct cmp));
	}
      sort_keys[0].mult = 1;
      sort_keys[0].row = 0;
      sort_keys[0].col = 0;
      sort_keys_num++;
    }
  sort_region ();
  io_repaint ();
}





#ifdef __STDC__
void
imove (struct rng * rng, int ch)
#else
void
imove (rng, ch)
     struct rng * rng;
     int ch;
#endif
{
  if ((ch > 0) && (ch != 27))
    pushed_back_char = ch;

  goto_region (rng);
}




/* Incremental navigation
 *
 * This should be called in edit mode while gathering arguments 
 * for a complex command.  The expected the_cmd_arg.
 */

#define MIN(A,B)	((A) < (B) ? (A) : (B))

/* PAGE_RULE can be 0: page by  rows, 1: cols, 2 shorter of rows/cols,
 *    		   -1: don't page at all.
 */

#ifdef __STDC__
void
inc_direction (int count, int page_rule, int hack_magic)
#else
void
inc_direction (count, page_rule, hack_magic)
     int count;
     int page_rule;
     int hack_magic;
#endif
{
  if (check_editting_mode ())
    return;

  if (page_rule >= 0)
    {
      int page_size;

      switch (page_rule)
	{
	default:
	case 0:
	  page_size = (cwin->screen.hr - cwin->screen.lr);
	  break;
	case 1:
	  page_size = (cwin->screen.hc - cwin->screen.lc);
	  break;
	case 2:
	  page_size = MIN ((cwin->screen.hr - cwin->screen.lr),
			   (cwin->screen.hc - cwin->screen.lc));
	  break;
	}
      count *= page_size;
    }

  if (the_cmd_frame->cmd && the_cmd_arg.inc_cmd)
    the_cmd_arg.inc_cmd (hack_magic, count);
}




/* The commands that move to the extreme of a row[col] may also move
 * forward or backward some number of col[row], according to the prefix
 * arg.  This is the logic of that.  This function returns the new col[row]
 * and operates on the presumption that MIN_ROW == MIN_COL and
 * MAX_ROW == MAX_COL.
 */

#ifdef __STDC__
static CELLREF
extreme_cmd_orth_motion (int count, CELLREF current)
#else
static CELLREF
extreme_cmd_orth_motion (count, current)
     int count;
     CELLREF current;
#endif
{
  --count;
  if (count > (MAX_ROW - current))
    count =  (MAX_ROW - current);
  else if (-count > (current - MIN_ROW))
    count = (MIN_ROW - current);
  return current + count;
}


#ifdef __STDC__
void
beginning_of_row (int count)
#else
void
beginning_of_row (count)
     int count;
#endif
{
  struct rng rng;
  rng.lr = extreme_cmd_orth_motion (count, curow);
  rng.lc = MIN_COL;
  rng.hr = mkrow;
  rng.hc = mkcol;
  goto_region (&rng);
}

#ifdef __STDC__
void
end_of_row (int count)
#else
void
end_of_row (count)
     int count;
#endif
{
  struct rng rng;
  rng.lr = extreme_cmd_orth_motion (count, curow);
  rng.lc = last_filled_col (rng.lr);
  rng.hr = mkrow;
  rng.hc = mkcol;
  goto_region (&rng);
}

#ifdef __STDC__
void
beginning_of_col (int count)
#else
void
beginning_of_col (count)
     int count;
#endif
{
  struct rng rng;
  rng.lr = MIN_ROW;
  rng.lc = extreme_cmd_orth_motion (count, cucol);
  rng.hr = mkrow;
  rng.hc = mkcol;
  goto_region (&rng);
}

#ifdef __STDC__
void
end_of_col (int count)
#else
void
end_of_col (count)
     int count;
#endif
{
  struct rng rng;
  rng.lc = extreme_cmd_orth_motion (count, cucol);
  rng.lr = last_filled_row (rng.lc);
  rng.hr = mkrow;
  rng.hc = mkcol;
  goto_region (&rng);
}


#ifdef __STDC__
static void
skip_empties (CELLREF * rout, CELLREF * cout, int magic)
#else
static void
skip_empties (rout, cout, magic)
     CELLREF * rout;
     CELLREF * cout;
     int magic;
#endif
{
  CELLREF r = *rout;
  CELLREF c = *cout;
  CELL * cp = find_cell (r, c);

  while (!cp || !GET_TYP (cp))
    {
      if (r != boundrymagic [rowmagic [magic] + 1])
	r += rowmagic [magic];
      else if (rowmagic [magic])
	break;
      if (c != boundrymagic [colmagic [magic] + 1])
	c += colmagic [magic];
      else if (colmagic[magic])
	break;
      cp = find_cell (r, c);
    }

  *rout = r;
  *cout = c;
}

#ifdef __STDC__
void
scan_cell_cursor (int magic, int count)
#else
void
scan_cell_cursor (magic, count)
     int magic;
     int count;
#endif
{
  CELLREF r = curow;
  CELLREF c = cucol;
  CELLREF last_r = r;
  CELLREF last_c = c;

  skip_empties (&r, &c, magic);
  {
    CELL * cp = find_cell (r, c);
    if (!(cp && GET_TYP (cp)))
      return;
  }
  while (count)
    {
      CELL * cp = find_cell (r, c);
      while (!cp || !GET_TYP (cp))
	{
	  if (r != boundrymagic [rowmagic [magic] + 1])
	    r += rowmagic [magic];
	  else if (rowmagic [magic])
	    break;
	  if (c != boundrymagic [colmagic [magic] + 1])
	    c += colmagic [magic];
	  else if (colmagic[magic])
	    break;
	  cp = find_cell (r, c);
	}
      while (cp && GET_TYP (cp))
	{
	  if (r != boundrymagic [rowmagic [magic] + 1])
	    r += rowmagic [magic];
	  else if (rowmagic[magic])
	    break;
	  if (c != boundrymagic [colmagic [magic] + 1])
	    c += colmagic [magic];
	  else if (colmagic [magic])
	    break;
	  last_r = r;
	  last_c = c;
	  cp = find_cell (r, c);
	}
      --count;
    }
  {
    struct rng rng;
    rng.lr = last_r;
    rng.lc = last_c;
    rng.hr = mkrow;
    rng.hc = mkcol;
    goto_region (&rng);
  }
}


#ifdef __STDC__
void
edit_cell (char * new_formula)
#else
void
edit_cell (new_formula)
     char * new_formula;
#endif
{
  char * fail;
  fail = new_value (setrow, setcol, new_formula);
  if (fail)
    io_error_msg (fail);
  else
    modified = 1;
}


#ifdef __STDC__
void
set_region_formula (struct rng * rng, char * str)
#else
void
set_region_formula (rng, str)
     struct rng * rng;
     char * str;
#endif
{
  CELLREF row, col;

  for (row = rng->lr; row <= rng->hr; ++row)
    for (col = rng->lc; col <= rng->hc; ++col)
      {
	char * error = new_value (row, col, str);
	if (!error)
	  modified = 1;
	if (error)
	  {
	    io_error_msg (error);
	    return;
	  }
      }
}

#ifdef __STDC__
void
goto_edit_cell (int c)
#else
void
goto_edit_cell (c)
     int c;
#endif
{
  pushed_back_char = c;
  execute_command ("edit-cell");
}


/* File i/o */

int sneaky_linec = 0;	/* for error reporting for now (see io-term.c) */

#ifdef __STDC__
void 
read_cmds_cmd (FILE *fp)
#else
void 
read_cmds_cmd (fp)
     FILE *fp;
#endif
{
  struct line line;
  char *ptr;
  init_line (&line);
  sneaky_linec = 0;
  while (read_line (&line, fp, &sneaky_linec))
    {
      for (ptr = line.buf; isspace (*ptr); ptr++);
      if (!*ptr || (*ptr == '#'))
	continue;
      execute_command (ptr);
    }
}


static int run_load_hooks = 1;
static char load_hooks_string[] = "load_hooks";

#ifdef __STDC__
void
read_file_and_run_hooks (FILE * fp, int ismerge, char * name)
#else
void
read_file_and_run_hooks (fp, ismerge, name)
     FILE * fp;
     int ismerge;
     char * name;
#endif
{
  if (!ismerge)
    {
      if (current_filename)
	free (current_filename);
      current_filename = name ? ck_savestr (name) : 0;
    }
  (*read_file)(fp, ismerge);
  if (run_load_hooks)
    {
      struct var * v;
      v = find_var (load_hooks_string, sizeof (load_hooks_string) - 1);
      if (v && v->var_flags != VAR_UNDEF)
	execute_command (load_hooks_string);
    }
}

/* If TURN_ON is 0, this toggles whether load hooks are run.
 * Otherwise, it turns load hooks on.
 */

#ifdef __STDC__
void
toggle_load_hooks (int turn_on)
#else
void
toggle_load_hooks (turn_on)
     int turn_on;
#endif
{
  if (!turn_on && run_load_hooks)
    {
      run_load_hooks = 0;
      io_info_msg ("load hooks turned off");
    }
  else
    {
      run_load_hooks = 1;
      io_info_msg ("load hooks turned on");
    }
}

#ifdef __STDC__
void
write_cmd (FILE *fp, char * name)
#else
void
write_cmd (fp, name)
     FILE *fp;
     char * name;
#endif
{
  if (current_filename)
    free (current_filename);
  current_filename = name ? ck_savestr (name) : 0;
  (*write_file) (fp, 0);
  modified = 0;
}

#ifdef __STDC__
void
read_cmd (FILE *fp, char * name)
#else
void
read_cmd (fp, name)
     FILE *fp;
     char * name;
#endif
{
  read_file_and_run_hooks (fp, 0, name);
}

#ifdef __STDC__
void
read_merge_cmd (FILE *fp)
#else
void
read_merge_cmd (fp)
     FILE *fp;
#endif
{
  (*read_file) (fp, 1);
}

#ifdef __STDC__
void
write_reg_cmd (FILE *fp, struct rng *rng)
#else
void
write_reg_cmd (fp, rng)
     FILE *fp;
     struct rng *rng;
#endif
{
  (*write_file) (fp, rng);
}




/* Cell attributes. */
#ifdef __STDC__
void
set_region_height (struct rng * rng, char * height)
#else
void
set_region_height (rng, height)
     struct rng * rng;
     char * height;
#endif
{
  int hgt;
  char * saved_height = height;

  while (isspace (*height))
    ++height;

  if (   !*height
      || words_imatch (&height, "d")
      || words_imatch (&height, "def")
      || words_imatch (&height, "default"))
    hgt = 0;
  else
    {
      hgt = astol (&height) + 1;
      if (hgt < 0)
        /* noreturn */
	io_error_msg ("Height (%d) can't be less than 0.", hgt);
    }      

  if (*height)
    {
      io_error_msg ("Unknown height '%s'", saved_height);
      /* Doesn't return */
    }
  {
    CELLREF cc;
    for (cc = rng->lr; ;cc++)
      {
	set_height (cc, hgt);
	if (cc == rng->hr)	/* This test goes here to prevent overflow. */
	  break;
      }
    io_recenter_all_win ();
  }
}

#ifdef __STDC__
void
set_region_width (struct rng * rng, char * width)
#else
void
set_region_width (rng, width)
     struct rng * rng;
     char * width;
#endif
{
  char * saved_width = width;
  int wid;

  while (isspace (*width))
    ++width;

  if (   !*width
      || words_imatch (&width, "d")
      || words_imatch (&width, "def")
      || words_imatch (&width, "default"))
    wid = 0;
  else
    {
      wid = astol (&width) + 1;
      if (wid < 0)
        /* noreturn */
	io_error_msg ("Width (%d) can't be less than 0.", wid);
    }      

  if (*width)
    {
      io_error_msg ("Unknown width '%s'", saved_width);
      /* No return. */
    }
  {
    CELLREF cc;
    for (cc = rng->lc; ;cc++)
      {
	set_width (cc, wid);
	if (cc == rng->hc)	/* This test goes here to prevent overflow. */
	  break;
      }
    io_recenter_all_win ();
  }
}


/* PROT may be `d', `p', or `u'. */

#ifdef __STDC__
void
set_region_protection (struct rng * rng, int prot)
#else
void
set_region_protection (rng, prot)
     struct rng * rng;
     int prot;
#endif
{
  if (isupper (prot))
    prot = tolower (prot);
  switch (prot)
    {
    case 'd':
      lock_region (rng, LCK_DEF);
      break;
    case 'p':
      lock_region (rng, LCK_LCK);
      break;
    case 'u':
      lock_region (rng, LCK_UNL);
      break;
    default:
      io_error_msg ("Bad argument to protect-region %c.", prot);
      break;
    }
}

#ifdef __STDC__
void
set_region_alignment (struct rng * rng, int align)
#else
void
set_region_alignment (rng, align)
     struct rng * rng;
     int align;
#endif
{
  int fun = chr_to_jst (align);
  if (fun != -1)
    format_region (rng, -1, fun);
  else			/* if (main_map[align]!=BREAK_CMD) */
    io_error_msg ("Unknown Justify '%s'", char_to_string (align));
}

#ifdef __STDC__
void
set_region_format (struct rng * rng, int fmt)
#else
void
set_region_format (rng, fmt)
     struct rng * rng;
     int fmt;
#endif
{
  format_region (rng, fmt, -1); 
}




#ifdef __STDC__
void
set_def_height (char * height)
#else
void
set_def_height (height)
     char * height;
#endif
{
  char * saved_height = height;
  int hgt;

  while (isspace (*height))
    ++height;

  if (   !*height
      || words_imatch (&height, "d")
      || words_imatch (&height, "def")
      || words_imatch (&height, "default"))
    hgt = 1;
  else
    {
      hgt = astol (&height);
      if (hgt < 0)
        /* noreturn */
	io_error_msg ("Height (%d) can't be less than 0.", hgt);
    }      

  if (*height)
    {
      io_error_msg ("Unknown height '%s'", saved_height);
      /* No return. */
    }
  default_height = hgt;
  io_recenter_all_win ();
}

#ifdef __STDC__
void
set_def_width (char * width)
#else
void
set_def_width (width)
     char * width;
#endif
{
  char * saved_width = width;
  int wid;

  while (isspace (*width))
    ++width;

  if (   !*width
      || words_imatch (&width, "d")
      || words_imatch (&width, "def")
      || words_imatch (&width, "default"))
    wid = 8;
  else
    {
      wid = astol (&width);
      if (wid < 0)
        /* noreturn */
	io_error_msg ("Width (%d) can't be less than 0.", wid);
    }      

  if (*width)
    {
      io_error_msg ("Unknown width '%s'", saved_width);
      /* No return. */
    }
  default_width = wid;
  io_recenter_all_win ();
}

/* PROT may be `d', `p', or `u'. */

#ifdef __STDC__
void
set_def_protection (int prot)
#else
void
set_def_protection (prot)
     int prot;
#endif
{
  if (isupper (prot))
    prot = tolower (prot);
  switch (prot)
    {
    case 'p':
      default_lock = LCK_LCK;
      break;
    case 'u':
      default_lock = LCK_UNL;
      break;
    default:
      io_error_msg ("Bad argument to set-default-protection %c.", prot);
      break;
    }
}

#ifdef __STDC__
void
set_def_alignment (int align)
#else
void
set_def_alignment (align)
     int align;
#endif
{
  int fun = chr_to_jst (align);
  if (fun == -1)
    io_error_msg ("Unknown justification.");

  default_jst = fun;
  io_repaint ();
}

#ifdef __STDC__
void
set_def_format (int fmt)
#else
void
set_def_format (fmt)
     int fmt;
#endif
{
  default_fmt = fmt;
  io_repaint ();
}



#ifdef __STDC__
void
define_usr_fmt (int fmt, char * pos_h, char * neg_h, char * pos_t,
		char * neg_t, char * zero, char * comma, char * decimal,
		char * precision, char * scale)
#else
void
define_usr_fmt (fmt, pos_h, neg_h, pos_t, neg_t, zero,
		comma, decimal, precision, scale)
     int fmt;
     char * pos_h;
     char * neg_h;
     char * pos_t;
     char * neg_t;
     char * zero;
     char * comma;
     char * decimal;
     char * precision;
     char * scale;
#endif
{
  char * usr_buf[9];
  if (fmt < 1 || fmt > 16)
    {
      io_error_msg ("Format number out of range %d (should be in [1..16].",
		    fmt);
      /* no return */
    }
  /* Vector to an older interface... */
  --fmt;
  usr_buf[0] = pos_h;
  usr_buf[1] = neg_h;
  usr_buf[2] = pos_t;
  usr_buf[3] = neg_t;
  usr_buf[4] = zero;
  usr_buf[5] = comma;
  usr_buf[6] = decimal;
  usr_buf[7] = precision;
  usr_buf[8] = scale;
  set_usr_stats (fmt, usr_buf);
}



/* Automatic motion while editting cell's: */

/* Hmm... where should this variable *really* go? */

static int auto_motion_direction = magic_down;

#ifdef __STDC__
void
set_auto_direction (enum motion_magic magic)
#else
void
set_auto_direction (magic)
     enum motion_magic magic;
#endif
{
  auto_motion_direction = magic;
  io_info_msg ("Auto-motion direction = %s.", motion_name[magic]);
}

#ifdef __STDC__
void
auto_move (void)
#else
void
auto_move ()
#endif
{
  shift_cell_cursor (auto_motion_direction, 1);
}

#ifdef __STDC__
void
auto_next_set (void)
#else
void
auto_next_set ()
#endif
{
  scan_cell_cursor (opposite_motion[auto_motion_direction], 1);
  {
    CELL * cp = find_cell (curow, cucol);
    if (!cp || !GET_TYP(cp))
      shift_cell_cursor (auto_motion_direction, 1);
  }
  shift_cell_cursor (complementary_motion[auto_motion_direction], 1);
}


/* This decompiles and then recompiles all of the formulas of cells.
 * This is never normally necessary unless you happen to have some
 * spreadsheets written when the byte-code compiler had bugs that
 * made your formulas produce parse errors.
 */

#ifdef __STDC__
void
recompile_spreadsheet (void)
#else
void
recompile_spreadsheet ()
#endif
{
  struct rng rng;
  CELL * cp;
  CELLREF r;
  CELLREF c;
  rng.lr = MIN_ROW;
  rng.lc = MIN_COL;
  rng.hr = MAX_ROW;
  rng.hc = MAX_COL;
  find_cells_in_range (&rng);
  for (cp = next_row_col_in_range (&r, &c);
       cp;
       cp = next_row_col_in_range(&r, &c))
    {
      char * form = decomp (r, c, cp);
      set_cell (r, c, form);
      if (my_cell)
	{
	  update_cell (my_cell);
	  if (is_constant (my_cell->cell_formula))
	    {
	      byte_free (my_cell->cell_formula);
	      my_cell->cell_formula = 0;
	    }
	  io_pr_cell (r, c, my_cell);
	  my_cell = 0;
	}
      decomp_free ();
    }
}

