ui/motif/Fsb.c

Sun, 07 Dec 2025 12:24:12 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 07 Dec 2025 12:24:12 +0100
changeset 964
c563220d9aea
parent 927
b8c0f718b141
permissions
-rw-r--r--

implement ui_listview_destroy (Motif)

/*
 * 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"

#include "../common/utils.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->getpathelm = ui_default_pathelm_func;
    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);
}

mercurial