/* 
 * xvscrl.c - Scroll Bar handling functions
 *
 * callable functions:
 *
 *   SCCreate()   -  creates the Scroll Bar window.
 *   SCSetRange() -  sets min/max/current values of control
 *   SCSetVal()   -  sets value of control 
 *   SCRedraw()   -  redraws scroll bar
 *   SCTrack()    -  called when clicked.  Operates control 'til mouseup
 */

/*
 * Copyright 1989, 1990, 1991, 1992 by John Bradley and
 *                       The University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation.
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any expressed or implied warranty.
 *
 * The author may be contacted via:
 *    US Mail:   John Bradley
 *               GRASP Lab, Room 301C
 *               3401 Walnut St.
 *               Philadelphia, PA  19104
 *
 *    Phone:     (215) 898-8813
 *    EMail:     bradley@cis.upenn.edu
 */


#include "xv.h"
#include "bitmaps.h"


static Pixmap upPix,  downPix;   /* up/down arrows */
static Pixmap up1Pix, down1Pix;  /* up/down arrows (lit up) */
static Pixmap sgray;             /* gray stipple for lit scrollbar */
static int    pixmaps_built=0;   /* true if pixmaps created already */


/* scroll regions */
#define UPLINE 0
#define UPPAGE 1
#define DNLINE 2
#define DNPAGE 3
#define THUMB  4

#define SCRLWAIT 150   /* milliseconds to wait between scrolls */

/* local functions */
#ifdef __STDC__
static int  whereInScrl(SCRL *, int, int);
static void drawArrow(SCRL *, int);
#else
static int  whereInScrl();
static void drawArrow();
#endif


/***************************************************/
void SCCreate(sp, parent, x, y, vert, len, minv, maxv, curv, page, 
	          fg, bg, func)
SCRL         *sp;
Window        parent;
int           x,y,vert,len,minv,maxv,curv,page;
unsigned long fg,bg;
void          (*func)();
{


  if (!pixmaps_built) {
    upPix    = XCreatePixmapFromBitmapData(theDisp, parent, 
		up_bits, up_width, up_height, fg, bg, dispDEEP);
    downPix  = XCreatePixmapFromBitmapData(theDisp, parent, 
	        down_bits, down_width, down_height, fg, bg, dispDEEP);
    up1Pix   = XCreatePixmapFromBitmapData(theDisp, parent, 
                up1_bits, up1_width, up1_height, fg, bg, dispDEEP);
    down1Pix = XCreatePixmapFromBitmapData(theDisp, parent, 
	        down1_bits, down1_width, down1_height,fg,bg,dispDEEP);
    sgray    = XCreatePixmapFromBitmapData(theDisp, parent,
	        scrlgray_bits, scrlgray_width, scrlgray_height,fg,bg,dispDEEP);
  }

  sp->vert = vert;
  sp->len  = len;
  sp->fg   = fg;
  sp->bg   = bg;
  sp->uplit = sp->dnlit = 0;

  if (vert) 
    sp->win = XCreateSimpleWindow(theDisp, parent,x,y,up_width-2,len,1,fg,bg);
  else FatalError("don't know HOW to make horizontal scrollbar");

  if (!sp->win) FatalError("can't create scrollbar window");

  sp->tsize  =  up_width-2;      /* really only if vertical */
  sp->tmin   =  up_height-1;
  sp->tmax   =  len - (up_height-1) - sp->tsize;
  sp->drawobj = func;

  SCSetRange(sp, minv, maxv, curv, page);
  XSelectInput(theDisp, sp->win, ExposureMask | ButtonPressMask);
}


/***************************************************/
void SCSetRange(sp, minv, maxv, curv, page)
SCRL *sp;
int   minv, maxv, curv, page;
{
  if (maxv<minv) maxv=minv;
  sp->min = minv;    sp->max = maxv;    sp->page = page;
  sp->active =  (minv < maxv);

  /* adjust scroll bar background */
  if (sp->active) XSetWindowBackgroundPixmap(theDisp, sp->win, sgray);
             else XSetWindowBackground(theDisp, sp->win, sp->bg);

  sp->val = -99999;  /* force a redraw */
  SCSetVal(sp, curv);
}


/***************************************************/
int SCSetVal(sp, curv)
SCRL *sp;
int   curv;
{
  /* returns '0' if no redraw was done */

  RANGE(curv, sp->min, sp->max);   /* make sure curv is in-range */

  if (sp->val == curv) return 0;

  sp->val = curv;

  if (sp->active) 
    sp->tpos = sp->tmin + ((sp->tmax - sp->tmin)*(curv - sp->min))
                        / (sp->max - sp->min);
  SCRedraw(sp);
  (sp->drawobj)();     /* redraw whatever the scrollbar controls */
  XFlush(theDisp);
  return 1;
}


/***************************************************/
void SCRedraw(sp)
SCRL *sp;
{
  XSetForeground(theDisp, theGC, sp->fg);
  XSetBackground(theDisp, theGC, sp->bg);

  XClearWindow(theDisp, sp->win);
  
  if (sp->vert) {    /* draw up/down arrows */
    drawArrow(sp,UPLINE);
    drawArrow(sp,DNLINE);

    if (sp->active) {   /* a thumb is necessary */
      XSetForeground(theDisp, theGC, sp->bg);
      XFillRectangle(theDisp, sp->win, theGC,
		     1, sp->tpos+1, sp->tsize-2, sp->tsize-2);
      XSetForeground(theDisp, theGC, sp->fg);
      XDrawRectangle(theDisp, sp->win, theGC,
		     0, sp->tpos, sp->tsize-1, sp->tsize-1);
    }
  }
}



/***************************************************/
static int whereInScrl(sp,x,y)
SCRL *sp;
int x,y;
{
  int v;

  /* returns region # that x,y is in.  Returns '-1' if none */

  v=0;
  if (sp->vert) {
    if (x<0 || x>up_width-2 || y<0 || y>sp->len) return -1;
    v = y;
  }

  /* once we know it's in scroll bar, only have to check 'v' versus len */
  if (v < sp->tmin)               return UPLINE;
  if (sp->active) {
    if (v <  sp->tpos)             return UPPAGE;
    if (v <  sp->tpos + sp->tsize) return THUMB;
    if (v <= sp->tmax + sp->tsize) return DNPAGE;
  }
  if (v > sp->tmax+sp->tsize)    return DNLINE;

  return -1;
}


/***************************************************/
static void drawArrow(sp,arr)
SCRL *sp;
int arr;
{
  /* only if vertical */
  if (arr == UPLINE) {
    if (sp->uplit) 
      XCopyArea(theDisp, up1Pix,  sp->win,theGC,0,0,up_width,up_height,-1,-1);
    else
      XCopyArea(theDisp, upPix,   sp->win,theGC,0,0,up_width,up_height,-1,-1);
  }

  else if (arr == DNLINE) {
    if (sp->dnlit) 
      XCopyArea(theDisp, down1Pix,sp->win,theGC,0,0,up_width,up_height,
		-1, sp->len-(up_height-1));
    else
      XCopyArea(theDisp, downPix, sp->win,theGC,0,0,up_width,up_height,
		-1, sp->len-(up_height-1));
  }

  XFlush(theDisp);
}


/***************************************************/
void SCTrack(sp,mx,my)
SCRL *sp;
int mx,my;
{
  Window       rW,cW;
  int          rx,ry, x,y, ipos, pos, lit, ty, tyoff, ty1;
  unsigned int mask;

  /* determine in which of the five regions of the scroll bar the mouse
     was clicked (upline, downline, uppage, downpage, thumb) */

  ty = tyoff = 0;

  XSetForeground(theDisp, theGC, sp->fg);
  XSetBackground(theDisp, theGC, sp->bg);

  /* light up appropriate bit of scroll bar */
  ipos = whereInScrl(sp,mx,my);
  lit = 1;

  switch (ipos) {
  case UPLINE:  sp->uplit = 1;
                if (sp->val > sp->min) SCSetVal(sp,sp->val-1);                
                Timer(SCRLWAIT);
                break;

  case DNLINE:  sp->dnlit = 1;
                if (sp->val < sp->max) SCSetVal(sp,sp->val+1);
                Timer(SCRLWAIT);
                break;

  case UPPAGE:  SCSetVal(sp,sp->val - sp->page);  break;
  case DNPAGE:  SCSetVal(sp,sp->val + sp->page);  break;
  case THUMB:   tyoff = sp->tpos - my;
                ty = sp->tpos;
                XSetState(theDisp, theGC, sp->fg, sp->bg, GXinvert, 
			  sp->fg ^ sp->bg);
                XDrawRectangle(theDisp,sp->win,theGC,
			       0, sp->tpos, sp->tsize-1, sp->tsize-1);
                break;
  }

  /* VERTICAL CODE ONLY */
  while (XQueryPointer(theDisp,sp->win,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
    if (!(mask & Button1Mask)) break;    /* button released */

    switch (ipos) {

    case THUMB:
      /* do thumb tracking */
      if (x<-16 || x>16+sp->tsize) {   /* outside tracking range */
	if (lit) {
	  lit = 0;
	  XDrawRectangle(theDisp,sp->win,theGC,0,ty,sp->tsize-1,sp->tsize-1);
	}
      }

      else {                           /* inside tracking range */
	if (!lit) {  /* not lit, just turn on */
	  lit = 1;
	  ty = y + tyoff;
	  RANGE(ty, sp->tmin, sp->tmax);
	  XDrawRectangle(theDisp,sp->win,theGC,0,ty,sp->tsize-1,sp->tsize-1);
	}
	else { /* already lit, more the thumb */
	  ty1 = y+tyoff;
	  RANGE(ty1, sp->tmin, sp->tmax);
	  if (ty != ty1) {    /* but only if mouse has moved */
	    XDrawRectangle(theDisp, sp->win, theGC, 
			   0,ty,sp->tsize-1,sp->tsize-1);
	    ty = ty1;
	    XDrawRectangle(theDisp, sp->win, theGC, 
			   0,ty,sp->tsize-1,sp->tsize-1);
	  }
	}
      }
      break;


    case UPLINE:
    case DNLINE:                     /* arrows */
      pos = whereInScrl(sp,x,y);
      if (pos == ipos) {
	if (!lit) { 
	  lit = 1; 
	  if (ipos == UPLINE) { sp->uplit = 1;  drawArrow(sp,UPLINE); }
	                 else { sp->dnlit = 1;  drawArrow(sp,DNLINE); }
	}

	else {
	  if (sp->val > sp->min && pos==UPLINE) {
	    SCSetVal(sp, sp->val-1);
	    Timer(SCRLWAIT);
	  }
	  else if (sp->val < sp->max && pos==DNLINE) {
	    SCSetVal(sp, sp->val+1);
	    Timer(SCRLWAIT);
	  }
	}
      }
      
      else {
	if (lit) { 
	  lit = 0; 
	  if (ipos == UPLINE) { sp->uplit = 0;  drawArrow(sp,UPLINE); }
	                 else { sp->dnlit = 0;  drawArrow(sp,DNLINE); }
	}
      }
      break;
      
    }
  }


  if (ipos == THUMB) {
    if (lit)    /* turn off */
      XDrawRectangle(theDisp,sp->win,theGC,0,ty,sp->tsize-1,sp->tsize-1);

    XSetState(theDisp, theGC, sp->fg, sp->bg, GXcopy, AllPlanes);

    if (lit && ty != sp->tpos) {    /* if thumb was moved, ROUND to new val */
      int dt, dv;
      dt = sp->tmax - sp->tmin;
      dv = sp->max  - sp->min;
      SCSetVal(sp, sp->min + (dv*(ty - sp->tmin)+dt/2) / dt);
    }
  }

  if (lit && ipos == UPLINE) { sp->uplit = 0; drawArrow(sp, UPLINE); }
  if (lit && ipos == DNLINE) { sp->dnlit = 0; drawArrow(sp, DNLINE); }
}




