/*  Part of XPCE --- The SWI-Prolog GUI toolkit

    Author:        Jan Wielemaker and Anjo Anjewierden
    E-mail:        jan@swi.psy.uva.nl
    WWW:           http://www.swi.psy.uva.nl/projects/xpce/
    Copyright (c)  1985-2002, University of Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

#include <h/kernel.h>
#include <h/dialog.h>

static status	nameDialogItem(DialogItem di, Name name);

		/********************************
		*         CREATE/DESTROY	*
		********************************/

status
createDialogItem(Any obj, Name name)
{ DialogItem di = obj;

  initialiseGraphical(di, ZERO, ZERO, ZERO, ZERO);

  if ( isDefault(name) )
    name = getClassNameObject(di);
  nameDialogItem(di, name);

  assign(di, status,		 NAME_inactive);
/*assign(di, message,		 NIL);
  assign(di, popup,		 NIL);
  assign(di, above,		 NIL);
  assign(di, below,		 NIL);
  assign(di, right,		 NIL);
  assign(di, left,		 NIL); */
  assign(di, reference,		 DEFAULT);
  assign(di, label_width,	 DEFAULT);
  assign(di, auto_label_align,	 ON);
  assign(di, auto_value_align,	 ON);
  assign(di, auto_align,	 ON);
  assign(di, accelerator,	 DEFAULT);

  succeed;
}


status
unlinkDialogItem(DialogItem di)
{ return unlinkGraphical((Graphical)di);
}


status
RedrawLabelDialogItem(Any obj, int acc,
		      int x, int y, int w, int h,
		      Name hadjust, Name vadjust, int flags)
{ DialogItem di = obj;

  if ( instanceOfObject(di->label, ClassImage) )
  { Image i = di->label;
    int iw = valInt(i->size->w);
    int ih = valInt(i->size->h);
    int ix, iy;

    if ( hadjust == NAME_left )
      ix = x;
    else if ( hadjust == NAME_center )
      ix = x + (w-iw)/2;
    else
      ix = x + w-iw;

    if ( vadjust == NAME_top )
      iy = y;
    else if ( vadjust == NAME_center )
      iy = y + (h-ih)/2;
    else
      iy = y + h-ih;

    r_image(i, 0, 0, ix, iy, iw, ih, ON);
  } else if ( instanceOfObject(di->label, ClassCharArray) )
  { CharArray label = di->label;

    str_label(&label->data, acc, di->label_font,
	      x, y, w, h,
	      hadjust, vadjust, flags);
  }

  succeed;
}


status
dia_label_size(Any obj, int *w, int *h, int *isimage)
{ DialogItem di = obj;

  if ( instanceOfObject(di->label, ClassImage) )
  { Image i = di->label;

    *w = valInt(i->size->w);
    *h = valInt(i->size->h);
    if ( isimage )
      *isimage = TRUE;
  } else
  { if ( isimage )
      *isimage = FALSE;

    if ( instanceOfObject(di->label, ClassCharArray) )
    { CharArray ca = di->label;

      str_size(&ca->data, di->label_font, w, h);
    } else
    { *w = *h = 0;
    }
  }

  succeed;
}



static status
deviceDialogItem(DialogItem di, Device dev)
{ if ( di->device != dev && notNil(di->device) )
  { Graphical gr = (Graphical) di;

    aboveGraphical(gr, NIL);
    belowGraphical(gr, NIL);
    rightGraphical(gr, NIL);
    leftGraphical(gr, NIL);
  }

  return deviceGraphical((Graphical)di, dev);
}


		/********************************
		*          ATTRIBUTES		*
		********************************/

status
labelDialogItem(DialogItem di, Any label)
{ return assignGraphical(di, NAME_label, label);
}


status
labelFontDialogItem(DialogItem di, FontObj font)
{ return assignGraphical(di, NAME_labelFont, font);
}


static CharArray
getLabelNameDialogItem(DialogItem di, Name name)
{ Any suffix, label = GetLabelNameName(name);

  if ( !label || !instanceOfObject(label, ClassCharArray) )
    label = name;			/* play safe */

  if ( (suffix = getClassVariableValueObject(di, NAME_labelSuffix)) )
    label = getEnsureSuffixCharArray(label, suffix);

  answer(label);
}


static status
nameDialogItem(DialogItem di, Name name)
{ Any label = get(di, NAME_labelName, name, EAV);

  assign(di, name, name);
  if ( !label )
    label = name;

  return sendv(di, NAME_label, 1, &label);
}


static status
lookDialogItem(DialogItem di, Name look)
{ return assignGraphical(di, NAME_look, look);
}


static status
acceleratorDialogItem(DialogItem di, Name acc)
{ return assignGraphical(di, NAME_accelerator, acc);
}


static status
backgroundDialogItem(DialogItem di, Any bg)
{ return assignGraphical(di, NAME_background, bg);
}


static status
labelFormatDialogItem(DialogItem di, Name format)
{ return assignGraphical(di, NAME_labelFormat, format);
}


		/********************************
		*        EVENT_HANDLING		*
		********************************/

static status
advanceEventDialogItem(Any obj, EventObj ev)
{ if ( (ev->id == toInt(9) ||
	ev->id == NAME_cursorRight ||
	ev->id == NAME_cursorLeft) &&
       getKeyboardFocusGraphical(obj) == ON )
  { Name dir = (ev->id == NAME_cursorLeft ? NAME_backwards : NAME_forwards);
    Device dev = ((Graphical)obj)->device;

    send(dev, NAME_advance, obj, DEFAULT, dir, EAV);

    succeed;
  }

  fail;
}


status
eventDialogItem(Any obj, EventObj ev)
{ DialogItem di = obj;

  if ( eventGraphical(di, ev) )
    succeed;
  if ( advanceEventDialogItem(obj, ev) )
    succeed;

  if ( di->active == ON && notNil(di->popup) && isDownEvent(ev) &&
       send(popupGesture(), NAME_event, ev, EAV) )
    succeed;

  fail;
}


static status
statusDialogItem(DialogItem di, Name stat)
{ assign(di, status, stat);
  changedDialogItem(di);

  succeed;
}


static status
cancelDialogItem(DialogItem di)
{ return send(di, NAME_status, NAME_inactive, EAV);
}


		/********************************
		*           REPAINT		*
		********************************/


status
changedDialogItem(Any obj)
{ DialogItem di = obj;

  CHANGING_GRAPHICAL(di,
	changedEntireImageGraphical(di));

  succeed;
}


static status
showDialogItem(DialogItem di, BoolObj val)
{ if ( val == OFF )
  { PceWindow sw = getWindowGraphical((Graphical) di);

    if ( sw != FAIL && sw->keyboard_focus == (Graphical) di )
      send(di->device, NAME_advance, di, EAV);
  }

  return DisplayedGraphical(di, val);
}


static BoolObj
getShowDialogItem(DialogItem di)
{ answer(di->displayed);
}


		/********************************
		*          POSITIONS		*
		********************************/

Point
getReferenceDialogItem(Any obj)
{ DialogItem i = obj;

  ComputeGraphical(i);
  if ( notDefault(i->reference) )
    answer(i->reference);

  fail;
}


static status
resetDialogItem(DialogItem i)
{ send(i, NAME_status, NAME_inactive, EAV);

  succeed;
}


static status
openDialogItem(DialogItem di)
{ if ( isNil(di->device) )
  { Dialog d;

    TRY( d = newObject(ClassDialog, EAV) );
    TRY( send(d, NAME_append, di, EAV) );
  }

  return send(di->device, NAME_open, EAV);
}


		/********************************
		*        COMMUNICATION		*
		********************************/

static BoolObj
getModifiedDialogItem(Dialog di)
{ answer(OFF);
}


status
modifiedDialogItem(Any di, BoolObj modified)
{ Dialog d = di;

  if ( modified == ON )
    return send(d->device, NAME_modifiedItem, d, ON, EAV);

  succeed;
}


		 /*******************************
		 *	   ACCELERATORS		*
		 *******************************/

#define ACC_WSEP  0
#define ACC_UPPER 1
#define ACC_LOWER 2
#define ACC_DIGIT 3

#define ACC_CHARSETSIZE 256

typedef struct
{ int	      acc;
  int	      index;
  int	      mode;			/* ACC_UPPER, ACC_ALNUM */
  const char *label;			/* TBD: wide-character version */
  Any	      object;
} abin, *Abin;


static status
acc_index(Abin a, unsigned char *used)
{ int i;

  if ( a->mode == ACC_WSEP )
  { i = a->index+1;

    do
    { int acc = a->label[i];

      if ( isalpha(acc) )
      { acc = tolower(acc);

	if ( !(used && used[acc]) )
	{ a->index = i;
	  a->acc = acc;
	  succeed;
	}
      }
      while( a->label[i] && !isspace(a->label[i]) )
	i++;
      while( a->label[i] && isspace(a->label[i]) )
	i++;
    } while( a->label[i] );

    a->mode = ACC_UPPER;
  }

  if ( a->mode == ACC_UPPER )
  { for( i = a->index+1; a->label[i]; i++ )
    { int acc = a->label[i];

      if ( isupper(acc) )
      { acc = tolower(acc);

	if ( used && used[acc] )
	  continue;

	a->index = i;
	a->acc = acc;
	succeed;
      }
    }

    a->mode = ACC_LOWER;
  }

  if ( a->mode == ACC_LOWER )
  { for( i = a->index+1; a->label[i]; i++ )
    { int acc = a->label[i];

      if ( islower(acc) )
      { if ( used && used[acc] )
	  continue;

	a->index = i;
	a->acc = acc;
	succeed;
      }
    }

    a->mode = ACC_DIGIT;
  }


  for( i = a->index+1; a->label[i]; i++ )
  { int acc = a->label[i];

    if ( isdigit(acc) )
    { if ( used && used[acc] )
	continue;

      a->index = i;
      a->acc = acc;
      succeed;
    }
  }

  fail;
}

static bool
wantsAccelerator(Any obj)
{ if ( hasSendMethodObject(obj, NAME_accelerator) &&
       hasGetMethodObject(obj, NAME_accelerator) )
  { Any acc0 = get(obj, NAME_accelerator, EAV);
    return !isNil(acc0);
  }

  return false;
}


status
assignAccelerators(Chain objects, Name prefix, Name label_method)
{ int  size = valInt(objects->size);
  Abin bins = alloca(sizeof(abin) * size);
  int  n;
  Cell cell;
  Abin a = bins;
  unsigned char used[ACC_CHARSETSIZE];
  int do_free = FALSE;

  if ( size && !bins )
  { bins = pceMalloc(sizeof(abin) * size);
    do_free = TRUE;
  }

  for(n=0; n<ACC_CHARSETSIZE; n++)
    used[n] = 0;

  for_cell(cell, objects)
  { Any lbl;
    const char *s;

    if ( !wantsAccelerator(cell->value) )
      continue;

    if ( hasGetMethodObject(cell->value, label_method) &&
	 (lbl = get(cell->value, label_method, EAV)) &&
	 ( !instanceOfObject(lbl, ClassCharArray) ||
	   !((CharArray)lbl)->data.s_iswide ) &&
	 (s = toCharp(lbl)) )
    { a->label = s;
      a->index = -1;
      a->mode  = ACC_WSEP;
      if ( acc_index(a, NULL) )
      { used[tolower(a->acc)]++;
	a->object = cell->value;
	DEBUG(NAME_accelerator,
	      Cprintf("Proposing %c for %s\n", a->acc, pp(cell->value)));
	a++;
      } else
	send(cell->value, NAME_accelerator, NIL, EAV);
    } else
      send(cell->value, NAME_accelerator, NIL, EAV);
  }

  size = a - bins;
  DEBUG(NAME_accelerator,
	Cprintf("Trying to find accelerators for %d objects\n", size));

  for(n=0; n<size; n++)
  { int acc = bins[n].acc;

    if ( used[acc] > 1 )
    { int m;

      for( m=n+1; m<size; m++ )
      { if ( acc == bins[m].acc )
	{ if ( acc_index(&bins[m], used) )
	    used[bins[m].acc] = 1;
	  else
	    bins[m].acc = 0;

	  used[acc]--;
	}
      }
    }
  }

  for(n=0; n<size; n++)
  { int acc = bins[n].acc;

    if ( acc > 0 )
    { char buf[100];

      sprintf(buf, "%s%c", strName(prefix), acc);
      send(bins[n].object, NAME_accelerator, CtoKeyword(buf), EAV);
    } else
      send(bins[n].object, NAME_accelerator, NIL, EAV);
  }

  if ( do_free )
    pceFree(bins);

  succeed;
}



		 /*******************************
		 *	 CLASS DECLARATION	*
		 *******************************/

/* Type declarations */


/* Instance Variables */

static vardecl var_dialogItem[] =
{ SV(NAME_label, "char_array|image*", IV_GET|IV_STORE, labelDialogItem,
     NAME_label, "Label of the item"),
  SV(NAME_labelFont, "font*", IV_GET|IV_STORE, labelFontDialogItem,
     NAME_label, "Font used for the label if not an image"),
  IV(NAME_labelWidth, "[int]", IV_NONE,
     NAME_layout, "Width of the label in pixels"),
  SV(NAME_labelFormat, "{left,center,right}", IV_GET|IV_STORE,
     labelFormatDialogItem,
     NAME_layout, "Align labels in their box"),
  SV(NAME_background, "image|colour*", IV_GET|IV_STORE, backgroundDialogItem,
     NAME_appearance, "Opaque background for item"),
  SV(NAME_status, "{inactive,active,preview,execute}", IV_GET|IV_STORE,
     statusDialogItem,
     NAME_event, "Status for event-processing"),
  IV(NAME_message, "[code]*", IV_BOTH,
     NAME_action, "Associated command"),
  IV(NAME_popup, "popup*", IV_BOTH,
     NAME_menu, "Associated popup menu"),
  SV(NAME_look, "{x,open_look,motif,win}|name",IV_GET|IV_STORE,lookDialogItem,
     NAME_appearance, "Look-and-feel switch"),
  IV(NAME_autoAlign, "bool", IV_BOTH,
     NAME_layout, "Item is automatically placed by its dialog"),
  IV(NAME_reference, "[point]", IV_SEND,
     NAME_layout, "Reference point for alignment"),
  IV(NAME_above, "graphical*", IV_GET,
     NAME_layout, "DialogItem above me"),
  IV(NAME_below, "graphical*", IV_GET,
     NAME_layout, "DialogItem below me"),
  IV(NAME_right, "graphical*", IV_GET,
     NAME_layout, "DialogItem right of me"),
  IV(NAME_left, "graphical*", IV_GET,
     NAME_layout, "DialogItem left of me"),
  IV(NAME_alignment, "{column,left,center,right}", IV_BOTH,
     NAME_layout, "Align in columns or right of item to the left"),
  IV(NAME_autoLabelAlign, "bool", IV_BOTH,
     NAME_layout, "Automatically align label"),
  IV(NAME_autoValueAlign, "bool", IV_BOTH,
     NAME_layout, "Automatically align value"),
  SV(NAME_accelerator, "[name]*", IV_GET|IV_STORE, acceleratorDialogItem,
     NAME_layout, "Automatically align value")
};

/* Send Methods */

static senddecl send_dialogItem[] =
{ SM(NAME_initialise, 1, "name=name", createDialogItem,
     DEFAULT, "Create from name"),
  SM(NAME_device, 1, "device*", deviceDialogItem,
     DEFAULT, "Device I'm displayed on"),
  SM(NAME_name, 1, "name", nameDialogItem,
     DEFAULT, "Change <-name, update <-label"),
  SM(NAME_reset, 0, NULL, resetDialogItem,
     DEFAULT, "Change status to `inactive'"),
/*SM(NAME_unlink, 0, NULL, unlinkDialogItem,		empty these days
     DEFAULT, "Remove left,right,above,below links"), */
  SM(NAME_default, 1, "any", virtualObject,
     NAME_apply, "Virtual method"),
  SM(NAME_modified, 1, "bool", modifiedDialogItem,
     NAME_apply, "Forward modification to associated <-device"),
  SM(NAME_cancel, 0, NULL, cancelDialogItem,
     NAME_event, "Cancel operation (enter inactive state)"),
  SM(NAME_event, 1, "event", eventDialogItem,
     NAME_event, "Process an event"),
  SM(NAME_labelWidth, 1, "[int]", virtualObject,
     NAME_layout, "Virtual method"),
  SM(NAME_valueWidth, 1, "[int]", virtualObject,
     NAME_layout, "Virtual method"),
  SM(NAME_open, 0, NULL, openDialogItem,
     NAME_organisation, "Create dialog with this item and ->open"),
  SM(NAME_show, 1, "bool", showDialogItem,
     NAME_organisation, "Equivalent to <->displayed")
};

/* Get Methods */

static getdecl get_dialogItem[] =
{ GM(NAME_default, 0, "any", NULL, getVirtualObject,
     NAME_apply, "Virtual method"),
  GM(NAME_modified, 0, "bool", NULL, getModifiedDialogItem,
     NAME_apply, "Virtual method (return @off)"),
  GM(NAME_labelName, 1, "name", "name", getLabelNameDialogItem,
     NAME_label, "Determine default-label from the name"),
  GM(NAME_labelWidth, 0, "int", NULL, getVirtualObject,
     NAME_layout, "Virtual method"),
  GM(NAME_reference, 0, "point", NULL, getReferenceDialogItem,
     NAME_layout, "Reference point for alignment"),
  GM(NAME_valueWidth, 0, "int", NULL, getVirtualObject,
     NAME_layout, "Virtual method"),
  GM(NAME_show, 0, "bool", NULL, getShowDialogItem,
     NAME_organisation, "Equivalent to <-displayed")
};

/* Resources */

static classvardecl rc_dialogItem[] =
{ RC(NAME_alignment, "{column,left,center,right}", "column",
     "Alignment in the row"),
  RC(NAME_background, "colour|pixmap*", "@_dialog_bg",
     "Background of the item"),
  RC(NAME_elevation, "elevation*", "1",
     "3-D elevation"),
  RC(NAME_labelFont, "font", "bold",
     "Default font for labels"),
  RC(NAME_labelFormat, "{left,center,right}", "right",
     "Alignment of the label in its box"),
  RC(NAME_labelSuffix, "name", ":",
     "Ensured suffix of label"),
  RC(NAME_look, "{x,open_look,motif,win,gtk}",
     UXWIN("open_look", "win"),
     "Look-and-feel switch"),
  RC(NAME_selectionHandles, RC_REFINE, "@nil",
     NULL),
  RC(NAME_valueFont, "font", "normal",
     "Default font for values")
};

/* Class Declaration */

static Name dialogItem_termnames[] = { NAME_label };

ClassDecl(dialogItem_decls,
          var_dialogItem, send_dialogItem, get_dialogItem, rc_dialogItem,
          1, dialogItem_termnames,
          "$Rev$");

status
makeClassDialogItem(Class class)
{ return declareClass(class, &dialogItem_decls);
}
