GnuCash  5.6-150-g038405b370+
gnc-option-gtk-ui.cpp
1 /********************************************************************\
2  * gnc-option-gtk-ui.cpp -- Gtk Widgets for manipulating options *
3  * Copyright 2022 John Ralls <jralls@ceridwen.us> *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA gnu@gnu.org *
21 \********************************************************************/
22 
23 #include <gnc-option.hpp>
24 #include <gnc-option-impl.hpp>
25 #include "gnc-option-gtk-ui.hpp"
26 #include <config.h> // for scanf format string
27 #include <memory>
28 #include <cstdint>
29 #include <qof.h>
30 #include <gnc-engine.h> // for GNC_MOD_GUI
31 #include <gnc-commodity.h> // for GNC_COMMODITY
32 #include "gnc-account-sel.h" // for GNC_ACCOUNT_SEL
33 #include "gnc-currency-edit.h" //for GNC_CURRENCY_EDIT
34 #include "gnc-commodity-edit.h" //for gnc_commodity_get_string
35 #include "gnc-date-edit.h" // for gnc_date_edit
36 #include "gnc-date-format.h" //for GNC_DATE_FORMAT
37 #include "gnc-general-select.h" // for GNC_GENERAL_SELECT
38 #include "gnc-option-uitype.hpp"
39 #include "gnc-tree-view-account.h" // for GNC_TREE_VIEW_ACCOUNT
40 #include "gnc-tree-model-budget.h" // for gnc_tree_model_budget
41 #include "misc-gnome-utils.h" // for xxxgtk_textview_set_text
42 
43 /*Something somewhere in windows.h defines ABSOLUTE to something and
44  *that contaminates using it in RelativeDateType. Undef it.
45  */
46 #ifdef ABSOLUTE
47 #undef ABSOLUTE
48 #endif
49 
50 /* This static indicates the debugging module that this .o belongs to. */
51 static QofLogModule log_module = GNC_MOD_GUI;
52 
53 //Init the class static.
54 std::vector<WidgetCreateFunc> GncOptionUIFactory::s_registry{static_cast<size_t>(GncOptionUIType::MAX_VALUE)};
55 bool GncOptionUIFactory::s_initialized{false};
56 static void gnc_options_ui_factory_initialize (void);
57 
58 void
59 GncOptionUIFactory::set_func(GncOptionUIType type, WidgetCreateFunc func)
60 {
61  s_registry[static_cast<size_t>(type)] = func;
62 }
63 
64 void
65 GncOptionUIFactory::create(GncOption& option, GtkGrid* page, int row)
66 {
67  if (!s_initialized)
68  {
69  gnc_options_ui_factory_initialize();
70  s_initialized = true;
71  }
72  auto type{option.get_ui_type()};
73  auto func{s_registry[static_cast<size_t>(type)]};
74  if (func)
75  func(option, page, row);
76  else
77  PERR("No function registered for type %d", static_cast<int>(type));
78 }
79 
80 GncOptionGtkUIItem::GncOptionGtkUIItem(GtkWidget* widget,
81  GncOptionUIType type) :
82  GncOptionUIItem{type},
83  m_widget{static_cast<GtkWidget*>(g_object_ref(widget))} {}
84 
85 GncOptionGtkUIItem::GncOptionGtkUIItem(const GncOptionGtkUIItem& item) :
86  GncOptionUIItem{item.get_ui_type()},
87  m_widget{static_cast<GtkWidget*>(g_object_ref(item.get_widget()))} {}
88 
89 GncOptionGtkUIItem::~GncOptionGtkUIItem()
90 {
91  if (m_widget)
92  g_object_unref(m_widget);
93 }
94 
95 void
96 GncOptionGtkUIItem::set_selectable(bool selectable) const noexcept
97 {
98  if (m_widget)
99  gtk_widget_set_sensitive (m_widget, selectable);
100 }
101 
102 void
104 {
105  if (m_widget)
106  g_object_unref(m_widget);
107  m_widget = nullptr;
108 }
109 
110 void
111 GncOptionGtkUIItem::set_widget(GtkWidget* widget)
112 {
113  if (get_ui_type() == GncOptionUIType::INTERNAL)
114  {
115  std::string error{"INTERNAL option, setting the UI item forbidden."};
116  throw std::logic_error(error);
117  }
118 
119  if (m_widget)
120  g_object_unref(m_widget);
121 
122  m_widget = static_cast<GtkWidget*>(g_object_ref(widget));
123 }
124 
125 SCM
126 GncOptionGtkUIItem::get_widget_scm_value(const GncOption& option) const
127 {
128  return SCM_BOOL_F;
129 }
130 /* ****************************************************************/
131 /* Option Widgets */
132 /* ***************************************************************/
133 
134 static inline GtkWidget* const
135 option_get_gtk_widget(const GncOption* option)
136 {
137  if (!option) return nullptr;
138  auto ui_item{dynamic_cast<const GncOptionGtkUIItem*>(option->get_ui_item())};
139  if (ui_item)
140  return ui_item->get_widget();
141 
142  return nullptr;
143 }
144 
145 static inline void
146 wrap_check_button (const GncOption& option, GtkWidget* widget, GtkGrid* page_box, int row)
147 {
148  auto enclosing{gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5)};
149  gtk_box_set_homogeneous (GTK_BOX (enclosing), FALSE);
150  gtk_box_pack_start(GTK_BOX(enclosing), widget, FALSE, FALSE, 0);
151  set_tool_tip(option, enclosing);
152  gtk_widget_show_all(enclosing);
153  /* attach the option widget to the second column of the grid */
154  grid_attach_widget (GTK_GRID(page_box), enclosing, row);
155 }
156 
158 {
159 public:
160  GncGtkBooleanUIItem(GtkWidget* widget) :
161  GncOptionGtkUIItem{widget, GncOptionUIType::BOOLEAN} {}
162  void set_ui_item_from_option(GncOption& option) noexcept override
163  {
164  auto widget{GTK_TOGGLE_BUTTON(get_widget())};
165  gtk_toggle_button_set_active(widget, option.get_value<bool>());
166  }
167  void set_option_from_ui_item(GncOption& option) noexcept override
168  {
169  auto widget{GTK_TOGGLE_BUTTON(get_widget())};
170  option.set_value(static_cast<bool>(gtk_toggle_button_get_active(widget)));
171  }
172  SCM get_widget_scm_value(const GncOption& option) const override
173  {
174  auto widget{GTK_TOGGLE_BUTTON(get_widget())};
175  return gtk_toggle_button_get_active(widget) ?
176  SCM_BOOL_T : SCM_BOOL_F;
177  }
178 };
179 
180 template <> void
181 create_option_widget<GncOptionUIType::BOOLEAN> (GncOption& option,
182  GtkGrid* page_box, int row)
183 
184 {
185  char *local_name{nullptr};
186  auto name{option.get_name().c_str()};
187  if (name && *name)
188  local_name = _(name);
189  auto widget{gtk_check_button_new_with_label (local_name)};
190 
191  auto ui_item{std::make_unique<GncGtkBooleanUIItem>(widget)};
192 
193  option.set_ui_item(std::move(ui_item));
194  option.set_ui_item_from_option();
195 
196  g_signal_connect(G_OBJECT(widget), "toggled",
197  G_CALLBACK(gnc_option_changed_widget_cb), &option);
198 
199  wrap_check_button(option, widget, page_box, row);
200 }
201 
203 {
204 public:
205  GncGtkStringUIItem(GtkWidget* widget) :
206  GncOptionGtkUIItem{widget, GncOptionUIType::STRING} {}
207  void set_ui_item_from_option(GncOption& option) noexcept override
208  {
209  auto widget{GTK_ENTRY(get_widget())};
210  gtk_entry_set_text(widget, option.get_value<std::string>().c_str());
211  }
212  void set_option_from_ui_item(GncOption& option) noexcept override
213  {
214  auto widget{GTK_ENTRY(get_widget())};
215  option.set_value(std::string{gtk_entry_get_text(widget)});
216  }
217 };
218 
219 template<> void
220 create_option_widget<GncOptionUIType::STRING> (GncOption& option,
221  GtkGrid *page_box, int row)
222 {
223  auto enclosing{gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5)};
224  gtk_widget_set_hexpand (GTK_WIDGET(enclosing), TRUE);
225  gtk_box_set_homogeneous (GTK_BOX (enclosing), FALSE);
226  auto widget = gtk_entry_new();
227  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
228  gtk_entry_set_alignment (GTK_ENTRY(widget), 1.0);
229  auto ui_item{std::make_unique<GncGtkStringUIItem>(widget)};
230 
231  option.set_ui_item(std::move(ui_item));
232  option.set_ui_item_from_option();
233 
234  g_signal_connect(G_OBJECT(widget), "changed",
235  G_CALLBACK(gnc_option_changed_widget_cb), &option);
236  gtk_box_pack_start(GTK_BOX(enclosing), widget, TRUE, TRUE, 0);
237  set_name_label(option, page_box, row, true);
238  set_tool_tip(option, enclosing);
239  gtk_widget_show_all(enclosing);
240  grid_attach_widget (GTK_GRID(page_box), enclosing, row);
241 }
242 
244 {
245 public:
246  GncGtkTextUIItem(GtkWidget* widget) :
247  GncOptionGtkUIItem{widget, GncOptionUIType::TEXT} {}
248  void set_ui_item_from_option(GncOption& option) noexcept override
249  {
250  auto widget{GTK_TEXT_VIEW(get_widget())};
251  xxxgtk_textview_set_text(widget, option.get_value<std::string>().c_str());
252  }
253  void set_option_from_ui_item(GncOption& option) noexcept override
254  {
255  auto widget{GTK_TEXT_VIEW(get_widget())};
256  auto str{xxxgtk_textview_get_text(widget)};
257  option.set_value(std::string{str});
258  g_free (str);
259  }
260 };
261 
262 template<> void
263 create_option_widget<GncOptionUIType::TEXT> (GncOption& option, GtkGrid *page_box, int row)
264 {
265  auto scroll = gtk_scrolled_window_new(NULL, NULL);
266  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
267  GTK_POLICY_NEVER,
268  GTK_POLICY_AUTOMATIC);
269  gtk_container_set_border_width(GTK_CONTAINER(scroll), 2);
270 
271  auto frame = gtk_frame_new(NULL);
272  gtk_container_add(GTK_CONTAINER(frame), scroll);
273 
274  auto enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
275  gtk_widget_set_vexpand (GTK_WIDGET(enclosing), TRUE);
276  gtk_widget_set_hexpand (GTK_WIDGET(enclosing), TRUE);
277  gtk_box_set_homogeneous (GTK_BOX (enclosing), FALSE);
278  auto widget = gtk_text_view_new();
279  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD);
280  gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), TRUE);
281  gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW(widget), FALSE);
282 
283  auto ui_item{std::make_unique<GncGtkTextUIItem>(widget)};
284  auto text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
285  option.set_ui_item(std::move(ui_item));
286  option.set_ui_item_from_option();
287 
288  g_signal_connect(G_OBJECT(text_buffer), "changed",
289  G_CALLBACK(gnc_option_changed_option_cb), &option);
290  gtk_container_add (GTK_CONTAINER (scroll), widget);
291  gtk_box_pack_start(GTK_BOX(enclosing), frame, TRUE, TRUE, 0);
292  set_name_label(option, page_box, row, true);
293  set_tool_tip(option, enclosing);
294  gtk_widget_show_all(enclosing);
295  grid_attach_widget(GTK_GRID(page_box), enclosing, row);
296 }
297 
299 {
300 public:
301  GncGtkCurrencyUIItem(GtkWidget* widget) :
302  GncOptionGtkUIItem{widget, GncOptionUIType::CURRENCY} {}
303  void set_ui_item_from_option(GncOption& option) noexcept override
304  {
305  auto widget{GNC_CURRENCY_EDIT(get_widget())};
306  auto currency{option.get_value<gnc_commodity*>()};
307 
308  if (currency)
309  gnc_currency_edit_set_currency(widget, GNC_COMMODITY(currency));
310  }
311  void set_option_from_ui_item(GncOption& option) noexcept override
312  {
313  auto widget{GNC_CURRENCY_EDIT(get_widget())};
314  auto currency = gnc_currency_edit_get_currency(widget);
315  option.set_value<gnc_commodity*>(currency);
316  }
317 };
318 
319 template<> void
320 create_option_widget<GncOptionUIType::CURRENCY> (GncOption& option, GtkGrid *page_box,
321  int row)
322 {
323  auto widget{gnc_currency_edit_new()};
324  auto ui_item{std::make_unique<GncGtkCurrencyUIItem>(widget)};
325  option.set_ui_item(std::move(ui_item));
326  option.set_ui_item_from_option();
327 
328  g_signal_connect(G_OBJECT(widget), "changed",
329  G_CALLBACK(gnc_option_changed_widget_cb), &option);
330  wrap_widget(option, widget, page_box, row);
331 }
332 
334 {
335 public:
336  GncGtkCommodityUIItem(GtkWidget* widget) :
337  GncOptionGtkUIItem{widget, GncOptionUIType::COMMODITY} {}
338  void set_ui_item_from_option(GncOption& option) noexcept override
339  {
340  auto widget{GNC_GENERAL_SELECT(get_widget())};
341  auto commodity{option.get_value<gnc_commodity*>()};
342 
343  if (commodity)
344  gnc_general_select_set_selected(widget, GNC_COMMODITY(commodity));
345  }
346  void set_option_from_ui_item(GncOption& option) noexcept override
347  {
348  auto widget{GNC_GENERAL_SELECT(get_widget())};
349  auto commodity{gnc_general_select_get_selected(widget)};
350  option.set_value<gnc_commodity*>(GNC_COMMODITY(commodity));
351  }
352 };
353 
354 template<> void
355 create_option_widget<GncOptionUIType::COMMODITY> (GncOption& option, GtkGrid *page_box,
356  int row)
357 {
358  auto widget = gnc_general_select_new(GNC_GENERAL_SELECT_TYPE_SELECT,
359  gnc_commodity_edit_get_string,
360  gnc_commodity_edit_new_select,
361  NULL);
362 
363  auto ui_item{std::make_unique<GncGtkCommodityUIItem>(widget)};
364  option.set_ui_item(std::move(ui_item));
365  option.set_ui_item_from_option();
366  g_signal_connect(G_OBJECT(GNC_GENERAL_SELECT(widget)->entry), "changed",
367  G_CALLBACK(gnc_option_changed_widget_cb), &option);
368  wrap_widget(option, widget, page_box, row);
369 }
370 
371 static GtkWidget*
372 create_multichoice_widget(GncOption& option)
373 {
374  auto num_values = option.num_permissible_values();
375 
376  g_return_val_if_fail(num_values >= 0, NULL);
377  auto renderer = gtk_cell_renderer_text_new();
378  auto store = gtk_list_store_new(1, G_TYPE_STRING);
379  /* Add values to the list store, entry and tooltip */
380  for (decltype(num_values) i = 0; i < num_values; i++)
381  {
382  GtkTreeIter iter;
383  auto itemstring = option.permissible_value_name(i);
384  gtk_list_store_append (store, &iter);
385  gtk_list_store_set(store, &iter, 0,
386  (itemstring && *itemstring) ? _(itemstring) : "", -1);
387  }
388  /* Create the new Combo with tooltip and add the store */
389  auto widget{GTK_WIDGET(gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)))};
390  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(widget), renderer, TRUE);
391  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(widget),
392  renderer, "text", 0);
393  g_object_unref(store);
394 
395  return widget;
396 }
397 
399 {
400 public:
401  GncGtkMultichoiceUIItem(GtkWidget* widget) :
402  GncOptionGtkUIItem{widget, GncOptionUIType::MULTICHOICE} {}
403  void set_ui_item_from_option(GncOption& option) noexcept override
404  {
405  auto widget{GTK_COMBO_BOX(get_widget())};
406  gtk_combo_box_set_active(widget, option.get_value<uint16_t>());
407  }
408  void set_option_from_ui_item(GncOption& option) noexcept override
409  {
410  auto widget{GTK_COMBO_BOX(get_widget())};
411  option.set_value<uint16_t>(static_cast<uint16_t>(gtk_combo_box_get_active(widget)));
412  }
413  SCM get_widget_scm_value(const GncOption& option) const override
414  {
415  auto widget{GTK_COMBO_BOX(get_widget())};
416  auto id{gtk_combo_box_get_active(widget)};
417  auto value{option.permissible_value(id)};
418  return scm_from_utf8_symbol(value);
419  }
420 };
421 
422 template<> void
423 create_option_widget<GncOptionUIType::MULTICHOICE> (GncOption& option, GtkGrid *page_box,
424  int row)
425 {
426  auto widget{create_multichoice_widget(option)};
427  auto ui_item{std::make_unique<GncGtkMultichoiceUIItem>(widget)};
428  option.set_ui_item(std::move(ui_item));
429  option.set_ui_item_from_option();
430  g_signal_connect(G_OBJECT(widget), "changed",
431  G_CALLBACK(gnc_option_changed_widget_cb), &option);
432  wrap_widget(option, widget, page_box, row);
433 }
434 
435 
437 {
438 public:
439  GncDateEntry() = default;
440  virtual ~GncDateEntry() = default;
441  virtual void set_entry_from_option(GncOption& option) = 0;
442  virtual void set_option_from_entry(GncOption& option) = 0;
443  // Get the widget that has data
444  virtual GtkWidget* get_entry() = 0;
445  // Get the widget that gets put on the page
446  virtual GtkWidget* get_widget() = 0;
447  virtual void toggle_relative(bool) {} //BothDateEntry only
448  virtual void block_signals(bool) = 0;
449 };
450 
451 
452 using GncDateEntryPtr = std::unique_ptr<GncDateEntry>;
453 
455 {
456 public:
457  AbsoluteDateEntry(GncOption& option);
458  ~AbsoluteDateEntry() = default;
459  void set_entry_from_option(GncOption& option) override;
460  void set_option_from_entry(GncOption& option) override;
461  GtkWidget* get_entry() override { return GTK_WIDGET(m_entry); }
462  GtkWidget* get_widget() override { return GTK_WIDGET(m_entry); }
463  void block_signals(bool) override;
464 private:
465  GNCDateEdit* m_entry;
466  unsigned long m_handler_id;
467 };
468 
469 AbsoluteDateEntry::AbsoluteDateEntry(GncOption& option) :
470  m_entry{GNC_DATE_EDIT(gnc_date_edit_new(time(NULL), FALSE, FALSE))}
471 {
472  auto entry = GNC_DATE_EDIT(m_entry)->date_entry;
473  m_handler_id = g_signal_connect(G_OBJECT(entry), "changed",
474  G_CALLBACK(gnc_option_changed_option_cb),
475  &option);
476 }
477 
478 void
479 AbsoluteDateEntry::block_signals(bool block)
480 {
481  auto entry{G_OBJECT(GNC_DATE_EDIT(m_entry)->date_entry)};
482  if (block)
483  g_signal_handler_block(entry, m_handler_id);
484  else
485  g_signal_handler_unblock(entry, m_handler_id);
486 }
487 
488 void
489 AbsoluteDateEntry::set_entry_from_option(GncOption& option)
490 {
491  gnc_date_edit_set_time(m_entry, option.get_value<time64>());
492 }
493 
494 void
495 AbsoluteDateEntry::set_option_from_entry(GncOption& option)
496 {
497  option.set_value<time64>(gnc_date_edit_get_date(m_entry));
498 }
499 
501 {
502 public:
503  RelativeDateEntry(GncOption& option);
504  ~RelativeDateEntry() = default;
505  void set_entry_from_option(GncOption& option) override;
506  void set_option_from_entry(GncOption& option) override;
507  GtkWidget* get_widget() override { return m_entry; }
508  GtkWidget* get_entry() override { return m_entry; }
509  void block_signals(bool) override;
510 private:
511  GtkWidget* m_entry;
512  unsigned long m_handler_id;
513 };
514 
515 
516 RelativeDateEntry::RelativeDateEntry(GncOption& option)
517 {
518 
519  auto renderer = gtk_cell_renderer_text_new();
520  auto store = gtk_list_store_new(1, G_TYPE_STRING);
521  /* Add values to the list store, entry and tooltip */
522  auto num = option.num_permissible_values();
523  for (decltype(num) index = 0; index < num; ++index)
524  {
525  GtkTreeIter iter;
526  gtk_list_store_append (store, &iter);
527  gtk_list_store_set (store, &iter, 0,
528  _(option.permissible_value_name(index)), -1);
529  }
530 
531  /* Create the new Combo with tooltip and add the store */
532  m_entry = GTK_WIDGET(gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)));
533  gtk_combo_box_set_active(GTK_COMBO_BOX(m_entry), 0);
534  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(m_entry), renderer, TRUE);
535  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(m_entry),
536  renderer, "text", 0);
537 
538  g_object_unref(store);
539 
540  m_handler_id = g_signal_connect(G_OBJECT(m_entry), "changed",
541  G_CALLBACK(gnc_option_changed_widget_cb),
542  &option);
543 }
544 
545 void
546 RelativeDateEntry::set_entry_from_option(GncOption& option)
547 {
548  gtk_combo_box_set_active(GTK_COMBO_BOX(m_entry), option.get_value<uint16_t>());
549 }
550 
551 void
552 RelativeDateEntry::set_option_from_entry(GncOption& option)
553 {
554  option.set_value<uint16_t>(gtk_combo_box_get_active(GTK_COMBO_BOX(m_entry)));
555 }
556 
557 void
558 RelativeDateEntry::block_signals(bool block)
559 {
560  if (block)
561  g_signal_handler_block(m_entry, m_handler_id);
562  else
563  g_signal_handler_unblock(m_entry, m_handler_id);
564 }
565 
566 using AbsoluteDateEntryPtr = std::unique_ptr<AbsoluteDateEntry>;
567 using RelativeDateEntryPtr = std::unique_ptr<RelativeDateEntry>;
568 
570 {
571 public:
572  BothDateEntry(GncOption& option);
573  ~BothDateEntry() = default; //The GncOptionGtkUIItem owns the widget
574  void set_entry_from_option(GncOption& option) override;
575  void set_option_from_entry(GncOption& option) override;
576  GtkWidget* get_widget() override { return m_widget; }
577  GtkWidget* get_entry() override;
578  void toggle_relative(bool use_absolute) override;
579  void block_signals(bool) override;
580 private:
581  GtkWidget* m_widget;
582  GtkWidget* m_abs_button;
583  AbsoluteDateEntryPtr m_abs_entry;
584  GtkWidget* m_rel_button;
585  RelativeDateEntryPtr m_rel_entry;
586  bool m_use_absolute = true;
587  unsigned long m_abs_hdlr;
588  unsigned long m_rel_hdlr;
589 };
590 
591 static void date_set_absolute_cb(GtkWidget *widget, gpointer data1);
592 static void date_set_relative_cb(GtkWidget *widget, gpointer data1);
593 
594 BothDateEntry::BothDateEntry(GncOption& option) :
595  m_widget{gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5)},
596  m_abs_button{gtk_radio_button_new(NULL)},
597  m_abs_entry{std::make_unique<AbsoluteDateEntry>(option)},
598  m_rel_button{
599  gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(m_abs_button))},
600  m_rel_entry{std::make_unique<RelativeDateEntry>(option)}
601 {
602  gtk_box_set_homogeneous (GTK_BOX(m_widget), FALSE);
603  m_abs_hdlr = g_signal_connect(G_OBJECT(m_abs_button), "toggled",
604  G_CALLBACK(date_set_absolute_cb), &option);
605  m_rel_hdlr = g_signal_connect(G_OBJECT(m_rel_button), "toggled",
606  G_CALLBACK(date_set_relative_cb), &option);
607 
608  gtk_box_pack_start(GTK_BOX(m_widget),
609  m_abs_button, FALSE, FALSE, 0);
610  gtk_box_pack_start(GTK_BOX(m_widget),
611  m_abs_entry->get_entry(), FALSE, FALSE, 0);
612  gtk_box_pack_start(GTK_BOX(m_widget),
613  m_rel_button, FALSE, FALSE, 0);
614  gtk_box_pack_start(GTK_BOX(m_widget),
615  m_rel_entry->get_entry(), FALSE, FALSE, 0);
616 
617 }
618 
619 GtkWidget*
620 BothDateEntry::get_entry()
621 {
622  return m_use_absolute ? m_abs_entry->get_entry() : m_rel_entry->get_entry();
623 }
624 
625 void
626 BothDateEntry::toggle_relative(bool use_absolute)
627 {
628  m_use_absolute = use_absolute;
629 
630  gtk_widget_set_sensitive(GTK_WIDGET(m_abs_entry->get_widget()),
631  m_use_absolute);
632  gtk_widget_set_sensitive(GTK_WIDGET(m_rel_entry->get_widget()),
633  !m_use_absolute);
634 }
635 
636 void
637 BothDateEntry::set_entry_from_option(GncOption& option)
638 {
639  m_use_absolute =
640  option.get_value<RelativeDatePeriod>() == RelativeDatePeriod::ABSOLUTE;
641  if (m_use_absolute)
642  m_abs_entry->set_entry_from_option(option);
643  else
644  m_rel_entry->set_entry_from_option(option);
645  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_rel_button),
646  !m_use_absolute);
647  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_abs_button),
648  m_use_absolute);
649 
650  toggle_relative(m_use_absolute);
651 }
652 
653 void
654 BothDateEntry::set_option_from_entry(GncOption& option)
655 {
656  if (m_use_absolute)
657  m_abs_entry->set_option_from_entry(option);
658  else
659  m_rel_entry->set_option_from_entry(option);
660 }
661 
662 void
663 BothDateEntry::block_signals(bool block)
664 {
665  if (block)
666  {
667  g_signal_handler_block(m_abs_button, m_abs_hdlr);
668  g_signal_handler_block(m_rel_button, m_rel_hdlr);
669  }
670  else
671  {
672  g_signal_handler_unblock(m_abs_button, m_abs_hdlr);
673  g_signal_handler_unblock(m_rel_button, m_rel_hdlr);
674  }
675  m_abs_entry->block_signals(block);
676  m_rel_entry->block_signals(block);
677 }
678 
680 {
681 public:
682  GncOptionDateUIItem(GncDateEntryPtr entry, GncOptionUIType type) :
683  GncOptionGtkUIItem{entry->get_widget(), type}, m_entry{std::move(entry)} { }
684  ~GncOptionDateUIItem() = default;
685  void clear_ui_item() override { m_entry = nullptr; }
686  void set_ui_item_from_option(GncOption& option) noexcept override
687  {
688  if (m_entry)
689  m_entry->set_entry_from_option(option);
690  }
691  void set_option_from_ui_item(GncOption& option) noexcept override
692  {
693  if (m_entry)
694  m_entry->set_option_from_entry(option);
695  }
696  GtkWidget* get_widget() const override
697  {
698  return m_entry->get_widget();
699  }
700  GncDateEntry* get_entry() { return m_entry.get(); }
701 private:
702  GncDateEntryPtr m_entry;
703 };
704 
705 static void
706 date_set_absolute_cb(GtkWidget *widget, gpointer data1)
707 {
708  GncOption* option = static_cast<decltype(option)>(data1);
709  auto ui_item = option->get_ui_item();
710  if (auto date_ui = dynamic_cast<const GncOptionDateUIItem* const>(ui_item))
711  {
712  const_cast<GncOptionDateUIItem*>(date_ui)->get_entry()->toggle_relative(true);
713  gnc_option_changed_option_cb(widget, option);
714  }
715 }
716 
717 static void
718 date_set_relative_cb(GtkWidget *widget, gpointer data1)
719 {
720  GncOption* option = static_cast<decltype(option)>(data1);
721  auto ui_item = option->get_ui_item();
722  if (auto date_ui = dynamic_cast<const GncOptionDateUIItem* const>(ui_item))
723  {
724  const_cast<GncOptionDateUIItem*>(date_ui)->get_entry()->toggle_relative(false);
725  gnc_option_changed_option_cb(widget, option);
726  }
727 }
728 
729 static void
730 create_date_option_widget(GncOption& option, GtkGrid *page_box, int row)
731 {
732  GtkWidget *enclosing{nullptr};
733  auto type = option.get_ui_type();
734  switch (type)
735  {
736  case GncOptionUIType::DATE_ABSOLUTE:
737  option.set_ui_item(std::make_unique<GncOptionDateUIItem>(std::make_unique<AbsoluteDateEntry>(option), type));
738  break;
739  case GncOptionUIType::DATE_RELATIVE:
740  option.set_ui_item(std::make_unique<GncOptionDateUIItem>(std::make_unique<RelativeDateEntry>(option), type));
741  break;
742  case GncOptionUIType::DATE_BOTH:
743  option.set_ui_item(std::make_unique<GncOptionDateUIItem>(std::make_unique<BothDateEntry>(option), type));
744  break;
745  default:
746  PERR("Attempted to create date option widget with wrong UI type %d",
747  static_cast<int>(type));
748  break;
749  }
750 
751  auto widget{option_get_gtk_widget(&option)};
752  if (type == GncOptionUIType::DATE_RELATIVE)
753  {
754  enclosing = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
755  gtk_box_set_homogeneous(GTK_BOX (enclosing), FALSE);
756 
757  gtk_box_pack_start(GTK_BOX(enclosing), widget, FALSE, FALSE, 0);
758  }
759  else
760  {
761  enclosing = gtk_frame_new(nullptr);
762  g_object_set(G_OBJECT(widget), "margin", 3, NULL);
763 
764  gtk_container_add (GTK_CONTAINER(enclosing), widget);
765  }
766 
767  gtk_widget_set_halign (GTK_WIDGET(enclosing), GTK_ALIGN_START);
768  set_name_label(option, page_box, row, false);
769  set_tool_tip(option, enclosing);
770  grid_attach_widget (GTK_GRID(page_box), enclosing, row);
771 
772  auto ui_item{dynamic_cast<GncOptionDateUIItem*>(option.get_ui_item())};
773  if (auto date_ui{ui_item ? ui_item->get_entry() : nullptr})
774  {
775  date_ui->block_signals(true);
776  date_ui->set_entry_from_option(option);
777  date_ui->block_signals(false);
778  }
779 
780  gtk_widget_show_all(enclosing);
781 }
782 
783 template<> void
784 create_option_widget<GncOptionUIType::DATE_ABSOLUTE>(GncOption& option,
785  GtkGrid *page_box, int row)
786 {
787  create_date_option_widget(option, page_box, row);
788 }
789 
790 template<> void
791 create_option_widget<GncOptionUIType::DATE_RELATIVE>(GncOption& option,
792  GtkGrid *page_box, int row)
793 {
794  create_date_option_widget(option, page_box, row);
795 }
796 
797 template<> void
798 create_option_widget<GncOptionUIType::DATE_BOTH>(GncOption& option,
799  GtkGrid *page_box, int row)
800 {
801  create_date_option_widget(option, page_box, row);
802 }
803 
804 using GncOptionAccountList = std::vector<GncGUID>;
805 
806 static void
807 account_select_all_cb(GtkWidget *widget, gpointer data)
808 {
809  GncOption* option = static_cast<decltype(option)>(data);
810  GncTreeViewAccount *tree_view;
811  GtkTreeSelection *selection;
812 
813  tree_view = GNC_TREE_VIEW_ACCOUNT(option_get_gtk_widget (option));
814  gtk_tree_view_expand_all(GTK_TREE_VIEW(tree_view));
815  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
816  gtk_tree_selection_select_all(selection);
817  gnc_option_changed_widget_cb(widget, option);
818 }
819 
820 static void
821 account_clear_all_cb(GtkWidget *widget, gpointer data)
822 {
823  GncOption* option = static_cast<decltype(option)>(data);
824  GncTreeViewAccount *tree_view;
825  GtkTreeSelection *selection;
826 
827  tree_view = GNC_TREE_VIEW_ACCOUNT(option_get_gtk_widget (option));
828  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
829  gtk_tree_selection_unselect_all(selection);
830  gnc_option_changed_widget_cb(widget, option);
831 }
832 
833 static void
834 account_select_children_cb(GtkWidget *widget, gpointer data)
835 {
836  GncOption* option = static_cast<decltype(option)>(data);
837  GncTreeViewAccount *tree_view;
838  GList *acct_list = NULL, *acct_iter = NULL;
839 
840  tree_view = GNC_TREE_VIEW_ACCOUNT(option_get_gtk_widget (option));
841  acct_list = gnc_tree_view_account_get_selected_accounts (tree_view);
842 
843  for (acct_iter = acct_list; acct_iter; acct_iter = acct_iter->next)
844  gnc_tree_view_account_select_subaccounts (tree_view, static_cast<Account*>(acct_iter->data));
845 
846  g_list_free (acct_list);
847 }
848 
849 static void
850 account_set_default_cb(GtkWidget* widget, gpointer data)
851 {
852  GncOption* option = static_cast<decltype(option)>(data);
853  account_clear_all_cb(widget, data);
854  option->set_value(option->get_default_value<GncOptionAccountList>());
855  option->set_ui_item_from_option();
856 }
857 
858 static void
859 show_hidden_toggled_cb(GtkWidget *widget, GncOption* option)
860 {
861  if (option->get_ui_type() != GncOptionUIType::ACCOUNT_LIST &&
862  option->get_ui_type() != GncOptionUIType::ACCOUNT_SEL)
863  return;
864 
865  auto tree_view = GNC_TREE_VIEW_ACCOUNT(option_get_gtk_widget(option));
866  AccountViewInfo avi;
867  gnc_tree_view_account_get_view_info (tree_view, &avi);
868  avi.show_hidden = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
869  gnc_tree_view_account_set_view_info (tree_view, &avi);
870  gnc_option_changed_widget_cb(widget, option);
871 }
872 
874 {
875 public:
876  explicit GncGtkAccountListUIItem(GtkWidget* widget) :
877  GncOptionGtkUIItem{widget, GncOptionUIType::ACCOUNT_LIST} {}
878  void set_ui_item_from_option(GncOption& option) noexcept override
879  {
880  auto widget{GNC_TREE_VIEW_ACCOUNT(get_widget())};
881  GList *acc_list = nullptr;
882  const GncOptionAccountList& accounts =
883  option.get_value<GncOptionAccountList>();
884  auto book{gnc_get_current_book()};
885  for (auto guid : accounts)
886  {
887  auto account{xaccAccountLookup(&guid, book)};
888  acc_list = g_list_prepend(acc_list, account);
889  }
890  acc_list = g_list_reverse(acc_list);
891  gnc_tree_view_account_set_selected_accounts(widget, acc_list, TRUE);
892  g_list_free(acc_list);
893  }
894  void set_option_from_ui_item(GncOption& option) noexcept override
895  {
896  auto widget{GNC_TREE_VIEW_ACCOUNT(get_widget())};
897  auto acc_list = gnc_tree_view_account_get_selected_accounts(widget);
898  GncOptionAccountList acc_vec;
899  acc_vec.reserve(g_list_length(acc_list));
900  for (auto node = acc_list; node; node = g_list_next(node))
901  {
902  auto guid{qof_entity_get_guid(node->data)};
903  acc_vec.push_back(*guid);
904  }
905  g_list_free(acc_list);
906  option.set_value(acc_vec);
907  }
908 };
909 
910 static GtkWidget*
911 create_account_widget(GncOption& option, char *name)
912 {
913  bool multiple_selection;
914  GtkWidget *scroll_win;
915  GtkWidget *button;
916  GtkWidget *frame;
917  GtkWidget *tree;
918  GtkWidget *vbox;
919  GtkWidget *bbox;
920  GList *acct_type_list;
921  GtkTreeSelection *selection;
922 
923  multiple_selection = option.is_multiselect();
924  acct_type_list = option.account_type_list();
925 
926  frame = gtk_frame_new(name);
927 
928  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
929  gtk_box_set_homogeneous (GTK_BOX (vbox), FALSE);
930 
931  gtk_container_add(GTK_CONTAINER(frame), vbox);
932 
933  tree = GTK_WIDGET(gnc_tree_view_account_new (FALSE));
934  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(tree), FALSE);
935  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree));
936  if (multiple_selection)
937  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
938  else
939  gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
940 
941  if (acct_type_list)
942  {
943  GList *node;
944  AccountViewInfo avi;
945  int i;
946 
947  gnc_tree_view_account_get_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi);
948 
949  for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
950  avi.include_type[i] = FALSE;
951  avi.show_hidden = TRUE;
952 
953  for (node = acct_type_list; node; node = node->next)
954  {
955  GNCAccountType type = static_cast<decltype(type)>(GPOINTER_TO_INT (node->data));
956  if (type < NUM_ACCOUNT_TYPES)
957  avi.include_type[type] = TRUE;
958  }
959 
960  gnc_tree_view_account_set_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi);
961  g_list_free (acct_type_list);
962  }
963  else
964  {
965  AccountViewInfo avi;
966  int i;
967 
968  gnc_tree_view_account_get_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi);
969 
970  for (i = 0; i < NUM_ACCOUNT_TYPES; i++)
971  avi.include_type[i] = TRUE;
972  avi.show_hidden = TRUE;
973  gnc_tree_view_account_set_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi);
974  }
975 
976  scroll_win = gtk_scrolled_window_new(NULL, NULL);
977  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_win),
978  GTK_POLICY_AUTOMATIC,
979  GTK_POLICY_AUTOMATIC);
980 
981  gtk_box_pack_start(GTK_BOX(vbox), scroll_win, TRUE, TRUE, 0);
982  gtk_container_set_border_width(GTK_CONTAINER(scroll_win), 5);
983 
984  bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
985  gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD);
986  gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 10);
987 
988  option.set_ui_item(std::make_unique<GncGtkAccountListUIItem>(tree));
989  option.set_ui_item_from_option();
990 
991  if (multiple_selection)
992  {
993  button = gtk_button_new_with_label(_("Select All"));
994  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
995  gtk_widget_set_tooltip_text(button, _("Select all accounts."));
996 
997  g_signal_connect(G_OBJECT(button), "clicked",
998  G_CALLBACK(account_select_all_cb), &option);
999 
1000  button = gtk_button_new_with_label(_("Clear All"));
1001  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1002  gtk_widget_set_tooltip_text(button, _("Clear the selection and unselect all accounts."));
1003 
1004  g_signal_connect(G_OBJECT(button), "clicked",
1005  G_CALLBACK(account_clear_all_cb), &option);
1006 
1007  button = gtk_button_new_with_label(_("Select Children"));
1008  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1009  gtk_widget_set_tooltip_text(button, _("Select all descendents of selected account."));
1010 
1011  g_signal_connect(G_OBJECT(button), "clicked",
1012  G_CALLBACK(account_select_children_cb), &option);
1013  }
1014 
1015  button = gtk_button_new_with_label(_("Select Default"));
1016  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1017  gtk_widget_set_tooltip_text(button, _("Select the default account selection."));
1018 
1019  g_signal_connect(G_OBJECT(button), "clicked",
1020  G_CALLBACK(account_set_default_cb), &option);
1021 
1022  gtk_widget_set_margin_start (GTK_WIDGET(bbox), 6);
1023  gtk_widget_set_margin_end (GTK_WIDGET(bbox), 6);
1024 
1025  if (multiple_selection)
1026  {
1027  /* Put the "Show hidden" checkbox on a separate line since
1028  the 4 buttons make the dialog too wide. */
1029  bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
1030  gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
1031  gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
1032  }
1033 
1034  button = gtk_check_button_new_with_label(_("Show Hidden Accounts"));
1035  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1036  gtk_widget_set_tooltip_text(button, _("Show accounts that have been marked hidden."));
1037  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
1038  g_signal_connect(G_OBJECT(button), "toggled",
1039  G_CALLBACK(show_hidden_toggled_cb), &option);
1040 
1041  gtk_container_add(GTK_CONTAINER(scroll_win), tree);
1042  return frame;
1043 }
1044 
1045 static void
1046 option_account_sel_changed_cb(GtkTreeSelection *sel, gpointer data)
1047 {
1048  auto tree_view{gtk_tree_selection_get_tree_view(sel)};
1049  gnc_option_changed_widget_cb(GTK_WIDGET(tree_view),
1050  static_cast<GncOption*>(data));
1051 }
1052 
1053 template<> void
1054 create_option_widget<GncOptionUIType::ACCOUNT_LIST>(GncOption& option,
1055  GtkGrid *page_box, int row)
1056 {
1057  auto enclosing{create_account_widget(option, nullptr)};
1058  gtk_widget_set_vexpand (GTK_WIDGET(enclosing), TRUE);
1059  gtk_widget_set_hexpand (GTK_WIDGET(enclosing), TRUE);
1060  set_name_label(option, page_box, row, true);
1061  set_tool_tip(option, enclosing);
1062  grid_attach_widget (GTK_GRID(page_box), enclosing, row);
1063 
1064  auto widget{option_get_gtk_widget(&option)};
1065  auto selection{gtk_tree_view_get_selection(GTK_TREE_VIEW(widget))};
1066  g_signal_connect(G_OBJECT(selection), "changed",
1067  G_CALLBACK(option_account_sel_changed_cb), &option);
1068  gtk_widget_show_all(enclosing);
1069 }
1070 
1072 {
1073 public:
1074  explicit GncGtkAccountSelUIItem(GtkWidget* widget) :
1075  GncOptionGtkUIItem{widget, GncOptionUIType::ACCOUNT_SEL} {}
1076  void set_ui_item_from_option(GncOption& option) noexcept override
1077  {
1078  auto widget{GNC_ACCOUNT_SEL(get_widget())};
1079  auto instance{option.get_value<const Account*>()};
1080  if (instance)
1081  gnc_account_sel_set_account(widget, const_cast<Account*>(instance), FALSE);
1082  }
1083  void set_option_from_ui_item(GncOption& option) noexcept override
1084  {
1085  auto widget{GNC_ACCOUNT_SEL(get_widget())};
1086 // Must cast it to const Account* to get the template specialization to recognize it.
1087  option.set_value(static_cast<const Account*>(gnc_account_sel_get_account(widget)));
1088  }
1089 };
1090 
1091 template<> void
1092 create_option_widget<GncOptionUIType::ACCOUNT_SEL> (GncOption& option,
1093  GtkGrid *page_box, int row)
1094 {
1095  auto acct_type_list{option.account_type_list()};
1096  auto widget{gnc_account_sel_new()};
1097  gnc_account_sel_set_acct_filters(GNC_ACCOUNT_SEL(widget),
1098  acct_type_list, NULL);
1099  g_list_free(acct_type_list);
1100 
1101  // gnc_account_sel doesn't emit a changed signal
1102  option.set_ui_item(std::make_unique<GncGtkAccountSelUIItem>(widget));
1103  option.set_ui_item_from_option();
1104 
1105  g_signal_connect(widget, "account_sel_changed",
1106  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1107  wrap_widget(option, widget, page_box, row);
1108  // wrap_widget sets the parent so this comes after.
1109  gtk_container_child_set(GTK_CONTAINER(gtk_widget_get_parent(widget)),
1110  widget, "fill", TRUE, "expand", TRUE,
1111  nullptr);
1112 }
1113 
1114 static void
1115 list_changed_cb(GtkTreeSelection *selection, GncOption* option)
1116 {
1117  GtkTreeView *view = GTK_TREE_VIEW(option_get_gtk_widget (option));
1118  gnc_option_changed_widget_cb(GTK_WIDGET(view), option);
1119 }
1120 
1121 static void
1122 list_select_all_cb(GtkWidget *widget, gpointer data)
1123 {
1124  GncOption* option = static_cast<decltype(option)>(data);
1125  GtkTreeView *view;
1126  GtkTreeSelection *selection;
1127 
1128  view = GTK_TREE_VIEW(option_get_gtk_widget(option));
1129  selection = gtk_tree_view_get_selection(view);
1130  gtk_tree_selection_select_all(selection);
1131  gnc_option_changed_widget_cb(GTK_WIDGET(view), option);
1132 }
1133 
1134 static void
1135 list_clear_all_cb(GtkWidget *widget, gpointer data)
1136 {
1137  GncOption* option = static_cast<decltype(option)>(data);
1138  GtkTreeView *view;
1139  GtkTreeSelection *selection;
1140 
1141  view = GTK_TREE_VIEW(option_get_gtk_widget(option));
1142  selection = gtk_tree_view_get_selection(view);
1143  gtk_tree_selection_unselect_all(selection);
1144  gnc_option_changed_widget_cb(GTK_WIDGET(view), option);
1145 }
1146 
1147 static void
1148 list_set_default_cb(GtkWidget *widget, gpointer data)
1149 {
1150  GncOption* option = static_cast<decltype(option)>(data);
1151  list_clear_all_cb(widget, data);
1152  option->set_value(option->get_default_value<GncMultichoiceOptionIndexVec>());
1153  option->set_ui_item_from_option();
1154 }
1155 
1157 {
1158 public:
1159  GncGtkListUIItem(GtkWidget* widget) :
1160  GncOptionGtkUIItem{widget, GncOptionUIType::LIST} {}
1161 
1162  void set_ui_item_from_option(GncOption& option) noexcept override
1163  {
1164  auto widget{GTK_TREE_VIEW(get_widget())};
1165  auto selection{gtk_tree_view_get_selection(widget)};
1166  gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
1167  g_signal_handlers_block_by_func(selection, (gpointer)list_changed_cb, &option);
1168  gtk_tree_selection_unselect_all(selection);
1169  for (auto index : option.get_value<GncMultichoiceOptionIndexVec>())
1170  {
1171  auto path{gtk_tree_path_new_from_indices(index, -1)};
1172  gtk_tree_selection_select_path(selection, path);
1173  gtk_tree_path_free(path);
1174  }
1175  g_signal_handlers_unblock_by_func(selection, (gpointer)list_changed_cb, &option);
1176  }
1177 
1178  void set_option_from_ui_item(GncOption& option) noexcept override
1179  {
1180  auto widget{GTK_TREE_VIEW(get_widget())};
1181  auto selection{gtk_tree_view_get_selection(widget)};
1182  auto selected_rows{gtk_tree_selection_get_selected_rows(selection, nullptr)};
1183  GncMultichoiceOptionIndexVec vec;
1184  for (auto row = selected_rows; row; row = g_list_next(row))
1185  {
1186  auto path{static_cast<GtkTreePath*>(row->data)};
1187  auto indices{gtk_tree_path_get_indices(path)};
1188  vec.push_back(*indices);
1189  }
1190  g_list_free_full(selected_rows, (GDestroyNotify)gtk_tree_path_free);
1191  option.set_value(vec);
1192  }
1193 };
1194 
1195 static GtkWidget *
1196 create_list_widget(GncOption& option, char *name)
1197 {
1198  auto frame = gtk_frame_new(name);
1199  auto hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1200  gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
1201  gtk_container_add(GTK_CONTAINER(frame), hbox);
1202 
1203  auto store = gtk_list_store_new(1, G_TYPE_STRING);
1204  auto view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
1205  g_object_unref(store);
1206  auto renderer = gtk_cell_renderer_text_new();
1207  auto column = gtk_tree_view_column_new_with_attributes("", renderer,
1208  "text", 0,
1209  NULL);
1210  gtk_tree_view_append_column(view, column);
1211  gtk_tree_view_set_headers_visible(view, FALSE);
1212 
1213  auto num_values = option.num_permissible_values();
1214  for (decltype(num_values) i = 0; i < num_values; i++)
1215  {
1216  GtkTreeIter iter;
1217  auto raw_string = option.permissible_value_name(i);
1218  auto string = (raw_string && *raw_string) ? _(raw_string) : "";
1219  gtk_list_store_append(store, &iter);
1220  gtk_list_store_set(store, &iter, 0, string ? string : "", -1);
1221  }
1222 
1223  option.set_ui_item(std::make_unique<GncGtkListUIItem>(GTK_WIDGET(view)));
1224  option.set_ui_item_from_option();
1225 
1226  gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(view), FALSE, FALSE, 0);
1227 
1228  auto selection = gtk_tree_view_get_selection(view);
1229  gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
1230  g_signal_connect(selection, "changed",
1231  G_CALLBACK(list_changed_cb), &option);
1232 
1233  auto bbox = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
1234  gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD);
1235  gtk_box_pack_end(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
1236 
1237  auto button = gtk_button_new_with_label(_("Select All"));
1238  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1239  gtk_widget_set_tooltip_text(button, _("Select all entries."));
1240 
1241  g_signal_connect(G_OBJECT(button), "clicked",
1242  G_CALLBACK(list_select_all_cb), &option);
1243 
1244  button = gtk_button_new_with_label(_("Clear All"));
1245  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1246  gtk_widget_set_tooltip_text(button, _("Clear the selection and unselect all entries."));
1247 
1248  g_signal_connect(G_OBJECT(button), "clicked",
1249  G_CALLBACK(list_clear_all_cb), &option);
1250 
1251  button = gtk_button_new_with_label(_("Select Default"));
1252  gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1253  gtk_widget_set_tooltip_text(button, _("Select the default selection."));
1254 
1255  g_signal_connect(G_OBJECT(button), "clicked",
1256  G_CALLBACK(list_set_default_cb), &option);
1257 
1258  g_object_set (G_OBJECT(hbox), "margin", 3, NULL);
1259 
1260  return frame;
1261 }
1262 
1263 template<> void
1264 create_option_widget<GncOptionUIType::LIST> (GncOption& option,
1265  GtkGrid *page_box, int row)
1266 {
1267 
1268  auto enclosing{create_list_widget(option, nullptr)};
1269  set_name_label(option, page_box, row, true);
1270  set_tool_tip(option, enclosing);
1271  grid_attach_widget (GTK_GRID(page_box), enclosing, row);
1272  gtk_widget_show(enclosing);
1273 }
1274 
1276 {
1277 public:
1278  GncGtkNumberRangeUIItem(GtkWidget* widget) :
1279  GncOptionGtkUIItem{widget, GncOptionUIType::NUMBER_RANGE} {}
1280  void set_ui_item_from_option(GncOption& option) noexcept override
1281  {
1282  double value;
1283  if (option.is_alternate())
1284  value = static_cast<double>(option.get_value<int>());
1285  else
1286  value = option.get_value<double>();
1287 
1288  gtk_spin_button_set_value(GTK_SPIN_BUTTON(get_widget()), value);
1289  }
1290  void set_option_from_ui_item(GncOption& option) noexcept override
1291  {
1292  auto value{gtk_spin_button_get_value(GTK_SPIN_BUTTON(get_widget()))};
1293  if (option.is_alternate())
1294  option.set_value<int>(static_cast<int>(value));
1295  else
1296  option.set_value<double>(value);
1297  }
1298 };
1299 
1300 /* New spin button configured with the values provided by the passed-in
1301  * GncOption, which had better contain a GncOptionRangeValue.
1302  *
1303  * Also used to set up the pixel spinner in the plot_size control.
1304  */
1305 static GtkSpinButton*
1306 create_range_spinner(GncOption& option)
1307 {
1308  gdouble lower_bound = G_MINDOUBLE;
1309  gdouble upper_bound = G_MAXDOUBLE;
1310  gdouble step_size = 1.0;
1311 
1312  if (option.is_alternate())
1313  {
1314  int tmp_lower_bound = G_MININT;
1315  int tmp_upper_bound = G_MAXINT;
1316  int tmp_step_size = 1.0;
1317  option.get_limits<int>(tmp_upper_bound, tmp_lower_bound, tmp_step_size);
1318  lower_bound =(double)tmp_lower_bound;
1319  upper_bound = (double)tmp_upper_bound;
1320  step_size = (double)tmp_step_size;
1321  }
1322  else
1323  option.get_limits<double>(upper_bound, lower_bound, step_size);
1324 
1325  auto adj = GTK_ADJUSTMENT(gtk_adjustment_new(lower_bound, lower_bound,
1326  upper_bound, step_size,
1327  step_size * 5.0,
1328  0));
1329 
1330  size_t num_decimals = 0;
1331  for (auto steps = step_size; steps < 1; steps *= 10)
1332  ++num_decimals;
1333  auto widget = gtk_spin_button_new(adj, step_size, num_decimals);
1334  gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(widget), TRUE);
1335 
1336  size_t num_digits = 0;
1337  for (auto bigger = MAX(ABS(lower_bound), ABS(upper_bound));
1338  bigger >= 1; bigger /= 10.0)
1339  ++num_digits;
1340  num_digits += num_decimals;
1341  gtk_entry_set_width_chars(GTK_ENTRY(widget), num_digits);
1342  gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget),
1343  (upper_bound / 2)); //default
1344  return GTK_SPIN_BUTTON(widget);
1345 }
1346 
1347 template<> void
1348 create_option_widget<GncOptionUIType::NUMBER_RANGE> (GncOption& option,
1349  GtkGrid *page_box, int row)
1350 {
1351  auto widget{create_range_spinner(option)};
1352  option.set_ui_item(std::make_unique<GncGtkNumberRangeUIItem>(GTK_WIDGET(widget)));
1353  option.set_ui_item_from_option();
1354 
1355  g_signal_connect(G_OBJECT(widget), "changed",
1356  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1357  wrap_widget(option, GTK_WIDGET(widget), page_box, row);
1358 }
1359 
1361 {
1362 public:
1363  GncGtkColorUIItem(GtkWidget* widget) :
1364  GncOptionGtkUIItem{widget, GncOptionUIType::COLOR} {}
1365  void set_ui_item_from_option(GncOption& option) noexcept override
1366  {
1367  GdkRGBA color;
1368  /* gdk_rgba_parse uses pango_color_parse for hex color strings instead
1369  * of pango_color_parse_with_alpha and that fails if the string length
1370  * is 8.
1371  */
1372  auto value{option.get_value<std::string>().substr(0,6)};
1373  auto rgba_str{g_strdup_printf("#%s", value.c_str())};
1374  if (gdk_rgba_parse(&color, rgba_str))
1375  {
1376  auto color_button = GTK_COLOR_CHOOSER(get_widget());
1377  gtk_color_chooser_set_rgba(color_button, &color);
1378  }
1379  g_free(rgba_str);
1380  }
1381  void set_option_from_ui_item(GncOption& option) noexcept override
1382  {
1383  GdkRGBA color;
1384  auto color_button = GTK_COLOR_CHOOSER(get_widget());
1385  gtk_color_chooser_get_rgba(color_button, &color);
1386  auto rgba_str = g_strdup_printf("%2x%2x%2x%2x",
1387  (uint8_t)(color.red * 255),
1388  (uint8_t)(color.green * 255),
1389  (uint8_t)(color.blue * 255),
1390  (uint8_t)(color.alpha * 255));
1391  auto rgb_str = g_strdup_printf("%2x%2x%2x",
1392  (uint8_t)(color.red * 255),
1393  (uint8_t)(color.green * 255),
1394  (uint8_t)(color.blue * 255));
1395 // sample-report.scm uses an old HTML4 attribute that doesn't understand alpha.
1396  option.set_value(std::string{rgb_str});
1397  g_free(rgba_str);
1398  g_free(rgb_str);
1399  }
1400 };
1401 
1402 template<> void
1403 create_option_widget<GncOptionUIType::COLOR> (GncOption& option, GtkGrid *page_box, int row)
1404 {
1405  auto widget = gtk_color_button_new();
1406  gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(widget), TRUE);
1407 
1408  option.set_ui_item(std::make_unique<GncGtkColorUIItem>(widget));
1409  option.set_ui_item_from_option();
1410 
1411  g_signal_connect(G_OBJECT(widget), "color-set",
1412  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1413  wrap_widget(option, widget, page_box, row);
1414 }
1415 
1417 {
1418 public:
1419  GncGtkFontUIItem(GtkWidget* widget) :
1420  GncOptionGtkUIItem{widget, GncOptionUIType::FONT} {}
1421  void set_ui_item_from_option(GncOption& option) noexcept override
1422  {
1423  GtkFontChooser *font_chooser = GTK_FONT_CHOOSER(get_widget());
1424  gtk_font_chooser_set_font(font_chooser,
1425  option.get_value<std::string>().c_str());
1426 
1427  }
1428  void set_option_from_ui_item(GncOption& option) noexcept override
1429  {
1430  GtkFontChooser *font_chooser = GTK_FONT_CHOOSER(get_widget());
1431  option.set_value(std::string{gtk_font_chooser_get_font(font_chooser)});
1432  }
1433 };
1434 
1435 template<> void
1436 create_option_widget<GncOptionUIType::FONT> (GncOption& option, GtkGrid *page_box, int row)
1437 {
1438  auto widget{gtk_font_button_new()};
1439  g_object_set(G_OBJECT(widget),
1440  "use-font", TRUE,
1441  "show-style", TRUE,
1442  "show-size", TRUE,
1443  (char *)NULL);
1444 
1445  option.set_ui_item(std::make_unique<GncGtkFontUIItem>(widget));
1446  option.set_ui_item_from_option();
1447  g_signal_connect(G_OBJECT(widget), "font-set",
1448  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1449  wrap_widget(option, widget, page_box, row);
1450 }
1451 /* A pointer to the last selected filename */
1452 #define LAST_SELECTION "last-selection"
1453 
1454 static void
1455 update_preview_cb (GtkFileChooser *chooser, void* data)
1456 {
1457  g_return_if_fail(chooser != NULL);
1458 
1459  ENTER("chooser %p", chooser);
1460  auto filename = gtk_file_chooser_get_preview_filename(chooser);
1461  DEBUG("chooser preview name is %s.", filename ? filename : "(null)");
1462  if (filename == NULL)
1463  {
1464  filename = g_strdup(static_cast<const char*>(g_object_get_data(G_OBJECT(chooser), LAST_SELECTION)));
1465  DEBUG("using last selection of %s", filename ? filename : "(null)");
1466  if (filename == NULL)
1467  {
1468  LEAVE("no usable name");
1469  return;
1470  }
1471  }
1472 
1473  auto image = GTK_IMAGE(gtk_file_chooser_get_preview_widget(chooser));
1474  auto pixbuf = gdk_pixbuf_new_from_file_at_size(filename, 128, 128, NULL);
1475  g_free(filename);
1476  auto have_preview = (pixbuf != NULL);
1477 
1478  gtk_image_set_from_pixbuf(image, pixbuf);
1479  if (pixbuf)
1480  g_object_unref(pixbuf);
1481 
1482  gtk_file_chooser_set_preview_widget_active(chooser, have_preview);
1483  LEAVE("preview visible is %d", have_preview);
1484 }
1485 
1486 static void
1487 change_image_cb (GtkFileChooser *chooser, void* data)
1488 {
1489  auto filename{gtk_file_chooser_get_preview_filename(chooser)};
1490  if (!filename)
1491  return;
1492  g_object_set_data_full(G_OBJECT(chooser), LAST_SELECTION, filename, g_free);
1493 }
1494 
1496 {
1497 public:
1498  GncGtkPixmapUIItem(GtkWidget* widget) :
1499  GncOptionGtkUIItem{widget, GncOptionUIType::PIXMAP} {}
1500  void set_ui_item_from_option(GncOption& option) noexcept override
1501  {
1502  auto string{option.get_value<std::string>()};
1503  if (!string.empty())
1504  {
1505  DEBUG("string = %s", string.c_str());
1506  auto chooser{GTK_FILE_CHOOSER(get_widget())};
1507  gtk_file_chooser_select_filename(chooser, string.c_str());
1508  auto filename{gtk_file_chooser_get_filename(chooser)};
1509  g_object_set_data_full(G_OBJECT(chooser), LAST_SELECTION,
1510  g_strdup(string.c_str()), g_free);
1511  DEBUG("Set %s, retrieved %s", string.c_str(),
1512  filename ? filename : "(null)");
1513  update_preview_cb(chooser, &option);
1514  }
1515  }
1516  void set_option_from_ui_item(GncOption& option) noexcept override
1517  {
1518  auto string = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(get_widget()));
1519  DEBUG("filename %s", string ? string : "(null)");
1520  if (string)
1521  {
1522  option.set_value(std::string{string});
1523  g_free(string);
1524  }
1525  }
1526 };
1527 
1528 template<> void
1529 create_option_widget<GncOptionUIType::PIXMAP> (GncOption& option,
1530  GtkGrid *page_box, int row)
1531 {
1532  auto enclosing{gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5)};
1533  gtk_box_set_homogeneous(GTK_BOX(enclosing), FALSE);
1534  auto button{gtk_button_new_with_label(_("Clear"))};
1535  gtk_widget_set_tooltip_text(button, _("Clear any selected image file."));
1536  auto widget{ gtk_file_chooser_button_new(_("Select image"),
1537  GTK_FILE_CHOOSER_ACTION_OPEN)};
1538  gtk_widget_set_tooltip_text(widget, _("Select an image file."));
1539  g_object_set(G_OBJECT(widget),
1540  "width-chars", 30,
1541  "preview-widget", gtk_image_new(),
1542  (char *)NULL);
1543  option.set_ui_item(std::make_unique<GncGtkPixmapUIItem>(widget));
1544  option.set_ui_item_from_option();
1545 
1546  g_signal_connect(G_OBJECT (widget), "selection-changed",
1547  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1548  g_signal_connect(G_OBJECT (widget), "selection-changed",
1549  G_CALLBACK(change_image_cb), &option);
1550  g_signal_connect(G_OBJECT (widget), "update-preview",
1551  G_CALLBACK(update_preview_cb), &option);
1552  g_signal_connect_swapped(G_OBJECT (button), "clicked",
1553  G_CALLBACK(gtk_file_chooser_unselect_all), widget);
1554 
1555  gtk_box_pack_start(GTK_BOX(enclosing), widget, FALSE, FALSE, 0);
1556  gtk_box_pack_start(GTK_BOX(enclosing), button, FALSE, FALSE, 0);
1557 
1558  gtk_widget_show(widget);
1559  set_name_label(option, page_box, row, false);
1560  set_tool_tip(option, enclosing);
1561  gtk_widget_show(enclosing);
1562  grid_attach_widget(page_box, enclosing, row);
1563 }
1564 
1565 static void
1566 radiobutton_set_cb(GtkWidget *w, gpointer data)
1567 {
1568  GncOption* option = static_cast<decltype(option)>(data);
1569  gpointer _current, _new_value;
1570  gint current, new_value;
1571 
1572  auto widget = option_get_gtk_widget(option);
1573 
1574  _current = g_object_get_data(G_OBJECT(widget), "gnc_radiobutton_index");
1575  current = GPOINTER_TO_INT (_current);
1576 
1577  _new_value = g_object_get_data (G_OBJECT(w), "gnc_radiobutton_index");
1578  new_value = GPOINTER_TO_INT (_new_value);
1579 
1580  if (current == new_value)
1581  return;
1582 
1583  g_object_set_data (G_OBJECT(widget), "gnc_radiobutton_index",
1584  GINT_TO_POINTER(new_value));
1585  gnc_option_changed_widget_cb(widget, option);
1586 }
1587 
1589 {
1590 public:
1591  GncGtkRadioButtonUIItem(GtkWidget* widget) :
1592  GncOptionGtkUIItem{widget, GncOptionUIType::RADIOBUTTON} {}
1593  void set_ui_item_from_option(GncOption& option) noexcept override
1594  {
1595  auto index{option.get_value<uint16_t>()};
1596  auto list{gtk_container_get_children(GTK_CONTAINER(get_widget()))};
1597  auto box{GTK_WIDGET(list->data)};
1598  g_list_free(list);
1599 
1600  list = gtk_container_get_children(GTK_CONTAINER(box));
1601  auto node{g_list_nth(list, index)};
1602  GtkButton* button{};
1603  if (node)
1604  {
1605  button = GTK_BUTTON(node->data);
1606  }
1607  else
1608  {
1609  PERR("Invalid Radio Button Selection %hu", index);
1610  g_list_free(list);
1611  return;
1612  }
1613  g_list_free(list);
1614  auto val{g_object_get_data (G_OBJECT (button),
1615  "gnc_radiobutton_index")};
1616  g_return_if_fail (GPOINTER_TO_UINT (val) == index);
1617 
1618  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
1619  }
1620  void set_option_from_ui_item(GncOption& option) noexcept override
1621  {
1622  auto index{g_object_get_data(G_OBJECT(get_widget()),
1623  "gnc_radiobutton_index")};
1624  option.set_value<uint16_t>(GPOINTER_TO_INT(index));
1625  }
1626 };
1627 
1628 static GtkWidget *
1629 create_radiobutton_widget(char *name, GncOption& option)
1630 {
1631  GtkWidget *frame, *box;
1632  GtkWidget *widget = NULL;
1633 
1634  auto num_values{option.num_permissible_values()};
1635 
1636  g_return_val_if_fail(num_values >= 0, NULL);
1637 
1638  /* Create our button frame */
1639  frame = gtk_frame_new (name);
1640 
1641  /* Create the button box */
1642  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
1643  gtk_box_set_homogeneous (GTK_BOX (box), FALSE);
1644  gtk_container_add (GTK_CONTAINER (frame), box);
1645 
1646  option.set_ui_item(std::make_unique<GncGtkPixmapUIItem>(frame));
1647  option.set_ui_item_from_option();
1648 
1649  /* Iterate over the options and create a radio button for each one */
1650  for (decltype(num_values) i = 0; i < num_values; i++)
1651  {
1652  auto label = option.permissible_value_name(i);
1653 
1654  widget =
1655  gtk_radio_button_new_with_label_from_widget (widget ?
1656  GTK_RADIO_BUTTON (widget) :
1657  NULL,
1658  label && *label ? _(label) : "");
1659  g_object_set_data (G_OBJECT (widget), "gnc_radiobutton_index",
1660  GINT_TO_POINTER (i));
1661  g_signal_connect(G_OBJECT(widget), "toggled",
1662  G_CALLBACK(radiobutton_set_cb), &option);
1663  gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
1664  }
1665 
1666  return frame;
1667 }
1668 
1669 template<> void
1670 create_option_widget<GncOptionUIType::RADIOBUTTON> (GncOption& option, GtkGrid *page_box, int row)
1671  {
1672  auto enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
1673  gtk_box_set_homogeneous (GTK_BOX (enclosing), FALSE);
1674  set_name_label(option, page_box, row, true);
1675  set_tool_tip(option, enclosing);
1676  auto widget = create_radiobutton_widget(NULL, option);
1677  gtk_box_pack_start(GTK_BOX(enclosing), widget, FALSE, FALSE, 0);
1678  gtk_widget_show_all(enclosing);
1679  grid_attach_widget(page_box, enclosing, row);
1680  }
1681 
1683 {
1684 public:
1685  GncGtkDateFormatUIItem(GtkWidget* widget) :
1686  GncOptionGtkUIItem{widget, GncOptionUIType::DATE_FORMAT} {}
1687  void set_ui_item_from_option(GncOption& option) noexcept override
1688  {
1689  auto widget{GNC_DATE_FORMAT(get_widget())};
1690  auto [format, months, years, custom] = option.get_value<GncOptionDateFormat>();
1691  gnc_date_format_set_format(widget, format);
1692  gnc_date_format_set_months(widget, months);
1693  gnc_date_format_set_years(widget, years);
1694  gnc_date_format_set_custom(widget, custom.c_str());
1695  }
1696  void set_option_from_ui_item(GncOption& option) noexcept override
1697  {
1698  auto widget{GNC_DATE_FORMAT(get_widget())};
1699  GncOptionDateFormat format{
1700  gnc_date_format_get_format(widget),
1701  gnc_date_format_get_months(widget),
1702  gnc_date_format_get_years(widget),
1703  gnc_date_format_get_custom(widget)};
1704  option.set_value(format);
1705  }
1706 };
1707 
1708 
1709 template<> void
1710 create_option_widget<GncOptionUIType::DATE_FORMAT> (GncOption& option,
1711  GtkGrid *page_box, int row)
1712 {
1713  auto enclosing = gnc_date_format_new_without_label ();
1714  set_name_label(option, page_box, row, true);
1715  set_tool_tip(option, enclosing);
1716  option.set_ui_item(std::make_unique<GncGtkDateFormatUIItem>(enclosing));
1717  option.set_ui_item_from_option();
1718 
1719  g_signal_connect(G_OBJECT(enclosing), "format_changed",
1720  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1721  gtk_widget_show_all(enclosing);
1722  grid_attach_widget(page_box, enclosing, row);
1723 }
1724 
1725 class PlotSize;
1726 static void plot_size_set_pixels(GtkWidget*, PlotSize*);
1727 static void plot_size_set_percent(GtkWidget*, PlotSize*);
1728 
1730 {
1731  GtkWidget *m_widget;
1732  GtkWidget *m_pixel_button;
1733  GtkWidget *m_percent_button;
1734  GtkWidget *m_range_spinner;
1735  GtkAdjustment *m_adj_pct;
1736  GtkAdjustment *m_adj_px;
1737  unsigned long m_percent_handler;
1738  unsigned long m_pixel_handler;
1739 public:
1740  PlotSize(GncOption& option);
1741  ~PlotSize();
1742  void set_entry_from_option(GncOption& option);
1743  void set_option_from_entry(GncOption& option);
1744  GtkWidget* get_widget() { return m_widget; }
1745  GtkWidget* get_spinner() { return m_range_spinner; }
1746  void set_pixels() { gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(m_range_spinner), m_adj_px); }
1747  void set_percent() { gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(m_range_spinner), m_adj_pct); }
1748 };
1749 
1750 PlotSize::PlotSize(GncOption& option) :
1751  m_widget{gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4)}, m_pixel_button{gtk_radio_button_new_with_label(nullptr, _("Pixels"))},
1752  m_percent_button{gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(m_pixel_button), _("Percent"))},
1753  m_range_spinner{GTK_WIDGET(create_range_spinner(option))},
1754  m_adj_pct{GTK_ADJUSTMENT(g_object_ref(gtk_adjustment_new(100.0, 10.0, 100.0, 1.0, 5.0, 0.0)))},
1755  m_adj_px{GTK_ADJUSTMENT(g_object_ref(gtk_adjustment_new(1000.0, 110.0, 10000.0, 10.0, 250.0, 0.0)))}
1756 {
1757  gtk_box_set_homogeneous(GTK_BOX(m_widget), FALSE);
1758  g_object_set (G_OBJECT(m_widget), "margin", 3, NULL);
1759  set_tool_tip(option, m_widget);
1760  gtk_box_pack_start(GTK_BOX(m_widget), GTK_WIDGET(m_pixel_button), FALSE, FALSE, 0);
1761  gtk_box_pack_start(GTK_BOX(m_widget), GTK_WIDGET(m_percent_button), FALSE, FALSE, 0);
1762  gtk_box_pack_start(GTK_BOX(m_widget), GTK_WIDGET(m_range_spinner),
1763  FALSE, FALSE, 0);
1764 
1765  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(m_pixel_button), FALSE);
1766  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(m_percent_button), TRUE);
1767 
1768  m_pixel_handler = g_signal_connect(m_pixel_button, "toggled", G_CALLBACK(plot_size_set_pixels), this);
1769  m_percent_handler = g_signal_connect(m_percent_button, "toggled", G_CALLBACK(plot_size_set_percent), this);
1770 }
1771 
1772 PlotSize::~PlotSize()
1773 {
1774  g_signal_handler_disconnect(m_pixel_button, m_pixel_handler);
1775  g_signal_handler_disconnect(m_percent_button, m_percent_handler);
1776  g_object_unref(m_adj_pct);
1777  g_object_unref(m_adj_px);
1778 }
1779 
1780 void
1781 PlotSize::set_option_from_entry(GncOption& option)
1782 {
1783  auto value{gtk_spin_button_get_value(GTK_SPIN_BUTTON(m_range_spinner))};
1784  if (option.is_alternate())
1785  option.set_value<int>(static_cast<int>(value));
1786  else
1787  option.set_value<double>(value);
1788 }
1789 
1790 void
1791 PlotSize::set_entry_from_option(GncOption& option)
1792 {
1793  double value;
1794  if (option.is_alternate())
1795  {
1796  auto int_value{option.get_value<int>()};
1797  value = static_cast<double>(int_value);
1798  }
1799  else
1800  {
1801  value = option.get_value<double>();
1802  }
1803 
1804  if (value > 100.0)
1805  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pixel_button), TRUE);
1806  else
1807  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_percent_button), TRUE);
1808 
1809  gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_range_spinner), value);
1810 }
1811 
1812 void
1813 plot_size_set_pixels(GtkWidget *widget, PlotSize *ps)
1814 {
1815  ps->set_pixels();
1816 }
1817 
1818 void
1819 plot_size_set_percent(GtkWidget *widget, PlotSize *ps)
1820 {
1821  ps->set_percent();
1822 }
1823 
1824 using PlotSizePtr = std::unique_ptr<PlotSize>;
1825 
1827 {
1828 public:
1829  GncGtkPlotSizeUIItem(PlotSizePtr&& plot_size) :
1830  GncOptionGtkUIItem{plot_size->get_widget(), GncOptionUIType::PLOT_SIZE},
1831  m_plot_size{std::move(plot_size)} {}
1832  void set_ui_item_from_option(GncOption& option) noexcept override
1833  {
1834  m_plot_size->set_entry_from_option(option);
1835  }
1836  void set_option_from_ui_item(GncOption& option) noexcept override
1837  {
1838  m_plot_size->set_option_from_entry(option);
1839  }
1840  PlotSize* get_plot_size() { return m_plot_size.get(); }
1841 private:
1842 PlotSizePtr m_plot_size;
1843 };
1844 
1845 template<> void
1846 create_option_widget<GncOptionUIType::PLOT_SIZE> (GncOption& option,
1847  GtkGrid *page_box, int row)
1848 {
1849 
1850  auto enclosing = gtk_frame_new(NULL);
1851  gtk_widget_set_halign (GTK_WIDGET(enclosing), GTK_ALIGN_START);
1852  set_name_label(option, page_box, row, false);
1853 
1854  option.set_ui_item(std::make_unique<GncGtkPlotSizeUIItem>(std::make_unique<PlotSize>(option)));
1855  option.set_ui_item_from_option();
1856 
1857  auto widget{option_get_gtk_widget(&option)};
1858  gtk_container_add(GTK_CONTAINER(enclosing), widget);
1859 
1860  gtk_widget_show_all(enclosing);
1861  grid_attach_widget(page_box, enclosing, row);
1862 
1863  auto ui_item{dynamic_cast<GncGtkPlotSizeUIItem*>(option.get_ui_item())};
1864  if (ui_item)
1865  g_signal_connect(G_OBJECT(ui_item->get_plot_size()->get_spinner()), "changed",
1866  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1867 }
1868 
1869 static GtkWidget *
1870 create_budget_widget(GncOption& option)
1871 {
1872  GtkTreeModel *tm;
1873  GtkComboBox *cb;
1874  GtkCellRenderer *cr;
1875 
1876  tm = gnc_tree_model_budget_new(gnc_get_current_book());
1877  cb = GTK_COMBO_BOX(gtk_combo_box_new_with_model(tm));
1878  g_object_unref(tm);
1879  cr = gtk_cell_renderer_text_new();
1880  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(cb), cr, TRUE);
1881 
1882  gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(cb), cr, "text",
1883  BUDGET_NAME_COLUMN, NULL);
1884  return GTK_WIDGET(cb);
1885 }
1886 
1888 {
1889 public:
1890  GncGtkBudgetUIItem(GtkWidget* widget) :
1891  GncOptionGtkUIItem{widget, GncOptionUIType::BUDGET} {}
1892  void set_ui_item_from_option(GncOption& option) noexcept override
1893  {
1894  GtkTreeIter iter;
1895  auto widget{GTK_COMBO_BOX(get_widget())};
1896  auto instance{option.get_value<const QofInstance*>()};
1897  if (instance)
1898  {
1899  auto tree_model{gtk_combo_box_get_model(widget)};
1900  if (gnc_tree_model_budget_get_iter_for_budget(tree_model, &iter,
1901  GNC_BUDGET(instance)))
1902  gtk_combo_box_set_active_iter(widget, &iter);
1903  }
1904  }
1905  void set_option_from_ui_item(GncOption& option) noexcept override
1906  {
1907  GtkTreeIter iter;
1908  auto widget{GTK_COMBO_BOX(get_widget())};
1909  if (gtk_combo_box_get_active_iter(widget, &iter))
1910  {
1911  auto tree_model{gtk_combo_box_get_model(widget)};
1912  auto budget{gnc_tree_model_budget_get_budget(tree_model, &iter)};
1913  option.set_value(qof_instance_cast(budget));
1914  }
1915  }
1916 };
1917 
1918 template<> void
1919 create_option_widget<GncOptionUIType::BUDGET> (GncOption& option,
1920  GtkGrid *page_box, int row)
1921 {
1922  auto widget{create_budget_widget(option)};
1923 
1924  option.set_ui_item(std::make_unique<GncGtkBudgetUIItem>(widget));
1925  option.set_ui_item_from_option();
1926 
1927  /* Maybe connect destroy handler for tree model here? */
1928  g_signal_connect(G_OBJECT(widget), "changed",
1929  G_CALLBACK(gnc_option_changed_widget_cb), &option);
1930 
1931  wrap_widget(option, widget, page_box, row);
1932 }
1933 
1934 static void
1935 gnc_options_ui_factory_initialize(void)
1936 {
1937  GncOptionUIFactory::set_func(GncOptionUIType::BOOLEAN,
1938  create_option_widget<GncOptionUIType::BOOLEAN>);
1939  GncOptionUIFactory::set_func(GncOptionUIType::STRING,
1940  create_option_widget<GncOptionUIType::STRING>);
1941  GncOptionUIFactory::set_func(GncOptionUIType::TEXT,
1942  create_option_widget<GncOptionUIType::TEXT>);
1943  GncOptionUIFactory::set_func(GncOptionUIType::CURRENCY,
1944  create_option_widget<GncOptionUIType::CURRENCY>);
1945  GncOptionUIFactory::set_func(GncOptionUIType::COMMODITY,
1946  create_option_widget<GncOptionUIType::COMMODITY>);
1947  GncOptionUIFactory::set_func(GncOptionUIType::MULTICHOICE,
1948  create_option_widget<GncOptionUIType::MULTICHOICE>);
1949  GncOptionUIFactory::set_func(GncOptionUIType::DATE_ABSOLUTE,
1950  create_option_widget<GncOptionUIType::DATE_ABSOLUTE>);
1951  GncOptionUIFactory::set_func(GncOptionUIType::DATE_RELATIVE,
1952  create_option_widget<GncOptionUIType::DATE_RELATIVE>);
1953  GncOptionUIFactory::set_func(GncOptionUIType::DATE_BOTH,
1954  create_option_widget<GncOptionUIType::DATE_BOTH>);
1955  GncOptionUIFactory::set_func(GncOptionUIType::ACCOUNT_LIST,
1956  create_option_widget<GncOptionUIType::ACCOUNT_LIST>);
1957  GncOptionUIFactory::set_func(GncOptionUIType::ACCOUNT_SEL,
1958  create_option_widget<GncOptionUIType::ACCOUNT_SEL>);
1959  GncOptionUIFactory::set_func(GncOptionUIType::LIST,
1960  create_option_widget<GncOptionUIType::LIST>);
1961  GncOptionUIFactory::set_func(GncOptionUIType::NUMBER_RANGE,
1962  create_option_widget<GncOptionUIType::NUMBER_RANGE>);
1963  GncOptionUIFactory::set_func(GncOptionUIType::COLOR,
1964  create_option_widget<GncOptionUIType::COLOR>);
1965  GncOptionUIFactory::set_func(GncOptionUIType::FONT,
1966  create_option_widget<GncOptionUIType::FONT>);
1967  GncOptionUIFactory::set_func(GncOptionUIType::PLOT_SIZE,
1968  create_option_widget<GncOptionUIType::PLOT_SIZE>);
1969  GncOptionUIFactory::set_func(GncOptionUIType::BUDGET,
1970  create_option_widget<GncOptionUIType::BUDGET>);
1971  GncOptionUIFactory::set_func(GncOptionUIType::PIXMAP,
1972  create_option_widget<GncOptionUIType::PIXMAP>);
1973  GncOptionUIFactory::set_func(GncOptionUIType::RADIOBUTTON,
1974  create_option_widget<GncOptionUIType::RADIOBUTTON>);
1975  GncOptionUIFactory::set_func(GncOptionUIType::DATE_FORMAT,
1976  create_option_widget<GncOptionUIType::DATE_FORMAT>);
1977 
1978 
1979 }
void gnc_tree_view_account_get_view_info(GncTreeViewAccount *view, AccountViewInfo *avi)
Given pointers to an account tree and old style filter block, this function will copy the current con...
static void set_func(GncOptionUIType type, WidgetCreateFunc func)
Register a WidgetCreateFunc.
bool is_multiselect() const noexcept
Definition: gnc-option.cpp:322
void gnc_currency_edit_set_currency(GNCCurrencyEdit *gce, const gnc_commodity *currency)
Set the widget to display a certain currency name.
GList * gnc_tree_view_account_get_selected_accounts(GncTreeViewAccount *view)
This function returns a list of the accounts associated with the selected items in the account tree v...
STRUCTS.
OptionUITypes.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
stop here; the following types just aren&#39;t ready for prime time
Definition: Account.h:161
void gnc_tree_view_account_set_view_info(GncTreeViewAccount *view, AccountViewInfo *avi)
Given pointers to an account tree and old style filter block, this function will applies the settings...
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
void gnc_tree_view_account_set_selected_accounts(GncTreeViewAccount *view, GList *account_list, gboolean show_last)
This function selects a set of accounts in the account tree view.
Represents the public interface for an option.
Definition: gnc-option.hpp:136
C++ Public interface for individual options.
Currency selection widget.
void gnc_tree_view_account_select_subaccounts(GncTreeViewAccount *view, Account *account)
This function selects all sub-accounts of an account in the account tree view.
uint16_t num_permissible_values() const
Implemented only for GncOptionMultiselectValue.
Definition: gnc-option.cpp:356
GtkTreeView implementation for gnucash account tree.
RelativeDatePeriod
Reporting periods relative to the current date.
GtkTreeView * gnc_tree_view_account_new(gboolean show_root)
Create a new account tree view.
gnc_commodity * gnc_currency_edit_get_currency(GNCCurrencyEdit *gce)
Retrieve the displayed currency of the widget.
Implementation templates and specializtions for GncOption values.
virtual void set_selectable(bool) const noexcept override
Control wether the widget is sensitive.
All type declarations for the whole Gnucash engine.
const GncGUID * qof_entity_get_guid(gconstpointer ent)
void clear_ui_item() override
Clear the data from the widget.
GNCAccountType
The account types are used to determine how the transaction data in the account is displayed...
Definition: Account.h:101
Holds a pointer to the UI item which will control the option and an enum representing the type of the...
GtkWidget * gnc_currency_edit_new(void)
Create a new GNCCurrencyEdit widget which can be used to provide an easy way to enter ISO currency co...
const char * permissible_value(uint16_t index) const
Implemented only for GncOptionMultiselectValue.
Definition: gnc-option.cpp:386
static void create(GncOption &option, GtkGrid *page, int row)
Create a widget.
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
class GncOptionGtkUIItem Gtk-specific Interface class for Option Widget
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
void get_limits(ValueType &, ValueType &, ValueType &) const noexcept
Implemented only for GncOptionNumericRange.
Definition: gnc-option.cpp:177
const char * permissible_value_name(uint16_t index) const
Implemented only for GncOptionMultiselectValue.
Definition: gnc-option.cpp:400
provides some utilities for working with the list of budgets in a book.
GncOptionUIType
Used by GncOptionClassifier to indicate to dialog-options what control should be displayed for the op...
Commodity handling public routines.
GList * account_type_list() const noexcept
Implemented only for GncOptionAccountListValue.
Definition: gnc-option.cpp:414
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id...
Definition: Account.cpp:2032