GnuCash  5.6-150-g038405b370+
gnc-plugin-page-register-sort.cpp
1 /**********************************************************************
2  * gnc-plugin-page-register-sort.c -- register page sort *
3  * *
4  * Copyright (C) 2026, Robert Fewell *
5  * *
6  * This program is free software; you can redistribute it and/or *
7  * modify it under the terms of the GNU General Public License as *
8  * published by the Free Software Foundation; either version 2 of *
9  * the License, or (at your option) any later version. *
10  * *
11  * This program is distributed in the hope that it will be useful, *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14  * GNU General Public License for more details. *
15  * *
16  * You should have received a copy of the GNU General Public License *
17  * along with this program; if not, contact: *
18  * *
19  * Free Software Foundation Voice: +1-617-542-5942 *
20  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21  * Boston, MA 02110-1301, USA gnu@gnu.org *
22  **********************************************************************/
23 
33 #include <config.h>
34 
35 #include <gtk/gtk.h>
36 #include <glib/gi18n.h>
37 
39 #include "gnc-plugin-page-register-sort.hpp"
40 #include "dialog-utils.h"
41 #include "gnc-state.h"
42 #include "gnc-prefs.h"
43 #include "gnc-ui-util.h"
44 #include "gnc-window.h"
45 #include "gnc-main-window.h"
46 #include "engine-helpers.h"
47 #include "qofbookslots.h"
48 #include "qof.h"
49 
50 #include <algorithm>
51 #include <string>
52 
53 /* This static indicates the debugging module that this .o belongs to. */
54 static QofLogModule log_module = GNC_MOD_GUI;
55 
56 static std::string DEFAULT_SORT_ORDER = "BY_STANDARD";
57 
59 {
60  GncPluginPage* plugin_page;
61  SplitRegister* reg;
62  GtkWidget* dialog;
63  GtkWidget* num_radio;
64  GtkWidget* act_radio;
65 
66  SortType original_sort_type;
67  bool original_reverse_order;
68  bool original_save_order;
69 
70  bool show_save_button;
71 };
72 
73 extern "C"
74 {
75 // These functions are the dialog callbacks. They're connected to their
76 // signals in gnc-plugin-page-register.glade so they mustn't be name-mangled.
77 void
78 gnc_ppr_sort_response_cb (GtkDialog* dialog,
79  gint response,
80  RegisterSortDialog *rsd);
81 
82 void
83 gnc_ppr_sort_button_cb (GtkToggleButton* button,
84  RegisterSortDialog *rsd);
85 
86 void
87 gnc_ppr_sort_order_save_cb (GtkToggleButton* button,
88  RegisterSortDialog *rsd);
89 
90 void
91 gnc_ppr_sort_order_reverse_cb (GtkToggleButton* button,
92  RegisterSortDialog *rsd);
93 }
94 
95 static inline bool
96 gboolean_to_bool (gboolean value)
97 {
98  return value ? true : false;
99 }
100 
101 static inline gboolean
102 bool_to_gboolean (bool value)
103 {
104  return value ? TRUE : FALSE;
105 }
106 
107 static void
108 gnc_ppr_check_for_empty_group (GKeyFile *state_file,
109  const gchar *state_section)
110 {
111  gsize num_keys;
112  gchar **keys = g_key_file_get_keys (state_file, state_section, &num_keys, nullptr);
113 
114  if (num_keys == 0)
115  gnc_state_drop_sections_for (state_section);
116 
117  g_strfreev (keys);
118 }
119 
120 static std::string
121 gnc_ppr_sort_get_order (GNCSplitReg *gsr)
122  {
123  if (!gsr)
124  return _("unknown");
125 
126  // get the sort_order from the .gcm file
127  GKeyFile* state_file = gnc_state_get_current();
128  auto state_section = gsr_get_register_state_section (gsr);
129  GError* error = nullptr;
130 
131  auto sort_text = g_key_file_get_string (state_file, state_section,
132  KEY_PAGE_SORT, &error);
133  std::string sort_order;
134 
135  if (error)
136  g_clear_error (&error);
137  else
138  {
139  sort_order = (sort_text);
140  g_free (sort_text);
141  }
142  g_free (state_section);
143 
144  return !sort_order.empty() ? sort_order : (DEFAULT_SORT_ORDER);
145 }
146 
147 static void
148 gnc_ppr_sort_set_order (GNCSplitReg *gsr, std::string sort_order)
149 {
150  if (!gsr)
151  return;
152 
153  // save sort_order to the .gcm file also
154  GKeyFile* state_file = gnc_state_get_current();
155  auto state_section = gsr_get_register_state_section (gsr);
156 
157  if (sort_order.compare (DEFAULT_SORT_ORDER) == 0)
158  {
159  if (g_key_file_has_key (state_file, state_section, KEY_PAGE_SORT, nullptr))
160  g_key_file_remove_key (state_file, state_section, KEY_PAGE_SORT, nullptr);
161 
162  gnc_ppr_check_for_empty_group (state_file, state_section);
163  }
164  else
165  g_key_file_set_string (state_file, state_section, KEY_PAGE_SORT, sort_order.c_str());
166 
167  g_free (state_section);
168 }
169 
170 static bool
171 gnc_ppr_sort_get_reversed (GNCSplitReg *gsr)
172 {
173  if (!gsr)
174  return false;
175 
176  // get the sort_reversed from the .gcm file
177  GKeyFile* state_file = gnc_state_get_current();
178  auto state_section = gsr_get_register_state_section (gsr);
179  GError* error = nullptr;
180  gboolean sort_reversed = g_key_file_get_boolean (state_file, state_section,
181  KEY_PAGE_SORT_REV, &error);
182 
183  if (error)
184  g_clear_error (&error);
185 
186  g_free (state_section);
187  return gboolean_to_bool (sort_reversed);
188 }
189 
190 static void
191 gnc_ppr_sort_set_reversed (GNCSplitReg* gsr, bool reverse_order)
192 {
193  if (!gsr)
194  return;
195 
196  // save reverse_order to the .gcm file also
197  GKeyFile* state_file = gnc_state_get_current();
198  auto state_section = gsr_get_register_state_section (gsr);
199 
200  if (!reverse_order)
201  {
202  if (g_key_file_has_key (state_file, state_section, KEY_PAGE_SORT_REV, nullptr))
203  g_key_file_remove_key (state_file, state_section, KEY_PAGE_SORT_REV, nullptr);
204 
205  gnc_ppr_check_for_empty_group (state_file, state_section);
206  }
207  else
208  g_key_file_set_boolean (state_file, state_section, KEY_PAGE_SORT_REV,
209  bool_to_gboolean (reverse_order));
210 
211  g_free (state_section);
212 }
213 
219 void
221 {
222  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(plugin_page));
223 
224  auto sd = gnc_plugin_page_register_get_sort_data (plugin_page);
225  auto gsr = gnc_plugin_page_register_get_gsr (plugin_page);
226  GNCLedgerDisplayType ledger_type = gnc_ledger_display_type (gsr->ledger);
227 
228  sd->save_order = false;
229 
230  // Set the sort direction for the split register and status of save order button
231  sd->reverse_order = gnc_ppr_sort_get_reversed (gsr);
232 
233  PINFO("Loaded Sort reversed order is %s", sd->reverse_order ? "true" : "false");
234 
235  gnc_split_reg_set_sort_reversed (gsr, sd->reverse_order, no_refresh);
236  if (sd->reverse_order)
237  sd->save_order = true;
238 
239  // Set the sort order for the split register and status of save order button
240  std::string sort_type = gnc_ppr_sort_get_order (gsr);
241 
242  PINFO("Loaded Sort type is %s", sort_type.c_str());
243 
244  SortType type = SortTypefromString (sort_type.c_str());
245 
246  gnc_split_reg_sort (gsr, type, no_force, no_refresh);
247 
248  if (sort_type.compare (DEFAULT_SORT_ORDER) != 0)
249  sd->save_order = true;
250 
251  if (ledger_type == LD_GL)
252  {
253  auto reg = gnc_ledger_display_get_split_register (gsr->ledger);
254 
255  if (reg->type != GENERAL_JOURNAL) // search ledger and the like
256  {
257  gnc_split_reg_sort (gsr, SortTypefromString (DEFAULT_SORT_ORDER.c_str()), no_force, no_refresh);
258  sd->reverse_order = false;
259  sd->save_order = false;
260  }
261  }
262 }
263 
273 static void
274 gnc_ppr_sort_book_option_changed (gpointer new_val,
275  gpointer user_data)
276 {
277  RegisterSortDialog *rsd = (RegisterSortDialog*)user_data;
278  gboolean* new_data = (gboolean*)new_val;
279 
280  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rsd->plugin_page));
281 
282  if (*new_data)
283  {
284  gtk_button_set_label (GTK_BUTTON(rsd->num_radio), _("Transaction Number"));
285  gtk_button_set_label (GTK_BUTTON(rsd->act_radio), _("Number/Action"));
286  }
287  else
288  {
289  gtk_button_set_label (GTK_BUTTON(rsd->num_radio), _("Number"));
290  gtk_button_set_label (GTK_BUTTON(rsd->act_radio), _("Action"));
291  }
292 
293  auto gsr = gnc_plugin_page_register_get_gsr (rsd->plugin_page);
294 
295  gnc_split_reg_sort (gsr, (SortType)gsr->sort_type, force, refresh);
296 }
297 
308 void
309 gnc_ppr_sort_response_cb (GtkDialog* dialog,
310  gint response,
311  RegisterSortDialog *rsd)
312 {
313  g_return_if_fail (GTK_IS_DIALOG(dialog));
314  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rsd->plugin_page));
315 
316  ENTER(" ");
317 
318  auto sd = gnc_plugin_page_register_get_sort_data (rsd->plugin_page);
319  auto gsr = gnc_plugin_page_register_get_gsr (rsd->plugin_page);
320 
321  if (response != GTK_RESPONSE_OK)
322  {
323  // Restore the original sort order
324  gnc_split_reg_set_sort_reversed (gsr, rsd->original_reverse_order, no_refresh);
325  sd->reverse_order = rsd->original_reverse_order;
326  // use force as sort_type may still be the same if only reverse_order changed
327  gnc_split_reg_sort (gsr, rsd->original_sort_type, force, refresh);
328  sd->sort_type = rsd->original_sort_type;
329  sd->save_order = rsd->original_save_order;
330  }
331  else
332  {
333  // clear the sort when unticking the save option
334  if ((!sd->save_order) && ((rsd->original_sort_type) ||
335  (rsd->original_reverse_order)))
336  {
337  gnc_ppr_sort_set_order (gsr, DEFAULT_SORT_ORDER);
338  gnc_ppr_sort_set_reversed (gsr, false);
339  }
340  rsd->original_sort_type = sd->sort_type;
341  rsd->original_reverse_order = sd->reverse_order;
342 
343  if (sd->save_order)
344  {
345  SortType type = gnc_split_reg_get_sort_type (gsr);
346  std::string sort_type = (SortTypeasString (type));
347 
348  gnc_ppr_sort_set_order (gsr, sort_type);
349  gnc_ppr_sort_set_reversed (gsr, sd->reverse_order);
350  }
351  }
352  gnc_book_option_remove_cb (OPTION_NAME_NUM_FIELD_SOURCE,
353  gnc_ppr_sort_book_option_changed,
354  (gpointer)rsd);
355  rsd->dialog = nullptr;
356  rsd->num_radio = nullptr;
357  rsd->act_radio = nullptr;
358  sd->dialog = nullptr;
359  g_free (rsd);
360  gtk_widget_destroy (GTK_WIDGET(dialog));
361  LEAVE (" ");
362 }
363 
371 void
372 gnc_ppr_sort_button_cb (GtkToggleButton* button,
373  RegisterSortDialog *rsd)
374 {
375  g_return_if_fail (GTK_IS_TOGGLE_BUTTON(button));
376  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rsd->plugin_page));
377 
378  auto name = gtk_buildable_get_name (GTK_BUILDABLE(button));
379 
380  ENTER("button %s(%p), page %p", name, button, rsd->plugin_page);
381 
382  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button)))
383  {
384  LEAVE("1st callback of pair. Defer to 2nd callback.");
385  return;
386  }
387 
388  auto gsr = gnc_plugin_page_register_get_gsr (rsd->plugin_page);
389 
390  SortType type = SortTypefromString (name);
391  gnc_split_reg_sort (gsr, type, no_force, refresh);
392  LEAVE (" ");
393 }
394 
402 void
403 gnc_ppr_sort_order_save_cb (GtkToggleButton* button,
404  RegisterSortDialog *rsd)
405 {
406  g_return_if_fail (GTK_IS_CHECK_BUTTON(button));
407  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rsd->plugin_page));
408 
409  ENTER("Save toggle button (%p), page %p", button, rsd->plugin_page);
410 
411  /* Compute the new save sort order */
412  auto sd = gnc_plugin_page_register_get_sort_data (rsd->plugin_page);
413 
414  if (gtk_toggle_button_get_active (button))
415  sd->save_order = true;
416  else
417  sd->save_order = false;
418  LEAVE (" ");
419 }
420 
428 void
429 gnc_ppr_sort_order_reverse_cb (GtkToggleButton* button,
430  RegisterSortDialog *rsd)
431 {
432  g_return_if_fail (GTK_IS_CHECK_BUTTON(button));
433  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rsd->plugin_page));
434 
435  ENTER("Reverse toggle button (%p), page %p", button, rsd->plugin_page);
436 
437  /* Compute the new save sort order */
438  auto sd = gnc_plugin_page_register_get_sort_data (rsd->plugin_page);
439  auto gsr = gnc_plugin_page_register_get_gsr (rsd->plugin_page);
440 
441  sd->reverse_order = gtk_toggle_button_get_active (button);
442  gnc_split_reg_set_sort_reversed (gsr, sd->reverse_order, refresh);
443  LEAVE (" ");
444 }
445 
452 static void
453 gnc_ppr_sort_dialog_create (RegisterSortDialog *rsd, SortData *sd)
454 {
455  /* Create the dialog */
456  auto builder = gtk_builder_new();
457  gnc_builder_add_from_file (builder, "gnc-plugin-page-register.glade", "sort_by_dialog");
458  auto dialog = GTK_WIDGET(gtk_builder_get_object (builder, "sort_by_dialog"));
459  rsd->dialog = dialog;
460  sd->dialog = rsd->dialog;
461 
462  gtk_window_set_transient_for (GTK_WINDOW(dialog),
463  gnc_window_get_gtk_window (GNC_WINDOW(
464  GNC_PLUGIN_PAGE(rsd->plugin_page)->window)));
465  /* Translators: The %s is the name of the plugin page */
466  auto title = g_strdup_printf (_("Sort %s by…"),
467  gnc_plugin_page_get_page_name (rsd->plugin_page));
468  gtk_window_set_title (GTK_WINDOW(dialog), title);
469  g_free (title);
470 
471  auto gsr = gnc_plugin_page_register_get_gsr (rsd->plugin_page);
472 
473  /* Set the button for the current sort order */
474  SortType sort = gnc_split_reg_get_sort_type (gsr);
475  auto name = SortTypeasString (sort);
476  auto button = GTK_WIDGET(gtk_builder_get_object (builder, name));
477  DEBUG("current sort %d, button %s(%p)", sort, name, button);
478  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
479  rsd->original_sort_type = sort;
480 
481  button = GTK_WIDGET(gtk_builder_get_object (builder, "sort_save"));
482  if (sd->save_order)
483  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
484 
485  rsd->original_save_order = sd->save_order;
486 
487  // hide the save button if appropriate
488  gtk_widget_set_visible (GTK_WIDGET(button), bool_to_gboolean (rsd->show_save_button));
489 
490  /* Set the button for the current reverse_order order */
491  button = GTK_WIDGET(gtk_builder_get_object (builder, "sort_reverse"));
492  if (sd->reverse_order)
493  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
494  rsd->original_reverse_order = sd->reverse_order;
495 
496  rsd->num_radio = GTK_WIDGET(gtk_builder_get_object (builder, "BY_NUM"));
497  rsd->act_radio = GTK_WIDGET(gtk_builder_get_object (builder, "BY_ACTION"));
498  /* Adjust labels related to Num/Action radio buttons based on book option */
499  if (rsd->reg && !rsd->reg->use_tran_num_for_num_field)
500  {
501  gtk_button_set_label (GTK_BUTTON(rsd->num_radio), _ ("Transaction Number"));
502  gtk_button_set_label (GTK_BUTTON(rsd->act_radio), _ ("Number/Action"));
503  }
504  gnc_book_option_register_cb (OPTION_NAME_NUM_FIELD_SOURCE,
505  (GncBOCb)gnc_ppr_sort_book_option_changed,
506  (gpointer)rsd);
507 
508  /* Wire it up */
509  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func,
510  rsd);
511 
512  /* Show it */
513  gtk_widget_show (dialog);
514  g_object_unref (G_OBJECT(builder));
515  LEAVE (" ");
516 }
517 
529 void
530 gnc_ppr_sort_dialog (GncPluginPage *plugin_page, SplitRegister* reg,
531  SortData *sd, bool show_save_button)
532 {
533  RegisterSortDialog *rsd;
534 
535  ENTER(" ");
536 
537  rsd = g_new0 (RegisterSortDialog, 1);
538 
539  rsd->plugin_page = plugin_page;
540  rsd->reg = reg;
541  rsd->show_save_button = show_save_button;
542 
543  gnc_ppr_sort_dialog_create (rsd, sd);
544 
545  LEAVE(" ");
546 }
void gnc_ppr_sort_order_save_cb(GtkToggleButton *button, RegisterSortDialog *rsd)
This function is called whenever the save sort order is checked or unchecked which allows saving of t...
Functions to load, save and get gui state.
void gnc_ppr_sort_response_cb(GtkDialog *dialog, gint response, RegisterSortDialog *rsd)
This function is called when the "Sort By…" dialog is closed.
The instance data structure for a content plugin.
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
gint gnc_state_drop_sections_for(const gchar *partial_name)
Drop all sections from the state file whose name contains partial_name.
Definition: gnc-state.c:260
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
const gchar * gnc_plugin_page_get_page_name(GncPluginPage *page)
Retrieve the name of this page.
Functions that are supported by all types of windows.
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
GKeyFile * gnc_state_get_current(void)
Returns a pointer to the most recently loaded state.
Definition: gnc-state.c:248
Functions for adding content to a window.
Functions providing a register page for the GnuCash UI.
void gnc_ppr_sort_order_reverse_cb(GtkToggleButton *button, RegisterSortDialog *rsd)
This function is called whenever the reverse sort order is checked or unchecked which allows reversin...
SortData * gnc_plugin_page_register_get_sort_data(GncPluginPage *plugin_page)
Get the SortData data structure associated with this register page.
Generic api to store and retrieve preferences.
void gnc_ppr_sort_dialog(GncPluginPage *plugin_page, SplitRegister *reg, SortData *sd, bool show_save_button)
This function is called for the sort dialog.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
void gnc_ppr_sort_button_cb(GtkToggleButton *button, RegisterSortDialog *rsd)
This function is called when a radio button in the "Sort By…" dialog is clicked. ...
SplitRegister * gnc_ledger_display_get_split_register(GNCLedgerDisplay *ld)
return the split register associated with a ledger display
GNCSplitReg * gnc_plugin_page_register_get_gsr(GncPluginPage *plugin_page)
Get the GNCSplitReg data structure associated with this register page.
void gnc_ppr_sort_update_register(GncPluginPage *plugin_page)
This function is called to update the register.