GnuCash  5.6-150-g038405b370+
gnc-plugin-page-register-filter.cpp
Go to the documentation of this file.
1 /**********************************************************************
2  * gnc-plugin-page-register-filter.cpp -- register page filter *
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 #include "dialog-utils.h"
38 #include "gnc-date.h"
39 #include "gnc-date-edit.h"
40 #include "gnc-glib-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 #include "Query.h"
50 
51 #include <algorithm>
52 #include <array>
53 #include <cstdio>
54 #include <iostream>
55 #include <sstream>
56 #include <string>
57 #include <vector>
58 
61 
62 static std::string DEFAULT_FILTER_NUM_DAYS_GL = "30";
63 static std::string DEFAULT_FILTER = "0x001f";
64 
65 /* This static indicates the debugging module that this .o belongs to. */
66 static QofLogModule log_module = GNC_MOD_GUI;
67 
69 {
70  GncPluginPage* plugin_page;
71  GtkWidget* dialog;
72  GtkWidget* table;
73  GtkWidget* start_date_choose;
74  GtkWidget* start_date_today;
75  GtkWidget* start_date;
76  GtkWidget* end_date_choose;
77  GtkWidget* end_date_today;
78  GtkWidget* end_date;
79  GtkWidget* num_days;
80 
81  cleared_match_t original_cleared_match;
82  time64 original_start_time;
83  time64 original_end_time;
84  int original_days;
85  bool original_save_filter;
86 
87  bool show_save_button;
88 };
89 
90 extern "C"
91 {
92 // These functions are the dialog callbacks. They're connected to their
93 // signals in gnc-plugin-page-register.glade so they mustn't be name-mangled.
94 void
95 gnc_ppr_filter_select_range_cb (GtkRadioButton* button,
97 void
98 gnc_ppr_filter_start_cb (GtkWidget* radio,
100 void
101 gnc_ppr_filter_end_cb (GtkWidget* radio,
102  RegisterFilterDialog* rfd);
103 void
104 gnc_ppr_filter_response_cb (GtkDialog* dialog,
105  gint response,
106  RegisterFilterDialog* rfd);
107 void
108 gnc_ppr_filter_status_select_all_cb (GtkButton* button,
109  RegisterFilterDialog* rfd);
110 void
111 gnc_ppr_filter_status_clear_all_cb (GtkButton* button,
112  RegisterFilterDialog* rfd);
113 void
114 gnc_ppr_filter_status_one_cb (GtkToggleButton* button,
115  RegisterFilterDialog* rfd);
116 void
117 gnc_ppr_filter_save_cb (GtkToggleButton* button,
118  RegisterFilterDialog* rfd);
119 void
120 gnc_ppr_filter_days_changed_cb (GtkSpinButton* button,
121  RegisterFilterDialog* rfd);
122 }
123 
125 {
126  std::string action_name;
127  int value;
128  GtkWidget* widget;
129 };
130 
131 static std::array<status_action, 5> status_actions {{
132  { "filter_status_reconciled", CLEARED_RECONCILED, nullptr },
133  { "filter_status_cleared", CLEARED_CLEARED, nullptr },
134  { "filter_status_voided", CLEARED_VOIDED, nullptr },
135  { "filter_status_frozen", CLEARED_FROZEN, nullptr },
136  { "filter_status_unreconciled", CLEARED_NO, nullptr }
137 }};
138 
139 #ifdef skip
140 static inline bool
141 gboolean_to_bool (gboolean value)
142 {
143  return value ? true : false;
144 }
145 #endif
146 static inline gboolean
147 bool_to_gboolean (bool value)
148 {
149  return value ? TRUE : FALSE;
150 }
151 
152 static std::string
153 get_filter_default_num_of_days (GNCLedgerDisplayType ledger_type)
154 {
155  if (ledger_type == LD_GL)
156  return DEFAULT_FILTER_NUM_DAYS_GL;
157  else
158  return "0";
159 }
160 
161 /* This function converts a time64 value date to a string */
162 static std::string
163 gnc_ppr_filter_time2dmy (time64 raw_time)
164 {
165  struct tm* timeinfo;
166  char date_string[11];
167 
168  timeinfo = gnc_localtime (&raw_time);
169  strftime (date_string, 11, "%d-%m-%Y", timeinfo);
170  PINFO("Date to string is %s", date_string);
171  gnc_tm_free (timeinfo);
172 
173  return (date_string);
174 }
175 
176 /* This function converts a string date to a time64 value */
177 static time64
178 gnc_ppr_filter_dmy2time (std::string date_string)
179 {
180  struct tm when;
181 
182  PINFO("Date from string is %s", date_string.c_str());
183  memset (&when, 0, sizeof (when));
184 
185  std::sscanf (date_string.c_str(), "%d-%d-%d", &when.tm_mday,
186  &when.tm_mon, &when.tm_year);
187 
188  when.tm_mon -= 1;
189  when.tm_year -= 1900;
190 
191  return gnc_mktime (&when);
192 }
193 
194 static std::vector<std::string>
195 split_filter_by_delimiter (std::string str, char delimiter)
196 {
197  std::istringstream ss;
198  std::vector<std::string> res;
199  std::string token;
200  ss.str (str);
201  while (std::getline (ss, token, delimiter))
202  {
203  res.push_back (token);
204  }
205  return res;
206 }
207 
208 static void
209 gnc_ppr_check_for_empty_group (GKeyFile *state_file,
210  const gchar *state_section)
211 {
212  gsize num_keys;
213  gchar **keys = g_key_file_get_keys (state_file, state_section, &num_keys, nullptr);
214 
215  if (num_keys == 0)
216  gnc_state_drop_sections_for (state_section);
217 
218  g_strfreev (keys);
219 }
220 
221 static std::string
222 gnc_ppr_filter_load_filter (GNCSplitReg *gsr, GNCLedgerDisplayType ledger_type)
223 {
224  // get the filter from the .gcm file
225  GKeyFile* state_file = gnc_state_get_current();
226  auto state_section = gsr_get_register_state_section (gsr);
227  GError* error = nullptr;
228 
229  auto filter = g_key_file_get_string (state_file, state_section,
230  KEY_PAGE_FILTER, &error);
231  std::string filter_str;
232 
233  if (error)
234  g_clear_error (&error);
235  else
236  filter_str = std::string (filter);
237 
238  g_free (filter);
239  g_free (state_section);
240 
241  if (!filter_str.empty())
242  return filter_str;
243 
244  return DEFAULT_FILTER + ";0;0;" + get_filter_default_num_of_days (ledger_type);
245 }
246 
247 static void
248 set_filterdata_to_defaults (FilterData *fd)
249 {
250  fd->cleared_match = (cleared_match_t)std::stol (DEFAULT_FILTER, nullptr, 16);
251  fd->start_time = 0;
252  fd->end_time = 0;
253  fd->days = 0;
254  fd->save_filter = false;
255 }
256 
257 static void
258 gnc_ppr_filter_load_filter_parts (GNCSplitReg *gsr, GNCLedgerDisplayType ledger_type, FilterData *fd)
259 {
260  set_filterdata_to_defaults (fd);
261  fd->dialog = nullptr;
262 
263  if (!gsr)
264  return;
265 
266  std::string filter_str = gnc_ppr_filter_load_filter (gsr, ledger_type);
267 
268  PINFO("Loaded Filter String is %s", filter_str.c_str());
269 
270  std::vector<std::string> split_filter = split_filter_by_delimiter (filter_str, ';');
271  int split_filter_size = split_filter.size();
272 
273  if (split_filter_size > 0 && (split_filter[0].compare (DEFAULT_FILTER)) != 0)
274  {
275  PINFO("Loaded Filter Status is %s", split_filter[0].c_str());
276 
277  fd->cleared_match = (cleared_match_t)std::stol (split_filter[0], nullptr, 16);
278  fd->save_filter = true;
279  }
280 
281  if (split_filter_size > 1 && (split_filter[1].compare (std::string ("0"))) != 0)
282  {
283  PINFO("Loaded Filter Start Date is %s", split_filter[1].c_str());
284 
285  fd->start_time = gnc_ppr_filter_dmy2time (split_filter[1]);
286  fd->start_time = gnc_time64_get_day_start (fd->start_time);
287  fd->save_filter = true;
288  }
289 
290  if (split_filter_size > 2 && (split_filter[2].compare (std::string ("0"))) != 0)
291  {
292  PINFO("Loaded Filter End Date is %s", split_filter[2].c_str());
293 
294  fd->end_time = gnc_ppr_filter_dmy2time (split_filter[2]);
295  fd->end_time = gnc_time64_get_day_end (fd->end_time);
296  fd->save_filter = true;
297  }
298 
299  // set the default for the number of days
300  fd->days = (int)std::stol (get_filter_default_num_of_days (ledger_type), nullptr, 10);
301 
302  if (split_filter_size > 3 &&
303  (split_filter[3].compare (get_filter_default_num_of_days (ledger_type)) != 0))
304  {
305  PINFO("Loaded Filter Days is %s", split_filter[3].c_str());
306 
307  fd->days = (int)std::stol (split_filter[3], nullptr, 10);
308  fd->save_filter = true;
309  }
310 }
311 
312 static void
313 gnc_ppr_filter_save_filter (GNCSplitReg *gsr, std::string filter)
314 
315 {
316  GNCLedgerDisplayType ledger_type = gnc_ledger_display_type (gsr->ledger);
317 
318  std::string default_filter_str = DEFAULT_FILTER + ";0;0;" +
319  get_filter_default_num_of_days (ledger_type);
320 
321  // save the filter to the .gcm file also
322  GKeyFile* state_file = gnc_state_get_current();
323  auto state_section = gsr_get_register_state_section (gsr);
324 
325  if (filter.empty() || (filter.compare (default_filter_str) == 0))
326  {
327  if (g_key_file_has_key (state_file, state_section, KEY_PAGE_FILTER, nullptr))
328  g_key_file_remove_key (state_file, state_section, KEY_PAGE_FILTER, nullptr);
329 
330  gnc_ppr_check_for_empty_group (state_file, state_section);
331  }
332  else
333  {
334  PINFO("The filter to save is %s", filter.c_str());
335  g_key_file_set_string (state_file, state_section, KEY_PAGE_FILTER,
336  filter.c_str());
337  }
338  g_free (state_section);
339 }
340 
341 static void
342 gnc_ppr_filter_save_filter_parts (GNCSplitReg *gsr, FilterData *fd)
343 {
344  if (!gsr)
345  return;
346 
347  std::string save_filter_str;
348 
349  if (fd->save_filter)
350  {
351  static const size_t buffer_size = 10;
352  char buffer [buffer_size];
353 
354  // cleared match
355  std::snprintf (buffer, buffer_size, "0x%04x", fd->cleared_match);
356  save_filter_str.append (buffer);
357 
358  // start time
359  if (fd->start_time != 0)
360  {
361  save_filter_str.append (";" + gnc_ppr_filter_time2dmy (fd->start_time));
362  }
363  else
364  save_filter_str.append (";0");
365 
366  // end time
367  if (fd->end_time != 0)
368  {
369  save_filter_str.append (";" + gnc_ppr_filter_time2dmy (fd->end_time));
370  }
371  else
372  save_filter_str.append (";0");
373 
374  // number of days
375  if (fd->days > 0)
376  {
377  save_filter_str.append (";" + std::to_string (fd->days));
378  }
379  else
380  save_filter_str.append (";0");
381  }
382  gnc_ppr_filter_save_filter (gsr, save_filter_str);
383 }
384 
385 static void
386 gpp_update_match_filter_text (cleared_match_t match, const guint mask,
387  const gchar* filter_name, GList **show, GList **hide)
388 {
389  if ((match & mask) == mask)
390  *show = g_list_prepend (*show, g_strdup (filter_name));
391  else
392  *hide = g_list_prepend (*hide, g_strdup (filter_name));
393 }
394 
403 void
405 {
406  GList *t_list = nullptr;
407 
408  ENTER(" ");
409 
410  auto gsr = gnc_plugin_page_register_get_gsr (plugin_page);
411 
412  // filtered start time
413  if (fd->start_time != 0)
414  {
415  auto sdate = qof_print_date (fd->start_time);
416  t_list = g_list_prepend
417  (t_list, g_strdup_printf ("%s %s", _("Start Date:"), sdate));
418  g_free (sdate);
419  }
420 
421  // filtered number of days
422  if (fd->days > 0)
423  {
424  t_list = g_list_prepend
425  (t_list, g_strdup_printf ("%s %d", _("Show previous number of days:"),
426  fd->days));
427  }
428  // filtered end time
429  if (fd->end_time != 0)
430  {
431  auto edate = qof_print_date (fd->end_time);
432  t_list = g_list_prepend
433  (t_list, g_strdup_printf ("%s %s", _("End Date:"), edate));
434  g_free (edate);
435  }
436 
437  // filtered match items
438  if (fd->cleared_match != CLEARED_ALL)
439  {
440  GList *show = nullptr;
441  GList *hide = nullptr;
442 
443  gpp_update_match_filter_text (fd->cleared_match, 0x01, _("Unreconciled"),
444  &show, &hide);
445  gpp_update_match_filter_text (fd->cleared_match, 0x02, _("Cleared"),
446  &show, &hide);
447  gpp_update_match_filter_text (fd->cleared_match, 0x04, _("Reconciled"),
448  &show, &hide);
449  gpp_update_match_filter_text (fd->cleared_match, 0x08, _("Frozen"),
450  &show, &hide);
451  gpp_update_match_filter_text (fd->cleared_match, 0x10, _("Voided"),
452  &show, &hide);
453 
454  show = g_list_reverse (show);
455  hide = g_list_reverse (hide);
456 
457  if (show)
458  {
459  auto str = gnc_list_formatter (show);
460  t_list = g_list_prepend
461  (t_list, g_strdup_printf ("%s %s", _("Show:"), str));
462  g_free (str);
463  }
464 
465  if (hide)
466  {
467  auto str = gnc_list_formatter (hide);
468  t_list = g_list_prepend
469  (t_list, g_strdup_printf ("%s %s", _("Hide:"), str));
470  g_free (str);
471  }
472 
473  g_list_free_full (show, g_free);
474  g_list_free_full (hide, g_free);
475  }
476 
477  t_list = g_list_reverse (t_list);
478 
479  if (t_list)
480  t_list = g_list_prepend (t_list, g_strdup (_("Filter By:")));
481 
482  // free the existing text if present
483  if (gsr->filter_text)
484  g_free (gsr->filter_text);
485 
486  // set the tooltip text variable in the gsr
487  gsr->filter_text = gnc_g_list_stringjoin (t_list, "\n");
488 
489  g_list_free_full (t_list, g_free);
490 
491  LEAVE(" ");
492 }
493 
506 static void
507 gnc_ppr_filter_update_status_query (GncPluginPage* plugin_page)
508 {
509  ENTER(" ");
510 
511  auto gsr = gnc_plugin_page_register_get_gsr (plugin_page);
512  if (!gsr->ledger)
513  {
514  LEAVE("no ledger");
515  return;
516  }
517 
518  // check if this a search register and save query
519  gnc_plugin_page_register_update_for_search_query (GNC_PLUGIN_PAGE_REGISTER(plugin_page));
520 
521  auto query = gnc_plugin_page_register_get_query (plugin_page);
522  if (!query)
523  {
524  LEAVE("no query found");
525  return;
526  }
527 
528  auto fd = gnc_plugin_page_register_get_filter_data (plugin_page);
529  auto reg = gnc_ledger_display_get_split_register (gsr->ledger);
530 
531  /* Remove the old status match */
532  if (reg->type != SEARCH_LEDGER)
533  {
534  GSList *param_list = qof_query_build_param_list (SPLIT_RECONCILE, nullptr);
535  qof_query_purge_terms (query, param_list);
536  g_slist_free (param_list);
537  }
538 
539  /* Install the new status match */
540  if (fd->cleared_match != CLEARED_ALL)
541  xaccQueryAddClearedMatch (query, fd->cleared_match, QOF_QUERY_AND);
542 
543  // Set filter tooltip for summary bar
544  gnc_ppr_filter_set_tooltip (plugin_page, fd);
545 
546  gnc_plugin_page_register_query_update (GNC_PLUGIN_PAGE_REGISTER(plugin_page), query);
547  LEAVE (" ");
548 }
549 
562 static void
563 gnc_ppr_filter_update_date_query (GncPluginPage* plugin_page)
564 {
565  ENTER(" ");
566 
567  auto gsr = gnc_plugin_page_register_get_gsr (plugin_page);
568  if (!gsr->ledger)
569  {
570  LEAVE("no ledger");
571  return;
572  }
573 
574  // check if this a search register and save query
575  gnc_plugin_page_register_update_for_search_query (GNC_PLUGIN_PAGE_REGISTER(plugin_page));
576 
577  auto query = gnc_plugin_page_register_get_query (plugin_page);
578  if (!query)
579  {
580  LEAVE("no query found");
581  return;
582  }
583 
584  auto fd = gnc_plugin_page_register_get_filter_data (plugin_page);
585  auto reg = gnc_ledger_display_get_split_register (gsr->ledger);
586 
587  /* Delete any existing old date spec. */
588  if (reg->type != SEARCH_LEDGER)
589  {
590  GSList *param_list = qof_query_build_param_list (SPLIT_TRANS,
591  TRANS_DATE_POSTED, nullptr);
592  qof_query_purge_terms (query, param_list);
593  g_slist_free (param_list);
594  }
595 
596  if (fd->start_time || fd->end_time)
597  {
598  /* Build a new spec */
599  xaccQueryAddDateMatchTT (query,
600  fd->start_time != 0, fd->start_time,
601  fd->end_time != 0, fd->end_time,
602  QOF_QUERY_AND);
603  }
604 
605  if (fd->days > 0)
606  {
607  time64 start;
608  struct tm tm;
609 
611 
612  tm.tm_mday = tm.tm_mday - fd->days;
613  start = gnc_mktime (&tm);
614  xaccQueryAddDateMatchTT (query, TRUE, start, FALSE, 0, QOF_QUERY_AND);
615  }
616 
617  // Set filter tooltip for summary bar
618  gnc_ppr_filter_set_tooltip (plugin_page, fd);
619 
620  gnc_plugin_page_register_query_update (GNC_PLUGIN_PAGE_REGISTER(plugin_page), query);
621  LEAVE(" ");
622 }
623 
630 void
632 {
633  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(plugin_page));
634 
635  auto fd = gnc_plugin_page_register_get_filter_data (plugin_page);
636 
637  set_filterdata_to_defaults (fd);
638 
639  gnc_ppr_filter_update_date_query (plugin_page);
640 }
641 
647 void
649 {
650  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(plugin_page));
651 
652  auto gsr = gnc_plugin_page_register_get_gsr (plugin_page);
653 
654  if (!gsr)
655  return;
656 
657  auto fd = gnc_plugin_page_register_get_filter_data (plugin_page);
658  GNCLedgerDisplayType ledger_type = gnc_ledger_display_type (gsr->ledger);
659 
660  /* Set the filter for the split register and status of save filter button */
661  fd->save_filter = false;
662 
663  gnc_ppr_filter_load_filter_parts (gsr, ledger_type, fd);
664 
665  if (ledger_type == LD_GL)
666  {
667  SplitRegister *reg = gnc_ledger_display_get_split_register (gsr->ledger);
668 
669  if (reg->type != GENERAL_JOURNAL) // search ledger and the like
670  set_filterdata_to_defaults (fd);
671  }
672  /* Update Query with Filter Status and Dates */
673  gnc_ppr_filter_update_status_query (plugin_page);
674  gnc_ppr_filter_update_date_query (plugin_page);
675 }
676 
686 void
687 gnc_ppr_filter_status_one_cb (GtkToggleButton* button,
689 {
690  g_return_if_fail (GTK_IS_CHECK_BUTTON(button));
691  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
692 
693  auto name = gtk_buildable_get_name (GTK_BUILDABLE(button));
694 
695  ENTER("toggle button %s (%p), plugin_page %p", name, button, rfd->plugin_page);
696 
697  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
698 
699  /* Determine what status bit to change */
700  int value = CLEARED_NONE;
701  for (const auto& action : status_actions)
702  {
703  if (action.action_name.compare (name) == 0)
704  {
705  value = action.value;
706  break;
707  }
708  }
709 
710  /* Compute the new match status */
711  if (gtk_toggle_button_get_active (button))
712  fd->cleared_match = (cleared_match_t)(fd->cleared_match | value);
713  else
714  fd->cleared_match = (cleared_match_t)(fd->cleared_match & ~value);
715 
716  gnc_ppr_filter_update_status_query (rfd->plugin_page);
717 
718  LEAVE(" ");
719 }
720 
721 static void
722 set_checkbutton_with_blocking (GtkWidget *widget,
723  GFunc function,
725  gboolean active)
726 {
727  PINFO("Block GtkToggleButton %p for setting active %s",
728  widget, active ? "TRUE" : "FALSE");
729  g_signal_handlers_block_by_func (widget,
730  (gpointer)function, rfd);
731  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(widget), active);
732  g_signal_handlers_unblock_by_func (widget,
733  (gpointer)function, rfd);
734 }
735 
744 void
747 {
748  g_return_if_fail (GTK_IS_BUTTON(button));
749  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
750 
751  ENTER("(button %p, page %p)", button, rfd->plugin_page);
752 
753  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
754 
755  /* Turn on all the check menu items */
756  for (const auto& action : status_actions)
757  {
758  set_checkbutton_with_blocking (action.widget,
760  rfd, TRUE);
761  }
762 
763  /* Set the requested status */
764  fd->cleared_match = CLEARED_ALL;
765  gnc_ppr_filter_update_status_query (rfd->plugin_page);
766  LEAVE(" ");
767 }
768 
777 void
780 {
781  g_return_if_fail (GTK_IS_BUTTON(button));
782  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
783 
784  ENTER("(button %p, page %p)", button, rfd->plugin_page);
785 
786  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
787 
788  /* Turn off all the check menu items */
789  for (const auto& action : status_actions)
790  {
791  set_checkbutton_with_blocking (action.widget,
793  rfd, FALSE);
794  }
795 
796  /* Set the requested status */
797  fd->cleared_match = CLEARED_NONE;
798  gnc_ppr_filter_update_status_query (rfd->plugin_page);
799  LEAVE(" ");
800 }
801 
812 static void
813 get_filter_times (RegisterFilterDialog* rfd)
814 {
815  time64 time_val;
816 
817  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
818 
819  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(rfd->start_date_choose)))
820  {
821  time_val = gnc_date_edit_get_date (GNC_DATE_EDIT(rfd->start_date));
822  time_val = gnc_time64_get_day_start (time_val);
823  fd->start_time = time_val;
824  }
825  else
826  {
827  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(rfd->start_date_today)))
828  fd->start_time = gnc_time64_get_today_start();
829  else
830  fd->start_time = 0;
831  }
832 
833  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(rfd->end_date_choose)))
834  {
835  time_val = gnc_date_edit_get_date (GNC_DATE_EDIT(rfd->end_date));
836  time_val = gnc_time64_get_day_end (time_val);
837  fd->end_time = time_val;
838  }
839  else
840  {
841  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(rfd->end_date_today)))
842  fd->end_time = gnc_time64_get_today_end();
843  else
844  fd->end_time = 0;
845  }
846 }
847 
858 void
859 gnc_ppr_filter_select_range_cb (GtkRadioButton* button,
861 {
862  g_return_if_fail (GTK_IS_RADIO_BUTTON(button));
863  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
864 
865  ENTER("(button %p, page %p)", button, rfd->plugin_page);
866 
867  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
868 
869  auto name = gtk_buildable_get_name (GTK_BUILDABLE(button));
870  gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button));
871 
872  if (active && g_strcmp0 (name, "filter_show_range") == 0)
873  {
874  gtk_widget_set_sensitive (rfd->table, active);
875  gtk_widget_set_sensitive (rfd->num_days, !active);
876  get_filter_times (rfd);
877  }
878  else if (active && g_strcmp0 (name, "filter_show_days") == 0)
879  {
880  gtk_widget_set_sensitive (rfd->table, !active);
881  gtk_widget_set_sensitive (rfd->num_days, active);
882  gtk_spin_button_set_value (GTK_SPIN_BUTTON(rfd->num_days), fd->days);
883  }
884  else
885  {
886  gtk_widget_set_sensitive (rfd->table, FALSE);
887  gtk_widget_set_sensitive (rfd->num_days, FALSE);
888  fd->start_time = 0;
889  fd->end_time = 0;
890  fd->days = 0;
891  }
892  gnc_ppr_filter_update_date_query (rfd->plugin_page);
893  LEAVE(" ");
894 }
895 
905 void
906 gnc_ppr_filter_days_changed_cb (GtkSpinButton* button,
908 {
909  g_return_if_fail (GTK_IS_SPIN_BUTTON(button));
910  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
911 
912  ENTER("(button %p, page %p)", button, rfd->plugin_page);
913 
914  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
915 
916  fd->days = gtk_spin_button_get_value (GTK_SPIN_BUTTON(button));
917  gnc_ppr_filter_update_date_query (rfd->plugin_page);
918 
919  LEAVE(" ");
920 }
921 
931 static void
932 gnc_ppr_filter_gde_changed_cb (GtkWidget* unused,
934 {
935  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
936 
937  ENTER("(widget %s(%p), page %p)",
938  gtk_buildable_get_name (GTK_BUILDABLE(unused)), unused, rfd->plugin_page);
939 
940  get_filter_times (rfd);
941  gnc_ppr_filter_update_date_query (rfd->plugin_page);
942 
943  LEAVE(" ");
944 }
945 
964 void
965 gnc_ppr_filter_start_cb (GtkWidget* radio,
967 {
968  g_return_if_fail (GTK_IS_RADIO_BUTTON(radio));
969  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
970 
971  ENTER("(radio %s(%p), page %p)",
972  gtk_buildable_get_name (GTK_BUILDABLE(radio)), radio, rfd->plugin_page);
973 
974  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(radio)))
975  {
976  LEAVE("1st callback of pair. Defer to 2nd callback.");
977  return;
978  }
979 
980  auto name = gtk_buildable_get_name (GTK_BUILDABLE(radio));
981  gboolean active = !g_strcmp0 (name, "start_date_choose");
982  gtk_widget_set_sensitive (rfd->start_date, active);
983  get_filter_times (rfd);
984  gnc_ppr_filter_update_date_query (rfd->plugin_page);
985 
986  LEAVE(" ");
987 }
988 
1007 void
1008 gnc_ppr_filter_end_cb (GtkWidget* radio,
1009  RegisterFilterDialog* rfd)
1010 {
1011  g_return_if_fail (GTK_IS_RADIO_BUTTON(radio));
1012  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
1013 
1014  ENTER("(radio %s(%p), page %p)",
1015  gtk_buildable_get_name (GTK_BUILDABLE(radio)), radio, rfd->plugin_page);
1016 
1017  if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(radio)))
1018  {
1019  LEAVE("1st callback of pair. Defer to 2nd callback.");
1020  return;
1021  }
1022 
1023  auto name = gtk_buildable_get_name (GTK_BUILDABLE(radio));
1024  gboolean active = !g_strcmp0 (name, "end_date_choose");
1025  gtk_widget_set_sensitive (rfd->end_date, active);
1026  get_filter_times (rfd);
1027  gnc_ppr_filter_update_date_query (rfd->plugin_page);
1028 
1029  LEAVE(" ");
1030 }
1031 
1039 void
1040 gnc_ppr_filter_save_cb (GtkToggleButton* button,
1041  RegisterFilterDialog* rfd)
1042 {
1043  g_return_if_fail (GTK_IS_CHECK_BUTTON(button));
1044  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
1045 
1046  ENTER("Save toggle button (%p), plugin_page %p", button, rfd->plugin_page);
1047 
1048  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
1049 
1050  /* Compute the new save filter status */
1051  if (gtk_toggle_button_get_active (button))
1052  fd->save_filter = true;
1053  else
1054  fd->save_filter = false;
1055 
1056  LEAVE(" ");
1057 }
1058 
1069 void
1070 gnc_ppr_filter_response_cb (GtkDialog* dialog,
1071  gint response,
1072  RegisterFilterDialog* rfd)
1073 {
1074  g_return_if_fail (GTK_IS_DIALOG(dialog));
1075  g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER(rfd->plugin_page));
1076 
1077  ENTER(" ");
1078 
1079  auto fd = gnc_plugin_page_register_get_filter_data (rfd->plugin_page);
1080  auto gsr = gnc_plugin_page_register_get_gsr (rfd->plugin_page);
1081 
1082  if (response != GTK_RESPONSE_OK)
1083  {
1084  /* Remove the old status match */
1085  fd->cleared_match = rfd->original_cleared_match;
1086  gnc_plugin_register_set_enable_refresh (GNC_PLUGIN_PAGE_REGISTER(rfd->plugin_page), FALSE);
1087  gnc_ppr_filter_update_status_query (rfd->plugin_page);
1088  gnc_plugin_register_set_enable_refresh (GNC_PLUGIN_PAGE_REGISTER(rfd->plugin_page), TRUE);
1089  fd->start_time = rfd->original_start_time;
1090  fd->end_time = rfd->original_end_time;
1091  fd->days = rfd->original_days;
1092  fd->save_filter = rfd->original_save_filter;
1093  gnc_ppr_filter_update_date_query (rfd->plugin_page);
1094  }
1095  else
1096  {
1097  // clear the filter when unticking the save option
1098  if (!fd->save_filter && rfd->original_save_filter)
1099  gnc_ppr_filter_save_filter (gsr, "");
1100 
1101  rfd->original_save_filter = fd->save_filter;
1102 
1103  if (fd->save_filter)
1104  gnc_ppr_filter_save_filter_parts (gsr, fd);
1105  }
1106  rfd->dialog = nullptr;
1107  fd->dialog = nullptr;
1108  g_free (rfd);
1109  gtk_widget_destroy (GTK_WIDGET(dialog));
1110  LEAVE(" ");
1111 }
1112 
1121 static void
1122 gnc_ppr_filter_dialog_create (RegisterFilterDialog* rfd, FilterData *fd, Query *query)
1123 {
1124  time64 start_time, end_time, time_val;
1125 
1126  /* Create the dialog */
1127  auto builder = gtk_builder_new();
1128  gnc_builder_add_from_file (builder, "gnc-plugin-page-register.glade",
1129  "days_adjustment");
1130  gnc_builder_add_from_file (builder, "gnc-plugin-page-register.glade",
1131  "filter_by_dialog");
1132  auto dialog = GTK_WIDGET(gtk_builder_get_object (builder, "filter_by_dialog"));
1133  rfd->dialog = dialog;
1134  fd->dialog = rfd->dialog;
1135 
1136  gtk_window_set_transient_for (GTK_WINDOW(dialog),
1137  gnc_window_get_gtk_window (GNC_WINDOW(
1138  GNC_PLUGIN_PAGE(rfd->plugin_page)->window)));
1139 
1140  /* Translators: The %s is the name of the plugin page */
1141  auto title = g_strdup_printf (_ ("Filter %s by…"),
1142  gnc_plugin_page_get_page_name (rfd->plugin_page));
1143  gtk_window_set_title (GTK_WINDOW(dialog), title);
1144  g_free (title);
1145 
1146  /* Set the check buttons for the current status */
1147  for (auto& action : status_actions)
1148  {
1149  auto toggle = GTK_WIDGET(gtk_builder_get_object (builder,
1150  action.action_name.c_str()));
1151  bool value = fd->cleared_match & action.value;
1152  action.widget = toggle;
1153  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(toggle), bool_to_gboolean (value));
1154  }
1155  rfd->original_cleared_match = fd->cleared_match;
1156 
1157  auto button = GTK_WIDGET(gtk_builder_get_object (builder, "filter_save"));
1158  if (fd->save_filter)
1159  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
1160 
1161  rfd->original_save_filter = fd->save_filter;
1162 
1163  // hide the save button if appropriate
1164  gtk_widget_set_visible (GTK_WIDGET(button), bool_to_gboolean (rfd->show_save_button));
1165 
1166  /* Set up number of days */
1167  rfd->num_days = GTK_WIDGET(gtk_builder_get_object (builder, "filter_show_num_days"));
1168  button = GTK_WIDGET(gtk_builder_get_object (builder, "filter_show_days"));
1169 
1170  if (fd->days > 0) // using number of days
1171  {
1172  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
1173  gtk_widget_set_sensitive (GTK_WIDGET(rfd->num_days), TRUE);
1174  gtk_spin_button_set_value (GTK_SPIN_BUTTON(rfd->num_days), fd->days);
1175  rfd->original_days = fd->days;
1176 
1177  /* Set the start_time and end_time to 0 */
1178  start_time = 0;
1179  end_time = 0;
1180  }
1181  else
1182  {
1183  gtk_widget_set_sensitive (GTK_WIDGET(rfd->num_days), FALSE);
1184  rfd->original_days = 0;
1185  fd->days = 0;
1186 
1187  /* Get the start and end times */
1188  xaccQueryGetDateMatchTT (query, &start_time, &end_time);
1189  }
1190 
1191  /* Set the date info */
1192  rfd->original_start_time = start_time;
1193  fd->start_time = start_time;
1194  rfd->original_end_time = end_time;
1195  fd->end_time = end_time;
1196 
1197  button = GTK_WIDGET(gtk_builder_get_object (builder, "filter_show_range"));
1198  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), start_time || end_time);
1199  auto table = GTK_WIDGET(gtk_builder_get_object (builder, "select_range_table"));
1200  rfd->table = table;
1201  gtk_widget_set_sensitive (GTK_WIDGET(table), start_time || end_time);
1202 
1203  rfd->start_date_choose = GTK_WIDGET(gtk_builder_get_object (builder, "start_date_choose"));
1204  rfd->start_date_today = GTK_WIDGET(gtk_builder_get_object (builder, "start_date_today"));
1205  rfd->end_date_choose = GTK_WIDGET(gtk_builder_get_object (builder, "end_date_choose"));
1206  rfd->end_date_today = GTK_WIDGET(gtk_builder_get_object (builder, "end_date_today"));
1207 
1208  bool sensitive;
1209  {
1210  /* Start date info */
1211  if (start_time == 0)
1212  {
1213  button = GTK_WIDGET(gtk_builder_get_object(builder, "start_date_earliest"));
1214  time_val = xaccQueryGetEarliestDateFound (query);
1215  sensitive = false;
1216  }
1217  else
1218  {
1219  time_val = start_time;
1220  if ((start_time >= gnc_time64_get_today_start()) &&
1221  (start_time <= gnc_time64_get_today_end()))
1222  {
1223  button = rfd->start_date_today;
1224  sensitive = false;
1225  }
1226  else
1227  {
1228  button = rfd->start_date_choose;
1229  sensitive = true;
1230  }
1231  }
1232  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
1233  rfd->start_date = gnc_date_edit_new (gnc_time (nullptr), FALSE, FALSE);
1234  auto hbox = GTK_WIDGET(gtk_builder_get_object (builder, "start_date_hbox"));
1235  gtk_box_pack_start (GTK_BOX(hbox), rfd->start_date, TRUE, TRUE, 0);
1236  gtk_widget_show (rfd->start_date);
1237  gtk_widget_set_sensitive (GTK_WIDGET(rfd->start_date), bool_to_gboolean (sensitive));
1238  gnc_date_edit_set_time (GNC_DATE_EDIT(rfd->start_date), time_val);
1239  g_signal_connect (G_OBJECT(rfd->start_date), "date-changed",
1240  G_CALLBACK(gnc_ppr_filter_gde_changed_cb), rfd);
1241  }
1242 
1243  {
1244  /* End date info */
1245  if (end_time == 0)
1246  {
1247  button = GTK_WIDGET(gtk_builder_get_object (builder, "end_date_latest"));
1248  time_val = xaccQueryGetLatestDateFound (query);
1249  sensitive = false;
1250  }
1251  else
1252  {
1253  time_val = end_time;
1254  if ((end_time >= gnc_time64_get_today_start()) &&
1255  (end_time <= gnc_time64_get_today_end()))
1256  {
1257  button = rfd->end_date_today;
1258  sensitive = false;
1259  }
1260  else
1261  {
1262  button = rfd->end_date_choose;
1263  sensitive = true;
1264  }
1265  }
1266  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), TRUE);
1267  rfd->end_date = gnc_date_edit_new (gnc_time (nullptr), FALSE, FALSE);
1268  auto hbox = GTK_WIDGET(gtk_builder_get_object (builder, "end_date_hbox"));
1269  gtk_box_pack_start (GTK_BOX(hbox), rfd->end_date, TRUE, TRUE, 0);
1270  gtk_widget_show (rfd->end_date);
1271  gtk_widget_set_sensitive (GTK_WIDGET(rfd->end_date), bool_to_gboolean (sensitive));
1272  gnc_date_edit_set_time (GNC_DATE_EDIT(rfd->end_date), time_val);
1273  g_signal_connect (G_OBJECT(rfd->end_date), "date-changed",
1274  G_CALLBACK(gnc_ppr_filter_gde_changed_cb), rfd);
1275  }
1276 
1277  /* Wire it up */
1278  gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, rfd);
1279 
1280  /* Show it */
1281  gtk_widget_show (dialog);
1282  g_object_unref (G_OBJECT(builder));
1283  LEAVE (" ");
1284 }
1285 
1297 void
1298 gnc_ppr_filter_by (GncPluginPage *plugin_page, Query *query,
1299  FilterData *fd, bool show_save_button)
1300 {
1301  RegisterFilterDialog *rfd;
1302 
1303  ENTER(" ");
1304 
1305  rfd = g_new0 (RegisterFilterDialog, 1);
1306 
1307  rfd->plugin_page = plugin_page;
1308  rfd->show_save_button = show_save_button;
1309 
1310  gnc_ppr_filter_dialog_create (rfd, fd, query);
1311 
1312  LEAVE(" ");
1313 }
Functions to load, save and get gui state.
Functions providing a register page filter for the GnuCash UI.
void gnc_plugin_register_set_enable_refresh(GncPluginPageRegister *page, gboolean enable_refresh)
This allows controlling when refreshes happen, used to reduce refreshes when different aspects of the...
The instance data structure for a content plugin.
Date and Time handling routines.
gchar * gnc_g_list_stringjoin(GList *list_of_strings, const gchar *sep)
Return a string joining a GList whose elements are gchar* strings.
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
void qof_query_purge_terms(QofQuery *q, QofQueryParamList *param_list)
Remove query terms of a particular type from q.
Definition: qofquery.cpp:705
FilterData * gnc_plugin_page_register_get_filter_data(GncPluginPage *plugin_page)
Get the FilterData data structure associated with this register page.
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
void gnc_ppr_filter_end_cb(GtkWidget *radio, RegisterFilterDialog *rfd)
This function is called when one of the end date radio buttons is selected.
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.
void gnc_ppr_filter_save_cb(GtkToggleButton *button, RegisterFilterDialog *rfd)
This function is called whenever the save status is checked or unchecked.
void gnc_ppr_filter_response_cb(GtkDialog *dialog, gint response, RegisterFilterDialog *rfd)
This function is called when the "Filter By…" dialog is closed.
void gnc_tm_get_today_start(struct tm *tm)
The gnc_tm_get_today_start() routine takes a pointer to a struct tm and fills it in with the first se...
Definition: gnc-date.cpp:1399
#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.
void gnc_tm_free(struct tm *time)
free a struct tm* created with gnc_localtime() or gnc_gmtime()
Definition: gnc-date.cpp:97
void gnc_ppr_filter_status_clear_all_cb(GtkButton *button, RegisterFilterDialog *rfd)
This function is called whenever the "clear all" status button is clicked.
void gnc_ppr_filter_select_range_cb(GtkRadioButton *button, RegisterFilterDialog *rfd)
This function is called when the radio buttons changes state.
char * qof_print_date(time64 secs)
Convenience; calls through to qof_print_date_dmy_buff().
Definition: gnc-date.cpp:610
Functions providing a register page for the GnuCash UI.
time64 gnc_time64_get_day_start(time64 time_val)
The gnc_time64_get_day_start() routine will take the given time in seconds and adjust it to the first...
Definition: gnc-date.cpp:1366
void gnc_ppr_filter_set_tooltip(GncPluginPage *plugin_page, FilterData *fd)
This function is used to update the tooltip shown in the register which shows a summary of the curren...
gchar * gnc_list_formatter(GList *strings)
This function takes a GList of char*, and uses locale-sensitive list formatter.
time64 gnc_time64_get_today_start(void)
The gnc_time64_get_today_start() routine returns a time64 value corresponding to the first second of ...
Definition: gnc-date.cpp:1417
void gnc_ppr_filter_update_register(GncPluginPage *plugin_page)
This function is called to update the register.
void gnc_ppr_filter_status_select_all_cb(GtkButton *button, RegisterFilterDialog *rfd)
This function is called whenever the "select all" status button is clicked.
time64 gnc_mktime(struct tm *time)
calculate seconds from the epoch given a time struct
Definition: gnc-date.cpp:219
void gnc_ppr_filter_by(GncPluginPage *plugin_page, Query *query, FilterData *fd, bool show_save_button)
This function is called for the filter dialog.
void gnc_ppr_filter_start_cb(GtkWidget *radio, RegisterFilterDialog *rfd)
This function is called when one of the start date radio buttons is selected.
struct tm * gnc_localtime(const time64 *secs)
fill out a time struct from a 64-bit time value.
Definition: gnc-date.cpp:103
GLib helper routines.
Generic api to store and retrieve preferences.
Query * gnc_plugin_page_register_get_query(GncPluginPage *plugin_page)
This function is called to get the query associated with this plugin page.
time64 gnc_time64_get_today_end(void)
The gnc_time64_get_today_end() routine returns a time64 value corresponding to the last second of tod...
Definition: gnc-date.cpp:1426
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
time64 gnc_time(time64 *tbuf)
get the current time
Definition: gnc-date.cpp:262
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
void gnc_plugin_page_register_query_update(GncPluginPageRegister *page, Query *query)
This updates the query after the filters have been applied.
time64 gnc_time64_get_day_end(time64 time_val)
The gnc_time64_get_day_end() routine will take the given time in seconds and adjust it to the last se...
Definition: gnc-date.cpp:1386
SplitRegister * gnc_ledger_display_get_split_register(GNCLedgerDisplay *ld)
return the split register associated with a ledger display
void gnc_plugin_page_register_update_for_search_query(GncPluginPageRegister *page)
This checks if the register is a search register and if so saves the query.
GNCSplitReg * gnc_plugin_page_register_get_gsr(GncPluginPage *plugin_page)
Get the GNCSplitReg data structure associated with this register page.
A Query.
Definition: qofquery.cpp:74
void gnc_ppr_filter_days_changed_cb(GtkSpinButton *button, RegisterFilterDialog *rfd)
This function is called when the "number of days" spin button is changed which is then saved and upda...
void gnc_ppr_filter_status_one_cb(GtkToggleButton *button, RegisterFilterDialog *rfd)
This function is called whenever one of the status entries is checked or unchecked.
void gnc_ppr_filter_clear_current_filter(GncPluginPage *plugin_page)
This function is used to clear the current filter so that a specific split can be shown in the regist...