GnuCash  5.6-150-g038405b370+
reconcile-view.c
1 /********************************************************************\
2  * reconcile-view.c -- A view of accounts to be reconciled for *
3  * GnuCash. *
4  * Copyright (C) 1998,1999 Jeremy Collins *
5  * Copyright (C) 1998-2000 Linas Vepstas *
6  * Copyright (C) 2012 Robert Fewell *
7  * *
8  * This program is free software; you can redistribute it and/or *
9  * modify it under the terms of the GNU General Public License as *
10  * published by the Free Software Foundation; either version 2 of *
11  * the License, or (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License*
19  * along with this program; if not, contact: *
20  * *
21  * Free Software Foundation Voice: +1-617-542-5942 *
22  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23  * Boston, MA 02110-1301, USA gnu@gnu.org *
24 \********************************************************************/
25 
26 #include <config.h>
27 
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30 #include <gdk/gdkkeysyms.h>
31 
32 #include "gnc-date.h"
33 #include "qof.h"
34 #include "qofbook.h"
35 #include "Transaction.h"
36 #include "gnc-ui-util.h"
37 #include "gnc-prefs.h"
38 #include "reconcile-view.h"
39 #include "search-param.h"
40 #include "gnc-component-manager.h"
41 
42 #define GNC_PREF_CHECK_CLEARED "check-cleared"
43 
44 /* Signal codes */
45 enum
46 {
47  TOGGLE_RECONCILED,
48  LINE_SELECTED,
49  DOUBLE_CLICK_SPLIT,
50  LAST_SIGNAL
51 };
52 
53 
55 static guint reconcile_view_signals[LAST_SIGNAL] = {0};
56 
58 static void gnc_reconcile_view_finalize (GObject *object);
59 static gpointer gnc_reconcile_view_is_reconciled (gpointer item,
60  gpointer user_data);
61 static void gnc_reconcile_view_line_toggled (GNCQueryView *qview,
62  gpointer item,
63  gpointer user_data);
64 static void gnc_reconcile_view_double_click_entry (GNCQueryView *qview,
65  gpointer item,
66  gpointer user_data);
67 static void gnc_reconcile_view_row_selected (GNCQueryView *qview,
68  gpointer item,
69  gpointer user_data);
70 static gboolean gnc_reconcile_view_key_press_cb (GtkWidget *widget,
71  GdkEventKey *event,
72  gpointer user_data);
73 static gboolean gnc_reconcile_view_tooltip_cb (GNCQueryView *qview,
74  gint x, gint y,
75  gboolean keyboard_mode,
76  GtkTooltip* tooltip,
77  gpointer* user_data);
78 
79 G_DEFINE_TYPE (GNCReconcileView, gnc_reconcile_view, GNC_TYPE_QUERY_VIEW)
80 
81 static gboolean
82 gnc_reconcile_view_tooltip_cb (GNCQueryView *qview, gint x, gint y,
83  gboolean keyboard_mode, GtkTooltip *tooltip,
84  gpointer *user_data)
85 {
86  GtkTreeModel* model;
87  GtkTreeIter iter;
88 
89  // If the Description is longer than can be display, show it in a tooltip
90  if (gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW(qview), &x, &y,
91  keyboard_mode, &model, NULL, &iter))
92  {
93  GtkTreeViewColumn *col;
94  GList *cols;
95  gint col_pos, col_width;
96  gchar* desc_text = NULL;
97 
98  /* Are we in keyboard tooltip mode, displays tooltip below/above treeview CTRL+F1 */
99  if (keyboard_mode == FALSE)
100  {
101  if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW(qview), x, y,
102  NULL, &col, NULL, NULL) == FALSE)
103  return FALSE;
104  }
105  else
106  gtk_tree_view_get_cursor (GTK_TREE_VIEW(qview), NULL, &col);
107 
108  cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(qview));
109  col_width = gtk_tree_view_column_get_width (col);
110  col_pos = g_list_index (cols, col);
111  g_list_free (cols);
112 
113  /* If column is not description, do not show tooltip */
114  if (col_pos != (REC_DESC - 1)) // allow for the pointer model column at 0
115  return FALSE;
116 
117  gtk_tree_model_get (model, &iter, REC_DESC, &desc_text, -1);
118 
119  if (desc_text)
120  {
121  PangoLayout* layout;
122  gint text_width;
123  gint root_x, root_y;
124  gint cur_x, cur_y;
125 
126  layout = gtk_widget_create_pango_layout (GTK_WIDGET(qview), desc_text);
127  pango_layout_get_pixel_size (layout, &text_width, NULL);
128  g_object_unref (layout);
129 
130  /* If text_width + 10 <= column_width, do not show tooltip */
131  if ((text_width + 10) <= col_width)
132  {
133  g_free (desc_text);
134  return FALSE;
135  }
136 
137  if (keyboard_mode == FALSE)
138  {
139  GdkSeat *seat;
140  GdkDevice *pointer;
141  GtkWindow *tip_win = NULL;
142  GdkWindow *parent_window;
143  GList *win_list, *node;
144 
145  parent_window = gtk_widget_get_parent_window (GTK_WIDGET(qview));
146 
147  seat = gdk_display_get_default_seat (gdk_window_get_display (parent_window));
148  pointer = gdk_seat_get_pointer (seat);
149 
150  gdk_window_get_device_position (parent_window, pointer, &cur_x, &cur_y, NULL);
151 
152  gdk_window_get_origin (parent_window, &root_x, &root_y);
153 
154  /* Get a list of toplevel windows */
155  win_list = gtk_window_list_toplevels ();
156 
157  /* Look for the gtk-tooltip window, we do this as gtk_widget_get_tooltip_window
158  does not seem to work for the default tooltip window, custom yes */
159  for (node = win_list; node != NULL; node = node->next)
160  {
161  if (g_strcmp0 (gtk_widget_get_name (node->data), "gtk-tooltip") == 0)
162  tip_win = node->data;
163  }
164  g_list_free (win_list);
165 
166  gtk_tooltip_set_text (tooltip, desc_text);
167 
168  if (GTK_IS_WINDOW(tip_win))
169  {
170  GdkMonitor *mon;
171  GdkRectangle monitor;
172  GtkRequisition requisition;
173  gint x, y;
174 
175  gtk_widget_get_preferred_size (GTK_WIDGET(tip_win), &requisition, NULL);
176 
177  x = root_x + cur_x + 10;
178  y = root_y + cur_y + 10;
179 
180  mon = gdk_display_get_monitor_at_point (gdk_window_get_display (parent_window), x, y);
181  gdk_monitor_get_geometry (mon, &monitor);
182 
183  if (x + requisition.width > monitor.x + monitor.width)
184  x -= x - (monitor.x + monitor.width) + requisition.width;
185  else if (x < monitor.x)
186  x = monitor.x;
187 
188  if (y + requisition.height > monitor.y + monitor.height)
189  y -= y - (monitor.y + monitor.height) + requisition.height;
190 
191  gtk_window_move (tip_win, x, y);
192  }
193  }
194  gtk_tooltip_set_text (tooltip, desc_text);
195  g_free (desc_text);
196  return TRUE;
197  }
198  }
199  return FALSE;
200 }
201 
202 gint
203 gnc_reconcile_view_get_column_width (GNCReconcileView *view, gint column)
204 {
205  GNCQueryView *qview = GNC_QUERY_VIEW(view);
206  GtkTreeViewColumn *col;
207 
208  // allow for pointer model column at column 0
209  col = gtk_tree_view_get_column (GTK_TREE_VIEW(qview), (column - 1));
210  return gtk_tree_view_column_get_width (col);
211 }
212 
213 void
214 gnc_reconcile_view_add_padding (GNCReconcileView *view, gint column, gint xpadding)
215 {
216  GNCQueryView *qview = GNC_QUERY_VIEW(view);
217  GtkTreeViewColumn *col;
218  GList *renderers;
219  GtkCellRenderer *cr0;
220  gint xpad, ypad;
221 
222  // allow for pointer model column at column 0
223  col = gtk_tree_view_get_column (GTK_TREE_VIEW(qview), (column - 1));
224  renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(col));
225  cr0 = g_list_nth_data (renderers, 0);
226  g_list_free (renderers);
227 
228  gtk_cell_renderer_get_padding (cr0, &xpad, &ypad);
229  gtk_cell_renderer_set_padding (cr0, xpadding, ypad);
230 }
231 
232 /****************************************************************************\
233  * gnc_reconcile_view_new *
234  * creates the account tree *
235  * *
236  * Args: account - the account to use in filling up the splits. *
237  * type - the type of view, RECLIST_DEBIT or RECLIST_CREDIT *
238  * statement_date - date of statement *
239  * Returns: the account tree widget, or NULL if there was a problem. *
240 \****************************************************************************/
241 static void
242 gnc_reconcile_view_construct (GNCReconcileView *view, Query *query)
243 {
244  GNCQueryView *qview = GNC_QUERY_VIEW(view);
245  GtkTreeViewColumn *col;
246  GtkTreeSelection *selection;
247  GList *renderers;
248  GtkCellRenderer *cr0;
249  gboolean inv_sort = FALSE;
250 
251  if (view->view_type == RECLIST_CREDIT)
252  inv_sort = TRUE;
253 
254  /* Construct the view */
255  gnc_query_view_construct (qview, view->column_list, query);
256  gnc_query_view_set_numerics (qview, TRUE, inv_sort);
257 
258  /* Set the description field to have spare space,
259  REC_DESC -1 to allow for the pointer model column at 0 */
260  col = gtk_tree_view_get_column (GTK_TREE_VIEW(qview), (REC_DESC - 1));
261  gtk_tree_view_column_set_expand (col, TRUE);
262 
263  /* Get the renderer of the description column and set ellipsize value */
264  renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(col));
265  cr0 = g_list_nth_data (renderers, 0);
266  g_list_free (renderers);
267  g_object_set (cr0, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
268 
269  gtk_widget_set_has_tooltip (GTK_WIDGET(qview), TRUE);
270 
271  /* Set the selection method */
272  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
273  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
274 
275  /* Now set up the signals for the QueryView */
276  g_signal_connect (G_OBJECT (qview), "column_toggled",
277  G_CALLBACK (gnc_reconcile_view_line_toggled), view);
278  g_signal_connect (G_OBJECT(qview), "double_click_entry",
279  G_CALLBACK(gnc_reconcile_view_double_click_entry), view);
280  g_signal_connect (G_OBJECT(qview), "row_selected",
281  G_CALLBACK(gnc_reconcile_view_row_selected), view);
282  g_signal_connect (G_OBJECT(qview), "key_press_event",
283  G_CALLBACK(gnc_reconcile_view_key_press_cb), view);
284  g_signal_connect (G_OBJECT(qview), "query-tooltip",
285  G_CALLBACK(gnc_reconcile_view_tooltip_cb), view);
286 }
287 
288 static gint
289 sort_date_helper (time64 date_a, time64 date_b)
290 {
291  gint ret = 0;
292 
293  if (date_a < date_b)
294  ret = -1;
295  else if (date_a > date_b)
296  ret = 1;
297 
298  return ret;
299 }
300 
301 static gint
302 sort_iter_compare_func (GtkTreeModel *model,
303  GtkTreeIter *a,
304  GtkTreeIter *b,
305  gpointer user_data)
306 {
307  gboolean rec_a, rec_b;
308  Split *split_a, *split_b;
309  time64 date_a, date_b;
310  gint ret = 0;
311 
312  gtk_tree_model_get (model, a, REC_POINTER, &split_a, REC_RECN, &rec_a, -1);
313  gtk_tree_model_get (model, b, REC_POINTER, &split_b, REC_RECN, &rec_b, -1);
314 
315  date_a = xaccTransGetDate (xaccSplitGetParent (split_a));
316  date_b = xaccTransGetDate (xaccSplitGetParent (split_b));
317 
318  if (rec_a > rec_b)
319  ret = -1;
320  else if (rec_b > rec_a)
321  ret = 1;
322  else ret = sort_date_helper (date_a, date_b);
323 
324  return ret;
325 }
326 
327 GtkWidget *
328 gnc_reconcile_view_new (Account *account, GNCReconcileViewType type,
329  time64 statement_date)
330 {
331  GNCReconcileView *view;
332  GtkListStore *liststore;
333  GtkTreeSortable *sortable;
334  gboolean include_children, auto_check;
335  GList *accounts = NULL;
336  GList *splits;
337  Query *query;
338  QofNumericMatch sign;
339 
340  g_return_val_if_fail (account, NULL);
341  g_return_val_if_fail ((type == RECLIST_DEBIT) ||
342  (type == RECLIST_CREDIT), NULL);
343 
344  view = g_object_new (GNC_TYPE_RECONCILE_VIEW, NULL);
345 
346  /* Create the list store with 6 columns and add to treeview,
347  column 0 will be a pointer to the entry */
348  liststore = gtk_list_store_new (6, G_TYPE_POINTER, G_TYPE_STRING,
349  G_TYPE_STRING, G_TYPE_STRING,
350  G_TYPE_STRING, G_TYPE_BOOLEAN);
351 
352  gtk_tree_view_set_model (GTK_TREE_VIEW(view), GTK_TREE_MODEL(liststore));
353  g_object_unref (liststore);
354 
355  view->account = account;
356  view->view_type = type;
357  view->statement_date = statement_date;
358 
359  query = qof_query_create_for (GNC_ID_SPLIT);
360  qof_query_set_book (query, gnc_get_current_book ());
361 
362  include_children = xaccAccountGetReconcileChildrenStatus (account);
363  if (include_children)
364  accounts = gnc_account_get_descendants (account);
365 
366  /* match the account */
367  accounts = g_list_prepend (accounts, account);
368 
369  xaccQueryAddAccountMatch (query, accounts, QOF_GUID_MATCH_ANY, QOF_QUERY_AND);
370 
371  g_list_free (accounts);
372 
373  sign = (type == RECLIST_CREDIT) ? QOF_NUMERIC_MATCH_CREDIT :
374  QOF_NUMERIC_MATCH_DEBIT;
375 
376  xaccQueryAddNumericMatch (query, gnc_numeric_zero (), sign, QOF_COMPARE_GTE,
377  QOF_QUERY_AND, SPLIT_AMOUNT, NULL);
378 
379  /* limit the matches only to Cleared and Non-reconciled splits */
380  xaccQueryAddClearedMatch (query, CLEARED_NO | CLEARED_CLEARED, QOF_QUERY_AND);
381 
382  /* Initialize the QueryList */
383  gnc_reconcile_view_construct (view, query);
384 
385  /* find the list of splits to auto-reconcile */
386  auto_check = gnc_prefs_get_bool (GNC_PREFS_GROUP_RECONCILE, GNC_PREF_CHECK_CLEARED);
387 
388  if (auto_check)
389  {
390  time64 statement_date_day_end = gnc_time64_get_day_end (statement_date);
391  for (splits = qof_query_run (query); splits; splits = splits->next)
392  {
393  Split *split = splits->data;
394  char recn = xaccSplitGetReconcile (split);
395  time64 trans_date = xaccTransGetDate (xaccSplitGetParent (split));
396 
397  /* Just an extra verification that our query is correct ;) */
398  g_assert (recn == NREC || recn == CREC);
399 
400  if (recn == CREC && trans_date <= statement_date_day_end)
401  g_hash_table_insert (view->reconciled, split, split);
402  }
403  }
404 
405  /* set up a separate sort function for the recn column as it is
406  * derived from a search function */
407  sortable = GTK_TREE_SORTABLE(gtk_tree_view_get_model (GTK_TREE_VIEW(view)));
408  gtk_tree_sortable_set_sort_func (sortable, REC_RECN, sort_iter_compare_func,
409  GINT_TO_POINTER (REC_RECN), NULL);
410 
411  /* Free the query -- we don't need it anymore */
412  qof_query_destroy (query);
413 
414  return GTK_WIDGET(view);
415 }
416 
417 static void
418 gnc_reconcile_view_init (GNCReconcileView *view)
419 {
420  GNCSearchParamSimple *param;
421  GList *columns = NULL;
422  gboolean num_action = qof_book_use_split_action_for_num_field (gnc_get_current_book());
423 
424  view->reconciled = g_hash_table_new (NULL, NULL);
425  view->account = NULL;
426  view->sibling = NULL;
427 
428  param = gnc_search_param_simple_new ();
429  gnc_search_param_set_param_fcn (param, QOF_TYPE_BOOLEAN,
430  gnc_reconcile_view_is_reconciled, view);
431  gnc_search_param_set_title ((GNCSearchParam *) param, C_("Column header for 'Reconciled'", "R"));
432  gnc_search_param_set_justify ((GNCSearchParam *) param, GTK_JUSTIFY_CENTER);
433  gnc_search_param_set_passive ((GNCSearchParam *) param, FALSE);
434  gnc_search_param_set_non_resizeable ((GNCSearchParam *) param, TRUE);
435  columns = g_list_prepend (columns, param);
436 
437  columns = gnc_search_param_prepend_with_justify (columns, _("Amount"),
438  GTK_JUSTIFY_RIGHT,
439  NULL, GNC_ID_SPLIT,
440  SPLIT_AMOUNT, NULL);
441  columns = gnc_search_param_prepend (columns, _("Description"), NULL,
442  GNC_ID_SPLIT, SPLIT_TRANS,
443  TRANS_DESCRIPTION, NULL);
444  columns = num_action ?
445  gnc_search_param_prepend_with_justify (columns, _("Num"),
446  GTK_JUSTIFY_CENTER,
447  NULL, GNC_ID_SPLIT,
448  SPLIT_ACTION, NULL) :
449  gnc_search_param_prepend_with_justify (columns, _("Num"),
450  GTK_JUSTIFY_CENTER,
451  NULL, GNC_ID_SPLIT,
452  SPLIT_TRANS, TRANS_NUM, NULL);
453  columns = gnc_search_param_prepend (columns, _("Date"),
454  NULL, GNC_ID_SPLIT,
455  SPLIT_TRANS,
456  TRANS_DATE_POSTED, NULL);
457 
458  view->column_list = columns;
459 }
460 
461 static void
462 gnc_reconcile_view_class_init (GNCReconcileViewClass *klass)
463 {
464  GObjectClass *object_class;
465 
466  object_class = G_OBJECT_CLASS(klass);
467 
468  reconcile_view_signals[TOGGLE_RECONCILED] =
469  g_signal_new ("toggle_reconciled",
470  G_OBJECT_CLASS_TYPE(object_class),
471  G_SIGNAL_RUN_FIRST,
472  G_STRUCT_OFFSET(GNCReconcileViewClass,
473  toggle_reconciled),
474  NULL, NULL,
475  g_cclosure_marshal_VOID__POINTER,
476  G_TYPE_NONE, 1,
477  G_TYPE_POINTER);
478 
479  reconcile_view_signals[LINE_SELECTED] =
480  g_signal_new ("line_selected",
481  G_OBJECT_CLASS_TYPE(object_class),
482  G_SIGNAL_RUN_FIRST,
483  G_STRUCT_OFFSET(GNCReconcileViewClass,
484  line_selected),
485  NULL, NULL,
486  g_cclosure_marshal_VOID__POINTER,
487  G_TYPE_NONE, 1,
488  G_TYPE_POINTER);
489 
490  reconcile_view_signals[DOUBLE_CLICK_SPLIT] =
491  g_signal_new ("double_click_split",
492  G_OBJECT_CLASS_TYPE(object_class),
493  G_SIGNAL_RUN_FIRST,
494  G_STRUCT_OFFSET(GNCReconcileViewClass,
495  double_click_split),
496  NULL, NULL,
497  g_cclosure_marshal_VOID__POINTER,
498  G_TYPE_NONE, 1,
499  G_TYPE_POINTER);
500 
501  object_class->finalize = gnc_reconcile_view_finalize;
502 
503  klass->toggle_reconciled = NULL;
504  klass->line_selected = NULL;
505  klass->double_click_split = NULL;
506 }
507 
508 static void
509 gnc_reconcile_view_toggle_split (GNCReconcileView *view, Split *split)
510 {
511  Split *current;
512 
513  g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
514  g_return_if_fail (view->reconciled != NULL);
515 
516  current = g_hash_table_lookup (view->reconciled, split);
517 
518  if (current == NULL)
519  g_hash_table_insert (view->reconciled, split, split);
520  else
521  g_hash_table_remove (view->reconciled, split);
522 }
523 
524 static void
525 gnc_reconcile_view_toggle (GNCReconcileView *view, Split *split)
526 {
527  g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
528  g_return_if_fail (view->reconciled != NULL);
529 
530  gnc_reconcile_view_toggle_split (view, split);
531 
532  g_signal_emit (G_OBJECT(view),
533  reconcile_view_signals[TOGGLE_RECONCILED], 0, split);
534 }
535 
536 static gboolean
537 follow_select_tree_path (GNCReconcileView *view)
538 {
539  if (view->rowref)
540  {
541  GtkTreePath *tree_path = gtk_tree_row_reference_get_path (view->rowref);
542  GNCQueryView qview = view->qview;
543  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(&qview));
544 
545  gtk_tree_selection_unselect_all (selection);
546  gtk_tree_selection_select_path (selection, tree_path);
547 
548  gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(&qview),
549  tree_path, NULL, FALSE, 0.0, 0.0);
550 
551  gtk_tree_path_free (tree_path);
552  gtk_tree_row_reference_free (view->rowref);
553  view->rowref = NULL;
554  }
555  return FALSE;
556 }
557 
558 static void
559 gnc_reconcile_view_line_toggled (GNCQueryView *qview,
560  gpointer item,
561  gpointer user_data)
562 {
563  GNCReconcileView *view;
564  GtkTreeModel *model;
565  GtkTreeIter iter;
566  gpointer entry;
567  GtkTreePath *tree_path;
568 
569  g_return_if_fail (user_data);
570  g_return_if_fail (GNC_IS_QUERY_VIEW(qview));
571 
572  view = user_data;
573 
574  model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
575  gtk_tree_model_iter_nth_child (model, &iter, NULL, qview->toggled_row);
576 
577  tree_path = gtk_tree_model_get_path (model, &iter);
578  view->rowref = gtk_tree_row_reference_new (model, tree_path);
579  gtk_tree_path_free (tree_path);
580 
581  gtk_list_store_set (GTK_LIST_STORE(model), &iter, qview->toggled_column,
582  GPOINTER_TO_INT(item), -1);
583 
584  tree_path = gtk_tree_row_reference_get_path (view->rowref);
585 
586  if (gtk_tree_model_get_iter (model, &iter, tree_path))
587  {
588  gtk_tree_model_get (model, &iter, REC_POINTER, &entry, -1);
589  gnc_reconcile_view_toggle (view, entry);
590  }
591 
592  // See if sorting on rec column, -1 to allow for the model pointer column at 0
593  if (qview->sort_column == REC_RECN - 1)
594  g_idle_add ((GSourceFunc)follow_select_tree_path, view);
595  else
596  {
597  gtk_tree_row_reference_free (view->rowref);
598  view->rowref = NULL;
599  }
600 
601  gtk_tree_path_free (tree_path);
602 }
603 
604 static void
605 gnc_reconcile_view_double_click_entry (GNCQueryView *qview,
606  gpointer item,
607  gpointer user_data)
608 {
609  GNCReconcileView *view;
610 
611  /* item is the entry */
612  g_return_if_fail (user_data);
613  g_return_if_fail (GNC_IS_QUERY_VIEW(qview));
614 
615  view = user_data;
616 
617  g_signal_emit(G_OBJECT(view),
618  reconcile_view_signals[DOUBLE_CLICK_SPLIT], 0, item);
619 }
620 
621 static void
622 gnc_reconcile_view_row_selected (GNCQueryView *qview,
623  gpointer item,
624  gpointer user_data)
625 {
626  GNCReconcileView *view;
627 
628  /* item is the number of selected entries */
629  g_return_if_fail (user_data);
630  g_return_if_fail (GNC_IS_QUERY_VIEW(qview));
631 
632  view = user_data;
633 
634  g_signal_emit (G_OBJECT(view),
635  reconcile_view_signals[LINE_SELECTED], 0, item);
636 }
637 
638 void
639 gnc_reconcile_view_set_list (GNCReconcileView *view, gboolean reconcile)
640 {
641  GNCQueryView *qview = GNC_QUERY_VIEW(view);
642  GtkTreeSelection *selection;
643  GtkTreeModel *model;
644  gpointer entry;
645  gboolean toggled;
646  GList *node;
647  GList *list_of_rows;
648  GList *rr_list = NULL;
649  GtkTreePath *last_tree_path = NULL;
650 
651  model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
652  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
653  list_of_rows = gtk_tree_selection_get_selected_rows (selection, &model);
654 
655  /* First create a list of Row references */
656  for (node = list_of_rows; node; node = node->next)
657  {
658  GtkTreeRowReference *rowref = gtk_tree_row_reference_new (model, node->data);
659  rr_list = g_list_prepend (rr_list, rowref);
660  gtk_tree_path_free (node->data);
661  }
662 
663  rr_list = g_list_reverse (rr_list);
664  for (node = rr_list; node; node = node->next)
665  {
666  GtkTreeIter iter;
667  GtkTreePath *path;
668  GtkTreeRowReference *rowref = node->data;
669 
670  path = gtk_tree_row_reference_get_path (rowref);
671 
672  if (gtk_tree_model_get_iter (model, &iter, path))
673  {
674  /* now iter is a valid row iterator */
675  gtk_tree_model_get (model, &iter, REC_POINTER, &entry,
676  REC_RECN, &toggled, -1);
677 
678  gtk_list_store_set (GTK_LIST_STORE(model), &iter, REC_RECN, reconcile, -1);
679 
680  if (last_tree_path)
681  gtk_tree_path_free (last_tree_path);
682  last_tree_path = gtk_tree_row_reference_get_path (rowref);
683 
684  if (reconcile != toggled)
685  gnc_reconcile_view_toggle (view, entry);
686  }
687  gtk_tree_path_free (path);
688  }
689 
690  if (last_tree_path)
691  {
692  // See if sorting on rec column, -1 to allow for the model pointer column at 0
693  if (qview->sort_column == REC_RECN -1)
694  {
695  gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(qview),
696  last_tree_path, NULL, FALSE, 0.0, 0.0);
697  }
698  gtk_tree_path_free (last_tree_path);
699  last_tree_path = NULL;
700  }
701  g_list_foreach (rr_list, (GFunc) gtk_tree_row_reference_free, NULL);
702  g_list_free (rr_list);
703 
704  // Out of site toggles on selected rows may not appear correctly drawn so
705  // queue a draw for the treeview widget
706  gtk_widget_queue_draw (GTK_WIDGET(qview));
707  g_list_free (list_of_rows);
708 }
709 
710 gint
711 gnc_reconcile_view_num_selected (GNCReconcileView *view )
712 {
713  GNCQueryView *qview = GNC_QUERY_VIEW(view);
714  GtkTreeSelection *selection;
715 
716  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
717  return gtk_tree_selection_count_selected_rows (selection);
718 }
719 
720 static gboolean
721 gnc_reconcile_view_set_toggle (GNCReconcileView *view)
722 {
723  GNCQueryView *qview = GNC_QUERY_VIEW(view);
724  GtkTreeSelection *selection;
725  GtkTreeModel *model;
726  gboolean toggled;
727  GList *node;
728  GList *list_of_rows;
729  gint num_toggled = 0;
730  gint num_selected = 0;
731 
732  model = gtk_tree_view_get_model (GTK_TREE_VIEW(qview));
733  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(qview));
734  list_of_rows = gtk_tree_selection_get_selected_rows (selection, &model);
735  num_selected = gtk_tree_selection_count_selected_rows (selection);
736 
737  /* We get a list of TreePaths */
738  for (node = list_of_rows; node; node = node->next)
739  {
740  GtkTreeIter iter;
741  toggled = FALSE;
742  if (gtk_tree_model_get_iter (model, &iter, node->data))
743  {
744  /* now iter is a valid row iterator */
745  gtk_tree_model_get (model, &iter, REC_RECN, &toggled, -1);
746 
747  if (toggled)
748  num_toggled++;
749  }
750  gtk_tree_path_free (node->data);
751  }
752  g_list_free (list_of_rows);
753 
754  if (num_toggled == num_selected)
755  return FALSE;
756  else
757  return TRUE;
758 }
759 
760 static gboolean
761 gnc_reconcile_view_key_press_cb (GtkWidget *widget, GdkEventKey *event,
762  gpointer user_data)
763 {
764  GNCReconcileView *view = GNC_RECONCILE_VIEW(user_data);
765  gboolean toggle;
766 
767  switch (event->keyval)
768  {
769  case GDK_KEY_space:
770  g_signal_stop_emission_by_name (widget, "key_press_event");
771 
772  toggle = gnc_reconcile_view_set_toggle (view);
773  gnc_reconcile_view_set_list (view, toggle);
774  return TRUE;
775  break;
776 
777  default:
778  return FALSE;
779  }
780 }
781 
782 static void
783 gnc_reconcile_view_finalize (GObject *object)
784 {
785  GNCReconcileView *view = GNC_RECONCILE_VIEW(object);
786 
787  g_list_free (view->column_list);
788  if (view->reconciled != NULL)
789  {
790  g_hash_table_destroy (view->reconciled);
791  view->reconciled = NULL;
792  }
793  G_OBJECT_CLASS(gnc_reconcile_view_parent_class)->finalize (object);
794 }
795 
796 gint
797 gnc_reconcile_view_get_num_splits (GNCReconcileView *view)
798 {
799  g_return_val_if_fail (view != NULL, 0);
800  g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), 0);
801 
802  return gnc_query_view_get_num_entries (GNC_QUERY_VIEW(view));
803 }
804 
805 Split *
806 gnc_reconcile_view_get_current_split (GNCReconcileView *view)
807 {
808  g_return_val_if_fail (view != NULL, NULL);
809  g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), NULL);
810 
811  return gnc_query_view_get_selected_entry (GNC_QUERY_VIEW(view));
812 }
813 
814 /********************************************************************\
815  * gnc_reconcile_view_is_reconciled *
816  * Is the item a reconciled split? *
817  * *
818  * Args: item - the split to be checked *
819  * user_data - a pointer to the GNCReconcileView *
820  * Returns: whether the split is to be reconciled. *
821 \********************************************************************/
822 static gpointer
823 gnc_reconcile_view_is_reconciled (gpointer item, gpointer user_data)
824 {
825  GNCReconcileView *view = user_data;
826  Split *current;
827 
828  g_return_val_if_fail (item, NULL);
829  g_return_val_if_fail (view, NULL);
830  g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), NULL);
831 
832  if (!view->reconciled)
833  return NULL;
834 
835  current = g_hash_table_lookup (view->reconciled, item);
836  return GINT_TO_POINTER(current != NULL);
837 }
838 
839 /********************************************************************\
840  * gnc_reconcile_view_refresh *
841  * refreshes the view *
842  * *
843  * Args: view - view to refresh *
844  * Returns: nothing *
845 \********************************************************************/
846 static gboolean
847 grv_refresh_helper (gpointer key, gpointer value, gpointer user_data)
848 {
849  GNCQueryView *qview = user_data;
850 
851  return !gnc_query_view_item_in_view (qview, key);
852 }
853 
854 void
855 gnc_reconcile_view_refresh (GNCReconcileView *view)
856 {
857  GNCQueryView *qview;
858 
859  g_return_if_fail (view != NULL);
860  g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
861 
862  qview = GNC_QUERY_VIEW(view);
863  gnc_query_view_refresh (qview);
864 
865  /* Ensure last selected split, if any, can be seen */
866  gnc_query_force_scroll_to_selection (qview);
867 
868  /* Now verify that everything in the reconcile hash is still in qview */
869  if (view->reconciled)
870  g_hash_table_foreach_remove (view->reconciled, grv_refresh_helper, qview);
871 }
872 
873 /********************************************************************\
874  * gnc_reconcile_view_reconciled_balance *
875  * returns the reconciled balance of the view *
876  * *
877  * Args: view - view to get reconciled balance of *
878  * Returns: reconciled balance (gnc_numeric) *
879 \********************************************************************/
880 static void
881 grv_balance_hash_helper (gpointer key, gpointer value, gpointer user_data)
882 {
883  Split *split = key;
884  gnc_numeric *total = user_data;
885 
886  *total = gnc_numeric_add_fixed (*total, xaccSplitGetAmount (split));
887 }
888 
889 gnc_numeric
890 gnc_reconcile_view_reconciled_balance (GNCReconcileView *view)
891 {
892  gnc_numeric total = gnc_numeric_zero ();
893 
894  g_return_val_if_fail (view != NULL, total);
895  g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), total);
896 
897  if (view->reconciled == NULL)
898  return total;
899 
900  g_hash_table_foreach (view->reconciled, grv_balance_hash_helper, &total);
901 
902  return gnc_numeric_abs (total);
903 }
904 
905 /********************************************************************\
906  * gnc_reconcile_view_commit *
907  * Commit the reconcile information in the view. Only change the *
908  * state of those items marked as reconciled. All others should *
909  * retain their previous state (none, cleared, voided, etc.). *
910  * *
911  * Args: view - view to commit *
912  * date - date to set as the reconcile date *
913  * Returns: nothing *
914 \********************************************************************/
915 static void
916 grv_commit_hash_helper (gpointer key, gpointer value, gpointer user_data)
917 {
918  Split *split = key;
919  time64 *date = user_data;
920 
921  xaccSplitSetReconcile (split, YREC);
922  xaccSplitSetDateReconciledSecs (split, *date);
923 }
924 
925 void
926 gnc_reconcile_view_commit (GNCReconcileView *view, time64 date)
927 {
928  g_return_if_fail (view != NULL);
929  g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
930 
931  if (view->reconciled == NULL)
932  return;
933 
934  gnc_suspend_gui_refresh ();
935  g_hash_table_foreach (view->reconciled, grv_commit_hash_helper, &date);
936  gnc_resume_gui_refresh ();
937 }
938 
939 /********************************************************************\
940  * gnc_reconcile_view_postpone *
941  * postpone the reconcile information in the view by setting *
942  * reconciled splits to cleared status *
943  * *
944  * Args: view - view to commit *
945  * Returns: nothing *
946 \********************************************************************/
947 void
948 gnc_reconcile_view_postpone (GNCReconcileView *view)
949 {
950  GtkTreeModel *model;
951  GtkTreeIter iter;
952  int num_splits;
953  int i;
954  gpointer entry = NULL;
955 
956  g_return_if_fail (view != NULL);
957  g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
958 
959  if (view->reconciled == NULL)
960  return;
961 
962  model = gtk_tree_view_get_model (GTK_TREE_VIEW(GNC_QUERY_VIEW(view)));
963  gtk_tree_model_get_iter_first (model, &iter);
964 
965  num_splits = gnc_query_view_get_num_entries (GNC_QUERY_VIEW(view));
966 
967  gnc_suspend_gui_refresh ();
968  for (i = 0; i < num_splits; i++)
969  {
970  char recn;
971 
972  gtk_tree_model_get (model, &iter, REC_POINTER, &entry, -1);
973 
974  // Don't change splits past reconciliation date that haven't been
975  // set to be reconciled
976  if (view->statement_date >= xaccTransGetDate (xaccSplitGetParent (entry)) ||
977  g_hash_table_lookup (view->reconciled, entry))
978  {
979  recn = g_hash_table_lookup (view->reconciled, entry) ? CREC : NREC;
980  xaccSplitSetReconcile (entry, recn);
981  }
982  gtk_tree_model_iter_next (model, &iter);
983  }
984  gnc_resume_gui_refresh ();
985 }
986 
987 /********************************************************************\
988  * gnc_reconcile_view_unselect_all *
989  * unselect all splits in the view *
990  * *
991  * Args: view - view to unselect all *
992  * Returns: nothing *
993 \********************************************************************/
994 void
995 gnc_reconcile_view_unselect_all (GNCReconcileView *view)
996 {
997  g_return_if_fail (view != NULL);
998  g_return_if_fail (GNC_IS_RECONCILE_VIEW(view));
999 
1000  gnc_query_view_unselect_all (GNC_QUERY_VIEW(view));
1001 }
1002 
1003 /********************************************************************\
1004  * gnc_reconcile_view_changed *
1005  * returns true if any splits have been reconciled *
1006  * *
1007  * Args: view - view to get changed status for *
1008  * Returns: true if any reconciled splits *
1009 \********************************************************************/
1010 gboolean
1011 gnc_reconcile_view_changed (GNCReconcileView *view)
1012 {
1013  g_return_val_if_fail (view != NULL, FALSE);
1014  g_return_val_if_fail (GNC_IS_RECONCILE_VIEW(view), FALSE);
1015 
1016  return g_hash_table_size (view->reconciled) != 0;
1017 }
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
Date and Time handling routines.
utility functions for the GnuCash UI
STRUCTS.
gboolean qof_book_use_split_action_for_num_field(const QofBook *book)
Returns TRUE if this book uses split action field as the &#39;Num&#39; field, FALSE if it uses transaction nu...
char xaccSplitGetReconcile(const Split *split)
Returns the value of the reconcile flag.
void xaccSplitSetReconcile(Split *split, char recn)
Set the reconcile flag.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
These expect a single object and expect the QofAccessFunc returns GncGUID*.
Definition: qofquerycore.h:113
void qof_query_destroy(QofQuery *query)
Frees the resources associate with a Query object.
#define YREC
The Split has been reconciled.
Definition: Split.h:74
void qof_query_set_book(QofQuery *query, QofBook *book)
Set the book to be searched.
gnc_numeric gnc_numeric_abs(gnc_numeric a)
Returns a newly created gnc_numeric that is the absolute value of the given gnc_numeric value...
#define CREC
The Split has been cleared.
Definition: Split.h:73
Encapsulate all the information about a dataset.
Generic api to store and retrieve preferences.
GList * gnc_account_get_descendants(const Account *account)
This routine returns a flat list of all of the accounts that are descendants of the specified account...
Definition: Account.cpp:3014
void xaccSplitSetDateReconciledSecs(Split *split, time64 secs)
Set the date on which this split was reconciled by specifying the time as time64. ...
gboolean xaccAccountGetReconcileChildrenStatus(const Account *acc)
DOCUMENT ME!
Definition: Account.cpp:4853
GList * qof_query_run(QofQuery *query)
Perform the query, return the results.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
time64 gnc_time64_get_day_end(time64 time_val)
The gnc_time64_get_day_end() routine will take the given time in seconds and adjust it to the last se...
Definition: gnc-date.cpp:1316
API for Transactions and Splits (journal entries)
QofNumericMatch
Comparisons for QOF_TYPE_NUMERIC, QOF_TYPE_DEBCRED.
Definition: qofquerycore.h:101
A Query.
Definition: qofquery.cpp:74
#define NREC
not reconciled or cleared
Definition: Split.h:76
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account&#39;s commodity.
Definition: gmock-Split.cpp:69