implement listview onsave handler (GTK)

Mon, 29 Sep 2025 20:16:07 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 29 Sep 2025 20:16:07 +0200
changeset 778
85b6cef7fcba
parent 777
622efebfab37
child 779
b84cbe57e0bd

implement listview onsave handler (GTK)

application/main.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/ui/tree.h file | annotate | diff | comparison | revisions
--- a/application/main.c	Sun Sep 28 20:09:08 2025 +0200
+++ b/application/main.c	Mon Sep 29 20:16:07 2025 +0200
@@ -570,6 +570,11 @@
     ui_linkbutton_value_set(doc->link, label, uri);
 }
 
+static UiBool list_save(UiList *list, int row, int col, UiCellValue value, void *userdata) {
+    printf("list new value at [%d, %d]: %s\n", row, col, value.string);
+    return FALSE;
+}
+
 void application_startup(UiEvent *event, void *data) {
     // global list
     UiContext *global = ui_global_context();
@@ -658,7 +663,7 @@
             UiModel *model = ui_model(obj->ctx, UI_ICON_TEXT, "col1", UI_INTEGER, "col2", UI_ICON, "col3", UI_ICON_TEXT, "col4", UI_INTEGER, "col5", UI_STRING_EDITABLE, "edit6", UI_STRING_EDITABLE, "edit7", -1);
             model->columnsize[0] = -1;
             ui_table(obj, .model = model, .list = doc->list2, .colspan = 2, .fill = TRUE, .contextmenu = menubuilder, .multiselection = TRUE, .fill = TRUE,
-                    .getvalue = table_getvalue, .getstyle = table_getstyle,
+                    .getvalue = table_getvalue, .getstyle = table_getstyle, .onsave = list_save,
                     .onactivate = action_table_activate, .onactivatedata = "activate",
                     .onselection = action_table_activate, .onselectiondata = "selection");
             ui_hbox(obj, .fill = FALSE) {
--- a/ui/gtk/list.c	Sun Sep 28 20:09:08 2025 +0200
+++ b/ui/gtk/list.c	Mon Sep 29 20:16:07 2025 +0200
@@ -95,6 +95,8 @@
     tableview->current_row = -1;
     tableview->getstyle = args->getstyle;
     tableview->getstyledata = args->getstyledata;
+    tableview->onsave = args->onsave;
+    tableview->onsavedata = args->onsavedata;
     
     if(args->getvalue2) {
         tableview->getvalue = args->getvalue2;
@@ -143,6 +145,57 @@
 
 /* END GObject wrapper for generic pointers */
 
+typedef struct UiCellEntry {
+    GtkEntry *entry;
+    UiListView *listview;
+    char *previous_value;
+    int row;
+    int col;
+} UiCellEntry;
+
+static void cell_save_value(UiCellEntry *data, int restore) {
+    if(data->listview && data->listview->onsave) {
+        UiVar *var = data->listview->var;
+        UiList *list = var ? var->value : NULL;
+        const char *str = ENTRY_GET_TEXT(data->entry);
+        UiCellValue value;
+        value.string = str;
+        if(data->listview->onsave(list, data->row, data->col, value, data->listview->onsavedata)) {
+            free(data->previous_value);
+            data->previous_value = strdup(str);
+        } else if(restore) {
+            ENTRY_SET_TEXT(data->entry, data->previous_value);
+        }
+    }
+}
+
+static void cell_entry_leave_focus(
+        GtkEventControllerFocus *self,
+        UiCellEntry *data)
+{
+    // TODO: use a different singal to track focus
+    //       we only want to call cell_save_value, when another entry is selected,
+    //       not when the window loses focus or something like that
+    cell_save_value(data, TRUE);
+}
+
+static void cell_entry_destroy(GtkWidget *object, UiCellEntry *data) {
+    free(data->previous_value);
+    free(data);
+}
+
+static void cell_entry_unmap(GtkWidget *w, UiCellEntry *data) {
+    const char *text = ENTRY_GET_TEXT(w);
+    cell_save_value(data, FALSE);
+}
+
+static void cell_entry_activate(
+        GtkEntry *self,
+        UiCellEntry *data)
+{
+    cell_save_value(data, TRUE);
+}
+
 static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
     UiColData *col = userdata;
     UiModel *model = col->listview->model;
@@ -163,6 +216,34 @@
         GtkWidget *textfield = gtk_entry_new();
         gtk_widget_add_css_class(textfield, "ui-table-entry");
         gtk_list_item_set_child(item, textfield);
+        
+        UiCellEntry *entry_data = malloc(sizeof(UiCellEntry));
+        entry_data->entry = GTK_ENTRY(textfield);
+        entry_data->listview = NULL;
+        entry_data->previous_value = NULL;
+        entry_data->col = 0;
+        entry_data->row = 0;
+        g_object_set_data(G_OBJECT(textfield), "ui_entry_data", entry_data);
+        
+        g_signal_connect(
+                textfield,
+                "destroy",
+                G_CALLBACK(cell_entry_destroy),
+                entry_data);
+        g_signal_connect(
+                textfield,
+                "activate",
+                G_CALLBACK(cell_entry_activate),
+                entry_data);
+        g_signal_connect(
+                textfield,
+                "unmap",
+                G_CALLBACK(cell_entry_unmap),
+                entry_data);
+        
+        GtkEventController *focus_controller = gtk_event_controller_focus_new();
+        g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data);
+        gtk_widget_add_controller(textfield, focus_controller);
     } else {
         GtkWidget *label = gtk_label_new(NULL);
         gtk_label_set_xalign(GTK_LABEL(label), 0);
@@ -298,6 +379,13 @@
             break;
         }
         case UI_STRING_EDITABLE: {
+            UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data");
+            if(entry) {
+                entry->listview = col->listview;
+                entry->row = obj->i;
+                entry->col = col->data_column;
+                entry->previous_value = strdup(data);
+            }
             ENTRY_SET_TEXT(child, data);
             break;
         }
@@ -320,6 +408,15 @@
             cxMapRemove(listview->bound_rows, row_key);
         }
     } // else: should not happen
+    
+    GtkWidget *child = gtk_list_item_get_child(item);
+    UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data");
+    if(entry) {
+        cell_save_value(entry, FALSE);
+        entry->listview = NULL;
+        free(entry->previous_value);
+        entry->previous_value = NULL;
+    }
 }
     
 
--- a/ui/gtk/list.h	Sun Sep 28 20:09:08 2025 +0200
+++ b/ui/gtk/list.h	Mon Sep 29 20:16:07 2025 +0200
@@ -81,6 +81,8 @@
     void              *ondragcompletedata;
     ui_callback       ondrop;
     void              *ondropdata;
+    ui_list_savefunc  onsave;
+    void              *onsavedata;
     UiListSelection   selection;
     
 } UiListView;
--- a/ui/ui/tree.h	Sun Sep 28 20:09:08 2025 +0200
+++ b/ui/ui/tree.h	Mon Sep 29 20:16:07 2025 +0200
@@ -55,6 +55,13 @@
     UI_STRING_EDITABLE
 } UiModelType;
 
+typedef union UiCellValue {
+    const char *string;
+    int64_t i;
+} UiCellValue;
+
+typedef UiBool (*ui_list_savefunc)(UiList *list, int row, int col, UiCellValue value, void *userdata);
+
 struct UiModel {
     /*
      * number of columns
@@ -135,6 +142,8 @@
     void* ondropdata;
     UiBool multiselection;
     UiMenuBuilder *contextmenu;
+    ui_list_savefunc onsave;
+    void *onsavedata;
     
     const int *groups;
 };

mercurial