gnucash master: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Thu Feb 2 16:59:15 EST 2023


Updated	 via  https://github.com/Gnucash/gnucash/commit/12db8eaa (commit)
	 via  https://github.com/Gnucash/gnucash/commit/cbf39074 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/07f3f536 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/b41e491e (commit)
	 via  https://github.com/Gnucash/gnucash/commit/b49b5c86 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/019214f1 (commit)
	from  https://github.com/Gnucash/gnucash/commit/63f8e731 (commit)



commit 12db8eaaf268f2b2e49f908e92f706f22a4be9db
Merge: 63f8e7314 cbf39074c
Author: John Ralls <jralls at ceridwen.us>
Date:   Thu Feb 2 13:27:51 2023 -0800

    Merge Bob Fewell's 'bug753307master' into master.

diff --cc po/POTFILES.in
index 2e80b84b3,2e80b84b3..d7bd709b4
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@@ -185,6 -185,6 +185,7 @@@ gnucash/gnome-utils/gnc-plugin-menu-add
  gnucash/gnome-utils/gnc-plugin-page.c
  gnucash/gnome-utils/gnc-query-view.c
  gnucash/gnome-utils/gnc-recurrence.c
++gnucash/gnome-utils/gnc-report-combo.c
  gnucash/gnome-utils/gnc-splash.c
  gnucash/gnome-utils/gnc-sx-instance-dense-cal-adapter.c
  gnucash/gnome-utils/gnc-sx-list-tree-model-adapter.c

commit cbf39074c2b3b552dd53f526d2a60036527fb7f7
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Wed Feb 1 16:27:56 2023 +0000

    Remove the old preference setting for default invoice report
    
    Remove preference setting and set the schema to be depreciated in
    version 5000 and removed in version 6000.

diff --git a/gnucash/gschemas/pref_transformations.xml b/gnucash/gschemas/pref_transformations.xml
index 55af940bf..4220b0073 100644
--- a/gnucash/gschemas/pref_transformations.xml
+++ b/gnucash/gschemas/pref_transformations.xml
@@ -2152,4 +2152,16 @@
 <obsolete old-path="org.gnucash.window.pages.account-tree.summary"
           old-key="end-period"/>
 
+<deprecate old-path="org.gnucash.GnuCash.dialogs.business.invoice"
+           old-key="invoice-printreport" />
+
 </release>
+
+<!--
+<release version="6000">
+
+<obsolete old-path="org.gnucash.GnuCash.dialogs.business.invoice"
+          old-key="invoice-printreport"/>
+
+</release>
+-->
diff --git a/gnucash/gtkbuilder/business-prefs.glade b/gnucash/gtkbuilder/business-prefs.glade
index 4e2664af5..770c973cb 100644
--- a/gnucash/gtkbuilder/business-prefs.glade
+++ b/gnucash/gtkbuilder/business-prefs.glade
@@ -16,26 +16,6 @@
     <property name="step-increment">1</property>
     <property name="page-increment">10</property>
   </object>
-  <object class="GtkListStore" id="liststore_printinvoice">
-    <columns>
-      <!-- column-name item -->
-      <column type="gchararray"/>
-    </columns>
-    <data>
-      <row>
-        <col id="0" translatable="yes">Printable Invoice</col>
-      </row>
-      <row>
-        <col id="0" translatable="yes">Tax Invoice</col>
-      </row>
-      <row>
-        <col id="0" translatable="yes">Easy Invoice</col>
-      </row>
-      <row>
-        <col id="0" translatable="yes">Fancy Invoice</col>
-      </row>
-    </data>
-  </object>
   <object class="GtkWindow" id="preferences_window">
     <property name="visible">True</property>
     <property name="can-focus">False</property>
@@ -158,18 +138,6 @@
             <property name="top-attach">6</property>
           </packing>
         </child>
-        <child>
-          <object class="GtkLabel">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="halign">start</property>
-            <property name="label" translatable="yes">Report for printing</property>
-          </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">7</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkCheckButton" id="pref/dialogs.business.invoice/tax-included">
             <property name="label" translatable="yes">_Tax included</property>
@@ -185,7 +153,7 @@
           </object>
           <packing>
             <property name="left-attach">0</property>
-            <property name="top-attach">8</property>
+            <property name="top-attach">7</property>
           </packing>
         </child>
         <child>
@@ -203,7 +171,7 @@
           </object>
           <packing>
             <property name="left-attach">0</property>
-            <property name="top-attach">9</property>
+            <property name="top-attach">8</property>
           </packing>
         </child>
         <child>
@@ -311,27 +279,6 @@
             <property name="top-attach">12</property>
           </packing>
         </child>
-        <child>
-          <object class="GtkComboBox" id="pref/dialogs.business.invoice/invoice-printreport">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="has-tooltip">True</property>
-            <property name="tooltip-markup">The invoice report to be used for printing.</property>
-            <property name="tooltip-text" translatable="yes">The invoice report to be used for printing.</property>
-            <property name="model">liststore_printinvoice</property>
-            <child>
-              <object class="GtkCellRendererText" id="cellrenderertext2"/>
-              <attributes>
-                <attribute name="text">0</attribute>
-              </attributes>
-            </child>
-          </object>
-          <packing>
-            <property name="left-attach">1</property>
-            <property name="top-attach">7</property>
-            <property name="width">2</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkSpinButton" id="pref/dialogs.business.invoice/days-in-advance">
             <property name="visible">True</property>
@@ -431,6 +378,15 @@
         <child>
           <placeholder/>
         </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
       </object>
     </child>
   </object>

commit 07f3f536cc09209308c1b6c011a2ba684e24f1ae
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Wed Feb 1 16:26:38 2023 +0000

    Allow to select saved report on print invoice button
    
    This change was inspired by mildred's #PR1247 and uses the new
    GncReportCombo to allow selection of 'Saved Invoice reports' when the
    invoice print button is used or when printing multiple invoices.
    
    This change presents a dialog with the default for the report combo set
    to the properties setting. This dialog has a timeout which is
    adjustable under properties and will stop if a key is pressed or combo
    popped so that a different report template can be selected and used.
    There is also an 'OK' button that stops the time out and prints and a
    'Cancel' button which cancels the print.

diff --git a/gnucash/gnome/dialog-invoice.c b/gnucash/gnome/dialog-invoice.c
index aeca0844e..786da9655 100644
--- a/gnucash/gnome/dialog-invoice.c
+++ b/gnucash/gnome/dialog-invoice.c
@@ -79,6 +79,7 @@
 #include "dialog-doclink-utils.h"
 #include "dialog-transfer.h"
 #include "gnc-uri-utils.h"
+#include "gnc-report-combo.h"
 
 #define DIALOG_NEW_INVOICE_CM_CLASS "dialog-new-invoice"
 #define DIALOG_VIEW_INVOICE_CM_CLASS "dialog-view-invoice"
@@ -778,15 +779,159 @@ gnc_invoice_window_blankCB (GtkWidget *widget, gpointer data)
     }
 }
 
+typedef struct dialog_args
+{
+    GtkProgressBar  *pb;
+    GtkWidget       *dialog;
+    gdouble          timeout;
+} dialog_args;
+
+static gboolean
+update_progress_bar (gpointer user_data)
+{
+    dialog_args     *args = user_data;
+    GtkProgressBar  *pb = args->pb;
+    gdouble          frac = gtk_progress_bar_get_fraction (pb);
+    gdouble          step = 0.1 / (args->timeout);
+
+    frac -= step;
+
+    if (frac < step)
+    {
+        gtk_dialog_response (GTK_DIALOG(args->dialog), GTK_RESPONSE_OK);
+        return FALSE;
+    }
+    gtk_progress_bar_set_fraction (pb, frac);
+    return TRUE;
+}
+
+static void
+combo_popped_cb (GObject    *gobject,
+                 GParamSpec *pspec,
+                 gpointer    user_data)
+{
+    gboolean popup_shown;
+
+    g_object_get (G_OBJECT(gobject), "popup-shown", &popup_shown, NULL);
+
+    if (popup_shown)
+        g_source_remove_by_user_data (user_data);
+}
+
+static gboolean
+dialog_key_press_event_cb (GtkWidget *widget, GdkEventKey *event,
+                           gpointer user_data)
+{
+     g_source_remove_by_user_data (user_data);
+     return FALSE;
+}
+
+static void
+combo_changed_cb (GtkComboBox *widget, gpointer user_data)
+{
+    g_source_remove_by_user_data (user_data);
+}
+
+/* This function will return the selected invoice report guid if
+ * the countdown times out or a selection is made and OK pressed.
+ * 
+ * If cancel is pressed then it return a NULL 
+ */
+static char*
+use_default_report_template_or_change (GtkWindow *parent)
+{
+    QofBook     *book = gnc_get_current_book ();
+    GtkWidget   *combo;
+    GtkBuilder  *builder;
+    GtkWidget   *dialog;
+    GtkWidget   *ok_button;
+    GtkWidget   *report_combo_hbox;
+    GtkWidget   *progress_bar;
+    gchar       *ret_guid = NULL;
+    gchar       *rep_guid = NULL;
+    gchar       *rep_name = NULL;
+    gint         result;
+    gdouble      timeout;
+    dialog_args *args;
+
+    timeout = qof_book_get_default_invoice_report_timeout (book);
+
+    if (timeout == 0)
+        return gnc_get_default_invoice_print_report ();
+
+    combo = gnc_default_invoice_report_combo ("gnc:custom-report-invoice-template-guids");
+
+    builder = gtk_builder_new ();
+    gnc_builder_add_from_file (builder, "dialog-invoice.glade", "invoice_print_dialog");
+
+    dialog = GTK_WIDGET(gtk_builder_get_object (builder, "invoice_print_dialog"));
+
+    gtk_window_set_transient_for (GTK_WINDOW(dialog), parent);
+
+    gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_OK);
+
+    ok_button = GTK_WIDGET(gtk_builder_get_object (builder, "ok_button"));
+    report_combo_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "report_combo_hbox"));
+    progress_bar = GTK_WIDGET(gtk_builder_get_object (builder, "progress_bar"));
+
+    gtk_box_pack_start (GTK_BOX(report_combo_hbox), GTK_WIDGET(combo), TRUE, TRUE, 0);
+
+    gtk_widget_grab_focus (ok_button);
+
+    rep_name = qof_book_get_default_invoice_report_name (book);
+    rep_guid = gnc_get_default_invoice_print_report ();
+
+    gnc_report_combo_set_active (GNC_REPORT_COMBO(combo),
+                                 rep_guid,
+                                 rep_name);
+    g_free (rep_guid);
+    g_free (rep_name);
+
+    gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(progress_bar), 1);
+
+    args = g_malloc (sizeof(dialog_args));
+    args->dialog = dialog;
+    args->pb = GTK_PROGRESS_BAR(progress_bar);
+    args->timeout = timeout;
+
+    gtk_widget_show_all (dialog);
+
+    g_object_unref (G_OBJECT(builder));
+
+    g_signal_connect (G_OBJECT(combo), "changed",
+                      G_CALLBACK(combo_changed_cb), args);
+
+    g_signal_connect (G_OBJECT(dialog), "key_press_event",
+                      G_CALLBACK(dialog_key_press_event_cb), args);
+
+    g_signal_connect (G_OBJECT(combo), "notify::popup-shown",
+                      G_CALLBACK (combo_popped_cb), args);
+
+    g_timeout_add (100, update_progress_bar, args);
+
+    result = gtk_dialog_run (GTK_DIALOG(dialog));
+
+    g_source_remove_by_user_data (args);
+
+    if (result == GTK_RESPONSE_OK)
+        ret_guid = gnc_report_combo_get_active_guid (GNC_REPORT_COMBO(combo));
+
+    gtk_widget_destroy (dialog);
+    g_free (args);
+
+    return ret_guid;
+}
+
 static GncPluginPage *
-gnc_invoice_window_print_invoice(GtkWindow *parent, GncInvoice *invoice)
+gnc_invoice_window_print_invoice (GtkWindow *parent, GncInvoice *invoice,
+                                  const gchar *report_guid)
 {
     SCM func, arg, arg2;
     SCM args = SCM_EOL;
     SCM is_invoice_guid;
     SCM scm_guid;
     int report_id;
-    char *report_guid = gnc_get_default_invoice_print_report ();
+    const gchar *use_report_guid = NULL;
     GncPluginPage *reportPage = NULL;
 
     g_return_val_if_fail (invoice, NULL);
@@ -795,13 +940,15 @@ gnc_invoice_window_print_invoice(GtkWindow *parent, GncInvoice *invoice)
     scm_guid = scm_from_utf8_string (report_guid);
 
     if (scm_is_false (scm_call_1 (is_invoice_guid, scm_guid)))
-        report_guid = g_strdup (gnc_get_builtin_default_invoice_print_report ()); // fallback if the option lookup failed
+        use_report_guid = gnc_get_builtin_default_invoice_print_report (); // fallback if the option lookup failed
+    else
+        use_report_guid = report_guid;
 
     func = scm_c_eval_string ("gnc:invoice-report-create");
     g_return_val_if_fail (scm_is_procedure (func), NULL);
 
     arg = SWIG_NewPointerObj(invoice, SWIG_TypeQuery("_p__gncInvoice"), 0);
-    arg2 = scm_from_utf8_string (report_guid);
+    arg2 = scm_from_utf8_string (use_report_guid);
     args = scm_cons2 (arg, arg2, args);
 
     /* scm_gc_protect_object(func); */
@@ -816,7 +963,6 @@ gnc_invoice_window_print_invoice(GtkWindow *parent, GncInvoice *invoice)
         reportPage = gnc_plugin_page_report_new (report_id);
         gnc_main_window_open_page (GNC_MAIN_WINDOW (parent), reportPage);
     }
-    g_free (report_guid);
     return reportPage;
 }
 
@@ -841,9 +987,17 @@ gnc_invoice_window_printCB (GtkWindow* parent, gpointer data)
                                       iw->reportPage))
         gnc_plugin_page_report_reload (GNC_PLUGIN_PAGE_REPORT (iw->reportPage));
     else
-        iw->reportPage = gnc_invoice_window_print_invoice
-            (parent, iw_get_invoice (iw));
+    {
+        gchar *report_guid = use_default_report_template_or_change (parent);
+
+        if (!report_guid)
+            return;
 
+        iw->reportPage = gnc_invoice_window_print_invoice (parent,
+                                                           iw_get_invoice (iw),
+                                                           report_guid);
+        g_free (report_guid);
+    }
     gnc_main_window_open_page (GNC_MAIN_WINDOW (iw->dialog), iw->reportPage);
 }
 
@@ -3104,8 +3258,9 @@ edit_invoice_cb (GtkWindow *dialog, gpointer inv, gpointer user_data)
 
 struct multi_edit_invoice_data
 {
-    gpointer user_data;
+    gpointer   user_data;
     GtkWindow *parent;
+    gchar     *report_guid;
 };
 
 static void
@@ -3272,27 +3427,37 @@ multi_post_invoice_cb (GtkWindow *dialog, GList *invoice_list, gpointer user_dat
 static void print_one_invoice_cb(GtkWindow *dialog, gpointer data, gpointer user_data)
 {
     GncInvoice *invoice = data;
-    gnc_invoice_window_print_invoice (dialog, invoice);
+    struct multi_edit_invoice_data *meid = user_data;
+    gnc_invoice_window_print_invoice (dialog, invoice, meid->report_guid);
 }
 
 static void
 multi_print_invoice_one (gpointer data, gpointer user_data)
 {
     struct multi_edit_invoice_data *meid = user_data;
-    print_one_invoice_cb (gnc_ui_get_main_window (GTK_WIDGET(meid->parent)), data, meid->user_data);
+    print_one_invoice_cb (gnc_ui_get_main_window (GTK_WIDGET(meid->parent)), data, meid);
 }
 
 static void
 multi_print_invoice_cb (GtkWindow *dialog, GList *invoice_list, gpointer user_data)
 {
+    gchar *report_guid = NULL;
     struct multi_edit_invoice_data meid;
 
     if (!gnc_list_length_cmp (invoice_list, 0))
         return;
 
+    report_guid = use_default_report_template_or_change (dialog);
+
+    if (!report_guid)
+        return;
+
     meid.user_data = user_data;
     meid.parent = dialog;
+    meid.report_guid = report_guid;
+
     g_list_foreach (invoice_list, multi_print_invoice_one, &meid);
+    g_free (report_guid);
 }
 
 static gpointer
diff --git a/gnucash/gtkbuilder/dialog-invoice.glade b/gnucash/gtkbuilder/dialog-invoice.glade
index 9e42c84d6..eba6cd0fb 100644
--- a/gnucash/gtkbuilder/dialog-invoice.glade
+++ b/gnucash/gtkbuilder/dialog-invoice.glade
@@ -628,6 +628,133 @@
       </object>
     </child>
   </object>
+  <object class="GtkDialog" id="invoice_print_dialog">
+    <property name="can-focus">False</property>
+    <property name="title" translatable="yes">Report template</property>
+    <property name="type-hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can-focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can-focus">False</property>
+            <property name="layout-style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel_button">
+                <property name="label" translatable="yes">_Cancel</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="receives-default">False</property>
+                <property name="use-underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ok_button">
+                <property name="label" translatable="yes">_OK</property>
+                <property name="visible">True</property>
+                <property name="can-focus">True</property>
+                <property name="is-focus">True</property>
+                <property name="can-default">True</property>
+                <property name="has-default">True</property>
+                <property name="receives-default">True</property>
+                <property name="use-underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="margin-start">6</property>
+            <property name="margin-end">6</property>
+            <property name="margin-top">6</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel" id="labe">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes">Use template report</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="report_combo_hbox">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="margin-start">6</property>
+                <property name="margin-end">6</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="label" translatable="yes">Choose a different report template before timeout</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkProgressBar" id="progress_bar">
+                <property name="visible">True</property>
+                <property name="can-focus">False</property>
+                <property name="margin-start">24</property>
+                <property name="margin-end">24</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel_button</action-widget>
+      <action-widget response="-5">ok_button</action-widget>
+    </action-widgets>
+  </object>
   <object class="GtkListStore" id="terms_store">
     <columns>
       <!-- column-name label -->
diff --git a/libgnucash/engine/gnc-optiondb.cpp b/libgnucash/engine/gnc-optiondb.cpp
index a3bd62990..cb13ed7b9 100644
--- a/libgnucash/engine/gnc-optiondb.cpp
+++ b/libgnucash/engine/gnc-optiondb.cpp
@@ -1277,9 +1277,13 @@ gnc_option_db_book_options(GncOptionDB* odb)
                                N_("The ID for your company (eg 'Tax-ID: 00-000000)."),
                                empty_string);
     gnc_register_invoice_print_report_option(odb, business_section,
-                                 OPTION_NAME_DEFAULT_INVOICE_REPORT, "e",
+                                 OPTION_NAME_DEFAULT_INVOICE_REPORT, "e1",
                                  N_("The invoice report to be used for printing."),
                                  empty_string);
+    gnc_register_number_range_option<double>(odb, business_section,
+                                     OPTION_NAME_DEFAULT_INVOICE_REPORT_TIMEOUT, "e2",
+                                     N_("Length of time to change the used invoice report. A value of 0 means disabled."),
+                                     0.0, 0.0, 10.0, 1.0);
     gnc_register_taxtable_option(odb, business_section,
                                  N_("Default Customer TaxTable"), "f1",
                                  N_("The default tax table to apply to customers."),
diff --git a/libgnucash/engine/qofbook.cpp b/libgnucash/engine/qofbook.cpp
index d2a9a76ec..f34c1d579 100644
--- a/libgnucash/engine/qofbook.cpp
+++ b/libgnucash/engine/qofbook.cpp
@@ -1098,6 +1098,21 @@ qof_book_get_default_invoice_report_name (const QofBook *book)
     return report_name;
 }
 
+gdouble
+qof_book_get_default_invoice_report_timeout (const QofBook *book)
+{
+    double ret = 0;
+    KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE(book));
+    KvpValue *value = root->get_slot ({KVP_OPTION_PATH,
+                                       OPTION_SECTION_BUSINESS,
+                                       OPTION_NAME_DEFAULT_INVOICE_REPORT_TIMEOUT});
+
+    if (value)
+        ret = {value->get<double>()};
+
+    return ret;
+}
+
 /* Note: this will fail if the book slots we're looking for here are flattened at some point !
  * When that happens, this function can be removed. */
 static Path opt_name_to_path (const char* opt_name)
diff --git a/libgnucash/engine/qofbook.h b/libgnucash/engine/qofbook.h
index 3ab6c0953..dc52cf4aa 100644
--- a/libgnucash/engine/qofbook.h
+++ b/libgnucash/engine/qofbook.h
@@ -305,6 +305,11 @@ gchar * qof_book_get_default_invoice_report_guid (const QofBook *book);
  */
 gchar * qof_book_get_default_invoice_report_name (const QofBook *book);
 
+/** Get the length of time available to change the used Invoice Report
+ *  when printing Invoices
+ */
+gdouble qof_book_get_default_invoice_report_timeout (const QofBook *book);
+
 /** Returns TRUE if this book uses split action field as the 'Num' field, FALSE
  *  if it uses transaction number field */
 gboolean qof_book_use_split_action_for_num_field (const QofBook *book);
diff --git a/libgnucash/engine/qofbookslots.h b/libgnucash/engine/qofbookslots.h
index ea0e32bcd..5b9eae8bd 100644
--- a/libgnucash/engine/qofbookslots.h
+++ b/libgnucash/engine/qofbookslots.h
@@ -72,6 +72,7 @@
 #define OPTION_SECTION_BUSINESS        N_("Business")
 
 #define OPTION_NAME_DEFAULT_INVOICE_REPORT  N_("Default Invoice Report")
+#define OPTION_NAME_DEFAULT_INVOICE_REPORT_TIMEOUT N_("Default Invoice Report Timeout")
 
 /** @} */
 

commit b41e491ec5757ebf1b71e2739ac6f92de2d419fe
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Wed Feb 1 16:10:04 2023 +0000

    Modify proposed code to use new GncReportCombo widget.

diff --git a/gnucash/gnome/business-gnome-utils.c b/gnucash/gnome/business-gnome-utils.c
index 5b56f10e8..c65028d2c 100644
--- a/gnucash/gnome/business-gnome-utils.c
+++ b/gnucash/gnome/business-gnome-utils.c
@@ -54,6 +54,7 @@
 #include "gnc-guile-utils.h"
 #include "gnc-prefs.h"
 #include "gnc-commodity.h"
+#include "gnc-report-combo.h"
 
 typedef enum
 {
@@ -106,7 +107,7 @@ gnc_get_builtin_default_invoice_print_report (void)
     return PRINTABLE_INVOICE_GUID;
 }
 
-const char * 
+const char *
 gnc_migrate_default_invoice_print_report (void)
 {
     QofBook *book = gnc_get_current_book ();
@@ -136,171 +137,41 @@ gnc_get_default_invoice_print_report (void)
     return default_guid;
 }
 
-static gboolean
-select_default (GtkWidget *combo, const gchar *default_guid)
-{
-    GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(combo));
-    GtkTreeIter iter;
-    gboolean found = FALSE;
-    gboolean valid_iter = gtk_tree_model_get_iter_first (model, &iter);
-
-    while (valid_iter)
-    {
-        gchar *guid;
-        gtk_tree_model_get (model, &iter, COL_INV_GUID, &guid, -1);
-
-        if (g_strcmp0 (default_guid, guid) == 0)
-        {
-            gtk_combo_box_set_active_iter (GTK_COMBO_BOX(combo), &iter);
-            g_free (guid);
-            found = TRUE;
-            break;
-        }
-        g_free (guid);
-        valid_iter = gtk_tree_model_iter_next (model, &iter);
-    }
-    return found;
-}
-
-/********************************************************************
- * update_invoice_list
- *
- * this procedure does the real work of displaying a sorted list of
- * available invoice reports
- ********************************************************************/
-static gchar *
-update_invoice_list (GtkWidget *combo)
+GtkWidget *
+gnc_default_invoice_report_combo (const char* guid_scm_function)
 {
-    SCM get_rpt_guids = scm_c_eval_string ("gnc:custom-report-invoice-template-guids");
+    GSList *invoice_list = NULL;
     SCM template_menu_name = scm_c_eval_string ("gnc:report-template-menu-name/report-guid");
+    SCM get_rpt_guids = scm_c_eval_string (guid_scm_function);
     SCM reportlist;
     SCM rpt_guids;
 
-    GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(combo));
-    GtkListStore *store = GTK_LIST_STORE(model);
-    gchar *default_guid = gnc_get_default_invoice_print_report ();
-    gchar *default_name = NULL;
-    gboolean have_default = FALSE;
-
-    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(model),
-                                          COL_INV_NAME, GTK_SORT_ASCENDING);
+    if (!scm_is_procedure (get_rpt_guids))
+        return NULL;
 
     reportlist = scm_call_0 (get_rpt_guids);
     rpt_guids = reportlist;
 
-    gtk_list_store_clear (store);
-
     if (scm_is_list (rpt_guids))
     {
-        int i;
-        GtkTreeIter iter;
-
-        for (i = 0; !scm_is_null (rpt_guids); i++)
+        for (int i = 0; !scm_is_null (rpt_guids); i++)
         {
             gchar *guid_str = scm_to_utf8_string (SCM_CAR(rpt_guids));
             gchar *name = gnc_scm_to_utf8_string (scm_call_2(template_menu_name,
                                                   SCM_CAR(rpt_guids), SCM_BOOL_F));
 
-            gtk_list_store_append (store, &iter);
-            gtk_list_store_set (store, &iter,
-                                COL_INV_NAME, name,
-                                COL_INV_GUID, guid_str,
-                                COL_INV_MISSING, FALSE,
-                                -1);
-            g_free (name);
-            g_free (guid_str);
-
-            rpt_guids = SCM_CDR(rpt_guids);
-        }
-    }
-
-    have_default = select_default (combo, default_guid);
-
-    if (!have_default)
-    {
-        GtkTreeIter iter;
-        QofBook *book = gnc_get_current_book ();
-        default_name = qof_book_get_default_invoice_report_name (book);
-
-        gtk_list_store_prepend (store, &iter);
-        gtk_list_store_set (store, &iter,
-                            COL_INV_NAME, default_name,
-                            COL_INV_GUID, default_guid,
-                            COL_INV_MISSING, TRUE,
-                            -1);
-
-        gtk_combo_box_set_active_iter (GTK_COMBO_BOX(combo), &iter);
-    }
-    g_free (default_guid);
-    return default_name;
-}
-
-static void
-combo_changed_cb (GtkComboBox *widget, gpointer user_data)
-{
-    GtkTreeIter iter;
-
-    if (gtk_combo_box_get_active_iter (widget, &iter))
-    {
-        GtkTreeModel *model = gtk_combo_box_get_model (widget);
-        gboolean missing;
-        gtk_tree_model_get (model, &iter, COL_INV_MISSING, &missing, -1);
-        // set visibility of the warning image
-        gtk_widget_set_visible (GTK_WIDGET(user_data), missing);
-        gtk_widget_queue_resize (GTK_WIDGET(widget));
-    }
-}
-
-void
-gnc_default_print_report_list (GtkWidget *combo, GtkWidget *warning)
-{
-    gchar *default_name = update_invoice_list (combo);
-
-    if (default_name)
-    {
-        /* Translators: %s is the default invoice report name. */
-        gchar *tool_tip = g_strdup_printf (_("'%s' is missing"),
-                                           default_name);
-        gtk_widget_show (warning);
+            // Note: invoice_list and entries freed in report combo
+            ReportListEntry *rle = g_new0 (ReportListEntry, 1);
 
-        gtk_widget_set_tooltip_text (warning, tool_tip);
-        g_free (tool_tip);
-    }
-    g_free (default_name);
-    g_signal_connect (G_OBJECT(combo), "changed",
-                      G_CALLBACK(combo_changed_cb), warning);
-}
+            rle->report_guid = guid_str;
+            rle->report_name = name;
 
-void
-gnc_default_print_report_list_combo_set_report (GtkComboBox *cbox,
-                                                const gchar *guid)
-{
-    if (guid && *guid)
-        select_default (GTK_WIDGET(cbox), guid);
-    else
-        select_default (GTK_WIDGET(cbox), gnc_get_builtin_default_invoice_print_report ());
-}
+            invoice_list = g_slist_append (invoice_list, rle);
 
-gchar*
-gnc_default_print_report_list_combo_get_report (GtkComboBox *cbox)
-{
-    GtkTreeModel *model = gtk_combo_box_get_model (cbox);
-    GtkTreeIter iter;
-    gchar *report = NULL;
-
-    if (gtk_combo_box_get_active_iter (cbox, &iter))
-    {
-        gchar *report_guid;
-        gchar *report_name;
-        gtk_tree_model_get (model, &iter, COL_INV_NAME, &report_name,
-                                          COL_INV_GUID, &report_guid,
-                                          -1);
-
-        report = g_strconcat (report_guid, "/", report_name, NULL);
-        g_free (report_guid);
-        g_free (report_name);
+            rpt_guids = SCM_CDR(rpt_guids);
+        }
     }
-    return report;
+    return gnc_report_combo_new (invoice_list);
 }
 
 static GtkWidget * gnc_owner_new (GtkWidget *label, GtkWidget *hbox,
diff --git a/gnucash/gnome/business-gnome-utils.h b/gnucash/gnome/business-gnome-utils.h
index a5c5356c7..96fef1be5 100644
--- a/gnucash/gnome/business-gnome-utils.h
+++ b/gnucash/gnome/business-gnome-utils.h
@@ -50,7 +50,7 @@ const char *gnc_get_builtin_default_invoice_print_report (void);
 
 /** Migrate the Default Invoice Report from prefs to book properties
  *  used to print Invoices
- * 
+ *
  * @return The guid of the saved Invoice Report
  */
 const char * gnc_migrate_default_invoice_print_report (void);
@@ -71,24 +71,14 @@ char *gnc_get_default_invoice_print_report (void);
  */
 void gnc_default_print_report_list (GtkWidget *combo, GtkWidget *warning);
 
-/** Retrieve the string representing the Invoice Report used as the default
- *  to print Invoices. This is a concatination of report name and guid
+/** Create a report combo to show a list of Invoice reports so that
+ *  a default Invoice Report can be selected.
  *
- *  @param combo The GtkComboBox that presents the list.
- * 
- *  @return The string used to represent the selected Invoice Report
- */
-gchar *gnc_default_print_report_list_combo_get_report (GtkComboBox *cbox);
-
-/** Set the active report to the guid string
+ *  @param guid_scm_function The SCM function to create the report list
  *
- *  @param combo The GtkComboBox that presents the list.
- * 
- *  @param guid The guid of the Invoice Report
+ *  @return The Widget for the report combo
  */
-void gnc_default_print_report_list_combo_set_report (GtkComboBox *cbox,
-                                                     const gchar *guid);
-
+GtkWidget * gnc_default_invoice_report_combo (const char* guid_scm_function);
 
 GtkWidget * gnc_owner_select_create (GtkWidget *label, GtkWidget *hbox,
                                      QofBook *book, GncOwner *owner);
diff --git a/gnucash/gnome/business-options-gnome.cpp b/gnucash/gnome/business-options-gnome.cpp
index 4e7f9dcdd..f8155ab77 100644
--- a/gnucash/gnome/business-options-gnome.cpp
+++ b/gnucash/gnome/business-options-gnome.cpp
@@ -33,6 +33,10 @@
 #include <gnc-general-search.h> // for GNC_GENERAL_SEARCH
 #include "dialog-utils.h" // for gnc_builder_add_from_file
 
+extern "C"
+{
+#include "gnc-report-combo.h"
+}
 
 #include <iostream>
 #include <sstream>
@@ -190,13 +194,23 @@ public:
         GncOptionGtkUIItem(widget, GncOptionUIType::INV_REPORT) {}
     void set_ui_item_from_option(GncOption& option) noexcept override
     {
-        auto guid_string{option.get_value<std::string>()};
-        gnc_default_print_report_list_combo_set_report (GTK_COMBO_BOX(get_widget()),
-                                                        guid_string.c_str());
+        std::string guid_string;
+        auto str{option.get_value<std::string>()};
+
+        if (str.empty())
+        {
+            static const std::string default_guid_string(gnc_get_builtin_default_invoice_print_report ());
+            guid_string = default_guid_string + "/ ";
+        }
+        else
+            guid_string = str;
+
+        gnc_report_combo_set_active_guid_name (GNC_REPORT_COMBO(get_widget()),
+                                               guid_string.c_str());
     }
     void set_option_from_ui_item(GncOption& option) noexcept override
     {
-        auto report_guid_name = gnc_default_print_report_list_combo_get_report (GTK_COMBO_BOX(get_widget()));
+        auto report_guid_name = gnc_report_combo_get_active_guid_name (GNC_REPORT_COMBO(get_widget()));
         option.set_value(std::string{report_guid_name});
         g_free (report_guid_name);
     }
@@ -207,28 +221,15 @@ create_option_widget<GncOptionUIType::INV_REPORT>(GncOption& option,
                                                   GtkGrid *page_box,
                                                   int row)
 {
-    constexpr const char* glade_file{"business-options-gnome.glade"};
-    constexpr const char* glade_store{"liststore_print_invoice"};
-    constexpr const char* glade_hbox{"invoice_report_hbox"};
-    constexpr const char* glade_menu{"invoice_report_combo"};
-    constexpr const char* glade_warning{"invoice_warning_image"};
-    auto builder{gtk_builder_new()};
-
-    gnc_builder_add_from_file(builder, glade_file, glade_store);
-    gnc_builder_add_from_file(builder, glade_file, glade_hbox);
-    auto widget{GTK_WIDGET(gtk_builder_get_object(builder, glade_menu))};
-    auto widget_hbox{GTK_WIDGET(gtk_builder_get_object(builder, glade_hbox))};
-    auto widget_warning{GTK_WIDGET(gtk_builder_get_object(builder, glade_warning))};
-    g_object_set_data(G_OBJECT(widget), "warning-image", widget_warning);
-    gnc_default_print_report_list (GTK_WIDGET(widget), GTK_WIDGET(widget_warning));
+    constexpr const char* inv_report{"gnc:custom-report-invoice-template-guids"};
+    auto widget = gnc_default_invoice_report_combo (inv_report);
     option.set_ui_item(std::make_unique<GncGtkInvReportUIItem>(widget));
     option.set_ui_item_from_option();
 
     g_signal_connect (G_OBJECT (widget), "changed",
                       G_CALLBACK (gnc_option_changed_widget_cb), &option);
 
-    wrap_widget (option, widget_hbox, page_box, row);
-    g_object_unref(builder); // Needs to wait until after widget has been reffed.
+    wrap_widget (option, widget, page_box, row);
 }
 
 void
diff --git a/gnucash/gtkbuilder/business-options-gnome.glade b/gnucash/gtkbuilder/business-options-gnome.glade
index 00fd7f999..99f03f625 100644
--- a/gnucash/gtkbuilder/business-options-gnome.glade
+++ b/gnucash/gtkbuilder/business-options-gnome.glade
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.38.2 -->
+<!-- Generated with glade 3.40.0 -->
 <interface>
   <requires lib="gtk+" version="3.22"/>
   <object class="GtkListStore" id="liststore_print_invoice">
@@ -12,46 +12,6 @@
       <column type="gboolean"/>
     </columns>
   </object>
-  <object class="GtkWindow" id="dummy_toplevel_window2">
-    <property name="can-focus">False</property>
-    <child>
-      <object class="GtkBox" id="invoice_report_hbox">
-        <property name="visible">True</property>
-        <property name="can-focus">False</property>
-        <property name="spacing">6</property>
-        <child>
-          <object class="GtkComboBox" id="invoice_report_combo">
-            <property name="visible">True</property>
-            <property name="can-focus">False</property>
-            <property name="model">liststore_print_invoice</property>
-            <child>
-              <object class="GtkCellRendererText" id="cell_renderer_text"/>
-              <attributes>
-                <attribute name="text">0</attribute>
-              </attributes>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkImage" id="invoice_warning_image">
-            <property name="can-focus">False</property>
-            <property name="no-show-all">True</property>
-            <property name="icon-name">dialog-warning</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-  </object>
   <object class="GtkListStore" id="taxtable_store">
     <columns>
       <!-- column-name taxtable_name -->

commit b49b5c86bb2a3f7ba8622c485d678462fd168dde
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Wed Feb 1 15:38:16 2023 +0000

    Create a new widget GncReportCombo to list reports
    
    This widget will provide a combo and populate a model with guids, names
    and a warning value that controls the visibility of a warning image.

diff --git a/gnucash/gnome-utils/CMakeLists.txt b/gnucash/gnome-utils/CMakeLists.txt
index 220fdd417..82c0c9aeb 100644
--- a/gnucash/gnome-utils/CMakeLists.txt
+++ b/gnucash/gnome-utils/CMakeLists.txt
@@ -86,6 +86,7 @@ set (gnome_utils_SOURCES
   gnc-plugin.c
   gnc-period-select.c
   gnc-query-view.c
+  gnc-report-combo.c
   gnc-splash.c
   gnc-sx-instance-dense-cal-adapter.c
   gnc-sx-list-tree-model-adapter.c
@@ -172,6 +173,7 @@ set (gnome_utils_HEADERS
   gnc-plugin.h
   gnc-period-select.h
   gnc-query-view.h
+  gnc-report-combo.h
   gnc-splash.h
   gnc-sx-instance-dense-cal-adapter.h
   gnc-sx-list-tree-model-adapter.h
diff --git a/gnucash/gnome-utils/gnc-report-combo.c b/gnucash/gnome-utils/gnc-report-combo.c
new file mode 100644
index 000000000..68d552e39
--- /dev/null
+++ b/gnucash/gnome-utils/gnc-report-combo.c
@@ -0,0 +1,551 @@
+/********************************************************************\
+ * gnc-report-combo.c -- report select widget for GnuCash           *
+ *                                                                  *
+ * Copyright (C) 2022 Bob 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 at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "gnc-report-combo.h"
+#include "gnc-ui-util.h"
+#include "gnc-engine.h"
+#include "dialog-utils.h"
+
+/** The debugging module used by this file. */
+static QofLogModule log_module = GNC_MOD_GUI;
+
+static void gnc_report_combo_init       (GncReportCombo      *grc);
+static void gnc_report_combo_class_init (GncReportComboClass *klass);
+static void gnc_report_combo_dispose    (GObject *object);
+static void gnc_report_combo_finalize   (GObject *object);
+
+#define GNC_REPORT_COMBO_PATH "gnc-report-combo-path"
+
+enum
+{
+    RC_NAME,
+    RC_GUID,
+    RC_MISSING
+};
+
+/** The instance private data for a content plugin. */
+typedef struct _GncReportComboPrivate
+{
+    GtkWidget   *combo;
+    GtkWidget   *warning_image;
+
+    const gchar *rpt_guids;
+
+    gboolean     block_signal;
+    gboolean     popup_shown;
+
+    gchar       *active_report_guid;
+    gchar       *active_report_name;
+
+} GncReportComboPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE(GncReportCombo, gnc_report_combo, GTK_TYPE_BOX)
+
+#define GET_PRIVATE(o)  \
+   ((GncReportComboPrivate*)g_type_instance_get_private ((GTypeInstance*)o, GNC_TYPE_REPORT_COMBO))
+
+enum
+{
+    SIGNAL_0,
+    CHANGED,
+    LAST_SIGNAL
+};
+
+static guint report_combo_signals [LAST_SIGNAL] = {0};
+
+enum
+{
+    PROP_0,
+    PROP_POPUP_SHOWN,
+    N_PROPERTIES
+};
+
+static GParamSpec *report_combo_properties [N_PROPERTIES] = {NULL,};
+
+static void
+gnc_report_combo_get_property (GObject    *object,
+                               guint       property_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+    GncReportCombo        *grc = GNC_REPORT_COMBO(object);
+    GncReportComboPrivate *priv = GET_PRIVATE(grc);
+
+    switch (property_id)
+    {
+    case PROP_POPUP_SHOWN:
+        g_value_set_boolean (value, priv->popup_shown);
+        break;
+
+    default:
+        /* We don't have any other property... */
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+        break;
+    }
+}
+
+/** Initialize the GncReportCombo class object.
+ *
+ *  @internal
+ *
+ *  @param klass A pointer to the newly created class object.
+ */
+static void
+gnc_report_combo_class_init (GncReportComboClass *klass)
+{
+    GObjectClass   *object_class = G_OBJECT_CLASS(klass);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+    object_class->get_property = gnc_report_combo_get_property;
+    object_class->dispose  = gnc_report_combo_dispose;
+    object_class->finalize = gnc_report_combo_finalize;
+
+    report_combo_signals [CHANGED] =
+        g_signal_new ("changed",
+                      G_OBJECT_CLASS_TYPE(object_class),
+                      G_SIGNAL_RUN_FIRST,
+                      G_STRUCT_OFFSET(GncReportComboClass, changed),
+                      NULL,
+                      NULL,
+                      g_cclosure_marshal_VOID__VOID,
+                      G_TYPE_NONE,
+                      0);
+
+    report_combo_properties [PROP_POPUP_SHOWN] =
+        g_param_spec_boolean ("popup-shown",
+                              "State of PopUp",
+                              "State of PopUp",
+                              FALSE /* default value */,
+                              G_PARAM_READABLE);
+
+    g_object_class_install_properties (object_class,
+                                       N_PROPERTIES,
+                                       report_combo_properties);
+}
+
+/** Initialize a GncReportCombo object.
+ *
+ *  @internal
+ *
+ *  @param grc A pointer to the newly created object.
+ */
+static void
+gnc_report_combo_init (GncReportCombo *grc)
+{
+    GncReportComboPrivate *priv;
+
+    g_return_if_fail (grc != NULL);
+    g_return_if_fail (GNC_IS_REPORT_COMBO(grc));
+
+    priv = GET_PRIVATE(grc);
+
+    // Set the name for this widget so it can be easily manipulated with css
+    gtk_widget_set_name (GTK_WIDGET(grc), "gnc-id-report-combo");
+
+    priv->block_signal = FALSE;
+    priv->active_report_guid = NULL;
+    priv->active_report_name = NULL;
+    priv->popup_shown = FALSE;
+}
+
+/** Dispopse the GncReportCombo object. This function is called from
+ *  the G_Object level to complete the destruction of the object.  It
+ *  should release any memory not previously released by the destroy
+ *  function (i.e. the private data structure), then chain up to the
+ *  parent's destroy function.
+ *
+ *  @param object The object being destroyed.
+ *
+ *  @internal
+ */
+static void
+gnc_report_combo_dispose (GObject *object)
+{
+    /* Do not free the private data structure itself. It is part of
+     * a larger memory block allocated by the type system. */
+
+    G_OBJECT_CLASS (gnc_report_combo_parent_class)->dispose (object);
+}
+
+/** Finalize the GncReportCombo object.  This function is called from
+ *  the G_Object level to complete the destruction of the object.  It
+ *  should release any memory not previously released by the destroy
+ *  function (i.e. the private data structure), then chain up to the
+ *  parent's finalize function.
+ *
+ *  @param object The object being finalized.
+ *
+ *  @internal
+ */
+static void
+gnc_report_combo_finalize (GObject *object)
+{
+    GncReportComboPrivate *priv;
+    GncReportCombo *grc;
+
+    g_return_if_fail (object != NULL);
+    g_return_if_fail (GNC_IS_REPORT_COMBO(object));
+
+    grc = GNC_REPORT_COMBO(object);
+    priv = GET_PRIVATE(grc);
+
+    g_free (priv->active_report_guid);
+    g_free (priv->active_report_name);
+
+    G_OBJECT_CLASS (gnc_report_combo_parent_class)->finalize (object);
+}
+
+/** This function sets the active combo entry based on the private
+ *  report guid and also checks to see if the report guid is in the
+ *  list of reports, if not a warning image and tooltip is shown.
+ *
+ *  @internal
+ *
+ *  @param grc The report combo.
+ *
+ *  @return TRUE if report guid is in the list, other wise FALSE.
+ */
+static gboolean
+select_active_and_check_exists (GncReportCombo *grc)
+{
+    GncReportComboPrivate *priv = GET_PRIVATE(grc);
+    GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(priv->combo));
+    GtkTreeIter iter;
+    gboolean valid_iter = gtk_tree_model_get_iter_first (model, &iter);
+    QofBook *book = gnc_get_current_book ();
+    gchar *tmp;
+
+    while (valid_iter)
+    {
+        gchar *guid;
+        gtk_tree_model_get (model, &iter, RC_GUID, &guid, -1);
+
+        if (g_strcmp0 (priv->active_report_guid, guid) == 0)
+        {
+            gtk_combo_box_set_active_iter (GTK_COMBO_BOX(priv->combo), &iter);
+            g_free (guid);
+            return TRUE;
+        }
+        g_free (guid);
+        valid_iter = gtk_tree_model_iter_next (model, &iter);
+    }
+
+    if (priv->active_report_name)
+        tmp = g_strdup (priv->active_report_name);
+    else
+        tmp = g_strdup (_("Selected Report is Missing"));
+
+    gtk_list_store_prepend (GTK_LIST_STORE(model), &iter);
+    gtk_list_store_set (GTK_LIST_STORE(model), &iter,
+                        RC_NAME, tmp,
+                        RC_GUID, priv->active_report_guid,
+                        RC_MISSING, TRUE,
+                        -1);
+
+    g_free (tmp);
+    gtk_combo_box_set_active_iter (GTK_COMBO_BOX(priv->combo), &iter);
+    return FALSE;
+}
+
+static void
+update_report_list (GncReportCombo *grc, GSList *report_list)
+{
+    GncReportComboPrivate *priv = GET_PRIVATE(grc);
+    GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(priv->combo));
+
+    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(model),
+                                          RC_NAME, GTK_SORT_ASCENDING);
+
+    gtk_list_store_clear (GTK_LIST_STORE(model));
+
+    if (report_list)
+    {
+        GtkTreeIter iter;
+
+        for (GSList* node = report_list; node != NULL; node = g_slist_next (node))
+        {
+            ReportListEntry *rle = node->data;
+
+            gtk_list_store_append (GTK_LIST_STORE(model), &iter);
+            gtk_list_store_set (GTK_LIST_STORE(model), &iter,
+                                RC_NAME, rle->report_name,
+                                RC_GUID, rle->report_guid,
+                                RC_MISSING, FALSE,
+                                -1);
+            g_free (rle->report_name);
+            g_free (rle->report_guid);
+            g_free (rle);
+        }
+    }
+    g_slist_free (report_list);
+}
+
+static void
+update_warning_tooltip (GncReportCombo *grc)
+{
+    GncReportComboPrivate *priv = GET_PRIVATE(grc);
+    gchar *tool_tip;
+
+    if (priv->active_report_name)
+        /* Translators: %s is the report name. */
+        tool_tip = g_strdup_printf (_("'%s' is missing"),
+                                      priv->active_report_name);
+    else
+        /* Translators: %s is the internal report guid. */
+        tool_tip = g_strdup_printf (_("Report with GUID '%s' is missing"),
+                                       priv->active_report_guid);
+
+    gtk_widget_show (priv->warning_image);
+    gtk_widget_set_tooltip_text (priv->warning_image, tool_tip);
+    g_free (tool_tip);
+}
+
+void
+gnc_report_combo_set_active (GncReportCombo *grc,
+                             const char* active_report_guid,
+                             const char* active_report_name)
+{
+    GncReportComboPrivate *priv;
+
+    g_return_if_fail (grc != NULL);
+    g_return_if_fail (GNC_IS_REPORT_COMBO(grc));
+
+    priv = GET_PRIVATE(grc);
+
+    g_free (priv->active_report_guid);
+
+    priv->active_report_guid = g_strdup (active_report_guid);
+
+    g_free (priv->active_report_name);
+
+    priv->active_report_name = g_strdup (active_report_name);
+
+    priv->block_signal = TRUE;
+
+    if (!select_active_and_check_exists (grc))
+        update_warning_tooltip (grc);
+
+    priv->block_signal = FALSE;
+}
+
+gchar *
+gnc_report_combo_get_active_guid (GncReportCombo *grc)
+{
+    GncReportComboPrivate *priv;
+    gchar *guid = NULL;
+    GtkTreeIter iter;
+
+    g_return_val_if_fail (grc != NULL, NULL);
+    g_return_val_if_fail (GNC_IS_REPORT_COMBO(grc), NULL);
+
+    priv = GET_PRIVATE(grc);
+
+    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX(priv->combo), &iter))
+    {
+        GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(priv->combo));
+        gtk_tree_model_get (model, &iter, RC_GUID, &guid, -1);
+    }
+    return guid;
+}
+
+gchar *
+gnc_report_combo_get_active_name (GncReportCombo *grc)
+{
+    GncReportComboPrivate *priv;
+    gchar *name = NULL;
+    GtkTreeIter iter;
+
+    g_return_val_if_fail (grc != NULL, NULL);
+    g_return_val_if_fail (GNC_IS_REPORT_COMBO(grc), NULL);
+
+    priv = GET_PRIVATE(grc);
+
+    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX(priv->combo), &iter))
+    {
+        GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(priv->combo));
+        gtk_tree_model_get (model, &iter, RC_NAME, &name, -1);
+    }
+    return name;
+}
+
+gchar*
+gnc_report_combo_get_active_guid_name (GncReportCombo *grc)
+{
+    GncReportComboPrivate *priv;
+    gchar *report = NULL;
+    GtkTreeIter iter;
+
+    g_return_val_if_fail (grc != NULL, NULL);
+    g_return_val_if_fail (GNC_IS_REPORT_COMBO(grc), NULL);
+
+    priv = GET_PRIVATE(grc);
+
+    if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX(priv->combo), &iter))
+    {
+        GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(priv->combo));
+        gchar *report_guid;
+        gchar *report_name;
+        gtk_tree_model_get (model, &iter, RC_NAME, &report_name,
+                                          RC_GUID, &report_guid,
+                                          -1);
+
+        report = g_strconcat (report_guid, "/", report_name, NULL);
+        g_free (report_guid);
+        g_free (report_name);
+    }
+    return report;
+}
+
+void
+gnc_report_combo_set_active_guid_name (GncReportCombo *grc,
+                                       const gchar *guid_name)
+{
+    GncReportComboPrivate *priv;
+
+    g_return_if_fail (grc != NULL);
+    g_return_if_fail (GNC_IS_REPORT_COMBO(grc));
+
+    priv = GET_PRIVATE(grc);
+
+    if (guid_name && *guid_name)
+    {
+        gchar *guid = NULL;
+        gchar *name = g_strstr_len (guid_name, -1, "/");
+
+        if (name)
+        {
+            guid = g_strndup (guid_name, (name - guid_name));
+            gnc_report_combo_set_active (grc, guid, name + 1);
+        }
+        g_free (guid);
+    }
+}
+
+static void
+combo_changed_cb (GtkComboBox *widget, gpointer user_data)
+{
+    GncReportCombo        *grc = GNC_REPORT_COMBO(user_data);
+    GncReportComboPrivate *priv = GET_PRIVATE(grc);
+    GtkTreeIter            iter;
+
+    if (gtk_combo_box_get_active_iter (widget, &iter))
+    {
+        GtkTreeModel *model = gtk_combo_box_get_model (widget);
+        gboolean missing;
+        gtk_tree_model_get (model, &iter, RC_MISSING, &missing, -1);
+        // set visibility of the warning image
+        gtk_widget_set_visible (priv->warning_image, missing);
+
+        if (!priv->block_signal)
+            g_signal_emit (grc, report_combo_signals [CHANGED], 0);
+
+        gtk_widget_queue_resize (GTK_WIDGET(widget));
+    }
+}
+
+static void
+combo_popped_cb (GObject    *gobject,
+                 GParamSpec *pspec,
+                 gpointer    user_data)
+{
+    GncReportCombo        *grc = GNC_REPORT_COMBO(user_data);
+    GncReportComboPrivate *priv = GET_PRIVATE(grc);
+    gboolean popup_shown;
+
+    g_object_get (G_OBJECT(gobject), "popup-shown", &popup_shown, NULL);
+
+    priv->popup_shown = popup_shown;
+    g_object_notify (G_OBJECT(grc), "popup-shown");
+}
+
+void
+gnc_report_combo_refresh (GncReportCombo *grc, GSList *report_list)
+{
+    GncReportComboPrivate *priv;
+
+    g_return_if_fail (grc != NULL);
+    g_return_if_fail (GNC_IS_REPORT_COMBO(grc));
+    g_return_if_fail (report_list != NULL);
+
+    priv = GET_PRIVATE(grc);
+
+    priv->block_signal = TRUE;
+
+    update_report_list (grc, report_list);
+
+    if (!select_active_and_check_exists (grc))
+        update_warning_tooltip (grc);
+
+    priv->block_signal = FALSE;
+}
+
+/*  Create a new GncReportCombo widget which can be used to select
+ *  a report from a GtkComboBox.
+ *
+ *  @return A GncReportCombo widget.
+ */
+GtkWidget *
+gnc_report_combo_new (GSList *report_list)
+{
+    GncReportCombo *grc;
+    GncReportComboPrivate *priv;
+    GtkListStore *store;
+    GtkCellRenderer *renderer;
+
+    store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+    grc = g_object_new (GNC_TYPE_REPORT_COMBO, NULL);
+
+    priv = GET_PRIVATE(grc);
+
+    priv->combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL(store));
+    g_object_unref (store);
+
+    renderer = gtk_cell_renderer_text_new ();
+    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(priv->combo), renderer, TRUE);
+    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(priv->combo), renderer,
+                                    "text", RC_NAME, NULL);
+
+    gtk_box_pack_start (GTK_BOX(grc), GTK_WIDGET(priv->combo), TRUE, TRUE, 0);
+    priv->warning_image = gtk_image_new_from_icon_name ("dialog-warning",
+                                                        GTK_ICON_SIZE_SMALL_TOOLBAR);
+    gtk_box_pack_start (GTK_BOX(grc), GTK_WIDGET(priv->warning_image), FALSE, FALSE, 6);
+    gtk_widget_set_no_show_all (GTK_WIDGET(priv->warning_image), TRUE);
+    gtk_widget_hide (GTK_WIDGET(priv->warning_image));
+
+    update_report_list (grc, report_list);
+
+    g_signal_connect (G_OBJECT(priv->combo), "changed",
+                      G_CALLBACK(combo_changed_cb), grc);
+
+    g_signal_connect (G_OBJECT(priv->combo), "notify::popup-shown",
+                      G_CALLBACK(combo_popped_cb), grc);
+
+    gtk_widget_show_all (GTK_WIDGET(grc));
+
+    return GTK_WIDGET(grc);
+}
diff --git a/gnucash/gnome-utils/gnc-report-combo.h b/gnucash/gnome-utils/gnc-report-combo.h
new file mode 100644
index 000000000..ff811ed42
--- /dev/null
+++ b/gnucash/gnome-utils/gnc-report-combo.h
@@ -0,0 +1,123 @@
+/********************************************************************\
+ * gnc-report-combo.h -- report select widget for GnuCash           *
+ *                                                                  *
+ * Copyright (C) 2022 Bob 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 at gnu.org                   *
+ *                                                                  *
+\********************************************************************/
+
+#ifndef GNC_REPORT_COMBO_H
+#define GNC_REPORT_COMBO_H
+
+#define GNC_TYPE_REPORT_COMBO            (gnc_report_combo_get_type())
+#define GNC_REPORT_COMBO(o)              (G_TYPE_CHECK_INSTANCE_CAST ((o), GNC_TYPE_REPORT_COMBO, GncReportCombo))
+#define GNC_REPORT_COMBO_CLASS(k)        (G_TYPE_CHECK_CLASS_CAST ((k), GNC_TYPE_REPORT_COMBO, GncReportComboClass))
+#define GNC_IS_REPORT_COMBO(o)           (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNC_TYPE_REPORT_COMBO))
+#define GNC_IS_REPORT_COMBO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), GNC_TYPE_REPORT_COMBO))
+#define GNC_REPORT_COMBO_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_REPORT_COMBO, GncReportComboClass))
+
+typedef struct
+{
+    GtkBox box;
+
+} GncReportCombo;
+
+typedef struct
+{
+    GtkBoxClass parent_class;
+    void (*changed) (GncReportCombo *grc);
+
+} GncReportComboClass;
+
+typedef struct
+{
+    char *report_guid;
+    char *report_name;
+} ReportListEntry;
+
+/** Return the GType for the GncReportCombo widget.
+ *
+ *  @return A GType value.
+ */
+GType gnc_report_combo_get_type (void) G_GNUC_CONST;
+
+/** Create a new GncReportCombo widget which can be used to provide
+ *  a list of reports and select one.
+ *
+ *  @param report_list The list of report guids to populate the model
+ *
+ *  @return A GncReportCombo widget.
+ */
+GtkWidget *gnc_report_combo_new (GSList *report_list);
+
+/** Refresh the report combo model.
+ *
+ *  @param grc The report combo widget.
+ *
+ *  @param report_list The report list to update the combo with.
+ */
+void gnc_report_combo_refresh (GncReportCombo *grc, GSList *report_list);
+
+/** Set the active report in the GncReportCombo widget.
+ *
+ *  @param grc The report combo widget.
+ *
+ *  @param active_report_guid A string representing the report guid
+ *
+ *  @param active_report_name A string representing the report name
+ */
+void gnc_report_combo_set_active (GncReportCombo *grc,
+                                  const char* active_report_guid,
+                                  const char* active_report_name);
+
+/** Get the active report guid string.
+ *
+ *  @param grc The report combo widget.
+ *
+ *  @return The string guid of the selected report or NULL if none active
+ */
+gchar * gnc_report_combo_get_active_guid (GncReportCombo *grc);
+
+/** Get the active report name string.
+ *
+ *  @param grc The report combo widget.
+ *
+ *  @return The string name of the selected report or NULL if none active
+ */
+gchar * gnc_report_combo_get_active_name (GncReportCombo *grc);
+
+/** Set the active report to the guid string
+ *
+ *  @param combo The GtkComboBox that presents the list.
+ *
+ *  @param guid_name The concatination of the guid/name of the Invoice Report
+ */
+void gnc_report_combo_set_active_guid_name (GncReportCombo *grc,
+                                            const gchar *guid_name);
+
+/** Get the active report name string.
+ *
+ *  @param grc The report combo widget.
+ *
+ *  @return The concatinated string of report guid and name of the selected
+ *          report or NULL if none active
+ */
+gchar * gnc_report_combo_get_active_guid_name (GncReportCombo *grc);
+
+#endif /* __GNC_REPORT_COMBO_H__ */

commit 019214f1c2151e922131ed25671680a8fee989bf
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Fri May 6 15:05:12 2022 +0100

    Bug753307 - Custom Report be selectable as default Report for Printing
    
    This commit changes the preference in Business->'Report for Printing'
    to be saved as a book property and allow the selection of any Invoice
    Report to be used as the default.

diff --git a/gnucash/gnome/business-gnome-utils.c b/gnucash/gnome/business-gnome-utils.c
index 1f09bc573..5b56f10e8 100644
--- a/gnucash/gnome/business-gnome-utils.c
+++ b/gnucash/gnome/business-gnome-utils.c
@@ -42,6 +42,7 @@
 
 #include "gnc-general-search.h"
 #include "qof.h"
+#include "qofbook.h"
 #include "business-gnome-utils.h"
 #include "dialog-customer.h"
 #include "dialog-job.h"
@@ -49,6 +50,9 @@
 #include "dialog-employee.h"
 #include "dialog-invoice.h"
 
+#include "guile-mappings.h"
+#include "gnc-guile-utils.h"
+#include "gnc-prefs.h"
 #include "gnc-commodity.h"
 
 typedef enum
@@ -57,6 +61,248 @@ typedef enum
     GNCSEARCH_TYPE_EDIT
 } GNCSearchType;
 
+enum
+{
+    COL_INV_NAME = 0,
+    COL_INV_GUID,
+    COL_INV_MISSING,
+    NUM_INV_COLS
+};
+
+#define PRINTABLE_INVOICE_GUID "5123a759ceb9483abf2182d01c140e8d"
+#define TAX_INVOICE_GUID       "0769e242be474010b4acf264a5512e6e"
+#define EASY_INVOICE_GUID      "67112f318bef4fc496bdc27d106bbda4"
+#define FANCY_INVOICE_GUID     "3ce293441e894423a2425d7a22dd1ac6"
+
+enum
+{
+    PRINTABLE_INVOICE_PREF_NUM = 0,
+    TAX_INVOICE_PREF_NUM,
+    EASY_INVOICE_PREF_NUM,
+    FANCY_INVOICE_PREF_NUM,
+};
+
+static const char* invoice_printreport_values[] =
+{
+    /* The list below are the guids of reports that can
+     * be used to print an invoice.
+     *
+     * Important: This list matches the order of existing saved
+     * preference entries.
+     */
+    PRINTABLE_INVOICE_GUID,
+    TAX_INVOICE_GUID,
+    EASY_INVOICE_GUID,
+    FANCY_INVOICE_GUID,
+    NULL
+};
+
+#define GNC_PREFS_GROUP_INVOICE    "dialogs.business.invoice"
+#define GNC_PREF_INV_PRINT_RPT     "invoice-printreport"
+
+const char *
+gnc_get_builtin_default_invoice_print_report (void)
+{
+    return PRINTABLE_INVOICE_GUID;
+}
+
+const char * 
+gnc_migrate_default_invoice_print_report (void)
+{
+    QofBook *book = gnc_get_current_book ();
+    int old_style_value = gnc_prefs_get_int (GNC_PREFS_GROUP_INVOICE,
+                                             GNC_PREF_INV_PRINT_RPT);
+
+    if (old_style_value >= TAX_INVOICE_PREF_NUM &&
+        old_style_value <= FANCY_INVOICE_PREF_NUM)
+    {
+        const gchar *ret = invoice_printreport_values[old_style_value];
+        qof_book_set_default_invoice_report (book, ret, " ");
+        return ret;
+    }
+    else
+        return gnc_get_builtin_default_invoice_print_report ();
+}
+
+char *
+gnc_get_default_invoice_print_report (void)
+{
+    QofBook *book = gnc_get_current_book ();
+    gchar *default_guid = qof_book_get_default_invoice_report_guid (book);
+
+    if (!default_guid)
+        return g_strdup (gnc_migrate_default_invoice_print_report ());
+
+    return default_guid;
+}
+
+static gboolean
+select_default (GtkWidget *combo, const gchar *default_guid)
+{
+    GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(combo));
+    GtkTreeIter iter;
+    gboolean found = FALSE;
+    gboolean valid_iter = gtk_tree_model_get_iter_first (model, &iter);
+
+    while (valid_iter)
+    {
+        gchar *guid;
+        gtk_tree_model_get (model, &iter, COL_INV_GUID, &guid, -1);
+
+        if (g_strcmp0 (default_guid, guid) == 0)
+        {
+            gtk_combo_box_set_active_iter (GTK_COMBO_BOX(combo), &iter);
+            g_free (guid);
+            found = TRUE;
+            break;
+        }
+        g_free (guid);
+        valid_iter = gtk_tree_model_iter_next (model, &iter);
+    }
+    return found;
+}
+
+/********************************************************************
+ * update_invoice_list
+ *
+ * this procedure does the real work of displaying a sorted list of
+ * available invoice reports
+ ********************************************************************/
+static gchar *
+update_invoice_list (GtkWidget *combo)
+{
+    SCM get_rpt_guids = scm_c_eval_string ("gnc:custom-report-invoice-template-guids");
+    SCM template_menu_name = scm_c_eval_string ("gnc:report-template-menu-name/report-guid");
+    SCM reportlist;
+    SCM rpt_guids;
+
+    GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(combo));
+    GtkListStore *store = GTK_LIST_STORE(model);
+    gchar *default_guid = gnc_get_default_invoice_print_report ();
+    gchar *default_name = NULL;
+    gboolean have_default = FALSE;
+
+    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(model),
+                                          COL_INV_NAME, GTK_SORT_ASCENDING);
+
+    reportlist = scm_call_0 (get_rpt_guids);
+    rpt_guids = reportlist;
+
+    gtk_list_store_clear (store);
+
+    if (scm_is_list (rpt_guids))
+    {
+        int i;
+        GtkTreeIter iter;
+
+        for (i = 0; !scm_is_null (rpt_guids); i++)
+        {
+            gchar *guid_str = scm_to_utf8_string (SCM_CAR(rpt_guids));
+            gchar *name = gnc_scm_to_utf8_string (scm_call_2(template_menu_name,
+                                                  SCM_CAR(rpt_guids), SCM_BOOL_F));
+
+            gtk_list_store_append (store, &iter);
+            gtk_list_store_set (store, &iter,
+                                COL_INV_NAME, name,
+                                COL_INV_GUID, guid_str,
+                                COL_INV_MISSING, FALSE,
+                                -1);
+            g_free (name);
+            g_free (guid_str);
+
+            rpt_guids = SCM_CDR(rpt_guids);
+        }
+    }
+
+    have_default = select_default (combo, default_guid);
+
+    if (!have_default)
+    {
+        GtkTreeIter iter;
+        QofBook *book = gnc_get_current_book ();
+        default_name = qof_book_get_default_invoice_report_name (book);
+
+        gtk_list_store_prepend (store, &iter);
+        gtk_list_store_set (store, &iter,
+                            COL_INV_NAME, default_name,
+                            COL_INV_GUID, default_guid,
+                            COL_INV_MISSING, TRUE,
+                            -1);
+
+        gtk_combo_box_set_active_iter (GTK_COMBO_BOX(combo), &iter);
+    }
+    g_free (default_guid);
+    return default_name;
+}
+
+static void
+combo_changed_cb (GtkComboBox *widget, gpointer user_data)
+{
+    GtkTreeIter iter;
+
+    if (gtk_combo_box_get_active_iter (widget, &iter))
+    {
+        GtkTreeModel *model = gtk_combo_box_get_model (widget);
+        gboolean missing;
+        gtk_tree_model_get (model, &iter, COL_INV_MISSING, &missing, -1);
+        // set visibility of the warning image
+        gtk_widget_set_visible (GTK_WIDGET(user_data), missing);
+        gtk_widget_queue_resize (GTK_WIDGET(widget));
+    }
+}
+
+void
+gnc_default_print_report_list (GtkWidget *combo, GtkWidget *warning)
+{
+    gchar *default_name = update_invoice_list (combo);
+
+    if (default_name)
+    {
+        /* Translators: %s is the default invoice report name. */
+        gchar *tool_tip = g_strdup_printf (_("'%s' is missing"),
+                                           default_name);
+        gtk_widget_show (warning);
+
+        gtk_widget_set_tooltip_text (warning, tool_tip);
+        g_free (tool_tip);
+    }
+    g_free (default_name);
+    g_signal_connect (G_OBJECT(combo), "changed",
+                      G_CALLBACK(combo_changed_cb), warning);
+}
+
+void
+gnc_default_print_report_list_combo_set_report (GtkComboBox *cbox,
+                                                const gchar *guid)
+{
+    if (guid && *guid)
+        select_default (GTK_WIDGET(cbox), guid);
+    else
+        select_default (GTK_WIDGET(cbox), gnc_get_builtin_default_invoice_print_report ());
+}
+
+gchar*
+gnc_default_print_report_list_combo_get_report (GtkComboBox *cbox)
+{
+    GtkTreeModel *model = gtk_combo_box_get_model (cbox);
+    GtkTreeIter iter;
+    gchar *report = NULL;
+
+    if (gtk_combo_box_get_active_iter (cbox, &iter))
+    {
+        gchar *report_guid;
+        gchar *report_name;
+        gtk_tree_model_get (model, &iter, COL_INV_NAME, &report_name,
+                                          COL_INV_GUID, &report_guid,
+                                          -1);
+
+        report = g_strconcat (report_guid, "/", report_name, NULL);
+        g_free (report_guid);
+        g_free (report_name);
+    }
+    return report;
+}
+
 static GtkWidget * gnc_owner_new (GtkWidget *label, GtkWidget *hbox,
                                   QofBook *book, GncOwner *owner,
                                   GNCSearchType type)
diff --git a/gnucash/gnome/business-gnome-utils.h b/gnucash/gnome/business-gnome-utils.h
index de298743c..a5c5356c7 100644
--- a/gnucash/gnome/business-gnome-utils.h
+++ b/gnucash/gnome/business-gnome-utils.h
@@ -41,6 +41,54 @@ extern "C" {
 
 #define GNC_PREF_AUTO_PAY "auto-pay"
 
+/** Retrieve the buitin guid for the Invoice Report to be
+ *  used as the default to print Invoices
+ *
+ * @return The guid of the saved Invoice Report
+ */
+const char *gnc_get_builtin_default_invoice_print_report (void);
+
+/** Migrate the Default Invoice Report from prefs to book properties
+ *  used to print Invoices
+ * 
+ * @return The guid of the saved Invoice Report
+ */
+const char * gnc_migrate_default_invoice_print_report (void);
+
+/** Retrieve the guid of the Invoice Report to be used to
+ *  print Invoices
+ *
+ * @return The guid of the saved Invoice Report
+ */
+char *gnc_get_default_invoice_print_report (void);
+
+/** Setup a combo used for displaying list of Invoice Reports.
+ *
+ *  @param combo The GtkComboBox that presents the list.
+ *
+ *  @param warning The warning image, displayed if the default guid is
+ *                 not in the Invoice list.
+ */
+void gnc_default_print_report_list (GtkWidget *combo, GtkWidget *warning);
+
+/** Retrieve the string representing the Invoice Report used as the default
+ *  to print Invoices. This is a concatination of report name and guid
+ *
+ *  @param combo The GtkComboBox that presents the list.
+ * 
+ *  @return The string used to represent the selected Invoice Report
+ */
+gchar *gnc_default_print_report_list_combo_get_report (GtkComboBox *cbox);
+
+/** Set the active report to the guid string
+ *
+ *  @param combo The GtkComboBox that presents the list.
+ * 
+ *  @param guid The guid of the Invoice Report
+ */
+void gnc_default_print_report_list_combo_set_report (GtkComboBox *cbox,
+                                                     const gchar *guid);
+
 
 GtkWidget * gnc_owner_select_create (GtkWidget *label, GtkWidget *hbox,
                                      QofBook *book, GncOwner *owner);
diff --git a/gnucash/gnome/business-options-gnome.cpp b/gnucash/gnome/business-options-gnome.cpp
index eb1041e58..4e7f9dcdd 100644
--- a/gnucash/gnome/business-options-gnome.cpp
+++ b/gnucash/gnome/business-options-gnome.cpp
@@ -183,6 +183,54 @@ create_option_widget<GncOptionUIType::TAX_TABLE>(GncOption& option,
     wrap_widget(option, widget, page_box, row);
 }
 
+class GncGtkInvReportUIItem : public GncOptionGtkUIItem
+{
+public:
+    GncGtkInvReportUIItem(GtkWidget* widget) :
+        GncOptionGtkUIItem(widget, GncOptionUIType::INV_REPORT) {}
+    void set_ui_item_from_option(GncOption& option) noexcept override
+    {
+        auto guid_string{option.get_value<std::string>()};
+        gnc_default_print_report_list_combo_set_report (GTK_COMBO_BOX(get_widget()),
+                                                        guid_string.c_str());
+    }
+    void set_option_from_ui_item(GncOption& option) noexcept override
+    {
+        auto report_guid_name = gnc_default_print_report_list_combo_get_report (GTK_COMBO_BOX(get_widget()));
+        option.set_value(std::string{report_guid_name});
+        g_free (report_guid_name);
+    }
+};
+
+template<> void
+create_option_widget<GncOptionUIType::INV_REPORT>(GncOption& option,
+                                                  GtkGrid *page_box,
+                                                  int row)
+{
+    constexpr const char* glade_file{"business-options-gnome.glade"};
+    constexpr const char* glade_store{"liststore_print_invoice"};
+    constexpr const char* glade_hbox{"invoice_report_hbox"};
+    constexpr const char* glade_menu{"invoice_report_combo"};
+    constexpr const char* glade_warning{"invoice_warning_image"};
+    auto builder{gtk_builder_new()};
+
+    gnc_builder_add_from_file(builder, glade_file, glade_store);
+    gnc_builder_add_from_file(builder, glade_file, glade_hbox);
+    auto widget{GTK_WIDGET(gtk_builder_get_object(builder, glade_menu))};
+    auto widget_hbox{GTK_WIDGET(gtk_builder_get_object(builder, glade_hbox))};
+    auto widget_warning{GTK_WIDGET(gtk_builder_get_object(builder, glade_warning))};
+    g_object_set_data(G_OBJECT(widget), "warning-image", widget_warning);
+    gnc_default_print_report_list (GTK_WIDGET(widget), GTK_WIDGET(widget_warning));
+    option.set_ui_item(std::make_unique<GncGtkInvReportUIItem>(widget));
+    option.set_ui_item_from_option();
+
+    g_signal_connect (G_OBJECT (widget), "changed",
+                      G_CALLBACK (gnc_option_changed_widget_cb), &option);
+
+    wrap_widget (option, widget_hbox, page_box, row);
+    g_object_unref(builder); // Needs to wait until after widget has been reffed.
+}
+
 void
 gnc_business_options_gnome_initialize(void)
 {
@@ -198,4 +246,6 @@ gnc_business_options_gnome_initialize(void)
                                  create_option_widget<GncOptionUIType::INVOICE>);
     GncOptionUIFactory::set_func(GncOptionUIType::TAX_TABLE,
                                  create_option_widget<GncOptionUIType::TAX_TABLE>);
+    GncOptionUIFactory::set_func(GncOptionUIType::INV_REPORT,
+                                 create_option_widget<GncOptionUIType::INV_REPORT>);
 }
diff --git a/gnucash/gnome/dialog-custom-report.c b/gnucash/gnome/dialog-custom-report.c
index 6a58d5be5..5436c46ba 100644
--- a/gnucash/gnome/dialog-custom-report.c
+++ b/gnucash/gnome/dialog-custom-report.c
@@ -28,6 +28,7 @@
 #include <libguile.h>
 #include "swig-runtime.h"
 
+#include "business-gnome-utils.h"
 #include "dialog-custom-report.h"
 #include "dialog-utils.h"
 #include "gnc-main-window.h"
@@ -36,6 +37,7 @@
 #include "gnc-guile-utils.h"
 #include "gnc-gui-query.h"
 #include "gnc-ui.h"
+#include "gnc-ui-util.h"
 #include "gnc-report.h"
 #include "gnc-plugin-page-report.h"
 
@@ -469,7 +471,23 @@ custom_report_name_edited_cb(GtkCellRendererText *renderer, gchar *path, gchar *
         return;
 
     if (scm_is_true (scm_call_2 (unique_name_func, guid, new_name_scm)))
+    {
+        gchar *default_guid = gnc_get_default_invoice_print_report ();
+
         custom_report_edit_report_name (guid, crd, new_text);
+
+        // check to see if default report name has been changed
+        if (g_strcmp0 (default_guid, scm_to_utf8_string (guid)) == 0)
+        {
+            QofBook *book = gnc_get_current_book ();
+            gchar *default_name = qof_book_get_default_invoice_report_name (book);
+
+            if (g_strcmp0 (default_name, new_text) != 0)
+                qof_book_set_default_invoice_report (book, default_guid, new_text);
+            g_free (default_name);
+        }
+        g_free (default_guid);
+    }
     else
         gnc_error_dialog (GTK_WINDOW (crd->dialog), "%s",
                           _("A saved report configuration with this name already exists, please choose another name.") );
diff --git a/gnucash/gnome/dialog-invoice.c b/gnucash/gnome/dialog-invoice.c
index 09ad5ac58..aeca0844e 100644
--- a/gnucash/gnome/dialog-invoice.c
+++ b/gnucash/gnome/dialog-invoice.c
@@ -783,19 +783,25 @@ gnc_invoice_window_print_invoice(GtkWindow *parent, GncInvoice *invoice)
 {
     SCM func, arg, arg2;
     SCM args = SCM_EOL;
+    SCM is_invoice_guid;
+    SCM scm_guid;
     int report_id;
-    const char *reportname = gnc_plugin_business_get_invoice_printreport();
+    char *report_guid = gnc_get_default_invoice_print_report ();
     GncPluginPage *reportPage = NULL;
 
     g_return_val_if_fail (invoice, NULL);
-    if (!reportname)
-        reportname = "5123a759ceb9483abf2182d01c140e8d"; // fallback if the option lookup failed
+
+    is_invoice_guid = scm_c_eval_string ("gnc:report-is-invoice-report?");
+    scm_guid = scm_from_utf8_string (report_guid);
+
+    if (scm_is_false (scm_call_1 (is_invoice_guid, scm_guid)))
+        report_guid = g_strdup (gnc_get_builtin_default_invoice_print_report ()); // fallback if the option lookup failed
 
     func = scm_c_eval_string ("gnc:invoice-report-create");
     g_return_val_if_fail (scm_is_procedure (func), NULL);
 
     arg = SWIG_NewPointerObj(invoice, SWIG_TypeQuery("_p__gncInvoice"), 0);
-    arg2 = scm_from_utf8_string(reportname);
+    arg2 = scm_from_utf8_string (report_guid);
     args = scm_cons2 (arg, arg2, args);
 
     /* scm_gc_protect_object(func); */
@@ -810,7 +816,7 @@ gnc_invoice_window_print_invoice(GtkWindow *parent, GncInvoice *invoice)
         reportPage = gnc_plugin_page_report_new (report_id);
         gnc_main_window_open_page (GNC_MAIN_WINDOW (parent), reportPage);
     }
-
+    g_free (report_guid);
     return reportPage;
 }
 
diff --git a/gnucash/gnome/gnc-plugin-business.c b/gnucash/gnome/gnc-plugin-business.c
index b877be8dc..b2196535a 100644
--- a/gnucash/gnome/gnc-plugin-business.c
+++ b/gnucash/gnome/gnc-plugin-business.c
@@ -112,7 +112,6 @@ static void bind_extra_toolbuttons_visibility (GncMainWindow *mainwindow);
 #define PLUGIN_UI_FILENAME  "gnc-plugin-business.ui"
 
 #define GNC_PREF_EXTRA_TOOLBUTTONS "enable-toolbuttons"
-#define GNC_PREF_INV_PRINT_RPT     "invoice-printreport"
 
 /** This variable maintains a pointer to the last window where a
  *  Business command was executed.  It is used to determine where new
@@ -1050,27 +1049,3 @@ gnc_plugin_business_add_to_window (GncPlugin *plugin,
                       G_CALLBACK(gnc_plugin_business_main_window_menu_changed),
                       plugin);
 }
-
-static const char* invoice_printreport_values[] =
-{
-    /* The list below are the guids of reports that can
-     * be used to print an invoice.
-     * Important: this list must be kept in sync with the one at the end
-     * of business-prefs.glade
-     */
-    "5123a759ceb9483abf2182d01c140e8d", // "Printable Invoice"
-    "0769e242be474010b4acf264a5512e6e", // "Tax Invoice"
-    "67112f318bef4fc496bdc27d106bbda4", // "Easy Invoice"
-    "3ce293441e894423a2425d7a22dd1ac6", // "Fancy Invoice"
-    NULL
-};
-
-const char *
-gnc_plugin_business_get_invoice_printreport (void)
-{
-    int value = gnc_prefs_get_int (GNC_PREFS_GROUP_INVOICE, GNC_PREF_INV_PRINT_RPT);
-    if (value >= 0 && value < 4)
-        return invoice_printreport_values[value];
-    else
-        return NULL;
-}
diff --git a/gnucash/gnome/gnc-plugin-business.h b/gnucash/gnome/gnc-plugin-business.h
index 9ded6cf98..73027cd58 100644
--- a/gnucash/gnome/gnc-plugin-business.h
+++ b/gnucash/gnome/gnc-plugin-business.h
@@ -64,8 +64,6 @@ void gnc_invoice_remind_bills_due (GtkWindow *parent);
 void gnc_invoice_remind_invoices_due (GtkWindow *parent);
 void gnc_invoice_remind_bills_due_cb (void);
 void gnc_invoice_remind_invoices_due_cb (void);
-const char *gnc_plugin_business_get_invoice_printreport(void);
-
 
 void gnc_plugin_business_split_reg_ui_update (GncPluginPage *plugin_page);
 
diff --git a/gnucash/gtkbuilder/business-options-gnome.glade b/gnucash/gtkbuilder/business-options-gnome.glade
index 23dd3ee34..00fd7f999 100644
--- a/gnucash/gtkbuilder/business-options-gnome.glade
+++ b/gnucash/gtkbuilder/business-options-gnome.glade
@@ -2,6 +2,56 @@
 <!-- Generated with glade 3.38.2 -->
 <interface>
   <requires lib="gtk+" version="3.22"/>
+  <object class="GtkListStore" id="liststore_print_invoice">
+    <columns>
+      <!-- column-name title -->
+      <column type="gchararray"/>
+      <!-- column-name guid -->
+      <column type="gchararray"/>
+      <!-- column-name missing -->
+      <column type="gboolean"/>
+    </columns>
+  </object>
+  <object class="GtkWindow" id="dummy_toplevel_window2">
+    <property name="can-focus">False</property>
+    <child>
+      <object class="GtkBox" id="invoice_report_hbox">
+        <property name="visible">True</property>
+        <property name="can-focus">False</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkComboBox" id="invoice_report_combo">
+            <property name="visible">True</property>
+            <property name="can-focus">False</property>
+            <property name="model">liststore_print_invoice</property>
+            <child>
+              <object class="GtkCellRendererText" id="cell_renderer_text"/>
+              <attributes>
+                <attribute name="text">0</attribute>
+              </attributes>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkImage" id="invoice_warning_image">
+            <property name="can-focus">False</property>
+            <property name="no-show-all">True</property>
+            <property name="icon-name">dialog-warning</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
   <object class="GtkListStore" id="taxtable_store">
     <columns>
       <!-- column-name taxtable_name -->
diff --git a/gnucash/report/report-core.scm b/gnucash/report/report-core.scm
index f215836f1..c12bfdbd8 100644
--- a/gnucash/report/report-core.scm
+++ b/gnucash/report/report-core.scm
@@ -32,6 +32,7 @@
 (use-modules (gnucash gnome-utils))
 (use-modules (ice-9 match))
 (use-modules (srfi srfi-1))
+(use-modules (srfi srfi-2))
 (use-modules (srfi srfi-9))
 (use-modules (srfi srfi-26))
 (use-modules (gnucash report report-register-hooks))
@@ -46,6 +47,7 @@
 (export <report>)
 (export gnc:all-report-template-guids)
 (export gnc:custom-report-template-guids)
+(export gnc:custom-report-invoice-template-guids)
 (export gnc:define-report)
 (export gnc:delete-report)
 (export gnc:find-report-template)
@@ -75,6 +77,7 @@
 (export gnc:report-export-thunk)
 (export gnc:report-export-types)
 (export gnc:report-id)
+(export gnc:report-is-invoice-report?)
 (export gnc:report-menu-name)
 (export gnc:report-name)
 (export gnc:report-needs-save?)
@@ -169,7 +172,7 @@
   (make-new-record-template version name report-guid parent-type options-generator
                             options-cleanup-cb options-changed-cb
                             renderer in-menu? menu-path menu-name
-                            menu-tip export-types export-thunk)
+                            menu-tip hook export-types export-thunk)
   report-template?
   (version report-template-version)
   (report-guid report-template-report-guid report-template-set-report-guid!)
@@ -183,11 +186,12 @@
   (menu-path report-template-menu-path)
   (menu-name report-template-menu-name)
   (menu-tip report-template-menu-tip)
+  (hook report-template-hook)
   (export-types report-template-export-types)
   (export-thunk report-template-export-thunk))
 
 (define (make-report-template)
-  (make-new-record-template #f #f #f #f #f #f #f #f #t #f #f #f #f #f))
+  (make-new-record-template #f #f #f #f #f #f #f #f #t #f #f #f #f #f #f))
 (define gnc:report-template-version report-template-version)
 (define gnc:report-template-report-guid report-template-report-guid)
 (define gnc:report-template-set-report-guid! report-template-set-report-guid!)
@@ -203,6 +207,7 @@
 (define gnc:report-template-menu-path report-template-menu-path)
 (define gnc:report-template-menu-name report-template-menu-name)
 (define gnc:report-template-menu-tip report-template-menu-tip)
+(define gnc:report-template-hook report-template-hook)
 (define gnc:report-template-export-types report-template-export-types)
 (define gnc:report-template-export-thunk report-template-export-thunk)
 
@@ -450,6 +455,21 @@ not found.")))
 (define (gnc:custom-report-template-guids)
   (map car (gnc:custom-report-templates-list)))
 
+(define (gnc:report-is-invoice-report? guid)
+  (let ((tmpl (gnc:find-report-template guid)))
+    (cond
+     ((not tmpl) #f)
+     ((eq? (gnc:report-template-hook tmpl) 'invoice) #t)
+     (else (and-let* ((type (gnc:report-template-parent-type tmpl))
+                      (template (gnc:find-report-template type)))
+             (eq? (gnc:report-template-hook template) 'invoice))))))
+
+;; return a list of the invoice report template guids.
+(define (gnc:custom-report-invoice-template-guids)
+  (hash-fold
+   (lambda (k v p) (if (gnc:report-is-invoice-report? k) (cons k p) p))
+    '() *gnc:_report-templates_*))
+
 (define (gnc:find-report-template guid)
   (hash-ref *gnc:_report-templates_* guid))
 
diff --git a/gnucash/report/reports/standard/invoice.scm b/gnucash/report/reports/standard/invoice.scm
index 20d0353b4..f04560313 100644
--- a/gnucash/report/reports/standard/invoice.scm
+++ b/gnucash/report/reports/standard/invoice.scm
@@ -851,6 +851,7 @@ for styling the invoice. Please see the exported report for the CSS class names.
  'menu-path (list gnc:menuname-business-reports)
  'options-generator (lambda () (options-generator 'invoice))
  'renderer reg-renderer
+ 'hook 'invoice
  'in-menu? #t)
 
 (gnc:define-report
@@ -860,6 +861,7 @@ for styling the invoice. Please see the exported report for the CSS class names.
  'menu-path (list gnc:menuname-business-reports)
  'options-generator (lambda () (options-generator 'easy-invoice))
  'renderer reg-renderer
+ 'hook 'invoice
  'in-menu? #t)
 
 (gnc:define-report
@@ -869,5 +871,6 @@ for styling the invoice. Please see the exported report for the CSS class names.
  'menu-path (list gnc:menuname-business-reports)
  'options-generator (lambda () (options-generator 'fancy-invoice))
  'renderer reg-renderer
+ 'hook 'invoice
  'in-menu? #t)
 
diff --git a/gnucash/report/reports/standard/taxinvoice.scm b/gnucash/report/reports/standard/taxinvoice.scm
index dbe2f35e2..085f06cb2 100644
--- a/gnucash/report/reports/standard/taxinvoice.scm
+++ b/gnucash/report/reports/standard/taxinvoice.scm
@@ -304,6 +304,7 @@
   'menu-name (N_ "Tax Invoice")
   'menu-tip (N_ "Display a customer invoice with tax columns (using eguile template)")
   'menu-path (list gnc:menuname-business-reports)
+  'hook 'invoice
   'options-generator options-generator
   'renderer report-renderer)
 
@@ -330,5 +331,6 @@
   'menu-name (N_ "Australian Tax Invoice")
   'menu-tip (N_ "Display an Australian customer invoice with tax columns (using eguile template)")
   'menu-path (list gnc:menuname-business-reports)
+  'hook 'invoice
   'options-generator au-tax-options-generator
   'renderer report-renderer)
diff --git a/libgnucash/engine/gnc-option-uitype.hpp b/libgnucash/engine/gnc-option-uitype.hpp
index 83b0f9347..7f856ef5d 100644
--- a/libgnucash/engine/gnc-option-uitype.hpp
+++ b/libgnucash/engine/gnc-option-uitype.hpp
@@ -66,6 +66,7 @@ enum class GncOptionUIType : unsigned int
     INVOICE,
     JOB,
     TAX_TABLE,
+    INV_REPORT,
     QUERY,
     REPORT_PLACEMENT,
     MAX_VALUE,  //Nake sure this one is always last
diff --git a/libgnucash/engine/gnc-optiondb.cpp b/libgnucash/engine/gnc-optiondb.cpp
index 9251618ca..a3bd62990 100644
--- a/libgnucash/engine/gnc-optiondb.cpp
+++ b/libgnucash/engine/gnc-optiondb.cpp
@@ -883,6 +883,16 @@ gnc_register_taxtable_option(GncOptionDB* db, const char* section,
     db->register_option(section, std::move(option));
 }
 
+void
+gnc_register_invoice_print_report_option(GncOptionDB* db, const char* section,
+                                         const char* name, const char* key,
+                                         const char* doc_string, std::string value)
+{
+    GncOption option{section, name, key, doc_string,
+                     value, GncOptionUIType::INV_REPORT};
+    db->register_option(section, std::move(option));
+}
+
 void
 gnc_register_counter_option(GncOptionDB* db, const char* section,
                             const char* name, const char* key,
@@ -1266,13 +1276,16 @@ gnc_option_db_book_options(GncOptionDB* odb)
     gnc_register_string_option(odb, business_section, N_("Company ID"), "c5",
                                N_("The ID for your company (eg 'Tax-ID: 00-000000)."),
                                empty_string);
-
+    gnc_register_invoice_print_report_option(odb, business_section,
+                                 OPTION_NAME_DEFAULT_INVOICE_REPORT, "e",
+                                 N_("The invoice report to be used for printing."),
+                                 empty_string);
     gnc_register_taxtable_option(odb, business_section,
-                                 N_("Default Customer TaxTable"), "e",
+                                 N_("Default Customer TaxTable"), "f1",
                                  N_("The default tax table to apply to customers."),
                                  nullptr);
     gnc_register_taxtable_option(odb, business_section,
-                                 N_("Default Vendor TaxTable"), "f",
+                                 N_("Default Vendor TaxTable"), "f2",
                                  N_("The default tax table to apply to vendors."),
                                  nullptr);
     gnc_register_dateformat_option(odb, business_section,
diff --git a/libgnucash/engine/gnc-optiondb.hpp b/libgnucash/engine/gnc-optiondb.hpp
index 7147001f3..6a587d41e 100644
--- a/libgnucash/engine/gnc-optiondb.hpp
+++ b/libgnucash/engine/gnc-optiondb.hpp
@@ -713,6 +713,31 @@ inline void gnc_register_taxtable_option(const GncOptionDBPtr& db,
                                  doc_string, value);
 }
 
+/**
+ * Create a new print report option and register it in the options database.
+ *
+ * @param db A GncOptionDB* for calling from C. Caller retains ownership.
+ * @param section The database section for the option.
+ * @param name The option name.
+ * @param doc_string A description of the option. This will be used in tooltips and should be marked for translation.
+ * @param value The initial and default value for the option.
+ */
+void gnc_register_invoice_print_report_option(GncOptionDB* db, const char* section,
+                                              const char* name, const char* key,
+                                              const char* doc_string, std::string value);
+
+/**
+ * As above but takes a const GncOptionDBPtr& (const std::unique_ptr<GncOptionDB>&) for calling from C++.
+ */
+inline void gnc_register_invoice_print_report_option(const GncOptionDBPtr& db,
+                                                     const char* section, const char* name,
+                                                     const char* key, const char* doc_string,
+                                                     std::string value)
+{
+    gnc_register_invoice_print_report_option(db.get(), section, name, key,
+                                             doc_string, value);
+}
+
 /**
  * Create a new counter option and register it in the options database.
  *
diff --git a/libgnucash/engine/qofbook.cpp b/libgnucash/engine/qofbook.cpp
index 5c4b8a8fb..d2a9a76ec 100644
--- a/libgnucash/engine/qofbook.cpp
+++ b/libgnucash/engine/qofbook.cpp
@@ -1014,6 +1014,90 @@ qof_book_option_num_autoreadonly_changed_cb (GObject *gobject,
     book->cached_num_days_autoreadonly_isvalid = FALSE;
 }
 
+static KvpValue*
+get_option_default_invoice_report_value (QofBook *book)
+{
+    KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE(book));
+    return root->get_slot ({KVP_OPTION_PATH,
+                            OPTION_SECTION_BUSINESS,
+                            OPTION_NAME_DEFAULT_INVOICE_REPORT});
+}
+
+void
+qof_book_set_default_invoice_report (QofBook *book, const gchar *guid,
+                                     const gchar *name)
+{
+    const gchar *existing_guid_name = nullptr;
+    gchar *new_guid_name;
+
+    if (!guid)
+        return;
+
+    KvpValue *value = get_option_default_invoice_report_value (book);
+
+    if (value)
+        existing_guid_name = {value->get<const char*>()};
+
+    new_guid_name = g_strconcat (guid, "/", name, nullptr);
+
+    if (g_strcmp0 (existing_guid_name, new_guid_name) != 0)
+    {
+        auto value = new KvpValue {g_strdup(new_guid_name)};
+        KvpFrame *root = qof_instance_get_slots (QOF_INSTANCE(book));
+        qof_book_begin_edit (book);
+        delete root->set_path ({KVP_OPTION_PATH,
+                                OPTION_SECTION_BUSINESS,
+                                OPTION_NAME_DEFAULT_INVOICE_REPORT}, value);
+        qof_instance_set_dirty (QOF_INSTANCE(book));
+        qof_book_commit_edit (book);
+    }
+    g_free (new_guid_name);
+}
+
+gchar *
+qof_book_get_default_invoice_report_guid (const QofBook *book)
+{
+    KvpValue *value = get_option_default_invoice_report_value (const_cast<QofBook*>(book));
+    gchar *report_guid = nullptr;
+
+    if (value)
+    {
+        auto str {value->get<const char*>()};
+        auto ptr = strchr (str, '/');
+        if (ptr)
+        {
+            if (ptr - str == GUID_ENCODING_LENGTH)
+            {
+                if (strlen (str) > GUID_ENCODING_LENGTH + 1)
+                    report_guid = g_strndup (&str[0], GUID_ENCODING_LENGTH);
+            }
+        }
+    }
+    return report_guid;
+}
+
+gchar *
+qof_book_get_default_invoice_report_name (const QofBook *book)
+{
+    KvpValue *value = get_option_default_invoice_report_value (const_cast<QofBook*>(book));
+    gchar *report_name = nullptr;
+
+    if (value)
+    {
+        auto str {value->get<const char*>()};
+        auto ptr = strchr (str, '/');
+        if (ptr)
+        {
+            if (ptr - str == GUID_ENCODING_LENGTH)
+            {
+                if (strlen (str) > GUID_ENCODING_LENGTH + 1)
+                    report_name = g_strdup (&str[GUID_ENCODING_LENGTH + 1]);
+            }
+        }
+    }
+    return report_name;
+}
+
 /* Note: this will fail if the book slots we're looking for here are flattened at some point !
  * When that happens, this function can be removed. */
 static Path opt_name_to_path (const char* opt_name)
diff --git a/libgnucash/engine/qofbook.h b/libgnucash/engine/qofbook.h
index 3dba3e957..3ab6c0953 100644
--- a/libgnucash/engine/qofbook.h
+++ b/libgnucash/engine/qofbook.h
@@ -289,6 +289,22 @@ gint qof_book_get_num_days_autoreadonly (const QofBook *book);
  * g_date_free() the object afterwards. */
 GDate* qof_book_get_autoreadonly_gdate (const QofBook *book);
 
+/** Save the Invoice Report name / guid to be used as the default for printing
+ *  Invoices
+ */
+void qof_book_set_default_invoice_report (QofBook *book, const gchar *guid,
+                                          const gchar *name);
+
+/** Get the guid of the Invoice Report to be used as the default for printing
+ *  Invoices
+ */
+gchar * qof_book_get_default_invoice_report_guid (const QofBook *book);
+
+/** Get the name of the Invoice Report to be used as the default for printing
+ *  Invoices
+ */
+gchar * qof_book_get_default_invoice_report_name (const QofBook *book);
+
 /** Returns TRUE if this book uses split action field as the 'Num' field, FALSE
  *  if it uses transaction number field */
 gboolean qof_book_use_split_action_for_num_field (const QofBook *book);
diff --git a/libgnucash/engine/qofbookslots.h b/libgnucash/engine/qofbookslots.h
index 4ed5fd8c2..ea0e32bcd 100644
--- a/libgnucash/engine/qofbookslots.h
+++ b/libgnucash/engine/qofbookslots.h
@@ -69,6 +69,10 @@
 #define OPTION_SECTION_BUDGETING       N_("Budgeting")
 #define OPTION_NAME_DEFAULT_BUDGET     N_("Default Budget")
 
+#define OPTION_SECTION_BUSINESS        N_("Business")
+
+#define OPTION_NAME_DEFAULT_INVOICE_REPORT  N_("Default Invoice Report")
+
 /** @} */
 
 /* For the grep-happy:



Summary of changes:
 gnucash/gnome-utils/CMakeLists.txt              |   2 +
 gnucash/gnome-utils/gnc-report-combo.c          | 551 ++++++++++++++++++++++++
 gnucash/gnome-utils/gnc-report-combo.h          | 123 ++++++
 gnucash/gnome/business-gnome-utils.c            | 117 +++++
 gnucash/gnome/business-gnome-utils.h            |  38 ++
 gnucash/gnome/business-options-gnome.cpp        |  51 +++
 gnucash/gnome/dialog-custom-report.c            |  18 +
 gnucash/gnome/dialog-invoice.c                  | 193 ++++++++-
 gnucash/gnome/gnc-plugin-business.c             |  25 --
 gnucash/gnome/gnc-plugin-business.h             |   2 -
 gnucash/gschemas/pref_transformations.xml       |  12 +
 gnucash/gtkbuilder/business-options-gnome.glade |  12 +-
 gnucash/gtkbuilder/business-prefs.glade         |  66 +--
 gnucash/gtkbuilder/dialog-invoice.glade         | 127 ++++++
 gnucash/report/report-core.scm                  |  24 +-
 gnucash/report/reports/standard/invoice.scm     |   3 +
 gnucash/report/reports/standard/taxinvoice.scm  |   2 +
 libgnucash/engine/gnc-option-uitype.hpp         |   1 +
 libgnucash/engine/gnc-optiondb.cpp              |  23 +-
 libgnucash/engine/gnc-optiondb.hpp              |  25 ++
 libgnucash/engine/qofbook.cpp                   |  99 +++++
 libgnucash/engine/qofbook.h                     |  21 +
 libgnucash/engine/qofbookslots.h                |   5 +
 po/POTFILES.in                                  |   1 +
 24 files changed, 1442 insertions(+), 99 deletions(-)
 create mode 100644 gnucash/gnome-utils/gnc-report-combo.c
 create mode 100644 gnucash/gnome-utils/gnc-report-combo.h



More information about the gnucash-changes mailing list