Hi folks,

I've been a gnucash user for several years and have always wanted the
ability to attach my scanned invoices or receipts to transactions.

Lurking about for some time, I'd been following bug #336843 and several
threads of others wanting similar functionality. Rather than wait
patiently, I found a few free hours last weekend and decided to attempt to
implement this myself.

Attached you'll find a patch which works adequately for my needs, but I
wanted to share with the larger community.

Primary functionality:
 - User selects a file via a dialog, the URI is stored in a new char* in
the Transaction structure.
 - User can similarly launch the associated file via the Transaction menu.
(Launches via gtk viewer, so filetypes such as .JPG, .PDF, etc are already
handled.)

The functionality works under Linux with the XML backend, however there are
a few untested areas and TODOs:
- SQL backend (untested), I added a column which of course would cause
issues for existing tables.
- Code to execute the associate file is modeled after gnc_gnome_help() -
which is platform dependent. I have no objective C experience so the Mac
functionality is completely not implemented. Windows is untested and likely
needs a tweak or two.
- Lastly, and a minor tweak - I intended to find the menu for the
right-click menu on a transaction and add similar menu options to what I
added in the Transaction menu.

Regards,
Patrick
diff --git a/src/backend/sql/gnc-transaction-sql.c b/src/backend/sql/gnc-transaction-sql.c
index eca64cf..51c7d85 100644
--- a/src/backend/sql/gnc-transaction-sql.c
+++ b/src/backend/sql/gnc-transaction-sql.c
@@ -83,6 +83,7 @@ static const GncSqlColumnTableEntry tx_col_table[] =
     { "post_date",     CT_TIMESPEC,       0,                      0,                 "post-date" },
     { "enter_date",    CT_TIMESPEC,       0,                      0,                 "enter-date" },
     { "description",   CT_STRING,         TX_MAX_DESCRIPTION_LEN, 0,                 "description" },
+    { "association",   CT_STRING,         TX_MAX_DESCRIPTION_LEN, 0,                 "association" },
     { NULL }
     /*@ +full_init_block @*/
 };
@@ -1124,6 +1125,10 @@ compile_split_query( GncSqlBackend* be, QofQuery* query )
                     {
                         convert_query_term_to_sql( be, "t.description", term, sql );
                     }
+                    else if ( strcmp( paramPath->next->data, TRANS_ASSOCIATION) == 0 )
+                    {
+                        convert_query_term_to_sql( be, "t.association", term, sql );
+                    }
                     else
                     {
                         unknownPath = TRUE;
diff --git a/src/backend/xml/gnc-transaction-xml-v2.c b/src/backend/xml/gnc-transaction-xml-v2.c
index 9fdbaf5..49d317a 100644
--- a/src/backend/xml/gnc-transaction-xml-v2.c
+++ b/src/backend/xml/gnc-transaction-xml-v2.c
@@ -179,6 +179,13 @@ gnc_transaction_dom_tree_create(Transaction *trn)
                         (xmlChar*)xaccTransGetDescription(trn));
     }
 
+    if (xaccTransGetAssociation(trn))
+    {
+        xmlNewTextChild(ret, NULL, BAD_CAST "trn:association",
+                        (xmlChar*)xaccTransGetAssociation(trn));
+    }
+
+
     {
         xmlNodePtr kvpnode = kvp_frame_to_dom_tree("trn:slots",
                              xaccTransGetSlots(trn));
@@ -510,6 +517,15 @@ trn_description_handler(xmlNodePtr node, gpointer trans_pdata)
 }
 
 static gboolean
+trn_association_handler(xmlNodePtr node, gpointer trans_pdata)
+{
+    struct trans_pdata *pdata = trans_pdata;
+    Transaction *trn = pdata->trans;
+
+    return set_tran_string(node, trn, xaccTransSetAssociation);
+}
+
+static gboolean
 trn_slots_handler(xmlNodePtr node, gpointer trans_pdata)
 {
     struct trans_pdata *pdata = trans_pdata;
@@ -567,6 +583,7 @@ struct dom_tree_handler trn_dom_handlers[] =
     { "trn:date-posted", trn_date_posted_handler, 1, 0 },
     { "trn:date-entered", trn_date_entered_handler, 1, 0 },
     { "trn:description", trn_description_handler, 0, 0 },
+    { "trn:association", trn_association_handler, 0, 0 },
     { "trn:slots", trn_slots_handler, 0, 0 },
     { "trn:splits", trn_splits_handler, 1, 0 },
     { NULL, NULL, 0, 0 },
diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c
index 567a275..b02d34a 100644
--- a/src/engine/Transaction.c
+++ b/src/engine/Transaction.c
@@ -194,6 +194,7 @@ enum
     PROP_0,
     PROP_NUM,
     PROP_DESCRIPTION,
+    PROP_ASSOCIATION,
     PROP_CURRENCY,
     PROP_POST_DATE,
     PROP_ENTER_DATE
@@ -262,6 +263,7 @@ gnc_transaction_init(Transaction* trans)
     /* Fill in some sane defaults */
     trans->num         = CACHE_INSERT("");
     trans->description = CACHE_INSERT("");
+    trans->association = CACHE_INSERT("");
 
     trans->common_currency = NULL;
     trans->splits = NULL;
@@ -314,6 +316,9 @@ gnc_transaction_get_property(GObject* object,
     case PROP_DESCRIPTION:
         g_value_set_string(value, tx->description);
         break;
+    case PROP_ASSOCIATION:
+        g_value_set_string(value, tx->association);
+        break;
     case PROP_CURRENCY:
         g_value_take_object(value, tx->common_currency);
         break;
@@ -472,7 +477,8 @@ xaccTransDump (const Transaction *trans, const char *tag)
     printf("    Posted:      %s\n", gnc_print_date(trans->date_posted));
     printf("    Num:         %s\n", trans->num ? trans->num : "(null)");
     printf("    Description: %s\n",
-           trans->description ? trans->description : "(null)");
+    printf("    Association: %s\n",
+           trans->association ? trans->association : "(null)");
     printf("    Currency:    %s\n",
            gnc_commodity_get_printname(trans->common_currency));
     printf("    version:     %x\n", qof_instance_get_version(trans));
@@ -542,6 +548,7 @@ xaccDupeTransaction (const Transaction *from)
 
     to->num         = CACHE_INSERT (from->num);
     to->description = CACHE_INSERT (from->description);
+    to->association = CACHE_INSERT (from->association);
 
     to->splits = g_list_copy (from->splits);
     for (node = to->splits; node; node = node->next)
@@ -586,6 +593,7 @@ xaccTransClone (const Transaction *from)
     to->date_posted     = from->date_posted;
     to->num             = CACHE_INSERT (from->num);
     to->description     = CACHE_INSERT (from->description);
+    to->association     = CACHE_INSERT (from->association);
     to->common_currency = from->common_currency;
     qof_instance_copy_version(to, from);
     qof_instance_copy_version_check(to, from);
@@ -726,6 +734,7 @@ xaccFreeTransaction (Transaction *trans)
     /* free up transaction strings */
     CACHE_REMOVE(trans->num);
     CACHE_REMOVE(trans->description);
+    CACHE_REMOVE(trans->association);
 
     /* Just in case someone looks up freed memory ... */
     trans->num         = (char *) 1;
@@ -847,6 +856,14 @@ xaccTransEqual(const Transaction *ta, const Transaction *tb,
         return FALSE;
     }
 
+    if ((same_book && ta->association != tb->association)
+            || (!same_book && g_strcmp0(ta->association, tb->association)))
+    {
+        PINFO ("assocations differ: %s vs %s", ta->association, tb->association);
+        return FALSE;
+    }
+
+
     if (kvp_frame_compare(ta->inst.kvp_data, tb->inst.kvp_data) != 0)
     {
         char *frame_a;
@@ -1603,6 +1620,7 @@ xaccTransRollbackEdit (Transaction *trans)
     orig = trans->orig;
     SWAP(trans->num, orig->num);
     SWAP(trans->description, orig->description);
+    SWAP(trans->association, orig->association);
     trans->date_entered = orig->date_entered;
     trans->date_posted = orig->date_posted;
     SWAP(trans->common_currency, orig->common_currency);
@@ -2007,6 +2025,25 @@ xaccTransSetDescription (Transaction *trans, const char *desc)
 }
 
 static void
+qofTransSetAssociation (Transaction *trans, const char *assoc)
+{
+    if (!qof_begin_edit(&trans->inst)) return;
+    xaccTransSetAssociation(trans, assoc);
+    qof_commit_edit(&trans->inst);
+}
+
+void
+xaccTransSetAssociation (Transaction *trans, const char *assoc)
+{
+    if (!trans || !assoc) return;
+    xaccTransBeginEdit(trans);
+
+    CACHE_REPLACE(trans->association, assoc);
+    qof_instance_set_dirty(QOF_INSTANCE(trans));
+    xaccTransCommitEdit(trans);
+}
+
+static void
 qofTransSetNotes (Transaction *trans, const char *notes)
 {
     if (!qof_begin_edit(&trans->inst)) return;
@@ -2090,6 +2127,12 @@ xaccTransGetDescription (const Transaction *trans)
 }
 
 const char *
+xaccTransGetAssociation (const Transaction *trans)
+{
+    return trans ? trans->association : NULL;
+}
+
+const char *
 xaccTransGetNotes (const Transaction *trans)
 {
     return trans ?
@@ -2665,6 +2708,11 @@ gboolean xaccTransRegister (void)
             (QofSetterFunc)qofTransSetDescription
         },
         {
+            TRANS_ASSOCIATION, QOF_TYPE_STRING,
+            (QofAccessFunc)xaccTransGetAssociation,
+            (QofSetterFunc)qofTransSetAssociation
+        },
+        {
             TRANS_DATE_ENTERED, QOF_TYPE_DATE,
             (QofAccessFunc)xaccTransRetDateEnteredTS,
             (QofSetterFunc)qofTransSetDateEntered
diff --git a/src/engine/Transaction.h b/src/engine/Transaction.h
index 51ba5e0..04c0c76 100644
--- a/src/engine/Transaction.h
+++ b/src/engine/Transaction.h
@@ -310,6 +310,9 @@ void          xaccTransSetNum (Transaction *trans, const char *num);
 /** Sets the transaction Description */
 void          xaccTransSetDescription (Transaction *trans, const char *desc);
 
+/** Sets the transaction Association */
+void          xaccTransSetAssociation (Transaction *trans, const char *desc);
+
 /** Sets the transaction Notes
  *
  The Notes field is only visible in the register in double-line mode */
@@ -323,6 +326,8 @@ void          xaccTransSetNotes (Transaction *trans, const char *notes);
 const char *  xaccTransGetNum (const Transaction *trans);
 /** Gets the transaction Description */
 const char *  xaccTransGetDescription (const Transaction *trans);
+/** Gets the transaction association */
+const char *  xaccTransGetAssociation(const Transaction *trans);
 /** Gets the transaction Notes
  *
  The Notes field is only visible in the register in double-line mode */
@@ -717,6 +722,7 @@ Timespec xaccTransGetVoidTime(const Transaction *tr);
 #define TRANS_KVP		"kvp"
 #define TRANS_NUM		"num"
 #define TRANS_DESCRIPTION	"desc"
+#define TRANS_ASSOCIATION	"assoc"
 #define TRANS_DATE_ENTERED	"date-entered"
 #define TRANS_DATE_POSTED	"date-posted"
 #define TRANS_DATE_DUE		"date-due"
diff --git a/src/engine/TransactionP.h b/src/engine/TransactionP.h
index d7935e8..1d19f55 100644
--- a/src/engine/TransactionP.h
+++ b/src/engine/TransactionP.h
@@ -89,6 +89,11 @@ struct transaction_s
      */
     char * description;
 
+    /* The association field is a user-assigned value.
+     * It is meant to store a filename for linkage to each transaction.
+     */
+    char * association;
+
     /* The common_currency field is the balancing common currency for
      * all the splits in the transaction.  Alternate, better(?) name:
      * "valuation currency": it is the currency in which all of the
diff --git a/src/gnome-utils/gnc-gnome-utils.c b/src/gnome-utils/gnc-gnome-utils.c
index d023e48..5172aad 100644
--- a/src/gnome-utils/gnc-gnome-utils.c
+++ b/src/gnome-utils/gnc-gnome-utils.c
@@ -398,6 +398,69 @@ gnc_gnome_help (const char *file_name, const char *anchor)
 
 #endif
 
+#ifdef MAC_INTEGRATION
+
+/* Don't be alarmed if this function looks strange to you: It's
+ * written in Objective-C, the native language of the OSX Cocoa
+ * toolkit.
+ */
+void
+gnc_launch_assoc (const char *uri)
+{
+    const gchar *message =
+	_("This function is not yet implemented on the Mac platform.");
+    gnc_error_dialog(NULL, "%s", message);
+    [pool release];
+    return;
+}
+#elif defined G_OS_WIN32 /* G_OS_WIN32 */
+void
+gnc_launch_assoc (const char *uri)
+{
+    gchar *found = NULL;
+
+    if (g_file_test (uri, G_FILE_TEST_IS_REGULAR))
+    {
+       found = g_strdup (fullpath);
+       break;
+    }
+
+    if (!found)
+    {
+        const gchar *message =
+            _("GnuCash could not find the associated file.");
+        gnc_error_dialog (NULL, message);
+    }
+    else
+    {
+        gnc_show_htmlhelp (found, anchor);
+    }
+    g_free (found);
+}
+#else
+void
+gnc_launch_assoc (const char *uri)
+{
+    GError *error = NULL;
+    gboolean success;
+
+    DEBUG ("Attempting to open uri %s", uri);
+    success = gtk_show_uri (NULL, uri, gtk_get_current_event_time (), &error);
+    if (success)
+        return;
+
+    g_assert(error != NULL);
+    {
+        const gchar *message =
+            _("GnuCash could not open the associated file.");
+        gnc_error_dialog(NULL, "%s", message);
+    }
+    PERR ("%s", error->message);
+    g_error_free(error);
+}
+
+#endif
+
 /********************************************************************\
  * gnc_gnome_get_pixmap                                             *
  *   returns a GtkWidget given a pixmap filename                    *
diff --git a/src/gnome-utils/gnc-gnome-utils.h b/src/gnome-utils/gnc-gnome-utils.h
index ce4b83f..52a279c 100644
--- a/src/gnome-utils/gnc-gnome-utils.h
+++ b/src/gnome-utils/gnc-gnome-utils.h
@@ -56,6 +56,9 @@ void gnc_gtk_add_rc_file (void);
  */
 void gnc_gnome_help (const char *file_name,
                      const char *anchor);
+/** Launch the default gnome browser and open the provided URI.
+ */
+void gnc_launch_assoc (const char *uri);
 
 /** Set the help callback to 'gnc_book_options_help_cb' to open a help browser
  *  and point it to the Book Options link in the Help file.
diff --git a/src/gnome-utils/gnc-tree-view.c b/src/gnome-utils/gnc-tree-view.c
index ce79914..1849d51 100644
--- a/src/gnome-utils/gnc-tree-view.c
+++ b/src/gnome-utils/gnc-tree-view.c
@@ -848,7 +848,7 @@ gnc_tree_view_get_column_order (GncTreeView *view,
         }
         num_cols++;
     }
-    DEBUG ("got %lu columns: %s", num_cols, col_names);
+    //DEBUG ("got %lu columns: %s", num_cols, col_names);
     col_str_list = g_strsplit (col_names, ";", 0);
 
     /* Clean up */
diff --git a/src/gnome/gnc-plugin-page-register.c b/src/gnome/gnc-plugin-page-register.c
index 33e1b26..f267645 100644
--- a/src/gnome/gnc-plugin-page-register.c
+++ b/src/gnome/gnc-plugin-page-register.c
@@ -170,6 +170,9 @@ static void gnc_plugin_page_register_cmd_scrub_current (GtkAction *action, GncPl
 static void gnc_plugin_page_register_cmd_account_report (GtkAction *action, GncPluginPageRegister *plugin_page);
 static void gnc_plugin_page_register_cmd_transaction_report (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_execassociated_transaction (GtkAction *action, GncPluginPageRegister *plugin_page);
+
 static void gnc_plugin_page_help_changed_cb( GNCSplitReg *gsr, GncPluginPageRegister *register_page );
 static void gnc_plugin_page_register_refresh_cb (GHashTable *changes, gpointer user_data);
 static void gnc_plugin_page_register_close_cb (gpointer user_data);
@@ -185,26 +188,30 @@ static void gnc_plugin_page_register_event_handler (QofInstance *entity,
 /*                          Actions                         */
 /************************************************************/
 
-#define CUT_TRANSACTION_LABEL         N_("Cu_t Transaction")
-#define COPY_TRANSACTION_LABEL        N_("_Copy Transaction")
-#define PASTE_TRANSACTION_LABEL       N_("_Paste Transaction")
-#define DUPLICATE_TRANSACTION_LABEL   N_("Dup_licate Transaction")
-#define DELETE_TRANSACTION_LABEL      N_("_Delete Transaction")
-#define CUT_SPLIT_LABEL               N_("Cu_t Split")
-#define COPY_SPLIT_LABEL              N_("_Copy Split")
-#define PASTE_SPLIT_LABEL             N_("_Paste Split")
-#define DUPLICATE_SPLIT_LABEL         N_("Dup_licate Split")
-#define DELETE_SPLIT_LABEL            N_("_Delete Split")
-#define CUT_TRANSACTION_TIP           N_("Cut the selected transaction into clipboard")
-#define COPY_TRANSACTION_TIP          N_("Copy the selected transaction into clipboard")
-#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 CUT_SPLIT_TIP                 N_("Cut the selected split into clipboard")
-#define COPY_SPLIT_TIP                N_("Copy the selected split into clipboard")
-#define PASTE_SPLIT_TIP               N_("Paste the split from the clipboard")
-#define DUPLICATE_SPLIT_TIP           N_("Make a copy of the current split")
-#define DELETE_SPLIT_TIP              N_("Delete the current split")
+#define CUT_TRANSACTION_LABEL            N_("Cu_t Transaction")
+#define COPY_TRANSACTION_LABEL           N_("_Copy Transaction")
+#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_LABEL      N_("_Associate Link with Transaction")
+#define EXECASSOCIATED_TRANSACTION_LABEL N_("_Open Associated Link")
+#define CUT_SPLIT_LABEL                  N_("Cu_t Split")
+#define COPY_SPLIT_LABEL                 N_("_Copy Split")
+#define PASTE_SPLIT_LABEL                N_("_Paste Split")
+#define DUPLICATE_SPLIT_LABEL            N_("Dup_licate Split")
+#define DELETE_SPLIT_LABEL               N_("_Delete Split")
+#define CUT_TRANSACTION_TIP              N_("Cut the selected transaction into clipboard")
+#define COPY_TRANSACTION_TIP             N_("Copy the selected transaction into clipboard")
+#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_TIP        N_("Associate a link with the current transaction")
+#define EXECASSOCIATED_TRANSACTION_TIP   N_("Open the associated link with the current transaction")
+#define CUT_SPLIT_TIP                    N_("Cut the selected split into clipboard")
+#define COPY_SPLIT_TIP                   N_("Copy the selected split into clipboard")
+#define PASTE_SPLIT_TIP                  N_("Paste the split from the clipboard")
+#define DUPLICATE_SPLIT_TIP              N_("Make a copy of the current split")
+#define DELETE_SPLIT_TIP                 N_("Delete the current split")
 
 static GtkActionEntry gnc_plugin_page_register_actions [] =
 {
@@ -297,6 +304,16 @@ static GtkActionEntry gnc_plugin_page_register_actions [] =
         "ReverseTransactionAction", NULL, N_("Add _Reversing Transaction"), NULL, NULL,
         G_CALLBACK (gnc_plugin_page_register_cmd_reverse_transaction)
     },
+    {
+        "AssociateTransactionAction", NULL, ASSOCIATE_TRANSACTION_LABEL, NULL,
+        ASSOCIATE_TRANSACTION_TIP,
+        G_CALLBACK (gnc_plugin_page_register_cmd_associate_transaction)
+    },
+    {
+        "ExecAssociatedTransactionAction", NULL, EXECASSOCIATED_TRANSACTION_LABEL, NULL,
+        EXECASSOCIATED_TRANSACTION_TIP,
+        G_CALLBACK (gnc_plugin_page_register_cmd_execassociated_transaction)
+    },
 
     /* View menu */
 
@@ -461,6 +478,8 @@ static action_toolbar_labels toolbar_labels[] =
     { "BlankTransactionAction",     N_("Blank") },
     { "ActionsReconcileAction",     N_("Reconcile") },
     { "ActionsAutoClearAction",     N_("Auto-clear") },
+    { "AssociateTransactionAction",     N_("Set Link") },
+    { "ExecAssociatedTransactionAction",     N_("Open Link") },
     { NULL, NULL },
 };
 
@@ -804,6 +823,7 @@ static const char* readonly_inactive_actions[] =
     "ScheduleTransactionAction",
     "ScrubAllAction",
     "ScrubCurrentAction",
+    "AssociateTransactionAction",
     NULL
 };
 
@@ -827,6 +847,8 @@ static const char* tran_action_labels[] =
     PASTE_TRANSACTION_LABEL,
     DUPLICATE_TRANSACTION_LABEL,
     DELETE_TRANSACTION_LABEL,
+    ASSOCIATE_TRANSACTION_LABEL,
+    EXECASSOCIATED_TRANSACTION_LABEL,
     NULL
 };
 
@@ -838,6 +860,8 @@ static const char* tran_action_tips[] =
     PASTE_TRANSACTION_TIP,
     DUPLICATE_TRANSACTION_TIP,
     DELETE_TRANSACTION_TIP,
+    ASSOCIATE_TRANSACTION_TIP,
+    EXECASSOCIATED_TRANSACTION_TIP,
     NULL
 };
 
@@ -3393,6 +3417,38 @@ gnc_plugin_page_register_cmd_delete_transaction (GtkAction *action,
 }
 
 static void
+gnc_plugin_page_register_cmd_associate_transaction (GtkAction *action,
+        GncPluginPageRegister *plugin_page)
+{
+    GncPluginPageRegisterPrivate *priv;
+
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+
+    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, NULL);
+    LEAVE(" ");
+
+}
+
+static void
+gnc_plugin_page_register_cmd_execassociated_transaction (GtkAction *action,
+        GncPluginPageRegister *plugin_page)
+{
+    GncPluginPageRegisterPrivate *priv;
+
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+
+    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);
+    LEAVE(" ");
+
+}
+
+static void
 gnc_plugin_page_register_cmd_blank_transaction (GtkAction *action,
         GncPluginPageRegister *plugin_page)
 {
diff --git a/src/gnome/gnc-split-reg.c b/src/gnome/gnc-split-reg.c
index 0c66364..04fe6ce 100644
--- a/src/gnome/gnc-split-reg.c
+++ b/src/gnome/gnc-split-reg.c
@@ -45,6 +45,7 @@
 #include "gnc-euro.h"
 #include "gnc-prefs.h"
 #include "gnc-gui-query.h"
+#include "gnc-gnome-utils.h"
 #include "gnc-ledger-display.h"
 #include "gnc-pricedb.h"
 #include "gnc-ui-util.h"
@@ -106,6 +107,8 @@ 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, gpointer ud );
+void gsr_default_execassociated_handler   ( GNCSplitReg *w, gpointer ud );
 
 static void gsr_emit_simple_signal( GNCSplitReg *gsr, const char *sigName );
 static void gsr_emit_help_changed( GnucashRegister *reg, gpointer user_data );
@@ -1006,6 +1009,97 @@ gnc_split_reg_reinitialize_trans_cb(GtkWidget *widget, gpointer data)
     gsr_emit_simple_signal( gsr, "reinit_ent" );
 }
 
+/**
+ * Associates a link with the current transaction.
+ **/
+void
+gsr_default_associate_handler( GNCSplitReg *gsr, gpointer data )
+{
+    CursorClass cursor_class;
+    SplitRegister *reg;
+    Transaction *trans;
+    Split *split;
+    GtkWidget *dialog;
+
+    reg = gnc_ledger_display_get_split_register( gsr->ledger );
+
+    /* get the current split based on cursor position */
+    split = gnc_split_register_get_current_split(reg);
+    if (split == NULL)
+    {
+        gnc_split_register_cancel_cursor_split_changes (reg);
+        return;
+    }
+
+    trans = xaccSplitGetParent(split);
+    cursor_class = gnc_split_register_get_current_cursor_class (reg);
+
+    if (cursor_class == CURSOR_CLASS_NONE)
+        return;
+
+    if (is_trans_readonly_and_warn(trans))
+        return;
+
+	dialog = gtk_file_chooser_dialog_new ("Open File",
+                                     GTK_WINDOW(gsr->window),
+                                     GTK_FILE_CHOOSER_ACTION_OPEN,
+                                     GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                     GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+                                     NULL);
+
+	if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+ 	{
+	    char *filename;
+            char *uri;
+
+	    filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+            uri = g_strdup_printf( "file://%s", filename);
+	    xaccTransSetAssociation(trans, uri);
+	    g_free(filename);
+         }
+
+	gtk_widget_destroy (dialog);
+
+}
+
+/**
+ * Executes the associated link with the current transaction.
+ **/
+void
+gsr_default_execassociated_handler( GNCSplitReg *gsr, gpointer data )
+{
+    CursorClass cursor_class;
+    SplitRegister *reg;
+    Transaction *trans;
+    Split *split;
+    GtkWidget *dialog;
+    const char *uri;
+
+    reg = gnc_ledger_display_get_split_register( gsr->ledger );
+
+    /* get the current split based on cursor position */
+    split = gnc_split_register_get_current_split(reg);
+    if (split == NULL)
+    {
+        gnc_split_register_cancel_cursor_split_changes (reg);
+        return;
+    }
+
+    trans = xaccSplitGetParent(split);
+    cursor_class = gnc_split_register_get_current_cursor_class (reg);
+
+    if (cursor_class == CURSOR_CLASS_NONE)
+        return;
+
+#ifdef DUMP_FUNCTIONS
+   xaccTransDump (trans, "ExecAssociated")
+#endif
+
+    uri = xaccTransGetAssociation(trans);
+    gnc_launch_assoc(uri);
+    return;
+}
+
 void
 gsr_default_delete_handler( GNCSplitReg *gsr, gpointer data )
 {
diff --git a/src/gnome/gnc-split-reg.h b/src/gnome/gnc-split-reg.h
index e9e8bce..ab4646e 100644
--- a/src/gnome/gnc-split-reg.h
+++ b/src/gnome/gnc-split-reg.h
@@ -242,6 +242,8 @@ void gnc_split_reg_balancing_entry (GNCSplitReg *gsr, Account *account,
                                     time64 statement_date, gnc_numeric balancing_amount);
 
 void gsr_default_delete_handler( GNCSplitReg *gsr, gpointer data );
+void gsr_default_associate_handler( GNCSplitReg *gsr, gpointer data );
+void gsr_default_execassociated_handler( GNCSplitReg *gsr, gpointer data );
 void gnc_split_reg_enter( GNCSplitReg *gsr, gboolean next_transaction );
 void gsr_default_delete_handler( GNCSplitReg *gsr, gpointer data );
 void gsr_default_reinit_handler( GNCSplitReg *gsr, gpointer data );
diff --git a/src/gnome/ui/gnc-plugin-page-register-ui.xml b/src/gnome/ui/gnc-plugin-page-register-ui.xml
index ddcc51c..82e8e61 100644
--- a/src/gnome/ui/gnc-plugin-page-register-ui.xml
+++ b/src/gnome/ui/gnc-plugin-page-register-ui.xml
@@ -20,6 +20,9 @@
       <menuitem name="VoidTransaction"    	action="VoidTransactionAction"/>
       <menuitem name="UnvoidTransaction"  	action="UnvoidTransactionAction"/>
       <menuitem name="ReverseTransaction" 	action="ReverseTransactionAction"/>
+      <separator name="TransactionSep3"/>
+      <menuitem name="AssociateTransaction"    	action="AssociateTransactionAction"/>
+      <menuitem name="ExecAssociateTransaction" action="ExecAssociatedTransactionAction"/>
     </menu>
 
     <menu name="View" action="ViewAction">
_______________________________________________
gnucash-devel mailing list
[email protected]
https://lists.gnucash.org/mailman/listinfo/gnucash-devel

Reply via email to