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

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@23061 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Christian Stimming 2013-06-23 07:57:40 +00:00
parent db8a08a3d0
commit 8237afd97f
4 changed files with 321 additions and 0 deletions

View File

@ -1393,6 +1393,230 @@ gnc_tree_control_split_reg_duplicate_current (GncTreeViewSplitReg *view)
}
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 @@ gnc_tree_control_split_reg_sort_changed_cb (GtkTreeSortable *sortable, gpointer
/* 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 );
}

View File

@ -72,6 +72,29 @@ Split * gnc_tree_control_split_reg_get_blank_split (GncTreeViewSplitReg *view);
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);

View File

@ -169,6 +169,8 @@ static void gnc_plugin_page_register2_cmd_scrub_all (GtkAction *action, GncPlugi
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 @@ static void gnc_plugin_page_register2_event_handler (QofInstance *entity,
#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 @@ static GtkActionEntry gnc_plugin_page_register2_actions [] =
"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 @@ static action_toolbar_labels toolbar_labels[] =
{ "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 @@ static const char* readonly_inactive_actions[] =
"EditPasteAction",
"CutTransactionAction",
"PasteTransactionAction",
TRANSACTION_UP_ACTION,
TRANSACTION_DOWN_ACTION,
"DuplicateTransactionAction",
"DeleteTransactionAction",
"RemoveTransactionSplitsAction",
@ -884,8 +903,11 @@ gnc_plugin_page_register2_ui_update (gpointer various, GncPluginPageRegister2 *p
/* 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 @@ gnc_plugin_page_register2_ui_update (gpointer various, GncPluginPageRegister2 *p
"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 @@ gnc_plugin_page_register2_cmd_shift_transaction_forward (GtkAction *action,
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(" ");
}
/*#################################################################################*/
/*#################################################################################*/

View File

@ -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"/>