/* This file is part of the FElt finite element analysis package. Copyright (C) 1993-2000 Jason I. Gobat and Darren C. Atkinson This program 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 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /************************************************************************ * File: filedialog.c * * * * Description: This file contains the private and public function and * * type definitions for the file file dialog box. * ************************************************************************/ # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include "Layout.h" # include "FileDialog.h" # include "TabGroup.h" # include "scroll.h" # include "util.h" # ifndef X_NOT_STDC_ENV # include # else extern char *getenv ( ); # endif # define MaxEntries 2048 # define MaxPathLen 2048 # define Waiting 0 # define Canceled -1 # define Okayed 1 struct file_dialog { Widget shell; /* transientShell */ Widget layout; /* Layout layout */ Widget label; /* Label label */ Widget directory; /* Label directory */ Widget entry; /* AsciiText entry */ Widget viewport; /* Viewport viewport */ Widget list; /* List list */ Widget toggle1; /* Toggle toggle1 */ Widget toggle2; /* Toggle toggle2 */ Widget toggle1_name; /* Toggle toggle1_name */ Widget toggle2_name; /* Toggle toggle2_name */ Widget okay; /* Command okay */ Widget cancel; /* Command cancel */ String path; String displayed; XtCallbackProc callback; XtPointer client_data; int status; }; /* Resources */ static Pixel highlight; static String dummy_list [ ] = { " ", "", "", "", "", "", "", "", "", "", NULL }; static char layout_string [ ] = "vertical { 4 \ horizontal { 4 label <+inf -100% *> 4 } 4 \ horizontal { 4 directory <+inf -100% *> 4 } 4 \ horizontal { 4 entry <+inf -100% *> 4 } 4 \ horizontal { 4 viewport <+inf -100% * +inf -100%> 4 } 4 \ horizontal { 4 toggle1 2 toggle1_name 4 <+inf -100%> \ toggle2 2 toggle2_name 4 } 4 \ horizontal { 4 okay 4 <+inf -100%> cancel 4 } 4 }"; static Arg color_args [ ] = { {XtNborderColor, (XtArgVal) &highlight}, }; static Arg layout_args [ ] = { {XtNlayout, (XtArgVal) NULL}, }; static Arg label_args [ ] = { {XtNresize, (XtArgVal) True}, {XtNjustify, (XtArgVal) XtJustifyLeft}, {XtNborderWidth, (XtArgVal) 0}, }; static Arg directory_args [ ] = { {XtNresize, (XtArgVal) True}, {XtNjustify, (XtArgVal) XtJustifyLeft}, {XtNborderWidth, (XtArgVal) 0}, }; static Arg entry_args [ ] = { {XtNresize, (XtArgVal) XawtextResizeWidth}, {XtNeditType, (XtArgVal) XawtextEdit}, {XtNborderWidth, (XtArgVal) 0}, }; static Arg viewport_args [ ] = { {XtNallowVert, (XtArgVal) True}, {XtNuseRight, (XtArgVal) True}, {XtNforceBars, (XtArgVal) True}, {XtNborderWidth, (XtArgVal) 0}, }; static Arg list_args [ ] = { {XtNdefaultColumns, (XtArgVal) 1}, {XtNforceColumns, (XtArgVal) 1}, {XtNresize, (XtArgVal) True}, {XtNlist, (XtArgVal) dummy_list}, }; static Arg name_args [ ] = { {XtNlabel, (XtArgVal) ""}, {XtNborderWidth, (XtArgVal) 0}, }; static Arg toggle_args [ ] = { {XtNlabel, (XtArgVal) " "}, }; /* Translation tables */ static String entry_table = "Return: FileDialogOkay()\n\ Escape: FileDialogCancel()\n\ : SetFocus() select-start()"; static XtTranslations entry_translations; static String viewport_table = "Return: FileDialogOkay()\n\ Escape: FileDialogCancel()\n\ : SetFocus()"; static XtTranslations viewport_translations; static String list_table = "(2): Set() Notify() FileDialogOkay()\n\ : Set() Notify()\n\ : Set() Notify() FileDialogOkay()"; static XtTranslations list_translations; static String okay_table = "Return: AutoRepeat(off) set()\n\ Return: AutoRepeat(saved) notify() unset()\n\ space: AutoRepeat(off) set()\n\ space: AutoRepeat(saved) notify() unset()\n\ Escape: FileDialogCancel()"; static XtTranslations okay_translations; static String cancel_table = "Return: AutoRepeat(off) set()\n\ Return: AutoRepeat(saved) notify() unset()\n\ space: AutoRepeat(off) set()\n\ space: AutoRepeat(saved) notify() unset()\n\ Escape: FileDialogCancel()"; static XtTranslations cancel_translations; static String toggle_table = "Return: FileDialogOkay()\n\ Escape: FileDialogCancel()\n\ space: toggle() notify()"; static XtTranslations toggle_translations; /************************************************************************ * Function: DisplayPath * * * * Description: Displays the path of the current directory by possibly * * shortening the directory at the front so that it fits. * ************************************************************************/ static void DisplayPath (filed, width) FileDialog filed; Dimension width; { Arg args [1]; int length; String ptr; XFontStruct *font; /* Retrieve the width if necessary. */ if (width == 0) { XtSetArg (args [0], XtNwidth, &width); XtGetValues (filed -> directory, args, 1); } /* Retrieve the font. */ XtSetArg (args [0], XtNfont, &font); XtGetValues (filed -> directory, args, 1); /* Prepare to shorten string so that it will fit. */ length = strlen (filed -> path); ptr = filed -> path; width -= GetTextWidth (font, "...", 3) + 4; /* Keep shortening the string until it fits. */ while (GetTextWidth (font, ptr, length) > width && length > 1) { ptr ++; length --; } /* Display the possibly shortened string. */ if (ptr != filed -> path) sprintf (filed -> displayed, "...%s", ptr); else strcpy (filed -> displayed, filed -> path); XtSetArg (args [0], XtNlabel, filed -> displayed); XtSetValues (filed -> directory, args, 1); } /************************************************************************ * Function: ResizeHandler * * * * Description: Event handler called upon StructureNotify events. If * * the event is specifically a ConfigureNotify event then * * the path to the current directory is redisplayed within * * the new window size. (We subtract 8 to account for the * * 4 pixel space between the label width and the edge of * * the shell window; we use an event handler rather than a * * translation so that we can pass the file dialog as * * client data. * ************************************************************************/ static void ResizeHandler (w, client_data, event, cont) Widget w; XtPointer client_data; XEvent *event; Boolean *cont; { if (event -> type == ConfigureNotify) DisplayPath ((FileDialog) client_data, event -> xconfigure.width - 8); } /************************************************************************ * Function: SortEntries * * * * Description: Used by qsort() to compare two strings; directories * * (which end with a /) are always placed before files * ************************************************************************/ static int SortEntries (s1, s2) String *s1; String *s2; { String dir1; String dir2; dir1 = strchr (*s1, '/'); dir2 = strchr (*s2, '/'); if (dir1 != NULL && dir2 == NULL) return -1; if (dir1 == NULL && dir2 != NULL) return 1; return strcmp (*s1, *s2); } /************************************************************************ * Function: ReadDirectory * * * * Description: Reads the entries of the specified directory, sorts the * * entries, and updates the associated list widget with * * the entries. Directories are placed before files in * * sorted order and directory names end with a /. If the * * directory cannot be opened, False is returned. * * Otherwise, True is returned. * ************************************************************************/ static Boolean ReadDirectory (filed, name) FileDialog filed; String name; { int count; DIR *dirp; struct stat buf; struct dirent *entry; static String list [MaxEntries] = {NULL}; char buffer [MaxPathLen]; if ((dirp = opendir (name)) == NULL) return False; count = 0; while ((entry = readdir (dirp)) != NULL && count < MaxEntries) { if (!strcmp (entry -> d_name, ".")) continue; XtFree (list [count]); sprintf (buffer, "%s/%s", name, entry -> d_name); if (!stat (buffer, &buf) && S_ISDIR (buf.st_mode)) { sprintf (buffer, "%s/", entry -> d_name); list [count ++] = XtNewString (buffer); } else list [count ++] = XtNewString (entry -> d_name); } closedir (dirp); qsort (list, count, sizeof (String), SortEntries); XawListChange (filed -> list, list, count, 0, True); XawViewportSetCoordinates (filed -> viewport, 0, 0); return True; } /************************************************************************ * Function: CopySelected * * * * Description: A callback for the List widget which copies the current * * entry to the entry widget. * ************************************************************************/ static void CopySelected (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { FileDialog filed; XawListReturnStruct *info; filed = (FileDialog) client_data; info = (XawListReturnStruct *) call_data; SetTextString (filed -> entry, info -> string); } /************************************************************************ * Function: Okay * * * * Description: A callback for the okay button which forms a path name * * from the directory and current entry. If the entry * * is an absolute path (begins with / or ~) then the * * current directory is ignored. The path name is then * * normalized (removing .., //, and ~). If the path names * * a file then that file is selected. Otherwise, the path * * is used as a directory which is then read. * ************************************************************************/ static void Okay (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { char old; char buffer [MaxPathLen]; Arg args [1]; String dest; String string; String ptr; FileDialog filed; struct passwd *entry; filed = (FileDialog) client_data; XtSetArg (args [0], XtNstring, &string); XtGetValues (filed -> entry, args, 1); /* If the entry is absolute ignore the current directory. Otherwise, concatenate the entry and directory to form the new path. */ if (string [0] == '/' || string [0] == '~') strcpy (buffer, string); else { strcpy (buffer, filed -> path); strcat (buffer, string); } /* Expand the tilde. */ ptr = buffer; dest = filed -> path; if (!strncmp (buffer, "~/", 2) || !strcmp (buffer, "~")) { strcpy (dest, getenv ("HOME")); dest += strlen (dest); ptr ++; } else if (buffer [0] == '~') { while (*ptr && *ptr != '/') ptr ++; old = *ptr; *ptr = 0; if ((entry = getpwnam (buffer + 1)) != NULL) { strcpy (dest, entry -> pw_dir); dest += strlen (dest); *ptr = old; } else ptr = buffer; } /* Finish normalizing the directory entry removing any .. or //. */ while (*ptr) { if (!strncmp (ptr, "//", 2) || !strcmp (ptr, "/")) ptr ++; else if (!strncmp (ptr, "/../", 4) || !strcmp (ptr, "/..")) { while (dest != filed -> path && *dest != '/') dest --; ptr +=3; } else *dest ++ = *ptr ++; *dest = 0; } if (filed -> path [0] == 0) strcpy (filed -> path, "/"); /* Try to read the normalized path as a directory. */ if (ReadDirectory (filed, filed -> path) == True) { /* Indicate the new directory and an empty entry. */ if (strcmp (filed -> path, "/")) strcat (filed -> path, "/"); DisplayPath (filed, 0); SetTextString (filed -> entry, ""); /* A file was selected. If this is the initial suggestion then decompose the suggestion into a directory and file name and use them. */ } else if (call_data != NULL) { ptr = strrchr (filed -> path, '/'); SetTextString (filed -> entry, ptr + 1); ptr [1] = 0; DisplayPath (filed, 0); ReadDirectory (filed, filed -> path); /* A file was selected. Call the callback or set the selected flag, whichever is appropriate. */ } else if (filed -> callback != NULL) filed -> callback (filed -> entry, filed -> client_data, filed -> path); else filed -> status = Okayed; } /************************************************************************ * Function: CheckToggle * * * * Description: * ************************************************************************/ static void CheckToggle (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { FileDialog filed; Boolean state; Arg args [1]; filed = (FileDialog) client_data; XtSetArg (args [0], XtNstate, &state); XtGetValues (w, args, 1); if (state) { XtSetArg (args [0], XtNstate, False); if (w == filed -> toggle1) XtSetValues (filed -> toggle2, args, 1); else XtSetValues (filed -> toggle1, args, 1); } else { XtSetArg (args [0], XtNstate, True); XtSetValues (w, args, 1); } } /************************************************************************ * Function: Cancel * * * * Description: A callback for the cancel button which indicates that * * the file dialog has been canceled by either calling the * * registered callback with a NULL string or by setting * * the canceled flag. * ************************************************************************/ static void Cancel (w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { FileDialog filed; filed = (FileDialog) client_data; if (filed -> callback != NULL) filed -> callback (filed -> entry, filed -> client_data, NULL); else filed -> status = Canceled; } /************************************************************************ * Function: FileDialogOkay * * * * Description: An action procedure which emulates pressing of the okay * * button. * ************************************************************************/ static void FileDialogOkay (w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal num_params; { if (XtClass (w) == listWidgetClass) w = XtParent (w); w = XtNameToWidget (XtParent (w), "okay"); XtCallCallbacks (w, XtNcallback, NULL); } /************************************************************************ * Function: FileDialogCancel * * * * Description: An action procedure which emulates pressing of the * * cancel button. * ************************************************************************/ static void FileDialogCancel (w, event, params, num_params) Widget w; XEvent *event; String *params; Cardinal num_params; { if (event -> type == ClientMessage) w = XtNameToWidget (w, "layout.cancel"); else w = XtNameToWidget (XtParent (w), "cancel"); XtCallCallbacks (w, XtNcallback, NULL); } /************************************************************************ * Function: FileDialogCreate * * * * Description: Creates and returns a new file dialog box. The dialog * * can be popped up using FileDialogSelect() which forces * * the user to make a selection, or by using FileDialog- * * Popup() which will call a callback when a file is * * selected or the dialog is canceled. The dialog can * * then be popped down using FileDialogPopdown(). * ************************************************************************/ FileDialog FileDialogCreate (parent, name, toggle_labels) Widget parent; String name; char **toggle_labels; { Arg args [1]; Widget group [6]; unsigned group_count; unsigned mask; FileDialog filed; static XtAppContext app_context = NULL; static XtActionsRec actions [ ] = {{"FileDialogOkay", FileDialogOkay}, {"FileDialogCancel", FileDialogCancel}}; /* Perform one time initialization. */ if (app_context == NULL) { app_context = XtWidgetToApplicationContext (parent); XtAppAddActions (app_context, actions, XtNumber (actions)); AddAutoRepeatAction (app_context); layout_args [0].value = StringToLayout (parent, layout_string); entry_translations = XtParseTranslationTable (entry_table); viewport_translations = XtParseTranslationTable (viewport_table); list_translations = XtParseTranslationTable (list_table); okay_translations = XtParseTranslationTable (okay_table); cancel_translations = XtParseTranslationTable (cancel_table); toggle_translations = XtParseTranslationTable (toggle_table); } /* Create the file dialog and its widgets. */ filed = XtNew (struct file_dialog); filed -> path = (String) XtMalloc (MaxPathLen); filed -> displayed = (String) XtMalloc (MaxPathLen); filed -> shell = XtCreatePopupShell (name, transientShellWidgetClass, parent, NULL, 0); filed -> layout = XtCreateManagedWidget ("layout", layoutWidgetClass, filed -> shell, layout_args, XtNumber (layout_args)); filed -> label = XtCreateManagedWidget ("label", labelWidgetClass, filed -> layout, label_args, XtNumber (label_args)); filed -> directory = XtCreateManagedWidget ("directory", labelWidgetClass, filed -> layout, directory_args, XtNumber (directory_args)); filed -> entry = XtCreateManagedWidget ("entry", asciiTextWidgetClass, filed -> layout, entry_args, XtNumber (entry_args)); filed -> viewport = XtCreateManagedWidget ("viewport", viewportWidgetClass, filed -> layout, viewport_args, XtNumber (viewport_args)); filed -> list = XtCreateManagedWidget ("list", listWidgetClass, filed -> viewport, list_args, XtNumber (list_args)); filed -> okay = XtCreateManagedWidget ("okay", commandWidgetClass, filed -> layout, NULL, 0); filed -> cancel = XtCreateManagedWidget ("cancel", commandWidgetClass, filed -> layout, NULL, 0); if (toggle_labels != NULL) { filed -> toggle1 = XtCreateManagedWidget ("toggle1", toggleWidgetClass, filed -> layout, toggle_args, XtNumber (toggle_args)); name_args [0].value = (XtArgVal) toggle_labels [0]; filed -> toggle1_name = XtCreateManagedWidget ("toggle1_name", labelWidgetClass, filed -> layout, name_args, XtNumber (name_args)); filed -> toggle2 = XtCreateManagedWidget ("toggle2", toggleWidgetClass, filed -> layout, toggle_args, XtNumber (toggle_args)); name_args [0].value = (XtArgVal) toggle_labels [1]; filed -> toggle2_name = XtCreateManagedWidget ("toggle2_name", labelWidgetClass, filed -> layout, name_args, XtNumber (name_args)); } else { filed -> toggle1 = NULL; filed -> toggle2 = NULL; } /* Create a tab group for the file dialog. */ group_count = 0; group [group_count++] = filed -> entry; group [group_count++] = filed -> viewport; if (toggle_labels != NULL) { group [group_count++] = filed -> toggle1; group [group_count++] = filed -> toggle2; } group [group_count++] = filed -> okay; group [group_count++] = filed -> cancel; XtGetValues (filed -> layout, color_args, XtNumber (color_args)); CreateTabGroup (filed -> shell, group, group_count, highlight, True); XtRealizeWidget (filed -> shell); /* Add the translations to each widget. */ AddDeleteWindowProtocol (filed -> shell, "FileDialogCancel()"); ListAddCursorTranslations (filed -> viewport); ListAddCursorAccelerators (filed -> viewport, filed -> entry); AddScrollableTextTranslations (filed -> entry); XtOverrideTranslations (filed -> entry, entry_translations); XtOverrideTranslations (filed -> viewport, viewport_translations); XtOverrideTranslations (filed -> okay, okay_translations); XtOverrideTranslations (filed -> cancel, cancel_translations); XtSetArg (args [0], XtNtranslations, list_translations); XtSetValues (filed -> list, args, 1); mask = StructureNotifyMask; XtAddEventHandler (filed -> directory, mask, False, ResizeHandler, filed); /* Add the necessary callbacks. */ XtAddCallback (filed -> list, XtNcallback, CopySelected, (XtPointer) filed); XtAddCallback (filed -> cancel, XtNcallback, Cancel, (XtPointer) filed); XtAddCallback (filed -> okay, XtNcallback, Okay, (XtPointer) filed); if (toggle_labels != NULL) { XtAddCallback (filed -> toggle1, XtNcallback, CheckToggle, (XtPointer) filed); XtAddCallback (filed -> toggle2, XtNcallback, CheckToggle, (XtPointer) filed); XtOverrideTranslations (filed -> toggle1, toggle_translations); XtOverrideTranslations (filed -> toggle2, toggle_translations); XtSetArg (args [0], XtNstate, True); XtSetValues (filed -> toggle1, args, 1); XtSetArg (args [0], XtNstate, False); XtSetValues (filed -> toggle2, args, 1); } return filed; } /************************************************************************ * Function: FileDialogSelect * * * * Description: Pops up the file dialog with the specified shell title, * * label, and suggestion and waits until a selection is * * made or the dialog is canceled; answer will point to * * the selection or NULL if the dialog was canceled * ************************************************************************/ void FileDialogSelect (filed, title, label, suggestion, answer, toggle) FileDialog filed; String title; String label; String suggestion; String *answer; String *toggle; { XEvent event; XtAppContext app_context; FileDialogPopup (filed, title, label, suggestion, NULL, NULL); filed -> status = Waiting; app_context = XtWidgetToApplicationContext (filed -> shell); while (filed -> status == Waiting) { XtAppNextEvent (app_context, &event); XtDispatchEvent (&event); } FileDialogPopdown (filed); if (answer != NULL) *answer = filed -> status == Canceled ? NULL : filed -> path; if (toggle != NULL && filed -> toggle1 != NULL) *toggle = FileDialogToggle (filed); } /************************************************************************ * Function: FileDialogToggle * * * * Description: Returns the name of the currently active toggle button. * ************************************************************************/ String FileDialogToggle (filed) FileDialog filed; { Arg args [1]; Boolean state; String label; XtSetArg (args [0], XtNstate, &state); XtGetValues (filed -> toggle1, args, 1); XtSetArg (args [0], XtNlabel, &label); if (state) XtGetValues (filed -> toggle1_name, args, 1); else XtGetValues (filed -> toggle2_name, args, 1); return label; } /************************************************************************ * Function: FileDialogSetToggles * * * * Description: Sets the labels for the toggle buttons of the specified * * file dialog. * ************************************************************************/ void FileDialogSetToggles (filed, label1, label2) FileDialog filed; String label1; String label2; { Arg args [1]; if (filed -> toggle1 != NULL && filed -> toggle2 != NULL) { XtSetArg (args [0], XtNlabel, label1); XtSetValues (filed -> toggle1_name, args, 1); XtSetArg (args [0], XtNlabel, label2); XtSetValues (filed -> toggle2_name, args, 1); } } /************************************************************************ * Function: FileDialogPopup * * * * Description: Pops up the file dialog with the specified shell title, * * label, and suggestion. If the suggestion is NULL then * * the current working directory is used. If the callback * * is not NULL then it will be called upon a selection or * * cancelation with the specified client data. * ************************************************************************/ void FileDialogPopup (filed, title, label, suggestion, callback, client_data) FileDialog filed; String title; String label; String suggestion; XtCallbackProc callback; XtPointer client_data; { Arg args [1]; XtSetArg (args [0], XtNtitle, title); XtSetValues (filed -> shell, args, 1); XtSetArg (args [0], XtNlabel, label); XtSetValues (filed -> label, args, 1); if (suggestion == NULL) getcwd (filed -> path, MaxPathLen); else if (suggestion [0] == '/' || suggestion [0] == '~') strcpy (filed -> path, suggestion); else { getcwd (filed -> path, MaxPathLen); strcat (filed -> path, "/"); strcat (filed -> path, suggestion); } SetTextString (filed -> entry, filed -> path); filed -> callback = callback; filed -> client_data = client_data; SetFocus (filed -> entry); XtCallCallbacks (filed -> okay, XtNcallback, (XtPointer) filed); XtPopup (filed -> shell, callback == NULL ? XtGrabExclusive : XtGrabNone); } /************************************************************************ * Function: FileDialogPopdown * * * * Descripion: Pops down the specified file dialog * ************************************************************************/ void FileDialogPopdown (filed) FileDialog filed; { XtPopdown (filed -> shell); }