/* callback.c
 * Callbacks for the X interface to the file conversion utility.
 *
 * Peter Webb, Summer 1990.
 */

/* X include files */

#include <X11/keysym.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>

/* Widget include files */

#include <X11/Xaw/Dialog.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/Text.h>
#include <X11/Xaw/Scrollbar.h>

/* Package include files */

#include "types.h"      /* Package-wide type definitions */
#include "error.h"      /* Error codes */
#include "extern.h"     /* External routines and global variables */
#include "reformat.h"   /* Package-wide contants */
#include "window.h"

#include <stdio.h>

/* Global variables */

int DescendDirectories = TRUE;

/* Static variables */

static Direction searchDir = Forward;
static DataType inputType = none, outputType = hdf;
static Arg args[MAX_ARGS];
static int numArgs;
static char input[BUF_SIZE], output[BUF_SIZE];

/* Set the global variable that controls the compression of HDF files. */

void CompressYesNoCallBack(widget, client_data, call_data)
  Widget widget;
  XtPointer client_data;
  XtPointer call_data;
{
  CompressHDF = (int) call_data;
}

/* Callback for performing actual file type conversion (finally doing some
 * work.)  Eventually this routine will loop over the global list of 
 * conversion operations, but for now, just get the current input file name,
 * input file type and output file name, and call Reformat.
 */

void ConvertCallBack(widget, client_data, call_data)
  Widget widget;
  XtPointer client_data;
  XtPointer call_data;
{
  Widget in_box, out_box;
  char *in_file, *out_file;

/* Get the input file and output file dialog boxes */

  in_box = XtNameToWidget(XtParent(widget), IN_FILE);
  out_box = XtNameToWidget(XtParent(widget), OUT_FILE);

/* Get the input and output file names */

  in_file = (char *)XawDialogGetValueString(in_box);
  out_file = (char *)XawDialogGetValueString(out_box);

/* Fully specify the path names */

  FullInputPath(in_file, input);
  FullOutputPath(out_file, output);

/* Do the conversion */

  Reformat(input, inputType, output, outputType);
}

/* Replace the text displayed in a dialog widget */

static void ReplaceDialogText(w, str)
  Widget w;
  char *str;
{
  Widget text;
  XawTextBlock text_blk;
  char *buf;
  int i, len;
  XawTextPosition display_pos, insert_pos;
  int char_width;
  XFontStruct *font;
  Dimension width;

  text = XtNameToWidget(w, "value");
  XawTextDisableRedisplay(text);

/* First, write enough blanks into the text field to erase the current
 * string.
 */

  if (str == NULL)
    {
      err_msg(ReplaceDialogTxt, NullPtr);
      return;
    }
  len = strlen(XawDialogGetValueString(w));
  if (len != 0)
    {
      buf = (char *)malloc(sizeof(char)*len + 1);
      for (i=0; i<len; i++)
	*(buf+i) = ' ';
      *(buf+i) = '\0';

      text_blk.ptr = buf;
      text_blk.firstPos = 0;
      text_blk.length = len;
      text_blk.format = FMT8BIT;
      XawTextReplace(text, 0, len, &text_blk);
      free(buf);
    }

  text_blk.ptr = str;
  text_blk.firstPos = 0;
  text_blk.length = strlen(str);
  text_blk.format = FMT8BIT;
  XawTextReplace(text, 0, len, &text_blk);

/* Now, scroll the text to the left, if necessary */

  insert_pos = strlen(str);
  XawTextSetInsertionPoint(text, insert_pos);

  numArgs = 0;
  XtSetArg(args[numArgs], XtNfont, &font); numArgs++;
  XtSetArg(args[numArgs], XtNwidth, &width); numArgs++;
  XtSetArg(args[numArgs], XtNdisplayPosition, &display_pos); numArgs++;
  XtGetValues(text, args, numArgs);

  char_width = font->max_bounds.rbearing - font->min_bounds.lbearing;

  if ((insert_pos - display_pos) * char_width >= width-char_width)
    display_pos = insert_pos - ((width/char_width)-1);
  else if (insert_pos <= display_pos)
    display_pos -= ((width/char_width)-1);
  if (display_pos < 0) display_pos = 0;

  numArgs = 0;
  XtSetArg(args[numArgs], XtNdisplayPosition, display_pos); numArgs++;
  XtSetValues(text, args, numArgs);
  
  XawTextEnableRedisplay(text);
}

/* Callback for setting the file type - client data == 0 means set the 
 * input file type, == 1 means set the output file type.  
 */

void FileTypeCallBack(widget, client_data, call_data)
  Widget widget;
  XtPointer client_data;
  XtPointer call_data;
{
  DataType d_type;
  XawListReturnStruct *elt = (XawListReturnStruct *)call_data;

  d_type = LookUpFileType(elt->string);
  if (d_type == none)
    err_msg(FileTypeCall, UnknownFileType);
  if ((int)client_data == TRUE)
    outputType = d_type;
  else inputType = d_type;

/* If the input type is RAW, then place the dimension dialog box on the 
 * screen.
 */

  if (d_type == raw8) /* Raw data */
    {
      if ((int)client_data == FALSE)
	MapDimensionBox(TRUE);
    }
  else MapDimensionBox(FALSE);
}

/* Callback for the help button. Popup the help window. */

void HelpCallBack(widget, client_data, call_data)
  Widget widget;
  XtPointer client_data;
  XtPointer call_data;
{
/* Popup the help window. */

  XtPopup((Widget)client_data, XtGrabNone);
}

/* Callback for the quit button */

void QuitCallBack(widget, client_data, call_data)
  Widget widget;
  XtPointer client_data;
  XtPointer call_data;
{
  XtDestroyApplicationContext((XtAppContext)client_data);
  exit(0);
}

/* Callback for the help window quit button - popdown the help shell */

void HelpQuitCallBack(widget, client_data, call_data)
  Widget widget;
  XtPointer client_data;
  XtPointer call_data;
{
  XtPopdown((Widget)client_data);
}

static void ChangeInputFiles(dir, form)
  char *dir;
  Widget form;
{
  Widget w1, list, v_scroll, viewport;
  char *input_files, *ptr;
  float t1;

/* Find the List widget holding the input files */

  viewport = XtNameToWidget(form, VIEWPORT);
  v_scroll = XtNameToWidget(viewport, "vertical");
  if (v_scroll == NULL)
    {
      err_msg(ChangeInFiles, InternalError);
      return;
    }
  list = XtNameToWidget(viewport, VIEW_LST);

/* Reset InputDirectory.  Must be done before calling ScanDir */
  
  strcpy(InputDirectory, dir);

/* Get the list of files in that directory */

  if (ScanDir(dir, &input_files, FALSE) == AllOk)
    {
      XawListChange(list, input_files, 0, 0, True);
      if (InputFiles != NULL) FreeStrArray(InputFiles);
      InputFiles = input_files;
    }
  else /* Bad directory specified */
    {
      XawListChange(list, EmptyList, 0, 0, False);
      if (InputFiles != NULL) FreeStrArray(InputFiles);
      InputFiles = NULL;
    }

/* Write the directory into the input directory box */

  w1 = XtNameToWidget(form, FROM_BOX);
  ReplaceDialogText(w1, InputDirectory, FALSE);

/* Input file is potentially invalid now. */

  if (ResetFileOnCd == TRUE)
    {
      w1 = XtNameToWidget(form, IN_FILE);
      ReplaceDialogText(w1, "", FALSE);
    }

/* Make sure that the vertical scrollbar is at the top of the widget.  Call
 * the "jumpProc" callbacks of the scrollbar, passing 0.0 as the position of
 * the pointer.
 */

  t1 = 0.0;
  XtCallCallbacks(v_scroll, XtNjumpProc, &t1);
}

/* When the user selects a string in the list widget, this routine is called.
 * The list widget contains the names of files.  When one is selected, mark
 * it as the input file that the user wishes to convert to another type. Add
 * it to the list of conversion operations.  At this point, the output file
 * is not known.  If the file is a directory, descend to that directory
 * instead (input files).
 */

void FileListCallBack(widget, client_data, call_data)
  Widget widget;
  XtPointer client_data;
  XtPointer call_data;
{
  XawListReturnStruct *list_elt;
  ErrorCode error;
  char name[FILE_SIZE], *ptr;
  FileType ftype;

/* Unhighlight the selection */

  XawListUnhighlight(widget);

/* Get the list element */

  list_elt = (XawListReturnStruct *)call_data;

/* Check the file type. If the file is a directory, descend into that
 * directory.  Otherwise place the file name in the input file dialog box
 * (passed in as client_data).
 */

  if ( (error = LastPathElement(list_elt->string, name)) != AllOk)
    return;

  if ((ftype = WhatFileType(name)) == dir && DescendDirectories == TRUE)

/* Check for special case of parent directory */

    {
      if (strcmp(name, "..") == 0)
	{
	  ptr = strrchr(InputDirectory, '/');
	  if (ptr == NULL)  /* Going up to directory above initial one */
	    {
	      err_msg(FileListCall, NotSupported);
	      return;
	    }
	  else
	    {
	      if (ptr == InputDirectory) *(++ptr) = '\0';
	      else *ptr = '\0';
	      strcpy(ScratchBuf, InputDirectory);
	    }
	}
      else if ( (error = FullInputPath(name, ScratchBuf)) != AllOk)
	return;
      ChangeInputFiles(ScratchBuf, XtParent(XtParent(widget)));
    }
  else if (ftype == unknown) return;
  else ReplaceDialogText((Widget)client_data, name);
}

/* The search routine */

static void HelpSearch(w, str)
  Widget w;
  char *str;
{
  XawTextBlock block;
  XawTextPosition pos, new_pos;

/* Construct a TextBlock containing the text to search for */

  block.ptr = str;
  block.length = strlen(block.ptr);
  block.firstPos = 0;
  block.format = FMT8BIT;

  new_pos = XawTextSearch(w, /* pos, */
			  (searchDir == Forward ? XawsdRight : XawsdLeft),
			  &block);

  if (new_pos != XawTextSearchError)
    {

/* Set the position of the insert cursor.  Since the cursor is always forced 
 * to be on the screen, this effectively scrolls the screen to the new 
 * position.
 */

      XawTextSetInsertionPoint(w, new_pos+(searchDir == Forward ? block.length
					   : 0 ) );
    }
  else /* Ring the bell */
    XBell(AppDisplay, 100);
}

/* The callback for the search button in the search dialog box */

void HelpSearchCallBack(w, client_data, call_data)
  Widget w;
  XtPointer client_data;
  XtPointer call_data;
{
  char *str;

/* Get the XtNstring resource */

  str = XawDialogGetValueString(XtParent(w));

/* Call the search routine */

  
  HelpSearch(XtNameToWidget(XtParent(XtParent(w)), HELP_TEXT), str);
}

/* The callback for the toggle widget on the help screen.   Toggle the
 * search direction.
 */

void HelpToggleCallBack(w, client_data, call_data)
  Widget w;
  XtPointer client_data;
  XtPointer call_data;
{
  String str;

/* Set the search direction */

  searchDir = (Direction)XawToggleGetCurrent(w);
}     

/*
 * Dialog box routines.
 */

/* Scroll the dialog box text when it is too large for the window.  This gets
 * called whenever a KeyPressEvent occurs in the text widget.
 */

void ScrollDialogText(box, client_data, event)
  Widget box;
  caddr_t client_data;
  XKeyEvent *event;
{
  long width = (long)client_data;
  char *str;
  XFontStruct *font;
  XawTextPosition insert_pos, display_pos;
  KeySym keysym;
  XawTextBlock text_blk;
  int len, char_width;

/* Disable redisplay, in an attempt to reduce flicker */

  XawTextDisableRedisplay(box);

/* Check key code.  The left arrow scrolls the text to the right if it is
 * in position 0.
 */

  numArgs = 0;
  XtSetArg(args[numArgs], XtNstring, &str); numArgs++;
  XtSetArg(args[numArgs], XtNfont, &font); numArgs++;
  XtSetArg(args[numArgs], XtNinsertPosition, &insert_pos); numArgs++;
  XtSetArg(args[numArgs], XtNdisplayPosition, &display_pos); numArgs++;
  XtGetValues(box, args, numArgs);

  char_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
  XLookupString(event, ScratchBuf, BUF_SIZE, &keysym, NULL);
  len = strlen(str);

  if ((keysym == XK_Left || keysym == XK_BackSpace || keysym == XK_Delete)
      && display_pos == insert_pos)
    {
      numArgs = 0;
      XtSetArg(args[numArgs], XtNdisplayPosition, display_pos - 1); numArgs++;
      XtSetValues(box, args, numArgs);
    }

/* Otherwise, check the length of the string against the width of the box.  */
 
  else if ((insert_pos - display_pos) * char_width >= width-char_width)
    {
      numArgs = 0;
      XtSetArg(args[numArgs], XtNdisplayPosition, display_pos + 1); numArgs++;
      XtSetValues(box, args, numArgs);
    }

/* Flush all changes to the screen */

  XawTextEnableRedisplay(box);
}

/* New action for the Dialog boxes.  Called when carriage return or line
 * feed occurs in the text input window.
 */

void DialogNewString(w, event, params, num_params)
  Widget w;
  XEvent *event;
  String *params;
  Cardinal *num_params;
{
  String str;
  char *name;
  Widget text, text_src, list, w1;
  XawTextPosition pos, new_pos;
  long num, *ptr;

/* Get the XtNstring resource */

  XtSetArg(args[0], XtNstring, &str);
  XtGetValues(w, args, 1);

/* Get the name of the parent */

  name = XtName(XtParent(w));

/* The parent of the text widget indicates in which dialog box the event
 * occurred.  The dialog box indicates what action should be taken.
 */

  if (strcmp(name, FROM_BOX) == 0) /* New input dir. */
    {
      ChangeInputFiles(str, XtParent(XtParent(w)));
    }
  else if (strcmp(name, TO_BOX) == 0) /* New output directory */
    {
      strcpy(OutputDirectory, str);
    }
  else if (strcmp(name, DIM_WIDTH) == 0) /* New image width */
    {
      Width = GetDimWidth();
    }
  else if (strcmp(name, DIM_HEIGHT) == 0) /* New image height */
    {
      Height = GetDimHeight();
    }
  else if (strcmp(name, SEARCH_BOX) == 0)  /*  Search box in help window */
    {

/* Get the pointer to the TextSource object contained in the text widget */

      text = XtNameToWidget(XtParent(XtParent(w)), HELP_TEXT);

      HelpSearch(text, str);

    } /* End if */
}

/* When the mouse cursor enters the text widget of a dialog box, highlight
 * the widget by doubling the width of its border.
 */

void DialogFocusIn(w, event, params, num_params)
  Widget w;
  XEvent *event;
  String *params;
  Cardinal *num_params;
{
  int arg_num;
  Arg arg_list[2];
  Dimension width;
  XEvent usr_event;

/* Get the old border width */

  arg_num = 0;
  XtSetArg(arg_list[arg_num], XtNborderWidth, &width); arg_num++;
  XtGetValues(w, arg_list, arg_num);

/* Set the new border width */

  width *= 2;
  arg_num = 0;
  XtSetArg(arg_list[arg_num], XtNborderWidth, width); arg_num++;
  XtSetValues(w, arg_list, arg_num);

/* Call the expose routine of the superclass */

  usr_event.type = Expose;
  XSendEvent(AppDisplay, XtWindow(w), True, ExposureMask, &usr_event);

}

/* When the mouse cursor exits the text widget of a dialog box, unhighlight
 * the widget by halving the width of its border.
 */

void DialogFocusOut(w, event, params, num_params)
  Widget w;
  XEvent *event;
  String *params;
  Cardinal *num_params;
{
  int arg_num;
  Arg arg_list[2];
  Dimension width;
  XEvent usr_event;

/* Get the old border width */

  arg_num = 0;
  XtSetArg(arg_list[arg_num], XtNborderWidth, &width); arg_num++;
  XtGetValues(w, arg_list, arg_num);

/* Set the new border width */

  width /= 2;
  arg_num = 0;
  XtSetArg(arg_list[arg_num], XtNborderWidth, width); arg_num++;
  XtSetValues(w, arg_list, arg_num);

/* Call the expose routine of the superclass */

  usr_event.type = Expose;
  XSendEvent(AppDisplay, XtWindow(w), True, ExposureMask, &usr_event);
}
