/* ui.c: user interface toolkit
 *
 * 1998-04-10  Philippe Bekaert  <philippe@dilbert.cs.kuleuven.ac.be>
 *
 * 	new functions CreateHelSubMenu() and CreateScrolledWindow().
 *
 */

#include "ui.h"
#include "uit.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/Form.h>
#include <Xm/MessageB.h>
#include <Xm/Frame.h>
#include <Xm/Label.h>
#include <Xm/RowColumn.h>
#include <Xm/ToggleB.h>
#include <Xm/CascadeB.h>
#include <Xm/TextF.h>
#include <Xm/Separator.h>
#include <Xm/SelectioB.h>
#include <Xm/FileSB.h>
#include <Xm/ScrolledW.h>

#include "error.h"
#include "pools.h"

/* Enables state changes of the input widget */
void EnableWidget(Widget widget)
{
  XtSetSensitive(widget, True);
}

/* Disables state changes of the input widget */
void DisableWidget(Widget widget)
{
  XtSetSensitive(widget, False);
}

/* Creates a push button with given widget name, parent widget, activate callback
 * (no activate callback if NULL is passed), and client data for the activate 
 * callback procedure. */
Widget CreatePushButton(Widget parent,
			char *widgetname, 
			void (*activate_callback)(Widget, XtPointer, XtPointer),
			XtPointer client_data)
{
  Widget pushb;

  pushb = XtVaCreateManagedWidget(widgetname,
				  xmPushButtonWidgetClass,
				  parent,
				  NULL);

  if (activate_callback)
    XtAddCallback(pushb, XmNactivateCallback, activate_callback, client_data);

  return pushb;
}

/* creates a template dialog */
Widget CreateDialog(Widget parent, char *name)
{
  return XmCreateTemplateDialog(parent, name, visargs, nrvisargs);
}

void SetDialogTitle(Widget dialog, char *title)
{
  XtVaSetValues(dialog,
		XtVaTypedArg, XmNtitle, XmRString,
		title, strlen(title)+1, NULL);
}

/* unmanages a message box child (e.g. for a template dialog created with
 * CreateDialog(). */
void UnmanageMessageBoxChild(Widget dialog, unsigned char child)
{
  Widget childw = XmMessageBoxGetChild(dialog, child);
  XtUnmanageChild(childw);
}

/* manages a message box child */
void ManageMessageBoxChild(Widget dialog, unsigned char child)
{
  Widget childw = XmMessageBoxGetChild(dialog, child);
  XtManageChild(childw);
}

/* creates a radio box */
Widget CreateRadioBox(Widget parent, char *name)
{
  return XmCreateRadioBox(parent, name, visargs, nrvisargs);
}

/* creates a rowcolumn widget */
Widget CreateRowColumn(Widget parent, char *name)
{
  return XmCreateRowColumn(parent, name, visargs, nrvisargs);
}

/* Creates and returns a label widget with given name */
Widget CreateLabel(Widget parent, char *name)
{
  return XtVaCreateManagedWidget(name,
				 xmLabelWidgetClass,
				 parent,
				 NULL);
}

/* stolen from the Motif demos */
/* support routine to get normal string from an XmString (Motif compound string) */
char *extract_normal_string(XmString cs)
{
  XmStringContext context;
  XmStringCharSet charset;
  XmStringDirection direction;
  Boolean separator;
  static char *primitive_string;
  static char buf[MAX_LABEL_STRING_LENGTH+1], *p;

  XmStringInitContext (&context,cs);

  p = buf;
  while (XmStringGetNextSegment (context,&primitive_string,
				 &charset,&direction,&separator)) {
    if (p != buf) *p++ = '\n';	/* separate segments with a '\n' */
    if (!primitive_string) {
      Warning("extract_normal_string", "no primitive string");
      break;
    }
    strcpy(p, primitive_string);
    p += strlen(primitive_string);
  }

  XmStringFreeContext (context);

  return buf;
}

/* Returns the message string displayed on the label widget. 
 * The string should be no longer than MAX_LABEL_STRING_LENGTH 
 * characters! */
char *GetLabelString(Widget label)
{
  XmString xmstr;
  XtVaGetValues(label, XmNlabelString, &xmstr, NULL);
  return strdup(extract_normal_string(xmstr));
}

/* Sets the message displayed on a label widget */
void SetLabelString(Widget label, char *message)
{
  XtVaSetValues(label,
		XtVaTypedArg,
		  XmNlabelString, XmRString, message, strlen(message)+1,
		NULL);
}

/* Creates and returns a form widget with given name */
Widget CreateForm(Widget parent,
		  char *name)
{
  return XtVaCreateWidget(name,
			  xmFormWidgetClass,
			  parent,
			  NULL);
}

/* Creates and returns a frame widget with name specified by framename.
 * If frametitlename is not NULL, a label widget is created with that name,
 * which is a child of the frame with type XmFRAME_TITLE_CHILD. */
Widget CreateFrame(Widget parent, 
		   char *framename, 
		   char *frametitlename)
{
  Widget frame, title;

  frame = XtVaCreateManagedWidget(framename,
				  xmFrameWidgetClass,
				  parent,
				  NULL);

  if (frametitlename) {
    title = XtVaCreateManagedWidget(frametitlename,
				    xmLabelWidgetClass,
				    frame,
				    XmNchildType, XmFRAME_TITLE_CHILD,
				    NULL);
  }

  return frame;
}

/* default valuechanged callback for toggle buttons. */
static void default_toggle_callback(Widget w, XtPointer client_data, XtPointer call_data)
{
  int *state = (int *)client_data;
  int set = (((XmToggleButtonCallbackStruct *)call_data)->set == XmSET);
  *state = set;
}

/* Creates and returns a toggle button with given name and parent widget. The initial
 * state will be "SET" if initially_set is TRUE or "UNSET" if FALSE. If
 * 'toggle_callback' is not null, it will be called with the given client_data
 * each time the toggle button changes state. If 'toggle_callback' is
 * null, but 'client_data' is not null, a default toggle callback is
 * installed that interprets 'client_data' as a pointer to an int and
 * will fill in 1 or 0 according to the state of the toggle button. */
Widget CreateToggleButton(Widget parent,
			  char *name,
			  Boolean initially_set,
			  void (*toggle_callback)(Widget, XtPointer, XtPointer),
			  XtPointer client_data)
{
  Widget toggleB;

  toggleB = XtVaCreateManagedWidget(name,
				    xmToggleButtonWidgetClass,
				    parent,
				    XmNset, initially_set,
				    NULL);

  if (toggle_callback) 
    XtAddCallback(toggleB, XmNvalueChangedCallback, toggle_callback, client_data);
  else if (client_data)
    XtAddCallback(toggleB, XmNvalueChangedCallback, default_toggle_callback, client_data);

  return toggleB;
}

/* Creates and returns a new toplevel shell widget with given 
 * window title. Use XtRealizeWidget(the_shell_widget) to bring
 * the separate window to the screen and XtDestroyWidget(the_shell_widget) 
 * to remove it again. */
Widget CreateShell(char *title)
{
  return XtAppCreateShell(title,
			  APP_CLASS_NAME,
			  topLevelShellWidgetClass,
			  display,
			  visargs, nrvisargs);
}

/* Creates and returns a menu bar with given name and parent widget */
Widget CreateMenuBar(Widget parent, char *menuname)
{
  return XmCreateMenuBar(parent, menuname, visargs, nrvisargs);
}

/* Creates a popup menu (no button involved), position with XmMenuPosition()
 * and pop up with XtManageChild(). */
Widget CreatePopupMenu(Widget parent, char *menuname)
{
  return XmCreatePopupMenu(parent, menuname, visargs, nrvisargs);
}

/* Creates a cascade button and corresponding pulldown menu with given
 * parent and names. */
Widget CreateSubMenu(Widget parent, char *buttonname, char *menuname)
{
  Widget menu, button;

  button = XtVaCreateManagedWidget(buttonname,
				   xmCascadeButtonWidgetClass,
				   parent,
				   NULL);

  menu = XmCreatePulldownMenu(parent, menuname, visargs, nrvisargs);

  XtVaSetValues(button,
		XmNsubMenuId, menu,
		NULL);

  return menu;
}

/* Creates a cascade button and corresponding help pulldown menu with given
 * parent and names. (sets also the XmNmenuHelpWidget resource) */
Widget CreateHelpSubMenu(Widget parent, char *buttonname, char *menuname)
{
  Widget menu, button;

  button = XtVaCreateManagedWidget(buttonname,
				   xmCascadeButtonWidgetClass,
				   parent,
				   NULL);

  menu = XmCreatePulldownMenu(parent, menuname, visargs, nrvisargs);

  XtVaSetValues(button,
		XmNsubMenuId, menu,
		NULL);

  XtVaSetValues(parent,
		XmNmenuHelpWidget, button,
		NULL);

  return menu;
}

/* Creates an option menu and corresponding submenu with given parent and
 * names. The OptionMenu widget (the button) is returned. Use 
 * CreateOptionsButton() to create buttons in the menu. */
Widget CreateOptionMenu(Widget parent, char *buttonname, char *submenuname)
{
  Widget menu, button;
  Arg args[8];
  int n = 0;

  menu = XmCreatePulldownMenu(parent, submenuname, visargs, nrvisargs);
  
  for (n=0; n<nrvisargs; n++)
    args[n] = visargs[n];
  XtSetArg(args[n], XmNsubMenuId, menu); n++;
  button = XmCreateOptionMenu(parent, buttonname, args, n);
  XtManageChild(button);

  return button;
}

/* Creates a push button for an option menu. If 'set' is TRUE, the button 
 * will be the initial choice. The parent widget should be an
 * option menu. */
Widget CreateOptionButton(Widget optionmenu, char *buttonname,
			  Boolean set, 
			  void (*activate_callback)(Widget, XtPointer, XtPointer),
			  XtPointer client_data)
{
  Widget submenu, button;

  XtVaGetValues(optionmenu,
		XmNsubMenuId, &submenu,
		NULL);

  button = CreatePushButton(submenu, buttonname, activate_callback, client_data);
  if (set) 
    XtVaSetValues(submenu,
		  XmNmenuHistory, button,
		  NULL);

  return button;
}

/* default callback procedure used to popup up a cascade dialog */
void DoPopup(Widget w, XtPointer client_data, XtPointer call_data)
{
  XtManageChild((Widget)client_data);
}

/* Creates a button with name buttonname and a dialog that will be popped up 
 * when pressing the button. If non-null, the 'create_dialog' function creates 
 * and returns the dialog to be popped up. The button widget is passed as the first 
 * argument to this function. The first argument is the parent widget of the dialog 
 * to be created. The second argument is the dialogname. If 'create_dialog'
 * is not specified (null), a template dialog with name 'dialogname' will be created.
 * If a popup callback is given, it will be responsible for popping
 * up the dialog. If no one is given, a default popup callback,
 * which just popups the dialog, is used. If a popup callback is specified,
 * but no client data, the dialog widget will be passed as client data.
 * This function returns the created dialog widget. */
Widget CreateCascadeDialog(Widget parent,
			   char *buttonname,
			   Widget (*create_dialog)(Widget, char *),
			   char *dialogname,
			   void (*popup_callback)(Widget, XtPointer, XtPointer),
			   XtPointer client_data)
{
  Widget button, dialog;

  button = XtVaCreateManagedWidget(buttonname,
				   xmPushButtonWidgetClass,
				   parent,
				   NULL);

  if (!create_dialog)
    dialog = XmCreateTemplateDialog(button, dialogname, visargs, nrvisargs);
  else
    dialog = create_dialog(button, dialogname);

  if (popup_callback) 
    XtAddCallback(button, XmNactivateCallback, popup_callback,
		  client_data ? client_data : (XtPointer)dialog);
  else
    XtAddCallback(button, XmNactivateCallback, DoPopup, (XtPointer)dialog);

  return dialog;
}

/* prints the initial value into buf and returns a format string to be used 
 * for the given form entry type. */
static char *handle_fet_type(FORMENTRYTYPE type, XtPointer pvalue, char *buf)
{
  char *format = (char *)NULL;

  /* fill in the initial value of the text field widget */
  switch (type) {
  case FET_SHORT:
    format = "%hd";
    sprintf(buf, format, *(short *)pvalue);
    break;
  case FET_INTEGER:
    format = "%d";
    sprintf(buf, format, *(int *)pvalue);
    break;
  case FET_LONG:
    format = "%ld";
    sprintf(buf, format, *(long *)pvalue);
    break;
  case FET_UNSIGNED_SHORT:
    format = "%hu";
    sprintf(buf, format, *(unsigned short *)pvalue);
    break;
  case FET_UNSIGNED:
    format = "%u";
    sprintf(buf, format, *(unsigned int *)pvalue);
    break;
  case FET_UNSIGNED_LONG:
    format = "%lu";
    sprintf(buf, format, *(unsigned long *)pvalue);
    break;
  case FET_FLOAT:
    format = "%g";
    sprintf(buf, format, *(float *)pvalue);
    break;
  case FET_DOUBLE:
    format = "%lg";
    sprintf(buf, format, *(double *)pvalue);
    break;
  case FET_STRING:
    format = ".*s";
    sprintf(buf, format, (char *)pvalue);
  default:
    Fatal(2, "handle_fet_type", "Invalid form entry type %d", type);    
  }

  return format;
}

/* helper struct and routines for CreateFormEntry() below */
typedef struct TEXTFIELDCONTENTS {
  char *format;
  XtPointer pvalue;
  Boolean *valid;
} TEXTFIELDCONTENTS;

static void TextFieldGetValue(Widget textf, XtPointer client_data, XtPointer call_data)
{
  TEXTFIELDCONTENTS *contents = (TEXTFIELDCONTENTS *)client_data;
  char *text = XmTextFieldGetString(textf);
  *(contents->valid) = (sscanf(text, contents->format, contents->pvalue)==1);
  free(text);
}

static void TextFieldDestroy(Widget textf, XtPointer client_data, XtPointer call_data)
{
  TEXTFIELDCONTENTS *contents = (TEXTFIELDCONTENTS *)client_data;
  Free((char *)contents, sizeof(TEXTFIELDCONTENTS));
}

/* Creates a form entry: a label and text field widget pair that can be used
 * to construct forms. The label widget, indicating the meaning of the value that 
 * can be edited in the text field widget, is optional. If no such label
 * widget is wanted, 'labelname' should be a NULL pointer. 'textfname' is the name
 * of the text field widget. 'type' determines the type of the value in the text field.
 * 'pvalue' is a pointer to the value to be displayed in the text field.
 * The value is updated each time after text is deleted or inserted from/into the text
 * field. 'valid' will contain True if the text field has valid contents
 * and False if not. The text field contents are considered to be valid if
 * sscanf(textfieldcontents, format, pvalue) returns 1. 'fieldlength' determines the
 * width (in characters) of the text field. The field length should be large enough to
 * contain the initial contents of the field. 'fieldlength' can be zero, in which case 
 * the default is used (250 characters buffer for holding the initial contents
 * of the field and 20 characters width text field widget). If 'labelname' is not NULL, 
 * the label and text field widget are packed together in a form with
 * name 'formEntry'. The widget returned is the text field widget is 'labelname'
 * is NULL, or the form widget containing both the label and text field widget
 * is 'labelname' is not NULL. */
Widget CreateFormEntry(Widget parent, char *labelname, char *textfname, 
		       FORMENTRYTYPE type, XtPointer pvalue, Boolean *valid, 
		       int fieldlength)
{
  Widget entry=(Widget)NULL, label, value;
  char *buf, defbuf[250];
  TEXTFIELDCONTENTS *contents;
  char *format = NULL;

  if (fieldlength >= 250)
    buf = (char *)Alloc(fieldlength+1);
  else
    buf = defbuf;

  if (labelname) {
    /* create a form widget to hold the label and text field widget */
    entry = XtVaCreateWidget("formEntry",
			     xmFormWidgetClass,
			     parent,
			     NULL);
  }

  /* create a text field widget */
  value = XtVaCreateManagedWidget(textfname,
				  xmTextFieldWidgetClass,
				  labelname ? entry : parent,
				  XmNrightAttachment,	XmATTACH_FORM,
				  NULL);

  if (fieldlength>0)
    XtVaSetValues(value,
		  XmNmaxLength, fieldlength,
		  NULL);

  format = handle_fet_type(type, pvalue, buf);
  *valid = True;
  XmTextFieldSetString(value, buf);

  if (fieldlength >= 250)
    Free((char *)buf, fieldlength+1);

  /* install a callback that will handle changes in the contents of the text field */
  contents = (TEXTFIELDCONTENTS *)Alloc(sizeof(TEXTFIELDCONTENTS));
  contents->format = format;
  contents->pvalue = pvalue;
  contents->valid = valid;
  XtAddCallback(value, XmNvalueChangedCallback, TextFieldGetValue, (XtPointer)contents);
  XtAddCallback(value, XmNdestroyCallback, TextFieldDestroy, (XtPointer)contents);

  if (labelname) {
    /* create a label widget which is positioned left of the text field widget */
    label = XtVaCreateManagedWidget(labelname, 
				    xmLabelWidgetClass,
				    entry,
				    XmNrightAttachment, 	XmATTACH_WIDGET,
				    XmNrightWidget,		value,
				    XmNtopAttachment,		XmATTACH_FORM,
				    XmNbottomAttachment,	XmATTACH_FORM,
				    NULL);

    XtManageChild(entry);
    return entry;
  } else
    return value;
}

/* Creates a prompt dialog with given parent and name, asking for a
 * value of specified type to be filled in in pvalue. valid will contain
 * TRUE if the value that was fille din is valid and FALSE if invalid.
 * If fieldlength is not zero, it defines the length of the editable text
 * field in the prompt dialog. */
Widget CreatePromptDialog(Widget parent, char *name,
			  FORMENTRYTYPE type, XtPointer pvalue, 
			  Boolean *valid, int fieldlength)
{
  Widget dialog, value;
  char *buf, defbuf[250];
  TEXTFIELDCONTENTS *contents;
  char *format = NULL;

  if (fieldlength >= 250)
    buf = (char *)Alloc(fieldlength+1);
  else
    buf = defbuf;

  dialog = XmCreatePromptDialog(parent, name, visargs, nrvisargs);
  value = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);

  if (fieldlength>0)
    XtVaSetValues(value,
		  XmNmaxLength, fieldlength,
		  NULL);

  format = handle_fet_type(type, pvalue, buf);
  *valid = True;
  XmTextFieldSetString(value, buf);

  if (fieldlength >= 250)
    Free((char *)buf, fieldlength+1);

  /* install a callback that will handle changes in the contents of the text field */
  contents = (TEXTFIELDCONTENTS *)Alloc(sizeof(TEXTFIELDCONTENTS));
  contents->format = format;
  contents->pvalue = pvalue;
  contents->valid = valid;
  XtAddCallback(value, XmNvalueChangedCallback, TextFieldGetValue, (XtPointer)contents);
  XtAddCallback(value, XmNdestroyCallback, TextFieldDestroy, (XtPointer)contents);

  UnmanageSelectionBoxChild(dialog, XmDIALOG_HELP_BUTTON);

  return dialog;
}			  

/* unmanages a selection box child (e.g. for a prompt dialog created with
 * CreatePromptDialog() or a file selection dialog. */
void UnmanageSelectionBoxChild(Widget dialog, unsigned char child)
{
  Widget childw = XmSelectionBoxGetChild(dialog, child);
  XtUnmanageChild(childw);
}

/* manages a selection box child */
void ManageSelectionBoxChild(Widget dialog, unsigned char child)
{
  Widget childw = XmSelectionBoxGetChild(dialog, child);
  XtManageChild(childw);
}

static void unmanage_callback(Widget dialog, XtPointer client_data, XtPointer call_data)
{
  XtUnmanageChild(dialog);
}

typedef struct FSDATA {
  char *open_mode;
  int (*process_file)(char *fname, FILE *fp, int ispipe, Widget fsbox);
} FSDATA;

static void process_file_callback(Widget dialog, XtPointer client_data, XtPointer call_data)
{
  FSDATA *fsdata = (FSDATA *)client_data;
  char *filename = extract_normal_string (((XmSelectionBoxCallbackStruct *)call_data)->value);
  char *ext, *cmd, *open_mode = fsdata->open_mode;
  int ispipe = FALSE, succes;
  FILE *fp = (FILE *)NULL;

  /* check the open mode */
  if (!open_mode || 
      (*open_mode != 'r' && *open_mode != 'w' && *open_mode != 'a')) {
    Fatal(2, NULL, "Invalid fopen() mode '%s'\n", open_mode ? open_mode : "(null)");
    return;
  }

  if (filename[0] != '\0' && filename[strlen(filename)-1] != '/') {
    /* open the file */
    cmd = Alloc(strlen(filename) + 20);
    ext = strrchr(filename, '.');
    if (filename[0] == '|') {
      sprintf(cmd, "%s", filename+1);
      fp = popen(cmd, open_mode);
      ispipe = TRUE;
    } else if (ext && strcmp(ext, ".gz")==0) {
      if (*open_mode == 'r')
	sprintf(cmd, "gunzip < %s", filename);
      else
	sprintf(cmd, "gzip > %s", filename);
      fp = popen(cmd, open_mode);
      ispipe = TRUE;
    } else if (ext && strcmp(ext, ".Z")==0) {
      if (*open_mode == 'r')
	sprintf(cmd, "uncompress < %s", filename);
      else
	sprintf(cmd, "compress > %s", filename);
      fp = popen(cmd, open_mode);
      ispipe = TRUE;
    } else {
      fp = fopen(filename, open_mode);
      ispipe = FALSE;
    }

    Free(cmd, strlen(filename) + 20);

    if (!fp) {
      Error(NULL, "Can't open file '%s' for %s", 
	    filename, *open_mode == 'r' ? "reading" : "writing");
      return;
    }
  }

  /* call the user supplied procedure to process the file */
  succes = fsdata->process_file(filename, fp, ispipe, dialog);

  if (fp) {
    /* close the file */
    if (ispipe)
      pclose(fp);
    else
      fclose(fp);
  }

  /* unmanage the file selection dialog if processing was succesful */
  if (succes)
    XtUnmanageChild(dialog);
}

/* Creates and returns a file selection dialog with given parent and
 * name. If a 'process_file' function is specified, an okCallback
 * function is installed that will try to open the file with specified
 * 'open_mode', call the 'process_file' function passing on
 * the file name, the FILE pointer (possibly NULL if no filename is 
 * entered), a flag indicating whether the file is a pipe or a regular 
 * file and the file selection box Widget. Finally the opened file is
 * closed and the file selection box unmanaged if the 'process_file'
 * function returned nonzero. The file name suffices .gz and .Z
 * are understood, and when the file name starts with a '|', it is
 * interpreted as a command to open a pipe to/from. */
Widget CreateFileSelectionDialog(Widget parent, char *name, 
				 int (*process_file)(char *fname, FILE *fp, int ispipe, Widget fsbox), char *open_mode)
{
  Widget dialog = XmCreateFileSelectionDialog(parent, name, visargs, nrvisargs);

  XtVaSetValues(dialog, XmNautoUnmanage, False, NULL);

  XtAddCallback(dialog, XmNcancelCallback, unmanage_callback, NULL);
  if (process_file) {
    FSDATA *fsdata = (FSDATA *)Alloc(sizeof(FSDATA));
    fsdata->open_mode = open_mode;
    fsdata->process_file = process_file;
    XtAddCallback(dialog, XmNokCallback, process_file_callback, (XtPointer)fsdata);
  }

  UnmanageSelectionBoxChild(dialog, XmDIALOG_HELP_BUTTON);

  return dialog;
}

/* Creates and returns a separator widget with given name */
Widget CreateSeparator(Widget parent, char *name)
{
  return XtVaCreateManagedWidget(name,
				 xmSeparatorWidgetClass,
				 parent,
				 NULL);
}

/* Creates a scrolled window widget with given parent widget and name.
 * If 'create_child' is NULL, a XmLabel widget with name 'childname' is created
 * as the work window child. Otherwise, 'create_child' is called
 * in order to create a more complex child. */
Widget CreateScrolledWindow(Widget parent, char *name, 
			    Widget (*create_child)(Widget parent, char *childname), char *child_name)
{
  Widget scrolledW, child;

  scrolledW = XtVaCreateManagedWidget(name, 
				      xmScrolledWindowWidgetClass,
				      parent,
				      NULL);

  if (create_child)
    child = create_child(scrolledW, child_name);
  else
    child = CreateLabel(scrolledW, child_name);

  XtVaSetValues(scrolledW,
		XmNworkWindow, child,
		NULL);

  return scrolledW;
}
