gnucash stable: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Sat Apr 18 19:49:37 EDT 2026


Updated	 via  https://github.com/Gnucash/gnucash/commit/8281853b (commit)
	 via  https://github.com/Gnucash/gnucash/commit/ff074121 (commit)
	from  https://github.com/Gnucash/gnucash/commit/87a1d41f (commit)



commit 8281853b17f3fa7b79f42ba8943cbde8301fc7e5
Merge: 87a1d41f9e ff07412179
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Apr 18 16:46:20 2026 -0700

    Merge Sherlock's 'sx-enable-from-list' into stable.


commit ff07412179d75cab11972e63a9e0827d3f49f535
Author: Sherlock <119709043+agwekixj at users.noreply.github.com>
Date:   Thu Apr 2 19:56:30 2026 -0700

    Bug 799759 - Users can't Enable entries via Checkboxes on Scheduled Transactions Page
    
    Implement the toggle of the Enabled box on the Scheduled Transactions list including use of the spacebar to toggle the Enabled box when the Enabled column is visible.  Also, added use of the Name column as the secondary column sort for all the other columns.

diff --git a/gnucash/gnome-utils/gnc-sx-list-tree-model-adapter.c b/gnucash/gnome-utils/gnc-sx-list-tree-model-adapter.c
index 6a99c0c518..60dbfc0721 100644
--- a/gnucash/gnome-utils/gnc-sx-list-tree-model-adapter.c
+++ b/gnucash/gnome-utils/gnc-sx-list-tree-model-adapter.c
@@ -342,13 +342,12 @@ gsltma_proxy_sort_column_changed (GtkTreeSortable *sortable, gpointer user_data)
     g_signal_emit_by_name (user_data, "sort-column-changed");
 }
 
-static gint
-_name_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
+static gint 
+_sort_iterators_to_instances (gpointer *user_data, GtkTreeIter *a, GtkTreeIter *b, 
+        gint (*instances_comparator)(GncSxInstances *a, GncSxInstances *b))
 {
-    gint rtn;
     GncSxListTreeModelAdapter *adapter = GNC_SX_LIST_TREE_MODEL_ADAPTER(user_data);
     GncSxInstances *a_inst, *b_inst;
-    gchar *a_caseless, *b_caseless;
 
     a_inst = gsltma_get_sx_instances_from_orig_iter (adapter, a);
     b_inst = gsltma_get_sx_instances_from_orig_iter (adapter, b);
@@ -357,9 +356,20 @@ _name_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer
     if (a_inst == NULL) return 1;
     if (b_inst == NULL) return -1;
 
+    return instances_comparator (a_inst, b_inst);
+}
+
+static gint
+_name_instances_compare (GncSxInstances *a_inst, GncSxInstances *b_inst)
+{
+    gchar *a_caseless, *b_caseless;
+    gint rtn;
+
     a_caseless = g_utf8_casefold (xaccSchedXactionGetName (a_inst->sx), -1);
     b_caseless = g_utf8_casefold (xaccSchedXactionGetName (b_inst->sx), -1);
+
     rtn = g_strcmp0 (a_caseless, b_caseless);
+
     g_free (a_caseless);
     g_free (b_caseless);
 
@@ -367,83 +377,100 @@ _name_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer
 }
 
 static gint
-_freq_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
+_name_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
 {
-    GncSxListTreeModelAdapter *adapter = GNC_SX_LIST_TREE_MODEL_ADAPTER(user_data);
-    GncSxInstances *a_inst, *b_inst;
+    return _sort_iterators_to_instances (user_data, a, b, _name_instances_compare);
+}
 
-    a_inst = gsltma_get_sx_instances_from_orig_iter (adapter, a);
-    b_inst = gsltma_get_sx_instances_from_orig_iter (adapter, b);
+static gint
+_freq_instances_compare (GncSxInstances *a_inst, GncSxInstances *b_inst)
+{
+    gint rtn;
 
-    if (a_inst == NULL && b_inst == NULL) return 0;
-    if (a_inst == NULL) return 1;
-    if (b_inst == NULL) return -1;
+    rtn = recurrenceListCmp (gnc_sx_get_schedule (a_inst->sx), gnc_sx_get_schedule (b_inst->sx));
+
+    return rtn ? rtn : _name_instances_compare (a_inst, b_inst);
+}
 
-    return recurrenceListCmp (gnc_sx_get_schedule (a_inst->sx), gnc_sx_get_schedule (b_inst->sx));
+static gint
+_freq_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
+{
+    return _sort_iterators_to_instances (user_data, a, b, _freq_instances_compare);
 }
 
 static gint
 _safe_invalidable_date_compare (const GDate *a, const GDate *b)
 {
-    if (!g_date_valid (a) && !g_date_valid (b))
-    {
-        return 0;
-    }
-    if (!g_date_valid (a))
-    {
-        return 1;
-    }
-    if (!g_date_valid (b))
-    {
-        return -1;
-    }
+    gboolean a_valid, b_valid;
+
+    a_valid = g_date_valid (a);
+    b_valid = g_date_valid (b);
+
+    if (!a_valid && !b_valid) return 0;
+    if (!a_valid) return 1;
+    if (!b_valid) return -1;
+
     return g_date_compare (a, b);
 }
 
+static gint
+_last_occur_instances_compare (GncSxInstances *a_inst, GncSxInstances *b_inst)
+{
+    gint rtn;
+
+    rtn = _safe_invalidable_date_compare (xaccSchedXactionGetLastOccurDate (a_inst->sx),
+                                          xaccSchedXactionGetLastOccurDate (b_inst->sx));
+
+    return rtn ? rtn : _name_instances_compare (a_inst, b_inst);
+}
+
 static gint
 _last_occur_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
 {
-    GncSxListTreeModelAdapter *adapter = GNC_SX_LIST_TREE_MODEL_ADAPTER(user_data);
-    GncSxInstances *a_inst, *b_inst;
+    return _sort_iterators_to_instances (user_data, a, b, _last_occur_instances_compare);
+}
 
-    a_inst = gsltma_get_sx_instances_from_orig_iter (adapter, a);
-    b_inst = gsltma_get_sx_instances_from_orig_iter (adapter, b);
+static gint
+_next_occur_instances_compare (GncSxInstances *a_inst, GncSxInstances *b_inst)
+{
+    gint rtn;
+
+    rtn = _safe_invalidable_date_compare (&a_inst->next_instance_date,
+                                          &b_inst->next_instance_date);
 
-    return _safe_invalidable_date_compare (xaccSchedXactionGetLastOccurDate (a_inst->sx),
-                                           xaccSchedXactionGetLastOccurDate (b_inst->sx));
+    return rtn ? rtn : _name_instances_compare (a_inst, b_inst);
 }
 
 static gint
 _next_occur_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
 {
-    GncSxListTreeModelAdapter *adapter = GNC_SX_LIST_TREE_MODEL_ADAPTER(user_data);
-    GncSxInstances *a_inst, *b_inst;
+    return _sort_iterators_to_instances (user_data, a, b, _next_occur_instances_compare);
+}
 
-    a_inst = gsltma_get_sx_instances_from_orig_iter (adapter, a);
-    b_inst = gsltma_get_sx_instances_from_orig_iter (adapter, b);
+static gint
+_enabled_instances_compare (GncSxInstances *a_inst, GncSxInstances *b_inst)
+{
+    gboolean a_enabled, b_enabled;
 
-    return _safe_invalidable_date_compare (&a_inst->next_instance_date,
-                                           &b_inst->next_instance_date);
+    a_enabled = xaccSchedXactionGetEnabled (a_inst->sx);
+    b_enabled = xaccSchedXactionGetEnabled (b_inst->sx);
+
+    if (a_enabled && !b_enabled) return 1;
+    if (!a_enabled && b_enabled) return -1;
+
+    return _name_instances_compare (a_inst, b_inst);
 }
 
 static gint
 _enabled_comparator (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
 {
-    GncSxListTreeModelAdapter *adapter = GNC_SX_LIST_TREE_MODEL_ADAPTER(user_data);
-    GncSxInstances *a_inst, *b_inst;
-
-    a_inst = gsltma_get_sx_instances_from_orig_iter (adapter, a);
-    b_inst = gsltma_get_sx_instances_from_orig_iter (adapter, b);
-
-    if (xaccSchedXactionGetEnabled (a_inst->sx) && !xaccSchedXactionGetEnabled (b_inst->sx)) return 1;
-    if (!xaccSchedXactionGetEnabled (a_inst->sx) && xaccSchedXactionGetEnabled (b_inst->sx)) return -1;
-    return 0;
+    return _sort_iterators_to_instances (user_data, a, b, _enabled_instances_compare);
 }
 
 static void
 gnc_sx_list_tree_model_adapter_init (GncSxListTreeModelAdapter *adapter)
 {
-  adapter->orig = gtk_tree_store_new (6, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
+    adapter->orig = gtk_tree_store_new (6, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
     adapter->real = GTK_TREE_MODEL_SORT(gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(adapter->orig)));
 
     // setup sorting
diff --git a/gnucash/gnome-utils/gnc-tree-view-sx-list.c b/gnucash/gnome-utils/gnc-tree-view-sx-list.c
index 1c82624bbb..9e07444023 100644
--- a/gnucash/gnome-utils/gnc-tree-view-sx-list.c
+++ b/gnucash/gnome-utils/gnc-tree-view-sx-list.c
@@ -56,6 +56,11 @@ struct _GncTreeViewSxList
     GncTreeView gnc_tree_view;
 
     GtkTreeModel *tree_model;
+
+    SchedXaction *sx;
+    GtkAdjustment *adjustment;
+    gdouble position;
+
     gboolean disposed;
 };
 
@@ -105,6 +110,85 @@ gnc_tree_view_sx_list_finalize(GObject *object)
     G_OBJECT_CLASS(gnc_tree_view_sx_list_parent_class)->finalize (object);
 }
 
+/************************************************************
+ *                        Callbacks                         *
+ ************************************************************/
+
+static gboolean
+gnc_tree_view_sx_list_restore (gpointer user_data)
+{
+    GncTreeViewSxList *view = user_data;
+
+    if (view->adjustment)
+    {
+        gtk_adjustment_set_value (view->adjustment, view->position);
+        view->adjustment = NULL;
+    }
+    if (view->sx)
+    {
+        SchedXaction *sx = view->sx;
+        GtkTreePath *path = gtk_tree_path_new_first ();
+
+        while (gnc_tree_view_path_is_valid (GNC_TREE_VIEW(view), path))
+        {
+            if (sx == gnc_tree_view_sx_list_get_sx_from_path (view, path))
+            {
+                GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
+
+                gtk_tree_selection_unselect_all (selection);
+                gtk_tree_selection_select_path (selection, path);
+                gtk_tree_view_set_cursor (GTK_TREE_VIEW(view), path, NULL, FALSE);
+                gtk_widget_grab_focus (GTK_WIDGET(view));
+                gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(view), path, NULL, FALSE, 0.0, 0.0);
+                break;
+            }
+            gtk_tree_path_next (path);
+        }
+        gtk_tree_path_free (path);
+
+        view->sx = NULL;
+    }
+
+    return FALSE;
+}
+
+static void
+gnc_tree_view_sx_list_enabled_toggled (GtkCellRendererToggle *cell,
+                                      const gchar *path_str,
+                                      gpointer user_data)
+{
+    GncTreeViewSxList *view = user_data;
+    GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
+    SchedXaction *sx = gnc_tree_view_sx_list_get_sx_from_path (view, path);
+
+    if (sx)
+    {
+        GtkTreeSortable *sortable = GTK_TREE_SORTABLE(view->tree_model);
+        gint sort_column_id;
+        GtkSortType sort_order;
+
+        if (gtk_tree_sortable_get_sort_column_id (sortable, &sort_column_id, &sort_order) &&
+            sort_column_id == SXLTMA_COL_ENABLED)
+        {
+            view->sx = sx;
+        }
+        else
+        {
+            view->adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(view));
+            view->position = gtk_adjustment_get_value (view->adjustment);
+        }
+
+        gboolean enabled = !gtk_cell_renderer_toggle_get_active (cell);
+
+        xaccSchedXactionSetEnabled (sx, enabled);
+
+        g_idle_add((GSourceFunc)gnc_tree_view_sx_list_restore, user_data);
+    }
+
+    gtk_tree_path_free (path);
+}
+
+
 GtkTreeView*
 gnc_tree_view_sx_list_new (GncSxInstanceModel *sx_instances)
 {
@@ -123,7 +207,7 @@ gnc_tree_view_sx_list_new (GncSxInstanceModel *sx_instances)
                                            C_("Single-character short column-title form of 'Enabled'", "E"),
                                            "enabled", SXLTMA_COL_ENABLED,
                                            GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS,
-                                           NULL, NULL);
+                                           NULL, gnc_tree_view_sx_list_enabled_toggled);
     g_object_set_data (G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
 
     col = gnc_tree_view_add_text_column (GNC_TREE_VIEW(view), _("Frequency"), "frequency", NULL,
diff --git a/gnucash/gnome/gnc-plugin-page-sx-list.cpp b/gnucash/gnome/gnc-plugin-page-sx-list.cpp
index ebf83de984..ff36278730 100644
--- a/gnucash/gnome/gnc-plugin-page-sx-list.cpp
+++ b/gnucash/gnome/gnc-plugin-page-sx-list.cpp
@@ -117,6 +117,7 @@ static void gnc_plugin_page_sx_list_destroy_widget (GncPluginPage *plugin_page);
 static void gnc_plugin_page_sx_list_save_page (GncPluginPage *plugin_page, GKeyFile *file, const gchar *group);
 static GncPluginPage *gnc_plugin_page_sx_list_recreate_page (GtkWidget *window, GKeyFile *file, const gchar *group);
 
+static gboolean gppsl_key_press_cb (GtkTreeView *tree_view, GdkEventKey *event, gpointer user_data);
 static void gppsl_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path,
                                     GtkTreeViewColumn *column, gpointer user_data);
 
@@ -543,6 +544,8 @@ gnc_plugin_page_sx_list_create_widget (GncPluginPage *plugin_page)
 
         g_signal_connect (G_OBJECT(selection), "changed",
                           (GCallback)gppsl_selection_changed_cb, (gpointer)page);
+        g_signal_connect (G_OBJECT(priv->tree_view), "key-press-event",
+                          (GCallback)gppsl_key_press_cb, (gpointer)page);
         g_signal_connect (G_OBJECT(priv->tree_view), "row-activated",
                           (GCallback)gppsl_row_activated_cb, (gpointer)page);
         g_signal_connect (G_OBJECT(gtk_tree_view_get_model (GTK_TREE_VIEW(priv->tree_view))),
@@ -850,6 +853,137 @@ gnc_plugin_page_sx_list_cmd_edit_tax_options (GSimpleAction *simple,
     LEAVE (" ");
 }
 
+static gboolean
+gppsl_is_enable_column_visible (GtkTreeView *tree_view)
+{
+    gboolean retval = FALSE;
+    GList *columns = gtk_tree_view_get_columns (tree_view);
+
+    for (GList *node = columns; node; node = node->next)
+    {
+        GtkTreeViewColumn *col = GTK_TREE_VIEW_COLUMN(node->data);
+        gint id = GPOINTER_TO_INT(g_object_get_data (G_OBJECT(col), MODEL_COLUMN));
+
+        if (id == SXLTMA_COL_ENABLED)
+        {
+            retval = gtk_tree_view_column_get_visible(col);
+            break;
+        }
+    }
+    g_list_free (columns);
+    return retval;
+}
+
+static gboolean
+gppsl_set_toggle (GtkTreeView *tree_view)
+{
+    GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+    GtkTreeModel *model;
+    GList *sel_list = gtk_tree_selection_get_selected_rows (selection, &model);
+    gint num_selected = gtk_tree_selection_count_selected_rows (selection);
+    gint num_toggled = 0;
+
+    for (GList *node = sel_list; node; node = node->next)
+    {
+        GtkTreePath *path = (GtkTreePath *)node->data;
+        GtkTreeIter iter;
+
+        if (gtk_tree_model_get_iter (model, &iter, path))
+        {
+            gboolean toggled;
+
+            gtk_tree_model_get (model, &iter, SXLTMA_COL_ENABLED, &toggled, -1);
+
+            if (toggled)
+            {
+                num_toggled++;
+            }
+        }
+        gtk_tree_path_free (path);
+    }
+    g_list_free (sel_list);
+
+    return num_toggled != num_selected;
+}
+
+static void
+gppsl_set_list (GncPluginPageSxList *sx_plugin_page, gboolean enable)
+{
+    GncPluginPageSxListPrivate *priv = GNC_PLUGIN_PAGE_SX_LIST_GET_PRIVATE(sx_plugin_page);
+    GtkTreeView *tree_view = priv->tree_view;
+    GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+    GtkTreeModel *model;
+    GList *sel_list = gtk_tree_selection_get_selected_rows (selection, &model);
+    GList *sx_list = nullptr;
+
+    gppsl_update_selected_list (sx_plugin_page, true, nullptr);
+
+    for (GList *node = sel_list; node; node = node->next)
+    {
+        GtkTreePath *path = (GtkTreePath *)node->data;
+        GncTreeViewSxList *view = GNC_TREE_VIEW_SX_LIST(tree_view);
+        SchedXaction *sx = gnc_tree_view_sx_list_get_sx_from_path (view, path);
+
+        if (sx)
+        {
+            GtkTreeIter iter;
+
+            gppsl_update_selected_list (sx_plugin_page, false, sx);
+
+            if (gtk_tree_model_get_iter (model, &iter, path))
+            {
+                gboolean toggled;
+
+                gtk_tree_model_get (model, &iter, SXLTMA_COL_ENABLED, &toggled, -1);
+                if (enable != toggled)
+                {
+                    sx_list = g_list_prepend (sx_list, sx);
+                }
+            }
+        }
+        
+        gtk_tree_path_free (path);
+    }
+    g_list_free (sel_list);
+
+    for (GList *node = sx_list; node; node = node->next)
+    {
+        if (node == sx_list)
+        {
+            if (node->next)
+            {
+                qof_event_suspend ();
+            }
+        }
+        else if (!node->next)
+        {
+            qof_event_resume ();
+        }
+
+        SchedXaction *sx = GNC_SCHEDXACTION(node->data);
+
+        xaccSchedXactionSetEnabled (sx, enable);
+    }
+    g_list_free (sx_list);
+}
+
+static gboolean
+gppsl_key_press_cb (GtkTreeView *tree_view, GdkEventKey *event, gpointer user_data)
+{
+    if (event->keyval != GDK_KEY_space || !gppsl_is_enable_column_visible (tree_view))
+    {
+        return FALSE;
+    }
+
+    g_signal_stop_emission_by_name (G_OBJECT(tree_view), "key-press-event");
+
+    gboolean enable = gppsl_set_toggle (tree_view);
+
+    gppsl_set_list (GNC_PLUGIN_PAGE_SX_LIST(user_data), enable);
+
+    return TRUE;
+}
+
 
 static void
 gppsl_row_activated_cb (GtkTreeView *tree_view,



Summary of changes:
 .../gnome-utils/gnc-sx-list-tree-model-adapter.c   | 121 +++++++++++--------
 gnucash/gnome-utils/gnc-tree-view-sx-list.c        |  86 ++++++++++++-
 gnucash/gnome/gnc-plugin-page-sx-list.cpp          | 134 +++++++++++++++++++++
 3 files changed, 293 insertions(+), 48 deletions(-)



More information about the gnucash-changes mailing list