gnucash master: Multiple changes pushed

Robert Fewell bobit at code.gnucash.org
Fri May 29 05:43:50 EDT 2020


Updated	 via  https://github.com/Gnucash/gnucash/commit/6e834940 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6b3f9bd8 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/ac029664 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/553b422e (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e2fac366 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/30f21763 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/2f9be875 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/69aeacb6 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/1b8cad00 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/f31749f4 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/785a6a8f (commit)
	 via  https://github.com/Gnucash/gnucash/commit/260c7b32 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/8723184f (commit)
	 via  https://github.com/Gnucash/gnucash/commit/e81e95ee (commit)
	 via  https://github.com/Gnucash/gnucash/commit/07d46d5d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/7e05d869 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/d282e645 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/1884ae20 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/282e456f (commit)
	 via  https://github.com/Gnucash/gnucash/commit/8596763d (commit)
	 via  https://github.com/Gnucash/gnucash/commit/456c3494 (commit)
	from  https://github.com/Gnucash/gnucash/commit/5091d7a6 (commit)



commit 6e83494008347d172bc567c76f58a4e60c323dad
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 17:00:47 2020 +0100

    Add opening the transaction association from register sheet.
    
    Add option to open transaction association by clicking on the
    association in the association cell.

diff --git a/gnucash/gnome/gnc-split-reg.c b/gnucash/gnome/gnc-split-reg.c
index eae824c27..926d6e92d 100644
--- a/gnucash/gnome/gnc-split-reg.c
+++ b/gnucash/gnome/gnc-split-reg.c
@@ -119,6 +119,7 @@ void gsr_default_reverse_txn_handler ( GNCSplitReg *w, gpointer ud );
 void gsr_default_associate_handler ( GNCSplitReg *w );
 void gsr_default_associate_open_handler ( GNCSplitReg *w );
 void gsr_default_associate_remove_handler ( GNCSplitReg *w );
+static void gsr_default_associate_from_sheet_handler ( GNCSplitReg *w );
 
 static void gsr_emit_simple_signal       ( GNCSplitReg *gsr, const char *sigName );
 static void gsr_emit_help_changed        ( GnucashRegister *reg, gpointer user_data );
@@ -578,6 +579,11 @@ gsr_create_table( GNCSplitReg *gsr )
 
     gtk_box_pack_start (GTK_BOX (gsr), GTK_WIDGET(gsr->reg), TRUE, TRUE, 0);
     gnucash_sheet_set_window (gnucash_register_get_sheet (gsr->reg), gsr->window);
+
+    // setup the callback for when the associate cell clicked on
+    gnucash_register_set_open_assoc_cb (gsr->reg,
+        (GFunc)gsr_default_associate_from_sheet_handler, gsr);
+
     gtk_widget_show ( GTK_WIDGET(gsr->reg) );
     g_signal_connect (gsr->reg, "activate_cursor",
                       G_CALLBACK(gnc_split_reg_record_cb), gsr);
@@ -1435,6 +1441,31 @@ gsr_default_associate_remove_handler (GNCSplitReg *gsr)
     xaccTransSetAssociation (trans, "");
 }
 
+static void
+gsr_default_associate_from_sheet_handler (GNCSplitReg *gsr)
+{
+    CursorClass cursor_class;
+    SplitRegister *reg = gnc_ledger_display_get_split_register (gsr->ledger);
+    Transaction *trans;
+    Split *split;
+    gchar *uri = NULL;
+
+    /* get the current split based on cursor position */
+    split = gnc_split_register_get_current_split (reg);
+    if (!split)
+        return;
+
+    trans = xaccSplitGetParent (split);
+
+    // fix an earlier error when storing relative paths before version 3.5
+    uri = gnc_assoc_convert_trans_associate_uri (trans, gsr->read_only);
+
+    if (uri)
+        gnc_assoc_open_uri (GTK_WINDOW (gsr->window), uri);
+
+    g_free (uri);
+}
+
 void
 gsr_default_delete_handler( GNCSplitReg *gsr, gpointer data )
 {
diff --git a/gnucash/register/register-gnome/gnucash-register.c b/gnucash/register/register-gnome/gnucash-register.c
index 37e892098..2e89f5692 100644
--- a/gnucash/register/register-gnome/gnucash-register.c
+++ b/gnucash/register/register-gnome/gnucash-register.c
@@ -644,3 +644,18 @@ GnucashSheet *gnucash_register_get_sheet (GnucashRegister *reg)
     return GNUCASH_SHEET(reg->sheet);
 }
 
+
+void
+gnucash_register_set_open_assoc_cb (GnucashRegister *reg,
+                                    GFunc cb, gpointer cb_data)
+{
+    GnucashSheet *sheet;
+
+    if (!reg || !reg->sheet)
+        return;
+    sheet = GNUCASH_SHEET(reg->sheet);
+    sheet->open_assoc_cb = cb;
+    sheet->open_assoc_cb_data = cb_data;
+}
+
+
diff --git a/gnucash/register/register-gnome/gnucash-register.h b/gnucash/register/register-gnome/gnucash-register.h
index 968dbd8cf..adb21d87b 100644
--- a/gnucash/register/register-gnome/gnucash-register.h
+++ b/gnucash/register/register-gnome/gnucash-register.h
@@ -81,6 +81,8 @@ void gnucash_register_paste_clipboard (GnucashRegister *reg);
 void gnucash_register_refresh_from_prefs (GnucashRegister *reg);
 void gnucash_register_set_moved_cb (GnucashRegister *reg,
                                     GFunc cb, gpointer cb_data);
+void gnucash_register_set_open_assoc_cb (GnucashRegister *reg,
+                                         GFunc cb, gpointer cb_data);
 
 GnucashSheet *gnucash_register_get_sheet (GnucashRegister *reg);
 void gnucash_register_reset_sheet_layout (GnucashRegister *reg);
diff --git a/gnucash/register/register-gnome/gnucash-sheet.c b/gnucash/register/register-gnome/gnucash-sheet.c
index b1ea6d2b8..fb688994a 100644
--- a/gnucash/register/register-gnome/gnucash-sheet.c
+++ b/gnucash/register/register-gnome/gnucash-sheet.c
@@ -1514,6 +1514,13 @@ gnucash_sheet_button_press_event (GtkWidget *widget, GdkEventButton *event)
 //FIXME does something need to be done if changed_cells is true or false ?
     gnucash_sheet_cursor_move (sheet, new_virt_loc);
 
+    // if clicked in associate cell, run call back
+    if (g_strcmp0 (gnc_table_get_cell_name (table, new_virt_loc), ASSOC_CELL) == 0)
+    {
+        if (sheet->open_assoc_cb)
+            (sheet->open_assoc_cb)(sheet->open_assoc_cb_data, NULL);
+    }
+
     if (button_1)
         gnucash_sheet_check_grab (sheet);
 
diff --git a/gnucash/register/register-gnome/gnucash-sheetP.h b/gnucash/register/register-gnome/gnucash-sheetP.h
index fa3370abb..cd132bdbb 100644
--- a/gnucash/register/register-gnome/gnucash-sheetP.h
+++ b/gnucash/register/register-gnome/gnucash-sheetP.h
@@ -95,6 +95,9 @@ struct _GnucashSheet
     GFunc moved_cb;
     gpointer moved_cb_data;
 
+    GFunc open_assoc_cb;
+    gpointer open_assoc_cb_data;
+
     guint shift_state;
     guint keyval_state;
     gboolean direct_update_cell; /** Indicates that this cell has special operation keys. */

commit 6b3f9bd80e4017afba23a664a1b0cfe93ee9addd
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:59:32 2020 +0100

    Bug 796531 - Display the Transaction Association link
    
    Displays the Transaction Association link in the Transaction Report and
    when selected will open the association outside of Gnucash as it
    currently does when opened from the register.

diff --git a/gnucash/gnome/top-level.c b/gnucash/gnome/top-level.c
index 2d3023c86..f7fdf34fa 100644
--- a/gnucash/gnome/top-level.c
+++ b/gnucash/gnome/top-level.c
@@ -33,6 +33,7 @@
 #include "business-urls.h"
 #include "combocell.h"
 #include "dialog-account.h"
+#include "dialog-assoc.h"
 #include "dialog-commodity.h"
 #include "dialog-invoice.h"
 #include "dialog-preferences.h"
@@ -166,6 +167,16 @@ gnc_html_register_url_cb (const char *location, const char *label,
         }
     }
 
+    else if (strncmp ("trans-association-guid=", location, strlen ("trans-association-guid=")) == 0)
+    {
+        if (!validate_type("trans-association-guid=", location, GNC_ID_TRANS, result, &guid, &entity))
+            return FALSE;
+
+        trans = (Transaction *) entity;
+        gnc_assoc_open_uri (gnc_ui_get_gtk_window (GTK_WIDGET(result->parent)), xaccTransGetAssociation (trans));
+        return TRUE;
+    }
+
     else if (strncmp ("split-guid=", location, strlen ("split-guid=")) == 0)
     {
         if (!validate_type("split-guid=", location, GNC_ID_SPLIT, result, &guid, &entity))
@@ -174,6 +185,7 @@ gnc_html_register_url_cb (const char *location, const char *label,
         split = (Split *) entity;
         account = xaccSplitGetAccount(split);
     }
+
     else
     {
         result->error_message =
diff --git a/gnucash/report/html-utilities.scm b/gnucash/report/html-utilities.scm
index de6bf81a8..1822c3598 100644
--- a/gnucash/report/html-utilities.scm
+++ b/gnucash/report/html-utilities.scm
@@ -44,6 +44,9 @@
 (define (gnc:transaction-anchor-text trans)
   (gnc:register-guid "trans-guid=" (gncTransGetGUID trans)))
 
+(define (gnc:transaction-association-anchor-text trans)
+  (gnc:register-guid "trans-association-guid=" (gncTransGetGUID trans)))
+
 (define (gnc:report-anchor-text report-id)
   (gnc-build-url URL-TYPE-REPORT
 		      (string-append "id=" (number->string report-id))
@@ -149,6 +152,11 @@
                        (gnc:transaction-anchor-text trans)
                        text)))
 
+(define (gnc:html-transaction-association-anchor trans text)
+  (gnc:make-html-text (gnc:html-markup-anchor
+                       (gnc:transaction-association-anchor-text trans)
+                       text)))
+
 (define (gnc:html-price-anchor price value)
   (gnc:make-html-text (if price
                           (gnc:html-markup-anchor
diff --git a/gnucash/report/report.scm b/gnucash/report/report.scm
index 174ba9d6a..b48b3d7d5 100644
--- a/gnucash/report/report.scm
+++ b/gnucash/report/report.scm
@@ -86,11 +86,13 @@
 (export gnc:account-anchor-text)
 (export gnc:split-anchor-text)
 (export gnc:transaction-anchor-text)
+(export gnc:transaction-association-anchor-text)
 (export gnc:report-anchor-text)
 (export gnc:make-report-anchor)
 (export gnc:html-account-anchor)
 (export gnc:html-split-anchor)
 (export gnc:html-transaction-anchor)
+(export gnc:html-transaction-association-anchor)
 (export gnc:html-price-anchor)
 (export gnc:customer-anchor-text)
 (export gnc:job-anchor-text)
diff --git a/gnucash/report/trep-engine.scm b/gnucash/report/trep-engine.scm
index 5d4e30dac..06089707f 100644
--- a/gnucash/report/trep-engine.scm
+++ b/gnucash/report/trep-engine.scm
@@ -945,6 +945,7 @@ be excluded from periodic reporting.")
       (list (N_ "Use Full Other Account Name")  "i"  (_ "Display the full account name?") #f)
       (list (N_ "Other Account Code")           "j"  (_ "Display the other account code?") #f)
       (list (N_ "Shares")                       "k"  (_ "Display the number of shares?") #f)
+      (list (N_ "Association")                  "l5" (_ "Display the transaction association") #f)
       (list (N_ "Price")                        "l"  (_ "Display the shares price?") #f)
       ;; note the "Amount" multichoice option in between here
       (list optname-grid                        "m5" (_ "Display a subtotal summary table.") #f)
@@ -1070,6 +1071,7 @@ be excluded from periodic reporting.")
                      (opt-val gnc:pagename-display (N_ "Other Account Name"))))
           (cons 'shares (opt-val gnc:pagename-display (N_ "Shares")))
           (cons 'price (opt-val gnc:pagename-display (N_ "Price")))
+          (cons 'association (opt-val gnc:pagename-display "Association"))
           (cons 'amount-single (eq? amount-setting 'single))
           (cons 'amount-double (eq? amount-setting 'double))
           (cons 'common-currency (opt-val gnc:pagename-general optname-common-currency))
@@ -1235,6 +1237,21 @@ be excluded from periodic reporting.")
                                   "number-cell"
                                   (xaccSplitGetAmount split)))))
 
+               (add-if (column-uses? 'association)
+                       (vector ""
+                               (lambda (split transaction-row?)
+                                 (let ((url (xaccTransGetAssociation
+                                             (xaccSplitGetParent split))))
+                                   (and (not (string-null? url))
+                                        (gnc:make-html-table-cell/markup
+                                         "text-cell"
+                                         (if opt-use-links?
+                                             (gnc:html-transaction-association-anchor
+                                              (xaccSplitGetParent split)
+                                              ;; Translators: 'A' is short for Association
+                                              (_ "A"))
+                                             (_ "A"))))))))
+
                (add-if (column-uses? 'price)
                        (vector (_ "Price")
                                (lambda (split transaction-row?)

commit ac029664260491cd380887e8c268aaea7f08afc5
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:58:51 2020 +0100

    Add a total entries label to the Association dialogue

diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index 603f2275b..51a239d7b 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -69,6 +69,7 @@ typedef struct
     GtkWidget    *window;
     GtkWidget    *view;
     GtkWidget    *path_head_label;
+    GtkWidget    *total_entries_label;
     gchar        *path_head;
     gboolean      is_list_trans;
     gboolean      book_ro;
@@ -470,6 +471,22 @@ update_model_with_changes (AssocDialog *assoc_dialog, GtkTreeIter *iter, const g
     g_free (scheme);
 }
 
+static void
+update_total_entries (AssocDialog *assoc_dialog)
+{
+    gint entries = gtk_tree_model_iter_n_children (GTK_TREE_MODEL(assoc_dialog->model), NULL);
+
+    if (entries > 0)
+    {
+        gchar *total = g_strdup_printf ("%s %d", _("Total Entries"), entries);
+        gtk_label_set_text (GTK_LABEL(assoc_dialog->total_entries_label), total);
+        gtk_widget_show (assoc_dialog->total_entries_label);
+        g_free (total);
+    }
+    else
+        gtk_widget_hide (assoc_dialog->total_entries_label);
+}
+
 static void
 row_selected_bus_cb (GtkTreeView *view, GtkTreePath *path,
                      GtkTreeViewColumn  *col, gpointer user_data)
@@ -527,6 +544,7 @@ row_selected_bus_cb (GtkTreeView *view, GtkTreePath *path,
                 // update the asooc parts for invoice window if present
                 gnc_invoice_update_assoc_for_window (invoice, ret_uri);
                 gtk_list_store_remove (GTK_LIST_STORE(assoc_dialog->model), &iter);
+                update_total_entries (assoc_dialog);
             }
             else // update uri
             {
@@ -608,7 +626,10 @@ row_selected_trans_cb (GtkTreeView *view, GtkTreePath *path,
         {
             xaccTransSetAssociation (trans, ret_uri);
             if (g_strcmp0 (ret_uri, "") == 0) // deleted uri
+            {
                 gtk_list_store_remove (GTK_LIST_STORE(assoc_dialog->model), &iter);
+                update_total_entries (assoc_dialog);
+            }
             else // updated uri
                 update_model_with_changes (assoc_dialog, &iter, ret_uri);
         }
@@ -738,6 +759,8 @@ get_bus_info (AssocDialog *assoc_dialog)
     qof_collection_foreach (qof_book_get_collection (book, GNC_ID_INVOICE),
                             add_bus_info_to_model, assoc_dialog);
 
+    update_total_entries (assoc_dialog);
+
     /* reconnect the model to the treeview */
     gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), assoc_dialog->model);
     g_object_unref (G_OBJECT(assoc_dialog->model));
@@ -762,6 +785,8 @@ get_trans_info (AssocDialog *assoc_dialog)
     qof_collection_foreach (qof_book_get_collection (book, GNC_ID_TRANS),
                             add_trans_info_to_model, assoc_dialog);
 
+    update_total_entries (assoc_dialog);
+
     /* reconnect the model to the treeview */
     gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), assoc_dialog->model);
     g_object_unref (G_OBJECT(assoc_dialog->model));
@@ -846,7 +871,7 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
 
     assoc_dialog->view = GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));
     assoc_dialog->path_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "path-head"));
-
+    assoc_dialog->total_entries_label = GTK_WIDGET(gtk_builder_get_object (builder, "total_entries_label"));
     assoc_dialog->path_head = gnc_assoc_get_path_head ();
 
     // display path head text and test if present
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index 3671f6a25..17adead41 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -706,6 +706,22 @@
             <property name="position">3</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkLabel" id="total_entries_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">start</property>
+            <property name="margin_left">6</property>
+            <property name="margin_right">6</property>
+            <property name="margin_top">3</property>
+            <property name="margin_bottom">3</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
         <child>
           <object class="GtkLabel" id="help_label">
             <property name="visible">True</property>
@@ -719,7 +735,7 @@ column, Association column to open the Association or Available to update</prope
             <property name="expand">False</property>
             <property name="fill">False</property>
             <property name="padding">5</property>
-            <property name="position">4</property>
+            <property name="position">5</property>
           </packing>
         </child>
       </object>

commit 553b422e198045c0992138895d18293bb6b9a2ae
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:53:26 2020 +0100

    Change Associations list dialog for Business
    
    Change the existing Transaction list dialog to cater for Business
    associations and add a menu option to the Business menu to open this
    dialog.

diff --git a/gnucash/gnome-utils/dialog-assoc-utils.c b/gnucash/gnome-utils/dialog-assoc-utils.c
index 5ddd525a1..d8f5a6716 100644
--- a/gnucash/gnome-utils/dialog-assoc-utils.c
+++ b/gnucash/gnome-utils/dialog-assoc-utils.c
@@ -42,10 +42,11 @@
 /* This static indicates the debugging module that this .o belongs to. */
 static QofLogModule log_module = GNC_MOD_GUI;
 
-/***********************************************************************/
+/* =================================================================== */
 
 static gchar *
-convert_uri_to_abs_path (const gchar *path_head, const gchar *uri, gchar *uri_scheme, gboolean return_uri)
+convert_uri_to_abs_path (const gchar *path_head, const gchar *uri, 
+                         gchar *uri_scheme, gboolean return_uri)
 {
     gchar *ret_value = NULL;
 
@@ -159,7 +160,7 @@ gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro)
     return g_strdup (uri);
 }
 
-/***********************************************************************/
+/* =================================================================== */
 
 static gchar *
 assoc_get_path_head_and_set (gboolean *path_head_set)
@@ -261,7 +262,7 @@ gnc_assoc_set_path_head_label (GtkWidget *path_head_label, const gchar *incoming
     g_free (path_head);
 }
 
-/***********************************************************************/
+/* =================================================================== */
 
 typedef struct
 {
diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index 56634950e..603f2275b 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -33,6 +33,7 @@
 #include "gnc-session.h"
 #include "Transaction.h"
 
+#include "gnc-plugin-page-invoice.h"
 #include "gnc-plugin-page-register.h"
 #include "gnc-main-window.h"
 #include "gnc-prefs.h"
@@ -42,19 +43,22 @@
 #include "gnc-uri-utils.h"
 #include "gnc-filepath-utils.h"
 #include "Account.h"
+#include "dialog-invoice.h"
 
 #define DIALOG_ASSOC_CM_CLASS    "dialog-assoc"
-#define GNC_PREFS_GROUP          "dialogs.trans-assoc"
+#define GNC_PREFS_GROUP_BUS      "dialogs.business-assoc"
+#define GNC_PREFS_GROUP_TRANS    "dialogs.trans-assoc"
 
 /** Enumeration for the tree-store */
 enum GncAssocColumn
 {
-    DATE_TRANS,
+    DATE_ITEM,
     DATE_INT64, // used just for sorting date_trans
-    DESC_TRANS,
+    DESC_ID,
+    DESC_ITEM,
     DISPLAY_URI,
     AVAILABLE,
-    URI_SPLIT,
+    ITEM_POINTER,
     URI,
     URI_RELATIVE, // used just for sorting relative_pix
     URI_RELATIVE_PIX
@@ -66,6 +70,7 @@ typedef struct
     GtkWidget    *view;
     GtkWidget    *path_head_label;
     gchar        *path_head;
+    gboolean      is_list_trans;
     gboolean      book_ro;
     GtkTreeModel *model;
     gint          component_id;
@@ -75,7 +80,7 @@ typedef struct
 /* This static indicates the debugging module that this .o belongs to. */
 static QofLogModule log_module = GNC_MOD_GUI;
 
-/***********************************************************************/
+/* =================================================================== */
 
 void
 gnc_assoc_open_uri (GtkWindow *parent, const gchar *uri)
@@ -100,7 +105,7 @@ gnc_assoc_open_uri (GtkWindow *parent, const gchar *uri)
     }
 }
 
-/***********************************************************************/
+/* =================================================================== */
 
 static void
 location_ok_cb (GtkEditable *editable, gpointer user_data)
@@ -344,7 +349,7 @@ gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *ur
 }
 
 
-/***********************************************************************/
+/* =================================================================== */
 
 
 static void close_handler (gpointer user_data);
@@ -466,8 +471,87 @@ update_model_with_changes (AssocDialog *assoc_dialog, GtkTreeIter *iter, const g
 }
 
 static void
-row_selected_cb (GtkTreeView *view, GtkTreePath *path,
-                  GtkTreeViewColumn  *col, gpointer user_data)
+row_selected_bus_cb (GtkTreeView *view, GtkTreePath *path,
+                     GtkTreeViewColumn  *col, gpointer user_data)
+{
+    AssocDialog   *assoc_dialog = user_data;
+    GtkTreeIter    iter;
+    GncInvoice    *invoice;
+    gchar         *uri = NULL;
+
+    // path describes a non-existing row - should not happen
+    g_return_if_fail (gtk_tree_model_get_iter (assoc_dialog->model, &iter, path));
+
+    gtk_tree_model_get (assoc_dialog->model, &iter, URI, &uri, ITEM_POINTER, &invoice, -1);
+
+    // Open associated link, subtract 1 to allow for date_int64
+    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DISPLAY_URI - 1) == col)
+        gnc_assoc_open_uri (GTK_WINDOW(assoc_dialog->window), uri);
+
+    if (!invoice)
+    {
+        g_free (uri);
+        return;
+    }
+
+    // Open Invoice, subtract 1 to allow for date_int64
+    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DESC_ID - 1) == col)
+    {
+        GncPluginPage *page;
+        InvoiceWindow *iw;
+
+        iw =  gnc_ui_invoice_edit (GTK_WINDOW(assoc_dialog->window), invoice);
+        gnc_plugin_page_invoice_new (iw);
+    }
+
+    // Open Invoice association dialog, subtract 1 to allow for date_int64
+    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), AVAILABLE - 1) == col)
+    {
+        gchar *ret_uri = NULL;
+
+        if (assoc_dialog->book_ro)
+        {
+            gnc_warning_dialog (GTK_WINDOW(assoc_dialog->window), "%s", _("Business item can not be modified."));
+            g_free (uri);
+            return;
+        }
+
+        ret_uri = gnc_assoc_get_uri_dialog (GTK_WINDOW(assoc_dialog->window), _("Change a Business Association"), uri);
+
+        if (ret_uri && g_strcmp0 (uri, ret_uri) != 0)
+        {
+            gncInvoiceSetAssociation (invoice, ret_uri);
+
+            if (g_strcmp0 (ret_uri, "") == 0) // delete uri
+            {
+                // update the asooc parts for invoice window if present
+                gnc_invoice_update_assoc_for_window (invoice, ret_uri);
+                gtk_list_store_remove (GTK_LIST_STORE(assoc_dialog->model), &iter);
+            }
+            else // update uri
+            {
+                gchar *display_uri;
+                gchar *scheme = gnc_uri_get_scheme (ret_uri);
+
+                display_uri = gnc_assoc_get_unescape_uri (assoc_dialog->path_head, ret_uri, scheme);
+
+                update_model_with_changes (assoc_dialog, &iter, ret_uri);
+
+                // update the asooc parts for invoice window if present
+                gnc_invoice_update_assoc_for_window (invoice, display_uri);
+
+                g_free (scheme);
+                g_free (display_uri);
+            }
+        }
+        g_free (ret_uri);
+    }
+    g_free (uri);
+}
+
+static void
+row_selected_trans_cb (GtkTreeView *view, GtkTreePath *path,
+                       GtkTreeViewColumn  *col, gpointer user_data)
 {
     AssocDialog   *assoc_dialog = user_data;
     GtkTreeIter    iter;
@@ -477,7 +561,7 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
     // path describes a non-existing row - should not happen
     g_return_if_fail (gtk_tree_model_get_iter (assoc_dialog->model, &iter, path));
 
-    gtk_tree_model_get (assoc_dialog->model, &iter, URI, &uri, URI_SPLIT, &split, -1);
+    gtk_tree_model_get (assoc_dialog->model, &iter, URI, &uri, ITEM_POINTER, &split, -1);
 
     // Open associated link, subtract 1 to allow for date_int64
     if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DISPLAY_URI - 1) == col)
@@ -490,13 +574,11 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
     }
 
     // Open transaction, subtract 1 to allow for date_int64
-    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DESC_TRANS - 1) == col)
+    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DESC_ITEM - 1) == col)
     {
         GncPluginPage *page;
         GNCSplitReg   *gsr;
-        Account       *account;
-
-        account = xaccSplitGetAccount (split);
+        Account       *account = xaccSplitGetAccount (split);
 
         page = gnc_plugin_page_register_new (account, FALSE);
         gnc_main_window_open_page (NULL, page);
@@ -535,6 +617,66 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
     g_free (uri);
 }
 
+static void
+add_bus_info_to_model (QofInstance* data, gpointer user_data)
+{
+    AssocDialog *assoc_dialog = user_data;
+    GncInvoice *invoice = GNC_INVOICE(data);
+    const gchar* uri = gncInvoiceGetAssociation (invoice);
+    GtkTreeIter   iter;
+
+    if (uri && *uri)
+    {
+        gchar *display_uri;
+        gboolean rel = FALSE;
+        gchar *scheme = gnc_uri_get_scheme (uri);
+        time64 t = gncInvoiceGetDateOpened (invoice);
+        gchar *inv_type;
+        char datebuff[MAX_DATE_LENGTH + 1];
+        memset (datebuff, 0, sizeof(datebuff));
+        if (t == 0)
+            t = gnc_time (NULL);
+        qof_print_date_buff (datebuff, sizeof(datebuff), t);
+
+        switch (gncInvoiceGetType (invoice))
+        {
+            case GNC_INVOICE_VEND_INVOICE:
+            case GNC_INVOICE_VEND_CREDIT_NOTE:
+                inv_type = _("Bill");
+                break;
+            case GNC_INVOICE_EMPL_INVOICE:
+            case GNC_INVOICE_EMPL_CREDIT_NOTE:
+                inv_type = _("Voucher");
+                break;
+            case GNC_INVOICE_CUST_INVOICE:
+            case GNC_INVOICE_CUST_CREDIT_NOTE:
+                inv_type = _("Invoice");
+                break;
+            default:
+                inv_type = _("Undefined");
+        }
+
+        if (!scheme) // path is relative
+            rel = TRUE;
+
+        display_uri = gnc_assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
+
+        gtk_list_store_append (GTK_LIST_STORE(assoc_dialog->model), &iter);
+
+        gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), &iter,
+                            DATE_ITEM, datebuff,
+                            DATE_INT64, t, // used just for sorting date column
+                            DESC_ID, gncInvoiceGetID (invoice),
+                            DESC_ITEM, inv_type,
+                            DISPLAY_URI, display_uri, AVAILABLE, _("Unknown"),
+                            ITEM_POINTER, invoice, URI, uri,
+                            URI_RELATIVE, rel, // used just for sorting relative column
+                            URI_RELATIVE_PIX, (rel == TRUE ? "emblem-default" : NULL), -1);
+        g_free (display_uri);
+        g_free (scheme);
+    }
+}
+
 static void
 add_trans_info_to_model (QofInstance* data, gpointer user_data)
 {
@@ -566,11 +708,11 @@ add_trans_info_to_model (QofInstance* data, gpointer user_data)
         display_uri = gnc_assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
 
         gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), &iter,
-                            DATE_TRANS, datebuff,
+                            DATE_ITEM, datebuff,
                             DATE_INT64, t, // used just for sorting date column
-                            DESC_TRANS, xaccTransGetDescription (trans),
+                            DESC_ITEM, xaccTransGetDescription (trans),
                             DISPLAY_URI, display_uri, AVAILABLE, _("Unknown"),
-                            URI_SPLIT, split, URI, uri,
+                            ITEM_POINTER, split, URI, uri,
                             URI_RELATIVE, rel, // used just for sorting relative column
                             URI_RELATIVE_PIX, (rel == TRUE ? "emblem-default" : NULL), -1);
         g_free (display_uri);
@@ -579,6 +721,28 @@ add_trans_info_to_model (QofInstance* data, gpointer user_data)
     }
 }
 
+static void
+get_bus_info (AssocDialog *assoc_dialog)
+{
+    QofBook *book = gnc_get_current_book();
+
+    /* disconnect the model from the treeview */
+    assoc_dialog->model = gtk_tree_view_get_model (GTK_TREE_VIEW(assoc_dialog->view));
+    g_object_ref (G_OBJECT(assoc_dialog->model));
+    gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), NULL);
+
+    /* Clear the list store */
+    gtk_list_store_clear (GTK_LIST_STORE(assoc_dialog->model));
+
+    /* Loop through the invoices */
+    qof_collection_foreach (qof_book_get_collection (book, GNC_ID_INVOICE),
+                            add_bus_info_to_model, assoc_dialog);
+
+    /* reconnect the model to the treeview */
+    gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), assoc_dialog->model);
+    g_object_unref (G_OBJECT(assoc_dialog->model));
+}
+
 static void
 get_trans_info (AssocDialog *assoc_dialog)
 {
@@ -618,7 +782,11 @@ gnc_assoc_dialog_reload_button_cb (GtkWidget *widget, gpointer user_data)
         gnc_assoc_set_path_head_label (assoc_dialog->path_head_label, NULL, NULL);
     }
     g_free (path_head);
-    get_trans_info (assoc_dialog);
+
+    if (assoc_dialog->is_list_trans)
+        get_trans_info (assoc_dialog);
+    else
+        get_bus_info (assoc_dialog);
 }
 
 static void
@@ -672,8 +840,6 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     button = GTK_WIDGET(gtk_builder_get_object (builder, "close_button"));
         g_signal_connect(button, "clicked", G_CALLBACK(gnc_assoc_dialog_close_button_cb), assoc_dialog);
 
-    gtk_window_set_title (GTK_WINDOW(assoc_dialog->window), _("Transaction Associations"));
-
     // Set the widget name and style context for this dialog so it can be easily manipulated with css
     gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-transaction-associations");
     gnc_widget_style_context_add_class (GTK_WIDGET(window), "gnc-class-association");
@@ -690,9 +856,6 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     tree_column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object (builder, "assoc"));
     gtk_tree_view_column_set_expand (tree_column, TRUE);
 
-    g_signal_connect (assoc_dialog->view, "row-activated",
-                      G_CALLBACK(row_selected_cb), (gpointer)assoc_dialog);
-
     /* default sort order */
     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(gtk_tree_view_get_model(
                                           GTK_TREE_VIEW(assoc_dialog->view))),
@@ -710,12 +873,42 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     g_signal_connect (assoc_dialog->window, "key_press_event",
                       G_CALLBACK(gnc_assoc_dialog_window_key_press_cb), assoc_dialog);
 
+    // Setup the correct parts for each dialog
+    if (assoc_dialog->is_list_trans)
+    {
+        GObject *desc_item_tree_column = G_OBJECT(gtk_builder_get_object (builder, "desc_item"));
+        GObject *desc_id_tree_column = G_OBJECT(gtk_builder_get_object (builder, "desc_id"));
+
+        gtk_window_set_title (GTK_WINDOW(window), _("Transaction Associations"));
+
+        gtk_tree_view_column_set_visible (GTK_TREE_VIEW_COLUMN(desc_id_tree_column), FALSE);
+        gtk_tree_view_column_set_title (GTK_TREE_VIEW_COLUMN(desc_item_tree_column), _("Description"));
+
+        g_signal_connect (assoc_dialog->view, "row-activated",
+                          G_CALLBACK(row_selected_trans_cb), (gpointer)assoc_dialog);
+        gnc_restore_window_size (GNC_PREFS_GROUP_TRANS, GTK_WINDOW(assoc_dialog->window), parent);
+        get_trans_info (assoc_dialog);
+    }
+    else
+    {
+        GtkWidget *help_label = GTK_WIDGET(gtk_builder_get_object (builder, "help_label"));
+        const gchar *item_string = N_(
+            "         To jump to the Business Item, double click on the entry in the id\n"
+            " column, Association column to open the Association or Available to update");
+
+        gtk_window_set_title (GTK_WINDOW(assoc_dialog->window), _("Business Associations"));
+        gtk_label_set_text (GTK_LABEL(help_label), gettext (item_string));
+
+        g_signal_connect (assoc_dialog->view, "row-activated",
+                          G_CALLBACK(row_selected_bus_cb), (gpointer)assoc_dialog);
+        gnc_restore_window_size (GNC_PREFS_GROUP_BUS, GTK_WINDOW(assoc_dialog->window), parent);
+        get_bus_info (assoc_dialog);
+    }
+
     gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, assoc_dialog);
 
     g_object_unref (G_OBJECT(builder));
 
-    gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(assoc_dialog->window), parent);
-    get_trans_info (assoc_dialog);
     gtk_widget_show_all (GTK_WIDGET(window));
 
     gtk_tree_view_columns_autosize (GTK_TREE_VIEW(assoc_dialog->view));
@@ -728,7 +921,10 @@ close_handler (gpointer user_data)
     AssocDialog *assoc_dialog = user_data;
 
     ENTER(" ");
-    gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(assoc_dialog->window));
+    if (assoc_dialog->is_list_trans)
+        gnc_save_window_size (GNC_PREFS_GROUP_TRANS, GTK_WINDOW(assoc_dialog->window));
+    else
+        gnc_save_window_size (GNC_PREFS_GROUP_BUS, GTK_WINDOW(assoc_dialog->window));
     gtk_widget_destroy (GTK_WIDGET(assoc_dialog->window));
     LEAVE(" ");
 }
@@ -745,37 +941,64 @@ show_handler (const char *klass, gint component_id,
               gpointer user_data, gpointer iter_data)
 {
     AssocDialog *assoc_dialog = user_data;
+    gboolean is_bus = GPOINTER_TO_INT(iter_data);
 
     ENTER(" ");
     if (!assoc_dialog)
     {
         LEAVE("No data structure");
-        return(FALSE);
+        return (FALSE);
     }
+
+    // test if the dialog is the right one
+    if (is_bus == assoc_dialog->is_list_trans)
+        return (FALSE);
+
     gtk_window_present (GTK_WINDOW(assoc_dialog->window));
     LEAVE(" ");
-    return(TRUE);
+    return (TRUE);
+}
+
+void
+gnc_assoc_business_dialog (GtkWindow *parent)
+{
+    AssocDialog *assoc_dialog;
+
+    ENTER(" ");
+    if (gnc_forall_gui_components (DIALOG_ASSOC_CM_CLASS, show_handler, GINT_TO_POINTER(1)))
+    {
+        LEAVE("Existing dialog raised");
+        return;
+    }
+    assoc_dialog = g_new0 (AssocDialog, 1);
+
+    assoc_dialog->is_list_trans = FALSE;
+
+    gnc_assoc_dialog_create (parent, assoc_dialog);
+
+    assoc_dialog->component_id = gnc_register_gui_component (DIALOG_ASSOC_CM_CLASS,
+                                                             refresh_handler, close_handler,
+                                                             assoc_dialog);
+
+    gnc_gui_component_set_session (assoc_dialog->component_id,
+                                   assoc_dialog->session);
+
+    LEAVE(" ");
 }
 
-/********************************************************************\
- * gnc_assoc_trans_dialog                                           *
- * opens a window showing the Associations of all Transactions      *
- *                                                                  *
- * Args:   parent  - the parent of the window to be created         *
- * Return: nothing                                                  *
-\********************************************************************/
 void
 gnc_assoc_trans_dialog (GtkWindow *parent)
 {
     AssocDialog *assoc_dialog;
 
     ENTER(" ");
-    if (gnc_forall_gui_components (DIALOG_ASSOC_CM_CLASS, show_handler, NULL))
+    if (gnc_forall_gui_components (DIALOG_ASSOC_CM_CLASS, show_handler, GINT_TO_POINTER(0)))
     {
         LEAVE("Existing dialog raised");
         return;
     }
     assoc_dialog = g_new0 (AssocDialog, 1);
+    assoc_dialog->is_list_trans = TRUE;
 
     gnc_assoc_dialog_create (parent, assoc_dialog);
 
diff --git a/gnucash/gnome/dialog-assoc.h b/gnucash/gnome/dialog-assoc.h
index ff5ef6bfc..e57e3e997 100644
--- a/gnucash/gnome/dialog-assoc.h
+++ b/gnucash/gnome/dialog-assoc.h
@@ -45,7 +45,18 @@ gchar * gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const g
  */
 void gnc_assoc_open_uri (GtkWindow *parent, const gchar *uri);
 
-/** Preset a dialog to list all the Transaction associations.
+/** Present a dialog to list all the Invoice associations.
+ *
+ *  A query is run to return all the invoice associations which
+ *  are then added to a tree view. From this tree view the invoice
+ *  and association can be opened along with a dialog to edit the
+ *  association.
+ *
+ *  @param parent The GtkWindow for the parent widget
+ */
+void gnc_assoc_business_dialog (GtkWindow *parent);
+
+/** Present a dialog to list all the Transaction associations.
  *
  *  A query is run to return all the transaction associations which
  *  are then added to a tree view. From this tree view the transaction
diff --git a/gnucash/gnome/dialog-invoice.c b/gnucash/gnome/dialog-invoice.c
index 903310aed..281295323 100644
--- a/gnucash/gnome/dialog-invoice.c
+++ b/gnucash/gnome/dialog-invoice.c
@@ -2652,6 +2652,37 @@ gnc_invoice_create_page (InvoiceWindow *iw, gpointer page)
     return dialog;
 }
 
+void
+gnc_invoice_update_assoc_for_window (GncInvoice *invoice, const gchar *uri)
+{
+    InvoiceWindow *iw = gnc_plugin_page_invoice_get_window (invoice);
+
+    if (iw)
+    {
+        GtkWidget *assoc_link_button = gnc_invoice_window_get_assoc_link_button (iw);
+
+        if (g_strcmp0 (uri, "") == 0) // deleted uri
+        {
+            GtkAction *uri_action;
+
+            // update the menu actions
+            uri_action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE(iw->page), "BusinessAssociationOpenAction");
+            gtk_action_set_sensitive (uri_action, FALSE);
+            uri_action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE(iw->page), "BusinessAssociationRemoveAction");
+            gtk_action_set_sensitive (uri_action, FALSE);
+
+            gtk_widget_hide (assoc_link_button);
+        }
+        else
+        {
+            gchar *display_uri = gnc_assoc_get_unescaped_just_uri (uri);
+            gtk_link_button_set_uri (GTK_LINK_BUTTON(assoc_link_button), display_uri);
+            gtk_widget_show (GTK_WIDGET(assoc_link_button));
+            g_free (display_uri);
+        }
+    }
+}
+
 static InvoiceWindow *
 gnc_invoice_window_new_invoice (GtkWindow *parent, InvoiceDialogType dialog_type, QofBook *bookp,
                                 const GncOwner *owner, GncInvoice *invoice)
diff --git a/gnucash/gnome/dialog-invoice.h b/gnucash/gnome/dialog-invoice.h
index 392b5abeb..002e4af37 100644
--- a/gnucash/gnome/dialog-invoice.h
+++ b/gnucash/gnome/dialog-invoice.h
@@ -81,6 +81,8 @@ GncInvoice * gnc_invoice_window_get_invoice (InvoiceWindow *iw);
 
 GtkWidget * gnc_invoice_window_get_assoc_link_button (InvoiceWindow *iw);
 
+void gnc_invoice_update_assoc_for_window (GncInvoice *invoice, const gchar *uri);
+
 GncInvoiceType gnc_invoice_get_type_from_window(InvoiceWindow *iw);
 
 #ifdef __GNC_PLUGIN_PAGE_H
diff --git a/gnucash/gnome/gnc-plugin-business.c b/gnucash/gnome/gnc-plugin-business.c
index ff9180ad6..92d6c30c1 100644
--- a/gnucash/gnome/gnc-plugin-business.c
+++ b/gnucash/gnome/gnc-plugin-business.c
@@ -29,6 +29,7 @@
 #include <glib/gi18n.h>
 #include <string.h>
 
+#include "dialog-assoc.h"
 #include "dialog-billterms.h"
 #include "dialog-customer.h"
 #include "dialog-employee.h"
@@ -113,6 +114,8 @@ static void gnc_plugin_business_cmd_employee_find_expense_voucher (GtkAction *ac
 static void gnc_plugin_business_cmd_employee_process_payment      (GtkAction *action,
         GncMainWindowActionData *data);
 
+static void gnc_plugin_business_cmd_assoc              (GtkAction *action,
+        GncMainWindowActionData *data);
 static void gnc_plugin_business_cmd_tax_tables         (GtkAction *action,
         GncMainWindowActionData *data);
 static void gnc_plugin_business_cmd_billing_terms      (GtkAction *action,
@@ -271,6 +274,11 @@ static GtkActionEntry gnc_plugin_actions [] =
     },
 
     /* Other menu items */
+    {
+        "BusinessAssocOpenAction", NULL, N_("Business _Associations"), NULL,
+        N_("View all Business Associations"),
+        G_CALLBACK (gnc_plugin_business_cmd_assoc)
+    },
     {
         "TaxTablesOpenAction", NULL, N_("Sales _Tax Table"), NULL,
         N_("View and edit the list of Sales Tax Tables (GST/VAT)"),
@@ -744,6 +752,16 @@ gnc_plugin_business_cmd_employee_process_payment (GtkAction *action,
     gnc_ui_payment_new (GTK_WINDOW (mw->window), priv->last_employee, gnc_get_current_book ());
 }
 
+static void
+gnc_plugin_business_cmd_assoc (GtkAction *action,
+                               GncMainWindowActionData *mw)
+{
+    g_return_if_fail (mw != NULL);
+    g_return_if_fail (GNC_IS_PLUGIN_BUSINESS (mw->data));
+
+    gnc_assoc_business_dialog (GTK_WINDOW (mw->window));
+}
+
 static void
 gnc_plugin_business_cmd_tax_tables (GtkAction *action,
                                     GncMainWindowActionData *mw)
diff --git a/gnucash/gnome/gnc-plugin-page-invoice.c b/gnucash/gnome/gnc-plugin-page-invoice.c
index bb8c12e0e..099d9aba8 100644
--- a/gnucash/gnome/gnc-plugin-page-invoice.c
+++ b/gnucash/gnome/gnc-plugin-page-invoice.c
@@ -502,6 +502,26 @@ static GObjectClass *parent_class = NULL;
 /*                      Implementation                      */
 /************************************************************/
 
+InvoiceWindow *
+gnc_plugin_page_invoice_get_window (GncInvoice *invoice)
+{
+    GncPluginPageInvoicePrivate *priv;
+    GncPluginPageInvoice *invoice_page;
+    const GList *item;
+
+    /* Is there an existing page? */
+    item = gnc_gobject_tracking_get_list (GNC_PLUGIN_PAGE_INVOICE_NAME);
+    for ( ; item; item = g_list_next(item))
+    {
+        invoice_page = (GncPluginPageInvoice *)item->data;
+        priv = GNC_PLUGIN_PAGE_INVOICE_GET_PRIVATE(invoice_page);
+
+        if (gnc_invoice_window_get_invoice (priv->iw) == invoice)
+            return priv->iw;
+    }
+    return NULL;
+}
+
 GncPluginPage *
 gnc_plugin_page_invoice_new (InvoiceWindow *iw)
 {
diff --git a/gnucash/gnome/gnc-plugin-page-invoice.h b/gnucash/gnome/gnc-plugin-page-invoice.h
index d8fb503ca..2e9d71d93 100644
--- a/gnucash/gnome/gnc-plugin-page-invoice.h
+++ b/gnucash/gnome/gnc-plugin-page-invoice.h
@@ -103,6 +103,16 @@ void gnc_plugin_page_invoice_update_menus (GncPluginPage *page, gboolean is_post
  */
 void gnc_plugin_page_invoice_update_title (GncPluginPage *page);
 
+
+/** Find the Invoice Window amongst the plugin pages for an Invoice,
+ *  if not present return NULL.
+ *
+ *  @param page A pointer to an invoice.
+ * 
+ *  @return The invoice or NULL.
+ */
+InvoiceWindow * gnc_plugin_page_invoice_get_window (GncInvoice *invoice);
+
 G_END_DECLS
 /** @} */
 /** @} */
diff --git a/gnucash/gschemas/org.gnucash.dialogs.gschema.xml.in b/gnucash/gschemas/org.gnucash.dialogs.gschema.xml.in
index b5529f902..eec0640f8 100644
--- a/gnucash/gschemas/org.gnucash.dialogs.gschema.xml.in
+++ b/gnucash/gschemas/org.gnucash.dialogs.gschema.xml.in
@@ -20,6 +20,7 @@
     <child name="new-hierarchy" schema="org.gnucash.dialogs.new-hierarchy"/>
     <child name="search" schema="org.gnucash.dialogs.search"/>
     <child name="transfer" schema="org.gnucash.dialogs.transfer"/>
+    <child name="business-assoc" schema="org.gnucash.dialogs.business-assoc"/>
     <child name="trans-assoc" schema="org.gnucash.dialogs.trans-assoc"/>
     <child name="options" schema="org.gnucash.dialogs.options"/>
   </schema>
@@ -237,6 +238,16 @@
     </key>
   </schema>
 
+  <schema id="org.gnucash.dialogs.business-assoc" path="/org/gnucash/dialogs/business-assoc/">
+    <key type="(iiii)" name="last-geometry">
+      <default>(-1,-1,-1,-1)</default>
+      <summary>Last window position and size</summary>
+      <description>This setting describes the size and position of the window when it was last closed.
+        The numbers are the X and Y coordinates of the top left corner of the window
+        followed by the width and height of the window.</description>
+    </key>
+  </schema>
+
   <schema id="org.gnucash.dialogs.trans-assoc" path="/org/gnucash/dialogs/trans-assoc/">
     <key type="(iiii)" name="last-geometry">
       <default>(-1,-1,-1,-1)</default>
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index 0f0e5e4e0..3671f6a25 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -469,13 +469,15 @@
       <column type="gchararray"/>
       <!-- column-name date_int64 -->
       <column type="gint64"/>
-      <!-- column-name desc -->
+      <!-- column-name desc_id -->
+      <column type="gchararray"/>
+      <!-- column-name desc_item -->
       <column type="gchararray"/>
       <!-- column-name display_uri -->
       <column type="gchararray"/>
       <!-- column-name available -->
       <column type="gchararray"/>
-      <!-- column-name split -->
+      <!-- column-name item_pointer -->
       <column type="gpointer"/>
       <!-- column-name uri -->
       <column type="gchararray"/>
@@ -621,16 +623,28 @@
                   </object>
                 </child>
                 <child>
-                  <object class="GtkTreeViewColumn" id="trans-desc">
+                  <object class="GtkTreeViewColumn" id="desc_id">
+                    <property name="title" translatable="yes">Id</property>
+                    <property name="sort_column_id">2</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext5"/>
+                      <attributes>
+                        <attribute name="text">2</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="desc_item">
                     <property name="resizable">True</property>
-                    <property name="title" translatable="yes">Description</property>
+                    <property name="title" translatable="yes">Type</property>
                     <property name="alignment">0.5</property>
                     <property name="reorderable">True</property>
-                    <property name="sort_column_id">2</property>
+                    <property name="sort_column_id">3</property>
                     <child>
                       <object class="GtkCellRendererText" id="cellrenderertext2"/>
                       <attributes>
-                        <attribute name="text">2</attribute>
+                        <attribute name="text">3</attribute>
                       </attributes>
                     </child>
                   </object>
@@ -641,13 +655,13 @@
                     <property name="title" translatable="yes">Association</property>
                     <property name="alignment">0.5</property>
                     <property name="reorderable">True</property>
-                    <property name="sort_column_id">3</property>
+                    <property name="sort_column_id">4</property>
                     <child>
                       <object class="GtkCellRendererText" id="cellrenderertext3">
                         <property name="ellipsize">start</property>
                       </object>
                       <attributes>
-                        <attribute name="text">3</attribute>
+                        <attribute name="text">4</attribute>
                       </attributes>
                     </child>
                   </object>
@@ -657,13 +671,13 @@
                     <property name="resizable">True</property>
                     <property name="title" translatable="yes">Available ?</property>
                     <property name="alignment">0.5</property>
-                    <property name="sort_column_id">4</property>
+                    <property name="sort_column_id">5</property>
                     <child>
                       <object class="GtkCellRendererText" id="cellrenderertext4">
                         <property name="xpad">10</property>
                       </object>
                       <attributes>
-                        <attribute name="text">4</attribute>
+                        <attribute name="text">5</attribute>
                       </attributes>
                     </child>
                   </object>
@@ -671,11 +685,11 @@
                 <child>
                   <object class="GtkTreeViewColumn" id="relative">
                     <property name="title" translatable="yes">Relative</property>
-                    <property name="sort_column_id">7</property>
+                    <property name="sort_column_id">8</property>
                     <child>
                       <object class="GtkCellRendererPixbuf" id="cellrendererpix1"/>
                       <attributes>
-                        <attribute name="icon-name">8</attribute>
+                        <attribute name="icon-name">9</attribute>
                       </attributes>
                     </child>
                   </object>
diff --git a/gnucash/ui/gnc-plugin-business-ui.xml b/gnucash/ui/gnc-plugin-business-ui.xml
index b29d89726..aca84e46c 100644
--- a/gnucash/ui/gnc-plugin-business-ui.xml
+++ b/gnucash/ui/gnc-plugin-business-ui.xml
@@ -42,6 +42,8 @@
 	  <menuitem name="EmployeeProcessPayment"   action="EmployeeProcessPaymentAction"/>
 	</menu>
 
+	<menuitem name="BusinessAssocOpen" action="BusinessAssocOpenAction"/>
+
         <placeholder name="BusinessPlaceholderTop"/>
         <separator name="Sep1"/>
 

commit e2fac366088e35d1958b2145ff0d66f0c7274b56
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:33:42 2020 +0100

    Add an Invoice section to dialog-utils for when the  path head changes

diff --git a/gnucash/gnome-utils/dialog-assoc-utils.c b/gnucash/gnome-utils/dialog-assoc-utils.c
index f384f18ed..5ddd525a1 100644
--- a/gnucash/gnome-utils/dialog-assoc-utils.c
+++ b/gnucash/gnome-utils/dialog-assoc-utils.c
@@ -29,6 +29,7 @@
 
 #include "dialog-utils.h"
 #include "Transaction.h"
+#include "gncInvoice.h"
 
 #include "gnc-prefs.h"
 #include "gnc-ui.h"
@@ -271,6 +272,43 @@ typedef struct
     gboolean     book_ro;
 }AssocUpdate;
 
+static void
+update_invoice_uri (QofInstance* data, gpointer user_data)
+{
+    AssocUpdate *assoc_update = user_data;
+    GncInvoice *invoice = GNC_INVOICE(data);
+    const gchar* uri = gncInvoiceGetAssociation (invoice);
+
+    if (uri && *uri)
+    {
+        gboolean rel = FALSE;
+        gchar *scheme = gnc_uri_get_scheme (uri);
+
+        if (!scheme) // path is relative
+            rel = TRUE;
+
+        // check for relative and we want to change them
+        if (rel && assoc_update->change_old)
+        {
+            gchar *new_uri = gnc_assoc_get_use_uri (assoc_update->old_path_head_uri, uri, scheme);
+            gncInvoiceSetAssociation (invoice, new_uri);
+            g_free (new_uri);
+        }
+        g_free (scheme);
+
+        // check for not relative and we want to change them
+        if (!rel && assoc_update->change_new && g_str_has_prefix (uri, assoc_update->new_path_head_uri))
+        {
+            // relative paths do not start with a '/'
+            const gchar *part = uri + strlen (assoc_update->new_path_head_uri);
+            gchar *new_uri = g_strdup (part);
+
+            gncInvoiceSetAssociation (invoice, new_uri);
+            g_free (new_uri);
+        }
+    }
+}
+
 static void
 update_trans_uri (QofInstance* data, gpointer user_data)
 {
@@ -341,6 +379,10 @@ change_relative_and_absolute_uri_paths (const gchar *old_path_head_uri, gboolean
     qof_collection_foreach (qof_book_get_collection (book, GNC_ID_TRANS),
                             update_trans_uri, assoc_update);
 
+    /* Loop through the invoices */
+    qof_collection_foreach (qof_book_get_collection (book, GNC_ID_INVOICE),
+                            update_invoice_uri, assoc_update);
+
     g_free (assoc_update);
 }
 

commit 30f21763e83048c8d3457d15d3ce4aa23d9ac160
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:21:29 2020 +0100

    Allow Associations to be added to invoices
    
    Use the existing associations functions to do the updating, opening and
    removing of the association for invoices and all objects that use the
    invoice interface. The actual association when present is added as a
    link button which is shown below the notes.

diff --git a/gnucash/gnome/dialog-invoice.c b/gnucash/gnome/dialog-invoice.c
index 0e3bf45ee..903310aed 100644
--- a/gnucash/gnome/dialog-invoice.c
+++ b/gnucash/gnome/dialog-invoice.c
@@ -73,7 +73,10 @@
 #include "gnc-main-window.h"
 #include "gnc-state.h"
 
+#include "dialog-assoc.h"
+#include "dialog-assoc-utils.h"
 #include "dialog-transfer.h"
+#include "gnc-uri-utils.h"
 
 /* Disable -Waddress.  GCC 4.2 warns (and fails to compile with -Werror) when
  * passing the address of a guid on the stack to QOF_BOOK_LOOKUP_ENTITY via
@@ -166,6 +169,8 @@ struct _invoice_window
     GtkWidget  * active_check;
     GtkWidget  * paid_label;
 
+    GtkWidget  * assoc_link_button;
+
     GtkWidget  * owner_box;
     GtkWidget  * owner_label;
     GtkWidget  * owner_choice;
@@ -229,12 +234,12 @@ static GtkWidget *
 iw_get_window (InvoiceWindow *iw)
 {
     if (iw->page)
-        return gnc_plugin_page_get_window(iw->page);
+        return gnc_plugin_page_get_window (iw->page);
     return iw->dialog;
 }
 
 GtkWidget *
-gnc_invoice_get_register(InvoiceWindow *iw)
+gnc_invoice_get_register (InvoiceWindow *iw)
 {
     if (iw)
         return (GtkWidget *)iw->reg;
@@ -242,7 +247,7 @@ gnc_invoice_get_register(InvoiceWindow *iw)
 }
 
 GtkWidget *
-gnc_invoice_get_notes(InvoiceWindow *iw)
+gnc_invoice_get_notes (InvoiceWindow *iw)
 {
     if (iw)
         return (GtkWidget *)iw->notes_text;
@@ -313,6 +318,24 @@ iw_get_invoice (InvoiceWindow *iw)
     return gncInvoiceLookup (iw->book, &iw->invoice_guid);
 }
 
+GncInvoice *
+gnc_invoice_window_get_invoice (InvoiceWindow *iw)
+{
+    if (!iw)
+        return NULL;
+
+    return iw_get_invoice (iw);
+}
+
+GtkWidget *
+gnc_invoice_window_get_assoc_link_button (InvoiceWindow *iw)
+{
+    if (!iw)
+        return NULL;
+
+    return iw->assoc_link_button;
+}
+
 static void
 set_gncEntry_switch_type (gpointer data, gpointer user_data)
 {
@@ -2371,6 +2394,15 @@ gnc_invoice_save_page (InvoiceWindow *iw,
     gnc_table_save_state (table, group_name);
 }
 
+static gboolean
+assoc_link_button_cb (GtkLinkButton *button, InvoiceWindow *iw)
+{
+    GncInvoice *invoice = gncInvoiceLookup (iw->book, &iw->invoice_guid);
+    gnc_assoc_open_uri (GTK_WINDOW(iw->dialog), gncInvoiceGetAssociation (invoice));
+
+    return TRUE;
+}
+
 GtkWidget *
 gnc_invoice_create_page (InvoiceWindow *iw, gpointer page)
 {
@@ -2383,6 +2415,7 @@ gnc_invoice_create_page (InvoiceWindow *iw, gpointer page)
     const gchar *prefs_group = NULL;
     gboolean is_credit_note = FALSE;
     const gchar *style_label = NULL;
+    const gchar *assoc_uri;
 
     invoice = gncInvoiceLookup (iw->book, &iw->invoice_guid);
     is_credit_note = gncInvoiceGetIsCreditNote (invoice);
@@ -2413,6 +2446,23 @@ gnc_invoice_create_page (InvoiceWindow *iw, gpointer page)
     iw->job_box = GTK_WIDGET (gtk_builder_get_object (builder, "page_job_hbox"));
     iw->paid_label = GTK_WIDGET (gtk_builder_get_object (builder, "paid_label"));
 
+    iw->assoc_link_button = GTK_WIDGET(gtk_builder_get_object (builder, "assoc_link_button"));
+    g_signal_connect (G_OBJECT(iw->assoc_link_button), "activate-link",
+                      G_CALLBACK(assoc_link_button_cb), iw);
+
+    /* invoice association */
+    assoc_uri = gncInvoiceGetAssociation (invoice);
+    if (assoc_uri)
+    {
+        gchar *display_uri = gnc_assoc_get_unescaped_just_uri (assoc_uri);
+        gtk_button_set_label (GTK_BUTTON(iw->assoc_link_button), _("Open Association:"));
+        gtk_link_button_set_uri (GTK_LINK_BUTTON(iw->assoc_link_button), display_uri);
+        gtk_widget_show (GTK_WIDGET (iw->assoc_link_button));
+        g_free (display_uri);
+    }
+    else
+        gtk_widget_hide (GTK_WIDGET (iw->assoc_link_button));
+
     // Add a style context for this label so it can be easily manipulated with css
     gnc_widget_style_context_add_class (GTK_WIDGET(iw->paid_label), "gnc-class-highlight");
 
diff --git a/gnucash/gnome/dialog-invoice.h b/gnucash/gnome/dialog-invoice.h
index 073268d40..392b5abeb 100644
--- a/gnucash/gnome/dialog-invoice.h
+++ b/gnucash/gnome/dialog-invoice.h
@@ -77,6 +77,10 @@ gchar *gnc_invoice_get_help (InvoiceWindow *iw);
 
 gchar *gnc_invoice_get_title (InvoiceWindow *iw);
 
+GncInvoice * gnc_invoice_window_get_invoice (InvoiceWindow *iw);
+
+GtkWidget * gnc_invoice_window_get_assoc_link_button (InvoiceWindow *iw);
+
 GncInvoiceType gnc_invoice_get_type_from_window(InvoiceWindow *iw);
 
 #ifdef __GNC_PLUGIN_PAGE_H
diff --git a/gnucash/gnome/gnc-plugin-page-invoice.c b/gnucash/gnome/gnc-plugin-page-invoice.c
index fb74f75a2..bb8c12e0e 100644
--- a/gnucash/gnome/gnc-plugin-page-invoice.c
+++ b/gnucash/gnome/gnc-plugin-page-invoice.c
@@ -40,8 +40,11 @@
 #include "gnucash-register.h"
 #include "gnc-prefs.h"
 #include "gnc-ui-util.h"
+#include "gnc-uri-utils.h"
 #include "gnc-window.h"
 #include "dialog-utils.h"
+#include "dialog-assoc.h"
+#include "dialog-assoc-utils.h"
 #include "gncInvoice.h"
 
 /* This static indicates the debugging module that this .o belongs to.  */
@@ -85,6 +88,9 @@ static void gnc_plugin_page_invoice_cmd_duplicateEntry (GtkAction *action, GncPl
 static void gnc_plugin_page_invoice_cmd_pay_invoice (GtkAction *action, GncPluginPageInvoice *plugin_page);
 static void gnc_plugin_page_invoice_cmd_save_layout (GtkAction *action, GncPluginPageInvoice *plugin_page);
 static void gnc_plugin_page_invoice_cmd_reset_layout (GtkAction *action, GncPluginPageInvoice *plugin_page);
+static void gnc_plugin_page_invoice_cmd_associate (GtkAction *action, GncPluginPageInvoice *plugin_page);
+static void gnc_plugin_page_invoice_cmd_associate_remove (GtkAction *action, GncPluginPageInvoice *plugin_page);
+static void gnc_plugin_page_invoice_cmd_associate_open (GtkAction *action, GncPluginPageInvoice *plugin_page);
 static void gnc_plugin_page_invoice_cmd_company_report (GtkAction *action, GncPluginPageInvoice *plugin_page);
 
 static void gnc_plugin_page_redraw_help_cb( GnucashRegister *gsr, GncPluginPageInvoice *invoice_page );
@@ -201,6 +207,21 @@ static GtkActionEntry gnc_plugin_page_invoice_actions [] =
         "Create a new invoice for the same owner as the current one",
         G_CALLBACK (gnc_plugin_page_invoice_cmd_new_invoice)
     },
+    {
+        "BusinessAssociationAction", NULL, "_Update Association for Invoice", NULL,
+        "Update Association for current Invoice",
+        G_CALLBACK (gnc_plugin_page_invoice_cmd_associate)
+    },
+    {
+        "BusinessAssociationOpenAction", NULL, "_Open Association for Invoice", NULL,
+        "Open Association for current Invoice",
+        G_CALLBACK (gnc_plugin_page_invoice_cmd_associate_open)
+    },
+    {
+        "BusinessAssociationRemoveAction", NULL, "_Remove Association from Invoice", NULL,
+        "Remove Association from Invoice",
+        G_CALLBACK (gnc_plugin_page_invoice_cmd_associate_remove)
+    },
     {
         "ToolsProcessPaymentAction", GNC_ICON_INVOICE_PAY, "_Pay Invoice", NULL,
         "Enter a payment for the owner of this invoice",
@@ -246,6 +267,8 @@ static const gchar *invoice_book_readwrite_actions[] =
     "EditDuplicateInvoiceAction",
     "BusinessNewInvoiceAction",
     "ToolsProcessPaymentAction",
+    "BusinessAssociationAction",
+    "BusinessAssociationRemoveAction",
     NULL
 };
 
@@ -285,6 +308,9 @@ static action_toolbar_labels invoice_action_labels[] =
     {"EditUnpostInvoiceAction", N_("_Unpost Invoice")},
     {"BusinessNewInvoiceAction", N_("New _Invoice")},
     {"ToolsProcessPaymentAction", N_("_Pay Invoice")},
+    {"BusinessAssociationAction", N_("_Update Association for Invoice")},
+    {"BusinessAssociationOpenAction", N_("_Open Association for Invoice")},
+    {"BusinessAssociationRemoveAction", N_("_Remove Association from Invoice")},
     {NULL, NULL},
 };
 
@@ -304,6 +330,9 @@ static action_toolbar_labels bill_action_labels[] =
     {"EditUnpostInvoiceAction", N_("_Unpost Bill")},
     {"BusinessNewInvoiceAction", N_("New _Bill")},
     {"ToolsProcessPaymentAction", N_("_Pay Bill")},
+    {"BusinessAssociationAction", N_("_Update Association for Bill")},
+    {"BusinessAssociationOpenAction", N_("_Open Association for Bill")},
+    {"BusinessAssociationRemoveAction", N_("_Remove Association from Bill")},
     {NULL, NULL},
 };
 
@@ -323,6 +352,9 @@ static action_toolbar_labels voucher_action_labels[] =
     {"EditUnpostInvoiceAction", N_("_Unpost Voucher")},
     {"BusinessNewInvoiceAction", N_("New _Voucher")},
     {"ToolsProcessPaymentAction", N_("_Pay Voucher")},
+    {"BusinessAssociationAction", N_("_Update Association for Voucher")},
+    {"BusinessAssociationOpenAction", N_("_Open Association for Voucher")},
+    {"BusinessAssociationRemoveAction", N_("_Remove Association from Voucher")},
     {NULL, NULL},
 };
 
@@ -342,6 +374,9 @@ static action_toolbar_labels creditnote_action_labels[] =
     {"EditUnpostInvoiceAction", N_("_Unpost Credit Note")},
     {"BusinessNewInvoiceAction", N_("New _Credit Note")},
     {"ToolsProcessPaymentAction", N_("_Pay Credit Note")},
+    {"BusinessAssociationAction", N_("_Update Association for Credit Note")},
+    {"BusinessAssociationOpenAction", N_("_Open Association for Credit Note")},
+    {"BusinessAssociationRemoveAction", N_("_Remove Association from Credit Note")},
     {NULL, NULL},
 };
 
@@ -356,6 +391,9 @@ static action_toolbar_labels invoice_action_tooltips[] = {
     {"BlankEntryAction", N_("Move to the blank entry at the bottom of the invoice")},
     {"ToolsProcessPaymentAction", N_("Enter a payment for the owner of this invoice") },
     {"ReportsCompanyReportAction", N_("Open a company report window for the owner of this invoice") },
+    {"BusinessAssociationAction", N_("Update Association for current invoice")},
+    {"BusinessAssociationOpenAction", N_("Open Association for current invoice")},
+    {"BusinessAssociationRemoveAction", N_("Remove Association from invoice")},
     {NULL, NULL},
 };
 
@@ -375,6 +413,9 @@ static action_toolbar_labels bill_action_tooltips[] = {
     {"BlankEntryAction", N_("Move to the blank entry at the bottom of the bill")},
     {"ToolsProcessPaymentAction", N_("Enter a payment for the owner of this bill") },
     {"ReportsCompanyReportAction", N_("Open a company report window for the owner of this bill") },
+    {"BusinessAssociationAction", N_("Update Association for current bill")},
+    {"BusinessAssociationOpenAction", N_("Open Association for current bill")},
+    {"BusinessAssociationRemoveAction", N_("Remove Association from bill")},
     {NULL, NULL},
 };
 
@@ -394,6 +435,9 @@ static action_toolbar_labels voucher_action_tooltips[] = {
     {"BlankEntryAction", N_("Move to the blank entry at the bottom of the voucher")},
     {"ToolsProcessPaymentAction", N_("Enter a payment for the owner of this voucher") },
     {"ReportsCompanyReportAction", N_("Open a company report window for the owner of this voucher") },
+    {"BusinessAssociationAction", N_("Update Association for current voucher")},
+    {"BusinessAssociationOpenAction", N_("Open Association for current voucher")},
+    {"BusinessAssociationRemoveAction", N_("Remove Association from voucher")},
     {NULL, NULL},
 };
 
@@ -413,6 +457,9 @@ static action_toolbar_labels creditnote_action_tooltips[] = {
     {"BlankEntryAction", N_("Move to the blank entry at the bottom of the credit note")},
     {"ToolsProcessPaymentAction", N_("Enter a payment for the owner of this credit note") },
     {"ReportsCompanyReportAction", N_("Open a company report window for the owner of this credit note") },
+    {"BusinessAssociationAction", N_("Update Association for credit note")},
+    {"BusinessAssociationOpenAction", N_("Open Association for credit note")},
+    {"BusinessAssociationRemoveAction", N_("Remove Association from credit note")},
     {NULL, NULL},
 };
 
@@ -550,6 +597,17 @@ gnc_plugin_page_invoice_finalize (GObject *object)
     LEAVE(" ");
 }
 
+static void
+update_assoc_actions (GncPluginPage *plugin_page, gboolean has_uri)
+{
+    GtkAction *uri_action;
+
+    uri_action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE(plugin_page), "BusinessAssociationOpenAction");
+    gtk_action_set_sensitive (uri_action, has_uri);
+    uri_action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE(plugin_page), "BusinessAssociationRemoveAction");
+    gtk_action_set_sensitive (uri_action, has_uri);
+}
+
 static void
 gnc_plugin_page_invoice_action_update (GtkActionGroup *action_group,
                                        action_toolbar_labels *action_list,
@@ -590,11 +648,13 @@ gnc_plugin_page_invoice_update_menus (GncPluginPage *page, gboolean is_posted, g
     GtkActionGroup *action_group;
     GncPluginPageInvoicePrivate *priv;
     GncInvoiceType invoice_type;
+    GncInvoice *invoice;
     gint i, j;
     action_toolbar_labels *label_list;
     action_toolbar_labels *tooltip_list;
     action_toolbar_labels *label_layout_list;
     action_toolbar_labels *tooltip_layout_list;
+    gboolean has_uri = FALSE;
 
     gboolean is_readonly = qof_book_is_readonly(gnc_get_current_book());
 
@@ -678,6 +738,13 @@ gnc_plugin_page_invoice_update_menus (GncPluginPage *page, gboolean is_posted, g
     gnc_plugin_page_invoice_action_update (action_group, label_layout_list, (void*)gtk_action_set_label);
     /* update the layout action tooltips */
     gnc_plugin_page_invoice_action_update (action_group, tooltip_layout_list, (void*)gtk_action_set_tooltip);
+
+    // update association buttons
+    invoice = gnc_invoice_window_get_invoice (priv->iw);
+    if (gncInvoiceGetAssociation (invoice))
+        has_uri = TRUE;
+
+    update_assoc_actions (page, has_uri);
 }
 
 
@@ -1269,6 +1336,107 @@ gnc_plugin_page_invoice_cmd_reset_layout (GtkAction *action,
     LEAVE(" ");
 }
 
+static void
+gnc_plugin_page_invoice_cmd_associate (GtkAction *action,
+        GncPluginPageInvoice *plugin_page)
+{
+    GncPluginPageInvoicePrivate *priv;
+    GtkWindow *parent;
+    GtkAction *uri_action;
+    GncInvoice *invoice;
+    const gchar *uri;
+    gchar *ret_uri;
+    gboolean has_uri = FALSE;
+
+    g_return_if_fail (GNC_IS_PLUGIN_PAGE_INVOICE(plugin_page));
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+    priv = GNC_PLUGIN_PAGE_INVOICE_GET_PRIVATE(plugin_page);
+    parent = GTK_WINDOW(gnc_plugin_page_get_window (GNC_PLUGIN_PAGE(plugin_page)));
+
+    invoice = gnc_invoice_window_get_invoice (priv->iw);
+    uri = gncInvoiceGetAssociation (invoice);
+
+    ret_uri = gnc_assoc_get_uri_dialog (parent, _("Change a Business Association"), uri);
+
+    if (ret_uri && g_strcmp0 (uri, ret_uri) != 0)
+    {
+        GtkWidget *assoc_link_button = gnc_invoice_window_get_assoc_link_button (priv->iw);
+
+        if (assoc_link_button)
+        {
+            if (g_strcmp0 (ret_uri, "") == 0)
+                gtk_widget_hide (GTK_WIDGET(assoc_link_button));
+            else
+            {
+                gchar *display_uri = gnc_assoc_get_unescaped_just_uri (ret_uri);
+                gtk_link_button_set_uri (GTK_LINK_BUTTON(assoc_link_button), display_uri);
+                gtk_widget_show (GTK_WIDGET(assoc_link_button));
+                g_free (display_uri);
+            }
+        }
+        gncInvoiceSetAssociation (invoice, ret_uri);
+        has_uri = TRUE;
+    }
+
+    // update the menu actions
+    update_assoc_actions (GNC_PLUGIN_PAGE(plugin_page), has_uri);
+
+    g_free (ret_uri);
+    LEAVE(" ");
+}
+
+static void
+gnc_plugin_page_invoice_cmd_associate_remove (GtkAction *action,
+        GncPluginPageInvoice *plugin_page)
+{
+    GncPluginPageInvoicePrivate *priv;
+    GtkWindow *parent;
+    GtkAction *uri_action;
+    GncInvoice *invoice;
+    GtkWidget *assoc_link_button;
+
+    g_return_if_fail (GNC_IS_PLUGIN_PAGE_INVOICE(plugin_page));
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+    priv = GNC_PLUGIN_PAGE_INVOICE_GET_PRIVATE(plugin_page);
+    parent = GTK_WINDOW(gnc_plugin_page_get_window (GNC_PLUGIN_PAGE(plugin_page)));
+
+    invoice = gnc_invoice_window_get_invoice (priv->iw);
+    gncInvoiceSetAssociation (invoice, "");
+
+    assoc_link_button = gnc_invoice_window_get_assoc_link_button (priv->iw);
+
+    if (assoc_link_button)
+        gtk_widget_hide (GTK_WIDGET(assoc_link_button));
+
+    // update the menu actions
+    update_assoc_actions (GNC_PLUGIN_PAGE(plugin_page), FALSE);
+
+    LEAVE(" ");
+}
+
+static void
+gnc_plugin_page_invoice_cmd_associate_open (GtkAction *action,
+        GncPluginPageInvoice *plugin_page)
+{
+    GncPluginPageInvoicePrivate *priv;
+    GtkWindow *parent;
+    GncInvoice *invoice;
+    const gchar *uri = NULL;
+
+    g_return_if_fail(GNC_IS_PLUGIN_PAGE_INVOICE(plugin_page));
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+    priv = GNC_PLUGIN_PAGE_INVOICE_GET_PRIVATE(plugin_page);
+    parent = GTK_WINDOW(gnc_plugin_page_get_window (GNC_PLUGIN_PAGE(plugin_page)));
+
+    invoice = gnc_invoice_window_get_invoice (priv->iw);
+    uri = gncInvoiceGetAssociation (invoice);
+
+    if (uri)
+        gnc_assoc_open_uri (parent, uri);
+
+    LEAVE(" ");
+}
+
 static void
 gnc_plugin_page_invoice_cmd_company_report (GtkAction *action,
         GncPluginPageInvoice *plugin_page)
diff --git a/gnucash/gtkbuilder/dialog-invoice.glade b/gnucash/gtkbuilder/dialog-invoice.glade
index fd9aa3c6a..6cb0c1158 100644
--- a/gnucash/gtkbuilder/dialog-invoice.glade
+++ b/gnucash/gtkbuilder/dialog-invoice.glade
@@ -381,22 +381,61 @@
                 <property name="can_focus">False</property>
                 <property name="label_xalign">0</property>
                 <child>
-                  <object class="GtkScrolledWindow" id="scrolledwindow1">
+                  <object class="GtkBox">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                    <property name="vexpand">True</property>
-                    <property name="border_width">3</property>
-                    <property name="hscrollbar_policy">never</property>
-                    <property name="shadow_type">in</property>
+                    <property name="orientation">vertical</property>
                     <child>
-                      <object class="GtkTextView" id="page_notes_text">
+                      <object class="GtkScrolledWindow" id="scrolledwindow1">
                         <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="wrap_mode">word</property>
-                        <property name="accepts_tab">False</property>
-                        <signal name="focus-out-event" handler="gnc_invoice_window_leave_notes_cb" swapped="no"/>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="vexpand">True</property>
+                        <property name="border_width">3</property>
+                        <property name="hscrollbar_policy">never</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkTextView" id="page_notes_text">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="wrap_mode">word</property>
+                            <property name="accepts_tab">False</property>
+                            <signal name="focus-out-event" handler="gnc_invoice_window_leave_notes_cb" swapped="no"/>
+                          </object>
+                        </child>
                       </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="assoc_hbox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkLinkButton" id="assoc_link_button">
+                            <property name="label" translatable="yes">Open Association</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="halign">start</property>
+                            <property name="relief">none</property>
+                            <property name="uri">http://www.gnucash.org</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
                     </child>
                   </object>
                 </child>
diff --git a/gnucash/ui/gnc-plugin-page-invoice-ui.xml b/gnucash/ui/gnc-plugin-page-invoice-ui.xml
index 8a823271e..45ce7eea8 100644
--- a/gnucash/ui/gnc-plugin-page-invoice-ui.xml
+++ b/gnucash/ui/gnc-plugin-page-invoice-ui.xml
@@ -39,6 +39,11 @@
     <placeholder name="AdditionalMenusPlaceholder">
       <menu name="Business" action="BusinessAction">
         <placeholder name="BusinessPlaceholderMiddle">
+          <separator name="BusinessSep1"/>
+          <menuitem name="BusinessAssociation"     action="BusinessAssociationAction"/>
+          <menuitem name="BusinessAssociationOpen"     action="BusinessAssociationOpenAction"/>
+          <menuitem name="BusinessAssociationRemove"     action="BusinessAssociationRemoveAction"/>
+          <separator name="BusinessSep2"/>
           <menuitem name="ToolsProcessPayment"     action="ToolsProcessPaymentAction"/>
         </placeholder>
       </menu>

commit 2f9be87549a2032ea54860a020bd2ea6ce5806a7
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:19:02 2020 +0100

    Replace tabs with spaces in gncInvoice.c/h
    
    Replace tabs with spaces, trim ending spaces and add spaces to function
    calls where appropriate.

diff --git a/libgnucash/engine/gncInvoice.c b/libgnucash/engine/gncInvoice.c
index c2a376a9c..0ae2dcf2f 100644
--- a/libgnucash/engine/gncInvoice.c
+++ b/libgnucash/engine/gncInvoice.c
@@ -62,8 +62,8 @@ struct _gncInvoice
     GncOwner      owner;
     GncOwner      billto;
     GncJob        *job;
-    time64      date_opened;
-    time64      date_posted;
+    time64        date_opened;
+    time64        date_posted;
 
     gnc_numeric   to_charge_amount;
 
@@ -87,26 +87,26 @@ static QofLogModule log_module = GNC_MOD_BUSINESS;
 #define GNC_INVOICE_ASSOC "assoc_uri"
 
 #define SET_STR(obj, member, str) { \
-	char * tmp; \
-	\
-	if (!g_strcmp0 (member, str)) return; \
-	gncInvoiceBeginEdit (obj); \
-	tmp = CACHE_INSERT (str); \
-	CACHE_REMOVE (member); \
-	member = tmp; \
-	}
+    char * tmp; \
+    \
+    if (!g_strcmp0 (member, str)) return; \
+    gncInvoiceBeginEdit (obj); \
+    tmp = CACHE_INSERT (str); \
+    CACHE_REMOVE (member); \
+    member = tmp; \
+    }
 
 static void mark_invoice (GncInvoice *invoice);
 static void
 mark_invoice (GncInvoice *invoice)
 {
-    qof_instance_set_dirty(&invoice->inst);
+    qof_instance_set_dirty (&invoice->inst);
     qof_event_gen (&invoice->inst, QOF_EVENT_MODIFY, NULL);
 }
 
-QofBook * gncInvoiceGetBook(GncInvoice *x)
+QofBook * gncInvoiceGetBook (GncInvoice *x)
 {
-    return qof_instance_get_book(QOF_INSTANCE(x));
+    return qof_instance_get_book (QOF_INSTANCE(x));
 }
 
 /* ================================================================== */
@@ -114,21 +114,21 @@ QofBook * gncInvoiceGetBook(GncInvoice *x)
 enum
 {
     PROP_0,
-//  PROP_ID,		/* Table */
-//  PROP_DATE_OPENED,	/* Table */
-//  PROP_DATE_POSTED,	/* Table */
-    PROP_NOTES,		/* Table */
-//  PROP_ACTIVE,	/* Table */
-//  PROP_CURRENCY,	/* Table */
-//  PROP_OWNER_TYPE,	/* Table */
-//  PROP_OWNER,		/* Table */
-//  PROP_TERMS,		/* Table */
-//  PROP_BILLING_ID,	/* Table */
-//  PROP_POST_TXN,	/* Table */
-//  PROP_POST_LOT,	/* Table */
-//  PROP_POST_ACCOUNT,	/* Table */
-//  PROP_BILLTO_TYPE,	/* Table */
-//  PROP_BILLTO,	/* Table */
+//  PROP_ID,            /* Table */
+//  PROP_DATE_OPENED,   /* Table */
+//  PROP_DATE_POSTED,   /* Table */
+    PROP_NOTES,         /* Table */
+//  PROP_ACTIVE,        /* Table */
+//  PROP_CURRENCY,      /* Table */
+//  PROP_OWNER_TYPE,    /* Table */
+//  PROP_OWNER,         /* Table */
+//  PROP_TERMS,         /* Table */
+//  PROP_BILLING_ID,    /* Table */
+//  PROP_POST_TXN,      /* Table */
+//  PROP_POST_LOT,      /* Table */
+//  PROP_POST_ACCOUNT,  /* Table */
+//  PROP_BILLTO_TYPE,   /* Table */
+//  PROP_BILLTO,        /* Table */
 //  PROP_CHARGE_AMOUNT, /* Table, (numeric) */
 };
 
@@ -136,20 +136,20 @@ enum
 G_DEFINE_TYPE(GncInvoice, gnc_invoice, QOF_TYPE_INSTANCE);
 
 static void
-gnc_invoice_init(GncInvoice* inv)
+gnc_invoice_init (GncInvoice* inv)
 {
     inv->date_posted = INT64_MAX;
     inv->date_opened = INT64_MAX;
 }
 
 static void
-gnc_invoice_dispose(GObject *invp)
+gnc_invoice_dispose (GObject *invp)
 {
     G_OBJECT_CLASS(gnc_invoice_parent_class)->dispose(invp);
 }
 
 static void
-gnc_invoice_finalize(GObject* invp)
+gnc_invoice_finalize (GObject* invp)
 {
     G_OBJECT_CLASS(gnc_invoice_parent_class)->finalize(invp);
 }
@@ -162,13 +162,13 @@ gnc_invoice_get_property (GObject         *object,
 {
     GncInvoice *inv;
 
-    g_return_if_fail(GNC_IS_INVOICE(object));
+    g_return_if_fail (GNC_IS_INVOICE(object));
 
     inv = GNC_INVOICE(object);
     switch (prop_id)
     {
     case PROP_NOTES:
-        g_value_set_string(value, inv->notes);
+        g_value_set_string (value, inv->notes);
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -179,20 +179,20 @@ gnc_invoice_get_property (GObject         *object,
 static void
 gnc_invoice_set_property (GObject         *object,
                           guint            prop_id,
-                          const GValue          *value,
+                          const GValue    *value,
                           GParamSpec      *pspec)
 {
     GncInvoice *inv;
 
-    g_return_if_fail(GNC_IS_INVOICE(object));
+    g_return_if_fail (GNC_IS_INVOICE(object));
 
     inv = GNC_INVOICE(object);
-    g_assert (qof_instance_get_editlevel(inv));
+    g_assert (qof_instance_get_editlevel (inv));
 
     switch (prop_id)
     {
     case PROP_NOTES:
-        gncInvoiceSetNotes(inv, g_value_get_string(value));
+        gncInvoiceSetNotes (inv, g_value_get_string (value));
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -202,28 +202,28 @@ gnc_invoice_set_property (GObject         *object,
 
 /** Returns a string representing this object */
 static gchar*
-impl_get_display_name(const QofInstance* inst)
+impl_get_display_name (const QofInstance* inst)
 {
     GncInvoice* inv;
     QofInstance* owner;
     gchar* s;
 
-    g_return_val_if_fail(inst != NULL, FALSE);
-    g_return_val_if_fail(GNC_IS_INVOICE(inst), FALSE);
+    g_return_val_if_fail (inst != NULL, FALSE);
+    g_return_val_if_fail (GNC_IS_INVOICE(inst), FALSE);
 
     inv = GNC_INVOICE(inst);
-    owner = qofOwnerGetOwner(&inv->owner);
+    owner = qofOwnerGetOwner (&inv->owner);
     if (owner != NULL)
     {
         gchar* display_name;
 
-        display_name = qof_instance_get_display_name(owner);
-        s = g_strdup_printf("Invoice %s (%s)", inv->id, display_name);
-        g_free(display_name);
+        display_name = qof_instance_get_display_name (owner);
+        s = g_strdup_printf ("Invoice %s (%s)", inv->id, display_name);
+        g_free (display_name);
     }
     else
     {
-        s = g_strdup_printf("Invoice %s", inv->id);
+        s = g_strdup_printf ("Invoice %s", inv->id);
     }
 
     return s;
@@ -231,12 +231,12 @@ impl_get_display_name(const QofInstance* inst)
 
 /** Does this object refer to a specific object */
 static gboolean
-impl_refers_to_object(const QofInstance* inst, const QofInstance* ref)
+impl_refers_to_object (const QofInstance* inst, const QofInstance* ref)
 {
     GncInvoice* inv;
 
-    g_return_val_if_fail(inst != NULL, FALSE);
-    g_return_val_if_fail(GNC_IS_INVOICE(inst), FALSE);
+    g_return_val_if_fail (inst != NULL, FALSE);
+    g_return_val_if_fail (GNC_IS_INVOICE(inst), FALSE);
 
     inv = GNC_INVOICE(inst);
 
@@ -275,7 +275,7 @@ impl_refers_to_object(const QofInstance* inst, const QofInstance* ref)
     objects on the list must not.
  */
 static GList*
-impl_get_typed_referring_object_list(const QofInstance* inst, const QofInstance* ref)
+impl_get_typed_referring_object_list (const QofInstance* inst, const QofInstance* ref)
 {
     if (!GNC_IS_BILLTERM(ref) && !GNC_IS_JOB(ref) && !GNC_IS_COMMODITY(ref) && !GNC_IS_ACCOUNT(ref)
             && !GNC_IS_TRANSACTION(ref) && !GNC_IS_LOT(ref))
@@ -283,13 +283,13 @@ impl_get_typed_referring_object_list(const QofInstance* inst, const QofInstance*
         return NULL;
     }
 
-    return qof_instance_get_referring_object_list_from_collection(qof_instance_get_collection(inst), ref);
+    return qof_instance_get_referring_object_list_from_collection (qof_instance_get_collection (inst), ref);
 }
 
 static void
 gnc_invoice_class_init (GncInvoiceClass *klass)
 {
-    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
     QofInstanceClass* qof_class = QOF_INSTANCE_CLASS(klass);
 
     gobject_class->dispose = gnc_invoice_dispose;
@@ -330,7 +330,7 @@ GncInvoice *gncInvoiceCreate (QofBook *book)
     invoice->billto.type = GNC_OWNER_CUSTOMER;
     invoice->active = TRUE;
 
-    invoice->to_charge_amount = gnc_numeric_zero();
+    invoice->to_charge_amount = gnc_numeric_zero ();
 
     qof_event_gen (&invoice->inst, QOF_EVENT_CREATE, NULL);
 
@@ -344,14 +344,14 @@ GncInvoice *gncInvoiceCopy (const GncInvoice *from)
     GList *node;
     GValue v = G_VALUE_INIT;
 
-    g_assert(from);
-    book = qof_instance_get_book(from);
-    g_assert(book);
+    g_assert (from);
+    book = qof_instance_get_book (from);
+    g_assert (book);
 
     invoice = g_object_new (GNC_TYPE_INVOICE, NULL);
     qof_instance_init_data (&invoice->inst, _GNC_MOD_NAME, book);
 
-    gncInvoiceBeginEdit(invoice);
+    gncInvoiceBeginEdit (invoice);
 
     invoice->id = CACHE_INSERT (from->id);
     invoice->notes = CACHE_INSERT (from->notes);
@@ -365,8 +365,8 @@ GncInvoice *gncInvoiceCopy (const GncInvoice *from)
     invoice->terms = from->terms;
     gncBillTermIncRef (invoice->terms);
 
-    gncOwnerCopy(&from->billto, &invoice->billto);
-    gncOwnerCopy(&from->owner, &invoice->owner);
+    gncOwnerCopy (&from->billto, &invoice->billto);
+    gncOwnerCopy (&from->owner, &invoice->owner);
     invoice->job = from->job; // FIXME: Need IncRef or similar here?!?
 
     invoice->to_charge_amount = from->to_charge_amount;
@@ -379,20 +379,20 @@ GncInvoice *gncInvoiceCopy (const GncInvoice *from)
     for (node = from->entries; node; node = node->next)
     {
         GncEntry *from_entry = node->data;
-        GncEntry *to_entry = gncEntryCreate(book);
-        gncEntryCopy(from_entry, to_entry, FALSE);
+        GncEntry *to_entry = gncEntryCreate (book);
+        gncEntryCopy (from_entry, to_entry, FALSE);
 
         switch (gncInvoiceGetOwnerType (invoice))
         {
         case GNC_OWNER_VENDOR:
         case GNC_OWNER_EMPLOYEE:
             // this is a vendor bill, or an expense voucher
-            gncBillAddEntry(invoice, to_entry);
+            gncBillAddEntry (invoice, to_entry);
             break;
         case GNC_OWNER_CUSTOMER:
         default:
             // this is an invoice
-            gncInvoiceAddEntry(invoice, to_entry);
+            gncInvoiceAddEntry (invoice, to_entry);
             break;
         }
     }
@@ -402,7 +402,7 @@ GncInvoice *gncInvoiceCopy (const GncInvoice *from)
     // Posted-date and the posted Txn is intentionally not copied; the
     // copy isn't "posted" but needs to be posted by the user.
     mark_invoice (invoice);
-    gncInvoiceCommitEdit(invoice);
+    gncInvoiceCommitEdit (invoice);
 
     return invoice;
 }
@@ -410,7 +410,7 @@ GncInvoice *gncInvoiceCopy (const GncInvoice *from)
 void gncInvoiceDestroy (GncInvoice *invoice)
 {
     if (!invoice) return;
-    qof_instance_set_destroying(invoice, TRUE);
+    qof_instance_set_destroying (invoice, TRUE);
     gncInvoiceCommitEdit (invoice);
 }
 
@@ -464,7 +464,7 @@ qofInvoiceSetOwner (GncInvoice *invoice, QofInstance *ent)
         return;
     }
     gncInvoiceBeginEdit (invoice);
-    qofOwnerSetEntity(&invoice->owner, ent);
+    qofOwnerSetEntity (&invoice->owner, ent);
     mark_invoice (invoice);
     gncInvoiceCommitEdit (invoice);
 }
@@ -477,7 +477,7 @@ qofInvoiceSetBillTo (GncInvoice *invoice, QofInstance *ent)
         return;
     }
     gncInvoiceBeginEdit (invoice);
-    qofOwnerSetEntity(&invoice->billto, ent);
+    qofOwnerSetEntity (&invoice->billto, ent);
     mark_invoice (invoice);
     gncInvoiceCommitEdit (invoice);
 }
@@ -485,7 +485,7 @@ qofInvoiceSetBillTo (GncInvoice *invoice, QofInstance *ent)
 void gncInvoiceSetDateOpenedGDate (GncInvoice *invoice, const GDate *date)
 {
     g_assert (date);
-    gncInvoiceSetDateOpened(invoice, time64CanonicalDayTime(gdate_to_time64 (*date)));
+    gncInvoiceSetDateOpened(invoice, time64CanonicalDayTime (gdate_to_time64 (*date)));
 }
 
 void gncInvoiceSetDateOpened (GncInvoice *invoice, time64 date)
@@ -571,7 +571,7 @@ void gncInvoiceSetIsCreditNote (GncInvoice *invoice, gboolean credit_note)
     if (!invoice) return;
     gncInvoiceBeginEdit (invoice);
     g_value_init (&v, G_TYPE_INT64);
-    g_value_set_int64(&v, credit_note ? 1 : 0);
+    g_value_set_int64 (&v, credit_note ? 1 : 0);
     qof_instance_set_kvp (QOF_INSTANCE (invoice), &v, 1, GNC_INVOICE_IS_CN);
     mark_invoice (invoice);
     gncInvoiceCommitEdit (invoice);
@@ -653,12 +653,12 @@ void gncInvoiceAddEntry (GncInvoice *invoice, GncEntry *entry)
 {
     GncInvoice *old;
 
-    g_assert(invoice);
-    g_assert(entry);
+    g_assert (invoice);
+    g_assert (entry);
     if (!invoice || !entry) return;
 
     old = gncEntryGetInvoice (entry);
-    if (old == invoice) return;	/* I already own this one */
+    if (old == invoice) return; /* I already own this one */
     if (old) gncInvoiceRemoveEntry (old, entry);
 
     gncInvoiceBeginEdit (invoice);
@@ -689,7 +689,7 @@ void gncInvoiceAddPrice (GncInvoice *invoice, GNCPrice *price)
 
     /* Keep only one price per commodity per invoice
      * So if a price was set previously remove it first */
-    node = g_list_first(invoice->prices);
+    node = g_list_first (invoice->prices);
     commodity = gnc_price_get_commodity (price);
     while (node != NULL)
     {
@@ -702,7 +702,7 @@ void gncInvoiceAddPrice (GncInvoice *invoice, GNCPrice *price)
     gncInvoiceBeginEdit (invoice);
     if (node)
         invoice->prices = g_list_delete_link (invoice->prices, node);
-    invoice->prices = g_list_prepend(invoice->prices, price);
+    invoice->prices = g_list_prepend (invoice->prices, price);
     mark_invoice (invoice);
     gncInvoiceCommitEdit (invoice);
 }
@@ -711,12 +711,12 @@ void gncBillAddEntry (GncInvoice *bill, GncEntry *entry)
 {
     GncInvoice *old;
 
-    g_assert(bill);
-    g_assert(entry);
+    g_assert (bill);
+    g_assert (entry);
     if (!bill || !entry) return;
 
     old = gncEntryGetBill (entry);
-    if (old == bill) return;	/* I already own this one */
+    if (old == bill) return;    /* I already own this one */
     if (old) gncBillRemoveEntry (old, entry);
 
     gncInvoiceBeginEdit (bill);
@@ -741,10 +741,10 @@ void gncBillRemoveEntry (GncInvoice *bill, GncEntry *entry)
 void gncInvoiceSortEntries (GncInvoice *invoice)
 {
     if (!invoice) return;
-    invoice->entries = g_list_sort(invoice->entries,
+    invoice->entries = g_list_sort (invoice->entries,
                                    (GCompareFunc)gncEntryCompare);
     gncInvoiceBeginEdit (invoice);
-    mark_invoice(invoice);
+    mark_invoice (invoice);
     gncInvoiceCommitEdit (invoice);
 }
 
@@ -800,8 +800,7 @@ const GncOwner * gncInvoiceGetOwner (const GncInvoice *invoice)
     return &invoice->owner;
 }
 
-static QofInstance*
-qofInvoiceGetOwner (GncInvoice *invoice)
+static QofInstance * qofInvoiceGetOwner (GncInvoice *invoice)
 {
     GncOwner *owner;
 
@@ -813,8 +812,7 @@ qofInvoiceGetOwner (GncInvoice *invoice)
     return QOF_INSTANCE(owner);
 }
 
-static QofInstance*
-qofInvoiceGetBillTo (GncInvoice *invoice)
+static QofInstance * qofInvoiceGetBillTo (GncInvoice *invoice)
 {
     GncOwner *billto;
 
@@ -869,8 +867,8 @@ const char * gncInvoiceGetAssociation (const GncInvoice *invoice)
 {
     GValue v = G_VALUE_INIT;
     if (!invoice) return NULL;
-    qof_instance_get_kvp (QOF_INSTANCE (invoice), &v, 1, GNC_INVOICE_ASSOC);
-    if (G_VALUE_HOLDS_STRING (&v))
+    qof_instance_get_kvp (QOF_INSTANCE(invoice), &v, 1, GNC_INVOICE_ASSOC);
+    if (G_VALUE_HOLDS_STRING(&v))
          return g_value_get_string (&v);
     return NULL;
 }
@@ -884,10 +882,9 @@ GncOwnerType gncInvoiceGetOwnerType (const GncInvoice *invoice)
     return (gncOwnerGetType (owner));
 }
 
-static gnc_numeric
-gncInvoiceSumTaxesInternal (AccountValueList *taxes)
+static gnc_numeric gncInvoiceSumTaxesInternal (AccountValueList *taxes)
 {
-    gnc_numeric tt = gnc_numeric_zero();
+    gnc_numeric tt = gnc_numeric_zero ();
 
     if (taxes)
     {
@@ -905,17 +902,16 @@ gncInvoiceSumTaxesInternal (AccountValueList *taxes)
     return tt;
 }
 
-static gnc_numeric
-gncInvoiceGetNetAndTaxesInternal (GncInvoice *invoice, gboolean use_value,
-                            AccountValueList **taxes,
-                            gboolean use_payment_type, GncEntryPaymentType type
-                           )
+static gnc_numeric gncInvoiceGetNetAndTaxesInternal (GncInvoice *invoice, gboolean use_value,
+                                                     AccountValueList **taxes,
+                                                     gboolean use_payment_type,
+                                                     GncEntryPaymentType type)
 {
     GList *node;
-    gnc_numeric net_total = gnc_numeric_zero();
+    gnc_numeric net_total = gnc_numeric_zero ();
     gboolean is_cust_doc, is_cn;
     AccountValueList *tv_list = NULL;
-    int denom = gnc_commodity_get_fraction(gncInvoiceGetCurrency(invoice));
+    int denom = gnc_commodity_get_fraction (gncInvoiceGetCurrency (invoice));
 
     g_return_val_if_fail (invoice, net_total);
 
@@ -926,7 +922,7 @@ gncInvoiceGetNetAndTaxesInternal (GncInvoice *invoice, gboolean use_value,
     is_cn = gncInvoiceGetIsCreditNote (invoice);
 
 
-    for (node = gncInvoiceGetEntries(invoice); node; node = node->next)
+    for (node = gncInvoiceGetEntries (invoice); node; node = node->next)
     {
         GncEntry *entry = node->data;
         gnc_numeric value, tax;
@@ -970,18 +966,17 @@ gncInvoiceGetNetAndTaxesInternal (GncInvoice *invoice, gboolean use_value,
     return net_total;
 }
 
-static gnc_numeric
-gncInvoiceGetTotalInternal(GncInvoice *invoice, gboolean use_value,
-                           gboolean use_tax,
-                           gboolean use_payment_type, GncEntryPaymentType type)
+static gnc_numeric gncInvoiceGetTotalInternal (GncInvoice *invoice, gboolean use_value,
+                                               gboolean use_tax,
+                                               gboolean use_payment_type, GncEntryPaymentType type)
 {
     AccountValueList *taxes;
     gnc_numeric total;
     int denom;
 
-    if (!invoice) return gnc_numeric_zero();
+    if (!invoice) return gnc_numeric_zero ();
 
-    total = gncInvoiceGetNetAndTaxesInternal(invoice, use_value, use_tax? &taxes : NULL, use_payment_type, type);
+    total = gncInvoiceGetNetAndTaxesInternal (invoice, use_value, use_tax? &taxes : NULL, use_payment_type, type);
 
     if (use_tax)
     {
@@ -997,26 +992,26 @@ gncInvoiceGetTotalInternal(GncInvoice *invoice, gboolean use_value,
 
 gnc_numeric gncInvoiceGetTotal (GncInvoice *invoice)
 {
-    if (!invoice) return gnc_numeric_zero();
-    return gncInvoiceGetTotalInternal(invoice, TRUE, TRUE, FALSE, 0);
+    if (!invoice) return gnc_numeric_zero ();
+    return gncInvoiceGetTotalInternal (invoice, TRUE, TRUE, FALSE, 0);
 }
 
 gnc_numeric gncInvoiceGetTotalSubtotal (GncInvoice *invoice)
 {
-    if (!invoice) return gnc_numeric_zero();
-    return gncInvoiceGetTotalInternal(invoice, TRUE, FALSE, FALSE, 0);
+    if (!invoice) return gnc_numeric_zero ();
+    return gncInvoiceGetTotalInternal (invoice, TRUE, FALSE, FALSE, 0);
 }
 
 gnc_numeric gncInvoiceGetTotalTax (GncInvoice *invoice)
 {
-    if (!invoice) return gnc_numeric_zero();
-    return gncInvoiceGetTotalInternal(invoice, FALSE, TRUE, FALSE, 0);
+    if (!invoice) return gnc_numeric_zero ();
+    return gncInvoiceGetTotalInternal (invoice, FALSE, TRUE, FALSE, 0);
 }
 
 gnc_numeric gncInvoiceGetTotalOf (GncInvoice *invoice, GncEntryPaymentType type)
 {
-    if (!invoice) return gnc_numeric_zero();
-    return gncInvoiceGetTotalInternal(invoice, TRUE, TRUE, TRUE, type);
+    if (!invoice) return gnc_numeric_zero ();
+    return gncInvoiceGetTotalInternal (invoice, TRUE, TRUE, TRUE, type);
 }
 
 AccountValueList *gncInvoiceGetTotalTaxList (GncInvoice *invoice)
@@ -1025,7 +1020,7 @@ AccountValueList *gncInvoiceGetTotalTaxList (GncInvoice *invoice)
     AccountValueList *taxes;
     if (!invoice) return NULL;
 
-    gncInvoiceGetNetAndTaxesInternal(invoice, FALSE, &taxes, FALSE, 0);
+    gncInvoiceGetNetAndTaxesInternal (invoice, FALSE, &taxes, FALSE, 0);
     return taxes;
 }
 
@@ -1058,15 +1053,15 @@ GncInvoiceType gncInvoiceGetType (const GncInvoice *invoice)
     switch (gncInvoiceGetOwnerType (invoice))
     {
     case GNC_OWNER_CUSTOMER:
-        return (gncInvoiceGetIsCreditNote(invoice) ?
+        return (gncInvoiceGetIsCreditNote (invoice) ?
                 GNC_INVOICE_CUST_CREDIT_NOTE :
                 GNC_INVOICE_CUST_INVOICE);
     case GNC_OWNER_VENDOR:
-        return (gncInvoiceGetIsCreditNote(invoice) ?
+        return (gncInvoiceGetIsCreditNote (invoice) ?
                 GNC_INVOICE_VEND_CREDIT_NOTE :
                 GNC_INVOICE_VEND_INVOICE);
     case GNC_OWNER_EMPLOYEE:
-        return (gncInvoiceGetIsCreditNote(invoice) ?
+        return (gncInvoiceGetIsCreditNote (invoice) ?
                 GNC_INVOICE_EMPL_CREDIT_NOTE :
                 GNC_INVOICE_EMPL_INVOICE);
     default:
@@ -1078,7 +1073,7 @@ GncInvoiceType gncInvoiceGetType (const GncInvoice *invoice)
 
 const char * gncInvoiceGetTypeString (const GncInvoice *invoice)
 {
-    GncInvoiceType type = gncInvoiceGetType(invoice);
+    GncInvoiceType type = gncInvoiceGetType (invoice);
     switch (type)
     {
     case GNC_INVOICE_CUST_INVOICE:
@@ -1138,7 +1133,7 @@ gboolean gncInvoiceGetIsCreditNote (const GncInvoice *invoice)
     GValue v = G_VALUE_INIT;
     if (!invoice) return FALSE;
     qof_instance_get_kvp (QOF_INSTANCE(invoice), &v, 1, GNC_INVOICE_IS_CN);
-    if (G_VALUE_HOLDS_INT64(&v) && g_value_get_int64(&v))
+    if (G_VALUE_HOLDS_INT64(&v) && g_value_get_int64 (&v))
         return TRUE;
     else
         return FALSE;
@@ -1147,7 +1142,7 @@ gboolean gncInvoiceGetIsCreditNote (const GncInvoice *invoice)
 
 gnc_numeric gncInvoiceGetToChargeAmount (const GncInvoice *invoice)
 {
-    if (!invoice) return gnc_numeric_zero();
+    if (!invoice) return gnc_numeric_zero ();
     return invoice->to_charge_amount;
 }
 
@@ -1157,18 +1152,18 @@ EntryList * gncInvoiceGetEntries (GncInvoice *invoice)
     return invoice->entries;
 }
 
-GNCPrice * gncInvoiceGetPrice(GncInvoice *invoice, gnc_commodity *commodity)
+GNCPrice * gncInvoiceGetPrice (GncInvoice *invoice, gnc_commodity *commodity)
 {
-    GList *node = g_list_first(invoice->prices);
+    GList *node = g_list_first (invoice->prices);
 
     while (node != NULL)
     {
         GNCPrice *curr = (GNCPrice*)node->data;
 
-        if (gnc_commodity_equal(commodity, gnc_price_get_commodity(curr)))
+        if (gnc_commodity_equal (commodity, gnc_price_get_commodity (curr)))
             return curr;
 
-        node = g_list_next(node);
+        node = g_list_next (node);
     }
 
     return NULL;
@@ -1181,11 +1176,11 @@ qofInvoiceGetEntries (GncInvoice *invoice)
     GList         *list;
     QofInstance     *entry;
 
-    entry_coll = qof_collection_new(GNC_ID_ENTRY);
-    for (list = gncInvoiceGetEntries(invoice); list != NULL; list = list->next)
+    entry_coll = qof_collection_new (GNC_ID_ENTRY);
+    for (list = gncInvoiceGetEntries (invoice); list != NULL; list = list->next)
     {
         entry = QOF_INSTANCE(list->data);
-        qof_collection_add_entity(entry_coll, entry);
+        qof_collection_add_entity (entry_coll, entry);
     }
     return entry_coll;
 }
@@ -1209,22 +1204,22 @@ qofInvoiceEntryCB (QofInstance *ent, gpointer user_data)
     }
     default :
     {
-        gncInvoiceAddEntry(invoice, (GncEntry*)ent);
+        gncInvoiceAddEntry (invoice, (GncEntry*)ent);
         break;
     }
     }
 }
 
 static void
-qofInvoiceSetEntries(GncInvoice *invoice, QofCollection *entry_coll)
+qofInvoiceSetEntries (GncInvoice *invoice, QofCollection *entry_coll)
 {
     if (!entry_coll)
     {
         return;
     }
-    if (0 == g_strcmp0(qof_collection_get_type(entry_coll), GNC_ID_ENTRY))
+    if (0 == g_strcmp0 (qof_collection_get_type (entry_coll), GNC_ID_ENTRY))
     {
-        qof_collection_foreach(entry_coll, qofInvoiceEntryCB, invoice);
+        qof_collection_foreach (entry_coll, qofInvoiceEntryCB, invoice);
     }
 }
 
@@ -1266,8 +1261,8 @@ gncInvoiceAttachToLot (GncInvoice *invoice, GNCLot *lot)
     if (!invoice || !lot)
         return;
 
-    if (invoice->posted_lot) return;	/* Cannot reset invoice's lot */
-    guid  = (GncGUID*)qof_instance_get_guid (QOF_INSTANCE (invoice));
+    if (invoice->posted_lot) return;    /* Cannot reset invoice's lot */
+    guid  = (GncGUID*)qof_instance_get_guid (QOF_INSTANCE(invoice));
     gnc_lot_begin_edit (lot);
     qof_instance_set (QOF_INSTANCE (lot), "invoice", guid, NULL);
     gnc_lot_commit_edit (lot);
@@ -1287,8 +1282,8 @@ GncInvoice * gncInvoiceGetInvoiceFromLot (GNCLot *lot)
     if (!invoice)
     {
         book = gnc_lot_get_book (lot);
-        qof_instance_get (QOF_INSTANCE (lot), "invoice", &guid, NULL);
-        invoice = gncInvoiceLookup(book, guid);
+        qof_instance_get (QOF_INSTANCE(lot), "invoice", &guid, NULL);
+        invoice = gncInvoiceLookup (book, guid);
         guid_free (guid);
         gnc_lot_set_cached_invoice (lot, invoice);
     }
@@ -1302,11 +1297,11 @@ gncInvoiceAttachToTxn (GncInvoice *invoice, Transaction *txn)
     if (!invoice || !txn)
         return;
 
-    if (invoice->posted_txn) return;	/* Cannot reset invoice's txn */
+    if (invoice->posted_txn) return;    /* Cannot reset invoice's txn */
 
     xaccTransBeginEdit (txn);
     qof_instance_set (QOF_INSTANCE (txn), "invoice", //Prop INVOICE
-		      qof_instance_get_guid (QOF_INSTANCE (invoice)), NULL);
+              qof_instance_get_guid (QOF_INSTANCE (invoice)), NULL);
     xaccTransSetTxnType (txn, TXN_TYPE_INVOICE);
     xaccTransCommitEdit (txn);
     gncInvoiceSetPostedTxn (invoice, txn);
@@ -1323,7 +1318,7 @@ gncInvoiceGetInvoiceFromTxn (const Transaction *txn)
 
     book = xaccTransGetBook (txn);
     qof_instance_get (QOF_INSTANCE (txn), "invoice", &guid, NULL);
-    invoice = gncInvoiceLookup(book, guid);
+    invoice = gncInvoiceLookup (book, guid);
     guid_free (guid);
     return invoice;
 }
@@ -1344,7 +1339,7 @@ gboolean gncInvoiceAmountPositive (const GncInvoice *invoice)
     default:
         /* Should never be reached.
          * If it is, perhaps a new value is added to GncInvoiceType ? */
-        g_assert_not_reached();
+        g_assert_not_reached ();
         return FALSE;
     }
 }
@@ -1424,7 +1419,7 @@ static gboolean gncInvoicePostAddSplit (QofBook *book,
 
     xaccSplitSetMemo (split, memo);
     /* set per book option */
-    gnc_set_num_action (NULL, split, gncInvoiceGetID(invoice), type);
+    gnc_set_num_action (NULL, split, gncInvoiceGetID (invoice), type);
 
     /* Need to insert this split into the account AND txn before
      * we set the Base Value.  Otherwise SetBaseValue complains
@@ -1445,7 +1440,7 @@ static gboolean gncInvoicePostAddSplit (QofBook *book,
      * The net effect is that the owner type is sufficient to determine whether a
      * value has to be reverted when converting an invoice/bill/cn amount to a split.
      */
-    if (gnc_commodity_equal(xaccAccountGetCommodity(acc), invoice->currency))
+    if (gnc_commodity_equal (xaccAccountGetCommodity (acc), invoice->currency))
     {
         xaccSplitSetBaseValue (split, value,
                                invoice->currency);
@@ -1453,7 +1448,7 @@ static gboolean gncInvoicePostAddSplit (QofBook *book,
     else
     {
         /*need to do conversion */
-        GNCPrice *price = gncInvoiceGetPrice(invoice, xaccAccountGetCommodity(acc));
+        GNCPrice *price = gncInvoiceGetPrice (invoice, xaccAccountGetCommodity (acc));
 
         if (price == NULL)
         {
@@ -1466,10 +1461,10 @@ static gboolean gncInvoicePostAddSplit (QofBook *book,
         else
         {
             gnc_numeric converted_amount;
-            xaccSplitSetValue(split, value);
-            converted_amount = gnc_numeric_div(value, gnc_price_get_value(price), GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
-            DEBUG("converting from %f to %f\n", gnc_numeric_to_double(value), gnc_numeric_to_double(converted_amount));
-            xaccSplitSetAmount(split, converted_amount);
+            xaccSplitSetValue (split, value);
+            converted_amount = gnc_numeric_div (value, gnc_price_get_value (price), GNC_DENOM_AUTO, GNC_HOW_RND_ROUND_HALF_UP);
+            DEBUG("converting from %f to %f\n", gnc_numeric_to_double (value), gnc_numeric_to_double (converted_amount));
+            xaccSplitSetAmount (split, converted_amount);
         }
     }
 
@@ -1493,14 +1488,14 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc,
     char *lot_title;
     Account *ccard_acct = NULL;
     const GncOwner *owner;
-    int denom = xaccAccountGetCommoditySCU(acc);
+    int denom = xaccAccountGetCommoditySCU (acc);
     AccountValueList *taxes;
 
     if (!invoice || !acc) return NULL;
     if (gncInvoiceIsPosted (invoice)) return NULL;
-    
+
     gncInvoiceBeginEdit (invoice);
-    book = qof_instance_get_book(invoice);
+    book = qof_instance_get_book (invoice);
 
     /* Stabilize the Billing Terms of this invoice */
     if (invoice->terms)
@@ -1570,7 +1565,7 @@ Transaction * gncInvoicePostToAccount (GncInvoice *invoice, Account *acc,
      * then create the appropriate splits in this txn.
      */
 
-    for (iter = gncInvoiceGetEntries(invoice); iter; iter = iter->next)
+    for (iter = gncInvoiceGetEntries (invoice); iter; iter = iter->next)
     {
         gnc_numeric value, tax;
         GncEntry * entry = iter->data;
@@ -1867,21 +1862,21 @@ gncInvoiceUnpost (GncInvoice *invoice, gboolean reset_tax_tables)
     /* if we've been asked to reset the tax tables, then do so */
     if (reset_tax_tables)
     {
-        gboolean is_cust_doc = (gncInvoiceGetOwnerType(invoice) == GNC_OWNER_CUSTOMER);
+        gboolean is_cust_doc = (gncInvoiceGetOwnerType (invoice) == GNC_OWNER_CUSTOMER);
         GList *iter;
 
-        for (iter = gncInvoiceGetEntries(invoice); iter; iter = iter->next)
+        for (iter = gncInvoiceGetEntries (invoice); iter; iter = iter->next)
         {
             GncEntry *entry = iter->data;
 
-            gncEntryBeginEdit(entry);
+            gncEntryBeginEdit (entry);
             if (is_cust_doc)
-                gncEntrySetInvTaxTable(entry,
-                                       gncTaxTableGetParent(gncEntryGetInvTaxTable(entry)));
+                gncEntrySetInvTaxTable (entry,
+                                        gncTaxTableGetParent (gncEntryGetInvTaxTable( entry)));
             else
-                gncEntrySetBillTaxTable(entry,
-                                        gncTaxTableGetParent(gncEntryGetBillTaxTable(entry)));
-            gncEntryCommitEdit(entry);
+                gncEntrySetBillTaxTable (entry,
+                                         gncTaxTableGetParent (gncEntryGetBillTaxTable (entry)));
+            gncEntryCommitEdit (entry);
         }
     }
 
@@ -2001,27 +1996,27 @@ gncInvoiceApplyPayment (const GncInvoice *invoice, Transaction *txn,
 gboolean gncInvoiceIsPosted (const GncInvoice *invoice)
 {
     if (!invoice) return FALSE;
-    return GNC_IS_TRANSACTION(gncInvoiceGetPostedTxn(invoice));
+    return GNC_IS_TRANSACTION(gncInvoiceGetPostedTxn (invoice));
 }
 
 gboolean gncInvoiceIsPaid (const GncInvoice *invoice)
 {
     if (!invoice) return FALSE;
     if (!invoice->posted_lot) return FALSE;
-    return gnc_lot_is_closed(invoice->posted_lot);
+    return gnc_lot_is_closed (invoice->posted_lot);
 }
 
 /* ================================================================== */
 
 void gncInvoiceBeginEdit (GncInvoice *invoice)
 {
-    qof_begin_edit(&invoice->inst);
+    qof_begin_edit (&invoice->inst);
 }
 
 static void gncInvoiceOnError (QofInstance *inst, QofBackendError errcode)
 {
     PERR("Invoice QofBackend Failure: %d", errcode);
-    gnc_engine_signal_commit_error( errcode );
+    gnc_engine_signal_commit_error (errcode);
 }
 
 static void gncInvoiceOnDone (QofInstance *invoice) { }
@@ -2059,28 +2054,28 @@ gboolean gncInvoiceEqual(const GncInvoice *a, const GncInvoice *b)
     if (a == NULL && b == NULL) return TRUE;
     if (a == NULL || b == NULL) return FALSE;
 
-    g_return_val_if_fail(GNC_IS_INVOICE(a), FALSE);
-    g_return_val_if_fail(GNC_IS_INVOICE(b), FALSE);
+    g_return_val_if_fail (GNC_IS_INVOICE(a), FALSE);
+    g_return_val_if_fail (GNC_IS_INVOICE(b), FALSE);
 
-    if (g_strcmp0(a->id, b->id) != 0)
+    if (g_strcmp0 (a->id, b->id) != 0)
     {
         PWARN("IDs differ: %s vs %s", a->id, b->id);
         return FALSE;
     }
 
-    if (g_strcmp0(a->notes, b->notes) != 0)
+    if (g_strcmp0 (a->notes, b->notes) != 0)
     {
         PWARN("Notes differ: %s vs %s", a->notes, b->notes);
         return FALSE;
     }
 
-    if (g_strcmp0(a->billing_id, b->billing_id) != 0)
+    if (g_strcmp0 (a->billing_id, b->billing_id) != 0)
     {
         PWARN("Billing IDs differ: %s vs %s", a->billing_id, b->billing_id);
         return FALSE;
     }
 
-    if (g_strcmp0(a->printname, b->printname) != 0)
+    if (g_strcmp0 (a->printname, b->printname) != 0)
     {
         PWARN("Printnames differ: %s vs %s", a->printname, b->printname);
         return FALSE;
@@ -2092,38 +2087,38 @@ gboolean gncInvoiceEqual(const GncInvoice *a, const GncInvoice *b)
         return FALSE;
     }
 
-    if (!gncBillTermEqual(a->terms, b->terms))
+    if (!gncBillTermEqual (a->terms, b->terms))
     {
         PWARN("Billterms differ");
         return FALSE;
     }
 
-    if (!gncJobEqual(a->job, b->job))
+    if (!gncJobEqual (a->job, b->job))
     {
         PWARN("Jobs differ");
         return FALSE;
     }
 
-    if (!gnc_commodity_equal(a->currency, b->currency))
+    if (!gnc_commodity_equal (a->currency, b->currency))
     {
         PWARN("Currencies differ");
         return FALSE;
     }
 
-    if (!xaccAccountEqual(a->posted_acc, b->posted_acc, TRUE))
+    if (!xaccAccountEqual (a->posted_acc, b->posted_acc, TRUE))
     {
         PWARN("Posted accounts differ");
         return FALSE;
     }
 
-    if (!xaccTransEqual(a->posted_txn, b->posted_txn, TRUE, TRUE, TRUE, FALSE))
+    if (!xaccTransEqual (a->posted_txn, b->posted_txn, TRUE, TRUE, TRUE, FALSE))
     {
         PWARN("Posted tx differ");
         return FALSE;
     }
 
 #if 0
-    if (!gncLotEqual(a->posted_lot, b->posted_lot))
+    if (!gncLotEqual (a->posted_lot, b->posted_lot))
     {
         PWARN("Posted lots differ");
         return FALSE;
@@ -2136,10 +2131,10 @@ gboolean gncInvoiceEqual(const GncInvoice *a, const GncInvoice *b)
     GList       *prices;
     GncOwner    owner;
     GncOwner    billto;
-    time64    date_opened;
-    time64    date_posted;
+    time64      date_opened;
+    time64      date_posted;
 
-    gnc_numeric	to_charge_amount;
+    gnc_numeric to_charge_amount;
 #endif
 
     return TRUE;
@@ -2154,7 +2149,7 @@ static const char * _gncInvoicePrintable (gpointer obj)
 
     g_return_val_if_fail (invoice, NULL);
 
-    if (qof_instance_get_dirty_flag(invoice) || invoice->printname == NULL)
+    if (qof_instance_get_dirty_flag (invoice) || invoice->printname == NULL)
     {
         if (invoice->printname) g_free (invoice->printname);
 
@@ -2167,21 +2162,21 @@ static const char * _gncInvoicePrintable (gpointer obj)
 }
 
 static void
-destroy_invoice_on_book_close(QofInstance *ent, gpointer data)
+destroy_invoice_on_book_close (QofInstance *ent, gpointer data)
 {
     GncInvoice* invoice = GNC_INVOICE(ent);
 
-    gncInvoiceBeginEdit(invoice);
-    gncInvoiceDestroy(invoice);
+    gncInvoiceBeginEdit (invoice);
+    gncInvoiceDestroy (invoice);
 }
 
 static void
-gnc_invoice_book_end(QofBook* book)
+gnc_invoice_book_end (QofBook* book)
 {
     QofCollection *col;
 
-    col = qof_book_get_collection(book, GNC_ID_INVOICE);
-    qof_collection_foreach(col, destroy_invoice_on_book_close, NULL);
+    col = qof_book_get_collection (book, GNC_ID_INVOICE);
+    qof_collection_foreach (col, destroy_invoice_on_book_close, NULL);
 }
 
 static QofObject gncInvoiceDesc =
@@ -2266,14 +2261,14 @@ gboolean gncInvoiceRegister (void)
     /* Make the compiler happy... */
     if (0)
     {
-        qofInvoiceSetEntries(NULL, NULL);
-        qofInvoiceGetEntries(NULL);
-        qofInvoiceSetOwner(NULL, NULL);
-        qofInvoiceGetOwner(NULL);
-        qofInvoiceSetBillTo(NULL, NULL);
-        qofInvoiceGetBillTo(NULL);
+        qofInvoiceSetEntries (NULL, NULL);
+        qofInvoiceGetEntries (NULL);
+        qofInvoiceSetOwner (NULL, NULL);
+        qofInvoiceGetOwner (NULL);
+        qofInvoiceSetBillTo (NULL, NULL);
+        qofInvoiceGetBillTo (NULL);
     }
-    if (!qof_choice_create(GNC_ID_INVOICE))
+    if (!qof_choice_create (GNC_ID_INVOICE))
     {
         return FALSE;
     }
@@ -2283,7 +2278,7 @@ gboolean gncInvoiceRegister (void)
 gchar *gncInvoiceNextID (QofBook *book, const GncOwner *owner)
 {
     gchar *nextID;
-    switch (gncOwnerGetType(gncOwnerGetEndOwner(owner)))
+    switch (gncOwnerGetType (gncOwnerGetEndOwner (owner)))
     {
     case GNC_OWNER_CUSTOMER:
         nextID = qof_book_increment_and_format_counter (book, "gncInvoice");
diff --git a/libgnucash/engine/gncInvoice.h b/libgnucash/engine/gncInvoice.h
index d5d1965b9..46e6f24bb 100644
--- a/libgnucash/engine/gncInvoice.h
+++ b/libgnucash/engine/gncInvoice.h
@@ -172,7 +172,7 @@ AccountValueList *gncInvoiceGetTotalTaxList (GncInvoice *invoice);
 
 typedef GList EntryList;
 EntryList * gncInvoiceGetEntries (GncInvoice *invoice);
-GNCPrice * gncInvoiceGetPrice(GncInvoice *invoice, gnc_commodity* commodity);
+GNCPrice * gncInvoiceGetPrice (GncInvoice *invoice, gnc_commodity* commodity);
 
 /** Depending on the invoice type, invoices have a different effect
  *  on the balance. Customer invoices increase the balance, while
@@ -307,14 +307,14 @@ gboolean gncInvoiceIsPaid (const GncInvoice *invoice);
 #define INVOICE_FROM_LOT    "invoice-from-lot"
 #define INVOICE_FROM_TXN    "invoice-from-txn"
 
-QofBook *gncInvoiceGetBook(GncInvoice *x);
+QofBook *gncInvoiceGetBook (GncInvoice *x);
 
 /** deprecated functions */
-#define gncInvoiceGetGUID(x) qof_instance_get_guid(QOF_INSTANCE(x))
-#define gncInvoiceRetGUID(x) (x ? *(qof_instance_get_guid(QOF_INSTANCE(x))) : *(guid_null()))
+#define gncInvoiceGetGUID(x) qof_instance_get_guid (QOF_INSTANCE(x))
+#define gncInvoiceRetGUID(x) (x ? *(qof_instance_get_guid (QOF_INSTANCE(x))) : *(guid_null()))
 
 /** Test support function used by test-dbi-business-stuff.c */
-gboolean gncInvoiceEqual(const GncInvoice *a, const GncInvoice *b);
+gboolean gncInvoiceEqual (const GncInvoice *a, const GncInvoice *b);
 
 #endif /* GNC_INVOICE_H_ */
 /** @} */

commit 69aeacb621427a91a4c01d09b77707cd280566c8
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:18:25 2020 +0100

    Add KVP Association property to GncInvoice

diff --git a/libgnucash/engine/gncInvoice.c b/libgnucash/engine/gncInvoice.c
index 364798b86..c2a376a9c 100644
--- a/libgnucash/engine/gncInvoice.c
+++ b/libgnucash/engine/gncInvoice.c
@@ -84,6 +84,7 @@ static QofLogModule log_module = GNC_MOD_BUSINESS;
 #define _GNC_MOD_NAME     GNC_ID_INVOICE
 
 #define GNC_INVOICE_IS_CN "credit-note"
+#define GNC_INVOICE_ASSOC "assoc_uri"
 
 #define SET_STR(obj, member, str) { \
 	char * tmp; \
@@ -537,6 +538,23 @@ void gncInvoiceSetNotes (GncInvoice *invoice, const char *notes)
     gncInvoiceCommitEdit (invoice);
 }
 
+void gncInvoiceSetAssociation (GncInvoice *invoice, const char *assoc)
+{
+    if (!invoice || !assoc) return;
+    gncInvoiceBeginEdit (invoice);
+    if (g_strcmp0 (assoc, "") == 0)
+        qof_instance_set_kvp (QOF_INSTANCE (invoice), NULL, 1, GNC_INVOICE_ASSOC);
+    else
+    {
+        GValue v = G_VALUE_INIT;
+        g_value_init (&v, G_TYPE_STRING);
+        g_value_set_string (&v, assoc);
+        qof_instance_set_kvp (QOF_INSTANCE (invoice), &v, 1, GNC_INVOICE_ASSOC);
+    }
+    qof_instance_set_dirty (QOF_INSTANCE(invoice));
+    gncInvoiceCommitEdit (invoice);
+}
+
 void gncInvoiceSetActive (GncInvoice *invoice, gboolean active)
 {
     if (!invoice) return;
@@ -847,6 +865,16 @@ const char * gncInvoiceGetNotes (const GncInvoice *invoice)
     return invoice->notes;
 }
 
+const char * gncInvoiceGetAssociation (const GncInvoice *invoice)
+{
+    GValue v = G_VALUE_INIT;
+    if (!invoice) return NULL;
+    qof_instance_get_kvp (QOF_INSTANCE (invoice), &v, 1, GNC_INVOICE_ASSOC);
+    if (G_VALUE_HOLDS_STRING (&v))
+         return g_value_get_string (&v);
+    return NULL;
+}
+
 GncOwnerType gncInvoiceGetOwnerType (const GncInvoice *invoice)
 {
     const GncOwner *owner;
@@ -2214,6 +2242,7 @@ gboolean gncInvoiceRegister (void)
         { INVOICE_IS_PAID,   QOF_TYPE_BOOLEAN, (QofAccessFunc)gncInvoiceIsPaid,    NULL },
         { INVOICE_BILLINGID, QOF_TYPE_STRING,  (QofAccessFunc)gncInvoiceGetBillingID, (QofSetterFunc)gncInvoiceSetBillingID },
         { INVOICE_NOTES,     QOF_TYPE_STRING,  (QofAccessFunc)gncInvoiceGetNotes,   (QofSetterFunc)gncInvoiceSetNotes },
+        { INVOICE_ASSOCIATION, QOF_TYPE_STRING, (QofAccessFunc)gncInvoiceGetAssociation, (QofSetterFunc)gncInvoiceSetAssociation },
         { INVOICE_ACC,       GNC_ID_ACCOUNT,   (QofAccessFunc)gncInvoiceGetPostedAcc, (QofSetterFunc)gncInvoiceSetPostedAcc },
         { INVOICE_POST_TXN,  GNC_ID_TRANS,     (QofAccessFunc)gncInvoiceGetPostedTxn, (QofSetterFunc)gncInvoiceSetPostedTxn },
         { INVOICE_POST_LOT,  GNC_ID_LOT,       (QofAccessFunc)gncInvoiceGetPostedLot, NULL/*(QofSetterFunc)gncInvoiceSetPostedLot*/ },
diff --git a/libgnucash/engine/gncInvoice.h b/libgnucash/engine/gncInvoice.h
index 4fc2e527e..d5d1965b9 100644
--- a/libgnucash/engine/gncInvoice.h
+++ b/libgnucash/engine/gncInvoice.h
@@ -108,6 +108,7 @@ void gncInvoiceSetDatePosted (GncInvoice *invoice, time64 date);
 void gncInvoiceSetTerms (GncInvoice *invoice, GncBillTerm *terms);
 void gncInvoiceSetBillingID (GncInvoice *invoice, const char *billing_id);
 void gncInvoiceSetNotes (GncInvoice *invoice, const char *notes);
+void gncInvoiceSetAssociation (GncInvoice *invoice, const char *assoc);
 void gncInvoiceSetCurrency (GncInvoice *invoice, gnc_commodity *currency);
 void gncInvoiceSetActive (GncInvoice *invoice, gboolean active);
 void gncInvoiceSetIsCreditNote (GncInvoice *invoice, gboolean credit_note);
@@ -143,6 +144,7 @@ time64 gncInvoiceGetDateDue (const GncInvoice *invoice);
 GncBillTerm * gncInvoiceGetTerms (const GncInvoice *invoice);
 const char * gncInvoiceGetBillingID (const GncInvoice *invoice);
 const char * gncInvoiceGetNotes (const GncInvoice *invoice);
+const char * gncInvoiceGetAssociation (const GncInvoice *invoice);
 GncOwnerType gncInvoiceGetOwnerType (const GncInvoice *invoice);
 GList * gncInvoiceGetTypeListForOwnerType (const GncOwnerType type);
 GncInvoiceType gncInvoiceGetType (const GncInvoice *invoice);
@@ -291,6 +293,7 @@ gboolean gncInvoiceIsPaid (const GncInvoice *invoice);
 #define INVOICE_TERMS       "terms"
 #define INVOICE_BILLINGID   "billing_id"
 #define INVOICE_NOTES       "notes"
+#define INVOICE_ASSOCIATION "assoc"
 #define INVOICE_ACC         "account"
 #define INVOICE_POST_TXN    "posted_txn"
 #define INVOICE_POST_LOT    "posted_lot"

commit 1b8cad00866dfe8fc3c6a8c16ac1a755b6f691a8
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:17:42 2020 +0100

    Change register sheet associations tooltip to use
     dialog-assoc-utils functions

diff --git a/gnucash/gnome-utils/dialog-assoc-utils.c b/gnucash/gnome-utils/dialog-assoc-utils.c
index 5818554f1..f384f18ed 100644
--- a/gnucash/gnome-utils/dialog-assoc-utils.c
+++ b/gnucash/gnome-utils/dialog-assoc-utils.c
@@ -118,6 +118,18 @@ gnc_assoc_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_sche
     return use_str;
 }
 
+gchar *
+gnc_assoc_get_unescaped_just_uri (const gchar *uri)
+{
+    gchar *path_head = gnc_assoc_get_path_head ();
+    gchar *uri_scheme = gnc_uri_get_scheme (uri);
+    gchar *ret_uri = gnc_assoc_get_unescape_uri (path_head, uri, uri_scheme);
+
+    g_free (path_head);
+    g_free (uri_scheme);
+    return ret_uri;
+}
+
 gchar *
 gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro)
 {
diff --git a/gnucash/gnome-utils/dialog-assoc-utils.h b/gnucash/gnome-utils/dialog-assoc-utils.h
index da6f0c4f5..9f91edcf7 100644
--- a/gnucash/gnome-utils/dialog-assoc-utils.h
+++ b/gnucash/gnome-utils/dialog-assoc-utils.h
@@ -89,6 +89,20 @@ gchar * gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro)
  */
 gchar * gnc_assoc_get_unescape_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme);
 
+/** Return an unescaped uri for display use just based on the uri.
+ *
+ *  The function allocates memory for the uri. The calling function should
+ *  free this memory with g_free when the unescaped uri is no longer needed.
+
+ *  Return an unesacped uri for displaying and if OS is windows change the
+ *  '/' to '\' to look like a traditional windows path
+ *
+ *  @param uri The association
+ *
+ *  @return The unescaped uri used for display purposes.
+ */
+gchar * gnc_assoc_get_unescaped_just_uri (const gchar *uri);
+
 /** Presents a dialog when the path head is changed.
  *
  *  When the path head is changed a dialog is raised that allows for
diff --git a/gnucash/register/ledger-core/split-register-model.c b/gnucash/register/ledger-core/split-register-model.c
index 4d25d3788..85fa59586 100644
--- a/gnucash/register/ledger-core/split-register-model.c
+++ b/gnucash/register/ledger-core/split-register-model.c
@@ -27,6 +27,7 @@
 
 #include "datecell.h"
 #include "dialog-utils.h"
+#include "dialog-assoc-utils.h"
 #include "gnc-engine.h"
 #include "gnc-prefs.h"
 #include "gnc-ui.h"
@@ -559,45 +560,8 @@ gnc_split_register_get_associate_tooltip (VirtualLocation virt_loc,
     uri = xaccTransGetAssociation (trans);
 
     // Check for uri is empty or NULL
-    if (uri && *uri != '\0')
-    {
-        gchar* scheme = gnc_uri_get_scheme (uri);
-        gchar* file_path = NULL;
-
-        if (!scheme) // relative path
-        {
-            gchar* path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL,
-                                                     "assoc-head");
-
-            if (path_head && *path_head != '\0') // not default entry
-                file_path = gnc_file_path_absolute (gnc_uri_get_path (path_head), uri);
-            else
-                file_path = gnc_file_path_absolute (NULL, uri);
-
-            g_free (path_head);
-        }
-
-        if (gnc_uri_is_file_scheme (scheme)) // absolute path
-            file_path = gnc_uri_get_path (uri);
-
-#ifdef G_OS_WIN32 // make path look like a traditional windows path
-        if (file_path)
-            file_path = g_strdelimit (file_path, "/", '\\');
-#endif
-
-        g_free (scheme);
-
-        if (!file_path)
-            return g_uri_unescape_string (uri, NULL);
-        else
-        {
-            gchar* file_uri_u = g_uri_unescape_string (file_path, NULL);
-            const gchar* filename = gnc_uri_get_path (file_uri_u);
-            g_free (file_uri_u);
-            g_free (file_path);
-            return g_strdup (filename);
-        }
-    }
+    if (uri && *uri)
+        return gnc_assoc_get_unescaped_just_uri (uri);
     else
         return NULL;
 }

commit f31749f4900f1e30502fbf267be6da49b677cb7b
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:08:36 2020 +0100

    Display glyphs for Transaction Associations
    
    Display glyphs in the register for transaction associations if a font
    has them. A test is made when the register is loaded to see if there is
    a font that has the two glyphs available otherwise the usual characters
    are used. The two glyphs used are...
    
    GLYPH_PAPERCLIP  // Codepoint U+1F4CE
    GLYPH_LINK       // Codepoint U+1F517

diff --git a/gnucash/register/ledger-core/split-register-layout.c b/gnucash/register/ledger-core/split-register-layout.c
index ae5973e85..d481745b9 100644
--- a/gnucash/register/ledger-core/split-register-layout.c
+++ b/gnucash/register/ledger-core/split-register-layout.c
@@ -699,7 +699,7 @@ gnc_split_register_layout_add_cells (SplitRegister* reg,
 
     gnc_register_add_cell (layout,
                            ASSOC_CELL,
-                           RECN_CELL_TYPE_NAME,
+                           ASSOC_CELL_TYPE_NAME,
                            C_ ("Column header for 'Associate'", "A"),
                            CELL_ALIGN_CENTER,
                            FALSE,
diff --git a/gnucash/register/ledger-core/split-register-load.c b/gnucash/register/ledger-core/split-register-load.c
index 10385c24d..bdc920a7b 100644
--- a/gnucash/register/ledger-core/split-register-load.c
+++ b/gnucash/register/ledger-core/split-register-load.c
@@ -34,6 +34,7 @@
 #include "gnc-gui-query.h"
 #include "numcell.h"
 #include "quickfillcell.h"
+#include "assoccell.h"
 #include "recncell.h"
 #include "split-register.h"
 #include "split-register-p.h"
@@ -71,19 +72,22 @@ gnc_split_register_load_recn_cells (SplitRegister* reg)
 static void
 gnc_split_register_load_associate_cells (SplitRegister* reg)
 {
-    RecnCell* cell;
+    AssocCell *cell;
+    const char * s;
 
     if (!reg) return;
 
-    cell = (RecnCell*)
+    cell = (AssocCell *)
            gnc_table_layout_get_cell (reg->table->layout, ASSOC_CELL);
 
     if (!cell) return;
 
     /* FIXME: These should get moved to an i18n function */
-    gnc_recn_cell_set_valid_flags (cell, "fw ", ' ');
-    gnc_recn_cell_set_flag_order (cell, "fw ");
-    gnc_recn_cell_set_read_only (cell, TRUE);
+    s = gnc_get_association_valid_flags ();
+    gnc_assoc_cell_set_valid_flags (cell, s, ' ');
+    gnc_assoc_cell_set_flag_order (cell, gnc_get_association_flag_order ());
+    gnc_assoc_cell_set_string_getter (cell, gnc_get_association_str);
+    gnc_assoc_cell_set_read_only (cell, TRUE);
 }
 
 static void
@@ -412,6 +416,9 @@ gnc_split_register_load (SplitRegister* reg, GList* slist,
     ((PriceCell*) gnc_table_layout_get_cell (table->layout, CRED_CELL),
      gnc_account_print_info (default_account, FALSE));
 
+    gnc_assoc_cell_set_use_glyphs
+    ((AssocCell *) gnc_table_layout_get_cell (table->layout, ASSOC_CELL));
+
     /* make sure we have a blank split */
     if (blank_split == NULL)
     {
diff --git a/gnucash/register/ledger-core/split-register-model.c b/gnucash/register/ledger-core/split-register-model.c
index 13a6002df..4d25d3788 100644
--- a/gnucash/register/ledger-core/split-register-model.c
+++ b/gnucash/register/ledger-core/split-register-model.c
@@ -33,6 +33,7 @@
 #include "gnc-uri-utils.h"
 #include "gnc-filepath-utils.h"
 #include "gnc-warnings.h"
+#include "assoccell.h"
 #include "pricecell.h"
 #include "recncell.h"
 #include "split-register.h"
@@ -840,8 +841,13 @@ gnc_split_register_get_associate_entry (VirtualLocation virt_loc,
     SplitRegister* reg = user_data;
     Transaction* trans;
     char associate;
-    static char s[2];
     const char* uri;
+    AssocCell *cell;
+
+    cell = (AssocCell *)gnc_table_layout_get_cell (reg->table->layout, ASSOC_CELL);
+
+    if (!cell)
+        return NULL;
 
     trans = gnc_split_register_get_trans (reg, virt_loc.vcell_loc);
     if (!trans)
@@ -851,24 +857,41 @@ gnc_split_register_get_associate_entry (VirtualLocation virt_loc,
     uri = xaccTransGetAssociation (trans);
 
     // Check for uri is empty or NULL
-    if (uri && g_strcmp0 (uri, "") != 0)
+    if (uri && *uri)
     {
         gchar* scheme = gnc_uri_get_scheme (uri);
 
         if (!scheme || g_strcmp0 (scheme, "file") == 0)
-            associate = 'f';
+            associate = FASSOC;
         else
-            associate = 'w';
+            associate = WASSOC;
 
         g_free (scheme);
     }
     else
         associate = ' ';
 
-    s[0] = associate;
-    s[1] = '\0';
+    if (gnc_assoc_get_use_glyphs (cell))
+        return gnc_assoc_get_glyph_from_flag (associate);
 
-    return s;
+    if (translate)
+        return gnc_get_association_str (associate);
+    else
+    {
+        static char s[2];
+
+        s[0] = associate;
+        s[1] = '\0';
+        return s;
+    }
+}
+
+static char *
+gnc_split_register_get_associate_help (VirtualLocation virt_loc,
+                                       gpointer user_data)
+{
+    // do not want contents displayed as help so return space
+    return g_strdup (" ");
 }
 
 #if 0
@@ -877,13 +900,13 @@ static char
 gnc_split_register_get_associate_value (SplitRegister* reg,
                                         VirtualLocation virt_loc)
 {
-    RecnCell* cell;
+    AssocCell *cell;
 
-    cell = (RecnCell*)gnc_table_layout_get_cell (reg->table->layout, ASSOC_CELL);
+    cell = (AssocCell *)gnc_table_layout_get_cell (reg->table->layout, ASSOC_CELL);
     if (!cell)
         return '\0';
 
-    return gnc_recn_cell_get_flag (cell);
+    return gnc_assoc_cell_get_flag (cell);
 }
 #endif
 
@@ -2027,6 +2050,16 @@ gnc_split_register_get_recn_io_flags (VirtualLocation virt_loc,
     return XACC_CELL_ALLOW_ALL | XACC_CELL_ALLOW_EXACT_ONLY;
 }
 
+static CellIOFlags
+gnc_split_register_get_assoc_io_flags (VirtualLocation virt_loc,
+                                       gpointer user_data)
+{
+    if (gnc_split_register_cursor_is_readonly (virt_loc, user_data))
+        return XACC_CELL_ALLOW_READ_ONLY;
+
+    return XACC_CELL_ALLOW_ALL | XACC_CELL_ALLOW_EXACT_ONLY;
+}
+
 static CellIOFlags
 gnc_split_register_get_ddue_io_flags (VirtualLocation virt_loc,
                                       gpointer user_data)
@@ -2786,6 +2819,10 @@ gnc_split_register_model_new (void)
                                       gnc_split_register_get_fdebt_help,
                                       FDEBT_CELL);
 
+    gnc_table_model_set_help_handler (model,
+                                      gnc_split_register_get_associate_help,
+                                      ASSOC_CELL);
+
     // io flag handlers
     gnc_table_model_set_io_flags_handler (
         model, gnc_split_register_get_standard_io_flags, DATE_CELL);
@@ -2838,7 +2875,7 @@ gnc_split_register_model_new (void)
         model, gnc_split_register_get_recn_io_flags, RECN_CELL);
 
     gnc_table_model_set_io_flags_handler (
-        model, gnc_split_register_get_recn_io_flags, ASSOC_CELL);
+        model, gnc_split_register_get_assoc_io_flags, ASSOC_CELL);
 
     gnc_table_model_set_io_flags_handler (
         model, gnc_split_register_get_recn_io_flags, TYPE_CELL);

commit 785a6a8fa6be60520f9f8f96fe3d36c09d83ae03
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:07:28 2020 +0100

    Add a new cell type for Transaction associations that
     supports the use of glyphs

diff --git a/gnucash/register/register-core/CMakeLists.txt b/gnucash/register/register-core/CMakeLists.txt
index 51fa9110c..c593b84d8 100644
--- a/gnucash/register/register-core/CMakeLists.txt
+++ b/gnucash/register/register-core/CMakeLists.txt
@@ -1,4 +1,5 @@
 set (register_core_SOURCES
+  assoccell.c
   basiccell.c
   cell-factory.c
   cellblock.c
@@ -18,6 +19,7 @@ set (register_core_SOURCES
 
 
 set (register_core_HEADERS
+  assoccell.h
   basiccell.h
   cell-factory.h
   cellblock.h
diff --git a/gnucash/register/register-core/assoccell.c b/gnucash/register/register-core/assoccell.c
new file mode 100644
index 000000000..c91a5d640
--- /dev/null
+++ b/gnucash/register/register-core/assoccell.c
@@ -0,0 +1,289 @@
+/********************************************************************\
+ * assoccell.c -- association checkbox cell                         *
+ *                                                                  *
+ * 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                   *
+ *                                                                  *
+\********************************************************************/
+
+/*
+ * FILE:
+ * assoccell.c
+ *
+ * FUNCTION:
+ * Implements a mouse-click cell that allows a series
+ * of values to be clicked through and return a glyth if
+ * font allows it.
+ *
+ * HISTORY:
+ * Copyright (c) 1998 Linas Vepstas
+ * Copyright (c) 2000 Dave Peticolas
+ * Copyright (c) 2001 Derek Atkins
+ * Copyright (c) 2020 Robert Fewell
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "basiccell.h"
+#include "gnc-engine.h"
+#include "assoccell.h"
+#include "gnc-ui-util.h"
+
+static void gnc_assoc_cell_set_value (BasicCell *_cell, const char *value);
+
+const char *
+gnc_assoc_get_glyph_from_flag (char association_flag)
+{
+    switch (association_flag)
+    {
+    case WASSOC:
+        return GLYPH_LINK;
+    case FASSOC:
+        return GLYPH_PAPERCLIP;
+    default:
+        return " ";
+    }
+}
+
+static const char
+gnc_assoc_get_flag_from_glyph (const char *glyph)
+{
+    if (strcmp (glyph, GLYPH_LINK) == 0)
+        return WASSOC;
+    else if (strcmp (glyph, GLYPH_PAPERCLIP) == 0)
+        return FASSOC;
+    else
+        return ' ';
+}
+
+gboolean
+gnc_assoc_get_use_glyphs (AssocCell *cell)
+{
+    return cell->use_glyphs;
+}
+
+static const char *
+gnc_assoc_cell_get_string (AssocCell *cell, char flag)
+{
+    static char str[2] = { 0, 0 };
+
+    if (cell->use_glyphs)
+        return gnc_assoc_get_glyph_from_flag (flag);
+
+    if (cell->get_string != NULL)
+        return (cell->get_string)(flag);
+
+    str[0] = flag;
+
+    return str;
+}
+
+static gboolean
+gnc_assoc_cell_enter (BasicCell *_cell,
+                     int *cursor_position,
+                     int *start_selection,
+                     int *end_selection)
+{
+    AssocCell *cell = (AssocCell *) _cell;
+    char * this_flag;
+
+    if (cell->confirm_cb &&
+            ! (cell->confirm_cb (cell->flag, cell->confirm_data)))
+        return FALSE;
+
+    if (cell->read_only == TRUE)
+        return FALSE;
+
+    /* Find the current flag in the list of flags */
+    this_flag = strchr (cell->flag_order, cell->flag);
+
+    if (this_flag == NULL || *this_flag == '\0')
+    {
+        /* If it's not there (or the list is empty) use default_flag */
+        cell->flag = cell->default_flag;
+    }
+    else
+    {
+        /* It is in the list -- choose the -next- item in the list (wrapping
+         * around as necessary).
+         */
+        this_flag++;
+        if (*this_flag != '\0')
+            cell->flag = *this_flag;
+        else
+            cell->flag = *(cell->flag_order);
+    }
+
+    /* And set the display */
+    gnc_assoc_cell_set_flag (cell, cell->flag);
+
+    return FALSE;
+}
+
+static void
+gnc_assoc_cell_init (AssocCell *cell)
+{
+    gnc_basic_cell_init (&cell->cell);
+
+    gnc_assoc_cell_set_flag (cell, '\0');
+    cell->confirm_cb = NULL;
+    cell->get_string = NULL;
+    cell->valid_flags = "";
+    cell->flag_order = "";
+    cell->read_only = FALSE;
+    cell->use_glyphs = FALSE;
+
+    cell->cell.enter_cell = gnc_assoc_cell_enter;
+    cell->cell.set_value = gnc_assoc_cell_set_value;
+}
+
+BasicCell *
+gnc_assoc_cell_new (void)
+{
+    AssocCell * cell;
+
+    cell = g_new0 (AssocCell, 1);
+
+    gnc_assoc_cell_init (cell);
+
+    return &cell->cell;
+}
+
+/* assumes we are given the untranslated form */
+static void
+gnc_assoc_cell_set_value (BasicCell *_cell, const char *value)
+{
+    AssocCell *cell = (AssocCell *) _cell;
+    char flag;
+
+    if (!value || *value == '\0')
+    {
+        cell->flag = cell->default_flag;
+        gnc_basic_cell_set_value_internal (_cell, "");
+        return;
+    }
+
+    if (cell->use_glyphs)
+        flag = gnc_assoc_get_flag_from_glyph (value);
+    else
+    {
+        flag = cell->default_flag;
+        if (strchr (cell->valid_flags, *value) != NULL)
+            flag = *value;
+    }
+    gnc_assoc_cell_set_flag (cell, flag);
+}
+
+void
+gnc_assoc_cell_set_flag (AssocCell *cell, char flag)
+{
+    const char *string;
+
+    g_return_if_fail (cell != NULL);
+
+    cell->flag = flag;
+    string = gnc_assoc_cell_get_string (cell, flag);
+
+    gnc_basic_cell_set_value_internal (&cell->cell, string);
+}
+
+char
+gnc_assoc_cell_get_flag (AssocCell *cell)
+{
+    g_return_val_if_fail (cell != NULL, '\0');
+
+    return cell->flag;
+}
+
+void
+gnc_assoc_cell_set_string_getter (AssocCell *cell,
+                                  AssocCellStringGetter get_string)
+{
+    g_return_if_fail (cell != NULL);
+
+    cell->get_string = get_string;
+}
+
+void
+gnc_assoc_cell_set_confirm_cb (AssocCell *cell, AssocCellConfirm confirm_cb,
+                               gpointer data)
+{
+    g_return_if_fail (cell != NULL);
+
+    cell->confirm_cb = confirm_cb;
+    cell->confirm_data = data;
+}
+
+void
+gnc_assoc_cell_set_valid_flags (AssocCell *cell, const char *flags,
+                                char default_flag)
+{
+    g_return_if_fail (cell != NULL);
+    g_return_if_fail (flags != NULL);
+
+    cell->valid_flags = (char *)flags;
+    cell->default_flag = default_flag;
+}
+
+void
+gnc_assoc_cell_set_flag_order (AssocCell *cell, const char *flags)
+{
+    g_return_if_fail (cell != NULL);
+    g_return_if_fail (flags != NULL);
+
+    cell->flag_order = (char *)flags;
+}
+
+void
+gnc_assoc_cell_set_read_only (AssocCell *cell, gboolean read_only)
+{
+    g_return_if_fail (cell != NULL);
+
+    cell->read_only = read_only;
+}
+
+void
+gnc_assoc_cell_set_use_glyphs (AssocCell *cell)
+{
+    gboolean use_glyphs = TRUE;
+    gchar *test_text;
+    GtkWidget *label;
+    PangoLayout *test_layout;
+    gint count;
+
+    g_return_if_fail (cell != NULL);
+
+    label = gtk_label_new (NULL);
+    test_text = g_strconcat (GLYPH_LINK, ",", GLYPH_PAPERCLIP, NULL);
+    test_layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), test_text);
+
+    pango_layout_set_text (test_layout, test_text, strlen (test_text));
+
+    count = pango_layout_get_unknown_glyphs_count (test_layout);
+
+    if (count != 0)
+        use_glyphs = FALSE;
+
+    g_object_unref (test_layout);
+    g_free (test_text);
+
+    cell->use_glyphs = use_glyphs;
+}
diff --git a/gnucash/register/register-core/assoccell.h b/gnucash/register/register-core/assoccell.h
new file mode 100644
index 000000000..519d27c63
--- /dev/null
+++ b/gnucash/register/register-core/assoccell.h
@@ -0,0 +1,100 @@
+/********************************************************************\
+ * assoccell.h -- association checkbox cell                         *
+ *                                                                  *
+ * 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                   *
+ *                                                                  *
+\********************************************************************/
+
+/** @addtogroup Cell Cell
+ * @{
+ * @file assoccell.h
+ * @struct AssocCell
+ * @brief The AssocCell object implements a cell handler
+ * that will cycle through a series of single-character
+ * values when clicked upon by the mouse and will return a glyph
+ * if font can show it.
+ */
+/* HISTORY:
+ * Copyright (c) 1998 Linas Vepstas
+ * Copyright (c) 2000 Dave Peticolas
+ * Copyright (c) 2001 Derek Atkins
+ * Copyright (c) 2020 Robert Fewell
+ */
+
+#ifndef ASSOC_CELL_H
+#define ASSOC_CELL_H
+
+#include <glib.h>
+
+#include "basiccell.h"
+
+#define GLYPH_PAPERCLIP "\360\237\223\216" // Codepoint U+1F4CE
+#define GLYPH_LINK      "\360\237\224\227" // Codepoint U+1F517
+
+typedef const char * (*AssocCellStringGetter) (char flag);
+typedef gboolean (*AssocCellConfirm) (char old_flag, gpointer data);
+
+typedef struct
+{
+    BasicCell cell;
+
+    char flag; /** The actual flag value */
+
+    char * valid_flags;     /** The list of valid flags */
+    char * flag_order;      /** Automatic flag selection order */
+    char   default_flag;    /** Default flag for unknown user input */
+
+    AssocCellStringGetter get_string;
+    AssocCellConfirm confirm_cb;
+    gpointer confirm_data;
+    gboolean read_only;
+    gboolean use_glyphs;
+} AssocCell;
+
+BasicCell * gnc_assoc_cell_new (void);
+
+void gnc_assoc_cell_set_flag (AssocCell *cell, char flag);
+char gnc_assoc_cell_get_flag (AssocCell *cell);
+
+void gnc_assoc_cell_set_confirm_cb (AssocCell *cell,
+                                    AssocCellConfirm confirm_cb,
+                                    gpointer data);
+
+void gnc_assoc_cell_set_string_getter (AssocCell *cell,
+                                       AssocCellStringGetter getter);
+
+/** note that @param flags is copied into the RecnCell directly, but 
+ * remains the "property" of the caller.  The caller must maintain the
+ * chars pointer, and the caller must setup a mechanism to 'free' the
+ * chars pointer.  The rationale is that you may have many AssocCell
+ * objects that use the same set of flags.
+ */
+void gnc_assoc_cell_set_valid_flags (AssocCell *cell, const char *flags,
+                                     char default_flag);
+void gnc_assoc_cell_set_flag_order (AssocCell *cell, const char *flags);
+
+void gnc_assoc_cell_set_read_only (AssocCell *cell, gboolean read_only);
+
+void gnc_assoc_cell_set_use_glyphs (AssocCell *cell);
+
+gboolean gnc_assoc_get_use_glyphs (AssocCell *cell);
+
+const char * gnc_assoc_get_glyph_from_flag (char association_flag);
+
+/** @} */
+#endif
diff --git a/gnucash/register/register-core/register-common.c b/gnucash/register/register-core/register-common.c
index c350a7650..47818881e 100644
--- a/gnucash/register/register-core/register-common.c
+++ b/gnucash/register/register-core/register-common.c
@@ -23,6 +23,7 @@
 
 #include <config.h>
 
+#include "assoccell.h"
 #include "basiccell.h"
 #include "cell-factory.h"
 #include "combocell.h"
@@ -57,6 +58,8 @@ gnc_register_init (void)
 
     gnc_register_add_cell_type (RECN_CELL_TYPE_NAME, gnc_recn_cell_new);
 
+    gnc_register_add_cell_type (ASSOC_CELL_TYPE_NAME, gnc_assoc_cell_new);
+
     gnc_register_add_cell_type (QUICKFILL_CELL_TYPE_NAME,
                                 gnc_quickfill_cell_new);
 
diff --git a/gnucash/register/register-core/register-common.h b/gnucash/register/register-core/register-common.h
index 6d1990a18..0d6ca83e5 100644
--- a/gnucash/register/register-core/register-common.h
+++ b/gnucash/register/register-core/register-common.h
@@ -67,6 +67,7 @@
 #define NUM_CELL_TYPE_NAME       "num-cell"
 #define PRICE_CELL_TYPE_NAME     "price-cell"
 #define RECN_CELL_TYPE_NAME      "recn-cell"
+#define ASSOC_CELL_TYPE_NAME     "assoc-cell"
 #define QUICKFILL_CELL_TYPE_NAME "quickfill-cell"
 #define FORMULA_CELL_TYPE_NAME   "formula-cell"
 #define CHECKBOX_CELL_TYPE_NAME	 "checkbox-cell"
diff --git a/libgnucash/app-utils/gnc-ui-util.c b/libgnucash/app-utils/gnc-ui-util.c
index 1091478d5..5b66feb02 100644
--- a/libgnucash/app-utils/gnc-ui-util.c
+++ b/libgnucash/app-utils/gnc-ui-util.c
@@ -902,6 +902,36 @@ gnc_get_reconcile_flag_order (void)
     return flags;
 }
 
+const char *
+gnc_get_association_str (char association_flag)
+{
+    switch (association_flag)
+    {
+    case WASSOC:
+        return C_("Association flag for 'web'", "w");
+    case FASSOC:
+        return C_("Association flag for 'file'", "f");
+    case ' ':
+        return " ";
+    default:
+        PERR("Bad association flag");
+        return NULL;
+    }
+}
+
+const char *
+gnc_get_association_valid_flags (void)
+{
+    static const char flags[] = { FASSOC, WASSOC, ' ', 0 };
+    return flags;
+}
+
+const char *
+gnc_get_association_flag_order (void)
+{
+    static const char flags[] = { FASSOC, WASSOC, ' ', 0 };
+    return flags;
+}
 
 static const char *
 equity_base_name (GNCEquityType equity_type)
@@ -1522,7 +1552,7 @@ PrintAmountInternal(char *buf, gnc_numeric val, const GNCPrintAmountInfo *info)
     }
     min_dp = info->min_decimal_places;
     max_dp = info->max_decimal_places;
-    
+
     /* Don to limit the number of decimal places _UNLESS_ force_fit is
      * true. */
     if (!info->force_fit)
diff --git a/libgnucash/app-utils/gnc-ui-util.h b/libgnucash/app-utils/gnc-ui-util.h
index 48ca85b52..ccc0e3a01 100644
--- a/libgnucash/app-utils/gnc-ui-util.h
+++ b/libgnucash/app-utils/gnc-ui-util.h
@@ -189,6 +189,29 @@ const char * gnc_get_reconcile_str (char reconciled_flag);
 const char * gnc_get_reconcile_valid_flags (void);
 const char * gnc_get_reconcile_flag_order (void);
 
+#define WASSOC 'w'
+#define FASSOC 'f'
+
+/** Get a string containing association valid flags
+ *
+ *  @return a string containing the list of associated flags
+ */
+const char *gnc_get_association_valid_flags (void);
+
+/** Get a string containing association flag order
+ *
+ * @return a string containing the association flag change order
+ */
+const char *gnc_get_association_flag_order (void);
+
+/** Get a string representing the association type
+ *
+ * @param  association_flag The flag to convert into a string
+ *
+ * @return the i18n'd association string
+ */
+const char *gnc_get_association_str (char association_flag);
+
 typedef enum
 {
     EQUITY_OPENING_BALANCE,
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 881c4f03a..62acd2cc8 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -396,6 +396,7 @@ gnucash/register/ledger-core/split-register-load.c
 gnucash/register/ledger-core/split-register-model.c
 gnucash/register/ledger-core/split-register-model-save.c
 gnucash/register/ledger-core/split-register-util.c
+gnucash/register/register-core/assoccell.c
 gnucash/register/register-core/basiccell.c
 gnucash/register/register-core/cellblock.c
 gnucash/register/register-core/cell-factory.c

commit 260c7b32dc8a7a5f2020a0c6c51c341904e5a048
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:05:34 2020 +0100

    Change the File dialogue for associations
    
    Change the way the user picks a file association from using a
    GtkFileChooserWidget to a GtkFileChooserButton so that you will get a
    native file dialogue if you have at least Gtk+ version 3.20

diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index c01f8603a..56634950e 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -124,9 +124,9 @@ location_ok_cb (GtkEditable *editable, gpointer user_data)
 }
 
 static void
-assoc_file_chooser_selection_changed_cb (GtkFileChooser *chooser, GtkWidget *ok_button)
+file_set_cb (GtkFileChooserButton *button, GtkWidget *ok_button)
 {
-    gchar *file_name = gtk_file_chooser_get_filename (chooser);
+    gchar *file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(button));
     gboolean file_true = FALSE;
 
     /* Test for a valid filename and not a directory */
@@ -153,15 +153,11 @@ uri_type_selected_cb (GtkToggleButton *button, GtkWidget *widget)
     {
         if (g_strcmp0 (gtk_buildable_get_name (
                              GTK_BUILDABLE(parent_hbox)), "location_hbox") == 0)
-        {
             location_ok_cb (GTK_EDITABLE(widget), ok_button);
-            gtk_window_resize (GTK_WINDOW(top), 600, 10); // width, height
-        }
         else
-        {
-            assoc_file_chooser_selection_changed_cb (GTK_FILE_CHOOSER(widget), ok_button);
-            gtk_window_resize (GTK_WINDOW(top), 900, 500);
-        }
+            file_set_cb (GTK_FILE_CHOOSER_BUTTON(widget), ok_button);
+
+        gtk_window_resize (GTK_WINDOW(top), 600, 10); // width, height
     }
     gtk_widget_grab_focus (GTK_WIDGET(widget));
 }
@@ -189,42 +185,42 @@ setup_location_dialog (GtkBuilder *builder, GtkWidget *button_loc, const gchar *
 }
 
 static void
-setup_file_dialog (GtkFileChooserWidget *fc, const gchar *path_head, const gchar *uri, gchar *scheme)
+setup_file_dialog (GtkBuilder *builder, GtkFileChooserButton *fcb,
+                   const gchar *path_head, const gchar *uri, gchar *scheme)
 {
     gchar *display_uri = gnc_assoc_get_unescape_uri (path_head, uri, scheme);
 
     if (display_uri)
     {
-        GtkWidget *label, *hbox;
+        GtkWidget *label;
+        GtkWidget *existing_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "existing_hbox"));
         GtkWidget *image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_SMALL_TOOLBAR);
         gchar     *use_uri = gnc_assoc_get_use_uri (path_head, uri, scheme);
         gchar     *uri_label = g_strdup_printf ("%s '%s'", _("Existing Association is"), display_uri);
 
-        hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
         label = gtk_label_new (uri_label);
 
         if (g_file_test (display_uri, G_FILE_TEST_EXISTS))
-            gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, TRUE, 0);
+            gtk_box_pack_start (GTK_BOX(existing_hbox), label, FALSE, TRUE, 0);
         else
         {
-            gtk_box_pack_start (GTK_BOX(hbox), image, FALSE, FALSE, 0);
-            gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, TRUE, 0);
+            gtk_box_pack_start (GTK_BOX(existing_hbox), image, FALSE, FALSE, 0);
+            gtk_box_pack_start (GTK_BOX(existing_hbox), label, FALSE, TRUE, 0);
         }
 
         PINFO("Path head: '%s', URI: '%s', Filename: '%s'", path_head, uri, display_uri);
 
-        gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(fc), hbox);
         gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_START);
 
         // Set the style context for this label so it can be easily manipulated with css
         gnc_widget_style_context_add_class (GTK_WIDGET(label), "gnc-class-highlight");
-        gtk_file_chooser_set_uri (GTK_FILE_CHOOSER(fc), use_uri);
-        gtk_widget_show_all (hbox);
+        gtk_file_chooser_set_uri (GTK_FILE_CHOOSER(fcb), use_uri);
+        gtk_widget_show_all (existing_hbox);
 
-        g_free (display_uri);
         g_free (uri_label);
         g_free (use_uri);
     }
+    g_free (display_uri);
 }
 
 gchar *
@@ -234,7 +230,7 @@ gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *ur
     GtkBuilder *builder;
     gboolean uri_is_file, have_uri = FALSE;
     GtkEntry *entry;
-    GtkFileChooserWidget *fc;
+    GtkFileChooserButton *fcb;
     GtkWidget *head_label;
     int result;
     gchar *ret_uri = NULL;
@@ -257,13 +253,12 @@ gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *ur
     head_label = GTK_WIDGET(gtk_builder_get_object (builder, "path_head_label"));
     ok_button = GTK_WIDGET(gtk_builder_get_object (builder, "ok_button"));
 
-    fc = GTK_FILE_CHOOSER_WIDGET(gtk_builder_get_object (builder, "file_chooser"));
-    g_object_set_data (G_OBJECT(fc), "okbut", ok_button);
-    g_signal_connect (G_OBJECT(fc), "selection-changed",
-                      G_CALLBACK(assoc_file_chooser_selection_changed_cb), ok_button);
+    fcb = GTK_FILE_CHOOSER_BUTTON(gtk_builder_get_object (builder, "file_chooser_button"));
+    g_object_set_data (G_OBJECT(fcb), "okbut", ok_button);
+    g_signal_connect (fcb, "file-set", G_CALLBACK(file_set_cb), ok_button);
 
     button_file = GTK_WIDGET(gtk_builder_get_object (builder, "file_assoc"));
-    g_signal_connect (button_file, "toggled", G_CALLBACK(uri_type_selected_cb), fc);
+    g_signal_connect (button_file, "toggled", G_CALLBACK(uri_type_selected_cb), fcb);
 
     gtk_widget_show_all (GTK_WIDGET(gtk_builder_get_object (builder, "file_hbox")));
 
@@ -302,7 +297,7 @@ gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *ur
         setup_location_dialog (builder, button_loc, uri);
 
     if (have_uri && uri_is_file) // file
-         setup_file_dialog (fc, path_head, uri, scheme);
+         setup_file_dialog (builder, fcb, path_head, uri, scheme);
 
     g_free (scheme);
     g_object_unref (G_OBJECT(builder));
@@ -321,7 +316,7 @@ gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *ur
         }
         else // file
         {
-            gchar *dialog_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER(fc));
+            gchar *dialog_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER(fcb));
 
             PINFO("Dialog File URI: '%s', Path head: '%s'", dialog_uri, path_head);
 
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index 7bb747fd7..0f0e5e4e0 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -4,8 +4,7 @@
   <requires lib="gtk+" version="3.10"/>
   <object class="GtkDialog" id="association_dialog">
     <property name="can_focus">False</property>
-    <property name="default_width">900</property>
-    <property name="default_height">500</property>
+    <property name="default_width">600</property>
     <property name="type_hint">dialog</property>
     <child>
       <placeholder/>
@@ -139,10 +138,9 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkFileChooserWidget" id="file_chooser">
+                  <object class="GtkFileChooserButton" id="file_chooser_button">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="local_only">False</property>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -151,13 +149,15 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkLabel" id="existing_label">
+                  <object class="GtkBox" id="existing_hbox">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="halign">start</property>
+                    <child>
+                      <placeholder/>
+                    </child>
                   </object>
                   <packing>
-                    <property name="expand">False</property>
+                    <property name="expand">True</property>
                     <property name="fill">True</property>
                     <property name="position">2</property>
                   </packing>

commit 8723184f4df3271645c8ef84a372a99e03f7fd61
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:04:11 2020 +0100

    Change gnc_launch_assoc function
    
    Change gnc_launch_assoc function to use uri functions from
    dialog-assoc-utils.c/h

diff --git a/gnucash/gnome-utils/gnc-gnome-utils.c b/gnucash/gnome-utils/gnc-gnome-utils.c
index f92c90c79..035d74018 100644
--- a/gnucash/gnome-utils/gnc-gnome-utils.c
+++ b/gnucash/gnome-utils/gnc-gnome-utils.c
@@ -40,6 +40,7 @@
 #include "gnc-splash.h"
 #include "gnc-window.h"
 #include "gnc-icons.h"
+#include "dialog-assoc-utils.h"
 #include "dialog-options.h"
 #include "dialog-commodity.h"
 #include "dialog-totd.h"
@@ -451,7 +452,7 @@ gnc_launch_assoc (GtkWindow *parent, const char *uri)
         return;
     }
 
-    gnc_error_dialog(parent, "%s", message);
+    gnc_error_dialog (parent, "%s", message);
 
     [pool release];
     return;
@@ -466,11 +467,10 @@ gnc_launch_assoc (GtkWindow *parent, const char *uri)
      * file URI so we have to do it. */
     if (gnc_uri_is_file_uri (uri))
     {
-        gchar *uri_ue = g_uri_unescape_string (uri, NULL);
-        filename = gnc_uri_get_path (uri_ue);
-        filename = g_strdelimit (filename, "/", '\\'); // needed for unc paths
+        gchar *uri_scheme = gnc_uri_get_scheme (uri);
+        filename = gnc_assoc_get_unescape_uri (NULL, uri, uri_scheme);
         winuri = (wchar_t *)g_utf8_to_utf16(filename, -1, NULL, NULL, NULL);
-        g_free (uri_ue);
+        g_free (uri_scheme);
     }
     else
         winuri = (wchar_t *)g_utf8_to_utf16(uri, -1, NULL, NULL, NULL);
@@ -484,7 +484,7 @@ gnc_launch_assoc (GtkWindow *parent, const char *uri)
         {
             const gchar *message =
             _("GnuCash could not find the associated file.");
-            gnc_error_dialog(parent, "%s:\n%s", message, filename);
+            gnc_error_dialog (parent, "%s:\n%s", message, filename);
         }
         g_free (wincmd);
         g_free (winuri);
@@ -509,28 +509,26 @@ gnc_launch_assoc (GtkWindow *parent, const char *uri)
     if (success)
         return;
 
-    g_assert(error != NULL);
+    g_assert (error != NULL);
     {
         gchar *error_uri = NULL;
         const gchar *message =
-            _("GnuCash could not open the associated URI:");
+            _("GnuCash could not open the associated file:");
 
         if (gnc_uri_is_file_uri (uri))
         {
-            gchar *uri_ue = g_uri_unescape_string (uri, NULL);
-            gchar *filename = gnc_uri_get_path (uri_ue);
-            error_uri = g_strdup (filename);
-            g_free (uri_ue);
-            g_free (filename);
+            gchar *uri_scheme = gnc_uri_get_scheme (uri);
+            error_uri = gnc_assoc_get_unescape_uri (NULL, uri, uri_scheme);
+            g_free (uri_scheme);
         }
         else
             error_uri = g_strdup (uri);
 
-        gnc_error_dialog(parent, "%s\n%s", message, error_uri);
+        gnc_error_dialog (parent, "%s\n%s", message, error_uri);
         g_free (error_uri);
     }
     PERR ("%s", error->message);
-    g_error_free(error);
+    g_error_free (error);
 }
 
 #endif

commit e81e95ee59e1766e60dabc96c8b89b3bb3da277f
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 16:03:05 2020 +0100

    Change dialog-assoc functions to use ones from dialog-assoc-utils

diff --git a/gnucash/gnome-utils/dialog-assoc-utils.c b/gnucash/gnome-utils/dialog-assoc-utils.c
index 30e1b3847..5818554f1 100644
--- a/gnucash/gnome-utils/dialog-assoc-utils.c
+++ b/gnucash/gnome-utils/dialog-assoc-utils.c
@@ -97,7 +97,7 @@ gnc_assoc_get_unescape_uri (const gchar *path_head, const gchar *uri, gchar *uri
     return display_str;
 }
 
-static gchar *
+gchar *
 gnc_assoc_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
 {
     gchar *use_str = NULL;
@@ -118,7 +118,7 @@ gnc_assoc_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_sche
     return use_str;
 }
 
-static gchar*
+gchar *
 gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro)
 {
     const gchar *uri = xaccTransGetAssociation (trans); // get the existing uri
@@ -196,7 +196,7 @@ gnc_assoc_get_path_head (void)
     return assoc_get_path_head_and_set (&path_head_set);
 }
 
-static void
+void
 gnc_assoc_set_path_head_label (GtkWidget *path_head_label, const gchar *incoming_path_head, const gchar *prefix)
 {
     gboolean path_head_set = FALSE;
diff --git a/gnucash/gnome-utils/dialog-assoc-utils.h b/gnucash/gnome-utils/dialog-assoc-utils.h
index 6d181b64f..da6f0c4f5 100644
--- a/gnucash/gnome-utils/dialog-assoc-utils.h
+++ b/gnucash/gnome-utils/dialog-assoc-utils.h
@@ -36,6 +36,43 @@
  */
 gchar * gnc_assoc_get_path_head (void);
 
+/** Sets the label text for displaying the path head in a dialog.
+ *
+ *  @param path_head_label The GtkLabel Widget
+ *  @param incoming_path_head The starting common path head
+ *  @param prefix A text string to place infront of the path head text
+ */
+void gnc_assoc_set_path_head_label (GtkWidget *path_head_label,
+                                    const gchar *incoming_path_head,
+                                    const gchar *prefix);
+
+/** Return a uri that can be used for opening it.
+ *
+ *  The function allocates memory for the uri. The calling function should
+ *  free this memory with g_free when uri is no longer needed.
+ *
+ *  If the uri scheme is 'file' or NULL, an absolute path is created and returned
+ *  otherwise the uri is returned.
+ *
+ *  @param path_head The starting common path head
+ *  @param uri The association
+ *  @param uri_scheme
+ *
+ *  @return The uri used for opening the association.
+ */
+gchar * gnc_assoc_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme);
+
+/** Corrects an earlier relative file association forrmat.
+ *
+ *  Prior to version 3.5, relative paths were stored starting as 'file:'
+ *  or 'file:/' depending on OS. This function changes them so that
+ *  relative paths are stored without a leading "/" and in native form.
+ *
+ *  @param trans The Transaction holding the association
+ *  @param book_ro TRUE if the book is read only
+ */
+gchar * gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro);
+
 /** Return an unescaped uri for display use.
  *
  *  The function allocates memory for the uri. The calling function should
diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index 484136106..c01f8603a 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -26,6 +26,7 @@
 #include <glib/gi18n.h>
 
 #include "dialog-assoc.h"
+#include "dialog-assoc-utils.h"
 
 #include "dialog-utils.h"
 #include "gnc-component-manager.h"
@@ -76,168 +77,14 @@ static QofLogModule log_module = GNC_MOD_GUI;
 
 /***********************************************************************/
 
-static gchar *
-convert_uri_to_abs_path (const gchar *path_head, const gchar *uri, gchar *uri_scheme, gboolean return_uri)
-{
-    gchar *ret_value = NULL;
-
-    if (!uri_scheme) // relative path
-    {
-        gchar *path = gnc_uri_get_path (path_head);
-        gchar *file_path = gnc_file_path_absolute (path, uri);
-
-        if (return_uri)
-            ret_value = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, file_path);
-        else
-            ret_value = g_strdup (file_path);
-
-        g_free (path);
-        g_free (file_path);
-    }
-
-    if (g_strcmp0 (uri_scheme, "file") == 0) // absolute path
-    {
-        if (return_uri)
-            ret_value = g_strdup (uri);
-        else
-            ret_value = gnc_uri_get_path (uri);
-    }
-    return ret_value;
-}
-
-static gchar *
-assoc_get_unescape_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
-{
-    gchar *display_str = NULL;
-
-    if (uri && *uri)
-    {
-        // if scheme is null or 'file' we should get a file path
-        gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, FALSE);
-
-        if (file_path)
-            display_str = g_uri_unescape_string (file_path, NULL);
-        else
-            display_str = g_uri_unescape_string (uri, NULL);
-
-        g_free (file_path);
-
-#ifdef G_OS_WIN32 // make path look like a traditional windows path
-        display_str = g_strdelimit (display_str, "/", '\\');
-#endif
-    }
-    DEBUG("Return display string is '%s'", display_str);
-    return display_str;
-}
-
-static gchar *
-assoc_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
-{
-    gchar *use_str = NULL;
-
-    if (uri && *uri)
-    {
-        // if scheme is null or 'file' we should get a file path
-        gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, TRUE);
-
-        if (file_path)
-            use_str = g_strdup (file_path);
-        else
-            use_str = g_strdup (uri);
-
-        g_free (file_path);
-    }
-    DEBUG("Return use string is '%s'", use_str);
-    return use_str;
-}
-
-static gchar *
-assoc_get_path_head_and_set (gboolean *path_head_set)
-{
-    gchar *ret_path = NULL;
-    gchar *path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL, "assoc-head");
-    *path_head_set = FALSE;
-
-    if (path_head && *path_head) // not default entry
-    {
-        *path_head_set = TRUE;
-        ret_path = g_strdup (path_head);
-    }
-    else
-    {
-        const gchar *doc = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
-
-        if (doc)
-            ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, doc);
-        else
-            ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, gnc_userdata_dir ());
-    }
-    // make sure there is a trailing '/'
-    if (!g_str_has_suffix (ret_path, "/"))
-    {
-        gchar *folder_with_slash = g_strconcat (ret_path, "/", NULL);
-        g_free (ret_path);
-        ret_path = g_strdup (folder_with_slash);
-        g_free (folder_with_slash);
-
-        if (*path_head_set) // prior to 3.5, assoc-head could be with or without a trailing '/'
-        {
-            if (!gnc_prefs_set_string (GNC_PREFS_GROUP_GENERAL, "assoc-head", ret_path))
-                PINFO ("Failed to save preference at %s, %s with %s",
-                       GNC_PREFS_GROUP_GENERAL, "assoc-head", ret_path);
-        }
-    }
-    g_free (path_head);
-    return ret_path;
-}
-
-static gchar *
-assoc_get_path_head (void)
-{
-    gboolean path_head_set = FALSE;
-
-    return assoc_get_path_head_and_set (&path_head_set);
-}
-
-static void
-assoc_set_path_head_label (GtkWidget *path_head_label)
-{
-    gboolean path_head_set = FALSE;
-    gchar *path_head = assoc_get_path_head_and_set (&path_head_set);
-    gchar *scheme = gnc_uri_get_scheme (path_head);
-    gchar *path_head_str = assoc_get_unescape_uri (NULL, path_head, scheme);
-    gchar *path_head_text;
-
-    if (path_head_set)
-    {
-        // test for current folder being present
-        if (g_file_test (path_head_str, G_FILE_TEST_IS_DIR))
-            path_head_text = g_strdup_printf ("%s '%s'", _("Path head for files is,"), path_head_str);
-        else
-            path_head_text = g_strdup_printf ("%s '%s'", _("Path head does not exist,"), path_head_str);
-    }
-    else
-        path_head_text = g_strdup_printf (_("Path head not set, using '%s' for relative paths"), path_head_str);
-
-    gtk_label_set_text (GTK_LABEL(path_head_label), path_head_text);
-
-    // Set the style context for this label so it can be easily manipulated with css
-    gnc_widget_style_context_add_class (GTK_WIDGET(path_head_label), "gnc-class-highlight");
-
-    g_free (scheme);
-    g_free (path_head_str);
-    g_free (path_head_text);
-    g_free (path_head);
-}
-
 void
 gnc_assoc_open_uri (GtkWindow *parent, const gchar *uri)
 {
     if (uri && *uri)
     {
         gchar     *scheme = gnc_uri_get_scheme (uri);
-        gchar  *path_head = assoc_get_path_head ();
-        gchar    *run_uri = assoc_get_use_uri (path_head, uri, scheme);
+        gchar  *path_head = gnc_assoc_get_path_head ();
+        gchar    *run_uri = gnc_assoc_get_use_uri (path_head, uri, scheme);
         gchar *run_scheme = gnc_uri_get_scheme (run_uri);
 
         PINFO("Open uri scheme is '%s', uri is '%s'", run_scheme, run_uri);
@@ -344,13 +191,13 @@ setup_location_dialog (GtkBuilder *builder, GtkWidget *button_loc, const gchar *
 static void
 setup_file_dialog (GtkFileChooserWidget *fc, const gchar *path_head, const gchar *uri, gchar *scheme)
 {
-    gchar *display_uri = assoc_get_unescape_uri (path_head, uri, scheme);
+    gchar *display_uri = gnc_assoc_get_unescape_uri (path_head, uri, scheme);
 
     if (display_uri)
     {
         GtkWidget *label, *hbox;
         GtkWidget *image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_SMALL_TOOLBAR);
-        gchar     *use_uri = assoc_get_use_uri (path_head, uri, scheme);
+        gchar     *use_uri = gnc_assoc_get_use_uri (path_head, uri, scheme);
         gchar     *uri_label = g_strdup_printf ("%s '%s'", _("Existing Association is"), display_uri);
 
         hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
@@ -391,7 +238,7 @@ gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *ur
     GtkWidget *head_label;
     int result;
     gchar *ret_uri = NULL;
-    gchar *path_head = assoc_get_path_head ();
+    gchar *path_head = gnc_assoc_get_path_head ();
     gchar *scheme = NULL;
 
     /* Create the dialog box */
@@ -431,7 +278,7 @@ gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *ur
     g_signal_connect (button_loc, "toggled", G_CALLBACK(uri_type_selected_cb), entry);
 
     // display path head text and test if present
-    assoc_set_path_head_label (head_label);
+    gnc_assoc_set_path_head_label (head_label, NULL, NULL);
 
     // Check for uri is empty or NULL
     if (uri && *uri)
@@ -565,7 +412,7 @@ assoc_dialog_update (AssocDialog *assoc_dialog)
 
         if (!scheme || gnc_uri_is_file_scheme (scheme))
         {
-            gchar *filename = assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
+            gchar *filename = gnc_assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
 
             if (g_file_test (filename, G_FILE_TEST_EXISTS))
                 gtk_list_store_set (GTK_LIST_STORE(model), &iter, AVAILABLE, _("File Found"), -1);
@@ -609,7 +456,7 @@ update_model_with_changes (AssocDialog *assoc_dialog, GtkTreeIter *iter, const g
     if (!scheme) // path is relative
         rel = TRUE;
 
-    display_uri = assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
+    display_uri = gnc_assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
     gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), iter,
                         DISPLAY_URI, display_uri, AVAILABLE, _("File Found"),
                         URI, uri,
@@ -693,34 +540,6 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
     g_free (uri);
 }
 
-gchar*
-gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro)
-{
-    const gchar *uri = xaccTransGetAssociation (trans); // get the existing uri
-    const gchar *part = NULL;
-
-    if (!uri)
-        return NULL;
-
-    if (g_str_has_prefix (uri, "file:") && !g_str_has_prefix (uri,"file://"))
-    {
-        /* fix an earlier error when storing relative paths before version 3.5
-         * they were stored starting as 'file:' or 'file:/' depending on OS
-         * relative paths are stored without a leading "/" and in native form
-         */
-        if (g_str_has_prefix (uri,"file:/"))
-            part = uri + strlen ("file:/");
-        else if (g_str_has_prefix (uri,"file:"))
-            part = uri + strlen ("file:");
-
-        if (!xaccTransGetReadOnly (trans) && !book_ro)
-            xaccTransSetAssociation (trans, part);
-
-        return g_strdup (part);
-    }
-    return g_strdup (uri);
-}
-
 static void
 add_trans_info_to_model (QofInstance* data, gpointer user_data)
 {
@@ -749,7 +568,7 @@ add_trans_info_to_model (QofInstance* data, gpointer user_data)
         if (!scheme) // path is relative
             rel = TRUE;
 
-        display_uri = assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
+        display_uri = gnc_assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
 
         gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), &iter,
                             DATE_TRANS, datebuff,
@@ -793,7 +612,7 @@ static void
 gnc_assoc_dialog_reload_button_cb (GtkWidget *widget, gpointer user_data)
 {
     AssocDialog *assoc_dialog = user_data;
-    gchar          *path_head = assoc_get_path_head ();
+    gchar          *path_head = gnc_assoc_get_path_head ();
 
     if (g_strcmp0 (path_head, assoc_dialog->path_head) != 0)
     {
@@ -801,7 +620,7 @@ gnc_assoc_dialog_reload_button_cb (GtkWidget *widget, gpointer user_data)
         assoc_dialog->path_head = g_strdup (path_head);
 
         // display path head text and test if present
-        assoc_set_path_head_label (assoc_dialog->path_head_label);
+        gnc_assoc_set_path_head_label (assoc_dialog->path_head_label, NULL, NULL);
     }
     g_free (path_head);
     get_trans_info (assoc_dialog);
@@ -867,10 +686,10 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     assoc_dialog->view = GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));
     assoc_dialog->path_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "path-head"));
 
-    assoc_dialog->path_head = assoc_get_path_head ();
+    assoc_dialog->path_head = gnc_assoc_get_path_head ();
 
     // display path head text and test if present
-    assoc_set_path_head_label (assoc_dialog->path_head_label);
+    gnc_assoc_set_path_head_label (assoc_dialog->path_head_label, NULL, NULL);
 
     // set the Associate column to be the one that expands
     tree_column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object (builder, "assoc"));
diff --git a/gnucash/gnome/dialog-assoc.h b/gnucash/gnome/dialog-assoc.h
index d64f1fdb2..ff5ef6bfc 100644
--- a/gnucash/gnome/dialog-assoc.h
+++ b/gnucash/gnome/dialog-assoc.h
@@ -45,17 +45,6 @@ gchar * gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const g
  */
 void gnc_assoc_open_uri (GtkWindow *parent, const gchar *uri);
 
-/** Corrects an earlier relative file association forrmat.
- *
- *  Prior to version 3.5, relative paths were stored starting as 'file:'
- *  or 'file:/' depending on OS. This function changes them so that
- *  relative paths are stored without a leading "/" and in native form.
- *
- *  @param trans The Transaction holding the association
- *  @param book_ro TRUE if the book is read only
- */
-gchar * gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro);
-
 /** Preset a dialog to list all the Transaction associations.
  *
  *  A query is run to return all the transaction associations which
diff --git a/gnucash/gnome/gnc-split-reg.c b/gnucash/gnome/gnc-split-reg.c
index c4ed4842b..eae824c27 100644
--- a/gnucash/gnome/gnc-split-reg.c
+++ b/gnucash/gnome/gnc-split-reg.c
@@ -38,6 +38,7 @@
 #include "SX-book.h"
 #include "dialog-account.h"
 #include "dialog-assoc.h"
+#include "dialog-assoc-utils.h"
 #include "dialog-sx-editor.h"
 #include "dialog-sx-from-trans.h"
 #include "gnc-component-manager.h"

commit 07d46d5d529ad5b1102e23356aa7aa75ec548f9c
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 15:50:26 2020 +0100

    Add option to change associations when path head changes
    
    When the path head is changed the associations are not updated so this
    change adds a dialog to ask if you want the old relative paths updated
    to absolute ones and whether to update existing absolute paths to new
    relative ones.

diff --git a/gnucash/gnome-utils/CMakeLists.txt b/gnucash/gnome-utils/CMakeLists.txt
index 6748b002e..098c88d17 100644
--- a/gnucash/gnome-utils/CMakeLists.txt
+++ b/gnucash/gnome-utils/CMakeLists.txt
@@ -30,6 +30,7 @@ set (gnome_utils_SOURCES
   assistant-xml-encoding.c
   cursors.c
   dialog-account.c
+  dialog-assoc-utils.c
   dialog-book-close.c
   dialog-commodity.c
   dialog-dup-trans.c
@@ -123,6 +124,7 @@ set (gnome_utils_HEADERS
   account-quickfill.h
   assistant-xml-encoding.h
   dialog-account.h
+  dialog-assoc-utils.h
   dialog-book-close.h
   dialog-commodity.h
   dialog-dup-trans.h
diff --git a/gnucash/gnome-utils/dialog-assoc-utils.c b/gnucash/gnome-utils/dialog-assoc-utils.c
new file mode 100644
index 000000000..30e1b3847
--- /dev/null
+++ b/gnucash/gnome-utils/dialog-assoc-utils.c
@@ -0,0 +1,390 @@
+/********************************************************************\
+ * dialog-assoc-utils.c -- Associations dialog Utils                *
+ * Copyright (C) 2020 Robert Fewell                                 *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include "dialog-assoc-utils.h"
+
+#include "dialog-utils.h"
+#include "Transaction.h"
+
+#include "gnc-prefs.h"
+#include "gnc-ui.h"
+#include "gnc-ui-util.h"
+#include "gnc-gnome-utils.h"
+#include "gnc-uri-utils.h"
+#include "gnc-filepath-utils.h"
+#include "Account.h"
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = GNC_MOD_GUI;
+
+/***********************************************************************/
+
+static gchar *
+convert_uri_to_abs_path (const gchar *path_head, const gchar *uri, gchar *uri_scheme, gboolean return_uri)
+{
+    gchar *ret_value = NULL;
+
+    if (!uri_scheme) // relative path
+    {
+        gchar *path = gnc_uri_get_path (path_head);
+        gchar *file_path = gnc_file_path_absolute (path, uri);
+
+        if (return_uri)
+            ret_value = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, file_path);
+        else
+            ret_value = g_strdup (file_path);
+
+        g_free (path);
+        g_free (file_path);
+    }
+
+    if (g_strcmp0 (uri_scheme, "file") == 0) // absolute path
+    {
+        if (return_uri)
+            ret_value = g_strdup (uri);
+        else
+            ret_value = gnc_uri_get_path (uri);
+    }
+    return ret_value;
+}
+
+gchar *
+gnc_assoc_get_unescape_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
+{
+    gchar *display_str = NULL;
+
+    if (uri && *uri)
+    {
+        // if scheme is null or 'file' we should get a file path
+        gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, FALSE);
+
+        if (file_path)
+            display_str = g_uri_unescape_string (file_path, NULL);
+        else
+            display_str = g_uri_unescape_string (uri, NULL);
+
+        g_free (file_path);
+
+#ifdef G_OS_WIN32 // make path look like a traditional windows path
+        display_str = g_strdelimit (display_str, "/", '\\');
+#endif
+    }
+    DEBUG("Return display string is '%s'", display_str);
+    return display_str;
+}
+
+static gchar *
+gnc_assoc_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
+{
+    gchar *use_str = NULL;
+
+    if (uri && *uri)
+    {
+        // if scheme is null or 'file' we should get a file path
+        gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, TRUE);
+
+        if (file_path)
+            use_str = g_strdup (file_path);
+        else
+            use_str = g_strdup (uri);
+
+        g_free (file_path);
+    }
+    DEBUG("Return use string is '%s'", use_str);
+    return use_str;
+}
+
+static gchar*
+gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro)
+{
+    const gchar *uri = xaccTransGetAssociation (trans); // get the existing uri
+    const gchar *part = NULL;
+
+    if (!uri)
+        return NULL;
+
+    if (g_str_has_prefix (uri, "file:") && !g_str_has_prefix (uri,"file://"))
+    {
+        /* fix an earlier error when storing relative paths before version 3.5
+         * they were stored starting as 'file:' or 'file:/' depending on OS
+         * relative paths are stored without a leading "/" and in native form
+         */
+        if (g_str_has_prefix (uri,"file:/"))
+            part = uri + strlen ("file:/");
+        else if (g_str_has_prefix (uri,"file:"))
+            part = uri + strlen ("file:");
+
+        if (!xaccTransGetReadOnly (trans) && !book_ro)
+            xaccTransSetAssociation (trans, part);
+
+        return g_strdup (part);
+    }
+    return g_strdup (uri);
+}
+
+/***********************************************************************/
+
+static gchar *
+assoc_get_path_head_and_set (gboolean *path_head_set)
+{
+    gchar *ret_path = NULL;
+    gchar *path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL, "assoc-head");
+    *path_head_set = FALSE;
+
+    if (path_head && *path_head) // not default entry
+    {
+        *path_head_set = TRUE;
+        ret_path = g_strdup (path_head);
+    }
+    else
+    {
+        const gchar *doc = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
+
+        if (doc)
+            ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, doc);
+        else
+            ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, gnc_userdata_dir ());
+    }
+    // make sure there is a trailing '/'
+    if (!g_str_has_suffix (ret_path, "/"))
+    {
+        gchar *folder_with_slash = g_strconcat (ret_path, "/", NULL);
+        g_free (ret_path);
+        ret_path = g_strdup (folder_with_slash);
+        g_free (folder_with_slash);
+
+        if (*path_head_set) // prior to 3.5, assoc-head could be with or without a trailing '/'
+        {
+            if (!gnc_prefs_set_string (GNC_PREFS_GROUP_GENERAL, "assoc-head", ret_path))
+                PINFO ("Failed to save preference at %s, %s with %s",
+                       GNC_PREFS_GROUP_GENERAL, "assoc-head", ret_path);
+        }
+    }
+    g_free (path_head);
+    return ret_path;
+}
+
+gchar *
+gnc_assoc_get_path_head (void)
+{
+    gboolean path_head_set = FALSE;
+
+    return assoc_get_path_head_and_set (&path_head_set);
+}
+
+static void
+gnc_assoc_set_path_head_label (GtkWidget *path_head_label, const gchar *incoming_path_head, const gchar *prefix)
+{
+    gboolean path_head_set = FALSE;
+    gchar *path_head = NULL;
+    gchar *scheme;
+    gchar *path_head_str;
+    gchar *path_head_text;
+
+    if (incoming_path_head)
+    {
+         path_head = g_strdup (incoming_path_head);
+         path_head_set = TRUE;
+    }
+    else
+        path_head = assoc_get_path_head_and_set (&path_head_set);
+
+    scheme = gnc_uri_get_scheme (path_head);
+    path_head_str = gnc_assoc_get_unescape_uri (NULL, path_head, scheme);
+
+    if (path_head_set)
+    {
+        // test for current folder being present
+        if (g_file_test (path_head_str, G_FILE_TEST_IS_DIR))
+            path_head_text = g_strdup_printf ("%s '%s'", _("Path head for files is,"), path_head_str);
+        else
+            path_head_text = g_strdup_printf ("%s '%s'", _("Path head does not exist,"), path_head_str);
+    }
+    else
+        path_head_text = g_strdup_printf (_("Path head not set, using '%s' for relative paths"), path_head_str);
+
+    if (prefix)
+    {
+        gchar *tmp = g_strdup (path_head_text);
+        g_free (path_head_text);
+
+        path_head_text = g_strdup_printf ("%s %s", prefix, tmp);
+
+        g_free (tmp);
+    }
+
+    gtk_label_set_text (GTK_LABEL(path_head_label), path_head_text);
+
+    // Set the style context for this label so it can be easily manipulated with css
+    gnc_widget_style_context_add_class (GTK_WIDGET(path_head_label), "gnc-class-highlight");
+
+    g_free (scheme);
+    g_free (path_head_str);
+    g_free (path_head_text);
+    g_free (path_head);
+}
+
+/***********************************************************************/
+
+typedef struct
+{
+    const gchar *old_path_head_uri;
+    gboolean     change_old;
+    const gchar *new_path_head_uri;
+    gboolean     change_new;
+    gboolean     book_ro;
+}AssocUpdate;
+
+static void
+update_trans_uri (QofInstance* data, gpointer user_data)
+{
+    AssocUpdate *assoc_update = user_data;
+    Transaction *trans = GNC_TRANSACTION(data);
+    gchar *uri;
+
+    // fix an earlier error when storing relative paths before version 3.5
+    uri = gnc_assoc_convert_trans_associate_uri (trans, assoc_update->book_ro);
+
+    if (uri && *uri)
+    {
+        gboolean rel = FALSE;
+        gchar *scheme = gnc_uri_get_scheme (uri);
+
+        if (!scheme) // path is relative
+            rel = TRUE;
+
+        // check for relative and we want to change them
+        if (rel && assoc_update->change_old)
+        {
+            gchar *new_uri = gnc_assoc_get_use_uri (assoc_update->old_path_head_uri, uri, scheme);
+
+            if (!xaccTransGetReadOnly (trans))
+                xaccTransSetAssociation (trans, new_uri);
+
+            g_free (new_uri);
+        }
+        g_free (scheme);
+
+        // check for not relative and we want to change them
+        if (!rel && assoc_update->change_new && g_str_has_prefix (uri, assoc_update->new_path_head_uri))
+        {
+            // relative paths do not start with a '/'
+            const gchar *part = uri + strlen (assoc_update->new_path_head_uri);
+            gchar *new_uri = g_strdup (part);
+
+            if (!xaccTransGetReadOnly (trans))
+                xaccTransSetAssociation (trans, new_uri);
+
+            g_free (new_uri);
+        }
+    }
+    g_free (uri);
+}
+
+static void
+change_relative_and_absolute_uri_paths (const gchar *old_path_head_uri, gboolean change_old,
+                                        const gchar *new_path_head_uri, gboolean change_new)
+{
+    QofBook      *book = gnc_get_current_book();
+    gboolean      book_ro = qof_book_is_readonly (book);
+    AssocUpdate  *assoc_update;
+
+    /* if book is read only, nothing to do */
+    if (book_ro)
+        return;
+
+    assoc_update = g_new0 (AssocUpdate, 1);
+
+    assoc_update->old_path_head_uri = old_path_head_uri;
+    assoc_update->new_path_head_uri = new_path_head_uri;
+    assoc_update->change_old = change_old;
+    assoc_update->change_new = change_new;
+    assoc_update->book_ro = book_ro;
+
+    /* Loop through the transactions */
+    qof_collection_foreach (qof_book_get_collection (book, GNC_ID_TRANS),
+                            update_trans_uri, assoc_update);
+
+    g_free (assoc_update);
+}
+
+void
+gnc_assoc_pref_path_head_changed (GtkWindow *parent, const gchar *old_path_head_uri)
+{
+    GtkWidget  *dialog;
+    GtkBuilder *builder;
+    GtkWidget  *use_old_path_head, *use_new_path_head;
+    GtkWidget  *old_head_label, *new_head_label;
+    GtkWidget  *old_hbox, *new_hbox;
+    gint        result;
+    gchar      *new_path_head_uri = gnc_assoc_get_path_head ();
+
+    if (g_strcmp0 (old_path_head_uri, new_path_head_uri) == 0)
+    {
+        g_free (new_path_head_uri);
+        return;
+    }
+
+    /* Create the dialog box */
+    builder = gtk_builder_new();
+    gnc_builder_add_from_file (builder, "dialog-assoc.glade", "association_path_head_changed_dialog");
+    dialog = GTK_WIDGET(gtk_builder_get_object (builder, "association_path_head_changed_dialog"));
+
+    if (parent != NULL)
+        gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(parent));
+
+    // Set the name and style context for this widget so it can be easily manipulated with css
+    gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-association-change");
+    gnc_widget_style_context_add_class (GTK_WIDGET(dialog), "gnc-class-association");
+
+    old_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "existing_path_head"));
+    new_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "new_path_head"));
+
+    use_old_path_head = GTK_WIDGET(gtk_builder_get_object (builder, "use_old_path_head"));
+    use_new_path_head = GTK_WIDGET(gtk_builder_get_object (builder, "use_new_path_head"));
+
+    // display path head text and test if present
+    gnc_assoc_set_path_head_label (old_head_label, old_path_head_uri, _("Exsiting"));
+    gnc_assoc_set_path_head_label (new_head_label, new_path_head_uri, _("New"));
+
+    gtk_widget_show (dialog);
+    g_object_unref (G_OBJECT(builder));
+
+    // run the dialog
+    result = gtk_dialog_run (GTK_DIALOG(dialog));
+    if (result == GTK_RESPONSE_OK)
+    {
+        gboolean use_old = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(use_old_path_head));
+        gboolean use_new = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(use_new_path_head));
+
+        if (use_old || use_new)
+            change_relative_and_absolute_uri_paths (old_path_head_uri, use_old,
+                                                    new_path_head_uri, use_new);
+    }
+    g_free (new_path_head_uri);
+    gtk_widget_destroy (dialog);
+}
diff --git a/gnucash/gnome-utils/dialog-assoc-utils.h b/gnucash/gnome-utils/dialog-assoc-utils.h
new file mode 100644
index 000000000..6d181b64f
--- /dev/null
+++ b/gnucash/gnome-utils/dialog-assoc-utils.h
@@ -0,0 +1,67 @@
+/********************************************************************\
+ * dialog-assoc-utils.h -- Associations dialog Utils                *
+ * Copyright (C) 2020 Robert Fewell                                 *
+ *                                                                  *
+ * This program is free software; you can redistribute it and/or    *
+ * modify it under the terms of the GNU General Public License as   *
+ * published by the Free Software Foundation; either version 2 of   *
+ * the License, or (at your option) any later version.              *
+ *                                                                  *
+ * This program is distributed in the hope that it will be useful,  *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
+ * GNU General Public License for more details.                     *
+ *                                                                  *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact:                        *
+ *                                                                  *
+ * Free Software Foundation           Voice:  +1-617-542-5942       *
+ * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
+ * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
+\********************************************************************/
+
+#ifndef DIALOG_ASSOC_UTILS_H
+#define DIALOG_ASSOC_UTILS_H
+
+/** Return the current associate path head uri.
+ *
+ *  This function will get the current associate path head from pref's.
+ *  If it is not set then a default path head is set based on either
+ *  the home directory or the user data directory.
+ *
+ *  The calling function should free the returned value with g_free when
+ *  the it is no longer needed.
+ *
+ *  @return The current associate path head.
+ */
+gchar * gnc_assoc_get_path_head (void);
+
+/** Return an unescaped uri for display use.
+ *
+ *  The function allocates memory for the uri. The calling function should
+ *  free this memory with g_free when the unescaped uri is no longer needed.
+
+ *  Return an unesacped uri for displaying and if OS is windows change the
+ *  '/' to '\' to look like a traditional windows path
+ *
+ *  @param path_head The starting common path head
+ *  @param uri The association
+ *  @param uri_scheme
+ *
+ *  @return The unescaped uri used for display purposes.
+ */
+gchar * gnc_assoc_get_unescape_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme);
+
+/** Presents a dialog when the path head is changed.
+ *
+ *  When the path head is changed a dialog is raised that allows for
+ *  existing relative file associations to be made absolute based on the
+ *  old_path_head_uri and existing absolute file associations to be made
+ *  relative based on the new_path_head_uri.
+ *
+ *  @param parent The GtkWindow for the parent widget
+ *  @param old_path_head_uri The old path head uri
+ */
+void gnc_assoc_pref_path_head_changed (GtkWindow *parent, const gchar *old_path_head_uri);
+
+#endif
diff --git a/gnucash/gnome-utils/dialog-preferences.c b/gnucash/gnome-utils/dialog-preferences.c
index 44e2015a2..137bf0bfe 100644
--- a/gnucash/gnome-utils/dialog-preferences.c
+++ b/gnucash/gnome-utils/dialog-preferences.c
@@ -77,6 +77,7 @@
 #include "gnc-ui-util.h"
 #include "gnc-component-manager.h"
 #include "dialog-preferences.h"
+#include "dialog-assoc-utils.h"
 
 #define DIALOG_PREFERENCES_CM_CLASS "dialog-newpreferences"
 #define GNC_PREFS_GROUP             "dialogs.preferences"
@@ -749,24 +750,29 @@ file_chooser_selected_cb (GtkFileChooser *fc, gpointer user_data)
 {
     GtkImage    *image = g_object_get_data (G_OBJECT(fc), "path_head_error");
     const gchar *group = g_object_get_data (G_OBJECT(fc), "group");
-    const gchar *pref = g_object_get_data (G_OBJECT(fc), "pref");
-    gchar       *folder = gtk_file_chooser_get_uri (fc);
+    const gchar  *pref = g_object_get_data (G_OBJECT(fc), "pref");
+    gchar        *folder_uri = gtk_file_chooser_get_uri (fc);
+    gchar *old_path_head_uri = gnc_assoc_get_path_head ();
 
     // make sure path_head ends with a trailing '/', 3.5 onwards
-    if (!g_str_has_suffix (folder, "/"))
+    if (!g_str_has_suffix (folder_uri, "/"))
     {
-        gchar *folder_with_slash = g_strconcat (folder, "/", NULL);
-        g_free (folder);
-        folder = g_strdup (folder_with_slash);
+        gchar *folder_with_slash = g_strconcat (folder_uri, "/", NULL);
+        g_free (folder_uri);
+        folder_uri = g_strdup (folder_with_slash);
         g_free (folder_with_slash);
     }
 
     gtk_widget_hide (GTK_WIDGET(image));
 
-    if (!gnc_prefs_set_string (group, pref, folder))
-        PINFO("Failed to save preference at %s, %s with %s", group, pref, folder);
-
-    g_free (folder);
+    if (!gnc_prefs_set_string (group, pref, folder_uri))
+        PINFO("Failed to save preference at %s, %s with %s", group, pref, folder_uri);
+    else
+        gnc_assoc_pref_path_head_changed (GTK_WINDOW(gtk_widget_get_toplevel (
+                                          GTK_WIDGET(fc))), old_path_head_uri);
+    
+    g_free (old_path_head_uri);
+    g_free (folder_uri);
 }
 
 /** Connect a GtkFileChooserButton widget to its stored value in the preferences database.
@@ -785,10 +791,10 @@ gnc_prefs_connect_file_chooser_button (GtkFileChooserButton *fcb, const gchar *b
     gchar *uri;
     gboolean folder_set = TRUE;
 
-    g_return_if_fail(GTK_FILE_CHOOSER_BUTTON(fcb));
+    g_return_if_fail (GTK_FILE_CHOOSER_BUTTON(fcb));
 
     if (boxname == NULL)
-        gnc_prefs_split_widget_name (gtk_buildable_get_name(GTK_BUILDABLE(fcb)), &group, &pref);
+        gnc_prefs_split_widget_name (gtk_buildable_get_name (GTK_BUILDABLE(fcb)), &group, &pref);
     else
         gnc_prefs_split_widget_name (boxname, &group, &pref);
 
@@ -796,7 +802,7 @@ gnc_prefs_connect_file_chooser_button (GtkFileChooserButton *fcb, const gchar *b
 
     PINFO("Uri is %s", uri);
 
-    if (uri && *uri != '\0') // default entry
+    if (uri && *uri) // default entry
     {
         gchar *path_head = g_filename_from_uri (uri, NULL, NULL);
 
@@ -809,21 +815,19 @@ gnc_prefs_connect_file_chooser_button (GtkFileChooserButton *fcb, const gchar *b
         g_free (path_head);
     }
 
-    image = g_object_get_data(G_OBJECT(fcb), "path_head_error");
+    image = g_object_get_data (G_OBJECT(fcb), "path_head_error");
 
     if (folder_set) // If current folder missing, display error and tt message
         gtk_widget_hide (GTK_WIDGET(image));
     else
     {
-        gchar *uri_u = g_uri_unescape_string (uri, NULL);
-        gchar *path_head = g_filename_from_uri (uri_u, NULL, NULL);
+        gchar *path_head = gnc_assoc_get_unescape_uri (NULL, uri, "file");
         gchar *ttip = g_strconcat (_("Path does not exist, "), path_head, NULL);
 
-        gtk_widget_set_tooltip_text(GTK_WIDGET(image), ttip);
+        gtk_widget_set_tooltip_text (GTK_WIDGET(image), ttip);
         gtk_widget_show (GTK_WIDGET(image));
 
         g_free (ttip);
-        g_free (uri_u);
         g_free (path_head);
     }
 
@@ -837,7 +841,7 @@ gnc_prefs_connect_file_chooser_button (GtkFileChooserButton *fcb, const gchar *b
     g_free (pref);
     g_free (uri);
 
-    gtk_widget_show_all(GTK_WIDGET(fcb));
+    gtk_widget_show_all (GTK_WIDGET(fcb));
 }
 
 /** Callback for a 'Clear' button for GtkFileChooserButton widget.
@@ -858,28 +862,40 @@ file_chooser_clear_cb (GtkButton *button, gpointer user_data)
     GtkWidget            *box;
     GtkWidget            *fcb_new;
     gchar                *boxname;
+    gchar                *old_path_head_uri = gnc_assoc_get_path_head ();
 
     /* We need to destroy the GtkFileChooserButton and recreate as there
        does not seem to be away of resetting the folder path to NONE */
     box = gtk_widget_get_parent (GTK_WIDGET(fcb));
-    gtk_widget_destroy (GTK_WIDGET(fcb));
+    g_signal_handlers_disconnect_by_func (button, file_chooser_clear_cb, fcb);
 
     if (!gnc_prefs_set_string (group, pref, ""))
         PINFO("Failed to Clear preference at %s, %s", group, pref);
+    else
+        gnc_assoc_pref_path_head_changed (GTK_WINDOW(gtk_widget_get_toplevel (
+                                          GTK_WIDGET(fcb))), old_path_head_uri);
+
+    gtk_widget_destroy (GTK_WIDGET(fcb));
 
     fcb_new = gtk_file_chooser_button_new (_("Select a folder"),
                              GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
 
     g_object_set_data (G_OBJECT(fcb_new), "path_head_error", image);
+    g_object_set_data_full (G_OBJECT(fcb_new),"group", g_strdup (group), (GDestroyNotify) g_free);
+    g_object_set_data_full (G_OBJECT(fcb_new),"pref", g_strdup (pref), (GDestroyNotify) g_free);
 
-    gtk_box_pack_start (GTK_BOX (box), fcb_new, TRUE, TRUE, 0);
-    gtk_box_reorder_child (GTK_BOX (box),fcb_new, 0);
+    gtk_box_pack_start (GTK_BOX(box), fcb_new, TRUE, TRUE, 0);
+    gtk_box_reorder_child (GTK_BOX(box), fcb_new, 0);
     gtk_widget_show (fcb_new);
 
+    g_signal_connect (GTK_BUTTON(button), "clicked",
+                      G_CALLBACK(file_chooser_clear_cb), fcb_new);
+
     boxname = g_strconcat ("pref/", group, "/", pref, NULL);
 
     gnc_prefs_connect_file_chooser_button (GTK_FILE_CHOOSER_BUTTON(fcb_new), boxname);
     g_free (boxname);
+    g_free (old_path_head_uri);
 }
 
 /****************************************************************************/
@@ -1385,6 +1401,7 @@ gnc_preferences_dialog_create(GtkWindow *parent)
     if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
             gtk_list_store_set (store, &iter, 1, buf, -1);
     g_date_free(gdate);
+    gtk_tree_path_free (path);
 
     locale_currency = gnc_locale_default_currency ();
     currency_name = gnc_commodity_get_printname(locale_currency);
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index 98b33e0ac..7bb747fd7 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -265,6 +265,204 @@
       <action-widget response="-5">ok_button</action-widget>
     </action-widgets>
   </object>
+  <object class="GtkDialog" id="association_path_head_changed_dialog">
+    <property name="can_focus">False</property>
+    <property name="default_width">450</property>
+    <property name="type_hint">dialog</property>
+    <child>
+      <placeholder/>
+    </child>
+    <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="button4">
+                <property name="label" translatable="yes">_OK</property>
+                <property name="visible">True</property>
+                <property name="can_focus">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="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Change Association path head</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="existing_path_head">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_top">6</property>
+                <style>
+                  <class name="gnc-class-highlight"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="existing_path_hbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_bottom">12</property>
+                <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">Existing relative file path associations will be converted to absolute ones by combining them with the existing path head unless box unticked.</property>
+                    <property name="wrap">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="use_old_path_head">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="halign">start</property>
+                    <property name="valign">end</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="new_path_head">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <style>
+                  <class name="gnc-class-highlight"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="new_path_hbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <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">Existing absolute file path associations will be converted to relative ones by comparing them to the new path head unless box unticked.</property>
+                    <property name="wrap">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkCheckButton" id="use_new_path_head">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="halign">start</property>
+                    <property name="valign">end</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">4</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="margin_top">12</property>
+                <property name="label" translatable="yes">Note: Only Associations that are not read-only will be changed.</property>
+                <property name="wrap">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">5</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="-5">button4</action-widget>
+    </action-widgets>
+  </object>
   <object class="GtkListStore" id="list-store">
     <columns>
       <!-- column-name date -->
diff --git a/gnucash/gtkbuilder/dialog-preferences.glade b/gnucash/gtkbuilder/dialog-preferences.glade
index ab70d7f27..e25b9a140 100644
--- a/gnucash/gtkbuilder/dialog-preferences.glade
+++ b/gnucash/gtkbuilder/dialog-preferences.glade
@@ -1698,7 +1698,7 @@ many months before the current month</property>
                   <object class="GtkBox" id="pref/general/assoc-head">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="tooltip_markup" translatable="yes">Path head for Transaction Associated files </property>
+                    <property name="tooltip_markup" translatable="yes">Path head for Associated files</property>
                     <child>
                       <placeholder/>
                     </child>
@@ -1716,7 +1716,7 @@ many months before the current month</property>
                       <object class="GtkLabel" id="label20">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Path head for Transaction Association Files</property>
+                        <property name="label" translatable="yes">Path head for Associated Files</property>
                         <property name="use_markup">True</property>
                       </object>
                       <packing>
@@ -1782,7 +1782,7 @@ many months before the current month</property>
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <property name="halign">start</property>
-                    <property name="label" translatable="yes"><b>Transaction Association</b></property>
+                    <property name="label" translatable="yes"><b>Associated Files</b></property>
                     <property name="use_markup">True</property>
                   </object>
                   <packing>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2306930f0..881c4f03a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -128,6 +128,7 @@ gnucash/gnome-utils/account-quickfill.c
 gnucash/gnome-utils/assistant-xml-encoding.c
 gnucash/gnome-utils/cursors.c
 gnucash/gnome-utils/dialog-account.c
+gnucash/gnome-utils/dialog-assoc-utils.c
 gnucash/gnome-utils/dialog-book-close.c
 gnucash/gnome-utils/dialog-commodity.c
 gnucash/gnome-utils/dialog-dup-trans.c

commit 7e05d8690ac77c504615db2ce511b30161054b8c
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 15:15:13 2020 +0100

    Allow the Transaction Association to be updated from list
    
    Allow the transaction Association to be updated from the list of
    associations by double clicking on the selected row column Available.

diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index dbd57d409..484136106 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -599,6 +599,30 @@ assoc_dialog_update (AssocDialog *assoc_dialog)
     g_object_unref (G_OBJECT(model));
 }
 
+static void
+update_model_with_changes (AssocDialog *assoc_dialog, GtkTreeIter *iter, const gchar *uri)
+{
+    gchar *display_uri;
+    gboolean rel = FALSE;
+    gchar *scheme = gnc_uri_get_scheme (uri);
+
+    if (!scheme) // path is relative
+        rel = TRUE;
+
+    display_uri = assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
+    gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), iter,
+                        DISPLAY_URI, display_uri, AVAILABLE, _("File Found"),
+                        URI, uri,
+                        URI_RELATIVE, rel, // used just for sorting relative column
+                        URI_RELATIVE_PIX, (rel == TRUE ? "emblem-default" : NULL), -1);
+
+    if (!rel && !gnc_uri_is_file_scheme (scheme))
+        gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), iter, AVAILABLE, _("Unknown"), -1);
+
+    g_free (display_uri);
+    g_free (scheme);
+}
+
 static void
 row_selected_cb (GtkTreeView *view, GtkTreePath *path,
                   GtkTreeViewColumn  *col, gpointer user_data)
@@ -617,7 +641,11 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
     if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DISPLAY_URI - 1) == col)
         gnc_assoc_open_uri (GTK_WINDOW(assoc_dialog->window), uri);
 
-    g_free (uri);
+    if (!split)
+    {
+        g_free (uri);
+        return;
+    }
 
     // Open transaction, subtract 1 to allow for date_int64
     if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DESC_TRANS - 1) == col)
@@ -626,9 +654,6 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
         GNCSplitReg   *gsr;
         Account       *account;
 
-        if (!split)
-            return;
-
         account = xaccSplitGetAccount (split);
 
         page = gnc_plugin_page_register_new (account, FALSE);
@@ -638,6 +663,34 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
 
         gnc_split_reg_jump_to_split (gsr, split);
     }
+
+    // Open transaction association dialog, subtract 1 to allow for date_int64
+    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), AVAILABLE - 1) == col)
+    {
+        Transaction *trans;
+        gchar       *ret_uri = NULL;
+
+        trans = xaccSplitGetParent (split);
+
+        if (xaccTransIsReadonlyByPostedDate (trans) || xaccTransGetReadOnly (trans) || assoc_dialog->book_ro)
+        {
+            gnc_warning_dialog (GTK_WINDOW(assoc_dialog->window), "%s", _("Transaction can not be modified."));
+            g_free (uri);
+            return;
+        }
+        ret_uri = gnc_assoc_get_uri_dialog (GTK_WINDOW(assoc_dialog->window), _("Change a Transaction Association"), uri);
+
+        if (ret_uri && g_strcmp0 (uri, ret_uri) != 0)
+        {
+            xaccTransSetAssociation (trans, ret_uri);
+            if (g_strcmp0 (ret_uri, "") == 0) // deleted uri
+                gtk_list_store_remove (GTK_LIST_STORE(assoc_dialog->model), &iter);
+            else // updated uri
+                update_model_with_changes (assoc_dialog, &iter, ret_uri);
+        }
+        g_free (ret_uri);
+    }
+    g_free (uri);
 }
 
 gchar*
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index 035287b6f..98b33e0ac 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -500,8 +500,8 @@
             <property name="can_focus">False</property>
             <property name="margin_top">3</property>
             <property name="margin_bottom">3</property>
-            <property name="label" translatable="yes">     To jump to the Transaction, double click on the entry in the
-Description column or Association column to open the Association</property>
+            <property name="label" translatable="yes">   To jump to the Transaction, double click on the entry in the Description
+column, Association column to open the Association or Available to update</property>
           </object>
           <packing>
             <property name="expand">False</property>

commit d282e645a72b98a5965c915ac32ba32dedf4f21d
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 15:06:05 2020 +0100

    Bug 797185 - Add Reload button for Transaction associations
    
    Currently to refresh the list the dialog needs to be closed and
    re-opened so add a button to reload the model instead. Also added
    another button that does a reload and location check in one.

diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index f0a7baeea..dbd57d409 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -599,20 +599,6 @@ assoc_dialog_update (AssocDialog *assoc_dialog)
     g_object_unref (G_OBJECT(model));
 }
 
-static void
-gnc_assoc_dialog_check_button_cb (GtkWidget * widget, gpointer user_data)
-{
-    AssocDialog   *assoc_dialog = user_data;
-    assoc_dialog_update (assoc_dialog);
-}
-
-static void
-gnc_assoc_dialog_close_button_cb (GtkWidget * widget, gpointer user_data)
-{
-    AssocDialog   *assoc_dialog = user_data;
-    gnc_close_gui_component (assoc_dialog->component_id);
-}
-
 static void
 row_selected_cb (GtkTreeView *view, GtkTreePath *path,
                   GtkTreeViewColumn  *col, gpointer user_data)
@@ -750,6 +736,47 @@ get_trans_info (AssocDialog *assoc_dialog)
     g_object_unref (G_OBJECT(assoc_dialog->model));
 }
 
+static void
+gnc_assoc_dialog_reload_button_cb (GtkWidget *widget, gpointer user_data)
+{
+    AssocDialog *assoc_dialog = user_data;
+    gchar          *path_head = assoc_get_path_head ();
+
+    if (g_strcmp0 (path_head, assoc_dialog->path_head) != 0)
+    {
+        g_free (assoc_dialog->path_head);
+        assoc_dialog->path_head = g_strdup (path_head);
+
+        // display path head text and test if present
+        assoc_set_path_head_label (assoc_dialog->path_head_label);
+    }
+    g_free (path_head);
+    get_trans_info (assoc_dialog);
+}
+
+static void
+gnc_assoc_dialog_reload_check_button_cb (GtkWidget *widget, gpointer user_data)
+{
+    AssocDialog *assoc_dialog = user_data;
+
+    gnc_assoc_dialog_reload_button_cb (widget, user_data);
+    assoc_dialog_update (assoc_dialog);
+}
+
+static void
+gnc_assoc_dialog_check_button_cb (GtkWidget *widget, gpointer user_data)
+{
+    AssocDialog *assoc_dialog = user_data;
+    assoc_dialog_update (assoc_dialog);
+}
+
+static void
+gnc_assoc_dialog_close_button_cb (GtkWidget *widget, gpointer user_data)
+{
+    AssocDialog *assoc_dialog = user_data;
+    gnc_close_gui_component (assoc_dialog->component_id);
+}
+
 static void
 gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
 {
@@ -768,6 +795,11 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     assoc_dialog->window = window;
     assoc_dialog->session = gnc_get_current_session();
 
+    button = GTK_WIDGET(gtk_builder_get_object (builder, "reload_button"));
+        g_signal_connect(button, "clicked", G_CALLBACK(gnc_assoc_dialog_reload_button_cb), assoc_dialog);
+    button = GTK_WIDGET(gtk_builder_get_object (builder, "reload_and_check_button"));
+        g_signal_connect(button, "clicked", G_CALLBACK(gnc_assoc_dialog_reload_check_button_cb), assoc_dialog);
+
     button = GTK_WIDGET(gtk_builder_get_object (builder, "check_button"));
         g_signal_connect(button, "clicked", G_CALLBACK(gnc_assoc_dialog_check_button_cb), assoc_dialog);
     button = GTK_WIDGET(gtk_builder_get_object (builder, "close_button"));
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index fae4d5a19..035287b6f 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -316,9 +316,37 @@
           <object class="GtkButtonBox" id="buttonbox">
             <property name="can_focus">False</property>
             <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="reload_and_check_button">
+                <property name="label" translatable="yes">Reload and Locate _Associations</property>
+                <property name="visible">True</property>
+                <property name="can_focus">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">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="reload_button">
+                <property name="label" translatable="yes">_Reload</property>
+                <property name="visible">True</property>
+                <property name="can_focus">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>
             <child>
               <object class="GtkButton" id="check_button">
-                <property name="label" translatable="yes">_Locate Association</property>
+                <property name="label" translatable="yes">_Locate Associations</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
@@ -327,7 +355,7 @@
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">1</property>
+                <property name="position">2</property>
               </packing>
             </child>
             <child>
@@ -343,7 +371,7 @@
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
-                <property name="position">2</property>
+                <property name="position">3</property>
               </packing>
             </child>
           </object>

commit 1884ae20936282a749346741b11a318c9d32a3ea
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 15:00:13 2020 +0100

    Bug 797185 - Allow sorting on all Transaction Association columns

diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index ac5648abf..f0a7baeea 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -46,7 +46,18 @@
 #define GNC_PREFS_GROUP          "dialogs.trans-assoc"
 
 /** Enumeration for the tree-store */
-enum GncAssocColumn {DATE_TRANS, DESC_TRANS, URI_U, AVAILABLE, URI_SPLIT, URI, URI_RELATIVE};
+enum GncAssocColumn
+{
+    DATE_TRANS,
+    DATE_INT64, // used just for sorting date_trans
+    DESC_TRANS,
+    DISPLAY_URI,
+    AVAILABLE,
+    URI_SPLIT,
+    URI,
+    URI_RELATIVE, // used just for sorting relative_pix
+    URI_RELATIVE_PIX
+};
 
 typedef struct
 {
@@ -528,56 +539,6 @@ gnc_assoc_dialog_window_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpo
         return FALSE;
 }
 
-static gint
-sort_iter_compare_func (GtkTreeModel *model,
-                        GtkTreeIter  *a,
-                        GtkTreeIter  *b,
-                        gpointer  user_data)
-{
-    gint ret = 0;
-    gchar *uri1, *uri2;
-
-    gtk_tree_model_get (model, a, URI_U, &uri1, -1);
-    gtk_tree_model_get (model, b, URI_U, &uri2, -1);
-
-    ret = g_utf8_collate (uri1, uri2);
-
-    g_free (uri1);
-    g_free (uri2);
-
-    return ret;
-}
-
-static void
-assoc_dialog_sort (AssocDialog *assoc_dialog)
-{
-    GtkTreeModel *model;
-    GtkTreeSortable *sortable;
-    gint id;
-    GtkSortType order;
-
-    model = gtk_tree_view_get_model (GTK_TREE_VIEW(assoc_dialog->view));
-
-    sortable = GTK_TREE_SORTABLE(model);
-
-    if (gtk_tree_sortable_get_sort_column_id (sortable, &id, &order))
-    {
-        if (order == GTK_SORT_ASCENDING)
-            order = GTK_SORT_DESCENDING;
-        else
-            order = GTK_SORT_ASCENDING;
-    }
-    else
-    {
-        gtk_tree_sortable_set_sort_func (sortable, URI, sort_iter_compare_func,
-                                         assoc_dialog, NULL);
-
-        order = GTK_SORT_ASCENDING;
-    }
-    /* set sort order */
-    gtk_tree_sortable_set_sort_column_id (sortable, URI, order);
-}
-
 static void
 assoc_dialog_update (AssocDialog *assoc_dialog)
 {
@@ -638,13 +599,6 @@ assoc_dialog_update (AssocDialog *assoc_dialog)
     g_object_unref (G_OBJECT(model));
 }
 
-static void
-gnc_assoc_dialog_sort_button_cb (GtkWidget * widget, gpointer user_data)
-{
-    AssocDialog   *assoc_dialog = user_data;
-    assoc_dialog_sort (assoc_dialog);
-}
-
 static void
 gnc_assoc_dialog_check_button_cb (GtkWidget * widget, gpointer user_data)
 {
@@ -673,14 +627,14 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
 
     gtk_tree_model_get (assoc_dialog->model, &iter, URI, &uri, URI_SPLIT, &split, -1);
 
-    // Open associated link
-    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), URI_U) == col)
+    // Open associated link, subtract 1 to allow for date_int64
+    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DISPLAY_URI - 1) == col)
         gnc_assoc_open_uri (GTK_WINDOW(assoc_dialog->window), uri);
 
     g_free (uri);
 
-    // Open transaction
-    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DESC_TRANS) == col)
+    // Open transaction, subtract 1 to allow for date_int64
+    if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DESC_TRANS - 1) == col)
     {
         GncPluginPage *page;
         GNCSplitReg   *gsr;
@@ -760,10 +714,12 @@ add_trans_info_to_model (QofInstance* data, gpointer user_data)
 
         gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), &iter,
                             DATE_TRANS, datebuff,
+                            DATE_INT64, t, // used just for sorting date column
                             DESC_TRANS, xaccTransGetDescription (trans),
-                            URI_U, display_uri, AVAILABLE, _("Unknown"),
+                            DISPLAY_URI, display_uri, AVAILABLE, _("Unknown"),
                             URI_SPLIT, split, URI, uri,
-                            URI_RELATIVE, (rel == TRUE ? "emblem-default" : NULL), -1);
+                            URI_RELATIVE, rel, // used just for sorting relative column
+                            URI_RELATIVE_PIX, (rel == TRUE ? "emblem-default" : NULL), -1);
         g_free (display_uri);
         g_free (scheme);
         g_free (uri);
@@ -801,7 +757,6 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     GtkBuilder        *builder;
     GtkTreeSelection  *selection;
     GtkTreeViewColumn *tree_column;
-    GtkCellRenderer   *cr;
     GtkWidget         *button;
 
     ENTER(" ");
@@ -813,8 +768,6 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     assoc_dialog->window = window;
     assoc_dialog->session = gnc_get_current_session();
 
-    button = GTK_WIDGET(gtk_builder_get_object (builder, "sort_button"));
-        g_signal_connect(button, "clicked", G_CALLBACK(gnc_assoc_dialog_sort_button_cb), assoc_dialog);
     button = GTK_WIDGET(gtk_builder_get_object (builder, "check_button"));
         g_signal_connect(button, "clicked", G_CALLBACK(gnc_assoc_dialog_check_button_cb), assoc_dialog);
     button = GTK_WIDGET(gtk_builder_get_object (builder, "close_button"));
@@ -835,26 +788,16 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     assoc_set_path_head_label (assoc_dialog->path_head_label);
 
     // set the Associate column to be the one that expands
-    tree_column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object (builder, "uri-entry"));
+    tree_column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object (builder, "assoc"));
     gtk_tree_view_column_set_expand (tree_column, TRUE);
 
-    /* Need to add toggle renderers here to get the xalign to work. */
-    tree_column = gtk_tree_view_column_new();
-    gtk_tree_view_column_set_title (tree_column, _("Relative"));
-    gtk_tree_view_append_column (GTK_TREE_VIEW(assoc_dialog->view), tree_column);
-    gtk_tree_view_column_set_alignment (tree_column, 0.5);
-    cr = gtk_cell_renderer_pixbuf_new();
-    gtk_tree_view_column_pack_start (tree_column, cr, TRUE);
-    // connect 'active' and set 'xalign' property of the cell renderer
-    gtk_tree_view_column_set_attributes (tree_column, cr, "icon-name", URI_RELATIVE, NULL);
-    gtk_cell_renderer_set_alignment (cr, 0.5, 0.5);
-
     g_signal_connect (assoc_dialog->view, "row-activated",
                       G_CALLBACK(row_selected_cb), (gpointer)assoc_dialog);
 
-    // set the Associate column to be the one that expands
-    tree_column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object (builder, "uri-entry"));
-    gtk_tree_view_column_set_expand (tree_column, TRUE);
+    /* default sort order */
+    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(gtk_tree_view_get_model(
+                                          GTK_TREE_VIEW(assoc_dialog->view))),
+                                          DATE_INT64, GTK_SORT_ASCENDING);
 
     // Set grid lines option to preference
     gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(assoc_dialog->view), gnc_tree_view_get_grid_lines_pref ());
@@ -876,6 +819,7 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     get_trans_info (assoc_dialog);
     gtk_widget_show_all (GTK_WIDGET(window));
 
+    gtk_tree_view_columns_autosize (GTK_TREE_VIEW(assoc_dialog->view));
     LEAVE(" ");
 }
 
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index a73290523..fae4d5a19 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -269,9 +269,11 @@
     <columns>
       <!-- column-name date -->
       <column type="gchararray"/>
+      <!-- column-name date_int64 -->
+      <column type="gint64"/>
       <!-- column-name desc -->
       <column type="gchararray"/>
-      <!-- column-name uri_u -->
+      <!-- column-name display_uri -->
       <column type="gchararray"/>
       <!-- column-name available -->
       <column type="gchararray"/>
@@ -280,6 +282,8 @@
       <!-- column-name uri -->
       <column type="gchararray"/>
       <!-- column-name uri_relative -->
+      <column type="gboolean"/>
+      <!-- column-name uri_relative_pix -->
       <column type="gchararray"/>
     </columns>
   </object>
@@ -312,20 +316,6 @@
           <object class="GtkButtonBox" id="buttonbox">
             <property name="can_focus">False</property>
             <property name="layout_style">end</property>
-            <child>
-              <object class="GtkButton" id="sort_button">
-                <property name="label" translatable="yes">_Sort Association</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="use_underline">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
             <child>
               <object class="GtkButton" id="check_button">
                 <property name="label" translatable="yes">_Locate Association</property>
@@ -394,6 +384,8 @@
                     <property name="resizable">True</property>
                     <property name="title" translatable="yes">Date</property>
                     <property name="alignment">0.5</property>
+                    <property name="reorderable">True</property>
+                    <property name="sort_column_id">1</property>
                     <child>
                       <object class="GtkCellRendererText" id="cellrenderertext1"/>
                       <attributes>
@@ -407,25 +399,29 @@
                     <property name="resizable">True</property>
                     <property name="title" translatable="yes">Description</property>
                     <property name="alignment">0.5</property>
+                    <property name="reorderable">True</property>
+                    <property name="sort_column_id">2</property>
                     <child>
                       <object class="GtkCellRendererText" id="cellrenderertext2"/>
                       <attributes>
-                        <attribute name="text">1</attribute>
+                        <attribute name="text">2</attribute>
                       </attributes>
                     </child>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkTreeViewColumn" id="uri-entry">
+                  <object class="GtkTreeViewColumn" id="assoc">
                     <property name="resizable">True</property>
                     <property name="title" translatable="yes">Association</property>
                     <property name="alignment">0.5</property>
+                    <property name="reorderable">True</property>
+                    <property name="sort_column_id">3</property>
                     <child>
                       <object class="GtkCellRendererText" id="cellrenderertext3">
                         <property name="ellipsize">start</property>
                       </object>
                       <attributes>
-                        <attribute name="text">2</attribute>
+                        <attribute name="text">3</attribute>
                       </attributes>
                     </child>
                   </object>
@@ -435,12 +431,25 @@
                     <property name="resizable">True</property>
                     <property name="title" translatable="yes">Available ?</property>
                     <property name="alignment">0.5</property>
+                    <property name="sort_column_id">4</property>
                     <child>
                       <object class="GtkCellRendererText" id="cellrenderertext4">
                         <property name="xpad">10</property>
                       </object>
                       <attributes>
-                        <attribute name="text">3</attribute>
+                        <attribute name="text">4</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkTreeViewColumn" id="relative">
+                    <property name="title" translatable="yes">Relative</property>
+                    <property name="sort_column_id">7</property>
+                    <child>
+                      <object class="GtkCellRendererPixbuf" id="cellrendererpix1"/>
+                      <attributes>
+                        <attribute name="icon-name">8</attribute>
                       </attributes>
                     </child>
                   </object>

commit 282e456f52f019e2139e0e7eec519c7fea267191
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 14:58:41 2020 +0100

    Move the transaction association dialog to dialog-assoc.c/h
    
    The transaction association dialog was all setup from gnc-split-reg but
    it seems logical to move all the association source to one file so this
    commit does that as well as changing the dialog to be based on a glade
    file. The existing toolbar menu entries have changed to Update, Open and
    Remove the association.

diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index a39e73071..ac5648abf 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -30,7 +30,6 @@
 #include "dialog-utils.h"
 #include "gnc-component-manager.h"
 #include "gnc-session.h"
-#include "Query.h"
 #include "Transaction.h"
 
 #include "gnc-plugin-page-register.h"
@@ -53,15 +52,448 @@ typedef struct
 {
     GtkWidget    *window;
     GtkWidget    *view;
-    const gchar  *path_head;
-    gboolean      path_head_set;
+    GtkWidget    *path_head_label;
+    gchar        *path_head;
+    gboolean      book_ro;
+    GtkTreeModel *model;
     gint          component_id;
     QofSession   *session;
 }AssocDialog;
 
-/* This static indicates the debugging module that this .o belongs to.  */
+/* This static indicates the debugging module that this .o belongs to. */
 static QofLogModule log_module = GNC_MOD_GUI;
 
+/***********************************************************************/
+
+static gchar *
+convert_uri_to_abs_path (const gchar *path_head, const gchar *uri, gchar *uri_scheme, gboolean return_uri)
+{
+    gchar *ret_value = NULL;
+
+    if (!uri_scheme) // relative path
+    {
+        gchar *path = gnc_uri_get_path (path_head);
+        gchar *file_path = gnc_file_path_absolute (path, uri);
+
+        if (return_uri)
+            ret_value = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, file_path);
+        else
+            ret_value = g_strdup (file_path);
+
+        g_free (path);
+        g_free (file_path);
+    }
+
+    if (g_strcmp0 (uri_scheme, "file") == 0) // absolute path
+    {
+        if (return_uri)
+            ret_value = g_strdup (uri);
+        else
+            ret_value = gnc_uri_get_path (uri);
+    }
+    return ret_value;
+}
+
+static gchar *
+assoc_get_unescape_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
+{
+    gchar *display_str = NULL;
+
+    if (uri && *uri)
+    {
+        // if scheme is null or 'file' we should get a file path
+        gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, FALSE);
+
+        if (file_path)
+            display_str = g_uri_unescape_string (file_path, NULL);
+        else
+            display_str = g_uri_unescape_string (uri, NULL);
+
+        g_free (file_path);
+
+#ifdef G_OS_WIN32 // make path look like a traditional windows path
+        display_str = g_strdelimit (display_str, "/", '\\');
+#endif
+    }
+    DEBUG("Return display string is '%s'", display_str);
+    return display_str;
+}
+
+static gchar *
+assoc_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
+{
+    gchar *use_str = NULL;
+
+    if (uri && *uri)
+    {
+        // if scheme is null or 'file' we should get a file path
+        gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, TRUE);
+
+        if (file_path)
+            use_str = g_strdup (file_path);
+        else
+            use_str = g_strdup (uri);
+
+        g_free (file_path);
+    }
+    DEBUG("Return use string is '%s'", use_str);
+    return use_str;
+}
+
+static gchar *
+assoc_get_path_head_and_set (gboolean *path_head_set)
+{
+    gchar *ret_path = NULL;
+    gchar *path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL, "assoc-head");
+    *path_head_set = FALSE;
+
+    if (path_head && *path_head) // not default entry
+    {
+        *path_head_set = TRUE;
+        ret_path = g_strdup (path_head);
+    }
+    else
+    {
+        const gchar *doc = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
+
+        if (doc)
+            ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, doc);
+        else
+            ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, gnc_userdata_dir ());
+    }
+    // make sure there is a trailing '/'
+    if (!g_str_has_suffix (ret_path, "/"))
+    {
+        gchar *folder_with_slash = g_strconcat (ret_path, "/", NULL);
+        g_free (ret_path);
+        ret_path = g_strdup (folder_with_slash);
+        g_free (folder_with_slash);
+
+        if (*path_head_set) // prior to 3.5, assoc-head could be with or without a trailing '/'
+        {
+            if (!gnc_prefs_set_string (GNC_PREFS_GROUP_GENERAL, "assoc-head", ret_path))
+                PINFO ("Failed to save preference at %s, %s with %s",
+                       GNC_PREFS_GROUP_GENERAL, "assoc-head", ret_path);
+        }
+    }
+    g_free (path_head);
+    return ret_path;
+}
+
+static gchar *
+assoc_get_path_head (void)
+{
+    gboolean path_head_set = FALSE;
+
+    return assoc_get_path_head_and_set (&path_head_set);
+}
+
+static void
+assoc_set_path_head_label (GtkWidget *path_head_label)
+{
+    gboolean path_head_set = FALSE;
+    gchar *path_head = assoc_get_path_head_and_set (&path_head_set);
+    gchar *scheme = gnc_uri_get_scheme (path_head);
+    gchar *path_head_str = assoc_get_unescape_uri (NULL, path_head, scheme);
+    gchar *path_head_text;
+
+    if (path_head_set)
+    {
+        // test for current folder being present
+        if (g_file_test (path_head_str, G_FILE_TEST_IS_DIR))
+            path_head_text = g_strdup_printf ("%s '%s'", _("Path head for files is,"), path_head_str);
+        else
+            path_head_text = g_strdup_printf ("%s '%s'", _("Path head does not exist,"), path_head_str);
+    }
+    else
+        path_head_text = g_strdup_printf (_("Path head not set, using '%s' for relative paths"), path_head_str);
+
+    gtk_label_set_text (GTK_LABEL(path_head_label), path_head_text);
+
+    // Set the style context for this label so it can be easily manipulated with css
+    gnc_widget_style_context_add_class (GTK_WIDGET(path_head_label), "gnc-class-highlight");
+
+    g_free (scheme);
+    g_free (path_head_str);
+    g_free (path_head_text);
+    g_free (path_head);
+}
+
+void
+gnc_assoc_open_uri (GtkWindow *parent, const gchar *uri)
+{
+    if (uri && *uri)
+    {
+        gchar     *scheme = gnc_uri_get_scheme (uri);
+        gchar  *path_head = assoc_get_path_head ();
+        gchar    *run_uri = assoc_get_use_uri (path_head, uri, scheme);
+        gchar *run_scheme = gnc_uri_get_scheme (run_uri);
+
+        PINFO("Open uri scheme is '%s', uri is '%s'", run_scheme, run_uri);
+
+        if (run_scheme) // make sure we have a scheme entry
+        {
+            gnc_launch_assoc (GTK_WINDOW (parent), run_uri);
+            g_free (run_scheme);
+        }
+        g_free (run_uri);
+        g_free (path_head);
+        g_free (scheme);
+    }
+}
+
+/***********************************************************************/
+
+static void
+location_ok_cb (GtkEditable *editable, gpointer user_data)
+{
+    GtkWidget *ok_button = user_data;
+    gboolean have_scheme = FALSE;
+    gchar *text = gtk_editable_get_chars (editable, 0, -1);
+    GtkWidget *warning_hbox = g_object_get_data (G_OBJECT(editable), "whbox");
+
+    if (text && *text)
+    {
+        gchar *scheme = gnc_uri_get_scheme (text);
+
+        if (scheme)
+            have_scheme = TRUE;
+        g_free (scheme);
+    }
+    gtk_widget_set_visible (warning_hbox, !have_scheme);
+    gtk_widget_set_sensitive (ok_button, have_scheme);
+    g_free (text);
+}
+
+static void
+assoc_file_chooser_selection_changed_cb (GtkFileChooser *chooser, GtkWidget *ok_button)
+{
+    gchar *file_name = gtk_file_chooser_get_filename (chooser);
+    gboolean file_true = FALSE;
+
+    /* Test for a valid filename and not a directory */
+    if (file_name && !g_file_test (file_name, G_FILE_TEST_IS_DIR))
+        file_true = TRUE;
+
+    gtk_widget_set_sensitive (ok_button, file_true);
+    g_free (file_name);
+}
+
+static void
+uri_type_selected_cb (GtkToggleButton *button, GtkWidget *widget)
+{
+    GtkWidget *top = gtk_widget_get_toplevel (widget);
+    GtkWidget *parent_hbox = gtk_widget_get_parent (widget);
+    GtkWidget *ok_button = g_object_get_data (G_OBJECT(widget), "okbut");
+    gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button));
+
+    // set the visibility of the parent hbox for widget
+    gtk_widget_set_visible (parent_hbox, active);
+
+    // make the window resize after hiding widgets
+    if (active)
+    {
+        if (g_strcmp0 (gtk_buildable_get_name (
+                             GTK_BUILDABLE(parent_hbox)), "location_hbox") == 0)
+        {
+            location_ok_cb (GTK_EDITABLE(widget), ok_button);
+            gtk_window_resize (GTK_WINDOW(top), 600, 10); // width, height
+        }
+        else
+        {
+            assoc_file_chooser_selection_changed_cb (GTK_FILE_CHOOSER(widget), ok_button);
+            gtk_window_resize (GTK_WINDOW(top), 900, 500);
+        }
+    }
+    gtk_widget_grab_focus (GTK_WIDGET(widget));
+}
+
+static void
+setup_location_dialog (GtkBuilder *builder, GtkWidget *button_loc, const gchar *uri)
+{
+    GtkLabel *location_label = GTK_LABEL(gtk_builder_get_object (builder, "location_label"));
+    GtkEntry *entry = GTK_ENTRY(gtk_builder_get_object (builder, "location_entry"));
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button_loc), TRUE);
+
+    // set entry settings
+    gtk_entry_set_width_chars (entry, 80);
+    gtk_entry_set_activates_default (entry, TRUE);
+    gtk_widget_grab_focus (GTK_WIDGET(entry));
+
+    // update label and set entry text if required
+    if (uri)
+    {
+        gtk_label_set_text (location_label, _("Amend URL:"));
+        gtk_entry_set_text (entry, uri);
+    }
+    else
+        gtk_label_set_text (location_label, _("Enter URL like http://www.gnucash.org:"));
+}
+
+static void
+setup_file_dialog (GtkFileChooserWidget *fc, const gchar *path_head, const gchar *uri, gchar *scheme)
+{
+    gchar *display_uri = assoc_get_unescape_uri (path_head, uri, scheme);
+
+    if (display_uri)
+    {
+        GtkWidget *label, *hbox;
+        GtkWidget *image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_SMALL_TOOLBAR);
+        gchar     *use_uri = assoc_get_use_uri (path_head, uri, scheme);
+        gchar     *uri_label = g_strdup_printf ("%s '%s'", _("Existing Association is"), display_uri);
+
+        hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+        label = gtk_label_new (uri_label);
+
+        if (g_file_test (display_uri, G_FILE_TEST_EXISTS))
+            gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, TRUE, 0);
+        else
+        {
+            gtk_box_pack_start (GTK_BOX(hbox), image, FALSE, FALSE, 0);
+            gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, TRUE, 0);
+        }
+
+        PINFO("Path head: '%s', URI: '%s', Filename: '%s'", path_head, uri, display_uri);
+
+        gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(fc), hbox);
+        gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_START);
+
+        // Set the style context for this label so it can be easily manipulated with css
+        gnc_widget_style_context_add_class (GTK_WIDGET(label), "gnc-class-highlight");
+        gtk_file_chooser_set_uri (GTK_FILE_CHOOSER(fc), use_uri);
+        gtk_widget_show_all (hbox);
+
+        g_free (display_uri);
+        g_free (uri_label);
+        g_free (use_uri);
+    }
+}
+
+gchar *
+gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *uri)
+{
+    GtkWidget *dialog, *button_loc, *button_file, *ok_button, *warning_hbox;
+    GtkBuilder *builder;
+    gboolean uri_is_file, have_uri = FALSE;
+    GtkEntry *entry;
+    GtkFileChooserWidget *fc;
+    GtkWidget *head_label;
+    int result;
+    gchar *ret_uri = NULL;
+    gchar *path_head = assoc_get_path_head ();
+    gchar *scheme = NULL;
+
+    /* Create the dialog box */
+    builder = gtk_builder_new();
+    gnc_builder_add_from_file (builder, "dialog-assoc.glade", "association_dialog");
+    dialog = GTK_WIDGET(gtk_builder_get_object (builder, "association_dialog"));
+    gtk_window_set_title (GTK_WINDOW(dialog), title);
+
+    if (parent != NULL)
+        gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(parent));
+
+    // Set the name and style context for this widget so it can be easily manipulated with css
+    gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-association");
+    gnc_widget_style_context_add_class (GTK_WIDGET(dialog), "gnc-class-association");
+
+    head_label = GTK_WIDGET(gtk_builder_get_object (builder, "path_head_label"));
+    ok_button = GTK_WIDGET(gtk_builder_get_object (builder, "ok_button"));
+
+    fc = GTK_FILE_CHOOSER_WIDGET(gtk_builder_get_object (builder, "file_chooser"));
+    g_object_set_data (G_OBJECT(fc), "okbut", ok_button);
+    g_signal_connect (G_OBJECT(fc), "selection-changed",
+                      G_CALLBACK(assoc_file_chooser_selection_changed_cb), ok_button);
+
+    button_file = GTK_WIDGET(gtk_builder_get_object (builder, "file_assoc"));
+    g_signal_connect (button_file, "toggled", G_CALLBACK(uri_type_selected_cb), fc);
+
+    gtk_widget_show_all (GTK_WIDGET(gtk_builder_get_object (builder, "file_hbox")));
+
+    warning_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "warning_hbox"));
+    entry = GTK_ENTRY(gtk_builder_get_object (builder, "location_entry"));
+    g_object_set_data (G_OBJECT(entry), "whbox", warning_hbox);
+    g_object_set_data (G_OBJECT(entry), "okbut", ok_button);
+
+    g_signal_connect (entry, "changed", G_CALLBACK(location_ok_cb), ok_button);
+
+    button_loc = GTK_WIDGET(gtk_builder_get_object (builder, "loc_assoc"));
+    g_signal_connect (button_loc, "toggled", G_CALLBACK(uri_type_selected_cb), entry);
+
+    // display path head text and test if present
+    assoc_set_path_head_label (head_label);
+
+    // Check for uri is empty or NULL
+    if (uri && *uri)
+    {
+        scheme = gnc_uri_get_scheme (uri);
+        have_uri = TRUE;
+
+        if (!scheme || g_strcmp0 (scheme, "file") == 0) // use the correct dialog
+            uri_is_file = TRUE;
+        else
+            uri_is_file = FALSE;
+    }
+    else
+    {
+        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button_loc), TRUE);
+        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button_file), TRUE);
+    }
+
+    // make sure we start with the right dialog
+    if (have_uri && !uri_is_file) // location
+        setup_location_dialog (builder, button_loc, uri);
+
+    if (have_uri && uri_is_file) // file
+         setup_file_dialog (fc, path_head, uri, scheme);
+
+    g_free (scheme);
+    g_object_unref (G_OBJECT(builder));
+
+    // run the dialog
+    result = gtk_dialog_run (GTK_DIALOG(dialog));
+    if (result == GTK_RESPONSE_OK) //ok button
+    {
+        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(button_loc))) // location
+        {
+            const gchar *dialog_uri = gtk_entry_get_text (GTK_ENTRY(entry));
+
+            ret_uri = g_strdup (dialog_uri);
+
+            DEBUG("Dialog Location URI: '%s'", dialog_uri);
+        }
+        else // file
+        {
+            gchar *dialog_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER(fc));
+
+            PINFO("Dialog File URI: '%s', Path head: '%s'", dialog_uri, path_head);
+
+            // relative paths do not start with a '/'
+            if (g_str_has_prefix (dialog_uri, path_head))
+            {
+                const gchar *part = dialog_uri + strlen (path_head);
+                ret_uri = g_strdup (part);
+            }
+            else
+                ret_uri = g_strdup (dialog_uri);
+
+            PINFO("Dialog File URI: '%s'", ret_uri);
+            g_free (dialog_uri);
+        }
+    }
+    else if (result == GTK_RESPONSE_REJECT) // remove button
+        ret_uri = g_strdup ("");
+    else
+        ret_uri = g_strdup (uri); // any other button
+
+    g_free (path_head);
+    gtk_widget_destroy (dialog);
+    return ret_uri;
+}
+
+
+/***********************************************************************/
+
+
 static void close_handler (gpointer user_data);
 
 static void
@@ -74,6 +506,7 @@ gnc_assoc_dialog_window_destroy_cb (GtkWidget *object, gpointer user_data)
 
     if (assoc_dialog->window)
     {
+        g_free (assoc_dialog->path_head);
         gtk_widget_destroy (assoc_dialog->window);
         assoc_dialog->window = NULL;
     }
@@ -137,7 +570,7 @@ assoc_dialog_sort (AssocDialog *assoc_dialog)
     else
     {
         gtk_tree_sortable_set_sort_func (sortable, URI, sort_iter_compare_func,
-                                assoc_dialog, NULL);
+                                         assoc_dialog, NULL);
 
         order = GTK_SORT_ASCENDING;
     }
@@ -145,49 +578,6 @@ assoc_dialog_sort (AssocDialog *assoc_dialog)
     gtk_tree_sortable_set_sort_column_id (sortable, URI, order);
 }
 
-static gchar *
-convert_uri_to_filename (AssocDialog *assoc_dialog, const gchar *uri, gchar *scheme)
-{
-    gchar *file_path = NULL;
-
-    if (!scheme) // relative path
-    {
-        if (assoc_dialog->path_head_set) // not default entry
-            file_path = gnc_file_path_absolute (gnc_uri_get_path (assoc_dialog->path_head), uri);
-        else
-            file_path = gnc_file_path_absolute (NULL, uri);
-    }
-
-    if (gnc_uri_is_file_scheme (scheme)) // absolute path
-        file_path = gnc_uri_get_path (uri);
-
-    return file_path;
-}
-
-static gchar *
-convert_uri_to_unescaped (AssocDialog *assoc_dialog, const gchar *uri, gchar *scheme)
-{
-    gchar *uri_u = NULL;
-    gchar *file_path = NULL;
-
-    // if scheme is null or 'file' we should get a file path
-    file_path = convert_uri_to_filename (assoc_dialog, uri, scheme);
-
-    if (file_path)
-    {
-        uri_u = g_uri_unescape_string (file_path, NULL);
-#ifdef G_OS_WIN32 // make path look like a traditional windows path
-        uri_u = g_strdelimit (uri_u, "/", '\\');
-#endif
-    }
-    else
-        uri_u = g_uri_unescape_string (uri, NULL);
-
-    g_free (file_path);
-
-    return uri_u;
-}
-
 static void
 assoc_dialog_update (AssocDialog *assoc_dialog)
 {
@@ -205,30 +595,29 @@ assoc_dialog_update (AssocDialog *assoc_dialog)
 
     while (valid)
     {
-        GNetworkMonitor    *nm;
-        GSocketConnectable *conn;
-        gchar              *uri;
-        gchar              *filename;
-        gchar              *scheme;
+        gchar *uri;
+        gchar *scheme;
 
         gtk_tree_model_get (model, &iter, URI, &uri, -1);
 
         scheme = gnc_uri_get_scheme (uri);
 
-        filename = convert_uri_to_unescaped (assoc_dialog, uri, scheme);
-
         if (!scheme || gnc_uri_is_file_scheme (scheme))
         {
+            gchar *filename = assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
+
             if (g_file_test (filename, G_FILE_TEST_EXISTS))
                 gtk_list_store_set (GTK_LIST_STORE(model), &iter, AVAILABLE, _("File Found"), -1);
             else
                 gtk_list_store_set (GTK_LIST_STORE(model), &iter, AVAILABLE, _("File Not Found"), -1);
+
+            g_free (filename);
         }
         else
         {
-            gchar *escaped = g_uri_escape_string (uri, ":/.", TRUE);
-            nm = g_network_monitor_get_default ();
-            conn = g_network_address_parse_uri (escaped, 80, NULL);
+            gchar           *escaped = g_uri_escape_string (uri, ":/.", TRUE);
+            GNetworkMonitor      *nm = g_network_monitor_get_default ();
+            GSocketConnectable *conn = g_network_address_parse_uri (escaped, 80, NULL);
 
             if (conn)
             {
@@ -241,12 +630,12 @@ assoc_dialog_update (AssocDialog *assoc_dialog)
         }
         g_free (uri);
         g_free (scheme);
-        g_free (filename);
+
         valid = gtk_tree_model_iter_next (model, &iter);
     }
     /* reconnect the model to the treeview */
     gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), model);
-    g_object_unref(G_OBJECT(model));
+    g_object_unref (G_OBJECT(model));
 }
 
 static void
@@ -275,50 +664,20 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
                   GtkTreeViewColumn  *col, gpointer user_data)
 {
     AssocDialog   *assoc_dialog = user_data;
-    GtkTreeModel  *model = gtk_tree_view_get_model (GTK_TREE_VIEW(assoc_dialog->view));
     GtkTreeIter    iter;
     Split         *split;
-    const gchar   *uri;
+    gchar         *uri = NULL;
 
-    if (!gtk_tree_model_get_iter (model, &iter, path))
-        return; /* path describes a non-existing row - should not happen */
+    // path describes a non-existing row - should not happen
+    g_return_if_fail (gtk_tree_model_get_iter (assoc_dialog->model, &iter, path));
 
-    gtk_tree_model_get (model, &iter, URI, &uri, URI_SPLIT, &split, -1);
+    gtk_tree_model_get (assoc_dialog->model, &iter, URI, &uri, URI_SPLIT, &split, -1);
 
     // Open associated link
     if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), URI_U) == col)
-    {
-        const gchar *uri_out = NULL;
-        gchar *uri_out_scheme;
-        gchar *uri_scheme = gnc_uri_get_scheme (uri);
-        gchar *file_path = NULL;
-
-        if (!uri_scheme) // relative path
-        {
-            if (assoc_dialog->path_head_set) // not default entry
-                file_path = gnc_file_path_absolute (gnc_uri_get_path (assoc_dialog->path_head), uri);
-            else
-                file_path = gnc_file_path_absolute (NULL, uri);
-
-            uri_out = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, file_path);
-        }
-        g_free (file_path);
-        g_free (uri_scheme);
+        gnc_assoc_open_uri (GTK_WINDOW(assoc_dialog->window), uri);
 
-        if (!uri_out)
-            uri_out = g_strdup (uri);
-
-        uri_out_scheme = gnc_uri_get_scheme (uri_out);
-
-        if (uri_out_scheme) // make sure we have a scheme entry
-        {
-            gnc_launch_assoc (gnc_ui_get_gtk_window(GTK_WIDGET (view)), uri_out);
-            g_free (uri_out_scheme);
-        }
-        else
-            gnc_error_dialog (gnc_ui_get_gtk_window(GTK_WIDGET (view)),
-                              "%s", _("This transaction is not associated with a valid URI."));
-    }
+    g_free (uri);
 
     // Open transaction
     if (gtk_tree_view_get_column (GTK_TREE_VIEW(assoc_dialog->view), DESC_TRANS) == col)
@@ -327,28 +686,22 @@ row_selected_cb (GtkTreeView *view, GtkTreePath *path,
         GNCSplitReg   *gsr;
         Account       *account;
 
-        /* This should never be true, but be paranoid */
         if (!split)
             return;
 
         account = xaccSplitGetAccount (split);
-        if (!account)
-            return;
 
         page = gnc_plugin_page_register_new (account, FALSE);
         gnc_main_window_open_page (NULL, page);
         gsr = gnc_plugin_page_register_get_gsr (page);
         gnc_split_reg_raise (gsr);
 
-        if (!gsr)
-            return;
-
         gnc_split_reg_jump_to_split (gsr, split);
     }
 }
 
-static gchar*
-gsr_convert_associate_uri (Transaction *trans)
+gchar*
+gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro)
 {
     const gchar *uri = xaccTransGetAssociation (trans); // get the existing uri
     const gchar *part = NULL;
@@ -358,103 +711,87 @@ gsr_convert_associate_uri (Transaction *trans)
 
     if (g_str_has_prefix (uri, "file:") && !g_str_has_prefix (uri,"file://"))
     {
-        // fix an earlier error when storing relative paths in version 3.3
-        // relative paths are stored without a leading "/" and in native form
-        if (g_str_has_prefix (uri,"file:/") && !g_str_has_prefix (uri,"file://"))
+        /* fix an earlier error when storing relative paths before version 3.5
+         * they were stored starting as 'file:' or 'file:/' depending on OS
+         * relative paths are stored without a leading "/" and in native form
+         */
+        if (g_str_has_prefix (uri,"file:/"))
             part = uri + strlen ("file:/");
-        else if (g_str_has_prefix (uri,"file:") && !g_str_has_prefix (uri,"file://"))
+        else if (g_str_has_prefix (uri,"file:"))
             part = uri + strlen ("file:");
 
-        if (part)
-        {
+        if (!xaccTransGetReadOnly (trans) && !book_ro)
             xaccTransSetAssociation (trans, part);
-            return g_strdup (part);
-        }
+
+        return g_strdup (part);
     }
     return g_strdup (uri);
 }
 
 static void
-get_trans_info (AssocDialog *assoc_dialog)
+add_trans_info_to_model (QofInstance* data, gpointer user_data)
 {
-    QofBook      *book = gnc_get_current_book();
-    Account      *root = gnc_book_get_root_account (book);
-    GList        *accts, *ptr;
-    GtkTreeModel *model;
-    GtkTreeIter   iter;
-    GList        *splits;
-    GHashTable   *trans_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+    AssocDialog *assoc_dialog = user_data;
+    Transaction *trans = GNC_TRANSACTION(data);
+    gchar       *uri;
+    GtkTreeIter  iter;
 
-    /* Get list of Accounts */
-    accts = gnc_account_get_descendants_sorted (root);
+    // fix an earlier error when storing relative paths before version 3.5
+    uri = gnc_assoc_convert_trans_associate_uri (trans, assoc_dialog->book_ro);
 
-    /* disconnect the model from the treeview */
-    model = gtk_tree_view_get_model (GTK_TREE_VIEW(assoc_dialog->view));
-    g_object_ref (G_OBJECT(model));
-    gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), NULL);
-
-    /* Go through list of accounts */
-    for (ptr = accts; ptr; ptr = g_list_next (ptr))
+    if (uri && *uri)
     {
-        Query  *query = qof_query_create_for (GNC_ID_SPLIT);
-        Account *acc = ptr->data;
+        Split *split = xaccTransGetSplit (trans, 0);
+        gchar *scheme = gnc_uri_get_scheme (uri);
+        gchar *display_uri;
+        gboolean rel = FALSE;
+        time64 t = xaccTransRetDatePosted (trans);
+        char datebuff[MAX_DATE_LENGTH + 1];
+        memset (datebuff, 0, sizeof(datebuff));
+        if (t == 0)
+            t = gnc_time (NULL);
+        qof_print_date_buff (datebuff, MAX_DATE_LENGTH, t);
+        gtk_list_store_append (GTK_LIST_STORE(assoc_dialog->model), &iter);
+
+        if (!scheme) // path is relative
+            rel = TRUE;
+
+        display_uri = assoc_get_unescape_uri (assoc_dialog->path_head, uri, scheme);
+
+        gtk_list_store_set (GTK_LIST_STORE(assoc_dialog->model), &iter,
+                            DATE_TRANS, datebuff,
+                            DESC_TRANS, xaccTransGetDescription (trans),
+                            URI_U, display_uri, AVAILABLE, _("Unknown"),
+                            URI_SPLIT, split, URI, uri,
+                            URI_RELATIVE, (rel == TRUE ? "emblem-default" : NULL), -1);
+        g_free (display_uri);
+        g_free (scheme);
+        g_free (uri);
+    }
+}
 
-        qof_query_set_book (query, book);
-        xaccQueryAddSingleAccountMatch (query, acc, QOF_QUERY_AND);
+static void
+get_trans_info (AssocDialog *assoc_dialog)
+{
+    QofBook *book = gnc_get_current_book();
 
-        /* Run the query */
-        for (splits = qof_query_run (query); splits; splits = splits->next)
-        {
-            Split       *split = splits->data;
-            Transaction *trans = xaccSplitGetParent (split);
-            const gchar *uri;
+    assoc_dialog->book_ro = qof_book_is_readonly (book);
 
-            // Look for trans already in trans_hash
-            if (g_hash_table_lookup (trans_hash, trans))
-                continue;
+    /* disconnect the model from the treeview */
+    assoc_dialog->model = gtk_tree_view_get_model (GTK_TREE_VIEW(assoc_dialog->view));
+    g_object_ref (G_OBJECT(assoc_dialog->model));
+    gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), NULL);
 
-            // fix an earlier error when storing relative paths in version 3.3
-            uri = gsr_convert_associate_uri (trans);
+    /* Clear the list store */
+    gtk_list_store_clear (GTK_LIST_STORE(assoc_dialog->model));
 
-            if (uri && *uri != '\0')
-            {
-                gchar *uri_u;
-                gboolean rel = FALSE;
-                gchar *scheme = gnc_uri_get_scheme (uri);
-                time64 t = xaccTransRetDatePosted (trans);
-                char datebuff[MAX_DATE_LENGTH + 1];
-                memset (datebuff, 0, sizeof(datebuff));
-                if (t == 0)
-                    t = gnc_time (NULL);
-                qof_print_date_buff (datebuff, MAX_DATE_LENGTH, t);
-                gtk_list_store_append (GTK_LIST_STORE(model), &iter);
-
-                if (!scheme) // path is relative
-                    rel = TRUE;
-
-                uri_u = convert_uri_to_unescaped (assoc_dialog, uri, scheme);
-
-                gtk_list_store_set (GTK_LIST_STORE(model), &iter,
-                                    DATE_TRANS, datebuff,
-                                    DESC_TRANS, xaccTransGetDescription (trans),
-                                    URI_U, uri_u, AVAILABLE, _("Unknown"),
-                                    URI_SPLIT, split, URI, uri,
-                                    URI_RELATIVE, (rel == TRUE ? "emblem-default" : NULL), -1);
-                g_free (uri_u);
-                g_free (scheme);
-            }
-            g_hash_table_insert (trans_hash, trans, trans); // add trans to trans_hash
-        }
-        qof_query_destroy (query);
-        g_list_free (splits);
-    }
+    /* Loop through the transactions */
+    qof_collection_foreach (qof_book_get_collection (book, GNC_ID_TRANS),
+                            add_trans_info_to_model, assoc_dialog);
 
     /* reconnect the model to the treeview */
-    gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), model);
-    g_object_unref(G_OBJECT(model));
-
-    g_hash_table_destroy (trans_hash);
-    g_list_free (accts);
+    gtk_tree_view_set_model (GTK_TREE_VIEW(assoc_dialog->view), assoc_dialog->model);
+    g_object_unref (G_OBJECT(assoc_dialog->model));
 }
 
 static void
@@ -463,7 +800,6 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     GtkWidget         *window;
     GtkBuilder        *builder;
     GtkTreeSelection  *selection;
-    GtkWidget         *path_head;
     GtkTreeViewColumn *tree_column;
     GtkCellRenderer   *cr;
     GtkWidget         *button;
@@ -477,7 +813,6 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     assoc_dialog->window = window;
     assoc_dialog->session = gnc_get_current_session();
 
-
     button = GTK_WIDGET(gtk_builder_get_object (builder, "sort_button"));
         g_signal_connect(button, "clicked", G_CALLBACK(gnc_assoc_dialog_sort_button_cb), assoc_dialog);
     button = GTK_WIDGET(gtk_builder_get_object (builder, "check_button"));
@@ -487,54 +822,21 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
 
     gtk_window_set_title (GTK_WINDOW(assoc_dialog->window), _("Transaction Associations"));
 
-    // Set the name for this dialog so it can be easily manipulated with css
+    // Set the widget name and style context for this dialog so it can be easily manipulated with css
     gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-transaction-associations");
+    gnc_widget_style_context_add_class (GTK_WIDGET(window), "gnc-class-association");
 
     assoc_dialog->view = GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));
-    path_head = GTK_WIDGET(gtk_builder_get_object (builder, "path-head"));
+    assoc_dialog->path_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "path-head"));
 
-    assoc_dialog->path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL, "assoc-head");
+    assoc_dialog->path_head = assoc_get_path_head ();
 
-    if (assoc_dialog->path_head && g_strcmp0 (assoc_dialog->path_head, "") != 0) // not default entry
-    {
-        gchar *path_head_ue_str = g_uri_unescape_string (assoc_dialog->path_head, NULL);
-        gchar *path_head_str = gnc_uri_get_path (path_head_ue_str);
-        gchar *path_head_label;
-#ifdef G_OS_WIN32 // make path look like a traditional windows path
-        path_head_str = g_strdelimit (path_head_str, "/", '\\');
-#endif
-        // test for current folder being present
-        if (g_file_test (path_head_str, G_FILE_TEST_IS_DIR))
-            path_head_label = g_strconcat (_("Path head for files is, "), path_head_str, NULL);
-        else
-            path_head_label = g_strconcat (_("Path head does not exist, "), path_head_str, NULL);
+    // display path head text and test if present
+    assoc_set_path_head_label (assoc_dialog->path_head_label);
 
-        assoc_dialog->path_head_set = TRUE;
-        gtk_label_set_text (GTK_LABEL(path_head), path_head_label);
-        g_free (path_head_label);
-        g_free (path_head_str);
-        g_free (path_head_ue_str);
-    }
-    else
-    {
-        const gchar *doc = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
-        gchar *path_head_label;
-        gchar *path_ret;
-
-        if (doc)
-            path_ret = g_strdup (doc);
-        else
-            path_ret = g_strdup (gnc_userdata_dir ());
-
-        path_head_label = g_strdup_printf (_("Path head not set, using '%s' for relative paths"), path_ret);
-        assoc_dialog->path_head_set = FALSE;
-        gtk_label_set_text (GTK_LABEL(path_head), path_head_label);
-        g_free (path_head_label);
-        g_free (path_ret);
-    }
-
-    // Set the style context for this label so it can be easily manipulated with css
-    gnc_widget_style_context_add_class (GTK_WIDGET(path_head), "gnc-class-highlight");
+    // set the Associate column to be the one that expands
+    tree_column = GTK_TREE_VIEW_COLUMN(gtk_builder_get_object (builder, "uri-entry"));
+    gtk_tree_view_column_set_expand (tree_column, TRUE);
 
     /* Need to add toggle renderers here to get the xalign to work. */
     tree_column = gtk_tree_view_column_new();
diff --git a/gnucash/gnome/dialog-assoc.h b/gnucash/gnome/dialog-assoc.h
index c79cd24e1..d64f1fdb2 100644
--- a/gnucash/gnome/dialog-assoc.h
+++ b/gnucash/gnome/dialog-assoc.h
@@ -23,6 +23,48 @@
 #ifndef DIALOG_ASSOC_H
 #define DIALOG_ASSOC_H
 
+/** Present the right edit dialog for the uri.
+ *
+ *  The function allocates memory for the uri. The calling function should
+ *  free this memory with g_free when uri is no longer needed.
+ *
+ *  @param parent The GtkWindow for the parent widget
+ *  @param title The dialog title to be used for the dialog
+ *  @param uri The old uri to be ammended in the dialog
+ *
+ *  @return The ammeded uri, can be NULL if deletion required.
+ */
+gchar * gnc_assoc_get_uri_dialog (GtkWindow *parent, const gchar *title, const gchar *uri);
+
+/** Open the association uri.
+ *
+ *  A check is made for the uri being valid and then gnc_launch_assoc is used
+ *
+ *  @param parent The GtkWindow for the parent widget
+ *  @param uri The association
+ */
+void gnc_assoc_open_uri (GtkWindow *parent, const gchar *uri);
+
+/** Corrects an earlier relative file association forrmat.
+ *
+ *  Prior to version 3.5, relative paths were stored starting as 'file:'
+ *  or 'file:/' depending on OS. This function changes them so that
+ *  relative paths are stored without a leading "/" and in native form.
+ *
+ *  @param trans The Transaction holding the association
+ *  @param book_ro TRUE if the book is read only
+ */
+gchar * gnc_assoc_convert_trans_associate_uri (gpointer trans, gboolean book_ro);
+
+/** Preset a dialog to list all the Transaction associations.
+ *
+ *  A query is run to return all the transaction associations which
+ *  are then added to a tree view. From this tree view the transaction
+ *  and association can be opened along with a dialog to edit the
+ *  association.
+ *
+ *  @param parent The GtkWindow for the parent widget
+ */
 void gnc_assoc_trans_dialog (GtkWindow *parent);
 
 #endif
diff --git a/gnucash/gnome/gnc-plugin-page-register.c b/gnucash/gnome/gnc-plugin-page-register.c
index dd3736508..9d0c7ec33 100644
--- a/gnucash/gnome/gnc-plugin-page-register.c
+++ b/gnucash/gnome/gnc-plugin-page-register.c
@@ -257,15 +257,14 @@ static void gnc_plugin_page_register_cmd_save_layout (GtkAction *action,
                                                       GncPluginPageRegister *plugin_page);
 static void gnc_plugin_page_register_cmd_reset_layout (GtkAction *action,
                                                        GncPluginPageRegister *plugin_page);
-static void gnc_plugin_page_register_cmd_associate_file_transaction (
-    GtkAction* action, GncPluginPageRegister* plugin_page);
-static void gnc_plugin_page_register_cmd_associate_location_transaction (
-    GtkAction* action, GncPluginPageRegister* plugin_page);
-static void gnc_plugin_page_register_cmd_execassociated_transaction (
-    GtkAction* action, GncPluginPageRegister* plugin_page);
-static void gnc_plugin_page_register_cmd_jump_associated_invoice (
-    GtkAction* action, GncPluginPageRegister* plugin_page);
-
+static void gnc_plugin_page_register_cmd_associate_transaction (GtkAction *action,
+                                                                GncPluginPageRegister *plugin_page);
+static void gnc_plugin_page_register_cmd_associate_transaction_open (GtkAction *action,
+                                                                     GncPluginPageRegister *plugin_page);
+static void gnc_plugin_page_register_cmd_associate_transaction_remove (GtkAction *action,
+                                                                       GncPluginPageRegister *plugin_page);
+static void gnc_plugin_page_register_cmd_jump_associated_invoice (GtkAction* action,
+                                                                  GncPluginPageRegister* plugin_page);
 static void gnc_plugin_page_help_changed_cb (GNCSplitReg* gsr,
                                              GncPluginPageRegister* register_page);
 static void gnc_plugin_page_popup_menu_cb (GNCSplitReg* gsr,
@@ -293,9 +292,9 @@ static GncInvoice* invoice_from_split (Split* split);
 #define PASTE_TRANSACTION_LABEL          N_("_Paste Transaction")
 #define DUPLICATE_TRANSACTION_LABEL      N_("Dup_licate Transaction")
 #define DELETE_TRANSACTION_LABEL         N_("_Delete Transaction")
-#define ASSOCIATE_TRANSACTION_FILE_LABEL      N_("_Associate File with Transaction")
-#define ASSOCIATE_TRANSACTION_LOCATION_LABEL  N_("_Associate Location with Transaction")
-#define EXECASSOCIATED_TRANSACTION_LABEL N_("_Open Associated File/Location")
+#define ASSOCIATE_TRANSACTION_LABEL      N_("Update _Association for Transaction")
+#define ASSOCIATE_TRANSACTION_OPEN_LABEL  N_("_Open Association for Transaction")
+#define ASSOCIATE_TRANSACTION_REMOVE_LABEL N_("Re_move Association from Transaction")
 #define JUMP_ASSOCIATED_INVOICE_LABEL     N_("Open Associated Invoice")
 #define CUT_SPLIT_LABEL                  N_("Cu_t Split")
 #define COPY_SPLIT_LABEL                 N_("_Copy Split")
@@ -307,9 +306,9 @@ static GncInvoice* invoice_from_split (Split* split);
 #define PASTE_TRANSACTION_TIP            N_("Paste the transaction from the clipboard")
 #define DUPLICATE_TRANSACTION_TIP        N_("Make a copy of the current transaction")
 #define DELETE_TRANSACTION_TIP           N_("Delete the current transaction")
-#define ASSOCIATE_TRANSACTION_FILE_TIP   N_("Associate a file with the current transaction")
-#define ASSOCIATE_TRANSACTION_LOCATION_TIP    N_("Associate a location with the current transaction")
-#define EXECASSOCIATED_TRANSACTION_TIP   N_("Open the associated file or location with the current transaction")
+#define ASSOCIATE_TRANSACTION_TIP        N_("Update Association for the current transaction")
+#define ASSOCIATE_TRANSACTION_OPEN_TIP   N_("Open Association for the current transaction")
+#define ASSOCIATE_TRANSACTION_REMOVE_TIP N_("Remove the association from the current transaction")
 #define JUMP_ASSOCIATED_INVOICE_TIP      N_("Open the associated invoice")
 #define CUT_SPLIT_TIP                    N_("Cut the selected split into clipboard")
 #define COPY_SPLIT_TIP                   N_("Copy the selected split into clipboard")
@@ -414,19 +413,19 @@ static GtkActionEntry gnc_plugin_page_register_actions [] =
         G_CALLBACK (gnc_plugin_page_register_cmd_reverse_transaction)
     },
     {
-        "AssociateTransactionFileAction", NULL, ASSOCIATE_TRANSACTION_FILE_LABEL, NULL,
-        ASSOCIATE_TRANSACTION_FILE_TIP,
-        G_CALLBACK (gnc_plugin_page_register_cmd_associate_file_transaction)
+        "AssociateTransactionAction", NULL, ASSOCIATE_TRANSACTION_LABEL, NULL,
+        ASSOCIATE_TRANSACTION_TIP,
+        G_CALLBACK (gnc_plugin_page_register_cmd_associate_transaction)
     },
     {
-        "AssociateTransactionLocationAction", NULL, ASSOCIATE_TRANSACTION_LOCATION_LABEL, NULL,
-        ASSOCIATE_TRANSACTION_LOCATION_TIP,
-        G_CALLBACK (gnc_plugin_page_register_cmd_associate_location_transaction)
+        "AssociateTransactionOpenAction", NULL, ASSOCIATE_TRANSACTION_OPEN_LABEL, NULL,
+        ASSOCIATE_TRANSACTION_OPEN_TIP,
+        G_CALLBACK (gnc_plugin_page_register_cmd_associate_transaction_open)
     },
     {
-        "ExecAssociatedTransactionAction", NULL, EXECASSOCIATED_TRANSACTION_LABEL, NULL,
-        EXECASSOCIATED_TRANSACTION_TIP,
-        G_CALLBACK (gnc_plugin_page_register_cmd_execassociated_transaction)
+        "AssociateTransactionRemoveAction", NULL, ASSOCIATE_TRANSACTION_REMOVE_LABEL, NULL,
+        ASSOCIATE_TRANSACTION_REMOVE_TIP,
+        G_CALLBACK (gnc_plugin_page_register_cmd_associate_transaction_remove)
     },
     {
         "JumpAssociatedInvoiceAction", NULL, JUMP_ASSOCIATED_INVOICE_LABEL, NULL,
@@ -617,9 +616,9 @@ static action_toolbar_labels toolbar_labels[] =
     { "BlankTransactionAction",             N_ ("Blank") },
     { "ActionsReconcileAction",             N_ ("Reconcile") },
     { "ActionsAutoClearAction",             N_ ("Auto-clear") },
-    { "AssociateTransactionFileAction",     N_ ("Associate File") },
-    { "AssociateTransactionLocationAction", N_ ("Associate Location") },
-    { "ExecAssociatedTransactionAction",    N_ ("Open File/Location") },
+    { "AssociateTransactionAction",         N_ ("Update Association") },
+    { "AssociateTransactionOpenAction",     N_ ("Open Association") },
+    { "AssociateTransactionRemoveAction",   N_ ("Remove Association") },
     { "JumpAssociatedInvoiceAction",        N_ ("Open Invoice") },
     { NULL, NULL },
 };
@@ -1006,8 +1005,8 @@ static const char* readonly_inactive_actions[] =
     "ScheduleTransactionAction",
     "ScrubAllAction",
     "ScrubCurrentAction",
-    "AssociateTransactionFileAction",
-    "AssociateTransactionLocationAction",
+    "AssociateTransactionAction",
+    "AssociateTransactionRemoveAction",
     NULL
 };
 
@@ -1031,9 +1030,9 @@ static const char* tran_action_labels[] =
     PASTE_TRANSACTION_LABEL,
     DUPLICATE_TRANSACTION_LABEL,
     DELETE_TRANSACTION_LABEL,
-    ASSOCIATE_TRANSACTION_FILE_LABEL,
-    ASSOCIATE_TRANSACTION_LOCATION_LABEL,
-    EXECASSOCIATED_TRANSACTION_LABEL,
+    ASSOCIATE_TRANSACTION_LABEL,
+    ASSOCIATE_TRANSACTION_OPEN_LABEL,
+    ASSOCIATE_TRANSACTION_REMOVE_LABEL,
     JUMP_ASSOCIATED_INVOICE_LABEL,
     NULL
 };
@@ -1046,9 +1045,9 @@ static const char* tran_action_tips[] =
     PASTE_TRANSACTION_TIP,
     DUPLICATE_TRANSACTION_TIP,
     DELETE_TRANSACTION_TIP,
-    ASSOCIATE_TRANSACTION_FILE_TIP,
-    ASSOCIATE_TRANSACTION_LOCATION_TIP,
-    EXECASSOCIATED_TRANSACTION_TIP,
+    ASSOCIATE_TRANSACTION_TIP,
+    ASSOCIATE_TRANSACTION_OPEN_TIP,
+    ASSOCIATE_TRANSACTION_REMOVE_TIP,
     JUMP_ASSOCIATED_INVOICE_TIP,
     NULL
 };
@@ -1151,11 +1150,16 @@ gnc_plugin_page_register_ui_update (gpointer various,
                                          "UnvoidTransactionAction");
     gtk_action_set_sensitive (GTK_ACTION (action), voided);
 
-    /* Set 'ExecAssociated' */
+    /* Set 'Open and Remove Associated' */
     uri = xaccTransGetAssociation (trans);
+
+    action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE(page),
+                                         "AssociateTransactionOpenAction");
+    gtk_action_set_sensitive (GTK_ACTION(action), (uri ? TRUE:FALSE));
+
     action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE (page),
-                                         "ExecAssociatedTransactionAction");
-    gtk_action_set_sensitive (GTK_ACTION (action), (uri && *uri));
+                                         "AssociateTransactionRemoveAction");
+    gtk_action_set_sensitive (GTK_ACTION(action), (uri ? TRUE:FALSE));
 
     /* Set 'ExecAssociatedInvoice' */
     inv = invoice_from_split (gnc_split_register_get_current_split (reg));
@@ -4526,8 +4530,8 @@ gnc_plugin_page_register_cmd_delete_transaction (GtkAction* action,
 }
 
 static void
-gnc_plugin_page_register_cmd_associate_file_transaction (GtkAction* action,
-                                                         GncPluginPageRegister* plugin_page)
+gnc_plugin_page_register_cmd_associate_transaction (GtkAction *action,
+                                                    GncPluginPageRegister* plugin_page)
 {
     GncPluginPageRegisterPrivate* priv;
 
@@ -4536,14 +4540,14 @@ gnc_plugin_page_register_cmd_associate_file_transaction (GtkAction* action,
     g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER (plugin_page));
 
     priv = GNC_PLUGIN_PAGE_REGISTER_GET_PRIVATE (plugin_page);
-    gsr_default_associate_handler (priv->gsr, TRUE);
+    gsr_default_associate_handler (priv->gsr);
     gnc_plugin_page_register_ui_update (NULL, plugin_page);
     LEAVE (" ");
 }
 
 static void
-gnc_plugin_page_register_cmd_associate_location_transaction (GtkAction* action,
-        GncPluginPageRegister* plugin_page)
+gnc_plugin_page_register_cmd_associate_transaction_open (GtkAction *action,
+                                                         GncPluginPageRegister* plugin_page)
 {
     GncPluginPageRegisterPrivate* priv;
 
@@ -4552,14 +4556,13 @@ gnc_plugin_page_register_cmd_associate_location_transaction (GtkAction* action,
     g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER (plugin_page));
 
     priv = GNC_PLUGIN_PAGE_REGISTER_GET_PRIVATE (plugin_page);
-    gsr_default_associate_handler (priv->gsr, FALSE);
-    gnc_plugin_page_register_ui_update (NULL, plugin_page);
+    gsr_default_associate_open_handler (priv->gsr);
     LEAVE (" ");
 }
 
 static void
-gnc_plugin_page_register_cmd_execassociated_transaction (GtkAction* action,
-                                                         GncPluginPageRegister* plugin_page)
+gnc_plugin_page_register_cmd_associate_transaction_remove (GtkAction *action,
+                                                           GncPluginPageRegister* plugin_page)
 {
     GncPluginPageRegisterPrivate* priv;
 
@@ -4568,7 +4571,8 @@ gnc_plugin_page_register_cmd_execassociated_transaction (GtkAction* action,
     g_return_if_fail (GNC_IS_PLUGIN_PAGE_REGISTER (plugin_page));
 
     priv = GNC_PLUGIN_PAGE_REGISTER_GET_PRIVATE (plugin_page);
-    gsr_default_execassociated_handler (priv->gsr, NULL);
+    gsr_default_associate_remove_handler (priv->gsr);
+    gnc_plugin_page_register_ui_update (NULL, plugin_page);
     LEAVE (" ");
 
 }
diff --git a/gnucash/gnome/gnc-split-reg.c b/gnucash/gnome/gnc-split-reg.c
index bc8b82571..c4ed4842b 100644
--- a/gnucash/gnome/gnc-split-reg.c
+++ b/gnucash/gnome/gnc-split-reg.c
@@ -37,6 +37,7 @@
 #include "qof.h"
 #include "SX-book.h"
 #include "dialog-account.h"
+#include "dialog-assoc.h"
 #include "dialog-sx-editor.h"
 #include "dialog-sx-from-trans.h"
 #include "gnc-component-manager.h"
@@ -114,8 +115,9 @@ void gsr_default_paste_txn_handler( GNCSplitReg *w, gpointer ud );
 void gsr_default_void_txn_handler ( GNCSplitReg *w, gpointer ud );
 void gsr_default_unvoid_txn_handler ( GNCSplitReg *w, gpointer ud );
 void gsr_default_reverse_txn_handler ( GNCSplitReg *w, gpointer ud );
-void gsr_default_associate_handler ( GNCSplitReg *w, gboolean uri_is_file );
-void gsr_default_execassociated_handler ( GNCSplitReg *w, gpointer ud );
+void gsr_default_associate_handler ( GNCSplitReg *w );
+void gsr_default_associate_open_handler ( GNCSplitReg *w );
+void gsr_default_associate_remove_handler ( GNCSplitReg *w );
 
 static void gsr_emit_simple_signal       ( GNCSplitReg *gsr, const char *sigName );
 static void gsr_emit_help_changed        ( GnucashRegister *reg, gpointer user_data );
@@ -1335,240 +1337,16 @@ gnc_split_reg_reinitialize_trans_cb(GtkWidget *widget, gpointer data)
     gsr_emit_simple_signal( gsr, "reinit_ent" );
 }
 
-static void
-gsr_default_associate_handler_file (GNCSplitReg *gsr, Transaction *trans, gboolean have_uri)
-{
-    GtkWidget *dialog;
-    gint       response;
-    gboolean   path_head_set = FALSE;
-    gchar     *path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL, "assoc-head");
-
-    dialog = gtk_file_chooser_dialog_new (_("Associate File with Transaction"),
-                                     GTK_WINDOW(gsr->window),
-                                     GTK_FILE_CHOOSER_ACTION_OPEN,
-                                     _("_Remove"), GTK_RESPONSE_REJECT,
-                                     _("_Cancel"), GTK_RESPONSE_CANCEL,
-                                     _("_OK"), GTK_RESPONSE_ACCEPT,
-                                     NULL);
-
-    gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER(dialog), FALSE);
-
-    path_head_set = (path_head && *path_head != '\0'); // not default entry
-
-    if (have_uri)
-    {
-        gchar *file_uri = NULL;
-        const gchar *uri = xaccTransGetAssociation (trans);
-        gchar *scheme = gnc_uri_get_scheme (uri);
-
-        if (!scheme) // relative path
-        {
-            gchar *file_path = NULL;
-            if (path_head_set) // not default entry
-                file_path = gnc_file_path_absolute (gnc_uri_get_path (path_head), uri);
-            else
-                file_path = gnc_file_path_absolute (NULL, uri);
-
-            file_uri = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, file_path);
-            g_free (file_path);
-        }
-
-        if (g_strcmp0 (scheme, "file") == 0) // absolute path
-            file_uri = g_strdup (uri);
-
-        if (file_uri)
-        {
-            GtkWidget *label;
-            gchar *file_uri_u = g_uri_unescape_string (file_uri, NULL);
-            gchar *filename = gnc_uri_get_path (file_uri_u);
-            gchar *uri_label;
-
-#ifdef G_OS_WIN32 // make path look like a traditional windows path
-            filename = g_strdelimit (filename, "/", '\\');
-#endif
-            uri_label = g_strconcat (_("Existing Association is '"), filename, "'", NULL);
-
-            PINFO("Path head: '%s', URI: '%s', Filename: '%s'", path_head, uri, filename);
-            label = gtk_label_new (uri_label);
-            gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), label);
-            gtk_label_set_ellipsize (GTK_LABEL(label), PANGO_ELLIPSIZE_START);
-
-            // Set the style context for this label so it can be easily manipulated with css
-            gnc_widget_style_context_add_class (GTK_WIDGET(label), "gnc-class-highlight");
-            gtk_file_chooser_set_uri (GTK_FILE_CHOOSER(dialog), file_uri);
-
-            g_free (uri_label);
-            g_free (filename);
-            g_free (file_uri_u);
-            g_free (file_uri);
-        }
-    }
-    response = gtk_dialog_run (GTK_DIALOG (dialog));
-
-    if (response == GTK_RESPONSE_REJECT)
-        xaccTransSetAssociation (trans, "");
-
-    if (response == GTK_RESPONSE_ACCEPT)
-    {
-        gchar *dialog_uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
-
-        // prior to 3.5, assoc-head could be with or without a trailing '/'
-        if (path_head_set && !g_str_has_suffix (path_head, "/"))
-        {
-            gchar *folder_with_slash = g_strconcat (path_head, "/", NULL);
-            g_free (path_head);
-            path_head = g_strdup (folder_with_slash);
-            g_free (folder_with_slash);
-
-            if (!gnc_prefs_set_string (GNC_PREFS_GROUP_GENERAL, "assoc-head", path_head))
-                PINFO("Failed to save preference at %s, %s with %s",
-                       GNC_PREFS_GROUP_GENERAL, "assoc-head", path_head);
-        }
-
-        PINFO("Dialog File URI: '%s', Path head: '%s'", dialog_uri, path_head);
-
-        // relative paths do not start with a '/'
-        if (path_head_set && g_str_has_prefix (dialog_uri, path_head))
-        {
-            const gchar *part = dialog_uri + strlen (path_head);
-
-            PINFO("Dialog URI: '%s', Part: '%s'", dialog_uri, part);
-            xaccTransSetAssociation (trans, part);
-        }
-        else
-        {
-            PINFO("Dialog URI: '%s'", dialog_uri);
-            xaccTransSetAssociation (trans, dialog_uri);
-        }
-        g_free (dialog_uri);
-    }
-
-    gtk_widget_destroy (dialog);
-}
-
-static void
-gsr_default_associate_handler_location_ok_cb (GtkEditable *editable, gpointer user_data)
-{
-    GtkWidget *ok_button = user_data;
-    gboolean have_scheme = FALSE;
-    gchar *text = gtk_editable_get_chars (editable, 0, -1);
-    gchar *scheme;
-
-    if (text && *text != '\0')
-    {
-        scheme = gnc_uri_get_scheme (text);
-        if (scheme)
-            have_scheme = TRUE;
-        g_free (scheme);
-    }
-    gtk_widget_set_sensitive (ok_button, have_scheme);
-    g_free (text);
-}
-
-static void
-gsr_default_associate_handler_location (GNCSplitReg *gsr, Transaction *trans, gboolean have_uri)
-{
-    GtkWidget *dialog, *entry, *label, *content_area, *ok_button;
-    gint response;
-
-    dialog = gtk_dialog_new_with_buttons (_("Associate Location with Transaction"),
-                                     GTK_WINDOW(gsr->window),
-                                     GTK_DIALOG_MODAL,
-                                     _("_Remove"), GTK_RESPONSE_REJECT,
-                                     _("_Cancel"), GTK_RESPONSE_CANCEL,
-                                     // OK Button added below
-                                     NULL);
-
-    ok_button = gtk_dialog_add_button (GTK_DIALOG(dialog), _("_OK"), GTK_RESPONSE_ACCEPT);
-    gtk_widget_set_sensitive (ok_button, FALSE);
-
-    content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
-
-    // add the entry text
-    entry = gtk_entry_new ();
-    gtk_entry_set_width_chars (GTK_ENTRY (entry), 80);
-    gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
-
-    g_signal_connect (entry, "changed",
-        G_CALLBACK(gsr_default_associate_handler_location_ok_cb), ok_button);
-
-    // add a label and set entry text if required
-    if (have_uri)
-    {
-        label = gtk_label_new (_("Amend URL"));
-        gtk_entry_set_text (GTK_ENTRY (entry), xaccTransGetAssociation (trans));
-    }
-    else
-        label = gtk_label_new (_("Enter URL like https://www.gnucash.org"));
-
-    // pack label and entry to content area
-    gnc_label_set_alignment (label, 0.0, 0.5);
-    gtk_container_add (GTK_CONTAINER (content_area), label);
-    gtk_container_add (GTK_CONTAINER (content_area), entry);
-
-    // set spacings
-    gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
-
-    // set the default response
-    gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
-
-    gtk_widget_show_all (dialog);
-
-    // run the dialog
-    response = gtk_dialog_run (GTK_DIALOG (dialog));
-
-    if (response == GTK_RESPONSE_REJECT)
-        xaccTransSetAssociation (trans, "");
-
-    if (response == GTK_RESPONSE_ACCEPT)
-    {
-        const gchar *dialog_uri = gtk_entry_get_text (GTK_ENTRY (entry));
-
-        DEBUG("Location URI: %s\n", dialog_uri);
-        xaccTransSetAssociation (trans, dialog_uri);
-    }
-    gtk_widget_destroy (dialog);
-}
-
-static gchar*
-gsr_convert_associate_uri (Transaction *trans)
-{
-    const gchar *uri = xaccTransGetAssociation (trans); // get the existing uri
-    const gchar *part = NULL;
-
-    if (!uri)
-        return NULL;
-
-    if (g_str_has_prefix (uri, "file:") && !g_str_has_prefix (uri,"file://"))
-    {
-        // fix an error when storing relative paths in version earlier than 3.5
-        // relative paths are stored without a leading "/" and in native form
-        if (g_str_has_prefix (uri,"file:/") && !g_str_has_prefix (uri,"file://"))
-            part = uri + strlen ("file:/");
-        else if (g_str_has_prefix (uri,"file:") && !g_str_has_prefix (uri,"file://"))
-            part = uri + strlen ("file:");
-
-        if (part)
-        {
-            xaccTransSetAssociation (trans, part);
-            return g_strdup (part);
-        }
-    }
-    return g_strdup (uri);
-}
-
-/**
- * Associates a URI with the current transaction.
- **/
+/* Edit the associated link for the current transaction. */
 void
-gsr_default_associate_handler (GNCSplitReg *gsr, gboolean uri_is_file)
+gsr_default_associate_handler (GNCSplitReg *gsr)
 {
     SplitRegister *reg = gnc_ledger_display_get_split_register (gsr->ledger);
     Split *split = gnc_split_register_get_current_split (reg);
     Transaction *trans;
     CursorClass cursor_class;
-    const gchar *uri;
-    gboolean have_uri = FALSE;
+    gchar *uri;
+    gchar *ret_uri;
 
     /* get the current split based on cursor position */
     if (!split)
@@ -1583,45 +1361,30 @@ gsr_default_associate_handler (GNCSplitReg *gsr, gboolean uri_is_file)
     if (cursor_class == CURSOR_CLASS_NONE)
         return;
 
-    // fix an earlier error when storing relative paths in version 3.3
-    uri = gsr_convert_associate_uri (trans);
-
     if (is_trans_readonly_and_warn (GTK_WINDOW(gsr->window), trans))
         return;
 
-    // Check for uri is empty or NULL
-    if (uri && *uri != '\0')
-    {
-        gchar *scheme = gnc_uri_get_scheme (uri);
-        have_uri = TRUE;
+    // fix an earlier error when storing relative paths before version 3.5
+    uri = gnc_assoc_convert_trans_associate_uri (trans, gsr->read_only);
 
-        if (!scheme || g_strcmp0 (scheme, "file") == 0) // use the correct dialog
-            uri_is_file = TRUE;
-        else
-            uri_is_file = FALSE;
+    ret_uri = gnc_assoc_get_uri_dialog (GTK_WINDOW(gsr->window), _("Change a Transaction Association"), uri);
 
-        g_free (scheme);
-    }
+    if (ret_uri && g_strcmp0 (uri, ret_uri) != 0)
+        xaccTransSetAssociation (trans, ret_uri);
 
-    if (uri_is_file == TRUE)
-        gsr_default_associate_handler_file (gsr, trans, have_uri);
-    else
-        gsr_default_associate_handler_location (gsr, trans, have_uri);
+    g_free (ret_uri);
+    g_free (uri);
 }
 
-/**
- * Executes the associated link with the current transaction.
- **/
+/* Opens the associated link for the current transaction. */
 void
-gsr_default_execassociated_handler (GNCSplitReg *gsr, gpointer data)
+gsr_default_associate_open_handler (GNCSplitReg *gsr)
 {
     CursorClass cursor_class;
     SplitRegister *reg = gnc_ledger_display_get_split_register (gsr->ledger);
     Transaction *trans;
     Split *split = gnc_split_register_get_current_split (reg);
-    const char *uri;
-    const char *run_uri = NULL;
-    gchar *uri_scheme;
+    gchar *uri;
 
     /* get the current split based on cursor position */
     if (!split)
@@ -1636,49 +1399,39 @@ gsr_default_execassociated_handler (GNCSplitReg *gsr, gpointer data)
     if (cursor_class == CURSOR_CLASS_NONE)
         return;
 
-#ifdef DUMP_FUNCTIONS
-    if (qof_log_check (log_module, QOF_LOG_DEBUG))
-        xaccTransDump (trans, "ExecAssociated");
-#endif
+    // fix an earlier error when storing relative paths before version 3.5
+    uri = gnc_assoc_convert_trans_associate_uri (trans, gsr->read_only);
 
-    // fix an earlier error when storing relative paths in version 3.3
-    uri = gsr_convert_associate_uri (trans);
-
-    if (!uri && g_strcmp0 (uri, "") == 0)
-        gnc_error_dialog (GTK_WINDOW (gsr->window), "%s", _("This transaction is not associated with a URI."));
-    else
-    {
-        gchar *scheme = gnc_uri_get_scheme (uri);
+    gnc_assoc_open_uri (GTK_WINDOW (gsr->window), uri);
+    g_free (uri);
+}
 
-        if (!scheme) // relative path
-        {
-            gchar *path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL, "assoc-head");
-            gchar *file_path;
+/* Removes the associated link for the current transaction. */
+void
+gsr_default_associate_remove_handler (GNCSplitReg *gsr)
+{
+    CursorClass cursor_class;
+    SplitRegister *reg = gnc_ledger_display_get_split_register (gsr->ledger);
+    Transaction *trans;
+    Split *split = gnc_split_register_get_current_split (reg);
 
-            if (path_head && g_strcmp0 (path_head, "") != 0) // not default entry
-                file_path = gnc_file_path_absolute (gnc_uri_get_path (path_head), uri);
-            else
-                file_path = gnc_file_path_absolute (NULL, uri);
+    /* get the current split based on cursor position */
+    if (!split)
+    {
+        gnc_split_register_cancel_cursor_split_changes (reg);
+        return;
+    }
 
-            run_uri = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, file_path);
-            g_free (path_head);
-            g_free (file_path);
-        }
+    trans = xaccSplitGetParent (split);
+    cursor_class = gnc_split_register_get_current_cursor_class (reg);
 
-        if (!run_uri)
-            run_uri = g_strdup (uri);
+    if (cursor_class == CURSOR_CLASS_NONE)
+        return;
 
-        uri_scheme = gnc_uri_get_scheme (run_uri);
+    if (is_trans_readonly_and_warn (GTK_WINDOW(gsr->window), trans))
+        return;
 
-        if (uri_scheme) // make sure we have a scheme entry
-        {
-            gnc_launch_assoc (GTK_WINDOW (gsr->window), run_uri);
-            g_free (uri_scheme);
-        }
-        else
-            gnc_error_dialog (GTK_WINDOW (gsr->window), "%s", _("This transaction is not associated with a valid URI."));
-    }
-    return;
+    xaccTransSetAssociation (trans, "");
 }
 
 void
diff --git a/gnucash/gnome/gnc-split-reg.h b/gnucash/gnome/gnc-split-reg.h
index dbd82f814..c7e0aa71b 100644
--- a/gnucash/gnome/gnc-split-reg.h
+++ b/gnucash/gnome/gnc-split-reg.h
@@ -275,8 +275,24 @@ const gchar *gnc_split_reg_get_register_state_group (GNCSplitReg *gsr);
 void gnc_split_reg_balancing_entry (GNCSplitReg *gsr, Account *account,
                                     time64 statement_date, gnc_numeric balancing_amount);
 
-void gsr_default_associate_handler (GNCSplitReg *gsr, gboolean uri_is_file);
-void gsr_default_execassociated_handler( GNCSplitReg *gsr, gpointer data );
+/** Default transaction association edit handler
+ *
+ *  @param gsr A pointer to GNCSplitReg
+ **/
+void gsr_default_associate_handler (GNCSplitReg *gsr);
+
+/** Default transaction association open handler
+ *
+ *  @param gsr A pointer to GNCSplitReg
+ **/
+void gsr_default_associate_open_handler (GNCSplitReg *gsr);
+
+/** Default transaction association delete handler
+ *
+ *  @param gsr A pointer to GNCSplitReg
+ **/
+void gsr_default_associate_remove_handler (GNCSplitReg *gsr);
+
 void gnc_split_reg_enter( GNCSplitReg *gsr, gboolean next_transaction );
 void gsr_default_delete_handler( GNCSplitReg *gsr, gpointer data );
 void gsr_default_cut_txn_handler( GNCSplitReg *gsr, gpointer data );
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index 776800869..a73290523 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -2,6 +2,269 @@
 <!-- Generated with glade 3.22.1 -->
 <interface>
   <requires lib="gtk+" version="3.10"/>
+  <object class="GtkDialog" id="association_dialog">
+    <property name="can_focus">False</property>
+    <property name="default_width">900</property>
+    <property name="default_height">500</property>
+    <property name="type_hint">dialog</property>
+    <child>
+      <placeholder/>
+    </child>
+    <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="remove_button">
+                <property name="label" translatable="yes">_Remove</property>
+                <property name="visible">True</property>
+                <property name="can_focus">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">0</property>
+              </packing>
+            </child>
+            <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">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>
+            <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="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">2</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="orientation">vertical</property>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">center</property>
+                <property name="margin_top">6</property>
+                <property name="margin_bottom">6</property>
+                <child>
+                  <object class="GtkRadioButton" id="file_assoc">
+                    <property name="label" translatable="yes">_File Association</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkRadioButton" id="loc_assoc">
+                    <property name="label" translatable="yes">_Location Association</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="use_underline">True</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+                    <property name="group">file_assoc</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="file_hbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="path_head_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">center</property>
+                    <property name="margin_bottom">6</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkFileChooserWidget" id="file_chooser">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="local_only">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="existing_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="location_hbox">
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkLabel" id="location_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="margin_top">3</property>
+                    <property name="margin_bottom">3</property>
+                    <property name="label" translatable="yes">Enter URL like http://www.gnucash.org</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkEntry" id="location_entry">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="can_default">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="warning_hbox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_top">3</property>
+                    <property name="margin_bottom">3</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkImage" id="warning_image">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="icon_name">dialog-warning</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="warning_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">Location does not start with a valid scheme</property>
+                        <style>
+                          <class name="gnc-class-highlight"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</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="-2">remove_button</action-widget>
+      <action-widget response="-6">cancel_button</action-widget>
+      <action-widget response="-5">ok_button</action-widget>
+    </action-widgets>
+  </object>
   <object class="GtkListStore" id="list-store">
     <columns>
       <!-- column-name date -->
@@ -34,6 +297,9 @@
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="label" translatable="yes">All Associations</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -195,6 +461,8 @@
           <object class="GtkLabel" id="help_label">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
+            <property name="margin_top">3</property>
+            <property name="margin_bottom">3</property>
             <property name="label" translatable="yes">     To jump to the Transaction, double click on the entry in the
 Description column or Association column to open the Association</property>
           </object>
diff --git a/gnucash/ui/gnc-plugin-page-register-ui.xml b/gnucash/ui/gnc-plugin-page-register-ui.xml
index dfbbcffbb..d2393375a 100644
--- a/gnucash/ui/gnc-plugin-page-register-ui.xml
+++ b/gnucash/ui/gnc-plugin-page-register-ui.xml
@@ -22,9 +22,9 @@
       <menuitem name="UnvoidTransaction"  	action="UnvoidTransactionAction"/>
       <menuitem name="ReverseTransaction" 	action="ReverseTransactionAction"/>
       <separator name="TransactionSep3"/>
-      <menuitem name="AssociateTransactionFile" action="AssociateTransactionFileAction"/>
-      <menuitem name="AssociateTransactionLocation" action="AssociateTransactionLocationAction"/>
-      <menuitem name="ExecAssociateTransaction" action="ExecAssociatedTransactionAction"/>
+      <menuitem name="AssociateTransaction" action="AssociateTransactionAction"/>
+      <menuitem name="AssociateTransactionOpen" action="AssociateTransactionOpenAction"/>
+      <menuitem name="AssociateTransactionRemove" action="AssociateTransactionRemoveAction"/>
       <separator name="TransactionSep4"/>
       <menuitem name="JumpAssociateInvoice" action="JumpAssociatedInvoiceAction"/>
     </menu>
@@ -106,9 +106,9 @@
       <menuitem name="RecordTransaction"       action="RecordTransactionAction"/>
       <menuitem name="CancelTransaction"       action="CancelTransactionAction"/>
       <separator name="PopupSep3"/>
-      <menuitem name="AssociateTransactionFile" action="AssociateTransactionFileAction"/>
-      <menuitem name="AssociateTransactionLocation" action="AssociateTransactionLocationAction"/>
-      <menuitem name="ExecAssociateTransaction" action="ExecAssociatedTransactionAction"/>
+      <menuitem name="AssociateTransaction" action="AssociateTransactionAction"/>
+      <menuitem name="AssociateTransactionOpen" action="AssociateTransactionOpenAction"/>
+      <menuitem name="AssociateTransactionRemove" action="AssociateTransactionRemoveAction"/>
       <separator name="PopupSep4"/>
       <menuitem name="JumpAssociateInvoice"    action="JumpAssociatedInvoiceAction"/>
       <separator name="PopupSep5"/>

commit 8596763d703d56a59f21db91e1ceb65eefb4d2c6
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Mon May 18 14:19:25 2020 +0100

    Minor text changes to reflect associated file name changes

diff --git a/gnucash/gnome/dialog-assoc.c b/gnucash/gnome/dialog-assoc.c
index 72a539747..a39e73071 100644
--- a/gnucash/gnome/dialog-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -1,6 +1,6 @@
 /********************************************************************\
- * dialog-trans-assoc.c -- Transaction associations dialog          *
- * Copyright (C) 2016 Robert Fewell                                 *
+ * dialog-assoc.c -- Associations dialog                            *
+ * Copyright (C) 2020 Robert Fewell                                 *
  *                                                                  *
  * This program is free software; you can redistribute it and/or    *
  * modify it under the terms of the GNU General Public License as   *
@@ -43,7 +43,7 @@
 #include "gnc-filepath-utils.h"
 #include "Account.h"
 
-#define DIALOG_ASSOC_CM_CLASS    "dialog-trans-assoc"
+#define DIALOG_ASSOC_CM_CLASS    "dialog-assoc"
 #define GNC_PREFS_GROUP          "dialogs.trans-assoc"
 
 /** Enumeration for the tree-store */
@@ -471,9 +471,9 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
     ENTER(" ");
     builder = gtk_builder_new();
     gnc_builder_add_from_file (builder, "dialog-assoc.glade", "list-store");
-    gnc_builder_add_from_file (builder, "dialog-assoc.glade", "transaction_association_window");
+    gnc_builder_add_from_file (builder, "dialog-assoc.glade", "association_window");
 
-    window = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_association_window"));
+    window = GTK_WIDGET(gtk_builder_get_object (builder, "association_window"));
     assoc_dialog->window = window;
     assoc_dialog->session = gnc_get_current_session();
 
@@ -613,14 +613,14 @@ show_handler (const char *klass, gint component_id,
 }
 
 /********************************************************************\
- * gnc_trans_assoc_dialog                                           *
+ * gnc_assoc_trans_dialog                                           *
  * opens a window showing the Associations of all Transactions      *
  *                                                                  *
  * Args:   parent  - the parent of the window to be created         *
  * Return: nothing                                                  *
 \********************************************************************/
 void
-gnc_trans_assoc_dialog (GtkWindow *parent)
+gnc_assoc_trans_dialog (GtkWindow *parent)
 {
     AssocDialog *assoc_dialog;
 
diff --git a/gnucash/gnome/dialog-assoc.h b/gnucash/gnome/dialog-assoc.h
index c95c5b759..c79cd24e1 100644
--- a/gnucash/gnome/dialog-assoc.h
+++ b/gnucash/gnome/dialog-assoc.h
@@ -1,6 +1,6 @@
 /********************************************************************\
- * dialog-trans-assoc.h -- Transaction associations dialog          *
- * Copyright (C) 2016 Robert Fewell                                 *
+ * dialog-assoc.h -- Associations dialog                            *
+ * Copyright (C) 2020 Robert Fewell                                 *
  *                                                                  *
  * This program is free software; you can redistribute it and/or    *
  * modify it under the terms of the GNU General Public License as   *
@@ -20,9 +20,9 @@
  * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
 \********************************************************************/
 
-#ifndef DIALOG_TRANS_ASSOC_H
-#define DIALOG_TRANS_ASSOC_H
+#ifndef DIALOG_ASSOC_H
+#define DIALOG_ASSOC_H
 
-void gnc_trans_assoc_dialog (GtkWindow *parent);
+void gnc_assoc_trans_dialog (GtkWindow *parent);
 
 #endif
diff --git a/gnucash/gnome/gnc-plugin-basic-commands.c b/gnucash/gnome/gnc-plugin-basic-commands.c
index d9b8c8096..4c7691c72 100644
--- a/gnucash/gnome/gnc-plugin-basic-commands.c
+++ b/gnucash/gnome/gnc-plugin-basic-commands.c
@@ -606,7 +606,7 @@ static void
 gnc_main_window_cmd_tools_trans_assoc (GtkAction *action, GncMainWindowActionData *data)
 {
     gnc_set_busy_cursor (NULL, TRUE);
-    gnc_trans_assoc_dialog (GTK_WINDOW (data->window));
+    gnc_assoc_trans_dialog (GTK_WINDOW (data->window));
     gnc_unset_busy_cursor (NULL);
 }
 
diff --git a/gnucash/gtkbuilder/dialog-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
index 484b79f72..776800869 100644
--- a/gnucash/gtkbuilder/dialog-assoc.glade
+++ b/gnucash/gtkbuilder/dialog-assoc.glade
@@ -20,7 +20,7 @@
       <column type="gchararray"/>
     </columns>
   </object>
-  <object class="GtkWindow" id="transaction_association_window">
+  <object class="GtkWindow" id="association_window">
     <property name="can_focus">False</property>
     <child type="titlebar">
       <placeholder/>
@@ -30,10 +30,10 @@
         <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <child>
-          <object class="GtkLabel" id="label3">
+          <object class="GtkLabel" id="title_label">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="label" translatable="yes">All Transaction Associations</property>
+            <property name="label" translatable="yes">All Associations</property>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -179,6 +179,9 @@
                     </child>
                   </object>
                 </child>
+                <style>
+                  <class name="gnc-class-treeview"/>
+                </style>
               </object>
             </child>
           </object>
@@ -189,7 +192,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="label4">
+          <object class="GtkLabel" id="help_label">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="label" translatable="yes">     To jump to the Transaction, double click on the entry in the

commit 456c3494ff6c7bb4008d6b2af0e1feec205aba1a
Author: Robert Fewell <14uBobIT at gmail.com>
Date:   Wed Apr 1 13:56:43 2020 +0100

    Rename the trans associate files
    
    Rename the files dialog-trans-assoc.c/h and the glade file to the less
    specific form dialog-assoc.c/h so that it can be used for other
    associations.

diff --git a/gnucash/gnome/CMakeLists.txt b/gnucash/gnome/CMakeLists.txt
index 01cdc055c..0d29dec9d 100644
--- a/gnucash/gnome/CMakeLists.txt
+++ b/gnucash/gnome/CMakeLists.txt
@@ -7,6 +7,7 @@ set (gnc_gnome_noinst_HEADERS
   business-options-gnome.h
   business-urls.h
   business-gnome-utils.h
+  dialog-assoc.h
   dialog-billterms.h
   dialog-choose-owner.h
   dialog-customer.h
@@ -32,7 +33,6 @@ set (gnc_gnome_noinst_HEADERS
   dialog-sx-editor2.h
   dialog-sx-from-trans.h
   dialog-sx-since-last-run.h
-  dialog-trans-assoc.h
   dialog-vendor.h
   gnc-budget-view.h
   gnc-plugin-account-tree.h
@@ -75,6 +75,7 @@ set (gnc_gnome_SOURCES
   business-options-gnome.c
   business-urls.c
   business-gnome-utils.c
+  dialog-assoc.c
   dialog-billterms.c
   dialog-choose-owner.c
   dialog-commodities.c
@@ -104,7 +105,6 @@ set (gnc_gnome_SOURCES
   dialog-sx-from-trans.c
   dialog-sx-since-last-run.c
   dialog-tax-info.c
-  dialog-trans-assoc.c
   dialog-vendor.c
   gnc-budget-view.c
   gnc-plugin-account-tree.c
diff --git a/gnucash/gnome/dialog-trans-assoc.c b/gnucash/gnome/dialog-assoc.c
similarity index 99%
rename from gnucash/gnome/dialog-trans-assoc.c
rename to gnucash/gnome/dialog-assoc.c
index 2af97f516..72a539747 100644
--- a/gnucash/gnome/dialog-trans-assoc.c
+++ b/gnucash/gnome/dialog-assoc.c
@@ -25,7 +25,7 @@
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 
-#include "dialog-trans-assoc.h"
+#include "dialog-assoc.h"
 
 #include "dialog-utils.h"
 #include "gnc-component-manager.h"
@@ -470,8 +470,8 @@ gnc_assoc_dialog_create (GtkWindow *parent, AssocDialog *assoc_dialog)
 
     ENTER(" ");
     builder = gtk_builder_new();
-    gnc_builder_add_from_file (builder, "dialog-trans-assoc.glade", "list-store");
-    gnc_builder_add_from_file (builder, "dialog-trans-assoc.glade", "transaction_association_window");
+    gnc_builder_add_from_file (builder, "dialog-assoc.glade", "list-store");
+    gnc_builder_add_from_file (builder, "dialog-assoc.glade", "transaction_association_window");
 
     window = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_association_window"));
     assoc_dialog->window = window;
diff --git a/gnucash/gnome/dialog-trans-assoc.h b/gnucash/gnome/dialog-assoc.h
similarity index 100%
rename from gnucash/gnome/dialog-trans-assoc.h
rename to gnucash/gnome/dialog-assoc.h
diff --git a/gnucash/gnome/gnc-plugin-basic-commands.c b/gnucash/gnome/gnc-plugin-basic-commands.c
index 4deddcfe7..d9b8c8096 100644
--- a/gnucash/gnome/gnc-plugin-basic-commands.c
+++ b/gnucash/gnome/gnc-plugin-basic-commands.c
@@ -39,6 +39,7 @@
 #include "gnc-plugin-basic-commands.h"
 #include "gnc-ui-util.h"
 
+#include "dialog-assoc.h"
 #include "dialog-book-close.h"
 #include "dialog-file-access.h"
 #include "dialog-fincalc.h"
@@ -47,7 +48,6 @@
 #include "dialog-imap-editor.h"
 #include "dialog-sx-since-last-run.h"
 #include "dialog-totd.h"
-#include "dialog-trans-assoc.h"
 #include "assistant-acct-period.h"
 #include "assistant-loan.h"
 #include "gnc-engine.h"
diff --git a/gnucash/gtkbuilder/CMakeLists.txt b/gnucash/gtkbuilder/CMakeLists.txt
index 83d17dce1..38d9a39b5 100644
--- a/gnucash/gtkbuilder/CMakeLists.txt
+++ b/gnucash/gtkbuilder/CMakeLists.txt
@@ -13,6 +13,7 @@ set (gtkbuilder_SOURCES
         business-prefs.glade
         dialog-account-picker.glade
         dialog-account.glade
+        dialog-assoc.glade
         dialog-bi-import-gui.glade
         dialog-billterms.glade
         dialog-book-close.glade
@@ -49,7 +50,6 @@ set (gtkbuilder_SOURCES
         dialog-tax-info.glade
         dialog-tax-table.glade
         dialog-totd.glade
-        dialog-trans-assoc.glade
         dialog-transfer.glade
         dialog-userpass.glade
         dialog-vendor.glade
diff --git a/gnucash/gtkbuilder/dialog-trans-assoc.glade b/gnucash/gtkbuilder/dialog-assoc.glade
similarity index 100%
rename from gnucash/gtkbuilder/dialog-trans-assoc.glade
rename to gnucash/gtkbuilder/dialog-assoc.glade
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 492b7dd48..2306930f0 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -53,6 +53,7 @@ gnucash/gnome/assistant-stock-split.c
 gnucash/gnome/business-gnome-utils.c
 gnucash/gnome/business-options-gnome.c
 gnucash/gnome/business-urls.c
+gnucash/gnome/dialog-assoc.c
 gnucash/gnome/dialog-billterms.c
 gnucash/gnome/dialog-choose-owner.c
 gnucash/gnome/dialog-commodities.c
@@ -82,7 +83,6 @@ gnucash/gnome/dialog-sx-editor.c
 gnucash/gnome/dialog-sx-from-trans.c
 gnucash/gnome/dialog-sx-since-last-run.c
 gnucash/gnome/dialog-tax-info.c
-gnucash/gnome/dialog-trans-assoc.c
 gnucash/gnome/dialog-vendor.c
 gnucash/gnome/gnc-budget-view.c
 gnucash/gnome/gnc-plugin-account-tree.c
@@ -241,6 +241,7 @@ gnucash/gtkbuilder/business-options-gnome.glade
 gnucash/gtkbuilder/business-prefs.glade
 gnucash/gtkbuilder/dialog-account.glade
 gnucash/gtkbuilder/dialog-account-picker.glade
+gnucash/gtkbuilder/dialog-assoc.glade
 gnucash/gtkbuilder/dialog-bi-import-gui.glade
 gnucash/gtkbuilder/dialog-billterms.glade
 gnucash/gtkbuilder/dialog-book-close.glade
@@ -277,7 +278,6 @@ gnucash/gtkbuilder/dialog-sx.glade
 gnucash/gtkbuilder/dialog-tax-info.glade
 gnucash/gtkbuilder/dialog-tax-table.glade
 gnucash/gtkbuilder/dialog-totd.glade
-gnucash/gtkbuilder/dialog-trans-assoc.glade
 gnucash/gtkbuilder/dialog-transfer.glade
 gnucash/gtkbuilder/dialog-userpass.glade
 gnucash/gtkbuilder/dialog-vendor.glade



Summary of changes:
 gnucash/gnome-utils/CMakeLists.txt                 |    2 +
 gnucash/gnome-utils/dialog-assoc-utils.c           |  445 +++++++++
 gnucash/gnome-utils/dialog-assoc-utils.h           |  118 +++
 gnucash/gnome-utils/dialog-preferences.c           |   61 +-
 gnucash/gnome-utils/gnc-gnome-utils.c              |   28 +-
 gnucash/gnome/CMakeLists.txt                       |    4 +-
 gnucash/gnome/dialog-assoc.c                       | 1038 ++++++++++++++++++++
 gnucash/gnome/dialog-assoc.h                       |   70 ++
 gnucash/gnome/dialog-invoice.c                     |   87 +-
 gnucash/gnome/dialog-invoice.h                     |    6 +
 gnucash/gnome/dialog-trans-assoc.c                 |  645 ------------
 gnucash/gnome/dialog-trans-assoc.h                 |   28 -
 gnucash/gnome/gnc-plugin-basic-commands.c          |    4 +-
 gnucash/gnome/gnc-plugin-business.c                |   18 +
 gnucash/gnome/gnc-plugin-page-invoice.c            |  188 ++++
 gnucash/gnome/gnc-plugin-page-invoice.h            |   10 +
 gnucash/gnome/gnc-plugin-page-register.c           |  100 +-
 gnucash/gnome/gnc-split-reg.c                      |  355 ++-----
 gnucash/gnome/gnc-split-reg.h                      |   20 +-
 gnucash/gnome/top-level.c                          |   12 +
 .../gschemas/org.gnucash.dialogs.gschema.xml.in    |   11 +
 gnucash/gtkbuilder/CMakeLists.txt                  |    2 +-
 gnucash/gtkbuilder/dialog-assoc.glade              |  744 ++++++++++++++
 gnucash/gtkbuilder/dialog-invoice.glade            |   61 +-
 gnucash/gtkbuilder/dialog-preferences.glade        |    6 +-
 gnucash/gtkbuilder/dialog-trans-assoc.glade        |  208 ----
 .../register/ledger-core/split-register-layout.c   |    2 +-
 gnucash/register/ledger-core/split-register-load.c |   17 +-
 .../register/ledger-core/split-register-model.c    |  101 +-
 gnucash/register/register-core/CMakeLists.txt      |    2 +
 .../register-core/{recncell.c => assoccell.c}      |  141 ++-
 gnucash/register/register-core/assoccell.h         |  100 ++
 gnucash/register/register-core/register-common.c   |    3 +
 gnucash/register/register-core/register-common.h   |    1 +
 gnucash/register/register-gnome/gnucash-register.c |   15 +
 gnucash/register/register-gnome/gnucash-register.h |    2 +
 gnucash/register/register-gnome/gnucash-sheet.c    |    7 +
 gnucash/register/register-gnome/gnucash-sheetP.h   |    3 +
 gnucash/report/html-utilities.scm                  |    8 +
 gnucash/report/report.scm                          |    2 +
 gnucash/report/trep-engine.scm                     |   17 +
 gnucash/ui/gnc-plugin-business-ui.xml              |    2 +
 gnucash/ui/gnc-plugin-page-invoice-ui.xml          |    5 +
 gnucash/ui/gnc-plugin-page-register-ui.xml         |   12 +-
 libgnucash/app-utils/gnc-ui-util.c                 |   32 +-
 libgnucash/app-utils/gnc-ui-util.h                 |   23 +
 libgnucash/engine/gncInvoice.c                     |  396 ++++----
 libgnucash/engine/gncInvoice.h                     |   13 +-
 po/POTFILES.in                                     |    6 +-
 49 files changed, 3614 insertions(+), 1567 deletions(-)
 create mode 100644 gnucash/gnome-utils/dialog-assoc-utils.c
 create mode 100644 gnucash/gnome-utils/dialog-assoc-utils.h
 create mode 100644 gnucash/gnome/dialog-assoc.c
 create mode 100644 gnucash/gnome/dialog-assoc.h
 delete mode 100644 gnucash/gnome/dialog-trans-assoc.c
 delete mode 100644 gnucash/gnome/dialog-trans-assoc.h
 create mode 100644 gnucash/gtkbuilder/dialog-assoc.glade
 delete mode 100644 gnucash/gtkbuilder/dialog-trans-assoc.glade
 copy gnucash/register/register-core/{recncell.c => assoccell.c} (56%)
 create mode 100644 gnucash/register/register-core/assoccell.h



More information about the gnucash-changes mailing list