/*********************************************************************/
/*  bibView: Administration of BibTeX-Databases                      */
/*           (Verwaltung von BibTeX-Literaturdatenbanken)            */
/*                                                                   */
/*  Module:  ComboBo.c                                               */
/*                                                                   */
/*             - Combo Box Widget                                    */
/*               Text widget with selection list                     */
/*                                                                   */
/*  Author:  Holger Martin,  martinh@informatik.tu-muenchen.de       */
/*           Peter M. Urban, urban@informatik.tu-muenchen.de         */
/*                                                                   */
/*  History:                                                         */
/*    11.22.91  HM   created                                         */
/*    05.26.92       Version 1.0 released                            */
/*                                                                   */
/*  Copyright 1992 TU MUENCHEN 					     */
/*    See ./Copyright for complete rights and liability information. */
/*                                                                   */
/*********************************************************************/


#include <X11/IntrinsicP.h>	
#include <X11/StringDefs.h>	
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/ShellP.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Command.h>
#include <X11/Xos.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <errno.h>

#ifndef HAVE_DIR
#include <dirent.h>
#else
#include <sys/dir.h>
#define dirent direct
#endif

#include <stdio.h>

extern char *getenv();

#include "ComboBoP.h"

#define Offset(field) XtOffsetOf(ComboBoxRec, comboBox.field)

static XtResource resources[] = {
    {XtNselectCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
	 Offset(select_callback), XtRCallback, (XtPointer) NULL},
    {XtNselectMenu, XtCSelectMenu, XtRString, sizeof(String),
	 Offset(select_menu), XtRString, (XtPointer) NULL},
    {XtNcancelCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
	 Offset(cancel_callback), XtRCallback, (XtPointer) NULL},
    {XtNmargin, XtCMargin, XtRDimension, sizeof(Dimension),
	 Offset(margin), XtRImmediate, (XtPointer) 10},
    {XtNnumberRows, XtCNumberStrings, XtRInt, sizeof(int),
	 Offset(number_rows), XtRImmediate, (XtPointer) 3},
    {XtNshowDotFiles, XtCShowDotFiles, XtRBoolean, sizeof(Boolean),
	 Offset(show_dot_files), XtRImmediate, (XtPointer) True},
    {XtNbellLevel, XtCBellLevel, XtRInt, sizeof(int),
	 Offset(bell_level), XtRImmediate, (XtPointer) 100},
    {XtNname, XtCName, XtRString, sizeof(String),    /* NEU */
	 Offset(name), XtRString, NULL},
    {XtNrcList, XtCRcList, XtRString, sizeof(String),    /* NEU */
	 Offset(rclist), XtRString, NULL},
};



#undef Offset

#define Child(w,child) (((ComboBoxWidget) w)->comboBox.child)
#define List(w) (((ComboBoxWidget) w)->comboBox.listList)
#define Rows(w) (((ComboBoxWidget) w)->comboBox.number_rows)
#define CurrentDir(w) (((ComboBoxWidget) w)->comboBox.currentDir)
#define WatchingChanges(w) (((ComboBoxWidget) w)->comboBox.watchingChanges)
#define Nomination(w) (((ComboBoxWidget) w)->comboBox.nomination)
#define ShowDotFiles(w) (((ComboBoxWidget) w)->comboBox.show_dot_files)
#define BellLevel(w) (((ComboBoxWidget) w)->comboBox.bell_level)
#define Name(w) (((ComboBoxWidget) w)->comboBox.name)
#define RcList(w) (((ComboBoxWidget) w)->comboBox.rclist)


static char listTranslations[] =
    "<Btn1Up>(2):         Set() Nominate() Unset()\n\
     <Btn1Down>,<Btn1Up>: Set() Notify() \n\
     <Btn2Up>:            Set() Notify()"; 
/*     <Btn2Up>:            Set() Notify() Nominate() Unset()"; */ 

static char filenameTranslations[] =
     "<Key>Return:  ";

static void Nominate();

static XtActionsRec pathActions[] = {
    "Nominate", Nominate,
};

static void                     ChangeDir();
static void                     FillWindow();
static void                     ReplaceFilename();
static void                     AsciiSourceChanged();
static void                     WatchForChanges();
static void                     DontWatchForChanges();
static void                     CollapsePath();

static void Initialize(), PositionChildren(), Realize(), Destroy();
static void ToggleAction();  /* NEU */
static void DestroyShell();

ComboBoxClassRec comboBoxClassRec = {
    /* Core class part */
  {
    /* superclass	     */	(WidgetClass) &widgetClassRec,
    /* class_name	     */ "ComboBox",
    /* widget_size	     */ sizeof(ComboBoxRec),
    /* class_initialize      */ NULL,
    /* class_part_initialize */ NULL,
    /* class_inited          */	FALSE,
    /* initialize	     */	Initialize,
    /* initialize_hook       */	NULL,
    /* realize		     */	Realize,
    /* actions		     */	NULL,
    /* num_actions	     */	0,
    /* resources	     */	resources,
    /* num_resources	     */	XtNumber(resources),
    /* xrm_class	     */	NULLQUARK,
    /* compress_motion	     */	TRUE,
    /* compress_exposure     */	XtExposeCompressMultiple,
    /* compress_enterleave   */	TRUE,
    /* visible_interest	     */	FALSE,
    /* destroy		     */ Destroy,
    /* resize		     */	PositionChildren,
    /* expose		     */	NULL,
    /* set_values	     */	NULL,
    /* set_values_hook       */	NULL,			
    /* set_values_almost     */	XtInheritSetValuesAlmost,  
    /* get_values_hook       */	NULL,
    /* accept_focus	     */	NULL,
    /* version		     */	XtVersion,
    /* callback offsets      */	NULL,
    /* tm_table              */	NULL,
    /* query_geometry	     */	XtInheritQueryGeometry,
    /* display_accelerator   */	NULL,
    /* extension	     */	NULL,
  },
   /* ComboBox class part */
  {
    /* extension	     */	NULL,
  }
};

WidgetClass comboBoxWidgetClass = (WidgetClass) &comboBoxClassRec;


#define MAX_AUSGABE   1000
static char *ausgabe;    /* fuer ComboBoGetString */


static void
CalculateSize(fnw, width, height)
    ComboBoxWidget fnw;
    Dimension *width, *height;
{
    int file_width = Child(fnw,filename_widget)->core.width +
	2 * Child(fnw,filename_widget)->core.border_width +
        fnw->comboBox.margin +                               /* NEU */
        Child(fnw,name_widget)->core.width +
	2 * Child(fnw,name_widget)->core.border_width;       


    int max;

    if (fnw->comboBox.margin == 0)
    {
      max =  file_width - 2 * Child(fnw,filename_widget)->core.border_width;
    }
    else
    {
      max = file_width;
    }

    *width = max + 2 * fnw->comboBox.margin;
    *height = Child(fnw,filename_widget)->core.height +
		    2 * fnw->comboBox.margin + 
		    2 * Child(fnw,filename_widget)->core.border_width; 
}

static void
PositionChildren(fnw)
    ComboBoxWidget fnw;
{

    if (fnw->comboBox.margin == 0)
    {
	XtConfigureWidget(Child(fnw,name_widget),    /* NEU */
			  0, 0,
			  fnw->core.width,
			  fnw->core.height,
			  0);
    }
    else           /* NEU */
    {
          XtConfigureWidget(Child(fnw,name_widget),
		      fnw->comboBox.margin,
		      fnw->comboBox.margin,
	              fnw->core.width -
                      3 * fnw->comboBox.margin -
                      2 * Child(fnw,name_widget)->core.border_width -
		      Child(fnw,filename_widget)->core.width -
                      2 * Child(fnw,filename_widget)->core.border_width, 
		      fnw->core.height -
		      2 * Child(fnw,name_widget)->core.border_width -
		      2 * fnw->comboBox.margin,
		      Child(fnw,name_widget)->core.border_width);
      }
    
    if (fnw->comboBox.margin == 0)
    {
	XtConfigureWidget(Child(fnw,filename_widget),
			  -(Child(fnw,filename_widget)->core.border_width),
			  fnw->core.height,
			  fnw->core.width,
			  Child(fnw,filename_widget)->core.height,
			  Child(fnw,filename_widget)->core.border_width);
    }
    else
    {
          XtConfigureWidget(Child(fnw,filename_widget),
		      Child(fnw,name_widget)->core.width +
		      Child(fnw,name_widget)->core.border_width +
                      2 * fnw->comboBox.margin,
		      fnw->comboBox.margin,
	              fnw->core.width -
                      3 * fnw->comboBox.margin -
                      Child(fnw,name_widget)->core.width -
                      2 * Child(fnw,name_widget)->core.border_width -
                      2 * Child(fnw,filename_widget)->core.border_width,
		      fnw->core.height -
		      2 * Child(fnw,filename_widget)->core.border_width -
		      2 * fnw->comboBox.margin,
		      Child(fnw,filename_widget)->core.border_width);
      }
}

/****************/
/* ToggleAction */
/****************/
static void ToggleAction(widget, clientData, callData)
Widget widget;
XtPointer clientData, callData;
{
   ComboBoxWidget hw;
   Position x,y,root_x,root_y;
   Boolean status;


   x = 0;
   y = 0;
   hw = (ComboBoxWidget)XtParent(widget);
   if (hw->comboBox.rclist == NULL) {
      XawToggleUnsetCurrent(Child(hw,name_widget));
      return;
   }

   XtVaGetValues(Child(hw,name_widget), XtNstate, &status, NULL);
   if (status == TRUE) {
     XtVaGetValues(widget, XtNx, &x, NULL);
     XtVaGetValues(widget, XtNy, &y, NULL);
     y += (((ComboBoxWidget) hw)->comboBox.margin);
     XtTranslateCoords(widget, x, y, &root_x, &root_y);
     XtMoveWidget(Child(hw,shell_widget), root_x+40, root_y+10);
     XtPopup(Child(hw,shell_widget), XtGrabNone);
   }
   else {
     XtPopdown(Child(hw,shell_widget));
   }
}



/* ARGSUSED */
static void
Initialize(req, new, args, num_args)
    Widget req, new;
    ArgList args;
    Cardinal *num_args;
{
    ComboBoxWidget fnw = (ComboBoxWidget) new;
    RcListNode *testlist;
    int anzahl;


    List(new) = NULL;
    Nomination(new).directoryPart = NULL;
    Nomination(new).filenamePart = NULL;
    ausgabe = XtCalloc(1, MAX_AUSGABE);
    
    WatchingChanges(new) = False;

    if (fnw->comboBox.name == NULL) 
        fnw->comboBox.name = XtNewString(fnw->core.name);
    else {
          fnw->comboBox.name = XtNewString(fnw->comboBox.name);
    }

    Child(fnw,name_widget)     /* NEU */
       = XtVaCreateWidget("name", toggleWidgetClass, new,
                           XtNlabel, fnw->comboBox.name,
                           XtNstate,   FALSE,
			   NULL);

    XtAddCallback(Child(fnw,name_widget), XtNcallback, ToggleAction, NULL); 
    XawToggleChangeRadioGroup(Child(fnw,name_widget),NULL);

    Child(fnw,shell_widget)
	= XtVaCreatePopupShell("shell", overrideShellWidgetClass,
			   new,
			   XtNsaveUnder, TRUE,
			   NULL);
    
    Child(fnw,viewport_widget)
	= XtVaCreateManagedWidget("viewport", viewportWidgetClass,
                           Child(fnw,shell_widget),
			   XtNallowVert, True,
                           XtNforceBars, True,
			   NULL);
 
    Child(fnw,list_widget) =
	XtVaCreateManagedWidget("list", listWidgetClass, Child(fnw,viewport_widget),
				XtNdefaultColumns, 1,
				XtNforceColumns, True,
				NULL);

    XtOverrideTranslations(Child(fnw,list_widget),
			   XtParseTranslationTable(listTranslations));

    XtAddCallback(Child(fnw,list_widget), XtNcallback, ReplaceFilename, NULL); 

    anzahl = 0;
    if (fnw->comboBox.rclist != NULL) {
      testlist = fnw->comboBox.rclist;
      while (testlist != NULL) {
        testlist = testlist->next;
        anzahl++;
      }
    }
    FillWindow(fnw,anzahl);


    Child(fnw,filename_widget)
	= XtVaCreateWidget("filename", asciiTextWidgetClass, new,
			   XtNeditType, XawtextEdit,
			   NULL);
    XtOverrideTranslations(Child(fnw,filename_widget),
			   XtParseTranslationTable(filenameTranslations));

    XtSetKeyboardFocus(new, Child(fnw,filename_widget));
    WatchForChanges(fnw);

    XtAppAddActions(XtWidgetToApplicationContext(new),
		    pathActions, XtNumber(pathActions));

    CalculateSize(fnw, &fnw->core.width, &fnw->core.height);
    PositionChildren(fnw);
}




static void 
Realize(w, valueMask, attributes)
    Widget w;
    XtValueMask *valueMask;
    XSetWindowAttributes *attributes;
{
    (*comboBoxWidgetClass->core_class.superclass->core_class.realize)
	(w, valueMask, attributes);

    XtRealizeWidget(Child(w,name_widget));     /* NEU */
    XtRealizeWidget(Child(w,shell_widget));
    XtRealizeWidget(Child(w,viewport_widget));
    XtRealizeWidget(Child(w,list_widget));
    XtRealizeWidget(Child(w,filename_widget));
    XMapSubwindows(XtDisplay(w), XtWindow(w));
}

static void Destroy(w)
    Widget w;
{
    int idx;
    ComboBoxWidget fnw = (ComboBoxWidget) w;
    
    XtDestroyWidget(Child(fnw,name_widget));    /* NEU */
    XtDestroyWidget(Child(fnw,list_widget));
    XtDestroyWidget(Child(fnw,viewport_widget));
    XtDestroyWidget(Child(fnw,shell_widget));    
    idx = 0;
    while (List(fnw)[idx])
    {
	XtFree(List(fnw)[idx++]);
    }
    XtFree(List(fnw)[idx]);
    XtFree((char *)List(fnw));

    XtFree(Nomination(fnw).directoryPart);
    XtFree(Nomination(fnw).filenamePart);
    XtFree(ausgabe);
}

/* ARGSUSED */

static void
DestroyShell(w, client_data, call_data)
     Widget w;
     XtPointer client_data, call_data;
{
    ComboBoxWidget fnw
        = (ComboBoxWidget) XtParent(w);
    XtPopdown(w);
    XtVaSetValues(Child(fnw,name_widget), XtNstate, FALSE, NULL);
}


static void
ChangeDirectory(fnw, position)
ComboBoxWidget fnw;
int position;
{
    String p;
    int m;

    if (position > 0)
    {
	p = CurrentDir(fnw);
	for (m = 0;  m < position;  ++m)
	{
	    while(*p++ != '/')
		;
	}
	*p = '\0';
    }

    XtVaSetValues(Child(fnw, filename_widget),
		  XtNstring, "",
		  NULL);

    FillWindow(fnw);

    PositionChildren(fnw);
}
	 
/* ARGSUSED */
static void
Nominate(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    ComboBoxWidget fnw;
    char *nomination, *home, selection[MAXPATHLEN], *newPath, *r;
    struct stat fstats;
    int status, len;
    if (XtIsSubclass(w, listWidgetClass))
    {
	fnw = (ComboBoxWidget) XtParent(XtParent(XtParent(w)));
    }
    else
    {
	fnw = (ComboBoxWidget) XtParent(w);
    }
    
    XtVaGetValues(Child(fnw,filename_widget),
		  XtNstring, &nomination,
		  NULL);

    
    selection[0] = '\0';
    if (*nomination == '/')
    {
	strcpy(selection, nomination);
    }
    else if (*nomination == '~' && (home = getenv("HOME")))
    {
	strcpy(selection, home);
	strcat(selection, &nomination[1]);
    }
    else
    {
	if (strlen(CurrentDir(fnw)) > 1)
	{
	    strcpy(selection, CurrentDir(fnw));
	}
	strcat(selection, "/");
	strcat(selection, nomination);
    }

    len = strlen(selection);
    if (len != 0)
    {
	newPath = (char *) XtMalloc(len + 2);
	CollapsePath(selection, newPath);
	status = stat(newPath, &fstats);
	if (status != -1 && fstats.st_mode & S_IFDIR)
	{
	    if (access(newPath, R_OK) == 0)
	    {
		if (newPath[strlen(newPath) - 1] != '/')
		{
		    strcat(newPath, "/");
		}
		strcpy(CurrentDir(fnw), newPath);
		ChangeDirectory(fnw, 0);
	    }
	    else
	    {
		XBell(XtDisplay(fnw), BellLevel(fnw));
	    }
	}
	else if (status == 0 || (status == -1 && errno == ENOENT))
	{
	    status = access(newPath, R_OK | W_OK);
	    r = (char *)rindex(newPath, '/');
	    XtFree(Nomination(fnw).filenamePart);
	    Nomination(fnw).filenamePart = XtNewString(r + 1);
	    Nomination(fnw).filenameStatus = (status == 0) ? status : errno;
	    *(r + 1) = '\0';
	    XtFree(Nomination(fnw).directoryPart);
	    status = access(newPath, R_OK);
	    if (strcmp(newPath, CurrentDir(fnw)) != 0 && status == 0)
	    {
		strcpy(CurrentDir(fnw), newPath);
		ChangeDirectory(fnw, 0);
		Nomination(fnw).directoryPart = XtNewString(CurrentDir(fnw));
	    }
	    else
	    {
		Nomination(fnw).directoryPart = XtNewString(newPath);
	    }
	    Nomination(fnw).directoryStatus = (status == 0) ? status : errno;
	    XtCallCallbacks((Widget) fnw, XtNselectCallback,
			                        (XtPointer) &Nomination(fnw));
	}
	else
	{
            XBell(XtDisplay(fnw), BellLevel(fnw));
	}
	XtFree(newPath);
    }
}

static void
FillWindow(fnw,num)
Widget fnw;
int num;
{
    XFontStruct *font;
    Dimension height, internalHeight, rowSpacing;
    int newNum, idx;
    char buf[MAXPATHLEN], *bp;
    String name;
    RcListNode *listnode;


    if (num <= 0)
    {
	return;
    }

    if (List(fnw))
    {
        idx = 0;
        while (List(fnw)[idx])
        {
            XtFree(List(fnw)[idx++]);
        }
	XtFree(List(fnw)[idx]);
	XtFree((char*)List(fnw));
    }
    List(fnw) = (String *) XtMalloc((num + 1) * sizeof(String));
    bp = buf + strlen(buf);
    listnode = RcList(fnw);
    for(idx = 0, newNum = 0; idx < num;  idx++)
    {
        name = listnode->data;
        List(fnw)[newNum] = XtMalloc(strlen(name) + 2);
        strcpy(List(fnw)[newNum], name);
        strcpy(bp, name);
        ++newNum;
        listnode = listnode->next;
    }


    List(fnw)[newNum] = NULL;

    XtVaGetValues(Child(fnw,list_widget),
                  XtNfont, &font,
                  XtNinternalHeight, &internalHeight,
		  XtNrowSpacing, &rowSpacing,
                  NULL);

    height = Rows(fnw) * (font->max_bounds.ascent +
		     font->max_bounds.descent + rowSpacing) -
			 rowSpacing + 2 * internalHeight; 

    XtVaSetValues(Child(fnw,viewport_widget),
                  XtNheight, height,
                  NULL);

    XawListChange(Child(fnw,list_widget), List(fnw), newNum, -1, True);
}




/* ARGSUSED */
static void
ReplaceFilename(w, client_data, call_data)
     Widget w;
     XtPointer client_data, call_data;
{

    ComboBoxWidget fnw
	= (ComboBoxWidget) XtParent(XtParent(XtParent(w)));

    XawListReturnStruct *list = XawListShowCurrent(Child(fnw,list_widget));

    XtVaSetValues(Child(fnw,filename_widget),
		  XtNstring, list->string,
		  NULL);

    XawTextSetInsertionPoint(Child(fnw,filename_widget),

			     (XawTextPosition) strlen(list->string));

    WatchForChanges(fnw);
    DestroyShell(Child(fnw,shell_widget)); 
}

/* ARGSUSED */
static void
AsciiSourceChanged(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    ComboBoxWidget fnw = (ComboBoxWidget) client_data;

    DontWatchForChanges(fnw);

    XawListUnhighlight(Child(fnw,list_widget));
}

static void
WatchForChanges(fnw)
     Widget fnw;
{
    if (!WatchingChanges(fnw))
    {
	XtAddCallback(XawTextGetSource(Child(fnw,filename_widget)), XtNcallback,
		      AsciiSourceChanged, (XtPointer) fnw);

	WatchingChanges(fnw) = True;
    }
}

static void
DontWatchForChanges(fnw)
     Widget fnw;
{
    XtRemoveCallback(XawTextGetSource(Child(fnw,filename_widget)), XtNcallback,
		     AsciiSourceChanged, (XtPointer) fnw);

    WatchingChanges(fnw) = False;
}

static void 
CollapsePath(in, out)
     char *in, *out;
{
    char *p = in, *q = out, *pend = p + strlen(p);
    
    while (p < pend)
    {
	if (*p != '/')
	{
	    *q++ = *p++;
	}
	else if (p + 1 < pend && *(p + 1) == '/')
	{
	    ++p;
	}
	else if ( (p + 2 == pend && *(p + 1) == '.') || 
		  (p + 2 < pend && *(p + 1) == '.' && *(p + 2) == '/') )
	{
	    p += 2;
	}
	else if ( (p + 3 == pend && *(p + 1) == '.' && *(p + 2) == '.') ||
		 (p + 3 < pend && *(p + 1) == '.'
		                      && *(p + 2) == '.' && *(p + 3) == '/') )
	{
	    while (q > out && *--q != '/')
		;
	    p += 3;
	}
	else
	{
	    *q++ = *p++;
	}
    }
    if (q == out)
    {
	*q++ = '/';
    }

    while (q > out)
    {
	if (*--q != '/')
	    break;
    }
    *++q = '\0';
}



String
ComboBoxGetString(fnw)
Widget fnw;
{

    if (XtIsSubclass(fnw, comboBoxWidgetClass))
    {
    
      ComboBoxStruct data =  Nomination(fnw);
        
      XtPopdown(Child(fnw, shell_widget));
      XtVaGetValues(Child(fnw,filename_widget),
		  XtNstring, &ausgabe,
		  NULL);
      if (strlen(ausgabe) == 0) return NULL;
      return ausgabe;
    }
    else
    {
	return NULL;
    }
}



void
ComboBoxSetString(fnw, dir)
     Widget fnw;
     String dir;
{
    if (!XtIsSubclass(fnw, comboBoxWidgetClass))
    {
	return;
    }

    strcpy(CurrentDir(fnw), dir);
    ChangeDirectory(fnw, 0);
}
