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