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