/* X11listitem - list items for X11 dialogs                            */
/* 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();

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

extern LVAL s_internals, s_window_id;

/* forward declarations */
LOCAL VOID draw_field_content _((Display *dpy, Window win,
				 char *text, int reversed));
LOCAL VOID draw_fields _((LVAL item));

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

/* configuration parameters - should be set using the defaults database */
extern XFontStruct *DialogFont;
extern unsigned long DialogBorderColor;
extern ColorPair DialogC;
extern unsigned int list_border_width;
extern int min_button_width;

extern GC DialogGC, DialogRGC;

extern XContext ObjectContext, ListFieldContext;

/***********************************************************************/
/**                                                                   **/
/**                           List Items                              **/
/**                                                                   **/
/***********************************************************************/

# define LIST_TEXT_PATTERN "MMMMMMMMMMMMMMM"
# define LIST_LEAD 5
# define LIST_PAD 15
# define MAX_LIST_ROWS 12
# define SCROLL_WIDTH 20
# define DOUBLE_CLICK_TIME 1000
# define DOUBLE_CLICK_MOVE 10

/***********************************************************************/
/**                                                                   **/
/**                     Double Click Detection                        **/
/**                                                                   **/
/***********************************************************************/

static int click_x, click_y, click_inited = FALSE;
static Time click_time;
Window click_win;

static int is_double_click(report)
     XEvent *report;
{
  int result, x, y, del_x, del_y, del_time;
  Time time;
  Window win;
  
  x = report->xbutton.x;
  y = report->xbutton.y;
  time = report->xbutton.time;
  win = report->xbutton.window;
  if (click_inited) {
    del_x = (click_x < x) ? x - click_x : click_x - x;
    del_y = (click_y < y) ? y - click_y : click_y - y;
    del_time = (click_time < time) ? time - click_time : click_time - time;
    result = (click_win == win
	      && del_x < DOUBLE_CLICK_MOVE 
	      && del_y < DOUBLE_CLICK_MOVE 
	      && del_time < DOUBLE_CLICK_TIME) ? TRUE : FALSE;
  }
  else result = FALSE;

  click_inited = ! result;
  click_x = x;
  click_y = y;
  click_time = time;
  click_win = win;

  return(result);
}

/***********************************************************************/
/**                                                                   **/
/**                    Basic Size Calculations                        **/
/**                                                                   **/
/***********************************************************************/

static Point data_size(data)
     LVAL data;
{
  Point sz;
  if (seqp(data)) { 
    sz.v = seqlen(data); 
    sz.h = 1;
  }
  else if (matrixp(data)) {
    sz.v = numrows(data);
    sz.h = numcols(data);
  }
  else xlerror("bad list item data", data);
  return(sz);
}

static Point cell_size()
{
  Point cellsz;

  cellsz = DialogStringSize(LIST_TEXT_PATTERN);
  cellsz.v += LIST_LEAD;
  cellsz.h += LIST_PAD;
  return(cellsz);
}

static int max_cols(item)
     LVAL item;
{
  LVAL columns = slot_value(item, s_columns);

  return((fixp(columns)) ? getfixnum(columns) : 1);
}

/***********************************************************************/
/**                                                                   **/
/**                        Index Conversion                           **/
/**                                                                   **/
/***********************************************************************/

static int field_index(dpy, win, vdims, ddims, offset)
     Display *dpy;
     Window win;
     Point vdims, ddims, offset;
{
  int vis_index, index;
  Point vpoint, dpoint;

  if (XFindContext(dpy, win, ListFieldContext, (caddr_t *) &vis_index) != 0)
    xlfail("could not find field index");
  vis_index--; /* needed to avoid storing a zero - confuses som X's */

  vpoint.v = vis_index / vdims.h;
  vpoint.h = vis_index % vdims.h;
  dpoint.v = offset.v + vpoint.v;
  dpoint.h = offset.h + vpoint.h;
  index = dpoint.v * ddims.h + dpoint.h;

  return(index);
}

/***********************************************************************/
/**                                                                   **/
/**                  Internal Data Represenation                      **/
/**                                                                   **/
/***********************************************************************/

static VOID make_internals(item)
     LVAL item;
{
  LVAL internals, data;
  int cols, num_fields;
  Point dsize, vsize;
  
  data = slot_value(item, s_list_data);
  cols = max_cols(item);
  dsize = data_size(data);
  vsize.h = (dsize.h > cols) ? cols : dsize.h;
  vsize.v = (dsize.v > MAX_LIST_ROWS) ? MAX_LIST_ROWS : dsize.v;
  num_fields = vsize.h * vsize.v;

  internals = newvector(8);
  set_slot_value(item, s_internals, internals);
  setelement(internals, 0, PointToList(vsize));
  setelement(internals, 1, PointToList(dsize));
  setelement(internals, 2,
	     matrixp(data) ? data : coerce_to_tvec(data, s_true));
  setelement(internals, 3, newvector(num_fields));
  setelement(internals, 4, integer_list_2(0, 0));
  setelement(internals, 5, NIL);
  setelement(internals, 6, NIL);
  setelement(internals, 7, NIL);
}

static Point visible_dims(internals)
     LVAL internals;
{
  return(ListToPoint(getelement(internals, 0)));
}

static Point data_dims(internals)
     LVAL internals;
{
  return(ListToPoint(getelement(internals, 1)));
}

static LVAL get_data(internals)
     LVAL internals;
{
  return(getelement(internals, 2));
}

static LVAL get_fields(internals)
     LVAL internals;
{
  return(getelement(internals, 3));
}

static Point get_offset(internals)
     LVAL internals;
{
  return(ListToPoint(getelement(internals, 4)));
}

static VOID set_offset(internals, offset)
     LVAL internals;
     Point offset;
{
  setelement(internals, 4, PointToList(offset));
}

static LVAL get_selection(internals)
     LVAL internals;
{
  return(getelement(internals, 5));
}

static int selection_index(internals)
     LVAL internals;
{
  LVAL sel = get_selection(internals);
  Point ddims, psel;

  if (sel == NIL) return(-1);
  else if (fixp(sel)) return(getfixnum(sel));
  else {
    ddims = data_dims(internals);
    psel = ListToPoint(sel);
    return(ddims.h * psel.h + psel.v);
  }
}

static VOID set_selection(item, val, index, use_val)
     LVAL item, val;
     int index, use_val;
{
  Point ddims, p;
  LVAL internals;

  internals = slot_value(item, s_internals);
  if (use_val) setelement(internals, 5, val);
  else if (vectorp(get_data(internals))) 
    setelement(internals, 5, cvfixnum((FIXTYPE) index));
  else {
    ddims = data_dims(internals);
    p.h = index / ddims.h;
    p.v = index % ddims.h;
    setelement(internals, 5, PointToList(p));
  }
  draw_fields(item);
}    

static VOID set_vscroll(internals, w, has)
     LVAL internals;
     Window w;
     int has;
{
  setelement(internals, 6, (has) ? cvfixnum((FIXTYPE) w) : NIL);
}

static VOID set_hscroll(internals, w, has)
     LVAL internals;
     Window w;
     int has;
{
  setelement(internals, 7, (has) ? cvfixnum((FIXTYPE) w) : NIL);
}

static int has_vscroll(internals)
     LVAL internals;
{
  return(getelement(internals, 6) != NIL);
}

static int has_hscroll(internals)
     LVAL internals;
{
  return(getelement(internals, 7) != NIL);
}

static Window get_vscroll(internals)
     LVAL internals;
{
  LVAL val = getelement(internals, 6);
  return(fixp(val) ? (Window) getfixnum(val) : None);
}

static Window get_hscroll(internals)
     LVAL internals;
{
  LVAL val = getelement(internals, 7);
  return(fixp(val) ? (Window) getfixnum(val) : None);
}

/***********************************************************************/
/**                                                                   **/
/**                       Drawing Routines                            **/
/**                                                                   **/
/***********************************************************************/

LOCAL VOID draw_field_content(dpy, win, text, reversed)
     Display *dpy;
     Window win;
     char *text;
     int reversed;
{
  Point ssz;
  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);
  ssz = DialogStringSize(text);
  x = LIST_PAD / 2;
  y = LIST_LEAD / 2 + DialogFont->max_bounds.ascent;
  len = strlen(text);
  XDrawString(dpy, win, gc, x, y, text, len);
  XSetWindowBackground(dpy, win, DialogC.back);
}

LOCAL VOID draw_fields(item)
     LVAL item;
{
  Display *dpy = StX11Display();
  Window win;
  LVAL fields, internals, data;
  Point vdims, ddims, offset;
  char *text;
  int sel, i, j, k, index, num_fields;

  internals = slot_value(item, s_internals);
  data = get_data(internals);
  if (darrayp(data)) data = getdarraydata(data);
  fields = get_fields(internals);
  vdims = visible_dims(internals);
  ddims = data_dims(internals);
  offset = get_offset(internals);
  sel = selection_index(internals);
  num_fields = getsize(fields);

  for (i = 0, k = 0; i < vdims.v; i++) {
    for (j = 0; j < vdims.h && k < num_fields; j++, k++) {
      win = getfixnum(gettvecelement(fields, k));
      index = (i + offset.v) * ddims.h + j + offset.h;
      text = checkstring(gettvecelement(data, index));
      draw_field_content(dpy, win, text, sel == index);
    }
  }
}

/***********************************************************************/
/**                                                                   **/
/**                        Event Handlers                             **/
/**                                                                   **/
/***********************************************************************/

static LVAL field_handler(report, modal)
     XEvent report;
     int modal;
{
  Display *dpy = StX11Display();
  Window win;
  LVAL item, internals, data;
  LVAL result = NIL;
  Point vdims, ddims, offset;
  char *text;
  int sel, index;

  win = report.xany.window;
  item = StX11ItemObject(dpy, win);
  internals = slot_value(item, s_internals);
  data = get_data(internals);
  if (darrayp(data)) data = getdarraydata(data);
  vdims = visible_dims(internals);
  ddims = data_dims(internals);
  offset = get_offset(internals);
  sel = selection_index(internals);

  if (item != NIL) {
    switch (report.type) {
    case Expose:
      index = field_index(dpy, win, vdims, ddims, offset);
      text = checkstring(gettvecelement(data, index));
      draw_field_content(dpy, win, text, sel == index);
      break;
    case ButtonPress:
      index = field_index(dpy, win, vdims, ddims, offset);
      set_selection(item, NIL, index, FALSE);
      if (is_double_click(&report)) 
        send_message_1L(item, sk_do_action, s_true);
      else 
	send_message(item, sk_do_action);
      break;
    default: 
      break;
    }
  }
  return(result);
}

static VOID scroll_action(item, s, which, x, y)
     LVAL item;
     Window s;
     int which, x, y;
{
  int is_h_scroll, val, max, page, pos, inc;
  Point size, offset, ddims, vdims, cellsz;
  LVAL internals;
  Window hscroll, vscroll;
  double side;

  internals = slot_value(item, s_internals);
  offset = get_offset(internals);
  ddims = data_dims(internals);
  vdims = visible_dims(internals);
  cellsz = cell_size();
  size.v = vdims.v * cellsz.v;
  size.h = vdims.h * cellsz.h;
  hscroll = get_hscroll(internals);
  vscroll = get_vscroll(internals);

  is_h_scroll = (s == hscroll) ? TRUE : FALSE;
  val = (is_h_scroll) ? offset.h : offset.v;
  max = (is_h_scroll) ? ddims.h : ddims.v;
  page = (is_h_scroll) ? vdims.h : vdims.v;
  side = (is_h_scroll) ? size.h : size.v;
  pos = (is_h_scroll) ? x * (max / side) : y * (max / side);
  inc = 1;
    
  switch (which) {
  case 'M': val = pos;  break;
  case 'L': val += inc; break;
  case 'R': val -= inc; break;
  }
  if (val + page > max) val = max - page;
  if (val < 0) val = 0;

  if (is_h_scroll) offset.h = val;
  else offset.v = val;
  set_offset(internals, offset);
  draw_fields(item);
  if (hscroll != None) AdjustScrollBar(hscroll, offset.h, vdims.h, ddims.h);
  if (vscroll != None) AdjustScrollBar(vscroll, offset.v, vdims.v, ddims.v);
}

/***********************************************************************/
/**                                                                   **/
/**                       Public Routines                             **/
/**                                                                   **/
/***********************************************************************/

VOID DialogListGetDefaultSize(item, width, height)
     LVAL item;
     int *width, *height;
{
  LVAL data = slot_value(item, s_list_data);
  Point sz, cellsz;
  int cols, m, n;

  cellsz = cell_size();

  cols = max_cols(item);
  sz = data_size(data);
  m = sz.v;
  n = sz.h;

  *height = (m <= MAX_LIST_ROWS) ? m * cellsz.v : MAX_LIST_ROWS * cellsz.v;
  *width = (n <= cols) ? n * cellsz.h : cols * cellsz.h;
  if (m > MAX_LIST_ROWS) *width +=  SCROLL_WIDTH;
  if (n > cols) *height += SCROLL_WIDTH;
}

VOID InstallListItem(win, item) 
     Window win;
     LVAL item;
{
  Display *dpy = StX11Display();
  Point loc, vsize, size, cellsz, dsize;
  Window panel, newfield, scroll;
  LVAL internals, fields;
  int num_fields, i, j, k;

  make_internals(item);

  internals = slot_value(item, s_internals);
  cellsz = cell_size();
  loc = ListToPoint(slot_value(item, s_location));
  vsize = visible_dims(internals);
  dsize = data_dims(internals);
  size.v = vsize.v * cellsz.v;
  size.h = vsize.h * cellsz.h;
  fields = get_fields(internals);
  num_fields = getsize(fields);

  panel = XCreateSimpleWindow(dpy, win, loc.h, loc.v, size.h, size.v,
			      list_border_width,
			      DialogBorderColor, DialogC.back);
  
  set_slot_value(item, s_window_id, cvfixnum((FIXTYPE) panel));
  
  if (XSaveContext(dpy, panel, ObjectContext, (caddr_t) item) != 0)
    xlfail("could not install object in window");

  if (dsize.h > vsize.h) {
    InstallScrollBar(win, item, loc.h, loc.v + size.v, size.h, SCROLL_WIDTH,
		     &scroll, scroll_action);
    set_hscroll(internals, scroll, TRUE);
    AdjustScrollBar(scroll, 0, vsize.h, dsize.h);
  }
  if (dsize.v > vsize.v) {
    InstallScrollBar(win, item, loc.h + size.h, loc.v, SCROLL_WIDTH, size.v,
		     &scroll, scroll_action);
    set_vscroll(internals, scroll, TRUE);
    AdjustScrollBar(scroll, 0, vsize.v, dsize.v);
  }

  for (i = 0, k = 0; i < vsize.v; i++) {
    for (j = 0; j < vsize.h && k < num_fields; j++, k++) {
      newfield = XCreateSimpleWindow(dpy, panel, cellsz.h * j, cellsz.v * i,
				     cellsz.h, cellsz.v, 0,
				     DialogBorderColor, DialogC.back);

      XSelectInput(dpy, newfield, ExposureMask | ButtonPressMask);
      install_dialog_item_handler(dpy, newfield, field_handler, item);
      if (XSaveContext(dpy, newfield, ObjectContext, (caddr_t) item) != 0)
	xlfail("could not install object in window");

      /* add 1 to index to avoid confusing context manager with zeros */
      if (XSaveContext(dpy, newfield, ListFieldContext, (caddr_t) (k + 1)) != 0)
	xlfail("could not install field index in window");
      setelement(fields, k, cvfixnum((FIXTYPE) newfield));
    }
  }
  XMapSubwindows(dpy, panel);
}

VOID DeleteListItem(win, item) 
     Window win;
     LVAL item;
{
  Display *dpy = StX11Display();
  Window panel, thefield;
  LVAL internals, fields;
  int k, num_fields;

  panel = (Window) getfixnum(slot_value(item, s_window_id));
  internals = slot_value(item, s_internals);
  fields = get_fields(internals);

  num_fields = getsize(fields);
  for (k = 0; k < num_fields; k++) {
    thefield = getfixnum(getelement(fields, k));
    delete_dialog_item_handler(dpy, thefield);
    if (XDeleteContext(dpy, thefield, ObjectContext) != 0)
      xlfail("could not delete object context");
    if (XDeleteContext(dpy, thefield, ListFieldContext) != 0)
      xlfail("could not delete list field context");
    setelement(fields, k, NIL);
  }

  if (has_hscroll(internals)) DeleteScrollBar(get_hscroll(internals));
  if (has_vscroll(internals)) DeleteScrollBar(get_vscroll(internals));

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

VOID DialogListItemSetText(item, index, text)
     LVAL item, index;
     char *text;
{
#ifdef DODO
/* 

  this is not needed since the matrix in the internals is eq to the
  one in the list-data slot already modified by the portable part of
  the code.  Besides, this code is wrong since it permutes the intex
  (as on the Mac).

*/
  LVAL internals, data;
  Point p, ddims;
  int i;

  internals = slot_value(item, s_internals);
  data = get_data(internals);
  if (darrayp(data)) data = getdarraydata(data);
  if (fixp(index)) i = getfixnum(index);
  else {
    p = ListToPoint(index);
    ddims = data_dims(internals);
    i = p.v * ddims.h + p.h;
  }
  if (0 <= i && i < getsize(data))
    settvecelement(data, i, cvstring(text));
  else xlerror("index out of range", index);
#endif
  draw_fields(item);
}

LVAL DialogListItemSelection(item, set, index) 
     LVAL item, index;
     int set;
{
  if (set) set_selection(item, index, 0, TRUE);
  return(get_selection(slot_value(item, s_internals)));
}


syntax highlighted by Code2HTML, v. 0.9.1