gnucash master: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Sat Feb 25 19:28:29 EST 2023


Updated	 via  https://github.com/Gnucash/gnucash/commit/6ab7b16d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/408b5ec2 (commit)
	from  https://github.com/Gnucash/gnucash/commit/0780cfbf (commit)



commit 6ab7b16d62d91a3f6cc50036599fb6b390dfbbb9
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Feb 25 16:21:45 2023 -0800

    [c++options] Restore the ability to set plot sizes in pixels.
    
    Enable and fix the previously untested GtkOptionGtkUIItem::PLOTSIZE.
    
    This has the potentially unfortunate side effect that integer range
    options are assumed to be plot sizes. That's correct for now, but
    if some report comes along that needs an integer range option for
    something else it will have to be differentiated.

diff --git a/gnucash/gnome-utils/gnc-option-gtk-ui.cpp b/gnucash/gnome-utils/gnc-option-gtk-ui.cpp
index a9fb30b30..687ff4566 100644
--- a/gnucash/gnome-utils/gnc-option-gtk-ui.cpp
+++ b/gnucash/gnome-utils/gnc-option-gtk-ui.cpp
@@ -24,6 +24,7 @@
 #include <gnc-option-impl.hpp>
 #include "gnc-option-gtk-ui.hpp"
 #include <config.h>  // for scanf format string
+#include <memory>
 #include <qof.h>
 #include <gnc-engine.h> // for GNC_MOD_GUI
 #include <gnc-commodity.h> // for GNC_COMMODITY
@@ -33,6 +34,7 @@
 #include "gnc-date-edit.h" // for gnc_date_edit
 #include "gnc-date-format.h" //for GNC_DATE_FORMAT
 #include "gnc-general-select.h" // for GNC_GENERAL_SELECT
+#include "gnc-option-uitype.hpp"
 #include "gnc-tree-view-account.h" // for GNC_TREE_VIEW_ACCOUNT
 #include "gnc-tree-model-budget.h" // for gnc_tree_model_budget
 #include "misc-gnome-utils.h" // for xxxgtk_textview_set_text
@@ -1699,115 +1701,148 @@ create_option_widget<GncOptionUIType::DATE_FORMAT> (GncOption& option,
     grid_attach_widget(page_box, enclosing, row);
 }
 
-static void
-gnc_rd_option_px_set_cb(GtkWidget *widget, GncOption* option)
+class PlotSize;
+static void plot_size_set_pixels(GtkWidget*, PlotSize*);
+static void plot_size_set_percent(GtkWidget*, PlotSize*);
+
+class PlotSize
 {
-    option->set_alternate(true);
-    gnc_option_changed_option_cb(widget, option);
+    GtkWidget *m_widget;
+    GtkWidget *m_pixel_button;
+    GtkWidget *m_percent_button;
+    GtkWidget *m_range_spinner;
+    GtkAdjustment *m_adj_pct;
+    GtkAdjustment *m_adj_px;
+    unsigned long m_percent_handler;
+    unsigned long m_pixel_handler;
+public:
+    PlotSize(GncOption& option);
+    ~PlotSize();
+    void set_entry_from_option(GncOption& option);
+    void set_option_from_entry(GncOption& option);
+    GtkWidget* get_widget() { return m_widget; }
+    GtkWidget* get_spinner() { return m_range_spinner; }
+    void set_pixels() { gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(m_range_spinner), m_adj_px); }
+    void set_percent() { gtk_spin_button_set_adjustment(GTK_SPIN_BUTTON(m_range_spinner), m_adj_pct); }
+};
+
+PlotSize::PlotSize(GncOption& option) :
+    m_widget{gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4)}, m_pixel_button{gtk_radio_button_new_with_label(nullptr, _("Pixels"))},
+    m_percent_button{gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(m_pixel_button), _("Percent"))},
+    m_range_spinner{GTK_WIDGET(create_range_spinner(option))},
+    m_adj_pct{GTK_ADJUSTMENT(g_object_ref(gtk_adjustment_new(100.0, 10.0, 100.0, 1.0, 5.0, 0.0)))},
+    m_adj_px{GTK_ADJUSTMENT(g_object_ref(gtk_adjustment_new(1000.0, 110.0, 10000.0, 10.0, 250.0, 0.0)))}
+{
+    gtk_box_set_homogeneous(GTK_BOX(m_widget), FALSE);
+    g_object_set (G_OBJECT(m_widget), "margin", 3, NULL);
+    set_tool_tip(option, m_widget);
+    gtk_box_pack_start(GTK_BOX(m_widget), GTK_WIDGET(m_pixel_button), FALSE, FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(m_widget), GTK_WIDGET(m_percent_button), FALSE, FALSE, 0);
+    gtk_box_pack_start(GTK_BOX(m_widget), GTK_WIDGET(m_range_spinner),
+                       FALSE, FALSE, 0);
+
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(m_pixel_button), FALSE);
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(m_percent_button), TRUE);
+
+    m_pixel_handler = g_signal_connect(m_pixel_button, "toggled", G_CALLBACK(plot_size_set_pixels), this);
+    m_percent_handler = g_signal_connect(m_percent_button, "toggled", G_CALLBACK(plot_size_set_percent), this);
 }
 
-static void
-gnc_rd_option_p_set_cb(GtkWidget *widget, GncOption* option)
+PlotSize::~PlotSize()
 {
-    option->set_alternate(false);
-    gnc_option_changed_option_cb(widget, option);
+    g_signal_handler_disconnect(m_pixel_button, m_pixel_handler);
+    g_signal_handler_disconnect(m_percent_button, m_percent_handler);
+    g_object_unref(m_adj_pct);
+    g_object_unref(m_adj_px);
 }
 
+void
+PlotSize::set_option_from_entry(GncOption& option)
+{
+    auto value{gtk_spin_button_get_value(GTK_SPIN_BUTTON(m_range_spinner))};
+    if (option.is_alternate())
+        option.set_value<int>(static_cast<int>(value));
+    else
+        option.set_value<double>(value);
+}
+
+void
+PlotSize::set_entry_from_option(GncOption& option)
+{
+    double value;
+    if (option.is_alternate())
+    {
+        auto int_value{option.get_value<int>()};
+        value = static_cast<double>(int_value);
+    }
+    else
+    {
+        value = option.get_value<double>();
+    }
+
+    if (value > 100.0)
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pixel_button), TRUE);
+    else
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_percent_button), TRUE);
+
+    gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_range_spinner), value);
+}
+
+void
+plot_size_set_pixels(GtkWidget *widget, PlotSize *ps)
+{
+    ps->set_pixels();
+}
+
+void
+plot_size_set_percent(GtkWidget *widget, PlotSize *ps)
+{
+    ps->set_percent();
+}
+
+using PlotSizePtr = std::unique_ptr<PlotSize>;
+
 class GncGtkPlotSizeUIItem : public GncOptionGtkUIItem
 {
 public:
-    GncGtkPlotSizeUIItem(GtkWidget* widget) :
-        GncOptionGtkUIItem{widget, GncOptionUIType::PLOT_SIZE} {}
+    GncGtkPlotSizeUIItem(PlotSizePtr&& plot_size) :
+        GncOptionGtkUIItem{plot_size->get_widget(), GncOptionUIType::PLOT_SIZE},
+        m_plot_size{std::move(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<double>());
-        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
+        m_plot_size->set_entry_from_option(option);
     }
     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);
-            option.set_value(gtk_spin_button_get_value(GTK_SPIN_BUTTON(get_widget())));
-        }
-        else
-        {
-            option.set_alternate(true);
-            option.set_value(gtk_spin_button_get_value(GTK_SPIN_BUTTON(get_widget())));
-        }
+        m_plot_size->set_option_from_entry(option);
     }
+    PlotSize* get_plot_size() { return m_plot_size.get(); }
+private:
+PlotSizePtr m_plot_size;
 };
 
-
 template<> void
 create_option_widget<GncOptionUIType::PLOT_SIZE> (GncOption& option,
                                                   GtkGrid *page_box, int row)
 {
-    GtkWidget *value_percent;
-    GtkWidget *px_butt, *p_butt;
-    GtkWidget *hbox;
-    GtkAdjustment *adj_percent;
 
     auto enclosing = gtk_frame_new(NULL);
     gtk_widget_set_halign (GTK_WIDGET(enclosing), GTK_ALIGN_START);
     set_name_label(option, page_box, row, false);
-    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);
-    set_tool_tip(option, hbox);
-
-    auto value_px = create_range_spinner(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);
-
-
-    px_butt = gtk_radio_button_new_with_label(NULL, _("Pixels"));
-    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(px_butt), TRUE);
-
 
-    p_butt = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(px_butt), _("Percent"));
-
-    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<GncGtkPlotSizeUIItem>(static_cast<GtkWidget*>(hbox)));
+    option.set_ui_item(std::make_unique<GncGtkPlotSizeUIItem>(std::make_unique<PlotSize>(option)));
     option.set_ui_item_from_option();
 
-    g_signal_connect(G_OBJECT(value_px), "changed",
-                     G_CALLBACK(gnc_option_changed_widget_cb), &option);
-    g_signal_connect(G_OBJECT(value_percent), "changed",
-                     G_CALLBACK(gnc_option_changed_widget_cb), &option);
-    g_signal_connect(G_OBJECT(px_butt), "toggled",
-                     G_CALLBACK(gnc_rd_option_px_set_cb), &option);
-    g_signal_connect(G_OBJECT(p_butt), "toggled",
-                     G_CALLBACK(gnc_rd_option_p_set_cb), &option);
+    auto widget{option_get_gtk_widget(&option)};
+    gtk_container_add(GTK_CONTAINER(enclosing), widget);
 
-    gtk_container_add(GTK_CONTAINER(enclosing), hbox);
     gtk_widget_show_all(enclosing);
     grid_attach_widget(page_box, enclosing, row);
+
+    auto ui_item{dynamic_cast<GncGtkPlotSizeUIItem*>(option.get_ui_item())};
+    if (ui_item)
+        g_signal_connect(G_OBJECT(ui_item->get_plot_size()->get_spinner()), "changed",
+                         G_CALLBACK(gnc_option_changed_widget_cb), &option);
 }
 
 static GtkWidget *
diff --git a/libgnucash/engine/gnc-option-impl.hpp b/libgnucash/engine/gnc-option-impl.hpp
index c145a430c..2fa0fa7c5 100644
--- a/libgnucash/engine/gnc-option-impl.hpp
+++ b/libgnucash/engine/gnc-option-impl.hpp
@@ -332,7 +332,13 @@ public:
                                    const char* key, const char* doc_string,
                                    ValueType value, ValueType min,
                                    ValueType max, ValueType step) :
-        OptionClassifier{section, name, key, doc_string},
+        GncOptionRangeValue<ValueType>{section, name, key, doc_string, value, min,
+                                       max, step, GncOptionUIType::NUMBER_RANGE} {}
+    GncOptionRangeValue<ValueType>(const char* section, const char* name,
+                                   const char* key, const char* doc_string,
+                                   ValueType value, ValueType min,
+                                   ValueType max, ValueType step, GncOptionUIType ui) :
+        OptionClassifier{section, name, key, doc_string}, m_ui_type{ui},
         m_value{value >= min && value <= max ? value : min},
         m_default_value{value >= min && value <= max ? value : min},
         m_min{min}, m_max{max}, m_step{step} {
diff --git a/libgnucash/engine/gnc-optiondb.cpp b/libgnucash/engine/gnc-optiondb.cpp
index 26d7f9f2c..92e5a245f 100644
--- a/libgnucash/engine/gnc-optiondb.cpp
+++ b/libgnucash/engine/gnc-optiondb.cpp
@@ -21,10 +21,12 @@
  *                                                                  *
 \********************************************************************/
 
+#include <cstdint>
 #include <functional>
 #include <string>
 #include <limits>
 #include <sstream>
+#include "gnc-option-uitype.hpp"
 #include "kvp-value.hpp"
 #include "qofbookslots.h"
 #include "guid.hpp"
@@ -768,9 +770,9 @@ gnc_register_number_plot_size_option(GncOptionDB* db,
                                      const char* key, const char* doc_string,
                                      int value)
 {
-    // Pixel values don't make much sense so always use percent.
+//65K is 10x reasonable, but it's a convenient constant.
     GncOption option{GncOptionRangeValue<int>{section, name, key, doc_string,
-                value, 10, 100, 1}};
+            value, 10, UINT16_MAX, 1, GncOptionUIType::PLOT_SIZE}};
     db->register_option(section, std::move(option));
 }
 

commit 408b5ec2165fb43bef18b80ff44ba4d2f1b67f5f
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Feb 23 13:32:46 2023 -0800

    [c++ options] Restore forward & backward compatibility, fix crash.
    
    Store Number Range option values as a pair '(percentage . value) or '(pixels .
    value) depending on the size of value as 4.x and earlier expect. When reading a
    stored number range option handle the possibility that value is fixed
    point (e.g. 50.0) as 4.x and earlier emit, avoiding a crash.

diff --git a/bindings/guile/gnc-optiondb.i b/bindings/guile/gnc-optiondb.i
index 9e8e8f471..77edfaff9 100644
--- a/bindings/guile/gnc-optiondb.i
+++ b/bindings/guile/gnc-optiondb.i
@@ -1080,6 +1080,18 @@ wrap_unique_ptr(GncOptionDBPtr, GncOptionDB);
         return scm_cons(desig, scm_from_int(val));
     }
 
+    static SCM
+    get_scm_value(const GncOptionRangeValue<double>& option)
+    {
+        return scm_from_double(option.get_value());
+    }
+
+    static SCM
+    get_scm_default_value(const GncOptionRangeValue<double>& option)
+    {
+        return scm_from_double(option.get_default_value());
+    }
+
     static SCM
     get_scm_value(const GncOptionDateValue& option)
     {
@@ -1285,14 +1297,14 @@ inline SCM return_scm_value(ValueType value)
                               is_same_decayed_v<decltype(option),
                               GncOptionRangeValue<double>>)
                 {
-                    auto serial{option.serialize()};
-                    if (serial.empty())
+                    auto serial{get_scm_value(option)};
+                    if (serial == SCM_BOOL_F)
                     {
                         return no_value;
                     }
                     else
                     {
-                        auto scm_str{scm_list_1(scm_from_utf8_string(serial.c_str()))};
+                        auto scm_str{scm_list_1(serial)};
                         return scm_simple_format(SCM_BOOL_F, ticked_format_str, scm_str);
                     }
                 }
@@ -1368,10 +1380,11 @@ inline SCM return_scm_value(ValueType value)
                     if constexpr (is_same_decayed_v<decltype(option),
                                   GncOptionRangeValue<int>>)
                     {
-                        if (scm_is_pair(new_value))
-                            option.set_value(scm_to_int(scm_cdr(new_value)));
-                        else
-                            option.set_value(scm_to_int(new_value));
+                        auto value_num{scm_is_pair(new_value) ? scm_cdr(new_value) : new_value};
+                        int value_int = scm_is_exact_integer(value_num) ?
+                            scm_to_int(value_num) :
+                            static_cast<int>(scm_to_double(value_num));
+                        option.set_value(value_int);
                         return;
                     }
                     if constexpr (is_same_decayed_v<decltype(option),
diff --git a/bindings/guile/test/test-gnc-option-scheme-output.scm b/bindings/guile/test/test-gnc-option-scheme-output.scm
index dfbdb2d80..e1a960299 100644
--- a/bindings/guile/test/test-gnc-option-scheme-output.scm
+++ b/bindings/guile/test/test-gnc-option-scheme-output.scm
@@ -127,6 +127,21 @@
 "
           (gncBudgetGetGUID value)))
 
+(define (test-number-range-output-template value)
+  (format #f "
+; Section: foo
+
+(let ((option (gnc:lookup-option options
+                                 \"foo\"
+                                 \"bar\")))
+  ((lambda (o) (if o (gnc:option-set-value o ~a))) option))
+
+" (if (exact-integer? value)
+      (if (< value 100)
+          (format #f "'(percent . ~d)" value)
+          (format #f "'(pixels . ~f)" value))
+      (format #f "'~f" value)
+      )))
 
 (define (test-option-scheme-output name make-option-func get-value-func test-template default value)
   (let ((odb (gnc:new-options))
@@ -438,10 +453,10 @@ veritatis et quasi architecto beatae vitae dicta sunt, explicabo.")
       (test-equal "number-range unchanged" test-unchanged-section-output-template
                   (gnc:generate-restore-forms odb "options"))
       (let ((option (gnc:lookup-option odb "foo" "bar"))
-            (test-template test-literal-output-template))
+            (test-template test-number-range-output-template))
         (gnc:option-set-value option 42.0)
         (test-equal "number-range form"
-                    (test-template (GncOption-serialize option))
+                    (test-template (string->number (GncOption-serialize option)))
                     (gnc:generate-restore-forms odb "options"))
         ))
     (test-end  "test-gnc-number-range-option-to-scheme"))
@@ -460,10 +475,10 @@ veritatis et quasi architecto beatae vitae dicta sunt, explicabo.")
       (test-equal "number-plot unchanged" test-unchanged-section-output-template
                   (gnc:generate-restore-forms odb "options"))
       (let ((option (gnc:lookup-option odb "foo" "bar"))
-            (test-template test-literal-output-template))
-        (gnc:option-set-value option 42)
+            (test-template test-number-range-output-template))
+        (gnc:option-set-value option 48)
         (test-equal "number-plot form"
-                    (test-template (GncOption-serialize option))
+                    (test-template (string->number (GncOption-serialize option)))
                     (gnc:generate-restore-forms odb "options"))
         ))
     (test-end  "test-gnc-number-plot-size-option-to-scheme"))



Summary of changes:
 bindings/guile/gnc-optiondb.i                      |  27 ++-
 .../guile/test/test-gnc-option-scheme-output.scm   |  25 ++-
 gnucash/gnome-utils/gnc-option-gtk-ui.cpp          | 191 ++++++++++++---------
 libgnucash/engine/gnc-option-impl.hpp              |   8 +-
 libgnucash/engine/gnc-optiondb.cpp                 |   6 +-
 5 files changed, 164 insertions(+), 93 deletions(-)



More information about the gnucash-changes mailing list