r23061 - gnucash/trunk/src - Bug #700804: Add up/down buttons for ordering of transactions in register2.

Christian Stimming cstim at code.gnucash.org
Sun Jun 23 03:57:43 EDT 2013


Author: cstim
Date: 2013-06-23 03:57:40 -0400 (Sun, 23 Jun 2013)
New Revision: 23061
Trac: http://svn.gnucash.org/trac/changeset/23061

Modified:
   gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.c
   gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.h
   gnucash/trunk/src/gnome/gnc-plugin-page-register2.c
   gnucash/trunk/src/gnome/ui/gnc-plugin-page-register2-ui.xml
Log:
Bug #700804: Add up/down buttons for ordering of transactions in register2.

This changes the ordering but only for txn with the same date
and same number.  The buttons are active only in cases where this is
possible, otherwise the buttons are inactive anyway.

- for reconciled splits, user will be asked
- book-closing txn are ignored as well as frozen splits
- After changing the sort order, update the UI buttons immediately as well

Modified: gnucash/trunk/src/gnome/gnc-plugin-page-register2.c
===================================================================
--- gnucash/trunk/src/gnome/gnc-plugin-page-register2.c	2013-06-20 20:44:43 UTC (rev 23060)
+++ gnucash/trunk/src/gnome/gnc-plugin-page-register2.c	2013-06-23 07:57:40 UTC (rev 23061)
@@ -169,6 +169,8 @@
 static void gnc_plugin_page_register2_cmd_scrub_current (GtkAction *action, GncPluginPageRegister2 *plugin_page);
 static void gnc_plugin_page_register2_cmd_account_report (GtkAction *action, GncPluginPageRegister2 *plugin_page);
 static void gnc_plugin_page_register2_cmd_transaction_report (GtkAction *action, GncPluginPageRegister2 *plugin_page);
+static void gnc_plugin_page_register2_cmd_entryUp (GtkAction *action, GncPluginPageRegister2 *plugin_page);
+static void gnc_plugin_page_register2_cmd_entryDown (GtkAction *action, GncPluginPageRegister2 *plugin_page);
 
 static void gnc_plugin_page_help_changed_cb (GNCSplitReg2 *gsr, GncPluginPageRegister2 *register_page );
 static void gnc_plugin_page_register2_refresh_cb (GHashTable *changes, gpointer user_data);
@@ -206,6 +208,9 @@
 #define DUPLICATE_SPLIT_TIP           N_("Make a copy of the current split")
 #define DELETE_SPLIT_TIP              N_("Delete the current split")
 
+#define TRANSACTION_UP_ACTION "TransactionUpAction"
+#define TRANSACTION_DOWN_ACTION "TransactionDownAction"
+
 static GtkActionEntry gnc_plugin_page_register2_actions [] =
 {
     /* File menu */
@@ -301,6 +306,16 @@
         "ShiftTransactionForwardAction", NULL, N_("_Shift Transaction Forward"), NULL, NULL,
         G_CALLBACK (gnc_plugin_page_register2_cmd_shift_transaction_forward)
     },
+    {
+        TRANSACTION_UP_ACTION, GTK_STOCK_GO_UP, N_("Move Transaction _Up"), NULL,
+        N_("Move the current transaction one row upwards. Only available if the date and number of both rows are identical and the register window is sorted by date."),
+        G_CALLBACK (gnc_plugin_page_register2_cmd_entryUp)
+    },
+    {
+        TRANSACTION_DOWN_ACTION, GTK_STOCK_GO_DOWN, N_("Move Transaction Do_wn"), NULL,
+        N_("Move the current transaction one row downwards. Only available if the date and number of both rows are identical and the register window is sorted by date."),
+        G_CALLBACK (gnc_plugin_page_register2_cmd_entryDown)
+    },
 
     /* View menu */
 
@@ -466,6 +481,8 @@
     { "BlankTransactionAction",     N_("Blank") },
     { "ActionsReconcileAction",     N_("Reconcile") },
     { "ActionsAutoClearAction",     N_("Auto-clear") },
+    { TRANSACTION_UP_ACTION, N_("Up") },
+    { TRANSACTION_DOWN_ACTION, N_("Down") },
     { NULL, NULL },
 };
 
@@ -798,6 +815,8 @@
     "EditPasteAction",
     "CutTransactionAction",
     "PasteTransactionAction",
+    TRANSACTION_UP_ACTION,
+    TRANSACTION_DOWN_ACTION,
     "DuplicateTransactionAction",
     "DeleteTransactionAction",
     "RemoveTransactionSplitsAction",
@@ -884,8 +903,11 @@
 
     /* Set 'Split Transaction' */
     priv = GNC_PLUGIN_PAGE_REGISTER2_GET_PRIVATE (page);
+    g_return_if_fail(priv);
     model = gnc_ledger_display2_get_split_model_register (priv->ledger);
     view = gnc_ledger_display2_get_split_view_register (priv->ledger);
+    g_return_if_fail(model);
+    g_return_if_fail(view);
 
     expanded = gnc_tree_view_split_reg_trans_expanded (view, NULL);
     action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE (page),
@@ -909,6 +931,16 @@
                                          "UnvoidTransactionAction");
     gtk_action_set_sensitive (GTK_ACTION (action), voided);
 
+    /* Modify the activeness of the up/down arrows */
+    {
+        GtkAction *action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE (page), TRANSACTION_UP_ACTION);
+        gtk_action_set_sensitive(action,
+                                 gnc_tree_control_split_reg_is_current_movable_updown(view, TRUE));
+        action = gnc_plugin_page_get_action (GNC_PLUGIN_PAGE (page), TRANSACTION_DOWN_ACTION);
+        gtk_action_set_sensitive(action,
+                                 gnc_tree_control_split_reg_is_current_movable_updown(view, FALSE));
+    }
+
     /* If we are in a readonly book, make any modifying action inactive */
     if (qof_book_is_readonly(gnc_get_current_book ()))
     {
@@ -2825,6 +2857,39 @@
     LEAVE(" ");
 }
 
+static void
+gnc_plugin_page_register2_cmd_entryUp (GtkAction *action,
+                                       GncPluginPageRegister2 *plugin_page)
+{
+    GncPluginPageRegister2Private *priv;
+    GncTreeViewSplitReg *view;
+    g_return_if_fail(GNC_IS_PLUGIN_PAGE_REGISTER2(plugin_page));
+
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+    priv = GNC_PLUGIN_PAGE_REGISTER2_GET_PRIVATE(plugin_page);
+    view = gnc_ledger_display2_get_split_view_register (priv->ledger);
+    g_return_if_fail(view);
+    gnc_tree_control_split_reg_move_current_entry_updown(view, TRUE);
+    LEAVE(" ");
+}
+
+static void
+gnc_plugin_page_register2_cmd_entryDown (GtkAction *action,
+                                         GncPluginPageRegister2 *plugin_page)
+{
+    GncPluginPageRegister2Private *priv;
+    GncTreeViewSplitReg *view;
+    g_return_if_fail(GNC_IS_PLUGIN_PAGE_REGISTER2(plugin_page));
+
+    ENTER("(action %p, plugin_page %p)", action, plugin_page);
+    priv = GNC_PLUGIN_PAGE_REGISTER2_GET_PRIVATE(plugin_page);
+    view = gnc_ledger_display2_get_split_view_register (priv->ledger);
+    g_return_if_fail(view);
+    gnc_tree_control_split_reg_move_current_entry_updown(view, FALSE);
+    LEAVE(" ");
+}
+
+
 /*#################################################################################*/
 /*#################################################################################*/
 

Modified: gnucash/trunk/src/gnome/ui/gnc-plugin-page-register2-ui.xml
===================================================================
--- gnucash/trunk/src/gnome/ui/gnc-plugin-page-register2-ui.xml	2013-06-20 20:44:43 UTC (rev 23060)
+++ gnucash/trunk/src/gnome/ui/gnc-plugin-page-register2-ui.xml	2013-06-23 07:57:40 UTC (rev 23061)
@@ -10,6 +10,8 @@
       <menuitem name="CutTransaction"     	action="CutTransactionAction"/>
       <menuitem name="CopyTransaction"    	action="CopyTransactionAction"/>
       <menuitem name="PasteTransaction"   	action="PasteTransactionAction"/>
+      <menuitem name="TransactionUp"            action="TransactionUpAction"/>
+      <menuitem name="TransactionDown"          action="TransactionDownAction"/>
       <menuitem name="DuplicateTransaction"    	action="DuplicateTransactionAction"/>
       <menuitem name="DeleteTransaction"       	action="DeleteTransactionAction"/>
       <menuitem name="RemoveTransactionSplits" 	action="RemoveTransactionSplitsAction"/>
@@ -64,6 +66,8 @@
 
   <toolbar name="DefaultToolbar">
     <placeholder name="DefaultToolbarPlaceholder">
+      <toolitem name="ToolbarTransactionUp"        action="TransactionUpAction"/>
+      <toolitem name="ToolbarTransactionDown"      action="TransactionDownAction"/>
       <toolitem name="ToolbarDuplicateTransaction" action="DuplicateTransactionAction"/>
       <toolitem name="ToolbarDeleteTransaction"    action="DeleteTransactionAction"/>
       <separator name="ToolbarSep66"/>
@@ -85,6 +89,8 @@
       <menuitem name="ViewFilterBy"            action="ViewFilterByAction"/>
     </placeholder>
     <placeholder name="PopupPlaceholder2">
+      <menuitem name="TransactionUp"           action="TransactionUpAction"/>
+      <menuitem name="TransactionDown"         action="TransactionDownAction"/>
       <menuitem name="DuplicateTransaction"    action="DuplicateTransactionAction"/>
       <menuitem name="DeleteTransaction"       action="DeleteTransactionAction"/>
       <menuitem name="RemoveTransactionSplits" action="RemoveTransactionSplitsAction"/>

Modified: gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.c
===================================================================
--- gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.c	2013-06-20 20:44:43 UTC (rev 23060)
+++ gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.c	2013-06-23 07:57:40 UTC (rev 23061)
@@ -1393,6 +1393,230 @@
 }
 
 
+static gboolean gtcsr_move_current_entry_updown(GncTreeViewSplitReg *view,
+                                                gboolean move_up, gboolean really_do_it)
+{
+    GncTreeModelSplitReg *model;
+    GtkTreePath *mpath = NULL, *spath = NULL, *spath_target = NULL, *mpath_target = NULL;
+    GtkTreeIter m_iter, m_iter_target;
+    gboolean resultvalue = FALSE;
+    g_return_val_if_fail(view, FALSE);
+
+    ENTER("");
+
+    if (view->sort_col != COL_DATE)
+    {
+        LEAVE("Not sorted by date - no up/down move available");
+        return FALSE;
+    }
+
+    // The allocated memory references will all be cleaned up in the
+    // updown_finish: label.
+
+    model = gnc_tree_view_split_reg_get_model_from_view (view);
+    g_return_val_if_fail(model, FALSE);
+
+    mpath = gnc_tree_view_split_reg_get_current_path (view);
+    if (!mpath)
+    {
+        LEAVE("No current path available - probably on the blank split.");
+        goto updown_finish;
+    }
+
+    spath = gnc_tree_view_split_reg_get_sort_path_from_model_path (view, mpath);
+    g_return_val_if_fail(spath, FALSE);
+
+    spath_target = gtk_tree_path_copy(spath);
+    if (move_up)
+    {
+        gboolean move_was_made = gtk_tree_path_prev(spath_target);
+        if (!move_was_made)
+        {
+            LEAVE("huh, no path_prev() possible");
+            goto updown_finish;
+        }
+    }
+    else
+    {
+        gtk_tree_path_next(spath_target);
+        // The path_next() function does not give a return value, see
+        // https://mail.gnome.org/archives/gtk-list/2010-January/msg00171.html
+    }
+
+    if (gtk_tree_path_compare(spath, spath_target) == 0)
+    {
+        LEAVE("oops, paths are equal");
+        goto updown_finish;
+    }
+
+    mpath_target = gnc_tree_view_split_reg_get_model_path_from_sort_path (view, spath_target);
+    if (!mpath_target)
+    {
+        LEAVE("no path to target row");
+        goto updown_finish;
+    }
+
+    if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &m_iter, mpath))
+    {
+        LEAVE("No iter for current row");
+        goto updown_finish;
+    }
+    if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &m_iter_target, mpath_target))
+    {
+        LEAVE("No iter for target row");
+        goto updown_finish;
+    }
+
+    {
+        gboolean is_blank, is_blank_target;
+        Split *current_split, *target_split;
+        Transaction *current_trans, *target_trans;
+        gnc_tree_model_split_reg_get_split_and_trans (GNC_TREE_MODEL_SPLIT_REG (model), &m_iter,
+                                                      NULL, NULL, NULL, &is_blank,
+                                                      &current_split, &current_trans);
+        gnc_tree_model_split_reg_get_split_and_trans (GNC_TREE_MODEL_SPLIT_REG (model), &m_iter_target,
+                                                      NULL, NULL, NULL, &is_blank_target,
+                                                      &target_split, &target_trans);
+        if (is_blank || is_blank_target)
+        {
+            LEAVE("blank split involved, ignored.");
+            goto updown_finish;
+        }
+        if (xaccTransEqual(current_trans, target_trans, TRUE, FALSE, FALSE, FALSE))
+        {
+            LEAVE("two times the same txn, ignored.");
+            goto updown_finish;
+        }
+        if (xaccTransGetIsClosingTxn(current_trans)
+                || xaccTransGetIsClosingTxn(target_trans))
+        {
+            LEAVE("One of the txn is book-closing - no re-ordering allowed.");
+            goto updown_finish;
+        }
+
+        /* Only continue if both have the same date and num, because the
+         * "standard ordering" is tied to the date anyway. */
+        {
+            Timespec t1, t2;
+            GDate d1 = xaccTransGetDatePostedGDate(current_trans),
+                  d2 = xaccTransGetDatePostedGDate(target_trans);
+            if (g_date_compare(&d1, &d2) != 0)
+            {
+                LEAVE("unequal DatePosted, ignoring");
+                goto updown_finish;
+            }
+            if (g_strcmp0(xaccTransGetNum(current_trans),
+                          xaccTransGetNum(target_trans)) != 0)
+            {
+                LEAVE("unequal Num, ignoring");
+                goto updown_finish;
+            }
+
+            /* Special treatment if the equality doesn't hold if we access the
+            dates as timespec. See the comment in gncEntrySetDateGDate() for the
+            reason: Some code used the timespec at noon for the EntryDate, other
+            code used the timespec at the start of day. */
+            t1 = xaccTransRetDatePostedTS(current_trans);
+            t2 = xaccTransRetDatePostedTS(target_trans);
+            if (really_do_it && !timespec_equal(&t1, &t2))
+            {
+                /* Timespecs are not equal, even though the GDates were equal? Then
+                we set the GDates again. This will force the timespecs to be equal
+                as well. */
+                xaccTransSetDatePostedGDate(current_trans, d1);
+                xaccTransSetDatePostedGDate(target_trans, d2);
+            }
+        }
+
+        // Check whether any of the two splits are frozen
+        if (xaccSplitGetReconcile(current_split) == FREC
+                || xaccSplitGetReconcile(target_split) == FREC)
+        {
+            LEAVE("either current or target split is frozen. No modification allowed.");
+            goto updown_finish;
+        }
+
+        // If really_do_it is FALSE, we are only in query mode and shouldn't
+        // modify anything. But if it is TRUE, please go ahead and do the move.
+        if (really_do_it)
+        {
+            // Check whether any of the two splits are reconciled
+            if (xaccSplitGetReconcile(current_split) == YREC
+                    && !gnc_tree_control_split_reg_recn_test(view, spath))
+            {
+                LEAVE("current split is reconciled and user chose not to modify it");
+                goto updown_finish;
+            }
+            if (xaccSplitGetReconcile(target_split) == YREC
+                    && !gnc_tree_control_split_reg_recn_test(view, spath_target))
+            {
+                LEAVE("target split is reconciled and user chose not to modify it");
+                goto updown_finish;
+            }
+
+            PINFO("Ok, about to switch ordering for current desc='%s' target desc='%s'",
+                  xaccTransGetDescription(current_trans),
+                  xaccTransGetDescription(target_trans));
+
+            gnc_suspend_gui_refresh ();
+
+            /* Swap the date-entered of both entries. That's already
+             * sufficient! */
+            {
+                Timespec time_current = xaccTransRetDateEnteredTS(current_trans);
+                Timespec time_target = xaccTransRetDateEnteredTS(target_trans);
+
+                /* Special treatment for identical times (potentially caused
+                 * by the "duplicate entry" command) */
+                if (timespec_equal(&time_current, &time_target))
+                {
+                    g_warning("Surprise - both DateEntered are equal.");
+                    /* We just increment the DateEntered of the previously
+                     * lower of the two by one second. This might still cause
+                     * issues if multiple entries had this problem, but
+                     * whatever. */
+                    if (move_up)
+                        time_current.tv_sec++;
+                    else
+                        time_target.tv_sec++;
+                }
+
+                /* Write the new DateEntered. */
+                xaccTransSetDateEnteredTS(current_trans, &time_target);
+                xaccTransSetDateEnteredTS(target_trans, &time_current);
+
+                /* FIXME: Do we need to notify anyone about the changed ordering? */
+            }
+
+            gnc_resume_gui_refresh ();
+
+            LEAVE("two txn switched, done.");
+        }
+        resultvalue = TRUE;
+        goto updown_finish;
+    }
+updown_finish:
+    // memory cleanup
+    //gtk_tree_path_free (mpath); // Should this be freed??
+    gtk_tree_path_free(spath);
+    gtk_tree_path_free(spath_target);
+    gtk_tree_path_free(mpath_target);
+    return resultvalue;
+}
+
+gboolean gnc_tree_control_split_reg_move_current_entry_updown (GncTreeViewSplitReg *view,
+                                                            gboolean move_up)
+{
+    return gtcsr_move_current_entry_updown(view, move_up, TRUE);
+}
+
+gboolean gnc_tree_control_split_reg_is_current_movable_updown (GncTreeViewSplitReg *view,
+                                                               gboolean move_up)
+{
+    return gtcsr_move_current_entry_updown(view, move_up, FALSE);
+}
+
+
 /* Save any open edited transactions on closing register */
 gboolean
 gnc_tree_control_split_reg_save (GncTreeViewSplitReg *view, gboolean reg_closing)
@@ -1965,6 +2189,9 @@
     /* scroll when view idle */
     g_idle_add ((GSourceFunc) gnc_tree_view_split_reg_scroll_to_cell, view);
 
+    /* Update the plugin page gui when idle */
+    g_idle_add ((GSourceFunc) gnc_tree_view_split_reg_call_uiupdate_cb, view);
+
     LEAVE("sort_col %d, sort_direction is %d  sort_depth is %d", view->sort_col, view->sort_direction, view->sort_depth );
 }
 

Modified: gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.h
===================================================================
--- gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.h	2013-06-20 20:44:43 UTC (rev 23060)
+++ gnucash/trunk/src/gnome-utils/gnc-tree-control-split-reg.h	2013-06-23 07:57:40 UTC (rev 23061)
@@ -72,6 +72,29 @@
 
 gboolean gnc_tree_control_split_reg_duplicate_current (GncTreeViewSplitReg *view);
 
+/** This implements the command of moving the current entry (where the
+ * cursor is currently located) one row upwards or downwards (depending on the move_up parameter),
+ * effectively swapping this row and the other row. If the other row
+ * is empty (or it is the blank entry), nothing will happen.
+ *
+ * \param move_up If TRUE, the current entry is moved upwards,
+ * otherwise downwards.
+ * \return Whether the current entry has been moved into the queried direction
+ */
+gboolean gnc_tree_control_split_reg_move_current_entry_updown (GncTreeViewSplitReg *reg,
+                                                            gboolean move_up);
+
+/** Query whether the current entry (where the cursor is currently located)
+ * can be moved one row upwards or downwards (depending on the move_up parameter).
+ *
+ * \param move_up If TRUE, it is asked whether the current entry can be moved upwards,
+ * otherwise downwards.
+ * \return Whether the current entry can be moved into the queried direction
+ */
+gboolean gnc_tree_control_split_reg_is_current_movable_updown (GncTreeViewSplitReg *view,
+                                                               gboolean move_up);
+
+
 gboolean gnc_tree_control_split_reg_save (GncTreeViewSplitReg *view, gboolean reg_closing);
 
 gboolean gnc_tree_control_split_reg_recn_change (GncTreeViewSplitReg *view, GtkTreePath *spath);



More information about the gnucash-changes mailing list