Thu, 03 Oct 2024 18:08:29 +0200
implement action_newfile
application/application.c | file | annotate | diff | comparison | revisions | |
application/davcontroller.c | file | annotate | diff | comparison | revisions | |
application/davcontroller.h | file | annotate | diff | comparison | revisions | |
ui/gtk/headerbar.c | file | annotate | diff | comparison | revisions | |
ui/gtk/list.c | file | annotate | diff | comparison | revisions | |
ui/gtk/list.h | file | annotate | diff | comparison | revisions | |
ui/gtk/menu.c | file | annotate | diff | comparison | revisions | |
ui/gtk/objs.mk | file | annotate | diff | comparison | revisions | |
ui/gtk/toolbar.c | file | annotate | diff | comparison | revisions | |
ui/gtk/toolbar.h | file | annotate | diff | comparison | revisions | |
ui/gtk/tree.c | file | annotate | diff | comparison | revisions | |
ui/gtk/tree.h | file | annotate | diff | comparison | revisions | |
ui/gtk/window.c | file | annotate | diff | comparison | revisions |
--- a/application/application.c Sun Sep 29 20:25:41 2024 +0200 +++ b/application/application.c Thu Oct 03 18:08:29 2024 +0200 @@ -98,8 +98,8 @@ } //ui_toolbar_add_default("Home", UI_TOOLBAR_LEFT); - ui_toolbar_add_default("NewWindow", UI_TOOLBAR_LEFT); - ui_toolbar_add_default("Refresh", UI_TOOLBAR_LEFT); + //ui_toolbar_add_default("NewWindow", UI_TOOLBAR_LEFT); + //ui_toolbar_add_default("Refresh", UI_TOOLBAR_LEFT); ui_toolbar_add_default("Repo", UI_TOOLBAR_LEFT); ui_toolbar_add_default("NewFolder", UI_TOOLBAR_CENTER); @@ -246,8 +246,26 @@ ui_listselection_free(sel); } +static void newfiledialog_result(UiEvent *event, void *data) { + DavBrowser *browser = event->document; + char *path = event->eventdata; + if (event->intval == 1) { + davbrowser_newfile(event->obj, browser, path); + } +} + void action_newfile(UiEvent *event, void *data) { - + DavBrowser *browser = event->document; + if(!browser->sn) { + return; + } + + ui_dialog(event->obj, + .content = "New File", + .input = TRUE, + .button1_label = "Create File", + .button2_label = "Cancel", + .result = newfiledialog_result); } @@ -260,5 +278,16 @@ } void action_mkcol(UiEvent *event, void *data) { - ui_dialog(event->obj, .content = "New Folder", .input = TRUE, .button1_label = "Create Folder", .button2_label = "Close", .result = newfolderdialog_result); + DavBrowser *browser = event->document; + if(!browser->sn) { + return; + } + + + ui_dialog(event->obj, + .content = "New Folder", + .input = TRUE, + .button1_label = "Create Folder", + .button2_label = "Cancel", + .result = newfolderdialog_result); }
--- a/application/davcontroller.c Sun Sep 29 20:25:41 2024 +0200 +++ b/application/davcontroller.c Thu Oct 03 18:08:29 2024 +0200 @@ -952,7 +952,7 @@ enum DavPathOpType { DAV_PATH_OP_DELETE = 0, - DAV_PATH_OP_MKCOL + DAV_PATH_OP_CREATE }; typedef struct DavPathOp { @@ -969,6 +969,8 @@ size_t *list_indices; // number of path/list_indices elements size_t nelm; + // path is collection + DavBool iscollection; // browser->current ptr when the PathOp started // used in combination with collection_ctn to check if the browser list changed @@ -981,6 +983,8 @@ DavBrowser *browser; DavResource *collection; int64_t collection_ctn; + enum DavPathOpType op; + DavBool iscollection; char *path; int res_index; @@ -1016,10 +1020,10 @@ return 0; } -static int uithr_pathop_mkcol_error(void *data) { +static int uithr_pathop_create_resource_error(void *data) { DavPathOpResult *result = data; - cxmutstr msg = cx_asprintf("Cannot create collection %s", result->path); + cxmutstr msg = cx_asprintf("Cannot create %s %s", result->iscollection ? "collection" : "resource", result->path); ui_dialog(result->ui, .title = "Error", .content = msg.ptr, .button1_label = "OK"); free(msg.ptr); @@ -1031,12 +1035,12 @@ return 0; } -static int uithr_pathop_mkcol_sucess(void *data) { +static int uithr_pathop_create_resource_sucess(void *data) { DavPathOpResult *result = data; if (result->browser->current == result->collection && result->browser->res_counter == result->collection_ctn) { DavResource *res = dav_resource_new(result->browser->sn, result->path); - res->iscollection = TRUE; + res->iscollection = result->iscollection; // TODO: add the resource at the correct position or sort the list after append ui_list_append(result->browser->resources, res); result->browser->resources->update(result->browser->resources, 0); @@ -1059,10 +1063,12 @@ result->browser = op->browser; result->collection = op->collection; result->collection_ctn = op->collection_ctn; + result->op = op->op; result->path = strdup(res->path); result->result = 0; result->res_index = op->list_indices[i]; result->errormsg = NULL; + result->iscollection = op->iscollection; if (op->op == DAV_PATH_OP_DELETE) { ui_threadfunc result_callback = uithr_pathop_delete_sucess; @@ -1071,15 +1077,15 @@ result_callback = uithr_pathop_delete_error; } ui_call_mainthread(result_callback, result); - } else if (op->op == DAV_PATH_OP_MKCOL) { - res->iscollection = TRUE; - ui_threadfunc result_callback = uithr_pathop_mkcol_sucess; + } else if (op->op == DAV_PATH_OP_CREATE) { + res->iscollection = op->iscollection; + ui_threadfunc result_callback = uithr_pathop_create_resource_sucess; if (dav_create(res)) { result->errormsg = op->sn->errorstr ? strdup(op->sn->errorstr) : NULL; - result_callback = uithr_pathop_mkcol_error; + result_callback = uithr_pathop_create_resource_error; } ui_call_mainthread(result_callback, result); - } + } dav_resource_free(res); free(op->path[i]); @@ -1118,15 +1124,16 @@ ui_job(ui, jobthr_path_op, op, NULL, NULL); } -void davbrowser_mkcol(UiObject *ui, DavBrowser *browser, const char *name) { +void davbrowser_create_resource(UiObject *ui, DavBrowser *browser, const char *name, DavBool iscollection) { DavPathOp *op = malloc(sizeof(DavPathOp)); op->ui = ui; op->browser = browser; - op->op = DAV_PATH_OP_MKCOL; + op->op = DAV_PATH_OP_CREATE; op->sn = dav_session_clone(browser->sn); op->path = calloc(1, sizeof(char*)); op->list_indices = calloc(1, sizeof(size_t)); op->nelm = 1; + op->iscollection = iscollection; op->path[0] = util_concat_path(browser->current->path, name); @@ -1135,3 +1142,11 @@ ui_job(ui, jobthr_path_op, op, NULL, NULL); } + +void davbrowser_mkcol(UiObject *ui, DavBrowser *browser, const char *name) { + davbrowser_create_resource(ui, browser, name, TRUE); +} + +void davbrowser_newfile(UiObject *ui, DavBrowser *browser, const char *name) { + davbrowser_create_resource(ui, browser, name, FALSE); +}
--- a/application/davcontroller.h Sun Sep 29 20:25:41 2024 +0200 +++ b/application/davcontroller.h Thu Oct 03 18:08:29 2024 +0200 @@ -62,7 +62,10 @@ void davbrowser_delete(UiObject *ui, DavBrowser *browser, UiListSelection selection); +void davbrowser_create_resource(UiObject *ui, DavBrowser *browser, const char *name, DavBool iscollection); + void davbrowser_mkcol(UiObject *ui, DavBrowser *browser, const char *name); +void davbrowser_newfile(UiObject *ui, DavBrowser *browser, const char *name); #ifdef __cplusplus }
--- a/ui/gtk/headerbar.c Sun Sep 29 20:25:41 2024 +0200 +++ b/ui/gtk/headerbar.c Thu Oct 03 18:08:29 2024 +0200 @@ -58,6 +58,11 @@ ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT); ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER); + + UiToolbarMenuItem *appmenu = uic_get_appmenu(); + if(appmenu) { + ui_add_headerbar_menu(headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT); } @@ -163,6 +168,10 @@ gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), item->args.icon); } + if(!item->args.label && !item->args.icon) { + gtk_menu_button_set_icon_name(GTK_MENU_BUTTON(menubutton), "open-menu-symbolic"); + } + GMenu *menu = g_menu_new(); ui_gmenu_add_menu_items(menu, 0, &item->menu, obj);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/gtk/list.c Thu Oct 03 18:08:29 2024 +0200 @@ -0,0 +1,666 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include "../common/context.h" +#include "../common/object.h" +#include "container.h" + +#include "list.h" +#include "image.h" + + +void* ui_strmodel_getvalue(void *elm, int column) { + return column == 0 ? elm : NULL; +} + +static GtkListStore* create_list_store(UiList *list, UiModel *model) { + int columns = model->columns; + GType types[2*columns]; + int c = 0; + for(int i=0;i<columns;i++,c++) { + switch(model->types[i]) { + case UI_STRING: + case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; + case UI_INTEGER: types[c] = G_TYPE_INT; break; + case UI_ICON: types[c] = G_TYPE_OBJECT; break; + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + types[c] = G_TYPE_OBJECT; + types[++c] = G_TYPE_STRING; + } + } + } + + GtkListStore *store = gtk_list_store_newv(c, types); + + if(list) { + void *elm = list->first(list); + while(elm) { + // insert new row + GtkTreeIter iter; + gtk_list_store_insert (store, &iter, -1); + + // set column values + int c = 0; + for(int i=0;i<columns;i++,c++) { + void *data = model->getvalue(elm, c); + + GValue value = G_VALUE_INIT; + switch(model->types[i]) { + case UI_STRING: + case UI_STRING_FREE: { + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, data); + if(model->types[i] == UI_STRING_FREE) { + free(data); + } + break; + } + case UI_INTEGER: { + g_value_init(&value, G_TYPE_INT); + int *intptr = data; + g_value_set_int(&value, *intptr); + break; + } + case UI_ICON: { + g_value_init(&value, G_TYPE_OBJECT); + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + g_value_set_object(&value, icon->info); // TODO: does this work? +#else + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + + if(icon->pixbuf) { + g_value_set_object(&value, icon->pixbuf); + } +#endif + break; + } + case UI_ICON_TEXT: + case UI_ICON_TEXT_FREE: { + UiIcon *icon = data; +#if GTK_MAJOR_VERSION >= 4 + GValue iconvalue = G_VALUE_INIT; + g_value_init(&iconvalue, G_TYPE_OBJECT); + g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); + gtk_list_store_set_value(store, &iter, c, &iconvalue); +#else + GValue pixbufvalue = G_VALUE_INIT; + if(!icon->pixbuf && icon->info) { + GError *error = NULL; + GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); + icon->pixbuf = pixbuf; + } + g_value_init(&pixbufvalue, G_TYPE_OBJECT); + g_value_set_object(&pixbufvalue, icon->pixbuf); + gtk_list_store_set_value(store, &iter, c, &pixbufvalue); +#endif + c++; + + char *str = model->getvalue(elm, c); + g_value_init(&value, G_TYPE_STRING); + g_value_set_string(&value, str); + if(model->types[i] == UI_ICON_TEXT_FREE) { + free(str); + } + break; + } + } + + gtk_list_store_set_value(store, &iter, c, &value); + } + + // next row + elm = list->next(list); + } + } + + return store; +} + + +UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create treeview + GtkWidget *view = gtk_tree_view_new(); + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 +#if GTK_MINOR_VERSION >= 8 + gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + // TODO: implement for older gtk3 +#endif +#else + // TODO: implement for gtk2 +#endif + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + + UiListView *listview = malloc(sizeof(UiListView)); + listview->obj = obj; + listview->widget = view; + listview->var = var; + listview->model = model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + listview); + + // bind var + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->obj = listview; + + // add callback + if(args.onactivate) { + UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); + event->obj = obj; + event->activatedata = args.onactivatedata; + event->activate = args.onactivate; + event->selection = NULL; + + g_signal_connect( + view, + "row-activated", + G_CALLBACK(ui_listview_activate_event), + event); + } + + // add widget to the current container + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + +/* +static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { + printf("drag begin\n"); + +} + +static void drag_end( + GtkWidget *widget, + GdkDragContext *context, + guint time, + gpointer udata) +{ + printf("drag end\n"); + +} +*/ + +/* +static GtkTargetEntry targetentries[] = + { + { "STRING", 0, 0 }, + { "text/plain", 0, 1 }, + { "text/uri-list", 0, 2 }, + }; +*/ + +UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + // create treeview + GtkWidget *view = gtk_tree_view_new(); + + UiModel *model = args.model; + int columns = model ? model->columns : 0; + + int addi = 0; + for(int i=0;i<columns;i++) { + GtkTreeViewColumn *column = NULL; + if(model->types[i] == UI_ICON_TEXT) { + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, model->titles[i]); + + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); + + gtk_tree_view_column_pack_end(column, textrenderer, TRUE); + gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); + + + gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); + gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); + + addi++; + } else if (model->types[i] == UI_ICON) { + GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + iconrenderer, + "pixbuf", + i + addi, + NULL); + } else { + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + model->titles[i], + renderer, + "text", + i + addi, + NULL); + } + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); + } + + //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); +#ifdef UI_GTK3 + //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); +#else + +#endif + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); + + //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); + //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); + + // add TreeView as observer to the UiList to update the TreeView if the + // data changes + UiListView *tableview = malloc(sizeof(UiListView)); + tableview->obj = obj; + tableview->widget = view; + tableview->var = var; + tableview->model = model; + g_signal_connect( + view, + "destroy", + G_CALLBACK(ui_listview_destroy), + tableview); + + // bind var + list->update = ui_listview_update; + list->getselection = ui_listview_getselection; + list->obj = tableview; + + // add callback + UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); + event->obj = obj; + event->activate = args.onactivate; + event->selection = args.onselection; + event->activatedata = args.onactivatedata; + event->selectiondata = args.onselectiondata; + if(args.onactivate) { + g_signal_connect( + view, + "row-activated", + G_CALLBACK(ui_listview_activate_event), + event); + } + if(args.onselection) { + GtkTreeSelection *selection = gtk_tree_view_get_selection( + GTK_TREE_VIEW(view)); + g_signal_connect( + selection, + "changed", + G_CALLBACK(ui_listview_selection_event), + event); + } + // TODO: destroy callback + + + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + + // add widget to the current container + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scroll_area), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS + SCROLLEDWINDOW_SET_CHILD(scroll_area, view); + + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, FALSE); + + // ct->current should point to view, not scroll_area, to make it possible + // to add a context menu + current->container->current = view; + + return scroll_area; +} + + +GtkWidget* ui_get_tree_widget(UIWIDGET widget) { + return SCROLLEDWINDOW_GET_CHILD(widget); +} + +static char** targets2array(char *target0, va_list ap, int *nelm) { + int al = 16; + char **targets = calloc(16, sizeof(char*)); + targets[0] = target0; + + int i = 1; + char *target; + while((target = va_arg(ap, char*)) != NULL) { + if(i >= al) { + al *= 2; + targets = realloc(targets, al*sizeof(char*)); + } + targets[i] = target; + i++; + } + + *nelm = i; + return targets; +} + +/* +static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { + GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); + for(int i=0;i<nelm;i++) { + targets[i].target = str[i]; + } + return targets; +} +*/ + +void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { + va_list ap; + va_start(ap, target0); + int nelm; + char **targets = targets2array(target0, ap, &nelm); + va_end(ap); + + // disabled + //ui_table_dragsource_a(tablewidget, actions, targets, nelm); + + free(targets); +} + +/* +void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { + GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); + gtk_tree_view_enable_model_drag_source( + GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), + GDK_BUTTON1_MASK, + t, + nelm, + GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); + free(t); +} + + +void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) { + va_list ap; + va_start(ap, target0); + int nelm; + char **targets = targets2array(target0, ap, &nelm); + va_end(ap); + ui_table_dragdest_a(tablewidget, actions, targets, nelm); + free(targets); +} + +void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { + GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); + gtk_tree_view_enable_model_drag_dest( + GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), + t, + nelm, + GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); + free(t); +} +*/ + +void ui_listview_update(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(list, view->model); + gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); + g_object_unref(G_OBJECT(store)); + // TODO: free old model +} + +UiListSelection ui_listview_getselection(UiList *list) { + UiListView *view = list->obj; + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), + NULL); + return selection; +} + +void ui_listview_destroy(GtkWidget *w, UiListView *v) { + gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); + ui_destroy_boundvar(v->obj->ctx, v->var); + // TODO: destroy model? + free(v); +} + +void ui_combobox_destroy(GtkWidget *w, UiListView *v) { + gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL); + ui_destroy_boundvar(v->obj->ctx, v->var); + // TODO: destroy model? + free(v); +} + + +void ui_listview_activate_event( + GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection( + gtk_tree_view_get_selection(treeview), + event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->activate(&e, event->activatedata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event) +{ + UiListSelection selection = ui_listview_selection(treeselection, event); + + UiEvent e; + e.obj = event->obj; + e.window = event->obj->window; + e.document = event->obj->ctx->document; + e.eventdata = &selection; + e.intval = selection.count > 0 ? selection.rows[0] : -1; + event->selection(&e, event->selectiondata); + + if(selection.count > 0) { + free(selection.rows); + } +} + +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event) +{ + GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); + + UiListSelection ls; + ls.count = g_list_length(rows); + ls.rows = calloc(ls.count, sizeof(int)); + GList *r = rows; + int i = 0; + while(r) { + GtkTreePath *path = r->data; + ls.rows[i] = ui_tree_path_list_index(path); + r = r->next; + i++; + } + return ls; +} + +int ui_tree_path_list_index(GtkTreePath *path) { + int depth = gtk_tree_path_get_depth(path); + if(depth == 0) { + fprintf(stderr, "UiError: treeview selection: depth == 0\n"); + return -1; + } + int *indices = gtk_tree_path_get_indices(path); + return indices[depth - 1]; +} + + +/* --------------------------- ComboBox --------------------------- */ + +UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { + UiObject* current = uic_current_obj(obj); + + UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); + model->getvalue = args.getvalue; + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); + + GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, combobox, FALSE); + current->container->current = combobox; + return combobox; +} + +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { + GtkWidget *combobox = gtk_combo_box_new(); + + UiListView *uicbox = malloc(sizeof(UiListView)); + uicbox->obj = obj; + uicbox->widget = combobox; + + UiList *list = var ? var->value : NULL; + GtkListStore *listmodel = create_list_store(list, model); + + if(listmodel) { + gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); + } + + uicbox->var = var; + uicbox->model = model; + + g_signal_connect( + combobox, + "destroy", + G_CALLBACK(ui_combobox_destroy), + uicbox); + + // bind var + if(list) { + list->update = ui_combobox_modelupdate; + // TODO: combobox getselection + list->obj = uicbox; + } + + GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); + gtk_cell_layout_set_attributes( + GTK_CELL_LAYOUT(combobox), + renderer, + "text", + 0, + NULL); + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); + + // add callback + if(f) { + UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); + event->obj = obj; + event->userdata = udata; + event->callback = f; + event->value = 0; + event->customdata = NULL; + + g_signal_connect( + combobox, + "changed", + G_CALLBACK(ui_combobox_change_event), + event); + } + + return combobox; +} + +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { + UiEvent event; + event.obj = e->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.intval = gtk_combo_box_get_active(widget); + e->callback(&event, e->userdata); +} + +void ui_combobox_modelupdate(UiList *list, int i) { + UiListView *view = list->obj; + GtkListStore *store = create_list_store(view->var->value, view->model); + gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/gtk/list.h Thu Oct 03 18:08:29 2024 +0200 @@ -0,0 +1,90 @@ +/* + * 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 TREE_H +#define TREE_H + +#include "../ui/tree.h" +#include "toolkit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct UiListView { + UiObject *obj; + GtkWidget *widget; + UiVar *var; + UiModel *model; +} UiListView; + +typedef struct UiTreeEventData { + UiObject *obj; + ui_callback activate; + ui_callback selection; + void *activatedata; + void *selectiondata; +} UiTreeEventData; + +void* ui_strmodel_getvalue(void *elm, int column); + +UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb); + +GtkWidget* ui_get_tree_widget(UIWIDGET widget); + +void ui_listview_update(UiList *list, int i); +UiListSelection ui_listview_getselection(UiList *list); + +void ui_combobox_destroy(GtkWidget *w, UiListView *v); +void ui_listview_destroy(GtkWidget *w, UiListView *v); + +void ui_listview_activate_event( + GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + UiTreeEventData *event); +void ui_listview_selection_event( + GtkTreeSelection *treeselection, + UiTreeEventData *event); +UiListSelection ui_listview_selection( + GtkTreeSelection *selection, + UiTreeEventData *event); +int ui_tree_path_list_index(GtkTreePath *path); + +UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); +GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata); +void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); +void ui_combobox_modelupdate(UiList *list, int i); + +#ifdef __cplusplus +} +#endif + +#endif /* TREE_H */ +
--- a/ui/gtk/menu.c Sun Sep 29 20:25:41 2024 +0200 +++ b/ui/gtk/menu.c Thu Oct 03 18:08:29 2024 +0200 @@ -522,10 +522,22 @@ void ui_gmenu_add_menu_items(GMenu *parent, int i, UiMenu *menu, UiObject *obj) { UiMenuItemI *it = menu->items_begin; int index = 0; + int index_section = 0; + GMenu *section = NULL; while(it) { - createMenuItem[it->type](parent, index, it, obj); + if(it->type == UI_MENU_SEPARATOR) { + section = g_menu_new(); + g_menu_append_section(parent, NULL, G_MENU_MODEL(section)); + index++; + index_section = 0; + } else { + if(section) { + createMenuItem[it->type](section, index_section++, it, obj); + } else { + createMenuItem[it->type](parent, index++, it, obj); + } + } it = it->next; - index++; } } @@ -563,7 +575,7 @@ } char action_name[32]; - snprintf(action_name, 32, "win.%s", item->id); + snprintf(action_name, 32, "win.%s", item->id); g_menu_append(parent, i->label, action_name); }
--- a/ui/gtk/objs.mk Sun Sep 29 20:25:41 2024 +0200 +++ b/ui/gtk/objs.mk Thu Oct 03 18:08:29 2024 +0200 @@ -38,7 +38,7 @@ GTKOBJ += button.o GTKOBJ += display.o GTKOBJ += text.o -GTKOBJ += tree.o +GTKOBJ += list.o GTKOBJ += image.o GTKOBJ += graphics.o GTKOBJ += range.o
--- a/ui/gtk/toolbar.c Sun Sep 29 20:25:41 2024 +0200 +++ b/ui/gtk/toolbar.c Thu Oct 03 18:08:29 2024 +0200 @@ -34,7 +34,7 @@ #include "menu.h" #include "button.h" #include "image.h" -#include "tree.h" +#include "list.h" #include <cx/mempool.h> #include <cx/hash_map.h> #include <cx/linked_list.h>
--- a/ui/gtk/toolbar.h Sun Sep 29 20:25:41 2024 +0200 +++ b/ui/gtk/toolbar.h Thu Oct 03 18:08:29 2024 +0200 @@ -34,7 +34,7 @@ #include <cx/map.h> #include <cx/list.h> -#include "tree.h" +#include "list.h" #ifdef __cplusplus extern "C" {
--- a/ui/gtk/tree.c Sun Sep 29 20:25:41 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,666 +0,0 @@ -/* - * 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 <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <stdarg.h> - -#include "../common/context.h" -#include "../common/object.h" -#include "container.h" - -#include "tree.h" -#include "image.h" - - -void* ui_strmodel_getvalue(void *elm, int column) { - return column == 0 ? elm : NULL; -} - -static GtkListStore* create_list_store(UiList *list, UiModel *model) { - int columns = model->columns; - GType types[2*columns]; - int c = 0; - for(int i=0;i<columns;i++,c++) { - switch(model->types[i]) { - case UI_STRING: - case UI_STRING_FREE: types[c] = G_TYPE_STRING; break; - case UI_INTEGER: types[c] = G_TYPE_INT; break; - case UI_ICON: types[c] = G_TYPE_OBJECT; break; - case UI_ICON_TEXT: - case UI_ICON_TEXT_FREE: { - types[c] = G_TYPE_OBJECT; - types[++c] = G_TYPE_STRING; - } - } - } - - GtkListStore *store = gtk_list_store_newv(c, types); - - if(list) { - void *elm = list->first(list); - while(elm) { - // insert new row - GtkTreeIter iter; - gtk_list_store_insert (store, &iter, -1); - - // set column values - int c = 0; - for(int i=0;i<columns;i++,c++) { - void *data = model->getvalue(elm, c); - - GValue value = G_VALUE_INIT; - switch(model->types[i]) { - case UI_STRING: - case UI_STRING_FREE: { - g_value_init(&value, G_TYPE_STRING); - g_value_set_string(&value, data); - if(model->types[i] == UI_STRING_FREE) { - free(data); - } - break; - } - case UI_INTEGER: { - g_value_init(&value, G_TYPE_INT); - int *intptr = data; - g_value_set_int(&value, *intptr); - break; - } - case UI_ICON: { - g_value_init(&value, G_TYPE_OBJECT); - UiIcon *icon = data; -#if GTK_MAJOR_VERSION >= 4 - g_value_set_object(&value, icon->info); // TODO: does this work? -#else - if(!icon->pixbuf && icon->info) { - GError *error = NULL; - GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); - icon->pixbuf = pixbuf; - } - - if(icon->pixbuf) { - g_value_set_object(&value, icon->pixbuf); - } -#endif - break; - } - case UI_ICON_TEXT: - case UI_ICON_TEXT_FREE: { - UiIcon *icon = data; -#if GTK_MAJOR_VERSION >= 4 - GValue iconvalue = G_VALUE_INIT; - g_value_init(&iconvalue, G_TYPE_OBJECT); - g_value_set_object(&iconvalue, ui_icon_pixbuf(icon)); - gtk_list_store_set_value(store, &iter, c, &iconvalue); -#else - GValue pixbufvalue = G_VALUE_INIT; - if(!icon->pixbuf && icon->info) { - GError *error = NULL; - GdkPixbuf *pixbuf = gtk_icon_info_load_icon(icon->info, &error); - icon->pixbuf = pixbuf; - } - g_value_init(&pixbufvalue, G_TYPE_OBJECT); - g_value_set_object(&pixbufvalue, icon->pixbuf); - gtk_list_store_set_value(store, &iter, c, &pixbufvalue); -#endif - c++; - - char *str = model->getvalue(elm, c); - g_value_init(&value, G_TYPE_STRING); - g_value_set_string(&value, str); - if(model->types[i] == UI_ICON_TEXT_FREE) { - free(str); - } - break; - } - } - - gtk_list_store_set_value(store, &iter, c, &value); - } - - // next row - elm = list->next(list); - } - } - - return store; -} - - -UIWIDGET ui_listview_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - // create treeview - GtkWidget *view = gtk_tree_view_new(); - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL); - gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); - - gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); -#ifdef UI_GTK3 -#if GTK_MINOR_VERSION >= 8 - gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); -#else - // TODO: implement for older gtk3 -#endif -#else - // TODO: implement for gtk2 -#endif - - UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - model->getvalue = args.getvalue; - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); - - UiListView *listview = malloc(sizeof(UiListView)); - listview->obj = obj; - listview->widget = view; - listview->var = var; - listview->model = model; - g_signal_connect( - view, - "destroy", - G_CALLBACK(ui_listview_destroy), - listview); - - // bind var - list->update = ui_listview_update; - list->getselection = ui_listview_getselection; - list->obj = listview; - - // add callback - if(args.onactivate) { - UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); - event->obj = obj; - event->activatedata = args.onactivatedata; - event->activate = args.onactivate; - event->selection = NULL; - - g_signal_connect( - view, - "row-activated", - G_CALLBACK(ui_listview_activate_event), - event); - } - - // add widget to the current container - GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); - gtk_scrolled_window_set_policy( - GTK_SCROLLED_WINDOW(scroll_area), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS - SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, scroll_area, FALSE); - - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; - - return scroll_area; -} - -/* -static void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer udata) { - printf("drag begin\n"); - -} - -static void drag_end( - GtkWidget *widget, - GdkDragContext *context, - guint time, - gpointer udata) -{ - printf("drag end\n"); - -} -*/ - -/* -static GtkTargetEntry targetentries[] = - { - { "STRING", 0, 0 }, - { "text/plain", 0, 1 }, - { "text/uri-list", 0, 2 }, - }; -*/ - -UIWIDGET ui_table_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - // create treeview - GtkWidget *view = gtk_tree_view_new(); - - UiModel *model = args.model; - int columns = model ? model->columns : 0; - - int addi = 0; - for(int i=0;i<columns;i++) { - GtkTreeViewColumn *column = NULL; - if(model->types[i] == UI_ICON_TEXT) { - column = gtk_tree_view_column_new(); - gtk_tree_view_column_set_title(column, model->titles[i]); - - GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); - GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); - - gtk_tree_view_column_pack_end(column, textrenderer, TRUE); - gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); - - - gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); - gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); - - addi++; - } else if (model->types[i] == UI_ICON) { - GtkCellRenderer *iconrenderer = gtk_cell_renderer_pixbuf_new(); - column = gtk_tree_view_column_new_with_attributes( - model->titles[i], - iconrenderer, - "pixbuf", - i + addi, - NULL); - } else { - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - column = gtk_tree_view_column_new_with_attributes( - model->titles[i], - renderer, - "text", - i + addi, - NULL); - } - gtk_tree_view_column_set_resizable(column, TRUE); - gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); - } - - //gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE); -#ifdef UI_GTK3 - //gtk_tree_view_set_activate_on_single_click(GTK_TREE_VIEW(view), TRUE); -#else - -#endif - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel)); - - //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL); - //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL); - - // add TreeView as observer to the UiList to update the TreeView if the - // data changes - UiListView *tableview = malloc(sizeof(UiListView)); - tableview->obj = obj; - tableview->widget = view; - tableview->var = var; - tableview->model = model; - g_signal_connect( - view, - "destroy", - G_CALLBACK(ui_listview_destroy), - tableview); - - // bind var - list->update = ui_listview_update; - list->getselection = ui_listview_getselection; - list->obj = tableview; - - // add callback - UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData)); - event->obj = obj; - event->activate = args.onactivate; - event->selection = args.onselection; - event->activatedata = args.onactivatedata; - event->selectiondata = args.onselectiondata; - if(args.onactivate) { - g_signal_connect( - view, - "row-activated", - G_CALLBACK(ui_listview_activate_event), - event); - } - if(args.onselection) { - GtkTreeSelection *selection = gtk_tree_view_get_selection( - GTK_TREE_VIEW(view)); - g_signal_connect( - selection, - "changed", - G_CALLBACK(ui_listview_selection_event), - event); - } - // TODO: destroy callback - - - GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view)); - gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); - - // add widget to the current container - GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); - gtk_scrolled_window_set_policy( - GTK_SCROLLED_WINDOW(scroll_area), - GTK_POLICY_AUTOMATIC, - GTK_POLICY_AUTOMATIC); // GTK_POLICY_ALWAYS - SCROLLEDWINDOW_SET_CHILD(scroll_area, view); - - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, scroll_area, FALSE); - - // ct->current should point to view, not scroll_area, to make it possible - // to add a context menu - current->container->current = view; - - return scroll_area; -} - - -GtkWidget* ui_get_tree_widget(UIWIDGET widget) { - return SCROLLEDWINDOW_GET_CHILD(widget); -} - -static char** targets2array(char *target0, va_list ap, int *nelm) { - int al = 16; - char **targets = calloc(16, sizeof(char*)); - targets[0] = target0; - - int i = 1; - char *target; - while((target = va_arg(ap, char*)) != NULL) { - if(i >= al) { - al *= 2; - targets = realloc(targets, al*sizeof(char*)); - } - targets[i] = target; - i++; - } - - *nelm = i; - return targets; -} - -/* -static GtkTargetEntry* targetstr2gtktargets(char **str, int nelm) { - GtkTargetEntry *targets = calloc(nelm, sizeof(GtkTargetEntry)); - for(int i=0;i<nelm;i++) { - targets[i].target = str[i]; - } - return targets; -} -*/ - -void ui_table_dragsource(UIWIDGET tablewidget, int actions, char *target0, ...) { - va_list ap; - va_start(ap, target0); - int nelm; - char **targets = targets2array(target0, ap, &nelm); - va_end(ap); - - // disabled - //ui_table_dragsource_a(tablewidget, actions, targets, nelm); - - free(targets); -} - -/* -void ui_table_dragsource_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { - GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); - gtk_tree_view_enable_model_drag_source( - GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), - GDK_BUTTON1_MASK, - t, - nelm, - GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); - free(t); -} - - -void ui_table_dragdest(UIWIDGET tablewidget, int actions, char *target0, ...) { - va_list ap; - va_start(ap, target0); - int nelm; - char **targets = targets2array(target0, ap, &nelm); - va_end(ap); - ui_table_dragdest_a(tablewidget, actions, targets, nelm); - free(targets); -} - -void ui_table_dragdest_a(UIWIDGET tablewidget, int actions, char **targets, int nelm) { - GtkTargetEntry* t = targetstr2gtktargets(targets, nelm); - gtk_tree_view_enable_model_drag_dest( - GTK_TREE_VIEW(ui_get_tree_widget(tablewidget)), - t, - nelm, - GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK); - free(t); -} -*/ - -void ui_listview_update(UiList *list, int i) { - UiListView *view = list->obj; - GtkListStore *store = create_list_store(list, view->model); - gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store)); - g_object_unref(G_OBJECT(store)); - // TODO: free old model -} - -UiListSelection ui_listview_getselection(UiList *list) { - UiListView *view = list->obj; - UiListSelection selection = ui_listview_selection( - gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget)), - NULL); - return selection; -} - -void ui_listview_destroy(GtkWidget *w, UiListView *v) { - gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL); - ui_destroy_boundvar(v->obj->ctx, v->var); - // TODO: destroy model? - free(v); -} - -void ui_combobox_destroy(GtkWidget *w, UiListView *v) { - gtk_combo_box_set_model(GTK_COMBO_BOX(w), NULL); - ui_destroy_boundvar(v->obj->ctx, v->var); - // TODO: destroy model? - free(v); -} - - -void ui_listview_activate_event( - GtkTreeView *treeview, - GtkTreePath *path, - GtkTreeViewColumn *column, - UiTreeEventData *event) -{ - UiListSelection selection = ui_listview_selection( - gtk_tree_view_get_selection(treeview), - event); - - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - e.document = event->obj->ctx->document; - e.eventdata = &selection; - e.intval = selection.count > 0 ? selection.rows[0] : -1; - event->activate(&e, event->activatedata); - - if(selection.count > 0) { - free(selection.rows); - } -} - -void ui_listview_selection_event( - GtkTreeSelection *treeselection, - UiTreeEventData *event) -{ - UiListSelection selection = ui_listview_selection(treeselection, event); - - UiEvent e; - e.obj = event->obj; - e.window = event->obj->window; - e.document = event->obj->ctx->document; - e.eventdata = &selection; - e.intval = selection.count > 0 ? selection.rows[0] : -1; - event->selection(&e, event->selectiondata); - - if(selection.count > 0) { - free(selection.rows); - } -} - -UiListSelection ui_listview_selection( - GtkTreeSelection *selection, - UiTreeEventData *event) -{ - GList *rows = gtk_tree_selection_get_selected_rows(selection, NULL); - - UiListSelection ls; - ls.count = g_list_length(rows); - ls.rows = calloc(ls.count, sizeof(int)); - GList *r = rows; - int i = 0; - while(r) { - GtkTreePath *path = r->data; - ls.rows[i] = ui_tree_path_list_index(path); - r = r->next; - i++; - } - return ls; -} - -int ui_tree_path_list_index(GtkTreePath *path) { - int depth = gtk_tree_path_get_depth(path); - if(depth == 0) { - fprintf(stderr, "UiError: treeview selection: depth == 0\n"); - return -1; - } - int *indices = gtk_tree_path_get_indices(path); - return indices[depth - 1]; -} - - -/* --------------------------- ComboBox --------------------------- */ - -UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs args) { - UiObject* current = uic_current_obj(obj); - - UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); - model->getvalue = args.getvalue; - - UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.list, args.varname, UI_VAR_LIST); - - GtkWidget *combobox = ui_create_combobox(obj, model, var, args.onactivate, args.onactivatedata); - UI_APPLY_LAYOUT1(current, args); - current->container->add(current->container, combobox, FALSE); - current->container->current = combobox; - return combobox; -} - -GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata) { - GtkWidget *combobox = gtk_combo_box_new(); - - UiListView *uicbox = malloc(sizeof(UiListView)); - uicbox->obj = obj; - uicbox->widget = combobox; - - UiList *list = var ? var->value : NULL; - GtkListStore *listmodel = create_list_store(list, model); - - if(listmodel) { - gtk_combo_box_set_model(GTK_COMBO_BOX(combobox), GTK_TREE_MODEL(listmodel)); - } - - uicbox->var = var; - uicbox->model = model; - - g_signal_connect( - combobox, - "destroy", - G_CALLBACK(ui_combobox_destroy), - uicbox); - - // bind var - if(list) { - list->update = ui_combobox_modelupdate; - // TODO: combobox getselection - list->obj = uicbox; - } - - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE); - gtk_cell_layout_set_attributes( - GTK_CELL_LAYOUT(combobox), - renderer, - "text", - 0, - NULL); - gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0); - - // add callback - if(f) { - UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData)); - event->obj = obj; - event->userdata = udata; - event->callback = f; - event->value = 0; - event->customdata = NULL; - - g_signal_connect( - combobox, - "changed", - G_CALLBACK(ui_combobox_change_event), - event); - } - - return combobox; -} - -void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e) { - UiEvent event; - event.obj = e->obj; - event.window = event.obj->window; - event.document = event.obj->ctx->document; - event.eventdata = NULL; - event.intval = gtk_combo_box_get_active(widget); - e->callback(&event, e->userdata); -} - -void ui_combobox_modelupdate(UiList *list, int i) { - UiListView *view = list->obj; - GtkListStore *store = create_list_store(view->var->value, view->model); - gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store)); -} -
--- a/ui/gtk/tree.h Sun Sep 29 20:25:41 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/* - * 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 TREE_H -#define TREE_H - -#include "../ui/tree.h" -#include "toolkit.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct UiListView { - UiObject *obj; - GtkWidget *widget; - UiVar *var; - UiModel *model; -} UiListView; - -typedef struct UiTreeEventData { - UiObject *obj; - ui_callback activate; - ui_callback selection; - void *activatedata; - void *selectiondata; -} UiTreeEventData; - -void* ui_strmodel_getvalue(void *elm, int column); - -UIWIDGET ui_listview_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); -UIWIDGET ui_table_var(UiObject *obj, UiVar *var, UiModel *model, UiListCallbacks cb); - -GtkWidget* ui_get_tree_widget(UIWIDGET widget); - -void ui_listview_update(UiList *list, int i); -UiListSelection ui_listview_getselection(UiList *list); - -void ui_combobox_destroy(GtkWidget *w, UiListView *v); -void ui_listview_destroy(GtkWidget *w, UiListView *v); - -void ui_listview_activate_event( - GtkTreeView *tree_view, - GtkTreePath *path, - GtkTreeViewColumn *column, - UiTreeEventData *event); -void ui_listview_selection_event( - GtkTreeSelection *treeselection, - UiTreeEventData *event); -UiListSelection ui_listview_selection( - GtkTreeSelection *selection, - UiTreeEventData *event); -int ui_tree_path_list_index(GtkTreePath *path); - -UIWIDGET ui_combobox_var(UiObject *obj, UiVar *var, ui_getvaluefunc getvalue, ui_callback f, void *udata); -GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, ui_callback f, void *udata); -void ui_combobox_change_event(GtkComboBox *widget, UiEventData *e); -void ui_combobox_modelupdate(UiList *list, int i); - -#ifdef __cplusplus -} -#endif - -#endif /* TREE_H */ -
--- a/ui/gtk/window.c Sun Sep 29 20:25:41 2024 +0200 +++ b/ui/gtk/window.c Thu Oct 03 18:08:29 2024 +0200 @@ -190,6 +190,74 @@ height); } +#ifdef UI_LIBADWAITA + +static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) { + UiEvent evt; + evt.obj = data->obj; + evt.document = evt.obj->ctx->document; + evt.window = evt.obj->window; + evt.eventdata = NULL; + evt.intval = 0; + + if(!strcmp(response, "btn1")) { + evt.intval = 1; + } else if(!strcmp(response, "btn2")) { + evt.intval = 2; + } + + if(data->customdata) { + GtkWidget *entry = data->customdata; + evt.eventdata = (void*)ENTRY_GET_TEXT(GTK_ENTRY(entry)); + } + + if(data->callback) { + data->callback(&evt, data->userdata); + } +} + +void ui_dialog_create(UiObject *parent, UiDialogArgs args) { + AdwDialog *dialog = adw_alert_dialog_new(args.title, args.content); + UiEventData *event = malloc(sizeof(UiEventData)); + event->callback = args.result; + event->userdata = args.resultdata; + event->customdata = NULL; + event->value = 0; + event->obj = parent; + + if(args.button1_label) { + adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn1", args.button1_label); + } + if(args.button2_label) { + adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "btn2", args.button2_label); + } + if(args.closebutton_label) { + adw_alert_dialog_add_response(ADW_ALERT_DIALOG(dialog), "close", args.closebutton_label); + adw_alert_dialog_set_close_response(ADW_ALERT_DIALOG(dialog), "close"); + } + + GtkWidget *entry = NULL; + if(args.input) { + entry = gtk_entry_new(); + adw_alert_dialog_set_extra_child(ADW_ALERT_DIALOG(dialog), entry); + event->customdata = entry; + } + + g_signal_connect( + dialog, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + g_signal_connect(dialog, "response", G_CALLBACK(dialog_response), event); + adw_dialog_present(dialog, parent->widget); + + if(entry) { + gtk_entry_grab_focus_without_selecting(GTK_ENTRY(entry)); + } +} +#else + static void ui_dialog_response (GtkDialog* self, gint response_id, gpointer user_data) { UiEventData *data = user_data; UiEvent evt; @@ -219,6 +287,9 @@ void ui_dialog_create(UiObject *parent, UiDialogArgs args) { GtkDialog *dialog = GTK_DIALOG(gtk_dialog_new()); + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget)); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + GtkWidget *dialog_w = GTK_WIDGET(dialog); if(args.title) { gtk_window_set_title(GTK_WINDOW(dialog), args.title); @@ -259,6 +330,7 @@ WINDOW_SHOW(GTK_WIDGET(dialog_w)); } +#endif #if GTK_MAJOR_VERSION >= 3