/*********************************************************************
 *
 *            EZFM---EZWGL based file manager for X11
 *
 *             Copyright (C) 1998  Maorong Zou
 *
 *
 *  This file is part of EZFM.
 *
 *  EZFM is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * EZFM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EZFM; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 *************************************************************************/



/***************************************************************
 *
 *  Double clicks action on items/icons
 *
 * There are two types of actions: builtin actions
 * like open a folder and actions rely on external
 * programs like execute a shell script.
 *
 */
#define _ACTION_C_

#include "ezfm.h"

/***************************************************************/
void ezfmDoSomething   MY_ANSIARGS((FileInfo *finfo));
void ezfmItemOpenDir   MY_ANSIARGS((void *obj, void *data));
void ezfmItemOpenDirN  MY_ANSIARGS((void *obj, void *data));
void ezfmSend2Netscape MY_ANSIARGS((void *obj, void *data));
void ezfmFollowLink    MY_ANSIARGS((void *obj, void *data));
void ezfmProcessCmdline MY_ANSIARGS((char *cmd, char *path, char *cmd_ret));
char **ezfmExtractDoubleClickCommands MY_ANSIARGS ((char *spec, int *cnt));

void ezfmExecutionErrorI MY_ANSIARGS ((char *l1, char *l2, char *l3, char *l4));
void ezfmExecutionError MY_ANSIARGS ((char *cmd, char *err));
void ezfmWarning        MY_ANSIARGS ((char *what, char *msg));

void executeShCmd        MY_ANSIARGS ((char *cmd, void (*f)(), int nargs, void **args));
void oneCommandDialogue MY_ANSIARGS((char *t, char *msg, char *cmd, int fcs, 
				     void (*f)(), int nargs, void **args));
void oneCommandDialogueNCF MY_ANSIARGS((char *t, char *msg, char *cmd, int fcs, 
                                        void (*f)(), int nargs, void **args, int ncf));

void ezfmQuotePathname   MY_ANSIARGS ((char *pin, char *pout));
/***************************************************************/
static void entryDestroyCallback MY_ANSIARGS((EZ_Widget *w, void *d));
static void executeCmd  MY_ANSIARGS((EZ_Widget *w, void *d));
static void executeICmd  MY_ANSIARGS((EZ_Widget *w, void *d));
static void cancelExecution MY_ANSIARGS((EZ_Widget *w, void *d));
static void watchChildProcess  MY_ANSIARGS((EZ_Timer *timer, void *data));
/***************************************************************
 *
 * There are mutiple double click actions associated to a file.
 * When double click happens, a record of the following type
 * is created for each action. They are deleted when the dialogue
 * box is removed.
 */
typedef struct {
  EZ_Widget    *toplevel;
  void         (*function)();
  FileInfo     *finfo;
} ActionRecord;
/***************************************************************
 *
 * For each external command in progress, there is a record of
 * the following type associated to the external process. It
 * is used for book keeping and for watching STDOUT/ERR file
 * descriptors of the child process.
 */
typedef struct {
  /* If there are any output from
   * the child process, the output from STDOUT is displayed
   * in txtOut and the output from STDERR is displayed in
   * txtErr.
   */
  EZ_Widget *frame, *txtErr, *txtOut, *btn;  
  pid_t     pid;              /* pid of the child process, it is /bin/sh !!!     */
  EZ_Input  *ierr, *iout;     /* two fileEvent handle, watch the output of child */
  int       fderr, fdout;     /* the STDOUT/STDERR file descriptor               */
  int       removed;          /* a flag, when the dialogue is removed, ezfm will */
                              /* stop watching the corresponding child process   */
  char      *cmdline;         /* the actuall commandline send to /bin/sh         */

  /* the next three field is a hack. It specifies a function to call when the
   * child process exits. Function is invoked as (function)(nargs, args)
   */
  void      (*function)();    
  int       nargs;            
  void      *args[10];
} execShCmdRecord;

/**************************************************************
 *
 *  Return the internal function represented by name.
 *  We have only a half dozen or so of such animals.
 */
#include "commandsBuiltIn.h"

void (*ezfmGetBuiltInCommand(name))()
     char *name;
{
  if(name)
    {
      BuiltInCommand *list = builtInCommands;
      while(list && list->name != NULL)
	{
	  if(!strcmp(name,list->name)) return(list->function);
	  list++;
	}
    }
  return(NULL);
}

/************************************************************
 *
 * Extract double click command from specification. Actions
 * are separated by ,
 */
char **ezfmExtractDoubleClickCommands(spec, cnt_ret)
     char *spec;         /* specification              */
     int  *cnt_ret;      /* return # of commands found */
{
  char **cmds = NULL, buf[1024];
  int cnt = 0;
  
  getSpecField(spec, SPEC_DCLICK_ACTION, buf);
  if(buf[0] != 0)
    {
      char *ptr, *t, *s, *start = buf;
      int i, len, ncnt = 0;
  
      /* skip white spaces */
      while(*start && ( *start == ' ' || *start == '\t')) start++;
      
      cnt = 1;
      ptr = start;
      while(*ptr) { if(*ptr == ',') cnt++; ptr++; }
      cmds = (char **)EZ_Malloc((cnt+1)* sizeof(char *));
      
      ptr = start;
      for(i = 0; i < cnt; i++)
	{
	  s = ptr;
	  len = 0;
	  while(*ptr && (*ptr == ' ' || *ptr == '\t')) ptr++;
	  s = ptr;
	  while(*ptr && *ptr != ',') { ptr++; len++;}
	  if(len > 0)
	    {
	      t = cmds[ncnt++] = (char *)EZ_Malloc((len+1) * sizeof(char));
	      while(s != ptr) *t++ = *s++; *t = '\0';
	    }
	  if(*ptr) ptr++;
	}
      cmds[ncnt] = NULL;
      cnt = ncnt;
    }
  if(cnt_ret) *cnt_ret = cnt;
  return(cmds);
}

/*******************************************************************
 *
 *  Execute a shell command.
 *
 *  When the close button is pressed on the execution dialogue,
 *  remove the fileEvent handle for the corresponding child
 *  process.
 */
static void execClseBtnCb(widget, data)
     EZ_Widget *widget; void *data;
{
  EZ_Widget *toplevel = (EZ_Widget *)data;
  execShCmdRecord *rc = (execShCmdRecord *)EZ_GetWidgetPtrData(toplevel);
  if(rc)
    {
      rc->removed = 1;
      close(rc->fderr);    close(rc->fdout);
      EZ_RemoveInput(rc->ierr);  EZ_RemoveInput(rc->iout);  
      rc->frame = NULL;
    }
  EZ_DestroyWidget(toplevel);
}

/****************************************************************
 *
 * This is the fileEvent callback. If child process output
 * anything, display the output.
 */
static void execShCmdCb(id, data, fd, mask)
     EZ_Input *id; void *data; int fd; int mask;
{
  char     buf[8196];
  int      n, first=0;
  char     *cmd = (char *)data;
  execShCmdRecord *rc = (execShCmdRecord *)data;
  EZ_Widget *txtOut, *txtErr = NULL, *tmp, *toplevel, *nwlab;

  if(rc->removed) return;

  if(mask & EZ_READABLE_MASK)
    {
      n = read(fd, buf, 8192);
      if(n== 0) return;
      buf[n]= 0;
    }

  toplevel = rc->frame;
  if(!EZ_WidgetExist(toplevel))
    {
      first = 1;
      toplevel = EZ_CreateWidgetXrm(EZ_WIDGET_FRAME,   NULL,
				    "ExecutionLog",    "executionLog",
				    EZ_TRANSIENT,      True,
				    EZ_ORIENTATION,    EZ_VERTICAL,
				    EZ_WIDTH_HINT,     640,
				    EZ_HEIGHT_HINT,    480,
				    EZ_FILL_MODE,      EZ_FILL_BOTH,
				    EZ_BG_PIXMAP,          bigPixmap, 
				    EZ_EVENT_HANDLER,      ConfigureNotifyHandler, NULL,
				    NULL);
      rc->frame = toplevel;
      tmp =EZ_CreateWidget(EZ_WIDGET_LABEL,      toplevel,
			   EZ_ORIENTATION,       EZ_VERTICAL,
			   EZ_IPADY, 10, EZ_PADY,  10,
			   EZ_SIZE, 0, 0,
			   NULL);
      nwlab=EZ_CreateWidget(EZ_WIDGET_NW_LABEL,  tmp,
			    EZ_LABEL_STRING,     "Execution Log for Command",
			    EZ_FONT_ID,          ezfmFontId[LABEL_LARGE_FONT],
			    EZ_TEXT_LINE_LENGTH,  80,
			    EZ_FOREGROUND,        "red",
			    NULL);
      nwlab=EZ_CreateWidget(EZ_WIDGET_NW_LABEL,  tmp,
			    EZ_FONT_ID,          ezfmFontId[TEXT_BOLD_FONT],
			    EZ_FOREGROUND,        "blue",
			    EZ_TEXT_LINE_LENGTH,  80,
			    EZ_LABEL_STRING,     rc->cmdline,
			    EZ_SIZE, 0, 0,
			    NULL);
      
      tmp = EZ_CreateWidget(EZ_WIDGET_FRAME, toplevel,
			    EZ_FILL_MODE,    EZ_FILL_BOTH,
			    EZ_LABEL_STRING, "Output",
			    NULL);
      EZ_DeActivateWidget(tmp);
      txtOut = EZ_CreateWidgetXrm(EZ_WIDGET_TEXT, tmp,
				  "ExecutionOutput", "executionOutput",
				  EZ_TEXT_WIDGET_EDITABLE, False,
				  EZ_OPTIONAL_HSCROLLBAR,  False,
				  EZ_BORDER_WIDTH,         1,
				  EZ_CLIENT_PTR_DATA,      tmp,
				  NULL);
      tmp = EZ_CreateWidget(EZ_WIDGET_FRAME, toplevel,
                            EZ_FILL_MODE,    EZ_FILL_BOTH,
                            EZ_LABEL_STRING, "Error Message",
                            NULL);
      EZ_DeActivateWidget(tmp);
      txtErr = EZ_CreateWidgetXrm(EZ_WIDGET_TEXT, tmp,
                                  "ExecutionError", "executionError",
                                  EZ_TEXT_WIDGET_EDITABLE, False,
                                  EZ_OPTIONAL_HSCROLLBAR,  False,
                                  EZ_BORDER_WIDTH,         1,
                                  EZ_CLIENT_PTR_DATA,      tmp,
                                  NULL); 
      
      tmp = EZ_CreateWidget(EZ_WIDGET_NORMAL_BUTTON, toplevel,
			    EZ_LABEL_STRING,         "Close",
			    EZ_UNDERLINE,             0,
			    EZ_CALLBACK,              execClseBtnCb, toplevel,
			    EZ_SIZE, 0, 0,
			    NULL);
      rc->btn = tmp;
      rc->txtErr = txtErr;
      rc->txtOut = txtOut;
      EZ_SetWidgetPtrData(toplevel, data);      
    }
  if(id == rc->ierr) tmp = rc->txtErr;
  else tmp = rc->txtOut;

  EZ_ActivateWidget(EZ_GetWidgetPtrData(tmp));
  EZ_FreezeWidget(tmp);  
  EZ_TextEndOfBuffer(tmp);
  EZ_TextInsertString(tmp, buf);
  EZ_UnFreezeWidget(tmp);
  if(!EZ_WidgetIsViewable(rc->btn)) 
    EZ_DisplayWidgetUnderPointer(rc->btn, -10, -10);
  EZ_SetFocusTo(rc->btn);
}
/*************************************************************************************
 *
 * When a child process is forked, a timer is created to watch the status
 * of the child process, this is the timer callback. It just clean up
 * the data structure allocated for the child process when child exits.
 */
static void watchChildProcess(timer, data)
     EZ_Timer *timer; void *data;
{
  execShCmdRecord *rc = (execShCmdRecord *)data;
  pid_t pid = rc->pid;
  if(kill(pid, 0) == 0) return;
  else
    {
      if(rc->removed == 0) /* command is completed */
	{
	  EZ_Widget *frame = rc->frame;
	  if(EZ_WidgetExist(frame)) EZ_SetWidgetPtrData(frame, NULL);
          close(rc->fdout); EZ_RemoveInput(rc->iout);
	  close(rc->fderr); EZ_RemoveInput(rc->ierr);  
	}
      if(rc->function) (rc->function)(rc->nargs, rc->args);
      (void)EZ_Free((char *)rc->cmdline);
      (void)EZ_Free((char *)rc);
    }
  EZ_CancelTimer(timer);
}

/*********************************************************************
 *
 *  This is how a child process is spawned.
 *
 * execute sh cmd 'command', invoke func(nargs, vargs) when complete 
 * vargs is a duplicated copy of args. (length is at most 10)
 */
void executeShCmd(command, func, nargs, args)
     char *command;
     void (*func)();
     int  nargs;
     void *args[];
{
  int         status, n, fd[2], pfd[2], xfd;
  pid_t       pid;
  char        buf[512], cmdline[2048];
  Display     *dpy;
  
  if(command == NULL || *command == '\0')
    {
      ezfmExecutionError("(null)", "NULL command");
      return;
    }
  /* make a copy of command, in case command is freed or disapears ... */
  strcpy(cmdline, command); 


  if(pipe(fd) != 0 || pipe(pfd) != 0)   /* pipe failed, return */
    {
      ezfmExecutionError(cmdline, "pipe() failed");
      return;
    }
  dpy = EZ_GetDisplay();
  XSync(dpy, False); 
  xfd = ConnectionNumber(dpy);
  
  if((pid = fork()) < 0) /* fork failed */
    {
      ezfmExecutionError(cmdline, "fork() failed");
      return;
    }

  if(pid > 0) 
    {
      /* parent, continue execution, create fileHandles to watch the child process */
      int i;
      char *data = (char *)EZ_Malloc( (strlen(cmdline) + 1) * sizeof(char));
      execShCmdRecord *rc = (execShCmdRecord *)EZ_Malloc(sizeof(execShCmdRecord));
      close(fd[1]);  close(pfd[1]); 
      (void)strcpy(data, cmdline);
      rc->frame = NULL;
      rc->pid = pid;
      rc->fderr = fd[0];
      rc->fdout = pfd[0];
      rc->cmdline = data;
      rc->removed = 0;
      if((ezfmFlags & IGNORE_EXEC_ERROR_BIT) == 0)
        rc->ierr = EZ_AddInput(fd[0],  EZ_READABLE_MASK, execShCmdCb, rc);
      else rc->ierr = NULL;
      rc->iout = EZ_AddInput(pfd[0], EZ_READABLE_MASK, execShCmdCb, rc);
      rc->function =func;
      rc->nargs = nargs;
      for(i = 0; i < nargs && i < 10; i++) rc->args[i] = args[i];
      EZ_CreateTimer(1, 0, -1, watchChildProcess, rc, 0);
    }
  else if(pid == 0) /* child, fork a child to execute command !!! */
    {
      int     status;
      char    *err;
      /* if we leave the X connection open, we'll almost always
       * get protocol errors !
       */
      close(xfd);          /* close X server connection     */
      close(fd[0]);        /* close read end of pipe        */
      close(pfd[0]);       /* close read end of pipe        */


      /* now we close stdout/stderr and dup them.
       * so any outupt will be caught by parent
       */
      if(dup2(pfd[1], STDOUT_FILENO) == -1 || dup2(fd[1], STDERR_FILENO)== -1)
	{ write(fd[1], "dup2() failed", 14); exit(1);}

      execl("/bin/sh", "sh", "-c", cmdline, NULL);
      /* if we ever get to here, some thing went wrong */
      err= (errno >= 0 && errno <sys_nerr)? 
        sys_errlist[errno] : "Unknown error";
      write(STDERR_FILENO, err, strlen(err));
      _exit(127);
    }
}
/***********************************************************************************
 *
 *             Actual work is done here
 *
 *  Figure out actions associated to a file, build the action dialogue and ask user
 *  for confirmation.
 */

/* this little procedure is used in the "openWith ..." entry */
static void entryMotionCB(w, d)  EZ_Widget *w; void *d; { EZ_EnableWidget((EZ_Widget *)d);}
/* */

void ezfmDoSomething(finfo)
     FileInfo *finfo;
{
  if(finfo)
    {
      int  i, ncmds = 0, internalOnly;
      char *ptr, *path = finfo->pathname;
      char *spec = finfo->ezfmData;
      char **cmds = ezfmExtractDoubleClickCommands(spec, &ncmds);
      char tmpstr[64];
      
      internalOnly = 0;
      if(cmds != NULL) 
	{
	  /* openDir is a special action */
	  ptr = cmds[0];
	  if(ptr[0] == '@' && (ptr[1] == 'o' || ptr[1] == 'f')) internalOnly = 1;
	}
      else ncmds = 0;
      /* for internal commands, don't ask for confirmation */
      if(internalOnly)
	{
	  void (*function)();
	  char *d = tmpstr, *s = cmds[0] + 1;
	  while(*s && *s != ' ' && *s !='\t') *d++ = *s++;  *d = '\0';
	  function = ezfmGetBuiltInCommand(tmpstr);
	  if(function)  function(NULL,finfo);
	  return;
	}
      else
	{
	  EZ_Widget *toplevel, *gbox, *firstBtn = NULL, *label, *entry, *btn;
	  int       cnt;
	  toplevel = EZ_CreateWidgetXrm(EZ_WIDGET_FRAME,    NULL, 
					"ActionDialogue", "actionDialogue",
					EZ_FILL_MODE,      EZ_FILL_BOTH,
					EZ_TRANSIENT,      True,
					EZ_BG_PIXMAP,          bigPixmap, 
					EZ_EVENT_HANDLER,      ConfigureNotifyHandler, NULL,
					NULL);
	  
	  gbox  = EZ_CreateWidget(EZ_WIDGET_FRAME,    toplevel,
				  EZ_PADX,            6, 
				  EZ_PADY,            6,       /*idx   min weight pad */
				  EZ_GRID_CONSTRAINS, EZ_COLUMN,  0,   340, 30,   0,
				  EZ_GRID_CONSTRAINS, EZ_COLUMN,  1,   60,  0,    0,
				  EZ_GRID_CONSTRAINS, EZ_ROW,     0,   16,  0,    0,
				  NULL);
	  label = EZ_CreateWidget(EZ_WIDGET_LABEL,         gbox,
				  EZ_GRID_CELL_GEOMETRY,   0, 0, 2, 1,
				  EZ_LABEL_POSITION,       EZ_CENTER,
				  EZ_FOREGROUND,           "red",
				  EZ_LABEL_STRING,         "Please choose an action",
                                  EZ_FONT_ID,              ezfmFontId[LABEL_LARGE_FONT], 
                                  EZ_LABEL_SHADOW,         1,1,
				  NULL);
	  
	  for(cnt = 0, i = 0; i <= ncmds; i++)
	    {
	      int   internal = 0;
	      char  *cmd = NULL, *btnLabel = NULL;
	      char  *ptr, command[1024];
	      void  (*function)() = NULL;

	      if(i < ncmds)
		{
		  ptr = cmds[i];
		  if(ptr[0] == '$')
		    {
		      char *d = tmpstr, *s = ptr+1;
		      while(*s && *s != ' ' && *s !='\t') *d++ = *s++;  *d = '\0';
		      cmd = lookupNameFromHashTable(tmpstr);
		      if(cmd) btnLabel = tmpstr;
		    }
		  else if(ptr[0] == '@')
		    {
		      char *d = tmpstr, *s = ptr+1;
		      while(*s && *s != ' ' && *s !='\t') *d++ = *s++;  *d = '\0';
		      function = ezfmGetBuiltInCommand(tmpstr);
		      if(function)
			{
			  internal = 1;
			  btnLabel = tmpstr;
			  cmd = ptr+1;
			}
		    }
		  else
		    {
		      cmd = ptr;
		      btnLabel = ptr;
		    }
		}
	      else /* the last one */
		{
		  btnLabel = "OpenWith ...";
		  cmd = "ReplaceMe ";
		}
	      if(btnLabel != NULL)
		{
		  ActionRecord *aRec = (ActionRecord *)EZ_Malloc(sizeof(ActionRecord));
		  aRec->toplevel = toplevel;
		  aRec->function = function;
		  aRec->finfo    = (FileInfo *)EZ_Malloc(sizeof(FileInfo));
		  memcpy(aRec->finfo, finfo, sizeof(FileInfo));
		  aRec->finfo->pathname = (char *)EZ_Malloc(256*sizeof(char));
		  strcpy(aRec->finfo->pathname, finfo->pathname);
		  cnt++;
		  ezfmProcessCmdline(cmd, path, command);
		  entry = EZ_CreateWidget(EZ_WIDGET_ENTRY,         gbox,
					  EZ_SELECTION_BACKGROUND, SelectionBackground,
					  EZ_GRID_CELL_GEOMETRY,   0, cnt , 1, 1,
					  EZ_GRID_CELL_PLACEMENT,  EZ_FILL_HORIZONTALLY, EZ_CENTER,
					  EZ_BORDER_WIDTH,         1,
					  EZ_ENTRY_STRING,         command,
					  EZ_DESTROY_CALLBACK,     entryDestroyCallback, aRec,
					  EZ_CLIENT_PTR_DATA,      aRec,
					  NULL);
		  EZ_EntryEditBeginningOfInput(entry);
		  if(internal) EZ_DisableWidget(entry);
		  
		  btn   = EZ_CreateWidget(EZ_WIDGET_NORMAL_BUTTON, gbox,
					  EZ_GRID_CELL_GEOMETRY,   1, cnt, 1, 1,
					  EZ_BORDER_WIDTH,         1,
					  EZ_PADX,                 6,
					  EZ_PADY,                 4,
					  EZ_LABEL_STRING,         btnLabel,
					  EZ_GRID_CELL_PLACEMENT,  EZ_FILL_BOTH, EZ_CENTER,
					  EZ_LABEL_POSITION,       EZ_LEFT,
					  EZ_FOREGROUND,           (i == ncmds ? "red": NULL),
					  NULL);
		  if(internal)
		    {
		      EZ_SetWidgetCallBack(btn,   executeICmd, entry);
		      EZ_SetWidgetCallBack(entry, executeICmd, entry);
		    }
		  else
		    {
		      EZ_SetWidgetCallBack(entry, executeCmd, entry);
		      EZ_SetWidgetCallBack(btn,   executeCmd, entry);
		    }
		  if(firstBtn == NULL) firstBtn = btn;
		  if(i == ncmds)
		    {
		      EZ_AddWidgetCallBack(entry, EZ_MOTION_CALLBACK,entryMotionCB, btn, 0);
		      EZ_DisableWidget(btn);
		    }
		}
	    }
	  /* add a cancel button */
	  {
	    EZ_Widget *cancel  = EZ_CreateWidget(EZ_WIDGET_NORMAL_BUTTON, gbox,
						 EZ_GRID_CELL_GEOMETRY,   0, (++cnt), 2, 1,
						 EZ_BORDER_WIDTH,         1,
						 EZ_PADX,                 6,
						 EZ_PADY,                 4,
						 EZ_SIZE,                 0, 0,
						 EZ_LABEL_STRING,         "Cancel",
						 EZ_UNDERLINE,            0,
						 EZ_GRID_CELL_PLACEMENT,  EZ_FILL_NONE, EZ_CENTER,
						 EZ_CALLBACK,             cancelExecution, toplevel,
						 NULL);	    
	    
	  }
	  if(firstBtn != NULL)
	    {
	      /* if action dialogue is disabled, fire the first application */
	      if(ncmds > 0 && (ezfmFlags & DONT_CONFIRM_ACT_BIT))
		{
		  Folder *folder = (Folder *)finfo->host;
		  if(folder)
		    {
		      EZ_BusyCursor(folder->frame, EZ_GetCursor(EZ_C_WATCH), 2);
		      XSync(EZ_GetDisplay(), False);
		    }
		  EZ_CallWidgetCallbacks(firstBtn);
		}
	      else
		{
		  EZ_DisplayWidgetUnderPointer(firstBtn, -10, -10);
		  /*EZ_SetGrab(toplevel);*/
		  EZ_SetFocusTo(firstBtn);
		}
	    }
	}
      for(i = 0; i < ncmds; i++) EZ_Free(cmds[i]);
      EZ_Free((char *)cmds);
    }
}
/*************************************************************************************
 *
 *  when entry is destroyed, clean up.
 */
static void entryDestroyCallback(entry, data)
     EZ_Widget *entry; void *data;
{
  ActionRecord *rec = (ActionRecord *)data;
  if(rec)
    {
      if(rec->finfo)
	{
	  if(rec->finfo->pathname) EZ_Free(rec->finfo->pathname);
	  EZ_Free((char *)rec->finfo);
	}
      EZ_Free((char *)rec);
    }
}

/************************************************************************************
 *
 * Execute internal action. procedure is of type   f(void *, FileInfo *)
 * with the first argument ignored.
 */
static void executeICmd(widget, data)
     EZ_Widget *widget; void *data;
{
  EZ_Widget     *entry = (EZ_Widget *)data;
  ActionRecord  *aRec = (ActionRecord *)EZ_GetWidgetPtrData(entry);
  if(aRec)
    {
      EZ_HideWidget(aRec->toplevel);
      EZ_ReleaseGrab();
      if(aRec->function) (aRec->function)(entry, aRec->finfo);
      EZ_DestroyWidget(aRec->toplevel);
    }
}
/*************************************************************************************
 *
 * Execute an external command.
 */
static void executeCmd(widget, data)
     EZ_Widget *widget; void *data;
{
  EZ_Widget     *entry = (EZ_Widget *)data;
  ActionRecord  *aRec = (ActionRecord *)EZ_GetWidgetPtrData(entry);  
 
  if(aRec)
    {
      char  *command = EZ_GetEntryString(entry);
      EZ_ReleaseGrab();
      EZ_HideWidget(aRec->toplevel);
      if(command) executeShCmd(command, NULL, 0, NULL);
      EZ_DestroyWidget(aRec->toplevel);
    }
}

static void cancelExecution(widget, data)
     EZ_Widget *widget; void *data;
{
  EZ_Widget     *toplevel = (EZ_Widget *)data;
  EZ_ReleaseGrab();
  EZ_DestroyWidget(toplevel);
}

/*******************************************************************************
 *
 *     Builtin actions
 */
void ezfmItemOpenDir(obj, data)
     void *obj; void *data;
{
  FileInfo *finfo = (FileInfo *)data;
  if(finfo)
    {
      Folder *folder = NULL;
      char   *path = finfo->pathname;
      char   pathname[1024];
      
      strcpy(pathname, path);
      if(finfo->type == ITEM) folder= (Folder *)finfo->host;
      OpenFolder(pathname, folder, 1, 0);
    }  
}

void ezfmItemOpenDirN(obj, data)
     void *obj; void *data;
{
  FileInfo *finfo = (FileInfo *)data;
  if(finfo)
    {
      char   *path = finfo->pathname;
      char   pathname[1024];
      strcpy(pathname, path);
      OpenFolder(pathname, NULL, 1, 0);
    }  
}

void ezfmSend2Netscape(obj, data) void *obj; void *data;
{
  FileInfo *finfo = (FileInfo *)data;
  char *pathname = finfo->pathname;
  char command[1024];
  int  ok = netscapeIsRunning();
  if(ok)
    sprintf(command, "netscape -remote openURL\\(file:%s\\)", pathname);
  else 
    sprintf(command, "netscape  file:%s", pathname);
  executeShCmd(command, NULL, 0, NULL);
}

void ezfmFollowLink(obj, data)
     void *obj; void *data;
{
  FileInfo *finfo = (FileInfo *)data;
  if(finfo)
    {
      struct stat stat_buf;
      char   *pathname = finfo->pathname;
      char   *newspec = NULL;

      if(lstat(pathname, &stat_buf) >= 0 && S_ISLNK(stat_buf.st_mode))
	{
	  char tmpstr[BUFSIZ+4], save[BUFSIZ+4];
	  struct stat tst_buf;
	  int  i, cnt;
	  strcpy(save, pathname);
	  for(i = 0; i < 16; i++)
	    {
	      if( (cnt = readlink(save, tmpstr, BUFSIZ)) > 0)
		{
		  tmpstr[cnt] = 0;
		  if(tmpstr[0] != '/') /* relative link */
		    {
		      char *ptr = strrchr(save, '/');
		      if(ptr) { ptr[1] = 0; strcat(save, tmpstr); }
		    }
		  else  strcpy(save,tmpstr); /* absolute lnk */
		  if(lstat(save, &tst_buf) < 0) break;
		  if( ! (S_ISLNK(tst_buf.st_mode)))
		    {
		      newspec = GetFileTypeInfo(save);
		      break;
		    }
		}
	    }
	}
      if(newspec != NULL)
	{
	  FileInfo nfinfo;
	  memcpy(&nfinfo, finfo, sizeof(FileInfo));
	  nfinfo.ezfmData = newspec;
	  ezfmDoSomething(&nfinfo);
	}
    }
}

/*******************************************************************************
 *
 *           Display execution error
 */
static void acknowledgeCallback(widget, data)
     EZ_Widget *widget; void *data;
{
  EZ_Widget *toplevel = (EZ_Widget *)data;
  EZ_ReleaseGrab();
  EZ_HideWidget(toplevel);
}

void ezfmExecutionError(cmd, err)
     char *cmd; char *err;
{
  ezfmExecutionErrorI("Ezfm has received the following error", err, 
		      "while executing", cmd);
}

void ezfmWarning(what, msg)
     char *what, *msg;
{
  ezfmExecutionErrorI(NULL,what, msg, NULL);
}
/****************************************************************************
 *
 *  Display a warning message like
 *
 *        Lab1
 *        Lab2  (red)
 *        lab3
 *        lab4  (blue)
 */
void ezfmExecutionErrorI(lab1, lab2, lab3, lab4)
     char *lab1, *lab2, *lab3, *lab4;
{
  static EZ_Widget    *errToplevel;
  EZ_Widget           *frame, *tmp, *ack;
  EZ_UnknownDataType  ud;

  if(! EZ_WidgetExist(errToplevel))
    {
      frame = EZ_CreateWidgetXrm(EZ_WIDGET_FRAME,       NULL,
				 "Warning", "warning",
				 EZ_TRANSIENT,          True,
				 EZ_FONT_ID,            ezfmFontId[LABEL_LARGE_FONT],
				 EZ_ORIENTATION,        EZ_VERTICAL,
				 EZ_FILL_MODE,          EZ_FILL_BOTH,
				 EZ_PADX,               20,
				 EZ_PADY,               10,
				 EZ_IPADY,              10,
				 EZ_SIZE,               0, 0,
				 EZ_LABEL_STRING,       "Warning",
				 EZ_BG_PIXMAP,          bigPixmap, 
				 EZ_EVENT_HANDLER,      ConfigureNotifyHandler, NULL,
				 NULL);
      errToplevel = frame;
      {ud.p = NULL; EZ_SetWidgetUnknownData(frame, 4, ud);}
      
      tmp =   EZ_CreateWidget(EZ_WIDGET_LABEL,       frame,
			      EZ_FONT_ID,            ezfmFontId[LABEL_MEDIUM_FONT],
			      EZ_LABEL_STRING,       lab1,
			      EZ_TEXT_LINE_LENGTH,   80,
			      EZ_PADY,               0,
			      NULL);
      { ud.p  = tmp;   EZ_SetWidgetUnknownData(frame, 0, ud);}

      tmp =   EZ_CreateWidget(EZ_WIDGET_LABEL,       frame,
			      EZ_FONT_ID,            ezfmFontId[TEXT_BOLD_FONT],
			      EZ_FOREGROUND,         "red",
			      EZ_LABEL_STRING,       lab2,
			      EZ_PADY,               0,
			      EZ_TEXT_LINE_LENGTH,   80,
			      NULL);
      { ud.p  = tmp;   EZ_SetWidgetUnknownData(frame, 1, ud);}
      tmp =   EZ_CreateWidget(EZ_WIDGET_LABEL,       frame,
			      EZ_FONT_ID,            ezfmFontId[LABEL_MEDIUM_FONT],
			      EZ_LABEL_STRING,       lab3,
			      EZ_PADY,               0,
			      EZ_TEXT_LINE_LENGTH,   80,
			      NULL);
      { ud.p  = tmp;   EZ_SetWidgetUnknownData(frame, 2, ud);}
      tmp =   EZ_CreateWidget(EZ_WIDGET_LABEL,       frame,
			      EZ_FONT_ID,            ezfmFontId[TEXT_BOLD_FONT],
			      EZ_LABEL_STRING,       lab4,
			      EZ_FOREGROUND,         "blue",
			      EZ_PADY,               0,
			      EZ_TEXT_LINE_LENGTH,   80,
			      NULL);
      { ud.p  = tmp;   EZ_SetWidgetUnknownData(frame, 3, ud);}
      tmp =   EZ_CreateWidget(EZ_WIDGET_LABEL,       frame,
			      EZ_BORDER_WIDTH,       1,
			      EZ_BORDER_TYPE,        EZ_BORDER_SUNKEN,
			      EZ_EXPAND,             True,
			      EZ_HEIGHT,             2,
			      NULL);      

      ack =   EZ_CreateWidget(EZ_WIDGET_NORMAL_BUTTON, frame,
			      EZ_LABEL_STRING,         "Acknowledge",
			      EZ_WIDTH,                0,
			      EZ_HEIGHT,               0,
			      EZ_CALLBACK,             acknowledgeCallback, frame,
			      NULL);
      { ud.p  = ack;   EZ_SetWidgetUnknownData(frame, 4, ud);}
    }
  /* setup error string */
  {
    EZ_UnknownDataType *ptr = EZ_GetWidgetUnknownDataPtr(errToplevel);
    tmp = (EZ_Widget *)ptr[0].p;
    EZ_ConfigureWidget(tmp, EZ_LABEL_STRING, lab1, NULL);
    tmp = (EZ_Widget *)ptr[1].p;
    EZ_ConfigureWidget(tmp, EZ_LABEL_STRING, lab2, NULL);
    tmp = (EZ_Widget *)ptr[2].p;
    EZ_ConfigureWidget(tmp, EZ_LABEL_STRING, lab3, NULL);
    tmp = (EZ_Widget *)ptr[3].p;
    EZ_ConfigureWidget(tmp, EZ_LABEL_STRING, lab4, NULL);
    tmp = (EZ_Widget *)ptr[4].p;
    EZ_DisplayWidgetUnderPointer(tmp, -10, -10); 
    EZ_DisplayWidget(tmp);
    EZ_SetGrab(errToplevel);
    EZ_SetFocusTo(tmp);
  }
}
/**********************************************************************************
 *
 *  The following implements a reusable command execution dialogue.
 */

static void executeOneCmd(widget, data)
     EZ_Widget *widget; void *data;
{
  EZ_Widget     *frame = (EZ_Widget *)data;
  EZ_Widget     *entry = (EZ_Widget *)EZ_GetWidgetPtrData(frame);
  char          *cmd = EZ_GetEntryString(entry);

  EZ_ReleaseGrab();
  if(cmd && *cmd)
    {
      EZ_UnknownDataType ud;
      int   i, nargs = 0;
      void  *args[16];
      void  (*func)() = NULL;
      EZ_GetWidgetUnknownData(frame, 0, &ud);  
      nargs = ud.i;
      EZ_GetWidgetUnknownData(frame, 1, &ud);  
      func = ud.p;
      for(i = 0; i < nargs; i++)
	{
	  EZ_GetWidgetUnknownData(frame, 2+i, &ud);  
	  args[i] = ud.p;
	}
      executeShCmd(cmd, func, nargs, args);
    }
  EZ_DestroyWidget(frame);
}

static void cancelCallback(widget, data)
     EZ_Widget *widget; void *data;
{
  EZ_Widget *frame = (EZ_Widget *)data;
  EZ_ReleaseGrab();
  EZ_DestroyWidget(frame);
}

void oneCommandDialogue(title, instr, command, kfocus, func, nargs, args)
     char *title, *instr, *command; int kfocus; void (*func)(); int nargs; void *args[]; 
{ oneCommandDialogueNCF(title, instr, command, kfocus, func, nargs, args, 0); }

void oneCommandDialogueNCF(title, instr, command, kfocus, func, nargs, args, ncfm)
     char *title, *instr, *command; int kfocus; void (*func)(); int nargs; void *args[]; int ncfm;
{
  EZ_Widget  *oneCmdToplevel, *frame, *label, *entry, *confirm, *cancel, *tmp;	      
  EZ_UnknownDataType ud;
  int i;

  oneCmdToplevel = EZ_CreateWidgetXrm(EZ_WIDGET_FRAME,    NULL,
				      "CommandDialogue",  "commandDialogue",
				      EZ_LABEL_STRING,    title==NULL?"Execution Dialogue":title,
				      EZ_TEXT_LINE_LENGTH, 30,
				      EZ_JUSTIFICATION,    EZ_CENTER,
				      EZ_TRANSIENT,       True,
				      EZ_IPADY,           10,
				      EZ_ORIENTATION,     EZ_VERTICAL,
				      EZ_FILL_MODE,       EZ_FILL_BOTH,
				      EZ_FONT_ID,         ezfmFontId[LABEL_LARGE_FONT],
				      EZ_BG_PIXMAP,          bigPixmap, 
				      EZ_EVENT_HANDLER,      ConfigureNotifyHandler, NULL,
				      NULL);
  /* remember execution callback */
  i = nargs + 4;
  ud.i = 0;       EZ_SetWidgetUnknownData(oneCmdToplevel, i, ud);
  ud.i = nargs;   EZ_SetWidgetUnknownData(oneCmdToplevel, 0, ud);
  ud.p = (void *)func; 
  if(nargs >= 10) { ud.p = NULL; nargs = 0; }
  EZ_SetWidgetUnknownData(oneCmdToplevel, 1, ud);
  for(i = 0; i < nargs; i++)
    {
      ud.p = args[i];
      EZ_SetWidgetUnknownData(oneCmdToplevel, 2+i, ud);
    }

  label  =           EZ_CreateWidget(EZ_WIDGET_LABEL,    oneCmdToplevel,
				     EZ_LABEL_STRING,    instr==NULL?"Please Confirm ... ":instr,
				     EZ_TEXT_LINE_LENGTH, 60,
				     EZ_JUSTIFICATION,    EZ_CENTER,
                                     EZ_FOREGROUND,         "red",
                                     EZ_FONT_ID,            ezfmFontId[LABEL_LARGE_FONT], 
                                     EZ_LABEL_SHADOW,       1,1,
				     NULL);
  entry =            EZ_CreateWidget(EZ_WIDGET_ENTRY,    oneCmdToplevel,
				     EZ_SELECTION_BACKGROUND, SelectionBackground,
				     EZ_TEXT_LINE_LENGTH,60,
				     EZ_CALLBACK,        executeOneCmd, oneCmdToplevel,
				     EZ_ENTRY_STRING,    command,
				     NULL);
	      
  frame =            EZ_CreateWidget(EZ_WIDGET_FRAME,   oneCmdToplevel,
				     EZ_HEIGHT,         0,
				     NULL);
  confirm =          EZ_CreateWidget(EZ_WIDGET_NORMAL_BUTTON, frame,
				     EZ_LABEL_STRING,   "Confirm",
				     EZ_UNDERLINE,       0,
				     EZ_CALLBACK,        executeOneCmd, oneCmdToplevel,
				     NULL );
  cancel =           EZ_CreateWidget(EZ_WIDGET_NORMAL_BUTTON, frame,
				     EZ_LABEL_STRING,   "Cancel",
				     EZ_UNDERLINE,       1,
				     EZ_CALLBACK,        cancelCallback, oneCmdToplevel,
				     NULL );

  EZ_SetWidgetPtrData(oneCmdToplevel, entry);
  EZ_SetWidgetPtrData(entry,    confirm);
  EZ_SetWidgetPtrData(confirm,  label);
  EZ_SetWidgetPtrData(label,   cancel);

  if(ncfm) /* do not confirm, just execute the command */
    {
      executeOneCmd(confirm, oneCmdToplevel);
    }
  else
    {
      if(kfocus != 'e') EZ_EntryEditBeginningOfInput(entry);
      if(kfocus == 'c') EZ_DisplayWidgetUnderPointer(cancel, -10, -10);
      else EZ_DisplayWidgetUnderPointer(confirm, -10, -10);
      EZ_SetGrab(oneCmdToplevel);
      if(kfocus == 'e' || kfocus == 'E') EZ_SetFocusTo(entry);
      else if(kfocus == 'c')  EZ_SetFocusTo(cancel);
      else  EZ_SetFocusTo(confirm);
    }
}
/******************************************************************************************/
void ezfmQuotePathname(pathIn, pathOut) char *pathIn, *pathOut;
{
  if(pathIn && pathOut)
    {
      char *p= pathIn, *q = pathOut;
      while(*p)
        {
          if(IS_SH_ESCAPE_C(*p)) {*q++ = '\\'; *q++ = *p;}
          else *q++ = *p;
          p++;
        }
      *q = 0;
    }
}
/******************************************************************************************/
void ezfmProcessCmdline(cmd, pathin, cmd_ret) char *cmd, *pathin, *cmd_ret;
{
  char *tptr, *q, *p, path[256];
  if(path == NULL) { strcpy(cmd_ret, cmd); return;}
  
  ezfmQuotePathname(pathin, path);
  
  tptr = strchr(cmd, '%');
  if( tptr == NULL) sprintf(cmd_ret, "%s %s ", cmd, path);
  else
    {
      char dirname[512], base[512], *basename, *filename, *t, *s, *r, *p, *q;
      strcpy(dirname,path); 
      t = strrchr(dirname, '/');
      if(t)
	{
	  *t = '\0';
	  filename = t+1;
	}
      else filename = dirname;
      t = strrchr(filename, '.');
      if(t) 
	{
	  strcpy(base, filename);
	  base[(int)(t - filename)] = '\0';
	  basename = base;
	}   
      else basename = filename;

      s = cmd;
      r = cmd_ret;
      t = tptr;
      while( t != NULL)
	{
	  q = t;
	  while(s != t) { *r++ = *s++;}
	  t++;
	  switch(*t)
	    {
	    case 'd':
	    case 'D':
	      p = dirname;
	      break;
	    case 'f':
	    case 'F':
	      p = filename;
	      break;
	    case 'p':
	    case 'P':
	      p = path;
	      break;
	    case 'b':
	    case 'B':
	      p = basename;
	      break;
	    default:
	      p = NULL;
	      *r++ = *q++;
	      *r++ = *q++; /* copy %. */
	      break;
	    }
	  if(p) {while(*p) *r++ = *p++;}
	  s=t+1;
	  t = strchr(s, '%');
	}
      while(*s) { *r++ = *s++;}
      *r = 0;
    }
}
/*******************************************************************************/
#undef _ACTION_C_
