From: Olaf Wintermann Date: Sun, 30 Nov 2025 17:22:07 +0000 (+0100) Subject: fix sublist_free X-Git-Url: https://uap-core.de/gitweb/?a=commitdiff_plain;h=HEAD;p=note.git fix sublist_free --- diff --git a/application/window.c b/application/window.c index 0b7bac2..cba82f8 100644 --- a/application/window.c +++ b/application/window.c @@ -156,9 +156,9 @@ void window_sidebar_getvalue(UiList *list, void *sublist_userdata, void *rowdata } static void sublist_free(UiContext *ctx, UiSubList *sublist) { - ui_list_free(sublist->value); - free((char*)sublist->header); - free(sublist); + ui_list_free(ctx, sublist->value); + ui_free(ctx, (char*)sublist->header); + ui_free(ctx, sublist); } /* diff --git a/ui/cocoa/webview.h b/ui/cocoa/webview.h new file mode 100644 index 0000000..479905b --- /dev/null +++ b/ui/cocoa/webview.h @@ -0,0 +1,58 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "toolkit.h" +#import "../ui/webview.h" + +#import + + +enum WebViewDataType { + WEBVIEW_CONTENT_URL, + WEBVIEW_CONTENT_CONTENT +}; + +struct UiWebViewData { + void *webview; + char *uri; + char *mimetype; + char *encoding; + char *content; + size_t contentlength; + enum WebViewDataType type; + + double zoom; + UiBool javascript; +}; + +UiWebViewData* ui_webview_data_clone(UiWebViewData *data); + +void* ui_webview_get(UiGeneric *g); +const char* ui_webview_get_type(UiGeneric *g); +int ui_webview_set(UiGeneric *g, void *data, const char *type); +void ui_webview_destroy(UiGeneric *g); diff --git a/ui/cocoa/webview.m b/ui/cocoa/webview.m new file mode 100644 index 0000000..088ad5f --- /dev/null +++ b/ui/cocoa/webview.m @@ -0,0 +1,273 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "webview.h" +#import "Container.h" + +UIWIDGET ui_webview_create(UiObject *obj, UiWebviewArgs *args) { + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_GENERIC); + + WKWebView *webview = [[WKWebView alloc]init]; + + UiLayout layout = UI_ARGS2LAYOUT(args); + ui_container_add(obj, webview, &layout); + + if(var) { + UiGeneric *value = var->value; + value->get = ui_webview_get; + value->get_type = ui_webview_get_type; + value->set = ui_webview_set; + value->destroy = ui_webview_destroy; + value->obj = (__bridge void*)webview; + if(value->value) { + ui_webview_set(value, value->value, UI_WEBVIEW_OBJECT_TYPE); + } else { + UiWebViewData *data = malloc(sizeof(UiWebViewData)); + memset(data, 0, sizeof(UiWebViewData)); + data->webview = (__bridge void*)webview; + data->javascript = TRUE; + data->zoom = 1; + value->value = value; + } + } + + return (__bridge void*)webview; +} + +UiWebViewData* ui_webview_data_clone(UiWebViewData *data) { + UiWebViewData *newdata = malloc(sizeof(UiWebViewData)); + memset(newdata, 0, sizeof(UiWebViewData)); + newdata->zoom = 1; + newdata->javascript = TRUE; + + if(data) { + newdata->uri = data->uri ? strdup(data->uri) : NULL; + newdata->mimetype = data->mimetype ? strdup(data->mimetype) : NULL; + newdata->encoding = data->encoding ? strdup(data->encoding) : NULL; + newdata->type = data->type; + newdata->javascript = data->javascript; + newdata->zoom = data->zoom; + if(data->content && data->contentlength > 0) { + newdata->content = malloc(data->contentlength + 1); + memcpy(newdata->content, data->content, data->contentlength); + newdata->content[data->contentlength] = 0; + newdata->contentlength = data->contentlength; + } + } + + return newdata; +} + +void ui_webview_data_free(UiWebViewData *data) { + if(!data) { + return; + } + free(data->uri); + free(data->mimetype); + free(data->encoding); + free(data->content); + free(data); +} + +void* ui_webview_get(UiGeneric *g) { + UiWebViewData *data = g->value; + WKWebView *webview = (__bridge WKWebView*)g->obj; + + if(data->type == WEBVIEW_CONTENT_URL) { + (void)ui_webview_get_uri(g); // this updates data->uri + } + data->zoom = webview.pageZoom; + + return ui_webview_data_clone(g->value); +} + +const char* ui_webview_get_type(UiGeneric *g) { + return UI_WEBVIEW_OBJECT_TYPE; +} + +int ui_webview_set(UiGeneric *g, void *data, const char *type) { + if(!data || !type) { + return 1; + } + if(strcmp(type, UI_WEBVIEW_OBJECT_TYPE)) { + return 1; + } + ui_webview_data_free(g->value); + g->value = ui_webview_data_clone(data); + + WKWebView *webview = (__bridge WKWebView*)g->obj; + UiWebViewData *webd = data; + if(webd->type == WEBVIEW_CONTENT_URL) { + const char *uri = webd->uri ? webd->uri : "about:blank"; + NSURL *url = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:uri]]; + if(url) { + NSURLRequest *req = [NSURLRequest requestWithURL:url]; + if(req) { + [webview loadRequest:req]; + } + } + } else { + NSString *mimetype = [[NSString alloc]initWithUTF8String: webd->mimetype ? webd->mimetype : "text/plain"]; + NSString *encoding = [[NSString alloc]initWithUTF8String: webd->encoding ? webd->encoding : "UTF-8"]; + NSString *urlStr = [[NSString alloc]initWithUTF8String: webd->uri ? webd->uri : "file:///"]; + NSURL *url = [NSURL URLWithString:urlStr]; + NSData *content = [NSData dataWithBytes:webd->content length:webd->contentlength]; + if(!url) { + url = [NSURL URLWithString:@"about:blank"]; + } + [webview loadData:content MIMEType:mimetype characterEncodingName:encoding baseURL:url]; + } + + webview.pageZoom = webd->zoom; + + return 1; +} + +void ui_webview_destroy(UiGeneric *g) { + ui_webview_data_free(g->value); + g->value = NULL; +} + + +void ui_webview_load_url(UiGeneric *g, const char *url) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + UiWebViewData *data = g->value; + data->type = WEBVIEW_CONTENT_URL; + + if(!url) { + url = "about:blank"; + } + + NSURL *nsurl = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]]; + if(nsurl) { + NSURLRequest *req = [NSURLRequest requestWithURL:nsurl]; + if(req) { + [webview loadRequest:req]; + } + } +} + +void ui_webview_load_content( + UiGeneric *g, + const char *uri, + const char *content, + size_t contentlength, + const char *mimetype, + const char *encoding) +{ + UiWebViewData *data = g->value; + WKWebView *webview = (__bridge WKWebView*)g->obj; + + data->type = WEBVIEW_CONTENT_CONTENT; + + free(data->uri); + data->uri = NULL; + free(data->mimetype); + free(data->encoding); + free(data->content); + data->type = WEBVIEW_CONTENT_URL; + + data->content = malloc(contentlength+1); + memcpy(data->content, content, contentlength); + data->content[contentlength] = 0; + + if(!mimetype) { + mimetype = "text/plain"; + } + if(!encoding) { + encoding = "UTF-8"; + } + + data->mimetype = strdup(mimetype); + data->encoding = strdup(encoding); + + NSString *mtype = [[NSString alloc]initWithUTF8String:mimetype]; + NSString *enc = [[NSString alloc]initWithUTF8String:encoding]; + NSData *ct = [NSData dataWithBytes:content length:contentlength]; + NSURL *url; + if(uri) { + NSString *uriStr = [[NSString alloc]initWithUTF8String:uri]; + url = [NSURL URLWithString:uriStr]; + } else { + url = [NSURL URLWithString:@"file:///"]; + } + [webview loadData:ct MIMEType:mtype characterEncodingName:enc baseURL:url]; +} + + +void ui_webview_reload(UiGeneric *g) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + [webview reload]; +} + +UiBool ui_webview_can_go_back(UiGeneric *g) { + return FALSE; +} + +UiBool ui_webview_can_go_forward(UiGeneric *g) { + return FALSE; +} + +void ui_webview_go_back(UiGeneric *g) { + +} + +void ui_webview_go_forward(UiGeneric *g) { + +} + +const char * ui_webview_get_uri(UiGeneric *g) { + UiWebViewData *data = g->value; + WKWebView *webview = (__bridge WKWebView*)g->obj; + + free(data->uri); + data->uri = NULL; + + NSURL *url = webview.URL; + if(url) { + NSString *s = [url absoluteString]; + if(s) { + data->uri = strdup(s.UTF8String); + } + } + return data->uri; +} + +void ui_webview_enable_javascript(UiGeneric *g, UiBool enable) { + // unsupported +} + +void ui_webview_set_zoom(UiGeneric *g, double zoom) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + webview.pageZoom = zoom; +} + +double ui_webview_get_zoom(UiGeneric *g) { + WKWebView *webview = (__bridge WKWebView*)g->obj; + return webview.pageZoom; +} diff --git a/ui/common/utils.c b/ui/common/utils.c new file mode 100644 index 0000000..5e01239 --- /dev/null +++ b/ui/common/utils.c @@ -0,0 +1,85 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "utils.h" +#include "properties.h" + +#include + +UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data) { + cxstring *pathelms; + size_t nelm = cx_strsplit_a(cxDefaultAllocator, cx_strn(full_path, len), CX_STR("/"), 4096, &pathelms); + + if (nelm == 0) { + *ret_nelm = 0; + return NULL; + } + + UiPathElm* elms = (UiPathElm*)calloc(nelm, sizeof(UiPathElm)); + size_t n = nelm; + int j = 0; + for (int i = 0; i < nelm; i++) { + cxstring c = pathelms[i]; + if (c.length == 0) { + if (i == 0) { + c.length = 1; + } + else { + n--; + continue; + } + } + + cxmutstr m = cx_strdup(c); + elms[j].name = m.ptr; + elms[j].name_len = m.length; + + size_t elm_path_len = c.ptr + c.length - full_path; + cxmutstr elm_path = cx_strdup(cx_strn(full_path, elm_path_len)); + elms[j].path = elm_path.ptr; + elms[j].path_len = elm_path.length; + + j++; + } + *ret_nelm = n; + + return elms; +} + +void ui_get_window_default_width(int *width, int *height) { + const char *width_str = ui_get_property("ui.window.width"); + const char *height_str = ui_get_property("ui.window.height"); + if(width_str && height_str) { + int w = atoi(width_str); + int h = atoi(height_str); + if(w > 0 && h > 0) { + *width = w; + *height = h; + } + } +} diff --git a/ui/common/utils.h b/ui/common/utils.h new file mode 100644 index 0000000..592ad4b --- /dev/null +++ b/ui/common/utils.h @@ -0,0 +1,52 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2017 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef UIC_UTILS_H +#define UIC_UTILS_H + +#include "../ui/toolkit.h" +#include "../ui/text.h" + +#ifdef __cplusplus +extern "C" { +#endif + +UiPathElm* ui_default_pathelm_func(const char* full_path, size_t len, size_t* ret_nelm, void* data); + +/* + * overrides width/height with values from + * ui.window.width and ui.window.height properties if available + */ +void ui_get_window_default_width(int *width, int *height); + + +#ifdef __cplusplus +} +#endif + +#endif /* UIC_UTILS_H */ + diff --git a/ui/motif/Fsb.c b/ui/motif/Fsb.c new file mode 100644 index 0000000..bcccc7c --- /dev/null +++ b/ui/motif/Fsb.c @@ -0,0 +1,2642 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +//define FSB_ENABLE_DETAIL + +#include "Fsb.h" +#include "FsbP.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "pathbar.h" + +#include "../common/utils.h" + +#ifdef FSB_ENABLE_DETAIL +#include +#endif + +#define WIDGET_SPACING 5 +#define WINDOW_SPACING 8 + +#define BUTTON_EXTRA_SPACE 4 + +#define DATE_FORMAT_SAME_YEAR "%b %d %H:%M" +#define DATE_FORMAT_OTHER_YEAR "%b %d %Y" + +#define KB_SUFFIX "KiB" +#define MB_SUFFIX "MiB" +#define GB_SUFFIX "GiB" +#define TB_SUFFIX "TiB" + +#define FSB_ERROR_TITLE "Error" +#define FSB_ERROR_CHAR "Character '/' is not allowed in file names" +#define FSB_ERROR_RENAME "Cannot rename file: %s" +#define FSB_ERROR_DELETE "Cannot delete file: %s" +#define FSB_ERROR_CREATE_FOLDER "Cannot create folder: %s" +#define FSB_ERROR_OPEN_DIR "Cannot open directory: %s" + +#define FSB_DETAIL_HEADINGS "Name|Size|Last Modified" + +static void fsb_class_init(void); +static void fsb_class_part_init (WidgetClass wc); +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args); +static void fsb_resize(Widget widget); +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes); +static void fsb_destroy(Widget widget); +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args); +static Boolean fsb_acceptfocus(Widget widget, Time *time); + +static void fsb_insert_child(Widget child); + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb); + +static int FSBGlobFilter(const char *a, const char *b); + +static void FSBUpdateTitle(Widget w); + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs); + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg); + +static void FSBRename(XnFileSelectionBox fsb, const char *path); +static void FSBDelete(XnFileSelectionBox fsb, const char *path); + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item); + +static char* set_selected_path(XnFileSelectionBox data, XmString item); + +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd); + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback); + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListSelect(Widget fsb, Widget view, const char *item); +static void FileListCleanup(Widget fsb, Widget view, void *userData); +static void FileListDestroy(Widget fsb, Widget view, void *userData); + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb); + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count); + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailUpdate(Widget fsb, Widget view, FileElm *dirlist, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +static void FileListDetailSelect(Widget fsb, Widget view, const char *item); +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData); +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData); +static void FileListDetailAdjustColWidth(Widget grid); +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth); +#endif + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u); + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value); + +static void CreateUI(XnFileSelectionBox w); +static FSBViewWidgets CreateView(XnFileSelectionBox w, FSBViewCreateProc createProc, void *userData, Boolean useDirList); +static void AddViewMenuItem(XnFileSelectionBox w, const char *name, int viewIndex); +static void SelectView(XnFileSelectionBox f, int view); + +static char* FSBDialogTitle(Widget w); + +static FSBViewWidgets CreateListView(Widget fsb, ArgList args, int n, void *userData); +static FSBViewWidgets CreateDetailView(Widget fsb, ArgList args, int n, void *userData); + +static const char* GetHomeDir(void); + +static char* ConcatPath(const char *parent, const char *name); +static char* FileName(char *path); +static char* ParentPath(const char *path); +//static int CheckFileName(const char *name); + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path); +static void filedialog_cleanup_filedata(XnFileSelectionBox data); + + +static XtResource resources[] = { + {XmNokCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.okCallback), XmRCallback, NULL}, + {XmNcancelCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffset(XnFileSelectionBox, fsb.cancelCallback), XmRCallback, NULL}, + {XnNwidgetSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.widgetSpacing), XmRImmediate, (XtPointer)WIDGET_SPACING}, + {XnNwindowSpacing, XmCSpacing, XmRDimension, sizeof(Dimension), XtOffset(XnFileSelectionBox, fsb.windowSpacing), XmRImmediate, (XtPointer)WINDOW_SPACING}, + {XnNfsbType, XnCfsbType, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.type), XmRImmediate, (XtPointer)FILEDIALOG_OPEN}, + {XnNshowHidden, XnCshowHidden, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHidden), XmRImmediate, (XtPointer)False}, + {XnNshowHiddenButton, XnCshowHiddenButton, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showHiddenButton), XmRImmediate, (XtPointer)True}, + {XnNshowViewMenu, XnCshowViewMenu, XmRBoolean, sizeof(Boolean), XtOffset(XnFileSelectionBox, fsb.showViewMenu), XmRImmediate, (XtPointer)False}, + {XnNselectedView, XnCselectedView, XmRInt, sizeof(int), XtOffset(XnFileSelectionBox, fsb.selectedview), XmRImmediate, (XtPointer)0}, + + {XnNdirectory, XnCdirectory, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.currentPath), XmRString, NULL}, + {XnNselectedPath, XnCselectedPath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.selectedPath), XmRString, NULL}, + {XnNhomePath, XnChomePath, XmRString, sizeof(XmString), XtOffset(XnFileSelectionBox, fsb.homePath), XmRString, NULL}, + + {XnNfilter,XnCfilter,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.filterStr), XmRString, "*"}, + {XnNfilterFunc,XnCfilterFunc,XmRFunction,sizeof(FSBFilterFunc),XtOffset(XnFileSelectionBox, fsb.filterFunc), XmRFunction, NULL}, + + {XnNlabelListView,XnClabelListView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelListView), XmRString, "List"}, + {XnNlabelDetailView,XnClabelDetailView,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDetailView), XmRString, "Detail"}, + {XnNlabelOpenFileTitle,XnClabelOpenFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpenFileTitle), XmRString, "Open File"}, + {XnNlabelSaveFileTitle,XnClabelSaveFileTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSaveFileTitle), XmRString, "Save File"}, + {XnNlabelDirUp,XnClabelDirUp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirUp), XmRString, "Dir Up"}, + {XnNlabelHome,XnClabelHome,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHome), XmRString, "Home"}, + {XnNlabelNewFolder,XnClabelNewFolder,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFolder), XmRString, "New Folder"}, + {XnNlabelFilterButton,XnClabelFilterButton,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFilterButton), XmRString, "Filter"}, + {XnNlabelShowHiddenFiles,XnClabelShowHiddenFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelShowHiddenFiles), XmRString, "Show hiden files"}, + {XnNlabelDirectories,XnClabelDirectories,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectories), XmRString, "Directories"}, + {XnNlabelFiles,XnClabelFiles,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFiles), XmRString, "Files"}, + {XnNlabelRename,XnClabelRename,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelRename), XmRString, "Rename"}, + {XnNlabelDelete,XnClabelDelete,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDelete), XmRString, "Delete"}, + {XnNlabelOpen,XnClabelOpen,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOpen), XmRString, "Open"}, + {XnNlabelSave,XnClabelSave,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelSave), XmRString, "Save"}, + {XnNlabelOk,XnClabelOk,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelOk), XmRString, "OK"}, + {XnNlabelCancel,XnClabelCancel,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelCancel), XmRString, "Cancel"}, + {XnNlabelHelp,XnClabelHelp,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelHelp), XmRString, "Help"}, + {XnNlabelFileName,XnClabelFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelFileName), XmRString, "New File Name"}, + {XnNlabelDirectoryName,XnClabelDirectoryName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDirectoryName), XmRString, "Directory name:"}, + {XnNlabelNewFileName,XnClabelNewFileName,XmRXmString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelNewFileName), XmRString, "New file name:"}, + {XnNlabelDeleteFile,XnClabelDeleteFile,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.labelDeleteFile), XmRString, "Delete file '%s'?"}, + {XnNdetailHeadings,XnCdetailHeadings,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.detailHeadings), XmRString,FSB_DETAIL_HEADINGS}, + {XnNdateFormatSameYear,XnCdateFormatSameYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatSameYear), XmRString,DATE_FORMAT_SAME_YEAR}, + {XnNdateFormatOtherYear,XnNdateFormatOtherYear,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.dateFormatOtherYear), XmRString,DATE_FORMAT_OTHER_YEAR}, + {XnNsuffixBytes,XnCsuffixBytes,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixBytes), XmRString,"bytes"}, + {XnNsuffixKB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixKB), XmRString,KB_SUFFIX}, + {XnNsuffixMB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixMB), XmRString,MB_SUFFIX}, + {XnNsuffixGB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixGB), XmRString,GB_SUFFIX}, + {XnNsuffixTB,XnCsuffixKB,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.suffixTB), XmRString,TB_SUFFIX}, + + {XnNerrorTitle,XnCerrorTitle,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorTitle), XmRString,FSB_ERROR_TITLE}, + {XnNerrorIllegalChar,XnCerrorIllegalChar,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorIllegalChar), XmRString,FSB_ERROR_CHAR}, + {XnNerrorRename,XnCerrorRename,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorRename), XmRString,FSB_ERROR_RENAME}, + {XnNerrorCreateFolder,XnCerrorCreateFolder,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorFolder), XmRString,FSB_ERROR_CREATE_FOLDER}, + {XnNerrorDelete,XnCerrorDelete,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorDelete), XmRString,FSB_ERROR_DELETE}, + {XnNerrorOpenDir,XnCerrorOpenDir,XmRString,sizeof(XmString),XtOffset(XnFileSelectionBox, fsb.errorOpenDir), XmRString,FSB_ERROR_OPEN_DIR} +}; + +static XtActionsRec actionslist[] = { + {"focusIn", FocusInAP}, + {"NULL", NULL} +}; + + +static char defaultTranslations[] = ": focusIn()"; + +static XtResource constraints[] = {}; + +FSBClassRec fsbWidgetClassRec = { + // Core Class + { + (WidgetClass)&xmFormClassRec, + "XnFSB", // class_name + sizeof(FSBRec), // widget_size + fsb_class_init, // class_initialize + fsb_class_part_init, // class_part_initialize + FALSE, // class_inited + fsb_init, // initialize + NULL, // initialize_hook + fsb_realize, // realize + actionslist, // actions + XtNumber(actionslist), // num_actions + resources, // resources + XtNumber(resources), // num_resources + NULLQUARK, // xrm_class + True, // compress_motion + True, // compress_exposure + True, // compress_enterleave + False, // visible_interest + fsb_destroy, // destroy + fsb_resize, // resize + XtInheritExpose, // expose + fsb_set_values, // set_values + NULL, // set_values_hook + XtInheritSetValuesAlmost, // set_values_almost + NULL, // get_values_hook + fsb_acceptfocus, // accept_focus + XtVersion, // version + NULL, // callback_offsets + defaultTranslations, // tm_table + XtInheritQueryGeometry, // query_geometry + XtInheritDisplayAccelerator, // display_accelerator + NULL, // extension + }, + // Composite Class + { + XtInheritGeometryManager, // geometry_manager + XtInheritChangeManaged, // change_managed + fsb_insert_child, // insert_child + XtInheritDeleteChild, // delete_child + NULL, // extension + }, + // Constraint Class + { + constraints, // resources + XtNumber(constraints), // num_resources + sizeof(XmFormConstraintRec), // constraint_size + NULL, // initialize + NULL, // destroy + NULL, // set_value + NULL, // extension + }, + // XmManager Class + { + XtInheritTranslations, // translations + NULL, // syn_resources + 0, // num_syn_resources + NULL, // syn_constraint_resources + 0, // num_syn_constraint_resources + XmInheritParentProcess, // parent_process + NULL // extension + }, + // XmBulletinBoard + { + FALSE, + NULL, + XmInheritFocusMovedProc, + NULL + }, + // XmForm Class + { + NULL + }, + // FSB Class + { + 0 + } +}; + +WidgetClass xnFsbWidgetClass = (WidgetClass)&fsbWidgetClassRec; + + +static void fsb_class_init(void) { + +} + +static void fsb_class_part_init (WidgetClass wc) { + FSBClassRec *fsbClass = (FSBClassRec*)wc; + XmFormClassRec *formClass = (XmFormClassRec*)xmFormWidgetClass; + + fsbClass->constraint_class.initialize = formClass->constraint_class.initialize; + fsbClass->constraint_class.set_values = formClass->constraint_class.set_values; +} + + +#define STRDUP_RES(a) if(a) a = strdup(a) +#define XMS_STRDUP_RES(a) if(a) a = XmStringCopy(a) + +static void fsb_init(Widget request, Widget neww, ArgList args, Cardinal *num_args) { + XnFileSelectionBox fsb = (XnFileSelectionBox)neww; + (xmFormClassRec.core_class.initialize)(request, neww, args, num_args); + + fsb->fsb.disable_set_values = 0; + + STRDUP_RES(fsb->fsb.homePath); + STRDUP_RES(fsb->fsb.selectedPath); + STRDUP_RES(fsb->fsb.currentPath); + STRDUP_RES(fsb->fsb.filterStr); + STRDUP_RES(fsb->fsb.labelListView); + STRDUP_RES(fsb->fsb.labelDetailView); + STRDUP_RES(fsb->fsb.labelOpenFileTitle); + STRDUP_RES(fsb->fsb.labelSaveFileTitle); + XMS_STRDUP_RES(fsb->fsb.labelDirUp); + XMS_STRDUP_RES(fsb->fsb.labelHome); + XMS_STRDUP_RES(fsb->fsb.labelNewFolder); + XMS_STRDUP_RES(fsb->fsb.labelFilterButton); + XMS_STRDUP_RES(fsb->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(fsb->fsb.labelDirectories); + XMS_STRDUP_RES(fsb->fsb.labelFiles); + XMS_STRDUP_RES(fsb->fsb.labelRename); + XMS_STRDUP_RES(fsb->fsb.labelDelete); + XMS_STRDUP_RES(fsb->fsb.labelOpen); + XMS_STRDUP_RES(fsb->fsb.labelSave); + XMS_STRDUP_RES(fsb->fsb.labelCancel); + XMS_STRDUP_RES(fsb->fsb.labelHelp); + XMS_STRDUP_RES(fsb->fsb.labelFileName); + XMS_STRDUP_RES(fsb->fsb.labelDirectoryName); + XMS_STRDUP_RES(fsb->fsb.labelNewFileName); + STRDUP_RES(fsb->fsb.labelDeleteFile); + STRDUP_RES(fsb->fsb.detailHeadings); + STRDUP_RES(fsb->fsb.dateFormatSameYear); + STRDUP_RES(fsb->fsb.dateFormatOtherYear); + STRDUP_RES(fsb->fsb.suffixBytes); + STRDUP_RES(fsb->fsb.suffixKB); + STRDUP_RES(fsb->fsb.suffixMB); + STRDUP_RES(fsb->fsb.suffixGB); + STRDUP_RES(fsb->fsb.suffixTB); + STRDUP_RES(fsb->fsb.errorTitle); + STRDUP_RES(fsb->fsb.errorIllegalChar); + STRDUP_RES(fsb->fsb.errorRename); + STRDUP_RES(fsb->fsb.errorFolder); + STRDUP_RES(fsb->fsb.errorDelete); + STRDUP_RES(fsb->fsb.errorOpenDir); + + CreateUI((XnFileSelectionBox)fsb); + + XtAddCallback(neww, XmNmapCallback, fsb_mapcb, NULL); +} + +#define STR_FREE(a) if(a) free(a) +#define XMSTR_FREE(a) if(a) XmStringFree(a) + +static void fsb_destroy(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + + // destroy all views + for(int i=0;ifsb.numviews;i++) { + FSBView v = w->fsb.view[i]; + v.destroy(widget, v.widget, v.userData); + } + + STR_FREE(w->fsb.homePath); + + // free filelists + filedialog_cleanup_filedata(w); + STR_FREE(w->fsb.currentPath); + STR_FREE(w->fsb.selectedPath); + STR_FREE(w->fsb.filterStr); + + PathBarDestroy(w->fsb.pathBar); + + // free strings + STR_FREE(w->fsb.labelListView); + STR_FREE(w->fsb.labelDetailView); + STR_FREE(w->fsb.labelOpenFileTitle); + STR_FREE(w->fsb.labelSaveFileTitle); + + XMSTR_FREE(w->fsb.labelDirUp); + XMSTR_FREE(w->fsb.labelHome); + XMSTR_FREE(w->fsb.labelNewFolder); + XMSTR_FREE(w->fsb.labelFilterButton); + XMSTR_FREE(w->fsb.labelShowHiddenFiles); + XMSTR_FREE(w->fsb.labelDirectories); + XMSTR_FREE(w->fsb.labelFiles); + XMSTR_FREE(w->fsb.labelRename); + XMSTR_FREE(w->fsb.labelDelete); + XMSTR_FREE(w->fsb.labelOpen); + XMSTR_FREE(w->fsb.labelSave); + XMSTR_FREE(w->fsb.labelCancel); + XMSTR_FREE(w->fsb.labelHelp); + XMSTR_FREE(w->fsb.labelFileName); + XMSTR_FREE(w->fsb.labelDirectoryName); + XMSTR_FREE(w->fsb.labelNewFileName); + STR_FREE(w->fsb.labelDeleteFile); + STR_FREE(w->fsb.detailHeadings); + + STR_FREE(w->fsb.dateFormatSameYear); + STR_FREE(w->fsb.dateFormatOtherYear); + STR_FREE(w->fsb.suffixBytes); + STR_FREE(w->fsb.suffixKB); + STR_FREE(w->fsb.suffixMB); + STR_FREE(w->fsb.suffixGB); + STR_FREE(w->fsb.suffixTB); + + STR_FREE(w->fsb.errorTitle); + STR_FREE(w->fsb.errorIllegalChar); + STR_FREE(w->fsb.errorRename); + STR_FREE(w->fsb.errorFolder); + STR_FREE(w->fsb.errorDelete); + STR_FREE(w->fsb.errorOpenDir); +} + +static void fsb_resize(Widget widget) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.resize)(widget); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void fsb_realize(Widget widget, XtValueMask *mask, XSetWindowAttributes *attributes) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + (xmFormClassRec.core_class.realize)(widget, mask, attributes); + + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + +#ifdef FSB_ENABLE_DETAIL + if(w->fsb.view[w->fsb.selectedview].update == FileListDetailUpdate) { + FileListDetailAdjustColWidth(w->fsb.grid); + } +#endif +} + +static void FSBUpdateTitle(Widget w) { + if(XtParent(w)->core.widget_class == xmDialogShellWidgetClass) { + char *title = FSBDialogTitle(w); + XtVaSetValues(XtParent(w), XmNtitle, title, NULL); + } +} + +static Boolean fsb_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { + Boolean r = False; + + XnFileSelectionBox o = (XnFileSelectionBox)old; + XnFileSelectionBox n = (XnFileSelectionBox)neww; + + int setOkBtnLabel = 0; + int ismanaged = XtIsManaged(neww); + Dimension width, height; + if(!ismanaged) { + width = n->core.width; + height = n->core.height; + if(n->fsb.pathBar) { + n->fsb.pathBar->disableResize = True; + } + } + + if(o->fsb.selectedview != n->fsb.selectedview) { + int selectedview = n->fsb.selectedview; + n->fsb.selectedview = o->fsb.selectedview; + SelectView(n, selectedview); + } + + char *updateDir = NULL; + int selectItem = 0; + if(o->fsb.selectedPath != n->fsb.selectedPath) { + STR_FREE(o->fsb.selectedPath); + STRDUP_RES(n->fsb.selectedPath); + XmTextFieldSetString(n->fsb.name, FileName(n->fsb.selectedPath)); + // also update current directory + updateDir = ParentPath(n->fsb.selectedPath); + selectItem = 1; + } + if(o->fsb.currentPath != n->fsb.currentPath) { + STR_FREE(o->fsb.currentPath); + updateDir = strdup(n->fsb.currentPath); + n->fsb.currentPath = NULL; + } + + if(o->fsb.filterStr != n->fsb.filterStr) { + STR_FREE(o->fsb.filterStr); + STRDUP_RES(n->fsb.filterStr); + XmTextFieldSetString(XmDropDownGetText(n->fsb.filter), n->fsb.filterStr); + if(!updateDir) { + filedialog_update_dir(n, NULL); + } + } + + if(updateDir) { + filedialog_update_dir(n, updateDir); + PathBarSetPath(n->fsb.pathBar, updateDir); + free(updateDir); + } + + if(o->fsb.type != n->fsb.type) { + if(n->fsb.type == FILEDIALOG_OPEN) { + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.separator, NULL); + XtUnmanageChild(n->fsb.name); + XtUnmanageChild(n->fsb.nameLabel); + } else { + XtManageChild(n->fsb.name); + XtManageChild(n->fsb.nameLabel); + XtVaSetValues(n->fsb.workarea, XmNbottomWidget, n->fsb.nameLabel, NULL); + } + FSBUpdateTitle(neww); + setOkBtnLabel = 1; + } + + // label strings + int updateTitle = 0; + if(o->fsb.labelListView != n->fsb.labelListView) { + STR_FREE(o->fsb.labelListView); + STRDUP_RES(n->fsb.labelListView); + XmString label = XmStringCreateLocalized(n->fsb.labelListView); + XtVaSetValues(n->fsb.viewSelectorList, XmNlabelString, label, NULL); + XmStringFree(label); + } + if(o->fsb.labelDetailView != n->fsb.labelDetailView) { + STR_FREE(o->fsb.labelDetailView); + STRDUP_RES(n->fsb.labelDetailView); + XmString label = XmStringCreateLocalized(n->fsb.labelDetailView); + XtVaSetValues(n->fsb.viewSelectorDetail, XmNlabelString, label, NULL); + if(n->fsb.detailToggleButton) { + XtVaSetValues(n->fsb.detailToggleButton, XmNlabelString, label, NULL); + } + XmStringFree(label); + } + if(o->fsb.labelOpenFileTitle != n->fsb.labelOpenFileTitle) { + STR_FREE(o->fsb.labelOpenFileTitle); + STRDUP_RES(n->fsb.labelOpenFileTitle); + updateTitle = 1; + } + if(o->fsb.labelSaveFileTitle != n->fsb.labelSaveFileTitle) { + STR_FREE(o->fsb.labelSaveFileTitle); + STRDUP_RES(n->fsb.labelSaveFileTitle); + updateTitle = 1; + } + + if(o->fsb.labelDirUp != n->fsb.labelDirUp) { + XMSTR_FREE(o->fsb.labelDirUp); + XMS_STRDUP_RES(n->fsb.labelDirUp); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelDirUp, NULL); + } + if(o->fsb.labelHome != n->fsb.labelHome) { + XMSTR_FREE(o->fsb.labelHome); + XMS_STRDUP_RES(n->fsb.labelHome); + XtVaSetValues(n->fsb.dirUp, XmNlabelString, n->fsb.labelHome, NULL); + } + if(o->fsb.labelNewFolder != n->fsb.labelNewFolder) { + XMSTR_FREE(o->fsb.labelNewFolder); + XMS_STRDUP_RES(n->fsb.labelNewFolder); + XtVaSetValues(n->fsb.newFolder, XmNlabelString, n->fsb.labelNewFolder, NULL); + } + if(o->fsb.labelFilterButton != n->fsb.labelFilterButton) { + XMSTR_FREE(o->fsb.labelFilterButton); + XMS_STRDUP_RES(n->fsb.labelFilterButton); + XtVaSetValues(n->fsb.filterButton, XmNlabelString, n->fsb.labelFilterButton, NULL); + } + if(o->fsb.labelShowHiddenFiles != n->fsb.labelShowHiddenFiles) { + XMSTR_FREE(o->fsb.labelShowHiddenFiles); + XMS_STRDUP_RES(n->fsb.labelShowHiddenFiles); + XtVaSetValues(n->fsb.showHiddenButtonW, XmNlabelString, n->fsb.labelShowHiddenFiles, NULL); + } + if(o->fsb.labelDirectories != n->fsb.labelDirectories) { + XMSTR_FREE(o->fsb.labelDirectories); + XMS_STRDUP_RES(n->fsb.labelDirectories); + XtVaSetValues(n->fsb.lsDirLabel, XmNlabelString, n->fsb.labelDirectories, NULL); + } + if(o->fsb.labelFiles != n->fsb.labelFiles) { + XMSTR_FREE(o->fsb.labelFiles); + XMS_STRDUP_RES(n->fsb.labelFiles); + XtVaSetValues(n->fsb.lsFileLabel, XmNlabelString, n->fsb.labelFiles, NULL); + } + int recreateContextMenu = 0; + if(o->fsb.labelRename != n->fsb.labelRename) { + XMSTR_FREE(o->fsb.labelRename); + XMS_STRDUP_RES(n->fsb.labelRename); + recreateContextMenu = 1; + } + if(o->fsb.labelDelete != n->fsb.labelDelete) { + XMSTR_FREE(o->fsb.labelDelete); + XMS_STRDUP_RES(n->fsb.labelDelete); + recreateContextMenu = 1; + } + + if(o->fsb.labelOpen != n->fsb.labelOpen) { + XMSTR_FREE(o->fsb.labelOpen); + XMS_STRDUP_RES(n->fsb.labelOpen); + setOkBtnLabel = 1; + } + if(o->fsb.labelSave != n->fsb.labelSave) { + XMSTR_FREE(o->fsb.labelSave); + XMS_STRDUP_RES(n->fsb.labelSave); + setOkBtnLabel = 1; + } + if(o->fsb.labelCancel != n->fsb.labelCancel) { + XMSTR_FREE(o->fsb.labelCancel); + XMS_STRDUP_RES(n->fsb.labelCancel); + XtVaSetValues(n->fsb.cancelBtn, XmNlabelString, n->fsb.labelCancel, NULL); + } + if(o->fsb.labelHelp != n->fsb.labelHelp) { + XMSTR_FREE(o->fsb.labelHelp); + XMS_STRDUP_RES(n->fsb.labelHelp); + XtVaSetValues(n->fsb.helpBtn, XmNlabelString, n->fsb.labelHelp, NULL); + } + if(o->fsb.labelFileName != n->fsb.labelFileName) { + XMSTR_FREE(o->fsb.labelFileName); + XMS_STRDUP_RES(n->fsb.labelFileName); + XtVaSetValues(n->fsb.nameLabel, XmNlabelString, n->fsb.labelFileName, NULL); + } + if(o->fsb.labelDirectoryName != n->fsb.labelDirectoryName) { + XMSTR_FREE(o->fsb.labelDirectoryName); + XMS_STRDUP_RES(n->fsb.labelDirectoryName); + } + if(o->fsb.labelNewFileName != n->fsb.labelNewFileName) { + XMSTR_FREE(o->fsb.labelNewFileName); + XMS_STRDUP_RES(n->fsb.labelNewFileName); + } + + if(o->fsb.labelDeleteFile != n->fsb.labelDeleteFile) { + STR_FREE(o->fsb.labelDeleteFile); + STRDUP_RES(n->fsb.labelDeleteFile); + } +#ifdef FSB_ENABLE_DETAIL + if(o->fsb.detailHeadings != n->fsb.detailHeadings) { + STR_FREE(o->fsb.detailHeadings); + STRDUP_RES(n->fsb.detailHeadings); + XtVaSetValues(n->fsb.grid, XmNsimpleHeadings, n->fsb.detailHeadings, NULL); + } +#endif + if(o->fsb.dateFormatSameYear != n->fsb.dateFormatSameYear) { + STR_FREE(o->fsb.dateFormatSameYear); + STRDUP_RES(n->fsb.dateFormatSameYear); + } + if(o->fsb.dateFormatOtherYear != n->fsb.dateFormatOtherYear) { + STR_FREE(o->fsb.dateFormatOtherYear); + STRDUP_RES(n->fsb.dateFormatOtherYear); + } + if(o->fsb.suffixBytes != n->fsb.suffixBytes) { + STR_FREE(o->fsb.suffixBytes); + STRDUP_RES(n->fsb.suffixBytes); + } + if(o->fsb.suffixMB != n->fsb.suffixMB) { + STR_FREE(o->fsb.suffixMB); + STRDUP_RES(n->fsb.suffixMB); + } + if(o->fsb.suffixGB != n->fsb.suffixGB) { + STR_FREE(o->fsb.suffixGB); + STRDUP_RES(n->fsb.suffixGB); + } + if(o->fsb.suffixTB != n->fsb.suffixTB) { + STR_FREE(o->fsb.suffixTB); + STRDUP_RES(n->fsb.suffixTB); + } + if(o->fsb.errorTitle != n->fsb.errorTitle) { + STR_FREE(o->fsb.errorTitle); + STRDUP_RES(n->fsb.errorTitle); + } + if(o->fsb.errorIllegalChar != n->fsb.errorIllegalChar) { + STR_FREE(o->fsb.errorIllegalChar); + STRDUP_RES(n->fsb.errorIllegalChar); + } + if(o->fsb.errorRename != n->fsb.errorRename) { + STR_FREE(o->fsb.errorRename); + STRDUP_RES(n->fsb.errorRename); + } + if(o->fsb.errorFolder != n->fsb.errorFolder) { + STR_FREE(o->fsb.errorFolder); + STRDUP_RES(n->fsb.errorFolder); + } + if(o->fsb.errorDelete != n->fsb.errorDelete) { + STR_FREE(o->fsb.errorDelete); + STRDUP_RES(n->fsb.errorDelete); + } + if(o->fsb.errorOpenDir != n->fsb.errorOpenDir) { + STR_FREE(o->fsb.errorOpenDir); + STRDUP_RES(n->fsb.errorOpenDir); + } + + if(updateTitle) { + FSBUpdateTitle(neww); + } + if(recreateContextMenu) { + XtDestroyWidget(n->fsb.listContextMenu); + XtDestroyWidget(n->fsb.gridContextMenu); + n->fsb.listContextMenu = CreateContextMenu(n, n->fsb.filelist, FileContextMenuCB); + n->fsb.gridContextMenu = CreateContextMenu(n, n->fsb.grid, FileContextMenuCB); + } + if(setOkBtnLabel) { + XtVaSetValues(n->fsb.okBtn, XmNlabelString, n->fsb.type == FILEDIALOG_OPEN ? n->fsb.labelOpen : n->fsb.labelSave, NULL); + } + + if(!ismanaged && !n->fsb.disable_set_values) { + n->fsb.disable_set_values = 1; + XtVaSetValues(neww, XmNwidth, width, XmNheight, height, NULL); + n->fsb.disable_set_values = 0; + + if(n->fsb.pathBar) + n->fsb.pathBar->disableResize = False; + } + + if(selectItem) { + if(ismanaged) { + FSBSelectItem(n, FileName(n->fsb.selectedPath)); + } + } + + Boolean fr = (xmFormClassRec.core_class.set_values)(old, request, neww, args, num_args); + return fr ? fr : r; +} + +static void fsb_insert_child(Widget child) { + XnFileSelectionBox p = (XnFileSelectionBox)XtParent(child); + (xmFormClassRec.composite_class.insert_child)(child); + + if(!p->fsb.gui_created) { + return; + } + + // custom child widget insert + XtVaSetValues(child, + XmNbottomAttachment, XmATTACH_WIDGET, + XmNbottomWidget, p->fsb.bottom_widget, + XmNbottomOffset, p->fsb.widgetSpacing, + XmNleftAttachment, XmATTACH_FORM, + XmNleftOffset, p->fsb.windowSpacing, + XmNrightAttachment, XmATTACH_FORM, + XmNrightAttachment, XmATTACH_FORM, + XmNrightOffset, p->fsb.windowSpacing, + NULL); + + + XtVaSetValues(p->fsb.listform, + XmNbottomWidget, child, + XmNbottomOffset, 0, + NULL); + + p->fsb.workarea = child; +} + +Boolean fsb_acceptfocus(Widget widget, Time *time) { + return 0; +} + +static void fsb_mapcb(Widget widget, XtPointer u, XtPointer cb) { + XnFileSelectionBox w = (XnFileSelectionBox)widget; + pathbar_resize(w->fsb.pathBar->widget, w->fsb.pathBar, NULL); + + if(w->fsb.type == FILEDIALOG_OPEN) { + FSBView view = w->fsb.view[w->fsb.selectedview]; + XmProcessTraversal(view.focus, XmTRAVERSE_CURRENT); + } else { + XmProcessTraversal(w->fsb.name, XmTRAVERSE_CURRENT); + } + + + if(w->fsb.selectedPath) { + FSBSelectItem(w, FileName(w->fsb.selectedPath)); + } +} + +static void FocusInAP(Widget w, XEvent *event, String *args, Cardinal *nArgs) { + +} + +static int apply_filter(XnFileSelectionBox w, const char *pattern, const char *string) { + if(!pattern) return 0; + + FSBFilterFunc func = w->fsb.filterFunc ? w->fsb.filterFunc : FSBGlobFilter; + return func(pattern, string); +} + +static int FSBGlobFilter(const char *a, const char *b) { + return fnmatch(a, b, 0); +} + + +static void errCB(Widget w, XtPointer d, XtPointer cbs) { + XtDestroyWidget(w); +} + +static void ErrDialog(XnFileSelectionBox w, const char *title, const char *errmsg) { + Arg args[16]; + int n = 0; + + XmString titleStr = XmStringCreateLocalized((char*)title); + XmString msg = XmStringCreateLocalized((char*)errmsg); + + XtSetArg(args[n], XmNdialogTitle, titleStr); n++; + XtSetArg(args[n], XmNselectionLabelString, msg); n++; + XtSetArg(args[n], XmNokLabelString, w->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, w->fsb.labelCancel); n++; + + Widget dialog = XmCreatePromptDialog ((Widget)w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget cancel = XmSelectionBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON); + XtUnmanageChild(cancel); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, errCB, NULL); + + XtManageChild(dialog); + + XmStringFree(titleStr); + XmStringFree(msg); +} + +static void rename_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + char *fileName = NULL; + XmStringGetLtoR(cb->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + // make sure the new file name doesn't contain a path separator + if(strchr(fileName, '/')) { + ErrDialog(fsb, fsb->fsb.errorTitle, fsb->fsb.errorIllegalChar); + XtFree(fileName); + return; + } + + char *parentPath = ParentPath(path); + char *newPath = ConcatPath(parentPath, fileName); + + if(rename(path, newPath)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorRename, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + filedialog_update_dir(fsb, parentPath); + } + + free(parentPath); + free(newPath); + XtFree(fileName); + XtDestroyWidget(XtParent(w)); +} + +static void selectionbox_cancel(Widget w, XtPointer data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBRename(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + + XmString filename = XmStringCreateLocalized(name); + XtSetArg(args[n], XmNselectionLabelString,fsb->fsb.labelNewFileName); n++; + XtSetArg(args[n], XmNtextString, filename); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelRename); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "RenameFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)rename_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + XmStringFree(filename); + XtManageChild(dialog); +} + +static void delete_file_cb(Widget w, const char *path, XmSelectionBoxCallbackStruct *cb) { + XnFileSelectionBox fsb = NULL; + XtVaGetValues(w, XmNuserData, &fsb, NULL); + + if(unlink(path)) { + char errmsg[256]; + snprintf(errmsg, 256, fsb->fsb.errorDelete, strerror(errno)); + ErrDialog(fsb, fsb->fsb.errorTitle, errmsg); + } else { + char *parentPath = ParentPath(path); + filedialog_update_dir(fsb, parentPath); + free(parentPath); + } + + XtDestroyWidget(XtParent(w)); +} + +static void FSBDelete(XnFileSelectionBox fsb, const char *path) { + Arg args[16]; + int n = 0; + Widget w = (Widget)fsb; + + char *name = FileName((char*)path); + size_t len = strlen(name); + size_t msglen = len + strlen(fsb->fsb.labelDeleteFile) + 4; + char *msg = malloc(msglen); + snprintf(msg, msglen, fsb->fsb.labelDeleteFile, name); + + XmString prompt = XmStringCreateLocalized(msg); + XtSetArg(args[n], XmNselectionLabelString, prompt); n++; + XtSetArg(args[n], XmNuserData, fsb); n++; + XtSetArg(args[n], XmNdialogTitle, fsb->fsb.labelDelete); n++; + XtSetArg(args[n], XmNokLabelString, fsb->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, fsb->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "DeleteFilePrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + Widget text = XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT); + XtUnmanageChild(text); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)delete_file_cb, (char*)path); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)selectionbox_cancel, NULL); + + free(msg); + XmStringFree(prompt); + XtManageChild(dialog); +} + +static void FSBSelectItem(XnFileSelectionBox fsb, const char *item) { + FSBView view = fsb->fsb.view[fsb->fsb.selectedview]; + if(view.select) { + view.select((Widget)fsb, view.widget, item); + } +} + +static char* set_selected_path(XnFileSelectionBox data, XmString item) +{ + char *name = NULL; + XmStringGetLtoR(item, XmFONTLIST_DEFAULT_TAG, &name); + if(!name) { + return NULL; + } + char *path = ConcatPath(data->fsb.currentPath, name); + XtFree(name); + + if(data->fsb.selectedPath) { + free(data->fsb.selectedPath); + } + data->fsb.selectedPath = path; + + return path; +} + +// item0: rename +// item1: delete +static void FileContextMenuCB(Widget item, XtPointer index, XtPointer cd) { + intptr_t i = (intptr_t)index; + Widget parent = XtParent(item); + XnFileSelectionBox fsb = NULL; + XtVaGetValues(parent, XmNuserData, &fsb, NULL); + + const char *path = fsb->fsb.selectedPath; + if(path) { + if(i == 0) { + FSBRename(fsb, path); + } else if(i == 1) { + FSBDelete(fsb, path); + } + } +} + +static Widget CreateContextMenu(XnFileSelectionBox fsb, Widget parent, XtCallbackProc callback) { + return XmVaCreateSimplePopupMenu( + parent, "popup", callback, XmNpopupEnabled, XmPOPUP_AUTOMATIC, + XmNuserData, fsb, + XmVaPUSHBUTTON, fsb->fsb.labelRename, 'R', NULL, NULL, + XmVaPUSHBUTTON, fsb->fsb.labelDelete, 'D', NULL, NULL, + NULL); +} + + +static void FileListUpdate(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData) { + XnFileSelectionBox data = userData; + FileListWidgetAdd(data, data->fsb.filelist, data->fsb.showHidden, filter, files, filecount); +} + +static void FileListSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numItems = 0; + XmStringTable items = NULL; + XtVaGetValues(w->fsb.filelist, XmNitemCount, &numItems, XmNitems, &items, NULL); + + for(int i=0;ifsb.filelist, i+1, False); + break; + } + XtFree(str); + } +} + +static void FileListCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + XmListDeleteAllItems(data->fsb.filelist); +} + +static void FileListDestroy(Widget fsb, Widget view, void *userData) { + // unused +} + +static void FileListActivateCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.end = True; + data->fsb.status = FILEDIALOG_OK; + data->fsb.selIsDir = False; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } +} + +static void FileListSelectCB(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *name = NULL; + XmStringGetLtoR(cb->item, XmFONTLIST_DEFAULT_TAG, &name); + XmTextFieldSetString(data->fsb.name, name); + XtFree(name); + } else { + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = False; + } + } +} + + +static void FileListWidgetAdd(XnFileSelectionBox fsb, Widget w, int showHidden, const char *filter, FileElm *ls, int count) +{ + if(count > 0) { + XmStringTable items = calloc(count, sizeof(XmString)); + int i = 0; + + for(int j=0;jpath); + if((!showHidden && name[0] == '.') || apply_filter(fsb, filter, name)) { + continue; + } + + items[i] = XmStringCreateLocalized(name); + i++; + } + XmListAddItems(w, items, i, 0); + for(i=0;ifsb.grid, data->fsb.showHidden, filter, files, filecount, maxnamelen); +} +#endif + +/* + * create file size string with kb/mb/gb/tb suffix + */ +static char* size_str(XnFileSelectionBox fsb, FileElm *f) { + char *str = malloc(16); + uint64_t size = f->size; + + if(f->isDirectory) { + str[0] = '\0'; + } else if(size < 0x400) { + snprintf(str, 16, "%d %s", (int)size, fsb->fsb.suffixBytes); + } else if(size < 0x100000) { + float s = (float)size/0x400; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x2800 && diff != 0) { + // size < 10 KiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixKB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixKB); + } + } else if(size < 0x40000000) { + float s = (float)size/0x100000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0xa00000 && diff != 0) { + // size < 10 MiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixMB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixMB); + } + } else if(size < 0x1000000000ULL) { + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 GiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixGB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixGB); + } + } else { + size /= 1024; + float s = (float)size/0x40000000; + int diff = (s*100 - (int)s*100); + if(diff > 90) { + diff = 0; + s += 0.10f; + } + if(size < 0x280000000 && diff != 0) { + // size < 10 TiB + snprintf(str, 16, "%.1f %s", s, fsb->fsb.suffixTB); + } else { + snprintf(str, 16, "%.0f %s", s, fsb->fsb.suffixTB); + } + } + return str; +} + +static char* date_str(XnFileSelectionBox fsb, time_t tm) { + struct tm t; + struct tm n; + time_t now = time(NULL); + + localtime_r(&tm, &t); + localtime_r(&now, &n); + + char *str = malloc(24); + if(t.tm_year == n.tm_year) { + strftime(str, 24, fsb->fsb.dateFormatSameYear, &t); + } else { + strftime(str, 24, fsb->fsb.dateFormatOtherYear, &t); + } + return str; +} + +#ifdef FSB_ENABLE_DETAIL +static void FileListDetailAdjustColWidth(Widget grid) { + XmLGridColumn column0 = XmLGridGetColumn(grid, XmCONTENT, 0); + XmLGridColumn column1 = XmLGridGetColumn(grid, XmCONTENT, 1); + XmLGridColumn column2 = XmLGridGetColumn(grid, XmCONTENT, 2); + + Dimension col0Width = XmLGridColumnWidthInPixels(column0); + Dimension col1Width = XmLGridColumnWidthInPixels(column1); + Dimension col2Width = XmLGridColumnWidthInPixels(column2); + + Dimension totalWidth = col0Width + col1Width + col2Width; + + Dimension gridWidth = 0; + Dimension gridShadow = 0; + XtVaGetValues(grid, XmNwidth, &gridWidth, XmNshadowThickness, &gridShadow, NULL); + + Dimension widthDiff = gridWidth - totalWidth - gridShadow - gridShadow; + + if(gridWidth > totalWidth) { + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, col0Width + widthDiff - XmLGridVSBWidth(grid) - 2, + XmNcolumnSizePolicy, XmCONSTANT, + NULL); + } +} + +static void FileListDetailAdd(XnFileSelectionBox fsb, Widget grid, int showHidden, const char *filter, FileElm *ls, int count, int maxWidth) +{ + XmLGridAddRows(grid, XmCONTENT, 1, count); + + int row = 0; + for(int i=0;ipath); + if((!showHidden && name[0] == '.') || (!e->isDirectory && apply_filter(fsb, filter, name))) { + continue; + } + + // name + XmString str = XmStringCreateLocalized(name); + XtVaSetValues(grid, + XmNcolumn, 0, + XmNrow, row, + XmNcellString, str, NULL); + XmStringFree(str); + // size + char *szbuf = size_str(fsb, e); + str = XmStringCreateLocalized(szbuf); + XtVaSetValues(grid, + XmNcolumn, 1, + XmNrow, row, + XmNcellString, str, NULL); + free(szbuf); + XmStringFree(str); + // date + char *datebuf = date_str(fsb, e->lastModified); + str = XmStringCreateLocalized(datebuf); + XtVaSetValues(grid, + XmNcolumn, 2, + XmNrow, row, + XmNcellString, str, NULL); + free(datebuf); + XmStringFree(str); + + XtVaSetValues(grid, XmNrow, row, XmNrowUserData, e, NULL); + row++; + } + + // remove unused rows + if(count > row) { + XmLGridDeleteRows(grid, XmCONTENT, row, count-row); + } + + if(maxWidth < 16) { + maxWidth = 16; + } + + XtVaSetValues(grid, + XmNcolumnRangeStart, 0, + XmNcolumnRangeEnd, 0, + XmNcolumnWidth, maxWidth, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 1, + XmNcolumnRangeEnd, 1, + XmNcolumnWidth, 9, + XmNcellAlignment, XmALIGNMENT_LEFT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + XtVaSetValues(grid, + XmNcolumnRangeStart, 2, + XmNcolumnRangeEnd, 2, + XmNcolumnWidth, 16, + XmNcellAlignment, XmALIGNMENT_RIGHT, + XmNcolumnSizePolicy, XmVARIABLE, + NULL); + + FileListDetailAdjustColWidth(grid); +} + +static void FileListDetailSelect(Widget fsb, Widget view, const char *item) { + XnFileSelectionBox w = (XnFileSelectionBox)fsb; + + int numRows = 0; + XtVaGetValues(w->fsb.grid, XmNrows, &numRows, NULL); + + XmLGridColumn col = XmLGridGetColumn(w->fsb.grid, XmCONTENT, 0); + for(int i=0;ifsb.grid, XmCONTENT, i); + FileElm *elm = NULL; + XtVaGetValues(w->fsb.grid, XmNrowPtr, row, XmNcolumnPtr, col, XmNrowUserData, &elm, NULL); + if(elm) { + if(!strcmp(item, FileName(elm->path))) { + XmLGridSelectRow(w->fsb.grid, i, False); + XmLGridFocusAndShowRow(w->fsb.grid, i+1); + break; + } + } + } +} + +static void FileListDetailCleanup(Widget fsb, Widget view, void *userData) { + XnFileSelectionBox data = userData; + // cleanup grid + Cardinal rows = 0; + XtVaGetValues(data->fsb.grid, XmNrows, &rows, NULL); + XmLGridDeleteRows(data->fsb.grid, XmCONTENT, 0, rows); +} + +static void FileListDetailDestroy(Widget fsb, Widget view, void *userData) { + // unused +} +#endif + +static void create_folder(Widget w, XnFileSelectionBox data, XmSelectionBoxCallbackStruct *cbs) { + char *fileName = NULL; + XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &fileName); + + char *newFolder = ConcatPath(data->fsb.currentPath ? data->fsb.currentPath : "", fileName); + if(mkdir(newFolder, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorFolder, strerror(errno)); + ErrDialog(data, data->fsb.errorTitle, errmsg); + } else { + char *p = strdup(data->fsb.currentPath); + filedialog_update_dir(data, p); + free(p); + } + free(newFolder); + + XtDestroyWidget(XtParent(w)); +} + +static void new_folder_cancel(Widget w, XnFileSelectionBox data, XtPointer d) { + XtDestroyWidget(XtParent(w)); +} + +static void FSBNewFolder(Widget w, XnFileSelectionBox data, XtPointer u) +{ + Arg args[16]; + int n = 0; + + XtSetArg(args[n], XmNdialogTitle, data->fsb.labelNewFolder); n++; + XtSetArg (args[n], XmNselectionLabelString, data->fsb.labelDirectoryName); n++; + XtSetArg(args[n], XmNokLabelString, data->fsb.labelOk); n++; + XtSetArg(args[n], XmNcancelLabelString, data->fsb.labelCancel); n++; + Widget dialog = XmCreatePromptDialog (w, "NewFolderPrompt", args, n); + + Widget help = XmSelectionBoxGetChild(dialog, XmDIALOG_HELP_BUTTON); + XtUnmanageChild(help); + + XtAddCallback(dialog, XmNokCallback, (XtCallbackProc)create_folder, data); + XtAddCallback(dialog, XmNcancelCallback, (XtCallbackProc)new_folder_cancel, data); + + XtManageChild(dialog); + +} + +static void FSBHome(Widget w, XnFileSelectionBox data, XtPointer u) { + const char *homePath = data->fsb.homePath ? data->fsb.homePath : GetHomeDir(); + filedialog_update_dir(data, homePath); + PathBarSetPath(data->fsb.pathBar, homePath); +} + + +/* + * file_cmp_field + * 0: compare path + * 1: compare size + * 2: compare mtime + */ +static int file_cmp_field = 0; + +/* + * 1 or -1 + */ +static int file_cmp_order = 1; + +static int filecmp(const void *f1, const void *f2) +{ + const FileElm *file1 = f1; + const FileElm *file2 = f2; + if(file1->isDirectory != file2->isDirectory) { + return file1->isDirectory < file2->isDirectory; + } + + int cmp_field = file_cmp_field; + int cmp_order = file_cmp_order; + if(file1->isDirectory) { + cmp_field = 0; + cmp_order = 1; + } + + int ret = 0; + switch(cmp_field) { + case 0: { + ret = strcmp(FileName(file1->path), FileName(file2->path)); + break; + } + case 1: { + if(file1->size < file2->size) { + ret = -1; + } else if(file1->size == file2->size) { + ret = 0; + } else { + ret = 1; + } + break; + } + case 2: { + if(file1->lastModified < file2->lastModified) { + ret = -1; + } else if(file1->lastModified == file2->lastModified) { + ret = 0; + } else { + ret = 1; + } + break; + } + } + + return ret * cmp_order; +} + + +static void free_files(FileElm *ls, int count) +{ + for(int i=0;ifsb.dirs, data->fsb.dircount); + free_files(data->fsb.files, data->fsb.filecount); + data->fsb.dirs = NULL; + data->fsb.files = NULL; + data->fsb.dircount = 0; + data->fsb.filecount = 0; + data->fsb.maxnamelen = 0; +} + +#define FILE_ARRAY_SIZE 1024 + +static void file_array_add(FileElm **files, int *alloc, int *count, FileElm elm) { + int c = *count; + int a = *alloc; + if(c >= a) { + a *= 2; + FileElm *newarray = realloc(*files, sizeof(FileElm) * a); + + *files = newarray; + *alloc = a; + } + + (*files)[c] = elm; + c++; + *count = c; +} + +static int filedialog_update_dir(XnFileSelectionBox data, const char *path) +{ + DIR *dir = NULL; + if(path) { + // try to check first, if we can open the path + dir = opendir(path); + if(!dir) { + char errmsg[256]; + snprintf(errmsg, 256, data->fsb.errorOpenDir, strerror(errno)); + + ErrDialog(data, data->fsb.errorTitle, errmsg); + return 1; + } + } + + FSBView view = data->fsb.view[data->fsb.selectedview]; + view.cleanup((Widget)data, view.widget, view.userData); + + if(view.useDirList) { + XmListDeleteAllItems(data->fsb.dirlist); + } + + /* read dir and insert items */ + if(path) { + int dircount = 0; + int filecount = 0; + size_t maxNameLen = 0; + + FileElm *dirs = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + FileElm *files = calloc(sizeof(FileElm), FILE_ARRAY_SIZE); + int dirs_alloc = FILE_ARRAY_SIZE; + int files_alloc = FILE_ARRAY_SIZE; + + filedialog_cleanup_filedata(data); + + /* dir reading complete - set the path textfield */ + XmTextFieldSetString(data->fsb.path, (char*)path); + char *oldPath = data->fsb.currentPath; + data->fsb.currentPath = strdup(path); + if(oldPath) { + free(oldPath); + } + path = data->fsb.currentPath; + + struct dirent *ent; + while((ent = readdir(dir)) != NULL) { + if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + + char *entpath = ConcatPath(path, ent->d_name); + + struct stat s; + if(stat(entpath, &s)) { + free(entpath); + continue; + } + + FileElm new_entry; + new_entry.path = entpath; + new_entry.isDirectory = S_ISDIR(s.st_mode); + new_entry.size = (uint64_t)s.st_size; + new_entry.lastModified = s.st_mtime; + + size_t nameLen = strlen(ent->d_name); + if(nameLen > maxNameLen) { + maxNameLen = nameLen; + } + + if(new_entry.isDirectory) { + file_array_add(&dirs, &dirs_alloc, &dircount, new_entry); + } else { + file_array_add(&files, &files_alloc, &filecount, new_entry); + } + } + closedir(dir); + + data->fsb.dirs = dirs; + data->fsb.files = files; + data->fsb.dircount = dircount; + data->fsb.filecount = filecount; + data->fsb.maxnamelen = maxNameLen; + + // sort file arrays + qsort(dirs, dircount, sizeof(FileElm), filecmp); + qsort(files, filecount, sizeof(FileElm), filecmp); + } + + Widget filterTF = XmDropDownGetText(data->fsb.filter); + char *filter = XmTextFieldGetString(filterTF); + char *filterStr = filter; + if(!filter || strlen(filter) == 0) { + filterStr = "*"; + } + + if(view.useDirList) { + FileListWidgetAdd(data, data->fsb.dirlist, data->fsb.showHidden, NULL, data->fsb.dirs, data->fsb.dircount); + view.update( + (Widget)data, + view.widget, + NULL, + 0, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } else { + view.update( + (Widget)data, + view.widget, + data->fsb.dirs, + data->fsb.dircount, + data->fsb.files, + data->fsb.filecount, + filterStr, + data->fsb.maxnamelen, + view.userData); + } + + if(filter) { + XtFree(filter); + } + + return 0; +} + + +static void dirlist_activate(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + if(!filedialog_update_dir(data, path)) { + PathBarSetPath(data->fsb.pathBar, path); + data->fsb.selIsDir = TRUE; + } + } +} + +static void dirlist_select(Widget w, XnFileSelectionBox data, XmListCallbackStruct *cb) +{ + char *path = set_selected_path(data, cb->item); + if(path) { + data->fsb.selIsDir = TRUE; + } +} + +static void filedialog_enable_detailview(Widget w, XnFileSelectionBox data, XmToggleButtonCallbackStruct *tb) { + SelectView(data, tb->set); // 0: list, 1: detail +} + + +static void filedialog_setshowhidden( + Widget w, + XnFileSelectionBox data, + XmToggleButtonCallbackStruct *tb) +{ + data->fsb.showHidden = tb->set; + filedialog_update_dir(data, NULL); +} + +static void filedialog_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); +} + +static void filedialog_update_filter(Widget w, XnFileSelectionBox data, XtPointer c) +{ + filedialog_update_dir(data, NULL); + +} + +static void filedialog_goup(Widget w, XnFileSelectionBox data, XtPointer d) +{ + char *newPath = ParentPath(data->fsb.currentPath); + filedialog_update_dir(data, newPath); + PathBarSetPath(data->fsb.pathBar, newPath); + free(newPath); +} + +static void filedialog_ok(Widget w, XnFileSelectionBox data, XtPointer d) +{ + if(data->fsb.type == FILEDIALOG_SAVE) { + char *newName = XmTextFieldGetString(data->fsb.name); + if(newName) { + if(strchr(newName, '/')) { + ErrDialog(data, data->fsb.errorTitle, data->fsb.errorIllegalChar); + XtFree(newName); + return; + } + + if(strlen(newName) > 0) { + char *selPath = ConcatPath(data->fsb.currentPath, newName); + if(data->fsb.selectedPath) free(data->fsb.selectedPath); + data->fsb.selectedPath = selPath; + } + XtFree(newName); + + data->fsb.selIsDir = False; + } + } + + if(data->fsb.selectedPath) { + if(!data->fsb.selIsDir) { + data->fsb.status = FILEDIALOG_OK; + data->fsb.end = True; + FileSelectionCallback(data, data->fsb.okCallback, XmCR_OK, data->fsb.selectedPath); + } + } +} + +static void filedialog_cancel(Widget w, XnFileSelectionBox data, XtPointer d) +{ + data->fsb.end = 1; + data->fsb.status = FILEDIALOG_CANCEL; + FileSelectionCallback(data, data->fsb.cancelCallback, XmCR_CANCEL, data->fsb.currentPath); +} + +static void filedialog_help(Widget w, XnFileSelectionBox data, XtPointer d) +{ + FileSelectionCallback(data, data->manager.help_callback, XmCR_HELP, data->fsb.currentPath); +} + +static void FileSelectionCallback(XnFileSelectionBox fsb, XtCallbackList cb, int reason, const char *value) { + XmFileSelectionBoxCallbackStruct cbs; + memset(&cbs, 0, sizeof(XmFileSelectionBoxCallbackStruct)); + + char *dir = fsb->fsb.currentPath; + size_t dirlen = dir ? strlen(dir) : 0; + if(dir && dirlen > 0) { + char *dir2 = NULL; + if(dir[dirlen-1] != '/') { + // add a trailing / to the dir string + dir2 = malloc(dirlen+2); + memcpy(dir2, dir, dirlen); + dir2[dirlen] = '/'; + dir2[dirlen+1] = '\0'; + dirlen++; + dir = dir2; + } + cbs.dir = XmStringCreateLocalized(dir); + cbs.dir_length = dirlen; + if(dir2) { + free(dir2); + } + } else { + cbs.dir = XmStringCreateLocalized(""); + cbs.dir_length = 0; + } + cbs.reason = reason; + + cbs.value = XmStringCreateLocalized((char*)value); + cbs.length = strlen(value); + + XtCallCallbackList((Widget)fsb, cb, (XtPointer)&cbs); + + XmStringFree(cbs.dir); + XmStringFree(cbs.value); +} + +static void CreateUI(XnFileSelectionBox w) { + Arg args[32]; + int n = 0; + XmString str; + + int widget_spacing = w->fsb.widgetSpacing; + int window_spacing = w->fsb.windowSpacing; + + Widget form = (Widget)w; + int type = w->fsb.type; + + XtVaSetValues((Widget)w, XmNautoUnmanage, False, NULL); + + /* upper part of the gui */ + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelDirUp); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNleftOffset, window_spacing); n++; + XtSetArg(args[n], XmNresizable, True); n++; + XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++; + w->fsb.dirUp = XmCreatePushButton(form, "DirUp", args, n); + XtManageChild(w->fsb.dirUp); + XtAddCallback(w->fsb.dirUp, XmNactivateCallback, + (XtCallbackProc)filedialog_goup, w); + + // View Option Menu + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightOffset, window_spacing); n++; + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget viewframe = XmCreateForm(form, "vframe", args, n); + XtManageChild(viewframe); + + w->fsb.viewMenu = XmCreatePulldownMenu(viewframe, "menu", NULL, 0); + + Widget view; + if(w->fsb.showViewMenu) { + n = 0; + XtSetArg(args[n], XmNsubMenuId, w->fsb.viewMenu); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNmarginWidth, 0); n++; + view = XmCreateOptionMenu(viewframe, "option_menu", args, n); + XtManageChild(view); + w->fsb.viewOption = view; + w->fsb.detailToggleButton = NULL; + } else { + n = 0; + str = XmStringCreateLocalized(w->fsb.labelDetailView); + XtSetArg(args[n], XmNlabelString, str); n++; + XtSetArg(args[n], XmNfillOnSelect, True); n++; + XtSetArg(args[n], XmNindicatorOn, False); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + if(w->fsb.selectedview == 1) { + XtSetArg(args[n], XmNset, 1); n++; + } + w->fsb.detailToggleButton = XmCreateToggleButton(viewframe, "ToggleDetailView", args, n); + XtManageChild(w->fsb.detailToggleButton); + view = w->fsb.detailToggleButton; + XmStringFree(str); + + XtAddCallback( + w->fsb.detailToggleButton, + XmNvalueChangedCallback, + (XtCallbackProc)filedialog_enable_detailview, + w); + + w->fsb.viewOption = NULL; + } + + n = 0; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightWidget, view); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++; + XtSetArg(args[n], XmNlabelString, w->fsb.labelNewFolder); n++; + w->fsb.newFolder = XmCreatePushButton(viewframe, "NewFolder", args, n); + XtManageChild(w->fsb.newFolder); + XtAddCallback( + w->fsb.newFolder, + XmNactivateCallback, + (XtCallbackProc)FSBNewFolder, + w); + + + n = 0; + XtSetArg(args[n], XmNlabelString, w->fsb.labelHome); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, w->fsb.newFolder); n++; + XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + w->fsb.home = XmCreatePushButton(viewframe, "Home", args, n); + XtManageChild(w->fsb.home); + XtAddCallback( + w->fsb.home, + XmNactivateCallback, + (XtCallbackProc)FSBHome, + w); + + // match visual appearance of detailToggleButton with the other buttons + if(w->fsb.detailToggleButton) { + Dimension highlight, shadow; + XtVaGetValues(w->fsb.newFolder, XmNshadowThickness, &shadow, XmNhighlightThickness, &highlight, NULL); + XtVaSetValues(w->fsb.detailToggleButton, XmNshadowThickness, shadow, XmNhighlightThickness, highlight, NULL); + } + + // pathbar + n = 0; + XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++; + XtSetArg(args[n], XmNtopOffset, window_spacing); n++; + XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNleftWidget, w->fsb.dirUp); n++; + XtSetArg(args[n], XmNleftOffset, widget_spacing); n++; + XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++; + XtSetArg(args[n], XmNrightWidget, viewframe); n++; + XtSetArg(args[n], XmNrightOffset, widget_spacing); n++; + XtSetArg(args[n], XmNshadowType, XmSHADOW_IN); n++; + Widget pathBarFrame = XmCreateFrame(form, "pathbar_frame", args, n); + XtManageChild(pathBarFrame); + w->fsb.pathBar = CreatePathBar(pathBarFrame, args, 0); + w->fsb.pathBar->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;ifsb.filecount;i++) { + const char *name = FileName(data->fsb.files[i].path); + if(!data->fsb.showHidden && name[0] == '.') continue; + + size_t namelen = strlen(name); + + size_t cmplen = namelen < nchars ? namelen : nchars; + if(!memcmp(name, chars, cmplen)) { + if(row <= selectedRow) { + if(match == -1) { + match = row; + } + } else { + match = row; + break; + } + } + + row++; + } + + if(match > -1) { + XmLGridSelectRow(w, match, True); + XmLGridFocusAndShowRow(w, match+1); + } else { + XBell(XtDisplay(w), 0); + } +} + +static void grid_header_clicked(Widget w, XnFileSelectionBox data, XmLGridCallbackStruct *cb) { + int new_cmp_field = 0; + switch(cb->column) { + case 0: { + new_cmp_field = 0; + break; + } + case 1: { + new_cmp_field = 1; + break; + } + case 2: { + new_cmp_field = 2; + break; + } + } + + if(new_cmp_field == file_cmp_field) { + file_cmp_order = -file_cmp_order; // revert sort order + } else { + file_cmp_field = new_cmp_field; // change file cmp order to new field + file_cmp_order = 1; + } + + int sort_type = file_cmp_order == 1 ? XmSORT_ASCENDING : XmSORT_DESCENDING; + XmLGridSetSort(data->fsb.grid, file_cmp_field, sort_type); + + qsort(data->fsb.files, data->fsb.filecount, sizeof(FileElm), filecmp); + + // refresh widget + filedialog_update_dir(data, NULL); +} + +static FSBViewWidgets CreateDetailView(Widget parent, ArgList args, int n, void *userData) { + XnFileSelectionBox w = userData; + + XtSetArg(args[n], XmNshadowThickness, 0); n++; + Widget gridcontainer = XmCreateFrame(parent, "gridcontainer", args, n); + XtManageChild(gridcontainer); + + n = 0; + XtSetArg(args[n], XmNcolumns, 3); n++; + XtSetArg(args[n], XmNheadingColumns, 0); n++; + XtSetArg(args[n], XmNheadingRows, 1); n++; + XtSetArg(args[n], XmNallowColumnResize, 1); n++; + XtSetArg(args[n], XmNsimpleHeadings, w->fsb.detailHeadings); n++; + XtSetArg(args[n], XmNhorizontalSizePolicy, XmCONSTANT); n++; + + w->fsb.grid = XmLCreateGrid(gridcontainer, "grid", args, n); + XmLGridSetIgnoreModifyVerify(w->fsb.grid, True); + XtManageChild(w->fsb.grid); + + XtVaSetValues( + w->fsb.grid, + XmNcellDefaults, True, + XtVaTypedArg, XmNblankBackground, XmRString, "white", 6, + XtVaTypedArg, XmNcellBackground, XmRString, "white", 6, + NULL); + + XtAddCallback(w->fsb.grid, XmNselectCallback, (XtCallbackProc)grid_select, w); + XtAddCallback(w->fsb.grid, XmNactivateCallback, (XtCallbackProc)grid_activate, w); + XtAddCallback(w->fsb.grid, XmNheaderClickCallback, (XtCallbackProc)grid_header_clicked, w); + XtAddCallback(w->fsb.grid, XmNgridKeyPressedCallback, (XtCallbackProc)grid_key_pressed, w); + + // context menu + w->fsb.gridContextMenu = CreateContextMenu(w, w->fsb.grid, FileContextMenuCB); + + FSBViewWidgets widgets; + widgets.view = gridcontainer; + widgets.focus = w->fsb.grid; + return widgets; +} +#endif + + +/* ------------------------------ Path Utils ------------------------------ */ + +const char* GetHomeDir(void) { + char *home = getenv("HOME"); + if(!home) { + home = getenv("USERPROFILE"); + if(!home) { + home = "/"; + } + } + return home; +} + +static char* ConcatPath(const char *parent, const char *name) +{ + size_t parentlen = strlen(parent); + size_t namelen = strlen(name); + + size_t pathlen = parentlen + namelen + 2; + char *path = malloc(pathlen); + + memcpy(path, parent, parentlen); + if(parentlen > 0 && parent[parentlen-1] != '/') { + path[parentlen] = '/'; + parentlen++; + } + if(name[0] == '/') { + name++; + namelen--; + } + memcpy(path+parentlen, name, namelen); + path[parentlen+namelen] = '\0'; + return path; +} + +static char* FileName(char *path) { + int si = 0; + int osi = 0; + int i = 0; + int p = 0; + char c; + while((c = path[i]) != 0) { + if(c == '/') { + osi = si; + si = i; + p = 1; + } + i++; + } + + char *name = path + si + p; + if(name[0] == 0) { + name = path + osi + p; + if(name[0] == 0) { + return path; + } + } + + return name; +} + +static char* ParentPath(const char *path) { + char *name = FileName((char*)path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + if(parentlen == 0) { + parentlen++; + } + char *parent = malloc(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + +// unused at the moment, maybe reactivate if more illegal characters +// are defined +/* +static int CheckFileName(const char *fileName) { + size_t len = strlen(fileName); + for(int i=0;ifsb.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); +} diff --git a/ui/motif/Fsb.h b/ui/motif/Fsb.h new file mode 100644 index 0000000..4257137 --- /dev/null +++ b/ui/motif/Fsb.h @@ -0,0 +1,233 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSB_H +#define FSB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern WidgetClass xnFsbWidgetClass; + +#define FILEDIALOG_OPEN 1 +#define FILEDIALOG_SAVE 2 + +#define FILEDIALOG_OK 1 +#define FILEDIALOG_CANCEL 2 + +#define XnNwidgetSpacing "fsbWidgetSpacing" +#define XnNwindowSpacing "fsbWindowSpacing" + +#define XnNfsbType "fsbType" +#define XnCfsbType "fsbType" + +#define XnNshowHidden "showHidden" +#define XnCshowHidden "showHidden" +#define XnNshowHiddenButton "showHiddenButton" +#define XnCshowHiddenButton "showHiddenButton" + +#define XnNshowViewMenu "showViewMenu" +#define XnCshowViewMenu "showViewMenu" + +#define XnNselectedView "fsbSelectedView" +#define XnCselectedView "fsbSelectedView" + +#define XnNdirectory "directory" +#define XnCdirectory "directory" +#define XnNselectedPath "selectedPath" +#define XnCselectedPath "selectedPath" +#define XnNhomePath "homePath" +#define XnChomePath "homePath" + +#define XnNfilter "filter" +#define XnCfilter "filter" + +#define XnNfilterFunc "filterFunc" +#define XnCfilterFunc "filterFunc" + +#define XnNlabelListView "labelListView" +#define XnClabelListView "labelListView" +#define XnNlabelDetailView "labelDetailView" +#define XnClabelDetailView "labelDetailView" +#define XnNlabelOpenFileTitle "labelOpenFileTitle" +#define XnClabelOpenFileTitle "labelOpenFileTitle" +#define XnNlabelSaveFileTitle "labelSaveFileTitle" +#define XnClabelSaveFileTitle "labelSaveFileTitel" +#define XnNlabelDirUp "labelDirUp" +#define XnClabelDirUp "labelDirUp" +#define XnNlabelHome "labelHome" +#define XnClabelHome "labelHome" +#define XnNlabelNewFolder "labelNewFolder" +#define XnClabelNewFolder "labelNewFolder" +#define XnNlabelFilter "labelFilter" +#define XnClabelFilter "labelFilter" +#define XnNlabelFilterButton "labelFilterButton" +#define XnClabelFilterButton "labelFilterButton" +#define XnNlabelShowHiddenFiles "labelShowHiddenFiles" +#define XnClabelShowHiddenFiles "labelShowHiddenFiles" +#define XnNlabelDirectories "labelDirectories" +#define XnClabelDirectories "labelDirectories" +#define XnNlabelFiles "labelFiles" +#define XnClabelFiles "labelFiles" +#define XnNlabelRename "labelRename" +#define XnClabelRename "labelRename" +#define XnNlabelDelete "labelDelete" +#define XnClabelDelete "labelDelete" +#define XnNlabelOpen "labelOpen" +#define XnClabelOpen "labelOpen" +#define XnNlabelSave "labelSave" +#define XnClabelSave "labelSave" +#define XnNlabelOk "labelOk" +#define XnClabelOk "labelOk" +#define XnNlabelCancel "labelCancel" +#define XnClabelCancel "labelCancel" +#define XnNlabelHelp "labelHelp" +#define XnClabelHelp "labelHelp" +#define XnNlabelFileName "labelFileName" +#define XnClabelFileName "labelFileName" +#define XnNlabelDirectoryName "labelDirectoryName" +#define XnClabelDirectoryName "labelDirectoryName" +#define XnNlabelNewFileName "labelNewFileName" +#define XnClabelNewFileName "labelNewFileName" +#define XnNlabelDeleteFile "labelDeleteFile" +#define XnClabelDeleteFile "labelDeleteFile" +#define XnNdetailHeadings "detailHeadings" +#define XnCdetailHeadings "detailHeadings" +#define XnNdateFormatSameYear "dateFormatSameYear" +#define XnCdateFormatSameYear "dateFormatSameYear" +#define XnNdateFormatOtherYear "dateFormatOtherYear" +#define XnCdateFormatOtherYear "dateFormatOtherYear" +#define XnNsuffixBytes "suffixBytes" +#define XnCsuffixBytes "suffixBytes" +#define XnNsuffixKB "suffixKB" +#define XnCsuffixKB "suffixKB" +#define XnNsuffixMB "suffixMB" +#define XnCsuffixMB "suffixMB" +#define XnNsuffixGB "suffixGB" +#define XnCsuffixGB "suffixGB" +#define XnNsuffixTB "suffixTB" +#define XnCsuffixTB "suffixTB" +#define XnNerrorTitle "errorTitle" +#define XnCerrorTitle "errorTitle" +#define XnNerrorIllegalChar "errorIllegalChar" +#define XnCerrorIllegalChar "errorIllegalChar" +#define XnNerrorRename "errorRename" +#define XnCerrorRename "errorRename" +#define XnNerrorCreateFolder "errorCreateFolder" +#define XnCerrorCreateFolder "errorCreateFolder" +#define XnNerrorDelete "errorDelete" +#define XnCerrorDelete "errorDelete" +#define XnNerrorOpenDir "errorOpenDir" +#define XnCerrorOpenDir "errorOpenDir" + +/* + * int FSBFilterFunc(const char *pattern, const char *string) + * + * Checks whether the string matches the pattern + * + * Return + * zero if the string matches the pattern + * non-zero if there is no match + */ +typedef int(*FSBFilterFunc)(const char*, const char*); + + +typedef struct FileElm FileElm; +struct FileElm { + char *path; + int isDirectory; + unsigned long long size; + time_t lastModified; +}; + +typedef struct { + Widget view; + Widget focus; +} FSBViewWidgets; + +enum XnFSBChild { + XnFSB_DIR_UP_BUTTON = 0, + XnFSB_HOME_BUTTON, + XnFSB_NEW_FOLDER_BUTTON, + XnFSB_DETAIL_TOGGLE_BUTTON, + XnFSB_VIEW_OPTION_BUTTON, + XnFSB_FILTER_DROPDOWN, + XnFSB_FILTER_BUTTON, + XnFSB_SHOW_HIDDEN_TOGGLE_BUTTON, + XnFSB_DIRECTORIES_LABEL, + XnFSB_FILES_LABEL, + XnFSB_DIRLIST, + XnFSB_FILELIST, + XnFSB_GRID, + XnFSB_OK_BUTTON, + XnFSB_CANCEL_BUTTON, + XnFSB_HELP_BUTTON +}; + +typedef FSBViewWidgets(*FSBViewCreateProc)(Widget parent, ArgList args, int n, void *userData); +typedef void(*FSBViewUpdateProc)(Widget fsb, Widget view, FileElm *dirs, int dircount, FileElm *files, int filecount, const char *filter, int maxnamelen, void *userData); +typedef void(*FSBViewSelectProc)(Widget fsb, Widget view, const char *item); +typedef void(*FSBViewCleanupProc)(Widget fsb, Widget view, void *userData); +typedef void(*FSBViewDestroyProc)(Widget fsb, Widget view, void *userData); + +Widget XnCreateFileSelectionDialog( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +Widget XnCreateFileSelectionBox( + Widget parent, + String name, + ArgList arglist, + Cardinal argcount); + +void XnFileSelectionBoxAddView( + Widget fsb, + const char *name, + FSBViewCreateProc create, + FSBViewUpdateProc update, + FSBViewSelectProc select, + FSBViewCleanupProc cleanup, + FSBViewDestroyProc destroy, + Boolean useDirList, + void *userData); + +//void XnFileSelectionBoxSetDirList(Widget fsb, const char **dirlist, size_t nelm); + +Widget XnFileSelectionBoxWorkArea(Widget fsb); + +Widget XnFileSelectionBoxGetChild(Widget fsb, enum XnFSBChild child); + +void XnFileSelectionBoxDeleteFilters(Widget fsb); + +void XnFileSelectionBoxAddFilter(Widget fsb, const char *filter); + +#ifdef __cplusplus +} +#endif + +#endif /* FSB_H */ diff --git a/ui/motif/FsbP.h b/ui/motif/FsbP.h new file mode 100644 index 0000000..6d520ca --- /dev/null +++ b/ui/motif/FsbP.h @@ -0,0 +1,212 @@ +/* + * Copyright 2021 Olaf Wintermann + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef FSBP_H +#define FSBP_H + +#include +#include +#include +#include +#include + +#include "Fsb.h" +#include "pathbar.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FSB_MAX_VIEWS 8 + + +typedef struct FSBView FSBView; +struct FSBView { + Widget widget; + Widget focus; + FSBViewUpdateProc update; + FSBViewSelectProc select; + FSBViewCleanupProc cleanup; + FSBViewDestroyProc destroy; + void *userData; + Boolean useDirList; +}; + + +typedef struct FSBClassPart { + int unused; +} FSBClassPart; + +typedef struct FSBClassRec { + CoreClassPart core_class; + CompositeClassPart composite_class; + ConstraintClassPart constraint_class; + XmManagerClassPart manager_class; + XmBulletinBoardClassPart bulletin_board_class; + XmFormClassPart form_class; + FSBClassPart fsb_class; +} FSBClassRec; + +typedef struct FSBPart { + XtCallbackList okCallback; + XtCallbackList cancelCallback; + + Dimension widgetSpacing; + Dimension windowSpacing; + + Boolean showHiddenButton; + + Widget path; + PathBar *pathBar; + Widget filter; + Widget filterButton; + Widget showHiddenButtonW; + + FSBFilterFunc filterFunc; + + char *filterStr; + + Widget dirUp; + Widget home; + Widget newFolder; + + Widget viewSelectorList; + Widget viewSelectorDetail; + + Widget viewMenu; + Widget viewOption; + Widget detailToggleButton; + + Widget filterForm; + Widget lsDirLabel; + Widget lsFileLabel; + + Widget listContextMenu; + Widget gridContextMenu; + + // icon view + + // dir/file list view + Widget listform; + Widget dirlist; + + FSBView view[FSB_MAX_VIEWS]; + int numviews; + int selectedview; + + Widget filelist; + Widget grid; + + Widget separator; + + Widget nameLabel; + Widget name; + + Widget bottom_widget; + + Widget workarea; + + Widget okBtn; + Widget cancelBtn; + Widget helpBtn; + + FileElm *dirs; + FileElm *files; + int dircount; + int filecount; + int maxnamelen; + + char *homePath; + + char *currentPath; + char *selectedPath; + int selIsDir; + Boolean showHidden; + Boolean showViewMenu; + + int type; + + int end; + int status; + + int disable_set_values; + int gui_created; + + char *labelListView; + char *labelDetailView; + char* labelOpenFileTitle; + char* labelSaveFileTitle; + XmString labelDirUp; + XmString labelHome; + XmString labelNewFolder; + XmString labelFilterButton; + XmString labelShowHiddenFiles; + XmString labelDirectories; + XmString labelFiles; + XmString labelRename; + XmString labelDelete; + XmString labelOpen; + XmString labelSave; + XmString labelOk; + XmString labelCancel; + XmString labelHelp; + XmString labelFileName; + XmString labelDirectoryName; + XmString labelNewFileName; + char *labelDeleteFile; + + char *detailHeadings; + + char *dateFormatSameYear; + char *dateFormatOtherYear; + char *suffixBytes; + char *suffixKB; + char *suffixMB; + char *suffixGB; + char *suffixTB; + + char *errorTitle; + char *errorIllegalChar; + char *errorRename; + char *errorFolder; + char *errorDelete; + char *errorOpenDir; +} FSBPart; + +typedef struct FSBRec { + CorePart core; + CompositePart composite; + ConstraintPart constraint; + XmManagerPart manager; + XmBulletinBoardPart bulletin_board; + XmFormPart form; + FSBPart fsb; +} FSBRec; + +typedef struct FSBRec *XnFileSelectionBox; + +#ifdef __cplusplus +} +#endif + +#endif /* FSBP_H */ + diff --git a/ui/motif/entry.c b/ui/motif/entry.c new file mode 100644 index 0000000..d21a400 --- /dev/null +++ b/ui/motif/entry.c @@ -0,0 +1,281 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "entry.h" + + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + Arg xargs[16]; + int n = 0; + + double min = args->min; + double max = args->max != 0 ? args->max : 1000; + + UiVar *var = NULL; + UiVarType vartype = 0; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(!var) { + if(args->intvalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; + } else if(args->rangevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(vartype == UI_VAR_RANGE) { + UiRange *r = var->value; + min = r->min; + max = r->max; + } + if(args->step == 0) { + args->step = 1; + } + + UiContainerPrivate *ctn = ui_obj_container(obj); + UiLayout layout = UI_ARGS2LAYOUT(args); + + + XtSetArg(xargs[n], XmNminimumValue, 0); n++; + XtSetArg(xargs[n], XmNmaximumValue, 100); n++; + XtSetArg(xargs[n], XmNincrementValue, 1); n++; + XtSetArg(xargs[n], XmNspinBoxChildType, XmNUMERIC); n++; + + Widget parent = ui_container_prepare(ctn, &layout, xargs, &n); + + char *name = args->name ? (char*)args->name : "button"; + Widget spinbox = XmCreateSimpleSpinBox(parent, name, xargs, n); + XtManageChild(spinbox); + ui_container_add(ctn, spinbox); + + ui_set_widget_groups(obj->ctx, spinbox, args->groups); + + WidgetList children; + Cardinal num_children; + unsigned char type; + + Widget textfield = NULL; + XtVaGetValues( + spinbox, + XmNchildren, &children, + XmNnumChildren, &num_children, + NULL); + + for(int i = 0;iobj = obj; + data->textfield = textfield; + data->var = var; + data->vartype = vartype; + data->obs = NULL; + data->onchange = args->onchange; + data->onchangedata = args->onchangedata; + data->value = 0; + data->min = min; + data->max = max; + data->increment = args->step; + data->digits = args->digits; + + UiObserver **obs = NULL; + if(var) { + double value = 0; + switch(vartype) { + default: break; + case UI_VAR_INTEGER: { + UiInteger *i = var->value; + i->get = ui_spinbutton_getint; + i->set = ui_spinbutton_setint; + i->obj = data; + value = (double)i->value; + obs = &i->observers; + break; + } + case UI_VAR_DOUBLE: { + UiDouble *d = var->value; + d->get = ui_spinbutton_getdouble; + d->set = ui_spinbutton_setdouble; + d->obj = data; + value = d->value; + obs = &d->observers; + break; + } + case UI_VAR_RANGE: { + UiRange *r = var->value; + r->get = ui_spinbutton_getrangeval; + r->set = ui_spinbutton_setrangeval; + r->setrange = ui_spinbutton_setrange; + r->setextent = ui_spinbutton_setextent; + r->obj = data; + value = r->value; + obs = &r->observers; + break; + } + } + ui_spinbox_set_value(data, value); + } + data->obs = obs; + + XtAddCallback( + spinbox, + XmNvalueChangedCallback, + (XtCallbackProc)ui_spinbox_value_changed, + data); + + XtAddCallback( + spinbox, + XmNdestroyCallback, + (XtCallbackProc)ui_destroy_data, + data); + + XmTextFieldSetString(textfield, "0"); + + + return spinbox; +} + +void ui_spinbox_set_value(UiSpinBox *spinbox, double value) { + if(value < spinbox->min) { + value = spinbox->min; + } + if(value > spinbox->max) { + value = spinbox->max; + } + + char buf[32]; + snprintf(buf, 32, "%.*f", spinbox->digits, spinbox->value); + XmTextFieldSetString(spinbox->textfield, buf); + spinbox->value = value; +} + +void ui_spinbox_value_changed(Widget widget, UiSpinBox *spinbox, XmSpinBoxCallbackStruct *cb) { + Boolean update_value = TRUE; + double value = spinbox->value; + switch(cb->reason) { + case XmCR_OK: { + update_value = FALSE; + break; + } + case XmCR_SPIN_NEXT: { + value += spinbox->increment; + break; + } + case XmCR_SPIN_PRIOR: { + value -= spinbox->increment; + break; + } + } + + if(update_value) { + ui_spinbox_set_value(spinbox, value); + + UiEvent event; + event.obj = spinbox->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = (int64_t)value; + event.set = ui_get_setop(); + + if(spinbox->onchange) { + spinbox->onchange(&event, spinbox->onchangedata); + } + + UiObserver *obs = *spinbox->obs; + ui_notify_evt(*spinbox->obs, &event); + } +} + +int64_t ui_spinbutton_getint(UiInteger *i) { + UiSpinBox *spinbox = i->obj; + i->value = (int64_t)spinbox->value; + return i->value; +} + +void ui_spinbutton_setint(UiInteger *i, int64_t val) { + UiSpinBox *spinbox = i->obj; + ui_spinbox_set_value(spinbox, (double)val); + i->value = spinbox->value; +} + +double ui_spinbutton_getdouble(UiDouble *d) { + UiSpinBox *spinbox = d->obj; + d->value = spinbox->value; + return d->value; +} + +void ui_spinbutton_setdouble(UiDouble *d, double val) { + UiSpinBox *spinbox = d->obj; + ui_spinbox_set_value(spinbox, val); + d->value = spinbox->value; +} + +double ui_spinbutton_getrangeval(UiRange *r) { + UiSpinBox *spinbox = r->obj; + r->value = spinbox->value; + return r->value; +} + +void ui_spinbutton_setrangeval(UiRange *r, double val) { + UiSpinBox *spinbox = r->obj; + ui_spinbox_set_value(spinbox, val); + r->value = spinbox->value; +} +void ui_spinbutton_setrange(UiRange *r, double min, double max) { + UiSpinBox *spinbox = r->obj; + spinbox->min = min; + spinbox->max = max; + r->min = min; + r->max = max; +} + +void ui_spinbutton_setextent(UiRange *r, double extent) { + UiSpinBox *spinbox = r->obj; + spinbox->increment = extent; + r->extent = extent; +} diff --git a/ui/motif/entry.h b/ui/motif/entry.h new file mode 100644 index 0000000..ba47f4e --- /dev/null +++ b/ui/motif/entry.h @@ -0,0 +1,77 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ENTRY_H +#define ENTRY_H + +#include "../ui/entry.h" +#include "container.h" +#include "toolkit.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiSpinBox { + UiObject *obj; + Widget textfield; + UiVar *var; + UiVarType vartype; + UiObserver **obs; + ui_callback onchange; + void* onchangedata; + double value; + double min; + double max; + double increment; + int digits; +} UiSpinBox; + +void ui_spinbox_set_value(UiSpinBox *spinbox, double value); + +void ui_spinbox_value_changed(Widget widget, UiSpinBox *spinbox, XmSpinBoxCallbackStruct *cb); + +int64_t ui_spinbutton_getint(UiInteger *i); +void ui_spinbutton_setint(UiInteger *i, int64_t val); + +double ui_spinbutton_getdouble(UiDouble *d); +void ui_spinbutton_setdouble(UiDouble *d, double val); + +double ui_spinbutton_getrangeval(UiRange *r); +void ui_spinbutton_setrangeval(UiRange *r, double val); +void ui_spinbutton_setrange(UiRange *r, double min, double max); +void ui_spinbutton_setextent(UiRange *r, double extent); + +#ifdef __cplusplus +} +#endif + +#endif /* ENTRY_H */ + diff --git a/ui/motif/pathbar.c b/ui/motif/pathbar.c new file mode 100644 index 0000000..4e0cde6 --- /dev/null +++ b/ui/motif/pathbar.c @@ -0,0 +1,439 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pathbar.h" + +#include +#include + + + +void pathbar_resize(Widget w, PathBar *p, XtPointer d) +{ + Dimension width, height; + XtVaGetValues(w, XmNwidth, &width, XmNheight, &height, NULL); + + Dimension *segW = (void*)XtCalloc(p->numSegments, sizeof(Dimension)); + + Dimension maxHeight = 0; + + /* get width/height from all widgets */ + Dimension pathWidth = 0; + for(int i=0;inumSegments;i++) { + Dimension segWidth; + Dimension segHeight; + XtVaGetValues(p->pathSegments[i], XmNwidth, &segWidth, XmNheight, &segHeight, NULL); + segW[i] = segWidth; + pathWidth += segWidth; + if(segHeight > maxHeight) { + maxHeight = segHeight; + } + } + Dimension tfHeight; + XtVaGetValues(p->textfield, XmNheight, &tfHeight, NULL); + if(tfHeight > maxHeight) { + maxHeight = tfHeight; + } + + Boolean arrows = False; + if(pathWidth + 10 > width) { + arrows = True; + pathWidth += p->lw + p->rw; + } + + /* calc max visible widgets */ + int start = 0; + if(arrows) { + Dimension vis = p->lw+p->rw; + for(int i=p->numSegments;i>0;i--) { + Dimension segWidth = segW[i-1]; + if(vis + segWidth + 10 > width) { + start = i; + arrows = True; + break; + } + vis += segWidth; + } + } else { + p->shift = 0; + } + + int leftShift = 0; + if(p->shift < 0) { + if(start + p->shift < 0) { + leftShift = start; + start = 0; + p->shift = -leftShift; + } else { + leftShift = -p->shift; /* negative shift */ + start += p->shift; + } + } + + int x = 0; + if(arrows) { + XtManageChild(p->left); + XtManageChild(p->right); + x = p->lw; + } else { + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + } + + for(int i=0;inumSegments;i++) { + if(i >= start && i < p->numSegments - leftShift && !p->input) { + XtVaSetValues(p->pathSegments[i], XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + x += segW[i]; + XtManageChild(p->pathSegments[i]); + } else { + XtUnmanageChild(p->pathSegments[i]); + } + } + + if(arrows) { + XtVaSetValues(p->left, XmNx, 0, XmNy, 0, XmNheight, maxHeight, NULL); + XtVaSetValues(p->right, XmNx, x, XmNy, 0, XmNheight, maxHeight, NULL); + } + + free(segW); + + Dimension rw, rh; + XtMakeResizeRequest(w, width, maxHeight, &rw, &rh); + + XtVaSetValues(p->textfield, XmNwidth, rw, XmNheight, rh, NULL); +} + +static void pathbarActivateTF(PathBar *p) +{ + XtUnmanageChild(p->left); + XtUnmanageChild(p->right); + XNETextSetSelection(p->textfield, 0, XNETextGetLastPosition(p->textfield), 0); + XtManageChild(p->textfield); + p->input = 1; + + XmProcessTraversal(p->textfield, XmTRAVERSE_CURRENT); + + pathbar_resize(p->widget, p, NULL); +} + +void PathBarActivateTextfield(PathBar *p) +{ + p->focus = 1; + pathbarActivateTF(p); +} + +void pathbar_input(Widget w, PathBar *p, XtPointer c) +{ + XmDrawingAreaCallbackStruct *cbs = (XmDrawingAreaCallbackStruct*)c; + XEvent *xevent = cbs->event; + + if (cbs->reason == XmCR_INPUT) { + if (xevent->xany.type == ButtonPress) { + p->focus = 0; + pathbarActivateTF(p); + } + } +} + +void pathbar_losingfocus(Widget w, PathBar *p, XtPointer c) +{ + if(--p->focus < 0) { + p->input = False; + XtUnmanageChild(p->textfield); + } +} + +static cxmutstr concat_path_s(cxstring base, cxstring path) { + if(!path.ptr) { + path = CX_STR(""); + } + + int add_separator = 0; + if(base.length != 0 && base.ptr[base.length-1] == '/') { + if(path.ptr[0] == '/') { + base.length--; + } + } else { + if(path.length == 0 || path.ptr[0] != '/') { + add_separator = 1; + } + } + + cxmutstr url; + if(add_separator) { + url = cx_strcat(3, base, CX_STR("/"), path); + } else { + url = cx_strcat(2, base, path); + } + + return url; +} + +char* pathbar_concat_path(const char *path1, const char *path2) { + return concat_path_s(cx_str(path1), cx_str(path2)).ptr; +} + +void pathbar_pathinput(Widget w, PathBar *p, XtPointer d) +{ + char *newpath = XNETextGetString(p->textfield); + if(newpath) { + if(newpath[0] == '~') { + char *p = newpath+1; + char *home = getenv("HOME"); + char *cp = pathbar_concat_path(home, p); + XtFree(newpath); + newpath = cp; + } else if(newpath[0] != '/') { + char curdir[2048]; + curdir[0] = 0; + getcwd(curdir, 2048); + char *cp = pathbar_concat_path(curdir, newpath); + XtFree(newpath); + newpath = cp; + } + + /* update path */ + PathBarSetPath(p, newpath); + if(p->updateDir) { + p->updateDir(p->updateDirData, newpath, -1); + } + XtFree(newpath); + + /* hide textfield and show path as buttons */ + XtUnmanageChild(p->textfield); + pathbar_resize(p->widget, p, NULL); + + if(p->focus_widget) { + XmProcessTraversal(p->focus_widget, XmTRAVERSE_CURRENT); + } + } +} + +void pathbar_shift_left(Widget w, PathBar *p, XtPointer d) +{ + p->shift--; + pathbar_resize(p->widget, p, NULL); +} + +void pathbar_shift_right(Widget w, PathBar *p, XtPointer d) +{ + if(p->shift < 0) { + p->shift++; + } + pathbar_resize(p->widget, p, NULL); +} + +static void pathTextEH(Widget widget, XtPointer data, XEvent *event, Boolean *dispatch) { + PathBar *pb = data; + if(event->type == KeyReleaseMask) { + if(event->xkey.keycode == 9) { + XtUnmanageChild(pb->textfield); + pathbar_resize(pb->widget, pb, NULL); + *dispatch = False; + } else if(event->xkey.keycode == 36) { + pathbar_pathinput(pb->textfield, pb, NULL); + *dispatch = False; + } + } +} + +PathBar* CreatePathBar(Widget parent, ArgList args, int n) +{ + PathBar *bar = (PathBar*)XtMalloc(sizeof(PathBar)); + bar->path = NULL; + bar->updateDir = NULL; + bar->updateDirData = NULL; + + bar->focus_widget = NULL; + + bar->getpathelm = NULL; + bar->getpathelmdata = NULL; + bar->current_pathelms = NULL; + + bar->shift = 0; + + XtSetArg(args[n], XmNmarginWidth, 0); n++; + XtSetArg(args[n], XmNmarginHeight, 0); n++; + bar->widget = XmCreateDrawingArea(parent, "pathbar", args, n); + XtAddCallback( + bar->widget, + XmNresizeCallback, + (XtCallbackProc)pathbar_resize, + bar); + XtAddCallback( + bar->widget, + XmNinputCallback, + (XtCallbackProc)pathbar_input, + bar); + + Arg a[4]; + XtSetArg(a[0], XmNshadowThickness, 0); + XtSetArg(a[1], XmNx, 0); + XtSetArg(a[2], XmNy, 0); + bar->textfield = XNECreateText(bar->widget, "pbtext", a, 3); + bar->input = 0; + XtAddCallback( + bar->textfield, + XmNlosingFocusCallback, + (XtCallbackProc)pathbar_losingfocus, + bar); + XtAddCallback(bar->textfield, XmNactivateCallback, + (XtCallbackProc)pathbar_pathinput, bar); + XtAddEventHandler(bar->textfield, KeyPressMask | KeyReleaseMask, FALSE, pathTextEH, bar); + + XtSetArg(a[0], XmNarrowDirection, XmARROW_LEFT); + bar->left = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtSetArg(a[0], XmNarrowDirection, XmARROW_RIGHT); + bar->right = XmCreateArrowButton(bar->widget, "pbbutton", a, 1); + XtAddCallback( + bar->left, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_left, + bar); + XtAddCallback( + bar->right, + XmNactivateCallback, + (XtCallbackProc)pathbar_shift_right, + bar); + + Pixel bg; + XtVaGetValues(bar->textfield, XmNbackground, &bg, NULL); + XtVaSetValues(bar->widget, XmNbackground, bg, NULL); + + XtManageChild(bar->left); + XtManageChild(bar->right); + + XtVaGetValues(bar->left, XmNwidth, &bar->lw, NULL); + XtVaGetValues(bar->right, XmNwidth, &bar->rw, NULL); + + bar->segmentAlloc = 16; + bar->numSegments = 0; + bar->pathSegments = (Widget*)XtCalloc(16, sizeof(Widget)); + + bar->selection = 0; + + return bar; +} + +void PathBarChangeDir(Widget w, PathBar *bar, XtPointer c) +{ + XmToggleButtonSetState(bar->pathSegments[bar->selection], False, False); + + int i; + for(i=0;inumSegments;i++) { + if(bar->pathSegments[i] == w) { + bar->selection = i; + XmToggleButtonSetState(w, True, False); + break; + } + } + + UiPathElm elm = bar->current_pathelms[i]; + cxmutstr path = cx_strdup(cx_strn(elm.path, elm.path_len)); + if(bar->updateDir) { + XNETextSetString(bar->textfield, path.ptr); + bar->updateDir(bar->updateDirData, path.ptr, i); + } + free(path.ptr); +} + +static void ui_pathelm_destroy(UiPathElm *elms, size_t nelm) { + for(int i=0;ipath) { + free(bar->path); + } + bar->path = strdup(path); + + for(int i=0;inumSegments;i++) { + XtDestroyWidget(bar->pathSegments[i]); + } + XtUnmanageChild(bar->textfield); + XtManageChild(bar->left); + XtManageChild(bar->right); + bar->input = False; + + Arg args[4]; + XmString str; + + bar->numSegments = 0; + + ui_pathelm_destroy(bar->current_pathelms, bar->numSegments); + size_t nelm = 0; + UiPathElm* path_elm = bar->getpathelm(bar->path, strlen(bar->path), &nelm, bar->getpathelmdata); + if (!path_elm) { + return; + } + bar->current_pathelms = path_elm; + bar->numSegments = nelm; + bar->pathSegments = realloc(bar->pathSegments, nelm * sizeof(Widget*)); + + for(int i=0;iwidget, "pbbutton", args, 3); + XtAddCallback( + button, + XmNvalueChangedCallback, + (XtCallbackProc)PathBarChangeDir, + bar); + XmStringFree(str); + + bar->pathSegments[i] = button; + } + + bar->selection = bar->numSegments-1; + XmToggleButtonSetState(bar->pathSegments[bar->selection], True, False); + + XNETextSetString(bar->textfield, (char*)path); + XNETextSetInsertionPosition(bar->textfield, XNETextGetLastPosition(bar->textfield)); + + pathbar_resize(bar->widget, bar, NULL); +} + +void PathBarDestroy(PathBar *pathbar) { + if(pathbar->path) { + XtFree(pathbar->path); + } + XtFree((void*)pathbar->pathSegments); + XtFree((void*)pathbar); +} diff --git a/ui/motif/pathbar.h b/ui/motif/pathbar.h new file mode 100644 index 0000000..90ba73b --- /dev/null +++ b/ui/motif/pathbar.h @@ -0,0 +1,95 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PATHBAR_H +#define PATHBAR_H + +#include + +#include "../ui/text.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define XNECreateText(parent,name,args,count) XmCreateTextField(parent,name,args,count) +#define XNETextSetString(widget,value) XmTextFieldSetString(widget,value) +#define XNETextGetString(widget) XmTextFieldGetString(widget) +#define XNETextGetLastPosition(widget) XmTextFieldGetLastPosition(widget) +#define XNETextSetInsertionPosition(widget, i) XmTextFieldSetInsertionPosition(widget, i) +#define XNETextSetSelection(w, f, l, t) XmTextFieldSetSelection(w, f, l, t) + +typedef void(*updatedir_callback)(void*,char*,int); + +typedef struct PathBar { + Widget widget; + Widget textfield; + + Widget focus_widget; + + Widget left; + Widget right; + Dimension lw; + Dimension rw; + + int shift; + + UiPathElm *current_pathelms; + Widget *pathSegments; + size_t numSegments; + size_t segmentAlloc; + + char *path; + int selection; + Boolean input; + + int focus; + + Boolean disableResize; + + updatedir_callback updateDir; + void *updateDirData; + + ui_pathelm_func getpathelm; + void *getpathelmdata; +} PathBar; + +PathBar* CreatePathBar(Widget parent, ArgList args, int n); +void PathBarSetPath(PathBar *bar, const char *path); +void PathBarDestroy(PathBar *bar); + +void pathbar_resize(Widget w, PathBar *p, XtPointer d); + +char* pathbar_concat_path(const char *path1, const char *path2); + +#ifdef __cplusplus +} +#endif + +#endif /* PATHBAR_H */ + diff --git a/ui/win32/list.c b/ui/win32/list.c new file mode 100644 index 0000000..5626458 --- /dev/null +++ b/ui/win32/list.c @@ -0,0 +1,455 @@ +/* +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include + +#include "list.h" +#include "container.h" + + + +static W32WidgetClass listview_widget_class = { + .eventproc = ui_listview_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_listview_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +static void* strmodel_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return col == 0 ? elm : NULL; +} + +static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata; + return getvalue(elm, col); +} + +static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) { + return NULL; +} + +/* + * Creates an UiListView widget object and initializes it from the UiListArgs + */ +static UiListView* create_listview_widget(UiObject *obj, W32WidgetClass *widget_class, HWND hwnd, UiListArgs *args, UiBool table) { + UiListView *listview = w32_widget_create(widget_class, hwnd, sizeof(UiListView)); + listview->widget.hwnd = hwnd; + listview->obj = obj; + listview->preferred_width = args->width ? args->width : 300; // 300: default width/height + listview->preferred_height = args->height ? args->height : 300; + listview->onactivate = args->onactivate; + listview->onactivatedata = args->onactivatedata; + listview->onselection = args->onselection; + listview->onselectiondata = args->onselectiondata; + listview->ondragstart = args->ondragstart; + listview->ondragstartdata = args->ondragstartdata; + listview->ondragcomplete = args->ondragcomplete; + listview->ondragcompletedata = args->ondragcompletedata; + listview->ondrop = args->ondrop; + listview->ondropdata = args->ondropdata; + listview->istable = table; + + // convert ui_getvaluefunc into ui_getvaluefunc2 if necessary + ui_getvaluefunc2 getvalue = args->getvalue2; + void *getvaluedata = args->getvalue2data; + if(!getvalue) { + if(args->getvalue) { + getvalue = getvalue_wrapper; + getvaluedata = (void*)args->getvalue; + } else { + getvalue = table ? null_getvalue : strmodel_getvalue; + } + } + listview->getvalue = getvalue; + listview->getvaluedata = getvaluedata; + + listview->var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST); + + return listview; +} + +static UIWIDGET listview_create(UiObject *obj, UiListArgs *args, UiBool table) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_LISTVIEW, + "", + WS_CHILD | WS_VISIBLE | LVS_REPORT, + 0, 0, 100, 100, + parent, + (HMENU)1337, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + ListView_SetExtendedListViewStyle( + hwnd, + LVS_EX_FULLROWSELECT //| LVS_EX_GRIDLINES + ); + + UiListView *listview = create_listview_widget(obj, &listview_widget_class, hwnd, args, table); + ui_container_add(container, (W32Widget*)listview, &layout); + + // init list model + // always initialize listview->model + int numcolumns = 0; + if (table) { + if (args->model) { + listview->model = ui_model_copy(obj->ctx, args->model); + numcolumns = listview->model->columns; + } else { + listview->model = ui_model_new(obj->ctx); + } + } else { + UiModel *model = ui_model_new(obj->ctx); + ui_model_add_column(obj->ctx, model, UI_STRING, "Test", -1); + listview->model = model; + numcolumns = 1; + } + + // create columns + UiModel *model = listview->model; + for (int i=0;itypes[i]; + char *title = model->titles[i]; + size_t titlelen = title ? strlen(title) : 0; + int size = model->columnsize[i]; + switch (type) { + default: { + col.mask = LVCF_TEXT | LVCF_WIDTH; + col.pszText = title; + col.cx = size > 0 ? size : titlelen*10+5; + break; + } + case UI_ICON: { + break; // TODO + } + } + ListView_InsertColumn(hwnd, i, &col); + } + + // bind the listview to the provided UiList + if (listview->var) { + UiList *list = listview->var->value; + list->obj = listview; + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->setselection = ui_listview_setselection; + + ui_listview_update(list, -1); + } + + return (W32Widget*)listview; +} + +static UiListSelection listview_get_selection(UiListView *listview) { + UiListSelection sel = { 0, NULL }; + HWND hwnd = listview->widget.hwnd; + + CX_ARRAY_DECLARE(int, indices); + cx_array_initialize(indices, 8); + + int index = -1; + while ((index = ListView_GetNextItem(hwnd, index, LVNI_SELECTED)) != -1) { + cx_array_simple_add(indices, index); + } + + if (indices_size > 0) { + sel.rows = indices; + sel.count = indices_size; + } + + return sel; +} + +// listview class event proc +int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + UiListView *listview = (UiListView*)widget; + switch (uMsg) { + case WM_NOTIFY: { + LPNMHDR hdr = (LPNMHDR)lParam; + switch (hdr->code) { + case LVN_ITEMCHANGED: { + LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; + int row = lv->iItem; + if ((lv->uChanged & LVIF_STATE) && (lv->uNewState & LVIS_SELECTED) && listview->onselection) { + UiListSelection sel = listview_get_selection(listview); + + UiEvent event; + event.obj = listview->obj; + event.window = listview->obj->window; + event.document = listview->obj->ctx->document; + event.eventdata = &sel; + event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION; + event.intval = row; + event.set = ui_get_setop(); + listview->onselection(&event, listview->onselectiondata); + + ui_listselection_free(sel); + } + break; + } + case LVN_ITEMACTIVATE: { + LPNMLISTVIEW lv = (LPNMLISTVIEW)lParam; + int row = lv->iItem; + if (listview->onactivate) { + UiEvent event; + event.obj = listview->obj; + event.window = listview->obj->window; + event.document = listview->obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + event.intval = row; + event.set = ui_get_setop(); + + if (listview->var) { + UiList *list = listview->var->value; + event.eventdata = list->get(list, row); + event.eventdatatype = UI_EVENT_DATA_LIST_ELM; + } + + listview->onactivate(&event, listview->onactivatedata); + } + break; + } + } + break; + } + } + + return 0; +} + +W32Size ui_listview_get_preferred_size(W32Widget *widget) { + UiListView *listview = (UiListView*)widget; + W32Size size; + size.width = listview->preferred_width; + size.height = listview->preferred_height; + return size; +} + +/* + * Creates and inserts an LVITEM + * + * list: An UiList bound to an UiListView + * row: row index + * elm: list element (same as list->get(list, row)) + */ +static void insert_item(UiList *list, int row, void *elm) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + UiModel *model = listview->model; + + LVITEM item; + item.mask = LVIF_TEXT; + item.iItem = row; + item.iSubItem = 0; + int idx = -1; + for (int col=0;colcolumns;col++) { + UiBool freeResult = FALSE; + // convert the list element to a value, that can be displayed in the list view + // TODO: handle all model types + char *str = listview->getvalue(list, elm, row, col, listview->getvaluedata, &freeResult); + if (col == 0) { + item.pszText = str; + idx = ListView_InsertItem(hwnd, &item); + } else { + ListView_SetItemText(hwnd, idx, col, str); + } + + if (freeResult) { + free(str); + } + } +} + +/* + * UiList->update function + * + * Updates one or all rows + * row: list index or -1 for updating all rows + */ +void ui_listview_update(UiList *list, int row) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + UiModel *model = listview->model; + if (row < 0) { + ListView_DeleteAllItems(hwnd); + void *elm = list->first(list); + int row = 0; + while (elm) { + insert_item(list, row, elm); + elm = list->next(list); + row++; + } + } else { + ListView_DeleteItem(hwnd, row); + void *elm = list->get(list, row); + insert_item(list, row, elm); + } + + // re-adjust all columns + for (int i=0;icolumns;i++) { + ListView_SetColumnWidth(hwnd, i, LVSCW_AUTOSIZE); + } +} + +UiListSelection ui_listview_getselection(UiList *list) { + UiListView *listview = (UiListView*)list->obj; + return listview_get_selection(listview); +} + +void ui_listview_setselection(UiList *list, UiListSelection selection) { + +} + +// public API +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) { + return listview_create(obj, args, FALSE); +} + +// public API +UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) { + return listview_create(obj, args, TRUE); +} + + +/* ------------------------------------ DropDown ------------------------------------*/ + +static W32WidgetClass dropdown_widget_class = { + .eventproc = ui_dropdown_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_dropdown_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + HWND hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + WC_COMBOBOX, + "", + WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, + 0, 0, 100, 100, + parent, + (HMENU)1337, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + UiListView *dropdown = create_listview_widget(obj, &dropdown_widget_class, hwnd, args, FALSE); + ui_container_add(container, (W32Widget*)dropdown, &layout); + + // bind the dropdown to the provided UiList + if (dropdown->var) { + UiList *list = dropdown->var->value; + list->obj = dropdown; + list->update = ui_dropdown_update; + list->getselection = ui_dropdown_getselection; + list->setselection = ui_dropdown_setselection; + + ui_dropdown_update(list, -1); + } + + + return (W32Widget*)dropdown; +} + +int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return 0; +} + +W32Size ui_dropdown_get_preferred_size(W32Widget *widget) { + W32Size size; + size.width = 200; + size.height = 30; + return size; +} + +static void dropdown_insert_item(UiList *list, int row, void *elm) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + + UiBool freeResult = FALSE; + char *str = listview->getvalue(list, elm, row, 0, listview->getvaluedata, &freeResult); + SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)str); + + if (freeResult) { + free(str); + } +} + +void ui_dropdown_update(UiList *list, int row) { + UiListView *listview = (UiListView*)list->obj; + HWND hwnd = listview->widget.hwnd; + if (row < 0) { + SendMessage(hwnd, CB_RESETCONTENT, 0, 0); + + void *elm = list->first(list); + int row = 0; + while (elm) { + dropdown_insert_item(list, row, elm); + elm = list->next(list); + row++; + } + } else { + SendMessage(hwnd, CB_DELETESTRING, row, 0); + void *elm = list->get(list, row); + dropdown_insert_item(list, row, elm); + } +} + +UiListSelection ui_dropdown_getselection(UiList *list) { + UiListSelection sel = { 0, NULL }; + UiListView *listview = (UiListView*)list->obj; + int index = (int)SendMessage(listview->widget.hwnd, CB_GETCURSEL, 0, 0); + if (index >= 0) { + sel.rows = malloc(sizeof(int)); + sel.rows[0] = index; + sel.count = 1; + } + return sel; +} + +void ui_dropdown_setselection(UiList *list, UiListSelection selection) { + UiListView *listview = (UiListView*)list->obj; + SendMessage(listview->widget.hwnd, CB_SETCURSEL, 0, 0); +} diff --git a/ui/win32/list.h b/ui/win32/list.h new file mode 100644 index 0000000..7b1d36b --- /dev/null +++ b/ui/win32/list.h @@ -0,0 +1,83 @@ +/* +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIST_H +#define LIST_H + +#include "toolkit.h" +#include "../ui/tree.h" +#include "win32.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListView { + W32Widget widget; + UiObject *obj; + UiVar *var; + ui_getvaluefunc2 getvalue; + void *getvaluedata; + ui_getstylefunc getstyle; + void *getstyledata; + UiModel *model; + UiBool istable; + int preferred_width; + int preferred_height; + ui_callback onactivate; + void* onactivatedata; + ui_callback onselection; + void* onselectiondata; + ui_callback ondragstart; + void* ondragstartdata; + ui_callback ondragcomplete; + void* ondragcompletedata; + ui_callback ondrop; + void* ondropdata; +} UiListView; + +int ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +W32Size ui_listview_get_preferred_size(W32Widget *widget); + +void ui_listview_update(UiList *list, int row); +UiListSelection ui_listview_getselection(UiList *list); +void ui_listview_setselection(UiList *list, UiListSelection selection); + +int ui_dropdown_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +W32Size ui_dropdown_get_preferred_size(W32Widget *widget); + +void ui_dropdown_update(UiList *list, int row); +UiListSelection ui_dropdown_getselection(UiList *list); +void ui_dropdown_setselection(UiList *list, UiListSelection selection); + +#ifdef __cplusplus +} +#endif + +#endif //_LIST_H \ No newline at end of file diff --git a/ui/win32/text.c b/ui/win32/text.c new file mode 100644 index 0000000..cc5f400 --- /dev/null +++ b/ui/win32/text.c @@ -0,0 +1,241 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "text.h" + +static W32WidgetClass textarea_widget_class = { + .eventproc = ui_textarea_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_textarea_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +UIWIDGET ui_textarea_create(UiObject *obj, UiTextAreaArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + int width = args->width >= 0 ? args->width : 100; + int height = args->height >= 0 ? args->height : 100; + + HWND hwnd = CreateWindowEx( + 0, + "EDIT", + "", + WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL, + 0, 0, width, height, + parent, + (HMENU)0, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + W32Widget *widget = w32_widget_create(&textarea_widget_class, hwnd, sizeof(UiTextArea)); + ui_container_add(container, widget, &layout); + + UiTextArea *textarea = (UiTextArea*)widget; + textarea->width = width; + textarea->widget.var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT); + textarea->widget.callback = args->onchange; + textarea->widget.callbackdata = args->onchangedata; + + if (textarea->widget.var) { + UiText *t = textarea->widget.var->value; + + if (t->value.ptr) { + // TODO: set textarea string + } + t->obj = widget; + t->save = ui_textarea_save; + t->destroy = ui_textarea_destroy; + t->restore = ui_textarea_restore; + t->get = ui_textarea_get; + t->set = ui_textarea_set; + t->getsubstr = ui_textarea_getsubstr; + t->insert = ui_textarea_insert; + t->setposition = ui_textarea_setposition; + t->position = ui_textarea_position; + t->setselection = ui_textarea_setselection; + t->selection = ui_textarea_selection; + t->length = ui_textarea_length; + t->remove = ui_textarea_remove; + } + + return widget; +} + +W32Size ui_textarea_get_preferred_size(W32Widget *widget) { + W32Size size; + UiTextArea *textarea = (UiTextArea*)widget; + size.width = textarea->width; + size.height = textarea->height; + return size; +} + +int ui_textarea_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return 0; +} + +void ui_textarea_save(UiText *text) { + +} + +void ui_textarea_destroy(UiText *text) { + +} + +void ui_textarea_restore(UiText *text) { + +} + +void ui_textarea_set(UiText *text, const char *str) { + +} + +char* ui_textarea_get(UiText *text) { + return NULL; +} + +char* ui_textarea_getsubstr(UiText *text, int begin, int end) { + return NULL; +} + +void ui_textarea_insert(UiText *text, int pos, char *str) { + +} + +void ui_textarea_setposition(UiText *text, int pos) { + +} + +int ui_textarea_position(UiText *text) { + return 0; +} + +void ui_textarea_setselection(UiText *text, int begin, int end) { + +} + +void ui_textarea_selection(UiText *text, int *begin, int *end) { + +} + +int ui_textarea_length(UiText *text) { + return 0; +} + +void ui_textarea_remove(UiText *text, int begin, int end) { + +} + +/* ----------------------------- TextField ----------------------------- */ + +static W32WidgetClass textfield_widget_class = { + .eventproc = ui_textfield_eventproc, + .enable = w32_widget_default_enable, + .show = w32_widget_default_show, + .get_preferred_size = ui_textfield_get_preferred_size, + .destroy = w32_widget_default_destroy +}; + +UIWIDGET ui_textfield_create(UiObject *obj, UiTextFieldArgs *args) { + HINSTANCE hInstance = GetModuleHandle(NULL); + UiContainerPrivate *container = ui_obj_container(obj); + HWND parent = ui_container_get_parent(container); + UiLayout layout = UI_ARGS2LAYOUT(args); + + int width = args->width >= 0 ? args->width : 100; + + HWND hwnd = CreateWindowEx( + WS_EX_CLIENTEDGE, + "EDIT", + "", + WS_VISIBLE | WS_CHILD | ES_LEFT | ES_AUTOHSCROLL, + 0, 0, width, 25, + parent, + (HMENU)0, + hInstance, + NULL); + ui_win32_set_ui_font(hwnd); + + W32Widget *widget = w32_widget_create(&textfield_widget_class, hwnd, sizeof(UiTextField)); + ui_container_add(container, widget, &layout); + + UiTextField *textfield = (UiTextField*)widget; + textfield->width = width; + textfield->widget.var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING); + textfield->widget.callback = args->onchange; + textfield->widget.callbackdata = args->onchangedata; + + if (textfield->widget.var) { + UiString *s = textfield->widget.var->value; + + if (s->value.ptr) { + // TODO: set textfield string + } + s->obj = widget; + s->get = ui_textfield_get; + s->set = ui_textfield_set; + } + + return widget; +} + +W32Size ui_textfield_get_preferred_size(W32Widget *widget) { + UiTextField *textfield = (UiTextField *)widget; + return (W32Size){ .width = textfield->width, .height = 32}; +} + +int ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return 0; +} + +char* ui_textfield_get(UiString *s) { + UiTextField *textfield = s->obj; + + if (s->value.free) { + s->value.free(s->value.ptr); + } + + int len = GetWindowTextLength(textfield->widget.widget.hwnd); + s->value.ptr = calloc(len+1, 1); + GetWindowText(textfield->widget.widget.hwnd, s->value.ptr, len+1); + + return s->value.ptr; +} + +void ui_textfield_set(UiString *s, const char *value) { + UiTextField *textfield = s->obj; + if (s->value.free) { + s->value.free(s->value.ptr); + } + s->value.ptr = NULL; + SetWindowText(textfield->widget.widget.hwnd, value); +} diff --git a/ui/win32/text.h b/ui/win32/text.h new file mode 100644 index 0000000..7eed5db --- /dev/null +++ b/ui/win32/text.h @@ -0,0 +1,70 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TEXT_H +#define TEXT_H + +#include "../ui/text.h" +#include "container.h" +#include "toolkit.h" + +typedef struct UiTextArea { + UiWidget widget; + int width; + int height; +} UiTextArea; + +typedef struct UiTextField { + UiWidget widget; + int width; +} UiTextField; + +W32Size ui_textarea_get_preferred_size(W32Widget *widget); +int ui_textarea_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +void ui_textarea_save(UiText *text); +void ui_textarea_destroy(UiText *text); +void ui_textarea_restore(UiText *text); +void ui_textarea_set(UiText *text, const char *str); +char* ui_textarea_get(UiText *text); +char* ui_textarea_getsubstr(UiText *text, int begin, int end); +void ui_textarea_insert(UiText *text, int pos, char *str); +void ui_textarea_setposition(UiText *text, int pos); +int ui_textarea_position(UiText *text); +void ui_textarea_setselection(UiText *text, int begin, int end); +void ui_textarea_selection(UiText *text, int *begin, int *end); +int ui_textarea_length(UiText *text); +void ui_textarea_remove(UiText *text, int begin, int end); + +W32Size ui_textfield_get_preferred_size(W32Widget *widget); +int ui_textfield_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +char* ui_textfield_get(UiString *s); +void ui_textfield_set(UiString *s, const char *value); + +#endif /* TEXT_H */ \ No newline at end of file