GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-tree-view.c
Go to the documentation of this file.
1 /*
2  * gnc-tree-view.c -- new GtkTreeView with extra features used by
3  * all the tree views in gnucash
4  *
5  * Copyright (C) 2003,2005 David Hampton <[email protected]>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, contact:
19  *
20  * Free Software Foundation Voice: +1-617-542-5942
21  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
22  * Boston, MA 02110-1301, USA [email protected]
23  */
24 
34 #include "config.h"
35 
36 #include <gtk/gtk.h>
37 #include <glib/gi18n.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <string.h>
40 
41 #include "gnc-tree-view.h"
42 #include "gnc-engine.h"
43 #include "gnc-gnome-utils.h"
44 #include "gnc-gobject-utils.h"
45 #include "gnc-cell-renderer-date.h"
46 #include "gnc-state.h"
47 
48 /* The actual state key for a particular column visibility. This is
49  * attached to the menu items that are in the column selection menu.
50  * Makes it very easy to update saved state when a menu item is toggled. */
51 #define STATE_KEY "state-key"
52 
53 /* State keys within this particular saved state section. */
54 #define STATE_KEY_SORT_COLUMN "sort_column"
55 #define STATE_KEY_SORT_ORDER "sort_order"
56 #define STATE_KEY_COLUMN_ORDER "column_order"
57 
58 /* Partial state keys within this particular saved state section. These
59  are appended to the various column names to create the actual
60  keys. */
61 #define STATE_KEY_SUFF_VISIBLE "visible"
62 #define STATE_KEY_SUFF_WIDTH "width"
63 
64 enum
65 {
66  PROP_0,
67  PROP_STATE_SECTION,
68  PROP_SHOW_COLUMN_MENU,
69 };
70 
73 /* This static indicates the debugging module that this .o belongs to. */
74 static QofLogModule log_module = GNC_MOD_GUI;
75 
76 /**** Declarations ******************************************************/
77 static void gnc_tree_view_class_init (GncTreeViewClass *klass);
78 static void gnc_tree_view_init (GncTreeView *view, GncTreeViewClass *klass);
79 static void gnc_tree_view_finalize (GObject *object);
80 static void gnc_tree_view_destroy (GtkObject *object);
81 static void gnc_tree_view_set_property (GObject *object,
82  guint prop_id,
83  const GValue *value,
84  GParamSpec *pspec);
85 static void gnc_tree_view_get_property (GObject *object,
86  guint prop_id,
87  GValue *value,
88  GParamSpec *pspec);
89 static gboolean gnc_tree_view_drop_ok_cb (GtkTreeView *view,
90  GtkTreeViewColumn *column,
91  GtkTreeViewColumn *prev_column,
92  GtkTreeViewColumn *next_column,
93  gpointer data);
94 static void gnc_tree_view_build_column_menu (GncTreeView *view);
95 static void gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
96  GncTreeView *view);
97 static gchar *gnc_tree_view_get_sort_order (GncTreeView *view);
98 static gchar *gnc_tree_view_get_sort_column (GncTreeView *view);
99 static gchar **gnc_tree_view_get_column_order (GncTreeView *view,
100  gsize *length);
101 
104 typedef struct GncTreeViewPrivate
105 {
106  /* Spacer column */
107  GtkTreeViewColumn *spacer_column;
108  GtkTreeViewColumn *selection_column;
109 
110  /* Column selection menu related values */
111  GtkTreeViewColumn *column_menu_column;
112  GtkWidget *column_menu;
113  gboolean show_column_menu;
114 
115  /* Sort callback model */
116  GtkTreeModel *sort_model;
117 
118  /* State related values */
119  gchar *state_section;
120  gboolean seen_state_visibility;
121  gulong columns_changed_cb_id;
122  gulong sort_column_changed_cb_id;
123  gulong size_allocate_cb_id;
125 
126 #define GNC_TREE_VIEW_GET_PRIVATE(o) \
127  (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_TREE_VIEW, GncTreeViewPrivate))
128 
129 
130 /************************************************************/
131 /* g_object required functions */
132 /************************************************************/
133 
137 static GObjectClass *parent_class = NULL;
138 
145 GType
147 {
148  static GType gnc_tree_view_type = 0;
149 
150  if (gnc_tree_view_type == 0)
151  {
152  static const GTypeInfo our_info =
153  {
154  sizeof (GncTreeViewClass),
155  NULL,
156  NULL,
157  (GClassInitFunc) gnc_tree_view_class_init,
158  NULL,
159  NULL,
160  sizeof (GncTreeView),
161  0,
162  (GInstanceInitFunc) gnc_tree_view_init
163  };
164 
165  gnc_tree_view_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
166  GNC_TREE_VIEW_NAME,
167  &our_info, 0);
168  }
169 
170  return gnc_tree_view_type;
171 }
172 
182 static void
183 gnc_tree_view_class_init (GncTreeViewClass *klass)
184 {
185  GObjectClass *gobject_class;
186  GtkObjectClass *gtkobject_class;
187 
188  parent_class = g_type_class_peek_parent (klass);
189 
190  gobject_class = G_OBJECT_CLASS (klass);
191  gtkobject_class = GTK_OBJECT_CLASS (klass);
192 
193  gobject_class->set_property = gnc_tree_view_set_property;
194  gobject_class->get_property = gnc_tree_view_get_property;
195 
196  g_type_class_add_private(klass, sizeof(GncTreeViewPrivate));
197 
198  g_object_class_install_property (gobject_class,
199  PROP_STATE_SECTION,
200  g_param_spec_string ("state-section",
201  "State Section",
202  "The section name in the saved state to use for (re)storing the treeview's visual state (visible columns, sort order,...",
203  NULL,
204  G_PARAM_READWRITE));
205  g_object_class_install_property (gobject_class,
206  PROP_SHOW_COLUMN_MENU,
207  g_param_spec_boolean ("show-column-menu",
208  "Show Column Menu",
209  "Show the column menu so user can change what columns are visible.",
210  FALSE,
211  G_PARAM_READWRITE));
212 
213  /* GObject signals */
214  gobject_class->finalize = gnc_tree_view_finalize;
215 
216  /* GtkObject signals */
217  gtkobject_class->destroy = gnc_tree_view_destroy;
218 }
219 
229 static void
230 gnc_tree_view_init (GncTreeView *view, GncTreeViewClass *klass)
231 {
232  GncTreeViewPrivate *priv;
233  GtkTreeViewColumn *column;
234  GtkWidget *icon;
235  GtkRequisition requisition;
236 
237  gnc_gobject_tracking_remember(G_OBJECT(view), G_OBJECT_CLASS(klass));
238 
239  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
240  priv->column_menu = NULL;
241  priv->show_column_menu = FALSE;
242  priv->sort_model = NULL;
243  priv->state_section = NULL;
244  priv->seen_state_visibility = FALSE;
245  priv->columns_changed_cb_id = 0;
246  priv->sort_column_changed_cb_id = 0;
247  priv->size_allocate_cb_id = 0;
248 
249  /* Ask gtk to help the user keep track of rows. */
250  g_object_set(view, "rules-hint", TRUE, NULL);
251 
252  /* Handle column drag and drop */
253  gtk_tree_view_set_column_drag_function(GTK_TREE_VIEW(view),
254  gnc_tree_view_drop_ok_cb, NULL, NULL);
255 
256  /* Create the next to last column which is always present, visible,
257  * and empty. Override the defaults and make this a one pixel wide
258  * column, but have it take up any extra space in the window. */
259  column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
260  -1, -1, NULL);
261  g_object_set(G_OBJECT(column),
262  "fixed-width", 1,
263  "expand", TRUE,
264  (gchar *)NULL);
265  priv->spacer_column = column;
266 
267  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
268 
269  /* Create the last column which contains the column selection
270  * widget. gnc_tree_view_add_text_column will do most of the
271  * work. */
272  icon = gtk_image_new_from_stock(GTK_STOCK_GO_DOWN,
273  GTK_ICON_SIZE_SMALL_TOOLBAR);
274  gtk_widget_show(icon);
275  gtk_widget_size_request(icon, &requisition);
276  column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
277  -1, -1, NULL);
278  g_object_set(G_OBJECT(column),
279  "clickable", TRUE,
280  "widget", icon,
281  "fixed-width", requisition.width + 10,
282  (gchar *)NULL);
283  priv->selection_column = column;
284  g_signal_connect(G_OBJECT(column), "clicked",
285  G_CALLBACK (gnc_tree_view_select_column_cb),
286  view);
287  priv->column_menu_column = column;
288 
289  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
290 }
291 
302 static void
303 gnc_tree_view_finalize (GObject *object)
304 {
305  ENTER("view %p", object);
306  g_return_if_fail (object != NULL);
307  g_return_if_fail (GNC_IS_TREE_VIEW (object));
308 
310 
311  if (G_OBJECT_CLASS (parent_class)->finalize)
312  G_OBJECT_CLASS (parent_class)->finalize (object);
313  LEAVE(" ");
314 }
315 
327 static void
328 gnc_tree_view_destroy (GtkObject *object)
329 {
330  GncTreeView *view;
331  GncTreeViewPrivate *priv;
332 
333  ENTER("view %p", object);
334  g_return_if_fail (object != NULL);
335  g_return_if_fail (GNC_IS_TREE_VIEW (object));
336 
337  view = GNC_TREE_VIEW (object);
338 
339  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
340 
341  if (priv->state_section)
342  {
344  }
345  g_free(priv->state_section);
346  priv->state_section = NULL;
347 
348  if (priv->column_menu)
349  {
350  DEBUG("removing column selection menu");
351  g_object_unref(priv->column_menu);
352  priv->column_menu = NULL;
353  }
354 
355  if (GTK_OBJECT_CLASS (parent_class)->destroy)
356  GTK_OBJECT_CLASS (parent_class)->destroy (object);
357  LEAVE(" ");
358 }
359 
362 /************************************************************/
363 /* g_object other functions */
364 /************************************************************/
365 
376 static void
377 gnc_tree_view_get_property (GObject *object,
378  guint prop_id,
379  GValue *value,
380  GParamSpec *pspec)
381 {
382  GncTreeView *view = GNC_TREE_VIEW (object);
383  GncTreeViewPrivate *priv;
384 
385  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
386  switch (prop_id)
387  {
388  case PROP_STATE_SECTION:
389  g_value_set_string (value, priv->state_section);
390  break;
391  case PROP_SHOW_COLUMN_MENU:
392  g_value_set_boolean (value, priv->show_column_menu);
393  break;
394  default:
395  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
396  break;
397  }
398 }
399 
400 
409 static void
410 gnc_tree_view_set_property (GObject *object,
411  guint prop_id,
412  const GValue *value,
413  GParamSpec *pspec)
414 {
415  GncTreeView *view = GNC_TREE_VIEW (object);
416 
417  switch (prop_id)
418  {
419  case PROP_STATE_SECTION:
420  gnc_tree_view_set_state_section (view, g_value_get_string (value));
421  break;
422  case PROP_SHOW_COLUMN_MENU:
423  gnc_tree_view_set_show_column_menu (view, g_value_get_boolean (value));
424  break;
425  default:
426  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
427  break;
428  }
429 }
430 
433 /************************************************************/
434 /* Auxiliary Functions */
435 /************************************************************/
454 static GtkTreeViewColumn *
455 view_column_find_by_model_id (GncTreeView *view,
456  const gint wanted)
457 {
458  GtkTreeViewColumn *column, *found = NULL;
459  GList *column_list, *tmp;
460  gint id;
461 
462  // ENTER("view %p, name %s", view, name);
463  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
464  for (tmp = column_list; tmp; tmp = g_list_next(tmp))
465  {
466  column = tmp->data;
467  id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), MODEL_COLUMN));
468  if (id != wanted)
469  continue;
470  found = column;
471  break;
472  }
473  g_list_free(column_list);
474 
475  // LEAVE("column %p", found);
476  return found;
477 }
478 
489 GtkTreeViewColumn *
491  const gchar *wanted)
492 {
493  GtkTreeViewColumn *column, *found = NULL;
494  GList *column_list, *tmp;
495  const gchar *name;
496 
497  // ENTER("view %p, wanted %s", view, wanted);
498  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
499  for (tmp = column_list; tmp; tmp = g_list_next(tmp))
500  {
501  column = tmp->data;
502  name = g_object_get_data(G_OBJECT(column), PREF_NAME);
503  if (!name || (strcmp(name, wanted) != 0))
504  continue;
505  found = column;
506  break;
507  }
508  g_list_free(column_list);
509 
510  // LEAVE("column %p", found);
511  return found;
512 }
513 
516 /************************************************************/
517 /* Tree Callbacks */
518 /************************************************************/
519 
548 static gboolean
549 gnc_tree_view_drop_ok_cb (GtkTreeView *view,
550  GtkTreeViewColumn *column,
551  GtkTreeViewColumn *prev_column,
552  GtkTreeViewColumn *next_column,
553  gpointer data)
554 {
555  const gchar *pref_name;
556 
557  /* Should we allow a drop at the left side of the tree view before
558  * the widget to open a new display level? I can think of cases
559  * where the user might want to do this with a checkbox column. */
560  if (prev_column == NULL)
561  return TRUE;
562 
563  /* Do not allow a drop at the right side of the tree view after the
564  * column selection widget. */
565  if (next_column == NULL)
566  return FALSE;
567 
568  /* Columns without pref names are considered fixed at the right hand
569  * side of the view. At the time of this writing, the only two are
570  * the column where the "column selection widget" is stored, and the
571  * "padding" column to the left of that where extra view space ends
572  * up. */
573  pref_name = g_object_get_data(G_OBJECT(prev_column), PREF_NAME);
574  if (!pref_name)
575  return FALSE;
576 
577  /* Everything else is allowed. */
578  return TRUE;
579 }
580 
583 /************************************************************/
584 /* State Setup / Callbacks */
585 /************************************************************/
586 
609 static gboolean
610 gnc_tree_view_column_visible (GncTreeView *view,
611  GtkTreeViewColumn *column,
612  const gchar *pref_name)
613 {
614  GncTreeViewPrivate *priv;
615  gboolean visible;
616  const gchar *col_name = pref_name;
617 
618  ENTER("column %p, name %s", column, pref_name ? pref_name : "(null)");
619  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
620  if (column)
621  {
622  if (g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE))
623  {
624  LEAVE("1, first column");
625  return TRUE;
626  }
627  col_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
628  DEBUG("col_name is %s", col_name ? col_name : "(null)");
629  }
630 
631  if (!col_name)
632  {
633  LEAVE("1, no pref name");
634  return TRUE;
635  }
636 
637  /* Using saved state ? */
638  if (priv->state_section)
639  {
640  GKeyFile *state_file = gnc_state_get_current ();
641  gchar *key = g_strdup_printf("%s_%s", col_name, STATE_KEY_SUFF_VISIBLE);
642 
643  if (g_key_file_has_key (state_file, priv->state_section, key, NULL))
644  {
645  visible = g_key_file_get_boolean (state_file, priv->state_section, key, NULL);
646  g_free(key);
647  LEAVE("%d, state defined visibility", visible);
648  return visible;
649  }
650  }
651 
652  /* Check the default columns list */
653  visible = column ?
654  (g_object_get_data(G_OBJECT(column), DEFAULT_VISIBLE) != NULL) : FALSE;
655  LEAVE("defaults says %d", visible);
656  return visible;
657 }
658 
669 static void
670 gnc_tree_view_update_visibility (GtkTreeViewColumn *column,
671  GncTreeView *view)
672 {
673  GncTreeViewPrivate *priv;
674  gchar *name, *key;
675  gboolean visible;
676 
677  g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
678  g_return_if_fail(GNC_IS_TREE_VIEW(view));
679 
680  ENTER(" ");
681  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
682  visible = gnc_tree_view_column_visible(view, column, NULL);
683  gtk_tree_view_column_set_visible(column, visible);
684  LEAVE("made %s", visible ? "visible" : "invisible");
685 }
686 
697 static gchar *
698 gnc_tree_view_get_sort_order (GncTreeView *view)
699 {
700  GncTreeViewPrivate *priv;
701  GtkTreeModel *s_model;
702  GtkSortType order;
703  gint current;
704  gchar *order_str = NULL;
705 
706  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
707  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
708  if (!s_model)
709  return NULL; /* no model, so sort order doesn't make sense */
710 
711  if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
712  &current, &order))
713  return NULL; /* Model is not sorted, return */
714 
715  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
716  current, order);
717  order_str = g_strdup (order == GTK_SORT_ASCENDING ? "ascending" : "descending");
718  DEBUG("current sort_order is %s", order_str);
719  return order_str;
720 }
721 
731 static gchar *
732 gnc_tree_view_get_sort_column (GncTreeView *view)
733 {
734  GtkTreeModel *s_model;
735  GtkTreeViewColumn *column;
736  GtkSortType order;
737  gint model_column, current;
738  const gchar *name;
739 
740  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
741  if (!s_model)
742  return NULL; /* no model -> no sort column */
743 
744  if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
745  &current, &order))
746  return NULL; /* model not sorted */
747 
748  column = view_column_find_by_model_id (view, current);
749  if (!column)
750  return NULL; /* column not visible, can't be used for sorting */
751 
752  name = g_object_get_data(G_OBJECT(column), PREF_NAME);
753  DEBUG("current sort column is %s", name ? name : "(NULL)");
754  return g_strdup (name);
755 }
756 
757 
758 
769 static gchar **
770 gnc_tree_view_get_column_order (GncTreeView *view,
771  gsize *length)
772 {
773  const GList *tmp;
774  GList *columns;
775  gulong num_cols = 0;
776  gchar *col_names = NULL;
777  gchar **col_str_list;
778 
779  /* First, convert from names to pointers */
780  ENTER(" ");
781 
782  columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
783  for (tmp = columns; tmp; tmp = g_list_next(tmp))
784  {
785  GtkTreeViewColumn *column = tmp->data;
786  const gchar *name = g_object_get_data(G_OBJECT(column), PREF_NAME);
787  if (!col_names)
788  col_names = g_strdup (name);
789  else
790  {
791  gchar *col_names_prev = col_names;
792  col_names = g_strjoin (";", col_names_prev, name, NULL);
793  g_free (col_names_prev);
794  }
795  num_cols++;
796  }
797  //DEBUG ("got %lu columns: %s", num_cols, col_names);
798  col_str_list = g_strsplit (col_names, ";", 0);
799 
800  /* Clean up */
801  g_list_free(columns);
802  g_free (col_names);
803 
804  LEAVE("column order get");
805  *length = num_cols;
806  return col_str_list;
807 }
808 
819 static void
820 gnc_tree_view_set_sort_order (GncTreeView *view,
821  const gchar *name)
822 {
823  GncTreeViewPrivate *priv;
824  GtkTreeModel *s_model;
825  GtkSortType order = GTK_SORT_ASCENDING;
826  gint current;
827 
828  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
829  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
830  if (!s_model)
831  return;
832  if (g_strcmp0 (name, "descending") == 0)
833  order = GTK_SORT_DESCENDING;
834  if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
835  &current, NULL))
836  current = GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID;
837  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
838  current, order);
839  DEBUG("sort_order set to %s", order == GTK_SORT_ASCENDING ? "ascending" : "descending");
840 }
841 
850 static void
851 gnc_tree_view_set_sort_column (GncTreeView *view,
852  const gchar *name)
853 {
854  GtkTreeModel *s_model;
855  GtkTreeViewColumn *column;
856  GtkSortType order;
857  gint model_column, current;
858 
859  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
860  if (!s_model)
861  return;
862 
863  column = gnc_tree_view_find_column_by_name(view, name);
864  if (!column)
865  {
866  gtk_tree_sortable_set_sort_column_id(
867  GTK_TREE_SORTABLE(s_model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
868  GTK_SORT_ASCENDING);
869  return;
870  }
871 
872  model_column =
873  GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), MODEL_COLUMN));
874  if (model_column == GNC_TREE_VIEW_COLUMN_DATA_NONE)
875  return;
876 
877  if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
878  &current, &order))
879  order = GTK_SORT_ASCENDING;
880 
881  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
882  model_column, order);
883  DEBUG("sort column set to %s", name);
884 }
885 
898 static void
899 gnc_tree_view_set_column_order (GncTreeView *view,
900  gchar **column_names,
901  gsize length)
902 {
903  GncTreeViewPrivate *priv;
904  GtkTreeViewColumn *column, *prev;
905  const GSList *tmp;
906  GSList *columns;
907  gsize idx;
908 
909  /* First, convert from names to pointers */
910  ENTER(" ");
911  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
912  columns = NULL;
913  for (idx = 0; idx < length; idx++)
914  {
915  const gchar *name = column_names[idx];
916  column = gnc_tree_view_find_column_by_name(view, name);
917  if (!column)
918  continue;
919  columns = g_slist_append(columns, column);
920  }
921 
922  /* Then reorder the columns */
923  for (prev = NULL, tmp = columns; tmp; tmp = g_slist_next(tmp))
924  {
925  column = tmp->data;
926  gtk_tree_view_move_column_after(GTK_TREE_VIEW(view), column, prev);
927  prev = column;
928  }
929 
930  /* Clean up */
931  g_slist_free(columns);
932  LEAVE("column order set");
933 }
934 
944 {
945  GncTreeViewPrivate *priv;
946  GKeyFile *state_file = gnc_state_get_current ();
947 
948  ENTER(" ");
949  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
950  if (!priv->state_section)
951  {
952  LEAVE("no state section");
953  return;
954  }
955 
956  g_key_file_remove_group (state_file, priv->state_section, NULL);
957  g_free(priv->state_section);
958  priv->state_section = NULL;
959  LEAVE(" ");
960 }
961 
969 void
971  const gchar *section)
972 {
973  GncTreeViewPrivate *priv;
974  GKeyFile *state_file;
975 
976  g_return_if_fail(GNC_IS_TREE_VIEW(view));
977 
978  ENTER("view %p, section %s", view, section);
979 
980  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
981 
982  /* Drop any previous state section */
983  if (priv->state_section)
985 
986  if (!section)
987  {
988  LEAVE("cleared state section");
989  return;
990  }
991 
992  /* Catch changes in state. Propagate to view. */
993  priv->state_section = g_strdup(section);
994 
995  state_file = gnc_state_get_current ();
996  if (g_key_file_has_group (state_file, priv->state_section))
997  {
998  gsize num_keys, idx;
999  gchar **keys = g_key_file_get_keys (state_file, priv->state_section, &num_keys, NULL);
1000  for (idx = 0; idx < num_keys; idx++)
1001  {
1002  gchar *key = keys[idx];
1003  if (g_strcmp0 (key, STATE_KEY_SORT_COLUMN) == 0)
1004  {
1005  gnc_tree_view_set_sort_column (view,
1006  g_key_file_get_string (state_file, priv->state_section, key, NULL));
1007  }
1008  else if (g_strcmp0 (key, STATE_KEY_SORT_ORDER) == 0)
1009  {
1010  gnc_tree_view_set_sort_order (view,
1011  g_key_file_get_string (state_file, priv->state_section, key, NULL));
1012  }
1013  else if (g_strcmp0 (key, STATE_KEY_COLUMN_ORDER) == 0)
1014  {
1015  gsize length;
1016  gchar **columns = g_key_file_get_string_list (state_file, priv->state_section,
1017  key, &length, NULL);
1018  gnc_tree_view_set_column_order (view, columns, length);
1019  g_strfreev (columns);
1020  }
1021  else
1022  {
1023  /* Make a copy of the local part of the key so it can be split
1024  * into column name and key type */
1025  gboolean known = FALSE;
1026  gchar *column_name = g_strdup (key);
1027  gchar *type_name = g_strrstr (column_name, "_");
1028  *type_name++ = '\0';
1029 
1030  if (g_strcmp0 (type_name, STATE_KEY_SUFF_VISIBLE) == 0)
1031  {
1032  GtkTreeViewColumn *column = gnc_tree_view_find_column_by_name (view, column_name);
1033  if (column)
1034  {
1035  known = TRUE;
1036  if (!g_object_get_data (G_OBJECT (column), ALWAYS_VISIBLE))
1037  {
1038  gtk_tree_view_column_set_visible (column,
1039  g_key_file_get_boolean (state_file, priv->state_section, key, NULL));
1040  }
1041  }
1042  }
1043  else if (g_strcmp0 (type_name, STATE_KEY_SUFF_WIDTH) == 0)
1044  {
1045  gint width = g_key_file_get_integer (state_file, priv->state_section, key, NULL);
1046  GtkTreeViewColumn *column = gnc_tree_view_find_column_by_name (view, column_name);
1047  if (column)
1048  {
1049  known = TRUE;
1050  if (width && (width != gtk_tree_view_column_get_width (column)))
1051  {
1052  gtk_tree_view_column_set_fixed_width (column, width);
1053  }
1054  }
1055  }
1056  if (!known)
1057  DEBUG ("Ignored key %s", key);
1058 
1059  g_free (column_name);
1060  }
1061  }
1062  }
1063 
1064  /* Rebuild the column visibility menu */
1065  gnc_tree_view_build_column_menu (view);
1066  LEAVE ("set state section");
1067 }
1068 
1075 const gchar *
1077 {
1078  GncTreeViewPrivate *priv;
1079 
1080  g_return_val_if_fail (GNC_IS_TREE_VIEW (view), NULL);
1081 
1082  priv = GNC_TREE_VIEW_GET_PRIVATE (view);
1083  return priv->state_section;
1084 }
1085 
1087 {
1088  GncTreeViewPrivate *priv;
1089 
1090  ENTER("view %p", view);
1091  g_return_if_fail (view != NULL);
1092  g_return_if_fail (GNC_IS_TREE_VIEW (view));
1093 
1094  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1095 
1096  if (priv->state_section)
1097  {
1098  /* Save state. Only store non-default values when possible. */
1099  GList *column_list, *tmp;
1100  GKeyFile *state_file = gnc_state_get_current();
1101  gsize num_cols = 0;
1102  gchar *sort_column = gnc_tree_view_get_sort_column (view);
1103  gchar *sort_order = gnc_tree_view_get_sort_order (view);
1104  gchar **col_order = gnc_tree_view_get_column_order (view, &num_cols);
1105 
1106  /* Default sort column is the first column */
1107  if (sort_column &&
1108  (gnc_tree_view_find_column_by_name (view, sort_column) !=
1109  gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0)))
1110  g_key_file_set_string (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, sort_column);
1111  else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, NULL))
1112  g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, NULL);
1113  g_free (sort_column);
1114 
1115 
1116  /* Default sort order is "ascending" */
1117  if (g_strcmp0 (sort_order, "descending") == 0)
1118  g_key_file_set_string (state_file, priv->state_section, STATE_KEY_SORT_ORDER, sort_order);
1119  else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_SORT_ORDER, NULL))
1120  g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_SORT_ORDER, NULL);
1121  g_free (sort_order);
1122 
1123  if (col_order && (num_cols > 0))
1124  g_key_file_set_string_list (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER,
1125  (const gchar**) col_order, num_cols);
1126  else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER, NULL))
1127  g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER, NULL);
1128 
1129 
1130  // ENTER("view %p, wanted %s", view, wanted);
1131  column_list = gtk_tree_view_get_columns (GTK_TREE_VIEW (view));
1132  for (tmp = column_list; tmp; tmp = g_list_next (tmp))
1133  {
1134  GtkTreeViewColumn *column = tmp->data;
1135  gchar *key=NULL;
1136  const gchar *name = g_object_get_data (G_OBJECT (column), PREF_NAME);
1137  if (!name)
1138  continue;
1139 
1140  if (!g_object_get_data (G_OBJECT (column), ALWAYS_VISIBLE))
1141  {
1142  key = g_strjoin ("_", name, STATE_KEY_SUFF_VISIBLE, NULL);
1143  g_key_file_set_boolean (state_file, priv->state_section, key,
1144  gtk_tree_view_column_get_visible (column));
1145  g_free (key);
1146  }
1147 
1148  key = g_strjoin ("_", name, STATE_KEY_SUFF_WIDTH, NULL);
1149  if (g_object_get_data (G_OBJECT(column), "default-width") &&
1150  (GPOINTER_TO_INT((g_object_get_data (G_OBJECT(column), "default-width")))
1151  != gtk_tree_view_column_get_width (column)))
1152  {
1153  g_key_file_set_integer (state_file, priv->state_section, key,
1154  gtk_tree_view_column_get_width (column));
1155  }
1156  else if (g_key_file_has_key (state_file, priv->state_section, key, NULL))
1157  g_key_file_remove_key (state_file, priv->state_section, key, NULL);
1158  g_free (key);
1159  }
1160  g_list_free(column_list);
1161  }
1162 
1163  LEAVE(" ");
1164 }
1165 
1166 
1169 /************************************************************/
1170 /* Column Selection Menu */
1171 /************************************************************/
1172 
1191 static void
1192 gnc_tree_view_create_menu_item (GtkTreeViewColumn *column,
1193  GncTreeView *view)
1194 {
1195  GncTreeViewPrivate *priv;
1196  GtkWidget *widget;
1197  const gchar *column_name, *pref_name;
1198  gchar *key;
1199  GBinding *binding;
1200 
1201  // ENTER("view %p, column %p", view, column);
1202  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1203  if (!priv->state_section)
1204  {
1205  // LEAVE("no state section");
1206  return;
1207  }
1208 
1209  pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
1210  if (!pref_name)
1211  {
1212  // LEAVE("column has no pref_name");
1213  return;
1214  }
1215 
1216  /* Create the menu if we don't have one already */
1217  if (!priv->column_menu)
1218  {
1219  priv->column_menu = gtk_menu_new();
1220  g_object_ref_sink(priv->column_menu);
1221  }
1222 
1223  /* Create the check menu item */
1224  column_name = g_object_get_data(G_OBJECT(column), REAL_TITLE);
1225  if (!column_name)
1226  column_name = gtk_tree_view_column_get_title(column);
1227  widget = gtk_check_menu_item_new_with_label(column_name);
1228  gtk_menu_shell_append(GTK_MENU_SHELL(priv->column_menu), widget);
1229 
1230  /* Should never be able to hide the first column */
1231  if (g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE))
1232  {
1233  g_object_set_data(G_OBJECT(widget), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
1234  gtk_widget_set_sensitive(widget, FALSE);
1235  }
1236 
1237  binding = g_object_bind_property (G_OBJECT (widget), "active", G_OBJECT (column), "visible", 0);
1238  g_object_set_data (G_OBJECT (widget), "column-binding", binding);
1239 
1240  /* Store data on the widget for callbacks */
1241  key = g_strdup_printf("%s_%s", pref_name, STATE_KEY_SUFF_VISIBLE);
1242  g_object_set_data_full(G_OBJECT(widget), STATE_KEY, key, g_free);
1243  // LEAVE(" ");
1244 }
1245 
1257 static void
1258 gnc_tree_view_build_column_menu (GncTreeView *view)
1259 {
1260  GncTreeViewPrivate *priv;
1261  GList *column_list;
1262 
1263  g_return_if_fail(GNC_IS_TREE_VIEW(view));
1264 
1265  ENTER("view %p", view);
1266  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1267 
1268  /* Destroy any old menu */
1269  if (priv->column_menu)
1270  {
1271  g_object_unref(priv->column_menu);
1272  priv->column_menu = NULL;
1273  }
1274 
1275  if (priv->show_column_menu && priv->state_section)
1276  {
1277  /* Show the menu popup button */
1278  if (priv->column_menu_column)
1279  gtk_tree_view_column_set_visible(priv->column_menu_column, TRUE);
1280 
1281  /* Now build a new menu */
1282  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
1283  g_list_foreach(column_list, (GFunc)gnc_tree_view_create_menu_item, view);
1284  g_list_free(column_list);
1285  }
1286  else
1287  {
1288  /* Hide the menu popup button */
1289  if (priv->column_menu_column)
1290  gtk_tree_view_column_set_visible(priv->column_menu_column, FALSE);
1291  }
1292  LEAVE("menu: show %d, section %s", priv->show_column_menu,
1293  priv->state_section ? priv->state_section : "(null)");
1294 }
1295 
1305 static void
1306 gnc_tree_view_update_column_menu_item (GtkCheckMenuItem *checkmenuitem,
1307  GncTreeView *view)
1308 {
1309  GncTreeViewPrivate *priv;
1310  gchar *key;
1311  gboolean visible;
1312 
1313  g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(checkmenuitem));
1314  g_return_if_fail(GNC_IS_TREE_VIEW(view));
1315 
1316  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1317  key = g_object_get_data(G_OBJECT(checkmenuitem), STATE_KEY);
1318  if (g_object_get_data(G_OBJECT(checkmenuitem), ALWAYS_VISIBLE))
1319  {
1320  visible = TRUE;
1321  }
1322  else
1323  {
1324  GBinding *binding = g_object_get_data (G_OBJECT (checkmenuitem), "column-binding");
1325  GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(g_binding_get_target (binding));
1326 
1327  visible = gtk_tree_view_column_get_visible (column);
1328  }
1329 
1330  gtk_check_menu_item_set_active(checkmenuitem, visible);
1331 }
1332 
1345 static void
1346 gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
1347  GncTreeView *view)
1348 {
1349  GncTreeViewPrivate *priv;
1350  GtkWidget *menu;
1351 
1352  g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
1353  g_return_if_fail(GNC_IS_TREE_VIEW(view));
1354 
1355  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1356  menu = priv->column_menu;
1357  if (!menu)
1358  return;
1359 
1360  /* Synchronize the menu before display */
1361  gtk_container_foreach(GTK_CONTAINER(menu),
1362  (GtkCallback)gnc_tree_view_update_column_menu_item,
1363  view);
1364 
1365  /* Ensure all components are visible */
1366  gtk_widget_show_all(menu);
1367 
1368  /* Pop the menu up at the button */
1369  gtk_menu_popup(GTK_MENU(priv->column_menu), NULL, GTK_WIDGET(menu),
1370  NULL, NULL, 0, gtk_get_current_event_time());
1371 }
1372 
1373 
1375  gchar *first_column_name,
1376  ...)
1377 {
1378  GncTreeViewPrivate *priv;
1379  GtkTreeViewColumn *column;
1380  gboolean hide_spacer;
1381  GList *columns, *tmp;
1382  gchar *name, *pref_name;
1383  va_list args;
1384 
1385  g_return_if_fail (GNC_IS_TREE_VIEW (view));
1386  ENTER(" ");
1387  va_start (args, first_column_name);
1388  priv = GNC_TREE_VIEW_GET_PRIVATE (view);
1389  name = first_column_name;
1390  hide_spacer = FALSE;
1391 
1392  /* First disable the expand property on all (non-infrastructure) columns. */
1393  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
1394  for (tmp = columns; tmp; tmp = g_list_next(tmp))
1395  {
1396  column = tmp->data;
1397  pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
1398  if (pref_name != NULL)
1399  gtk_tree_view_column_set_expand(column, FALSE);
1400  }
1401  g_list_free(columns);
1402 
1403  /* Now enable it on the requested columns. */
1404  while (name != NULL)
1405  {
1406  column = gnc_tree_view_find_column_by_name(view, name);
1407  if (column != NULL)
1408  {
1409  gtk_tree_view_column_set_expand(column, TRUE);
1410  hide_spacer = TRUE;
1411  }
1412  name = va_arg (args, gchar*);
1413  }
1414  va_end (args);
1415 
1416  gtk_tree_view_column_set_visible (priv->spacer_column, !hide_spacer);
1417 
1418  LEAVE(" ");
1419 }
1420 
1421 
1422 /* Links the cell backgrounds of the two control columns to the model or
1423  cell data function */
1424 static void
1425 update_control_cell_renderers_background (GncTreeView *view, GtkTreeViewColumn *col, gint column, GtkTreeCellDataFunc func )
1426 {
1427  GList *renderers;
1428  GtkCellRenderer *cell;
1429  GList *node;
1430 
1431  renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (col));
1432 
1433  /* Update the cell background in the list of renderers */
1434  for (node = renderers; node; node = node->next)
1435  {
1436  cell = node->data;
1437  if (func == NULL)
1438  gtk_tree_view_column_add_attribute (col, cell, "cell-background", column);
1439  else
1440  gtk_tree_view_column_set_cell_data_func (col, cell, func, view, NULL);
1441  }
1442  g_list_free (renderers);
1443 }
1444 
1445 
1446 /* This function links the cell backgrounds of the two control columns to a column
1447  in the model that has color strings or a cell data function */
1448 void
1449 gnc_tree_view_set_control_column_background (GncTreeView *view, gint column, GtkTreeCellDataFunc func )
1450 {
1451  GncTreeViewPrivate *priv;
1452 
1453  g_return_if_fail (GNC_IS_TREE_VIEW (view));
1454 
1455  ENTER("view %p, column %d, func %p", view, column, func);
1456  priv = GNC_TREE_VIEW_GET_PRIVATE (view);
1457 
1458  update_control_cell_renderers_background (view, priv->spacer_column, column, func);
1459  update_control_cell_renderers_background (view, priv->selection_column, column, func);
1460 
1461  LEAVE(" ");
1462 }
1463 
1464 
1465 /* This allows the columns to be setup without the model connected */
1466 //FIXME I think this should be specified as a parameter to the add columns functions...
1467 void
1468 gnc_tree_view_set_sort_user_data (GncTreeView *view, GtkTreeModel *s_model)
1469 {
1470  GncTreeViewPrivate *priv;
1471 
1472  g_return_if_fail (GNC_IS_TREE_VIEW (view));
1473 
1474  ENTER("view %p, sort_model %p", view, s_model);
1475  priv = GNC_TREE_VIEW_GET_PRIVATE (view);
1476 
1477  priv->sort_model = s_model;
1478  LEAVE(" ");
1479 }
1480 
1481 
1488 void
1490  gboolean visible)
1491 {
1492  GncTreeViewPrivate *priv;
1493 
1494  g_return_if_fail(GNC_IS_TREE_VIEW(view));
1495 
1496  ENTER("view %p, show menu %d", view, visible);
1497  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1498  priv->show_column_menu = visible;
1499  gnc_tree_view_build_column_menu(view);
1500  LEAVE(" ");
1501 }
1502 
1509 gboolean
1511 {
1512  GncTreeViewPrivate *priv;
1513 
1514  g_return_val_if_fail(GNC_IS_TREE_VIEW(view), FALSE);
1515 
1516  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1517  return(priv->show_column_menu);
1518 }
1519 
1522 /************************************************************/
1523 /* Tree View Creation */
1524 /************************************************************/
1525 
1526 static gint
1527 gnc_tree_view_count_visible_columns(GncTreeView *view)
1528 {
1529  GList *columns, *node;
1530  gint count = 0;
1531 
1532  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
1533  for (node = columns; node; node = node->next)
1534  {
1535  GtkTreeViewColumn *col = GTK_TREE_VIEW_COLUMN(node->data);
1536 
1537  if (g_object_get_data(G_OBJECT(col), DEFAULT_VISIBLE) ||
1538  g_object_get_data(G_OBJECT(col), ALWAYS_VISIBLE))
1539  count++;
1540  }
1541  g_list_free(columns);
1542  return count;
1543 }
1544 
1545 void
1547 {
1548  GncTreeViewPrivate *priv;
1549  GtkTreeViewColumn *column;
1550  GList *columns;
1551  gboolean hide_spacer;
1552 
1553  g_return_if_fail(GNC_IS_TREE_VIEW(view));
1554 
1555  ENTER(" ");
1556 
1557  /* Update the view and saved state */
1558  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
1559  g_list_foreach(columns, (GFunc)gnc_tree_view_update_visibility, view);
1560  g_list_free(columns);
1561 
1562  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1563  if (priv->state_section)
1564  priv->seen_state_visibility = TRUE;
1565 
1566  /* If only the first column is visible, hide the spacer and make that
1567  * column expand. */
1568  hide_spacer = (gnc_tree_view_count_visible_columns(view) == 1);
1569  column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), 0);
1570  gtk_tree_view_column_set_expand(column, hide_spacer);
1571  gtk_tree_view_column_set_visible(priv->spacer_column, !hide_spacer);
1572  gtk_tree_view_column_set_visible(priv->selection_column, !hide_spacer);
1573 
1574  LEAVE(" ");
1575 }
1576 
1577 
1607 static void
1608 gnc_tree_view_column_properties (GncTreeView *view,
1609  GtkTreeViewColumn *column,
1610  const gchar *pref_name,
1611  gint data_column,
1612  gint default_width,
1613  gboolean resizable,
1614  GtkTreeIterCompareFunc column_sort_fn)
1615 {
1616  GncTreeViewPrivate *priv;
1617  GtkTreeModel *s_model;
1618  gboolean visible;
1619  int width = 0;
1620  gchar *key;
1621 
1622  /* Set data used by other functions */
1623  if (pref_name)
1624  g_object_set_data(G_OBJECT(column), PREF_NAME, (gpointer)pref_name);
1625  if (data_column == 0)
1626  g_object_set_data(G_OBJECT(column), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
1627  g_object_set_data(G_OBJECT(column), MODEL_COLUMN,
1628  GINT_TO_POINTER(data_column));
1629 
1630  /* Get visibility */
1631  visible = gnc_tree_view_column_visible(view, NULL, pref_name);
1632 
1633  /* Set column attributes (without the sizing) */
1634  g_object_set(G_OBJECT(column),
1635  "visible", visible,
1636  "resizable", resizable && pref_name != NULL,
1637  "reorderable", pref_name != NULL,
1638  NULL);
1639 
1640  /* Get width */
1641  if (default_width == 0)
1642  {
1643  /* Set the sizing column attributes */
1644  g_object_set(G_OBJECT(column),
1645  "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
1646  NULL);
1647  }
1648  else
1649  {
1650  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1651 
1652  /* If saved state comes back with a width of zero (or there is no saved
1653  * state width) the use the default width for the column. Allow for
1654  * padding L and R of the displayed data. */
1655  if (width == 0)
1656  width = default_width + 10;
1657  if (width == 0)
1658  width = 10;
1659 
1660  /* Set the sizing column attributes (including fixed-width) */
1661  g_object_set(G_OBJECT(column),
1662  "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
1663  "fixed-width", width,
1664  NULL);
1665  /* Save the initially calculated preferred width for later
1666  * comparison to the actual width when saving state. Can't
1667  * use the "fixed-width" property for that because it changes
1668  * when the user resizes the column.
1669  */
1670  g_object_set_data (G_OBJECT(column),
1671  "default-width", GINT_TO_POINTER (width));
1672  }
1673 
1674  s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
1675  if (GTK_IS_TREE_SORTABLE(s_model))
1676  {
1677  gtk_tree_view_column_set_sort_column_id (column, data_column);
1678  if (column_sort_fn)
1679  {
1680  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(s_model),
1681  data_column, column_sort_fn,
1682  GINT_TO_POINTER(data_column),
1683  NULL /* destroy fn */);
1684  }
1685  }
1686 
1687  // Used in registers, sort model not connected to view yet
1688  priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1689  if (priv->sort_model != NULL)
1690  {
1691  gtk_tree_view_column_set_sort_column_id (column, data_column);
1692  if (column_sort_fn)
1693  {
1694  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(priv->sort_model),
1695  data_column, column_sort_fn,
1696  view,
1697  NULL /* destroy fn */);
1698  }
1699  }
1700 
1701  /* Add to the column selection menu */
1702  if (pref_name)
1703  {
1704  gnc_tree_view_create_menu_item(column, view);
1705  }
1706 }
1707 
1718 GtkTreeViewColumn *
1720  const gchar *column_title,
1721  const gchar *column_short_title,
1722  const gchar *pref_name,
1723  gint model_data_column,
1724  gint model_visibility_column,
1725  GtkTreeIterCompareFunc column_sort_fn,
1726  renderer_toggled toggle_edited_cb)
1727 {
1728  GtkTreeViewColumn *column;
1729  GtkCellRenderer *renderer;
1730 
1731  g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1732 
1733  renderer = gtk_cell_renderer_toggle_new ();
1734  column =
1735  gtk_tree_view_column_new_with_attributes (column_short_title,
1736  renderer,
1737  "active", model_data_column,
1738  NULL);
1739 
1740  /* Add the full title to the object for menu creation */
1741  g_object_set_data_full(G_OBJECT(column), REAL_TITLE,
1742  g_strdup(column_title), g_free);
1743  if (toggle_edited_cb)
1744  g_signal_connect(G_OBJECT(renderer), "toggled",
1745  G_CALLBACK(toggle_edited_cb), view);
1746 
1747  if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1748  gtk_tree_view_column_add_attribute (column, renderer,
1749  "visible", model_visibility_column);
1750 
1751 
1752  gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1753  0, FALSE, column_sort_fn);
1754 
1755  gnc_tree_view_append_column (view, column);
1756 
1757  /* Also add the full title to the object as a tooltip */
1758  gtk_widget_set_tooltip_text(column->button, column_title);
1759 
1760  return column;
1761 }
1762 
1771 GtkTreeViewColumn *
1773  const gchar *column_title,
1774  const gchar *pref_name,
1775  const gchar *stock_icon_name,
1776  const gchar *sizing_text,
1777  gint model_data_column,
1778  gint model_visibility_column,
1779  GtkTreeIterCompareFunc column_sort_fn)
1780 {
1781  GtkTreeViewColumn *column;
1782  GtkCellRenderer *renderer;
1783  PangoLayout* layout;
1784  int default_width, title_width;
1785 
1786  g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1787 
1788  column = gtk_tree_view_column_new ();
1789  gtk_tree_view_column_set_title (column, column_title);
1790 
1791  /* Set up an icon renderer if requested */
1792  if (stock_icon_name)
1793  {
1794  renderer = gtk_cell_renderer_pixbuf_new ();
1795  g_object_set (renderer, "stock-id", stock_icon_name, NULL);
1796  gtk_tree_view_column_pack_start (column, renderer, FALSE);
1797  }
1798 
1799  /* Set up a text renderer and attributes */
1800  renderer = gtk_cell_renderer_text_new ();
1801  gtk_tree_view_column_pack_start (column, renderer, TRUE);
1802 
1803  /* Set renderer attributes controlled by the model */
1804  if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
1805  gtk_tree_view_column_add_attribute (column, renderer,
1806  "text", model_data_column);
1807  if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1808  gtk_tree_view_column_add_attribute (column, renderer,
1809  "visible", model_visibility_column);
1810 
1811  /* Default size is the larger of the column title and the sizing text */
1812  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
1813  pango_layout_get_pixel_size(layout, &title_width, NULL);
1814  g_object_unref(layout);
1815  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
1816  pango_layout_get_pixel_size(layout, &default_width, NULL);
1817  g_object_unref(layout);
1818  default_width = MAX(default_width, title_width);
1819  if (default_width)
1820  default_width += 10; /* padding on either side */
1821  gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1822  default_width, TRUE, column_sort_fn);
1823 
1824  gnc_tree_view_append_column (view, column);
1825  return column;
1826 }
1827 
1828 
1829 
1838 GtkTreeViewColumn *
1840  const gchar *column_title,
1841  const gchar *pref_name,
1842  const gchar *stock_icon_name,
1843  const gchar *sizing_text,
1844  gint model_data_column,
1845  gint model_visibility_column,
1846  GtkTreeIterCompareFunc column_sort_fn)
1847 {
1848  GtkTreeViewColumn *column;
1849  GtkCellRenderer *renderer;
1850  PangoLayout* layout;
1851  int default_width, title_width;
1852 
1853  g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1854 
1855  column = gtk_tree_view_column_new ();
1856  gtk_tree_view_column_set_title (column, column_title);
1857 
1858  /* Set up an icon renderer if requested */
1859  if (stock_icon_name)
1860  {
1861  renderer = gtk_cell_renderer_pixbuf_new ();
1862  g_object_set (renderer, "stock-id", stock_icon_name, NULL);
1863  gtk_tree_view_column_pack_start (column, renderer, FALSE);
1864  }
1865 
1866  /* Set up a text renderer and attributes */
1867  renderer = gnc_cell_renderer_date_new(TRUE);
1868  gtk_tree_view_column_pack_start (column, renderer, TRUE);
1869 
1870  /* Set renderer attributes controlled by the model */
1871  if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
1872  gtk_tree_view_column_add_attribute (column, renderer,
1873  "text", model_data_column);
1874  if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1875  gtk_tree_view_column_add_attribute (column, renderer,
1876  "visible", model_visibility_column);
1877 
1878  /* Default size is the larger of the column title and the sizing text */
1879  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
1880  pango_layout_get_pixel_size(layout, &title_width, NULL);
1881  g_object_unref(layout);
1882  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
1883  pango_layout_get_pixel_size(layout, &default_width, NULL);
1884  g_object_unref(layout);
1885  default_width = MAX(default_width, title_width);
1886  if (default_width)
1887  default_width += 10; /* padding on either side */
1888  gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1889  default_width, TRUE, column_sort_fn);
1890 
1891  gnc_tree_view_append_column (view, column);
1892  return column;
1893 }
1894 
1895 
1896 GtkTreeViewColumn *
1898  const gchar *column_title,
1899  const gchar *pref_name,
1900  const gchar *sizing_text,
1901  gint model_data_column,
1902  gint model_visibility_column,
1903  GtkTreeModel *combo_tree_model,
1904  gint combo_model_text_column,
1905  GtkTreeIterCompareFunc column_sort_fn)
1906 {
1907  GtkTreeViewColumn *column;
1908  GtkCellRenderer *renderer;
1909  PangoLayout* layout;
1910  int default_width, title_width;
1911 
1912  g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1913 
1914  column = gtk_tree_view_column_new ();
1915  gtk_tree_view_column_set_title (column, gettext(column_title));
1916 
1917  /* Set up a renderer and attributes */
1918  renderer = gtk_cell_renderer_combo_new ();
1919  gtk_tree_view_column_pack_start (column, renderer, TRUE);
1920 
1921  /* Set renderer attributes controlled by the model */
1922  if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
1923  gtk_tree_view_column_add_attribute (column, renderer,
1924  "text", model_data_column);
1925  if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1926  gtk_tree_view_column_add_attribute (column, renderer,
1927  "visible", model_visibility_column);
1928 
1929  /* Default size is the larger of the column title and the sizing text */
1930  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
1931  pango_layout_get_pixel_size(layout, &title_width, NULL);
1932  g_object_unref(layout);
1933  layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
1934  pango_layout_get_pixel_size(layout, &default_width, NULL);
1935  g_object_unref(layout);
1936  default_width = MAX(default_width, title_width);
1937  if (default_width)
1938  default_width += 10; /* padding on either side */
1939 
1940  gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1941  default_width, TRUE, column_sort_fn);
1942 
1943  /* Stuff specific to combo */
1944  if (combo_tree_model)
1945  {
1946  g_object_set(G_OBJECT(renderer), "model", combo_tree_model,
1947  "text-column", combo_model_text_column, NULL);
1948  }
1949  /* TODO: has-entry? */
1950 
1951  gnc_tree_view_append_column (view, column);
1952  return column;
1953 }
1954 
1955 GtkCellRenderer *
1956 gnc_tree_view_column_get_renderer(GtkTreeViewColumn *column)
1957 {
1958  GList *renderers;
1959  GtkCellRenderer *cr = NULL;
1960 
1961  g_return_val_if_fail(GTK_TREE_VIEW_COLUMN(column), NULL);
1962 
1963  /* Get the list of one renderer */
1964  renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
1965  if (g_list_length(renderers) > 0)
1966  cr = GTK_CELL_RENDERER(renderers->data);
1967  g_list_free(renderers);
1968 
1969  return cr;
1970 }
1971 
1982 GtkTreeViewColumn *
1984  const gchar *column_title,
1985  const gchar *pref_name,
1986  const gchar *sizing_text,
1987  gint model_data_column,
1988  gint model_color_column,
1989  gint model_visibility_column,
1990  GtkTreeIterCompareFunc column_sort_fn)
1991 {
1992  GtkTreeViewColumn *column;
1993  GtkCellRenderer *renderer;
1994 
1995  column = gnc_tree_view_add_text_column (view, column_title, pref_name,
1996  NULL, sizing_text, model_data_column,
1997  model_visibility_column,
1998  column_sort_fn);
1999 
2000  renderer = gnc_tree_view_column_get_renderer(column);
2001 
2002  /* Right align the column title and data */
2003  g_object_set(G_OBJECT(column), "alignment", 1.0, NULL);
2004  g_object_set(G_OBJECT(renderer), "xalign", 1.0, NULL);
2005 
2006  /* Change the text color */
2007  if (model_color_column != GNC_TREE_VIEW_COLUMN_COLOR_NONE)
2008  gtk_tree_view_column_add_attribute (column, renderer,
2009  "foreground", model_color_column);
2010 
2011  return column;
2012 }
2013 
2022 gint
2024  GtkTreeViewColumn *column)
2025 {
2026  GList *columns;
2027  int n;
2028 
2029  /* There's no easy way to get this number. */
2030  columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
2031  n = g_list_length(columns);
2032  g_list_free(columns);
2033 
2034  /* Ignore the initial two columns (the spacer and the selection menu) */
2035  if (n >= 2)
2036  n -= 2;
2037  return gtk_tree_view_insert_column (GTK_TREE_VIEW(view), column, n);
2038 }
2039 
2040 static gboolean
2041 get_column_next_to(GtkTreeView *tv, GtkTreeViewColumn **col, gboolean backward)
2042 {
2043  GList *cols, *node;
2044  GtkTreeViewColumn *c = NULL;
2045  gint seen = 0;
2046  gboolean wrapped = FALSE;
2047 
2048  cols = gtk_tree_view_get_columns(tv);
2049  g_return_val_if_fail(g_list_length(cols) > 0, FALSE);
2050 
2051  node = g_list_find(cols, *col);
2052  g_return_val_if_fail(node, FALSE);
2053  do
2054  {
2055  node = backward ? node->prev : node->next;
2056  if (!node)
2057  {
2058  wrapped = TRUE;
2059  node = backward ? g_list_last(cols) : cols;
2060  }
2061  c = GTK_TREE_VIEW_COLUMN(node->data);
2062  if (c && gtk_tree_view_column_get_visible(c))
2063  seen++;
2064  if (c == *col) break;
2065  }
2066  while (!seen);
2067 
2068  g_list_free(cols);
2069  *col = c;
2070  return wrapped;
2071 }
2072 
2073 gboolean
2074 gnc_tree_view_path_is_valid(GncTreeView *view, GtkTreePath *path)
2075 {
2076  GtkTreeView *tv = GTK_TREE_VIEW(view);
2077  GtkTreeModel *s_model;
2078  GtkTreeIter iter;
2079 
2080  s_model = gtk_tree_view_get_model(tv);
2081  return gtk_tree_model_get_iter(s_model, &iter, path);
2082 }
2083 
2084 void
2085 gnc_tree_view_keynav(GncTreeView *view, GtkTreeViewColumn **col,
2086  GtkTreePath *path, GdkEventKey *event)
2087 {
2088  GtkTreeView *tv = GTK_TREE_VIEW(view);
2089  gint depth;
2090  gboolean shifted;
2091 
2092  if (event->type != GDK_KEY_PRESS) return;
2093 
2094  switch (event->keyval)
2095  {
2096  case GDK_Tab:
2097  case GDK_ISO_Left_Tab:
2098  case GDK_KP_Tab:
2099  shifted = event->state & GDK_SHIFT_MASK;
2100  if (get_column_next_to(tv, col, shifted))
2101  {
2102  /* This is the end (or beginning) of the line, buddy. */
2103  depth = gtk_tree_path_get_depth(path);
2104  if (shifted)
2105  {
2106  if (!gtk_tree_path_prev(path) && depth > 1)
2107  {
2108  gtk_tree_path_up(path);
2109  }
2110  }
2111  else if (gtk_tree_view_row_expanded(tv, path))
2112  {
2113  gtk_tree_path_down(path);
2114  }
2115  else
2116  {
2117  gtk_tree_path_next(path);
2118  if (!gnc_tree_view_path_is_valid(view, path) && depth > 2)
2119  {
2120  gtk_tree_path_prev(path);
2121  gtk_tree_path_up(path);
2122  gtk_tree_path_next(path);
2123  }
2124  if (!gnc_tree_view_path_is_valid(view, path) && depth > 1)
2125  {
2126  gtk_tree_path_prev(path);
2127  gtk_tree_path_up(path);
2128  gtk_tree_path_next(path);
2129  }
2130  }
2131  }
2132  break;
2133 
2134  case GDK_Return:
2135  case GDK_KP_Enter:
2136  if (gtk_tree_view_row_expanded(tv, path))
2137  {
2138  gtk_tree_path_down(path);
2139  }
2140  else
2141  {
2142  depth = gtk_tree_path_get_depth(path);
2143  gtk_tree_path_next(path);
2144  if (!gnc_tree_view_path_is_valid(view, path) && depth > 1)
2145  {
2146  gtk_tree_path_prev(path);
2147  gtk_tree_path_up(path);
2148  gtk_tree_path_next(path);
2149  }
2150  }
2151  break;
2152  }
2153  return;
2154 }
2155 
void gnc_tree_view_set_sort_user_data(GncTreeView *view, GtkTreeModel *s_model)
Functions to load, save and get gui state.
void gnc_tree_view_expand_columns(GncTreeView *view, gchar *first_column_name,...)
GtkTreeViewColumn * gnc_tree_view_add_text_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *stock_icon_name, const gchar *sizing_text, gint model_data_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn)
void gnc_gobject_tracking_remember(GObject *object, GObjectClass *klass)
void gnc_gobject_tracking_forget(GObject *object)
common utilities for manipulating a GtkTreeView within gnucash
#define DEBUG(format, args...)
Definition: qoflog.h:255
gboolean gnc_tree_view_get_show_column_menu(GncTreeView *view)
GtkTreeViewColumn * gnc_tree_view_add_combo_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *sizing_text, gint model_data_column, gint model_visibility_column, GtkTreeModel *combo_tree_model, gint combo_model_text_column, GtkTreeIterCompareFunc column_sort_fn)
void gnc_tree_view_remove_state_information(GncTreeView *view)
void gnc_tree_view_set_show_column_menu(GncTreeView *view, gboolean visible)
GtkTreeViewColumn * gnc_tree_view_add_numeric_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *sizing_text, gint model_data_column, gint model_color_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn)
GtkTreeViewColumn * gnc_tree_view_find_column_by_name(GncTreeView *view, const gchar *wanted)
#define ENTER(format, args...)
Definition: qoflog.h:261
GKeyFile * gnc_state_get_current(void)
Definition: gnc-state.c:252
gint gnc_tree_view_append_column(GncTreeView *view, GtkTreeViewColumn *column)
GtkTreeViewColumn * gnc_tree_view_add_toggle_column(GncTreeView *view, const gchar *column_title, const gchar *column_short_title, const gchar *pref_name, gint model_data_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn, renderer_toggled toggle_edited_cb)
GtkCellRenderer * gnc_tree_view_column_get_renderer(GtkTreeViewColumn *column)
Gobject helper routines.
GtkTreeViewColumn * gnc_tree_view_add_date_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *stock_icon_name, const gchar *sizing_text, gint model_data_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn)
void gnc_tree_view_configure_columns(GncTreeView *view)
void gnc_tree_view_save_state(GncTreeView *view)
void gnc_tree_view_set_control_column_background(GncTreeView *view, gint column, GtkTreeCellDataFunc func)
Gnome specific utility functions.
All type declarations for the whole Gnucash engine.
const gchar * gnc_tree_view_get_state_section(GncTreeView *view)
void gnc_tree_view_set_state_section(GncTreeView *view, const gchar *section)
GType gnc_tree_view_get_type(void)
struct GncTreeViewPrivate GncTreeViewPrivate
#define LEAVE(format, args...)
Definition: qoflog.h:271
const gchar * QofLogModule
Definition: qofid.h:89