# HG changeset patch # User Olaf Wintermann # Date 1707225442 -3600 # Node ID af411868ab9ba678d53ddbb07b855ab1dac3b61c # Parent 7cfd36aa005b9bd46fd170f7c9a90fb657351806 implement dnd upload (without progressbar) diff -r 7cfd36aa005b -r af411868ab9b application/application.c --- a/application/application.c Wed Jan 31 12:55:11 2024 +0100 +++ b/application/application.c Tue Feb 06 14:17:22 2024 +0100 @@ -86,7 +86,7 @@ } ui_toolbar_item("NewFolder", .icon = "NewFolder"); ui_toolbar_item("NewFile", .icon = "Add"); - ui_toolbar_item("Upload", .label = "Upload", .icon = "Upload"); + ui_toolbar_item("Upload", .label = "Upload", .icon = "Upload", .onclick = action_upload_file); ui_toolbar_item("Download", .icon = "SaveLocal"); ui_toolbar_item("Remove", .icon = "Delete"); ui_toolbar_toggleitem("LocalBrowser", .icon = "DockLeft", .label = "Local Browser"); @@ -163,3 +163,13 @@ DavBrowser *browser = event->document; davbrowser_connect2repo(event->obj, browser, repo, ""); } + +static void file_selected(UiEvent *event, void *data) { + UiFileList *files = event->eventdata; + + char *file = files->files[0]; +} + +void action_upload_file(UiEvent *event, void *data) { + ui_openfiledialog(event->obj, UI_FILEDIALOG_SELECT_MULTI, file_selected, NULL); +} diff -r 7cfd36aa005b -r af411868ab9b application/application.h --- a/application/application.h Wed Jan 31 12:55:11 2024 +0100 +++ b/application/application.h Tue Feb 06 14:17:22 2024 +0100 @@ -102,6 +102,8 @@ void action_repo_selected(UiEvent *event, void *data); +void action_upload_file(UiEvent *event, void *data); + #ifdef __cplusplus } #endif diff -r 7cfd36aa005b -r af411868ab9b application/davcontroller.c --- a/application/davcontroller.c Wed Jan 31 12:55:11 2024 +0100 +++ b/application/davcontroller.c Tue Feb 06 14:17:22 2024 +0100 @@ -33,6 +33,8 @@ #include "config.h" +#include "system.h" + #include #include @@ -234,3 +236,137 @@ browser->navstack_enabled = true; } } + + +// ------------------------------------- File Upload ------------------------------------- + +typedef struct DavFileUpload { + UiObject *ui; + DavBrowser *browser; + DavSession *sn; + UiFileList files; + char *base_path; + + UiObject *dialog; + UiDouble *progress; + UiString *file_label; + UiString *speed_label; +} DavFileUpload; + +typedef struct DUFile { + char *path; + char *upload_path; +} DUFile; + +static int jobthr_file_upload(void *data) { + DavFileUpload *upload = data; + + CxList *stack = cxLinkedListCreateSimple(sizeof(DUFile)); + for (int i = 0; i < upload->files.nfiles; i++) { + DUFile f; + f.path = upload->files.files[i]; + f.upload_path = util_path_file_name(f.path); + cxListInsert(stack, 0, &f); + } + + while (stack->size > 0) { + DUFile *f = cxListAt(stack, 0); + + char *path = util_concat_path(upload->base_path, f->upload_path); + DavResource *res = dav_resource_new(upload->sn, path); + + SYS_STAT s; + if (!stat(f->path, &s)) { + if (S_ISDIR(s.st_mode)) { + res->iscollection = 1; + dav_create(res); + + SYS_DIR dir = sys_opendir(f->path); + if (dir) { + SysDirEnt *entry; + int nument = 0; + while((entry = sys_readdir(dir)) != NULL) { + if(!strcmp(entry->name, ".") || !strcmp(entry->name, "..")) { + continue; + } + + cxmutstr newpath = util_concat_sys_path(cx_str(f->path), cx_str(entry->name)); + char *new_upload_path = util_concat_path(f->upload_path, entry->name); + + DUFile child; + child.path = newpath.ptr; + child.upload_path = new_upload_path; + cxListAdd(stack, &child); + } + + sys_closedir(dir); + } + } else if (S_ISREG(s.st_mode)) { + FILE *in = sys_fopen(f->path, "rb"); + if (in) { + dav_set_content(res, in, (dav_read_func)fread, (dav_seek_func)fseek); + //dav_set_content_length(res, s.st_size); + int err = dav_store(res); + if (err) { + // TODO: error + + } + fclose(in); + } + } + } // TODO: else error msg + + dav_resource_free(res); + free(path); + + cxListRemove(stack, 0); + } + + return 0; +} + +static void uithr_file_upload_finished(UiEvent *event, void *data) { + DavFileUpload *upload = data; +} + + +void davbrowser_upload_files(UiObject *ui, DavBrowser *browser, UiFileList files) { + if (!browser->sn) { + return; // TODO: error msg + } + + // we need a clone of the current session, because the upload + // is done in a separate thread + DavSession *upload_session = dav_session_clone(browser->sn); + + // create upload obj, that contains all relevant data for the upload + DavFileUpload *upload = malloc(sizeof(DavFileUpload)); + upload->ui = ui; + upload->browser = browser; + upload->sn = upload_session; + upload->files = files; + upload->base_path = strdup(browser->current->path); + + // create upload progress window + UiObject *dialog = ui_simple_window("Upload", upload); + upload->dialog = dialog; + ui_window_size(dialog, 450, 120); + upload->progress = ui_double_new(dialog->ctx, NULL); + upload->file_label = ui_string_new(dialog->ctx, NULL); + upload->speed_label = ui_string_new(dialog->ctx, NULL); + + ui_vbox(dialog, .margin = 10, .spacing = 10) { + ui_llabel(dialog, .value = upload->file_label); + ui_progressbar(dialog, .value = upload->progress); + ui_llabel(dialog, .value = upload->speed_label); + } + + ui_set(upload->file_label, ""); + ui_set(upload->speed_label, ""); + ui_set(upload->progress, 0); + + //ui_show(dialog); + + // start upload and stat threads + ui_job(ui, jobthr_file_upload, upload, uithr_file_upload_finished, upload); +} diff -r 7cfd36aa005b -r af411868ab9b application/davcontroller.h --- a/application/davcontroller.h Wed Jan 31 12:55:11 2024 +0100 +++ b/application/davcontroller.h Tue Feb 06 14:17:22 2024 +0100 @@ -56,6 +56,8 @@ void davbrowser_navigation_back(UiObject *ui, DavBrowser *browser); void davbrowser_navigation_forward(UiObject *ui, DavBrowser *browser); +void davbrowser_upload_files(UiObject *ui, DavBrowser *browser, UiFileList files); + #ifdef __cplusplus } diff -r 7cfd36aa005b -r af411868ab9b application/system.h --- a/application/system.h Wed Jan 31 12:55:11 2024 +0100 +++ b/application/system.h Tue Feb 06 14:17:22 2024 +0100 @@ -80,6 +80,19 @@ #endif + +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif + +#ifdef _WIN32 +#ifndef S_ISDIR +#define S_ISDIR(mode) ((mode) & _S_IFMT) == _S_IFDIR +#define S_ISREG(mode) ((mode) & _S_IFMT) == _S_IFREG +#endif +#endif + + typedef int(*stat_func)(const char*, SYS_STAT *); void sys_init(void); diff -r 7cfd36aa005b -r af411868ab9b application/window.c --- a/application/window.c Wed Jan 31 12:55:11 2024 +0100 +++ b/application/window.c Tue Feb 06 14:17:22 2024 +0100 @@ -31,6 +31,7 @@ #include "davcontroller.h" #include +#include #include @@ -67,7 +68,7 @@ // main content UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Name", UI_STRING, "Type", UI_STRING_FREE, "Last Modified", UI_STRING_FREE, "Size", -1); model->getvalue = (ui_getvaluefunc)window_resource_table_getvalue; - ui_table(obj, .fill = UI_ON, .model = model, .onactivate = action_list_activate, .varname = "reslist"); + ui_table(obj, .fill = UI_ON, .model = model, .onactivate = action_list_activate, .ondrop = action_dnd_drop,.varname = "reslist"); // status bar ui_hbox(obj, .fill = UI_OFF) { @@ -215,3 +216,11 @@ } } } + +void action_dnd_drop(UiEvent *event, void *data) { + UiListDnd *listdnd = event->eventdata; + UiDnD *dnd = listdnd->dnd; + UiFileList files = ui_selection_geturis(dnd); + + davbrowser_upload_files(event->obj, event->document, files); +} diff -r 7cfd36aa005b -r af411868ab9b application/window.h --- a/application/window.h Wed Jan 31 12:55:11 2024 +0100 +++ b/application/window.h Tue Feb 06 14:17:22 2024 +0100 @@ -62,6 +62,8 @@ void action_list_activate(UiEvent *event, void *data); +void action_dnd_drop(UiEvent *event, void *data); + #ifdef __cplusplus } #endif diff -r 7cfd36aa005b -r af411868ab9b libidav/Makefile --- a/libidav/Makefile Wed Jan 31 12:55:11 2024 +0100 +++ b/libidav/Makefile Tue Feb 06 14:17:22 2024 +0100 @@ -26,8 +26,6 @@ # POSSIBILITY OF SUCH DAMAGE. # -BUILD_ROOT = .. - include ../config.mk # list of source files diff -r 7cfd36aa005b -r af411868ab9b libidav/config.c --- a/libidav/config.c Wed Jan 31 12:55:11 2024 +0100 +++ b/libidav/config.c Tue Feb 06 14:17:22 2024 +0100 @@ -579,6 +579,9 @@ stype = "https"; } else if(type == DAV_HTTP_PROXY) { stype = "http"; + } else { + fprintf(stderr, "unknown proxy type\n"); + return 1; } if(!proxy) { diff -r 7cfd36aa005b -r af411868ab9b libidav/session.c --- a/libidav/session.c Wed Jan 31 12:55:11 2024 +0100 +++ b/libidav/session.c Tue Feb 06 14:17:22 2024 +0100 @@ -50,7 +50,7 @@ } DavSession *sn = malloc(sizeof(DavSession)); memset(sn, 0, sizeof(DavSession)); - sn->mp = cxMempoolCreate(DAV_SESSION_MEMPOOL_SIZE, NULL); + sn->mp = cxBasicMempoolCreate(DAV_SESSION_MEMPOOL_SIZE); sn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); sn->key = NULL; sn->errorstr = NULL; @@ -94,7 +94,7 @@ curl_easy_setopt(sn->handle, CURLOPT_URL, base_url); // add to context - cxListAdd(context->sessions, sn); + dav_context_add_session(context, sn); sn->context = context; return sn; @@ -114,6 +114,35 @@ return sn; } +DavSession* dav_session_clone(DavSession *sn) { + CURL *newhandle = curl_easy_duphandle(sn->handle); + + DavSession *newsn = malloc(sizeof(DavSession)); + memset(newsn, 0, sizeof(DavSession)); + newsn->mp = cxMempoolCreate(DAV_SESSION_MEMPOOL_SIZE, NULL); + newsn->pathcache = cxHashMapCreate(sn->mp->allocator, CX_STORE_POINTERS, DAV_PATH_CACHE_SIZE); + newsn->key = sn->key; + newsn->errorstr = NULL; + newsn->error = DAV_OK; + newsn->flags = 0; + + newsn->handle = newhandle; + + newsn->base_url = cx_strdup_a(newsn->mp->allocator, cx_str(sn->base_url)).ptr; + newsn->auth_prompt = sn->auth_prompt; + newsn->authprompt_userdata = sn->authprompt_userdata; + newsn->logfunc = sn->logfunc; + newsn->get_progress = sn->get_progress; + newsn->put_progress = sn->put_progress; + newsn->progress_userdata = sn->progress_userdata; + + // add to context + dav_context_add_session(sn->context, newsn); + newsn->context = sn->context; + + return newsn; +} + void dav_session_set_auth(DavSession *sn, const char *user, const char *password) { if(user && password) { dav_session_set_auth_s(sn, cx_str(user), cx_str(password)); @@ -302,12 +331,8 @@ void dav_session_destroy(DavSession *sn) { // remove session from context - CxList *sessions = sn->context->sessions; - ssize_t i = cxListFind(sessions, sn); - if(i >= 0) { - cxListRemove(sessions, i); - } else { - printf("Error: session not found in ctx->sessions\n"); + if (dav_context_remove_session(sn->context, sn)) { + fprintf(stderr, "Error: session not found in ctx->sessions\n"); dav_session_destructor(sn); } } diff -r 7cfd36aa005b -r af411868ab9b libidav/utils.c --- a/libidav/utils.c Wed Jan 31 12:55:11 2024 +0100 +++ b/libidav/utils.c Tue Feb 06 14:17:22 2024 +0100 @@ -612,6 +612,28 @@ } } +const char* util_resource_name_c(const char *url, char pathseparator) { + cxstring urlstr = cx_str(url); + if(urlstr.ptr[urlstr.length-1] == pathseparator) { + urlstr.length--; + } + cxstring resname = cx_strrchr(urlstr, pathseparator); + if(resname.length > 1) { + return resname.ptr+1; + } else { + return url; + } +} + +const char* util_path_file_name(const char *url) { +#ifdef _WIN32 + return util_resource_name_c(url, '\\'); +#else + return util_resource_name_c(url, '/'); +#endif +} + + int util_mkdir(char *path, mode_t mode) { #ifdef _WIN32 return mkdir(path); @@ -658,6 +680,40 @@ return url; } +cxmutstr util_concat_path_ext(cxstring base, cxstring path, char separator) { + if(!path.ptr) { + path = CX_STR(""); + } + + int add_separator = 0; + if(base.length != 0 && base.ptr[base.length-1] == separator) { + if(path.ptr[0] == separator) { + base.length--; + } + } else { + if(path.length == 0 || path.ptr[0] != separator) { + add_separator = 1; + } + } + + cxmutstr url; + if(add_separator) { + url = cx_strcat(3, base, cx_strn(&separator, 1), path); + } else { + url = cx_strcat(2, base, path); + } + + return url; +} + +cxmutstr util_concat_sys_path(cxstring base, cxstring path) { +#ifdef _WIN32 + return util_concat_path_ext(base, path, '\\'); +#else + return util_concat_path_ext(base, path, '/'); +#endif +} + char* util_get_url(DavSession *sn, const char *href) { cxstring base = cx_str(sn->base_url); cxstring href_str = cx_str(href); @@ -716,6 +772,17 @@ return parent; } +char* util_sys_parent_path(const char *path) { + const char *name = util_path_file_name(path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + char *parent = malloc(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + char* util_size_str(DavBool iscollection, uint64_t contentlength) { char *str = malloc(16); uint64_t size = contentlength; diff -r 7cfd36aa005b -r af411868ab9b libidav/utils.h --- a/libidav/utils.h Wed Jan 31 12:55:11 2024 +0100 +++ b/libidav/utils.h Tue Feb 06 14:17:22 2024 +0100 @@ -78,8 +78,13 @@ cxstring util_url_path_s(cxstring url); char* util_url_decode(DavSession *sn, const char *url); const char* util_resource_name(const char *url); +const char* util_resource_name_c(const char *url, char pathseparator); +const char* util_path_file_name(const char *url); + char* util_concat_path(const char *url_base, const char *path); cxmutstr util_concat_path_s(cxstring url_base, cxstring path); +cxmutstr util_concat_path_ext(cxstring url_base, cxstring path, char separator); +cxmutstr util_concat_sys_path(cxstring base, cxstring path); char* util_get_url(DavSession *sn, const char *href); void util_set_url(DavSession *sn, const char *href); @@ -97,6 +102,7 @@ char* util_path_to_url(DavSession *sn, const char *path); char* util_parent_path(const char *path); +char* util_sys_parent_path(const char *path); char* util_size_str(DavBool iscollection, uint64_t contentlength); char* util_date_str(time_t tm); diff -r 7cfd36aa005b -r af411868ab9b libidav/webdav.c --- a/libidav/webdav.c Wed Jan 31 12:55:11 2024 +0100 +++ b/libidav/webdav.c Tue Feb 06 14:17:22 2024 +0100 @@ -146,15 +146,68 @@ free(ctx); } +#ifndef _WIN32 + +void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) { + if (enable) { + pthread_mutex_init(&ctx->mutex, NULL); + } else { + pthread_mutex_destroy(&ctx->mutex); + } + ctx->mtsafe = enable; +} + +void dav_context_lock(DavContext *ctx) { + if (ctx->mtsafe) { + pthread_mutex_lock(&ctx->mutex); + } +} + +void dav_context_unlock(DavContext *ctx) { + if (ctx->mtsafe) { + pthread_mutex_unlock(&ctx->mutex); + } +} + +#else + +void dav_context_set_mtsafe(DavContext *ctx, DavBool enable) { + if (enable) { + ctx->mutex = CreateMutex(NULL, FALSE, NULL); + } else { + CloseHandle(ctx->mutex); + } + ctx->mtsafe = enable; +} + +void dav_context_lock(DavContext *ctx) { + if (ctx->mtsafe) { + WaitForSingleObject(ctx->mutex, INFINITE); + } +} + +void dav_context_unlock(DavContext *ctx) { + if (ctx->mtsafe) { + ReleaseMutex(ctx->mutex); + } +} + +#endif + void dav_context_add_key(DavContext *context, DavKey *key) { + dav_context_lock(context); cxMapPut(context->keys, cx_hash_key_str(key->name), key); + dav_context_unlock(context); } DavKey* dav_context_get_key(DavContext *context, const char *name) { + DavKey *key = NULL; + dav_context_lock(context); if(name) { - return cxMapGet(context->keys, cx_hash_key_str(name)); + key = cxMapGet(context->keys, cx_hash_key_str(name)); } - return NULL; + dav_context_unlock(context); + return key; } int dav_add_namespace(DavContext *context, const char *prefix, const char *name) { @@ -164,8 +217,19 @@ } char *p = strdup(prefix); + if (!p) { + free(namespace); + return 1; + } char *n = strdup(name); - + if (!n) { + free(namespace); + free(p); + return 1; + } + + dav_context_lock(context); + int err = 0; if(p && n) { namespace->prefix = p; @@ -178,19 +242,29 @@ if(p) free(p); if(n) free(n); } + + dav_context_unlock(context); return err; } DavNamespace* dav_get_namespace(DavContext *context, const char *prefix) { - return cxMapGet(context->namespaces, cx_hash_key_str(prefix)); + dav_context_lock(context); + DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key_str(prefix)); + dav_context_unlock(context); + return ns; } DavNamespace* dav_get_namespace_s(DavContext *context, cxstring prefix) { - return cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length)); + dav_context_lock(context); + DavNamespace *ns = cxMapGet(context->namespaces, cx_hash_key(prefix.ptr, prefix.length)); + dav_context_unlock(context); + return ns; } int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt) { + dav_context_lock(context); + CxHashKey hkey = cx_hash_key_str(ns); DavNSInfo *info = cxMapGet(context->namespaceinfo, hkey); if(!info) { @@ -200,15 +274,21 @@ } else { info->encrypt = encrypt; } + + dav_context_unlock(context); return 0; } int dav_namespace_is_encrypted(DavContext *context, const char *ns) { + int ret = 0; + dav_context_lock(context); + DavNSInfo *info = cxMapGet(context->namespaceinfo, cx_hash_key_str(ns)); if(info) { - return info->encrypt; + ret = info->encrypt; } - return 0; + dav_context_unlock(context); + return ret; } void dav_get_property_namespace_str( @@ -262,6 +342,28 @@ } } +int dav_context_add_session(DavContext *context, DavSession *sn) { + dav_context_lock(context); + int ret = cxListAdd(context->sessions, sn); + dav_context_unlock(context); + return ret; +} + +int dav_context_remove_session(DavContext *context, DavSession *sn) { + int ret = 0; + dav_context_lock(context); + CxList *sessions = context->sessions; + ssize_t i = cxListFind(sessions, sn); + if(i >= 0) { + cxListRemove(sessions, i); + } else { + ret = 1; + } + dav_context_unlock(context); + return ret; +} + + // TODO: add sstr_t version of dav_get_property_ns void dav_set_effective_href(DavSession *sn, DavResource *resource) { diff -r 7cfd36aa005b -r af411868ab9b libidav/webdav.h --- a/libidav/webdav.h Wed Jan 31 12:55:11 2024 +0100 +++ b/libidav/webdav.h Tue Feb 06 14:17:22 2024 +0100 @@ -39,6 +39,12 @@ #include #include +#ifndef _WIN32 +#include +#else +#include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -67,6 +73,12 @@ typedef struct DavInputStream DavInputStream; typedef struct DavOutputStream DavOutputStream; +#ifndef _WIN32 +#define DAV_MUTEX pthread_mutex_t +#else +#define DAV_MUTEX HANDLE +#endif + typedef size_t(*dav_read_func)(void*, size_t, size_t, void*); typedef size_t(*dav_write_func)(const void*, size_t, size_t, void*); typedef int(*dav_seek_func)(const void *, long, int); @@ -180,12 +192,14 @@ }; struct DavContext { - CxMap *namespaces; - CxMap *namespaceinfo; - CxMap *keys; - CxList *sessions; - DavProxy *http_proxy; - DavProxy *https_proxy; + CxMap *namespaces; + CxMap *namespaceinfo; + CxMap *keys; + CxList *sessions; + DavProxy *http_proxy; + DavProxy *https_proxy; + DAV_MUTEX mutex; + DavBool mtsafe; }; struct DavProxy { @@ -251,6 +265,10 @@ DavContext* dav_context_new(void); void dav_context_destroy(DavContext *ctx); +void dav_context_set_mtsafe(DavContext *ctx, DavBool enable); + +void dav_context_lock(DavContext *ctx); +void dav_context_unlock(DavContext *ctx); void dav_context_add_key(DavContext *context, DavKey *key); DavKey* dav_context_get_key(DavContext *context, const char *name); @@ -262,12 +280,16 @@ int dav_enable_namespace_encryption(DavContext *context, const char *ns, DavBool encrypt); int dav_namespace_is_encrypted(DavContext *context, const char *ns); +int dav_context_add_session(DavContext *context, DavSession *sn); +int dav_context_remove_session(DavContext *context, DavSession *sn); + DavSession* dav_session_new(DavContext *context, char *base_url); DavSession* dav_session_new_auth( DavContext *context, char *base_url, char *user, char *password); +DavSession* dav_session_clone(DavSession *sn); void dav_session_set_auth(DavSession *sn, const char *user, const char *password); void dav_session_set_auth_s(DavSession *sn, cxstring user, cxstring password); void dav_session_set_baseurl(DavSession *sn, char *base_url); diff -r 7cfd36aa005b -r af411868ab9b ui/common/types.c --- a/ui/common/types.c Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/common/types.c Tue Feb 06 14:17:22 2024 +0100 @@ -466,3 +466,19 @@ UiStr ui_str_free(char *str, void (*freefunc)(void *v)) { return (UiStr) { str, freefunc }; } + + +UiFileList ui_filelist_copy(UiFileList list) { + char **newlist = calloc(sizeof(char*), list.nfiles); + for (int i = 0; i < list.nfiles; i++) { + newlist[i] = strdup(list.files[i]); + } + return (UiFileList) { newlist, list.nfiles }; +} + +void ui_filelist_free(UiFileList list) { + for (int i = 0; i < list.nfiles; i++) { + free(list.files[i]); + } + free(list.files); +} diff -r 7cfd36aa005b -r af411868ab9b ui/ui/dnd.h --- a/ui/ui/dnd.h Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/ui/dnd.h Tue Feb 06 14:17:22 2024 +0100 @@ -41,7 +41,7 @@ UIEXPORT void ui_selection_seturis(UiDnD *sel, char **uris, int nelm); UIEXPORT char* ui_selection_gettext(UiDnD *sel); -UIEXPORT char** ui_selection_geturis(UiDnD *sel, size_t *nelm); +UIEXPORT UiFileList ui_selection_geturis(UiDnD *sel); #ifdef __cplusplus diff -r 7cfd36aa005b -r af411868ab9b ui/ui/toolkit.h --- a/ui/ui/toolkit.h Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/ui/toolkit.h Tue Feb 06 14:17:22 2024 +0100 @@ -155,6 +155,8 @@ typedef struct UiStr UiStr; +typedef struct UiFileList UiFileList; + /* begin opaque types */ typedef struct UiContext UiContext; typedef struct UiContainer UiContainer; @@ -356,6 +358,11 @@ UI_OFF }; +struct UiFileList { + char **files; + size_t nfiles; +}; + UIEXPORT void ui_init(const char *appname, int argc, char **argv); UIEXPORT const char* ui_appname(); @@ -453,6 +460,9 @@ UIEXPORT void ui_list_addobsv(UiList *list, ui_callback f, void *data); UIEXPORT void ui_list_notify(UiList *list); +UiFileList ui_filelist_copy(UiFileList list); +void ui_filelist_free(UiFileList list); + UIEXPORT void ui_clipboard_set(char *str); UIEXPORT char* ui_clipboard_get(); diff -r 7cfd36aa005b -r af411868ab9b ui/ui/window.h --- a/ui/ui/window.h Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/ui/window.h Tue Feb 06 14:17:22 2024 +0100 @@ -35,13 +35,19 @@ extern "C" { #endif +#define UI_FILEDIALOG_SELECT_SINGLE 0 +#define UI_FILEDIALOG_SELECT_MULTI 1 +#define UI_FILEDIALOG_SELECT_FOLDER 2 + UIEXPORT UiObject* ui_window(const char *title, void *window_data); -UIEXPORT UiObject* ui_simplewindow(char *title, void *window_data); +UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data); UIEXPORT void ui_window_size(UiObject *obj, int width, int height); -char* ui_openfiledialog(UiObject *obj); -char* ui_savefiledialog(UiObject *obj); +UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata); +UIEXPORT void ui_savefiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata); + + #ifdef __cplusplus } diff -r 7cfd36aa005b -r af411868ab9b ui/winui/dnd.cpp --- a/ui/winui/dnd.cpp Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/winui/dnd.cpp Tue Feb 06 14:17:22 2024 +0100 @@ -31,6 +31,13 @@ #include "dnd.h" #include "util.h" +#include + +using namespace winrt; +using namespace Windows::ApplicationModel::DataTransfer; +using namespace Windows::Storage; +using namespace Windows::Storage::Streams; + UIEXPORT void ui_selection_settext(UiDnD* dnd, char* str, int len) { if (dnd->data) { if (len < 0) { @@ -54,6 +61,31 @@ return nullptr; } -UIEXPORT char** ui_selection_geturis(UiDnD* dnd, size_t* nelm) { - return nullptr; + +UIEXPORT UiFileList ui_selection_geturis(UiDnD *dnd) { + UiFileList flist; + flist.files = nullptr; + flist.nfiles = 0; + + if (dnd->dataview.Contains(StandardDataFormats::StorageItems())) { + UiFileList *flist_ptr = &flist; + + // we need to execute this in a different thread + // this could block the main gui thread, but shouldn't happen with a simple uri list + std::thread getDataThread([dnd, flist_ptr]() { + auto items = dnd->dataview.GetStorageItemsAsync().get(); + + char **uris = (char**)calloc(items.Size(), sizeof(char*)); + flist_ptr->files = uris; + flist_ptr->nfiles = items.Size(); + + int i = 0; + for (IStorageItem const& item : items) { + winrt::hstring path = item.Path(); + uris[i++] = wchar2utf8(path.c_str(), path.size()); + } + }); + getDataThread.join(); + } + return flist; } diff -r 7cfd36aa005b -r af411868ab9b ui/winui/dnd.h --- a/ui/winui/dnd.h Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/winui/dnd.h Tue Feb 06 14:17:22 2024 +0100 @@ -36,4 +36,5 @@ winrt::Microsoft::UI::Xaml::DropCompletedEventArgs dndcompletedargs = { nullptr }; winrt::Microsoft::UI::Xaml::DragEventArgs drageventargs = { nullptr }; winrt::Windows::ApplicationModel::DataTransfer::DataPackage data = { nullptr }; + winrt::Windows::ApplicationModel::DataTransfer::DataPackageView dataview = { nullptr }; }; diff -r 7cfd36aa005b -r af411868ab9b ui/winui/label.cpp --- a/ui/winui/label.cpp Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/winui/label.cpp Tue Feb 06 14:17:22 2024 +0100 @@ -99,7 +99,7 @@ void ui_label_set(UiString* str, const char* newvalue) { UiWidget* widget = (UiWidget*)str->obj; - TextBox box = widget->uielement.as(); + TextBlock box = widget->uielement.as(); box.Text(ui_wstring_set(str, newvalue)); } diff -r 7cfd36aa005b -r af411868ab9b ui/winui/pch.h --- a/ui/winui/pch.h Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/winui/pch.h Tue Feb 06 14:17:22 2024 +0100 @@ -34,6 +34,12 @@ #include #include #include +#include + #include #include + +#include + +#include diff -r 7cfd36aa005b -r af411868ab9b ui/winui/table.cpp --- a/ui/winui/table.cpp Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/winui/table.cpp Tue Feb 06 14:17:22 2024 +0100 @@ -385,7 +385,7 @@ dnd.dndstartargs = { nullptr }; dnd.dndcompletedargs = { nullptr }; dnd.drageventargs = args; - dnd.data = args.Data(); + dnd.dataview = args.DataView(); UiListDnd dndevt; dndevt.selection = uiselection(); @@ -404,6 +404,9 @@ free(dndevt.selection.rows); } })); + cellBorder.DragOver(DragEventHandler([this](winrt::Windows::Foundation::IInspectable const& sender, DragEventArgs const& args){ + args.AcceptedOperation(winrt::Windows::ApplicationModel::DataTransfer::DataPackageOperation::Copy); + })); } // set the cell value diff -r 7cfd36aa005b -r af411868ab9b ui/winui/window.cpp --- a/ui/winui/window.cpp Wed Jan 31 12:55:11 2024 +0100 +++ b/ui/winui/window.cpp Tue Feb 06 14:17:22 2024 +0100 @@ -28,6 +28,7 @@ #include "pch.h" + #include "window.h" #include "appmenu.h" @@ -52,54 +53,12 @@ using namespace Microsoft::UI::Xaml::Markup; using namespace Windows::UI::Xaml::Interop; using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Storage::Pickers; UiWindow::UiWindow(winrt::Microsoft::UI::Xaml::Window& win) : window(win) {} UiObject* ui_window(const char* title, void* window_data) { - CxMempool* mp = cxBasicMempoolCreate(256); - UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); - - obj->ctx = uic_context(obj, mp); - obj->window = window_data; - - Window window = Window(); - //Window window = make(); - - winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" }; - Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested); - - window.ExtendsContentIntoTitleBar(true); - - Grid grid = Grid(); - window.Content(grid); - - StackPanel titleBar = StackPanel(); - Thickness titleBarPadding = { 10, 5, 5, 10 }; - titleBar.Padding(titleBarPadding); - titleBar.Orientation(Orientation::Horizontal); - TextBlock titleLabel = TextBlock(); - titleBar.Children().Append(titleLabel); - - if (title) { - wchar_t* wtitle = str2wstr(title, nullptr); - window.Title(wtitle); - titleLabel.Text(hstring(wtitle)); - free(wtitle); - } - - window.SetTitleBar(titleBar); - - obj->wobj = new UiWindow(window); - ui_context_add_window_destructor(obj->ctx, obj->wobj); - - window.Closed([obj](IInspectable const& sender, WindowEventArgs) { - cxMempoolDestroy(obj->ctx->mp); - }); - - obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0); - - titleBar.VerticalAlignment(VerticalAlignment::Top); - obj->container->Add(titleBar, false); + UiObject* obj = ui_simple_window(title, window_data); if (uic_get_menu_list()) { // create/add menubar @@ -171,6 +130,55 @@ obj->container->Add(toolbar_grid, false); } + return obj; +} + +UIEXPORT UiObject* ui_simple_window(const char *title, void *window_data) { + CxMempool* mp = cxBasicMempoolCreate(256); + UiObject* obj = (UiObject*)cxCalloc(mp->allocator, 1, sizeof(UiObject)); + + obj->ctx = uic_context(obj, mp); + obj->window = window_data; + + Window window = Window(); + //Window window = make(); + + winrt::Windows::Foundation::Uri resourceLocator{ L"ms-appx:///MainWindow.xaml" }; + Application::LoadComponent(window, resourceLocator, ComponentResourceLocation::Nested); + + window.ExtendsContentIntoTitleBar(true); + + Grid grid = Grid(); + window.Content(grid); + + StackPanel titleBar = StackPanel(); + Thickness titleBarPadding = { 10, 5, 5, 10 }; + titleBar.Padding(titleBarPadding); + titleBar.Orientation(Orientation::Horizontal); + TextBlock titleLabel = TextBlock(); + titleBar.Children().Append(titleLabel); + + if (title) { + wchar_t* wtitle = str2wstr(title, nullptr); + window.Title(wtitle); + titleLabel.Text(hstring(wtitle)); + free(wtitle); + } + + window.SetTitleBar(titleBar); + + obj->wobj = new UiWindow(window); + ui_context_add_window_destructor(obj->ctx, obj->wobj); + + window.Closed([obj](IInspectable const& sender, WindowEventArgs) { + cxMempoolDestroy(obj->ctx->mp); + }); + + obj->container = new UiBoxContainer(grid, UI_BOX_CONTAINER_VBOX, 0, 0); + + titleBar.VerticalAlignment(VerticalAlignment::Top); + obj->container->Add(titleBar, false); + obj->window = window_data; return obj; @@ -185,3 +193,99 @@ win->window.AppWindow().Resize(wsize); } } + +static void filedialog_callback( + UiObject *obj, + ui_callback file_selected_callback, + void *cbdata, + winrt::Windows::Foundation::Collections::IVectorView result) +{ + UiFileList flist; + flist.nfiles = result.Size(); + flist.files = new char*[flist.nfiles]; + + int i = 0; + for (auto const& file : result) { + winrt::hstring path = file.Path(); + flist.files[i++] = wchar2utf8(path.c_str(), path.size()); + } + + UiEvent evt; + evt.obj = obj; + evt.document = obj->ctx->document; + evt.window = obj->window; + evt.eventdata = &flist; + evt.intval = 0; + file_selected_callback(&evt, cbdata); + + for (int i = 0; i < flist.nfiles;i++) { + free(flist.files[i]); + } + delete[] flist.files; +} + +static Windows::Foundation::IAsyncAction open_filedialog_async(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + FileOpenPicker openFileDialog = FileOpenPicker(); + auto initializeWithWindow { openFileDialog.as<::IInitializeWithWindow>() + }; + + HWND hwnd{ nullptr }; + winrt::check_hresult(obj->wobj->window.as()->get_WindowHandle(&hwnd)); + + initializeWithWindow->Initialize(hwnd); + + openFileDialog.FileTypeFilter().Append(L"*"); + + if ((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) { + auto files = co_await openFileDialog.PickMultipleFilesAsync(); + filedialog_callback(obj, file_selected_callback, cbdata, files); + } else { + auto file = co_await openFileDialog.PickSingleFileAsync(); + auto files = single_threaded_vector(); + files.Append(file); + filedialog_callback(obj, file_selected_callback, cbdata, files.GetView()); + } +} + +static Windows::Foundation::IAsyncAction folderdialog_async(UiObject *obj, ui_callback file_selected_callback, void *cbdata) { + FolderPicker folderPicker = FolderPicker(); + auto initializeWithWindow { folderPicker.as<::IInitializeWithWindow>() + }; + + HWND hwnd{ nullptr }; + winrt::check_hresult(obj->wobj->window.as()->get_WindowHandle(&hwnd)); + + initializeWithWindow->Initialize(hwnd); + + folderPicker.FileTypeFilter().Append(L"*"); + + auto folder = co_await folderPicker.PickSingleFolderAsync(); + if (folder) { + winrt::hstring hpath = folder.Path(); + char *cpath = wchar2utf8(hpath.c_str(), hpath.size()); + + UiFileList flist; + flist.nfiles = 1; + flist.files = &cpath; + + UiEvent evt; + evt.obj = obj; + evt.document = obj->ctx->document; + evt.window = obj->window; + evt.eventdata = &flist; + evt.intval = 0; + file_selected_callback(&evt, cbdata); + } +} + +UIEXPORT void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + if ((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { + folderdialog_async(obj, file_selected_callback, cbdata); + } else { + open_filedialog_async(obj, mode, file_selected_callback, cbdata); + } +} + +UIEXPORT void ui_savefiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + +}