#define TRACE
#include <cdk_int.h>

/*
 * $Author: tom $
 * $Date: 2016/11/20 20:12:18 $
 * $Revision: 1.85 $
 */

/*
 * Declare file local prototypes.
 */
/* *INDENT-OFF* */
static BINDFN_PROTO (completeFilenameCB);
static BINDFN_PROTO (displayFileInfoCB);
static BINDFN_PROTO (fselectAdjustScrollCB);
static char *contentToPath (CDKFSELECT *fselect, char *content);
static char *errorMessage (const char *format);
static char *expandTilde (const char *filename);
static char *format1Date (const char *format, time_t value);
static char *format1Number (const char *format, long value);
static char *format1String (const char *format, const char *string);
static char *format3String (const char *format, const char *s1, const char *s2, const char *s3);
static char *format1StrVal (const char *format, const char *string, int value);
static char *trim1Char (char *source);
static int createList (CDKFSELECT *widget, CDK_CSTRING2 list, int listSize);
static void setPWD (CDKFSELECT *fselect);
/* *INDENT-ON* */

DeclareSetXXchar (static, _setMy);
DeclareCDKObjects (FSELECT, Fselect, _setMy, String);

/*
 * This creates a file selection widget.
 */
CDKFSELECT *newCDKFselect (CDKSCREEN *cdkscreen,
			   int xplace,
			   int yplace,
			   int height,
			   int width,
			   const char *title,
			   const char *label,
			   chtype fieldAttribute,
			   chtype fillerChar,
			   chtype highlight,
			   const char *dAttribute,
			   const char *fAttribute,
			   const char *lAttribute,
			   const char *sAttribute,
			   boolean Box,
			   boolean shadow)
{
   /* *INDENT-EQLS* */
   CDKFSELECT *fselect  = 0;
   int parentWidth      = getmaxx (cdkscreen->window);
   int parentHeight     = getmaxy (cdkscreen->window);
   int boxWidth;
   int boxHeight;
   int xpos             = xplace;
   int ypos             = yplace;
   int tempWidth        = 0;
   int tempHeight       = 0;
   int labelLen, junk;
   chtype *chtypeString;
   int x;
   /* *INDENT-OFF* */
   static const struct
   {
      int from;
      int to;
   } bindings[] =
   {
      { CDK_BACKCHAR,	KEY_PPAGE },
      { CDK_FORCHAR,	KEY_NPAGE },
   };
   /* *INDENT-ON* */

   if ((fselect = newCDKObject (CDKFSELECT, &my_funcs)) == 0)
        return (0);

   setCDKFselectBox (fselect, Box);

   /*
    * If the height is a negative value, the height will
    * be ROWS-height, otherwise, the height will be the
    * given height.
    */
   boxHeight = setWidgetDimension (parentHeight, height, 0);

   /*
    * If the width is a negative value, the width will
    * be COLS-width, otherwise, the width will be the
    * given width.
    */
   boxWidth = setWidgetDimension (parentWidth, width, 0);

   /* Rejustify the x and y positions if we need to. */
   alignxy (cdkscreen->window, &xpos, &ypos, boxWidth, boxHeight);

   /* Make sure the box isn't too small. */
   boxWidth = (boxWidth < 15 ? 15 : boxWidth);
   boxHeight = (boxHeight < 6 ? 6 : boxHeight);

   /* Make the file selector window. */
   fselect->win = newwin (boxHeight, boxWidth, ypos, xpos);

   /* Is the window null? */
   if (fselect->win == 0)
   {
      destroyCDKObject (fselect);
      return (0);
   }
   keypad (fselect->win, TRUE);

   /* *INDENT-EQLS* Set some variables. */
   ScreenOf (fselect)           = cdkscreen;
   fselect->parent              = cdkscreen->window;
   fselect->dirAttribute        = copyChar (dAttribute);
   fselect->fileAttribute       = copyChar (fAttribute);
   fselect->linkAttribute       = copyChar (lAttribute);
   fselect->sockAttribute       = copyChar (sAttribute);
   fselect->highlight           = highlight;
   fselect->fillerCharacter     = fillerChar;
   fselect->fieldAttribute      = fieldAttribute;
   fselect->boxHeight           = boxHeight;
   fselect->boxWidth            = boxWidth;
   fselect->fileCounter         = 0;
   fselect->pwd                 = 0;
   initExitType (fselect);
   ObjOf (fselect)->inputWindow = fselect->win;
   fselect->shadow              = shadow;
   fselect->shadowWin           = 0;

   /* Get the present working directory. */
   setPWD (fselect);

   /* Get the contents of the current directory. */
   setCDKFselectDirContents (fselect);

   /* Create the entry field in the selector. */
   chtypeString = char2Chtype (label, &labelLen, &junk);
   freeChtype (chtypeString);
   tempWidth = (isFullWidth (width)
		? FULL
		: boxWidth - 2 - labelLen);
   fselect->entryField = newCDKEntry (cdkscreen,
				      getbegx (fselect->win),
				      getbegy (fselect->win),
				      title, label,
				      fieldAttribute, fillerChar,
				      vMIXED, tempWidth, 0, 512,
				      Box, FALSE);

   /* Make sure the widget was created. */
   if (fselect->entryField == 0)
   {
      destroyCDKObject (fselect);
      return (0);
   }

   /* Set the lower left/right characters of the entry field. */
   setCDKEntryLLChar (fselect->entryField, ACS_LTEE);
   setCDKEntryLRChar (fselect->entryField, ACS_RTEE);

   /* Define the callbacks for the entry field. */
   bindCDKObject (vENTRY,
		  fselect->entryField,
		  KEY_UP,
		  fselectAdjustScrollCB,
		  fselect);
   bindCDKObject (vENTRY,
		  fselect->entryField,
		  KEY_PPAGE,
		  fselectAdjustScrollCB,
		  fselect);
   bindCDKObject (vENTRY,
		  fselect->entryField,
		  KEY_DOWN,
		  fselectAdjustScrollCB,
		  fselect);
   bindCDKObject (vENTRY,
		  fselect->entryField,
		  KEY_NPAGE,
		  fselectAdjustScrollCB,
		  fselect);
   bindCDKObject (vENTRY,
		  fselect->entryField,
		  KEY_TAB,
		  completeFilenameCB,
		  fselect);
   bindCDKObject (vENTRY,
		  fselect->entryField,
		  CTRL ('^'),
		  displayFileInfoCB,
		  fselect);

   /* Put the current working directory in the entry field. */
   setCDKEntryValue (fselect->entryField, fselect->pwd);

   /* Create the scrolling list in the selector. */
   tempHeight = getmaxy (fselect->entryField->win) - BorderOf (fselect);
   tempWidth = (isFullWidth (width)
		? FULL
		: boxWidth - 1);
   fselect->scrollField = newCDKScroll (cdkscreen,
					getbegx (fselect->win),
					getbegy (fselect->win) + tempHeight,
					RIGHT,
					boxHeight - tempHeight,
					tempWidth,
					0,
					(CDK_CSTRING2)fselect->dirContents,
					fselect->fileCounter,
					NONUMBERS, fselect->highlight,
					Box, FALSE);

   /* Set the lower left/right characters of the entry field. */
   setCDKScrollULChar (fselect->scrollField, ACS_LTEE);
   setCDKScrollURChar (fselect->scrollField, ACS_RTEE);

   /* Do we want a shadow? */
   if (shadow)
   {
      fselect->shadowWin = newwin (boxHeight, boxWidth, ypos + 1, xpos + 1);
   }

   /* Setup the key bindings. */
   for (x = 0; x < (int)SIZEOF (bindings); ++x)
      bindCDKObject (vFSELECT,
		     fselect,
		     (chtype)bindings[x].from,
		     getcCDKBind,
		     (void *)(long)bindings[x].to);

   registerCDKObject (cdkscreen, vFSELECT, fselect);

   return (fselect);
}

/*
 * This erases the file selector from the screen.
 */
static void _eraseCDKFselect (CDKOBJS *object)
{
   if (validCDKObject (object))
   {
      CDKFSELECT *fselect = (CDKFSELECT *)object;

      eraseCDKScroll (fselect->scrollField);
      eraseCDKEntry (fselect->entryField);
      eraseCursesWindow (fselect->win);
   }
}

/*
 * This moves the fselect field to the given location.
 */
static void _moveCDKFselect (CDKOBJS *object,
			     int xplace,
			     int yplace,
			     boolean relative,
			     boolean refresh_flag)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;
   /* *INDENT-EQLS* */
   int currentX = getbegx (fselect->win);
   int currentY = getbegy (fselect->win);
   int xpos     = xplace;
   int ypos     = yplace;
   int xdiff    = 0;
   int ydiff    = 0;

   /*
    * If this is a relative move, then we will adjust where we want
    * to move to.
    */
   if (relative)
   {
      xpos = getbegx (fselect->win) + xplace;
      ypos = getbegy (fselect->win) + yplace;
   }

   /* Adjust the window if we need to. */
   alignxy (WindowOf (fselect), &xpos, &ypos, fselect->boxWidth, fselect->boxHeight);

   /* Get the difference. */
   xdiff = currentX - xpos;
   ydiff = currentY - ypos;

   /* Move the window to the new location. */
   moveCursesWindow (fselect->win, -xdiff, -ydiff);
   moveCursesWindow (fselect->shadowWin, -xdiff, -ydiff);

   /* Move the sub-widgets. */
   moveCDKEntry (fselect->entryField, xplace, yplace, relative, FALSE);
   moveCDKScroll (fselect->scrollField, xplace, yplace, relative, FALSE);

   /* Redraw the window, if they asked for it. */
   if (refresh_flag)
   {
      drawCDKFselect (fselect, ObjOf (fselect)->box);
   }
}

/*
 * The fselect's focus resides in the entry widget.  But the scroll widget
 * will not draw items highlighted unless it has focus.  Temporarily adjust the
 * focus of the scroll widget when drawing on it to get the right highlighting.
 */
#define SaveFocus(widget) \
   boolean save = HasFocusObj (ObjOf (widget->scrollField)); \
   HasFocusObj (ObjOf (widget->scrollField)) = \
   HasFocusObj (ObjOf (widget->entryField))

#define RestoreFocus(widget) \
   HasFocusObj (ObjOf (widget->scrollField)) = save

static void drawMyScroller (CDKFSELECT *widget)
{
   SaveFocus (widget);
   drawCDKScroll (widget->scrollField, ObjOf (widget->scrollField)->box);
   RestoreFocus (widget);
}

static void injectMyScroller (CDKFSELECT *widget, chtype key)
{
   SaveFocus (widget);
   (void)injectCDKScroll (widget->scrollField, key);
   RestoreFocus (widget);
}

/*
 * This draws the file selector widget.
 */
static void _drawCDKFselect (CDKOBJS *object, boolean Box GCC_UNUSED)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   /* Draw in the shadow if we need to. */
   if (fselect->shadowWin != 0)
   {
      drawShadow (fselect->shadowWin);
   }

   /* Draw in the entry field. */
   drawCDKEntry (fselect->entryField, ObjOf (fselect->entryField)->box);

   /* Draw in the scroll field. */
   drawMyScroller (fselect);
}

/*
 * This means you want to use the given file selector. It takes input
 * from the keyboard, and when it's done, it fills the entry info
 * element of the structure with what was typed.
 */
char *activateCDKFselect (CDKFSELECT *fselect, chtype *actions)
{
   chtype input = 0;
   boolean functionKey;
   char *ret = 0;

   /* Draw the widget. */
   drawCDKFselect (fselect, ObjOf (fselect)->box);

   if (actions == 0)
   {
      for (;;)
      {
	 input = (chtype)getchCDKObject (ObjOf (fselect->entryField), &functionKey);

	 /* Inject the character into the widget. */
	 ret = injectCDKFselect (fselect, input);
	 if (fselect->exitType != vEARLY_EXIT)
	 {
	    return ret;
	 }
      }
   }
   else
   {
      int length = chlen (actions);
      int x = 0;

      /* Inject each character one at a time. */
      for (x = 0; x < length; x++)
      {
	 ret = injectCDKFselect (fselect, actions[x]);
	 if (fselect->exitType != vEARLY_EXIT)
	 {
	    return ret;
	 }
      }
   }

   /* Set the exit type and exit. */
   setExitType (fselect, 0);
   return 0;
}

/*
 * This injects a single character into the file selector.
 */
static int _injectCDKFselect (CDKOBJS *object, chtype input)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;
   char *filename;
   boolean file;
   char *ret = unknownString;
   bool complete = FALSE;

   /* Let the user play. */
   filename = injectCDKEntry (fselect->entryField, input);

   /* Copy the entry field exitType to the fileselector. */
   copyExitType (fselect, fselect->entryField);

   /* If we exited early, make sure we don't interpret it as a file. */
   if (fselect->exitType == vEARLY_EXIT)
   {
      return 0;
   }

   /* Can we change into the directory? */
   file = chdir (filename);
   if (chdir (fselect->pwd) != 0)
   {
      return 0;
   }

   /* If it's not a directory, return the filename. */
   if (file != 0)
   {
      /* It's a regular file, create the full path. */
      fselect->pathname = copyChar (filename);

      /* Return the complete pathname. */
      ret = (fselect->pathname);
      complete = TRUE;
   }
   else
   {
      /* Set the file selector information. */
      setCDKFselect (fselect, filename,
		     fselect->fieldAttribute, fselect->fillerCharacter,
		     fselect->highlight,
		     fselect->dirAttribute, fselect->fileAttribute,
		     fselect->linkAttribute, fselect->sockAttribute,
		     ObjOf (fselect)->box);

      /* Redraw the scrolling list. */
      drawMyScroller (fselect);
   }

   if (!complete)
      setExitType (fselect, 0);

   ResultOf (fselect).valueString = ret;
   return (ret != unknownString);
}

/*
 * This function sets the information inside the file selector.
 */
void setCDKFselect (CDKFSELECT *fselect,
		    const char *directory,
		    chtype fieldAttrib,
		    chtype filler,
		    chtype highlight,
		    const char *dirAttribute,
		    const char *fileAttribute,
		    const char *linkAttribute,
		    const char *sockAttribute,
		    boolean Box GCC_UNUSED)
{
   /* *INDENT-EQLS* */
   CDKSCROLL *fscroll   = fselect->scrollField;
   CDKENTRY *fentry     = fselect->entryField;
   char *tempDir        = 0;

   /* Keep the info sent to us. */
   fselect->fieldAttribute = fieldAttrib;
   fselect->fillerCharacter = filler;
   fselect->highlight = highlight;

   /* Set the attributes of the entry field/scrolling list. */
   setCDKEntryFillerChar (fentry, filler);
   setCDKScrollHighlight (fscroll, highlight);

   /* Only do the directory stuff if the directory is not null. */
   if (directory != 0)
   {
      char *newDirectory;

      /* Try to expand the directory if it starts with a ~ */
      if ((tempDir = expandTilde (directory)) != 0)
      {
	 newDirectory = tempDir;
      }
      else
      {
	 newDirectory = copyChar (directory);
      }

      /* Change directories. */
      if (chdir (newDirectory) != 0)
      {
	 char *mesg[10];

	 Beep ();

	 /* Could not get into the directory, pop up a little message. */
	 mesg[0] = format1String ("<C>Could not change into %s", newDirectory);
	 mesg[1] = errorMessage ("<C></U>%s");
	 mesg[2] = copyChar (" ");
	 mesg[3] = copyChar ("<C>Press Any Key To Continue.");

	 /* Pop Up a message. */
	 popupLabel (ScreenOf (fselect), (CDK_CSTRING2)mesg, 4);

	 /* Clean up some memory. */
	 freeCharList (mesg, 4);

	 /* Get out of here. */
	 eraseCDKFselect (fselect);
	 drawCDKFselect (fselect, ObjOf (fselect)->box);
	 freeChar (newDirectory);
	 return;
      }
      freeChar (newDirectory);
   }

   /*
    * If the information coming in is the same as the information
    * that is already there, there is no need to destroy it.
    */
   if (fselect->pwd != directory)
   {
      setPWD (fselect);
   }
   if (fselect->fileAttribute != fileAttribute)
   {
      /* Remove the old pointer and set the new value. */
      freeChar (fselect->fileAttribute);
      fselect->fileAttribute = copyChar (fileAttribute);
   }
   if (fselect->dirAttribute != dirAttribute)
   {
      /* Remove the old pointer and set the new value. */
      freeChar (fselect->dirAttribute);
      fselect->dirAttribute = copyChar (dirAttribute);
   }
   if (fselect->linkAttribute != linkAttribute)
   {
      /* Remove the old pointer and set the new value. */
      freeChar (fselect->linkAttribute);
      fselect->linkAttribute = copyChar (linkAttribute);
   }
   if (fselect->sockAttribute != sockAttribute)
   {
      /* Remove the old pointer and set the new value. */
      freeChar (fselect->sockAttribute);
      fselect->sockAttribute = copyChar (sockAttribute);
   }

   /* Set the contents of the entry field. */
   setCDKEntryValue (fentry, fselect->pwd);
   drawCDKEntry (fentry, ObjOf (fentry)->box);

   /* Get the directory contents. */
   if (setCDKFselectDirContents (fselect) == 0)
   {
      Beep ();
      return;
   }

   /* Set the values in the scrolling list. */
   setCDKScrollItems (fscroll,
		      (CDK_CSTRING2)fselect->dirContents,
		      fselect->fileCounter,
		      FALSE);
}

/*
 * This creates a list of the files in the current directory.
 */
int setCDKFselectDirContents (CDKFSELECT *fselect)
{
   struct stat fileStat;
   char **dirList = 0;
   int fileCount;
   int x = 0;

   /* Get the directory contents. */
   fileCount = CDKgetDirectoryContents (fselect->pwd, &dirList);
   if (fileCount <= 0)
   {
      /* We couldn't read the directory. Return. */
      CDKfreeStrings (dirList);
      return 0;
   }

   /* Clean out the old directory list. */
   CDKfreeStrings (fselect->dirContents);
   fselect->dirContents = dirList;
   fselect->fileCounter = fileCount;

   /* Set the properties of the files. */
   for (x = 0; x < fselect->fileCounter; x++)
   {
      char *oldItem;
      const char *attr = "";
      const char *mode = "?";

      /* FIXME: access() would give a more correct answer */
      if (lstat (dirList[x], &fileStat) == 0)
      {
	 mode = " ";
	 if ((fileStat.st_mode & S_IXUSR) != 0)
	 {
	    mode = "*";
	 }
#if defined (S_IXGRP) && defined (S_IXOTH)
	 else if (((fileStat.st_mode & S_IXGRP) != 0) ||
		  ((fileStat.st_mode & S_IXOTH) != 0))
	 {
	    mode = "*";
	 }
#endif
      }

      switch (mode2Filetype (fileStat.st_mode))
      {
      case 'l':
	 attr = fselect->linkAttribute;
	 mode = "@";
	 break;
      case '@':
	 attr = fselect->sockAttribute;
	 mode = "&";
	 break;
      case '-':
	 attr = fselect->fileAttribute;
	 break;
      case 'd':
	 attr = fselect->dirAttribute;
	 mode = "/";
	 break;
      default:
	 break;
      }
      oldItem = dirList[x];
      fselect->dirContents[x] = format3String ("%s%s%s", attr, dirList[x], mode);
      free (oldItem);
   }
   return 1;
}

char **getCDKFselectDirContents (CDKFSELECT *fselect, int *count)
{
   (*count) = fselect->fileCounter;
   return fselect->dirContents;
}

/*
 * This sets the current directory of the file selector.
 */
int setCDKFselectDirectory (CDKFSELECT *fselect, const char *directory)
{
   /* *INDENT-EQLS* */
   CDKENTRY *fentry     = fselect->entryField;
   CDKSCROLL *fscroll   = fselect->scrollField;
   int result           = 1;

   /*
    * If the directory supplied is the same as what is already
    * there, return.
    */
   if (fselect->pwd != directory)
   {
      /* Try to chdir into the given directory. */
      if (chdir (directory) != 0)
      {
	 result = 0;
      }
      else
      {
	 setPWD (fselect);

	 /* Set the contents of the entry field. */
	 setCDKEntryValue (fentry, fselect->pwd);
	 drawCDKEntry (fentry, ObjOf (fentry)->box);

	 /* Get the directory contents. */
	 if (setCDKFselectDirContents (fselect) == 0)
	 {
	    result = 0;
	 }
	 else
	 {
	    /* Set the values in the scrolling list. */
	    setCDKScrollItems (fscroll,
			       (CDK_CSTRING2)fselect->dirContents,
			       fselect->fileCounter,
			       FALSE);
	 }
      }
   }
   return result;
}
char *getCDKFselectDirectory (CDKFSELECT *fselect)
{
   return fselect->pwd;
}

/*
 * This sets the filler character of the entry field.
 */
void setCDKFselectFillerChar (CDKFSELECT *fselect, chtype filler)
{
   CDKENTRY *fentry = fselect->entryField;
   fselect->fillerCharacter = filler;
   setCDKEntryFillerChar (fentry, filler);
}
chtype getCDKFselectFillerChar (CDKFSELECT *fselect)
{
   return fselect->fillerCharacter;
}

/*
 * This sets the highlight bar of the scrolling list.
 */
void setCDKFselectHighlight (CDKFSELECT *fselect, chtype highlight)
{
   CDKSCROLL *fscroll = (CDKSCROLL *)fselect->scrollField;
   fselect->highlight = highlight;
   setCDKScrollHighlight (fscroll, highlight);

}
chtype getCDKFselectHighlight (CDKFSELECT *fselect)
{
   return fselect->highlight;
}

/*
 * This sets the attribute of the directory attribute in the
 * scrolling list.
 */
void setCDKFselectDirAttribute (CDKFSELECT *fselect, const char *attribute)
{
   /* Make sure they are not the same. */
   if (fselect->dirAttribute != attribute)
   {
      freeChar (fselect->dirAttribute);
      fselect->dirAttribute = copyChar (attribute);

      setCDKFselectDirContents (fselect);
   }
}
char *getCDKFselectDirAttribute (CDKFSELECT *fselect)
{
   return fselect->dirAttribute;
}

/*
 * This sets the attribute of the link attribute in the
 * scrolling list.
 */
void setCDKFselectLinkAttribute (CDKFSELECT *fselect, const char *attribute)
{
   /* Make sure they are not the same. */
   if (fselect->linkAttribute != attribute)
   {
      freeChar (fselect->linkAttribute);
      fselect->linkAttribute = copyChar (attribute);

      setCDKFselectDirContents (fselect);
   }
}
char *getCDKFselectLinkAttribute (CDKFSELECT *fselect)
{
   return fselect->linkAttribute;
}

/*
 * This sets the attribute of the link attribute in the
 * scrolling list.
 */
void setCDKFselectSocketAttribute (CDKFSELECT *fselect, const char *attribute)
{
   /* Make sure they are not the same. */
   if (fselect->sockAttribute != attribute)
   {
      freeChar (fselect->sockAttribute);
      fselect->sockAttribute = copyChar (attribute);

      setCDKFselectDirContents (fselect);
   }
}
char *getCDKFselectSocketAttribute (CDKFSELECT *fselect)
{
   return fselect->sockAttribute;
}

/*
 * This sets the attribute of the link attribute in the
 * scrolling list.
 */
void setCDKFselectFileAttribute (CDKFSELECT *fselect, const char *attribute)
{
   /* Make sure they are not the same. */
   if (fselect->fileAttribute != attribute)
   {
      freeChar (fselect->fileAttribute);
      fselect->fileAttribute = copyChar (attribute);

      setCDKFselectDirContents (fselect);
   }
}
char *getCDKFselectFileAttribute (CDKFSELECT *fselect)
{
   return fselect->fileAttribute;
}

/*
 * This sets the box attribute of the widget.
 */
void setCDKFselectBox (CDKFSELECT *fselect, boolean Box)
{
   ObjOf (fselect)->box = Box;
   ObjOf (fselect)->borderSize = Box ? 1 : 0;
}
boolean getCDKFselectBox (CDKFSELECT *fselect)
{
   return ObjOf (fselect)->box;
}

/*
 * This sets the contents of the widget
 */
void setCDKFselectContents (CDKFSELECT *widget,
			    CDK_CSTRING2 list,
			    int listSize)
{
   /* *INDENT-EQLS* */
   CDKSCROLL *scrollp   = widget->scrollField;
   CDKENTRY *entry      = widget->entryField;

   if (!createList (widget, list, listSize))
      return;

   /* Set the information in the scrolling list. */
   setCDKScroll (scrollp,
		 (CDK_CSTRING2)widget->dirContents,
		 widget->fileCounter,
		 NONUMBERS,
		 scrollp->highlight,
		 ObjOf (scrollp)->box);

   /* Clean out the entry field. */
   setCDKFselectCurrentItem (widget, 0);
   cleanCDKEntry (entry);

   /* Redraw the widget. */
   eraseCDKFselect (widget);
   drawCDKFselect (widget, ObjOf (widget)->box);
}

char **getCDKFselectContents (CDKFSELECT *widget,
			      int *size)
{
   (*size) = widget->fileCounter;
   return widget->dirContents;
}

/*
 * Get/set the current position in the scroll-widget.
 */
int getCDKFselectCurrentItem (CDKFSELECT *widget)
{
   return getCDKScrollCurrent (widget->scrollField);
}

void setCDKFselectCurrentItem (CDKFSELECT *widget,
			       int item)
{
   if (widget->fileCounter != 0)
   {
      char *data;

      setCDKScrollCurrent (widget->scrollField, item);

      data = contentToPath (widget,
			    widget->dirContents[getCDKScrollCurrentItem (widget->scrollField)]);
      setCDKEntryValue (widget->entryField, data);

      free (data);
   }
}

/*
 * These functions set the drawing characters of the widget.
 */
static void _setMyULchar (CDKOBJS *object, chtype character)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   setCDKEntryULChar (fselect->entryField, character);
}
static void _setMyURchar (CDKOBJS *object, chtype character)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   setCDKEntryURChar (fselect->entryField, character);
}
static void _setMyLLchar (CDKOBJS *object, chtype character)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   setCDKScrollLLChar (fselect->scrollField, character);
}
static void _setMyLRchar (CDKOBJS *object, chtype character)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   setCDKScrollLRChar (fselect->scrollField, character);
}
static void _setMyVTchar (CDKOBJS *object, chtype character)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   setCDKEntryVerticalChar (fselect->entryField, character);
   setCDKScrollVerticalChar (fselect->scrollField, character);
}
static void _setMyHZchar (CDKOBJS *object, chtype character)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   setCDKEntryHorizontalChar (fselect->entryField, character);
   setCDKScrollHorizontalChar (fselect->scrollField, character);
}
static void _setMyBXattr (CDKOBJS *object, chtype character)
{
   CDKFSELECT *fselect = (CDKFSELECT *)object;

   setCDKEntryBoxAttribute (fselect->entryField, character);
   setCDKScrollBoxAttribute (fselect->scrollField, character);
}

/*
 * This sets the background attribute of the widget.
 */
static void _setBKattrFselect (CDKOBJS *object, chtype attrib)
{
   if (object != 0)
   {
      CDKFSELECT *widget = (CDKFSELECT *)object;

      setCDKEntryBackgroundAttrib (widget->entryField, attrib);
      setCDKScrollBackgroundAttrib (widget->scrollField, attrib);
   }
}

/*
 * This destroys the file selector.
 */
static void _destroyCDKFselect (CDKOBJS *object)
{
   if (object != 0)
   {
      CDKFSELECT *fselect = (CDKFSELECT *)object;

      cleanCDKObjectBindings (vFSELECT, fselect);

      /* Free up the character pointers. */
      freeChar (fselect->pwd);
      freeChar (fselect->pathname);
      freeChar (fselect->dirAttribute);
      freeChar (fselect->fileAttribute);
      freeChar (fselect->linkAttribute);
      freeChar (fselect->sockAttribute);
      CDKfreeStrings (fselect->dirContents);

      /* Destroy the other Cdk objects. */
      destroyCDKScroll (fselect->scrollField);
      destroyCDKEntry (fselect->entryField);

      /* Free up the window pointers. */
      deleteCursesWindow (fselect->shadowWin);
      deleteCursesWindow (fselect->win);

      /* Clean the key bindings. */
      /* Unregister the object. */
      unregisterCDKObject (vFSELECT, fselect);
   }
}

/*
 ********************************
 * Callback functions.
 ********************************
 */

/*
 * This is a callback to the scrolling list which displays information
 * about the current file. (and the whole directory as well)
 */
static int displayFileInfoCB (EObjectType objectType GCC_UNUSED,
			      void *object,
			      void *clientData,
			      chtype key GCC_UNUSED)
{
   CDKENTRY *entry = (CDKENTRY *)object;
   CDKFSELECT *fselect = (CDKFSELECT *)clientData;
   CDKLABEL *infoLabel;
   struct stat fileStat;
#ifdef HAVE_PWD_H
   struct passwd *pwEnt;
   struct group *grEnt;
#endif
   char *filename;
   const char *filetype;
   char *mesg[10];
   char stringMode[15];
   int intMode;
   boolean functionKey;

   filename = fselect->entryField->info;

   if (lstat (filename, &fileStat) == 0)
   {
      switch (mode2Filetype (fileStat.st_mode))
      {
      case 'l':
	 filetype = "Symbolic Link";
	 break;
      case '@':
	 filetype = "Socket";
	 break;
      case '-':
	 filetype = "Regular File";
	 break;
      case 'd':
	 filetype = "Directory";
	 break;
      case 'c':
	 filetype = "Character Device";
	 break;
      case 'b':
	 filetype = "Block Device";
	 break;
      case '&':
	 filetype = "FIFO Device";
	 break;
      default:
	 filetype = "Unknown";
	 break;
      }
   }
   else
   {
      filetype = "Unknown";
   }

   /* Get the user name and group name. */
#ifdef HAVE_PWD_H
   pwEnt = getpwuid (fileStat.st_uid);
   grEnt = getgrgid (fileStat.st_gid);
#endif

   /* Convert the mode_t type to both string and int. */
   intMode = mode2Char (stringMode, fileStat.st_mode);

   /* Create the message. */
   mesg[0] = format1String ("Directory  : </U>%s", fselect->pwd);
   mesg[1] = format1String ("Filename   : </U>%s", filename);
#ifdef HAVE_PWD_H
   mesg[2] = format1StrVal ("Owner      : </U>%s<!U> (%d)",
			    pwEnt->pw_name,
			    (int)fileStat.st_uid);
   mesg[3] = format1StrVal ("Group      : </U>%s<!U> (%d)",
			    grEnt->gr_name,
			    (int)fileStat.st_gid);
#else
   mesg[2] = format1Number ("Owner      : (%ld)", (long)fileStat.st_uid);
   mesg[3] = format1Number ("Group      : (%ld)", (long)fileStat.st_gid);
#endif
   mesg[4] = format1StrVal ("Permissions: </U>%s<!U> (%o)", stringMode, intMode);
   mesg[5] = format1Number ("Size       : </U>%ld<!U> bytes", (long)fileStat.st_size);
   mesg[6] = format1Date ("Last Access: </U>%s", fileStat.st_atime);
   mesg[7] = format1Date ("Last Change: </U>%s", fileStat.st_ctime);
   mesg[8] = format1String ("File Type  : </U>%s", filetype);

   /* Create the pop up label. */
   infoLabel = newCDKLabel (entry->obj.screen,
			    CENTER, CENTER,
			    (CDK_CSTRING2)mesg, 9,
			    TRUE, FALSE);
   drawCDKLabel (infoLabel, TRUE);
   getchCDKObject (ObjOf (infoLabel), &functionKey);

   /* Clean up some memory. */
   destroyCDKLabel (infoLabel);
   freeCharList (mesg, 9);

   /* Redraw the file selector. */
   drawCDKFselect (fselect, ObjOf (fselect)->box);
   return (TRUE);
}

static char *make_pathname (const char *directory, const char *filename)
{
   size_t need = strlen (filename) + 2;
   bool root = (strcmp (directory, "/") == 0);
   char *result;

   if (!root)
      need += strlen (directory);
   if ((result = (char *)malloc (need)) != 0)
   {
      if (root)
	 sprintf (result, "/%s", filename);
      else
	 sprintf (result, "%s/%s", directory, filename);
   }
   return result;
}

/*
 * Return the plain string that corresponds to an item in dirContents[].
 */
static char *contentToPath (CDKFSELECT *fselect, char *content)
{
   chtype *tempChtype;
   char *tempChar;
   char *result;
   int j, j2;

   tempChtype = char2Chtype (content, &j, &j2);
   tempChar = chtype2Char (tempChtype);
   trim1Char (tempChar);	/* trim the 'mode' stored on the end */

   /* Create the pathname. */
   result = make_pathname (fselect->pwd, tempChar);

   /* Clean up. */
   freeChtype (tempChtype);
   freeChar (tempChar);
   return result;
}

/*
 * This tries to complete the filename.
 */
static int completeFilenameCB (EObjectType objectType GCC_UNUSED,
			       void *object GCC_UNUSED,
			       void *clientData,
			       chtype key GCC_UNUSED)
{
   /* *INDENT-EQLS* */
   CDKFSELECT *fselect  = (CDKFSELECT *)clientData;
   CDKSCROLL *scrollp   = fselect->scrollField;
   CDKENTRY *entry      = fselect->entryField;
   char *filename       = copyChar (entry->info);
   char *mydirname      = dirName (filename);
   char *newFilename    = 0;
   size_t filenameLen   = 0;
   int isDirectory;
   char **list;

   /* Make sure the filename is not null/empty. */
   if (filename == 0 ||
       (filenameLen = strlen (filename)) == 0)
   {
      Beep ();
      freeChar (filename);
      freeChar (mydirname);
      return (TRUE);
   }

   /* Try to expand the filename if it starts with a ~ */
   if ((newFilename = expandTilde (filename)) != 0)
   {
      freeChar (filename);
      filename = newFilename;
      setCDKEntryValue (entry, filename);
      drawCDKEntry (entry, ObjOf (entry)->box);
   }

   /* Make sure we can change into the directory. */
   isDirectory = chdir (filename);
   if (chdir (fselect->pwd) != 0)
   {
      freeChar (filename);
      freeChar (mydirname);
      return FALSE;
   }

   setCDKFselect (fselect,
		  isDirectory ? mydirname : filename,
		  fselect->fieldAttribute,
		  fselect->fillerCharacter,
		  fselect->highlight,
		  fselect->dirAttribute,
		  fselect->fileAttribute,
		  fselect->linkAttribute,
		  fselect->sockAttribute,
		  ObjOf (fselect)->box);
   freeChar (mydirname);

   /* If we can, change into the directory. */
   if (isDirectory)
   {
      /*
       * Set the entry field with the filename so the current
       * filename selection shows up.
       */
      setCDKEntryValue (entry, filename);
      drawCDKEntry (entry, ObjOf (entry)->box);
   }

   /* Create the file list. */
   if ((list = typeMallocN (char *, fselect->fileCounter)) != 0)
   {
      int Index, x;

      for (x = 0; x < fselect->fileCounter; x++)
      {
	 list[x] = contentToPath (fselect, fselect->dirContents[x]);
      }

      /* Look for a unique filename match. */
      Index = searchList ((CDK_CSTRING2)list, fselect->fileCounter, filename);

      /* If the index is less than zero, return we didn't find a match. */
      if (Index < 0)
      {
	 Beep ();
      }
      else
      {
	 /* Move to the current item in the scrolling list. */
	 int difference = Index - scrollp->currentItem;
	 int absoluteDifference = abs (difference);
	 if (difference < 0)
	 {
	    for (x = 0; x < absoluteDifference; x++)
	    {
	       injectMyScroller (fselect, KEY_UP);
	    }
	 }
	 else if (difference > 0)
	 {
	    for (x = 0; x < absoluteDifference; x++)
	    {
	       injectMyScroller (fselect, KEY_DOWN);
	    }
	 }
	 drawMyScroller (fselect);

	 /* Ok, we found a match, is the next item similar? */
	 if (Index + 1 < fselect->fileCounter &&
	     0 != list[Index + 1] &&
	     0 == strncmp (list[Index + 1], filename, filenameLen))
	 {
	    int currentIndex = Index;
	    int baseChars = (int)filenameLen;
	    int matches = 0;

	    /* Determine the number of files which match. */
	    while (currentIndex < fselect->fileCounter)
	    {
	       if (list[currentIndex] != 0)
	       {
		  if (strncmp (list[currentIndex], filename, filenameLen) == 0)
		  {
		     matches++;
		  }
	       }
	       currentIndex++;
	    }

	    /* Start looking for the common base characters. */
	    for (;;)
	    {
	       int secondaryMatches = 0;
	       for (x = Index; x < Index + matches; x++)
	       {
		  if (list[Index][baseChars] == list[x][baseChars])
		  {
		     secondaryMatches++;
		  }
	       }

	       if (secondaryMatches != matches)
	       {
		  Beep ();
		  break;
	       }

	       /* Inject the character into the entry field. */
	       (void)injectCDKEntry (fselect->entryField,
				     (chtype)list[Index][baseChars]);
	       baseChars++;
	    }
	 }
	 else
	 {
	    /* Set the entry field with the found item. */
	    setCDKEntryValue (entry, list[Index]);
	    drawCDKEntry (entry, ObjOf (entry)->box);
	 }
      }
      freeCharList (list, (unsigned)fselect->fileCounter);
      free (list);
   }
   freeChar (filename);
   return (TRUE);
}

/*
 * This allows the user to delete a file.
 */
void deleteFileCB (EObjectType objectType GCC_UNUSED, void *object, void *clientData)
{
   CDKSCROLL *fscroll = (CDKSCROLL *)object;
   CDKFSELECT *fselect = (CDKFSELECT *)clientData;
   const char *buttons[] =
   {
      "No",
      "Yes"
   };
   CDKDIALOG *question;
   char *mesg[10], *filename;

   /* Get the filename which is to be deleted. */
   filename = chtype2Char (fscroll->item[fscroll->currentItem]);
   trim1Char (filename);

   /* Create the dialog message. */
   mesg[0] = copyChar ("<C>Are you sure you want to delete the file:");
   mesg[1] = format1String ("<C></U>\"%s\"?", filename);

   /* Create the dialog box. */
   question = newCDKDialog (ScreenOf (fselect), CENTER, CENTER,
			    (CDK_CSTRING2)mesg, 2,
			    (CDK_CSTRING2)buttons, 2,
			    A_REVERSE, TRUE, TRUE, FALSE);
   freeCharList (mesg, 2);

   /* If the said yes then try to nuke it. */
   if (activateCDKDialog (question, 0) == 1)
   {
      /* If we were successful, reload the scrolling list. */
      if (unlink (filename) == 0)
      {
	 /* Set the file selector information. */
	 setCDKFselect (fselect, fselect->pwd,
			fselect->fieldAttribute,
			fselect->fillerCharacter,
			fselect->highlight,
			fselect->dirAttribute,
			fselect->fileAttribute,
			fselect->linkAttribute,
			fselect->sockAttribute,
			ObjOf (fselect)->box);
      }
      else
      {
	 /* Pop up a message. */
	 mesg[0] = copyChar (errorMessage ("<C>Cannot delete file: %s"));
	 mesg[1] = copyChar (" ");
	 mesg[2] = copyChar ("<C>Press any key to continue.");
	 popupLabel (ScreenOf (fselect), (CDK_CSTRING2)mesg, 3);
	 freeCharList (mesg, 3);
      }
   }

   /* Clean up. */
   destroyCDKDialog (question);

   /* Redraw the file selector. */
   drawCDKFselect (fselect, ObjOf (fselect)->box);
}

/*
 * This function sets the pre-process function.
 */
void setCDKFselectPreProcess (CDKFSELECT *fselect, PROCESSFN callback, void *data)
{
   setCDKEntryPreProcess (fselect->entryField, callback, data);
   setCDKScrollPreProcess (fselect->scrollField, callback, data);
}

/*
 * This function sets the post-process function.
 */
void setCDKFselectPostProcess (CDKFSELECT *fselect, PROCESSFN callback, void *data)
{
   setCDKEntryPostProcess (fselect->entryField, callback, data);
   setCDKScrollPostProcess (fselect->scrollField, callback, data);
}

/*
 * Start of callback functions.
 */
static int fselectAdjustScrollCB (EObjectType objectType GCC_UNUSED,
				  void *object GCC_UNUSED,
				  void *clientData,
				  chtype key)
{
   /* *INDENT-EQLS* */
   CDKFSELECT *fselect  = (CDKFSELECT *)clientData;
   CDKSCROLL *scrollp   = (CDKSCROLL *)fselect->scrollField;
   CDKENTRY *entry      = (CDKENTRY *)fselect->entryField;

   if (scrollp->listSize > 0)
   {
      char *current;
      char *temp;

      /* Move the scrolling list. */
      injectMyScroller (fselect, key);

      /* Get the currently highlighted filename. */
      current = chtype2Char (scrollp->item[scrollp->currentItem]);
      trim1Char (current);

      temp = make_pathname (fselect->pwd, current);

      /* Set the value in the entry field. */
      setCDKEntryValue (entry, temp);
      drawCDKEntry (entry, ObjOf (entry)->box);

      freeChar (current);
      freeChar (temp);
      return (TRUE);
   }
   Beep ();
   return (FALSE);
}

/*
 * trim the 'mode' from a copy of a dirContents[] entry.
 */
static char *trim1Char (char *source)
{
   size_t len;

   if ((len = strlen (source)) != 0)
      source[--len] = '\0';
   return source;
}

static char *format1Date (const char *format, time_t value)
{
   char *result;
   char *temp = ctime (&value);

   if ((result = (char *)malloc (strlen (format) + strlen (temp) + 1)) != 0)
   {
      sprintf (result, format, trim1Char (temp));
   }
   return result;
}

static char *format1Number (const char *format, long value)
{
   char *result;

   if ((result = (char *)malloc (strlen (format) + 20)) != 0)
      sprintf (result, format, value);
   return result;
}

static char *format3String (const char *format,
			    const char *s1,
			    const char *s2,
			    const char *s3)
{
   char *result;

   if ((result = (char *)malloc (strlen (format) +
				 strlen (s1) +
				 strlen (s2) +
				 strlen (s3))) != 0)
      sprintf (result, format, s1, s2, s3);
   return result;
}

static char *format1String (const char *format, const char *string)
{
   char *result;

   if ((result = (char *)malloc (strlen (format) + strlen (string))) != 0)
      sprintf (result, format, string);
   return result;
}

static char *format1StrVal (const char *format, const char *string, int value)
{
   char *result;

   if ((result = (char *)malloc (strlen (format) + strlen (string) + 20)) != 0)
      sprintf (result, format, string, value);
   return result;
}

static char *errorMessage (const char *format)
{
   char *message;
#ifdef HAVE_STRERROR
   message = strerror (errno);
#else
   message = "Unknown reason.";
#endif
   return format1String (format, message);
}

/*
 * This takes a ~ type account name and returns the full pathname.
 */
static char *expandTilde (const char *filename)
{
   char *result = 0;
   char *account;
   char *pathname;
   int len;

   /* Make sure the filename is not null/empty, and begins with a tilde */
   if ((filename != 0) &&
       (len = (int)strlen (filename)) != 0 &&
       filename[0] == '~' &&
       (account = copyChar (filename)) != 0 &&
       (pathname = copyChar (filename)) != 0)
   {
      bool slash = FALSE;
      const char *home;
      int x;
      int len_a = 0;
      int len_p = 0;
      struct passwd *accountInfo;

      /* Find the account name in the filename. */
      for (x = 1; x < len; x++)
      {
	 if (filename[x] == '/' && !slash)
	 {
	    slash = TRUE;
	 }
	 else if (slash)
	 {
	    pathname[len_p++] = filename[x];
	 }
	 else
	 {
	    account[len_a++] = filename[x];
	 }
      }
      account[len_a] = '\0';
      pathname[len_p] = '\0';

      home = 0;
#ifdef HAVE_PWD_H
      if (strlen (account) != 0 &&
	  (accountInfo = getpwnam (account)) != 0)
      {
	 home = accountInfo->pw_dir;
      }
#endif
      if (home == 0 || *home == '\0')
	 home = getenv ("HOME");
      if (home == 0 || *home == '\0')
	 home = "/";

      /*
       * Construct the full pathname. We do this because someone
       * may have a pathname at the end of the account name
       * and we want to keep it.
       */
      result = make_pathname (home, pathname);

      freeChar (account);
      freeChar (pathname);
   }
   return result;
}

/*
 * Store the name of the current working directory.
 */
static void setPWD (CDKFSELECT *fselect)
{
   char buffer[512];
   freeChar (fselect->pwd);
   if (getcwd (buffer, sizeof (buffer)) == 0)
      strcpy (buffer, ".");
   fselect->pwd = copyChar (buffer);
}

static void destroyInfo (CDKFSELECT *widget)
{
   CDKfreeStrings (widget->dirContents);
   widget->dirContents = 0;

   widget->fileCounter = 0;
}

static int createList (CDKFSELECT *widget, CDK_CSTRING2 list, int listSize)
{
   int status = 0;

   if (listSize >= 0)
   {
      char **newlist = typeCallocN (char *, listSize + 1);

      if (newlist != 0)
      {
	 int x;

	 /* Copy in the new information. */
	 status = 1;
	 for (x = 0; x < listSize; x++)
	 {
	    if ((newlist[x] = copyChar (list[x])) == 0)
	    {
	       status = 0;
	       break;
	    }
	 }
	 if (status)
	 {
	    destroyInfo (widget);
	    widget->fileCounter = listSize;
	    widget->dirContents = newlist;
	 }
	 else
	 {
	    CDKfreeStrings (newlist);
	 }
      }
   }
   else
   {
      destroyInfo (widget);
      status = TRUE;
   }
   return status;
}

static void _focusCDKFselect (CDKOBJS *object)
{
   CDKFSELECT *widget = (CDKFSELECT *)object;

   FocusObj (ObjOf (widget->entryField));
}

static void _unfocusCDKFselect (CDKOBJS *object)
{
   CDKFSELECT *widget = (CDKFSELECT *)object;

   UnfocusObj (ObjOf (widget->entryField));
}

dummyRefreshData (Fselect)

dummySaveData (Fselect)
