42cd412bec79d4006ea4c7496e846b3afb479bb5
[uwplayer.git] / application / Fsb.c
1 /*
2  * Copyright 2021 Olaf Wintermann
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a 
5  * copy of this software and associated documentation files (the "Software"), 
6  * to deal in the Software without restriction, including without limitation 
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
8  * and/or sell copies of the Software, and to permit persons to whom the 
9  * Software is furnished to do so, subject to the following conditions:
10  * 
11  * The above copyright notice and this permission notice shall be included in 
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
17  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
20  * DEALINGS IN THE SOFTWARE.
21  */
22
23 //define FSB_ENABLE_DETAIL
24
25 #include "Fsb.h"
26 #include "FsbP.h"
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <inttypes.h>
34 #include <errno.h>
35
36 #include <sys/stat.h>
37 #include <limits.h>
38 #include <dirent.h>
39 #include <fnmatch.h>
40
41 #include <Xm/XmAll.h>
42 #include <Xm/DropDown.h>
43
44 #ifdef FSB_ENABLE_DETAIL
45 #include <XmL/Grid.h>
46 #endif
47
48 #define WIDGET_SPACING 5
49 #define WINDOW_SPACING 8
50
51 #define BUTTON_EXTRA_SPACE 4
52
53 #define DATE_FORMAT_SAME_YEAR  "%b %d %H:%M"
54 #define DATE_FORMAT_OTHER_YEAR "%b %d  %Y"
55
56 #define KB_SUFFIX "KiB"
57 #define MB_SUFFIX "MiB"
58 #define GB_SUFFIX "GiB"
59 #define TB_SUFFIX "TiB"
60
61 #define FSB_ERROR_TITLE          "Error"
62 #define FSB_ERROR_CHAR           "Character '/' is not allowed in file names"
63 #define FSB_ERROR_RENAME         "Cannot rename file: %s"
64 #define FSB_ERROR_DELETE         "Cannot delete file: %s"
65 #define FSB_ERROR_CREATE_FOLDER  "Cannot create folder: %s"
66 #define FSB_ERROR_OPEN_DIR       "Cannot open directory: %s"
67
68 #define FSB_DETAIL_HEADINGS "Name|Size|Last Modified"
69
70 static void fsb_class_init(void);
71 static void fsb_class_part_init (WidgetClass wc);
72 static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args);
73 static void fsb_resize(Widget widget);
74 static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes);
75 static void fsb_destroy(Widget widget);
76 static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args);
77 static Boolean fsb_acceptfocus(Widget widget, Time *time);
78
79 static void fsb_insert_child(Widget child);
80
81 static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb);
82
83 static int FSBGlobFilter(const char *a, const char *b);
84
85 static void FSBUpdateTitle(Widget w);
86
87 static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs);
88
89 static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg);
90
91 static void FSBRename(XnFileSelectionBox fsb, const char *path);
92 static void FSBDelete(XnFileSelectionBox fsb, const char *path);
93
94 static void FSBSelectItem(XnFileSelectionBox fsb, const char *item);
95
96 static char* set_selected_path(XnFileSelectionBox data, XmString item);
97
98 static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd);
99
100 static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback);
101
102 static void FileListUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData);
103 static void FileListSelect(Widget fsb, Widget view, const char *item);
104 static void FileListCleanup(Widget fsb, Widget view, void *userData);
105 static void FileListDestroy(Widget fsb, Widget view, void *userData);
106
107 static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb);
108 static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb);
109
110 static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count);
111
112 #ifdef FSB_ENABLE_DETAIL
113 static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData);
114 static void FileListDetailSelect(Widget fsb, Widget view, const char *item);
115 static void FileListDetailCleanup(Widget fsb, Widget view, void *userData);
116 static void FileListDetailDestroy(Widget fsb, Widget view, void *userData);
117 static void FileListDetailAdjustColWidth(Widget grid);
118 static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth);
119 #endif
120
121 static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u);
122
123 static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u);
124
125 static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value);
126
127 static void CreateUI(XnFileSelectionBox w);
128 static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc createProc, void *userData, Boolean useDirList);
129 static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex);
130 static void SelectView(XnFileSelectionBox f, int view);
131
132 static char* FSBDialogTitle(Widget w);
133
134 static FSBViewWidgets CreateListView(Widget fsb, ArgList args, int n, void *userData);
135 static FSBViewWidgets CreateDetailView(Widget fsb, ArgList args, int n, void *userData);
136
137 static const char* GetHomeDir(void);
138
139 static char* ConcatPath(const char *parent, const char *name);
140 static char* FileName(char *path);
141 static char* ParentPath(const char *path);
142 //static int   CheckFileName(const char *name);
143
144 static int filedialog_update_dir(XnFileSelectionBox data, const char *path);
145 static void filedialog_cleanup_filedata(XnFileSelectionBox data);
146
147 static void pathbar_resize(Widget w, PathBar *p, XtPointer d);
148
149 static XtResource resources[] = {
150     {XmNokCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.okCallback), XmRCallback, NULL},
151     {XmNcancelCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.cancelCallback), XmRCallback, NULL},
152     {XnNwidgetSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.widgetSpacing), XmRImmediate, (XtPointer)WIDGET_SPACING},
153     {XnNwindowSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.windowSpacing), XmRImmediate, (XtPointer)WINDOW_SPACING},
154     {XnNfsbType, XnCfsbType, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.type), XmRImmediate, (XtPointer)FILEDIALOG_OPEN},
155     {XnNshowHidden, XnCshowHidden, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHidden), XmRImmediate, (XtPointer)False},
156     {XnNshowHiddenButton, XnCshowHiddenButton, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHiddenButton), XmRImmediate, (XtPointer)True},
157     {XnNshowViewMenu, XnCshowViewMenu, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showViewMenu), XmRImmediate, (XtPointer)False},
158     {XnNselectedView, XnCselectedView, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.selectedview), XmRImmediate, (XtPointer)0},
159     
160     {XnNdirectory, XnCdirectory, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.currentPath), XmRString, NULL},
161     {XnNselectedPath, XnCselectedPath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.selectedPath), XmRString, NULL},
162     {XnNhomePath, XnChomePath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.homePath), XmRString, NULL},
163     
164     {XnNfilter,XnCfilter,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.filterStr), XmRString, "*"},
165     {XnNfilterFunc,XnCfilterFunc,XmRFunction,sizeof(FSBFilterFunc),XtOffset(XnFileSelectionBox, fsb.filterFunc), XmRFunction, NULL},
166     
167     {XnNlabelListView,XnClabelListView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelListView), XmRString, "List"},
168     {XnNlabelDetailView,XnClabelDetailView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDetailView), XmRString, "Detail"},
169     {XnNlabelOpenFileTitle,XnClabelOpenFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpenFileTitle), XmRString, "Open File"},
170     {XnNlabelSaveFileTitle,XnClabelSaveFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSaveFileTitle), XmRString, "Save File"},
171     {XnNlabelDirUp,XnClabelDirUp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirUp), XmRString, "Dir Up"},
172     {XnNlabelHome,XnClabelHome,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHome), XmRString, "Home"},
173     {XnNlabelNewFolder,XnClabelNewFolder,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFolder), XmRString, "New Folder"},
174     {XnNlabelFilterButton,XnClabelFilterButton,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFilterButton), XmRString, "Filter"},
175     {XnNlabelShowHiddenFiles,XnClabelShowHiddenFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelShowHiddenFiles), XmRString, "Show hiden files"},
176     {XnNlabelDirectories,XnClabelDirectories,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectories), XmRString, "Directories"},
177     {XnNlabelFiles,XnClabelFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFiles), XmRString, "Files"},
178     {XnNlabelRename,XnClabelRename,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelRename), XmRString, "Rename"},
179     {XnNlabelDelete,XnClabelDelete,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDelete), XmRString, "Delete"},
180     {XnNlabelOpen,XnClabelOpen,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpen), XmRString, "Open"},
181     {XnNlabelSave,XnClabelSave,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSave), XmRString, "Save"},
182     {XnNlabelOk,XnClabelOk,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOk), XmRString, "OK"},
183     {XnNlabelCancel,XnClabelCancel,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelCancel), XmRString, "Cancel"},
184     {XnNlabelHelp,XnClabelHelp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHelp), XmRString, "Help"},
185     {XnNlabelFileName,XnClabelFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFileName), XmRString, "New File Name"},
186     {XnNlabelDirectoryName,XnClabelDirectoryName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectoryName), XmRString, "Directory name:"},
187     {XnNlabelNewFileName,XnClabelNewFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFileName), XmRString, "New file name:"},
188     {XnNlabelDeleteFile,XnClabelDeleteFile,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDeleteFile), XmRString, "Delete file '%s'?"},
189     {XnNdetailHeadings,XnCdetailHeadings,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.detailHeadings), XmRString,FSB_DETAIL_HEADINGS},
190     {XnNdateFormatSameYear,XnCdateFormatSameYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatSameYear), XmRString,DATE_FORMAT_SAME_YEAR},
191     {XnNdateFormatOtherYear,XnNdateFormatOtherYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatOtherYear), XmRString,DATE_FORMAT_OTHER_YEAR},
192     {XnNsuffixBytes,XnCsuffixBytes,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixBytes), XmRString,"bytes"},
193     {XnNsuffixKB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixKB), XmRString,KB_SUFFIX},
194     {XnNsuffixMB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixMB), XmRString,MB_SUFFIX},
195     {XnNsuffixGB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixGB), XmRString,GB_SUFFIX},
196     {XnNsuffixTB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixTB), XmRString,TB_SUFFIX},
197     
198     {XnNerrorTitle,XnCerrorTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorTitle), XmRString,FSB_ERROR_TITLE},
199     {XnNerrorIllegalChar,XnCerrorIllegalChar,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorIllegalChar), XmRString,FSB_ERROR_CHAR},
200     {XnNerrorRename,XnCerrorRename,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorRename), XmRString,FSB_ERROR_RENAME},
201     {XnNerrorCreateFolder,XnCerrorCreateFolder,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorFolder), XmRString,FSB_ERROR_CREATE_FOLDER},
202     {XnNerrorDelete,XnCerrorDelete,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorDelete), XmRString,FSB_ERROR_DELETE},
203     {XnNerrorOpenDir,XnCerrorOpenDir,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorOpenDir), XmRString,FSB_ERROR_OPEN_DIR}
204 };
205
206 static XtActionsRec actionslist[] = {
207   {"focusIn", FocusInAP},
208   {"NULL", NULL}
209 };
210
211
212 static char defaultTranslations[] = "<FocusIn>:                 focusIn()";
213
214 static XtResource constraints[] = {};
215
216 FSBClassRec fsbWidgetClassRec = {
217     // Core Class
218     {
219         (WidgetClass)&xmFormClassRec,
220         "XnFSB",                         // class_name
221         sizeof(FSBRec),                  // widget_size
222         fsb_class_init,                  // class_initialize
223         fsb_class_part_init,             // class_part_initialize
224         FALSE,                           // class_inited
225         fsb_init,                        // initialize
226         NULL,                            // initialize_hook
227         fsb_realize,                     // realize
228         actionslist,                     // actions
229         XtNumber(actionslist),           // num_actions
230         resources,                       // resources
231         XtNumber(resources),             // num_resources
232         NULLQUARK,                       // xrm_class
233         True,                            // compress_motion
234         True,                            // compress_exposure
235         True,                            // compress_enterleave
236         False,                           // visible_interest
237         fsb_destroy,                     // destroy
238         fsb_resize,                      // resize
239         XtInheritExpose,                 // expose
240         fsb_set_values,                  // set_values
241         NULL,                            // set_values_hook
242         XtInheritSetValuesAlmost,        // set_values_almost
243         NULL,                            // get_values_hook
244         fsb_acceptfocus,                 // accept_focus
245         XtVersion,                       // version
246         NULL,                            // callback_offsets
247         defaultTranslations,             // tm_table
248         XtInheritQueryGeometry,          // query_geometry
249         XtInheritDisplayAccelerator,     // display_accelerator
250         NULL,                            // extension
251     },
252     // Composite Class
253     {
254         XtInheritGeometryManager, // geometry_manager 
255         XtInheritChangeManaged,   // change_managed
256         fsb_insert_child,         // insert_child 
257         XtInheritDeleteChild,     // delete_child  
258         NULL,                     // extension   
259     },
260     // Constraint Class
261     {
262         constraints,                 // resources
263         XtNumber(constraints),       // num_resources   
264         sizeof(XmFormConstraintRec), // constraint_size  
265         NULL,                        // initialize 
266         NULL,                        // destroy
267         NULL,                        // set_value
268         NULL,                        // extension 
269     },
270     // XmManager Class
271     {
272         XtInheritTranslations, // translations
273         NULL, // syn_resources
274         0,    // num_syn_resources
275         NULL, // syn_constraint_resources
276         0,    // num_syn_constraint_resources
277         XmInheritParentProcess, // parent_process
278         NULL  // extension
279     },
280     // XmBulletinBoard
281     {
282         FALSE,
283         NULL,
284         XmInheritFocusMovedProc,
285         NULL
286     },
287     // XmForm Class
288     {
289         NULL
290     },
291     // FSB Class
292     {
293         0
294     }
295 };
296
297 WidgetClass xnFsbWidgetClass = (WidgetClass)&fsbWidgetClassRec;
298
299
300 static void fsb_class_init(void) {
301
302 }
303
304 static void fsb_class_part_init (WidgetClass wc) {
305     FSBClassRec *fsbClass = (FSBClassRec*)wc;
306     XmFormClassRec *formClass = (XmFormClassRec*)xmFormWidgetClass;
307     
308     fsbClass->constraint_class.initialize = formClass->constraint_class.initialize;
309     fsbClass->constraint_class.set_values = formClass->constraint_class.set_values;
310 }
311
312
313 #define STRDUP_RES(a) if(a) a = strdup(a)
314 #define XMS_STRDUP_RES(a) if(a) a = XmStringCopy(a)
315
316 static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) {
317     XnFileSelectionBox fsb = (XnFileSelectionBox)neww;
318     (xmFormClassRec.core_class.initialize)(request, neww, args, num_args);
319     
320     fsb->fsb.disable_set_values = 0;
321     
322     STRDUP_RES(fsb->fsb.homePath);
323     STRDUP_RES(fsb->fsb.selectedPath);
324     STRDUP_RES(fsb->fsb.currentPath);
325     STRDUP_RES(fsb->fsb.filterStr);
326     STRDUP_RES(fsb->fsb.labelListView);
327     STRDUP_RES(fsb->fsb.labelDetailView);
328     STRDUP_RES(fsb->fsb.labelOpenFileTitle);
329     STRDUP_RES(fsb->fsb.labelSaveFileTitle);
330     XMS_STRDUP_RES(fsb->fsb.labelDirUp);
331     XMS_STRDUP_RES(fsb->fsb.labelHome);
332     XMS_STRDUP_RES(fsb->fsb.labelNewFolder);
333     XMS_STRDUP_RES(fsb->fsb.labelFilterButton);
334     XMS_STRDUP_RES(fsb->fsb.labelShowHiddenFiles);
335     XMS_STRDUP_RES(fsb->fsb.labelDirectories);
336     XMS_STRDUP_RES(fsb->fsb.labelFiles);
337     XMS_STRDUP_RES(fsb->fsb.labelRename);
338     XMS_STRDUP_RES(fsb->fsb.labelDelete);
339     XMS_STRDUP_RES(fsb->fsb.labelOpen);
340     XMS_STRDUP_RES(fsb->fsb.labelSave);
341     XMS_STRDUP_RES(fsb->fsb.labelCancel);
342     XMS_STRDUP_RES(fsb->fsb.labelHelp);
343     XMS_STRDUP_RES(fsb->fsb.labelFileName);
344     XMS_STRDUP_RES(fsb->fsb.labelDirectoryName);
345     XMS_STRDUP_RES(fsb->fsb.labelNewFileName);
346     STRDUP_RES(fsb->fsb.labelDeleteFile);
347     STRDUP_RES(fsb->fsb.detailHeadings);
348     STRDUP_RES(fsb->fsb.dateFormatSameYear);
349     STRDUP_RES(fsb->fsb.dateFormatOtherYear);
350     STRDUP_RES(fsb->fsb.suffixBytes);
351     STRDUP_RES(fsb->fsb.suffixKB);
352     STRDUP_RES(fsb->fsb.suffixMB);
353     STRDUP_RES(fsb->fsb.suffixGB);
354     STRDUP_RES(fsb->fsb.suffixTB);
355     STRDUP_RES(fsb->fsb.errorTitle);
356     STRDUP_RES(fsb->fsb.errorIllegalChar);
357     STRDUP_RES(fsb->fsb.errorRename);
358     STRDUP_RES(fsb->fsb.errorFolder);
359     STRDUP_RES(fsb->fsb.errorDelete);
360     STRDUP_RES(fsb->fsb.errorOpenDir);
361     
362     CreateUI((XnFileSelectionBox)fsb);
363     
364     XtAddCallback(neww, XmNmapCallback, fsb_mapcb, NULL);
365 }
366
367 #define STR_FREE(a) if(a) free(a)
368 #define XMSTR_FREE(a) if(a) XmStringFree(a)
369
370 static void fsb_destroy(Widget widget) {
371     XnFileSelectionBox w = (XnFileSelectionBox)widget;
372     
373     // destroy all views
374     for(int i=0;i<w->fsb.numviews;i++) {
375         FSBView v = w->fsb.view[i];
376         v.destroy(widget, v.widget, v.userData);
377     }
378     
379     STR_FREE(w->fsb.homePath);
380     
381     // free filelists
382     filedialog_cleanup_filedata(w);
383     STR_FREE(w->fsb.currentPath);
384     STR_FREE(w->fsb.selectedPath);
385     STR_FREE(w->fsb.filterStr);
386     
387     PathBarDestroy(w->fsb.pathBar);
388     
389     // free strings
390     STR_FREE(w->fsb.labelListView);
391     STR_FREE(w->fsb.labelDetailView);
392     STR_FREE(w->fsb.labelOpenFileTitle);
393     STR_FREE(w->fsb.labelSaveFileTitle);
394     
395     XMSTR_FREE(w->fsb.labelDirUp);
396     XMSTR_FREE(w->fsb.labelHome);
397     XMSTR_FREE(w->fsb.labelNewFolder);
398     XMSTR_FREE(w->fsb.labelFilterButton);
399     XMSTR_FREE(w->fsb.labelShowHiddenFiles);
400     XMSTR_FREE(w->fsb.labelDirectories);
401     XMSTR_FREE(w->fsb.labelFiles);
402     XMSTR_FREE(w->fsb.labelRename);
403     XMSTR_FREE(w->fsb.labelDelete);
404     XMSTR_FREE(w->fsb.labelOpen);
405     XMSTR_FREE(w->fsb.labelSave);
406     XMSTR_FREE(w->fsb.labelCancel);
407     XMSTR_FREE(w->fsb.labelHelp);
408     XMSTR_FREE(w->fsb.labelFileName);
409     XMSTR_FREE(w->fsb.labelDirectoryName);
410     XMSTR_FREE(w->fsb.labelNewFileName);
411     STR_FREE(w->fsb.labelDeleteFile);
412     STR_FREE(w->fsb.detailHeadings);
413     
414     STR_FREE(w->fsb.dateFormatSameYear);
415     STR_FREE(w->fsb.dateFormatOtherYear);
416     STR_FREE(w->fsb.suffixBytes);
417     STR_FREE(w->fsb.suffixKB);
418     STR_FREE(w->fsb.suffixMB);
419     STR_FREE(w->fsb.suffixGB);
420     STR_FREE(w->fsb.suffixTB);
421     
422     STR_FREE(w->fsb.errorTitle);
423     STR_FREE(w->fsb.errorIllegalChar);
424     STR_FREE(w->fsb.errorRename);
425     STR_FREE(w->fsb.errorFolder);
426     STR_FREE(w->fsb.errorDelete);
427     STR_FREE(w->fsb.errorOpenDir);
428 }
429
430 static void fsb_resize(Widget widget) {
431     XnFileSelectionBox w = (XnFileSelectionBox)widget;
432     (xmFormClassRec.core_class.resize)(widget);
433     
434 #ifdef FSB_ENABLE_DETAIL
435     if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) {
436         FileListDetailAdjustColWidth(w->fsb.grid);
437     }
438 #endif
439 }
440
441 static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) {
442     XnFileSelectionBox w = (XnFileSelectionBox)widget;   
443     (xmFormClassRec.core_class.realize)(widget, mask, attributes);
444     
445     FSBView view = w->fsb.view[w->fsb.selectedview];
446     XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT);
447     
448 #ifdef FSB_ENABLE_DETAIL
449     if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) {
450         FileListDetailAdjustColWidth(w->fsb.grid);
451     }
452 #endif
453 }
454
455 static void FSBUpdateTitle(Widget w) {
456     if(XtParent(w)->core.widget_class == xmDialogShellWidgetClass) {
457         char *title = FSBDialogTitle(w);
458         XtVaSetValues(XtParent(w), XmNtitle, title, NULL);
459     }
460 }
461
462 static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) {
463     Boolean r = False;
464
465     XnFileSelectionBox o = (XnFileSelectionBox)old;
466     XnFileSelectionBox n = (XnFileSelectionBox)neww;
467     
468     int setOkBtnLabel = 0;
469     int ismanaged = XtIsManaged(neww);
470     Dimension width, height;
471     if(!ismanaged) {
472         width = n->core.width;
473         height = n->core.height;
474         if(n->fsb.pathBar) {
475             n->fsb.pathBar->disableResize = True;
476         }
477     }
478     
479     if(o->fsb.selectedview != n->fsb.selectedview) {
480         int selectedview = n->fsb.selectedview;
481         n->fsb.selectedview = o->fsb.selectedview;
482         SelectView(n, selectedview);
483     }
484     
485     char *updateDir = NULL;
486     int selectItem = 0;
487     if(o->fsb.selectedPath != n->fsb.selectedPath) {
488         STR_FREE(o->fsb.selectedPath);
489         STRDUP_RES(n->fsb.selectedPath);
490         XmTextFieldSetString(n->fsb.name, FileName(n->fsb.selectedPath));
491         // also update current directory
492         updateDir = ParentPath(n->fsb.selectedPath);
493         selectItem = 1;
494     }
495     if(o->fsb.currentPath != n->fsb.currentPath) {
496         STR_FREE(o->fsb.currentPath);
497         updateDir = strdup(n->fsb.currentPath);
498         n->fsb.currentPath = NULL;
499     }
500     
501     if(o->fsb.filterStr != n->fsb.filterStr) {
502         STR_FREE(o->fsb.filterStr);
503         STRDUP_RES(n->fsb.filterStr);
504         XmTextFieldSetString(XmDropDownGetText(n->fsb.filter), n->fsb.filterStr);
505         if(!updateDir) {
506             filedialog_update_dir(n, NULL);
507         }
508     }
509     
510     if(updateDir) {
511         filedialog_update_dir(n, updateDir);
512         PathBarSetPath(n->fsb.pathBar, updateDir);
513         free(updateDir);
514     }
515     
516     if(o->fsb.type != n->fsb.type) {
517         if(n->fsb.type == FILEDIALOG_OPEN) {
518             XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.separator, NULL);
519             XtUnmanageChild(n->fsb.name);
520             XtUnmanageChild(n->fsb.nameLabel);
521         } else {
522             XtManageChild(n->fsb.name);
523             XtManageChild(n->fsb.nameLabel);
524             XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.nameLabel, NULL);
525         }
526         FSBUpdateTitle(neww);
527         setOkBtnLabel = 1;
528     }
529       
530     // label strings
531     int updateTitle = 0;
532     if(o->fsb.labelListView != n->fsb.labelListView) {
533         STR_FREE(o->fsb.labelListView);
534         STRDUP_RES(n->fsb.labelListView);
535         XmString label = XmStringCreateLocalized(n->fsb.labelListView);
536         XtVaSetValues(n->fsb.viewSelectorList, XmNlabelString, label, NULL);
537         XmStringFree(label);
538     }
539     if(o->fsb.labelDetailView != n->fsb.labelDetailView) {
540         STR_FREE(o->fsb.labelDetailView);
541         STRDUP_RES(n->fsb.labelDetailView);
542         XmString label = XmStringCreateLocalized(n->fsb.labelDetailView);
543         XtVaSetValues(n->fsb.viewSelectorDetail, XmNlabelString, label, NULL);
544         if(n->fsb.detailToggleButton) {
545             XtVaSetValues(n->fsb.detailToggleButton, XmNlabelString, label, NULL);
546         }
547         XmStringFree(label);
548     }
549     if(o->fsb.labelOpenFileTitle != n->fsb.labelOpenFileTitle) {
550         STR_FREE(o->fsb.labelOpenFileTitle);
551         STRDUP_RES(n->fsb.labelOpenFileTitle);
552         updateTitle = 1;
553     }
554     if(o->fsb.labelSaveFileTitle != n->fsb.labelSaveFileTitle) {
555         STR_FREE(o->fsb.labelSaveFileTitle);
556         STRDUP_RES(n->fsb.labelSaveFileTitle);
557         updateTitle = 1;
558     }
559     
560     if(o->fsb.labelDirUp != n->fsb.labelDirUp) {
561         XMSTR_FREE(o->fsb.labelDirUp);
562         XMS_STRDUP_RES(n->fsb.labelDirUp);
563         XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelDirUp, NULL);
564     }
565     if(o->fsb.labelHome != n->fsb.labelHome) {
566         XMSTR_FREE(o->fsb.labelHome);
567         XMS_STRDUP_RES(n->fsb.labelHome);
568         XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelHome, NULL);
569     }
570     if(o->fsb.labelNewFolder != n->fsb.labelNewFolder) {
571         XMSTR_FREE(o->fsb.labelNewFolder);
572         XMS_STRDUP_RES(n->fsb.labelNewFolder);
573         XtVaSetValues(n->fsb.newFolder, XmNlabelString, n->fsb.labelNewFolder, NULL);
574     }
575     if(o->fsb.labelFilterButton != n->fsb.labelFilterButton) {
576         XMSTR_FREE(o->fsb.labelFilterButton);
577         XMS_STRDUP_RES(n->fsb.labelFilterButton);
578         XtVaSetValues(n->fsb.filterButton, XmNlabelString, n->fsb.labelFilterButton, NULL);
579     }
580     if(o->fsb.labelShowHiddenFiles != n->fsb.labelShowHiddenFiles) {
581         XMSTR_FREE(o->fsb.labelShowHiddenFiles);
582         XMS_STRDUP_RES(n->fsb.labelShowHiddenFiles);
583         XtVaSetValues(n->fsb.showHiddenButtonW, XmNlabelString, n->fsb.labelShowHiddenFiles, NULL);
584     }
585     if(o->fsb.labelDirectories != n->fsb.labelDirectories) {
586         XMSTR_FREE(o->fsb.labelDirectories);
587         XMS_STRDUP_RES(n->fsb.labelDirectories);
588         XtVaSetValues(n->fsb.lsDirLabel, XmNlabelString, n->fsb.labelDirectories, NULL);
589     }
590     if(o->fsb.labelFiles != n->fsb.labelFiles) {
591         XMSTR_FREE(o->fsb.labelFiles);
592         XMS_STRDUP_RES(n->fsb.labelFiles);
593         XtVaSetValues(n->fsb.lsFileLabel, XmNlabelString, n->fsb.labelFiles, NULL);
594     }
595     int recreateContextMenu = 0;
596     if(o->fsb.labelRename != n->fsb.labelRename) {
597         XMSTR_FREE(o->fsb.labelRename);
598         XMS_STRDUP_RES(n->fsb.labelRename);
599         recreateContextMenu = 1;
600     }
601     if(o->fsb.labelDelete != n->fsb.labelDelete) {
602         XMSTR_FREE(o->fsb.labelDelete);
603         XMS_STRDUP_RES(n->fsb.labelDelete);
604         recreateContextMenu = 1;
605     }
606
607     if(o->fsb.labelOpen != n->fsb.labelOpen) {
608         XMSTR_FREE(o->fsb.labelOpen);
609         XMS_STRDUP_RES(n->fsb.labelOpen);
610         setOkBtnLabel = 1;
611     }
612     if(o->fsb.labelSave != n->fsb.labelSave) {
613         XMSTR_FREE(o->fsb.labelSave);
614         XMS_STRDUP_RES(n->fsb.labelSave);
615         setOkBtnLabel = 1;
616     }
617     if(o->fsb.labelCancel != n->fsb.labelCancel) {
618         XMSTR_FREE(o->fsb.labelCancel);
619         XMS_STRDUP_RES(n->fsb.labelCancel);
620         XtVaSetValues(n->fsb.cancelBtn, XmNlabelString, n->fsb.labelCancel, NULL);
621     }
622     if(o->fsb.labelHelp != n->fsb.labelHelp) {
623         XMSTR_FREE(o->fsb.labelHelp);
624         XMS_STRDUP_RES(n->fsb.labelHelp);
625         XtVaSetValues(n->fsb.helpBtn, XmNlabelString, n->fsb.labelHelp, NULL);
626     }
627     if(o->fsb.labelFileName != n->fsb.labelFileName) {
628         XMSTR_FREE(o->fsb.labelFileName);
629         XMS_STRDUP_RES(n->fsb.labelFileName);
630         XtVaSetValues(n->fsb.nameLabel, XmNlabelString, n->fsb.labelFileName, NULL);
631     }
632     if(o->fsb.labelDirectoryName != n->fsb.labelDirectoryName) {
633         XMSTR_FREE(o->fsb.labelDirectoryName);
634         XMS_STRDUP_RES(n->fsb.labelDirectoryName);
635     }
636     if(o->fsb.labelNewFileName != n->fsb.labelNewFileName) {
637         XMSTR_FREE(o->fsb.labelNewFileName);
638         XMS_STRDUP_RES(n->fsb.labelNewFileName);
639     }
640     
641     if(o->fsb.labelDeleteFile != n->fsb.labelDeleteFile) {
642         STR_FREE(o->fsb.labelDeleteFile);
643         STRDUP_RES(n->fsb.labelDeleteFile);
644     }
645 #ifdef FSB_ENABLE_DETAIL
646     if(o->fsb.detailHeadings != n->fsb.detailHeadings) {
647         STR_FREE(o->fsb.detailHeadings);
648         STRDUP_RES(n->fsb.detailHeadings);
649         XtVaSetValues(n->fsb.grid, XmNsimpleHeadings, n->fsb.detailHeadings, NULL);
650     }
651 #endif
652     if(o->fsb.dateFormatSameYear != n->fsb.dateFormatSameYear) {
653         STR_FREE(o->fsb.dateFormatSameYear);
654         STRDUP_RES(n->fsb.dateFormatSameYear);
655     }
656     if(o->fsb.dateFormatOtherYear != n->fsb.dateFormatOtherYear) {
657         STR_FREE(o->fsb.dateFormatOtherYear);
658         STRDUP_RES(n->fsb.dateFormatOtherYear);
659     }
660     if(o->fsb.suffixBytes != n->fsb.suffixBytes) {
661         STR_FREE(o->fsb.suffixBytes);
662         STRDUP_RES(n->fsb.suffixBytes);
663     }
664     if(o->fsb.suffixMB != n->fsb.suffixMB) {
665         STR_FREE(o->fsb.suffixMB);
666         STRDUP_RES(n->fsb.suffixMB);
667     }
668     if(o->fsb.suffixGB != n->fsb.suffixGB) {
669         STR_FREE(o->fsb.suffixGB);
670         STRDUP_RES(n->fsb.suffixGB);
671     }
672     if(o->fsb.suffixTB != n->fsb.suffixTB) {
673         STR_FREE(o->fsb.suffixTB);
674         STRDUP_RES(n->fsb.suffixTB);
675     }
676     if(o->fsb.errorTitle != n->fsb.errorTitle) {
677         STR_FREE(o->fsb.errorTitle);
678         STRDUP_RES(n->fsb.errorTitle);
679     }
680     if(o->fsb.errorIllegalChar != n->fsb.errorIllegalChar) {
681         STR_FREE(o->fsb.errorIllegalChar);
682         STRDUP_RES(n->fsb.errorIllegalChar);
683     }
684     if(o->fsb.errorRename != n->fsb.errorRename) {
685         STR_FREE(o->fsb.errorRename);
686         STRDUP_RES(n->fsb.errorRename);
687     }
688     if(o->fsb.errorFolder != n->fsb.errorFolder) {
689         STR_FREE(o->fsb.errorFolder);
690         STRDUP_RES(n->fsb.errorFolder);
691     }
692     if(o->fsb.errorDelete != n->fsb.errorDelete) {
693         STR_FREE(o->fsb.errorDelete);
694         STRDUP_RES(n->fsb.errorDelete);
695     }
696     if(o->fsb.errorOpenDir != n->fsb.errorOpenDir) {
697         STR_FREE(o->fsb.errorOpenDir);
698         STRDUP_RES(n->fsb.errorOpenDir);
699     }
700     
701     if(updateTitle) {
702         FSBUpdateTitle(neww);
703     }
704     if(recreateContextMenu) {
705         XtDestroyWidget(n->fsb.listContextMenu);
706         XtDestroyWidget(n->fsb.gridContextMenu);
707         n->fsb.listContextMenu = CreateContextMenu(n, n->fsb.filelist, FileContextMenuCB);
708         n->fsb.gridContextMenu = CreateContextMenu(n, n->fsb.grid, FileContextMenuCB);
709     }
710     if(setOkBtnLabel) {
711         XtVaSetValues(n->fsb.okBtn, XmNlabelString, n->fsb.type == FILEDIALOG_OPEN ? n->fsb.labelOpen : n->fsb.labelSave, NULL);
712     }
713     
714     if(!ismanaged && !n->fsb.disable_set_values) {
715         n->fsb.disable_set_values = 1;
716         XtVaSetValues(neww, XmNwidth, width, XmNheight, height, NULL);
717         n->fsb.disable_set_values = 0;
718         
719         if(n->fsb.pathBar)
720             n->fsb.pathBar->disableResize = False;
721     }
722     
723     if(selectItem) {
724         if(ismanaged) {
725             FSBSelectItem(n, FileName(n->fsb.selectedPath));
726         }
727     }
728      
729     Boolean fr = (xmFormClassRec.core_class.set_values)(old, request, neww, args, num_args);
730     return fr ? fr : r;
731 }
732
733 static void fsb_insert_child(Widget child) {
734     XnFileSelectionBox p = (XnFileSelectionBox)XtParent(child);
735     (xmFormClassRec.composite_class.insert_child)(child);
736     
737     if(!p->fsb.gui_created) {
738         return;
739     }
740     
741     // custom child widget insert
742     XtVaSetValues(child,
743             XmNbottomAttachment, XmATTACH_WIDGET,
744             XmNbottomWidget, p->fsb.bottom_widget,
745             XmNbottomOffset, p->fsb.widgetSpacing,
746             XmNleftAttachment, XmATTACH_FORM,
747             XmNleftOffset, p->fsb.windowSpacing,
748             XmNrightAttachment, XmATTACH_FORM,
749             XmNrightAttachment, XmATTACH_FORM,
750             XmNrightOffset, p->fsb.windowSpacing,
751             NULL);
752     
753     
754     XtVaSetValues(p->fsb.listform,
755             XmNbottomWidget, child,
756             XmNbottomOffset, 0,
757             NULL);
758     
759     p->fsb.workarea = child;
760 }
761
762 Boolean fsb_acceptfocus(Widget widget, Time *time) {
763     return 0;
764 }
765
766 static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb) {
767     XnFileSelectionBox w = (XnFileSelectionBox)widget;
768     pathbar_resize(w->fsb.pathBar->widget, w->fsb.pathBar, NULL);
769     
770     if(w->fsb.type == FILEDIALOG_OPEN) {
771         FSBView view = w->fsb.view[w->fsb.selectedview];
772         XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT);
773     } else {
774         XmProcessTraversal(w->fsb.name, XmTRAVERSE_CURRENT);
775     }
776     
777     
778     if(w->fsb.selectedPath) {
779         FSBSelectItem(w, FileName(w->fsb.selectedPath));
780     }
781 }
782
783 static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) {
784     
785 }
786
787 static int apply_filter(XnFileSelectionBox w, const char *pattern, const char *string) {
788     if(!pattern) return 0;
789     
790     FSBFilterFunc func = w->fsb.filterFunc ? w->fsb.filterFunc : FSBGlobFilter;
791     return func(pattern, string);
792 }
793
794 static int FSBGlobFilter(const char *a, const char *b) {
795     return fnmatch(a, b, 0);
796 }
797
798
799 static void errCB(Widget w, XtPointer d, XtPointer cbs) {
800     XtDestroyWidget(w);
801 }
802
803 static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg) {
804     Arg args[16];
805     int n = 0;
806     
807     XmString titleStr = XmStringCreateLocalized((char*)title);
808     XmString msg = XmStringCreateLocalized((char*)errmsg);
809     
810     XtSetArg(args[n], XmNdialogTitle, titleStr); n++;
811     XtSetArg(args[n], XmNselectionLabelString, msg); n++;
812     XtSetArg(args[n], XmNokLabelString, w->fsb.labelOk); n++;
813     XtSetArg(args[n], XmNcancelLabelString, w->fsb.labelCancel); n++;
814     
815     Widget dialog = XmCreatePromptDialog ((Widget)w, "NewFolderPrompt", args, n);
816     
817     Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
818     XtUnmanageChild(help);
819     Widget cancel = XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON);
820     XtUnmanageChild(cancel);
821     Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);
822     XtUnmanageChild(text);
823     
824     XtAddCallback(dialog, XmNokCallback, errCB, NULL);
825     
826     XtManageChild(dialog);
827     
828     XmStringFree(titleStr);
829     XmStringFree(msg);
830 }
831
832 static void rename_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) {
833     XnFileSelectionBox fsb = NULL;
834     XtVaGetValues(w, XmNuserData, &fsb, NULL);
835     
836     char *fileName = NULL;
837     XmStringGetLtoR(cb->value, XmSTRING_DEFAULT_CHARSET, &fileName);
838     
839     // make sure the new file name doesn't contain a path separator
840     if(strchr(fileName, '/')) {
841         ErrDialog(fsb, fsb->fsb.errorTitle, fsb->fsb.errorIllegalChar);
842         XtFree(fileName);
843         return;
844     }
845     
846     char *parentPath = ParentPath(path);
847     char *newPath = ConcatPath(parentPath, fileName);
848     
849     if(rename(path, newPath)) {
850         char errmsg[256];
851         snprintf(errmsg, 256, fsb->fsb.errorRename, strerror(errno));
852         ErrDialog(fsb, fsb->fsb.errorTitle, errmsg);
853     } else {
854         filedialog_update_dir(fsb, parentPath);
855     }
856     
857     free(parentPath);
858     free(newPath);
859     XtFree(fileName);
860     XtDestroyWidget(XtParent(w));
861 }
862
863 static void selectionbox_cancel(Widget w, XtPointer data, XtPointer d) {
864     XtDestroyWidget(XtParent(w));
865 }
866
867 static void FSBRename(XnFileSelectionBox fsb, const char *path) {
868     Arg args[16];
869     int n = 0;
870     Widget w = (Widget)fsb;
871     
872     char *name = FileName((char*)path);
873     
874     XmString filename = XmStringCreateLocalized(name);
875     XtSetArg(args[n], XmNselectionLabelString,fsb->fsb.labelNewFileName); n++;
876     XtSetArg(args[n], XmNtextString, filename); n++;
877     XtSetArg(args[n], XmNuserData, fsb); n++;
878     XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelRename); n++;
879     XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++;
880     XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++;
881     Widget dialog = XmCreatePromptDialog (w, "RenameFilePrompt", args, n);
882     
883     Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
884     XtUnmanageChild(help);
885     
886     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)rename_file_cb, (char*)path);
887     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL);
888     
889     XmStringFree(filename);
890     XtManageChild(dialog);
891 }
892
893 static void delete_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) {
894     XnFileSelectionBox fsb = NULL;
895     XtVaGetValues(w, XmNuserData, &fsb, NULL);
896     
897     if(unlink(path)) {
898         char errmsg[256];
899         snprintf(errmsg, 256, fsb->fsb.errorDelete, strerror(errno));
900         ErrDialog(fsb, fsb->fsb.errorTitle, errmsg);
901     } else {
902         char *parentPath = ParentPath(path);
903         filedialog_update_dir(fsb, parentPath);
904         free(parentPath);
905     }
906     
907     XtDestroyWidget(XtParent(w));
908 }
909
910 static void FSBDelete(XnFileSelectionBox fsb, const char *path) {
911     Arg args[16];
912     int n = 0;
913     Widget w = (Widget)fsb;
914     
915     char *name = FileName((char*)path);
916     size_t len = strlen(name);
917     size_t msglen = len + strlen(fsb->fsb.labelDeleteFile) + 4;
918     char *msg = malloc(msglen);
919     snprintf(msg, msglen, fsb->fsb.labelDeleteFile, name);
920     
921     XmString prompt = XmStringCreateLocalized(msg);
922     XtSetArg(args[n], XmNselectionLabelString, prompt); n++;
923     XtSetArg(args[n], XmNuserData, fsb); n++;
924     XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelDelete); n++;
925     XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++;
926     XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++;
927     Widget dialog = XmCreatePromptDialog (w, "DeleteFilePrompt", args, n);
928     
929     Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
930     XtUnmanageChild(help);
931     Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT);
932     XtUnmanageChild(text);
933     
934     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)delete_file_cb, (char*)path);
935     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL);
936     
937     free(msg);
938     XmStringFree(prompt);
939     XtManageChild(dialog);
940 }
941
942 static void FSBSelectItem(XnFileSelectionBox fsb, const char *item) {
943     FSBView view = fsb->fsb.view[fsb->fsb.selectedview];
944     if(view.select) {
945         view.select((Widget)fsb, view.widget, item);
946     }
947 }
948
949 static char* set_selected_path(XnFileSelectionBox data, XmString item)
950 {
951     char *name = NULL;
952     XmStringGetLtoR(item, XmFONTLIST_DEFAULT_TAG, &name);
953     if(!name) {
954         return NULL;
955     }
956     char *path = ConcatPath(data->fsb.currentPath, name);
957     XtFree(name);
958     
959     if(data->fsb.selectedPath) {
960         free(data->fsb.selectedPath);
961     }
962     data->fsb.selectedPath = path;
963     
964     return path;
965 }
966
967 // item0: rename
968 // item1: delete
969 static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd) {
970     intptr_t i = (intptr_t)index;
971     Widget parent = XtParent(item);
972     XnFileSelectionBox fsb = NULL;
973     XtVaGetValues(parent, XmNuserData, &fsb, NULL);
974     
975     const char *path = fsb->fsb.selectedPath;
976     if(path) {
977         if(i == 0) {
978             FSBRename(fsb, path);
979         } else if(i == 1) {
980             FSBDelete(fsb, path);
981         }
982     }
983 }
984
985 static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback) {
986     return XmVaCreateSimplePopupMenu(
987             parent, "popup", callback, XmNpopupEnabled, XmPOPUP_AUTOMATIC,
988             XmNuserData, fsb,
989             XmVaPUSHBUTTON, fsb->fsb.labelRename, 'R', NULL, NULL,
990             XmVaPUSHBUTTON, fsb->fsb.labelDelete, 'D', NULL, NULL,
991             NULL);
992 }
993
994
995 static void FileListUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) {
996     XnFileSelectionBox data = userData;
997     FileListWidgetAdd(data, data->fsb.filelist, data->fsb.showHidden, filter, files, filecount);
998 }
999
1000 static void FileListSelect(Widget fsb, Widget view, const char *item) {
1001     XnFileSelectionBox w = (XnFileSelectionBox)fsb;
1002     
1003     int numItems = 0;
1004     XmStringTable items = NULL;
1005     XtVaGetValues(w->fsb.filelist, XmNitemCount, &numItems, XmNitems, &items, NULL);
1006     
1007     for(int i=0;i<numItems;i++) {
1008         char *str = NULL;
1009         XmStringGetLtoR(items[i], XmFONTLIST_DEFAULT_TAG, &str);
1010         if(!strcmp(str, item)) {
1011             XmListSelectPos(w->fsb.filelist, i+1, False);
1012             break;
1013         }
1014         XtFree(str);
1015     }
1016 }
1017
1018 static void FileListCleanup(Widget fsb, Widget view, void *userData) {
1019     XnFileSelectionBox data = userData;
1020     XmListDeleteAllItems(data->fsb.filelist);
1021 }
1022
1023 static void FileListDestroy(Widget fsb, Widget view, void *userData) {
1024     // unused
1025 }
1026
1027 static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
1028 {
1029     char *path = set_selected_path(data, cb->item);
1030     if(path) {
1031         data->fsb.end = True;
1032         data->fsb.status = FILEDIALOG_OK;
1033         data->fsb.selIsDir = False;
1034         FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath);
1035     }
1036 }
1037
1038 static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
1039 {
1040     if(data->fsb.type == FILEDIALOG_SAVE) {
1041         char *name = NULL;
1042         XmStringGetLtoR(cb->item, XmFONTLIST_DEFAULT_TAG, &name);
1043         XmTextFieldSetString(data->fsb.name, name);
1044         XtFree(name);
1045     } else {
1046         char *path = set_selected_path(data, cb->item);
1047         if(path) {
1048             data->fsb.selIsDir = False;
1049         }
1050     }
1051 }
1052
1053
1054 static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count)
1055 {   
1056     if(count > 0) {
1057         XmStringTable items = calloc(count, sizeof(XmString));
1058         int i = 0;
1059         
1060         for(int j=0;j<count;j++) {
1061             FileElm *e = &ls[j];
1062             
1063             char *name = FileName(e->path);
1064             if((!showHidden && name[0] == '.') || apply_filter(fsb, filter, name)) {
1065                 continue;
1066             }
1067             
1068             items[i] = XmStringCreateLocalized(name);
1069             i++;
1070         }
1071         XmListAddItems(w, items, i, 0);
1072         for(i=0;i<count;i++) {
1073             XmStringFree(items[i]);
1074         }
1075         free(items);
1076     }
1077 }
1078
1079 #ifdef FSB_ENABLE_DETAIL
1080 static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) {
1081     XnFileSelectionBox data = userData;
1082     FileListDetailAdd(data, data->fsb.grid, data->fsb.showHidden, filter, files, filecount, maxnamelen);
1083 }
1084 #endif
1085
1086 /*
1087  * create file size string with kb/mb/gb/tb suffix
1088  */
1089 static char* size_str(XnFileSelectionBox fsb, FileElm *f) {
1090     char *str = malloc(16);
1091     uint64_t size = f->size;
1092     
1093     if(f->isDirectory) {
1094         str[0] = '\0';
1095     } else if(size < 0x400) {
1096         snprintf(str, 16, "%d %s", (int)size, fsb->fsb.suffixBytes);
1097     } else if(size < 0x100000) {
1098         float s = (float)size/0x400;
1099         int diff = (s*100 - (int)s*100);
1100         if(diff > 90) {
1101             diff = 0;
1102             s += 0.10f;
1103         }
1104         if(size < 0x2800 && diff != 0) {
1105             // size < 10 KiB
1106             snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixKB);
1107         } else {
1108             snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixKB);
1109         }
1110     } else if(size < 0x40000000) {
1111         float s = (float)size/0x100000;
1112         int diff = (s*100 - (int)s*100);
1113         if(diff > 90) {
1114             diff = 0;
1115             s += 0.10f;
1116         }
1117         if(size < 0xa00000 && diff != 0) {
1118             // size < 10 MiB
1119             snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixMB);
1120         } else {
1121             snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixMB);
1122         }
1123     } else if(size < 0x1000000000ULL) {
1124         float s = (float)size/0x40000000;
1125         int diff = (s*100 - (int)s*100);
1126         if(diff > 90) {
1127             diff = 0;
1128             s += 0.10f;
1129         }
1130         if(size < 0x280000000 && diff != 0) {
1131             // size < 10 GiB
1132             snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixGB);
1133         } else {
1134             snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixGB);
1135         }
1136     } else {
1137         size /= 1024;
1138         float s = (float)size/0x40000000;
1139         int diff = (s*100 - (int)s*100);
1140         if(diff > 90) {
1141             diff = 0;
1142             s += 0.10f;
1143         }
1144         if(size < 0x280000000 && diff != 0) {
1145             // size < 10 TiB
1146             snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixTB);
1147         } else {
1148             snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixTB);
1149         }
1150     }
1151     return str;
1152 }
1153
1154 static char* date_str(XnFileSelectionBox fsb, time_t tm) {
1155     struct tm t;
1156     struct tm n;
1157     time_t now = time(NULL);
1158     
1159     localtime_r(&tm, &t);
1160     localtime_r(&now, &n);
1161     
1162     char *str = malloc(24);
1163     if(t.tm_year == n.tm_year) {
1164         strftime(str, 24, fsb->fsb.dateFormatSameYear, &t);
1165     } else {
1166         strftime(str, 24, fsb->fsb.dateFormatOtherYear, &t);
1167     }
1168     return str;
1169 }
1170
1171 #ifdef FSB_ENABLE_DETAIL
1172 static void FileListDetailAdjustColWidth(Widget grid) {
1173     XmLGridColumn column0 = XmLGridGetColumn(grid, XmCONTENT, 0);
1174     XmLGridColumn column1 = XmLGridGetColumn(grid, XmCONTENT, 1);
1175     XmLGridColumn column2 = XmLGridGetColumn(grid, XmCONTENT, 2);
1176     
1177     Dimension col0Width = XmLGridColumnWidthInPixels(column0);
1178     Dimension col1Width = XmLGridColumnWidthInPixels(column1);
1179     Dimension col2Width = XmLGridColumnWidthInPixels(column2);
1180     
1181     Dimension totalWidth = col0Width + col1Width + col2Width;
1182     
1183     Dimension gridWidth = 0;
1184     Dimension gridShadow = 0;
1185     XtVaGetValues(grid, XmNwidth, &gridWidth, XmNshadowThickness, &gridShadow, NULL);
1186     
1187     Dimension widthDiff = gridWidth - totalWidth - gridShadow - gridShadow;
1188     
1189     if(gridWidth > totalWidth) {
1190             XtVaSetValues(grid,
1191             XmNcolumnRangeStart, 0,
1192             XmNcolumnRangeEnd, 0,
1193             XmNcolumnWidth, col0Width + widthDiff - XmLGridVSBWidth(grid) - 2,
1194             XmNcolumnSizePolicy, XmCONSTANT,
1195             NULL);
1196     }
1197 }
1198
1199 static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth)
1200 {
1201     XmLGridAddRows(grid, XmCONTENT, 1, count);
1202     
1203     int row = 0;
1204     for(int i=0;i<count;i++) {
1205         FileElm *e = &ls[i];
1206         
1207         char *name = FileName(e->path);
1208         if((!showHidden && name[0] == '.') || (!e->isDirectory && apply_filter(fsb, filter, name))) {
1209             continue;
1210         }
1211         
1212         // name
1213         XmString str = XmStringCreateLocalized(name);
1214         XtVaSetValues(grid,
1215                 XmNcolumn, 0, 
1216                 XmNrow, row,
1217                 XmNcellString, str, NULL);
1218         XmStringFree(str);
1219         // size
1220         char *szbuf = size_str(fsb, e);
1221         str = XmStringCreateLocalized(szbuf);
1222         XtVaSetValues(grid,
1223                 XmNcolumn, 1, 
1224                 XmNrow, row,
1225                 XmNcellString, str, NULL);
1226         free(szbuf);
1227         XmStringFree(str);
1228         // date
1229         char *datebuf = date_str(fsb, e->lastModified);
1230         str = XmStringCreateLocalized(datebuf);
1231         XtVaSetValues(grid,
1232                 XmNcolumn, 2, 
1233                 XmNrow, row,
1234                 XmNcellString, str, NULL);
1235         free(datebuf);
1236         XmStringFree(str);
1237         
1238         XtVaSetValues(grid, XmNrow, row, XmNrowUserData, e, NULL);
1239         row++;
1240     }
1241     
1242     // remove unused rows
1243     if(count > row) {
1244         XmLGridDeleteRows(grid, XmCONTENT, row, count-row);
1245     }
1246     
1247     if(maxWidth < 16) {
1248         maxWidth = 16;
1249     }
1250     
1251     XtVaSetValues(grid,
1252         XmNcolumnRangeStart, 0,
1253         XmNcolumnRangeEnd, 0,
1254         XmNcolumnWidth, maxWidth,
1255         XmNcellAlignment, XmALIGNMENT_LEFT,
1256         XmNcolumnSizePolicy, XmVARIABLE,
1257         NULL);
1258     XtVaSetValues(grid,
1259         XmNcolumnRangeStart, 1,
1260         XmNcolumnRangeEnd, 1,
1261         XmNcolumnWidth, 9,
1262         XmNcellAlignment, XmALIGNMENT_LEFT,
1263         XmNcolumnSizePolicy, XmVARIABLE,
1264         NULL);
1265     XtVaSetValues(grid,
1266         XmNcolumnRangeStart, 2,
1267         XmNcolumnRangeEnd, 2,
1268         XmNcolumnWidth, 16,
1269         XmNcellAlignment, XmALIGNMENT_RIGHT,
1270         XmNcolumnSizePolicy, XmVARIABLE,
1271         NULL);
1272     
1273     FileListDetailAdjustColWidth(grid);
1274 }
1275
1276 static void FileListDetailSelect(Widget fsb, Widget view, const char *item) {
1277     XnFileSelectionBox w = (XnFileSelectionBox)fsb;
1278     
1279     int numRows = 0;
1280     XtVaGetValues(w->fsb.grid, XmNrows, &numRows, NULL);
1281     
1282     XmLGridColumn col = XmLGridGetColumn(w->fsb.grid, XmCONTENT, 0);
1283     for(int i=0;i<numRows;i++) {
1284         XmLGridRow row = XmLGridGetRow(w->fsb.grid, XmCONTENT, i);
1285         FileElm *elm = NULL;
1286         XtVaGetValues(w->fsb.grid, XmNrowPtr, row, XmNcolumnPtr, col, XmNrowUserData, &elm, NULL);
1287         if(elm) {
1288             if(!strcmp(item, FileName(elm->path))) {
1289                 XmLGridSelectRow(w->fsb.grid, i, False);
1290                 XmLGridFocusAndShowRow(w->fsb.grid, i+1);
1291                 break;
1292             }
1293         }
1294     }
1295 }
1296
1297 static void FileListDetailCleanup(Widget fsb, Widget view, void *userData) {
1298     XnFileSelectionBox data = userData;
1299     // cleanup grid
1300     Cardinal rows = 0;
1301     XtVaGetValues(data->fsb.grid, XmNrows, &rows, NULL);
1302     XmLGridDeleteRows(data->fsb.grid, XmCONTENT, 0, rows);
1303 }
1304
1305 static void FileListDetailDestroy(Widget fsb, Widget view, void *userData) {
1306     // unused
1307 }
1308 #endif
1309
1310 static void create_folder(Widget w, XnFileSelectionBox data, XmSelectionBoxCallbackStruct *cbs) {
1311     char *fileName = NULL;
1312     XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &fileName);
1313     
1314     char *newFolder = ConcatPath(data->fsb.currentPath ? data->fsb.currentPath : "", fileName);
1315     if(mkdir(newFolder, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {
1316         char errmsg[256];
1317         snprintf(errmsg, 256, data->fsb.errorFolder, strerror(errno));
1318         ErrDialog(data, data->fsb.errorTitle, errmsg);
1319     } else {
1320         char *p = strdup(data->fsb.currentPath);
1321         filedialog_update_dir(data, p);
1322         free(p);
1323     }
1324     free(newFolder);
1325     
1326     XtDestroyWidget(XtParent(w));
1327 }
1328
1329 static void new_folder_cancel(Widget w, XnFileSelectionBox data, XtPointer d) {
1330     XtDestroyWidget(XtParent(w));
1331 }
1332
1333 static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u)
1334 {
1335     Arg args[16];
1336     int n = 0;
1337     
1338     XtSetArg(args[n], XmNdialogTitle, data->fsb.labelNewFolder); n++;
1339     XtSetArg (args[n], XmNselectionLabelString, data->fsb.labelDirectoryName); n++;
1340     XtSetArg(args[n], XmNokLabelString, data->fsb.labelOk); n++;
1341     XtSetArg(args[n], XmNcancelLabelString, data->fsb.labelCancel); n++;
1342     Widget dialog = XmCreatePromptDialog (w, "NewFolderPrompt", args, n);
1343     
1344     Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
1345     XtUnmanageChild(help);
1346     
1347     XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)create_folder, data);
1348     XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)new_folder_cancel, data);
1349     
1350     XtManageChild(dialog);
1351     
1352 }
1353
1354 static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u) {
1355     const char *homePath = data->fsb.homePath ? data->fsb.homePath : GetHomeDir();
1356     filedialog_update_dir(data, homePath);
1357     PathBarSetPath(data->fsb.pathBar, homePath);
1358 }
1359
1360
1361 /*
1362  * file_cmp_field
1363  * 0: compare path
1364  * 1: compare size
1365  * 2: compare mtime
1366  */
1367 static int file_cmp_field = 0;
1368
1369 /*
1370  * 1 or -1
1371  */
1372 static int file_cmp_order = 1;
1373
1374 static int filecmp(const void *f1, const void *f2)
1375 {
1376     const FileElm *file1 = f1;
1377     const FileElm *file2 = f2;
1378     if(file1->isDirectory != file2->isDirectory) {
1379         return file1->isDirectory < file2->isDirectory;
1380     }
1381     
1382     int cmp_field = file_cmp_field;
1383     int cmp_order = file_cmp_order;
1384     if(file1->isDirectory) {
1385         cmp_field = 0;
1386         cmp_order = 1;
1387     }
1388     
1389     int ret = 0;
1390     switch(cmp_field) {
1391         case 0: {
1392             ret = strcmp(FileName(file1->path), FileName(file2->path));
1393             break;
1394         }
1395         case 1: {
1396             if(file1->size < file2->size) {
1397                 ret = -1;
1398             } else if(file1->size == file2->size) {
1399                 ret = 0;
1400             } else {
1401                 ret = 1;
1402             }
1403             break;
1404         }
1405         case 2: {
1406             if(file1->lastModified < file2->lastModified) {
1407                 ret = -1;
1408             } else if(file1->lastModified == file2->lastModified) {
1409                 ret = 0;
1410             } else {
1411                 ret = 1;
1412             }
1413             break;
1414         }
1415     }
1416     
1417     return ret * cmp_order;
1418 }
1419
1420
1421 static void free_files(FileElm *ls, int count)
1422 {
1423     for(int i=0;i<count;i++) {
1424         if(ls[i].path) {
1425             free(ls[i].path);
1426         }
1427     }
1428     free(ls);
1429 }
1430
1431 static void filedialog_cleanup_filedata(XnFileSelectionBox data)
1432 {
1433     free_files(data->fsb.dirs, data->fsb.dircount);
1434     free_files(data->fsb.files, data->fsb.filecount);
1435     data->fsb.dirs = NULL;
1436     data->fsb.files = NULL;
1437     data->fsb.dircount = 0;
1438     data->fsb.filecount = 0;
1439     data->fsb.maxnamelen = 0;
1440 }
1441
1442 #define FILE_ARRAY_SIZE 1024
1443
1444 static void file_array_add(FileElm **files, int *alloc, int *count, FileElm elm) {
1445     int c = *count;
1446     int a = *alloc;
1447     if(c >= a) {
1448         a *= 2;
1449         FileElm *newarray = realloc(*files, sizeof(FileElm) * a);
1450         
1451         *files = newarray;
1452         *alloc = a;
1453     }
1454     
1455     (*files)[c] = elm;
1456     c++;
1457     *count = c;
1458 }
1459
1460 static int filedialog_update_dir(XnFileSelectionBox data, const char *path)
1461 {
1462     DIR *dir = NULL;
1463     if(path) {
1464         // try to check first, if we can open the path
1465         dir = opendir(path);
1466         if(!dir) {
1467             char errmsg[256];
1468             snprintf(errmsg, 256, data->fsb.errorOpenDir, strerror(errno));
1469             
1470             ErrDialog(data, data->fsb.errorTitle, errmsg);
1471             return 1;
1472         }
1473     }
1474     
1475     FSBView view = data->fsb.view[data->fsb.selectedview];
1476     view.cleanup((Widget)data, view.widget, view.userData);
1477       
1478     if(view.useDirList) {
1479         XmListDeleteAllItems(data->fsb.dirlist);
1480     }
1481     
1482     /* read dir and insert items */
1483     if(path) {
1484         int dircount = 0; 
1485         int filecount = 0;
1486         size_t maxNameLen = 0;
1487         
1488         FileElm *dirs = calloc(sizeof(FileElm), FILE_ARRAY_SIZE);
1489         FileElm *files = calloc(sizeof(FileElm), FILE_ARRAY_SIZE);
1490         int dirs_alloc = FILE_ARRAY_SIZE;
1491         int files_alloc = FILE_ARRAY_SIZE;
1492         
1493         filedialog_cleanup_filedata(data);
1494     
1495         /* dir reading complete - set the path textfield */  
1496         XmTextFieldSetString(data->fsb.path, (char*)path);
1497         char *oldPath = data->fsb.currentPath;
1498         data->fsb.currentPath = strdup(path);
1499         if(oldPath) {
1500             free(oldPath);
1501         }
1502         path = data->fsb.currentPath;
1503
1504         struct dirent *ent;
1505         while((ent = readdir(dir)) != NULL) {
1506             if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) {
1507                 continue;
1508             }
1509
1510             char *entpath = ConcatPath(path, ent->d_name);
1511
1512             struct stat s;
1513             if(stat(entpath, &s)) {
1514                 free(entpath);
1515                 continue;
1516             }
1517
1518             FileElm new_entry;
1519             new_entry.path = entpath;
1520             new_entry.isDirectory = S_ISDIR(s.st_mode);
1521             new_entry.size = (uint64_t)s.st_size;
1522             new_entry.lastModified = s.st_mtime;
1523
1524             size_t nameLen = strlen(ent->d_name);
1525             if(nameLen > maxNameLen) {
1526                 maxNameLen = nameLen;
1527             }
1528
1529             if(new_entry.isDirectory) {
1530                 file_array_add(&dirs, &dirs_alloc, &dircount, new_entry);
1531             } else {
1532                 file_array_add(&files, &files_alloc, &filecount, new_entry);
1533             }
1534         }
1535         closedir(dir);
1536         
1537         data->fsb.dirs = dirs;
1538         data->fsb.files = files;
1539         data->fsb.dircount = dircount;
1540         data->fsb.filecount = filecount;
1541         data->fsb.maxnamelen = maxNameLen;
1542         
1543         // sort file arrays
1544         qsort(dirs, dircount, sizeof(FileElm), filecmp);
1545         qsort(files, filecount, sizeof(FileElm), filecmp);
1546     }
1547     
1548     Widget filterTF = XmDropDownGetText(data->fsb.filter);
1549     char *filter = XmTextFieldGetString(filterTF);
1550     char *filterStr = filter;
1551     if(!filter || strlen(filter) == 0) {
1552         filterStr = "*";
1553     }
1554     
1555     if(view.useDirList) {
1556         FileListWidgetAdd(data, data->fsb.dirlist, data->fsb.showHidden, NULL, data->fsb.dirs, data->fsb.dircount);
1557         view.update(
1558                 (Widget)data,
1559                 view.widget,
1560                 NULL,
1561                 0,
1562                 data->fsb.files,
1563                 data->fsb.filecount,
1564                 filterStr,
1565                 data->fsb.maxnamelen,
1566                 view.userData);
1567     } else {
1568         view.update(
1569                 (Widget)data,
1570                 view.widget,
1571                 data->fsb.dirs,
1572                 data->fsb.dircount,
1573                 data->fsb.files,
1574                 data->fsb.filecount,
1575                 filterStr,
1576                 data->fsb.maxnamelen,
1577                 view.userData);
1578     }
1579     
1580     if(filter) {
1581         XtFree(filter);
1582     }
1583     
1584     return 0;
1585 }
1586
1587
1588 static void dirlist_activate(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
1589 {
1590     char *path = set_selected_path(data, cb->item);
1591     if(path) {
1592         if(!filedialog_update_dir(data, path)) {
1593             PathBarSetPath(data->fsb.pathBar, path);
1594             data->fsb.selIsDir = TRUE;
1595         } 
1596     }    
1597 }
1598
1599 static void dirlist_select(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb)
1600 {
1601     char *path = set_selected_path(data, cb->item);
1602     if(path) {
1603         data->fsb.selIsDir = TRUE;
1604     }
1605 }
1606
1607 static void filedialog_enable_detailview(Widget w, XnFileSelectionBox data, XmToggleButtonCallbackStruct *tb) {
1608     SelectView(data, tb->set); // 0: list, 1: detail
1609 }
1610
1611
1612 static void filedialog_setshowhidden(
1613         Widget w,
1614         XnFileSelectionBox data,
1615         XmToggleButtonCallbackStruct *tb)
1616 {
1617     data->fsb.showHidden = tb->set;
1618     filedialog_update_dir(data, NULL);
1619 }
1620
1621 static void filedialog_filter(Widget w, XnFileSelectionBox data, XtPointer c)
1622 {
1623     filedialog_update_dir(data, NULL);
1624 }
1625
1626 static void filedialog_update_filter(Widget w, XnFileSelectionBox data, XtPointer c)
1627 {
1628     filedialog_update_dir(data, NULL);
1629     
1630 }
1631
1632 static void filedialog_goup(Widget w, XnFileSelectionBox data, XtPointer d)
1633 {
1634     char *newPath = ParentPath(data->fsb.currentPath);
1635     filedialog_update_dir(data, newPath);
1636     PathBarSetPath(data->fsb.pathBar, newPath);
1637     free(newPath);
1638 }
1639
1640 static void filedialog_ok(Widget w, XnFileSelectionBox data, XtPointer d)
1641 {
1642     if(data->fsb.type == FILEDIALOG_SAVE) {
1643         char *newName = XmTextFieldGetString(data->fsb.name);
1644         if(newName) {
1645             if(strchr(newName, '/')) {
1646                 ErrDialog(data, data->fsb.errorTitle, data->fsb.errorIllegalChar);
1647                 XtFree(newName);
1648                 return;
1649             }
1650             
1651             if(strlen(newName) > 0) {
1652                 char *selPath = ConcatPath(data->fsb.currentPath, newName);
1653                 if(data->fsb.selectedPath) free(data->fsb.selectedPath);
1654                 data->fsb.selectedPath = selPath;
1655             }
1656             XtFree(newName);
1657             
1658             data->fsb.selIsDir = False;
1659         }
1660     }
1661     
1662     if(data->fsb.selectedPath) {
1663         if(!data->fsb.selIsDir) {
1664             data->fsb.status = FILEDIALOG_OK;
1665             data->fsb.end = True;
1666             FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath);
1667         }
1668     }
1669 }
1670
1671 static void filedialog_cancel(Widget w, XnFileSelectionBox data, XtPointer d)
1672 {
1673     data->fsb.end = 1;
1674     data->fsb.status = FILEDIALOG_CANCEL;
1675     FileSelectionCallback(data, data->fsb.cancelCallback, XmCR_CANCEL, data->fsb.currentPath);
1676 }
1677
1678 static void filedialog_help(Widget w, XnFileSelectionBox data, XtPointer d)
1679 {
1680     FileSelectionCallback(data, data->manager.help_callback, XmCR_HELP, data->fsb.currentPath);
1681 }
1682
1683 static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value) {
1684     XmFileSelectionBoxCallbackStruct cbs;
1685     memset(&cbs, 0, sizeof(XmFileSelectionBoxCallbackStruct));
1686     
1687     char *dir = fsb->fsb.currentPath;
1688     size_t dirlen = dir ? strlen(dir) : 0;
1689     if(dir && dirlen > 0) {
1690         char *dir2 = NULL;
1691         if(dir[dirlen-1] != '/') {
1692             // add a trailing / to the dir string 
1693             dir2 = malloc(dirlen+2);
1694             memcpy(dir2, dir, dirlen);
1695             dir2[dirlen] = '/';
1696             dir2[dirlen+1] = '\0';
1697             dirlen++;
1698             dir = dir2;
1699         }
1700         cbs.dir = XmStringCreateLocalized(dir);
1701         cbs.dir_length = dirlen;
1702         if(dir2) {
1703             free(dir2);
1704         }
1705     } else {
1706         cbs.dir = XmStringCreateLocalized("");
1707         cbs.dir_length = 0;
1708     }
1709     cbs.reason = reason;
1710     
1711     cbs.value = XmStringCreateLocalized((char*)value);
1712     cbs.length = strlen(value);
1713     
1714     XtCallCallbackList((Widget)fsb, cb, (XtPointer)&cbs);
1715     
1716     XmStringFree(cbs.dir);
1717     XmStringFree(cbs.value);
1718 }
1719
1720 static void CreateUI(XnFileSelectionBox w) { 
1721     Arg args[32];
1722     int n = 0;
1723     XmString str;
1724        
1725     int widget_spacing = w->fsb.widgetSpacing;
1726     int window_spacing = w->fsb.windowSpacing;
1727     
1728     Widget form = (Widget)w;
1729     int type = w->fsb.type;
1730     
1731     XtVaSetValues((Widget)w, XmNautoUnmanage, False, NULL);
1732      
1733     /* upper part of the gui */
1734     
1735     n = 0;
1736     XtSetArg(args[n], XmNlabelString, w->fsb.labelDirUp); n++;
1737     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1738     XtSetArg(args[n], XmNtopOffset, window_spacing); n++;
1739     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
1740     XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
1741     XtSetArg(args[n], XmNresizable, True); n++;
1742     XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++;
1743     w->fsb.dirUp = XmCreatePushButton(form, "DirUp", args, n);
1744     XtManageChild(w->fsb.dirUp);
1745     XtAddCallback(w->fsb.dirUp, XmNactivateCallback,
1746                  (XtCallbackProc)filedialog_goup, w);
1747     
1748     // View Option Menu
1749     n = 0;
1750     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1751     XtSetArg(args[n], XmNtopOffset, window_spacing); n++;
1752     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1753     XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
1754     XtSetArg(args[n], XmNshadowThickness, 0); n++;
1755     Widget viewframe = XmCreateForm(form, "vframe", args, n);
1756     XtManageChild(viewframe);
1757
1758     w->fsb.viewMenu = XmCreatePulldownMenu(viewframe, "menu", NULL, 0);
1759     
1760     Widget view;
1761     if(w->fsb.showViewMenu) {
1762         n = 0;
1763         XtSetArg(args[n], XmNsubMenuId, w->fsb.viewMenu); n++;
1764         XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1765         XtSetArg(args[n], XmNmarginHeight, 0); n++;
1766         XtSetArg(args[n], XmNmarginWidth, 0); n++;
1767         view = XmCreateOptionMenu(viewframe, "option_menu", args, n);
1768         XtManageChild(view);
1769         w->fsb.viewOption = view;
1770         w->fsb.detailToggleButton = NULL;
1771     } else {
1772         n = 0;
1773         str = XmStringCreateLocalized(w->fsb.labelDetailView);
1774         XtSetArg(args[n], XmNlabelString, str); n++;
1775         XtSetArg(args[n], XmNfillOnSelect, True); n++;
1776         XtSetArg(args[n], XmNindicatorOn, False); n++;
1777         XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1778         XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1779         XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1780         if(w->fsb.selectedview == 1) {
1781             XtSetArg(args[n], XmNset, 1); n++;
1782         }
1783         w->fsb.detailToggleButton = XmCreateToggleButton(viewframe, "ToggleDetailView", args, n);
1784         XtManageChild(w->fsb.detailToggleButton);
1785         view = w->fsb.detailToggleButton;
1786         XmStringFree(str);
1787         
1788         XtAddCallback(
1789             w->fsb.detailToggleButton,
1790             XmNvalueChangedCallback,
1791             (XtCallbackProc)filedialog_enable_detailview,
1792             w);
1793         
1794         w->fsb.viewOption = NULL;
1795     }    
1796
1797     n = 0;
1798     XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
1799     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1800     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1801     XtSetArg(args[n], XmNrightWidget, view); n++;
1802     XtSetArg(args[n], XmNmarginHeight, 0); n++;
1803     XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
1804     XtSetArg(args[n], XmNlabelString, w->fsb.labelNewFolder); n++;
1805     w->fsb.newFolder = XmCreatePushButton(viewframe, "NewFolder", args, n);
1806     XtManageChild(w->fsb.newFolder);
1807     XtAddCallback(
1808             w->fsb.newFolder,
1809             XmNactivateCallback,
1810             (XtCallbackProc)FSBNewFolder,
1811             w);
1812     
1813
1814     n = 0;
1815     XtSetArg(args[n], XmNlabelString, w->fsb.labelHome); n++;
1816     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
1817     XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
1818     XtSetArg(args[n], XmNrightWidget, w->fsb.newFolder); n++;
1819     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1820     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1821     w->fsb.home = XmCreatePushButton(viewframe, "Home", args, n);
1822     XtManageChild(w->fsb.home);
1823     XtAddCallback(
1824             w->fsb.home,
1825             XmNactivateCallback,
1826             (XtCallbackProc)FSBHome,
1827             w);
1828     
1829     // match visual appearance of detailToggleButton with the other buttons
1830     if(w->fsb.detailToggleButton) {
1831         Dimension highlight, shadow;
1832         XtVaGetValues(w->fsb.newFolder, XmNshadowThickness, &shadow, XmNhighlightThickness, &highlight, NULL);
1833         XtVaSetValues(w->fsb.detailToggleButton, XmNshadowThickness, shadow, XmNhighlightThickness, highlight, NULL);
1834     }
1835     
1836     // pathbar
1837     n = 0;
1838     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1839     XtSetArg(args[n], XmNtopOffset, window_spacing); n++;
1840     XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
1841     XtSetArg(args[n], XmNleftWidget, w->fsb.dirUp); n++;
1842     XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
1843     XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
1844     XtSetArg(args[n], XmNrightWidget, viewframe); n++;
1845     XtSetArg(args[n], XmNrightOffset, widget_spacing); n++;
1846     XtSetArg(args[n], XmNshadowType, XmSHADOW_IN); n++;
1847     Widget pathBarFrame = XmCreateFrame(form, "pathbar_frame", args, n);
1848     XtManageChild(pathBarFrame);
1849     w->fsb.pathBar = CreatePathBar(pathBarFrame, args, 0);
1850     w->fsb.pathBar->updateDir = (updatedir_callback)filedialog_update_dir;
1851     w->fsb.pathBar->updateDirData = w;
1852     XtManageChild(w->fsb.pathBar->widget);
1853     w->fsb.path = XmCreateTextField(form, "textfield", args, 0);
1854     
1855     XtVaSetValues(w->fsb.dirUp, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL);
1856     if(!w->fsb.showViewMenu) {
1857         XtVaSetValues(viewframe, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL);
1858     }
1859     
1860     n = 0;
1861     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
1862     XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
1863     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
1864     XtSetArg(args[n], XmNtopWidget, pathBarFrame); n++;
1865     XtSetArg(args[n], XmNtopOffset, 2*widget_spacing); n++;
1866     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1867     XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
1868     w->fsb.filterForm = XmCreateForm(form, "filterform", args, n);
1869     XtManageChild(w->fsb.filterForm);
1870     
1871     n = 0;
1872     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1873     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
1874     XtSetArg(args[n], XmNlabelString, w->fsb.labelDirectories); n++;
1875     w->fsb.lsDirLabel = XmCreateLabel(w->fsb.filterForm, "labelDirs", args, n);
1876     XtManageChild(w->fsb.lsDirLabel);
1877     
1878     n = 0;
1879     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1880     XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
1881     XtSetArg(args[n], XmNleftPosition, 35); n++;
1882     XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
1883     XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++;
1884     w->fsb.lsFileLabel = XmCreateLabel(w->fsb.filterForm, "labelFiles", args, n);
1885     XtManageChild(w->fsb.lsFileLabel);
1886       
1887     if(w->fsb.showHiddenButton) {
1888         n = 0;
1889         XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1890         XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1891         XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1892         XtSetArg(args[n], XmNlabelString, w->fsb.labelShowHiddenFiles); n++;
1893         XtSetArg(args[n], XmNset, w->fsb.showHidden); n++;
1894         w->fsb.showHiddenButtonW = XmCreateToggleButton(w->fsb.filterForm, "showHidden", args, n);
1895         XtManageChild(w->fsb.showHiddenButtonW);
1896         XtAddCallback(w->fsb.showHiddenButtonW, XmNvalueChangedCallback,
1897                      (XtCallbackProc)filedialog_setshowhidden, w);
1898     }
1899     
1900     n = 0;
1901     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1902     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1903     XtSetArg(args[n], XmNlabelString, w->fsb.labelFilterButton); n++;
1904     if(w->fsb.showHiddenButton) {
1905         XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
1906         XtSetArg(args[n], XmNrightWidget, w->fsb.showHiddenButtonW); n++;
1907     } else {
1908         XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1909     }
1910     w->fsb.filterButton = XmCreatePushButton(w->fsb.filterForm, "filedialog_filter", args, n);
1911     XtManageChild(w->fsb.filterButton);
1912     XtAddCallback(w->fsb.filterButton, XmNactivateCallback,
1913                  (XtCallbackProc)filedialog_filter, w);
1914     
1915     n = 0;
1916     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1917     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1918     XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
1919     XtSetArg(args[n], XmNleftWidget, w->fsb.lsFileLabel); n++;
1920     XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
1921     XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
1922     XtSetArg(args[n], XmNrightWidget, w->fsb.filterButton); n++;
1923     XtSetArg(args[n], XmNrightOffset, widget_spacing); n++;
1924     XtSetArg(args[n], XmNshowLabel, False); n++;
1925     XtSetArg(args[n], XmNuseTextField, True); n++;
1926     XtSetArg(args[n], XmNverify, False); n++;
1927     w->fsb.filter = XmCreateDropDown(w->fsb.filterForm, "filedialog_filter_textfield", args, n);
1928     XtManageChild(w->fsb.filter);
1929     XmTextFieldSetString(XmDropDownGetText(w->fsb.filter), w->fsb.filterStr);
1930     XtAddCallback(XmDropDownGetText(w->fsb.filter), XmNactivateCallback,
1931                  (XtCallbackProc)filedialog_filter, w);
1932     XtAddCallback(w->fsb.filter, XmNupdateTextCallback,
1933                  (XtCallbackProc)filedialog_update_filter, w);
1934     Widget filterList = XmDropDownGetList(w->fsb.filter);
1935     str = XmStringCreateSimple("*");
1936     XmListAddItem(filterList, str, 0);
1937     XmStringFree(str);
1938         
1939     /* lower part */
1940     n = 0;
1941     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1942     XtSetArg(args[n], XmNbottomOffset, window_spacing); n++;
1943     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
1944     XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
1945     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1946     XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
1947     XtSetArg(args[n], XmNtopOffset, widget_spacing * 2); n++;
1948     Widget buttons = XmCreateForm(form, "buttons", args, n);
1949     XtManageChild(buttons);
1950     
1951     n = 0;
1952     str = type == FILEDIALOG_OPEN ? w->fsb.labelOpen : w->fsb.labelSave;
1953     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1954     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1955     XtSetArg(args[n], XmNlabelString, str); n++;
1956     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
1957     XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
1958     XtSetArg(args[n], XmNrightPosition, 14); n++;
1959     w->fsb.okBtn = XmCreatePushButton(buttons, "filedialog_open", args, n);
1960     XtManageChild(w->fsb.okBtn);
1961     XmStringFree(str);
1962     XtAddCallback(w->fsb.okBtn, XmNactivateCallback,
1963                  (XtCallbackProc)filedialog_ok, w);
1964     
1965     n = 0;
1966     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1967     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1968     XtSetArg(args[n], XmNlabelString, w->fsb.labelHelp); n++;
1969     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1970     XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
1971     XtSetArg(args[n], XmNleftPosition, 86); n++;
1972     w->fsb.helpBtn = XmCreatePushButton(buttons, "filedialog_help", args, n);
1973     XtManageChild(w->fsb.helpBtn);
1974     XtAddCallback(w->fsb.helpBtn, XmNactivateCallback,
1975                  (XtCallbackProc)filedialog_help, w);
1976     
1977     n = 0;
1978     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
1979     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
1980     XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
1981     XtSetArg(args[n], XmNleftPosition, 43); n++;
1982     XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
1983     XtSetArg(args[n], XmNrightPosition, 57); n++;
1984     XtSetArg(args[n], XmNlabelString, w->fsb.labelCancel); n++;
1985     w->fsb.cancelBtn = XmCreatePushButton(buttons, "filedialog_cancel", args, n);
1986     XtManageChild(w->fsb.cancelBtn);
1987     XtAddCallback(w->fsb.cancelBtn, XmNactivateCallback,
1988                  (XtCallbackProc)filedialog_cancel, w);
1989     
1990     n = 0;
1991     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
1992     XtSetArg(args[n], XmNbottomWidget, buttons); n++;
1993     XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
1994     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
1995     XtSetArg(args[n], XmNleftOffset, 1); n++;
1996     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
1997     XtSetArg(args[n], XmNrightOffset, 1); n++;
1998     w->fsb.separator = XmCreateSeparator(form, "ofd_separator", args, n);
1999     XtManageChild(w->fsb.separator);
2000     
2001     Widget bottomWidget = w->fsb.separator;
2002     
2003     n = 0;
2004     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
2005     XtSetArg(args[n], XmNbottomWidget, w->fsb.separator); n++;
2006     XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
2007     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
2008     XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
2009     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
2010     XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
2011     w->fsb.name = XmCreateTextField(form, "textfield", args, n);
2012     XtAddCallback(w->fsb.name, XmNactivateCallback,
2013              (XtCallbackProc)filedialog_ok, w);
2014
2015     n = 0;
2016     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
2017     XtSetArg(args[n], XmNbottomWidget, w->fsb.name); n++;
2018     XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
2019     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
2020     XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
2021     XtSetArg(args[n], XmNlabelString, w->fsb.labelFileName); n++;
2022     w->fsb.nameLabel = XmCreateLabel(form, "label", args, n);
2023         
2024     if(type == FILEDIALOG_SAVE) {
2025         bottomWidget = w->fsb.nameLabel;
2026         XtManageChild(w->fsb.name);
2027         XtManageChild(w->fsb.nameLabel);
2028     }
2029     w->fsb.bottom_widget = bottomWidget;
2030     
2031     
2032     // middle 
2033     // form for dir/file lists
2034     n = 0;
2035     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
2036     XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
2037     XtSetArg(args[n], XmNtopWidget, w->fsb.filterForm); n++;
2038     XtSetArg(args[n], XmNtopOffset, widget_spacing); n++;
2039     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
2040     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
2041     XtSetArg(args[n], XmNbottomWidget, bottomWidget); n++;
2042     XtSetArg(args[n], XmNleftOffset, window_spacing); n++;
2043     XtSetArg(args[n], XmNrightOffset, window_spacing); n++;
2044     XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
2045     XtSetArg(args[n], XmNwidth, 580); n++;
2046     XtSetArg(args[n], XmNheight, 400); n++;
2047     w->fsb.listform = XmCreateForm(form, "fds_listform", args, n); 
2048     
2049     // dir/file lists
2050     
2051     n = 0;
2052     XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
2053     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
2054     XtSetArg(args[n], XmNtopWidget, w->fsb.lsDirLabel); n++;
2055     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
2056     XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
2057     XtSetArg(args[n], XmNrightPosition, 35); n++;
2058     w->fsb.dirlist = XmCreateScrolledList(w->fsb.listform, "dirlist", args, n);
2059     Dimension width, height;
2060     XtMakeResizeRequest(w->fsb.dirlist, 150, 200, &width, &height);
2061     XtManageChild(w->fsb.dirlist);
2062     
2063     XtAddCallback(
2064             w->fsb.dirlist,
2065             XmNdefaultActionCallback,
2066             (XtCallbackProc)dirlist_activate,
2067             w); 
2068     XtAddCallback(
2069             w->fsb.dirlist,
2070             XmNbrowseSelectionCallback,
2071             (XtCallbackProc)dirlist_select,
2072             w);
2073     
2074     // FileList
2075     XnFileSelectionBoxAddView(
2076             (Widget)w,
2077             w->fsb.labelListView,
2078             CreateListView,
2079             FileListUpdate,
2080             FileListSelect,
2081             FileListCleanup,
2082             FileListDestroy,
2083             True,
2084             w);
2085     
2086     // Detail FileList
2087 #ifdef FSB_ENABLE_DETAIL
2088     XnFileSelectionBoxAddView(
2089             (Widget)w,
2090             w->fsb.labelDetailView,
2091             CreateDetailView,
2092             FileListDetailUpdate,
2093             FileListDetailSelect,
2094             FileListDetailCleanup,
2095             FileListDetailDestroy,
2096             True,
2097             w);
2098 #endif
2099        
2100     /*
2101     n = 0;
2102     XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
2103     XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++;
2104     XtSetArg(args[n], XmNleftOffset, widget_spacing); n++;
2105     //XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
2106     //XtSetArg(args[n], XmNbottomWidget, w->fsb.filelist); n++;
2107     XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++;
2108     XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
2109     XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++;
2110     w->fsb.lsFileLabel = XmCreateLabel(w->fsb.listform, "label", args, n);
2111     XtManageChild(w->fsb.lsFileLabel);
2112     */
2113     
2114     XtManageChild(w->fsb.listform);
2115     
2116     int selview = w->fsb.selectedview;
2117     if(selview < 2) {
2118         XtManageChild(w->fsb.view[selview].widget);
2119     } else {
2120         w->fsb.selectedview = 0;
2121     }
2122     
2123     
2124     if(w->fsb.selectedPath) {
2125         char *parentPath = ParentPath(w->fsb.selectedPath);
2126         filedialog_update_dir(w, parentPath);
2127         PathBarSetPath(w->fsb.pathBar, parentPath);
2128         free(parentPath);
2129         
2130         if(w->fsb.type == FILEDIALOG_SAVE) {
2131             XmTextFieldSetString(w->fsb.name, FileName(w->fsb.selectedPath));
2132         }
2133     } else {
2134         char cwd[PATH_MAX];
2135         const char *currentPath = w->fsb.currentPath;
2136         if(!currentPath) {
2137             if(getcwd(cwd, PATH_MAX)) {
2138                 currentPath = cwd;
2139             } else {
2140                 currentPath = GetHomeDir();
2141             }
2142         }
2143         
2144         filedialog_update_dir(w, currentPath);
2145         PathBarSetPath(w->fsb.pathBar, w->fsb.currentPath);
2146     }
2147
2148
2149     w->fsb.selectedview = selview;
2150          
2151     XtVaSetValues((Widget)w, XmNcancelButton, w->fsb.cancelBtn, NULL);
2152     
2153     w->fsb.gui_created = 1;
2154 }
2155
2156 static char* FSBDialogTitle(Widget widget) {
2157     XnFileSelectionBox w = (XnFileSelectionBox)widget;
2158     if(w->fsb.type == FILEDIALOG_OPEN) {
2159         return w->fsb.labelOpenFileTitle;
2160     } else {
2161         return w->fsb.labelSaveFileTitle;
2162     }
2163 }
2164
2165 static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc create, void *userData, Boolean useDirList) {
2166     Arg args[64];
2167     int n = 0;
2168     
2169     XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
2170     XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
2171     if(useDirList) {
2172         XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
2173         XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
2174         XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++;
2175         XtSetArg(args[n], XmNleftOffset, w->fsb.widgetSpacing); n++;
2176     } else {
2177         XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
2178         XtSetArg(args[n], XmNtopOffset, w->fsb.widgetSpacing); n++;
2179         XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
2180     }
2181     
2182     return create(w->fsb.listform, args, n, userData);
2183 }
2184
2185
2186 typedef struct FSBViewSelection {
2187     XnFileSelectionBox fsb;
2188     int index;
2189 } FSBViewSelection;
2190
2191 static void SelectView(XnFileSelectionBox f, int view) {
2192     FSBView current = f->fsb.view[f->fsb.selectedview];
2193     FSBView newview = f->fsb.view[view];
2194     
2195     XtUnmanageChild(current.widget);
2196     if(newview.useDirList != current.useDirList) {
2197         if(current.useDirList) {
2198             XtUnmanageChild(f->fsb.listform);
2199         } else {
2200             XtManageChild(f->fsb.listform);
2201         }
2202     }
2203     
2204     current.cleanup((Widget)f, current.widget, current.userData);
2205     XtManageChild(newview.widget);
2206     
2207     f->fsb.selectedview = view;
2208     
2209     filedialog_update_dir(f, NULL);
2210     XmProcessTraversal(newview.focus, XmTRAVERSE_CURRENT);
2211 }
2212
2213 static void SelectViewCallback(Widget w, FSBViewSelection *data, XtPointer u) {
2214     SelectView(data->fsb, data->index);
2215 }
2216
2217 static void SelectViewItemDestroy(Widget w, FSBViewSelection *data, XtPointer u) {
2218     free(data);
2219 }
2220
2221 static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex) {
2222     Arg args[4];
2223     int n = 0;
2224     
2225     XmString label = XmStringCreateLocalized((char*)name);
2226     
2227     XtSetArg(args[n], XmNlabelString, label); n++;
2228     XtSetArg(args[1], XmNpositionIndex, w->fsb.selectedview == w->fsb.numviews ? 0 : w->fsb.numviews+1); n++;
2229     Widget item = XmCreatePushButton(w->fsb.viewMenu, "menuitem", args, n);
2230     
2231     if(viewIndex == 0) {
2232         w->fsb.viewSelectorList = item;
2233     } else if(viewIndex == 1) {
2234         w->fsb.viewSelectorDetail = item;
2235     }
2236    
2237     XtManageChild(item);
2238     XmStringFree(label);
2239     
2240     FSBViewSelection *data = malloc(sizeof(FSBViewSelection));
2241     data->fsb = w;
2242     data->index = viewIndex;
2243     
2244     XtAddCallback(
2245             item,
2246             XmNactivateCallback,
2247             (XtCallbackProc)SelectViewCallback,
2248             data);
2249     XtAddCallback(
2250             item,
2251             XmNdestroyCallback,
2252             (XtCallbackProc)SelectViewItemDestroy,
2253             data);
2254 }
2255
2256 static FSBViewWidgets CreateListView(Widget parent, ArgList args, int n, void *userData) {
2257     XnFileSelectionBox fsb = (XnFileSelectionBox)userData;
2258     
2259     XtSetArg(args[n], XmNshadowThickness, 0); n++;
2260     Widget frame = XmCreateFrame(parent, "filelistframe", args, n);
2261     
2262     fsb->fsb.filelist = XmCreateScrolledList(frame, "filelist", NULL, 0);
2263     XtManageChild(fsb->fsb.filelist);
2264     
2265     XtAddCallback(
2266             fsb->fsb.filelist,
2267             XmNdefaultActionCallback,
2268             (XtCallbackProc)FileListActivateCB,
2269             userData); 
2270     XtAddCallback(
2271             fsb->fsb.filelist,
2272             XmNbrowseSelectionCallback,
2273             (XtCallbackProc)FileListSelectCB,
2274             userData);
2275     
2276     fsb->fsb.listContextMenu = CreateContextMenu(fsb, fsb->fsb.filelist, FileContextMenuCB);
2277     
2278     FSBViewWidgets widgets;
2279     widgets.view = frame;
2280     widgets.focus = fsb->fsb.filelist;
2281     return widgets;
2282 }
2283
2284 #ifdef FSB_ENABLE_DETAIL
2285 static void set_path_from_row(XnFileSelectionBox data, int row) {
2286     FileElm *elm = NULL;
2287     XmLGridRow rowPtr = XmLGridGetRow(data->fsb.grid, XmCONTENT, row);
2288     XtVaGetValues(data->fsb.grid, XmNrowPtr, rowPtr, XmNrowUserData, &elm, NULL);
2289     if(!elm) {
2290         fprintf(stderr, "error: no row data\n");
2291         return;
2292     }
2293     
2294     char *path = strdup(elm->path);
2295     
2296     data->fsb.selIsDir = False;
2297     if(data->fsb.type == FILEDIALOG_SAVE) {
2298         XmTextFieldSetString(data->fsb.name, FileName(path));
2299     }
2300     
2301     if(data->fsb.selectedPath) {
2302         free(data->fsb.selectedPath);
2303     }
2304     data->fsb.selectedPath = path;
2305 }
2306
2307 static void grid_select(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) {
2308     set_path_from_row(data, cb->row);
2309 }
2310
2311 static void grid_activate(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) {
2312     set_path_from_row(data, cb->row);
2313     data->fsb.end = True;
2314     data->fsb.status = FILEDIALOG_OK;
2315     
2316     FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath);
2317 }
2318  
2319 static void grid_key_pressed(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) {
2320     char chars[16];
2321     KeySym keysym;
2322     int nchars;
2323     
2324     nchars = XLookupString(&cb->event->xkey, chars, 15, &keysym, NULL);
2325     
2326     if(nchars == 0) return;
2327
2328     // if data->showHidden is 0, data->files contains more items than the grid
2329     // this means SelectedRow might not be the correct index for data->files
2330     // we have to count files manually and increase 'row', if the file
2331     // is actually displayed in the grid
2332     int row = 0;
2333     int selectedRow = XmLGridGetSelectedRow(w);
2334     
2335     int match = -1;
2336     
2337     for(int i=0;i<data->fsb.filecount;i++) {
2338         const char *name = FileName(data->fsb.files[i].path);
2339         if(!data->fsb.showHidden && name[0] == '.') continue;
2340         
2341         size_t namelen = strlen(name);
2342         
2343         size_t cmplen = namelen < nchars ? namelen : nchars;
2344         if(!memcmp(name, chars, cmplen)) {
2345             if(row <= selectedRow) {
2346                 if(match == -1) {
2347                     match = row;
2348                 }
2349             } else {
2350                 match = row;
2351                 break;
2352             }
2353         }
2354         
2355         row++;
2356     }
2357     
2358     if(match > -1) {
2359         XmLGridSelectRow(w, match, True);
2360         XmLGridFocusAndShowRow(w, match+1);
2361     } else {
2362         XBell(XtDisplay(w), 0);
2363     }
2364 }
2365
2366 static void grid_header_clicked(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { 
2367     int new_cmp_field = 0;
2368     switch(cb->column) {
2369         case 0: {
2370             new_cmp_field = 0;            
2371             break;
2372         }
2373         case 1: {
2374             new_cmp_field = 1;
2375             break;
2376         }
2377         case 2: {
2378             new_cmp_field = 2;
2379             break;
2380         }
2381     }
2382     
2383     if(new_cmp_field == file_cmp_field) {
2384         file_cmp_order = -file_cmp_order; // revert sort order
2385     } else {
2386         file_cmp_field = new_cmp_field; // change file cmp order to new field
2387         file_cmp_order = 1;
2388     }
2389     
2390     int sort_type = file_cmp_order == 1 ? XmSORT_ASCENDING : XmSORT_DESCENDING;
2391     XmLGridSetSort(data->fsb.grid, file_cmp_field, sort_type);
2392     
2393     qsort(data->fsb.files, data->fsb.filecount, sizeof(FileElm), filecmp);
2394     
2395     // refresh widget
2396     filedialog_update_dir(data, NULL);
2397
2398
2399 static FSBViewWidgets CreateDetailView(Widget parent, ArgList args, int n, void *userData) {
2400     XnFileSelectionBox w = userData;
2401     
2402     XtSetArg(args[n], XmNshadowThickness, 0); n++;
2403     Widget gridcontainer = XmCreateFrame(parent, "gridcontainer", args, n);
2404     XtManageChild(gridcontainer);
2405     
2406     n = 0;
2407     XtSetArg(args[n], XmNcolumns, 3); n++;
2408     XtSetArg(args[n], XmNheadingColumns, 0); n++;
2409     XtSetArg(args[n], XmNheadingRows, 1); n++;
2410     XtSetArg(args[n], XmNallowColumnResize, 1); n++;
2411     XtSetArg(args[n], XmNsimpleHeadings, w->fsb.detailHeadings); n++;
2412     XtSetArg(args[n], XmNhorizontalSizePolicy, XmCONSTANT); n++;
2413     
2414     w->fsb.grid = XmLCreateGrid(gridcontainer, "grid", args, n);
2415     XmLGridSetIgnoreModifyVerify(w->fsb.grid, True);
2416     XtManageChild(w->fsb.grid);
2417     
2418     XtVaSetValues(
2419             w->fsb.grid,
2420             XmNcellDefaults, True,
2421             XtVaTypedArg, XmNblankBackground, XmRString, "white", 6,
2422             XtVaTypedArg, XmNcellBackground, XmRString, "white", 6,
2423             NULL);
2424     
2425     XtAddCallback(w->fsb.grid, XmNselectCallback, (XtCallbackProc)grid_select, w);
2426     XtAddCallback(w->fsb.grid, XmNactivateCallback, (XtCallbackProc)grid_activate, w);
2427     XtAddCallback(w->fsb.grid, XmNheaderClickCallback, (XtCallbackProc)grid_header_clicked, w);
2428     XtAddCallback(w->fsb.grid, XmNgridKeyPressedCallback, (XtCallbackProc)grid_key_pressed, w);
2429     
2430     // context menu
2431     w->fsb.gridContextMenu = CreateContextMenu(w, w->fsb.grid, FileContextMenuCB);
2432     
2433     FSBViewWidgets widgets;
2434     widgets.view = gridcontainer;
2435     widgets.focus = w->fsb.grid;
2436     return widgets;
2437 }
2438 #endif
2439
2440
2441 /* ------------------------------ Path Utils  ------------------------------ */
2442
2443 const char* GetHomeDir(void) {
2444     char *home = getenv("HOME");
2445     if(!home) {
2446         home = getenv("USERPROFILE");
2447         if(!home) {
2448             home = "/";
2449         }
2450     }
2451     return home;
2452 }
2453
2454 static char* ConcatPath(const char *parent, const char *name)
2455
2456     size_t parentlen = strlen(parent);
2457     size_t namelen = strlen(name);
2458     
2459     size_t pathlen = parentlen + namelen + 2;
2460     char *path = malloc(pathlen);
2461     
2462     memcpy(path, parent, parentlen);
2463     if(parentlen > 0 && parent[parentlen-1] != '/') {
2464         path[parentlen] = '/';
2465         parentlen++;
2466     }
2467     if(name[0] == '/') {
2468         name++;
2469         namelen--;
2470     }
2471     memcpy(path+parentlen, name, namelen);
2472     path[parentlen+namelen] = '\0';
2473     return path;
2474 }
2475
2476 static char* FileName(char *path) {
2477     int si = 0;
2478     int osi = 0;
2479     int i = 0;
2480     int p = 0;
2481     char c;
2482     while((c = path[i]) != 0) {
2483         if(c == '/') {
2484             osi = si;
2485             si = i;
2486             p = 1;
2487         }
2488         i++;
2489     }
2490     
2491     char *name = path + si + p;
2492     if(name[0] == 0) {
2493         name = path + osi + p;
2494         if(name[0] == 0) {
2495             return path;
2496         }
2497     }
2498     
2499     return name;
2500 }
2501
2502 static char* ParentPath(const char *path) {
2503     char *name = FileName((char*)path);
2504     size_t namelen = strlen(name);
2505     size_t pathlen = strlen(path);
2506     size_t parentlen = pathlen - namelen;
2507     if(parentlen == 0) {
2508         parentlen++;
2509     }
2510     char *parent = malloc(parentlen + 1);
2511     memcpy(parent, path, parentlen);
2512     parent[parentlen] = '\0';
2513     return parent;
2514 }
2515
2516 // unused at the moment, maybe reactivate if more illegal characters
2517 // are defined
2518 /*
2519 static int CheckFileName(const char *fileName) {
2520     size_t len = strlen(fileName);
2521     for(int i=0;i<len;i++) {
2522         if(fileName[i] == '/') {
2523             return 0;
2524         }
2525     }
2526     return 1;
2527 }
2528 */
2529
2530 /* ------------------------------ PathBar  ------------------------------ */
2531
2532 static void pathbar_resize(Widget w, PathBar *p, XtPointer d)
2533 {
2534     if(p->disableResize) return;
2535     
2536     Dimension width, height;
2537     XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL);
2538     
2539     Dimension xoff;
2540     XtVaGetValues(p->down, XmNwidth, &xoff, NULL);
2541     
2542     Dimension *segW = calloc(p->numSegments, sizeof(Dimension));
2543     
2544     Dimension maxHeight = 0;
2545     
2546     /* get width/height from all widgets */
2547     Dimension pathWidth = 0;
2548     for(int i=0;i<p->numSegments;i++) {
2549         Dimension segWidth;
2550         Dimension segHeight;
2551         XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL);
2552         segW[i] = segWidth;
2553         pathWidth += segWidth;
2554         if(segHeight > maxHeight) {
2555             maxHeight = segHeight;
2556         }
2557     }
2558     Dimension tfHeight;
2559     XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL);
2560     if(tfHeight > maxHeight) {
2561         maxHeight = tfHeight;
2562     }
2563     
2564     Boolean arrows = False;
2565     if(pathWidth + xoff + 10 > width) {
2566         arrows = True;
2567         //pathWidth += p->lw + p->rw;
2568     }
2569     
2570     /* calc max visible widgets */
2571     int start = 0;
2572     if(arrows) {
2573         Dimension vis = p->lw+p->rw;
2574         for(int i=p->numSegments;i>0;i--) {
2575             Dimension segWidth = segW[i-1];
2576             if(vis + segWidth + xoff + 10 > width) {
2577                 start = i;
2578                 arrows = True;
2579                 break;
2580             }
2581             vis += segWidth;
2582         }
2583     } else {
2584         p->shift = 0;
2585     }
2586     
2587     int leftShift = 0;
2588     if(p->shift < 0) {
2589         if(start + p->shift < 0) {
2590             leftShift = start;
2591             start = 0;
2592             p->shift = -leftShift;
2593         } else {
2594             leftShift = -p->shift; /* negative shift */
2595             start += p->shift;
2596         }
2597     }
2598     
2599     int x = 0;
2600     if(arrows) {
2601         XtManageChild(p->left);
2602         XtManageChild(p->right);
2603         x += p->lw;
2604     } else {
2605         XtUnmanageChild(p->left);
2606         XtUnmanageChild(p->right);
2607     }
2608     
2609     for(int i=0;i<p->numSegments;i++) {
2610         if(i >= start && i < p->numSegments - leftShift && !p->input) {
2611             XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL);
2612             x += segW[i];
2613             XtManageChild(p->pathSegments[i]);
2614         } else {
2615             XtUnmanageChild(p->pathSegments[i]);
2616         }
2617     }
2618     
2619     if(arrows) {
2620         XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL);
2621         XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL);
2622     }
2623     XtVaSetValues(p->down, XmNx, width-xoff, XmNheight, maxHeight, NULL);
2624     free(segW);
2625     
2626     Dimension rw, rh;
2627     XtMakeResizeRequest(w, width, maxHeight, &rw, &rh);
2628     XtVaSetValues(p->textfield, XmNwidth, rw-xoff, XmNheight, rh, NULL);
2629 }
2630
2631 static void pathbar_input(Widget w, PathBar *p, XtPointer c)
2632 {
2633     XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c;
2634     XEvent *xevent = cbs->event;
2635     
2636     if (cbs->reason == XmCR_INPUT) {
2637         if (xevent->xany.type == ButtonPress) {
2638             XtUnmanageChild(p->left);
2639             XtUnmanageChild(p->right);
2640             
2641             XtManageChild(p->textfield);
2642             p->input = 1;
2643             
2644             XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT);
2645             
2646             pathbar_resize(p->widget, p, NULL);
2647         }
2648     }
2649 }
2650
2651 static void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c)
2652 {
2653     p->input = False;
2654     XtUnmanageChild(p->textfield);
2655     pathbar_resize(p->widget, p, NULL);
2656 }
2657
2658 static void pathbar_pathinput(Widget w, PathBar *pb, XtPointer d)
2659 {
2660     char *newpath = XmTextFieldGetString(pb->textfield);
2661     if(newpath) {
2662         if(newpath[0] == '~') {
2663             char *p = newpath+1;
2664             char *cp = ConcatPath(GetHomeDir(), p);
2665             XtFree(newpath);
2666             newpath = cp;
2667         } else if(newpath[0] != '/') {
2668             char cwd[PATH_MAX];
2669             if(!getcwd(cwd, sizeof(cwd))) {
2670                 cwd[0] = '/';
2671                 cwd[1] = '\0';
2672             }
2673             char *cp = ConcatPath(cwd, newpath);
2674             XtFree(newpath);
2675             newpath = cp;
2676         }
2677         
2678         /* update path */
2679         if(pb->updateDir) {
2680             if(!pb->updateDir(pb->updateDirData, newpath)) {
2681                 PathBarSetPath(pb, newpath);
2682             }
2683         } else {
2684             PathBarSetPath(pb, newpath);
2685         }
2686         XtFree(newpath);
2687         
2688         /* hide textfield and show path as buttons */
2689         XtUnmanageChild(pb->textfield);
2690         pathbar_resize(pb->widget, pb, NULL);
2691     }
2692 }
2693
2694 static void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) {
2695     p->shift--;
2696     pathbar_resize(p->widget, p, NULL);
2697 }
2698
2699 static void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) {
2700     if(p->shift < 0) {
2701         p->shift++;
2702     }
2703     pathbar_resize(p->widget, p, NULL);
2704 }
2705
2706 static void pathbar_list_select(Widget w, PathBar *p, XmListCallbackStruct *cb) {
2707     char *value = NULL;
2708     XmStringGetLtoR(cb->item, XmSTRING_DEFAULT_CHARSET, &value);
2709     p->updateDir(p->updateDirData, value);
2710     PathBarSetPath(p, value);
2711     free(value);
2712 }
2713
2714 static void pathbar_popup(Widget w, PathBar *p, XtPointer d) {
2715     Widget parent = XtParent(w);
2716     Display *dp = XtDisplay(parent);
2717     Window root = XDefaultRootWindow(dp);
2718     
2719     int x, y;
2720     Window child;
2721     XTranslateCoordinates(dp, XtWindow(parent), root, 0, 0, &x, &y, &child);
2722
2723     XtManageChild(p->list);
2724     XtPopupSpringLoaded(p->popup);
2725     XtVaSetValues(p->popup, XmNx, x, XmNy, y + parent->core.height, XmNwidth, parent->core.width, XmNheight, 200, NULL);
2726     
2727     XmProcessTraversal(p->list, XmTRAVERSE_CURRENT);
2728 }
2729
2730 static void popupEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
2731     PathBar *bar = data;
2732     
2733     Window w1 = bar->hs ? XtWindow(bar->hs) : 0;
2734     Window w2 = bar->vs ? XtWindow(bar->vs) : 0;
2735     
2736     if(event->type == ButtonPress) {
2737         if(event->xbutton.window != 0 && (event->xbutton.window == w1 || event->xbutton.window == w2)) {
2738             bar->popupScrollEvent = 1;
2739         } else {
2740             bar->popupScrollEvent = 0;
2741         }
2742     } else if(event->type == ButtonRelease) {
2743         if(bar->popupScrollEvent) {
2744             *dispatch = False;
2745         }
2746         bar->popupScrollEvent = 0;
2747     } else if(event->type == KeyReleaseMask) {
2748         int keycode = event->xkey.keycode;
2749         if(keycode == 36 || keycode == 9) {
2750             XtUnmapWidget(bar->popup);
2751         }
2752     }
2753 }
2754
2755 static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) {
2756     PathBar *pb = data;
2757     if(event->type == KeyReleaseMask) {
2758         if(event->xkey.keycode == 9) {
2759             XtUnmanageChild(pb->textfield);
2760             pathbar_resize(pb->widget, pb, NULL);
2761             *dispatch = False;
2762         }
2763     }
2764 }
2765
2766
2767 PathBar* CreatePathBar(Widget parent, ArgList args, int n)
2768 {
2769     PathBar *bar = malloc(sizeof(PathBar));
2770     bar->path = NULL;
2771     bar->updateDir = NULL;
2772     bar->updateDirData = NULL;
2773     bar->disableResize = False;
2774     
2775     bar->shift = 0;
2776     
2777     XtSetArg(args[n], XmNmarginWidth, 0); n++;
2778     XtSetArg(args[n], XmNmarginHeight, 0); n++;
2779     bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n);
2780     XtAddCallback(
2781             bar->widget,
2782             XmNresizeCallback,
2783             (XtCallbackProc)pathbar_resize,
2784             bar);
2785     XtAddCallback(
2786             bar->widget,
2787             XmNinputCallback,
2788             (XtCallbackProc)pathbar_input,
2789             bar);
2790     
2791     n = 0;
2792     XtSetArg(args[n], XmNownerEvents, True), n++;
2793     XtSetArg(args[n], XmNgrabStyle, GrabModeSync), n++;
2794     bar->popup = XmCreateGrabShell(bar->widget, "pbpopup", args, n);
2795     bar->list = XmCreateScrolledList(bar->popup, "pblist", NULL, 0);
2796     XtAddEventHandler(bar->popup, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask, FALSE, popupEH, bar);
2797     bar->popupScrollEvent = 0;
2798     
2799     XtAddCallback(
2800             bar->list,
2801             XmNdefaultActionCallback,
2802             (XtCallbackProc)pathbar_list_select,
2803             bar); 
2804     XtAddCallback(
2805             bar->list,
2806             XmNbrowseSelectionCallback,
2807             (XtCallbackProc)pathbar_list_select,
2808             bar);
2809     
2810     bar->vs = NULL;
2811     bar->hs = NULL;
2812     XtVaGetValues(XtParent(bar->list), XmNhorizontalScrollBar, &bar->hs, XmNverticalScrollBar, &bar->vs, NULL);
2813     
2814     Arg a[4];
2815     XtSetArg(a[0], XmNshadowThickness, 0);
2816     XtSetArg(a[1], XmNx, 0);
2817     XtSetArg(a[2], XmNy, 0);
2818     bar->textfield = XmCreateTextField(bar->widget, "pbtext", a, 3);
2819     bar->input = 0;
2820     XtAddCallback(
2821             bar->textfield,
2822             XmNlosingFocusCallback,
2823             (XtCallbackProc)pathbar_losingfocus,
2824             bar);
2825     XtAddCallback(bar->textfield, XmNactivateCallback,
2826                  (XtCallbackProc)pathbar_pathinput, bar);
2827     XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar);
2828     
2829     XtSetArg(a[0], XmNarrowDirection, XmARROW_DOWN);
2830     bar->down = XmCreateArrowButton(bar->widget, "pbbutton", a, 1);
2831     XtManageChild(bar->down);
2832     XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT);
2833     bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1);
2834     XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT);
2835     bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1);
2836     XtAddCallback(
2837                 bar->down,
2838                 XmNactivateCallback,
2839                 (XtCallbackProc)pathbar_popup,
2840                 bar);
2841     XtAddCallback(
2842                 bar->left,
2843                 XmNactivateCallback,
2844                 (XtCallbackProc)pathbar_shift_left,
2845                 bar);
2846     XtAddCallback(
2847                 bar->right,
2848                 XmNactivateCallback,
2849                 (XtCallbackProc)pathbar_shift_right,
2850                 bar);
2851     
2852     Pixel bg;
2853     XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL);
2854     XtVaSetValues(bar->widget, XmNbackground, bg, NULL);
2855     
2856     //XtManageChild(bar->left);
2857     //XtManageChild(bar->right);
2858     
2859     XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL);
2860     XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL);
2861     
2862     bar->segmentAlloc = 16;
2863     bar->numSegments = 0;
2864     bar->pathSegments = calloc(16, sizeof(Widget));
2865     
2866     bar->selection = 0;
2867     
2868     return bar;
2869 }
2870
2871 static void PathBarChangeDir(Widget w, PathBar *bar, XtPointer unused) {
2872     XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False);
2873     
2874     for(int i=0;i<bar->numSegments;i++) {  
2875         if(bar->pathSegments[i] == w) {
2876             bar->selection = i;
2877             XmToggleButtonSetState(w, True, False);
2878             break;
2879         }
2880     }
2881     
2882     int plen = strlen(bar->path);
2883     int countSeg = 0;
2884     for(int i=0;i<=plen;i++) {
2885         char c = bar->path[i];
2886         if(c == '/' || c == '\0') {
2887             if(countSeg == bar->selection) {
2888                 char *dir = malloc(i+2);
2889                 memcpy(dir, bar->path, i+1);
2890                 dir[i+1] = '\0';
2891                 if(bar->updateDir) {
2892                     bar->updateDir(bar->updateDirData, dir);
2893                 }
2894                 free(dir);
2895             }
2896             countSeg++;
2897         }
2898     }
2899 }
2900
2901 void PathBarSetPath(PathBar *bar, const char *path) {
2902     if(bar->path) {
2903         free(bar->path);
2904     }
2905     bar->path = strdup(path);
2906      
2907     for(int i=0;i<bar->numSegments;i++) {
2908         XtDestroyWidget(bar->pathSegments[i]);
2909     }
2910     XtUnmanageChild(bar->textfield);
2911     //XtManageChild(bar->left);
2912     //XtManageChild(bar->right);
2913     bar->input = False;
2914     
2915     Arg args[4];
2916     XmString str;
2917     
2918     bar->numSegments = 0;
2919     
2920     int i=0;
2921     if(path[0] == '/') {
2922         str = XmStringCreateLocalized("/");
2923         XtSetArg(args[0], XmNlabelString, str);
2924         XtSetArg(args[1], XmNfillOnSelect, True);
2925         XtSetArg(args[2], XmNindicatorOn, False);
2926         bar->pathSegments[0] = XmCreateToggleButton(
2927                 bar->widget, "pbbutton", args, 3);
2928         XtAddCallback(
2929                 bar->pathSegments[0],
2930                 XmNvalueChangedCallback,
2931                 (XtCallbackProc)PathBarChangeDir,
2932                 bar);
2933         XmStringFree(str);
2934         bar->numSegments++;
2935         i++;
2936     }
2937     
2938     int len = strlen(path);
2939     int begin = i;
2940     for(;i<=len;i++) {
2941         char c = path[i];
2942         if((c == '/' || c == '\0') && i > begin) {
2943             char *segStr = malloc(i - begin + 1);
2944             memcpy(segStr, path+begin, i-begin);
2945             segStr[i-begin] = '\0';
2946             begin = i+1;
2947             
2948             str = XmStringCreateLocalized(segStr);
2949             free(segStr);
2950             XtSetArg(args[0], XmNlabelString, str);
2951             XtSetArg(args[1], XmNfillOnSelect, True);
2952             XtSetArg(args[2], XmNindicatorOn, False);
2953             Widget button = XmCreateToggleButton(bar->widget, "pbbutton", args, 3);
2954             XtAddCallback(
2955                     button,
2956                     XmNvalueChangedCallback,
2957                     (XtCallbackProc)PathBarChangeDir,
2958                     bar);
2959             XmStringFree(str);
2960             
2961             if(bar->numSegments >= bar->segmentAlloc) {
2962                 bar->segmentAlloc += 8;
2963                 bar->pathSegments = realloc(bar->pathSegments, bar->segmentAlloc * sizeof(Widget));
2964             }
2965             
2966             bar->pathSegments[bar->numSegments++] = button;
2967         }
2968     }
2969     
2970     bar->selection = bar->numSegments-1;
2971     XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False);
2972     
2973     XmTextFieldSetString(bar->textfield, (char*)path);
2974     XmTextFieldSetInsertionPosition(bar->textfield, XmTextFieldGetLastPosition(bar->textfield));
2975     
2976     pathbar_resize(bar->widget, bar, NULL);
2977 }
2978
2979 void PathBarSetDirList(PathBar *bar, const char **dirlist, size_t nelm) {
2980     XmStringTable items = calloc(nelm, sizeof(XmString));
2981     for(int i=0;i<nelm;i++) {
2982         items[i] = XmStringCreateLocalized((char*)dirlist[i]);
2983     }
2984     XmListDeleteAllItems(bar->list);
2985     XmListAddItems(bar->list, items, nelm, 0);
2986     XmListSelectPos(bar->list, 1, False);
2987     for(int i=0;i<nelm;i++) {
2988         XmStringFree(items[i]);
2989     }
2990     free(items);
2991 }
2992
2993 void PathBarDestroy(PathBar *bar) {
2994     if(bar->path) {
2995         free(bar->path);
2996     }
2997     free(bar->pathSegments);
2998     free(bar);
2999 }
3000
3001 /* ------------------------------ public API ------------------------------ */
3002
3003 Widget XnCreateFileSelectionDialog(
3004         Widget parent,
3005         String name,
3006         ArgList arglist,
3007         Cardinal argcount)
3008 {
3009     Widget dialog = XmCreateDialogShell(parent, "FileDialog", NULL, 0);
3010     Widget fsb = XnCreateFileSelectionBox(dialog, name, arglist, argcount);
3011     char *title = FSBDialogTitle(fsb);
3012     XtVaSetValues(dialog, XmNtitle, title, NULL);
3013     return fsb;
3014 }
3015
3016 Widget XnCreateFileSelectionBox(
3017         Widget parent,
3018         String name,
3019         ArgList arglist,
3020         Cardinal argcount)
3021 {
3022     Widget fsb = XtCreateWidget(name, xnFsbWidgetClass, parent, arglist, argcount);
3023     return fsb;
3024 }
3025
3026 void XnFileSelectionBoxAddView(
3027         Widget fsb,
3028         const char *name,
3029         FSBViewCreateProc create,
3030         FSBViewUpdateProc update,
3031         FSBViewSelectProc select,
3032         FSBViewCleanupProc cleanup,
3033         FSBViewDestroyProc destroy,
3034         Boolean useDirList,
3035         void *userData)
3036 {
3037     XnFileSelectionBox f = (XnFileSelectionBox)fsb;
3038     if(f->fsb.numviews >= FSB_MAX_VIEWS) {
3039         fprintf(stderr, "XnFileSelectionBox: too many views\n");
3040         return;
3041     }
3042     
3043     FSBView view;
3044     view.update = update;
3045     view.select = select;
3046     view.cleanup = cleanup;
3047     view.destroy = destroy;
3048     view.useDirList = useDirList;
3049     view.userData = userData;
3050     
3051     FSBViewWidgets widgets = CreateView(f, create, userData, useDirList);
3052     view.widget = widgets.view;
3053     view.focus = widgets.focus;
3054     
3055     AddViewMenuItem(f, name, f->fsb.numviews);
3056     
3057     f->fsb.view[f->fsb.numviews++] = view;
3058 }
3059
3060 void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm) {
3061     XnFileSelectionBox f = (XnFileSelectionBox)fsb;
3062     PathBarSetDirList(f->fsb.pathBar, dirlist, nelm);
3063 }
3064
3065 Widget XnFileSelectionBoxWorkArea(Widget fsb) {
3066     XnFileSelectionBox f = (XnFileSelectionBox)fsb;
3067     return f->fsb.workarea;
3068 }
3069
3070 Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child) {
3071     XnFileSelectionBox w = (XnFileSelectionBox)fsb;
3072     switch(child) {
3073         case XnFSB_DIR_UP_BUTTON: return w->fsb.dirUp;
3074         case XnFSB_HOME_BUTTON: return w->fsb.home;
3075         case XnFSB_NEW_FOLDER_BUTTON: return w->fsb.newFolder;
3076         case XnFSB_DETAIL_TOGGLE_BUTTON: return w->fsb.detailToggleButton;
3077         case XnFSB_VIEW_OPTION_BUTTON: return w->fsb.viewOption;
3078         case XnFSB_FILTER_DROPDOWN: return w->fsb.filter;
3079         case XnFSB_FILTER_BUTTON: return w->fsb.filterButton;
3080         case XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON: return w->fsb.showHiddenButtonW;
3081         case XnFSB_DIRECTORIES_LABEL: return w->fsb.lsDirLabel;
3082         case XnFSB_FILES_LABEL: return w->fsb.lsFileLabel;
3083         case XnFSB_DIRLIST: return w->fsb.dirlist;
3084         case XnFSB_FILELIST: return w->fsb.filelist;
3085         case XnFSB_GRID: return w->fsb.grid;
3086         case XnFSB_OK_BUTTON: return w->fsb.okBtn;
3087         case XnFSB_CANCEL_BUTTON: return w->fsb.cancelBtn;
3088         case XnFSB_HELP_BUTTON: return w->fsb.helpBtn;
3089     }
3090     return NULL;
3091 }
3092
3093 void XnFileSelectionBoxDeleteFilters(Widget fsb) {
3094     XnFileSelectionBox w = (XnFileSelectionBox)fsb;
3095     Widget filterList = XmDropDownGetList(w->fsb.filter);
3096     XmListDeleteAllItems(filterList);
3097 }
3098
3099 void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter) {
3100     XnFileSelectionBox w = (XnFileSelectionBox)fsb;
3101     Widget filterList = XmDropDownGetList(w->fsb.filter);
3102     
3103     XmString str = XmStringCreateSimple((char*)filter);
3104     XmListAddItem(filterList, str, 0);
3105     XmStringFree(str);
3106 }