/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/

/* This creates/deletes/divides/grows/shrinks windows.  */

#include "jove.h"

RCS("$Id: wind.c,v 14.32 1993/06/21 21:52:28 tom Exp tom $")

#include "ctype.h"
#include "maps.h"
#include "termcap.h"

private const char	onlyone[] = "You only have one window!",
			toosmall[] = "Resulting window would be too small.";

Window	*curwind ZERO,
	*fwind ZERO;

/* First line in a Window */

int
FLine(w)
register const Window	*w;
{
	register Window	*wp = fwind;
	register int	lineno = 0;

	while (wp != w) {
		lineno += wp->w_height;
		wp = wp->w_next;
		if (wp == fwind)
			complain("?window?");
	}
	return lineno;
}

/* Set the topline of the window, calculating its number in the buffer.
   This is for numbering the lines only. */

void
SetTop(w, line)
register Window	*w;
register Line	*line;
{
	w->w_top = line;
#ifdef AUTOSCROLL
	w->w_flags |= W_NOAUTOSCROLL;
#endif
	if (
#ifdef WINDOWS
	    w->w_control ||
#endif
	    w->w_flags & (W_NUMLINES|W_TOPNUM))
		w->w_topnum = lineno(line);
}

private void OnlyOne __(( void ));
private void
OnlyOne()
{
	register const Window	*w = fwind;

	if (one_windp(w))
		complain(onlyone);
}

/* Make NEW the current Window */

void
SetWind(new)
register Window	*new;
{
	register Buffer	*cb = curbuf;

	if (!Asking) {		/* can you say kludge? */
		register Window	*cw = curwind;

		cw->w_line = /*curline*/ cb->b_dot;
		cw->w_char = /*curchar*/ cb->b_char;
		cw->w_bufp = /*curbuf*/ cb;
	}
	cb = new->w_bufp;
	SetBuf(cb);
	if (!inlist(/*curbuf*/ cb->b_first, new->w_line)) {
		new->w_line = /*curline*/ cb->b_dot;
		new->w_char = /*curchar*/ cb->b_char;
	}
	DotTo(new->w_line, new->w_char);
#if NO	/* [TRH] this is now assured by DotTo */
    {
	register int	len = strlen(linebuf);

	if (new->w_char > len)
		new->w_char = /*curchar*/ cb->b_char = len;
    }
#endif
	curwind = new;
}

/* Delete `wp' from the screen.  If it is the only window left
   on the screen, then complain.  It gives its body
   to the next window if there is one, otherwise the previous
   window gets the body.  */

void
del_wind(wp)
register Window	*wp;
{
	register Window	*prev = wp->w_prev,
			*next = wp->w_next;

	OnlyOne();

	prev->w_next = next;
	next->w_prev = prev;

	if (fwind == wp) {
		fwind = next;
		SetTop(next, prev_line(next->w_top, wp->w_height));
		prev = next;	/* this is a cheat... */
	}
	prev->w_height += wp->w_height;
	if (curwind == wp)
		SetWind(prev);

#ifdef WINDOWS
	RemoveScrollBar(wp);
	Windchange++;
#endif
	free((void_*) wp);
}

/* Divide the window WP N times, or at least once.  Complains if WP is too
   small to be split into that many pieces.  It returns the new window. */

Window *
div_wind(wp, n)
register Window	*wp;
{
	register Window	*new;
	register int	amt;

	if (++n < 2)
		n = 2;
	amt = wp->w_height / n;
	if (amt < 2)
		complain(toosmall);

	while (--n) {
		new = (Window *) emalloc(sizeof (Window));
		new->w_flags = 0;
		new->w_offset = 0;

		new->w_height = amt;
		wp->w_height -= amt;

		/* set the lines such that w_line is the center in
		   each Window --NO: let redisplay handle that. */
		new->w_line = wp->w_line;
		new->w_char = wp->w_char;
		new->w_bufp = wp->w_bufp;
		new->w_top = wp->w_top; /* prev_line(new->w_line, HALF(new)); */

		/* Link the new window into the list */
		new->w_prev = wp;
		new->w_next = wp->w_next;
		new->w_next->w_prev = new;
		wp->w_next = new;
#ifdef WINDOWS
		new->w_control = 0;
		Windchange++;
#endif
	}
	return new;
}

/* Initialize the first window setting the bounds to the size of the
   screen.  There is no buffer with this window.  See parse for the
   setting of this window. */

void
winit()
{
	register Window	*w;

	curwind = fwind = w = (Window *) emalloc(sizeof (Window));
	bzero(w, sizeof(Window));
	w->w_next = w->w_prev = w;
	w->w_height = ILI;
#ifdef WINDOWS
	Windchange++;
#endif
}

/* Change window into the next window. Curwind becomes the new window. */

DEF_CMD( "next-window", NextWindow, NO )
{
	OnlyOne();
	SetWind(curwind->w_next);
}

DEF_CMD( "previous-window", PrevWindow, NO )
{
	OnlyOne();
	SetWind(curwind->w_prev);
}

/* delete the current window if it isn't the only one left */

DEF_CMD( "delete-current-window", DelCurWindow, NO )
{
	register Window	*cw = curwind;
	register Buffer	*b;

	if (exp_p)
		if (exp < 0)
			cw = cw->w_prev;
		else
			cw = cw->w_next;
	b = cw->w_bufp;

	del_wind(cw);

	if (b != curbuf)
		SetABuf(b);
}

/* put the current line of `w' in the middle of the window */

void
CentWind(w)
register Window	*w;
{
	SetTop(w, prev_line(w->w_line, HALF(w)));
}

DEF_INT( "scroll-step",	ScrollStep, V_BASE10) ZERO; _IF(def PRIVATE)

/* Calculate the new topline of the window.  If ScrollStep == 0
   it means we should center the current line in the window. */

void
CalcWind(w)
register Window	*w;
{
	register Line	*newtop;
	register int	nlines,
			up;

	if ((nlines = ScrollStep) > 0 &&
	    (up = inorder(w->w_line, 0, w->w_top, 0)) >= 0) {

		/* nlines = min(ScrollStep - 1, HALF(w)); */
		if (--nlines > HALF(w))
			nlines = HALF(w);
		newtop = next_line(w->w_line, (up) ? -nlines :
				   nlines - (SIZE(w) - 1));
		if (LineDist(newtop, w->w_top) < SIZE(w) - 1) {
			SetTop(w, newtop);
			return;
		}
	}
	CentWind(w);
}

/* This is bound to C-X 4 [BFT^T].  To make the screen stay the
   same we have to remember various things, like the current
   top line in the current window.  It's sorta gross, but it's
   necessary because of the way this is implemented. (i.e., in
   terms of do_find(), do_select() which manipulate the windows. */

DEF_CMD( "window-find", WindFind, SPARSE|ARG(WINDFINDMAP_INDEX) )
{
	register Window	*cw = curwind;
	register Buffer	*obuf = curbuf;
	register Line	*otop = cw->w_top;
	Bufpos		odot;

	DOTsave(&odot);

#ifndef FIXED_MAPS
#   define WFindMap	((sparsemap *) Maps[ObjArg(LastCmd)].k_bind)
#endif
	ExecNow(findsparse(WFindMap, waitchar()));
#   undef WFindMap

	if (one_windp(cw))
		div_wind(cw, 0);

	tiewind(cw->w_next, curbuf);
	SetTop(cw->w_next, cw->w_top);	/* find-tag sets it */
#if (HIGHLIGHT)
	/* Transfer highlight information (find-tag sets it.) */
	WHighLight(cw->w_next, cw->w_highlighted_line);
	WUnHighLight(cw);
#endif

	/* [TRH 5-Jul-89] this used to be before tiewind where find-tag
	   to same buffer was screwed up by it. */
	SetBuf(obuf);
	SetDot(&odot);
	SetTop(cw, otop);	/* there! it's as if we did nothing */

	NextWindow();
}

/* Go into one window mode by deleting all the other windows */

DEF_CMD( "delete-other-windows", OneWindow, NO )
{
	register Window	*cw = curwind;

	while (!one_windp(cw))
		del_wind(cw->w_prev);
}

Window *
windbp(bp)
const Buffer	*bp;
{
	register const Buffer *b;
	register Window	*w = fwind;

	if (b = bp) do {
		if (w->w_bufp == b)
			return w;
	} while ((w = w->w_next) != fwind);
	return NULL;
}

/* Scroll the next Window */

DEF_CMD( "page-next-window", PageNWind, NO )
{
	NextWindow();
	NextPage();
	PrevWindow();
}

/* Put a window with the buffer `name' in it.  Erase the buffer if
   `clobber' is non-zero. */

void
pop_wind(name, clobber, btype)
register const char	*name;
{
	register Window	*wp;
	register Buffer	*newb;

	if (newb = buf_exists(name)) {
		if (clobber) {
			initlist(newb);
			newb->b_modified = NO;
		}
	}
	else {
		/* create a new (empty) buffer and set its type. */
		newb = do_select((Window *) 0, name);
		if (btype > 0)
			SETBUFTYPE(newb, btype);
	}
	if ((wp = windbp(newb)) == NULL) {
		/* Buffer is not tied to any window.  Use any
		   window -other than curwind- with matching buffer
		   type.  If there isn't any, use previous window,
		   or split window if we have just a single one.  */
		wp = curwind;
		do {
			if ((wp = wp->w_prev) == curwind) {
				wp = wp->w_prev;
				if (one_windp(wp))
					wp = div_wind(wp, 0);
				break;
			}
		} while (wp->w_bufp->b_type != btype);
	}
	tiewind(wp, newb);
	SetWind(wp);
}

/* Change the size of the window by inc.  First arg is the window,
   second is the increment. */

void
WindSize(w, inc)
register Window	*w;
int	inc;
{
	register int	i;

	OnlyOne();

	/* Change made from original code so that growing a window
	   exactly offsets effect of shrinking a window, i.e.,
	   doing either followed by the other restores original
	   sizes of all affected windows. */

	if (((i = inc) < 0 ? w->w_height + i : w->w_prev->w_height - i) < 2)
		complain(toosmall);

	/* {{Should we try to steal from other windows if a resulting window
	     gets too small?  Should we then REMOVE windows if there is still
	     insufficient size?}} */

	w->w_height += i;
	w->w_prev->w_height -= i;
#ifdef WINDOWS
	Windchange++;
#endif
}

DEF_CMD( "shrink-window", GrowWindow, NEGATE );
DEF_CMD( "grow-window", GrowWindow, NO )
{
	WindSize(curwind, exp);
}

DEF_CMD( "number-lines-in-window", WNumLines, NO )
{
	register Window	*cw = curwind;

	cw->w_flags ^= W_NUMLINES;
	SetTop(cw, cw->w_top);
}

DEF_CMD( "visible-spaces-in-window", WVisSpace, NO )
{
	register Window	*cw = curwind;

	cw->w_flags ^= W_VISSPACE;
	ClrWindow(cw);
}

/* Return the line number that `line' occupies in `w' */

int
in_window(w, line)
register Window	*w;
Line		*line;
{
	register int	i = 0;
	register Line	*top;

	if (top = w->w_top) do {
		if (++i >= w->w_height)
			break;
		if (top == line)
			return FLine(w) + i - 1;
	} while (top = top->l_next);

	return -1;
}

DEF_CMD( "split-current-window", SplitWind, NO )
{
	SetWind(div_wind(curwind, exp - 1));
}

/*
 * Scroll window left or right. (replaces [TRH] "horizontal-offset-in-window")
 */
DEF_CMD( "scroll-left",  WindScroll, NEGATE );
DEF_CMD( "scroll-right", WindScroll, NO )
{
	register Window	*w = curwind;
	register int	num;

	if (num = exp) {			/* num == 0 means reset */
		if (exp_p != YES)
			num *= 10;
		if ((num += w->w_offset) < 0)
			num = 0;
	}
	w->w_offset = num;
	updmodline();
}

/*
 * [TRH] set size of window to specific number of lines
 */
DEF_CMD( "resize-window", WindResize, NO )
{
	register Window	*w = curwind;
	register int	wsize = SIZE(w);

	OnlyOne();
	WindSize(w, exp_ask_int(wsize) - wsize);
}

/* Goto the window with the named buffer.  If no such window
   exists, pop one and attach the buffer to it.
   [TRH] in fact, pop_wind does just the job. */

DEF_CMD( "goto-window-with-buffer", GotoWind, NO )
{
	register Buffer	*cb = curbuf;

	pop_wind(ask_buf(def_buf(cb)), NO, NO);
	if (cb != curbuf)
		SetABuf(cb);
}

#ifdef RESHAPING
/* Reshape the screen (and the windows it displays) if its size changes. */

void
win_reshape()
{
	register int	old_LI = LI,
			old_CO = CO,
			diff;

	/*
	 * Get new line/col info.
	 */
	ttsize();

	/*
	 * LI has changed, and now holds the
	 * new value.  See how much the size
	 * changed.
	 */
	if ((diff = LI - old_LI) || (CO != old_CO)) {
		if (diff) {
			register Window	*w = curwind;
			register int	n = 0;

			/*
			 * count windows. If there are more than the new
			 * screen can contain, keep deleting windows
			 * until it fits.
			 * spread the difference evenly over the windows.
			 */
			do n++; while ((w = w->w_next) != curwind);
			if (diff < 0) {
				n *= 2;
				while (n > ILI) {
					del_wind(w->w_next);
					n -= 2;
				}
			}
			do {
				if ((n = (w->w_height * LI) / old_LI) < 2)
					n = 2;
				diff -= n - w->w_height;
				w->w_height = n;
			} while ((w = w->w_next) != curwind);

			if (diff < 0)
				do {
					if (w->w_height > 2)
						w->w_height--;
					w = w->w_next;
				} while (++diff < 0);
			else
				while (--diff >= 0) {
					w->w_height++;
					w = w->w_next;
				}
		}
		make_scr();
		ClAndRedraw();	/* just to be sure... */
#ifdef WINDOWS
		WindChange++;
#endif
		redisplay();
	}
}
#endif /* RESHAPING */

/*======================================================================
 * $Log: wind.c,v $
 * Revision 14.32  1993/06/21  21:52:28  tom
 * (win_reshape): moved from tty.c.
 *
 * Revision 14.31  1993/02/15  02:01:52  tom
 *  remove (void) casts; a random optimization.
 *
 * Revision 14.30  1993/02/05  00:07:33  tom
 * cleanup whitespace; some random optimizations.
 *
 * Revision 14.28  1992/10/24  01:24:24  tom
 * convert to "port{ansi,defs}.h" conventions; "window-find" is made
 * transparent to keymap completion, preserves highlighting to other window.
 *
 * Revision 14.26  1992/08/26  23:57:01  tom
 * use ExecNow(0 in "wind-find" for immediate macro execution;
 * PRIVATE-ized some Variable defs; add RCS directives.
 *
 */
