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
25 /** @addtogroup GUI
26 @{ */
27 /** @addtogroup GncTreeView
28 * @{ */
29 /** @file gnc-tree-view.c
30 @brief Base GncTreeView implementation for gnucash trees.
31 @author David Hampton <hampton@employees.org>
32 */
33
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-cell-renderer-text-view.h"
47 #include "gnc-state.h"
48 #include "gnc-prefs.h"
49 #include "dialog-utils.h"
50
51 /* The actual state key for a particular column visibility. This is
52 * attached to the menu items that are in the column selection menu.
53 * Makes it very easy to update saved state when a menu item is toggled. */
54 #define STATE_KEY "state-key"
55
56 /* State keys within this particular saved state section. */
57 #define STATE_KEY_SORT_COLUMN "sort_column"
58 #define STATE_KEY_SORT_ORDER "sort_order"
59 #define STATE_KEY_COLUMN_ORDER "column_order"
60
61 /* Partial state keys within this particular saved state section. These
62 are appended to the various column names to create the actual
63 keys. */
64 #define STATE_KEY_SUFF_VISIBLE "visible"
65 #define STATE_KEY_SUFF_WIDTH "width"
66
67 enum
68 {
69 PROP_0,
70 PROP_STATE_SECTION,
71 PROP_SHOW_COLUMN_MENU,
72 };
73
74 /** Static Globals *******************************************************/
75
76 /* This static indicates the debugging module that this .o belongs to. */
77 static QofLogModule log_module = GNC_MOD_GUI;
78
79 /**** Declarations ******************************************************/
80 static void gnc_tree_view_class_init (GncTreeViewClass *klass);
81 static void gnc_tree_view_init (GncTreeView *view, void *data);
82 static void gnc_tree_view_finalize (GObject *object);
83 static void gnc_tree_view_destroy (GtkWidget *widget);
84 static void gnc_tree_view_set_property (GObject *object,
85 guint prop_id,
86 const GValue *value,
87 GParamSpec *pspec);
88 static void gnc_tree_view_get_property (GObject *object,
89 guint prop_id,
90 GValue *value,
91 GParamSpec *pspec);
92 static gboolean gnc_tree_view_drop_ok_cb (GtkTreeView *view,
93 GtkTreeViewColumn *column,
94 GtkTreeViewColumn *prev_column,
95 GtkTreeViewColumn *next_column,
96 gpointer data);
97 static void gnc_tree_view_build_column_menu (GncTreeView *view);
98 static void gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
99 GncTreeView *view);
100 static gchar *gnc_tree_view_get_sort_order (GncTreeView *view);
101 static gchar *gnc_tree_view_get_sort_column (GncTreeView *view);
102 static gchar **gnc_tree_view_get_column_order (GncTreeView *view,
103 gsize *length);
104
105 /** Private Data Structure ***********************************************/
106
107 typedef 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;
129 } GncTreeViewPrivate;
130
131 GNC_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*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_TREE_VIEW))
136
137
138 /************************************************************/
139 /* g_object required functions */
140 /************************************************************/
141
142 /** @name Gnc Tree View Object Implementation
143 @{ */
144
145 static GObjectClass *parent_class = NULL;
146
147 /** Initialize the class for the new base gnucash tree view. This
148 * will set up any function pointers that override functions in the
149 * parent class, and also installs the properties that are unique to
150 * this class.
151 *
152 * @param klass The new class structure created by the object system.
153 *
154 * @internal
155 */
156 static void
gnc_tree_view_class_init(GncTreeViewClass * klass)157 gnc_tree_view_class_init (GncTreeViewClass *klass)
158 {
159 GObjectClass *gobject_class;
160 GtkWidgetClass *gtkwidget_class;
161
162 parent_class = g_type_class_peek_parent (klass);
163
164 gobject_class = G_OBJECT_CLASS(klass);
165 gtkwidget_class = GTK_WIDGET_CLASS(klass);
166
167 gobject_class->set_property = gnc_tree_view_set_property;
168 gobject_class->get_property = gnc_tree_view_get_property;
169
170 g_object_class_install_property (gobject_class,
171 PROP_STATE_SECTION,
172 g_param_spec_string ("state-section",
173 "State Section",
174 "The section name in the saved state to use for (re)storing the treeview's visual state (visible columns, sort order,...",
175 NULL,
176 G_PARAM_READWRITE));
177 g_object_class_install_property (gobject_class,
178 PROP_SHOW_COLUMN_MENU,
179 g_param_spec_boolean ("show-column-menu",
180 "Show Column Menu",
181 "Show the column menu so user can change what columns are visible.",
182 FALSE,
183 G_PARAM_READWRITE));
184
185 /* GObject signals */
186 gobject_class->finalize = gnc_tree_view_finalize;
187
188 /* GtkWidget signals */
189 gtkwidget_class->destroy = gnc_tree_view_destroy;
190 }
191
192 static void
gnc_tree_view_update_grid_lines(gpointer prefs,gchar * pref,gpointer user_data)193 gnc_tree_view_update_grid_lines (gpointer prefs, gchar* pref, gpointer user_data)
194 {
195 GncTreeView *view = user_data;
196 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(view), gnc_tree_view_get_grid_lines_pref ());
197 }
198
199 static gboolean
gnc_tree_view_select_column_icon_cb(GtkWidget * widget,GdkEventButton * event,gpointer user_data)200 gnc_tree_view_select_column_icon_cb (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
201 {
202 GncTreeView *view = user_data;
203 GncTreeViewPrivate *priv;
204 GtkStyleContext *stylectxt = gtk_widget_get_style_context (widget);
205 GtkBorder padding;
206
207 // if the event button is not the right one, leave.
208 if (event->button != 1)
209 return FALSE;
210
211 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
212
213 gtk_style_context_get_padding (stylectxt, GTK_STATE_FLAG_NORMAL, &padding);
214
215 if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
216 {
217 if (event->x < (gtk_widget_get_allocated_width (priv->column_menu_icon_box) + padding.left))
218 gnc_tree_view_select_column_cb (priv->column_menu_column, view);
219 }
220 else
221 {
222 if (event->x > (gtk_widget_get_allocated_width (widget) -
223 (gtk_widget_get_allocated_width (priv->column_menu_icon_box) + padding.right)))
224 gnc_tree_view_select_column_cb (priv->column_menu_column, view);
225 }
226 return FALSE;
227 }
228
229 /** Initialize a new instance of a base gnucash tree view. This
230 * function allocates and initializes the object private storage
231 * space. It also adds the new object to a list (for memory tracking
232 * purposes), and sets up a callback for the column drag function.
233 *
234 * @param view The new object instance created by the object system.
235 *
236 * @internal
237 */
238 static void
gnc_tree_view_init(GncTreeView * view,void * data)239 gnc_tree_view_init (GncTreeView *view, void *data)
240 {
241 GncTreeViewPrivate *priv;
242 GtkTreeViewColumn *column;
243 GtkWidget *sep, *icon;
244
245 GncTreeViewClass *klass = (GncTreeViewClass*)data;
246
247 gnc_gobject_tracking_remember (G_OBJECT(view),
248 G_OBJECT_CLASS(klass));
249
250 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
251 priv->column_menu = NULL;
252 priv->show_column_menu = FALSE;
253 priv->sort_model = NULL;
254 priv->state_section = NULL;
255 priv->seen_state_visibility = FALSE;
256 priv->columns_changed_cb_id = 0;
257 priv->sort_column_changed_cb_id = 0;
258 priv->size_allocate_cb_id = 0;
259
260 // Set the name for this widget so it can be easily manipulated with css
261 gtk_widget_set_name (GTK_WIDGET(view), "gnc-id-tree-view");
262
263 /* Handle column drag and drop */
264 gtk_tree_view_set_column_drag_function (GTK_TREE_VIEW(view),
265 gnc_tree_view_drop_ok_cb, NULL, NULL);
266
267 // Set grid lines option to preference
268 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(view), gnc_tree_view_get_grid_lines_pref ());
269 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_HORIZONTAL,
270 gnc_tree_view_update_grid_lines, view);
271 gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_VERTICAL,
272 gnc_tree_view_update_grid_lines, view);
273
274 /* Create the last column which contains the column selection
275 * widget. gnc_tree_view_add_text_column will do most of the
276 * work. */
277 icon = gtk_image_new_from_icon_name ("pan-down-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
278
279 priv->column_menu_icon_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
280 gtk_box_set_homogeneous (GTK_BOX(priv->column_menu_icon_box), FALSE);
281
282 gtk_widget_set_margin_start (GTK_WIDGET(icon), 5);
283
284 gtk_box_pack_end (GTK_BOX(priv->column_menu_icon_box), icon, FALSE, FALSE, 0);
285
286 sep = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
287 gtk_box_pack_end (GTK_BOX(priv->column_menu_icon_box), sep, FALSE, FALSE, 0);
288
289 gtk_widget_show_all (priv->column_menu_icon_box);
290
291 column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
292 -1, -1, NULL);
293 g_object_set (G_OBJECT(column),
294 "clickable", TRUE,
295 "widget", priv->column_menu_icon_box,
296 "alignment", 1.0,
297 "expand", TRUE,
298 (gchar *)NULL);
299
300 priv->column_menu_column = column;
301
302 // get the actual column button by looking at the parents of the column_menu_icon
303 {
304 GtkWidget *mybox = gtk_widget_get_parent (icon);
305 GtkWidget *walign = gtk_widget_get_parent (mybox);
306 GtkWidget *box = gtk_widget_get_parent (walign);
307 GtkWidget *button = gtk_widget_get_parent (box);
308
309 if (!GTK_IS_BUTTON(button)) // just in case this order changes.
310 {
311 // this will fire for the whole column header
312 g_signal_connect (G_OBJECT(column), "clicked",
313 G_CALLBACK(gnc_tree_view_select_column_cb),
314 view);
315 }
316 else
317 {
318 /* this part will restrict the mouse click to just where the
319 icon is, tried using an eventbox but it would only work
320 some of the time */
321 gtk_widget_set_events (button, GDK_BUTTON_PRESS_MASK);
322
323 g_signal_connect (G_OBJECT(button), "button_press_event",
324 G_CALLBACK(gnc_tree_view_select_column_icon_cb),
325 view);
326 }
327 }
328 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
329 }
330
331 /** Finalize the GncTreeView object. This function is called from the
332 * G_Object level to complete the destruction of the object. It
333 * should release any memory not previously released by the destroy
334 * function (i.e. the private data structure), then chain up to the
335 * parent's destroy function.
336 *
337 * @param object The object being destroyed.
338 *
339 * @internal
340 */
341 static void
gnc_tree_view_finalize(GObject * object)342 gnc_tree_view_finalize (GObject *object)
343 {
344 ENTER("view %p", object);
345 g_return_if_fail (object != NULL);
346 g_return_if_fail (GNC_IS_TREE_VIEW(object));
347
348 gnc_gobject_tracking_forget (object);
349
350 if (G_OBJECT_CLASS(parent_class)->finalize)
351 G_OBJECT_CLASS(parent_class)->finalize (object);
352 LEAVE(" ");
353 }
354
355 /** Destroy the GncTreeView widget. This function is called (possibly
356 * multiple times) from the Gtk_Object level to destroy the widget.
357 * It should release any memory owned by the widget that isn't
358 * fundamental to the implementation. In this function any active
359 * callbacks are disconnected, all memory other than the private data
360 * structure are freed.
361 *
362 * @param widget The widget being destroyed.
363 *
364 * @internal
365 */
366 static void
gnc_tree_view_destroy(GtkWidget * widget)367 gnc_tree_view_destroy (GtkWidget *widget)
368 {
369 GncTreeView *view;
370 GncTreeViewPrivate *priv;
371
372 ENTER("view %p", widget);
373 g_return_if_fail (widget != NULL);
374 g_return_if_fail (GNC_IS_TREE_VIEW(widget));
375
376 view = GNC_TREE_VIEW(widget);
377
378 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_HORIZONTAL,
379 gnc_tree_view_update_grid_lines, view);
380 gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL, GNC_PREF_GRID_LINES_VERTICAL,
381 gnc_tree_view_update_grid_lines, view);
382
383 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
384
385 if (priv->state_section)
386 {
387 gnc_tree_view_save_state (view);
388 }
389 g_free (priv->state_section);
390 priv->state_section = NULL;
391
392 if (priv->column_menu)
393 {
394 DEBUG("removing column selection menu");
395 g_object_unref (priv->column_menu);
396 priv->column_menu = NULL;
397 }
398
399 if (GTK_WIDGET_CLASS(parent_class)->destroy)
400 GTK_WIDGET_CLASS(parent_class)->destroy (widget);
401 LEAVE(" ");
402 }
403
404 /** @} */
405
406 /************************************************************/
407 /* g_object other functions */
408 /************************************************************/
409
410 /** @name Gnc Tree View Object Implementation
411 @{ */
412
413 /** Retrieve a property specific to this GncTreeView object. This is
414 * nothing more than a dispatch function for routines that can be
415 * called directly. It has the nice feature of allowing a single
416 * function call to retrieve multiple properties.
417 *
418 * @internal
419 */
420 static void
gnc_tree_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)421 gnc_tree_view_get_property (GObject *object,
422 guint prop_id,
423 GValue *value,
424 GParamSpec *pspec)
425 {
426 GncTreeView *view = GNC_TREE_VIEW(object);
427 GncTreeViewPrivate *priv;
428
429 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
430 switch (prop_id)
431 {
432 case PROP_STATE_SECTION:
433 g_value_set_string (value, priv->state_section);
434 break;
435 case PROP_SHOW_COLUMN_MENU:
436 g_value_set_boolean (value, priv->show_column_menu);
437 break;
438 default:
439 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
440 break;
441 }
442 }
443
444
445 /** Set a property specific to this GncTreeView object. This is
446 * nothing more than a dispatch function for routines that can be
447 * called directly. It has the nice feature of allowing a new view
448 * to be created with a varargs list specifying the properties,
449 * instead of having to explicitly call each property function.
450 *
451 * @internal
452 */
453 static void
gnc_tree_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)454 gnc_tree_view_set_property (GObject *object,
455 guint prop_id,
456 const GValue *value,
457 GParamSpec *pspec)
458 {
459 GncTreeView *view = GNC_TREE_VIEW(object);
460
461 switch (prop_id)
462 {
463 case PROP_STATE_SECTION:
464 gnc_tree_view_set_state_section (view, g_value_get_string (value));
465 break;
466 case PROP_SHOW_COLUMN_MENU:
467 gnc_tree_view_set_show_column_menu (view, g_value_get_boolean (value));
468 break;
469 default:
470 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
471 break;
472 }
473 }
474
475 /** @} */
476
477 /************************************************************/
478 /* Auxiliary Functions */
479 /************************************************************/
480 /** @name Gnc Tree View Auxiliary Functions
481 @{ */
482
483 /** Find a tree column given a column id number from the underlying
484 * data model. This function should only be called by code that has
485 * visibility into the data model. The column id numbers shouldn't
486 * be used for any other purpose.
487 *
488 * This function simply runs the list of all (visible and invisible)
489 * columns looking for a match. Column id numbers were attached to
490 * each column at the time the column was created.
491 *
492 * @param view The visible tree widget.
493 *
494 * @param wanted The column id number to find.
495 *
496 * @internal
497 */
498 static GtkTreeViewColumn *
view_column_find_by_model_id(GncTreeView * view,const gint wanted)499 view_column_find_by_model_id (GncTreeView *view,
500 const gint wanted)
501 {
502 GtkTreeViewColumn *column, *found = NULL;
503 GList *column_list, *tmp;
504 gint id;
505
506 // ENTER("view %p, name %s", view, name);
507 column_list = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
508 for (tmp = column_list; tmp; tmp = g_list_next (tmp))
509 {
510 column = tmp->data;
511 id = GPOINTER_TO_INT(g_object_get_data (G_OBJECT(column), MODEL_COLUMN));
512 if (id != wanted)
513 continue;
514 found = column;
515 break;
516 }
517 g_list_free (column_list);
518
519 // LEAVE("column %p", found);
520 return found;
521 }
522
523 /** Find a tree column given the "pref name" used with saved state. This
524 * function simply runs the list of all (visible and invisible)
525 * columns looking for a match. Column names were attached to each
526 * column at the time the column was created.
527 *
528 * @param view The visible tree widget.
529 *
530 * @param wanted The "pref name" to find.
531 *
532 */
533 GtkTreeViewColumn *
gnc_tree_view_find_column_by_name(GncTreeView * view,const gchar * wanted)534 gnc_tree_view_find_column_by_name (GncTreeView *view,
535 const gchar *wanted)
536 {
537 GtkTreeViewColumn *column, *found = NULL;
538 GList *column_list, *tmp;
539 const gchar *name;
540
541 // ENTER("view %p, wanted %s", view, wanted);
542 column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
543 for (tmp = column_list; tmp; tmp = g_list_next (tmp))
544 {
545 column = tmp->data;
546 name = g_object_get_data (G_OBJECT(column), PREF_NAME);
547 if (!name || (strcmp(name, wanted) != 0))
548 continue;
549 found = column;
550 break;
551 }
552 g_list_free (column_list);
553
554 // LEAVE("column %p", found);
555 return found;
556 }
557
558 /** @} */
559
560 /************************************************************/
561 /* Tree Callbacks */
562 /************************************************************/
563
564 /** @name Gnc Tree View Callback Functions
565 @{ */
566
567 /** This function is called to determine whether it is acceptable to
568 * drop a dragged tree column at a given location. This function
569 * will be called multiple times by the GtkTreeView code while the
570 * user is dragging a column. Each call is a check to see if the
571 * proposed location is an acceptable place to drop the column. In
572 * the case of a GncTreeView, the only drop locations that are
573 * prohibited are on the right side of the visible tree.
574 *
575 * @param view The visible tree widget.
576 *
577 * @param column The column being dragged.
578 *
579 * @param prev_column The column before (left) of the proposed drop
580 * location.
581 *
582 * @param next_column The column after (right) of the proposed drop
583 * location.
584 *
585 * @param data Unused.
586 *
587 * @return TRUE if this drop location is acceptable. FALSE if not
588 * acceptable.
589 *
590 * @internal
591 */
592 static gboolean
gnc_tree_view_drop_ok_cb(GtkTreeView * view,GtkTreeViewColumn * column,GtkTreeViewColumn * prev_column,GtkTreeViewColumn * next_column,gpointer data)593 gnc_tree_view_drop_ok_cb (GtkTreeView *view,
594 GtkTreeViewColumn *column,
595 GtkTreeViewColumn *prev_column,
596 GtkTreeViewColumn *next_column,
597 gpointer data)
598 {
599 const gchar *pref_name;
600
601 /* Should we allow a drop at the left side of the tree view before
602 * the widget to open a new display level? I can think of cases
603 * where the user might want to do this with a checkbox column. */
604 if (prev_column == NULL)
605 return TRUE;
606
607 /* Do not allow a drop at the right side of the tree view after the
608 * column selection widget. */
609 if (next_column == NULL)
610 return FALSE;
611
612 /* Columns without pref names are considered fixed at the right hand
613 * side of the view. At the time of this writing, the only two are
614 * the column where the "column selection widget" is stored, and the
615 * "padding" column to the left of that where extra view space ends
616 * up. */
617 pref_name = g_object_get_data (G_OBJECT(prev_column), PREF_NAME);
618 if (!pref_name)
619 return FALSE;
620
621 /* Everything else is allowed. */
622 return TRUE;
623 }
624
625 /** @} */
626
627 /************************************************************/
628 /* State Setup / Callbacks */
629 /************************************************************/
630
631 /** @name Gnc Tree View state Callback / Related Functions
632 @{ */
633
634 /** Determine the visibility of a column. This function first looks
635 * for columns specially marked to be always visible, or columns
636 * without a preference name. These are always shown. Next, this
637 * function checks if visibility is stored in saved state. If so
638 * use the value found there. If none of the above the default
639 * visibility for the column is used.
640 *
641 * @param view A GncTreeView.
642 *
643 * @param column The GtkTreeViewColumn in question. Either this
644 * value or the pref_name parameter must be non-NULL.
645 *
646 * @param pref_name The name of the column in question. Either this
647 * value or the column parameter must be non-NULL.
648 *
649 * @return TRUE if the column should be visible. FALSE otherwise.
650 *
651 * @internal
652 */
653 static gboolean
gnc_tree_view_column_visible(GncTreeView * view,GtkTreeViewColumn * column,const gchar * pref_name)654 gnc_tree_view_column_visible (GncTreeView *view,
655 GtkTreeViewColumn *column,
656 const gchar *pref_name)
657 {
658 GncTreeViewPrivate *priv;
659 gboolean visible;
660 const gchar *col_name = pref_name;
661
662 ENTER("column %p, name %s", column, pref_name ? pref_name : "(null)");
663 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
664 if (column)
665 {
666 if (g_object_get_data (G_OBJECT(column), ALWAYS_VISIBLE))
667 {
668 LEAVE("1, first column");
669 return TRUE;
670 }
671 col_name = g_object_get_data (G_OBJECT(column), PREF_NAME);
672 DEBUG("col_name is %s", col_name ? col_name : "(null)");
673 }
674
675 if (!col_name)
676 {
677 LEAVE("1, no pref name");
678 return TRUE;
679 }
680
681 /* Using saved state ? */
682 if (priv->state_section)
683 {
684 GKeyFile *state_file = gnc_state_get_current ();
685 gchar *key = g_strdup_printf ("%s_%s", col_name, STATE_KEY_SUFF_VISIBLE);
686
687 if (g_key_file_has_key (state_file, priv->state_section, key, NULL))
688 {
689 visible = g_key_file_get_boolean (state_file, priv->state_section, key, NULL);
690 g_free (key);
691 LEAVE("%d, state defined visibility", visible);
692 return visible;
693 }
694 }
695
696 /* Check the default columns list */
697 visible = column ?
698 (g_object_get_data (G_OBJECT(column), DEFAULT_VISIBLE) != NULL) : FALSE;
699 LEAVE("defaults says %d", visible);
700 return visible;
701 }
702
703 /** This function updates the visibility of a single column. It
704 * checks if the column should be visible, and if so tells the view
705 * to show the column.
706 *
707 * @param column The column whose visibility should be updated.
708 *
709 * @param view The GncTreeView containing the column.
710 *
711 * @internal
712 */
713 static void
gnc_tree_view_update_visibility(GtkTreeViewColumn * column,GncTreeView * view)714 gnc_tree_view_update_visibility (GtkTreeViewColumn *column,
715 GncTreeView *view)
716 {
717 gboolean visible;
718
719 g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN(column));
720 g_return_if_fail (GNC_IS_TREE_VIEW(view));
721
722 ENTER(" ");
723 visible = gnc_tree_view_column_visible (view, column, NULL);
724 gtk_tree_view_column_set_visible (column, visible);
725 LEAVE("made %s", visible ? "visible" : "invisible");
726 }
727
728 /** Get the sort order for the sort column, converted into a string.
729 *
730 * @param view The tree view.
731 *
732 * @return a string representing the sort order. NULL if not sorted, else
733 * either "ascending" or "descending".
734 * Should be freed with g_free if no longer needed.
735 *
736 * @internal
737 */
738 static gchar *
gnc_tree_view_get_sort_order(GncTreeView * view)739 gnc_tree_view_get_sort_order (GncTreeView *view)
740 {
741 GtkTreeModel *s_model;
742 GtkSortType order;
743 gint current;
744 gchar *order_str = NULL;
745
746 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
747 if (!s_model)
748 return NULL; /* no model, so sort order doesn't make sense */
749
750 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
751 ¤t, &order))
752 return NULL; /* Model is not sorted, return */
753
754 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(s_model),
755 current, order);
756 order_str = g_strdup (order == GTK_SORT_ASCENDING ? "ascending" : "descending");
757 DEBUG("current sort_order is %s", order_str);
758 return order_str;
759 }
760
761 /** Get the current sort column.
762 *
763 * @param view The tree view.
764 *
765 * @return a string with the name of the sort column, or NULL if not sorted.
766 * Should be freed with g_free if no longer needed.
767 *
768 * @internal
769 */
770 static gchar *
gnc_tree_view_get_sort_column(GncTreeView * view)771 gnc_tree_view_get_sort_column (GncTreeView *view)
772 {
773 GtkTreeModel *s_model;
774 GtkTreeViewColumn *column;
775 GtkSortType order;
776 gint current;
777 const gchar *name;
778
779 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
780 if (!s_model)
781 return NULL; /* no model -> no sort column */
782
783 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
784 ¤t, &order))
785 return NULL; /* model not sorted */
786
787 column = view_column_find_by_model_id (view, current);
788 if (!column)
789 return NULL; /* column not visible, can't be used for sorting */
790
791 name = g_object_get_data (G_OBJECT(column), PREF_NAME);
792 DEBUG("current sort column is %s", name ? name : "(NULL)");
793 return g_strdup (name);
794 }
795
796
797
798 /** Get the current column order.
799 *
800 * @param view The tree view.
801 *
802 * @return an array of strings with the names of the columns in the order
803 * they are currently ordered.
804 * Should be freed with g_free if no longer needed.
805 *
806 * @internal
807 */
808 static gchar **
gnc_tree_view_get_column_order(GncTreeView * view,gsize * length)809 gnc_tree_view_get_column_order (GncTreeView *view,
810 gsize *length)
811 {
812 const GList *tmp;
813 GList *columns;
814 gulong num_cols = 0;
815 gchar *col_names = NULL;
816 gchar **col_str_list;
817
818 /* First, convert from names to pointers */
819 ENTER(" ");
820
821 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
822 for (tmp = columns; tmp; tmp = g_list_next(tmp))
823 {
824 GtkTreeViewColumn *column = tmp->data;
825 const gchar *name = g_object_get_data (G_OBJECT(column), PREF_NAME);
826 if (!col_names)
827 col_names = g_strdup (name);
828 else
829 {
830 gchar *col_names_prev = col_names;
831 col_names = g_strjoin (";", col_names_prev, name, NULL);
832 g_free (col_names_prev);
833 }
834 num_cols++;
835 }
836 //DEBUG ("got %lu columns: %s", num_cols, col_names);
837 col_str_list = g_strsplit (col_names, ";", 0);
838
839 /* Clean up */
840 g_list_free (columns);
841 g_free (col_names);
842
843 LEAVE("column order get");
844 *length = num_cols;
845 return col_str_list;
846 }
847
848 /** Set the sort order for the sort column (if there is one)
849 * of this tree view.
850 *
851 * @param view The tree view.
852 *
853 * @param name The sort order enum (in string form). Either
854 * "ascending" or "descending".
855 *
856 * @internal
857 */
858 static void
gnc_tree_view_set_sort_order(GncTreeView * view,const gchar * name)859 gnc_tree_view_set_sort_order (GncTreeView *view,
860 const gchar *name)
861 {
862 GtkTreeModel *s_model;
863 GtkSortType order = GTK_SORT_ASCENDING;
864 gint current;
865
866 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
867 if (!s_model)
868 return;
869 if (g_strcmp0 (name, "descending") == 0)
870 order = GTK_SORT_DESCENDING;
871 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
872 ¤t, NULL))
873 current = GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID;
874 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(s_model),
875 current, order);
876 DEBUG("sort_order set to %s", order == GTK_SORT_ASCENDING ? "ascending" : "descending");
877 }
878
879 /** Set the sort column for this tree view.
880 *
881 * @param view The tree view.
882 *
883 * @param name The name of the column that should be made the sort column.
884 *
885 * @internal
886 */
887 static void
gnc_tree_view_set_sort_column(GncTreeView * view,const gchar * name)888 gnc_tree_view_set_sort_column (GncTreeView *view,
889 const gchar *name)
890 {
891 GtkTreeModel *s_model;
892 GtkTreeViewColumn *column;
893 GtkSortType order;
894 gint model_column, current;
895
896 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
897 if (!s_model)
898 return;
899
900 column = gnc_tree_view_find_column_by_name (view, name);
901 if (!column)
902 {
903 gtk_tree_sortable_set_sort_column_id (
904 GTK_TREE_SORTABLE(s_model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
905 GTK_SORT_ASCENDING);
906 return;
907 }
908
909 model_column =
910 GPOINTER_TO_INT(g_object_get_data (G_OBJECT(column), MODEL_COLUMN));
911 if (model_column == GNC_TREE_VIEW_COLUMN_DATA_NONE)
912 return;
913
914 if (!gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE(s_model),
915 ¤t, &order))
916 order = GTK_SORT_ASCENDING;
917
918 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(s_model),
919 model_column, order);
920 DEBUG("sort column set to %s", name);
921 }
922
923 /** Set the order of the columns (visible and invisible) for this
924 * tree view.
925 *
926 * @param view The tree view.
927 *
928 * @param column_names An array of strings. These strings are the
929 * names of the columns in the order they should appear.
930 *
931 * @param length The number of strings in the array.
932 *
933 * @internal
934 */
935 static void
gnc_tree_view_set_column_order(GncTreeView * view,gchar ** column_names,gsize length)936 gnc_tree_view_set_column_order (GncTreeView *view,
937 gchar **column_names,
938 gsize length)
939 {
940 GtkTreeViewColumn *column, *prev;
941 const GSList *tmp;
942 GSList *columns;
943 gsize idx;
944
945 /* First, convert from names to pointers */
946 ENTER(" ");
947 columns = NULL;
948 for (idx = 0; idx < length; idx++)
949 {
950 const gchar *name = column_names [idx];
951 column = gnc_tree_view_find_column_by_name (view, name);
952 if (!column)
953 continue;
954 columns = g_slist_append (columns, column);
955 }
956
957 /* Then reorder the columns */
958 for (prev = NULL, tmp = columns; tmp; tmp = g_slist_next (tmp))
959 {
960 column = tmp->data;
961 gtk_tree_view_move_column_after (GTK_TREE_VIEW(view), column, prev);
962 prev = column;
963 }
964
965 /* Clean up */
966 g_slist_free (columns);
967 LEAVE("column order set");
968 }
969
970 /** Completely wipe the treeview's state information (column visibility, width,
971 * sorting order,..). This function may be called at any time;
972 * either when the user wants to disconnect or
973 * when the view object is being destroyed.
974 *
975 * @param view The tree view.
976 */
977
gnc_tree_view_remove_state_information(GncTreeView * view)978 void gnc_tree_view_remove_state_information (GncTreeView *view)
979 {
980 GncTreeViewPrivate *priv;
981 GKeyFile *state_file = gnc_state_get_current ();
982
983 ENTER(" ");
984 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
985 if (!priv->state_section)
986 {
987 LEAVE("no state section");
988 return;
989 }
990
991 g_key_file_remove_group (state_file, priv->state_section, NULL);
992 g_free (priv->state_section);
993 priv->state_section = NULL;
994 LEAVE(" ");
995 }
996
997 /** Set up or remove an association between a saved state section
998 * and the display of a view. It will first remove any existing association,
999 * and then install the new one. If the new section has state
1000 * information, update the view with this information.
1001 *
1002 * Parameters are defined in gnc-tree-view.h
1003 */
1004 void
gnc_tree_view_set_state_section(GncTreeView * view,const gchar * section)1005 gnc_tree_view_set_state_section (GncTreeView *view,
1006 const gchar *section)
1007 {
1008 GncTreeViewPrivate *priv;
1009 GKeyFile *state_file;
1010
1011 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1012
1013 ENTER("view %p, section %s", view, section);
1014
1015 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1016
1017 /* Drop any previous state section */
1018 if (priv->state_section)
1019 gnc_tree_view_remove_state_information (view);
1020
1021 if (!section)
1022 {
1023 LEAVE("cleared state section");
1024 return;
1025 }
1026
1027 /* Catch changes in state. Propagate to view. */
1028 priv->state_section = g_strdup (section);
1029
1030 state_file = gnc_state_get_current ();
1031 if (g_key_file_has_group (state_file, priv->state_section))
1032 {
1033 gsize num_keys, idx;
1034 gchar **keys = g_key_file_get_keys (state_file, priv->state_section, &num_keys, NULL);
1035 for (idx = 0; idx < num_keys; idx++)
1036 {
1037 gchar *key = keys[idx];
1038 if (g_strcmp0 (key, STATE_KEY_SORT_COLUMN) == 0)
1039 {
1040 gnc_tree_view_set_sort_column (view,
1041 g_key_file_get_string (state_file, priv->state_section, key, NULL));
1042 }
1043 else if (g_strcmp0 (key, STATE_KEY_SORT_ORDER) == 0)
1044 {
1045 gnc_tree_view_set_sort_order (view,
1046 g_key_file_get_string (state_file, priv->state_section, key, NULL));
1047 }
1048 else if (g_strcmp0 (key, STATE_KEY_COLUMN_ORDER) == 0)
1049 {
1050 gsize length;
1051 gchar **columns = g_key_file_get_string_list (state_file, priv->state_section,
1052 key, &length, NULL);
1053 gnc_tree_view_set_column_order (view, columns, length);
1054 g_strfreev (columns);
1055 }
1056 else
1057 {
1058 /* Make a copy of the local part of the key so it can be split
1059 * into column name and key type */
1060 gboolean known = FALSE;
1061 gchar *column_name = g_strdup (key);
1062 gchar *type_name = g_strrstr (column_name, "_");
1063
1064 if (type_name != NULL) //guard against not finding '_'
1065 {
1066 *type_name++ = '\0';
1067
1068 if (g_strcmp0 (type_name, STATE_KEY_SUFF_VISIBLE) == 0)
1069 {
1070 GtkTreeViewColumn *column = gnc_tree_view_find_column_by_name (view, column_name);
1071 if (column)
1072 {
1073 known = TRUE;
1074 if (!g_object_get_data (G_OBJECT (column), ALWAYS_VISIBLE))
1075 {
1076 gtk_tree_view_column_set_visible (column,
1077 g_key_file_get_boolean (state_file, priv->state_section, key, NULL));
1078 }
1079 }
1080 }
1081 else if (g_strcmp0 (type_name, STATE_KEY_SUFF_WIDTH) == 0)
1082 {
1083 gint width = g_key_file_get_integer (state_file, priv->state_section, key, NULL);
1084 GtkTreeViewColumn *column = gnc_tree_view_find_column_by_name (view, column_name);
1085 if (column)
1086 {
1087 known = TRUE;
1088 if (width && (width != gtk_tree_view_column_get_width (column)))
1089 {
1090 gtk_tree_view_column_set_fixed_width (column, width);
1091 }
1092 }
1093 }
1094 if (!known)
1095 DEBUG ("Ignored key %s", key);
1096
1097 g_free (column_name);
1098 }
1099 }
1100 }
1101 g_strfreev (keys);
1102 }
1103
1104 /* Rebuild the column visibility menu */
1105 gnc_tree_view_build_column_menu (view);
1106 LEAVE ("set state section");
1107 }
1108
1109 /** Get the name of the state section this tree view is associated with.
1110 * It returns the same value passed to gnc_tree_view_set_state_section();
1111 * i.e. a string like "dialogs/edit_prices".
1112 *
1113 * Parameters are defined in gnc-tree-view.h
1114 */
1115 const gchar *
gnc_tree_view_get_state_section(GncTreeView * view)1116 gnc_tree_view_get_state_section (GncTreeView *view)
1117 {
1118 GncTreeViewPrivate *priv;
1119
1120 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1121
1122 priv = GNC_TREE_VIEW_GET_PRIVATE (view);
1123 return priv->state_section;
1124 }
1125
gnc_tree_view_save_state(GncTreeView * view)1126 void gnc_tree_view_save_state (GncTreeView *view)
1127 {
1128 GncTreeViewPrivate *priv;
1129
1130 ENTER("view %p", view);
1131 g_return_if_fail (view != NULL);
1132 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1133
1134 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1135
1136 if (priv->state_section)
1137 {
1138 /* Save state. Only store non-default values when possible. */
1139 GList *column_list, *tmp;
1140 GKeyFile *state_file = gnc_state_get_current();
1141 gsize num_cols = 0;
1142 gchar *sort_column = gnc_tree_view_get_sort_column (view);
1143 gchar *sort_order = gnc_tree_view_get_sort_order (view);
1144 gchar **col_order = gnc_tree_view_get_column_order (view, &num_cols);
1145
1146 /* Default sort column is the name column */
1147 if (sort_column && (g_strcmp0 (sort_column, "name") != 0))
1148 g_key_file_set_string (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, sort_column);
1149 else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, NULL))
1150 g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_SORT_COLUMN, NULL);
1151 g_free (sort_column);
1152
1153
1154 /* Default sort order is "ascending" */
1155 if (g_strcmp0 (sort_order, "descending") == 0)
1156 g_key_file_set_string (state_file, priv->state_section, STATE_KEY_SORT_ORDER, sort_order);
1157 else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_SORT_ORDER, NULL))
1158 g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_SORT_ORDER, NULL);
1159 g_free (sort_order);
1160
1161 if (col_order && (num_cols > 0))
1162 g_key_file_set_string_list (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER,
1163 (const gchar**) col_order, num_cols);
1164 else if (g_key_file_has_key (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER, NULL))
1165 g_key_file_remove_key (state_file, priv->state_section, STATE_KEY_COLUMN_ORDER, NULL);
1166
1167 g_strfreev (col_order);
1168
1169
1170 // ENTER("view %p, wanted %s", view, wanted);
1171 column_list = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1172 for (tmp = column_list; tmp; tmp = g_list_next (tmp))
1173 {
1174 GtkTreeViewColumn *column = tmp->data;
1175 gchar *key=NULL;
1176 const gchar *name = g_object_get_data (G_OBJECT(column), PREF_NAME);
1177 if (!name)
1178 continue;
1179
1180 if (!g_object_get_data (G_OBJECT(column), ALWAYS_VISIBLE))
1181 {
1182 key = g_strjoin ("_", name, STATE_KEY_SUFF_VISIBLE, NULL);
1183 g_key_file_set_boolean (state_file, priv->state_section, key,
1184 gtk_tree_view_column_get_visible (column));
1185 g_free (key);
1186 }
1187
1188 key = g_strjoin ("_", name, STATE_KEY_SUFF_WIDTH, NULL);
1189 if (g_object_get_data (G_OBJECT(column), "default-width") &&
1190 (GPOINTER_TO_INT((g_object_get_data (G_OBJECT(column), "default-width")))
1191 != gtk_tree_view_column_get_width (column)))
1192 {
1193 g_key_file_set_integer (state_file, priv->state_section, key,
1194 gtk_tree_view_column_get_width (column));
1195 }
1196 else if (g_key_file_has_key (state_file, priv->state_section, key, NULL))
1197 g_key_file_remove_key (state_file, priv->state_section, key, NULL);
1198 g_free (key);
1199 }
1200 g_list_free (column_list);
1201 }
1202
1203 LEAVE(" ");
1204 }
1205
1206
1207 /** @} */
1208
1209 /************************************************************/
1210 /* Column Selection Menu */
1211 /************************************************************/
1212
1213 /** @name Gnc Tree View Column Selection Menu Related Functions
1214 @{ */
1215
1216 /** This function is called to create a single checkmenuitem in the
1217 * column selection menu. It is called once for each column in the
1218 * view. It creates a menu item for the corresponding column, and
1219 * attaches to it a copy of the state key for this column's
1220 * visibility. This makes the toggle callback function trivial.
1221 *
1222 * This function will create the column selection menu if one doesn't
1223 * already exist.
1224 *
1225 * @param column Create a menu item for this column.
1226 *
1227 * @param view The tree view.
1228 *
1229 * @internal
1230 */
1231 static void
gnc_tree_view_create_menu_item(GtkTreeViewColumn * column,GncTreeView * view)1232 gnc_tree_view_create_menu_item (GtkTreeViewColumn *column,
1233 GncTreeView *view)
1234 {
1235 GncTreeViewPrivate *priv;
1236 GtkWidget *widget;
1237 const gchar *column_name, *pref_name;
1238 gchar *key;
1239 GBinding *binding;
1240
1241 // ENTER("view %p, column %p", view, column);
1242 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1243 if (!priv->state_section)
1244 {
1245 // LEAVE("no state section");
1246 return;
1247 }
1248
1249 pref_name = g_object_get_data (G_OBJECT(column), PREF_NAME);
1250 if (!pref_name)
1251 {
1252 // LEAVE("column has no pref_name");
1253 return;
1254 }
1255
1256 /* Create the menu if we don't have one already */
1257 if (!priv->column_menu)
1258 {
1259 priv->column_menu = gtk_menu_new();
1260 g_object_ref_sink (priv->column_menu);
1261 }
1262
1263 /* Create the check menu item */
1264 column_name = g_object_get_data (G_OBJECT(column), REAL_TITLE);
1265 if (!column_name)
1266 column_name = gtk_tree_view_column_get_title (column);
1267 widget = gtk_check_menu_item_new_with_label (column_name);
1268 gtk_menu_shell_append (GTK_MENU_SHELL(priv->column_menu), widget);
1269
1270 /* Should never be able to hide the first column */
1271 if (g_object_get_data (G_OBJECT(column), ALWAYS_VISIBLE))
1272 {
1273 g_object_set_data (G_OBJECT(widget), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
1274 gtk_widget_set_sensitive (widget, FALSE);
1275 }
1276
1277 binding = g_object_bind_property (G_OBJECT(widget), "active", G_OBJECT(column), "visible", 0);
1278 g_object_set_data (G_OBJECT(widget), "column-binding", binding);
1279
1280 /* Store data on the widget for callbacks */
1281 key = g_strdup_printf ("%s_%s", pref_name, STATE_KEY_SUFF_VISIBLE);
1282 g_object_set_data_full (G_OBJECT(widget), STATE_KEY, key, g_free);
1283 // LEAVE(" ");
1284 }
1285
1286 /** This function is called to build the column selection menu. It
1287 * first destroys any old column selection menu, then checks to see
1288 * if a new menu should be built. If so, it calls the
1289 * gnc_tree_view_create_menu_item() for each column in the view.
1290 * This function is invoked when either the "state-section" or the
1291 * "show-column-menu" property is changed on the view.
1292 *
1293 * @param view Build a selection menu for this tree view.
1294 *
1295 * @internal
1296 */
1297 static void
gnc_tree_view_build_column_menu(GncTreeView * view)1298 gnc_tree_view_build_column_menu (GncTreeView *view)
1299 {
1300 GncTreeViewPrivate *priv;
1301 GList *column_list;
1302
1303 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1304
1305 ENTER("view %p", view);
1306 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1307
1308 /* Destroy any old menu */
1309 if (priv->column_menu)
1310 {
1311 g_object_unref (priv->column_menu);
1312 priv->column_menu = NULL;
1313 }
1314
1315 if (priv->show_column_menu && priv->state_section)
1316 {
1317 /* Show the menu popup button */
1318 if (priv->column_menu_column)
1319 gtk_tree_view_column_set_visible (priv->column_menu_column, TRUE);
1320
1321 /* Now build a new menu */
1322 column_list = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1323 g_list_foreach (column_list, (GFunc)gnc_tree_view_create_menu_item, view);
1324 g_list_free (column_list);
1325 }
1326 else
1327 {
1328 /* Hide the menu popup button */
1329 if (priv->column_menu_column)
1330 gtk_tree_view_column_set_visible (priv->column_menu_column, FALSE);
1331 }
1332 LEAVE("menu: show %d, section %s", priv->show_column_menu,
1333 priv->state_section ? priv->state_section : "(null)");
1334 }
1335
1336 /** This function is called to synchronize the checkbox on a menu item
1337 * with the current visibility for the corresponding column.
1338 *
1339 * @param checkmenuitem The menu item to update.
1340 *
1341 * @param view The tree view.
1342 *
1343 * @internal
1344 */
1345 static void
gnc_tree_view_update_column_menu_item(GtkCheckMenuItem * checkmenuitem,GncTreeView * view)1346 gnc_tree_view_update_column_menu_item (GtkCheckMenuItem *checkmenuitem,
1347 GncTreeView *view)
1348 {
1349 gboolean visible;
1350
1351 g_return_if_fail (GTK_IS_CHECK_MENU_ITEM(checkmenuitem));
1352 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1353
1354 if (g_object_get_data (G_OBJECT(checkmenuitem), ALWAYS_VISIBLE))
1355 {
1356 visible = TRUE;
1357 }
1358 else
1359 {
1360 GBinding *binding = g_object_get_data (G_OBJECT(checkmenuitem), "column-binding");
1361 GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN(g_binding_get_target (binding));
1362
1363 visible = gtk_tree_view_column_get_visible (column);
1364 }
1365 gtk_check_menu_item_set_active (checkmenuitem, visible);
1366 }
1367
1368 /** This function when the user clicks on the button to show the
1369 * column selection menu. It first synchronize the checkboxes on all
1370 * menu item with the state visibility values. It then pops up the
1371 * menu for the user to choose from.
1372 *
1373 * @param column The tree column containing the column selection
1374 * button.
1375 *
1376 * @param view The tree view.
1377 *
1378 * @internal
1379 */
1380 static void
gnc_tree_view_select_column_cb(GtkTreeViewColumn * column,GncTreeView * view)1381 gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
1382 GncTreeView *view)
1383 {
1384 GncTreeViewPrivate *priv;
1385 GtkWidget *menu;
1386
1387 g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN(column));
1388 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1389
1390 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1391 menu = priv->column_menu;
1392 if (!menu)
1393 return;
1394
1395 /* Synchronize the menu before display */
1396 gtk_container_foreach (GTK_CONTAINER(menu),
1397 (GtkCallback)gnc_tree_view_update_column_menu_item,
1398 view);
1399
1400 /* Ensure all components are visible */
1401 gtk_widget_show_all (menu);
1402
1403 /* Pop the menu up at the button */
1404 gtk_menu_popup_at_pointer (GTK_MENU(priv->column_menu), NULL);
1405 }
1406
1407
gnc_tree_view_expand_columns(GncTreeView * view,gchar * first_column_name,...)1408 void gnc_tree_view_expand_columns (GncTreeView *view,
1409 gchar *first_column_name,
1410 ...)
1411 {
1412 GncTreeViewPrivate *priv;
1413 GtkTreeViewColumn *column;
1414 gboolean hide_spacer;
1415 GList *columns, *tmp;
1416 gchar *name, *pref_name;
1417 va_list args;
1418
1419 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1420 ENTER(" ");
1421 va_start (args, first_column_name);
1422 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1423 name = first_column_name;
1424 hide_spacer = FALSE;
1425
1426 /* First disable the expand property on all (non-infrastructure) columns. */
1427 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1428 for (tmp = columns; tmp; tmp = g_list_next (tmp))
1429 {
1430 column = tmp->data;
1431 pref_name = g_object_get_data (G_OBJECT(column), PREF_NAME);
1432 if (pref_name != NULL)
1433 gtk_tree_view_column_set_expand (column, FALSE);
1434 }
1435 g_list_free(columns);
1436
1437 /* Now enable it on the requested columns. */
1438 while (name != NULL)
1439 {
1440 column = gnc_tree_view_find_column_by_name (view, name);
1441 if (column != NULL)
1442 {
1443 gtk_tree_view_column_set_expand (column, TRUE);
1444 hide_spacer = TRUE;
1445 }
1446 name = va_arg (args, gchar*);
1447 }
1448 va_end (args);
1449
1450 LEAVE(" ");
1451 }
1452
1453
1454 /* Links the cell backgrounds of the two control columns to the model or
1455 cell data function */
1456 static void
update_control_cell_renderers_background(GncTreeView * view,GtkTreeViewColumn * col,gint column,GtkTreeCellDataFunc func)1457 update_control_cell_renderers_background (GncTreeView *view, GtkTreeViewColumn *col,
1458 gint column, GtkTreeCellDataFunc func )
1459 {
1460 GList *renderers;
1461 GtkCellRenderer *cell;
1462 GList *node;
1463
1464 renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(col));
1465
1466 /* Update the cell background in the list of renderers */
1467 for (node = renderers; node; node = node->next)
1468 {
1469 cell = node->data;
1470 if (func == NULL)
1471 gtk_tree_view_column_add_attribute (col, cell, "cell-background", column);
1472 else
1473 gtk_tree_view_column_set_cell_data_func (col, cell, func, view, NULL);
1474 }
1475 g_list_free (renderers);
1476 }
1477
1478
1479 /* This function links the cell backgrounds of the two control columns to a column
1480 in the model that has color strings or a cell data function */
1481 void
gnc_tree_view_set_control_column_background(GncTreeView * view,gint column,GtkTreeCellDataFunc func)1482 gnc_tree_view_set_control_column_background (GncTreeView *view, gint column, GtkTreeCellDataFunc func )
1483 {
1484 GncTreeViewPrivate *priv;
1485
1486 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1487
1488 ENTER("view %p, column %d, func %p", view, column, func);
1489 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1490
1491 update_control_cell_renderers_background (view, priv->column_menu_column, column, func);
1492
1493 LEAVE(" ");
1494 }
1495
1496
1497 /* This allows the columns to be setup without the model connected */
1498 //FIXME I think this should be specified as a parameter to the add columns functions...
1499 void
gnc_tree_view_set_sort_user_data(GncTreeView * view,GtkTreeModel * s_model)1500 gnc_tree_view_set_sort_user_data (GncTreeView *view, GtkTreeModel *s_model)
1501 {
1502 GncTreeViewPrivate *priv;
1503
1504 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1505
1506 ENTER("view %p, sort_model %p", view, s_model);
1507 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1508
1509 priv->sort_model = s_model;
1510 LEAVE(" ");
1511 }
1512
1513
1514 /** This function is called to set the "show-column-menu" property on
1515 * this view. This function has no visible effect if the
1516 * "state-section" property has not been set.
1517 *
1518 * Parameters are defined in gnc-tree-view.h
1519 */
1520 void
gnc_tree_view_set_show_column_menu(GncTreeView * view,gboolean visible)1521 gnc_tree_view_set_show_column_menu (GncTreeView *view,
1522 gboolean visible)
1523 {
1524 GncTreeViewPrivate *priv;
1525
1526 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1527
1528 ENTER("view %p, show menu %d", view, visible);
1529 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1530 priv->show_column_menu = visible;
1531 gnc_tree_view_build_column_menu (view);
1532 LEAVE(" ");
1533 }
1534
1535 /** This function is called to get the current value of the
1536 * "show-column-menu" property. It returns the same value passed to
1537 * gnc_tree_view_set_show_menu_column().
1538 *
1539 * Parameters are defined in gnc-tree-view.h
1540 */
1541 gboolean
gnc_tree_view_get_show_column_menu(GncTreeView * view)1542 gnc_tree_view_get_show_column_menu (GncTreeView *view)
1543 {
1544 GncTreeViewPrivate *priv;
1545
1546 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), FALSE);
1547
1548 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1549 return (priv->show_column_menu);
1550 }
1551
1552 /** @} */
1553
1554 /************************************************************/
1555 /* Tree View Creation */
1556 /************************************************************/
1557
1558 static gint
gnc_tree_view_count_visible_columns(GncTreeView * view)1559 gnc_tree_view_count_visible_columns (GncTreeView *view)
1560 {
1561 GList *columns, *node;
1562 gint count = 0;
1563
1564 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1565 for (node = columns; node; node = node->next)
1566 {
1567 GtkTreeViewColumn *col = GTK_TREE_VIEW_COLUMN(node->data);
1568
1569 if (g_object_get_data (G_OBJECT(col), DEFAULT_VISIBLE) ||
1570 g_object_get_data (G_OBJECT(col), ALWAYS_VISIBLE))
1571 count++;
1572 }
1573 g_list_free (columns);
1574 return count;
1575 }
1576
1577 void
gnc_tree_view_configure_columns(GncTreeView * view)1578 gnc_tree_view_configure_columns (GncTreeView *view)
1579 {
1580 GncTreeViewPrivate *priv;
1581 GtkTreeViewColumn *column;
1582 GList *columns;
1583 gboolean hide_menu_column;
1584
1585 g_return_if_fail (GNC_IS_TREE_VIEW(view));
1586
1587 ENTER(" ");
1588
1589 /* Update the view and saved state */
1590 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
1591 g_list_foreach (columns, (GFunc)gnc_tree_view_update_visibility, view);
1592 g_list_free (columns);
1593
1594 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1595 if (priv->state_section)
1596 priv->seen_state_visibility = TRUE;
1597
1598 /* If only the first column is visible, hide the spacer and make that
1599 * column expand. */
1600 hide_menu_column = (gnc_tree_view_count_visible_columns (view) == 1);
1601 column = gtk_tree_view_get_column (GTK_TREE_VIEW(view), 0);
1602 gtk_tree_view_column_set_expand (column, hide_menu_column);
1603 gtk_tree_view_column_set_visible (priv->column_menu_column, !hide_menu_column);
1604
1605 LEAVE(" ");
1606 }
1607
1608
1609 /** This internal function sets a variety of common properties on a
1610 * newly created GtkTreeViewColumn and its renderer.
1611 *
1612 * @param view A pointer to a generic GncTreeView.
1613 *
1614 * @param column The newly created tree view column.
1615 *
1616 * @param pref_name The internal name of this column. This name is
1617 * used in several functions to look up the column, and it is also
1618 * used to create the keys used to record the column width and
1619 * visibility in saved state.
1620 *
1621 * @param data_column The index of the GtkTreeModel data column used
1622 * to determine the data that will be displayed in this column for
1623 * each row in the view. Use GNC_TREE_VIEW_COLUMN_DATA_NONE if you
1624 * plan on using an non-model data source for this column.
1625 *
1626 * @param default_width The width this column should be if not
1627 * specified by saved state. If the this value is zero, the column will
1628 * be marked as automatically sized.
1629 *
1630 * @param resizable Whether to mark the column as user resizable.
1631 * This marking is only relevant for fixed width columns.
1632 *
1633 * @param column_sort_fn The function that GtkTreeModelSort
1634 * will call to compare two rows to determine their displayed order.
1635 *
1636 * @internal
1637 */
1638 static void
gnc_tree_view_column_properties(GncTreeView * view,GtkTreeViewColumn * column,const gchar * pref_name,gint data_column,gint default_width,gboolean resizable,GtkTreeIterCompareFunc column_sort_fn)1639 gnc_tree_view_column_properties (GncTreeView *view,
1640 GtkTreeViewColumn *column,
1641 const gchar *pref_name,
1642 gint data_column,
1643 gint default_width,
1644 gboolean resizable,
1645 GtkTreeIterCompareFunc column_sort_fn)
1646 {
1647 GncTreeViewPrivate *priv;
1648 GtkTreeModel *s_model;
1649 gboolean visible;
1650 int width = 0;
1651
1652 /* Set data used by other functions */
1653 if (pref_name)
1654 g_object_set_data (G_OBJECT(column), PREF_NAME, (gpointer)pref_name);
1655 if (data_column == 0)
1656 g_object_set_data (G_OBJECT(column), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
1657 g_object_set_data (G_OBJECT(column), MODEL_COLUMN,
1658 GINT_TO_POINTER(data_column));
1659
1660 /* Get visibility */
1661 visible = gnc_tree_view_column_visible (view, NULL, pref_name);
1662
1663 /* Set column attributes (without the sizing) */
1664 g_object_set (G_OBJECT(column),
1665 "visible", visible,
1666 "resizable", resizable && pref_name != NULL,
1667 "reorderable", pref_name != NULL,
1668 NULL);
1669
1670 /* Get width */
1671 if (default_width == 0)
1672 {
1673 /* Set the sizing column attributes */
1674 g_object_set (G_OBJECT(column),
1675 "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
1676 NULL);
1677 }
1678 else
1679 {
1680
1681 /* If saved state comes back with a width of zero (or there is no saved
1682 * state width) the use the default width for the column. Allow for
1683 * padding L and R of the displayed data. */
1684 if (width == 0)
1685 width = default_width + 10;
1686 if (width == 0)
1687 width = 10;
1688
1689 /* Set the sizing column attributes (including fixed-width) */
1690 g_object_set (G_OBJECT(column),
1691 "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
1692 "fixed-width", width,
1693 NULL);
1694 /* Save the initially calculated preferred width for later
1695 * comparison to the actual width when saving state. Can't
1696 * use the "fixed-width" property for that because it changes
1697 * when the user resizes the column.
1698 */
1699 g_object_set_data (G_OBJECT(column),
1700 "default-width", GINT_TO_POINTER(width));
1701 }
1702
1703 s_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
1704 if (GTK_IS_TREE_SORTABLE(s_model))
1705 {
1706 gtk_tree_view_column_set_sort_column_id (column, data_column);
1707 if (column_sort_fn)
1708 {
1709 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(s_model),
1710 data_column, column_sort_fn,
1711 GINT_TO_POINTER(data_column),
1712 NULL /* destroy fn */);
1713 }
1714 }
1715
1716 // Used in registers, sort model not connected to view yet
1717 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1718 if (priv->sort_model != NULL)
1719 {
1720 gtk_tree_view_column_set_sort_column_id (column, data_column);
1721 if (column_sort_fn)
1722 {
1723 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(priv->sort_model),
1724 data_column, column_sort_fn,
1725 view,
1726 NULL /* destroy fn */);
1727 }
1728 }
1729
1730 /* Add to the column selection menu */
1731 if (pref_name)
1732 {
1733 gnc_tree_view_create_menu_item (column, view);
1734 }
1735 }
1736
1737 /** This function adds a new toggle column to a GncTreeView base view.
1738 * It takes all the parameters necessary to hook a GtkTreeModel
1739 * column to a GtkTreeViewColumn. It handles creating a tooltip to
1740 * show the full title name, and setting the sort and edit callback
1741 * functions. If the tree has a state section associated with it,
1742 * this function also wires up the column so that its visibility and
1743 * width are remembered.
1744 *
1745 * Parameters are defined in gnc-tree-view.h
1746 */
1747 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)1748 gnc_tree_view_add_toggle_column (GncTreeView *view,
1749 const gchar *column_title,
1750 const gchar *column_short_title,
1751 const gchar *pref_name,
1752 gint model_data_column,
1753 gint model_visibility_column,
1754 GtkTreeIterCompareFunc column_sort_fn,
1755 renderer_toggled toggle_edited_cb)
1756 {
1757 GtkTreeViewColumn *column;
1758 GtkCellRenderer *renderer;
1759
1760 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1761
1762 renderer = gtk_cell_renderer_toggle_new ();
1763 if (!toggle_edited_cb)
1764 {
1765 gtk_cell_renderer_toggle_set_activatable (GTK_CELL_RENDERER_TOGGLE(renderer), FALSE);
1766 }
1767 column =
1768 gtk_tree_view_column_new_with_attributes (column_short_title,
1769 renderer,
1770 "active", model_data_column,
1771 NULL);
1772
1773 /* Add the full title to the object for menu creation */
1774 g_object_set_data_full (G_OBJECT(column), REAL_TITLE,
1775 g_strdup(column_title), g_free);
1776 if (toggle_edited_cb)
1777 g_signal_connect (G_OBJECT(renderer), "toggled",
1778 G_CALLBACK(toggle_edited_cb), view);
1779
1780 if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1781 gtk_tree_view_column_add_attribute (column, renderer,
1782 "visible", model_visibility_column);
1783
1784
1785 gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1786 0, FALSE, column_sort_fn);
1787
1788 gnc_tree_view_append_column (view, column);
1789
1790 /* Also add the full title to the object as a tooltip */
1791 gtk_widget_set_tooltip_text (gtk_tree_view_column_get_button (column), column_title);
1792
1793 return column;
1794 }
1795
1796 static void
renderer_editing_canceled_cb(GtkCellRenderer * renderer,gpointer user_data)1797 renderer_editing_canceled_cb (GtkCellRenderer *renderer, gpointer user_data)
1798 {
1799 GncTreeView *view = user_data;
1800 GncTreeViewPrivate *priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1801 if (priv->editing_finished_cb)
1802 (priv->editing_finished_cb)(view, priv->editing_cb_data);
1803 }
1804
1805 static void
renderer_editing_started_cb(GtkCellRenderer * renderer,GtkCellEditable * editable,gchar * path,gpointer user_data)1806 renderer_editing_started_cb (GtkCellRenderer *renderer,
1807 GtkCellEditable *editable, gchar *path, gpointer user_data)
1808 {
1809 GncTreeView *view = user_data;
1810 GncTreeViewPrivate *priv = GNC_TREE_VIEW_GET_PRIVATE(view);
1811 if (priv->editing_started_cb)
1812 (priv->editing_started_cb)(view, priv->editing_cb_data);
1813 }
1814
1815 static void
renderer_edited_cb(GtkCellRendererText * renderer,gchar * path,gchar * new_text,gpointer user_data)1816 renderer_edited_cb (GtkCellRendererText *renderer, gchar *path,
1817 gchar *new_text, 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
1825
1826 static GtkTreeViewColumn *
add_text_column_variant(GncTreeView * view,GtkCellRenderer * renderer,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)1827 add_text_column_variant (GncTreeView *view, GtkCellRenderer *renderer,
1828 const gchar *column_title,
1829 const gchar *pref_name,
1830 const gchar *icon_name,
1831 const gchar *sizing_text,
1832 gint model_data_column,
1833 gint model_visibility_column,
1834 GtkTreeIterCompareFunc column_sort_fn)
1835 {
1836 GtkTreeViewColumn *column;
1837 PangoLayout* layout;
1838 int default_width, title_width;
1839
1840 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1841
1842 column = gtk_tree_view_column_new ();
1843 gtk_tree_view_column_set_title (column, column_title);
1844
1845 /* Set up an icon renderer if requested */
1846 if (icon_name)
1847 {
1848 GtkCellRenderer *renderer_pix = gtk_cell_renderer_pixbuf_new ();
1849 g_object_set (renderer_pix, "icon-name", icon_name, NULL);
1850 gtk_tree_view_column_pack_start (column, renderer_pix, FALSE);
1851 }
1852
1853 /* Set up a text renderer and attributes */
1854 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1855
1856 /* Set up the callbacks for when editing */
1857 g_signal_connect (G_OBJECT(renderer), "editing-canceled",
1858 (GCallback)renderer_editing_canceled_cb, view);
1859
1860 g_signal_connect (G_OBJECT(renderer), "editing-started",
1861 (GCallback)renderer_editing_started_cb, view);
1862
1863 g_signal_connect (G_OBJECT(renderer), "edited",
1864 (GCallback)renderer_edited_cb, view);
1865
1866 /* Set renderer attributes controlled by the model */
1867 if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
1868 gtk_tree_view_column_add_attribute (column, renderer,
1869 "text", model_data_column);
1870 if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
1871 gtk_tree_view_column_add_attribute (column, renderer,
1872 "visible", model_visibility_column);
1873
1874 /* Default size is the larger of the column title and the sizing text */
1875 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
1876 pango_layout_get_pixel_size (layout, &title_width, NULL);
1877 g_object_unref (layout);
1878 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
1879 pango_layout_get_pixel_size (layout, &default_width, NULL);
1880 g_object_unref (layout);
1881 default_width = MAX(default_width, title_width);
1882 if (default_width)
1883 default_width += 10; /* padding on either side */
1884 gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
1885 default_width, TRUE, column_sort_fn);
1886
1887 gnc_tree_view_append_column (view, column);
1888 return column;
1889 }
1890
1891
1892 /** This function adds a new text column to a GncTreeView base view.
1893 * It takes all the parameters necessary to hook a GtkTreeModel
1894 * column to a GtkTreeViewColumn. If the tree has a state section
1895 * associated with it, this function also wires up the column so that
1896 * its visibility and width are remembered.
1897 *
1898 * Parameters are defined in gnc-tree-view.h
1899 */
1900 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)1901 gnc_tree_view_add_text_column (GncTreeView *view,
1902 const gchar *column_title,
1903 const gchar *pref_name,
1904 const gchar *icon_name,
1905 const gchar *sizing_text,
1906 gint model_data_column,
1907 gint model_visibility_column,
1908 GtkTreeIterCompareFunc column_sort_fn)
1909 {
1910 GtkCellRenderer *renderer;
1911
1912 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1913
1914 renderer = gtk_cell_renderer_text_new ();
1915
1916 return add_text_column_variant (view, renderer,
1917 column_title, pref_name,
1918 icon_name, sizing_text,
1919 model_data_column,
1920 model_visibility_column,
1921 column_sort_fn);
1922 }
1923
1924 /** This function adds a new text view column to a GncTreeView base view.
1925 * It takes all the parameters necessary to hook a GtkTreeModel
1926 * column to a GtkTreeViewColumn. If the tree has a state section
1927 * associated with it, this function also wires up the column so that
1928 * its visibility and width are remembered.
1929 *
1930 * Parameters are defined in gnc-tree-view.h
1931 */
1932 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)1933 gnc_tree_view_add_text_view_column (GncTreeView *view,
1934 const gchar *column_title,
1935 const gchar *pref_name,
1936 const gchar *icon_name,
1937 const gchar *sizing_text,
1938 gint model_data_column,
1939 gint model_visibility_column,
1940 GtkTreeIterCompareFunc column_sort_fn)
1941 {
1942 GtkCellRenderer *renderer;
1943
1944 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1945
1946 renderer = gnc_cell_renderer_text_view_new ();
1947
1948 return add_text_column_variant (view, renderer,
1949 column_title, pref_name,
1950 icon_name, sizing_text,
1951 model_data_column,
1952 model_visibility_column,
1953 column_sort_fn);
1954 }
1955
1956
1957 /** This function adds a new date column to a GncTreeView base view.
1958 * It takes all the parameters necessary to hook a GtkTreeModel
1959 * column to a GtkTreeViewColumn. If the tree has a state section
1960 * associated with it, this function also wires up the column so that
1961 * its visibility and width are remembered.
1962 *
1963 * Parameters are defined in gnc-tree-view.h
1964 */
1965 GtkTreeViewColumn *
gnc_tree_view_add_date_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)1966 gnc_tree_view_add_date_column (GncTreeView *view,
1967 const gchar *column_title,
1968 const gchar *pref_name,
1969 const gchar *icon_name,
1970 const gchar *sizing_text,
1971 gint model_data_column,
1972 gint model_visibility_column,
1973 GtkTreeIterCompareFunc column_sort_fn)
1974 {
1975 GtkTreeViewColumn *column;
1976 GtkCellRenderer *renderer;
1977 PangoLayout* layout;
1978 int default_width, title_width;
1979
1980 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
1981
1982 column = gtk_tree_view_column_new ();
1983 gtk_tree_view_column_set_title (column, column_title);
1984
1985 /* Set up an icon renderer if requested */
1986 if (icon_name)
1987 {
1988 renderer = gtk_cell_renderer_pixbuf_new ();
1989 g_object_set (renderer, "icon-name", icon_name, NULL);
1990 gtk_tree_view_column_pack_start (column, renderer, FALSE);
1991 }
1992
1993 /* Set up a text renderer and attributes */
1994 renderer = gnc_cell_renderer_date_new (TRUE);
1995 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1996
1997 /* Set renderer attributes controlled by the model */
1998 if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
1999 gtk_tree_view_column_add_attribute (column, renderer,
2000 "text", model_data_column);
2001 if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
2002 gtk_tree_view_column_add_attribute (column, renderer,
2003 "visible", model_visibility_column);
2004
2005 /* Default size is the larger of the column title and the sizing text */
2006 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
2007 pango_layout_get_pixel_size (layout, &title_width, NULL);
2008 g_object_unref (layout);
2009 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
2010 pango_layout_get_pixel_size (layout, &default_width, NULL);
2011 g_object_unref (layout);
2012 default_width = MAX(default_width, title_width);
2013 if (default_width)
2014 default_width += 10; /* padding on either side */
2015 gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
2016 default_width, TRUE, column_sort_fn);
2017
2018 gnc_tree_view_append_column (view, column);
2019 return column;
2020 }
2021
2022
2023 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)2024 gnc_tree_view_add_combo_column (GncTreeView *view,
2025 const gchar *column_title,
2026 const gchar *pref_name,
2027 const gchar *sizing_text,
2028 gint model_data_column,
2029 gint model_visibility_column,
2030 GtkTreeModel *combo_tree_model,
2031 gint combo_model_text_column,
2032 GtkTreeIterCompareFunc column_sort_fn)
2033 {
2034 GtkTreeViewColumn *column;
2035 GtkCellRenderer *renderer;
2036 PangoLayout* layout;
2037 int default_width, title_width;
2038
2039 g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
2040
2041 column = gtk_tree_view_column_new ();
2042 gtk_tree_view_column_set_title (column, gettext(column_title));
2043
2044 /* Set up a renderer and attributes */
2045 renderer = gtk_cell_renderer_combo_new ();
2046 gtk_tree_view_column_pack_start (column, renderer, TRUE);
2047
2048 /* Set renderer attributes controlled by the model */
2049 if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
2050 gtk_tree_view_column_add_attribute (column, renderer,
2051 "text", model_data_column);
2052 if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
2053 gtk_tree_view_column_add_attribute (column, renderer,
2054 "visible", model_visibility_column);
2055
2056 /* Default size is the larger of the column title and the sizing text */
2057 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
2058 pango_layout_get_pixel_size (layout, &title_width, NULL);
2059 g_object_unref (layout);
2060 layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
2061 pango_layout_get_pixel_size (layout, &default_width, NULL);
2062 g_object_unref (layout);
2063 default_width = MAX(default_width, title_width);
2064 if (default_width)
2065 default_width += 10; /* padding on either side */
2066
2067 gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
2068 default_width, TRUE, column_sort_fn);
2069
2070 /* Stuff specific to combo */
2071 if (combo_tree_model)
2072 {
2073 g_object_set (G_OBJECT(renderer), "model", combo_tree_model,
2074 "text-column", combo_model_text_column, NULL);
2075 }
2076 /* TODO: has-entry? */
2077
2078 gnc_tree_view_append_column (view, column);
2079 return column;
2080 }
2081
2082 GtkCellRenderer *
gnc_tree_view_column_get_renderer(GtkTreeViewColumn * column)2083 gnc_tree_view_column_get_renderer (GtkTreeViewColumn *column)
2084 {
2085 GList *renderers;
2086 GtkCellRenderer *cr = NULL;
2087
2088 g_return_val_if_fail (GTK_TREE_VIEW_COLUMN(column), NULL);
2089
2090 /* Get the list of one renderer */
2091 renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(column));
2092 if (g_list_length (renderers) > 0)
2093 cr = GTK_CELL_RENDERER(renderers->data);
2094 g_list_free (renderers);
2095
2096 return cr;
2097 }
2098
2099 /** This function adds a new numeric column to a GncTreeView base
2100 * view. It takes all the parameters necessary to hook a
2101 * GtkTreeModel column to a GtkTreeViewColumn. If the tree has a
2102 * state section associated with it, this function also wires up the
2103 * column so that its visibility and width are remembered. A numeric
2104 * column is nothing more then a text column with a few extra
2105 * attributes.
2106 *
2107 * Parameters are defined in gnc-tree-view.h
2108 */
2109 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)2110 gnc_tree_view_add_numeric_column (GncTreeView *view,
2111 const gchar *column_title,
2112 const gchar *pref_name,
2113 const gchar *sizing_text,
2114 gint model_data_column,
2115 gint model_color_column,
2116 gint model_visibility_column,
2117 GtkTreeIterCompareFunc column_sort_fn)
2118 {
2119 GtkTreeViewColumn *column;
2120 GtkCellRenderer *renderer;
2121 gfloat alignment = 1.0;
2122
2123 column = gnc_tree_view_add_text_column (view, column_title, pref_name,
2124 NULL, sizing_text, model_data_column,
2125 model_visibility_column,
2126 column_sort_fn);
2127
2128 renderer = gnc_tree_view_column_get_renderer (column);
2129
2130 /* Right align the column title and data for both ltr and rtl */
2131 if (gtk_widget_get_direction (GTK_WIDGET(view)) == GTK_TEXT_DIR_RTL)
2132 alignment = 0.0;
2133
2134 g_object_set (G_OBJECT(column), "alignment", alignment, NULL);
2135 g_object_set (G_OBJECT(renderer), "xalign", alignment, NULL);
2136
2137 /* Change the text color */
2138 if (model_color_column != GNC_TREE_VIEW_COLUMN_COLOR_NONE)
2139 gtk_tree_view_column_add_attribute (column, renderer,
2140 "foreground", model_color_column);
2141
2142 return column;
2143 }
2144
2145 /** Add a column to a view based upon a GncTreeView. This function
2146 * knows about the two special columns on the right side of this type
2147 * of view, and adds the new column before these two columns. You
2148 * could say that it appends to the data columns and ignores the
2149 * infrastructure columns.
2150 *
2151 * Parameters are defined in gnc-tree-view.h
2152 */
2153 gint
gnc_tree_view_append_column(GncTreeView * view,GtkTreeViewColumn * column)2154 gnc_tree_view_append_column (GncTreeView *view,
2155 GtkTreeViewColumn *column)
2156 {
2157 GList *columns;
2158 int n;
2159
2160 /* There's no easy way to get this number. */
2161 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(view));
2162 n = g_list_length (columns);
2163 g_list_free (columns);
2164
2165 /* Ignore the initial column, the selection menu */
2166 if (n >= 1)
2167 n -= 1;
2168 return gtk_tree_view_insert_column (GTK_TREE_VIEW(view), column, n);
2169 }
2170
2171 static gboolean
get_column_next_to(GtkTreeView * tv,GtkTreeViewColumn ** col,gboolean backward)2172 get_column_next_to (GtkTreeView *tv, GtkTreeViewColumn **col, gboolean backward)
2173 {
2174 GList *cols, *node;
2175 GtkTreeViewColumn *c = NULL;
2176 gint seen = 0;
2177 gboolean wrapped = FALSE;
2178
2179 cols = gtk_tree_view_get_columns (tv);
2180 g_return_val_if_fail (g_list_length (cols) > 0, FALSE);
2181
2182 node = g_list_find (cols, *col);
2183 g_return_val_if_fail (node, FALSE);
2184 do
2185 {
2186 node = backward ? node->prev : node->next;
2187 if (!node)
2188 {
2189 wrapped = TRUE;
2190 node = backward ? g_list_last (cols) : cols;
2191 }
2192 c = GTK_TREE_VIEW_COLUMN (node->data);
2193 if (c && gtk_tree_view_column_get_visible (c))
2194 seen++;
2195 if (c == *col) break;
2196 }
2197 while (!seen);
2198
2199 g_list_free (cols);
2200 *col = c;
2201 return wrapped;
2202 }
2203
2204 gboolean
gnc_tree_view_path_is_valid(GncTreeView * view,GtkTreePath * path)2205 gnc_tree_view_path_is_valid (GncTreeView *view, GtkTreePath *path)
2206 {
2207 GtkTreeView *tv = GTK_TREE_VIEW(view);
2208 GtkTreeModel *s_model;
2209 GtkTreeIter iter;
2210
2211 s_model = gtk_tree_view_get_model (tv);
2212 return gtk_tree_model_get_iter (s_model, &iter, path);
2213 }
2214
2215 void
gnc_tree_view_keynav(GncTreeView * view,GtkTreeViewColumn ** col,GtkTreePath * path,GdkEventKey * event)2216 gnc_tree_view_keynav (GncTreeView *view, GtkTreeViewColumn **col,
2217 GtkTreePath *path, GdkEventKey *event)
2218 {
2219 GtkTreeView *tv = GTK_TREE_VIEW(view);
2220 gint depth;
2221 gboolean shifted;
2222
2223 if (event->type != GDK_KEY_PRESS) return;
2224
2225 switch (event->keyval)
2226 {
2227 case GDK_KEY_Tab:
2228 case GDK_KEY_ISO_Left_Tab:
2229 case GDK_KEY_KP_Tab:
2230 shifted = event->state & GDK_SHIFT_MASK;
2231 if (get_column_next_to (tv, col, shifted))
2232 {
2233 /* This is the end (or beginning) of the line, buddy. */
2234 depth = gtk_tree_path_get_depth (path);
2235 if (shifted)
2236 {
2237 if (!gtk_tree_path_prev (path) && depth > 1)
2238 {
2239 gtk_tree_path_up (path);
2240 }
2241 }
2242 else if (gtk_tree_view_row_expanded (tv, path))
2243 {
2244 gtk_tree_path_down (path);
2245 }
2246 else
2247 {
2248 gtk_tree_path_next (path);
2249 if (!gnc_tree_view_path_is_valid (view, path) && depth > 2)
2250 {
2251 gtk_tree_path_prev (path);
2252 gtk_tree_path_up (path);
2253 gtk_tree_path_next (path);
2254 }
2255 if (!gnc_tree_view_path_is_valid (view, path) && depth > 1)
2256 {
2257 gtk_tree_path_prev (path);
2258 gtk_tree_path_up (path);
2259 gtk_tree_path_next (path);
2260 }
2261 }
2262 }
2263 break;
2264
2265 case GDK_KEY_Return:
2266 case GDK_KEY_KP_Enter:
2267 if (gtk_tree_view_row_expanded (tv, path))
2268 {
2269 gtk_tree_path_down (path);
2270 }
2271 else
2272 {
2273 depth = gtk_tree_path_get_depth (path);
2274 gtk_tree_path_next (path);
2275 if (!gnc_tree_view_path_is_valid (view, path) && depth > 1)
2276 {
2277 gtk_tree_path_prev (path);
2278 gtk_tree_path_up (path);
2279 gtk_tree_path_next (path);
2280 }
2281 }
2282 break;
2283 }
2284 return;
2285 }
2286
2287 void
gnc_tree_view_set_editing_started_cb(GncTreeView * view,GFunc editing_started_cb,gpointer editing_cb_data)2288 gnc_tree_view_set_editing_started_cb (GncTreeView *view, GFunc editing_started_cb, gpointer editing_cb_data)
2289 {
2290 GncTreeViewPrivate *priv;
2291
2292 if (!view && !editing_started_cb)
2293 return;
2294
2295 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
2296
2297 priv->editing_started_cb = editing_started_cb;
2298 priv->editing_cb_data = editing_cb_data;
2299 }
2300
2301 void
gnc_tree_view_set_editing_finished_cb(GncTreeView * view,GFunc editing_finished_cb,gpointer editing_cb_data)2302 gnc_tree_view_set_editing_finished_cb (GncTreeView *view, GFunc editing_finished_cb, gpointer editing_cb_data)
2303 {
2304 GncTreeViewPrivate *priv;
2305
2306 if (!view && !editing_finished_cb)
2307 return;
2308
2309 priv = GNC_TREE_VIEW_GET_PRIVATE(view);
2310
2311 priv->editing_finished_cb = editing_finished_cb;
2312 priv->editing_cb_data = editing_cb_data;
2313 }
2314
2315 /** @} */
2316 /** @} */
2317