/*
 * $Id: window.c,v 1.11 2001/02/13 23:38:06 danny Exp $
 *
 * Copyright © 1992, 1993, 1999, 2001 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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef	WITH_DMALLOC
#include <dstdlib.h>
#endif

#include <stdio.h>
#include "global.h"
#include "window.h"
#include "io-generic.h"
#include "io-abstract.h"
#include "io-utils.h"
#include "io-term.h"
#include "cmd.h"
#include "lists.h"
#include "regions.h"

/* Low level window operators. */

#define MIN_WIN_HEIGHT(W) (W->bottom_edge_r \
			   + label_rows * (W->flags & WIN_EDGES ? 2 : 1))

#define MIN_WIN_WIDTH(W) (W->right_edge_c \
			  + label_emcols * (W->flags & WIN_EDGES ? 6 : 1))
#define MIN_CWIN_HEIGHT  MIN_WIN_HEIGHT(cwin)
#define MIN_CWIN_WIDTH  MIN_WIN_WIDTH(cwin)


static void 
do_close_window (int num)
{
  int n;
  struct window *win, *kwin;
  int nlf, nrt, nup, nbl;
  int klo, kho, kld, khd;
  int lo, ho, ld, hd;
  struct tmp
    {
      int l, r, u, b;
    }
   *tmpptr;

  if (nwin == 1)
    {
      io_error_msg ("Attempt to delete sole ordinary window.");
      return;
    }
  tmpptr = ck_malloc (sizeof (struct tmp) * nwin);

  kwin = &wins[num];
  nlf = nrt = nup = nbl = 0;
  klo = kwin->win_over - kwin->lh_wid;
  kho = kwin->win_over + kwin->numc + kwin->right_edge_c - 1;
  kld = kwin->win_down - (kwin->lh_wid ? label_rows : 0);
  khd = kwin->win_down + kwin->numr + kwin->bottom_edge_r - 1;

  for (win = wins; win < &wins[nwin]; win++)
    {
      lo = win->win_over - win->lh_wid;
      ho = win->win_over + win->numc + win->right_edge_c - 1;
      ld = win->win_down - (win->lh_wid ? label_rows : 0);
      hd = win->win_down + win->numr + win->bottom_edge_r - 1;

      /* Match to the left ? */
      if (lo == kho + 1)
	{
	  if (ld >= kld && hd <= khd)
	    tmpptr[nrt++].r = win - wins;
	  else if (hd >= kld && ld <= khd)
	    nrt = nwin;
	}
      else if (ho == klo - 1)
	{
	  if (ld >= kld && hd <= khd)
	    tmpptr[nlf++].l = win - wins;
	  else if (hd >= kld && ld <= khd)
	    nlf = nwin;
	}
      else if (ld == khd + 1)
	{
	  if (lo >= klo && ho <= kho)
	    tmpptr[nbl++].b = win - wins;
	  else if (ho >= kho && lo <= kho)
	    nbl = nwin;
	}
      else if (hd == kld - 1)
	{
	  if (lo >= klo && ho <= kho)
	    tmpptr[nup++].u = win - wins;
	  else if (ho >= kho && lo <= kho)
	    nup = nwin;
	}

    }
  if (nrt == 0)
    nrt = nwin;
  if (nlf == 0)
    nlf = nwin;
  if (nbl == 0)
    nbl = nwin;
  if (nup == 0)
    nup = nwin;
  if (nrt <= nlf && nrt <= nbl && nrt <= nup)
    for (n = 0; n < nrt; n++)
      {
	wins[tmpptr[n].r].numc
	  += kwin->lh_wid + kwin->numc + kwin->right_edge_c;
	wins[tmpptr[n].r].win_over
	  -= kwin->lh_wid + kwin->numc + kwin->right_edge_c;
      }
  else if (nlf <= nbl && nlf <= nup)
    for (n = 0; n < nlf; n++)
      wins[tmpptr[n].l].numc
	+= kwin->lh_wid + kwin->numc + kwin->right_edge_c;
  else if (nbl <= nup)
    for (n = 0; n < nbl; n++)
      {
	wins[tmpptr[n].b].numr
	  += kwin->numr + (kwin->lh_wid ? 1 : 0) * label_rows
	  + kwin->bottom_edge_r;

	wins[tmpptr[n].b].win_down
	  -= kwin->numr + (kwin->lh_wid ? 1 : 0) * label_rows
	  + kwin->bottom_edge_r;
      }
  else
    for (n = 0; n < nup; n++)
      wins[tmpptr[n].u].numr
	+= kwin->numr + (kwin->lh_wid ? 1 : 0) * label_rows;

  if (kwin == cwin && kwin != wins)
    --cwin;
  if (cwin == &wins[nwin - 1])
    --cwin;
  while (kwin < &wins[nwin])
    {
      *kwin = kwin[1];
      kwin++;
    }
  --nwin;
  io_recenter_all_win ();
  return;
}

int
win_label_cols (struct window * win, CELLREF hr)
{
  int lh;

  if ((win->flags & WIN_EDGES) == 0)
    lh = 0;
#if BITS_PER_CELLREF>8
  else if ((win->flags & WIN_PAG_HZ) || hr >= 10000)
    lh = 7;
  else if (hr >= 1000)
    lh = 6;
  else if (hr >= 100)
    lh = 5;
#else
  else if ((win->flags & WIN_PAG_HZ) || hr >= 100)
    lh = 5;
#endif
  else if (hr > 10)
    lh = 4;
  else
    lh = 3;
  lh *= label_emcols;
  return lh;
}

int
win_label_rows (struct window * win)
{
  return (win->flags & WIN_EDGES) ? label_rows : 0;
}

static void 
set_numcols (struct window *win, CELLREF hr)
{
  int lh = win_label_cols (win, hr);
  win->win_over -= win->lh_wid - lh;
  win->numc += win->lh_wid - lh;
  win->lh_wid = lh;
}


static void 
page_axis (CELLREF cur, int (*get) (CELLREF), int total,
	   CELLREF *loP, CELLREF *hiP)
{
  CELLREF lo, hi;
  int w, ww;

  lo = hi = MIN_ROW;
  w = (*get) (hi);
  for (;;)
    {
      ww = (*get) (hi + 1);
      while (w + ww <= total && hi < MAX_ROW)
	{
	  hi++;
	  w += ww;
	  ww = (*get) (hi + 1);
	}
      if (hi >= cur)
	break;
      hi++;
      lo = hi;
      w = ww;
    }
  if (lo > cur || hi > MAX_ROW)
    io_error_msg ("Can't find a non-zero-sized cell page_axis");
  *loP = lo;
  *hiP = hi;
}


static void 
recenter_axis (CELLREF cur, int (*get) (CELLREF), int total,
	       CELLREF *loP, CELLREF *hiP)
{
  CELLREF lo, hi;
  int tot;
  int n;
  int more;

  lo = hi = cur;
  n = tot = (*get) (cur);
  do
    {
      if (lo > MIN_ROW && tot + (n = (*get) (lo - 1)) <= total)
	{
	  --lo;
	  tot += n;
	  more = 1;
	}
      else
	more = 0;
      if (hi < MAX_ROW && tot + (n = (*get) (hi + 1)) <= total)
	{
	  hi++;
	  tot += n;
	  more++;
	}
    }
  while (more);
  *loP = lo;
  *hiP = hi;
}

static void 
recenter_window (struct window *win)
{
  if (win->flags & WIN_PAG_VT)
    page_axis (win->win_curow, get_scaled_height, win->numr,
	       &(win->screen.lr), &(win->screen.hr));
  else
    recenter_axis (win->win_curow, get_scaled_height, win->numr,
		   &(win->screen.lr), &(win->screen.hr));
  set_numcols (win, win->screen.hr);
  if (win->flags & WIN_PAG_HZ)
    page_axis (win->win_cucol, get_scaled_width, win->numc,
	       &(win->screen.lc), &(win->screen.hc));
  else
    recenter_axis (win->win_cucol, get_scaled_width, win->numc,
		   &(win->screen.lc), &(win->screen.hc));
}


/*
 * RESIZE_SCREEN adjusts the windows list after a screen size change.
 * It presumes that Global->scr_lines and Global->scr_cols are the new values.  DR and DC
 * are the changes that just occured to those values.
 */
static void 
resize_screen (int dr, int dc)
{
  int x, n;
  int lines;
  int firstln;
  int ncols;
  int firstcol;
  int old_lines;

  if (!nwin)
    return;

  lines = Global->scr_lines - (!!user_status * status_rows) - input_rows;
  old_lines = lines - dr;
  firstln = (user_input > 0) * input_rows + (user_status > 0) * status_rows;

  /* First, delete windows that will shrink too much. */
  cwin->win_curow = curow;
  cwin->win_cucol = cucol;
  if (dr < 0)
    for (x = 0; x < nwin; x++)
      {
	int rlow =
	(wins[x].win_down - (wins[x].lh_wid ? label_rows : 0) - firstln);
	int rhi = ((wins[x].win_down + wins[x].numr + wins[x].bottom_edge_r)
		   - firstln);
	int sqbelow = dr * rlow;
	int sqtohere = dr * rhi;
	sqbelow /= old_lines;
	sqtohere /= old_lines;
	if (wins[x].numr <= sqbelow - sqtohere)
	  {
	    do_close_window (x);
	    x--;
	  }
      }
  for (x = 0; x < nwin; ++x)
    {
      int rlow =
      (wins[x].win_down - (wins[x].lh_wid ? label_rows : 0) - firstln);
      int rhi = ((wins[x].win_down + wins[x].numr + wins[x].bottom_edge_r)
		 - firstln);
      int sqbelow = dr * rlow;
      int sqtohere = dr * rhi;
      sqbelow /= old_lines;
      sqtohere /= old_lines;
      wins[x].win_down += sqbelow;
      wins[x].numr += sqtohere - sqbelow;
    }

  /* then columns */
  firstcol = 0;
  ncols = Global->scr_cols;
  ncols -= dc;

  /* First, delete windows that will shrink too much. */
  if (dc < 0)
    for (x = 0; x < nwin; x++)
      {
	int clow = (wins[x].win_over - wins[x].lh_wid) - firstcol;
	int chi = (wins[x].win_over + wins[x].numc + wins[x].right_edge_c
		   - firstcol);
	int sqbelow = dc * clow;
	int sqtohere = dc * chi;
	sqbelow /= ncols;
	sqtohere /= ncols;
	if (wins[x].numc <= sqbelow - sqtohere)
	  {
	    do_close_window (x);
	    x--;
	  }
      }
  for (x = 0; x < nwin; ++x)
    {
      int clow = (wins[x].win_over - wins[x].lh_wid) - firstcol;
      int chi = (wins[x].win_over + wins[x].numc + wins[x].right_edge_c
		 - firstcol);
      int sqbelow = dc * clow;
      int sqtohere = dc * chi;
      sqbelow /= ncols;
      sqtohere /= ncols;
      wins[x].win_over += sqbelow;
      wins[x].numc += sqtohere - sqbelow;
    }
  for (n = 0; n < nwin; n++)
    recenter_window (&wins[n]);
  io_repaint ();
}

static void
shift_linked_window (long dn, long ov)
{
  struct window *win;

  win = cwin;
  while (win->link != -1)
    {
      win = &wins[win->link];
      if (win == cwin)		/* Loop check! */
	return;
      if ((win->flags & WIN_LCK_VT) == 0)
	win->win_curow += dn;
      if ((win->flags & WIN_LCK_HZ) == 0)
	win->win_cucol += ov;
      if (win->win_curow < win->screen.lr || win->win_curow > win->screen.hr
	  || win->win_cucol < win->screen.lc || win->win_cucol > win->screen.hc)
	recenter_window (win);
    }
}


static void 
find_nonzero (CELLREF *curp, CELLREF lo, CELLREF hi,
	      int (*get) (CELLREF))
{
  CELLREF cc;
  int n;

  cc = *curp;

  if (cc < hi)
    {
      cc++;
      while ((n = (*get) (cc)) == 0)
	{
	  if (cc == hi)
	    break;
	  cc++;
	}
      if (n)
	{
	  *curp = cc;
	  return;
	}
    }
  if (cc > lo)
    {
      --cc;
      while ((n = (*get) (cc)) == 0)
	{
	  if (cc == lo)
	    break;
	  --cc;
	}
      if (n)
	{
	  *curp = cc;
	  return;
	}
    }
}

static int 
scroll_axis (CELLREF cur, int over, int total, int (*get) (CELLREF),
	     CELLREF *ret1, CELLREF *ret2, int *offp) 
{
  int tot; 

  int inc;
  CELLREF fini;
  int num;
  CELLREF p1, p2;
  int n;

  inc = (over > 0 ? 1 : -1);
  fini = over > 0 ? MAX_ROW : MIN_ROW;
  num = over > 0 ? over : -over;

  if (inc > 0 ? *ret2 == MAX_ROW : *ret1 == MIN_ROW)
    return 1;
  p1 = inc > 0 ? *ret2 + 1 : *ret1 - 1;
  p2 = p1;
  for (;;)
    {
      --num;
      tot = (*get) (p1);
      while (p2 != fini && tot + (n = (*get) (p2 + inc)) <= total)
	{
	  p2 += inc;
	  tot += n;
	}
      if (!num || p2 == fini)
	break;
    }
  if (num)
    return 1;
  while (tot + (n = (*get) (p1 - inc)) <= total)
    {
      p1 -= inc;
      tot += n;
      if (inc > 0)
	(*offp)++;
    }
  if (p1 > p2)
    {
      *ret1 = p2;
      *ret2 = p1;
    }
  else
    {
      *ret1 = p1;
      *ret2 = p2;
    }
  return 0;
}

static int 
page_scroll_axis (CELLREF cur, int over, int total, int (*get) (CELLREF),
		  CELLREF *ret1, CELLREF *ret2, int *offp)
{
  int n_over;
  CELLREF lo, hi;
  int tot;
  int ww = 0;

  n_over = 0;
  lo = hi = MIN_ROW;
  tot = (*get) (hi);
  for (;;)
    {
      while (hi < MAX_ROW && tot + (ww = (*get) (hi + 1)) <= total)
	{
	  hi++;
	  tot += ww;
	}
      if (hi >= cur)
	break;
      hi++;
      n_over++;
      lo = hi;
      tot = ww;
    }
  n_over += over;
  if (n_over < 0)
    return 1;

  lo = hi = MIN_ROW;
  tot = (*get) (hi);
  for (;;)
    {
      while (hi < MAX_ROW && tot + (ww = (*get) (hi + 1)) <= total)
	{
	  hi++;
	  tot += ww;
	}
      if (!n_over || hi == MAX_ROW)
	break;
      --n_over;
      hi++;
      lo = hi;
      tot = ww;
    }
  if (hi == MAX_ROW && n_over)
    return 1;
  *ret1 = lo;
  *ret2 = hi;
  return 0;
}



/* External window interface */

void 
io_set_label_size (int r, int c)
{
  /* fixme */
}

void 
io_set_scr_size (int lines, int cols)
{
  int dl = lines - Global->scr_lines;
  int dc = cols - Global->scr_cols;

  Global->scr_lines = lines;
  Global->scr_cols = cols;

  resize_screen (dl, dc);
}

void 
io_set_input_rows (int n)
{
  input_rows = n;
  io_set_input_status (user_input, user_status, 1);
}

void 
io_set_status_rows (int n)
{
  status_rows = n;
  io_set_input_status (user_input, user_status, 1);
}

void 
io_set_input_status (int inp, int stat, int redraw)
{
  int inpv = inp < 0 ? -inp : inp;
  int inpsgn = inp == inpv ? 1 : -1;
  int statv = stat < 0 ? -stat : stat;
  int statsgn = stat == statv ? 1 : -1;
  int new_ui;
  int new_us;
  int new_inp;
  int new_stat;

  if (inpv == 0 || inpv > 2)
    io_error_msg ("Bad input location %d; it should be +/- 1, or 2", inp);
  else if (statv > 2)
    io_error_msg ("Bad status location %d; it should be +/- 0, 1, or 2",
		  inp);
  else
    {
      new_ui = inp;
      new_us = stat;
      if (inpsgn != statsgn)
	{
	  if (inpsgn > 0)
	    {
	      new_inp = 0;
	      new_stat = Global->scr_lines - status_rows;
	    }
	  else
	    {
	      new_inp = Global->scr_lines - input_rows;
	      new_stat = 0;
	    }
	}
      else
	{
	  if (inpv > statv)
	    {
	      new_inp = new_us ? status_rows : 0;
	      new_stat = 0;
	    }
	  else
	    {
	      new_inp = 0;
	      new_stat = input_rows;
	    }
	  if (inpsgn < 0)
	    {
	      new_stat = Global->scr_lines - new_stat - status_rows;
	      new_inp = Global->scr_lines - new_inp - input_rows;
	    }
	}
      if (redraw)
	{
	  int vchange =
	  (((new_ui > 0 ? input_rows : 0)
	    + (new_us > 0 ? status_rows : 0))
	   - ((user_input > 0 ? input_rows : 0)
	      + (user_status > 0 ? status_rows : 0)));
	  int grow = (user_status
		      ? (new_us ? 0 : status_rows)
		      : (new_us ? -status_rows : 0));
	  int cell_top =
	  ((user_status > 0 ? status_rows : 0)
	   + (user_input > 0 ? input_rows : 0));

	  if (grow < 0)
	    {
	      int x;
	    re:
	      for (x = 0; x < nwin; ++x)
		{
		  int top = wins[x].win_down - win_label_rows(&wins[x]);
		  if (cell_top == top && (wins[x].numr <= -grow))
		    {
		      do_close_window (x);
		      goto re;
		    }
		}
	    }

	  if (grow)
	    {
	      int x;
	      for (x = 0; x < nwin; ++x)
		{
		  int top =
		  wins[x].win_down - win_label_rows (&wins[x]);
		  if (cell_top == top)
		    wins[x].numr -= vchange;
		}
	    }
	  if (vchange)
	    {
	      int x;
	      for (x = 0; x < nwin; ++x)
		wins[x].win_down += vchange;
	    }
	  io_repaint ();
	}
      user_input = new_ui;
      user_status = new_us;
      Global->input = new_inp;
      Global->status = new_stat;
    }
}

void 
io_set_cwin (struct window *win)
{
  io_hide_cell_cursor ();
  cwin->win_curow = curow;
  cwin->win_cucol = cucol;
  cwin = win;
  curow = cwin->win_curow;
  cucol = cwin->win_cucol;
  io_display_cell_cursor ();
}


void 
io_pr_cell (CELLREF r, CELLREF c, CELL *cp)
{
  struct window *win;

  for (win = wins; win < &wins[nwin]; win++)
    {
      if (r < win->screen.lr || r > win->screen.hr
	  || c < win->screen.lc || c > win->screen.hc)
	continue;
      io_pr_cell_win (win, r, c, cp);
    }
}

void
io_redo_region (struct rng * rng)
{
  CELL * cp;
  CELLREF r, c;
  find_cells_in_range (rng);
  cp = next_row_col_in_range (&r, &c);
  while (cp)
    {
      io_pr_cell (r, c, cp);
      cp = next_row_col_in_range (&r, &c);
    }
}

/* Create a new window by splitting the current one. */
void 
io_win_open (int hv, int where)
{
  int tmp;
  struct window *win;

  if (   (!hv
	  && (where < MIN_CWIN_WIDTH
	      || (cwin->numc + cwin->lh_wid + cwin->right_edge_c - where
		       < MIN_CWIN_WIDTH)))
      || (hv
	  && (where < MIN_CWIN_HEIGHT
	      || (cwin->numr + cwin->bottom_edge_r
		  + (cwin->lh_wid ? label_rows : 0) - where
		  < MIN_CWIN_HEIGHT))))
    {
      io_error_msg ("Window won't fit!");
      return;
    }

  nwin++;
  tmp = cwin - wins;
  wins = ck_realloc (wins, nwin * sizeof (struct window));
  win = &wins[nwin - 1];
  cwin = &wins[tmp];
  win->id = win_id++;
  win->bottom_edge_r = cwin->bottom_edge_r;
  win->right_edge_c = cwin->right_edge_c;
  /* set_numcols will take care of fixing win_over if edges are on. */
  win->win_over = cwin->win_over + (hv ? 0 : where) - cwin->lh_wid;
  win->win_down = cwin->win_down + (hv ? where : 0);
  win->flags = cwin->flags;
  win->link = -1;
  win->lh_wid = 0;
  win->win_slops = 0;
  win->numc = cwin->numc + cwin->lh_wid + (hv ? 0 : -where);
  win->numr = cwin->numr + (hv ? -where : 0);
  win->win_curow = curow;
  win->win_cucol = cucol;
  set_numcols (win, curow);
  cwin->numc -= (hv ? 0 : win->numc + win->lh_wid + win->right_edge_c);
  cwin->numr -=
    (hv ? win->numr + (win->lh_wid ? label_rows : 0) + win->bottom_edge_r
     : 0);
  cwin->win_curow = curow;
  cwin->win_cucol = cucol;
  io_hide_cell_cursor ();
  win = cwin;
  cwin = &wins[nwin - 1];
  recenter_window (cwin);
  recenter_window (win);
  io_display_cell_cursor ();
  io_repaint ();
}

void 
io_win_close (struct window *win)
{
  do_close_window (win - wins);
}

void 
io_move_cell_cursor (CELLREF rr, CELLREF cc)
{
  if (cwin->link != -1)
    shift_linked_window ((long) rr - curow, (long) cc - cucol);
  if (rr < cwin->screen.lr || rr > cwin->screen.hr
      || cc < cwin->screen.lc || cc > cwin->screen.hc)
    {
      cwin->win_curow = curow = rr;
      cwin->win_cucol = cucol = cc;
      recenter_window (cwin);
      io_repaint_win (cwin);
      if (cwin->link > 0)
	io_repaint_win (&wins[cwin->link]);
    }
  else
    {
      io_hide_cell_cursor ();
      curow = rr;
      cucol = cc;
      io_display_cell_cursor ();
      io_update_status ();
    }
  if (get_scaled_width (cucol) == 0)
    find_nonzero (&cucol, cwin->screen.lc, cwin->screen.hc, get_scaled_width);
  if (get_scaled_height (curow) == 0)
    find_nonzero (&curow, cwin->screen.lr, cwin->screen.hr, get_scaled_height);
}

void 
io_shift_cell_cursor (int dirn, int repeat)
{
  CELLREF c;
  CELLREF r;
  int w = 0;
  int over, down;

  over = colmagic[dirn] * repeat;
  down = rowmagic[dirn] * repeat;
  if (over > 0)
    {
      c = cucol;
      while (c < MAX_COL && over-- > 0)
	{
	  c++;
	  while ((w = get_scaled_width (c)) == 0 && c < MAX_COL)
	    c++;
	}
      if (over > 0 || c == cucol || w == 0)
	{
	  io_error_msg ("Can't go right");
	  return;
	}
    }
  else if (over < 0)
    {
      c = cucol;
      while (c > MIN_COL && over++ < 0)
	{
	  --c;
	  while ((w = get_scaled_width (c)) == 0 && c > MIN_COL)
	    --c;
	}
      if (over < 0 || c == cucol || w == 0)
	{
	  io_error_msg ("Can't go left");
	  return;
	}
    }
  else
    c = cucol;

  if (down > 0)
    {
      r = curow;
      while (r < MAX_ROW && down-- > 0)
	{
	  r++;
	  while ((w = get_scaled_height (r)) == 0 && r < MAX_ROW)
	    r++;
	}
      if (down > 0 || r == curow || w == 0)
	{
	  io_error_msg ("Can't go down");
	  return;
	}
    }
  else if (down < 0)
    {
      r = curow;
      while (r > MIN_ROW && down++ < 0)
	{
	  --r;
	  while ((w = get_scaled_height (r)) == 0 && r > MIN_ROW)
	    --r;
	}
      if (down < 0 || r == curow || w == 0)
	{
	  io_error_msg ("Can't go up");
	  return;
	}
    }
  else
    r = curow;

  io_move_cell_cursor (r, c);
}

void 
io_scroll_cell_cursor (int magic, int repeat)
{
  int off_dn, off_rt;

  struct rng s;
  CELLREF cr, cc;
  int over, down;
  int ret;

  over = colmagic[magic];
  down = rowmagic[magic];

  s.lr = cwin->screen.lr;
  s.hr = cwin->screen.hr;
  if (down)
    {
      off_dn = curow - cwin->screen.lr;
      if (cwin->flags & WIN_PAG_VT)
	ret = page_scroll_axis
	  (curow, down, cwin->numr, get_scaled_height, &(s.lr), &(s.hr), &off_dn);
      else
	ret = scroll_axis
	  (curow, down, cwin->numr, get_scaled_height, &(s.lr), &(s.hr), &off_dn);
      cr = (off_dn > s.hr - s.lr) ? s.hr : s.lr + off_dn;
      if (ret)
	io_error_msg ("Can't scroll that far");
      set_numcols (cwin, s.hr);
    }
  else
    cr = curow;

  off_rt = cucol - cwin->screen.lc;

  s.lc = cwin->screen.lc;
  s.hc = cwin->screen.hc;
  if (over)
    {
      if (cwin->flags & WIN_PAG_HZ)
	ret = page_scroll_axis
	  (cucol, over, cwin->numc, get_scaled_width, &(s.lc), &(s.hc), &off_rt);
      else
	ret = scroll_axis (cucol, over, cwin->numc, get_scaled_width, &(s.lc), &(s.hc), &off_rt);
      if (ret)
	io_error_msg ("Can't scroll that far");
      cc = (s.hc - s.lc < off_rt) ? s.hc : s.lc + off_rt;
    }
  else if ((cwin->flags & WIN_PAG_HZ) == 0)
    /* ... */
    cc = cucol;
  else
    cc = cucol;

  /*fixme The original has a big #if 0 here. */
  if (cwin->link != -1)
    shift_linked_window ((long) cr - curow, (long) cc - cucol);

  cwin->screen = s;
  curow = cr;
  cucol = cc;

  if (get_scaled_width (cucol) == 0)
    find_nonzero (&cucol, cwin->screen.lc, cwin->screen.hc, get_scaled_width);
  if (get_scaled_height (curow) == 0)
    find_nonzero (&curow, cwin->screen.lr, cwin->screen.hr, get_scaled_height);

  io_repaint_win (cwin);
  if (cwin->link > 0)
    io_repaint_win (&wins[cwin->link]);
}

void 
io_recenter_cur_win (void)
{
  cwin->win_curow = curow;
  cwin->win_cucol = cucol;
  recenter_window (cwin);
  io_repaint_win (cwin);
  if (cwin->link > 0)
    io_repaint_win (&wins[cwin->link]);
}

void 
io_recenter_all_win (void)
{
  int n;
  if (!nwin)
    return;
  cwin->win_curow = curow;
  cwin->win_cucol = cucol;
  for (n = 0; n < nwin; n++)
    recenter_window (&wins[n]);
  io_repaint ();
}

void 
io_set_win_flags (struct window *w, int f)
{
  if ((f & WIN_EDGES) && !(w->flags & WIN_EDGES))
    {
      if (w->numr < 2 || w->numc < 6)
	io_error_msg ("Edges wouldn't fit!");
      w->win_down++;
      w->numr--;
      set_numcols (w, w->screen.hr);
    }
  else if (!(f & WIN_EDGES) && (w->flags & WIN_EDGES))
    {
      w->win_over -= w->lh_wid;
      w->numc += w->lh_wid;
      w->lh_wid = 0;
      w->win_down--;
      w->numr++;
    }
  w->flags = f;
}

void 
io_write_window_config (struct line * out)
{
  int n;
  char buf[90];
  struct line scratch;
  scratch.alloc = 0;
  scratch.buf = 0;

  cwin->win_curow = curow;
  cwin->win_cucol = cucol;
  sprint_line (out, "O;status %d\n", user_status);
  if (nwin > 1)
    {
      /* ... */ /* fixme ? */
    }
  for (n = 0; n < nwin; n++)
    {
      buf[0] = '\0';
      if (wins[n].flags & WIN_LCK_HZ)
	strcat (buf, ",lockh");
      if (wins[n].flags & WIN_LCK_VT)
	strcat (buf, ",lockv");
      if (wins[n].flags & WIN_PAG_HZ)
	strcat (buf, ",pageh");
      if (wins[n].flags & WIN_PAG_VT)
	strcat (buf, ",pagev");
      if (wins[n].flags & WIN_EDGE_REV)
	strcat (buf, ",standout");
      if ((wins[n].flags & WIN_EDGES) == 0)
	strcat (buf, ",noedges");
      scratch = *out;
      out->alloc = 0;
      out->buf = 0;
      sprint_line (out, "%sW;N%d;A%u %u;C%d %d %d;O%s\n",
		   scratch.buf, n + 1, wins[n].win_curow, wins[n].win_cucol,
		   7, 0, 7, buf + 1);
      free (scratch.buf);
    }
}

void 
io_read_window_config (char * line)
{
  int wnum = 0;
  char *text;
  CELLREF nrow = NON_ROW, ncol = NON_COL;
  char *split = 0;
  char *opts = 0;
  struct window *win;

  text = line;
  for (;;)
    {
      switch (*text++)
	{
	  /* Window Number */
	case 'N':
	  wnum = astol (&text);
	  break;
	  /* Cursor At */
	case 'A':
	  nrow = astol (&text);
	  ncol = astol (&text);
	  break;
	  /* JF: Window options */
	case 'O':
	  opts = text;
	  while (*text && *text != ';')
	    text++;
	  break;
	  /* Split into two windows */
	case 'S':
	  split = text;
	  while (*text && *text != ';')
	    text++;
	  break;
	  /* Set Colors NOT supported */
	case 'C':
	  while (*text && *text != ';')
	    text++;
	  break;
	  /* Alternate border NOT supported. . . */
	case 'B':
	  break;
	default:
	  --text;
	  break;
	}
      if (*text == '\0' || *text == '\n')
	break;
      if (*text != ';')
	{
	  char *bad;

	  bad = text;
	  while (*text && *text != ';')
	    text++;
	  if (*text)
	    *text++ = '\0';
	  io_error_msg ("Unknown SYLK window cmd: %s", bad);
	  if (!*text)
	    break;
	}
      else
	*text++ = '\0';
    }
  if (wnum < 1 || wnum > nwin)
    {
      io_error_msg ("Window %d out of range in SYLK line %s", wnum, line);
      return;
    }
  --wnum;
  win = &wins[wnum];
  if (nrow != NON_ROW)
    {
      win->win_curow = nrow;
      win->win_cucol = ncol;
      if (win == cwin)
	{
	  curow = nrow;
	  cucol = ncol;
	}
      recenter_window (win);
    }
  if (split)
    {
      int hv = 0;
      int where;
      int link;
      struct window *new;

      switch (*split++)
	{
	case 'H':
	case 'h':
	  hv = 1;
	  break;
	case 'v':
	case 'V':
	  hv = 0;
	  break;
	case 't':
	case 'T':
	  io_error_msg ("Window split titles not supported");
	  return;
	default:
	  break;
	}
      if (*split == 'L')
	{
	  link = wnum;
	  split++;
	}
      else
	link = -1;

      where = astol (&split);

      if (hv ? where >= win->numr : where >= win->numc)
	io_error_msg ("Can't split window: screen too small");

      nwin++;
      wins = ck_realloc (wins, nwin * sizeof (struct window));
      cwin = wins;
      win = &wins[wnum];
      new = &wins[nwin - 1];

      win->numc -= (hv ? 0 : where);
      win->numr -= (hv ? where : 0);
      win->win_curow = curow;
      win->win_cucol = cucol;

      new->flags = WIN_EDGES | WIN_EDGE_REV;	/* Mplan defaults */
      new->lh_wid = 0;		/* For now */
      new->link = link;

      new->win_over = win->win_over + (hv ? -win->lh_wid : win->numc);
      new->win_down = win->win_down + (hv ? win->numr + 1 : 0);
      new->numc = (hv ? win->numc + win->lh_wid : where);
      new->numr = (hv ? where - 1 : win->numr);
      new->win_curow = curow;
      new->win_cucol = cucol;
      set_numcols (new, curow);
      recenter_window (win);
      recenter_window (new);
    }
  if (opts)
    {
      char *np;
      while ((np =(char *) index (opts, ',')))
	{
	  *np = '\0';
	  set_options (opts);
	  *np++ = ';';
	  opts = np;
	}
      if ((np = (char *)rindex (opts, '\n')))
	*np = '\0';
      set_options (opts);
    }
}

static void 
init_mouse (void)
{
  Global->current_mouse = Global->free_mouse =
  (struct mouse_event *) ck_malloc (sizeof (struct mouse_event));
  Global->free_mouse->next = Global->free_mouse;
  Global->free_mouse->prev = Global->free_mouse;
}

static int mouse_location ();

int 
enqueue_mouse_event (int r, int c, int button, int downp)
{
  struct mouse_event *m = Global->free_mouse;
  if (m->next == Global->current_mouse)
    {
      m->next =
	(struct mouse_event *) ck_malloc (sizeof (struct mouse_event));
      m->next->prev = m;
      m->next->next = Global->current_mouse;
      Global->current_mouse->prev = m->next;
      m->seq = Global->mouse_id++;
      if (m->seq > 255)
	panic ("Too many mouse events enqueued.");
    }
  Global->free_mouse = m->next;
  m->row = r;
  m->col = c;
  m->button = button;
  m->downp = downp;
  m->location = mouse_location (&m->r, &m->c, m);
  return m->seq;
}

void 
dequeue_mouse_event (struct mouse_event *out, int seq)
{
  Global->free_mouse->seq = seq;
  while (Global->current_mouse->seq != seq)
    Global->current_mouse = Global->current_mouse->next;
  if (Global->current_mouse == Global->free_mouse)
    {
      out->seq = seq;
      out->button = MOUSE_QERROR;
      return;
    }
  *out = *Global->current_mouse;
  out->next = out->prev = 0;
  Global->current_mouse = Global->current_mouse->next;
}



void 
io_init_windows (int sl, int sc, int ui, int us, int ir, int sr,
		 int lr, int lc) 
{
  print_width = 80;		/* default ascii print width */
  Global->scr_lines = sl;
  Global->scr_cols = sc;
  input_rows = ir;
  status_rows = sr;
  label_rows = lr;
  label_emcols = lc;
  io_set_input_status (ui, us, 0);
  nwin = 1;
  wins = cwin = ck_malloc (sizeof (struct window));
  wins->id = win_id++;
  wins->win_over = 0;		/* This will be fixed by a future set_numcols */
  wins->win_down = (label_rows
		    + (user_status > 0) * status_rows
		    + (user_input > 0) * input_rows);
  wins->flags = WIN_EDGES | WIN_EDGE_REV;
  wins->numr = (Global->scr_lines - label_rows - !!user_status * status_rows
		- input_rows - default_bottom_border);
  wins->numc = Global->scr_cols - default_right_border;
  wins->bottom_edge_r = default_bottom_border;
  wins->right_edge_c = default_right_border;
  wins->link = -1;
  wins->lh_wid = 0;
  wins->win_curow = MIN_ROW;
  wins->win_cucol = MIN_COL;
  wins->win_slops = 0;
  init_mouse ();
}

static int 
mouse_location (CELLREF *cr, CELLREF *cc, struct mouse_event *ev)
{
  int n;
  if (ev->row >= Global->input && ev->row <= Global->input + input_rows)
    return MOUSE_ON_INPUT;
  if (user_status && ev->row >= Global->status
      && ev->row <= Global->status + status_rows)
    return MOUSE_ON_STATUS;
  for (n = 0; n < nwin; ++n)
    {
      struct window *w = &wins[n];
      if (ev->row >= w->win_down
	  && ev->row < w->win_down + w->numr
	  && ev->col < w->win_over + w->numc
	  && ev->col >= w->win_over)
	{
	  int row_off = ev->row - w->win_down;
	  int col_off = ev->col - w->win_over;
	  int rh = 0;
	  int cw = 0;
	  CELLREF c, r;
	  for (c = w->screen.lc; c <= w->screen.hc; ++c)
	    if ((cw += get_scaled_width (c)) > col_off)
	      break;
	  *cc = c;
	  for (r = w->screen.lr; r <= w->screen.hr; ++r)
	    if ((rh += get_scaled_height (r)) > row_off)
	      break;
	  *cr = r;
	  return n;
	}
    }
  return MOUSE_ON_EDGE;
}



syntax highlighted by Code2HTML, v. 0.9.1