From aac8efdabff36309e2ca299432e29397d5b2e769 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Wed, 17 Dec 2025 07:35:09 +0800 Subject: [PATCH] [window-reconcile.cpp] add autoclear toolbar button in reconcile ui --- gnucash/gnome/window-reconcile.cpp | 117 +++++++++++++++++++++++++++++ gnucash/ui/gnc-reconcile-window.ui | 17 +++++ 2 files changed, 134 insertions(+) diff --git a/gnucash/gnome/window-reconcile.cpp b/gnucash/gnome/window-reconcile.cpp index 2dfc774bde..ffc39510d0 100644 --- a/gnucash/gnome/window-reconcile.cpp +++ b/gnucash/gnome/window-reconcile.cpp @@ -37,6 +37,8 @@ #endif #include +#include + #include "Account.hpp" #include "Scrub.h" #include "Scrub3.h" @@ -44,6 +46,7 @@ #include "dialog-transfer.h" #include "dialog-utils.h" #include "gnc-amount-edit.h" +#include "gnc-autoclear.h" #include "gnc-component-manager.h" #include "gnc-date.h" #include "gnc-date-edit.h" @@ -82,10 +85,14 @@ struct _RecnWindow GtkBuilder *builder; /* The builder object */ GSimpleActionGroup *simple_action_group; /* The action group for the window */ + GtkWidget *autoclear_button; GtkAccelGroup *accel_group; GncPluginPage *page; + SplitsVec autoclear_splits; + SplitsVec initially_cleared_splits; + GtkWidget *starting; /* The starting balance */ GtkWidget *ending; /* The ending balance */ GtkWidget *recn_date; /* The statement date */ @@ -137,6 +144,7 @@ static void recn_destroy_cb (GtkWidget *w, gpointer data); static void recn_cancel (RecnWindow *recnData); static gboolean recn_delete_cb (GtkWidget *widget, GdkEvent *event, gpointer data); static gboolean recn_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer data); +static void recnAutoClearCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data); static void recnFinishCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data); static void recnPostponeCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data); static void recnCancelCB (GSimpleAction *simple, GVariant *parameter, gpointer user_data); @@ -193,6 +201,64 @@ has_account_different_commodities(const Account *account) return result != NULL; } +static const char* +get_autoclear_icon (GError* error) +{ + static std::unordered_map icon_names = + { + { Autoclear::ABORT_NONE, "media-playback-start" }, + { Autoclear::ABORT_NOP, "media-playback-stop" }, + { Autoclear::ABORT_MULTI, "dialog-information" }, + { Autoclear::ABORT_TIMEOUT, "dialog-error" }, + { Autoclear::ABORT_UNREACHABLE, "dialog-error" }, + }; + auto it = icon_names.find (error ? error->code : Autoclear::ABORT_NONE); + return it == icon_names.end() ? "dialog-information" : it->second; +} + +static void +calculate_autoclear (RecnWindow *recnData) +{ + g_return_if_fail (recnData); + + GError* error = nullptr; + Account* acct = xaccAccountLookup (&recnData->account, gnc_get_current_book ()); + +#define MAX_AUTOCLEAR_SECONDS 1 + + GList *splits_to_clear = gnc_account_get_autoclear_splits + (acct, recnData->new_ending, recnData->statement_date, &error, MAX_AUTOCLEAR_SECONDS); + + gtk_widget_set_sensitive (recnData->autoclear_button, error == nullptr); + + gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (recnData->autoclear_button), + get_autoclear_icon (error)); + + recnData->autoclear_splits = recnData->initially_cleared_splits; + for (auto n = splits_to_clear; n; n = n->next) + recnData->autoclear_splits.push_back (GNC_SPLIT (n->data)); + + if (error) + { + gtk_widget_set_tooltip_text (recnData->autoclear_button, _(error->message)); + g_error_free (error); + return; + } + + auto num_splits = g_list_length (splits_to_clear); + char date_buff[MAX_DATE_LENGTH+1]; + qof_print_date_buff (date_buff, MAX_DATE_LENGTH, recnData->statement_date); + char* tooltip = g_strdup_printf + (ngettext("Automatically select %u transaction up to %s that clears to %s", + "Automatically select %u transactions up to %s that clear to %s", + num_splits), + num_splits, date_buff, + xaccPrintAmount (recnData->new_ending, gnc_account_print_info (acct, true))); + gtk_widget_set_tooltip_text (recnData->autoclear_button, tooltip); + + g_free (tooltip); + g_list_free (splits_to_clear); +} /********************************************************************\ * recnRefresh * @@ -305,6 +371,8 @@ recnRecalculateBalance (RecnWindow *recnData) "TransBalanceAction"); g_simple_action_set_enabled (G_SIMPLE_ACTION(action), !gnc_numeric_zero_p (diff)); + calculate_autoclear (recnData); + return diff; } @@ -1770,6 +1838,7 @@ static GActionEntry recWindow_actions_entries [] = { "RecnFinishAction", recnFinishCB, NULL, NULL, NULL }, { "RecnPostponeAction", recnPostponeCB, NULL, NULL, NULL }, { "RecnCancelAction", recnCancelCB, NULL, NULL, NULL }, + { "RecnAutoClearAction", recnAutoClearCB, NULL, NULL, NULL }, { "AccountOpenAccountAction", gnc_recn_open_cb, NULL, NULL, NULL }, { "AccountEditAccountAction", gnc_recn_edit_account_cb, NULL, NULL, NULL }, @@ -1845,6 +1914,8 @@ recnWindowWithBalance (GtkWidget *parent, Account *account, gnc_numeric new_endi recnData->statement_date = statement_date; recnData->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); recnData->delete_refresh = FALSE; + new (&recnData->autoclear_splits) SplitsVec(); + new (&recnData->initially_cleared_splits) SplitsVec(); gnc_recn_set_window_name(recnData); @@ -1860,6 +1931,13 @@ recnWindowWithBalance (GtkWidget *parent, Account *account, gnc_numeric new_endi gtk_widget_show(dock); gtk_box_pack_start(GTK_BOX (vbox), dock, FALSE, TRUE, 0); + auto init_cleared = [&recnData](Split* s) + { + if (xaccSplitGetReconcile (s) == CREC) + recnData->initially_cleared_splits.push_back (s); + }; + gnc_account_foreach_split_until_date (account, statement_date, init_cleared); + { GtkToolbar *tool_bar; GMenuModel *menu_model; @@ -1883,6 +1961,8 @@ recnWindowWithBalance (GtkWidget *parent, Account *account, gnc_numeric new_endi return NULL; } + recnData->autoclear_button = GTK_WIDGET(gtk_builder_get_object(recnData->builder, "autoclear_button")); + menu_model = (GMenuModel *)gtk_builder_get_object (recnData->builder, "recwin-menu"); menu_bar = gtk_menu_bar_new_from_model (menu_model); gtk_container_add (GTK_CONTAINER(vbox), menu_bar); @@ -2200,6 +2280,9 @@ recn_destroy_cb (GtkWidget *w, gpointer data) if (recnData->accel_group) g_object_unref(recnData->accel_group); + recnData->autoclear_splits.~SplitsVec(); + recnData->initially_cleared_splits.~SplitsVec(); + //Disable the actions, the handlers try to access recnData for (gint i = 0; i < num_actions; i++) { @@ -2424,3 +2507,37 @@ recnCancelCB (GSimpleAction *simple, auto recnData = static_cast(user_data); recn_cancel(recnData); } + +/********************************************************************\ + * recnAutoClearCB * + * handles the auto-clear button click * + * * + * Args: simple - the action * + * parameter - unused * + * user_data - the reconcile window data * + * Return: none * +\********************************************************************/ +static void +recnAutoClearCB (GSimpleAction *simple, + GVariant *parameter, + gpointer user_data) +{ + auto recnData = static_cast(user_data); + + if (recnData->autoclear_splits.empty()) + return; + + gnc_suspend_gui_refresh (); + gnc_reconcile_view_unclear_all (GNC_RECONCILE_VIEW(recnData->debit)); + gnc_reconcile_view_unclear_all (GNC_RECONCILE_VIEW(recnData->credit)); + std::for_each (recnData->autoclear_splits.begin(), + recnData->autoclear_splits.end(), + [recnData](Split* split) + { + auto view = gnc_numeric_positive_p (xaccSplitGetAmount (split)) + ? recnData->debit : recnData->credit; + gnc_reconcile_view_set_cleared (GNC_RECONCILE_VIEW(view), split); + }); + recnRefresh (recnData); + gnc_resume_gui_refresh (); +} diff --git a/gnucash/ui/gnc-reconcile-window.ui b/gnucash/ui/gnc-reconcile-window.ui index 9f2a1f88b7..c5ac697115 100644 --- a/gnucash/ui/gnc-reconcile-window.ui +++ b/gnucash/ui/gnc-reconcile-window.ui @@ -20,6 +20,11 @@ recwin.RecnPostponeAction <Primary>p + + _Autoclear + recwin.RecnAutoClearAction + <Primary><Shift>c + _Cancel recwin.RecnCancelAction @@ -120,6 +125,18 @@ True False + + + True + False + False + Automatically clear matching transactions + Auto Clear + True + media-playback-start + recwin.RecnAutoClearAction + + True