GnuCash c935c2f+
Loading...
Searching...
No Matches
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. */
51static QofLogModule log_module = GNC_MOD_GUI;
52
53//Init the class static.
54std::vector<WidgetCreateFunc> GncOptionUIFactory::s_registry{static_cast<size_t>(GncOptionUIType::MAX_VALUE)};
55bool GncOptionUIFactory::s_initialized{false};
56static void gnc_options_ui_factory_initialize (void);
57
58void
60{
61 s_registry[static_cast<size_t>(type)] = func;
62}
63
64void
65GncOptionUIFactory::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
80GncOptionGtkUIItem::GncOptionGtkUIItem(GtkWidget* widget,
81 GncOptionUIType type) :
82 GncOptionUIItem{type},
83 m_widget{static_cast<GtkWidget*>(g_object_ref(widget))} {}
84
85GncOptionGtkUIItem::GncOptionGtkUIItem(const GncOptionGtkUIItem& item) :
86 GncOptionUIItem{item.get_ui_type()},
87 m_widget{static_cast<GtkWidget*>(g_object_ref(item.get_widget()))} {}
88
89GncOptionGtkUIItem::~GncOptionGtkUIItem()
90{
91 if (m_widget)
92 g_object_unref(m_widget);
93}
94
95void
96GncOptionGtkUIItem::set_selectable(bool selectable) const noexcept
97{
98 if (m_widget)
99 gtk_widget_set_sensitive (m_widget, selectable);
100}
101
102void
104{
105 if (m_widget)
106 g_object_unref(m_widget);
107 m_widget = nullptr;
108}
109
110void
111GncOptionGtkUIItem::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
125SCM
126GncOptionGtkUIItem::get_widget_scm_value(const GncOption& option) const
127{
128 return SCM_BOOL_F;
129}
130/* ****************************************************************/
131/* Option Widgets */
132/* ***************************************************************/
133
134static inline GtkWidget* const
135option_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
145static inline void
146wrap_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{
159public:
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
180template <> void
181create_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{
204public:
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
219template<> void
220create_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{
245public:
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
262template<> void
263create_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{
301public:
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
320template<> void
321create_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{
336public:
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
355template<> void
356create_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
372static GtkWidget*
373create_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{
401public:
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
423template<> void
424create_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{
439public:
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
453using GncDateEntryPtr = std::unique_ptr<GncDateEntry>;
454
456{
457public:
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;
465private:
466 GNCDateEdit* m_entry;
467 unsigned long m_handler_id;
468};
469
470AbsoluteDateEntry::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
479void
480AbsoluteDateEntry::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
489void
490AbsoluteDateEntry::set_entry_from_option(GncOption& option)
491{
492 gnc_date_edit_set_time(m_entry, option.get_value<time64>());
493}
494
495void
496AbsoluteDateEntry::set_option_from_entry(GncOption& option)
497{
498 option.set_value<time64>(gnc_date_edit_get_date(m_entry));
499}
500
502{
503public:
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;
511private:
512 GtkWidget* m_entry;
513 unsigned long m_handler_id;
514};
515
516
517RelativeDateEntry::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
546void
547RelativeDateEntry::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
552void
553RelativeDateEntry::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
558void
559RelativeDateEntry::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
567using AbsoluteDateEntryPtr = std::unique_ptr<AbsoluteDateEntry>;
568using RelativeDateEntryPtr = std::unique_ptr<RelativeDateEntry>;
569
571{
572public:
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;
581private:
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
592static void date_set_absolute_cb(GtkWidget *widget, gpointer data1);
593static void date_set_relative_cb(GtkWidget *widget, gpointer data1);
594
595BothDateEntry::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
620GtkWidget*
621BothDateEntry::get_entry()
622{
623 return m_use_absolute ? m_abs_entry->get_entry() : m_rel_entry->get_entry();
624}
625
626void
627BothDateEntry::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
637void
638BothDateEntry::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
654void
655BothDateEntry::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
663void
664BothDateEntry::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{
682public:
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(); }
702private:
703 GncDateEntryPtr m_entry;
704};
705
706static void
707date_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
718static void
719date_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
730static void
731create_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
784template<> void
785create_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
791template<> void
792create_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
798template<> void
799create_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
805using GncOptionAccountList = std::vector<GncGUID>;
806
807static void
808account_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
821static void
822account_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
834static void
835account_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
850static void
851account_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
859static void
860show_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{
876public:
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
911static GtkWidget*
912create_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
1046static void
1047option_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
1054template<> void
1055create_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{
1074public:
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
1092template<> void
1093create_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
1115static void
1116list_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
1122static void
1123list_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
1135static void
1136list_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
1148static void
1149list_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{
1159public:
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
1196static GtkWidget *
1197create_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
1264template<> void
1265create_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{
1278public:
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 */
1306static GtkSpinButton*
1307create_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
1348template<> void
1349create_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{
1363public:
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
1403template<> void
1404create_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{
1419public:
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
1436template<> void
1437create_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
1455static void
1456update_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
1487static void
1488change_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{
1498public:
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
1529template<> void
1530create_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
1566static void
1567radiobutton_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{
1591public:
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
1629static GtkWidget *
1630create_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
1670template<> void
1671create_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{
1685public:
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
1710template<> void
1711create_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
1726class PlotSize;
1727static void plot_size_set_pixels(GtkWidget*, PlotSize*);
1728static 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;
1740public:
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
1751PlotSize::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
1773PlotSize::~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
1781void
1782PlotSize::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
1791void
1792PlotSize::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
1813void
1814plot_size_set_pixels(GtkWidget *widget, PlotSize *ps)
1815{
1816 ps->set_pixels();
1817}
1818
1819void
1820plot_size_set_percent(GtkWidget *widget, PlotSize *ps)
1821{
1822 ps->set_percent();
1823}
1824
1825using PlotSizePtr = std::unique_ptr<PlotSize>;
1826
1828{
1829public:
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(); }
1842private:
1843PlotSizePtr m_plot_size;
1844};
1845
1846template<> void
1847create_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
1870static GtkWidget *
1871create_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{
1890public:
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
1919template<> void
1920create_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
1935static void
1936gnc_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}
class GncOptionGtkUIItem Gtk-specific Interface class for Option Widget
virtual void set_selectable(bool) const noexcept override
Control wether the widget is sensitive.
void clear_ui_item() override
Clear the data from the widget.
static void set_func(GncOptionUIType type, WidgetCreateFunc func)
Register a WidgetCreateFunc.
static void create(GncOption &option, GtkGrid *page, int row)
Create a widget.
Holds a pointer to the UI item which will control the option and an enum representing the type of the...
Represents the public interface for an option.
Commodity handling public routines.
Currency selection widget.
All type declarations for the whole Gnucash engine.
Implementation templates and specializtions for GncOption values.
OptionUITypes.
C++ Public interface for individual options.
provides some utilities for working with the list of budgets in a book.
GtkTreeView implementation for gnucash account tree.
GNCAccountType
The account types are used to determine how the transaction data in the account is displayed.
Definition Account.h:103
Account * xaccAccountLookup(const GncGUID *guid, QofBook *book)
The xaccAccountLookup() subroutine will return the account associated with the given id,...
Definition Account.cpp:2050
@ NUM_ACCOUNT_TYPES
stop here; the following types just aren't ready for prime time
Definition Account.h:161
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.
GList * account_type_list() const noexcept
Implemented only for GncOptionAccountListValue.
const char * permissible_value(uint16_t index) const
Implemented only for GncOptionMultiselectValue.
const char * permissible_value_name(uint16_t index) const
Implemented only for GncOptionMultiselectValue.
bool is_multiselect() const noexcept
uint16_t num_permissible_values() const
Implemented only for GncOptionMultiselectValue.
void gnc_currency_edit_set_currency(GNCCurrencyEdit *gce, const gnc_commodity *currency)
Set the widget to display a certain currency name.
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...
gnc_commodity * gnc_currency_edit_get_currency(GNCCurrencyEdit *gce)
Retrieve the displayed currency of the 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.
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...
GtkTreeView * gnc_tree_view_account_new(gboolean show_root)
Create a new account tree view.
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...
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...
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.
const GncGUID * qof_entity_get_guid(gconstpointer ent)
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#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
RelativeDatePeriod
Reporting periods relative to the current date.
GncOptionUIType
Used by GncOptionClassifier to indicate to dialog-options what control should be displayed for the op...
STRUCTS.