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             &current, &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             &current, &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             &current, 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                                                &current, &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