/* X11buttons - buttons for X11 dialogs and windows                    */
/* XLISP-STAT 2.1 Copyright (c) 1990, by Luke Tierney                  */
/* Additions to Xlisp 2.1, Copyright (c) 1989 by David Michael Betz    */
/* You may give out copies of this software; for conditions see the    */
/* file COPYING included with this distribution.                       */
 
/***********************************************************************/
/**                                                                   **/
/**                    General Includes and Definitions               **/
/**                                                                   **/
/***********************************************************************/

#include "dialogs.h"

extern Display *StX11Display();
extern Point DialogStringSize();
extern LVAL StX11ItemObject();
extern char *checkstring();
extern LVAL s_window_id;

#define NullWindow ((Window) 0)

typedef struct {
  unsigned long fore, back;
} ColorPair;

/* layout defines */
# define BUTTON_PAD 15
# define BUTTON_LEAD 5

# define CloseButton 1
# define MenuButton 2

# define CLOSE_TEXT "Close"
# define MENU_TEXT "Menu"

/* forward declarations */
LOCAL LVAL special_button_object _((Display *dpy, Window button));
LOCAL VOID draw_button _((Display *dpy, Window win, LVAL item, int reversed));
LOCAL VOID draw_special_button _((Display *dpy, Window win,
				  int type, int reversed));
LOCAL VOID delete_special_button _((Window win, int type));

/***********************************************************************/
/**                                                                   **/
/**                        Global Variables                           **/
/**                                                                   **/
/***********************************************************************/

/* configuration parameters - should be set using the defaults database */
extern XFontStruct *DialogFont;
extern unsigned long DialogBorderColor, ButtonBorderColor;
extern ColorPair DialogC, ButtonC;
extern unsigned int dialog_border_width, button_border_width;
extern int min_button_height, min_button_width, dialog_item_gap;

extern GC DialogGC, DialogRGC;

extern XContext EventContext, ObjectContext, CloseContext, MenuContext;

extern LVAL s_menu, sk_select;

/***********************************************************************/
/**                                                                   **/
/**                         Button Items                              **/
/**                                                                   **/
/***********************************************************************/

static LVAL track_button(dpy, win, item, modal)
     Display *dpy;
     Window win;
     LVAL item;
     int modal;
{
  int done = FALSE;
  LVAL result = NIL;
  XEvent report;

  draw_button(dpy, win, item, TRUE);

  while (! done) {
    XNextEvent(dpy, &report);
    switch (report.type) {
    case ButtonRelease:
      done = TRUE;
      result = item;
      break;
    case LeaveNotify:
      done = TRUE;
      if (report.xcrossing.window == win)
	XSetWindowBorderWidth(dpy, win, button_border_width);
      break;
    default:
      break;
    }
  }
  draw_button(dpy, win, item, FALSE);
      
  if (! modal && result != NIL) send_message(item, sk_do_action);

  return(result);
}

LOCAL VOID draw_button(dpy, win, item, reversed)
     Display *dpy;
     Window win;
     LVAL item;
     int reversed;
{
  Point ssz, bsz;
  char *text;
  int x, y, len;
  GC gc;
  unsigned long color;

  gc = (reversed) ? DialogRGC : DialogGC;
  color = (reversed) ? DialogC.fore : DialogC.back;

  XSetWindowBackground(dpy, win, color);
  XClearWindow(dpy, win);
  text = checkstring(slot_value(item, s_text));
  ssz = DialogStringSize(text);
  bsz = ListToPoint(slot_value(item, s_size));
  x = (bsz.h - ssz.h) / 2;
  y = (bsz.v - ssz.v) / 2 + DialogFont->max_bounds.ascent;
  len = strlen(text);
  XDrawString(dpy, win, gc, x, y, text, len);
  XSetWindowBackground(dpy, win, DialogC.back);
}
  
static LVAL button_handler(report, modal)
     XEvent report;
     int modal;
{
  Display *dpy = StX11Display();
  Window win;
  LVAL item;
  LVAL result = NIL;

  win = report.xany.window;
  item = StX11ItemObject(dpy, win);
  if (item != NIL) {
    switch (report.type) {
    case Expose:
      draw_button(dpy, win, item, FALSE);
      break;
    case ButtonPress:
      result = track_button(dpy, win, item, modal);
      break;
    case ButtonRelease:
      break;
    case EnterNotify:
      XSetWindowBorderWidth(dpy,report.xcrossing.window, 
			    button_border_width + 1);
      break;
    case LeaveNotify:
      XSetWindowBorderWidth(dpy, report.xcrossing.window, button_border_width);
      break;
    default: 
      break;
    }
  }
  return(result);
}

VOID InstallButtonItem(win, item)
     Window win;
     LVAL item;
{
  Display *dpy = StX11Display();
  Point loc, size;
  Window button;

  loc = ListToPoint(slot_value(item, s_location));
  size = ListToPoint(slot_value(item, s_size));
  button = XCreateSimpleWindow(dpy, win, loc.h, loc.v, size.h, size.v,
			       button_border_width,
                               ButtonBorderColor, ButtonC.back);
  XSelectInput(dpy, button, 
	       StructureNotifyMask | ExposureMask | EnterWindowMask |
               LeaveWindowMask | ButtonPressMask | ButtonReleaseMask);

  set_slot_value(item, s_window_id, cvfixnum((FIXTYPE) button));

  install_dialog_item_handler(dpy, button, button_handler, item);
  if (XSaveContext(dpy, button, ObjectContext, (caddr_t) item) != 0)
    xlfail("could not install object in window");
}

VOID DeleteButtonItem(win, item)
     Window win;
     LVAL item;
{
  Display *dpy = StX11Display();
  Window button;

  button = (Window) getfixnum(slot_value(item, s_window_id));

  delete_dialog_item_handler(dpy, button);
  if (XDeleteContext(dpy, button, ObjectContext) != 0)
    xlfail("could not delete object context");
  set_slot_value(item, s_window_id, NIL);
}

VOID DialogButtonGetDefaultSize(item, width, height)
     LVAL item;
     int *width, *height;
{
  Point sz;

  sz = DialogStringSize(checkstring(slot_value(item, s_text)));
  if (width != NULL) *width = max(sz.h + BUTTON_PAD, min_button_width);
  if (height != NULL) *height = max(sz.v + BUTTON_LEAD, min_button_height);
}

/***********************************************************************/
/**                                                                   **/
/**                          Close Buttons                            **/
/**                                                                   **/
/***********************************************************************/

int ClosePanelHeight()
{
  Point sz;

  sz = DialogStringSize(CLOSE_TEXT);
  return (sz.v + 4 * BUTTON_LEAD);
}

LOCAL LVAL special_button_object(dpy, button)
     Display *dpy;
     Window button;
{
  LVAL object;

  if (XFindContext(dpy, button, ObjectContext, (caddr_t *) &object) == 0 
      && objectp(object))
    return(object);
  else return(NIL);
}

static Point special_button_size(type)
     int type;
{
  char *text = NULL;
  Point bsz, ssz;

  switch (type) {
  case CloseButton: text = CLOSE_TEXT; break;
  case MenuButton:  text = MENU_TEXT;  break;
  default: break;
  }

  ssz = DialogStringSize(text);
  bsz.h = max(ssz.h + BUTTON_PAD, min_button_width);
  bsz.v = max(ssz.v + BUTTON_LEAD, min_button_height);
  return(bsz);
}

static VOID track_special_button(dpy, win, type, modal)
     Display *dpy;
     Window win;
     int type, modal;
{
  int done = FALSE, result = FALSE;
  XEvent report;
  LVAL object;

  draw_special_button(dpy, win, type, TRUE);

  while (! done) {
    XNextEvent(dpy, &report);
    switch (report.type) {
    case ButtonRelease:
      done = TRUE;
      result = TRUE;
      break;
    case LeaveNotify:
      done = TRUE;
      if (report.xcrossing.window == win)
	XSetWindowBorderWidth(dpy, win, button_border_width);
      break;
    default:
      break;
    }
  }
  draw_special_button(dpy, win, type, FALSE);

  if (! modal && result == TRUE) {
    switch (type) {
    case CloseButton: 
      object = special_button_object(dpy, win);
      if (object != NIL) send_message(object, sk_close);
      break;
    default:
      break;
    }
  }
}

LOCAL VOID draw_special_button(dpy, win, type, reversed)
     Display *dpy;
     Window win;
     int type, reversed;
{
  Point ssz, bsz;
  char *text = NULL;
  int x, y, len;
  GC gc;
  unsigned long color;

  switch (type) {
  case CloseButton: text = CLOSE_TEXT; break;
  case MenuButton:  text = MENU_TEXT; break;
  default: break;
  }

  gc = (reversed) ? DialogRGC : DialogGC;
  color = (reversed) ? DialogC.fore : DialogC.back;

  XSetWindowBackground(dpy, win, color);
  XClearWindow(dpy, win);
  ssz = DialogStringSize(text);
  bsz = special_button_size(type);
  x = (bsz.h - ssz.h) / 2;
  y = (bsz.v - ssz.v) / 2 + DialogFont->max_bounds.ascent;
  len = strlen(text);
  XDrawString(dpy, win, gc, x, y, text, len);
  XSetWindowBackground(dpy, win, DialogC.back);
}
  
static LVAL basic_special_button_handler(report, modal, type)
     XEvent report;
     int modal, type;
{
  Display *dpy = StX11Display();
  int screen = StX11Screen();
  Window win, child;
  int x, y, item;
  LVAL object, menu;

  if (modal) return(NIL);

  win = report.xany.window;
  object = special_button_object(dpy, win);
  if (objectp(object) && GETWINDOWADDRESS(object) != NullWindow) {
    switch (report.type) {
    case Expose:
      draw_special_button(dpy, win, type, FALSE);
      break;
    case ButtonPress:
      switch(type) {
      case MenuButton:
	XTranslateCoordinates(dpy, win, RootWindow(dpy, screen),
			      report.xbutton.x, report.xbutton.y, &x, &y,
			      &child);
	menu = slot_value(object, s_menu);
	if (menu_p(menu)) {
	  draw_special_button(dpy, win, type, TRUE);
	  XSetWindowBorderWidth(dpy, report.xcrossing.window, 
				button_border_width);
	  item = StMObPopup(menu, x, y, NIL);
	  draw_special_button(dpy, win, type, FALSE);
	  if (item > 0) send_message1(menu, sk_select, item);
	}
	else 
	  XSetWindowBorderWidth(dpy, report.xcrossing.window, 
				button_border_width);
	break;
      default:
	track_special_button(dpy, win, type, modal);
	break;
      }
      break;
    case ButtonRelease:
      break;
    case EnterNotify:
      XSetWindowBorderWidth(dpy,report.xcrossing.window, 
			    button_border_width + 1);
      break;
    case LeaveNotify:
      XSetWindowBorderWidth(dpy, report.xcrossing.window, button_border_width);
      break;
    default: 
      break;
    }
  }
  return(NIL);
}

static LVAL close_button_handler(report, modal)
     XEvent report;
     int modal;
{
  return(basic_special_button_handler(report, modal, CloseButton));
}

static LVAL menu_button_handler(report, modal)
     XEvent report;
     int modal;
{
  return(basic_special_button_handler(report, modal, MenuButton));
}

static VOID install_special_button(win, object, type)
     Window win;
     LVAL object;
     int type;
{
  Display *dpy = StX11Display();
  Point loc, size;
  Window button;
  LVAL (*handler)() = NULL;
  int width, height, gravity = 0;
  XSetWindowAttributes winattr;

  switch (type) {
  case CloseButton:
    loc.h = BUTTON_LEAD;
    loc.v = BUTTON_LEAD;
    size = special_button_size(type);
    handler = close_button_handler;
    gravity = NorthWestGravity;
    break;
  case MenuButton:
    size = special_button_size(type);
    loc.v = BUTTON_LEAD;
    StWGetSize(win, &width, &height, TRUE);
    loc.h = width - size.h - BUTTON_LEAD;
    handler = menu_button_handler;
    gravity = NorthEastGravity;
  default:
    break;
  }

  button = XCreateSimpleWindow(dpy, win, loc.h, loc.v, size.h, size.v,
			       button_border_width,
                               ButtonBorderColor, ButtonC.back);
  XSelectInput(dpy, button, 
	       StructureNotifyMask | ExposureMask | EnterWindowMask |
               LeaveWindowMask | ButtonPressMask | ButtonReleaseMask);

  winattr.win_gravity = gravity;
  XChangeWindowAttributes(dpy, button, CWWinGravity, &winattr);

  switch (type) {
  case CloseButton:
    if (XSaveContext(dpy, win, CloseContext, (caddr_t) button) != 0)
      xlfail("could not install close button context");
    break;
  case MenuButton:
    if (XSaveContext(dpy, win, MenuContext, (caddr_t) button) != 0)
      xlfail("could not install menu button context");
    break;
  default:
    break;
  }
  if (XSaveContext(dpy, button, EventContext, (caddr_t) handler) != 0)
    xlfail("could not install event handler");
  if (XSaveContext(dpy, button, ObjectContext, (caddr_t) object) != 0)
    xlfail("could not install object in window");
}

LOCAL VOID delete_special_button(win, type)
     Window win;
     int type;
{
  Display *dpy = StX11Display();
  Window button;
  XContext context = (XContext) 0;

  switch (type) {
  case CloseButton: context = CloseContext; break;
  case MenuButton:  context = MenuContext;  break;
  default: break;
  }

  if (XFindContext(dpy, win, context, (caddr_t *) &button) == 0) {
    if (XDeleteContext(dpy, win, context) != 0)
      xlfail("could not delete buttont context");
    if (XDeleteContext(dpy, button, EventContext) != 0)
      xlfail("could not delete event context");
    if (XDeleteContext(dpy, button, ObjectContext) != 0)
      xlfail("could not delete object context");
  }
}

VOID InstallCloseButton(win, object)
     Window win;
     LVAL object;
{
  install_special_button(win, object, CloseButton);
}

VOID DeleteCloseButton(win)
     Window win;
{
  delete_special_button(win, CloseButton);
}

VOID InstallMenuButton(win, object)
     Window win;
     LVAL object;
{
  install_special_button(win, object, MenuButton);
}

VOID DeleteMenuButton(win)
     Window win;
{
  delete_special_button(win, MenuButton);
}


syntax highlighted by Code2HTML, v. 0.9.1