Sun, 23 Nov 2025 08:35:40 +0100
implement ui_openfiledialog (Motif)
| ui/motif/Fsb.c | file | annotate | diff | comparison | revisions | |
| ui/motif/Fsb.h | file | annotate | diff | comparison | revisions | |
| ui/motif/FsbP.h | file | annotate | diff | comparison | revisions | |
| ui/motif/objs.mk | file | annotate | diff | comparison | revisions | |
| ui/motif/pathbar.c | file | annotate | diff | comparison | revisions | |
| ui/motif/pathbar.h | file | annotate | diff | comparison | revisions | |
| ui/motif/text.c | file | annotate | diff | comparison | revisions | |
| ui/motif/window.c | file | annotate | diff | comparison | revisions |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/Fsb.c Sun Nov 23 08:35:40 2025 +0100 @@ -0,0 +1,2639 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +//define FSB_ENABLE_DETAIL + +#include "Fsb.h" +#include "FsbP.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <time.h> +#include <inttypes.h> +#include <errno.h> + +#include <sys/stat.h> +#include <limits.h> +#include <dirent.h> +#include <fnmatch.h> + +#include <Xm/XmAll.h> +#include <Xm/DropDown.h> + +#include "pathbar.h" + +#ifdef FSB_ENABLE_DETAIL +#include <XmL/Grid.h> +#endif + +#define WIDGET_SPACING 5 +#define WINDOW_SPACING 8 + +#define BUTTON_EXTRA_SPACE 4 + +#define DATE_FORMAT_SAME_YEAR "%b %d %H:%M" +#define DATE_FORMAT_OTHER_YEAR "%b %d %Y" + +#define KB_SUFFIX "KiB" +#define MB_SUFFIX "MiB" +#define GB_SUFFIX "GiB" +#define TB_SUFFIX "TiB" + +#define FSB_ERROR_TITLE "Error" +#define FSB_ERROR_CHAR "Character '/' is not allowed in file names" +#define FSB_ERROR_RENAME "Cannot rename file: %s" +#define FSB_ERROR_DELETE "Cannot delete file: %s" +#define FSB_ERROR_CREATE_FOLDER "Cannot create folder: %s" +#define FSB_ERROR_OPEN_DIR "Cannot open directory: %s" + +#define FSB_DETAIL_HEADINGS "Name|Size|Last Modified" + +static void fsb_class_init(void); +static void fsb_class_part_init (WidgetClass wc); +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args); +static void fsb_resize(Widget widget); +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes); +static void fsb_destroy(Widget widget); +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args); +static Boolean fsb_acceptfocus(Widget widget, Time *time); + +static void fsb_insert_child(Widget child); + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb); + +static int FSBGlobFilter(const char *a, const char *b); + +static void FSBUpdateTitle(Widget w); + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg); + +static void FSBRename(XnFileSelectionBox fsb, const char *path); +static void FSBDelete(XnFileSelectionBox fsb, const char *path); + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item); + +static char* set_selected_path(XnFileSelectionBox data, XmString item); + +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd); + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback); + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListSelect(Widget fsb, Widget view, const char *item); +static void FileListCleanup(Widget fsb, Widget view, void *userData); +static void FileListDestroy(Widget fsb, Widget view, void *userData); + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count); + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListDetailSelect(Widget fsb, Widget view, const char *item); +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData); +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData); +static void FileListDetailAdjustColWidth(Widget grid); +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth); +#endif + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value); + +static void CreateUI(XnFileSelectionBox w); +static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc createProc, void *userData, Boolean useDirList); +static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex); +static void SelectView(XnFileSelectionBox f, int view); + +static char* FSBDialogTitle(Widget w); + +static FSBViewWidgets CreateListView(Widget fsb, ArgList args, int n, void *userData); +static FSBViewWidgets CreateDetailView(Widget fsb, ArgList args, int n, void *userData); + +static const char* GetHomeDir(void); + +static char* ConcatPath(const char *parent, const char *name); +static char* FileName(char *path); +static char* ParentPath(const char *path); +//static int CheckFileName(const char *name); + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path); +static void filedialog_cleanup_filedata(XnFileSelectionBox data); + + +static XtResource resources[] = { + {XmNokCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.okCallback), XmRCallback, NULL}, + {XmNcancelCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.cancelCallback), XmRCallback, NULL}, + {XnNwidgetSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.widgetSpacing), XmRImmediate, (XtPointer)WIDGET_SPACING}, + {XnNwindowSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.windowSpacing), XmRImmediate, (XtPointer)WINDOW_SPACING}, + {XnNfsbType, XnCfsbType, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.type), XmRImmediate, (XtPointer)FILEDIALOG_OPEN}, + {XnNshowHidden, XnCshowHidden, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHidden), XmRImmediate, (XtPointer)False}, + {XnNshowHiddenButton, XnCshowHiddenButton, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHiddenButton), XmRImmediate, (XtPointer)True}, + {XnNshowViewMenu, XnCshowViewMenu, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showViewMenu), XmRImmediate, (XtPointer)False}, + {XnNselectedView, XnCselectedView, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.selectedview), XmRImmediate, (XtPointer)0}, + + {XnNdirectory, XnCdirectory, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.currentPath), XmRString, NULL}, + {XnNselectedPath, XnCselectedPath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.selectedPath), XmRString, NULL}, + {XnNhomePath, XnChomePath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.homePath), XmRString, NULL}, + + {XnNfilter,XnCfilter,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.filterStr), XmRString, "*"}, + {XnNfilterFunc,XnCfilterFunc,XmRFunction,sizeof(FSBFilterFunc),XtOffset(XnFileSelectionBox, fsb.filterFunc), XmRFunction, NULL}, + + {XnNlabelListView,XnClabelListView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelListView), XmRString, "List"}, + {XnNlabelDetailView,XnClabelDetailView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDetailView), XmRString, "Detail"}, + {XnNlabelOpenFileTitle,XnClabelOpenFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpenFileTitle), XmRString, "Open File"}, + {XnNlabelSaveFileTitle,XnClabelSaveFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSaveFileTitle), XmRString, "Save File"}, + {XnNlabelDirUp,XnClabelDirUp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirUp), XmRString, "Dir Up"}, + {XnNlabelHome,XnClabelHome,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHome), XmRString, "Home"}, + {XnNlabelNewFolder,XnClabelNewFolder,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFolder), XmRString, "New Folder"}, + {XnNlabelFilterButton,XnClabelFilterButton,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFilterButton), XmRString, "Filter"}, + {XnNlabelShowHiddenFiles,XnClabelShowHiddenFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelShowHiddenFiles), XmRString, "Show hiden files"}, + {XnNlabelDirectories,XnClabelDirectories,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectories), XmRString, "Directories"}, + {XnNlabelFiles,XnClabelFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFiles), XmRString, "Files"}, + {XnNlabelRename,XnClabelRename,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelRename), XmRString, "Rename"}, + {XnNlabelDelete,XnClabelDelete,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDelete), XmRString, "Delete"}, + {XnNlabelOpen,XnClabelOpen,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpen), XmRString, "Open"}, + {XnNlabelSave,XnClabelSave,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSave), XmRString, "Save"}, + {XnNlabelOk,XnClabelOk,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOk), XmRString, "OK"}, + {XnNlabelCancel,XnClabelCancel,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelCancel), XmRString, "Cancel"}, + {XnNlabelHelp,XnClabelHelp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHelp), XmRString, "Help"}, + {XnNlabelFileName,XnClabelFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFileName), XmRString, "New File Name"}, + {XnNlabelDirectoryName,XnClabelDirectoryName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectoryName), XmRString, "Directory name:"}, + {XnNlabelNewFileName,XnClabelNewFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFileName), XmRString, "New file name:"}, + {XnNlabelDeleteFile,XnClabelDeleteFile,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDeleteFile), XmRString, "Delete file '%s'?"}, + {XnNdetailHeadings,XnCdetailHeadings,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.detailHeadings), XmRString,FSB_DETAIL_HEADINGS}, + {XnNdateFormatSameYear,XnCdateFormatSameYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatSameYear), XmRString,DATE_FORMAT_SAME_YEAR}, + {XnNdateFormatOtherYear,XnNdateFormatOtherYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatOtherYear), XmRString,DATE_FORMAT_OTHER_YEAR}, + {XnNsuffixBytes,XnCsuffixBytes,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixBytes), XmRString,"bytes"}, + {XnNsuffixKB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixKB), XmRString,KB_SUFFIX}, + {XnNsuffixMB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixMB), XmRString,MB_SUFFIX}, + {XnNsuffixGB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixGB), XmRString,GB_SUFFIX}, + {XnNsuffixTB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixTB), XmRString,TB_SUFFIX}, + + {XnNerrorTitle,XnCerrorTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorTitle), XmRString,FSB_ERROR_TITLE}, + {XnNerrorIllegalChar,XnCerrorIllegalChar,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorIllegalChar), XmRString,FSB_ERROR_CHAR}, + {XnNerrorRename,XnCerrorRename,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorRename), XmRString,FSB_ERROR_RENAME}, + {XnNerrorCreateFolder,XnCerrorCreateFolder,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorFolder), XmRString,FSB_ERROR_CREATE_FOLDER}, + {XnNerrorDelete,XnCerrorDelete,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorDelete), XmRString,FSB_ERROR_DELETE}, + {XnNerrorOpenDir,XnCerrorOpenDir,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorOpenDir), XmRString,FSB_ERROR_OPEN_DIR} +}; + +static XtActionsRec actionslist[] = { + {"focusIn", FocusInAP}, + {"NULL", NULL} +}; + + +static char defaultTranslations[] = "<FocusIn>: focusIn()"; + +static XtResource constraints[] = {}; + +FSBClassRec fsbWidgetClassRec = { + // Core Class + { + (WidgetClass)&xmFormClassRec, + "XnFSB", // class_name + sizeof(FSBRec), // widget_size + fsb_class_init, // class_initialize + fsb_class_part_init, // class_part_initialize + FALSE, // class_inited + fsb_init, // initialize + NULL, // initialize_hook + fsb_realize, // realize + actionslist, // actions + XtNumber(actionslist), // num_actions + resources, // resources + XtNumber(resources), // num_resources + NULLQUARK, // xrm_class + True, // compress_motion + True, // compress_exposure + True, // compress_enterleave + False, // visible_interest + fsb_destroy, // destroy + fsb_resize, // resize + XtInheritExpose, // expose + fsb_set_values, // set_values + NULL, // set_values_hook + XtInheritSetValuesAlmost, // set_values_almost + NULL, // get_values_hook + fsb_acceptfocus, // accept_focus + XtVersion, // version + NULL, // callback_offsets + defaultTranslations, // tm_table + XtInheritQueryGeometry, // query_geometry + XtInheritDisplayAccelerator, // display_accelerator + NULL, // extension + }, + // Composite Class + { + XtInheritGeometryManager, // geometry_manager + XtInheritChangeManaged, // change_managed + fsb_insert_child, // insert_child + XtInheritDeleteChild, // delete_child + NULL, // extension + }, + // Constraint Class + { + constraints, // resources + XtNumber(constraints), // num_resources + sizeof(XmFormConstraintRec), // constraint_size + NULL, // initialize + NULL, // destroy + NULL, // set_value + NULL, // extension + }, + // XmManager Class + { + XtInheritTranslations, // translations + NULL, // syn_resources + 0, // num_syn_resources + NULL, // syn_constraint_resources + 0, // num_syn_constraint_resources + XmInheritParentProcess, // parent_process + NULL // extension + }, + // XmBulletinBoard + { + FALSE, + NULL, + XmInheritFocusMovedProc, + NULL + }, + // XmForm Class + { + NULL + }, + // FSB Class + { + 0 + } +}; + +WidgetClass xnFsbWidgetClass = (WidgetClass)&fsbWidgetClassRec; + + +static void fsb_class_init(void) { + +} + +static void fsb_class_part_init (WidgetClass wc) { + FSBClassRec *fsbClass = (FSBClassRec*)wc; + XmFormClassRec *formClass = (XmFormClassRec*)xmFormWidgetClass; + + fsbClass->constraint_class.initialize = formClass->constraint_class.initialize; + fsbClass->constraint_class.set_values = formClass->constraint_class.set_values; +} + + +#define STRDUP_RES(a) if(a) a = strdup(a) +#define XMS_STRDUP_RES(a) if(a) a = XmStringCopy(a) + +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) { + XnFileSelectionBox fsb = (XnFileSelectionBox)neww; + (xmFormClassRec.core_class.initialize)(request, neww, args, num_args); + + fsb->fsb.disable_set_values = 0; + + STRDUP_RES(fsb->fsb.homePath); + STRDUP_RES(fsb->fsb.selectedPath); + STRDUP_RES(fsb->fsb.currentPath); + STRDUP_RES(fsb->fsb.filterStr); + STRDUP_RES(fsb->fsb.labelListView); + STRDUP_RES(fsb->fsb.labelDetailView); + STRDUP_RES(fsb->fsb.labelOpenFileTitle); + STRDUP_RES(fsb->fsb.labelSaveFileTitle); + XMS_STRDUP_RES(fsb->fsb.labelDirUp); + XMS_STRDUP_RES(fsb->fsb.labelHome); + XMS_STRDUP_RES(fsb->fsb.labelNewFolder); + XMS_STRDUP_RES(fsb->fsb.labelFilterButton); + XMS_STRDUP_RES(fsb->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(fsb->fsb.labelDirectories); + XMS_STRDUP_RES(fsb->fsb.labelFiles); + XMS_STRDUP_RES(fsb->fsb.labelRename); + XMS_STRDUP_RES(fsb->fsb.labelDelete); + XMS_STRDUP_RES(fsb->fsb.labelOpen); + XMS_STRDUP_RES(fsb->fsb.labelSave); + XMS_STRDUP_RES(fsb->fsb.labelCancel); + XMS_STRDUP_RES(fsb->fsb.labelHelp); + XMS_STRDUP_RES(fsb->fsb.labelFileName); + XMS_STRDUP_RES(fsb->fsb.labelDirectoryName); + XMS_STRDUP_RES(fsb->fsb.labelNewFileName); + STRDUP_RES(fsb->fsb.labelDeleteFile); + STRDUP_RES(fsb->fsb.detailHeadings); + STRDUP_RES(fsb->fsb.dateFormatSameYear); + STRDUP_RES(fsb->fsb.dateFormatOtherYear); + STRDUP_RES(fsb->fsb.suffixBytes); + STRDUP_RES(fsb->fsb.suffixKB); + STRDUP_RES(fsb->fsb.suffixMB); + STRDUP_RES(fsb->fsb.suffixGB); + STRDUP_RES(fsb->fsb.suffixTB); + STRDUP_RES(fsb->fsb.errorTitle); + STRDUP_RES(fsb->fsb.errorIllegalChar); + STRDUP_RES(fsb->fsb.errorRename); + STRDUP_RES(fsb->fsb.errorFolder); + STRDUP_RES(fsb->fsb.errorDelete); + STRDUP_RES(fsb->fsb.errorOpenDir); + + CreateUI((XnFileSelectionBox)fsb); + + XtAddCallback(neww, XmNmapCallback, fsb_mapcb, NULL); +} + +#define STR_FREE(a) if(a) free(a) +#define XMSTR_FREE(a) if(a) XmStringFree(a) + +static void fsb_destroy(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + + // destroy all views + for(int i=0;i<w->fsb.numviews;i++) { + FSBView v = w->fsb.view[i]; + v.destroy(widget, v.widget, v.userData); + } + + STR_FREE(w->fsb.homePath); + + // free filelists + filedialog_cleanup_filedata(w); + STR_FREE(w->fsb.currentPath); + STR_FREE(w->fsb.selectedPath); + STR_FREE(w->fsb.filterStr); + + PathBarDestroy(w->fsb.pathBar); + + // free strings + STR_FREE(w->fsb.labelListView); + STR_FREE(w->fsb.labelDetailView); + STR_FREE(w->fsb.labelOpenFileTitle); + STR_FREE(w->fsb.labelSaveFileTitle); + + XMSTR_FREE(w->fsb.labelDirUp); + XMSTR_FREE(w->fsb.labelHome); + XMSTR_FREE(w->fsb.labelNewFolder); + XMSTR_FREE(w->fsb.labelFilterButton); + XMSTR_FREE(w->fsb.labelShowHiddenFiles); + XMSTR_FREE(w->fsb.labelDirectories); + XMSTR_FREE(w->fsb.labelFiles); + XMSTR_FREE(w->fsb.labelRename); + XMSTR_FREE(w->fsb.labelDelete); + XMSTR_FREE(w->fsb.labelOpen); + XMSTR_FREE(w->fsb.labelSave); + XMSTR_FREE(w->fsb.labelCancel); + XMSTR_FREE(w->fsb.labelHelp); + XMSTR_FREE(w->fsb.labelFileName); + XMSTR_FREE(w->fsb.labelDirectoryName); + XMSTR_FREE(w->fsb.labelNewFileName); + STR_FREE(w->fsb.labelDeleteFile); + STR_FREE(w->fsb.detailHeadings); + + STR_FREE(w->fsb.dateFormatSameYear); + STR_FREE(w->fsb.dateFormatOtherYear); + STR_FREE(w->fsb.suffixBytes); + STR_FREE(w->fsb.suffixKB); + STR_FREE(w->fsb.suffixMB); + STR_FREE(w->fsb.suffixGB); + STR_FREE(w->fsb.suffixTB); + + STR_FREE(w->fsb.errorTitle); + STR_FREE(w->fsb.errorIllegalChar); + STR_FREE(w->fsb.errorRename); + STR_FREE(w->fsb.errorFolder); + STR_FREE(w->fsb.errorDelete); + STR_FREE(w->fsb.errorOpenDir); +} + +static void fsb_resize(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.resize)(widget); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.realize)(widget, mask, attributes); + + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void FSBUpdateTitle(Widget w) { + if(XtParent(w)->core.widget_class == xmDialogShellWidgetClass) { + char *title = FSBDialogTitle(w); + XtVaSetValues(XtParent(w), XmNtitle, title, NULL); + } +} + +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + Boolean r = False; + + XnFileSelectionBox o = (XnFileSelectionBox)old; + XnFileSelectionBox n = (XnFileSelectionBox)neww; + + int setOkBtnLabel = 0; + int ismanaged = XtIsManaged(neww); + Dimension width, height; + if(!ismanaged) { + width = n->core.width; + height = n->core.height; + if(n->fsb.pathBar) { + n->fsb.pathBar->disableResize = True; + } + } + + if(o->fsb.selectedview != n->fsb.selectedview) { + int selectedview = n->fsb.selectedview; + n->fsb.selectedview = o->fsb.selectedview; + SelectView(n, selectedview); + } + + char *updateDir = NULL; + int selectItem = 0; + if(o->fsb.selectedPath != n->fsb.selectedPath) { + STR_FREE(o->fsb.selectedPath); + STRDUP_RES(n->fsb.selectedPath); + XmTextFieldSetString(n->fsb.name, FileName(n->fsb.selectedPath)); + // also update current directory + updateDir = ParentPath(n->fsb.selectedPath); + selectItem = 1; + } + if(o->fsb.currentPath != n->fsb.currentPath) { + STR_FREE(o->fsb.currentPath); + updateDir = strdup(n->fsb.currentPath); + n->fsb.currentPath = NULL; + } + + if(o->fsb.filterStr != n->fsb.filterStr) { + STR_FREE(o->fsb.filterStr); + STRDUP_RES(n->fsb.filterStr); + XmTextFieldSetString(XmDropDownGetText(n->fsb.filter), n->fsb.filterStr); + if(!updateDir) { + filedialog_update_dir(n, NULL); + } + } + + if(updateDir) { + filedialog_update_dir(n, updateDir); + PathBarSetPath(n->fsb.pathBar, updateDir); + free(updateDir); + } + + if(o->fsb.type != n->fsb.type) { + if(n->fsb.type == FILEDIALOG_OPEN) { + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.separator, NULL); + XtUnmanageChild(n->fsb.name); + XtUnmanageChild(n->fsb.nameLabel); + } else { + XtManageChild(n->fsb.name); + XtManageChild(n->fsb.nameLabel); + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.nameLabel, NULL); + } + FSBUpdateTitle(neww); + setOkBtnLabel = 1; + } + + // label strings + int updateTitle = 0; + if(o->fsb.labelListView != n->fsb.labelListView) { + STR_FREE(o->fsb.labelListView); + STRDUP_RES(n->fsb.labelListView); + XmString label = XmStringCreateLocalized(n->fsb.labelListView); + XtVaSetValues(n->fsb.viewSelectorList, XmNlabelString, label, NULL); + XmStringFree(label); + } + if(o->fsb.labelDetailView != n->fsb.labelDetailView) { + STR_FREE(o->fsb.labelDetailView); + STRDUP_RES(n->fsb.labelDetailView); + XmString label = XmStringCreateLocalized(n->fsb.labelDetailView); + XtVaSetValues(n->fsb.viewSelectorDetail, XmNlabelString, label, NULL); + if(n->fsb.detailToggleButton) { + XtVaSetValues(n->fsb.detailToggleButton, XmNlabelString, label, NULL); + } + XmStringFree(label); + } + if(o->fsb.labelOpenFileTitle != n->fsb.labelOpenFileTitle) { + STR_FREE(o->fsb.labelOpenFileTitle); + STRDUP_RES(n->fsb.labelOpenFileTitle); + updateTitle = 1; + } + if(o->fsb.labelSaveFileTitle != n->fsb.labelSaveFileTitle) { + STR_FREE(o->fsb.labelSaveFileTitle); + STRDUP_RES(n->fsb.labelSaveFileTitle); + updateTitle = 1; + } + + if(o->fsb.labelDirUp != n->fsb.labelDirUp) { + XMSTR_FREE(o->fsb.labelDirUp); + XMS_STRDUP_RES(n->fsb.labelDirUp); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelDirUp, NULL); + } + if(o->fsb.labelHome != n->fsb.labelHome) { + XMSTR_FREE(o->fsb.labelHome); + XMS_STRDUP_RES(n->fsb.labelHome); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelHome, NULL); + } + if(o->fsb.labelNewFolder != n->fsb.labelNewFolder) { + XMSTR_FREE(o->fsb.labelNewFolder); + XMS_STRDUP_RES(n->fsb.labelNewFolder); + XtVaSetValues(n->fsb.newFolder, XmNlabelString, n->fsb.labelNewFolder, NULL); + } + if(o->fsb.labelFilterButton != n->fsb.labelFilterButton) { + XMSTR_FREE(o->fsb.labelFilterButton); + XMS_STRDUP_RES(n->fsb.labelFilterButton); + XtVaSetValues(n->fsb.filterButton, XmNlabelString, n->fsb.labelFilterButton, NULL); + } + if(o->fsb.labelShowHiddenFiles != n->fsb.labelShowHiddenFiles) { + XMSTR_FREE(o->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(n->fsb.labelShowHiddenFiles); + XtVaSetValues(n->fsb.showHiddenButtonW, XmNlabelString, n->fsb.labelShowHiddenFiles, NULL); + } + if(o->fsb.labelDirectories != n->fsb.labelDirectories) { + XMSTR_FREE(o->fsb.labelDirectories); + XMS_STRDUP_RES(n->fsb.labelDirectories); + XtVaSetValues(n->fsb.lsDirLabel, XmNlabelString, n->fsb.labelDirectories, NULL); + } + if(o->fsb.labelFiles != n->fsb.labelFiles) { + XMSTR_FREE(o->fsb.labelFiles); + XMS_STRDUP_RES(n->fsb.labelFiles); + XtVaSetValues(n->fsb.lsFileLabel, XmNlabelString, n->fsb.labelFiles, NULL); + } + int recreateContextMenu = 0; + if(o->fsb.labelRename != n->fsb.labelRename) { + XMSTR_FREE(o->fsb.labelRename); + XMS_STRDUP_RES(n->fsb.labelRename); + recreateContextMenu = 1; + } + if(o->fsb.labelDelete != n->fsb.labelDelete) { + XMSTR_FREE(o->fsb.labelDelete); + XMS_STRDUP_RES(n->fsb.labelDelete); + recreateContextMenu = 1; + } + + if(o->fsb.labelOpen != n->fsb.labelOpen) { + XMSTR_FREE(o->fsb.labelOpen); + XMS_STRDUP_RES(n->fsb.labelOpen); + setOkBtnLabel = 1; + } + if(o->fsb.labelSave != n->fsb.labelSave) { + XMSTR_FREE(o->fsb.labelSave); + XMS_STRDUP_RES(n->fsb.labelSave); + setOkBtnLabel = 1; + } + if(o->fsb.labelCancel != n->fsb.labelCancel) { + XMSTR_FREE(o->fsb.labelCancel); + XMS_STRDUP_RES(n->fsb.labelCancel); + XtVaSetValues(n->fsb.cancelBtn, XmNlabelString, n->fsb.labelCancel, NULL); + } + if(o->fsb.labelHelp != n->fsb.labelHelp) { + XMSTR_FREE(o->fsb.labelHelp); + XMS_STRDUP_RES(n->fsb.labelHelp); + XtVaSetValues(n->fsb.helpBtn, XmNlabelString, n->fsb.labelHelp, NULL); + } + if(o->fsb.labelFileName != n->fsb.labelFileName) { + XMSTR_FREE(o->fsb.labelFileName); + XMS_STRDUP_RES(n->fsb.labelFileName); + XtVaSetValues(n->fsb.nameLabel, XmNlabelString, n->fsb.labelFileName, NULL); + } + if(o->fsb.labelDirectoryName != n->fsb.labelDirectoryName) { + XMSTR_FREE(o->fsb.labelDirectoryName); + XMS_STRDUP_RES(n->fsb.labelDirectoryName); + } + if(o->fsb.labelNewFileName != n->fsb.labelNewFileName) { + XMSTR_FREE(o->fsb.labelNewFileName); + XMS_STRDUP_RES(n->fsb.labelNewFileName); + } + + if(o->fsb.labelDeleteFile != n->fsb.labelDeleteFile) { + STR_FREE(o->fsb.labelDeleteFile); + STRDUP_RES(n->fsb.labelDeleteFile); + } +#ifdef FSB_ENABLE_DETAIL + if(o->fsb.detailHeadings != n->fsb.detailHeadings) { + STR_FREE(o->fsb.detailHeadings); + STRDUP_RES(n->fsb.detailHeadings); + XtVaSetValues(n->fsb.grid, XmNsimpleHeadings, n->fsb.detailHeadings, NULL); + } +#endif + if(o->fsb.dateFormatSameYear != n->fsb.dateFormatSameYear) { + STR_FREE(o->fsb.dateFormatSameYear); + STRDUP_RES(n->fsb.dateFormatSameYear); + } + if(o->fsb.dateFormatOtherYear != n->fsb.dateFormatOtherYear) { + STR_FREE(o->fsb.dateFormatOtherYear); + STRDUP_RES(n->fsb.dateFormatOtherYear); + } + if(o->fsb.suffixBytes != n->fsb.suffixBytes) { + STR_FREE(o->fsb.suffixBytes); + STRDUP_RES(n->fsb.suffixBytes); + } + if(o->fsb.suffixMB != n->fsb.suffixMB) { + STR_FREE(o->fsb.suffixMB); + STRDUP_RES(n->fsb.suffixMB); + } + if(o->fsb.suffixGB != n->fsb.suffixGB) { + STR_FREE(o->fsb.suffixGB); + STRDUP_RES(n->fsb.suffixGB); + } + if(o->fsb.suffixTB != n->fsb.suffixTB) { + STR_FREE(o->fsb.suffixTB); + STRDUP_RES(n->fsb.suffixTB); + } + if(o->fsb.errorTitle != n->fsb.errorTitle) { + STR_FREE(o->fsb.errorTitle); + STRDUP_RES(n->fsb.errorTitle); + } + if(o->fsb.errorIllegalChar != n->fsb.errorIllegalChar) { + STR_FREE(o->fsb.errorIllegalChar); + STRDUP_RES(n->fsb.errorIllegalChar); + } + if(o->fsb.errorRename != n->fsb.errorRename) { + STR_FREE(o->fsb.errorRename); + STRDUP_RES(n->fsb.errorRename); + } + if(o->fsb.errorFolder != n->fsb.errorFolder) { + STR_FREE(o->fsb.errorFolder); + STRDUP_RES(n->fsb.errorFolder); + } + if(o->fsb.errorDelete != n->fsb.errorDelete) { + STR_FREE(o->fsb.errorDelete); + STRDUP_RES(n->fsb.errorDelete); + } + if(o->fsb.errorOpenDir != n->fsb.errorOpenDir) { + STR_FREE(o->fsb.errorOpenDir); + STRDUP_RES(n->fsb.errorOpenDir); + } + + if(updateTitle) { + FSBUpdateTitle(neww); + } + if(recreateContextMenu) { + XtDestroyWidget(n->fsb.listContextMenu); + XtDestroyWidget(n->fsb.gridContextMenu); + n->fsb.listContextMenu = CreateContextMenu(n, n->fsb.filelist, FileContextMenuCB); + n->fsb.gridContextMenu = CreateContextMenu(n, n->fsb.grid, FileContextMenuCB); + } + if(setOkBtnLabel) { + XtVaSetValues(n->fsb.okBtn, XmNlabelString, n->fsb.type == FILEDIALOG_OPEN ? n->fsb.labelOpen : n->fsb.labelSave, NULL); + } + + if(!ismanaged && !n->fsb.disable_set_values) { + n->fsb.disable_set_values = 1; + XtVaSetValues(neww, XmNwidth, width, XmNheight, height, NULL); + n->fsb.disable_set_values = 0; + + if(n->fsb.pathBar) + n->fsb.pathBar->disableResize = False; + } + + if(selectItem) { + if(ismanaged) { + FSBSelectItem(n, FileName(n->fsb.selectedPath)); + } + } + + Boolean fr = (xmFormClassRec.core_class.set_values)(old, request, neww, args, num_args); + return fr ? fr : r; +} + +static void fsb_insert_child(Widget child) { + XnFileSelectionBox p = (XnFileSelectionBox)XtParent(child); + (xmFormClassRec.composite_class.insert_child)(child); + + if(!p->fsb.gui_created) { + return; + } + + // custom child widget insert + XtVaSetValues(child, + XmNbottomAttachment, XmATTACH_WIDGET, + XmNbottomWidget, p->fsb.bottom_widget, + XmNbottomOffset, p->fsb.widgetSpacing, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, p->fsb.windowSpacing, + XmNrightAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, p->fsb.windowSpacing, + NULL); + + + XtVaSetValues(p->fsb.listform, + XmNbottomWidget, child, + XmNbottomOffset, 0, + NULL); + + p->fsb.workarea = child; +} + +Boolean fsb_acceptfocus(Widget widget, Time *time) { + return 0; +} + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + pathbar_resize(w->fsb.pathBar->widget, w->fsb.pathBar, NULL); + + if(w->fsb.type == FILEDIALOG_OPEN) { + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + } else { + XmProcessTraversal(w->fsb.name, XmTRAVERSE_CURRENT); + } + + + if(w->fsb.selectedPath) { + FSBSelectItem(w, FileName(w->fsb.selectedPath)); + } +} + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { + +} + +static int apply_filter(XnFileSelectionBox w, const char *pattern, const char *string) { + if(!pattern) return 0; + + FSBFilterFunc func = w->fsb.filterFunc ? w->fsb.filterFunc : FSBGlobFilter; + return func(pattern, string); +} + +static int FSBGlobFilter(const char *a, const char *b) { + return fnmatch(a, b, 0); +} + + +static void errCB(Widget w, XtPointer d, XtPointer cbs) { + XtDestroyWidget(w); +} + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg) { + Arg args[16]; + int n = 0; + + XmString titleStr = XmStringCreateLocalized((char*)title); + XmString msg = XmStringCreateLocalized((char*)errmsg); + + XtSetArg(args[n], XmNdialogTitle, titleStr); n++; + XtSetArg(args[n], XmNselectionLabelString, msg); n++; + XtSetArg(args[n], XmNokLabelString, w->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, w->fsb.labelCancel); n++; + + Widget dialog = XmCreatePromptDialog ((Widget)w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget cancel = XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON); + XtUnmanageChild(cancel); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, errCB, NULL); + + XtManageChild(dialog); + + XmStringFree(titleStr); + XmStringFree(msg); +} + +static void rename_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + char *fileName = NULL; + XmStringGetLtoR(cb->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + // make sure the new file name doesn't contain a path separator + if(strchr(fileName, '/')) { + ErrDialog(fsb, fsb->fsb.errorTitle, fsb->fsb.errorIllegalChar); + XtFree(fileName); + return; + } + + char *parentPath = ParentPath(path); + char *newPath = ConcatPath(parentPath, fileName); + + if(rename(path, newPath)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorRename, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + filedialog_update_dir(fsb, parentPath); + } + + free(parentPath); + free(newPath); + XtFree(fileName); + XtDestroyWidget(XtParent(w)); +} + +static void selectionbox_cancel(Widget w, XtPointer data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBRename(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + + XmString filename = XmStringCreateLocalized(name); + XtSetArg(args[n], XmNselectionLabelString,fsb->fsb.labelNewFileName); n++; + XtSetArg(args[n], XmNtextString, filename); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelRename); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "RenameFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)rename_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + XmStringFree(filename); + XtManageChild(dialog); +} + +static void delete_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + if(unlink(path)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorDelete, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + char *parentPath = ParentPath(path); + filedialog_update_dir(fsb, parentPath); + free(parentPath); + } + + XtDestroyWidget(XtParent(w)); +} + +static void FSBDelete(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + size_t len = strlen(name); + size_t msglen = len + strlen(fsb->fsb.labelDeleteFile) + 4; + char *msg = malloc(msglen); + snprintf(msg, msglen, fsb->fsb.labelDeleteFile, name); + + XmString prompt = XmStringCreateLocalized(msg); + XtSetArg(args[n], XmNselectionLabelString, prompt); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelDelete); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "DeleteFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)delete_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + free(msg); + XmStringFree(prompt); + XtManageChild(dialog); +} + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item) { + FSBView view = fsb->fsb.view[fsb->fsb.selectedview]; + if(view.select) { + view.select((Widget)fsb, view.widget, item); + } +} + +static char* set_selected_path(XnFileSelectionBox data, XmString item) +{ + char *name = NULL; + XmStringGetLtoR(item, XmFONTLIST_DEFAULT_TAG, &name); + if(!name) { + return NULL; + } + char *path = ConcatPath(data->fsb.currentPath, name); + XtFree(name); + + if(data->fsb.selectedPath) { + free(data->fsb.selectedPath); + } + data->fsb.selectedPath = path; + + return path; +} + +// item0: rename +// item1: delete +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd) { + intptr_t i = (intptr_t)index; + Widget parent = XtParent(item); + XnFileSelectionBox fsb = NULL; + XtVaGetValues(parent, XmNuserData, &fsb, NULL); + + const char *path = fsb->fsb.selectedPath; + if(path) { + if(i == 0) { + FSBRename(fsb, path); + } else if(i == 1) { + FSBDelete(fsb, path); + } + } +} + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback) { + return XmVaCreateSimplePopupMenu( + parent, "popup", callback, XmNpopupEnabled, XmPOPUP_AUTOMATIC, + XmNuserData, fsb, + XmVaPUSHBUTTON, fsb->fsb.labelRename, 'R', NULL, NULL, + XmVaPUSHBUTTON, fsb->fsb.labelDelete, 'D', NULL, NULL, + NULL); +} + + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) { + XnFileSelectionBox data = userData; + FileListWidgetAdd(data, data->fsb.filelist, data->fsb.showHidden, filter, files, filecount); +} + +static void FileListSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numItems = 0; + XmStringTable items = NULL; + XtVaGetValues(w->fsb.filelist, XmNitemCount, &numItems, XmNitems, &items, NULL); + + for(int i=0;i<numItems;i++) { + char *str = NULL; + XmStringGetLtoR(items[i], XmFONTLIST_DEFAULT_TAG, &str); + if(!strcmp(str, item)) { + XmListSelectPos(w->fsb.filelist, i+1, False); + break; + } + XtFree(str); + } +} + +static void FileListCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + XmListDeleteAllItems(data->fsb.filelist); +} + +static void FileListDestroy(Widget fsb, Widget view, void *userData) { + // unused +} + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.end = True; + data->fsb.status = FILEDIALOG_OK; + data->fsb.selIsDir = False; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } +} + +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *name = NULL; + XmStringGetLtoR(cb->item, XmFONTLIST_DEFAULT_TAG, &name); + XmTextFieldSetString(data->fsb.name, name); + XtFree(name); + } else { + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = False; + } + } +} + + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count) +{ + if(count > 0) { + XmStringTable items = calloc(count, sizeof(XmString)); + int i = 0; + + for(int j=0;j<count;j++) { + FileElm *e = &ls[j]; + + char *name = FileName(e->path); + if((!showHidden && name[0] == '.') || apply_filter(fsb, filter, name)) { + continue; + } + + items[i] = XmStringCreateLocalized(name); + i++; + } + XmListAddItems(w, items, i, 0); + for(i=0;i<count;i++) { + XmStringFree(items[i]); + } + free(items); + } +} + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) { + XnFileSelectionBox data = userData; + FileListDetailAdd(data, data->fsb.grid, data->fsb.showHidden, filter, files, filecount, maxnamelen); +} +#endif + +/* + * create file size string with kb/mb/gb/tb suffix + */ +static char* size_str(XnFileSelectionBox fsb, FileElm *f) { + char *str = malloc(16); + uint64_t size = f->size; + + if(f->isDirectory) { + str[0] = '\0'; + } else if(size < 0x400) { + snprintf(str, 16, "%d %s", (int)size, fsb->fsb.suffixBytes); + } else if(size < 0x100000) { + float s = (float)size/0x400; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x2800 && diff != 0) { + // size < 10 KiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixKB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixKB); + } + } else if(size < 0x40000000) { + float s = (float)size/0x100000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0xa00000 && diff != 0) { + // size < 10 MiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixMB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixMB); + } + } else if(size < 0x1000000000ULL) { + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 GiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixGB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixGB); + } + } else { + size /= 1024; + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 TiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixTB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixTB); + } + } + return str; +} + +static char* date_str(XnFileSelectionBox fsb, time_t tm) { + struct tm t; + struct tm n; + time_t now = time(NULL); + + localtime_r(&tm, &t); + localtime_r(&now, &n); + + char *str = malloc(24); + if(t.tm_year == n.tm_year) { + strftime(str, 24, fsb->fsb.dateFormatSameYear, &t); + } else { + strftime(str, 24, fsb->fsb.dateFormatOtherYear, &t); + } + return str; +} + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailAdjustColWidth(Widget grid) { + XmLGridColumn column0 = XmLGridGetColumn(grid, XmCONTENT, 0); + XmLGridColumn column1 = XmLGridGetColumn(grid, XmCONTENT, 1); + XmLGridColumn column2 = XmLGridGetColumn(grid, XmCONTENT, 2); + + Dimension col0Width = XmLGridColumnWidthInPixels(column0); + Dimension col1Width = XmLGridColumnWidthInPixels(column1); + Dimension col2Width = XmLGridColumnWidthInPixels(column2); + + Dimension totalWidth = col0Width + col1Width + col2Width; + + Dimension gridWidth = 0; + Dimension gridShadow = 0; + XtVaGetValues(grid, XmNwidth, &gridWidth, XmNshadowThickness, &gridShadow, NULL); + + Dimension widthDiff = gridWidth - totalWidth - gridShadow - gridShadow; + + if(gridWidth > totalWidth) { + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, col0Width + widthDiff - XmLGridVSBWidth(grid) - 2, + XmNcolumnSizePolicy, XmCONSTANT, + NULL); + } +} + +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth) +{ + XmLGridAddRows(grid, XmCONTENT, 1, count); + + int row = 0; + for(int i=0;i<count;i++) { + FileElm *e = &ls[i]; + + char *name = FileName(e->path); + if((!showHidden && name[0] == '.') || (!e->isDirectory && apply_filter(fsb, filter, name))) { + continue; + } + + // name + XmString str = XmStringCreateLocalized(name); + XtVaSetValues(grid, + XmNcolumn, 0, + XmNrow, row, + XmNcellString, str, NULL); + XmStringFree(str); + // size + char *szbuf = size_str(fsb, e); + str = XmStringCreateLocalized(szbuf); + XtVaSetValues(grid, + XmNcolumn, 1, + XmNrow, row, + XmNcellString, str, NULL); + free(szbuf); + XmStringFree(str); + // date + char *datebuf = date_str(fsb, e->lastModified); + str = XmStringCreateLocalized(datebuf); + XtVaSetValues(grid, + XmNcolumn, 2, + XmNrow, row, + XmNcellString, str, NULL); + free(datebuf); + XmStringFree(str); + + XtVaSetValues(grid, XmNrow, row, XmNrowUserData, e, NULL); + row++; + } + + // remove unused rows + if(count > row) { + XmLGridDeleteRows(grid, XmCONTENT, row, count-row); + } + + if(maxWidth < 16) { + maxWidth = 16; + } + + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, maxWidth, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 1, + XmNcolumnRangeEnd, 1, + XmNcolumnWidth, 9, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 2, + XmNcolumnRangeEnd, 2, + XmNcolumnWidth, 16, + XmNcellAlignment, XmALIGNMENT_RIGHT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + + FileListDetailAdjustColWidth(grid); +} + +static void FileListDetailSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numRows = 0; + XtVaGetValues(w->fsb.grid, XmNrows, &numRows, NULL); + + XmLGridColumn col = XmLGridGetColumn(w->fsb.grid, XmCONTENT, 0); + for(int i=0;i<numRows;i++) { + XmLGridRow row = XmLGridGetRow(w->fsb.grid, XmCONTENT, i); + FileElm *elm = NULL; + XtVaGetValues(w->fsb.grid, XmNrowPtr, row, XmNcolumnPtr, col, XmNrowUserData, &elm, NULL); + if(elm) { + if(!strcmp(item, FileName(elm->path))) { + XmLGridSelectRow(w->fsb.grid, i, False); + XmLGridFocusAndShowRow(w->fsb.grid, i+1); + break; + } + } + } +} + +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + // cleanup grid + Cardinal rows = 0; + XtVaGetValues(data->fsb.grid, XmNrows, &rows, NULL); + XmLGridDeleteRows(data->fsb.grid, XmCONTENT, 0, rows); +} + +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData) { + // unused +} +#endif + +static void create_folder(Widget w, XnFileSelectionBox data, XmSelectionBoxCallbackStruct *cbs) { + char *fileName = NULL; + XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + char *newFolder = ConcatPath(data->fsb.currentPath ? data->fsb.currentPath : "", fileName); + if(mkdir(newFolder, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorFolder, strerror(errno)); + ErrDialog(data, data->fsb.errorTitle, errmsg); + } else { + char *p = strdup(data->fsb.currentPath); + filedialog_update_dir(data, p); + free(p); + } + free(newFolder); + + XtDestroyWidget(XtParent(w)); +} + +static void new_folder_cancel(Widget w, XnFileSelectionBox data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u) +{ + Arg args[16]; + int n = 0; + + XtSetArg(args[n], XmNdialogTitle, data->fsb.labelNewFolder); n++; + XtSetArg (args[n], XmNselectionLabelString, data->fsb.labelDirectoryName); n++; + XtSetArg(args[n], XmNokLabelString, data->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, data->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)create_folder, data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)new_folder_cancel, data); + + XtManageChild(dialog); + +} + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u) { + const char *homePath = data->fsb.homePath ? data->fsb.homePath : GetHomeDir(); + filedialog_update_dir(data, homePath); + PathBarSetPath(data->fsb.pathBar, homePath); +} + + +/* + * file_cmp_field + * 0: compare path + * 1: compare size + * 2: compare mtime + */ +static int file_cmp_field = 0; + +/* + * 1 or -1 + */ +static int file_cmp_order = 1; + +static int filecmp(const void *f1, const void *f2) +{ + const FileElm *file1 = f1; + const FileElm *file2 = f2; + if(file1->isDirectory != file2->isDirectory) { + return file1->isDirectory < file2->isDirectory; + } + + int cmp_field = file_cmp_field; + int cmp_order = file_cmp_order; + if(file1->isDirectory) { + cmp_field = 0; + cmp_order = 1; + } + + int ret = 0; + switch(cmp_field) { + case 0: { + ret = strcmp(FileName(file1->path), FileName(file2->path)); + break; + } + case 1: { + if(file1->size < file2->size) { + ret = -1; + } else if(file1->size == file2->size) { + ret = 0; + } else { + ret = 1; + } + break; + } + case 2: { + if(file1->lastModified < file2->lastModified) { + ret = -1; + } else if(file1->lastModified == file2->lastModified) { + ret = 0; + } else { + ret = 1; + } + break; + } + } + + return ret * cmp_order; +} + + +static void free_files(FileElm *ls, int count) +{ + for(int i=0;i<count;i++) { + if(ls[i].path) { + free(ls[i].path); + } + } + free(ls); +} + +static void filedialog_cleanup_filedata(XnFileSelectionBox data) +{ + free_files(data->fsb.dirs, data->fsb.dircount); + free_files(data->fsb.files, data->fsb.filecount); + data->fsb.dirs = NULL; + data->fsb.files = NULL; + data->fsb.dircount = 0; + data->fsb.filecount = 0; + data->fsb.maxnamelen = 0; +} + +#define FILE_ARRAY_SIZE 1024 + +static void file_array_add(FileElm **files, int *alloc, int *count, FileElm elm) { + int c = *count; + int a = *alloc; + if(c >= a) { + a *= 2; + FileElm *newarray = realloc(*files, sizeof(FileElm) * a); + + *files = newarray; + *alloc = a; + } + + (*files)[c] = elm; + c++; + *count = c; +} + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path) +{ + DIR *dir = NULL; + if(path) { + // try to check first, if we can open the path + dir = opendir(path); + if(!dir) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorOpenDir, strerror(errno)); + + ErrDialog(data, data->fsb.errorTitle, errmsg); + return 1; + } + } + + FSBView view = data->fsb.view[data->fsb.selectedview]; + view.cleanup((Widget)data, view.widget, view.userData); + + if(view.useDirList) { + XmListDeleteAllItems(data->fsb.dirlist); + } + + /* read dir and insert items */ + if(path) { + int dircount = 0; + int filecount = 0; + size_t maxNameLen = 0; + + FileElm *dirs = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + FileElm *files = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + int dirs_alloc = FILE_ARRAY_SIZE; + int files_alloc = FILE_ARRAY_SIZE; + + filedialog_cleanup_filedata(data); + + /* dir reading complete - set the path textfield */ + XmTextFieldSetString(data->fsb.path, (char*)path); + char *oldPath = data->fsb.currentPath; + data->fsb.currentPath = strdup(path); + if(oldPath) { + free(oldPath); + } + path = data->fsb.currentPath; + + struct dirent *ent; + while((ent = readdir(dir)) != NULL) { + if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + + char *entpath = ConcatPath(path, ent->d_name); + + struct stat s; + if(stat(entpath, &s)) { + free(entpath); + continue; + } + + FileElm new_entry; + new_entry.path = entpath; + new_entry.isDirectory = S_ISDIR(s.st_mode); + new_entry.size = (uint64_t)s.st_size; + new_entry.lastModified = s.st_mtime; + + size_t nameLen = strlen(ent->d_name); + if(nameLen > maxNameLen) { + maxNameLen = nameLen; + } + + if(new_entry.isDirectory) { + file_array_add(&dirs, &dirs_alloc, &dircount, new_entry); + } else { + file_array_add(&files, &files_alloc, &filecount, new_entry); + } + } + closedir(dir); + + data->fsb.dirs = dirs; + data->fsb.files = files; + data->fsb.dircount = dircount; + data->fsb.filecount = filecount; + data->fsb.maxnamelen = maxNameLen; + + // sort file arrays + qsort(dirs, dircount, sizeof(FileElm), filecmp); + qsort(files, filecount, sizeof(FileElm), filecmp); + } + + Widget filterTF = XmDropDownGetText(data->fsb.filter); + char *filter = XmTextFieldGetString(filterTF); + char *filterStr = filter; + if(!filter || strlen(filter) == 0) { + filterStr = "*"; + } + + if(view.useDirList) { + FileListWidgetAdd(data, data->fsb.dirlist, data->fsb.showHidden, NULL, data->fsb.dirs, data->fsb.dircount); + view.update( + (Widget)data, + view.widget, + NULL, + 0, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } else { + view.update( + (Widget)data, + view.widget, + data->fsb.dirs, + data->fsb.dircount, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } + + if(filter) { + XtFree(filter); + } + + return 0; +} + + +static void dirlist_activate(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + if(!filedialog_update_dir(data, path)) { + PathBarSetPath(data->fsb.pathBar, path); + data->fsb.selIsDir = TRUE; + } + } +} + +static void dirlist_select(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = TRUE; + } +} + +static void filedialog_enable_detailview(Widget w, XnFileSelectionBox data, XmToggleButtonCallbackStruct *tb) { + SelectView(data, tb->set); // 0: list, 1: detail +} + + +static void filedialog_setshowhidden( + Widget w, + XnFileSelectionBox data, + XmToggleButtonCallbackStruct *tb) +{ + data->fsb.showHidden = tb->set; + filedialog_update_dir(data, NULL); +} + +static void filedialog_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); +} + +static void filedialog_update_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); + +} + +static void filedialog_goup(Widget w, XnFileSelectionBox data, XtPointer d) +{ + char *newPath = ParentPath(data->fsb.currentPath); + filedialog_update_dir(data, newPath); + PathBarSetPath(data->fsb.pathBar, newPath); + free(newPath); +} + +static void filedialog_ok(Widget w, XnFileSelectionBox data, XtPointer d) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *newName = XmTextFieldGetString(data->fsb.name); + if(newName) { + if(strchr(newName, '/')) { + ErrDialog(data, data->fsb.errorTitle, data->fsb.errorIllegalChar); + XtFree(newName); + return; + } + + if(strlen(newName) > 0) { + char *selPath = ConcatPath(data->fsb.currentPath, newName); + if(data->fsb.selectedPath) free(data->fsb.selectedPath); + data->fsb.selectedPath = selPath; + } + XtFree(newName); + + data->fsb.selIsDir = False; + } + } + + if(data->fsb.selectedPath) { + if(!data->fsb.selIsDir) { + data->fsb.status = FILEDIALOG_OK; + data->fsb.end = True; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } + } +} + +static void filedialog_cancel(Widget w, XnFileSelectionBox data, XtPointer d) +{ + data->fsb.end = 1; + data->fsb.status = FILEDIALOG_CANCEL; + FileSelectionCallback(data, data->fsb.cancelCallback, XmCR_CANCEL, data->fsb.currentPath); +} + +static void filedialog_help(Widget w, XnFileSelectionBox data, XtPointer d) +{ + FileSelectionCallback(data, data->manager.help_callback, XmCR_HELP, data->fsb.currentPath); +} + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value) { + XmFileSelectionBoxCallbackStruct cbs; + memset(&cbs, 0, sizeof(XmFileSelectionBoxCallbackStruct)); + + char *dir = fsb->fsb.currentPath; + size_t dirlen = dir ? strlen(dir) : 0; + if(dir && dirlen > 0) { + char *dir2 = NULL; + if(dir[dirlen-1] != '/') { + // add a trailing / to the dir string + dir2 = malloc(dirlen+2); + memcpy(dir2, dir, dirlen); + dir2[dirlen] = '/'; + dir2[dirlen+1] = '\0'; + dirlen++; + dir = dir2; + } + cbs.dir = XmStringCreateLocalized(dir); + cbs.dir_length = dirlen; + if(dir2) { + free(dir2); + } + } else { + cbs.dir = XmStringCreateLocalized(""); + cbs.dir_length = 0; + } + cbs.reason = reason; + + cbs.value = XmStringCreateLocalized((char*)value); + cbs.length = strlen(value); + + XtCallCallbackList((Widget)fsb, cb, (XtPointer)&cbs); + + XmStringFree(cbs.dir); + XmStringFree(cbs.value); +} + +static void CreateUI(XnFileSelectionBox w) { + Arg args[32]; + int n = 0; + XmString str; + + int widget_spacing = w->fsb.widgetSpacing; + int window_spacing = w->fsb.windowSpacing; + + Widget form = (Widget)w; + int type = w->fsb.type; + + XtVaSetValues((Widget)w, XmNautoUnmanage, False, NULL); + + /* upper part of the gui */ + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelDirUp); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNresizable, True); n++; + XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++; + w->fsb.dirUp = XmCreatePushButton(form, "DirUp", args, n); + XtManageChild(w->fsb.dirUp); + XtAddCallback(w->fsb.dirUp, XmNactivateCallback, + (XtCallbackProc)filedialog_goup, w); + + // View Option Menu + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget viewframe = XmCreateForm(form, "vframe", args, n); + XtManageChild(viewframe); + + w->fsb.viewMenu = XmCreatePulldownMenu(viewframe, "menu", NULL, 0); + + Widget view; + if(w->fsb.showViewMenu) { + n = 0; + XtSetArg(args[n], XmNsubMenuId, w->fsb.viewMenu); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNmarginWidth, 0); n++; + view = XmCreateOptionMenu(viewframe, "option_menu", args, n); + XtManageChild(view); + w->fsb.viewOption = view; + w->fsb.detailToggleButton = NULL; + } else { + n = 0; + str = XmStringCreateLocalized(w->fsb.labelDetailView); + XtSetArg(args[n], XmNlabelString, str); n++; + XtSetArg(args[n], XmNfillOnSelect, True); n++; + XtSetArg(args[n], XmNindicatorOn, False); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + if(w->fsb.selectedview == 1) { + XtSetArg(args[n], XmNset, 1); n++; + } + w->fsb.detailToggleButton = XmCreateToggleButton(viewframe, "ToggleDetailView", args, n); + XtManageChild(w->fsb.detailToggleButton); + view = w->fsb.detailToggleButton; + XmStringFree(str); + + XtAddCallback( + w->fsb.detailToggleButton, + XmNvalueChangedCallback, + (XtCallbackProc)filedialog_enable_detailview, + w); + + w->fsb.viewOption = NULL; + } + + n = 0; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightWidget, view); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelNewFolder); n++; + w->fsb.newFolder = XmCreatePushButton(viewframe, "NewFolder", args, n); + XtManageChild(w->fsb.newFolder); + XtAddCallback( + w->fsb.newFolder, + XmNactivateCallback, + (XtCallbackProc)FSBNewFolder, + w); + + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelHome); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.newFolder); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + w->fsb.home = XmCreatePushButton(viewframe, "Home", args, n); + XtManageChild(w->fsb.home); + XtAddCallback( + w->fsb.home, + XmNactivateCallback, + (XtCallbackProc)FSBHome, + w); + + // match visual appearance of detailToggleButton with the other buttons + if(w->fsb.detailToggleButton) { + Dimension highlight, shadow; + XtVaGetValues(w->fsb.newFolder, XmNshadowThickness, &shadow, XmNhighlightThickness, &highlight, NULL); + XtVaSetValues(w->fsb.detailToggleButton, XmNshadowThickness, shadow, XmNhighlightThickness, highlight, NULL); + } + + // pathbar + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirUp); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, viewframe); n++; + XtSetArg(args[n], XmNrightOffset, widget_spacing); n++; + XtSetArg(args[n], XmNshadowType, XmSHADOW_IN); n++; + Widget pathBarFrame = XmCreateFrame(form, "pathbar_frame", args, n); + XtManageChild(pathBarFrame); + w->fsb.pathBar = CreatePathBar(pathBarFrame, args, 0); + w->fsb.pathBar->updateDir = (updatedir_callback)filedialog_update_dir; + w->fsb.pathBar->updateDirData = w; + XtManageChild(w->fsb.pathBar->widget); + w->fsb.path = XmCreateTextField(form, "textfield", args, 0); + + XtVaSetValues(w->fsb.dirUp, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL); + if(!w->fsb.showViewMenu) { + XtVaSetValues(viewframe, XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET, XmNbottomWidget, pathBarFrame, NULL); + } + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, pathBarFrame); n++; + XtSetArg(args[n], XmNtopOffset, 2*widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + w->fsb.filterForm = XmCreateForm(form, "filterform", args, n); + XtManageChild(w->fsb.filterForm); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelDirectories); n++; + w->fsb.lsDirLabel = XmCreateLabel(w->fsb.filterForm, "labelDirs", args, n); + XtManageChild(w->fsb.lsDirLabel); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 35); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++; + w->fsb.lsFileLabel = XmCreateLabel(w->fsb.filterForm, "labelFiles", args, n); + XtManageChild(w->fsb.lsFileLabel); + + if(w->fsb.showHiddenButton) { + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelShowHiddenFiles); n++; + XtSetArg(args[n], XmNset, w->fsb.showHidden); n++; + w->fsb.showHiddenButtonW = XmCreateToggleButton(w->fsb.filterForm, "showHidden", args, n); + XtManageChild(w->fsb.showHiddenButtonW); + XtAddCallback(w->fsb.showHiddenButtonW, XmNvalueChangedCallback, + (XtCallbackProc)filedialog_setshowhidden, w); + } + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFilterButton); n++; + if(w->fsb.showHiddenButton) { + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.showHiddenButtonW); n++; + } else { + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + } + w->fsb.filterButton = XmCreatePushButton(w->fsb.filterForm, "filedialog_filter", args, n); + XtManageChild(w->fsb.filterButton); + XtAddCallback(w->fsb.filterButton, XmNactivateCallback, + (XtCallbackProc)filedialog_filter, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.lsFileLabel); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.filterButton); n++; + XtSetArg(args[n], XmNrightOffset, widget_spacing); n++; + XtSetArg(args[n], XmNshowLabel, False); n++; + XtSetArg(args[n], XmNuseTextField, True); n++; + XtSetArg(args[n], XmNverify, False); n++; + w->fsb.filter = XmCreateDropDown(w->fsb.filterForm, "filedialog_filter_textfield", args, n); + XtManageChild(w->fsb.filter); + XmTextFieldSetString(XmDropDownGetText(w->fsb.filter), w->fsb.filterStr); + XtAddCallback(XmDropDownGetText(w->fsb.filter), XmNactivateCallback, + (XtCallbackProc)filedialog_filter, w); + XtAddCallback(w->fsb.filter, XmNupdateTextCallback, + (XtCallbackProc)filedialog_update_filter, w); + Widget filterList = XmDropDownGetList(w->fsb.filter); + str = XmStringCreateSimple("*"); + XmListAddItem(filterList, str, 0); + XmStringFree(str); + + /* lower part */ + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNtopOffset, widget_spacing * 2); n++; + Widget buttons = XmCreateForm(form, "buttons", args, n); + XtManageChild(buttons); + + n = 0; + str = type == FILEDIALOG_OPEN ? w->fsb.labelOpen : w->fsb.labelSave; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, str); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 14); n++; + w->fsb.okBtn = XmCreatePushButton(buttons, "filedialog_open", args, n); + XtManageChild(w->fsb.okBtn); + XmStringFree(str); + XtAddCallback(w->fsb.okBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_ok, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelHelp); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 86); n++; + w->fsb.helpBtn = XmCreatePushButton(buttons, "filedialog_help", args, n); + XtManageChild(w->fsb.helpBtn); + XtAddCallback(w->fsb.helpBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_help, w); + + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNleftPosition, 43); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 57); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelCancel); n++; + w->fsb.cancelBtn = XmCreatePushButton(buttons, "filedialog_cancel", args, n); + XtManageChild(w->fsb.cancelBtn); + XtAddCallback(w->fsb.cancelBtn, XmNactivateCallback, + (XtCallbackProc)filedialog_cancel, w); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, buttons); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, 1); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, 1); n++; + w->fsb.separator = XmCreateSeparator(form, "ofd_separator", args, n); + XtManageChild(w->fsb.separator); + + Widget bottomWidget = w->fsb.separator; + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, w->fsb.separator); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + w->fsb.name = XmCreateTextField(form, "textfield", args, n); + XtAddCallback(w->fsb.name, XmNactivateCallback, + (XtCallbackProc)filedialog_ok, w); + + n = 0; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, w->fsb.name); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFileName); n++; + w->fsb.nameLabel = XmCreateLabel(form, "label", args, n); + + if(type == FILEDIALOG_SAVE) { + bottomWidget = w->fsb.nameLabel; + XtManageChild(w->fsb.name); + XtManageChild(w->fsb.nameLabel); + } + w->fsb.bottom_widget = bottomWidget; + + + // middle + // form for dir/file lists + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNtopWidget, w->fsb.filterForm); n++; + XtSetArg(args[n], XmNtopOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomWidget, bottomWidget); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNwidth, 580); n++; + XtSetArg(args[n], XmNheight, 400); n++; + w->fsb.listform = XmCreateForm(form, "fds_listform", args, n); + + // dir/file lists + + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopWidget, w->fsb.lsDirLabel); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++; + XtSetArg(args[n], XmNrightPosition, 35); n++; + w->fsb.dirlist = XmCreateScrolledList(w->fsb.listform, "dirlist", args, n); + Dimension width, height; + XtMakeResizeRequest(w->fsb.dirlist, 150, 200, &width, &height); + XtManageChild(w->fsb.dirlist); + + XtAddCallback( + w->fsb.dirlist, + XmNdefaultActionCallback, + (XtCallbackProc)dirlist_activate, + w); + XtAddCallback( + w->fsb.dirlist, + XmNbrowseSelectionCallback, + (XtCallbackProc)dirlist_select, + w); + + // FileList + XnFileSelectionBoxAddView( + (Widget)w, + w->fsb.labelListView, + CreateListView, + FileListUpdate, + FileListSelect, + FileListCleanup, + FileListDestroy, + True, + w); + + // Detail FileList +#ifdef FSB_ENABLE_DETAIL + XnFileSelectionBoxAddView( + (Widget)w, + w->fsb.labelDetailView, + CreateDetailView, + FileListDetailUpdate, + FileListDetailSelect, + FileListDetailCleanup, + FileListDetailDestroy, + True, + w); +#endif + + /* + n = 0; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + //XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; + //XtSetArg(args[n], XmNbottomWidget, w->fsb.filelist); n++; + XtSetArg(args[n], XmNbottomOffset, widget_spacing); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelFiles); n++; + w->fsb.lsFileLabel = XmCreateLabel(w->fsb.listform, "label", args, n); + XtManageChild(w->fsb.lsFileLabel); + */ + + XtManageChild(w->fsb.listform); + + int selview = w->fsb.selectedview; + if(selview < 2) { + XtManageChild(w->fsb.view[selview].widget); + } else { + w->fsb.selectedview = 0; + } + + + if(w->fsb.selectedPath) { + char *parentPath = ParentPath(w->fsb.selectedPath); + filedialog_update_dir(w, parentPath); + PathBarSetPath(w->fsb.pathBar, parentPath); + free(parentPath); + + if(w->fsb.type == FILEDIALOG_SAVE) { + XmTextFieldSetString(w->fsb.name, FileName(w->fsb.selectedPath)); + } + } else { + char cwd[PATH_MAX]; + const char *currentPath = w->fsb.currentPath; + if(!currentPath) { + if(getcwd(cwd, PATH_MAX)) { + currentPath = cwd; + } else { + currentPath = GetHomeDir(); + } + } + + filedialog_update_dir(w, currentPath); + PathBarSetPath(w->fsb.pathBar, w->fsb.currentPath); + } + + + w->fsb.selectedview = selview; + + XtVaSetValues((Widget)w, XmNcancelButton, w->fsb.cancelBtn, NULL); + + w->fsb.gui_created = 1; +} + +static char* FSBDialogTitle(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + if(w->fsb.type == FILEDIALOG_OPEN) { + return w->fsb.labelOpenFileTitle; + } else { + return w->fsb.labelSaveFileTitle; + } +} + +static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc create, void *userData, Boolean useDirList) { + Arg args[64]; + int n = 0; + + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + if(useDirList) { + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirlist); n++; + XtSetArg(args[n], XmNleftOffset, w->fsb.widgetSpacing); n++; + } else { + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, w->fsb.widgetSpacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + } + + return create(w->fsb.listform, args, n, userData); +} + + +typedef struct FSBViewSelection { + XnFileSelectionBox fsb; + int index; +} FSBViewSelection; + +static void SelectView(XnFileSelectionBox f, int view) { + FSBView current = f->fsb.view[f->fsb.selectedview]; + FSBView newview = f->fsb.view[view]; + + XtUnmanageChild(current.widget); + if(newview.useDirList != current.useDirList) { + if(current.useDirList) { + XtUnmanageChild(f->fsb.listform); + } else { + XtManageChild(f->fsb.listform); + } + } + + current.cleanup((Widget)f, current.widget, current.userData); + XtManageChild(newview.widget); + + f->fsb.selectedview = view; + + filedialog_update_dir(f, NULL); + XmProcessTraversal(newview.focus, XmTRAVERSE_CURRENT); +} + +static void SelectViewCallback(Widget w, FSBViewSelection *data, XtPointer u) { + SelectView(data->fsb, data->index); +} + +static void SelectViewItemDestroy(Widget w, FSBViewSelection *data, XtPointer u) { + free(data); +} + +static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex) { + Arg args[4]; + int n = 0; + + XmString label = XmStringCreateLocalized((char*)name); + + XtSetArg(args[n], XmNlabelString, label); n++; + XtSetArg(args[1], XmNpositionIndex, w->fsb.selectedview == w->fsb.numviews ? 0 : w->fsb.numviews+1); n++; + Widget item = XmCreatePushButton(w->fsb.viewMenu, "menuitem", args, n); + + if(viewIndex == 0) { + w->fsb.viewSelectorList = item; + } else if(viewIndex == 1) { + w->fsb.viewSelectorDetail = item; + } + + XtManageChild(item); + XmStringFree(label); + + FSBViewSelection *data = malloc(sizeof(FSBViewSelection)); + data->fsb = w; + data->index = viewIndex; + + XtAddCallback( + item, + XmNactivateCallback, + (XtCallbackProc)SelectViewCallback, + data); + XtAddCallback( + item, + XmNdestroyCallback, + (XtCallbackProc)SelectViewItemDestroy, + data); +} + +static FSBViewWidgets CreateListView(Widget parent, ArgList args, int n, void *userData) { + XnFileSelectionBox fsb = (XnFileSelectionBox)userData; + + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget frame = XmCreateFrame(parent, "filelistframe", args, n); + + fsb->fsb.filelist = XmCreateScrolledList(frame, "filelist", NULL, 0); + XtManageChild(fsb->fsb.filelist); + + XtAddCallback( + fsb->fsb.filelist, + XmNdefaultActionCallback, + (XtCallbackProc)FileListActivateCB, + userData); + XtAddCallback( + fsb->fsb.filelist, + XmNbrowseSelectionCallback, + (XtCallbackProc)FileListSelectCB, + userData); + + fsb->fsb.listContextMenu = CreateContextMenu(fsb, fsb->fsb.filelist, FileContextMenuCB); + + FSBViewWidgets widgets; + widgets.view = frame; + widgets.focus = fsb->fsb.filelist; + return widgets; +} + +#ifdef FSB_ENABLE_DETAIL +static void set_path_from_row(XnFileSelectionBox data, int row) { + FileElm *elm = NULL; + XmLGridRow rowPtr = XmLGridGetRow(data->fsb.grid, XmCONTENT, row); + XtVaGetValues(data->fsb.grid, XmNrowPtr, rowPtr, XmNrowUserData, &elm, NULL); + if(!elm) { + fprintf(stderr, "error: no row data\n"); + return; + } + + char *path = strdup(elm->path); + + data->fsb.selIsDir = False; + if(data->fsb.type == FILEDIALOG_SAVE) { + XmTextFieldSetString(data->fsb.name, FileName(path)); + } + + if(data->fsb.selectedPath) { + free(data->fsb.selectedPath); + } + data->fsb.selectedPath = path; +} + +static void grid_select(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + set_path_from_row(data, cb->row); +} + +static void grid_activate(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + set_path_from_row(data, cb->row); + data->fsb.end = True; + data->fsb.status = FILEDIALOG_OK; + + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); +} + +static void grid_key_pressed(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + char chars[16]; + KeySym keysym; + int nchars; + + nchars = XLookupString(&cb->event->xkey, chars, 15, &keysym, NULL); + + if(nchars == 0) return; + + // if data->showHidden is 0, data->files contains more items than the grid + // this means SelectedRow might not be the correct index for data->files + // we have to count files manually and increase 'row', if the file + // is actually displayed in the grid + int row = 0; + int selectedRow = XmLGridGetSelectedRow(w); + + int match = -1; + + for(int i=0;i<data->fsb.filecount;i++) { + const char *name = FileName(data->fsb.files[i].path); + if(!data->fsb.showHidden && name[0] == '.') continue; + + size_t namelen = strlen(name); + + size_t cmplen = namelen < nchars ? namelen : nchars; + if(!memcmp(name, chars, cmplen)) { + if(row <= selectedRow) { + if(match == -1) { + match = row; + } + } else { + match = row; + break; + } + } + + row++; + } + + if(match > -1) { + XmLGridSelectRow(w, match, True); + XmLGridFocusAndShowRow(w, match+1); + } else { + XBell(XtDisplay(w), 0); + } +} + +static void grid_header_clicked(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + int new_cmp_field = 0; + switch(cb->column) { + case 0: { + new_cmp_field = 0; + break; + } + case 1: { + new_cmp_field = 1; + break; + } + case 2: { + new_cmp_field = 2; + break; + } + } + + if(new_cmp_field == file_cmp_field) { + file_cmp_order = -file_cmp_order; // revert sort order + } else { + file_cmp_field = new_cmp_field; // change file cmp order to new field + file_cmp_order = 1; + } + + int sort_type = file_cmp_order == 1 ? XmSORT_ASCENDING : XmSORT_DESCENDING; + XmLGridSetSort(data->fsb.grid, file_cmp_field, sort_type); + + qsort(data->fsb.files, data->fsb.filecount, sizeof(FileElm), filecmp); + + // refresh widget + filedialog_update_dir(data, NULL); +} + +static FSBViewWidgets CreateDetailView(Widget parent, ArgList args, int n, void *userData) { + XnFileSelectionBox w = userData; + + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget gridcontainer = XmCreateFrame(parent, "gridcontainer", args, n); + XtManageChild(gridcontainer); + + n = 0; + XtSetArg(args[n], XmNcolumns, 3); n++; + XtSetArg(args[n], XmNheadingColumns, 0); n++; + XtSetArg(args[n], XmNheadingRows, 1); n++; + XtSetArg(args[n], XmNallowColumnResize, 1); n++; + XtSetArg(args[n], XmNsimpleHeadings, w->fsb.detailHeadings); n++; + XtSetArg(args[n], XmNhorizontalSizePolicy, XmCONSTANT); n++; + + w->fsb.grid = XmLCreateGrid(gridcontainer, "grid", args, n); + XmLGridSetIgnoreModifyVerify(w->fsb.grid, True); + XtManageChild(w->fsb.grid); + + XtVaSetValues( + w->fsb.grid, + XmNcellDefaults, True, + XtVaTypedArg, XmNblankBackground, XmRString, "white", 6, + XtVaTypedArg, XmNcellBackground, XmRString, "white", 6, + NULL); + + XtAddCallback(w->fsb.grid, XmNselectCallback, (XtCallbackProc)grid_select, w); + XtAddCallback(w->fsb.grid, XmNactivateCallback, (XtCallbackProc)grid_activate, w); + XtAddCallback(w->fsb.grid, XmNheaderClickCallback, (XtCallbackProc)grid_header_clicked, w); + XtAddCallback(w->fsb.grid, XmNgridKeyPressedCallback, (XtCallbackProc)grid_key_pressed, w); + + // context menu + w->fsb.gridContextMenu = CreateContextMenu(w, w->fsb.grid, FileContextMenuCB); + + FSBViewWidgets widgets; + widgets.view = gridcontainer; + widgets.focus = w->fsb.grid; + return widgets; +} +#endif + + +/* ------------------------------ Path Utils ------------------------------ */ + +const char* GetHomeDir(void) { + char *home = getenv("HOME"); + if(!home) { + home = getenv("USERPROFILE"); + if(!home) { + home = "/"; + } + } + return home; +} + +static char* ConcatPath(const char *parent, const char *name) +{ + size_t parentlen = strlen(parent); + size_t namelen = strlen(name); + + size_t pathlen = parentlen + namelen + 2; + char *path = malloc(pathlen); + + memcpy(path, parent, parentlen); + if(parentlen > 0 && parent[parentlen-1] != '/') { + path[parentlen] = '/'; + parentlen++; + } + if(name[0] == '/') { + name++; + namelen--; + } + memcpy(path+parentlen, name, namelen); + path[parentlen+namelen] = '\0'; + return path; +} + +static char* FileName(char *path) { + int si = 0; + int osi = 0; + int i = 0; + int p = 0; + char c; + while((c = path[i]) != 0) { + if(c == '/') { + osi = si; + si = i; + p = 1; + } + i++; + } + + char *name = path + si + p; + if(name[0] == 0) { + name = path + osi + p; + if(name[0] == 0) { + return path; + } + } + + return name; +} + +static char* ParentPath(const char *path) { + char *name = FileName((char*)path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + if(parentlen == 0) { + parentlen++; + } + char *parent = malloc(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + +// unused at the moment, maybe reactivate if more illegal characters +// are defined +/* +static int CheckFileName(const char *fileName) { + size_t len = strlen(fileName); + for(int i=0;i<len;i++) { + if(fileName[i] == '/') { + return 0; + } + } + return 1; +} +*/ + + +/* ------------------------------ public API ------------------------------ */ + +Widget XnCreateFileSelectionDialog( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount) +{ + Widget dialog = XmCreateDialogShell(parent, "FileDialog", NULL, 0); + Widget fsb = XnCreateFileSelectionBox(dialog, name, arglist, argcount); + char *title = FSBDialogTitle(fsb); + XtVaSetValues(dialog, XmNtitle, title, NULL); + return fsb; +} + +Widget XnCreateFileSelectionBox( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount) +{ + Widget fsb = XtCreateWidget(name, xnFsbWidgetClass, parent, arglist, argcount); + return fsb; +} + +void XnFileSelectionBoxAddView( + Widget fsb, + const char *name, + FSBViewCreateProc create, + FSBViewUpdateProc update, + FSBViewSelectProc select, + FSBViewCleanupProc cleanup, + FSBViewDestroyProc destroy, + Boolean useDirList, + void *userData) +{ + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + if(f->fsb.numviews >= FSB_MAX_VIEWS) { + fprintf(stderr, "XnFileSelectionBox: too many views\n"); + return; + } + + FSBView view; + view.update = update; + view.select = select; + view.cleanup = cleanup; + view.destroy = destroy; + view.useDirList = useDirList; + view.userData = userData; + + FSBViewWidgets widgets = CreateView(f, create, userData, useDirList); + view.widget = widgets.view; + view.focus = widgets.focus; + + AddViewMenuItem(f, name, f->fsb.numviews); + + f->fsb.view[f->fsb.numviews++] = view; +} + +/* +void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm) { + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + PathBarSetDirList(f->fsb.pathBar, dirlist, nelm); +} +*/ + +Widget XnFileSelectionBoxWorkArea(Widget fsb) { + XnFileSelectionBox f = (XnFileSelectionBox)fsb; + return f->fsb.workarea; +} + +Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + switch(child) { + case XnFSB_DIR_UP_BUTTON: return w->fsb.dirUp; + case XnFSB_HOME_BUTTON: return w->fsb.home; + case XnFSB_NEW_FOLDER_BUTTON: return w->fsb.newFolder; + case XnFSB_DETAIL_TOGGLE_BUTTON: return w->fsb.detailToggleButton; + case XnFSB_VIEW_OPTION_BUTTON: return w->fsb.viewOption; + case XnFSB_FILTER_DROPDOWN: return w->fsb.filter; + case XnFSB_FILTER_BUTTON: return w->fsb.filterButton; + case XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON: return w->fsb.showHiddenButtonW; + case XnFSB_DIRECTORIES_LABEL: return w->fsb.lsDirLabel; + case XnFSB_FILES_LABEL: return w->fsb.lsFileLabel; + case XnFSB_DIRLIST: return w->fsb.dirlist; + case XnFSB_FILELIST: return w->fsb.filelist; + case XnFSB_GRID: return w->fsb.grid; + case XnFSB_OK_BUTTON: return w->fsb.okBtn; + case XnFSB_CANCEL_BUTTON: return w->fsb.cancelBtn; + case XnFSB_HELP_BUTTON: return w->fsb.helpBtn; + } + return NULL; +} + +void XnFileSelectionBoxDeleteFilters(Widget fsb) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + Widget filterList = XmDropDownGetList(w->fsb.filter); + XmListDeleteAllItems(filterList); +} + +void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + Widget filterList = XmDropDownGetList(w->fsb.filter); + + XmString str = XmStringCreateSimple((char*)filter); + XmListAddItem(filterList, str, 0); + XmStringFree(str); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/Fsb.h Sun Nov 23 08:35:40 2025 +0100 @@ -0,0 +1,233 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSB_H +#define FSB_H + +#include <X11/Intrinsic.h> +#include <Xm/PrimitiveP.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern WidgetClass xnFsbWidgetClass; + +#define FILEDIALOG_OPEN 1 +#define FILEDIALOG_SAVE 2 + +#define FILEDIALOG_OK 1 +#define FILEDIALOG_CANCEL 2 + +#define XnNwidgetSpacing "fsbWidgetSpacing" +#define XnNwindowSpacing "fsbWindowSpacing" + +#define XnNfsbType "fsbType" +#define XnCfsbType "fsbType" + +#define XnNshowHidden "showHidden" +#define XnCshowHidden "showHidden" +#define XnNshowHiddenButton "showHiddenButton" +#define XnCshowHiddenButton "showHiddenButton" + +#define XnNshowViewMenu "showViewMenu" +#define XnCshowViewMenu "showViewMenu" + +#define XnNselectedView "fsbSelectedView" +#define XnCselectedView "fsbSelectedView" + +#define XnNdirectory "directory" +#define XnCdirectory "directory" +#define XnNselectedPath "selectedPath" +#define XnCselectedPath "selectedPath" +#define XnNhomePath "homePath" +#define XnChomePath "homePath" + +#define XnNfilter "filter" +#define XnCfilter "filter" + +#define XnNfilterFunc "filterFunc" +#define XnCfilterFunc "filterFunc" + +#define XnNlabelListView "labelListView" +#define XnClabelListView "labelListView" +#define XnNlabelDetailView "labelDetailView" +#define XnClabelDetailView "labelDetailView" +#define XnNlabelOpenFileTitle "labelOpenFileTitle" +#define XnClabelOpenFileTitle "labelOpenFileTitle" +#define XnNlabelSaveFileTitle "labelSaveFileTitle" +#define XnClabelSaveFileTitle "labelSaveFileTitel" +#define XnNlabelDirUp "labelDirUp" +#define XnClabelDirUp "labelDirUp" +#define XnNlabelHome "labelHome" +#define XnClabelHome "labelHome" +#define XnNlabelNewFolder "labelNewFolder" +#define XnClabelNewFolder "labelNewFolder" +#define XnNlabelFilter "labelFilter" +#define XnClabelFilter "labelFilter" +#define XnNlabelFilterButton "labelFilterButton" +#define XnClabelFilterButton "labelFilterButton" +#define XnNlabelShowHiddenFiles "labelShowHiddenFiles" +#define XnClabelShowHiddenFiles "labelShowHiddenFiles" +#define XnNlabelDirectories "labelDirectories" +#define XnClabelDirectories "labelDirectories" +#define XnNlabelFiles "labelFiles" +#define XnClabelFiles "labelFiles" +#define XnNlabelRename "labelRename" +#define XnClabelRename "labelRename" +#define XnNlabelDelete "labelDelete" +#define XnClabelDelete "labelDelete" +#define XnNlabelOpen "labelOpen" +#define XnClabelOpen "labelOpen" +#define XnNlabelSave "labelSave" +#define XnClabelSave "labelSave" +#define XnNlabelOk "labelOk" +#define XnClabelOk "labelOk" +#define XnNlabelCancel "labelCancel" +#define XnClabelCancel "labelCancel" +#define XnNlabelHelp "labelHelp" +#define XnClabelHelp "labelHelp" +#define XnNlabelFileName "labelFileName" +#define XnClabelFileName "labelFileName" +#define XnNlabelDirectoryName "labelDirectoryName" +#define XnClabelDirectoryName "labelDirectoryName" +#define XnNlabelNewFileName "labelNewFileName" +#define XnClabelNewFileName "labelNewFileName" +#define XnNlabelDeleteFile "labelDeleteFile" +#define XnClabelDeleteFile "labelDeleteFile" +#define XnNdetailHeadings "detailHeadings" +#define XnCdetailHeadings "detailHeadings" +#define XnNdateFormatSameYear "dateFormatSameYear" +#define XnCdateFormatSameYear "dateFormatSameYear" +#define XnNdateFormatOtherYear "dateFormatOtherYear" +#define XnCdateFormatOtherYear "dateFormatOtherYear" +#define XnNsuffixBytes "suffixBytes" +#define XnCsuffixBytes "suffixBytes" +#define XnNsuffixKB "suffixKB" +#define XnCsuffixKB "suffixKB" +#define XnNsuffixMB "suffixMB" +#define XnCsuffixMB "suffixMB" +#define XnNsuffixGB "suffixGB" +#define XnCsuffixGB "suffixGB" +#define XnNsuffixTB "suffixTB" +#define XnCsuffixTB "suffixTB" +#define XnNerrorTitle "errorTitle" +#define XnCerrorTitle "errorTitle" +#define XnNerrorIllegalChar "errorIllegalChar" +#define XnCerrorIllegalChar "errorIllegalChar" +#define XnNerrorRename "errorRename" +#define XnCerrorRename "errorRename" +#define XnNerrorCreateFolder "errorCreateFolder" +#define XnCerrorCreateFolder "errorCreateFolder" +#define XnNerrorDelete "errorDelete" +#define XnCerrorDelete "errorDelete" +#define XnNerrorOpenDir "errorOpenDir" +#define XnCerrorOpenDir "errorOpenDir" + +/* + * int FSBFilterFunc(const char *pattern, const char *string) + * + * Checks whether the string matches the pattern + * + * Return + * zero if the string matches the pattern + * non-zero if there is no match + */ +typedef int(*FSBFilterFunc)(const char*, const char*); + + +typedef struct FileElm FileElm; +struct FileElm { + char *path; + int isDirectory; + unsigned long long size; + time_t lastModified; +}; + +typedef struct { + Widget view; + Widget focus; +} FSBViewWidgets; + +enum XnFSBChild { + XnFSB_DIR_UP_BUTTON = 0, + XnFSB_HOME_BUTTON, + XnFSB_NEW_FOLDER_BUTTON, + XnFSB_DETAIL_TOGGLE_BUTTON, + XnFSB_VIEW_OPTION_BUTTON, + XnFSB_FILTER_DROPDOWN, + XnFSB_FILTER_BUTTON, + XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON, + XnFSB_DIRECTORIES_LABEL, + XnFSB_FILES_LABEL, + XnFSB_DIRLIST, + XnFSB_FILELIST, + XnFSB_GRID, + XnFSB_OK_BUTTON, + XnFSB_CANCEL_BUTTON, + XnFSB_HELP_BUTTON +}; + +typedef FSBViewWidgets(*FSBViewCreateProc)(Widget parent, ArgList args, int n, void *userData); +typedef void(*FSBViewUpdateProc)(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +typedef void(*FSBViewSelectProc)(Widget fsb, Widget view, const char *item); +typedef void(*FSBViewCleanupProc)(Widget fsb, Widget view, void *userData); +typedef void(*FSBViewDestroyProc)(Widget fsb, Widget view, void *userData); + +Widget XnCreateFileSelectionDialog( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +Widget XnCreateFileSelectionBox( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +void XnFileSelectionBoxAddView( + Widget fsb, + const char *name, + FSBViewCreateProc create, + FSBViewUpdateProc update, + FSBViewSelectProc select, + FSBViewCleanupProc cleanup, + FSBViewDestroyProc destroy, + Boolean useDirList, + void *userData); + +//void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm); + +Widget XnFileSelectionBoxWorkArea(Widget fsb); + +Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child); + +void XnFileSelectionBoxDeleteFilters(Widget fsb); + +void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter); + +#ifdef __cplusplus +} +#endif + +#endif /* FSB_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/FsbP.h Sun Nov 23 08:35:40 2025 +0100 @@ -0,0 +1,212 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSBP_H +#define FSBP_H + +#include <X11/CoreP.h> +#include <Xm/XmP.h> +#include <Xm/PrimitiveP.h> +#include <Xm/ManagerP.h> +#include <Xm/FormP.h> + +#include "Fsb.h" +#include "pathbar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FSB_MAX_VIEWS 8 + + +typedef struct FSBView FSBView; +struct FSBView { + Widget widget; + Widget focus; + FSBViewUpdateProc update; + FSBViewSelectProc select; + FSBViewCleanupProc cleanup; + FSBViewDestroyProc destroy; + void *userData; + Boolean useDirList; +}; + + +typedef struct FSBClassPart { + int unused; +} FSBClassPart; + +typedef struct FSBClassRec { + CoreClassPart core_class; + CompositeClassPart composite_class; + ConstraintClassPart constraint_class; + XmManagerClassPart manager_class; + XmBulletinBoardClassPart bulletin_board_class; + XmFormClassPart form_class; + FSBClassPart fsb_class; +} FSBClassRec; + +typedef struct FSBPart { + XtCallbackList okCallback; + XtCallbackList cancelCallback; + + Dimension widgetSpacing; + Dimension windowSpacing; + + Boolean showHiddenButton; + + Widget path; + PathBar *pathBar; + Widget filter; + Widget filterButton; + Widget showHiddenButtonW; + + FSBFilterFunc filterFunc; + + char *filterStr; + + Widget dirUp; + Widget home; + Widget newFolder; + + Widget viewSelectorList; + Widget viewSelectorDetail; + + Widget viewMenu; + Widget viewOption; + Widget detailToggleButton; + + Widget filterForm; + Widget lsDirLabel; + Widget lsFileLabel; + + Widget listContextMenu; + Widget gridContextMenu; + + // icon view + + // dir/file list view + Widget listform; + Widget dirlist; + + FSBView view[FSB_MAX_VIEWS]; + int numviews; + int selectedview; + + Widget filelist; + Widget grid; + + Widget separator; + + Widget nameLabel; + Widget name; + + Widget bottom_widget; + + Widget workarea; + + Widget okBtn; + Widget cancelBtn; + Widget helpBtn; + + FileElm *dirs; + FileElm *files; + int dircount; + int filecount; + int maxnamelen; + + char *homePath; + + char *currentPath; + char *selectedPath; + int selIsDir; + Boolean showHidden; + Boolean showViewMenu; + + int type; + + int end; + int status; + + int disable_set_values; + int gui_created; + + char *labelListView; + char *labelDetailView; + char* labelOpenFileTitle; + char* labelSaveFileTitle; + XmString labelDirUp; + XmString labelHome; + XmString labelNewFolder; + XmString labelFilterButton; + XmString labelShowHiddenFiles; + XmString labelDirectories; + XmString labelFiles; + XmString labelRename; + XmString labelDelete; + XmString labelOpen; + XmString labelSave; + XmString labelOk; + XmString labelCancel; + XmString labelHelp; + XmString labelFileName; + XmString labelDirectoryName; + XmString labelNewFileName; + char *labelDeleteFile; + + char *detailHeadings; + + char *dateFormatSameYear; + char *dateFormatOtherYear; + char *suffixBytes; + char *suffixKB; + char *suffixMB; + char *suffixGB; + char *suffixTB; + + char *errorTitle; + char *errorIllegalChar; + char *errorRename; + char *errorFolder; + char *errorDelete; + char *errorOpenDir; +} FSBPart; + +typedef struct FSBRec { + CorePart core; + CompositePart composite; + ConstraintPart constraint; + XmManagerPart manager; + XmBulletinBoardPart bulletin_board; + XmFormPart form; + FSBPart fsb; +} FSBRec; + +typedef struct FSBRec *XnFileSelectionBox; + +#ifdef __cplusplus +} +#endif + +#endif /* FSBP_H */ +
--- a/ui/motif/objs.mk Sat Nov 22 18:40:24 2025 +0100 +++ b/ui/motif/objs.mk Sun Nov 23 08:35:40 2025 +0100 @@ -39,6 +39,7 @@ MOTIFOBJ += button.o MOTIFOBJ += label.o MOTIFOBJ += text.o +MOTIFOBJ += pathbar.o MOTIFOBJ += list.o MOTIFOBJ += graphics.o MOTIFOBJ += range.o @@ -46,6 +47,7 @@ MOTIFOBJ += image.o MOTIFOBJ += Grid.o MOTIFOBJ += entry.o +MOTIFOBJ += Fsb.o TOOLKITOBJS += $(MOTIFOBJ:%=$(MOTIF_OBJPRE)%) TOOLKITSOURCE += $(MOTIFOBJ:%.o=motif/%.c)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/pathbar.c Sun Nov 23 08:35:40 2025 +0100 @@ -0,0 +1,439 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pathbar.h" + +#include <unistd.h> +#include <cx/string.h> + + + +void pathbar_resize(Widget w, PathBar *p, XtPointer d) +{ + Dimension width, height; + XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); + + Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); + + Dimension maxHeight = 0; + + /* get width/height from all widgets */ + Dimension pathWidth = 0; + for(int i=0;i<p->numSegments;i++) { + Dimension segWidth; + Dimension segHeight; + XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); + segW[i] = segWidth; + pathWidth += segWidth; + if(segHeight > maxHeight) { + maxHeight = segHeight; + } + } + Dimension tfHeight; + XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); + if(tfHeight > maxHeight) { + maxHeight = tfHeight; + } + + Boolean arrows = False; + if(pathWidth + 10 > width) { + arrows = True; + pathWidth += p->lw + p->rw; + } + + /* calc max visible widgets */ + int start = 0; + if(arrows) { + Dimension vis = p->lw+p->rw; + for(int i=p->numSegments;i>0;i--) { + Dimension segWidth = segW[i-1]; + if(vis + segWidth + 10 > width) { + start = i; + arrows = True; + break; + } + vis += segWidth; + } + } else { + p->shift = 0; + } + + int leftShift = 0; + if(p->shift < 0) { + if(start + p->shift < 0) { + leftShift = start; + start = 0; + p->shift = -leftShift; + } else { + leftShift = -p->shift; /* negative shift */ + start += p->shift; + } + } + + int x = 0; + if(arrows) { + XtManageChild(p->left); + XtManageChild(p->right); + x = p->lw; + } else { + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + } + + for(int i=0;i<p->numSegments;i++) { + if(i >= start && i < p->numSegments - leftShift && !p->input) { + XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + x += segW[i]; + XtManageChild(p->pathSegments[i]); + } else { + XtUnmanageChild(p->pathSegments[i]); + } + } + + if(arrows) { + XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); + XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + } + + free(segW); + + Dimension rw, rh; + XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); + + XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); +} + +static void pathbarActivateTF(PathBar *p) +{ + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); + XtManageChild(p->textfield); + p->input = 1; + + XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); + + pathbar_resize(p->widget, p, NULL); +} + +void PathBarActivateTextfield(PathBar *p) +{ + p->focus = 1; + pathbarActivateTF(p); +} + +void pathbar_input(Widget w, PathBar *p, XtPointer c) +{ + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + p->focus = 0; + pathbarActivateTF(p); + } + } +} + +void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) +{ + if(--p->focus < 0) { + p->input = False; + XtUnmanageChild(p->textfield); + } +} + +static cxmutstr concat_path_s(cxstring base, cxstring path) { + if(!path.ptr) { + path = CX_STR(""); + } + + int add_separator = 0; + if(base.length != 0 && base.ptr[base.length-1] == '/') { + if(path.ptr[0] == '/') { + base.length--; + } + } else { + if(path.length == 0 || path.ptr[0] != '/') { + add_separator = 1; + } + } + + cxmutstr url; + if(add_separator) { + url = cx_strcat(3, base, CX_STR("/"), path); + } else { + url = cx_strcat(2, base, path); + } + + return url; +} + +static char* ConcatPath(const char *path1, const char *path2) { + return concat_path_s(cx_str(path1), cx_str(path2)).ptr; +} + +void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) +{ + char *newpath = XNETextGetString(p->textfield); + if(newpath) { + if(newpath[0] == '~') { + char *p = newpath+1; + char *home = getenv("HOME"); + char *cp = ConcatPath(home, p); + XtFree(newpath); + newpath = cp; + } else if(newpath[0] != '/') { + char curdir[2048]; + curdir[0] = 0; + getcwd(curdir, 2048); + char *cp = ConcatPath(curdir, newpath); + XtFree(newpath); + newpath = cp; + } + + /* update path */ + PathBarSetPath(p, newpath); + if(p->updateDir) { + p->updateDir(p->updateDirData, newpath, -1); + } + XtFree(newpath); + + /* hide textfield and show path as buttons */ + XtUnmanageChild(p->textfield); + pathbar_resize(p->widget, p, NULL); + + if(p->focus_widget) { + XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); + } + } +} + +void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) +{ + p->shift--; + pathbar_resize(p->widget, p, NULL); +} + +void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) +{ + if(p->shift < 0) { + p->shift++; + } + pathbar_resize(p->widget, p, NULL); +} + +static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + PathBar *pb = data; + if(event->type == KeyReleaseMask) { + if(event->xkey.keycode == 9) { + XtUnmanageChild(pb->textfield); + pathbar_resize(pb->widget, pb, NULL); + *dispatch = False; + } else if(event->xkey.keycode == 36) { + pathbar_pathinput(pb->textfield, pb, NULL); + *dispatch = False; + } + } +} + +PathBar* CreatePathBar(Widget parent, ArgList args, int n) +{ + PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); + bar->path = NULL; + bar->updateDir = NULL; + bar->updateDirData = NULL; + + bar->focus_widget = NULL; + + bar->getpathelm = NULL; + bar->getpathelmdata = NULL; + bar->current_pathelms = NULL; + + bar->shift = 0; + + XtSetArg(args[n], XmNmarginWidth, 0); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); + XtAddCallback( + bar->widget, + XmNresizeCallback, + (XtCallbackProc)pathbar_resize, + bar); + XtAddCallback( + bar->widget, + XmNinputCallback, + (XtCallbackProc)pathbar_input, + bar); + + Arg a[4]; + XtSetArg(a[0], XmNshadowThickness, 0); + XtSetArg(a[1], XmNx, 0); + XtSetArg(a[2], XmNy, 0); + bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); + bar->input = 0; + XtAddCallback( + bar->textfield, + XmNlosingFocusCallback, + (XtCallbackProc)pathbar_losingfocus, + bar); + XtAddCallback(bar->textfield, XmNactivateCallback, + (XtCallbackProc)pathbar_pathinput, bar); + XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); + + XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); + bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); + bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtAddCallback( + bar->left, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_left, + bar); + XtAddCallback( + bar->right, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_right, + bar); + + Pixel bg; + XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); + XtVaSetValues(bar->widget, XmNbackground, bg, NULL); + + XtManageChild(bar->left); + XtManageChild(bar->right); + + XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); + XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); + + bar->segmentAlloc = 16; + bar->numSegments = 0; + bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); + + bar->selection = 0; + + return bar; +} + +void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) +{ + XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); + + int i; + for(i=0;i<bar->numSegments;i++) { + if(bar->pathSegments[i] == w) { + bar->selection = i; + XmToggleButtonSetState(w, True, False); + break; + } + } + + UiPathElm elm = bar->current_pathelms[i]; + cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len)); + if(bar->updateDir) { + XNETextSetString(bar->textfield, path.ptr); + bar->updateDir(bar->updateDirData, path.ptr, i); + } + free(path.ptr); +} + +static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { + for(int i=0;i<nelm;i++) { + free(elms[i].name); + free(elms[i].path); + } + free(elms); +} + +void PathBarSetPath(PathBar *bar, const char *path) +{ + if(bar->path) { + free(bar->path); + } + bar->path = strdup(path); + + for(int i=0;i<bar->numSegments;i++) { + XtDestroyWidget(bar->pathSegments[i]); + } + XtUnmanageChild(bar->textfield); + XtManageChild(bar->left); + XtManageChild(bar->right); + bar->input = False; + + Arg args[4]; + XmString str; + + bar->numSegments = 0; + + ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); + size_t nelm = 0; + UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); + if (!path_elm) { + return; + } + bar->current_pathelms = path_elm; + bar->numSegments = nelm; + bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); + + for(int i=0;i<nelm;i++) { + UiPathElm elm = path_elm[i]; + + cxmutstr name = cx_strdup(cx_strn(elm.name, elm.name_len)); + str = XmStringCreateLocalized(elm.name); + free(name.ptr); + + XtSetArg(args[0], XmNlabelString, str); + XtSetArg(args[1], XmNfillOnSelect, True); + XtSetArg(args[2], XmNindicatorOn, False); + Widget button = XmCreateToggleButton(bar->widget, "pbbutton", args, 3); + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)PathBarChangeDir, + bar); + XmStringFree(str); + + bar->pathSegments[i] = button; + } + + bar->selection = bar->numSegments-1; + XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); + + XNETextSetString(bar->textfield, (char*)path); + XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); + + pathbar_resize(bar->widget, bar, NULL); +} + +void PathBarDestroy(PathBar *pathbar) { + if(pathbar->path) { + XtFree(pathbar->path); + } + XtFree((void*)pathbar->pathSegments); + XtFree((void*)pathbar); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/motif/pathbar.h Sun Nov 23 08:35:40 2025 +0100 @@ -0,0 +1,93 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PATHBAR_H +#define PATHBAR_H + +#include <Xm/XmAll.h> + +#include "../ui/text.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define XNECreateText(parent,name,args,count) XmCreateTextField(parent,name,args,count) +#define XNETextSetString(widget,value) XmTextFieldSetString(widget,value) +#define XNETextGetString(widget) XmTextFieldGetString(widget) +#define XNETextGetLastPosition(widget) XmTextFieldGetLastPosition(widget) +#define XNETextSetInsertionPosition(widget, i) XmTextFieldSetInsertionPosition(widget, i) +#define XNETextSetSelection(w, f, l, t) XmTextFieldSetSelection(w, f, l, t) + +typedef void(*updatedir_callback)(void*,char*,int); + +typedef struct PathBar { + Widget widget; + Widget textfield; + + Widget focus_widget; + + Widget left; + Widget right; + Dimension lw; + Dimension rw; + + int shift; + + UiPathElm *current_pathelms; + Widget *pathSegments; + size_t numSegments; + size_t segmentAlloc; + + char *path; + int selection; + Boolean input; + + int focus; + + Boolean disableResize; + + updatedir_callback updateDir; + void *updateDirData; + + ui_pathelm_func getpathelm; + void *getpathelmdata; +} PathBar; + +PathBar* CreatePathBar(Widget parent, ArgList args, int n); +void PathBarSetPath(PathBar *bar, const char *path); +void PathBarDestroy(PathBar *bar); + +void pathbar_resize(Widget w, PathBar *p, XtPointer d); + +#ifdef __cplusplus +} +#endif + +#endif /* PATHBAR_H */ +
--- a/ui/motif/text.c Sat Nov 22 18:40:24 2025 +0100 +++ b/ui/motif/text.c Sun Nov 23 08:35:40 2025 +0100 @@ -32,6 +32,7 @@ #include "text.h" #include "container.h" +#include "pathbar.h" #include <cx/string.h> @@ -508,459 +509,6 @@ } - - - -/* -------------------- path bar -------------------- */ - -#define XNECreateText(parent,name,args,count) XmCreateTextField(parent,name,args,count) -#define XNETextSetString(widget,value) XmTextFieldSetString(widget,value) -#define XNETextGetString(widget) XmTextFieldGetString(widget) -#define XNETextGetLastPosition(widget) XmTextFieldGetLastPosition(widget) -#define XNETextSetInsertionPosition(widget, i) XmTextFieldSetInsertionPosition(widget, i) -#define XNETextSetSelection(w, f, l, t) XmTextFieldSetSelection(w, f, l, t) - -typedef void(*updatedir_callback)(void*,char*,int); - -typedef struct PathBar { - Widget widget; - Widget textfield; - - Widget focus_widget; - - Widget left; - Widget right; - Dimension lw; - Dimension rw; - - int shift; - - UiPathElm *current_pathelms; - Widget *pathSegments; - size_t numSegments; - size_t segmentAlloc; - - char *path; - int selection; - Boolean input; - - int focus; - - updatedir_callback updateDir; - void *updateDirData; - - ui_pathelm_func getpathelm; - void *getpathelmdata; -} PathBar; - -void PathBarSetPath(PathBar *bar, const char *path); - -void pathbar_resize(Widget w, PathBar *p, XtPointer d) -{ - Dimension width, height; - XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); - - Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); - - Dimension maxHeight = 0; - - /* get width/height from all widgets */ - Dimension pathWidth = 0; - for(int i=0;i<p->numSegments;i++) { - Dimension segWidth; - Dimension segHeight; - XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); - segW[i] = segWidth; - pathWidth += segWidth; - if(segHeight > maxHeight) { - maxHeight = segHeight; - } - } - Dimension tfHeight; - XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); - if(tfHeight > maxHeight) { - maxHeight = tfHeight; - } - - Boolean arrows = False; - if(pathWidth + 10 > width) { - arrows = True; - pathWidth += p->lw + p->rw; - } - - /* calc max visible widgets */ - int start = 0; - if(arrows) { - Dimension vis = p->lw+p->rw; - for(int i=p->numSegments;i>0;i--) { - Dimension segWidth = segW[i-1]; - if(vis + segWidth + 10 > width) { - start = i; - arrows = True; - break; - } - vis += segWidth; - } - } else { - p->shift = 0; - } - - int leftShift = 0; - if(p->shift < 0) { - if(start + p->shift < 0) { - leftShift = start; - start = 0; - p->shift = -leftShift; - } else { - leftShift = -p->shift; /* negative shift */ - start += p->shift; - } - } - - int x = 0; - if(arrows) { - XtManageChild(p->left); - XtManageChild(p->right); - x = p->lw; - } else { - XtUnmanageChild(p->left); - XtUnmanageChild(p->right); - } - - for(int i=0;i<p->numSegments;i++) { - if(i >= start && i < p->numSegments - leftShift && !p->input) { - XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); - x += segW[i]; - XtManageChild(p->pathSegments[i]); - } else { - XtUnmanageChild(p->pathSegments[i]); - } - } - - if(arrows) { - XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); - XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); - } - - free(segW); - - Dimension rw, rh; - XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); - - XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); -} - -static void pathbarActivateTF(PathBar *p) -{ - XtUnmanageChild(p->left); - XtUnmanageChild(p->right); - XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); - XtManageChild(p->textfield); - p->input = 1; - - XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); - - pathbar_resize(p->widget, p, NULL); -} - -void PathBarActivateTextfield(PathBar *p) -{ - p->focus = 1; - pathbarActivateTF(p); -} - -void pathbar_input(Widget w, PathBar *p, XtPointer c) -{ - XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; - XEvent *xevent = cbs->event; - - if (cbs->reason == XmCR_INPUT) { - if (xevent->xany.type == ButtonPress) { - p->focus = 0; - pathbarActivateTF(p); - } - } -} - -void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) -{ - if(--p->focus < 0) { - p->input = False; - XtUnmanageChild(p->textfield); - } -} - -static cxmutstr concat_path_s(cxstring base, cxstring path) { - if(!path.ptr) { - path = CX_STR(""); - } - - int add_separator = 0; - if(base.length != 0 && base.ptr[base.length-1] == '/') { - if(path.ptr[0] == '/') { - base.length--; - } - } else { - if(path.length == 0 || path.ptr[0] != '/') { - add_separator = 1; - } - } - - cxmutstr url; - if(add_separator) { - url = cx_strcat(3, base, CX_STR("/"), path); - } else { - url = cx_strcat(2, base, path); - } - - return url; -} - -static char* ConcatPath(const char *path1, const char *path2) { - return concat_path_s(cx_str(path1), cx_str(path2)).ptr; -} - -void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) -{ - char *newpath = XNETextGetString(p->textfield); - if(newpath) { - if(newpath[0] == '~') { - char *p = newpath+1; - char *home = getenv("HOME"); - char *cp = ConcatPath(home, p); - XtFree(newpath); - newpath = cp; - } else if(newpath[0] != '/') { - char curdir[2048]; - curdir[0] = 0; - getcwd(curdir, 2048); - char *cp = ConcatPath(curdir, newpath); - XtFree(newpath); - newpath = cp; - } - - /* update path */ - PathBarSetPath(p, newpath); - if(p->updateDir) { - p->updateDir(p->updateDirData, newpath, -1); - } - XtFree(newpath); - - /* hide textfield and show path as buttons */ - XtUnmanageChild(p->textfield); - pathbar_resize(p->widget, p, NULL); - - if(p->focus_widget) { - XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); - } - } -} - -void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) -{ - p->shift--; - pathbar_resize(p->widget, p, NULL); -} - -void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) -{ - if(p->shift < 0) { - p->shift++; - } - pathbar_resize(p->widget, p, NULL); -} - -static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { - PathBar *pb = data; - if(event->type == KeyReleaseMask) { - if(event->xkey.keycode == 9) { - XtUnmanageChild(pb->textfield); - pathbar_resize(pb->widget, pb, NULL); - *dispatch = False; - } else if(event->xkey.keycode == 36) { - pathbar_pathinput(pb->textfield, pb, NULL); - *dispatch = False; - } - } -} - -PathBar* CreatePathBar(Widget parent, ArgList args, int n) -{ - PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); - bar->path = NULL; - bar->updateDir = NULL; - bar->updateDirData = NULL; - - bar->focus_widget = NULL; - - bar->getpathelm = NULL; - bar->getpathelmdata = NULL; - bar->current_pathelms = NULL; - - bar->shift = 0; - - XtSetArg(args[n], XmNmarginWidth, 0); n++; - XtSetArg(args[n], XmNmarginHeight, 0); n++; - bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); - XtAddCallback( - bar->widget, - XmNresizeCallback, - (XtCallbackProc)pathbar_resize, - bar); - XtAddCallback( - bar->widget, - XmNinputCallback, - (XtCallbackProc)pathbar_input, - bar); - - Arg a[4]; - XtSetArg(a[0], XmNshadowThickness, 0); - XtSetArg(a[1], XmNx, 0); - XtSetArg(a[2], XmNy, 0); - bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); - bar->input = 0; - XtAddCallback( - bar->textfield, - XmNlosingFocusCallback, - (XtCallbackProc)pathbar_losingfocus, - bar); - XtAddCallback(bar->textfield, XmNactivateCallback, - (XtCallbackProc)pathbar_pathinput, bar); - XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); - - XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); - bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); - XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); - bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); - XtAddCallback( - bar->left, - XmNactivateCallback, - (XtCallbackProc)pathbar_shift_left, - bar); - XtAddCallback( - bar->right, - XmNactivateCallback, - (XtCallbackProc)pathbar_shift_right, - bar); - - Pixel bg; - XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); - XtVaSetValues(bar->widget, XmNbackground, bg, NULL); - - XtManageChild(bar->left); - XtManageChild(bar->right); - - XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); - XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); - - bar->segmentAlloc = 16; - bar->numSegments = 0; - bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); - - bar->selection = 0; - - return bar; -} - -void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) -{ - XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); - - int i; - for(i=0;i<bar->numSegments;i++) { - if(bar->pathSegments[i] == w) { - bar->selection = i; - XmToggleButtonSetState(w, True, False); - break; - } - } - - UiPathElm elm = bar->current_pathelms[i]; - cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len)); - if(bar->updateDir) { - XNETextSetString(bar->textfield, path.ptr); - bar->updateDir(bar->updateDirData, path.ptr, i); - } - free(path.ptr); -} - -static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { - for(int i=0;i<nelm;i++) { - free(elms[i].name); - free(elms[i].path); - } - free(elms); -} - -void PathBarSetPath(PathBar *bar, const char *path) -{ - if(bar->path) { - free(bar->path); - } - bar->path = strdup(path); - - for(int i=0;i<bar->numSegments;i++) { - XtDestroyWidget(bar->pathSegments[i]); - } - XtUnmanageChild(bar->textfield); - XtManageChild(bar->left); - XtManageChild(bar->right); - bar->input = False; - - Arg args[4]; - XmString str; - - bar->numSegments = 0; - - ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); - size_t nelm = 0; - UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); - if (!path_elm) { - return; - } - bar->current_pathelms = path_elm; - bar->numSegments = nelm; - bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); - - for(int i=0;i<nelm;i++) { - UiPathElm elm = path_elm[i]; - - cxmutstr name = cx_strdup(cx_strn(elm.name, elm.name_len)); - str = XmStringCreateLocalized(elm.name); - free(name.ptr); - - XtSetArg(args[0], XmNlabelString, str); - XtSetArg(args[1], XmNfillOnSelect, True); - XtSetArg(args[2], XmNindicatorOn, False); - Widget button = XmCreateToggleButton(bar->widget, "pbbutton", args, 3); - XtAddCallback( - button, - XmNvalueChangedCallback, - (XtCallbackProc)PathBarChangeDir, - bar); - XmStringFree(str); - - bar->pathSegments[i] = button; - } - - bar->selection = bar->numSegments-1; - XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); - - XNETextSetString(bar->textfield, (char*)path); - XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); - - pathbar_resize(bar->widget, bar, NULL); -} - -void PathBarDestroy(PathBar *pathbar) { - if(pathbar->path) { - XtFree(pathbar->path); - } - XtFree((void*)pathbar->pathSegments); - XtFree((void*)pathbar); -} - - /* ---------------------------- Path Text Field ---------------------------- */ static void destroy_pathbar(Widget w, XtPointer *data, XtPointer d) {
--- a/ui/motif/window.c Sat Nov 22 18:40:24 2025 +0100 +++ b/ui/motif/window.c Sun Nov 23 08:35:40 2025 +0100 @@ -37,6 +37,7 @@ #include "../common/context.h" #include "Grid.h" +#include "Fsb.h" #include <cx/mempool.h> @@ -142,3 +143,72 @@ UiObject* ui_simple_window(const char *title, void *window_data) { return create_window(title, window_data, TRUE); } + +static void filedialog_event(UiEventData *event, int result, UiFileList flist) { + UiEvent evt; + evt.obj = event->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.intval = result; + + evt.eventdata = &flist; + evt.eventdatatype = UI_EVENT_DATA_FILE_LIST; + + if(event->callback) { + event->callback(&evt, event->userdata); + } +} + +static void filedialog_select( + Widget widget, + UiEventData *data, + XmFileSelectionBoxCallbackStruct *selection) +{ + UiFileList flist; + + char *value = NULL; + XmStringGetLtoR(selection->value, XmSTRING_DEFAULT_CHARSET, &value); + flist.files = &value; + flist.nfiles = 1; + + filedialog_event(data, 1, flist); + + XtFree(value); + + XtUnmanageChild(widget); + XtDestroyWidget(widget); +} + +static void filedialog_cancel( + Widget widget, + UiEventData *data, + XmFileSelectionBoxCallbackStruct *selection) +{ + UiFileList flist; + flist.files = NULL; + flist.nfiles = 0; + filedialog_event(data, 0, flist); + + XtUnmanageChild(widget); + XtDestroyWidget(widget); +} + +void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + Widget dialog = XnCreateFileSelectionDialog(obj->widget, "dialog", NULL, 0); + + UiEventData *data = malloc(sizeof(UiEventData)); + memset(data, 0, sizeof(UiEventData)); + data->obj = obj; + data->callback = file_selected_callback; + data->userdata = cbdata; + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)filedialog_select, data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)filedialog_cancel, data); + //XtAddCallback(dialog, XmNhelpCallback, (XtCallbackProc)filedialog_help, wd); + + XtManageChild(dialog); +} + +void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { + +}