/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #ifdef FSB_ENABLE_DETAIL #include #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 void pathbar_resize(Widget w, PathBar *p, XtPointer d); 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()"; 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;ifsb.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;ifsb.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;jpath); if((!showHidden && name[0] == '.') || apply_filter(fsb, filter, name)) { continue; } items[i] = XmStringCreateLocalized(name); i++; } XmListAddItems(w, items, i, 0); for(i=0;ifsb.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;ipath); 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;ifsb.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;ifsb.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;ifsb.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;idisableResize) return; Dimension width, height; XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); Dimension xoff; XtVaGetValues(p->down, XmNwidth, &xoff, NULL); Dimension *segW = calloc(p->numSegments, sizeof(Dimension)); Dimension maxHeight = 0; /* get width/height from all widgets */ Dimension pathWidth = 0; for(int i=0;inumSegments;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 + xoff + 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 + xoff + 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;inumSegments;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); } XtVaSetValues(p->down, XmNx, width-xoff, XmNheight, maxHeight, NULL); free(segW); Dimension rw, rh; XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); XtVaSetValues(p->textfield, XmNwidth, rw-xoff, XmNheight, rh, NULL); } static 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) { XtUnmanageChild(p->left); XtUnmanageChild(p->right); XtManageChild(p->textfield); p->input = 1; XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); pathbar_resize(p->widget, p, NULL); } } } static void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) { p->input = False; XtUnmanageChild(p->textfield); pathbar_resize(p->widget, p, NULL); } static void pathbar_pathinput(Widget w, PathBar *pb, XtPointer d) { char *newpath = XmTextFieldGetString(pb->textfield); if(newpath) { if(newpath[0] == '~') { char *p = newpath+1; char *cp = ConcatPath(GetHomeDir(), p); XtFree(newpath); newpath = cp; } else if(newpath[0] != '/') { char cwd[PATH_MAX]; if(!getcwd(cwd, sizeof(cwd))) { cwd[0] = '/'; cwd[1] = '\0'; } char *cp = ConcatPath(cwd, newpath); XtFree(newpath); newpath = cp; } /* update path */ if(pb->updateDir) { if(!pb->updateDir(pb->updateDirData, newpath)) { PathBarSetPath(pb, newpath); } } else { PathBarSetPath(pb, newpath); } XtFree(newpath); /* hide textfield and show path as buttons */ XtUnmanageChild(pb->textfield); pathbar_resize(pb->widget, pb, NULL); } } static void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) { p->shift--; pathbar_resize(p->widget, p, NULL); } static void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) { if(p->shift < 0) { p->shift++; } pathbar_resize(p->widget, p, NULL); } static void pathbar_list_select(Widget w, PathBar *p, XmListCallbackStruct *cb) { char *value = NULL; XmStringGetLtoR(cb->item, XmSTRING_DEFAULT_CHARSET, &value); p->updateDir(p->updateDirData, value); PathBarSetPath(p, value); free(value); } static void pathbar_popup(Widget w, PathBar *p, XtPointer d) { Widget parent = XtParent(w); Display *dp = XtDisplay(parent); Window root = XDefaultRootWindow(dp); int x, y; Window child; XTranslateCoordinates(dp, XtWindow(parent), root, 0, 0, &x, &y, &child); XtManageChild(p->list); XtPopupSpringLoaded(p->popup); XtVaSetValues(p->popup, XmNx, x, XmNy, y + parent->core.height, XmNwidth, parent->core.width, XmNheight, 200, NULL); XmProcessTraversal(p->list, XmTRAVERSE_CURRENT); } static void popupEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { PathBar *bar = data; Window w1 = bar->hs ? XtWindow(bar->hs) : 0; Window w2 = bar->vs ? XtWindow(bar->vs) : 0; if(event->type == ButtonPress) { if(event->xbutton.window != 0 && (event->xbutton.window == w1 || event->xbutton.window == w2)) { bar->popupScrollEvent = 1; } else { bar->popupScrollEvent = 0; } } else if(event->type == ButtonRelease) { if(bar->popupScrollEvent) { *dispatch = False; } bar->popupScrollEvent = 0; } else if(event->type == KeyReleaseMask) { int keycode = event->xkey.keycode; if(keycode == 36 || keycode == 9) { XtUnmapWidget(bar->popup); } } } 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; } } } PathBar* CreatePathBar(Widget parent, ArgList args, int n) { PathBar *bar = malloc(sizeof(PathBar)); bar->path = NULL; bar->updateDir = NULL; bar->updateDirData = NULL; bar->disableResize = False; 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); n = 0; XtSetArg(args[n], XmNownerEvents, True), n++; XtSetArg(args[n], XmNgrabStyle, GrabModeSync), n++; bar->popup = XmCreateGrabShell(bar->widget, "pbpopup", args, n); bar->list = XmCreateScrolledList(bar->popup, "pblist", NULL, 0); XtAddEventHandler(bar->popup, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask, FALSE, popupEH, bar); bar->popupScrollEvent = 0; XtAddCallback( bar->list, XmNdefaultActionCallback, (XtCallbackProc)pathbar_list_select, bar); XtAddCallback( bar->list, XmNbrowseSelectionCallback, (XtCallbackProc)pathbar_list_select, bar); bar->vs = NULL; bar->hs = NULL; XtVaGetValues(XtParent(bar->list), XmNhorizontalScrollBar, &bar->hs, XmNverticalScrollBar, &bar->vs, NULL); Arg a[4]; XtSetArg(a[0], XmNshadowThickness, 0); XtSetArg(a[1], XmNx, 0); XtSetArg(a[2], XmNy, 0); bar->textfield = XmCreateTextField(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_DOWN); bar->down = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); XtManageChild(bar->down); 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->down, XmNactivateCallback, (XtCallbackProc)pathbar_popup, bar); 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 = calloc(16, sizeof(Widget)); bar->selection = 0; return bar; } static void PathBarChangeDir(Widget w, PathBar *bar, XtPointer unused) { XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); for(int i=0;inumSegments;i++) { if(bar->pathSegments[i] == w) { bar->selection = i; XmToggleButtonSetState(w, True, False); break; } } int plen = strlen(bar->path); int countSeg = 0; for(int i=0;i<=plen;i++) { char c = bar->path[i]; if(c == '/' || c == '\0') { if(countSeg == bar->selection) { char *dir = malloc(i+2); memcpy(dir, bar->path, i+1); dir[i+1] = '\0'; if(bar->updateDir) { bar->updateDir(bar->updateDirData, dir); } free(dir); } countSeg++; } } } void PathBarSetPath(PathBar *bar, const char *path) { if(bar->path) { free(bar->path); } bar->path = strdup(path); for(int i=0;inumSegments;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; int i=0; if(path[0] == '/') { str = XmStringCreateLocalized("/"); XtSetArg(args[0], XmNlabelString, str); XtSetArg(args[1], XmNfillOnSelect, True); XtSetArg(args[2], XmNindicatorOn, False); bar->pathSegments[0] = XmCreateToggleButton( bar->widget, "pbbutton", args, 3); XtAddCallback( bar->pathSegments[0], XmNvalueChangedCallback, (XtCallbackProc)PathBarChangeDir, bar); XmStringFree(str); bar->numSegments++; i++; } int len = strlen(path); int begin = i; for(;i<=len;i++) { char c = path[i]; if((c == '/' || c == '\0') && i > begin) { char *segStr = malloc(i - begin + 1); memcpy(segStr, path+begin, i-begin); segStr[i-begin] = '\0'; begin = i+1; str = XmStringCreateLocalized(segStr); free(segStr); 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); if(bar->numSegments >= bar->segmentAlloc) { bar->segmentAlloc += 8; bar->pathSegments = realloc(bar->pathSegments, bar->segmentAlloc * sizeof(Widget)); } bar->pathSegments[bar->numSegments++] = button; } } bar->selection = bar->numSegments-1; XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); XmTextFieldSetString(bar->textfield, (char*)path); XmTextFieldSetInsertionPosition(bar->textfield, XmTextFieldGetLastPosition(bar->textfield)); pathbar_resize(bar->widget, bar, NULL); } void PathBarSetDirList(PathBar *bar, const char **dirlist, size_t nelm) { XmStringTable items = calloc(nelm, sizeof(XmString)); for(int i=0;ilist); XmListAddItems(bar->list, items, nelm, 0); XmListSelectPos(bar->list, 1, False); for(int i=0;ipath) { free(bar->path); } free(bar->pathSegments); free(bar); } /* ------------------------------ 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); }