diff --git a/gnucash/gnome-utils/CMakeLists.txt b/gnucash/gnome-utils/CMakeLists.txt index 35efced09e..76fe198fcf 100644 --- a/gnucash/gnome-utils/CMakeLists.txt +++ b/gnucash/gnome-utils/CMakeLists.txt @@ -36,7 +36,7 @@ set (gnome_utils_SOURCES dialog-dup-trans.c dialog-file-access.c dialog-object-references.c - dialog-options.c + dialog-options.cpp dialog-preferences.c dialog-query-view.c dialog-reset-warnings.c @@ -59,7 +59,7 @@ set (gnome_utils_SOURCES gnc-currency-edit.c gnc-date-delta.c gnc-date-edit.c - gnc-date-format.c + gnc-date-format.c gnc-dense-cal.c gnc-dense-cal-model.c gnc-dense-cal-store.c diff --git a/gnucash/gnome-utils/dialog-options.cpp b/gnucash/gnome-utils/dialog-options.cpp new file mode 100644 index 0000000000..9b04423b7f --- /dev/null +++ b/gnucash/gnome-utils/dialog-options.cpp @@ -0,0 +1,2887 @@ +/********************************************************************\ + * dialog-options.cpp -- GNOME option handling * + * Copyright (C) 1998-2000 Linas Vepstas * + * Copyright (c) 2006 David Hampton * + * Copyright (c) 2011 Robert Fewell * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +extern "C" +{ +#include +} + +#include // To include as C++ overriding later indirect includes +#include "dialog-options.h" +extern "C" +{ +#include +#include +#include +#include "swig-runtime.h" + +#include "gnc-tree-model-budget.h" //FIXME? +#include "gnc-budget.h" +#include + +#include "dialog-utils.h" +#include "gnc-engine-guile.h" +#include "glib-guile.h" +#include "gnc-account-sel.h" +#include "gnc-tree-view-account.h" +#include "gnc-combott.h" +#include "gnc-commodity-edit.h" +#include "gnc-component-manager.h" +#include "gnc-general-select.h" +#include "gnc-currency-edit.h" +#include "gnc-date-edit.h" +#include "gnc-engine.h" +#include "gnc-prefs.h" +#include "gnc-gui-query.h" +#include "gnc-session.h" +#include "gnc-ui.h" +#include "gnc-guile-utils.h" +#include "guile-mappings.h" +#include "gnc-date-format.h" +#include "misc-gnome-utils.h" +} + +#include + +#define GNC_PREF_CLOCK_24H "clock-24h" + + +#include +//#include +#include +#include +#include + +#define FUNC_NAME G_STRFUNC +/* TODO: clean up "register-stocks" junk + */ + + +/* This static indicates the debugging module that this .o belongs to. */ +static QofLogModule log_module = GNC_MOD_GUI; + +template inline const QofInstance* +qof_instance_cast(Instance inst) +{ + static_assert(std::is_pointer_v, "Pointers Only!"); + return reinterpret_cast(inst); +} + +static constexpr const char* DIALOG_OPTIONS_CM_CLASS{"dialog-options"}; +static constexpr const char* GNC_PREFS_GROUP{"dialogs.options"}; + +/* + * Point where preferences switch control method from a set of + * notebook tabs to a list. + */ +#define MAX_TAB_COUNT 6 + +/* A pointer to the last selected filename */ +#define LAST_SELECTION "last-selection" + +struct gnc_option_win +{ + GtkWidget * window; + GtkWidget * notebook; + GtkWidget * page_list_view; + GtkWidget * page_list; + + bool toplevel; + + GNCOptionWinCallback apply_cb; + gpointer apply_cb_data; + + GNCOptionWinCallback help_cb; + gpointer help_cb_data; + + GNCOptionWinCallback close_cb; + gpointer close_cb_data; + + /* Hold onto this for a complete reset */ + GncOptionDB *option_db; + + /* Hold on to this to unregister the right class */ + const char *component_class; + + /* widget being destroyed */ + bool destroyed; +}; + +enum page_tree +{ + PAGE_INDEX = 0, + PAGE_NAME, + NUM_COLUMNS +}; + +class GncOptionGtkUIItem : public GncOptionUIItem +{ +public: + GncOptionGtkUIItem(GtkWidget* widget, GncOptionUIType type) : + GncOptionUIItem(type), + m_widget{static_cast(g_object_ref(widget))} {} + GncOptionGtkUIItem(const GncOptionGtkUIItem& item) : + GncOptionUIItem{item.get_ui_type()}, + m_widget{static_cast(g_object_ref(item.get_widget()))} {} + GncOptionGtkUIItem(GncOptionGtkUIItem&&) = default; + virtual ~GncOptionGtkUIItem() override + { + if (m_widget) + g_object_unref(m_widget); + } + void clear_ui_item() override + { + if (m_widget) + g_object_unref(m_widget); + m_widget = nullptr; + } + void set_widget(GtkWidget* widget) + { + if (get_ui_type() == GncOptionUIType::INTERNAL) + { + std::string error{"INTERNAL option, setting the UI item forbidden."}; + throw std::logic_error(std::move(error)); + } + + if (m_widget) + g_object_unref(m_widget); + + m_widget = static_cast(g_object_ref(widget)); + } + virtual GtkWidget* const get_widget() const { return m_widget; } + +private: + GtkWidget* m_widget; +}; + +static GNCOptionWinCallback global_help_cb = NULL; +gpointer global_help_cb_data = NULL; + +static void dialog_reset_cb(GtkWidget * w, gpointer data); +void dialog_list_select_cb (GtkTreeSelection *selection, + gpointer data); +static void component_close_handler (gpointer data); + +static void +section_reset_widgets(GncOptionSection* section) +{ +} + +GtkWidget* const +gnc_option_get_gtk_widget (const GncOption& option) +{ + auto ui_item{dynamic_cast(option.get_ui_item())}; + if (ui_item) + return ui_item->get_widget(); + + return nullptr; +} + +static void +dialog_changed_internal (GtkWidget *widget, bool sensitive) +{ + while (widget && !GTK_IS_WINDOW(widget)) + widget = gtk_widget_get_parent(widget); + if (widget == NULL) + return; + + /* find the ok and cancel buttons, we know where they will be so do it + this way as opposed to using gtk_container_foreach, much less iteration */ + if (GTK_IS_CONTAINER(widget)) + { + GList *children = gtk_container_get_children(GTK_CONTAINER(widget)); + for (GList *it = children; it; it = it->next) + { + if (GTK_IS_BOX (GTK_WIDGET(it->data))) + { + GList *children = gtk_container_get_children(GTK_CONTAINER(it->data)); + for (GList *it = children; it; it = it->next) + { + if (GTK_IS_BUTTON_BOX (GTK_WIDGET(it->data))) + { + GList *children = gtk_container_get_children(GTK_CONTAINER(it->data)); + for (GList *it = children; it; it = it->next) + { + if (g_strcmp0 (gtk_widget_get_name(GTK_WIDGET(it->data)), "ok_button") == 0) + gtk_widget_set_sensitive (GTK_WIDGET(it->data), sensitive); + + if (g_strcmp0 (gtk_widget_get_name(GTK_WIDGET(it->data)), "apply_button") == 0) + gtk_widget_set_sensitive (GTK_WIDGET(it->data), sensitive); + } + g_list_free (children); + } + } + g_list_free (children); + } + } + g_list_free (children); + } +} + +void +gnc_options_dialog_changed (GNCOptionWin *win) +{ + if (!win) return; + + dialog_changed_internal (win->window, TRUE); +} + +static void +widget_changed_cb(GtkWidget *widget, GncOption* option) +{ + const_cast(option->get_ui_item())->set_dirty(true); +} + +void +option_changed_cb(GtkWidget *dummy, GncOption* option) +{ + option->set_ui_item_from_option(); +} + + +/********************************************************************\ + * set_selectable * + * Change the selectable state of the widget that represents a * + * GUI option. * + * * + * Args: option - option to change widget state for * + * selectable - if false, update the widget so that it * + * cannot be selected by the user. If true, * + * update the widget so that it can be selected.* + * Return: nothing * +\********************************************************************/ +static void +set_selectable (GncOption& option, bool selectable) +{ + auto widget = gnc_option_get_gtk_widget(option); + if (widget) + gtk_widget_set_sensitive (widget, selectable); +} + +template GtkWidget* +create_option_widget(GncOption& option, GtkGrid*, GtkLabel*, char*, GtkWidget**, + bool*) +{ + return nullptr; +} + +static GtkWidget* option_widget_factory(GncOption& option, GtkGrid* page, + GtkLabel* name, char* description, + GtkWidget** enclosing, bool* packed); +static void +gnc_option_set_ui_widget(GncOption& option, GtkGrid *page_box, gint grid_row) +{ + GtkWidget *enclosing = NULL; + GtkWidget *value = NULL; + bool packed = FALSE; + char *name, *documentation; + GtkLabel *name_label; + + ENTER("option %p(%s), box %p", + &option, option.get_name().c_str(), page_box); + auto type = option.get_ui_type(); + if (type == GncOptionUIType::INTERNAL) + { + LEAVE("internal type"); + return; + } + + + + const char* raw_name = option.get_name().c_str(); + if (raw_name && *raw_name) + name = _(raw_name); + else + name = nullptr; + + const char* raw_documentation = option.get_docstring().c_str(); + if (raw_documentation && *raw_documentation) + documentation = _(raw_documentation); + else + documentation = nullptr; + + name_label = GTK_LABEL(gtk_label_new (name)); + auto widget = option_widget_factory(option, page_box, name_label, + documentation, &enclosing, &packed); + /* attach the name label to the first column of the grid and + align to the end */ + gtk_grid_attach (GTK_GRID(page_box), GTK_WIDGET(name_label), + 0, grid_row, // left, top + 1, 1); // width, height + gtk_widget_set_halign (GTK_WIDGET(name_label), GTK_ALIGN_END); + + if (!packed && (enclosing != NULL)) + { + /* Pack option widget into an extra eventbox because otherwise the + "documentation" tooltip is not displayed. */ + GtkWidget *eventbox = gtk_event_box_new(); + + gtk_container_add (GTK_CONTAINER (eventbox), enclosing); + + /* attach the option widget to the second column of the grid */ + gtk_grid_attach (GTK_GRID(page_box), eventbox, + 1, grid_row, // left, top + 1, 1); // width, height + + gtk_widget_set_tooltip_text (eventbox, documentation); + } + + if (value != NULL) + gtk_widget_set_tooltip_text(value, documentation); + + LEAVE(" "); +} + +static GtkBox* +create_content_box() +{ + auto content_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); + gtk_widget_set_name (content_box, "page-content-box"); + gtk_box_set_homogeneous (GTK_BOX (content_box), FALSE); + + gtk_container_set_border_width(GTK_CONTAINER(content_box), 12); + return GTK_BOX(content_box); +} + +static GtkGrid* +create_options_box(GtkBox* content_box) +{ + auto options_scrolled_win = gtk_scrolled_window_new(NULL, NULL); + gtk_box_pack_start(GTK_BOX(content_box), options_scrolled_win, + TRUE, TRUE, 0); + + /* Build space for the content - the options box */ + auto options_box = gtk_grid_new(); // this will have two columns + gtk_widget_set_name (options_box, "options-box"); + gtk_grid_set_row_homogeneous (GTK_GRID(options_box), FALSE); + gtk_grid_set_column_homogeneous (GTK_GRID(options_box), FALSE); + gtk_grid_set_row_spacing (GTK_GRID(options_box), 6); + gtk_grid_set_column_spacing (GTK_GRID(options_box), 6); + + gtk_container_set_border_width(GTK_CONTAINER(options_box), 0); + gtk_container_add (GTK_CONTAINER(options_scrolled_win), + GTK_WIDGET(options_box)); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(options_scrolled_win), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + return GTK_GRID(options_box); +} + +static GtkButtonBox* +create_reset_button_box(GtkBox* page_content_box) +{ + auto buttonbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout (GTK_BUTTON_BOX (buttonbox), + GTK_BUTTONBOX_EDGE); + gtk_container_set_border_width(GTK_CONTAINER (buttonbox), 5); + gtk_box_pack_end(GTK_BOX(page_content_box), buttonbox, FALSE, FALSE, 0); + return GTK_BUTTON_BOX(buttonbox); +} + +static int +setup_notebook_pages(GNCOptionWin* propertybox, GtkBox* page_content_box, + const char* name) +{ + auto page_count = gtk_notebook_page_num(GTK_NOTEBOOK(propertybox->notebook), + GTK_WIDGET(page_content_box)); + + if (propertybox->page_list_view) + { + /* Build the matching list item for selecting from large page sets */ + auto view = GTK_TREE_VIEW(propertybox->page_list_view); + auto list = GTK_LIST_STORE(gtk_tree_view_get_model(view)); + + PINFO("Page name is %s and page_count is %d", name, page_count); + GtkTreeIter iter; + gtk_list_store_append(list, &iter); + gtk_list_store_set(list, &iter, + PAGE_NAME, _(name), + PAGE_INDEX, page_count, + -1); + + if (page_count > MAX_TAB_COUNT - 1) /* Convert 1-based -> 0-based */ + { + gtk_widget_show(propertybox->page_list); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(propertybox->notebook), FALSE); + gtk_notebook_set_show_border(GTK_NOTEBOOK(propertybox->notebook), FALSE); + } + else + gtk_widget_hide(propertybox->page_list); + + } + return page_count; +} + +static int +gnc_options_dialog_append_page(GNCOptionWin * propertybox, + GncOptionSectionPtr& section) +{ + auto name = section->get_name().c_str(); + if (!name) + return -1; + + if (strncmp(name, "__", 2) == 0) + return -1; + + auto page_label = gtk_label_new(_(name)); + PINFO("Page_label is %s", _(name)); + gtk_widget_show(page_label); + + /* Build this options page */ + auto page_content_box = create_content_box(); + auto options_box = create_options_box(page_content_box); + + /* Create all the options */ + size_t row = 0; + section->foreach_option( + [options_box, &row](GncOption& option) { + g_object_set_data (G_OBJECT(options_box), "options-grid-row", + GINT_TO_POINTER(row)); + gnc_option_set_ui_widget(option, GTK_GRID(options_box), row); + ++row; + }); + + /* Add a button box at the bottom of the page */ + auto buttonbox = create_reset_button_box(page_content_box); + /* The reset button on each option page */ + auto reset_button = gtk_button_new_with_label (_("Reset defaults")); + gtk_widget_set_tooltip_text(reset_button, + _("Reset all values to their defaults.")); + + g_signal_connect(G_OBJECT(reset_button), "clicked", + G_CALLBACK(dialog_reset_cb), propertybox); + g_object_set_data(G_OBJECT(reset_button), "section", + static_cast(section.get())); + gtk_box_pack_end(GTK_BOX(buttonbox), reset_button, FALSE, FALSE, 0); + gtk_widget_show_all(GTK_WIDGET(page_content_box)); + gtk_notebook_append_page(GTK_NOTEBOOK(propertybox->notebook), + GTK_WIDGET(page_content_box), page_label); + + /* Switch to selection from a list if the page count threshold is reached */ + return setup_notebook_pages(propertybox, page_content_box, name); +} + +/********************************************************************\ + * gnc_options_dialog_build_contents * + * builds an options dialog given a property box and an options * + * database and make the dialog visible * + * * + * Args: propertybox - gnome property box to use * + * odb - option database to use * + * Return: nothing * +\********************************************************************/ +void +gnc_options_dialog_build_contents (GNCOptionWin *propertybox, + GncOptionDB *odb) +{ + gnc_options_dialog_build_contents_full (propertybox, odb, true); +} + +/********************************************************************\ + * gnc_options_dialog_build_contents_full * + * builds an options dialog given a property box and an options * + * database and make the dialog visible depending on the * + * show_dialog flag * + * * + * Args: propertybox - gnome property box to use * + * odb - option database to use * + * show_dialog - should dialog be made visible or not * + * Return: nothing * +\********************************************************************/ +void +gnc_options_dialog_build_contents_full (GNCOptionWin *propertybox, + GNCOptionDB *odb, gboolean show_dialog) +{ + gint default_page = -1; + + gint page; + + g_return_if_fail (propertybox != NULL); + g_return_if_fail (odb != NULL); + + propertybox->option_db = odb; + + auto num_sections = odb->num_sections(); + auto default_section = odb->get_default_section(); + + PINFO("Default Section name is %s", default_section->get_name().c_str()); + + odb->foreach_section( + [propertybox, default_section, &default_page] + (GncOptionSectionPtr& section) { + auto page = gnc_options_dialog_append_page(propertybox, section); + if (section.get() == default_section) + default_page = page; + }); + + /* call each option widget changed callbacks once at this point, now that + * all options widgets exist. + * + * Note that this may be superfluous as each create_option_widget + * specialization calls option.set_ui_item_from_option after creating the UI + * item. + */ + odb->foreach_section( + [](GncOptionSectionPtr& section) { + section->foreach_option( + [](GncOption& option) { + option.set_ui_item_from_option(); + }); + }); + + gtk_notebook_popup_enable(GTK_NOTEBOOK(propertybox->notebook)); + if (default_page >= 0) + { + /* Find the page list and set the selection to the default page */ + GtkTreeSelection* selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(propertybox->page_list_view)); + GtkTreeIter iter; + GtkTreeModel *model; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(propertybox->page_list_view)); + gtk_tree_model_iter_nth_child(model, &iter, NULL, default_page); + gtk_tree_selection_select_iter (selection, &iter); + gtk_notebook_set_current_page(GTK_NOTEBOOK(propertybox->notebook), default_page); + } + dialog_changed_internal(propertybox->window, FALSE); + if (show_dialog) + gtk_widget_show(propertybox->window); +} + +GtkWidget * +gnc_options_dialog_widget(GNCOptionWin * win) +{ + return win->window; +} + +GtkWidget * +gnc_options_page_list(GNCOptionWin * win) +{ + return win->page_list; +} + +GtkWidget * +gnc_options_dialog_notebook(GNCOptionWin * win) +{ + return win->notebook; +} + +static void +gnc_options_dialog_help_button_cb(GtkWidget * widget, GNCOptionWin *win) +{ + if (win->help_cb) + (win->help_cb)(win, win->help_cb_data); +} + +static void +gnc_options_dialog_cancel_button_cb(GtkWidget * widget, GNCOptionWin *win) +{ + gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(win->window)); + + if (win->close_cb) + (win->close_cb)(win, win->close_cb_data); + else + gtk_widget_hide(win->window); +} + +static void +gnc_options_dialog_apply_button_cb(GtkWidget * widget, GNCOptionWin *win) +{ + GNCOptionWinCallback close_cb = win->close_cb; + + win->close_cb = NULL; + if (win->apply_cb) + win->apply_cb (win, win->apply_cb_data); + win->close_cb = close_cb; + gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(win->window)); + dialog_changed_internal (win->window, FALSE); +} + +static void +gnc_options_dialog_ok_button_cb(GtkWidget * widget, GNCOptionWin *win) +{ + GNCOptionWinCallback close_cb = win->close_cb; + + win->close_cb = NULL; + if (win->apply_cb) + win->apply_cb (win, win->apply_cb_data); + win->close_cb = close_cb; + + gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(win->window)); + + if (win->close_cb) + (win->close_cb)(win, win->close_cb_data); + else + gtk_widget_hide(win->window); +} + +static void +gnc_options_dialog_destroy_cb (GtkWidget *object, GNCOptionWin *win) +{ + if (!win) return; + + if (win->destroyed == FALSE) + { + if (win->close_cb) + (win->close_cb)(win, win->close_cb_data); + } +} + +static bool +gnc_options_dialog_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + GNCOptionWin *win = static_cast(data); + + if (event->keyval == GDK_KEY_Escape) + { + component_close_handler (win); + return TRUE; + } + else + return FALSE; +} + +static void +dialog_reset_cb(GtkWidget * w, gpointer data) +{ + GNCOptionWin *win = static_cast(data); + gpointer val; + + val = g_object_get_data(G_OBJECT(w), "section"); + g_return_if_fail (val); + g_return_if_fail (win); + + auto section = static_cast(val); + section->foreach_option( + [](GncOption& option) { + option.set_ui_item_from_option(); + const_cast(option.get_ui_item())->set_dirty(true); + }); + + dialog_changed_internal (win->window, TRUE); +} + +void +dialog_list_select_cb (GtkTreeSelection *selection, + gpointer data) +{ + GNCOptionWin * win = static_cast(data); + GtkTreeModel *list; + GtkTreeIter iter; + gint index = 0; + + if (!gtk_tree_selection_get_selected(selection, &list, &iter)) + return; + gtk_tree_model_get(list, &iter, + PAGE_INDEX, &index, + -1); + PINFO("Index is %d", index); + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), index); +} + +static void +component_close_handler (gpointer data) +{ + GNCOptionWin *win = static_cast(data); + gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(win->window)); + gnc_options_dialog_cancel_button_cb (NULL, win); +} + +/* gnc_options_dialog_new: + * + * - Opens the dialog-options glade file + * - Connects signals specified in the builder file + * - Sets the window's title + * - Initializes a new GtkNotebook, and adds it to the window + * + */ +GNCOptionWin * +gnc_options_dialog_new(gchar *title, GtkWindow *parent) +{ + return gnc_options_dialog_new_modal(FALSE, title, NULL, parent); +} + +/* gnc_options_dialog_new_modal: + * + * - Opens the dialog-options glade file + * - Connects signals specified in the builder file + * - Sets the window's title + * - Initializes a new GtkNotebook, and adds it to the window + * - If modal TRUE, hides 'apply' button + * - If component_class is provided, it is used, otherwise, + * DIALOG_OPTIONS_CM_CLASS is used; this is used to distinguish the + * book-option dialog from report dialogs. The book-option dialog is a + * singleton, so if a dialog already exists it will be raised to the top of + * the window stack instead of creating a new dialog. + */ +GNCOptionWin * +gnc_options_dialog_new_modal(gboolean modal, gchar *title, + const char *component_class, + GtkWindow *parent) +{ + GNCOptionWin *retval; + GtkBuilder *builder; + GtkWidget *hbox; + gint component_id; + GtkWidget *button; + + retval = g_new0(GNCOptionWin, 1); + builder = gtk_builder_new(); + gnc_builder_add_from_file (builder, "dialog-options.glade", "gnucash_options_window"); + retval->window = GTK_WIDGET(gtk_builder_get_object (builder, "gnucash_options_window")); + retval->page_list = GTK_WIDGET(gtk_builder_get_object (builder, "page_list_scroll")); + + /* Page List */ + { + GtkTreeView *view; + GtkListStore *store; + GtkTreeSelection *selection; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + retval->page_list_view = GTK_WIDGET(gtk_builder_get_object (builder, "page_list_treeview")); + + view = GTK_TREE_VIEW(retval->page_list_view); + + store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_INT, G_TYPE_STRING); + gtk_tree_view_set_model(view, GTK_TREE_MODEL(store)); + g_object_unref(store); + + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes(_("Page"), renderer, + "text", PAGE_NAME, NULL); + gtk_tree_view_append_column(view, column); + + gtk_tree_view_column_set_alignment(column, 0.5); + + selection = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE); + g_signal_connect (selection, "changed", + G_CALLBACK (dialog_list_select_cb), retval); + } + + button = GTK_WIDGET(gtk_builder_get_object (builder, "helpbutton")); + g_signal_connect(button, "clicked", G_CALLBACK(gnc_options_dialog_help_button_cb), retval); + button = GTK_WIDGET(gtk_builder_get_object (builder, "cancelbutton")); + g_signal_connect(button, "clicked", G_CALLBACK(gnc_options_dialog_cancel_button_cb), retval); + button = GTK_WIDGET(gtk_builder_get_object (builder, "applybutton")); + g_signal_connect(button, "clicked", G_CALLBACK(gnc_options_dialog_apply_button_cb), retval); + button = GTK_WIDGET(gtk_builder_get_object (builder, "okbutton")); + g_signal_connect(button, "clicked", G_CALLBACK(gnc_options_dialog_ok_button_cb), retval); + + gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, retval); + + gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(retval->window), parent); + + if (title) + gtk_window_set_title(GTK_WINDOW(retval->window), title); + + /* modal */ + if (modal == TRUE) + { + GtkWidget *apply_button; + + apply_button = GTK_WIDGET(gtk_builder_get_object (builder, "applybutton")); + gtk_widget_hide (apply_button); + } + + /* glade doesn't support a notebook with zero pages */ + hbox = GTK_WIDGET(gtk_builder_get_object (builder, "notebook_placeholder")); + retval->notebook = gtk_notebook_new(); + + gtk_widget_set_vexpand (retval->notebook, TRUE); + + gtk_widget_show(retval->notebook); + gtk_box_pack_start(GTK_BOX(hbox), retval->notebook, TRUE, TRUE, 5); + + component_id = gnc_register_gui_component (retval->component_class, + NULL, component_close_handler, + retval); + gnc_gui_component_set_session (component_id, gnc_get_current_session()); + + g_signal_connect (retval->window, "destroy", + G_CALLBACK(gnc_options_dialog_destroy_cb), retval); + + g_signal_connect (retval->window, "key_press_event", + G_CALLBACK(gnc_options_dialog_window_key_press_cb), retval); + + g_object_unref(G_OBJECT(builder)); + + retval->destroyed = FALSE; + return retval; +} + +void +gnc_options_dialog_set_apply_cb(GNCOptionWin * win, GNCOptionWinCallback cb, + gpointer data) +{ + win->apply_cb = cb; + win->apply_cb_data = data; +} + +void +gnc_options_dialog_set_help_cb(GNCOptionWin * win, GNCOptionWinCallback cb, + gpointer data) +{ + win->help_cb = cb; + win->help_cb_data = data; +} + +void +gnc_options_dialog_set_close_cb(GNCOptionWin * win, GNCOptionWinCallback cb, + gpointer data) +{ + win->close_cb = cb; + win->close_cb_data = data; +} + +void +gnc_options_dialog_set_global_help_cb(GNCOptionWinCallback thunk, + gpointer cb_data) +{ + global_help_cb = thunk; + global_help_cb_data = cb_data; +} + +/* This is for global program preferences. */ +void +gnc_options_dialog_destroy(GNCOptionWin * win) +{ + if (!win) return; + + gnc_unregister_gui_component_by_data(win->component_class, win); + + win->destroyed = TRUE; + gtk_widget_destroy(win->window); + + win->window = NULL; + win->notebook = NULL; + win->apply_cb = NULL; + win->help_cb = NULL; + win->component_class = NULL; + + g_free(win); +} + +/* ****************************************************************/ +/* Option Widgets */ +/* ***************************************************************/ + +static void +align_label (GtkLabel *name_label) +{ + /* some option widgets have a large vertical foot print so align + the label name to the top and add a small top margin */ + gtk_widget_set_valign (GTK_WIDGET(name_label), GTK_ALIGN_START); + gtk_widget_set_margin_top (GTK_WIDGET(name_label), 6); +} + +class GncGtkBooleanUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkBooleanUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::BOOLEAN} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GTK_TOGGLE_BUTTON(get_widget())}; + gtk_toggle_button_set_active(widget, option.get_value()); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GTK_TOGGLE_BUTTON(get_widget())}; + option.set_value(gtk_toggle_button_get_active(widget)); + } +}; + +template <> GtkWidget * +create_option_widget (GncOption& option, + GtkGrid* page_box, + GtkLabel* name_label, + char* documentation, + /* Return values */ + GtkWidget** enclosing, + bool* packed) +{ + + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + auto widget = gtk_check_button_new (); + + auto ui_item{std::make_unique(GncGtkBooleanUIItem{widget})}; + + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(widget_changed_cb), &option); + + option.set_ui_item(std::move(ui_item)); + option.set_ui_item_from_option(); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + + return widget; +} + +class GncGtkStringUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkStringUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::STRING} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GTK_ENTRY(get_widget())}; + gtk_entry_set_text(widget, option.get_value().c_str()); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GTK_ENTRY(get_widget())}; + option.set_value(gtk_entry_get_text(widget)); + } +}; + +template<> GtkWidget* +create_option_widget (GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_widget_set_hexpand (GTK_WIDGET(*enclosing), TRUE); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + auto widget = gtk_entry_new(); + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + gtk_entry_set_alignment (GTK_ENTRY(widget), 1.0); + auto ui_item{std::make_unique(widget)}; + + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(widget_changed_cb), &option); + option.set_ui_item(std::move(ui_item)); + option.set_ui_item_from_option(); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, TRUE, TRUE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +class GncGtkTextUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkTextUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::TEXT} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GTK_TEXT_VIEW(get_widget())}; + xxxgtk_textview_set_text(widget, option.get_value().c_str()); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GTK_TEXT_VIEW(get_widget())}; + option.set_value(xxxgtk_textview_get_text(widget)); + } +}; + +template<> GtkWidget* +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + align_label (name_label); + + auto scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_container_set_border_width(GTK_CONTAINER(scroll), 2); + + auto frame = gtk_frame_new(NULL); + gtk_container_add(GTK_CONTAINER(frame), scroll); + + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); + gtk_widget_set_vexpand (GTK_WIDGET(*enclosing), TRUE); + gtk_widget_set_hexpand (GTK_WIDGET(*enclosing), TRUE); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + auto widget = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD); + gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), TRUE); + gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW(widget), FALSE); + gtk_container_add (GTK_CONTAINER (scroll), widget); + + auto ui_item{std::make_unique(widget)}; + auto text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget)); + g_signal_connect(G_OBJECT(text_buffer), "changed", + G_CALLBACK(widget_changed_cb), &option); + option.set_ui_item(std::move(ui_item)); + option.set_ui_item_from_option(); + + gtk_box_pack_start(GTK_BOX(*enclosing), frame, TRUE, TRUE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +class GncGtkCurrencyUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkCurrencyUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::CURRENCY} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GNC_CURRENCY_EDIT(get_widget())}; + auto currency = + GNC_COMMODITY(option.get_value()); + gnc_currency_edit_set_currency(widget, currency); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GNC_CURRENCY_EDIT(get_widget())}; + auto currency = gnc_currency_edit_get_currency(widget); + option.set_value(qof_instance_cast(currency)); + } +}; + +template<> GtkWidget* +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + auto widget = gnc_currency_edit_new(); + auto ui_item{std::make_unique(widget)}; + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(widget_changed_cb), &option); + option.set_ui_item(std::move(ui_item)); + option.set_ui_item_from_option(); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +class GncGtkCommodityUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkCommodityUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::COMMODITY} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GNC_GENERAL_SELECT(get_widget())}; + auto commodity = + GNC_COMMODITY(option.get_value()); + gnc_general_select_set_selected(widget, commodity); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GNC_GENERAL_SELECT(get_widget())}; + auto commodity{gnc_general_select_get_selected(widget)}; + option.set_value(qof_instance_cast(commodity)); + } +}; + +template<> GtkWidget* +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + auto widget = gnc_general_select_new(GNC_GENERAL_SELECT_TYPE_SELECT, + gnc_commodity_edit_get_string, + gnc_commodity_edit_new_select, + NULL); + + auto ui_item{std::make_unique(widget)}; + + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(widget_changed_cb), &option); + option.set_ui_item(std::move(ui_item)); + option.set_ui_item_from_option(); + + if (documentation != NULL) + gtk_widget_set_tooltip_text(GNC_GENERAL_SELECT(widget)->entry, + documentation); + + g_signal_connect(G_OBJECT(GNC_GENERAL_SELECT(widget)->entry), "changed", + G_CALLBACK(widget_changed_cb), &option); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +static GtkWidget* +create_multichoice_widget(GncOption& option) +{ + auto num_values = option.num_permissible_values(); + + g_return_val_if_fail(num_values >= 0, NULL); + + /* GtkComboBox still does not support per-item tooltips, so have + created a basic one called Combott implemented in gnc-combott. + Have highlighted changes in this file with comments for when + the feature of per-item tooltips is implemented in gtk, + see https://bugs.gnucash.org/show_bug.cgi?id=303717 */ + + auto store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + /* Add values to the list store, entry and tooltip */ + for (auto i = 0; i < num_values; i++) + { + GtkTreeIter iter; + auto itemstring = option.permissible_value_name(i); + auto description = option.permissible_value_description(i); + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, + (itemstring && *itemstring) ? _(itemstring) : "", 1, + (description && *description) ? _(description) : "", -1); + } + /* Create the new Combo with tooltip and add the store */ + auto widget = GTK_WIDGET(gnc_combott_new()); + g_object_set( G_OBJECT( widget ), "model", GTK_TREE_MODEL(store), NULL ); + g_object_unref(store); + + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(widget_changed_cb), &option); + + return widget; +} + +class GncGtkMultichoiceUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkMultichoiceUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::MULTICHOICE} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GNC_COMBOTT(get_widget())}; + gnc_combott_set_active(widget, option.get_value()); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GNC_COMBOTT(get_widget())}; + option.set_value(gnc_combott_get_active(widget)); + } +}; + +template<> GtkWidget* +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + + auto widget = create_multichoice_widget(option); + auto ui_item{std::make_unique(widget)}; + +//GncCombott doesn't emit a changed signal + option.set_ui_item(std::move(ui_item)); + option.set_ui_item_from_option(); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + + +class GncDateEntry +{ +public: + GncDateEntry() = default; + virtual ~GncDateEntry() = default; + virtual void set_entry_from_option(GncOption& option) = 0; + virtual void set_option_from_entry(GncOption& option) = 0; + // Get the widget that has data + virtual GtkWidget* get_entry() = 0; + // Get the widget that gets put on the page + virtual GtkWidget* get_widget() = 0; + virtual void toggle_relative(bool) {} //BothDateEntry only +}; + + +using GncDateEntryPtr = std::unique_ptr; + +class AbsoluteDateEntry : public GncDateEntry +{ +public: + AbsoluteDateEntry(GncOption& option); + ~AbsoluteDateEntry() { g_object_unref(G_OBJECT(m_entry)); } + void set_entry_from_option(GncOption& option) override; + void set_option_from_entry(GncOption& option) override; + GtkWidget* get_entry() override { return GTK_WIDGET(m_entry); } + GtkWidget* get_widget() override { return GTK_WIDGET(m_entry); } +private: + GNCDateEdit* m_entry; +}; + +AbsoluteDateEntry::AbsoluteDateEntry(GncOption& option) : + m_entry{GNC_DATE_EDIT(gnc_date_edit_new(time(NULL), FALSE, FALSE))} +{ + auto entry = GNC_DATE_EDIT(m_entry)->date_entry; + g_signal_connect(G_OBJECT(entry), "changed", + G_CALLBACK(option_changed_cb), &option); +} + +void +AbsoluteDateEntry::set_entry_from_option(GncOption& option) +{ + gnc_date_edit_set_time(m_entry, option.get_value()); +} + +void +AbsoluteDateEntry::set_option_from_entry(GncOption& option) +{ + option.set_value(gnc_date_edit_get_date(m_entry)); +} + +class RelativeDateEntry : public GncDateEntry +{ +public: + RelativeDateEntry(GncOption& option); + ~RelativeDateEntry() { g_object_unref(G_OBJECT(m_entry)); }; + void set_entry_from_option(GncOption& option) override; + void set_option_from_entry(GncOption& option) override; + GtkWidget* get_widget() override { return m_entry; } + GtkWidget* get_entry() override { return m_entry; } +private: + GtkWidget* m_entry; +}; + + +RelativeDateEntry::RelativeDateEntry(GncOption& option) : + m_entry{GTK_WIDGET(gnc_combott_new())} + +{ + + /* GtkComboBox still does not support per-item tooltips, so have + created a basic one called Combott implemented in gnc-combott. + Have highlighted changes in this file with comments for when + the feature of per-item tooltips is implemented in gtk, + see https://bugs.gnucash.org/show_bug.cgi?id=303717 */ + + auto store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + /* Add values to the list store, entry and tooltip */ + auto num = option.num_permissible_values(); + for (auto index = 0; index < num; ++index) + { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, + option.permissible_value_name(index), 1, + option.permissible_value_description(index), -1); + } + + /* Create the new Combo with tooltip and add the store */ + g_object_set( G_OBJECT(m_entry), "model", GTK_TREE_MODEL(store), nullptr); + g_object_unref(store); + + g_signal_connect(G_OBJECT(m_entry), "changed", + G_CALLBACK(widget_changed_cb), &option); +} + +void +RelativeDateEntry::set_entry_from_option(GncOption& option) +{ + gnc_combott_set_active(GNC_COMBOTT(m_entry), option.get_value()); +} + +void +RelativeDateEntry::set_option_from_entry(GncOption& option) +{ + option.set_value(gnc_combott_get_active(GNC_COMBOTT(m_entry))); +} + +using AbsoluteDateEntryPtr = std::unique_ptr; +using RelativeDateEntryPtr = std::unique_ptr; + +class BothDateEntry : public GncDateEntry +{ +public: + BothDateEntry(GncOption& option); + ~BothDateEntry(); //The GncOptionGtkUIItem owns the widget + void set_entry_from_option(GncOption& option) override; + void set_option_from_entry(GncOption& option) override; + GtkWidget* get_widget() override { return m_widget; } + GtkWidget* get_entry() override; + void toggle_relative(bool use_absolute) override; +private: + GtkWidget* m_widget; + GtkWidget* m_abs_button; + AbsoluteDateEntryPtr m_abs_entry; + GtkWidget* m_rel_button; + RelativeDateEntryPtr m_rel_entry; + bool m_use_absolute = true; +}; + +static void date_set_absolute_cb(GtkWidget *widget, gpointer data1); +static void date_set_relative_cb(GtkWidget *widget, gpointer data1); + +BothDateEntry::BothDateEntry(GncOption& option) : + m_widget{gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5)}, + m_abs_button{gtk_radio_button_new(NULL)}, + m_abs_entry{std::make_unique(option)}, + m_rel_button{ + gtk_radio_button_new_from_widget(GTK_RADIO_BUTTON(m_abs_button))}, + m_rel_entry{std::make_unique(option)} +{ + gtk_box_set_homogeneous (GTK_BOX(m_widget), FALSE); + g_signal_connect(G_OBJECT(m_abs_button), "toggled", + G_CALLBACK(date_set_absolute_cb), &option); + g_signal_connect(G_OBJECT(m_rel_button), "toggled", + G_CALLBACK(date_set_relative_cb), &option); + + gtk_box_pack_start(GTK_BOX(m_widget), + m_abs_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(m_widget), + m_abs_entry->get_entry(), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(m_widget), + m_rel_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(m_widget), + m_rel_entry->get_entry(), FALSE, FALSE, 0); + +} + +BothDateEntry::~BothDateEntry() +{ + g_object_unref(G_OBJECT(m_abs_button)); + g_object_unref(G_OBJECT(m_rel_button)); +} + +GtkWidget* +BothDateEntry::get_entry() +{ + return m_use_absolute ? m_abs_entry->get_entry() : m_rel_entry->get_entry(); +} + +void +BothDateEntry::toggle_relative(bool use_absolute) +{ + if (use_absolute) + { + gtk_widget_set_sensitive(m_abs_entry->get_entry(), TRUE); + gtk_widget_set_sensitive(m_rel_entry->get_entry(), FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_abs_button), TRUE); + } + else + { + gtk_widget_set_sensitive(m_rel_entry->get_entry(), TRUE); + gtk_widget_set_sensitive(m_abs_entry->get_entry(), FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_rel_button), TRUE); + } + m_use_absolute = use_absolute; +} + +void +BothDateEntry::set_entry_from_option(GncOption& option) +{ + if (m_use_absolute) + m_abs_entry->set_entry_from_option(option); + else + m_rel_entry->set_entry_from_option(option); +} + +void +BothDateEntry::set_option_from_entry(GncOption& option) +{ + if (m_use_absolute) + m_abs_entry->set_option_from_entry(option); + else + m_rel_entry->set_option_from_entry(option); +} + + +class GncOptionDateUIItem : public GncOptionGtkUIItem +{ +public: + GncOptionDateUIItem(GncDateEntryPtr entry, GncOptionUIType type) : + GncOptionGtkUIItem{nullptr, type}, m_entry{std::move(entry)} { } + ~GncOptionDateUIItem() = default; + void clear_ui_item() override { m_entry = nullptr; } + void set_ui_item_from_option(GncOption& option) noexcept override + { + if (m_entry) + m_entry->set_entry_from_option(option); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + if (m_entry) + m_entry->set_option_from_entry(option); + } + GtkWidget* const get_widget() const override + { + return m_entry->get_widget(); + } + GncDateEntry* get_entry() { return m_entry.get(); } +private: + GncDateEntryPtr m_entry; +}; + +static void +date_set_absolute_cb(GtkWidget *widget, gpointer data1) +{ + GncOption* option = static_cast(data1); + auto ui_item = option->get_ui_item(); + if (auto date_ui = dynamic_cast(ui_item)) + { + const_cast(date_ui)->get_entry()->toggle_relative(true); + option_changed_cb(widget, option); + } +} + +static void +date_set_relative_cb(GtkWidget *widget, gpointer data1) +{ + GncOption* option = static_cast(data1); + auto ui_item = option->get_ui_item(); + if (auto date_ui = dynamic_cast(ui_item)) + { + const_cast(date_ui)->get_entry()->toggle_relative(false); + option_changed_cb(widget, option); + } +} + +GtkWidget* +create_date_option_widget(GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + auto grid_row = GPOINTER_TO_INT(g_object_get_data + (G_OBJECT(page_box), "options-grid-row")); + auto type = option.get_ui_type(); + switch (type) + { + case GncOptionUIType::DATE_ABSOLUTE: + option.set_ui_item(std::make_unique(std::make_unique(option), type)); + break; + case GncOptionUIType::DATE_RELATIVE: + option.set_ui_item(std::make_unique(std::make_unique(option), type)); + break; + case GncOptionUIType::DATE_BOTH: + option.set_ui_item(std::make_unique(std::make_unique(option), type)); + break; + default: + PERR("Attempted to create date option widget with wrong UI type %d", type); + return nullptr; + break; + } + + option.set_ui_item_from_option(); + auto widget{gnc_option_get_gtk_widget(option)}; + if (type == GncOptionUIType::DATE_RELATIVE) + { + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + } + else + { + *enclosing = gtk_frame_new (NULL); + g_object_set (G_OBJECT(widget), "margin", 3, NULL); + + gtk_container_add (GTK_CONTAINER(*enclosing), widget); + } + + gtk_widget_set_halign (GTK_WIDGET(*enclosing), GTK_ALIGN_START); + + /* Pack option widget into an extra eventbox because otherwise the + "documentation" tooltip is not displayed. */ + auto eventbox = gtk_event_box_new(); + gtk_container_add (GTK_CONTAINER (eventbox), *enclosing); + + gtk_grid_attach (GTK_GRID(page_box), eventbox, 1, grid_row, 1, 1); + *packed = TRUE; + + gtk_widget_set_tooltip_text (eventbox, documentation); + + gtk_widget_show_all(*enclosing); + return widget; +} + +template<> GtkWidget* +create_option_widget(GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + return create_date_option_widget(option, page_box, name_label, + documentation, enclosing, packed); +} + +template<> GtkWidget* +create_option_widget(GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + return create_date_option_widget(option, page_box, name_label, + documentation, enclosing, packed); +} + +template<> GtkWidget* +create_option_widget(GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + return create_date_option_widget(option, page_box, name_label, + documentation, enclosing, packed); +} + +using GncOptionAccountList = std::vector; + +static void +account_select_all_cb(GtkWidget *widget, gpointer data) +{ + GncOption* option = static_cast(data); + GncTreeViewAccount *tree_view; + GtkTreeSelection *selection; + + tree_view = GNC_TREE_VIEW_ACCOUNT(gnc_option_get_gtk_widget (*option)); + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree_view)); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); + gtk_tree_selection_select_all(selection); + widget_changed_cb(widget, option); +} + +static void +account_clear_all_cb(GtkWidget *widget, gpointer data) +{ + GncOption* option = static_cast(data); + GncTreeViewAccount *tree_view; + GtkTreeSelection *selection; + + tree_view = GNC_TREE_VIEW_ACCOUNT(gnc_option_get_gtk_widget (*option)); + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); + gtk_tree_selection_unselect_all(selection); + widget_changed_cb(widget, option); +} + +static void +account_select_children_cb(GtkWidget *widget, gpointer data) +{ + GncOption* option = static_cast(data); + GncTreeViewAccount *tree_view; + GList *acct_list = NULL, *acct_iter = NULL; + + tree_view = GNC_TREE_VIEW_ACCOUNT(gnc_option_get_gtk_widget (*option)); + acct_list = gnc_tree_view_account_get_selected_accounts (tree_view); + + for (acct_iter = acct_list; acct_iter; acct_iter = acct_iter->next) + gnc_tree_view_account_select_subaccounts (tree_view, static_cast(acct_iter->data)); + + g_list_free (acct_list); +} + +static void +account_set_default_cb(GtkWidget* widget, gpointer data) +{ + GncOption* option = static_cast(data); + account_clear_all_cb(widget, data); + option->set_value(option->get_default_value()); + option->set_ui_item_from_option(); +} + +static void +show_hidden_toggled_cb(GtkWidget *widget, GncOption* option) +{ + if (option->get_ui_type() != GncOptionUIType::ACCOUNT_LIST && + option->get_ui_type() != GncOptionUIType::ACCOUNT_SEL) + return; + + auto tree_view = GNC_TREE_VIEW_ACCOUNT(gnc_option_get_gtk_widget (option)); + AccountViewInfo avi; + gnc_tree_view_account_get_view_info (tree_view, &avi); + avi.show_hidden = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)); + gnc_tree_view_account_set_view_info (tree_view, &avi); + widget_changed_cb(widget, option); +} + +class GncGtkAccountListUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkAccountListUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::ACCOUNT_LIST} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GNC_TREE_VIEW_ACCOUNT(get_widget())}; + GList *acc_list = nullptr; + const GncOptionAccountList& accounts = + option.get_value(); + for (auto account : accounts) + g_list_prepend(acc_list, static_cast(const_cast(account))); + g_list_reverse(acc_list); + gnc_tree_view_account_set_selected_accounts(widget, acc_list, TRUE); + g_list_free(acc_list); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GNC_TREE_VIEW_ACCOUNT(get_widget())}; + auto acc_list = gnc_tree_view_account_get_selected_accounts(widget); + GncOptionAccountList acc_vec(g_list_length(acc_list)); + for (auto node = acc_list; node; node = g_list_next(node)) + acc_vec.push_back(static_cast(node->data)); + g_list_free(acc_list); + option.set_value(acc_vec); + } +}; + +GtkWidget* +create_account_widget(GncOption& option, char *name) +{ + bool multiple_selection; + GtkWidget *scroll_win; + GtkWidget *button; + GtkWidget *frame; + GtkWidget *tree; + GtkWidget *vbox; + GtkWidget *bbox; + GList *acct_type_list; + GtkTreeSelection *selection; + + multiple_selection = option.is_multiselect(); + acct_type_list = option.account_type_list(); + + frame = gtk_frame_new(name); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_box_set_homogeneous (GTK_BOX (vbox), FALSE); + + gtk_container_add(GTK_CONTAINER(frame), vbox); + + tree = GTK_WIDGET(gnc_tree_view_account_new (FALSE)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(tree), FALSE); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(tree)); + if (multiple_selection) + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + else + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + + if (acct_type_list) + { + GList *node; + AccountViewInfo avi; + int i; + + gnc_tree_view_account_get_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi); + + for (i = 0; i < NUM_ACCOUNT_TYPES; i++) + avi.include_type[i] = FALSE; + avi.show_hidden = FALSE; + + for (node = acct_type_list; node; node = node->next) + { + GNCAccountType type = static_cast(GPOINTER_TO_INT (node->data)); + avi.include_type[type] = TRUE; + } + + gnc_tree_view_account_set_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi); + g_list_free (acct_type_list); + } + else + { + AccountViewInfo avi; + int i; + + gnc_tree_view_account_get_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi); + + for (i = 0; i < NUM_ACCOUNT_TYPES; i++) + avi.include_type[i] = TRUE; + avi.show_hidden = FALSE; + gnc_tree_view_account_set_view_info (GNC_TREE_VIEW_ACCOUNT (tree), &avi); + } + + scroll_win = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_box_pack_start(GTK_BOX(vbox), scroll_win, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(scroll_win), 5); + gtk_container_add(GTK_CONTAINER(scroll_win), tree); + + bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 10); + + if (multiple_selection) + { + button = gtk_button_new_with_label(_("Select All")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Select all accounts.")); + + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(account_select_all_cb), &option); + + button = gtk_button_new_with_label(_("Clear All")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Clear the selection and unselect all accounts.")); + + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(account_clear_all_cb), &option); + + button = gtk_button_new_with_label(_("Select Children")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Select all descendents of selected account.")); + + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(account_select_children_cb), &option); + } + + button = gtk_button_new_with_label(_("Select Default")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Select the default account selection.")); + + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(account_set_default_cb), &option); + + gtk_widget_set_margin_start (GTK_WIDGET(bbox), 6); + gtk_widget_set_margin_end (GTK_WIDGET(bbox), 6); + + if (multiple_selection) + { + /* Put the "Show hidden" checkbox on a separate line since the 4 buttons make + the dialog too wide. */ + bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START); + gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); + } + + button = gtk_check_button_new_with_label(_("Show Hidden Accounts")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Show accounts that have been marked hidden.")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE); + g_signal_connect(G_OBJECT(button), "toggled", + G_CALLBACK(show_hidden_toggled_cb), &option); + + option.set_ui_item(std::make_unique(tree)); + option.set_ui_item_from_option(); + + return frame; +} + +template<> GtkWidget* +create_option_widget(GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + align_label (name_label); + + *enclosing = create_account_widget(option, NULL); + gtk_widget_set_vexpand (GTK_WIDGET(*enclosing), TRUE); + gtk_widget_set_hexpand (GTK_WIDGET(*enclosing), TRUE); + + gtk_widget_set_tooltip_text(*enclosing, documentation); + + auto grid_row = GPOINTER_TO_INT(g_object_get_data + (G_OBJECT(page_box), "options-grid-row")); + gtk_grid_attach (GTK_GRID(page_box), *enclosing, 1, grid_row, 1, 1); + *packed = TRUE; + + auto widget = gnc_option_get_gtk_widget(option); + auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); + g_signal_connect(G_OBJECT(selection), "changed", + G_CALLBACK(widget_changed_cb), &option); + + // gtk_clist_set_row_height(GTK_CLIST(value), 0); + // gtk_widget_set_size_request(value, -1, GTK_CLIST(value)->row_height * 10); + gtk_widget_show_all(*enclosing); + return widget; +} + +class GncGtkAccountSelUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkAccountSelUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::ACCOUNT_SEL} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GNC_ACCOUNT_SEL(get_widget())}; + gnc_account_sel_set_account(widget, + GNC_ACCOUNT(option.get_value()), + FALSE); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GNC_ACCOUNT_SEL(get_widget())}; + option.set_value(qof_instance_cast((gnc_account_sel_get_account(widget)))); + } +}; + +template<> GtkWidget* +create_option_widget (GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + auto acct_type_list = option.account_type_list(); + auto widget = gnc_account_sel_new (); + gnc_account_sel_set_acct_filters(GNC_ACCOUNT_SEL(widget), + acct_type_list, NULL); + g_list_free(acct_type_list); + g_signal_connect(widget, "account_sel_changed", + G_CALLBACK(widget_changed_cb), &option); + + +// gnc_account_sel doesn't emit a changed signal + option.set_ui_item(std::make_unique(widget)); + option.set_ui_item_from_option(); + + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +static void +list_changed_cb(GtkTreeSelection *selection, GncOption* option) +{ + GtkTreeView *view = GTK_TREE_VIEW(gnc_option_get_gtk_widget (*option)); + widget_changed_cb(GTK_WIDGET(view), option); +} + +static void +list_select_all_cb(GtkWidget *widget, gpointer data) +{ + GncOption* option = static_cast(data); + GtkTreeView *view; + GtkTreeSelection *selection; + + view = GTK_TREE_VIEW(gnc_option_get_gtk_widget(*option)); + selection = gtk_tree_view_get_selection(view); + gtk_tree_selection_select_all(selection); + widget_changed_cb(GTK_WIDGET(view), option); +} + +static void +list_clear_all_cb(GtkWidget *widget, gpointer data) +{ + GncOption* option = static_cast(data); + GtkTreeView *view; + GtkTreeSelection *selection; + + view = GTK_TREE_VIEW(gnc_option_get_gtk_widget (*option)); + selection = gtk_tree_view_get_selection(view); + gtk_tree_selection_unselect_all(selection); + widget_changed_cb(GTK_WIDGET(view), option); +} + +static void +list_set_default_cb(GtkWidget *widget, gpointer data) +{ + GncOption* option = static_cast(data); + list_clear_all_cb(widget, data); + option->set_value(option->get_default_value()); + option->set_ui_item_from_option(); +} + +class GncGtkListUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkListUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::MULTICHOICE} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GTK_TREE_VIEW(get_widget())}; + auto selection{gtk_tree_view_get_selection(widget)}; + gtk_tree_selection_unselect_all(selection); + for (auto index : option.get_value()) + { + auto path{gtk_tree_path_new_from_indices(index, -1)}; + gtk_tree_selection_select_path(selection, path); + gtk_tree_path_free(path); + } + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GTK_TREE_VIEW(get_widget())}; + auto selection{gtk_tree_view_get_selection(widget)}; + auto rows{option.num_permissible_values()}; + GncMultichoiceOptionIndexVec vec; + for (size_t row = 0; row < rows; ++row) + { + auto path{gtk_tree_path_new_from_indices(row, -1)}; + auto selected{gtk_tree_selection_path_is_selected(selection, path)}; + gtk_tree_path_free(path); + if (selected) + vec.push_back(row); + } + option.set_value(vec); + } +}; + +static GtkWidget * +create_list_widget(GncOption& option, char *name) +{ + GtkListStore *store; + GtkTreeView *view; + GtkTreeIter iter; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkTreeSelection *selection; + + GtkWidget *button; + GtkWidget *frame; + GtkWidget *hbox; + GtkWidget *bbox; + + frame = gtk_frame_new(name); + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE); + gtk_container_add(GTK_CONTAINER(frame), hbox); + + store = gtk_list_store_new(1, G_TYPE_STRING); + view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store))); + g_object_unref(store); + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes("", renderer, + "text", 0, + NULL); + gtk_tree_view_append_column(view, column); + gtk_tree_view_set_headers_visible(view, FALSE); + + auto num_values = option.num_permissible_values(); + for (auto i = 0; i < num_values; i++) + { + auto raw_string = option.permissible_value_name(i); + auto string = (raw_string && *raw_string) ? _(raw_string) : ""; + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, string ? string : "", -1); + } + + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(view), FALSE, FALSE, 0); + + selection = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + g_signal_connect(selection, "changed", + G_CALLBACK(list_changed_cb), &option); + + bbox = gtk_button_box_new (GTK_ORIENTATION_VERTICAL); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_end(GTK_BOX(hbox), bbox, FALSE, FALSE, 0); + + button = gtk_button_new_with_label(_("Select All")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Select all entries.")); + + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(list_select_all_cb), &option); + + button = gtk_button_new_with_label(_("Clear All")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Clear the selection and unselect all entries.")); + + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(list_clear_all_cb), &option); + + button = gtk_button_new_with_label(_("Select Default")); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text(button, _("Select the default selection.")); + + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(list_set_default_cb), &option); + + g_object_set (G_OBJECT(hbox), "margin", 3, NULL); + + option.set_ui_item(std::make_unique(GTK_WIDGET(view))); + option.set_ui_item_from_option(); + + return frame; +} + +template<> GtkWidget * +create_option_widget (GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + GtkWidget *value; + GtkWidget *eventbox; + gint grid_row = GPOINTER_TO_INT(g_object_get_data + (G_OBJECT(page_box), "options-grid-row")); + + *enclosing = create_list_widget(option, NULL); + value = gnc_option_get_gtk_widget (option); + + align_label (name_label); + + /* Pack option widget into an extra eventbox because otherwise the + "documentation" tooltip is not displayed. */ + eventbox = gtk_event_box_new(); + gtk_container_add (GTK_CONTAINER (eventbox), *enclosing); + + gtk_grid_attach (GTK_GRID(page_box), eventbox, 1, grid_row, 1, 1); + *packed = TRUE; + + gtk_widget_set_tooltip_text(eventbox, documentation); + + gtk_widget_show(*enclosing); + return value; +} + +class GncGtkNumberRangeUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkNumberRangeUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::NUMBER_RANGE} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + gtk_spin_button_set_value(GTK_SPIN_BUTTON(get_widget()), + option.get_value()); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + option.set_value(gtk_spin_button_get_value(GTK_SPIN_BUTTON(get_widget()))); + } +}; + +/* New spin button configured with the values provided by the passed-in + * GncOption, which had better contain a GncOptionRangeValue. + * + * Also used to set up the pixel spinner in the plot_size control. + */ +static GtkSpinButton* +create_range_spinner(GncOption& option) +{ + gdouble lower_bound = G_MINDOUBLE; + gdouble upper_bound = G_MAXDOUBLE; + gdouble step_size = 1.0; + + option.get_limits(upper_bound, lower_bound, step_size); + auto adj = GTK_ADJUSTMENT(gtk_adjustment_new(lower_bound, lower_bound, + upper_bound, step_size, + step_size * 5.0, + 0)); + + size_t num_decimals = 0; + for (auto steps = step_size; steps < 1; steps *= 10) + ++num_decimals; + auto widget = gtk_spin_button_new(adj, step_size, num_decimals); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(widget), TRUE); + + size_t num_digits = 0; + for (auto bigger = MAX(ABS(lower_bound), ABS(upper_bound)); + bigger >= 1; bigger /= 10.0) + ++num_digits; + num_digits += num_decimals; + gtk_entry_set_width_chars(GTK_ENTRY(widget), num_digits); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), + (upper_bound / 2)); //default + return GTK_SPIN_BUTTON(widget); +} + +template<> GtkWidget * +create_option_widget (GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + GtkAdjustment *adj; + size_t num_decimals = 0; + + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + + auto widget = create_range_spinner(option); + option.set_ui_item(std::make_unique(GTK_WIDGET(widget))); + option.set_ui_item_from_option(); + + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(widget_changed_cb), &option); + + gtk_box_pack_start(GTK_BOX(*enclosing), GTK_WIDGET(widget), + FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return GTK_WIDGET(widget); +} + +class GncGtkColorUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkColorUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::COLOR} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + GdkRGBA color; + if (gdk_rgba_parse(&color, + option.get_value().c_str())) + { + auto color_button = GTK_COLOR_CHOOSER(get_widget()); + gtk_color_chooser_set_rgba(color_button, &color); + } + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + GdkRGBA color; + auto color_button = GTK_COLOR_CHOOSER(get_widget()); + gtk_color_chooser_set_rgba(color_button, &color); + auto rgba_str = gdk_rgba_to_string(&color); + option.set_value(rgba_str); + g_free(rgba_str); + } +}; + +template<> GtkWidget * +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + bool use_alpha; + + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + + auto widget = gtk_color_button_new(); + gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(widget), TRUE); + + option.set_ui_item(std::make_unique(widget)); + option.set_ui_item_from_option(); + + g_signal_connect(G_OBJECT(widget), "color-set", + G_CALLBACK(widget_changed_cb), &option); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +class GncGtkFontUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkFontUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::FONT} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + GtkFontButton *font_button = GTK_FONT_BUTTON(get_widget()); + gtk_font_button_set_font_name(font_button, option.get_value().c_str()); + + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + GtkFontButton *font_button = GTK_FONT_BUTTON(get_widget()); + option.set_value(gtk_font_button_get_font_name(font_button)); + } +}; + +template<> GtkWidget * +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + auto widget = gtk_font_button_new(); + g_object_set(G_OBJECT(widget), + "use-font", TRUE, + "show-style", TRUE, + "show-size", TRUE, + (char *)NULL); + + option.set_ui_item(std::make_unique(widget)); + option.set_ui_item_from_option(); + + g_signal_connect(G_OBJECT(widget), "font-set", + G_CALLBACK(widget_changed_cb), &option); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +static void +update_preview_cb (GtkFileChooser *chooser, void* data) +{ + g_return_if_fail(chooser != NULL); + + ENTER("chooser %p", chooser); + auto filename = gtk_file_chooser_get_preview_filename(chooser); + DEBUG("chooser preview name is %s.", filename ? filename : "(null)"); + if (filename == NULL) + { + filename = g_strdup(static_cast(g_object_get_data(G_OBJECT(chooser), LAST_SELECTION))); + DEBUG("using last selection of %s", filename ? filename : "(null)"); + if (filename == NULL) + { + LEAVE("no usable name"); + return; + } + } + + auto image = GTK_IMAGE(gtk_file_chooser_get_preview_widget(chooser)); + auto pixbuf = gdk_pixbuf_new_from_file_at_size(filename, 128, 128, NULL); + g_free(filename); + auto have_preview = (pixbuf != NULL); + + gtk_image_set_from_pixbuf(image, pixbuf); + if (pixbuf) + g_object_unref(pixbuf); + + gtk_file_chooser_set_preview_widget_active(chooser, have_preview); + LEAVE("preview visible is %d", have_preview); +} + +static void +change_image_cb (GtkFileChooser *chooser, void* data) +{ + auto filename{gtk_file_chooser_get_preview_filename(chooser)}; + if (!filename) + return; + g_object_set_data_full(G_OBJECT(chooser), LAST_SELECTION, filename, g_free); +} + +class GncGtkPixmapUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkPixmapUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::PIXMAP} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto string = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(get_widget())); + DEBUG("filename %s", string ? string : "(null)"); + option.set_value(string); + g_free(string); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto string{option.get_value()}; + if (!string.empty()) + { + DEBUG("string = %s", string.c_str()); + auto chooser{GTK_FILE_CHOOSER(get_widget())}; + gtk_file_chooser_select_filename(chooser, string.c_str()); + auto filename{gtk_file_chooser_get_filename(chooser)}; + g_object_set_data_full(G_OBJECT(chooser), LAST_SELECTION, + g_strdup(string.c_str()), g_free); + DEBUG("Set %s, retrieved %s", string.c_str(), + filename ? filename : "(null)"); + update_preview_cb(chooser, &option); + } + } +}; + +template<> GtkWidget * +create_option_widget (GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + + auto button = gtk_button_new_with_label(_("Clear")); + gtk_widget_set_tooltip_text(button, _("Clear any selected image file.")); + + auto widget = gtk_file_chooser_button_new(_("Select image"), + GTK_FILE_CHOOSER_ACTION_OPEN); + gtk_widget_set_tooltip_text(widget, _("Select an image file.")); + g_object_set(G_OBJECT(widget), + "width-chars", 30, + "preview-widget", gtk_image_new(), + (char *)NULL); + g_signal_connect(G_OBJECT (widget), "selection-changed", + G_CALLBACK(widget_changed_cb), &option); + g_signal_connect(G_OBJECT (widget), "selection-changed", + G_CALLBACK(change_image_cb), &option); + g_signal_connect(G_OBJECT (widget), "update-preview", + G_CALLBACK(update_preview_cb), &option); + g_signal_connect_swapped(G_OBJECT (button), "clicked", + G_CALLBACK(gtk_file_chooser_unselect_all), widget); + + option.set_ui_item(std::make_unique(widget)); + option.set_ui_item_from_option(); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(*enclosing), button, FALSE, FALSE, 0); + + gtk_widget_show(widget); + gtk_widget_show(*enclosing); + + return widget; +} + +static void +radiobutton_set_cb(GtkWidget *w, gpointer data) +{ + GncOption* option = static_cast(data); + GtkWidget *widget; + gpointer _current, _new_value; + gint current, new_value; + + widget = gnc_option_get_gtk_widget (option); + + _current = g_object_get_data(G_OBJECT(widget), "gnc_radiobutton_index"); + current = GPOINTER_TO_INT (_current); + + _new_value = g_object_get_data (G_OBJECT(w), "gnc_radiobutton_index"); + new_value = GPOINTER_TO_INT (_new_value); + + if (current == new_value) + return; + + g_object_set_data (G_OBJECT(widget), "gnc_radiobutton_index", + GINT_TO_POINTER(new_value)); + widget_changed_cb(widget, option); +} + +class GncGtkRadioButtonUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkRadioButtonUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::RADIOBUTTON} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto index{option.get_value()}; + auto list{gtk_container_get_children(GTK_CONTAINER(get_widget()))}; + auto box{GTK_WIDGET(list->data)}; + g_list_free(list); + + list = gtk_container_get_children(GTK_CONTAINER(box)); + auto node{g_list_nth(list, index)}; + GtkButton* button{}; + if (node) + { + button = GTK_BUTTON(node->data); + } + else + { + PERR("Invalid Radio Button Selection %lu", index); + g_list_free(list); + return; + } + g_list_free(list); + auto val{g_object_get_data (G_OBJECT (button), + "gnc_radiobutton_index")}; + g_return_if_fail (GPOINTER_TO_INT (val) == index); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto index{g_object_get_data(G_OBJECT(get_widget()), + "gnc_radiobutton_index")}; + option.set_value(GPOINTER_TO_INT(index)); + } +}; + +static GtkWidget * +create_radiobutton_widget(char *name, GncOption& option) +{ + GtkWidget *frame, *box; + GtkWidget *widget = NULL; + + auto num_values{option.num_permissible_values()}; + + g_return_val_if_fail(num_values >= 0, NULL); + + /* Create our button frame */ + frame = gtk_frame_new (name); + + /* Create the button box */ + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); + gtk_box_set_homogeneous (GTK_BOX (box), FALSE); + gtk_container_add (GTK_CONTAINER (frame), box); + + /* Iterate over the options and create a radio button for each one */ + for (auto i = 0; i < num_values; i++) + { + auto label = option.permissible_value_name(i); + auto tip = option.permissible_value_description(i); + + widget = + gtk_radio_button_new_with_label_from_widget (widget ? + GTK_RADIO_BUTTON (widget) : + NULL, + label && *label ? _(label) : ""); + g_object_set_data (G_OBJECT (widget), "gnc_radiobutton_index", + GINT_TO_POINTER (i)); + gtk_widget_set_tooltip_text(widget, tip && *tip ? _(tip) : ""); + g_signal_connect(G_OBJECT(widget), "toggled", + G_CALLBACK(radiobutton_set_cb), &option); + gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0); + } + + return frame; +} + +template<> GtkWidget * +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + + align_label (name_label); + + auto widget = create_radiobutton_widget(NULL, option); + + option.set_ui_item(std::make_unique(widget)); + option.set_ui_item_from_option(); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +class GncGtkDateFormatUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkDateFormatUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::STRING} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widget{GNC_DATE_FORMAT(get_widget())}; + gnc_date_format_set_custom(widget, + option.get_value().c_str()); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widget{GNC_DATE_FORMAT(get_widget())}; + option.set_value(std::string{gnc_date_format_get_custom(widget)}); + } +}; + + +template<> GtkWidget * +create_option_widget (GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + *enclosing = gnc_date_format_new_without_label (); + align_label (name_label); + + option.set_ui_item(std::make_unique(*enclosing)); + option.set_ui_item_from_option(); + + g_signal_connect(G_OBJECT(*enclosing), "format_changed", + G_CALLBACK(widget_changed_cb), &option); + gtk_widget_show_all(*enclosing); + return *enclosing; +} + +static void +gnc_plot_size_option_set_select_method(GncOption& option, bool set_buttons) +{ + GList* widget_list; + GtkWidget *px_widget, *p_widget; + GtkWidget *widget; + + widget = gnc_option_get_gtk_widget (option); + + widget_list = gtk_container_get_children(GTK_CONTAINER(widget)); + // px_button item 0 + px_widget = static_cast(g_list_nth_data(widget_list, 1)); + // p_button item 2 + p_widget = static_cast(g_list_nth_data(widget_list, 3)); + g_list_free(widget_list); + + if (set_buttons) + { + gtk_widget_set_sensitive(px_widget, TRUE); + gtk_widget_set_sensitive(p_widget, FALSE); + } + else + { + gtk_widget_set_sensitive(p_widget, TRUE); + gtk_widget_set_sensitive(px_widget, FALSE); + } +} + +static void +gnc_rd_option_px_set_cb(GtkWidget *widget, GncOption* option) +{ + option->set_alternate(true); + option_changed_cb(widget, option); +} + +static void +gnc_rd_option_p_set_cb(GtkWidget *widget, GncOption* option) +{ + option->set_alternate(false); + option_changed_cb(widget, option); +} + +class GncGtkPlotSizeUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkPlotSizeUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::PLOT_SIZE} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + auto widgets{gtk_container_get_children(GTK_CONTAINER(get_widget()))}; + GtkWidget *button{}, *spin{}; + if (option.is_alternate()) + { + button = GTK_WIDGET(g_list_nth_data(widgets, 2)); + spin = GTK_WIDGET(g_list_nth_data(widgets, 3)); + } + else + { + button = GTK_WIDGET(g_list_nth_data(widgets, 2)); + spin = GTK_WIDGET(g_list_nth_data(widgets, 3)); + } + gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), + option.get_value()); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE); + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + auto widgets{gtk_container_get_children(GTK_CONTAINER(get_widget()))}; + auto px_button{GTK_BUTTON(g_list_nth_data(widgets, 0))}; + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(px_button))) + { + option.set_alternate(false); + auto spin{g_list_nth_data(widgets, 1)}; + option.set_value(gtk_spin_button_get_value(GTK_SPIN_BUTTON(get_widget()))); + } + else + { + option.set_alternate(true); + auto spin{g_list_nth_data(widgets, 3)}; + option.set_value(gtk_spin_button_get_value(GTK_SPIN_BUTTON(get_widget()))); + } + } +}; + + +template<> GtkWidget * +create_option_widget (GncOption& option, + GtkGrid *page_box, + GtkLabel *name_label, + char *documentation, + /* Return values */ + GtkWidget **enclosing, + bool *packed) +{ + GtkWidget *value_percent; + GtkWidget *px_butt, *p_butt; + GtkWidget *hbox; + GtkAdjustment *adj_percent; + + *enclosing = gtk_frame_new(NULL); + gtk_widget_set_halign (GTK_WIDGET(*enclosing), GTK_ALIGN_START); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE); + g_object_set (G_OBJECT(hbox), "margin", 3, NULL); + + gtk_container_add(GTK_CONTAINER(*enclosing), hbox); + auto value_px = create_range_spinner(option); + + g_signal_connect(G_OBJECT(value_px), "changed", + G_CALLBACK(widget_changed_cb), &option); + + adj_percent = GTK_ADJUSTMENT(gtk_adjustment_new(1, 10, 100, 1, 5.0, 0)); + value_percent = gtk_spin_button_new(adj_percent, 1, 0); + gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(value_percent), TRUE); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(value_percent), 100); //default + gtk_entry_set_width_chars(GTK_ENTRY(value_percent), 3); + gtk_widget_set_sensitive(value_percent, FALSE); + + g_signal_connect(G_OBJECT(value_percent), "changed", + G_CALLBACK(widget_changed_cb), &option); + + px_butt = gtk_radio_button_new_with_label(NULL, _("Pixels")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(px_butt), TRUE); + + g_signal_connect(G_OBJECT(px_butt), "toggled", + G_CALLBACK(gnc_rd_option_px_set_cb), &option); + + p_butt = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(px_butt), _("Percent")); + g_signal_connect(G_OBJECT(p_butt), "toggled", + G_CALLBACK(gnc_rd_option_p_set_cb), &option); + + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(px_butt), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(value_px), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(p_butt), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(value_percent), + FALSE, FALSE, 0); + + option.set_ui_item(std::make_unique(static_cast(hbox))); + option.set_ui_item_from_option(); + + gtk_widget_show_all(*enclosing); + return hbox; +} + +static GtkWidget * +create_budget_widget(GncOption& option) +{ + GtkTreeModel *tm; + GtkComboBox *cb; + GtkCellRenderer *cr; + + tm = gnc_tree_model_budget_new(gnc_get_current_book()); + cb = GTK_COMBO_BOX(gtk_combo_box_new_with_model(tm)); + g_object_unref(tm); + cr = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(cb), cr, TRUE); + + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(cb), cr, "text", + BUDGET_NAME_COLUMN, NULL); + return GTK_WIDGET(cb); +} + +class GncGtkBudgetUIItem : public GncOptionGtkUIItem +{ +public: + GncGtkBudgetUIItem(GtkWidget* widget) : + GncOptionGtkUIItem{widget, GncOptionUIType::BUDGET} {} + void set_ui_item_from_option(GncOption& option) noexcept override + { + GtkTreeIter iter; + auto widget{GTK_COMBO_BOX(get_widget())}; + auto budget{GNC_BUDGET(option.get_value())}; + auto tree_model{gtk_combo_box_get_model(widget)}; + if (gnc_tree_model_budget_get_iter_for_budget(tree_model, &iter, + budget)) + gtk_combo_box_set_active_iter(widget, &iter); + + } + void set_option_from_ui_item(GncOption& option) noexcept override + { + GtkTreeIter iter; + auto widget{GTK_COMBO_BOX(get_widget())}; + gtk_combo_box_get_active_iter(widget, &iter); + auto tree_model{gtk_combo_box_get_model(widget)}; + auto budget{gnc_tree_model_budget_get_budget(tree_model, &iter)}; + option.set_value(qof_instance_cast(budget)); + } +}; + +template<> GtkWidget * +create_option_widget (GncOption& option, GtkGrid *page_box, + GtkLabel *name_label, char *documentation, + /* Return values */ + GtkWidget **enclosing, bool *packed) +{ + *enclosing = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_box_set_homogeneous (GTK_BOX (*enclosing), FALSE); + + auto widget{create_budget_widget(option)}; + + option.set_ui_item(std::make_unique(widget)); + option.set_ui_item_from_option(); + + /* Maybe connect destroy handler for tree model here? */ + g_signal_connect(G_OBJECT(widget), "changed", + G_CALLBACK(widget_changed_cb), &option); + + gtk_box_pack_start(GTK_BOX(*enclosing), widget, FALSE, FALSE, 0); + gtk_widget_show_all(*enclosing); + return widget; +} + +static GtkWidget* +option_widget_factory(GncOption& option, GtkGrid* page, GtkLabel* name, + char* description, GtkWidget** enclosing, bool* packed) +{ + switch(option.get_ui_type()) + { + case INTERNAL: + return nullptr; + case BOOLEAN: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case STRING: + return create_option_widget(option, page, name, description, + enclosing, packed); + case TEXT: + return create_option_widget(option, page, name, description, + enclosing, packed); + case CURRENCY: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case COMMODITY: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case MULTICHOICE: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case DATE_ABSOLUTE: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case DATE_RELATIVE: + return create_option_widget(option, page, name, + description, + enclosing, packed); + case DATE_BOTH: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case ACCOUNT_LIST: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case ACCOUNT_SEL: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case LIST: + return create_option_widget(option, page, name, description, + enclosing, packed); + case NUMBER_RANGE: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case COLOR: + return create_option_widget(option, page, name, description, + enclosing, packed); + case FONT: + return create_option_widget(option, page, name, description, + enclosing, packed); + case PLOT_SIZE: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case BUDGET: + return create_option_widget(option, page, name, description, + enclosing, packed); + case PIXMAP: + return create_option_widget(option, page, name, description, + enclosing, packed); + case RADIOBUTTON: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case DATE_FORMAT: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case OWNER: + return create_option_widget(option, page, name, description, + enclosing, packed); + case CUSTOMER: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case VENDOR: + return create_option_widget(option, page, name, description, + enclosing, packed); + case EMPLOYEE: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case INVOICE: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case TAX_TABLE: + return create_option_widget(option, page, name, + description, enclosing, + packed); + case QUERY: + return create_option_widget(option, page, name, description, + enclosing, packed); + } +} + +void +gnc_options_dialog_set_new_book_option_values (GncOptionDB *odb) +{ + if (!odb) return; + auto num_split_action = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, + GNC_PREF_NUM_SOURCE); + if (num_split_action) + { + auto option{odb->find_option(OPTION_SECTION_ACCOUNTS, + OPTION_NAME_NUM_FIELD_SOURCE)}; + auto num_source_button{gnc_option_get_gtk_widget(option)}; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (num_source_button), + num_split_action); + } +} + + +static void +gnc_book_options_help_cb (GNCOptionWin *win, gpointer dat) +{ + gnc_gnome_help (GTK_WINDOW (win), HF_HELP, HL_BOOK_OPTIONS); +} + +void +gnc_options_dialog_set_book_options_help_cb (GNCOptionWin *win) +{ + gnc_options_dialog_set_help_cb(win, + (GNCOptionWinCallback)gnc_book_options_help_cb, + NULL); +} + +/* Dummy function impls. The following functions are declared in + * dialog_options.h and used by clients but they're made obsolete by the new + * options system. They're here to satisfy the linker. + */ +GtkWidget* const +gnc_option_get_gtk_widget (GncOption *option) +{ + return nullptr; +} + +void +gnc_options_ui_initialize (void) +{ + return; +} diff --git a/gnucash/gnome-utils/dialog-options.h b/gnucash/gnome-utils/dialog-options.h index 3000717348..b3613de210 100644 --- a/gnucash/gnome-utils/dialog-options.h +++ b/gnucash/gnome-utils/dialog-options.h @@ -24,12 +24,23 @@ #define OPTIONS_DIALOG_H #include -#include "option-util.h" #include +#ifdef __cplusplus +class GncOption; +class GncOptionDB; +using GNCOption = GncOption; +using GNCOptionDB = GncOptionDB; +extern "C" +{ +#else +#include "option-util.h" +typedef GNCOption GncOption; +typedef GNCOptionDB GncOptionDB; +#endif /** A simple wrapper that casts the gpointer result of * gnc_option_get_widget() already into a GtkWidget*. */ -GtkWidget *gnc_option_get_gtk_widget (GNCOption *option); +GtkWidget* const gnc_option_get_gtk_widget (GncOption *option); typedef struct gnc_option_win GNCOptionWin; @@ -39,7 +50,6 @@ GNCOptionWin * gnc_options_dialog_new_modal (gboolean modal, gchar *title, const char *component_class, GtkWindow *parent); GNCOptionWin * gnc_options_dialog_new (gchar *title, GtkWindow *parent); -GNCOptionWin * gnc_options_dialog_new_w_dialog (gchar *title, GtkWidget *dialog); void gnc_options_dialog_destroy (GNCOptionWin * win); void gnc_options_register_stocks (void); @@ -49,8 +59,8 @@ GtkWidget * gnc_options_dialog_notebook (GNCOptionWin * win); void gnc_options_dialog_changed (GNCOptionWin *win); -void gnc_option_changed_widget_cb (GtkWidget *widget, GNCOption *option); -void gnc_option_changed_option_cb (GtkWidget *dummy, GNCOption *option); +void gnc_option_changed_widget_cb (GtkWidget *widget, GncOption *option); +void gnc_option_changed_option_cb (GtkWidget *dummy, GncOption *option); void gnc_options_dialog_set_apply_cb (GNCOptionWin * win, GNCOptionWinCallback thunk, @@ -67,50 +77,21 @@ void gnc_options_dialog_set_global_help_cb (GNCOptionWinCallback thunk, void gnc_options_dialog_build_contents (GNCOptionWin *win, GNCOptionDB *odb); - void gnc_options_dialog_build_contents_full (GNCOptionWin *win, GNCOptionDB *odb, gboolean show_dialog); +void gnc_options_ui_initialize (void); -/* Both apply_cb and close_cb should be scheme functions with 0 arguments. - * References to these functions will be held until the close_cb is called +/** Set the help callback to 'gnc_book_options_help_cb' to open a help browser + * and point it to the Book Options link in the Help file. */ -void gnc_options_dialog_set_scm_callbacks (GNCOptionWin *win, - SCM apply_cb, - SCM close_cb); - -/*****************************************************************/ -/* Option Registration */ - -/* Function to set the UI widget based upon the option */ -typedef GtkWidget * -(*GNCOptionUISetWidget) (GNCOption *option, GtkGrid *page_box, - GtkLabel *name_label, char *documentation, - /* Return values */ - GtkWidget **enclosing, gboolean *packed); - -/* Function to set the UI Value for a particular option */ -typedef gboolean -(*GNCOptionUISetValue) (GNCOption *option, gboolean use_default, - GtkWidget *widget, SCM value); - -/* Function to get the UI Value for a particular option */ -typedef SCM -(*GNCOptionUIGetValue) (GNCOption *option, GtkWidget *widget); - - -typedef struct gnc_option_def -{ - const char * option_name; - GNCOptionUISetWidget set_widget; - GNCOptionUISetValue set_value; - GNCOptionUIGetValue get_value; -} GNCOptionDef_t; - - -/* Register a new option type in the UI */ -void gnc_options_ui_initialize (void); -void gnc_options_ui_register_option (GNCOptionDef_t *option); -GNCOptionDef_t * gnc_options_ui_get_option (const char *option_name); +void gnc_options_dialog_set_book_options_help_cb (GNCOptionWin *win); +/** Set the initial values of new book options to values specified in user + * preferences. + */ +void gnc_options_dialog_set_new_book_option_values (GNCOptionDB *odb); +#ifdef __cplusplus +} +#endif #endif /* OPTIONS_DIALOG_H */ diff --git a/gnucash/gnome-utils/gnc-gnome-utils.c b/gnucash/gnome-utils/gnc-gnome-utils.c index a771f3525b..58e4272a9b 100644 --- a/gnucash/gnome-utils/gnc-gnome-utils.c +++ b/gnucash/gnome-utils/gnc-gnome-utils.c @@ -99,6 +99,7 @@ gnc_book_options_help_cb (GNCOptionWin *win, gpointer dat) gnc_gnome_help (GTK_WINDOW(gnc_options_dialog_widget (win)), HF_HELP, HL_BOOK_OPTIONS); } +#if 0 // Reimplemented in dialog-options.cpp void gnc_options_dialog_set_book_options_help_cb (GNCOptionWin *win) { @@ -129,6 +130,7 @@ gnc_options_dialog_set_new_book_option_values (GNCOptionDB *odb) num_source_is_split_action); } } +#endif static void gnc_style_sheet_options_help_cb (GNCOptionWin *win, gpointer dat) diff --git a/gnucash/gnome/business-options-gnome.c b/gnucash/gnome/business-options-gnome.c index afee518cae..a9bc680dd1 100644 --- a/gnucash/gnome/business-options-gnome.c +++ b/gnucash/gnome/business-options-gnome.c @@ -42,9 +42,10 @@ #include "dialog-invoice.h" #define FUNC_NAME G_STRFUNC - +/* To be consolidated into dialog-options.cpp later. */ +#if 0 static GtkWidget * -create_owner_widget (GNCOption *option, GncOwnerType type, GtkWidget *hbox) +create_owner_widget (GncOption *option, GncOwnerType type, GtkWidget *hbox) { GtkWidget *widget; GncOwner owner; @@ -91,7 +92,7 @@ make_name_label (char *name) static GncOwnerType -get_owner_type_from_option (GNCOption *option) +get_owner_type_from_option (GncOption *option) { SCM odata = gnc_option_get_option_data (option); @@ -102,7 +103,7 @@ get_owner_type_from_option (GNCOption *option) /* Function to set the UI widget based upon the option */ static GtkWidget * -owner_set_widget (GNCOption *option, GtkGrid *page_box, +owner_set_widget (GncOption *option, GtkGrid *page_box, GtkLabel *name_label, char *documentation, /* Return values */ GtkWidget **enclosing, gboolean *packed) @@ -123,7 +124,7 @@ owner_set_widget (GNCOption *option, GtkGrid *page_box, /* Function to set the UI Value for a particular option */ static gboolean -owner_set_value (GNCOption *option, gboolean use_default, +owner_set_value (GncOption *option, gboolean use_default, GtkWidget *widget, SCM value) { GncOwner owner_def; @@ -150,7 +151,7 @@ owner_set_value (GNCOption *option, gboolean use_default, /* Function to get the UI Value for a particular option */ static SCM -owner_get_value (GNCOption *option, GtkWidget *widget) +owner_get_value (GncOption *option, GtkWidget *widget) { static GncOwner owner; /* XXX: might cause trouble? */ GncOwnerType type; @@ -169,7 +170,7 @@ owner_get_value (GNCOption *option, GtkWidget *widget) /* Function to set the UI widget based upon the option */ static GtkWidget * -customer_set_widget (GNCOption *option, GtkGrid *page_box, +customer_set_widget (GncOption *option, GtkGrid *page_box, GtkLabel *name_label, char *documentation, /* Return values */ GtkWidget **enclosing, gboolean *packed) @@ -189,7 +190,7 @@ customer_set_widget (GNCOption *option, GtkGrid *page_box, /* Function to set the UI Value for a particular option */ static gboolean -customer_set_value (GNCOption *option, gboolean use_default, +customer_set_value (GncOption *option, gboolean use_default, GtkWidget *widget, SCM value) { GncOwner owner; @@ -209,7 +210,7 @@ customer_set_value (GNCOption *option, gboolean use_default, /* Function to get the UI Value for a particular option */ static SCM -customer_get_value (GNCOption *option, GtkWidget *widget) +customer_get_value (GncOption *option, GtkWidget *widget) { GncOwner owner; @@ -225,7 +226,7 @@ customer_get_value (GNCOption *option, GtkWidget *widget) /* Function to set the UI widget based upon the option */ static GtkWidget * -vendor_set_widget (GNCOption *option, GtkGrid *page_box, +vendor_set_widget (GncOption *option, GtkGrid *page_box, GtkLabel *name_label, char *documentation, /* Return values */ GtkWidget **enclosing, gboolean *packed) @@ -245,7 +246,7 @@ vendor_set_widget (GNCOption *option, GtkGrid *page_box, /* Function to set the UI Value for a particular option */ static gboolean -vendor_set_value (GNCOption *option, gboolean use_default, +vendor_set_value (GncOption *option, gboolean use_default, GtkWidget *widget, SCM value) { GncOwner owner; @@ -265,7 +266,7 @@ vendor_set_value (GNCOption *option, gboolean use_default, /* Function to get the UI Value for a particular option */ static SCM -vendor_get_value (GNCOption *option, GtkWidget *widget) +vendor_get_value (GncOption *option, GtkWidget *widget) { GncOwner owner; @@ -280,7 +281,7 @@ vendor_get_value (GNCOption *option, GtkWidget *widget) /* Function to set the UI widget based upon the option */ static GtkWidget * -employee_set_widget (GNCOption *option, GtkGrid *page_box, +employee_set_widget (GncOption *option, GtkGrid *page_box, GtkLabel *name_label, char *documentation, /* Return values */ GtkWidget **enclosing, gboolean *packed) @@ -300,7 +301,7 @@ employee_set_widget (GNCOption *option, GtkGrid *page_box, /* Function to set the UI Value for a particular option */ static gboolean -employee_set_value (GNCOption *option, gboolean use_default, +employee_set_value (GncOption *option, gboolean use_default, GtkWidget *widget, SCM value) { GncOwner owner; @@ -320,7 +321,7 @@ employee_set_value (GNCOption *option, gboolean use_default, /* Function to get the UI Value for a particular option */ static SCM -employee_get_value (GNCOption *option, GtkWidget *widget) +employee_get_value (GncOption *option, GtkWidget *widget) { GncOwner owner; @@ -335,7 +336,7 @@ employee_get_value (GNCOption *option, GtkWidget *widget) static GtkWidget * -create_invoice_widget (GNCOption *option, GtkWidget *hbox) +create_invoice_widget (GncOption *option, GtkWidget *hbox) { GtkWidget *widget; @@ -352,7 +353,7 @@ create_invoice_widget (GNCOption *option, GtkWidget *hbox) /* Function to set the UI widget based upon the option */ static GtkWidget * -invoice_set_widget (GNCOption *option, GtkGrid *page_box, +invoice_set_widget (GncOption *option, GtkGrid *page_box, GtkLabel *name_label, char *documentation, /* Return values */ GtkWidget **enclosing, gboolean *packed) @@ -372,7 +373,7 @@ invoice_set_widget (GNCOption *option, GtkGrid *page_box, /* Function to set the UI Value for a particular option */ static gboolean -invoice_set_value (GNCOption *option, gboolean use_default, +invoice_set_value (GncOption *option, gboolean use_default, GtkWidget *widget, SCM value) { GncInvoice *invoice; @@ -390,7 +391,7 @@ invoice_set_value (GNCOption *option, gboolean use_default, /* Function to get the UI Value for a particular option */ static SCM -invoice_get_value (GNCOption *option, GtkWidget *widget) +invoice_get_value (GncOption *option, GtkWidget *widget) { GncInvoice *invoice; @@ -404,7 +405,7 @@ invoice_get_value (GNCOption *option, GtkWidget *widget) static GtkWidget * -create_taxtable_widget (GNCOption *option, GtkWidget *hbox) +create_taxtable_widget (GncOption *option, GtkWidget *hbox) { GtkWidget *widget; GtkBuilder *builder; @@ -428,7 +429,7 @@ create_taxtable_widget (GNCOption *option, GtkWidget *hbox) /* Function to set the UI widget based upon the option */ static GtkWidget * -taxtable_set_widget (GNCOption *option, GtkGrid *page_box, +taxtable_set_widget (GncOption *option, GtkGrid *page_box, GtkLabel *name_label, char *documentation, /* Return values */ GtkWidget **enclosing, gboolean *packed) @@ -448,7 +449,7 @@ taxtable_set_widget (GNCOption *option, GtkGrid *page_box, /* Function to set the UI Value for a particular option */ static gboolean -taxtable_set_value (GNCOption *option, gboolean use_default, +taxtable_set_value (GncOption *option, gboolean use_default, GtkWidget *widget, SCM value) { GncTaxTable *taxtable; @@ -466,7 +467,7 @@ taxtable_set_value (GNCOption *option, gboolean use_default, /* Function to get the UI Value for a particular option */ static SCM -taxtable_get_value (GNCOption *option, GtkWidget *widget) +taxtable_get_value (GncOption *option, GtkWidget *widget) { GncTaxTable *taxtable; @@ -474,28 +475,11 @@ taxtable_get_value (GNCOption *option, GtkWidget *widget) return SWIG_NewPointerObj(taxtable, SWIG_TypeQuery("_p__gncTaxTable"), 0); } - +#endif void gnc_business_options_gnome_initialize (void) { - int i; - static GNCOptionDef_t options[] = - { - { "owner", owner_set_widget, owner_set_value, owner_get_value }, - { - "customer", customer_set_widget, customer_set_value, - customer_get_value - }, - { "vendor", vendor_set_widget, vendor_set_value, vendor_get_value }, - { "employee", employee_set_widget, employee_set_value, employee_get_value }, - { "invoice", invoice_set_widget, invoice_set_value, invoice_get_value }, - { "taxtable", taxtable_set_widget, taxtable_set_value, taxtable_get_value }, - { NULL } - }; - - SWIG_GetModule(NULL); /* Work-around for SWIG bug. */ - for (i = 0; options[i].option_name; i++) - gnc_options_ui_register_option (&(options[i])); +/* Create the above option types. */ } diff --git a/gnucash/gnucash.cpp b/gnucash/gnucash.cpp index d7a76466a0..0592960ee8 100644 --- a/gnucash/gnucash.cpp +++ b/gnucash/gnucash.cpp @@ -33,12 +33,15 @@ #include "gnucash-core-app.hpp" extern "C" { +#include #include #include +#include // For define GNC_MOD_GUI #include #include #include #include +#include #include #include #include diff --git a/libgnucash/app-utils/gnc-option.cpp b/libgnucash/app-utils/gnc-option.cpp index 42c069742d..50b087a4a4 100644 --- a/libgnucash/app-utils/gnc-option.cpp +++ b/libgnucash/app-utils/gnc-option.cpp @@ -505,7 +505,6 @@ GncOption::from_scheme(std::istream& iss) * the template implementation in the public header. */ -using GncOptionAccountList = std::vector; template class GncOptionValidatedValue; @@ -544,17 +543,20 @@ template const char* GncOption::get_default_value() const; template std::string GncOption::get_default_value() const; template const QofInstance* GncOption::get_default_value() const; template RelativeDatePeriod GncOption::get_default_value() const; +template GncOptionAccountList GncOption::get_default_value() const; template GncMultichoiceOptionIndexVec GncOption::get_default_value() const; template void GncOption::set_value(bool); template void GncOption::set_value(int); template void GncOption::set_value(int64_t); template void GncOption::set_value(double); +template void GncOption::set_value(char*); template void GncOption::set_value(const char*); template void GncOption::set_value(std::string); template void GncOption::set_value(const QofInstance*); template void GncOption::set_value(RelativeDatePeriod); template void GncOption::set_value(size_t); +template void GncOption::set_value(GncOptionAccountList); template void GncOption::set_value(GncMultichoiceOptionIndexVec); template void GncOption::get_limits(double&, double&, double&) const noexcept; diff --git a/libgnucash/engine/qofbook.h b/libgnucash/engine/qofbook.h index b4401382b1..7c0d330355 100644 --- a/libgnucash/engine/qofbook.h +++ b/libgnucash/engine/qofbook.h @@ -74,7 +74,12 @@ typedef struct KvpValueImpl KvpValue; typedef void (*QofBookDirtyCB) (QofBook *, gboolean dirty, gpointer user_data); +#ifdef __cplusplus +class GncOptionDB; +using GNCOptionDB = GncOptionDB; +#else typedef struct gnc_option_db GNCOptionDB; +#endif typedef void (*GNCOptionSave) (GNCOptionDB*, QofBook*, gboolean); typedef void (*GNCOptionLoad) (GNCOptionDB*, QofBook*); diff --git a/po/POTFILES.in b/po/POTFILES.in index 39773851c6..84ae44d040 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -136,6 +136,7 @@ gnucash/gnome-utils/dialog-dup-trans.c gnucash/gnome-utils/dialog-file-access.c gnucash/gnome-utils/dialog-object-references.c gnucash/gnome-utils/dialog-options.c +gnucash/gnome-utils/dialog-options.cpp gnucash/gnome-utils/dialog-preferences.c gnucash/gnome-utils/dialog-query-view.c gnucash/gnome-utils/dialog-reset-warnings.c @@ -528,6 +529,7 @@ libgnucash/app-utils/gnc-gsettings.c libgnucash/app-utils/gnc-helpers.c libgnucash/app-utils/gnc-help-utils.c libgnucash/app-utils/gnc-option.cpp +libgnucash/app-utils/gnc-option-date.cpp libgnucash/app-utils/gnc-optiondb.cpp libgnucash/app-utils/gnc-option-impl.cpp libgnucash/app-utils/gnc-prefs-utils.c