GnuCash c935c2f+
Loading...
Searching...
No Matches
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 <hampton@employees.org>
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 gnu@gnu.org
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-glib-utils.h"
44#include "gnc-gnome-utils.h"
45#include "gnc-gobject-utils.h"
46#include "gnc-cell-renderer-label.h"
47#include "gnc-cell-renderer-text-view.h"
48#include "gnc-state.h"
49#include "gnc-prefs.h"
50#include "dialog-utils.h"
51
52/* The actual state key for a particular column visibility. This is
53 * attached to the menu items that are in the column selection menu.
54 * Makes it very easy to update saved state when a menu item is toggled. */
55#define STATE_KEY "state-key"
56
57/* State keys within this particular saved state section. */
58#define STATE_KEY_SORT_COLUMN "sort_column"
59#define STATE_KEY_SORT_ORDER "sort_order"
60#define STATE_KEY_COLUMN_ORDER "column_order"
61
62/* Partial state keys within this particular saved state section. These
63 are appended to the various column names to create the actual
64 keys. */
65#define STATE_KEY_SUFF_VISIBLE "visible"
66#define STATE_KEY_SUFF_WIDTH "width"
67
68enum
69{
70 PROP_0,
71 PROP_STATE_SECTION,
72 PROP_SHOW_COLUMN_MENU,
73};
74
77/* This static indicates the debugging module that this .o belongs to. */
78static QofLogModule log_module = GNC_MOD_GUI;
79
80/**** Declarations ******************************************************/
81static void gnc_tree_view_constructed (GObject *object);
82static void gnc_tree_view_finalize (GObject *object);
83static void gnc_tree_view_destroy (GtkWidget *widget);
84static void gnc_tree_view_set_property (GObject *object,
85 guint prop_id,
86 const GValue *value,
87 GParamSpec *pspec);
88static void gnc_tree_view_get_property (GObject *object,
89 guint prop_id,
90 GValue *value,
91 GParamSpec *pspec);
92static gboolean gnc_tree_view_drop_ok_cb (GtkTreeView *view,
93 GtkTreeViewColumn *column,
94 GtkTreeViewColumn *prev_column,
95 GtkTreeViewColumn *next_column,
96 gpointer data);
97static void gnc_tree_view_build_column_menu (GncTreeView *view);
98static void gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
99 GncTreeView *view);
100static gchar *gnc_tree_view_get_sort_order (GncTreeView *view);
101static gchar *gnc_tree_view_get_sort_column (GncTreeView *view);
102static gchar **gnc_tree_view_get_column_order (GncTreeView *view,
103 gsize *length);
104
107typedef struct GncTreeViewPrivate
108{
109 /* Column selection menu related values */
110 GtkTreeViewColumn *column_menu_column;
111 GtkWidget *column_menu;
112 gboolean show_column_menu;
113 GtkWidget *column_menu_icon_box;
114
115 /* Sort callback model */
116 GtkTreeModel *sort_model;
117
118 /* Editing callback functions */
119 GFunc editing_started_cb;
120 GFunc editing_finished_cb;
121 gpointer editing_cb_data;
122
123 /* State related values */
124 gchar *state_section;
125 gboolean seen_state_visibility;
126 gulong columns_changed_cb_id;
127 gulong sort_column_changed_cb_id;
128 gulong size_allocate_cb_id;
130
131G_DEFINE_TYPE_WITH_CODE(GncTreeView, gnc_tree_view, GTK_TYPE_TREE_VIEW,
132 G_ADD_PRIVATE(GncTreeView))
133
134#define GNC_TREE_VIEW_GET_PRIVATE(o) \
135 ((GncTreeViewPrivate*)gnc_tree_view_get_instance_private((GncTreeView*)o))
136
137
138/************************************************************/
139/* g_object required functions */
140/************************************************************/
141
154static void
155gnc_tree_view_class_init (GncTreeViewClass *klass)
156{
157 GObjectClass *gobject_class;
158 GtkWidgetClass *gtkwidget_class;
159
160 gobject_class = G_OBJECT_CLASS(klass);
161 gtkwidget_class = GTK_WIDGET_CLASS(klass);
162
163 gobject_class->set_property = gnc_tree_view_set_property;
164 gobject_class->get_property = gnc_tree_view_get_property;
165
166 g_object_class_install_property (gobject_class,
167 PROP_STATE_SECTION,
168 g_param_spec_string ("state-section",
169 "State Section",
170 "The section name in the saved state to use for (re)storing the treeview's visual state (visible columns, sort order,...",
171 NULL,
172 G_PARAM_READWRITE));
173 g_object_class_install_property (gobject_class,
174 PROP_SHOW_COLUMN_MENU,
175 g_param_spec_boolean ("show-column-menu",
176 "Show Column Menu",
177 "Show the column menu so user can change what columns are visible.",
178 FALSE,
179 G_PARAM_READWRITE));
180
181 /* GObject signals */
182 gobject_class->constructed = gnc_tree_view_constructed;
183 gobject_class->finalize = gnc_tree_view_finalize;
184
185 /* GtkWidget signals */
186 gtkwidget_class->destroy = gnc_tree_view_destroy;
187}
188
189static void
190gnc_tree_view_update_grid_lines (gpointer prefs, gchar* pref, gpointer user_data)
191{
192 GncTreeView *view = user_data;
193 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(view), gnc_tree_view_get_grid_lines_pref ());
194}
195
196static gboolean
197gnc_tree_view_select_column_icon_cb (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
198{
199 GncTreeView *view = user_data;
200 GncTreeViewPrivate *priv;
201 GtkStyleContext *stylectxt = gtk_widget_get_style_context (widget);
202 GtkBorder padding;
203
204 // if the event button is not the right one, leave.
205 if (event->button != 1)
206 return FALSE;
207
208 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
209
210 gtk_style_context_get_padding (stylectxt, GTK_STATE_FLAG_NORMAL, &padding);
211
212 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
213 {
214 if (event->x < (gtk_widget_get_allocated_width (priv->column_menu_icon_box) + padding.left))
215 gnc_tree_view_select_column_cb (priv->column_menu_column, view);
216 }
217 else
218 {
219 if (event->x > (gtk_widget_get_allocated_width (widget) -
220 (gtk_widget_get_allocated_width (priv->column_menu_icon_box) + padding.right)))
221 gnc_tree_view_select_column_cb (priv->column_menu_column, view);
222 }
223 return FALSE;
224}
225
235static void
236gnc_tree_view_init (GncTreeView *view)
237{
238 GncTreeViewPrivate *priv;
239 GtkTreeViewColumn *column;
240 GtkWidget *sep, *icon;
241
242 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
243 priv->column_menu = NULL;
244 priv->show_column_menu = FALSE;
245 priv->sort_model = NULL;
246 priv->state_section = NULL;
247 priv->seen_state_visibility = FALSE;
248 priv->columns_changed_cb_id = 0;
249 priv->sort_column_changed_cb_id = 0;
250 priv->size_allocate_cb_id = 0;
251
252 // Set the name for this widget so it can be easily manipulated with css
253 gtk_widget_set_name (GTK_WIDGET(view), "gnc-id-tree-view");
254
255 /* Handle column drag and drop */
256 gtk_tree_view_set_column_drag_function (GTK_TREE_VIEW(view),
257 gnc_tree_view_drop_ok_cb, NULL, NULL);
258
259 // Set grid lines option to preference
260 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(view), gnc_tree_view_get_grid_lines_pref ());
261 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_HORIZONTAL,
262 gnc_tree_view_update_grid_lines, view);
263 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_VERTICAL,
264 gnc_tree_view_update_grid_lines, view);
265
266 /* Create the last column which contains the column selection
267 * widget. gnc_tree_view_add_text_column will do most of the
268 * work. */
269 icon = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
270
271 priv->column_menu_icon_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
272 gtk_box_set_homogeneous (GTK_BOX(priv->column_menu_icon_box), FALSE);
273
274 gtk_widget_set_margin_start (GTK_WIDGET(icon), 5);
275
276 gtk_box_pack_end (GTK_BOX(priv->column_menu_icon_box), icon, FALSE, FALSE, 0);
277
278 sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
279 gtk_box_pack_end (GTK_BOX(priv->column_menu_icon_box), sep, FALSE, FALSE, 0);
280
281 gtk_widget_show_all (priv->column_menu_icon_box);
282
283 column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
284 -1, -1, NULL);
285 g_object_set (G_OBJECT(column),
286 "clickable", TRUE,
287 "widget", priv->column_menu_icon_box,
288 "alignment", 1.0,
289 "expand", TRUE,
290 (gchar *)NULL);
291
292 priv->column_menu_column = column;
293
294 // get the actual column button by looking at the parents of the column_menu_icon
295 {
296 GtkWidget *mybox = gtk_widget_get_parent (icon);
297 GtkWidget *walign = gtk_widget_get_parent (mybox);
298 GtkWidget *box = gtk_widget_get_parent (walign);
299 GtkWidget *button = gtk_widget_get_parent (box);
300
301 if (!GTK_IS_BUTTON(button)) // just in case this order changes.
302 {
303 // this will fire for the whole column header
304 g_signal_connect (G_OBJECT(column), "clicked",
305 G_CALLBACK(gnc_tree_view_select_column_cb),
306 view);
307 }
308 else
309 {
310 /* this part will restrict the mouse click to just where the
311 icon is, tried using an eventbox but it would only work
312 some of the time */
313 gtk_widget_set_events (button, GDK_BUTTON_PRESS_MASK);
314
315 g_signal_connect (G_OBJECT(button), "button_press_event",
316 G_CALLBACK(gnc_tree_view_select_column_icon_cb),
317 view);
318 }
319 }
320 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
321}
322
329 static void
330gnc_tree_view_constructed (GObject *obj)
331{
333
334 G_OBJECT_CLASS (gnc_tree_view_parent_class)->constructed (obj);
335}
336
347static void
348gnc_tree_view_finalize (GObject *object)
349{
350 ENTER("view %p", object);
351 g_return_if_fail (object != NULL);
352 g_return_if_fail (GNC_IS_TREE_VIEW(object));
353
355
356 G_OBJECT_CLASS(gnc_tree_view_parent_class)->finalize (object);
357 LEAVE(" ");
358}
359
371static void
372gnc_tree_view_destroy (GtkWidget *widget)
373{
374 GncTreeView *view;
375 GncTreeViewPrivate *priv;
376
377 ENTER("view %p", widget);
378 g_return_if_fail (widget != NULL);
379 g_return_if_fail (GNC_IS_TREE_VIEW(widget));
380
381 view = GNC_TREE_VIEW(widget);
382
383 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_HORIZONTAL,
384 gnc_tree_view_update_grid_lines, view);
385 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_VERTICAL,
386 gnc_tree_view_update_grid_lines, view);
387
388 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
389
390 if (priv->state_section)
391 {
393 }
394 g_free (priv->state_section);
395 priv->state_section = NULL;
396
397 if (priv->column_menu)
398 {
399 DEBUG("removing column selection menu");
400 g_object_unref (priv->column_menu);
401 priv->column_menu = NULL;
402 }
403
404 GTK_WIDGET_CLASS(gnc_tree_view_parent_class)->destroy (widget);
405 LEAVE(" ");
406}
407
410/************************************************************/
411/* g_object other functions */
412/************************************************************/
413
424static void
425gnc_tree_view_get_property (GObject *object,
426 guint prop_id,
427 GValue *value,
428 GParamSpec *pspec)
429{
430 GncTreeView *view = GNC_TREE_VIEW(object);
431 GncTreeViewPrivate *priv;
432
433 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
434 switch (prop_id)
435 {
436 case PROP_STATE_SECTION:
437 g_value_set_string (value, priv->state_section);
438 break;
439 case PROP_SHOW_COLUMN_MENU:
440 g_value_set_boolean (value, priv->show_column_menu);
441 break;
442 default:
443 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
444 break;
445 }
446}
447
448
457static void
458gnc_tree_view_set_property (GObject *object,
459 guint prop_id,
460 const GValue *value,
461 GParamSpec *pspec)
462{
463 GncTreeView *view = GNC_TREE_VIEW(object);
464
465 switch (prop_id)
466 {
467 case PROP_STATE_SECTION:
468 gnc_tree_view_set_state_section (view, g_value_get_string (value));
469 break;
470 case PROP_SHOW_COLUMN_MENU:
471 gnc_tree_view_set_show_column_menu (view, g_value_get_boolean (value));
472 break;
473 default:
474 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
475 break;
476 }
477}
478
481/************************************************************/
482/* Auxiliary Functions */
483/************************************************************/
502static GtkTreeViewColumn *
503view_column_find_by_model_id (GncTreeView *view,
504 const gint wanted)
505{
506 GtkTreeViewColumn *column, *found = NULL;
507 GList *column_list, *tmp;
508 gint id;
509
510 // ENTER("view %p, name %s", view, name);
511 column_list = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
512 for (tmp = column_list; tmp; tmp = g_list_next (tmp))
513 {
514 column = tmp->data;
515 id = GPOINTER_TO_INT(g_object_get_data (G_OBJECT(column), MODEL_COLUMN));
516 if (id != wanted)
517 continue;
518 found = column;
519 break;
520 }
521 g_list_free (column_list);
522
523 // LEAVE("column %p", found);
524 return found;
525}
526
537GtkTreeViewColumn *
539 const gchar *wanted)
540{
541 GtkTreeViewColumn *column, *found = NULL;
542 GList *column_list, *tmp;
543 const gchar *name;
544
545 // ENTER("view %p, wanted %s", view, wanted);
546 column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
547 for (tmp = column_list; tmp; tmp = g_list_next (tmp))
548 {
549 column = tmp->data;
550 name = g_object_get_data (G_OBJECT(column), PREF_NAME);
551 if (!name || (strcmp(name, wanted) != 0))
552 continue;
553 found = column;
554 break;
555 }
556 g_list_free (column_list);
557
558 // LEAVE("column %p", found);
559 return found;
560}
561
564/************************************************************/
565/* Tree Callbacks */
566/************************************************************/
567
596static gboolean
597gnc_tree_view_drop_ok_cb (GtkTreeView *view,
598 GtkTreeViewColumn *column,
599 GtkTreeViewColumn *prev_column,
600 GtkTreeViewColumn *next_column,
601 gpointer data)
602{
603 const gchar *pref_name;
604
605 /* Should we allow a drop at the left side of the tree view before
606 * the widget to open a new display level? I can think of cases
607 * where the user might want to do this with a checkbox column. */
608 if (prev_column == NULL)
609 return TRUE;
610
611 /* Do not allow a drop at the right side of the tree view after the
612 * column selection widget. */
613 if (next_column == NULL)
614 return FALSE;
615
616 /* Columns without pref names are considered fixed at the right hand
617 * side of the view. At the time of this writing, the only two are
618 * the column where the "column selection widget" is stored, and the
619 * "padding" column to the left of that where extra view space ends
620 * up. */
621 pref_name = g_object_get_data (G_OBJECT(prev_column), PREF_NAME);
622 if (!pref_name)
623 return FALSE;
624
625 /* Everything else is allowed. */
626 return TRUE;
627}
628
631/************************************************************/
632/* State Setup / Callbacks */
633/************************************************************/
634
657static gboolean
658gnc_tree_view_column_visible (GncTreeView *view,
659 GtkTreeViewColumn *column,
660 const gchar *pref_name)
661{
662 GncTreeViewPrivate *priv;
663 gboolean visible;
664 const gchar *col_name = pref_name;
665
666 ENTER("column %p, name %s", column, pref_name ? pref_name : "(null)");
667 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
668 if (column)
669 {
670 if (g_object_get_data (G_OBJECT(column), ALWAYS_VISIBLE))
671 {
672 LEAVE("1, first column");
673 return TRUE;
674 }
675 col_name = g_object_get_data (G_OBJECT(column), PREF_NAME);
676 DEBUG("col_name is %s", col_name ? col_name : "(null)");
677 }
678
679 if (!col_name)
680 {
681 LEAVE("1, no pref name");
682 return TRUE;
683 }
684
685 /* Using saved state ? */
686 if (priv->state_section)
687 {
688 GKeyFile *state_file = gnc_state_get_current ();
689 gchar *key = g_strdup_printf ("%s_%s", col_name, STATE_KEY_SUFF_VISIBLE);
690
691 if (g_key_file_has_key (state_file, priv->state_section, key, NULL))
692 {
693 visible = g_key_file_get_boolean (state_file, priv->state_section, key, NULL);
694 g_free (key);
695 LEAVE("%d, state defined visibility", visible);
696 return visible;
697 }
698 }
699
700 /* Check the default columns list */
701 visible = column ?
702 (g_object_get_data (G_OBJECT(column), DEFAULT_VISIBLE) != NULL) : FALSE;
703 LEAVE("defaults says %d", visible);
704 return visible;
705}
706
717static void
718gnc_tree_view_update_visibility (GtkTreeViewColumn *column,
719 GncTreeView *view)
720{
721 gboolean visible;
722
723 g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN(column));
724 g_return_if_fail (GNC_IS_TREE_VIEW(view));
725
726 ENTER(" ");
727 visible = gnc_tree_view_column_visible (view, column, NULL);
728 gtk_tree_view_column_set_visible (column, visible);
729 LEAVE("made %s", visible ? "visible" : "invisible");
730}
731
742static gchar *
743gnc_tree_view_get_sort_order (GncTreeView *view)
744{
745 GtkTreeModel *s_model;
746 GtkSortType order;
747 gint current;
748 gchar *order_str = NULL;
749
750 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
751 if (!s_model)
752 return NULL; /* no model, so sort order doesn't make sense */
753
754 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
755 &current, &order))
756 return NULL; /* Model is not sorted, return */
757
758 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(s_model),
759 current, order);
760 order_str = g_strdup (order == GTK_SORT_ASCENDING ? "ascending" : "descending");
761 DEBUG("current sort_order is %s", order_str);
762 return order_str;
763}
764
774static gchar *
775gnc_tree_view_get_sort_column (GncTreeView *view)
776{
777 GtkTreeModel *s_model;
778 GtkTreeViewColumn *column;
779 GtkSortType order;
780 gint current;
781 const gchar *name;
782
783 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
784 if (!s_model)
785 return NULL; /* no model -> no sort column */
786
787 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
788 &current, &order))
789 return NULL; /* model not sorted */
790
791 column = view_column_find_by_model_id (view, current);
792 if (!column)
793 return NULL; /* column not visible, can't be used for sorting */
794
795 name = g_object_get_data (G_OBJECT(column), PREF_NAME);
796 DEBUG("current sort column is %s", name ? name : "(NULL)");
797 return g_strdup (name);
798}
799
800
801
812static gchar **
813gnc_tree_view_get_column_order (GncTreeView *view,
814 gsize *length)
815{
816 const GList *tmp;
817 GList *columns;
818 gulong num_cols = 0;
819 gchar *col_names = NULL;
820 gchar **col_str_list;
821
822 /* First, convert from names to pointers */
823 ENTER(" ");
824
825 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
826 for (tmp = columns; tmp; tmp = g_list_next(tmp))
827 {
828 GtkTreeViewColumn *column = tmp->data;
829 const gchar *name = g_object_get_data (G_OBJECT(column), PREF_NAME);
830 if (!col_names)
831 col_names = g_strdup (name);
832 else
833 {
834 gchar *col_names_prev = col_names;
835 col_names = g_strjoin (";", col_names_prev, name, NULL);
836 g_free (col_names_prev);
837 }
838 num_cols++;
839 }
840 //DEBUG ("got %lu columns: %s", num_cols, col_names);
841 col_str_list = g_strsplit (col_names, ";", 0);
842
843 /* Clean up */
844 g_list_free (columns);
845 g_free (col_names);
846
847 LEAVE("column order get");
848 *length = num_cols;
849 return col_str_list;
850}
851
862static void
863gnc_tree_view_set_sort_order (GncTreeView *view,
864 const gchar *name)
865{
866 GtkTreeModel *s_model;
867 GtkSortType order = GTK_SORT_ASCENDING;
868 gint current;
869
870 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
871 if (!s_model)
872 return;
873 if (g_strcmp0 (name, "descending") == 0)
874 order = GTK_SORT_DESCENDING;
875 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
876 &current, NULL))
877 current = GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID;
878 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(s_model),
879 current, order);
880 DEBUG("sort_order set to %s", order == GTK_SORT_ASCENDING ? "ascending" : "descending");
881}
882
891static void
892gnc_tree_view_set_sort_column (GncTreeView *view,
893 const gchar *name)
894{
895 GtkTreeModel *s_model;
896 GtkTreeViewColumn *column;
897 GtkSortType order;
898 gint model_column, current;
899
900 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
901 if (!s_model)
902 return;
903
904 column = gnc_tree_view_find_column_by_name (view, name);
905 if (!column)
906 {
907 gtk_tree_sortable_set_sort_column_id (
908 GTK_TREE_SORTABLE(s_model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
909 GTK_SORT_ASCENDING);
910 return;
911 }
912
913 model_column =
914 GPOINTER_TO_INT(g_object_get_data (G_OBJECT(column), MODEL_COLUMN));
915 if (model_column == GNC_TREE_VIEW_COLUMN_DATA_NONE)
916 return;
917
918 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
919 &current, &order))
920 order = GTK_SORT_ASCENDING;
921
922 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(s_model),
923 model_column, order);
924 DEBUG("sort column set to %s", name);
925}
926
939static void
940gnc_tree_view_set_column_order (GncTreeView *view,
941 gchar **column_names,
942 gsize length)
943{
944 GtkTreeViewColumn *column, *prev;
945 const GSList *tmp;
946 GSList *columns;
947 gsize idx;
948
949 /* First, convert from names to pointers */
950 ENTER(" ");
951 columns = NULL;
952 for (idx = 0; idx < length; idx++)
953 {
954 const gchar *name = column_names [idx];
955 column = gnc_tree_view_find_column_by_name (view, name);
956 if (!column)
957 continue;
958 columns = g_slist_append (columns, column);
959 }
960
961 /* Then reorder the columns */
962 for (prev = NULL, tmp = columns; tmp; tmp = g_slist_next (tmp))
963 {
964 column = tmp->data;
965 gtk_tree_view_move_column_after (GTK_TREE_VIEW(view), column, prev);
966 prev = column;
967 }
968
969 /* Clean up */
970 g_slist_free (columns);
971 LEAVE("column order set");
972}
973
983{
984 GncTreeViewPrivate *priv;
985 GKeyFile *state_file = gnc_state_get_current ();
986
987 ENTER(" ");
988 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
989 if (!priv->state_section)
990 {
991 LEAVE("no state section");
992 return;
993 }
994
995 g_key_file_remove_group (state_file, priv->state_section, NULL);
996 g_free (priv->state_section);
997 priv->state_section = NULL;
998 LEAVE(" ");
999}
1000
1008void
1010 const gchar *section)
1011{
1012 GncTreeViewPrivate *priv;
1013 GKeyFile *state_file;
1014
1015 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1016
1017 ENTER("view %p, section %s", view, section);
1018
1019 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1020
1021 /* Drop any previous state section */
1022 if (priv->state_section)
1024
1025 if (!section)
1026 {
1027 LEAVE("cleared state section");
1028 return;
1029 }
1030
1031 /* Catch changes in state. Propagate to view. */
1032 priv->state_section = g_strdup (section);
1033
1034 state_file = gnc_state_get_current ();
1035 if (g_key_file_has_group (state_file, priv->state_section))
1036 {
1037 gsize num_keys, idx;
1038 gchar **keys = g_key_file_get_keys (state_file, priv->state_section, &num_keys, NULL);
1039 for (idx = 0; idx < num_keys; idx++)
1040 {
1041 gchar *key = keys[idx];
1042 if (g_strcmp0 (key, STATE_KEY_SORT_COLUMN) == 0)
1043 {
1044 gchar *name = g_key_file_get_string (state_file, priv->state_section,
1045 key, NULL);
1046 gnc_tree_view_set_sort_column (view, name);
1047 g_free (name);
1048 }
1049 else if (g_strcmp0 (key, STATE_KEY_SORT_ORDER) == 0)
1050 {
1051 gchar *name = g_key_file_get_string (state_file, priv->state_section,
1052 key, NULL);
1053 gnc_tree_view_set_sort_order (view, name);
1054 g_free (name);
1055 }
1056 else if (g_strcmp0 (key, STATE_KEY_COLUMN_ORDER) == 0)
1057 {
1058 gsize length;
1059 gchar **columns = g_key_file_get_string_list (state_file, priv->state_section,
1060 key, &length, NULL);
1061 gnc_tree_view_set_column_order (view, columns, length);
1062 g_strfreev (columns);
1063 }
1064 else
1065 {
1066 /* Make a copy of the local part of the key so it can be split
1067 * into column name and key type */
1068 gboolean known = FALSE;
1069 gchar *column_name = g_strdup (key);
1070 gchar *type_name = g_strrstr (column_name, "_");
1071
1072 if (type_name != NULL) //guard against not finding '_'
1073 {
1074 *type_name++ = '\0';
1075
1076 if (g_strcmp0 (type_name, STATE_KEY_SUFF_VISIBLE) == 0)
1077 {
1078 GtkTreeViewColumn *column = gnc_tree_view_find_column_by_name (view, column_name);
1079 if (column)
1080 {
1081 known = TRUE;
1082 if (!g_object_get_data (G_OBJECT (column), ALWAYS_VISIBLE))
1083 {
1084 gtk_tree_view_column_set_visible (column,
1085 g_key_file_get_boolean (state_file, priv->state_section, key, NULL));
1086 }
1087 }
1088 }
1089 else if (g_strcmp0 (type_name, STATE_KEY_SUFF_WIDTH) == 0)
1090 {
1091 gint width = g_key_file_get_integer (state_file, priv->state_section, key, NULL);
1092 GtkTreeViewColumn *column = gnc_tree_view_find_column_by_name (view, column_name);
1093 if (column)
1094 {
1095 known = TRUE;
1096 if (width && (width != gtk_tree_view_column_get_width (column)))
1097 {
1098 gtk_tree_view_column_set_fixed_width (column, width);
1099 }
1100 }
1101 }
1102 if (!known)
1103 DEBUG ("Ignored key %s", key);
1104
1105 g_free (column_name);
1106 }
1107 }
1108 }
1109 g_strfreev (keys);
1110 }
1111
1112 /* Rebuild the column visibility menu */
1113 gnc_tree_view_build_column_menu (view);
1114 LEAVE ("set state section");
1115}
1116
1123const gchar *
1125{
1126 GncTreeViewPrivate *priv;
1127
1128 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1129
1130 priv = GNC_TREE_VIEW_GET_PRIVATE (view);
1131 return priv->state_section;
1132}
1133
1134void gnc_tree_view_save_state (GncTreeView *view)
1135{
1136 GncTreeViewPrivate *priv;
1137
1138 ENTER("view %p", view);
1139 g_return_if_fail (view != NULL);
1140 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1141
1142 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1143
1144 if (priv->state_section)
1145 {
1146 /* Save state. Only store non-default values when possible. */
1147 GList *column_list, *tmp;
1148 GKeyFile *state_file = gnc_state_get_current();
1149 gsize num_cols = 0;
1150 gchar *sort_column = gnc_tree_view_get_sort_column (view);
1151 gchar *sort_order = gnc_tree_view_get_sort_order (view);
1152 gchar **col_order = gnc_tree_view_get_column_order (view, &num_cols);
1153
1154 /* Default sort column is the name column */
1155 if (sort_column && (g_strcmp0 (sort_column, "name") != 0))
1156 g_key_file_set_string (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, sort_column);
1157 else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, NULL))
1158 g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, NULL);
1159 g_free (sort_column);
1160
1161
1162 /* Default sort order is "ascending" */
1163 if (g_strcmp0 (sort_order, "descending") == 0)
1164 g_key_file_set_string (state_file, priv->state_section, STATE_KEY_SORT_ORDER, sort_order);
1165 else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_SORT_ORDER, NULL))
1166 g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_SORT_ORDER, NULL);
1167 g_free (sort_order);
1168
1169 if (col_order && (num_cols > 0))
1170 g_key_file_set_string_list (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER,
1171 (const gchar**) col_order, num_cols);
1172 else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER, NULL))
1173 g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER, NULL);
1174
1175 g_strfreev (col_order);
1176
1177
1178 // ENTER("view %p, wanted %s", view, wanted);
1179 column_list = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1180 for (tmp = column_list; tmp; tmp = g_list_next (tmp))
1181 {
1182 GtkTreeViewColumn *column = tmp->data;
1183 gchar *key=NULL;
1184 const gchar *name = g_object_get_data (G_OBJECT(column), PREF_NAME);
1185 if (!name)
1186 continue;
1187
1188 if (!g_object_get_data (G_OBJECT(column), ALWAYS_VISIBLE))
1189 {
1190 key = g_strjoin ("_", name, STATE_KEY_SUFF_VISIBLE, NULL);
1191 g_key_file_set_boolean (state_file, priv->state_section, key,
1192 gtk_tree_view_column_get_visible (column));
1193 g_free (key);
1194 }
1195
1196 key = g_strjoin ("_", name, STATE_KEY_SUFF_WIDTH, NULL);
1197 if (g_object_get_data (G_OBJECT(column), "default-width") &&
1198 (GPOINTER_TO_INT((g_object_get_data (G_OBJECT(column), "default-width")))
1199 != gtk_tree_view_column_get_width (column)))
1200 {
1201 g_key_file_set_integer (state_file, priv->state_section, key,
1202 gtk_tree_view_column_get_width (column));
1203 }
1204 else if (g_key_file_has_key (state_file, priv->state_section, key, NULL))
1205 g_key_file_remove_key (state_file, priv->state_section, key, NULL);
1206 g_free (key);
1207 }
1208 g_list_free (column_list);
1209 }
1210
1211 LEAVE(" ");
1212}
1213
1214
1217/************************************************************/
1218/* Column Selection Menu */
1219/************************************************************/
1220
1239static void
1240gnc_tree_view_create_menu_item (GtkTreeViewColumn *column,
1241 GncTreeView *view)
1242{
1243 GncTreeViewPrivate *priv;
1244 GtkWidget *widget;
1245 const gchar *column_name, *pref_name;
1246 gchar *key;
1247 GBinding *binding;
1248
1249 // ENTER("view %p, column %p", view, column);
1250 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1251 if (!priv->state_section)
1252 {
1253 // LEAVE("no state section");
1254 return;
1255 }
1256
1257 pref_name = g_object_get_data (G_OBJECT(column), PREF_NAME);
1258 if (!pref_name)
1259 {
1260 // LEAVE("column has no pref_name");
1261 return;
1262 }
1263
1264 /* Create the menu if we don't have one already */
1265 if (!priv->column_menu)
1266 {
1267 priv->column_menu = gtk_menu_new();
1268 g_object_ref_sink (priv->column_menu);
1269 }
1270
1271 /* Create the check menu item */
1272 column_name = g_object_get_data (G_OBJECT(column), REAL_TITLE);
1273 if (!column_name)
1274 column_name = gtk_tree_view_column_get_title (column);
1275 widget = gtk_check_menu_item_new_with_label (column_name);
1276 gtk_menu_shell_append (GTK_MENU_SHELL(priv->column_menu), widget);
1277
1278 /* Should never be able to hide the first column */
1279 if (g_object_get_data (G_OBJECT(column), ALWAYS_VISIBLE))
1280 {
1281 g_object_set_data (G_OBJECT(widget), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
1282 gtk_widget_set_sensitive (widget, FALSE);
1283 }
1284
1285 binding = g_object_bind_property (G_OBJECT(widget), "active", G_OBJECT(column), "visible", 0);
1286 g_object_set_data (G_OBJECT(widget), "column-binding", binding);
1287
1288 /* Store data on the widget for callbacks */
1289 key = g_strdup_printf ("%s_%s", pref_name, STATE_KEY_SUFF_VISIBLE);
1290 g_object_set_data_full (G_OBJECT(widget), STATE_KEY, key, g_free);
1291 // LEAVE(" ");
1292}
1293
1294static gint
1295column_menu_sort (GtkTreeViewColumn *columna, GtkTreeViewColumn *columnb)
1296{
1297 const gchar *column_namea = g_object_get_data (G_OBJECT(columna), REAL_TITLE);
1298 const gchar *column_nameb = g_object_get_data (G_OBJECT(columnb), REAL_TITLE);
1299
1300 if (!column_namea)
1301 column_namea = gtk_tree_view_column_get_title (columna);
1302
1303 if (!column_nameb)
1304 column_nameb = gtk_tree_view_column_get_title (columnb);
1305
1306 return safe_utf8_collate (column_namea, column_nameb);
1307}
1308
1320static void
1321gnc_tree_view_build_column_menu (GncTreeView *view)
1322{
1323 GncTreeViewPrivate *priv;
1324 GList *column_list;
1325
1326 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1327
1328 ENTER("view %p", view);
1329 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1330
1331 /* Destroy any old menu */
1332 if (priv->column_menu)
1333 {
1334 g_object_unref (priv->column_menu);
1335 priv->column_menu = NULL;
1336 }
1337
1338 if (priv->show_column_menu && priv->state_section)
1339 {
1340 /* Show the menu popup button */
1341 if (priv->column_menu_column)
1342 gtk_tree_view_column_set_visible (priv->column_menu_column, TRUE);
1343
1344 /* Now build a new menu */
1345 column_list = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1346 column_list = g_list_sort (column_list, (GCompareFunc)column_menu_sort);
1347 g_list_foreach (column_list, (GFunc)gnc_tree_view_create_menu_item, view);
1348 g_list_free (column_list);
1349 }
1350 else
1351 {
1352 /* Hide the menu popup button */
1353 if (priv->column_menu_column)
1354 gtk_tree_view_column_set_visible (priv->column_menu_column, FALSE);
1355 }
1356 LEAVE("menu: show %d, section %s", priv->show_column_menu,
1357 priv->state_section ? priv->state_section : "(null)");
1358}
1359
1369static void
1370gnc_tree_view_update_column_menu_item (GtkCheckMenuItem *checkmenuitem,
1371 GncTreeView *view)
1372{
1373 gboolean visible;
1374
1375 g_return_if_fail (GTK_IS_CHECK_MENU_ITEM(checkmenuitem));
1376 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1377
1378 if (g_object_get_data (G_OBJECT(checkmenuitem), ALWAYS_VISIBLE))
1379 {
1380 visible = TRUE;
1381 }
1382 else
1383 {
1384 GBinding *binding = g_object_get_data (G_OBJECT(checkmenuitem), "column-binding");
1385 GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(g_binding_dup_target (binding));
1386
1387 visible = gtk_tree_view_column_get_visible (column);
1388 g_object_unref(column);
1389 }
1390 gtk_check_menu_item_set_active (checkmenuitem, visible);
1391}
1392
1405static void
1406gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
1407 GncTreeView *view)
1408{
1409 GncTreeViewPrivate *priv;
1410 GtkWidget *menu;
1411
1412 g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN(column));
1413 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1414
1415 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1416 menu = priv->column_menu;
1417 if (!menu)
1418 return;
1419
1420 /* Synchronize the menu before display */
1421 gtk_container_foreach (GTK_CONTAINER(menu),
1422 (GtkCallback)gnc_tree_view_update_column_menu_item,
1423 view);
1424
1425 /* Ensure all components are visible */
1426 gtk_widget_show_all (menu);
1427
1428 /* Pop the menu up at the button */
1429 gtk_menu_popup_at_pointer (GTK_MENU(priv->column_menu), NULL);
1430}
1431
1432
1433void gnc_tree_view_expand_columns (GncTreeView *view,
1434 gchar *first_column_name,
1435 ...)
1436{
1437 GtkTreeViewColumn *column;
1438 GList *columns, *tmp;
1439 gchar *name, *pref_name;
1440 va_list args;
1441
1442 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1443 ENTER(" ");
1444 va_start (args, first_column_name);
1445 name = first_column_name;
1446
1447 /* First disable the expand property on all (non-infrastructure) columns. */
1448 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1449 for (tmp = columns; tmp; tmp = g_list_next (tmp))
1450 {
1451 column = tmp->data;
1452 pref_name = g_object_get_data (G_OBJECT(column), PREF_NAME);
1453 if (pref_name != NULL)
1454 gtk_tree_view_column_set_expand (column, FALSE);
1455 }
1456 g_list_free(columns);
1457
1458 /* Now enable it on the requested columns. */
1459 while (name != NULL)
1460 {
1461 column = gnc_tree_view_find_column_by_name (view, name);
1462 if (column != NULL)
1463 {
1464 gtk_tree_view_column_set_expand (column, TRUE);
1465 }
1466 name = va_arg (args, gchar*);
1467 }
1468 va_end (args);
1469
1470 LEAVE(" ");
1471}
1472
1473
1474/* Links the cell backgrounds of the two control columns to the model or
1475 cell data function */
1476static void
1477update_control_cell_renderers_background (GncTreeView *view, GtkTreeViewColumn *col,
1478 gint column, GtkTreeCellDataFunc func )
1479{
1480 GList *renderers;
1481 GtkCellRenderer *cell;
1482 GList *node;
1483
1484 renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(col));
1485
1486 /* Update the cell background in the list of renderers */
1487 for (node = renderers; node; node = node->next)
1488 {
1489 cell = node->data;
1490 if (func == NULL)
1491 gtk_tree_view_column_add_attribute (col, cell, "cell-background", column);
1492 else
1493 gtk_tree_view_column_set_cell_data_func (col, cell, func, view, NULL);
1494 }
1495 g_list_free (renderers);
1496}
1497
1498
1499/* This function links the cell backgrounds of the two control columns to a column
1500 in the model that has color strings or a cell data function */
1501void
1502gnc_tree_view_set_control_column_background (GncTreeView *view, gint column, GtkTreeCellDataFunc func )
1503{
1504 GncTreeViewPrivate *priv;
1505
1506 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1507
1508 ENTER("view %p, column %d, func %p", view, column, func);
1509 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1510
1511 update_control_cell_renderers_background (view, priv->column_menu_column, column, func);
1512
1513 LEAVE(" ");
1514}
1515
1516
1517/* This allows the columns to be setup without the model connected */
1518//FIXME I think this should be specified as a parameter to the add columns functions...
1519void
1520gnc_tree_view_set_sort_user_data (GncTreeView *view, GtkTreeModel *s_model)
1521{
1522 GncTreeViewPrivate *priv;
1523
1524 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1525
1526 ENTER("view %p, sort_model %p", view, s_model);
1527 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1528
1529 priv->sort_model = s_model;
1530 LEAVE(" ");
1531}
1532
1533
1540void
1542 gboolean visible)
1543{
1544 GncTreeViewPrivate *priv;
1545
1546 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1547
1548 ENTER("view %p, show menu %d", view, visible);
1549 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1550 priv->show_column_menu = visible;
1551 gnc_tree_view_build_column_menu (view);
1552 LEAVE(" ");
1553}
1554
1561gboolean
1563{
1564 GncTreeViewPrivate *priv;
1565
1566 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), FALSE);
1567
1568 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1569 return (priv->show_column_menu);
1570}
1571
1574/************************************************************/
1575/* Tree View Creation */
1576/************************************************************/
1577
1578static gint
1579gnc_tree_view_count_visible_columns (GncTreeView *view)
1580{
1581 GList *columns, *node;
1582 gint count = 0;
1583
1584 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1585 for (node = columns; node; node = node->next)
1586 {
1587 GtkTreeViewColumn *col = GTK_TREE_VIEW_COLUMN(node->data);
1588
1589 if (g_object_get_data (G_OBJECT(col), DEFAULT_VISIBLE) ||
1590 g_object_get_data (G_OBJECT(col), ALWAYS_VISIBLE))
1591 count++;
1592 }
1593 g_list_free (columns);
1594 return count;
1595}
1596
1597void
1599{
1600 GncTreeViewPrivate *priv;
1601 GtkTreeViewColumn *column;
1602 GList *columns;
1603 gboolean hide_menu_column;
1604
1605 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1606
1607 ENTER(" ");
1608
1609 /* Update the view and saved state */
1610 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1611 g_list_foreach (columns, (GFunc)gnc_tree_view_update_visibility, view);
1612 g_list_free (columns);
1613
1614 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1615 if (priv->state_section)
1616 priv->seen_state_visibility = TRUE;
1617
1618 /* If only the first column is visible, hide the spacer and make that
1619 * column expand. */
1620 hide_menu_column = (gnc_tree_view_count_visible_columns (view) == 1);
1621 column = gtk_tree_view_get_column (GTK_TREE_VIEW(view), 0);
1622 gtk_tree_view_column_set_expand (column, hide_menu_column);
1623 gtk_tree_view_column_set_visible (priv->column_menu_column, !hide_menu_column);
1624
1625 LEAVE(" ");
1626}
1627
1628
1658static void
1659gnc_tree_view_column_properties (GncTreeView *view,
1660 GtkTreeViewColumn *column,
1661 const gchar *pref_name,
1662 gint data_column,
1663 gint default_width,
1664 gboolean resizable,
1665 GtkTreeIterCompareFunc column_sort_fn)
1666{
1667 GncTreeViewPrivate *priv;
1668 GtkTreeModel *s_model;
1669 gboolean visible;
1670 int width = 0;
1671
1672 /* Set data used by other functions */
1673 if (pref_name)
1674 g_object_set_data (G_OBJECT(column), PREF_NAME, (gpointer)pref_name);
1675 if (data_column == 0)
1676 g_object_set_data (G_OBJECT(column), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
1677 g_object_set_data (G_OBJECT(column), MODEL_COLUMN,
1678 GINT_TO_POINTER(data_column));
1679
1680 /* Get visibility */
1681 visible = gnc_tree_view_column_visible (view, NULL, pref_name);
1682
1683 /* Set column attributes (without the sizing) */
1684 g_object_set (G_OBJECT(column),
1685 "visible", visible,
1686 "resizable", resizable && pref_name != NULL,
1687 "reorderable", pref_name != NULL,
1688 NULL);
1689
1690 /* Get width */
1691 if (default_width == 0)
1692 {
1693 /* Set the sizing column attributes */
1694 g_object_set (G_OBJECT(column),
1695 "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
1696 NULL);
1697 }
1698 else
1699 {
1700
1701 /* If saved state comes back with a width of zero (or there is no saved
1702 * state width) the use the default width for the column. Allow for
1703 * padding L and R of the displayed data. */
1704 if (width == 0)
1705 width = default_width + 10;
1706 if (width == 0)
1707 width = 10;
1708
1709 /* Set the sizing column attributes (including fixed-width) */
1710 g_object_set (G_OBJECT(column),
1711 "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
1712 "fixed-width", width,
1713 NULL);
1714 /* Save the initially calculated preferred width for later
1715 * comparison to the actual width when saving state. Can't
1716 * use the "fixed-width" property for that because it changes
1717 * when the user resizes the column.
1718 */
1719 g_object_set_data (G_OBJECT(column),
1720 "default-width", GINT_TO_POINTER(width));
1721 }
1722
1723 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
1724 if (GTK_IS_TREE_SORTABLE(s_model))
1725 {
1726 gtk_tree_view_column_set_sort_column_id (column, data_column);
1727 if (column_sort_fn)
1728 {
1729 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(s_model),
1730 data_column, column_sort_fn,
1731 GINT_TO_POINTER(data_column),
1732 NULL /* destroy fn */);
1733 }
1734 }
1735
1736 // Used in registers, sort model not connected to view yet
1737 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1738 if (priv->sort_model != NULL)
1739 {
1740 gtk_tree_view_column_set_sort_column_id (column, data_column);
1741 if (column_sort_fn)
1742 {
1743 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(priv->sort_model),
1744 data_column, column_sort_fn,
1745 view,
1746 NULL /* destroy fn */);
1747 }
1748 }
1749
1750 /* Add to the column selection menu */
1751 if (pref_name)
1752 {
1753 gnc_tree_view_create_menu_item (column, view);
1754 }
1755}
1756
1767GtkTreeViewColumn *
1769 const gchar *column_title,
1770 const gchar *column_short_title,
1771 const gchar *pref_name,
1772 gint model_data_column,
1773 gint model_visibility_column,
1774 GtkTreeIterCompareFunc column_sort_fn,
1775 renderer_toggled toggle_edited_cb)
1776{
1777 GtkTreeViewColumn *column;
1778 GtkCellRenderer *renderer;
1779
1780 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1781
1782 renderer = gtk_cell_renderer_toggle_new ();
1783 if (!toggle_edited_cb)
1784 {
1785 gtk_cell_renderer_toggle_set_activatable (GTK_CELL_RENDERER_TOGGLE(renderer), FALSE);
1786 }
1787 column =
1788 gtk_tree_view_column_new_with_attributes (column_short_title,
1789 renderer,
1790 "active", model_data_column,
1791 NULL);
1792
1793 /* Add the full title to the object for menu creation */
1794 g_object_set_data_full (G_OBJECT(column), REAL_TITLE,
1795 g_strdup(column_title), g_free);
1796 if (toggle_edited_cb)
1797 g_signal_connect (G_OBJECT(renderer), "toggled",
1798 G_CALLBACK(toggle_edited_cb), view);
1799
1800 if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1801 gtk_tree_view_column_add_attribute (column, renderer,
1802 "visible", model_visibility_column);
1803
1804
1805 gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1806 0, FALSE, column_sort_fn);
1807
1808 gnc_tree_view_append_column (view, column);
1809
1810 /* Also add the full title to the object as a tooltip */
1811 gtk_widget_set_tooltip_text (gtk_tree_view_column_get_button (column), column_title);
1812
1813 return column;
1814}
1815
1816static void
1817renderer_editing_canceled_cb (GtkCellRenderer *renderer, gpointer user_data)
1818{
1819 GncTreeView *view = user_data;
1820 GncTreeViewPrivate *priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1821 if (priv->editing_finished_cb)
1822 (priv->editing_finished_cb)(view, priv->editing_cb_data);
1823}
1824
1825static void
1826renderer_editing_started_cb (GtkCellRenderer *renderer,
1827 GtkCellEditable *editable, gchar *path, gpointer user_data)
1828{
1829 GncTreeView *view = user_data;
1830 GncTreeViewPrivate *priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1831 if (priv->editing_started_cb)
1832 (priv->editing_started_cb)(view, priv->editing_cb_data);
1833}
1834
1835static void
1836renderer_edited_cb (GtkCellRendererText *renderer, gchar *path,
1837 gchar *new_text, gpointer user_data)
1838{
1839 GncTreeView *view = user_data;
1840 GncTreeViewPrivate *priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1841 if (priv->editing_finished_cb)
1842 (priv->editing_finished_cb)(view, priv->editing_cb_data);
1843}
1844
1845
1846static GtkTreeViewColumn *
1847add_text_column_variant (GncTreeView *view, GtkCellRenderer *renderer,
1848 const gchar *column_title,
1849 const gchar *pref_name,
1850 const gchar *icon_name,
1851 const gchar *sizing_text,
1852 gint model_data_column,
1853 gint model_visibility_column,
1854 GtkTreeIterCompareFunc column_sort_fn)
1855{
1856 GtkTreeViewColumn *column;
1857 PangoLayout* layout;
1858 int default_width, title_width;
1859
1860 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1861
1862 column = gtk_tree_view_column_new ();
1863 gtk_tree_view_column_set_title (column, column_title);
1864
1865 /* Set up an icon renderer if requested */
1866 if (icon_name)
1867 {
1868 GtkCellRenderer *renderer_pix = gtk_cell_renderer_pixbuf_new ();
1869 g_object_set (renderer_pix, "icon-name", icon_name, NULL);
1870 gtk_tree_view_column_pack_start (column, renderer_pix, FALSE);
1871 }
1872
1873 /* Set up a text renderer and attributes */
1874 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1875
1876 /* Set up the callbacks for when editing */
1877 g_signal_connect (G_OBJECT(renderer), "editing-canceled",
1878 (GCallback)renderer_editing_canceled_cb, view);
1879
1880 g_signal_connect (G_OBJECT(renderer), "editing-started",
1881 (GCallback)renderer_editing_started_cb, view);
1882
1883 g_signal_connect (G_OBJECT(renderer), "edited",
1884 (GCallback)renderer_edited_cb, view);
1885
1886 /* Set renderer attributes controlled by the model */
1887 if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
1888 gtk_tree_view_column_add_attribute (column, renderer,
1889 "text", model_data_column);
1890 if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1891 gtk_tree_view_column_add_attribute (column, renderer,
1892 "visible", model_visibility_column);
1893
1894 /* Default size is the larger of the column title and the sizing text */
1895 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
1896 pango_layout_get_pixel_size (layout, &title_width, NULL);
1897 g_object_unref (layout);
1898 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
1899 pango_layout_get_pixel_size (layout, &default_width, NULL);
1900 g_object_unref (layout);
1901 default_width = MAX(default_width, title_width);
1902 if (default_width)
1903 default_width += 10; /* padding on either side */
1904 gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1905 default_width, TRUE, column_sort_fn);
1906
1907 gnc_tree_view_append_column (view, column);
1908 return column;
1909}
1910
1911
1920GtkTreeViewColumn *
1922 const gchar *column_title,
1923 const gchar *pref_name,
1924 const gchar *icon_name,
1925 const gchar *sizing_text,
1926 gint model_data_column,
1927 gint model_visibility_column,
1928 GtkTreeIterCompareFunc column_sort_fn)
1929{
1930 GtkCellRenderer *renderer;
1931
1932 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1933
1934 renderer = gtk_cell_renderer_text_new ();
1935
1936 return add_text_column_variant (view, renderer,
1937 column_title, pref_name,
1938 icon_name, sizing_text,
1939 model_data_column,
1940 model_visibility_column,
1941 column_sort_fn);
1942}
1943
1952GtkTreeViewColumn *
1954 const gchar *column_title,
1955 const gchar *pref_name,
1956 const gchar *icon_name,
1957 const gchar *sizing_text,
1958 gint model_data_column,
1959 gint model_visibility_column,
1960 GtkTreeIterCompareFunc column_sort_fn)
1961{
1962 GtkCellRenderer *renderer;
1963
1964 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1965
1966 renderer = gnc_cell_renderer_text_view_new ();
1967
1968 return add_text_column_variant (view, renderer,
1969 column_title, pref_name,
1970 icon_name, sizing_text,
1971 model_data_column,
1972 model_visibility_column,
1973 column_sort_fn);
1974}
1975
1984GtkTreeViewColumn *
1986 const gchar *column_title,
1987 const gchar *pref_name,
1988 const gchar *sizing_text,
1989 gint model_data_column,
1990 gint model_visibility_column,
1991 GtkTreeIterCompareFunc column_sort_fn)
1992{
1993 GtkTreeViewColumn *column;
1994 PangoLayout* layout;
1995 int default_width, title_width;
1996 GtkCellRenderer *renderer;
1997
1998 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1999
2000 renderer = gtk_cell_renderer_pixbuf_new ();
2001
2002 column = gtk_tree_view_column_new ();
2003 gtk_tree_view_column_set_title (column, column_title);
2004
2005 /* Set up a text renderer and attributes */
2006 gtk_tree_view_column_pack_start (column, renderer, TRUE);
2007
2008 /* Set renderer attributes controlled by the model */
2009 if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
2010 gtk_tree_view_column_add_attribute (column, renderer,
2011 "icon-name", model_data_column);
2012 if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
2013 gtk_tree_view_column_add_attribute (column, renderer,
2014 "visible", model_visibility_column);
2015
2016 /* Default size is the larger of the column title and the sizing text */
2017 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
2018 pango_layout_get_pixel_size (layout, &title_width, NULL);
2019 g_object_unref (layout);
2020 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
2021 pango_layout_get_pixel_size (layout, &default_width, NULL);
2022 g_object_unref (layout);
2023 default_width = MAX(default_width, title_width);
2024 if (default_width)
2025 default_width += 10; /* padding on either side */
2026 gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
2027 default_width, TRUE, column_sort_fn);
2028
2029 gnc_tree_view_append_column (view, column);
2030 return column;
2031}
2032
2033GtkCellRenderer *
2034gnc_tree_view_column_get_renderer (GtkTreeViewColumn *column)
2035{
2036 GList *renderers;
2037 GtkCellRenderer *cr = NULL;
2038
2039 g_return_val_if_fail (GTK_TREE_VIEW_COLUMN(column), NULL);
2040
2041 /* Get the list of one renderer */
2042 renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(column));
2043 if (g_list_length (renderers) > 0)
2044 cr = GTK_CELL_RENDERER(renderers->data);
2045 g_list_free (renderers);
2046
2047 return cr;
2048}
2049
2060GtkTreeViewColumn *
2062 const gchar *column_title,
2063 const gchar *pref_name,
2064 const gchar *sizing_text,
2065 gint model_data_column,
2066 gint model_color_column,
2067 gint model_visibility_column,
2068 GtkTreeIterCompareFunc column_sort_fn)
2069{
2070 GtkTreeViewColumn *column;
2071 GtkCellRenderer *renderer;
2072 gfloat alignment = 1.0;
2073
2074 /* Use GncCellRendererLabel so the user can click a cell and select
2075 * its text (e.g. to copy it to the clipboard), without being able
2076 * to modify the underlying data. */
2077 renderer = gnc_cell_renderer_label_new ();
2078 column = add_text_column_variant (view, renderer, column_title, pref_name,
2079 NULL, sizing_text, model_data_column,
2080 model_visibility_column, column_sort_fn);
2081
2082 /* Right align the column title and data for both ltr and rtl */
2083 if (gtk_widget_get_direction (GTK_WIDGET(view)) == GTK_TEXT_DIR_RTL)
2084 alignment = 0.0;
2085
2086 g_object_set (G_OBJECT(column), "alignment", alignment, NULL);
2087 g_object_set (G_OBJECT(renderer), "xalign", alignment, NULL);
2088
2089 /* Change the text color */
2090 if (model_color_column != GNC_TREE_VIEW_COLUMN_COLOR_NONE)
2091 gtk_tree_view_column_add_attribute (column, renderer,
2092 "foreground", model_color_column);
2093
2094 return column;
2095}
2096
2105gint
2107 GtkTreeViewColumn *column)
2108{
2109 int n = gtk_tree_view_get_n_columns (GTK_TREE_VIEW(view));
2110
2111 /* Ignore the initial column, the selection menu */
2112 if (n >= 1)
2113 n -= 1;
2114 return gtk_tree_view_insert_column (GTK_TREE_VIEW(view), column, n);
2115}
2116
2117static gboolean
2118get_column_next_to (GtkTreeView *tv, GtkTreeViewColumn **col, gboolean backward)
2119{
2120 GList *cols, *node;
2121 GtkTreeViewColumn *c = NULL;
2122 gint seen = 0;
2123 gboolean wrapped = FALSE;
2124
2125 cols = gtk_tree_view_get_columns (tv);
2126 g_return_val_if_fail (cols != NULL, FALSE);
2127
2128 node = g_list_find (cols, *col);
2129 g_return_val_if_fail (node, FALSE);
2130 do
2131 {
2132 node = backward ? node->prev : node->next;
2133 if (!node)
2134 {
2135 wrapped = TRUE;
2136 node = backward ? g_list_last (cols) : cols;
2137 }
2138 c = GTK_TREE_VIEW_COLUMN (node->data);
2139 if (c && gtk_tree_view_column_get_visible (c))
2140 seen++;
2141 if (c == *col) break;
2142 }
2143 while (!seen);
2144
2145 g_list_free (cols);
2146 *col = c;
2147 return wrapped;
2148}
2149
2150gboolean
2151gnc_tree_view_path_is_valid (GncTreeView *view, GtkTreePath *path)
2152{
2153 GtkTreeView *tv = GTK_TREE_VIEW(view);
2154 GtkTreeModel *s_model;
2155 GtkTreeIter iter;
2156
2157 s_model = gtk_tree_view_get_model (tv);
2158 return gtk_tree_model_get_iter (s_model, &iter, path);
2159}
2160
2161void
2162gnc_tree_view_keynav (GncTreeView *view, GtkTreeViewColumn **col,
2163 GtkTreePath *path, GdkEventKey *event)
2164{
2165 GtkTreeView *tv = GTK_TREE_VIEW(view);
2166 gint depth;
2167 gboolean shifted;
2168
2169 if (event->type != GDK_KEY_PRESS) return;
2170
2171 switch (event->keyval)
2172 {
2173 case GDK_KEY_Tab:
2174 case GDK_KEY_ISO_Left_Tab:
2175 case GDK_KEY_KP_Tab:
2176 shifted = event->state & GDK_SHIFT_MASK;
2177 if (get_column_next_to (tv, col, shifted))
2178 {
2179 /* This is the end (or beginning) of the line, buddy. */
2180 depth = gtk_tree_path_get_depth (path);
2181 if (shifted)
2182 {
2183 if (!gtk_tree_path_prev (path) && depth > 1)
2184 {
2185 gtk_tree_path_up (path);
2186 }
2187 }
2188 else if (gtk_tree_view_row_expanded (tv, path))
2189 {
2190 gtk_tree_path_down (path);
2191 }
2192 else
2193 {
2194 gtk_tree_path_next (path);
2195 if (!gnc_tree_view_path_is_valid (view, path) && depth > 2)
2196 {
2197 gtk_tree_path_prev (path);
2198 gtk_tree_path_up (path);
2199 gtk_tree_path_next (path);
2200 }
2201 if (!gnc_tree_view_path_is_valid (view, path) && depth > 1)
2202 {
2203 gtk_tree_path_prev (path);
2204 gtk_tree_path_up (path);
2205 gtk_tree_path_next (path);
2206 }
2207 }
2208 }
2209 break;
2210
2211 case GDK_KEY_Return:
2212 case GDK_KEY_KP_Enter:
2213 if (gtk_tree_view_row_expanded (tv, path))
2214 {
2215 gtk_tree_path_down (path);
2216 }
2217 else
2218 {
2219 depth = gtk_tree_path_get_depth (path);
2220 gtk_tree_path_next (path);
2221 if (!gnc_tree_view_path_is_valid (view, path) && depth > 1)
2222 {
2223 gtk_tree_path_prev (path);
2224 gtk_tree_path_up (path);
2225 gtk_tree_path_next (path);
2226 }
2227 }
2228 break;
2229 }
2230 return;
2231}
2232
2233void
2234gnc_tree_view_set_editing_started_cb (GncTreeView *view, GFunc editing_started_cb, gpointer editing_cb_data)
2235{
2236 GncTreeViewPrivate *priv;
2237
2238 if (!view && !editing_started_cb)
2239 return;
2240
2241 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
2242
2243 priv->editing_started_cb = editing_started_cb;
2244 priv->editing_cb_data = editing_cb_data;
2245}
2246
2247void
2248gnc_tree_view_set_editing_finished_cb (GncTreeView *view, GFunc editing_finished_cb, gpointer editing_cb_data)
2249{
2250 GncTreeViewPrivate *priv;
2251
2252 if (!view && !editing_finished_cb)
2253 return;
2254
2255 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
2256
2257 priv->editing_finished_cb = editing_finished_cb;
2258 priv->editing_cb_data = editing_cb_data;
2259}
2260
All type declarations for the whole Gnucash engine.
GLib helper routines.
Gnome specific utility functions.
Gobject helper routines.
Generic api to store and retrieve preferences.
Functions to load, save and get gui state.
common utilities for manipulating a GtkTreeView within gnucash
GKeyFile * gnc_state_get_current(void)
Returns a pointer to the most recently loaded state.
Definition gnc-state.c:248
void gnc_tree_view_configure_columns(GncTreeView *view)
Make all the correct columns visible, respecting their default visibility setting,...
void gnc_tree_view_set_editing_finished_cb(GncTreeView *view, GFunc editing_finished_cb, gpointer editing_cb_data)
Setup a callback for when the user finishes editing so appropriate actions can be taken like enable t...
GtkCellRenderer * gnc_tree_view_column_get_renderer(GtkTreeViewColumn *column)
Return the "main" cell renderer from a GtkTreeViewColumn added to a GncTreeView my one of the conveni...
GtkTreeViewColumn * gnc_tree_view_add_text_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *icon_name, const gchar *sizing_text, gint model_data_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn)
This function adds a new text column to a GncTreeView base view.
void gnc_tree_view_remove_state_information(GncTreeView *view)
Completely wipe the treeview's state information (column visibility, width, sorting order,...
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)
This function adds a new toggle column to a GncTreeView base view.
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)
This function adds a new numeric column to a GncTreeView base view.
void gnc_tree_view_set_control_column_background(GncTreeView *view, gint column, GtkTreeCellDataFunc func)
This function links the cell backgrounds of the two control columns to a column in the model that has...
void gnc_tree_view_set_editing_started_cb(GncTreeView *view, GFunc editing_started_cb, gpointer editing_cb_data)
Setup a callback for when the user starts editing so appropriate actions can be taken like disable th...
void gnc_tree_view_save_state(GncTreeView *view)
This function is called to write the treeview's state information (column visibility,...
void gnc_tree_view_set_show_column_menu(GncTreeView *view, gboolean visible)
This function is called to set the "show-column-menu" property on this view.
GtkTreeViewColumn * gnc_tree_view_add_text_view_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *icon_name, const gchar *sizing_text, gint model_data_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn)
This function adds a new text view column to a GncTreeView base view.
GtkTreeViewColumn * gnc_tree_view_add_pix_column(GncTreeView *view, const gchar *column_title, const gchar *pref_name, const gchar *sizing_text, gint model_data_column, gint model_visibility_column, GtkTreeIterCompareFunc column_sort_fn)
This function adds a new pixbuf view column to a GncTreeView base view.
const gchar * gnc_tree_view_get_state_section(GncTreeView *view)
Get the name of the state section this tree view is associated with.
void gnc_tree_view_set_state_section(GncTreeView *view, const gchar *section)
Set up or remove an association between a saved state section and the display of a view.
void gnc_tree_view_expand_columns(GncTreeView *view, gchar *first_column_name,...)
This function set the columns that will be allocated the free space in the view.
gboolean gnc_tree_view_get_show_column_menu(GncTreeView *view)
This function is called to get the current value of the "show-column-menu" property.
gint gnc_tree_view_append_column(GncTreeView *view, GtkTreeViewColumn *column)
Add a column to a view based upon a GncTreeView.
void gnc_tree_view_set_sort_user_data(GncTreeView *view, GtkTreeModel *s_model)
This allows the columns to be setup without the model connected.
GtkTreeViewColumn * gnc_tree_view_find_column_by_name(GncTreeView *view, const gchar *wanted)
Find a tree column given the "pref name" used with saved state.
void gnc_gobject_tracking_remember(GObject *object)
Tell gnucash to remember this object in the database.
void gnc_gobject_tracking_forget(GObject *object)
Tell gnucash to remember this object in the database.
int safe_utf8_collate(const char *da, const char *db)
Collate two UTF-8 strings.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
void gnc_prefs_remove_cb_by_func(const gchar *group, const gchar *pref_name, gpointer func, gpointer user_data)
Remove a function that was registered for a callback when the given preference changed.
gulong gnc_prefs_register_cb(const char *group, const gchar *pref_name, gpointer func, gpointer user_data)
Register a callback that gets triggered when the given preference changes.
Private Data Structure.