/*
 *	$Source: /u1/X/DECToolkit/src/RCS/VPane.c,v $
 *	$Header: VPane.c,v 1.1 86/12/17 09:03:33 swick Exp $
 */

#ifndef lint
static char *rcsid_VPane_c = "$Header: VPane.c,v 1.1 86/12/17 09:03:33 swick Exp $";
#endif	lint

#ifndef lint
static  char    *sccsid = "@(#)VPane.c	1.4          12/11/86";
#endif lint
/*
 *			  COPYRIGHT 1986
 *		   DIGITAL EQUIPMENT CORPORATION
 *		       MAYNARD, MASSACHUSETTS
 *			ALL RIGHTS RESERVED.
 *
 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 *
 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT RIGHTS,
 * APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN ADDITION TO THAT
 * SET FORTH ABOVE.
 *
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting documentation,
 * and that the name of Digital Equipment Corporation not be used in advertising
 * or publicity pertaining to distribution of the software without specific, 
 * written prior permission.
 */


/* Basic code outline by Smokey Wallace 
         Modification by Harry Hersh  */

#include <stdio.h>
#include <X/Xlib.h>
#include <X/cursors/double_arrow.cursor>
#include <X/cursors/double_arrow_mask.cursor>
#include "Toolkit.h"

/* Paned window are a major organizing device for application programs.
 * The consist of an overall frame, within which tiled subwindows can be
 * added, removed, and varied in height, both by the user and by the program.
 * The subwindows extend the full width of the window frame. The subwindows
 * within a paned window frame are completely general: they are typically
 * used as variable windows for text or graphics editing and for other
 * applications where a user would want direct control over the relative
 * size of windows.
 */

/* Private definitions. */

/* -----> Defines */

#define BORDERWIDTH	1
#define KNOBWIDTH	9
#define KNOBHEIGHT	9

typedef struct {
    Window w;		/* Subwindow */
    int y;		/* Location in master window */
    int min, max;	/* Minimum and maximum height. */
    short autochange;	/* Whether we're allowed this subwindow's height
			   without an explicite command from the user. */
    int height;		/* Current height. */
    int dheight;	/* Desired height. */
    Window knob;	/* The knob for this subwindow. */
} PanelInfo, *PanelPtr;

typedef struct {
    Window window;	/* Window containing everything. */
    int width, height;	/* Dimension of master window. */
    int heightused;	/* Total height used by subwindows. */
    int numpanels;	/* How many windows within it. */
    PanelInfo *sub;	/* Array of info about the sub windows. */
    int whichtracking;	/* Which knob we are tracking, if any */
    int tracklocation;	/* The current track line. */
    int refiguremode;	/* Whether to refigure things right now. */
} WindowFrameInfo, *WindowFramePtr;

/* -----> Static Variables */

static int initialized = FALSE;
static int WindowFrameEntry;

static Initialize()
{
   WindowFrameEntry = UniqueEntryType();
   initialized = TRUE;
}


/* -----> Utilities */

static WindowFramePtr WindowFramePtrFromWindow(w)
Window w;
{
    WindowFramePtr ctx;
    if (FindEntry(w, WindowFrameEntry, &ctx) != ERRNONE)
       return FALSE;
    return ctx;
}


static TryResize(ctx, newwidth, newheight)
WindowFramePtr ctx;
int newwidth, newheight;
{
    WindowBox requestBox, replyBox;
    int     result;

    requestBox.x = 0;
    requestBox.y = 0;
    if (newwidth  < 1) newwidth = 1;
    if (newheight < 1) newheight = 1;
    requestBox.w = newwidth;
    requestBox.h = newheight;
    result = TMakeGeometryRequest(ctx->window, resize,
	    &requestBox, &replyBox);
    if (result == REQUESTALMOST) {
	requestBox = replyBox;
	result = TMakeGeometryRequest(ctx->window, resize,
		&requestBox, &replyBox);
    }
    if (result == REQUESTYES) {
	ctx->width = replyBox.w;
	ctx->height = replyBox.h;
	return TRUE;
    }
    return FALSE;
}

static RefigureVLocations(ctx, position)
WindowFramePtr ctx;
int position;
{
    PanelPtr sub;
    int     i, old, y, kx, ky;

    if (ctx->numpanels == 0 || !ctx->refiguremode)
	return;
    ctx->heightused = 0;
    for (i = 0, sub = ctx->sub; i < ctx->numpanels; i++, sub++) {
	if (sub->dheight < sub->min)
	    sub->dheight = sub->min;
	if (sub->dheight > sub->max)
	    sub->dheight = sub->max;
	ctx->heightused += sub->dheight;
    }

    ctx->heightused += BORDERWIDTH * (ctx->numpanels - 1);
    sub = &(ctx->sub[position]);
    while (ctx->heightused != ctx->height) {
	if (sub->autochange) {
	    old = sub->dheight;
	    sub->dheight = ctx->height - ctx->heightused + old;
	    if (sub->dheight < sub->min)
		sub->dheight = sub->min;
	    if (sub->dheight > sub->max)
		sub->dheight = sub->max;
	    ctx->heightused += (sub->dheight - old);
	}
	sub++;
	if (sub - ctx->sub == ctx->numpanels)
	    sub = ctx->sub;
	if (sub - ctx->sub == position)
	    break;
    }
    if (ctx->heightused != ctx->height)
	TryResize(ctx, ctx->width, ctx->height);
    y = -BORDERWIDTH;
    for (i = 0, sub = ctx->sub; i < ctx->numpanels; i++, sub++) {
	if (y != sub->y || sub->dheight != sub->height) {
	    if (sub->dheight == sub->height)
		XMoveWindow(sub->w, -BORDERWIDTH, y);
	    else
		XConfigureWindow(sub->w, -BORDERWIDTH, y,
			ctx->width, sub->dheight);
	    sub->y = y;
	    sub->height = sub->dheight;
	}
	y += sub->dheight + BORDERWIDTH;
    }
    kx = ctx->width - KNOBWIDTH;
    for (i = 0, sub = ctx->sub; i < ctx->numpanels; i++, sub++) {
	ky = sub->y + sub->height - (KNOBHEIGHT / 2);
	if (i == ctx->numpanels - 1)
	    ky = -99;
	XMoveWindow(sub->knob, kx, ky);
    }
}

static DrawVTrackLine(ctx)
  WindowFramePtr ctx;
{
    XLine(ctx->window, 0, ctx->tracklocation, ctx->width, ctx->tracklocation, 
	    1, 1, ~0, GXinvert, AllPlanes);
}


/* =================================================================== */

/* Semi-public Procedures. */

static WindowFrameGeometryRequest()
{
    return REQUESTNO;
}

static HandleExposeWindow(event)
  XExposeWindowEvent *event;
{
    WindowFramePtr ctx;
    int     i;
    if (event->type == ResizeWindow && event->subwindow == 0) {
	ctx = WindowFramePtrFromWindow(event->window);
	if (!ctx)
	    return ERRNOTFOUND;
	if (ctx->width != event->width || ctx->height != event->height) {
	    if (ctx->width != event->width) {
		for (i = 0; i < ctx->numpanels; i++) {
		    XChangeWindow(ctx->sub[i].w,
			    event->width, ctx->sub[i].height);
		}
	    }
	    ctx->width = event->width;
	    ctx->height = event->height;
	    for (i = ctx->numpanels - 1;
		    i > 0 && !(ctx->sub[i].autochange);
		    i--);
	    RefigureVLocations(ctx, i);
	}
	return PROCESSED;
    }
    return NOTHANDLED;
}


static HandleKnob(event)
  XButtonEvent *event;
{
    WindowFramePtr ctx;
    int     position, diff, x, y;
    Window subw;
    ctx = WindowFramePtrFromWindow(event->window);
    if (!ctx)
       return ERRNOTFOUND;
    for (position = 0; position < ctx->numpanels; position++)
	if (ctx->sub[position].knob == event->window)
	    break;
    if (position >= ctx->numpanels)
	return NOTHANDLED;
    XInterpretLocator(ctx->window, &x, &y, &subw, event->location);
    switch (event->type) {
	case ButtonPressed: 
	    if (ctx->whichtracking != -1)
		return NOTHANDLED;
	    ctx->whichtracking = position;
	    ctx->tracklocation = -99;
	    XClipDrawThrough(ctx->window);
	/* Fall through */

	case MouseMoved: 
	case ButtonReleased: 
	    if (ctx->whichtracking == -1)
		return NOTHANDLED;
	    DrawVTrackLine(ctx);	/* Erase old line */
	    ctx->tracklocation = y;
	    if (event->type != ButtonReleased) {
		DrawVTrackLine(ctx);/* Draw new line */
		return PROCESSED;
	    }
	    position = ctx->whichtracking;
	    diff = ctx->tracklocation -
		(ctx->sub[position].y + ctx->sub[position].height);
	    ctx->sub[position].dheight += diff;
	    if (position == ctx->numpanels - 2)
		ctx->sub[position + 1].dheight -= diff;
	    XClipClipped(ctx->window);
	    RefigureVLocations(ctx, position + 1);
	    ctx->whichtracking = -1;
	    return PROCESSED;
    }
    return NOTHANDLED;
}
  

/* =================================================================== */

/* Public Procedures. */

/*
 * This procedure makes a paned window frame from an existing window
 * for one or more vertically arranged tiled windows.
 */

Status TMakeVWindowFrame(w)
  Window w;
{
   WindowFramePtr ctx;
   WindowInfo     info;

/* -----> Perform initialization */

  if (!initialized) Initialize();

/* -----> Set up data frame data structure */

    ctx = (WindowFramePtr) Tmalloc(sizeof(WindowFrameInfo));
    SaveEntry(w, WindowFrameEntry, ctx);
    XQueryWindow(w, &info);
    ctx->window = w;
    ctx->width = info.width;
    ctx->height = info.height;
    ctx->heightused = 0;
    ctx->numpanels = 0;
    ctx->sub = (PanelInfo *) Tmalloc(1);
    ctx->whichtracking = -1;
    ctx->refiguremode = TRUE;
    TSetXEventDispatch(w, HandleExposeWindow, ExposeWindow, 0);
}


/* ================================================================= */

/*
 * Deletes the paned window Frame, including the OpaqueFrame
 * structure associated with it.
 */

Status TDeleteVWindowFrame(w)
  Window w;
{
    WindowFramePtr ctx;
    int i;
    ctx = WindowFramePtrFromWindow(w);
    if(!ctx)
      return ERRNOTFOUND;
    XUnmapWindow(w);
    for (i=ctx->numpanels-1 ; i>=0 ; i--) 
	TDeleteFromVWindowFrame(w, ctx->sub[i].w);
    DeleteEntry(w, WindowFrameEntry);
    free(ctx->numpanels);
    free(ctx);
}

/* ================================================================= */

/*
 * Adds a sub-window to the paned window in a particular location by
 * readjusting the height of any permitted sub-windows within their
 * specified bounds.
 */

Window TAddToVWindowFrame(w, arglist)
Window w;			/* Window frame */
Targ * arglist;
{
    WindowFramePtr ctx;
    Window    pw;		
    int       i, needed, position;
    int       status;
    PanelPtr  sub;
    Pixmap    border, bkground;
    int y;
    int min, max;
    short autochange;
    int height;	
    int dheight;

    ctx = WindowFramePtrFromWindow(w);
    if(!ctx)
        return ERRNOTFOUND;
    ctx->numpanels++;
    ctx->sub = (PanelPtr)
	Trealloc(ctx->sub, ctx->numpanels * sizeof(PanelInfo));

/* -----> Set Defaults */

   pw         = NULL;
   position   = ctx -> numpanels - 1;
   border     = BlackPixmap;
   bkground   = WhitePixmap;
   y          = ctx -> height;
   height     = ctx -> height / ctx -> numpanels;
   min        = KNOBHEIGHT + 1;
   max        = ctx -> height;
   autochange = TRUE;

/* -----> Parse Arguments */

   while (arglist -> name) {
      switch (arglist -> name) {
	 case T_PANED_WINDOW:
	    pw = (Window) arglist ->data;
	    break;
         case T_PANED_ORIENT:
            break;
         case T_PANED_POSITION:
            position = (int) arglist -> data;
            break;
         case T_PANED_HEIGHT:
            height = (int) arglist -> data;
            break;
         case T_PANED_WIDTH:
            break;
         case T_PANED_MIN:
            min = (int) arglist -> data;
            break;
         case T_PANED_MAX:
            max = (int) arglist -> data;
            break;
         case T_PANED_AUTOCHANGE:
            autochange = (short) arglist -> data;
            break;
         case T_PANED_BRCOLOR:
            border = (int) arglist -> data;
            break;
         case T_PANED_BACKGROUND:
            bkground = (int) arglist -> data;
            break;
         default:
            break;
      }
   arglist++;
   }

    for (i = ctx->numpanels - 1; i > position; i--)
	ctx->sub[i] = ctx->sub[i - 1];
    sub = &(ctx->sub[position]);

    sub->w = pw;
    sub->y = y;
    sub->height = sub->dheight = height;
    sub->autochange = autochange;
    sub->min = min;
    sub->max = max;

/* -----> Create Window Pane */
    if (pw == NULL)
	sub->w = XCreateWindow (w, -BORDERWIDTH, sub->y, ctx->width,
                           sub->height, BORDERWIDTH, border, bkground);
    else
	XConfigureWindow (sub->w, -BORDERWIDTH, sub->y, ctx->width,
			   sub->height);
    needed = sub->height + BORDERWIDTH + ctx->heightused;
    if (needed > ctx->height)
	TryResize(ctx, ctx->width, needed);
    TSetGeometryRequest(sub->w, WindowFrameGeometryRequest);
    sub->knob = XCreateWindow(ctx->window, -99, -99, KNOBWIDTH, KNOBHEIGHT,
	    0, 0, BlackPixmap);
    SaveEntry(sub->knob, WindowFrameEntry, ctx);

#ifdef CURSORS
    XDefineCursor(sub->knob, GetCursor("double_arrow"));
#else
    XDefineCursor(sub->knob, XCreateCursor(
                             double_arrow_width, double_arrow_height,
                             double_arrow_bits,  double_arrow_mask_bits,
                             double_arrow_x_hot, double_arrow_y_hot,
                             BlackPixel, WhitePixel, GXcopy));
#endif

    XMapWindow(sub->w);
    if (min != max)
       XMapWindow(sub->knob);
    TSetXEventDispatch(sub->knob, HandleKnob, knobMask, 0);
    RefigureVLocations(ctx, position);
    for (i=0 ; i<ctx->numpanels; i++)
       XRaiseWindow(ctx->sub[i].knob);
    return(sub->w);
}


/* ================================================================= */

/*
 * Removes a sub-window from the paned window and readjust any permitted
 * sub-windows within their specified bounds.
 */

Status TDeleteFromVWindowFrame(w, subwindow)
  Window w, subwindow;
{
    WindowFramePtr ctx;
    int     i, j, r;
    ctx = WindowFramePtrFromWindow(w);
    if (!ctx)
	return ERRNOTFOUND;
    j = FALSE;
    for (i = 0; i < ctx->numpanels; i++) {
	if (ctx->sub[i].w == subwindow) {
	    j = TRUE;
	    DeleteEntry(ctx->sub[i].knob, WindowFrameEntry);
	    XDestroyWindow(ctx->sub[i].knob);
	    r = TryResize(ctx, ctx->width,
		    ctx->height - ctx->sub[i].height - BORDERWIDTH);
	}
	if (j && i < ctx->numpanels - 1)
	    ctx->sub[i] = ctx->sub[i + 1];
    }
    if (!j)
	return;
    ctx->numpanels--;
    if (ctx->numpanels > 0) {
	ctx->sub = (PanelPtr)
	    Trealloc(ctx->sub, ctx->numpanels * sizeof(PanelInfo));
    }
    for (i=ctx->numpanels - 1; i>0 ; i--)
	if (ctx->sub[i].autochange) break;
    RefigureVLocations(ctx, i);
}

/* =================================================================== */

/*
 * Enables or disables adjustments to pane boundaries.
 *
 */

TPanedVRefigureMode(window, mode)
  Window window;
  short mode;
{
    WindowFramePtr ctx;
    int     i;
    ctx = WindowFramePtrFromWindow(window);
    if (ctx) {
	ctx->refiguremode = mode;
	if (mode) {
	    for (i = ctx->numpanels - 1; i > 0; i--)
		if (ctx->sub[i].autochange)
		    break;
	    RefigureVLocations(ctx, i);
	}
    }
}

/* =================================================================== */

/*
 * This procedure returns the currently set of arguments, and
 * other state information. If any errors are detected
 * during the parsing of the input arguments, an exit will occur.
 *
 */

Status TGetVPaneAttr (tw, arglist)
  Window tw;			/* tool window (pane) */
  Targ *arglist;
  {
    int return_code;		/* return code from window creation */
    int i;
    WindowFramePtr ctx;
    Window *parent, **children;
    int *nchildren;
    PanelPtr pane;

/* -----> First find parent, then the frame, then the right pane */

    return_code = XQueryTree(tw, parent, nchildren, children);
    if (!return_code)
       return ERRNOTFOUND;

    ctx = WindowFramePtrFromWindow(parent);
    if (!ctx)
       return ERRNOTFOUND;

    for (i = 0; i < ctx->numpanels; i++)
        if(ctx->sub[i].w == tw)
           pane = &(ctx->sub[i]);
    if (!pane)
       return ERRNOTFOUND;

 
/* -----> Parsing input parameters */

   while (arglist -> name) {
      switch (arglist -> name) {
         case T_PANED_ORIENT:
            arglist -> data = (caddr_t) VERTICAL;
            break;
         case T_PANED_POSITION:
            arglist -> data = (caddr_t) FALSE;
            break;
         case T_PANED_HEIGHT:
            arglist -> data = (caddr_t) pane -> height;
            break;
         case T_PANED_WIDTH:
            arglist -> data = (caddr_t) ctx -> width;
            break;
         case T_PANED_MIN:
            arglist -> data = (caddr_t) pane -> min;
            break;
         case T_PANED_MAX:
            arglist -> data = (caddr_t) pane -> max;
            break;
         case T_PANED_AUTOCHANGE:
            arglist -> data = (caddr_t) pane ->autochange;
            break;
         default:
            break;
      }
   arglist++;
   }
}

/* =================================================================== */

/*
 * This procedure sets or changes arguments for a paned window.
 * If any errors are detected during the parsing of the input arguments,
 * an exit will occur.
 */

Status TSetVPaneAttr (tw, arglist)
  Window tw;			/* tool window */
  Targ *arglist;
  {
    int return_code;		/* return code from window creation */
    int i;
    WindowFramePtr ctx;
    Window *parent, **children;
    int *nchildren;
    PanelPtr pane;

/* -----> First find parent, then the frame, then the right pane */

    return_code = XQueryTree(tw, parent, nchildren, children);
    if (!return_code)
       return ERRNOTFOUND;

    ctx = WindowFramePtrFromWindow(parent);
    if (!ctx)
       return ERRNOTFOUND;

    for (i = 0; i < ctx->numpanels; i++)
        if(ctx->sub[i].w == tw)
           pane = &(ctx->sub[i]);
    if (!pane)
       return ERRNOTFOUND;

 
/* -----> Parsing input parameters */

   while (arglist -> name) {
      switch (arglist -> name) {
         case T_PANED_ORIENT:
            arglist -> data = (caddr_t) FALSE;
            break;
         case T_PANED_POSITION:
            arglist -> data = (caddr_t) FALSE;
            break;
         case T_PANED_HEIGHT:
            arglist -> data = (caddr_t) pane -> height;
            break;
         case T_PANED_WIDTH:
            arglist -> data = (caddr_t) ctx -> width;
            break;
         case T_PANED_MIN:
            arglist -> data = (caddr_t) pane -> min;
            break;
         case T_PANED_MAX:
            arglist -> data = (caddr_t) pane -> max;
            break;
         case T_PANED_AUTOCHANGE:
            arglist -> data = (caddr_t) pane ->autochange;
            break;
         default:
            break;
      }
   arglist++;
   }

    RefigureVLocations(ctx, 1);
}

