GnuCash 2.4.99
gnc-tree-view.c
Go to the documentation of this file.
00001 /*
00002  * gnc-tree-view.c -- new GtkTreeView with extra features used by
00003  *                    all the tree views in gnucash
00004  *
00005  * Copyright (C) 2003,2005 David Hampton <hampton@employees.org>
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License as
00009  * published by the Free Software Foundation; either version 2 of
00010  * the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, contact:
00019  *
00020  * Free Software Foundation           Voice:  +1-617-542-5942
00021  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
00022  * Boston, MA  02110-1301,  USA       gnu@gnu.org
00023  */
00024 
00034 #include "config.h"
00035 
00036 #include <gtk/gtk.h>
00037 #include <glib/gi18n.h>
00038 #include <gdk/gdkkeysyms.h>
00039 #include <string.h>
00040 
00041 #include "gnc-tree-view.h"
00042 #include "gnc-engine.h"
00043 #include "gnc-gconf-utils.h"
00044 #include "gnc-gnome-utils.h"
00045 #include "gnc-gobject-utils.h"
00046 
00047 /* The column id refers to a specific column in the tree model.  It is
00048  * also attached to the side of the tree column to allow lookup of a
00049  * GtkTreeViewColumn when passed a column id from the underlying
00050  * model. By convention, negative column numbers are used when the
00051  * visible column has no association with the underlying model.*/
00052 #define MODEL_COLUMN "model_column"
00053 
00054 /* For checkbox columns, this contains the real title for the column. */
00055 #define REAL_TITLE  "real_title"
00056 
00057 /* The name of this column as it should appear in gconf.  This is
00058  * attached to the column when it is created, and used to map back and
00059  * forth to gconf keys.  The actual gconf keys are built from these
00060  * strings. */
00061 #define PREF_NAME  "pref-name"
00062 
00063 /* The actual gconf key for a particular column visibility.  This is
00064  * attached to the menu items that are in the column selection menu.
00065  * Makes it very easy to update gconf when a menu item is toggled. */
00066 #define GCONF_KEY  "gconf-key"
00067 
00068 /* Gconf keys within this particular section of gconf. */
00069 #define GCONF_KEY_SORT_COLUMN  "sort_column"
00070 #define GCONF_KEY_SORT_ORDER   "sort_order"
00071 #define GCONF_KEY_COLUMN_ORDER "column_order"
00072 
00073 /* Partial gconf keys within this particular section of gconf. These
00074    are appended to the various column names to create the actual
00075    keys. */
00076 #define GCONF_KEY_VISIBLE      "visible"
00077 #define GCONF_KEY_WIDTH        "width"
00078 
00079 enum
00080 {
00081     PROP_0,
00082     PROP_GCONF_SECTION,
00083     PROP_SHOW_COLUMN_MENU,
00084 };
00085 
00088 /* This static indicates the debugging module that this .o belongs to.  */
00089 static QofLogModule log_module = GNC_MOD_GUI;
00090 
00091 /**** Declarations ******************************************************/
00092 static void gnc_tree_view_class_init (GncTreeViewClass *klass);
00093 static void gnc_tree_view_init (GncTreeView *view, GncTreeViewClass *klass);
00094 static void gnc_tree_view_finalize (GObject *object);
00095 static void gnc_tree_view_destroy (GtkObject *object);
00096 static void gnc_tree_view_set_property (GObject         *object,
00097                                         guint            prop_id,
00098                                         const GValue    *value,
00099                                         GParamSpec      *pspec);
00100 static void gnc_tree_view_get_property (GObject         *object,
00101                                         guint            prop_id,
00102                                         GValue          *value,
00103                                         GParamSpec      *pspec);
00104 static void gnc_tree_view_remove_gconf(GncTreeView *view);
00105 static gboolean gnc_tree_view_drop_ok_cb (GtkTreeView *view,
00106         GtkTreeViewColumn *column,
00107         GtkTreeViewColumn *prev_column,
00108         GtkTreeViewColumn *next_column,
00109         gpointer data);
00110 static void gtk_tree_view_sort_column_changed_cb (GtkTreeSortable *treesortable,
00111         GncTreeView *view);
00112 static void gnc_tree_view_build_column_menu (GncTreeView *view);
00113 static void gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
00114         GncTreeView *view);
00115 
00118 typedef struct GncTreeViewPrivate
00119 {
00120     /*  Spacer column */
00121     GtkTreeViewColumn *spacer_column;
00122     GtkTreeViewColumn *selection_column;
00123 
00124     /* Column selection menu related values */
00125     GtkTreeViewColumn *column_menu_column;
00126     GtkWidget         *column_menu;
00127     gboolean           show_column_menu;
00128 
00129     /* Gconf related values */
00130     gchar             *gconf_section;
00131     gboolean           seen_gconf_visibility;
00132     gulong             columns_changed_cb_id;
00133     gulong             sort_column_changed_cb_id;
00134     gulong             size_allocate_cb_id;
00135 } GncTreeViewPrivate;
00136 
00137 #define GNC_TREE_VIEW_GET_PRIVATE(o)  \
00138    (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_TREE_VIEW, GncTreeViewPrivate))
00139 
00140 
00141 /************************************************************/
00142 /*               g_object required functions                */
00143 /************************************************************/
00144 
00148 static GObjectClass *parent_class = NULL;
00149 
00156 GType
00157 gnc_tree_view_get_type (void)
00158 {
00159     static GType gnc_tree_view_type = 0;
00160 
00161     if (gnc_tree_view_type == 0)
00162     {
00163         static const GTypeInfo our_info =
00164         {
00165             sizeof (GncTreeViewClass),
00166             NULL,
00167             NULL,
00168             (GClassInitFunc) gnc_tree_view_class_init,
00169             NULL,
00170             NULL,
00171             sizeof (GncTreeView),
00172             0,
00173             (GInstanceInitFunc) gnc_tree_view_init
00174         };
00175 
00176         gnc_tree_view_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
00177                              GNC_TREE_VIEW_NAME,
00178                              &our_info, 0);
00179     }
00180 
00181     return gnc_tree_view_type;
00182 }
00183 
00193 static void
00194 gnc_tree_view_class_init (GncTreeViewClass *klass)
00195 {
00196     GObjectClass *gobject_class;
00197     GtkObjectClass *gtkobject_class;
00198 
00199     parent_class = g_type_class_peek_parent (klass);
00200 
00201     gobject_class = G_OBJECT_CLASS (klass);
00202     gtkobject_class = GTK_OBJECT_CLASS (klass);
00203 
00204     gobject_class->set_property = gnc_tree_view_set_property;
00205     gobject_class->get_property = gnc_tree_view_get_property;
00206 
00207     g_type_class_add_private(klass, sizeof(GncTreeViewPrivate));
00208 
00209     g_object_class_install_property (gobject_class,
00210                                      PROP_GCONF_SECTION,
00211                                      g_param_spec_string ("gconf-section",
00212                                              "Gconf Section",
00213                                              "The Gconf section to use for storing settings",
00214                                              NULL,
00215                                              G_PARAM_READWRITE));
00216     g_object_class_install_property (gobject_class,
00217                                      PROP_SHOW_COLUMN_MENU,
00218                                      g_param_spec_boolean ("show-column-menu",
00219                                              "Show Column Menu",
00220                                              "Show the column menu so user can change what columns are visible.",
00221                                              FALSE,
00222                                              G_PARAM_READWRITE));
00223 
00224     /* GObject signals */
00225     gobject_class->finalize = gnc_tree_view_finalize;
00226 
00227     /* GtkObject signals */
00228     gtkobject_class->destroy = gnc_tree_view_destroy;
00229 }
00230 
00240 static void
00241 gnc_tree_view_init (GncTreeView *view, GncTreeViewClass *klass)
00242 {
00243     GncTreeViewPrivate *priv;
00244     GtkTreeViewColumn *column;
00245     GtkWidget *icon;
00246     GtkRequisition requisition;
00247 
00248     gnc_gobject_tracking_remember(G_OBJECT(view), G_OBJECT_CLASS(klass));
00249 
00250     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00251     priv->column_menu = NULL;
00252     priv->show_column_menu = FALSE;
00253     priv->gconf_section = NULL;
00254     priv->seen_gconf_visibility = FALSE;
00255     priv->columns_changed_cb_id = 0;
00256     priv->sort_column_changed_cb_id = 0;
00257     priv->size_allocate_cb_id = 0;
00258 
00259     /* Ask gtk to help the user keep track of rows. */
00260     g_object_set(view, "rules-hint", TRUE, NULL);
00261 
00262     /* Handle column drag and drop */
00263     gtk_tree_view_set_column_drag_function(GTK_TREE_VIEW(view),
00264                                            gnc_tree_view_drop_ok_cb, NULL, NULL);
00265 
00266     /* Create the next to last column which is always present, visible,
00267      * and empty. Override the defaults and make this a one pixel wide
00268      * column, but have it take up any extra space in the window. */
00269     column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
00270                                             -1, -1, NULL);
00271     g_object_set(G_OBJECT(column),
00272                  "fixed-width", 1,
00273                  "expand", TRUE,
00274                  (gchar *)NULL);
00275     priv->spacer_column = column;
00276 
00277     /* Create the last column which contains the column selection
00278      * widget.  gnc_tree_view_add_text_column will do most of the
00279      * work. */
00280     icon = gtk_image_new_from_stock(GTK_STOCK_GO_DOWN,
00281                                     GTK_ICON_SIZE_SMALL_TOOLBAR);
00282     gtk_widget_show(icon);
00283     gtk_widget_size_request(icon, &requisition);
00284     column = gnc_tree_view_add_text_column (view, NULL, NULL, NULL, NULL,
00285                                             -1, -1, NULL);
00286     g_object_set(G_OBJECT(column),
00287                  "clickable", TRUE,
00288                  "widget", icon,
00289                  "fixed-width", requisition.width + 10,
00290                  (gchar *)NULL);
00291     priv->selection_column = column;
00292     g_signal_connect(G_OBJECT(column), "clicked",
00293                      G_CALLBACK (gnc_tree_view_select_column_cb),
00294                      view);
00295     priv->column_menu_column = column;
00296 }
00297 
00308 static void
00309 gnc_tree_view_finalize (GObject *object)
00310 {
00311     GncTreeView *view;
00312     GncTreeViewPrivate *priv;
00313 
00314     ENTER("view %p", object);
00315     g_return_if_fail (object != NULL);
00316     g_return_if_fail (GNC_IS_TREE_VIEW (object));
00317 
00318     gnc_gobject_tracking_forget(object);
00319 
00320     view = GNC_TREE_VIEW (object);
00321     priv = GNC_TREE_VIEW_GET_PRIVATE (view);
00322 
00323     if (G_OBJECT_CLASS (parent_class)->finalize)
00324         G_OBJECT_CLASS (parent_class)->finalize (object);
00325     LEAVE(" ");
00326 }
00327 
00339 static void
00340 gnc_tree_view_destroy (GtkObject *object)
00341 {
00342     GncTreeView *view;
00343     GncTreeViewPrivate *priv;
00344 
00345     ENTER("view %p", object);
00346     g_return_if_fail (object != NULL);
00347     g_return_if_fail (GNC_IS_TREE_VIEW (object));
00348 
00349     view = GNC_TREE_VIEW (object);
00350 
00351     gnc_tree_view_remove_gconf(view);
00352 
00353     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00354 
00355     if (priv->column_menu)
00356     {
00357         DEBUG("removing column selection menu");
00358         gtk_widget_unref(priv->column_menu);
00359         priv->column_menu = NULL;
00360     }
00361 
00362     if (GTK_OBJECT_CLASS (parent_class)->destroy)
00363         GTK_OBJECT_CLASS (parent_class)->destroy (object);
00364     LEAVE(" ");
00365 }
00366 
00369 /************************************************************/
00370 /*                g_object other functions                  */
00371 /************************************************************/
00372 
00383 static void
00384 gnc_tree_view_get_property (GObject     *object,
00385                             guint        prop_id,
00386                             GValue      *value,
00387                             GParamSpec  *pspec)
00388 {
00389     GncTreeView *view = GNC_TREE_VIEW (object);
00390     GncTreeViewPrivate *priv;
00391 
00392     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00393     switch (prop_id)
00394     {
00395     case PROP_GCONF_SECTION:
00396         g_value_set_string (value, priv->gconf_section);
00397         break;
00398     case PROP_SHOW_COLUMN_MENU:
00399         g_value_set_boolean (value, priv->show_column_menu);
00400         break;
00401     default:
00402         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
00403         break;
00404     }
00405 }
00406 
00407 
00416 static void
00417 gnc_tree_view_set_property (GObject      *object,
00418                             guint         prop_id,
00419                             const GValue *value,
00420                             GParamSpec   *pspec)
00421 {
00422     GncTreeView *view = GNC_TREE_VIEW (object);
00423 
00424     switch (prop_id)
00425     {
00426     case PROP_GCONF_SECTION:
00427         gnc_tree_view_set_gconf_section (view, g_value_get_string (value));
00428         break;
00429     case PROP_SHOW_COLUMN_MENU:
00430         gnc_tree_view_set_show_column_menu (view, g_value_get_boolean (value));
00431         break;
00432     default:
00433         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
00434         break;
00435     }
00436 }
00437 
00440 /************************************************************/
00441 /*                   Auxiliary Functions                    */
00442 /************************************************************/
00443 
00462 static GtkTreeViewColumn *
00463 view_column_find_by_model_id (GncTreeView *view,
00464                               const gint wanted)
00465 {
00466     GtkTreeViewColumn *column, *found = NULL;
00467     GList *column_list, *tmp;
00468     gint id;
00469 
00470     // ENTER("view %p, name %s", view, name);
00471     column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
00472     for (tmp = column_list; tmp; tmp = g_list_next(tmp))
00473     {
00474         column = tmp->data;
00475         id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), MODEL_COLUMN));
00476         if (id != wanted)
00477             continue;
00478         found = column;
00479         break;
00480     }
00481     g_list_free(column_list);
00482 
00483     // LEAVE("column %p", found);
00484     return found;
00485 }
00486 
00497 GtkTreeViewColumn *
00498 gnc_tree_view_find_column_by_name (GncTreeView *view,
00499                                    const gchar *wanted)
00500 {
00501     GtkTreeViewColumn *column, *found = NULL;
00502     GList *column_list, *tmp;
00503     const gchar *name;
00504 
00505     // ENTER("view %p, wanted %s", view, wanted);
00506     column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
00507     for (tmp = column_list; tmp; tmp = g_list_next(tmp))
00508     {
00509         column = tmp->data;
00510         name = g_object_get_data(G_OBJECT(column), PREF_NAME);
00511         if (!name || (strcmp(name, wanted) != 0))
00512             continue;
00513         found = column;
00514         break;
00515     }
00516     g_list_free(column_list);
00517 
00518     // LEAVE("column %p", found);
00519     return found;
00520 }
00521 
00524 /************************************************************/
00525 /*                     Tree Callbacks                       */
00526 /************************************************************/
00527 
00556 static gboolean
00557 gnc_tree_view_drop_ok_cb (GtkTreeView *view,
00558                           GtkTreeViewColumn *column,
00559                           GtkTreeViewColumn *prev_column,
00560                           GtkTreeViewColumn *next_column,
00561                           gpointer data)
00562 {
00563     const gchar *pref_name;
00564 
00565     /* Should we allow a drop at the left side of the tree view before
00566      * the widget to open a new display level?  I can think of cases
00567      * where the user might want to do this with a checkbox column. */
00568     if (prev_column == NULL)
00569         return TRUE;
00570 
00571     /* Do not allow a drop at the right side of the tree view after the
00572      * column selection widget.  */
00573     if (next_column == NULL)
00574         return FALSE;
00575 
00576     /* Columns without pref names are considered fixed at the right hand
00577      * side of the view.  At the time of this writing, the only two are
00578      * the column where the "column selection widget" is stored, and the
00579      * "padding" column to the left of that where extra view space ends
00580      * up. */
00581     pref_name = g_object_get_data(G_OBJECT(prev_column), PREF_NAME);
00582     if (!pref_name)
00583         return FALSE;
00584 
00585     /* Everything else is allowed. */
00586     return TRUE;
00587 }
00588 
00600 static void
00601 gtk_tree_view_sort_column_changed_cb (GtkTreeSortable *treesortable,
00602                                       GncTreeView *view)
00603 {
00604     GncTreeViewPrivate *priv;
00605     GtkTreeViewColumn *column;
00606     const gchar             *gconf_section;
00607     gchar                   *column_pref_name;
00608     GtkSortType              order;
00609     gint                     id;
00610 
00611     g_return_if_fail(GTK_IS_TREE_SORTABLE(treesortable));
00612     g_return_if_fail(GNC_IS_TREE_VIEW(view));
00613 
00614     ENTER(" ");
00615     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00616     if (!priv->gconf_section)
00617     {
00618         LEAVE("no gconf section");
00619         return;
00620     }
00621 
00622     /* Set defaults, then extract data from the model */
00623     if (!gtk_tree_sortable_get_sort_column_id(treesortable, &id, &order))
00624     {
00625         order = GTK_SORT_ASCENDING;
00626         id = GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID;
00627     }
00628     column = view_column_find_by_model_id (view, id);
00629     column_pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
00630 
00631     if (!column_pref_name)
00632         column_pref_name = "none";
00633 
00634     /* Store the values in gconf */
00635     gconf_section = priv->gconf_section;
00636     gnc_gconf_set_string(gconf_section, GCONF_KEY_SORT_COLUMN,
00637                          column_pref_name, NULL);
00638     gnc_gconf_set_string(gconf_section, GCONF_KEY_SORT_ORDER,
00639                          gnc_enum_to_nick(GTK_TYPE_SORT_TYPE, order), NULL);
00640     LEAVE(" ");
00641 }
00642 
00656 static void
00657 gtk_tree_view_columns_changed_cb (GncTreeView *view,
00658                                   gpointer data)
00659 {
00660     GncTreeViewPrivate *priv;
00661     GList *column_list, *tmp;
00662     GSList *column_names = NULL;
00663     gchar *name;
00664 
00665     //ENTER("view %p, data %p", view, data);
00666     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00667     column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
00668     for (tmp = column_list; tmp; tmp = g_list_next(tmp))
00669     {
00670         name = g_object_get_data(tmp->data, PREF_NAME);
00671         if (!name)
00672             continue;
00673         column_names = g_slist_append(column_names, name);
00674         //DEBUG("%s", name);
00675     }
00676     g_list_free(column_list);
00677 
00678     gnc_gconf_set_list(priv->gconf_section, GCONF_KEY_COLUMN_ORDER,
00679                        GCONF_VALUE_STRING, column_names, NULL);
00680     g_slist_free(column_names);
00681     //LEAVE(" ");
00682 }
00683 
00698 static void
00699 gtk_tree_view_size_allocate_helper (GtkTreeViewColumn *column,
00700                                     GncTreeView *view)
00701 {
00702     GncTreeViewPrivate *priv;
00703     const gchar *column_pref_name;
00704     gint new_width, current_width;
00705     gchar *key;
00706 
00707     g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
00708     g_return_if_fail(GNC_IS_TREE_VIEW(view));
00709 
00710     /* Any updates to be made? */
00711     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00712     column_pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
00713     if (!column_pref_name)
00714         return;
00715     new_width = gtk_tree_view_column_get_width (column);
00716     if (!new_width)
00717         return;
00718 
00719     /* Do it */
00720     key = g_strdup_printf("%s_%s", column_pref_name, GCONF_KEY_WIDTH);
00721     current_width = gnc_gconf_get_int(priv->gconf_section, key, NULL);
00722     if (new_width != current_width)
00723     {
00724         gnc_gconf_set_int(priv->gconf_section, key, new_width, NULL);
00725         DEBUG("set %s width to %d", column_pref_name, new_width);
00726     }
00727     g_free(key);
00728 }
00729 
00750 static void
00751 gtk_tree_view_size_allocate_cb (GtkWidget *widget,
00752                                 GtkAllocation *allocation,
00753                                 gpointer data)
00754 {
00755     GncTreeView *view;
00756     GList *column_list;
00757 
00758     g_return_if_fail(GNC_IS_TREE_VIEW(widget));
00759 
00760     view = GNC_TREE_VIEW(widget);
00761     column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
00762     g_list_foreach(column_list, (GFunc)gtk_tree_view_size_allocate_helper, view);
00763     g_list_free(column_list);
00764 }
00765 
00768 /************************************************************/
00769 /*                  Gconf Setup / Callbacks                 */
00770 /************************************************************/
00771 
00797 static gboolean
00798 gnc_tree_view_column_visible (GncTreeView *view,
00799                               GtkTreeViewColumn *column,
00800                               const gchar *pref_name)
00801 {
00802     GncTreeViewPrivate *priv;
00803     gboolean visible;
00804     gchar *key;
00805 
00806     ENTER("column %p, name %s", column, pref_name ? pref_name : "(null)");
00807     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00808     if (column)
00809     {
00810         if (g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE))
00811         {
00812             LEAVE("1, first column");
00813             return TRUE;
00814         }
00815         pref_name = (gchar *)g_object_get_data(G_OBJECT(column), PREF_NAME);
00816         DEBUG("pref_name is %s", pref_name ? pref_name : "(null)");
00817     }
00818 
00819     if (!pref_name)
00820     {
00821         LEAVE("1, no pref name");
00822         return TRUE;
00823     }
00824 
00825     /* Using gconf? */
00826     if (priv->gconf_section)
00827     {
00828         if (priv->seen_gconf_visibility)
00829         {
00830             key = g_strdup_printf("%s_%s", pref_name, GCONF_KEY_VISIBLE);
00831             visible = gnc_gconf_get_bool(priv->gconf_section, key, NULL);
00832             g_free(key);
00833             LEAVE("%d, gconf visibility", visible);
00834             return visible;
00835         }
00836 
00837         visible = column ?
00838                   (g_object_get_data(G_OBJECT(column), DEFAULT_VISIBLE) != NULL) : FALSE;
00839         LEAVE("%d, gconf but using defaults", visible);
00840         return visible;
00841     }
00842 
00843     /* Check the default columns list */
00844     visible = column ?
00845               (g_object_get_data(G_OBJECT(column), DEFAULT_VISIBLE) != NULL) : FALSE;
00846     LEAVE("defaults says %d", visible);
00847     return visible;
00848 }
00849 
00860 static void
00861 gnc_tree_view_update_visibility (GtkTreeViewColumn *column,
00862                                  GncTreeView *view)
00863 {
00864     GncTreeViewPrivate *priv;
00865     gchar *name, *key;
00866     gboolean visible;
00867 
00868     g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
00869     g_return_if_fail(GNC_IS_TREE_VIEW(view));
00870 
00871     ENTER(" ");
00872     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00873     visible = gnc_tree_view_column_visible(view, column, NULL);
00874     gtk_tree_view_column_set_visible(column, visible);
00875     if (priv->gconf_section)
00876     {
00877         name = (gchar *)g_object_get_data(G_OBJECT(column), PREF_NAME);
00878         if (!name)
00879         {
00880             LEAVE("no pref name");
00881             return;
00882         }
00883         key = g_strdup_printf("%s_%s", name, GCONF_KEY_VISIBLE);
00884         gnc_gconf_set_bool(priv->gconf_section, key, visible, NULL);
00885         g_free(key);
00886         LEAVE("made %s, set gconf key", visible ? "visible" : "invisible");
00887         return;
00888     }
00889     LEAVE("made %s", visible ? "visible" : "invisible");
00890 }
00891 
00906 static void
00907 gnc_tree_view_set_sort_order (GncTreeView *view,
00908                               const gchar *name)
00909 {
00910     GncTreeViewPrivate *priv;
00911     GtkTreeModel *s_model;
00912     GtkSortType order;
00913     gint current;
00914 
00915     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00916     s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
00917     if (!s_model)
00918         return;
00919     order = gnc_enum_from_nick(GTK_TYPE_SORT_TYPE, name, GTK_SORT_ASCENDING);
00920     if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
00921             &current, NULL))
00922         current = GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID;
00923     g_signal_handler_block(s_model, priv->sort_column_changed_cb_id);
00924     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
00925                                          current, order);
00926     g_signal_handler_unblock(s_model, priv->sort_column_changed_cb_id);
00927     DEBUG("sort_order set to %s", gnc_enum_to_nick(GTK_TYPE_SORT_TYPE, order));
00928 }
00929 
00944 static void
00945 gnc_tree_view_set_sort_column (GncTreeView *view,
00946                                const gchar *name)
00947 {
00948     GncTreeViewPrivate *priv;
00949     GtkTreeModel *s_model;
00950     GtkTreeViewColumn *column;
00951     GtkSortType order;
00952     gint model_column, current;
00953 
00954     s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
00955     if (!s_model)
00956         return;
00957     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
00958     column = gnc_tree_view_find_column_by_name(view, name);
00959 
00960     if (!column)
00961     {
00962         g_signal_handler_block(s_model, priv->sort_column_changed_cb_id);
00963         gtk_tree_sortable_set_sort_column_id(
00964             GTK_TREE_SORTABLE(s_model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
00965             GTK_SORT_ASCENDING);
00966         g_signal_handler_unblock(s_model, priv->sort_column_changed_cb_id);
00967         return;
00968     }
00969 
00970     model_column =
00971         GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), MODEL_COLUMN));
00972     if (model_column == GNC_TREE_VIEW_COLUMN_DATA_NONE)
00973         return;
00974 
00975     if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
00976             &current, &order))
00977         order = GTK_SORT_ASCENDING;
00978     g_signal_handler_block(s_model, priv->sort_column_changed_cb_id);
00979     gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
00980                                          model_column, order);
00981     g_signal_handler_unblock(s_model, priv->sort_column_changed_cb_id);
00982     DEBUG("sort column set to %s", name);
00983 }
00984 
00998 static void
00999 gnc_tree_view_set_column_order (GncTreeView *view,
01000                                 const GSList *column_names)
01001 {
01002     GncTreeViewPrivate *priv;
01003     GtkTreeViewColumn *column, *prev;
01004     const gchar *name;
01005     const GSList *tmp;
01006     GSList *columns;
01007 
01008     /* First, convert from names to pointers */
01009     ENTER(" ");
01010     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01011     columns = NULL;
01012     for (tmp = column_names; tmp; tmp = g_slist_next(tmp))
01013     {
01014         name = gconf_value_get_string(tmp->data);
01015         column = gnc_tree_view_find_column_by_name(view, name);
01016         if (!column)
01017             continue;
01018         columns = g_slist_append(columns, column);
01019     }
01020 
01021     /* Then reorder the columns */
01022     g_signal_handler_block(view, priv->columns_changed_cb_id);
01023     for (prev = NULL, tmp = columns; tmp; tmp = g_slist_next(tmp))
01024     {
01025         column = tmp->data;
01026         gtk_tree_view_move_column_after(GTK_TREE_VIEW(view), column, prev);
01027         prev = column;
01028     }
01029     g_signal_handler_unblock(view, priv->columns_changed_cb_id);
01030 
01031     /* Clean up */
01032     g_slist_free(columns);
01033     LEAVE("column order set");
01034 }
01035 
01055 static void
01056 gnc_tree_view_gconf_changed (GConfClient *client,
01057                              guint cnxn_id,
01058                              GConfEntry *entry,
01059                              gpointer data)
01060 {
01061     GncTreeView *view;
01062     GncTreeViewPrivate *priv;
01063     GtkTreeViewColumn *column;
01064     GConfValue *value;
01065     const gchar *key, *local;
01066     gchar *column_name, *type_name;
01067     gboolean known;
01068     gint width;
01069 
01070     g_return_if_fail(GNC_IS_TREE_VIEW(data));
01071 
01072     ENTER(" ");
01073     view = GNC_TREE_VIEW(data);
01074     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01075     key = gconf_entry_get_key(entry);
01076     value = gconf_entry_get_value(entry);
01077     if (!value)
01078     {
01079         /* Values can be unset */
01080         LEAVE("Unset valued for %s", key);
01081         return;
01082     }
01083 
01084     DEBUG("Key %s, value %p", key, value);
01085     local = strrchr(key, '/') + 1;
01086     if (strcmp(local, GCONF_KEY_SORT_COLUMN) == 0)
01087     {
01088         gnc_tree_view_set_sort_column(view, gconf_value_get_string(value));
01089     }
01090     else if (strcmp(local, GCONF_KEY_SORT_ORDER) == 0)
01091     {
01092         gnc_tree_view_set_sort_order(view, gconf_value_get_string(value));
01093     }
01094     else if (strcmp(local, GCONF_KEY_COLUMN_ORDER) == 0)
01095     {
01096         gnc_tree_view_set_column_order(view, gconf_value_get_list(value));
01097     }
01098     else
01099     {
01100         /* Make a copy of the local part of the key so it can be split
01101          * into column name and key type */
01102         known = FALSE;
01103         column_name = g_strdup(local);
01104         type_name = strrchr(column_name, '_');
01105         *type_name++ = '\0';
01106 
01107         if (strcmp(type_name, GCONF_KEY_VISIBLE) == 0)
01108         {
01109             priv->seen_gconf_visibility = TRUE;
01110             column = gnc_tree_view_find_column_by_name(view, column_name);
01111             if (column)
01112             {
01113                 known = TRUE;
01114                 if (!g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE))
01115                 {
01116                     gtk_tree_view_column_set_visible(column, gconf_value_get_bool(value));
01117                 }
01118             }
01119         }
01120         else if (strcmp(type_name, GCONF_KEY_WIDTH) == 0)
01121         {
01122             width = gconf_value_get_int(value);
01123             column = gnc_tree_view_find_column_by_name(view, column_name);
01124             if (column)
01125             {
01126                 known = TRUE;
01127                 if (width && (width != gtk_tree_view_column_get_width(column)))
01128                 {
01129                     gtk_tree_view_column_set_fixed_width(column, width);
01130                 }
01131             }
01132         }
01133         if (!known)
01134         {
01135             DEBUG("Ignored key %s", key);
01136         }
01137         g_free(column_name);
01138     }
01139     LEAVE(" ");
01140 }
01141 
01153 static void
01154 gnc_tree_view_gconf_force_update (GncTreeView *view)
01155 {
01156     GncTreeViewPrivate *priv;
01157     GSList *all_entries, *etmp;
01158     GList *columns;
01159 
01160     ENTER("view %p", view);
01161     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01162     all_entries = gnc_gconf_client_all_entries(priv->gconf_section);
01163 
01164     /* Set a flag indicating that the gconf data section may be empty.
01165      * It will be checked later on and appropriate action taken if its
01166      * still set. */
01167     priv->seen_gconf_visibility = FALSE;
01168 
01169     /* Pull all the entries from gconf */
01170     for (etmp = all_entries; etmp; etmp = g_slist_next(etmp))
01171     {
01172         gnc_tree_view_gconf_changed(NULL, 0, etmp->data, view);
01173         gconf_entry_free(etmp->data);
01174     }
01175     g_slist_free(all_entries);
01176 
01177     /* No visibilities seen yet.  Write out any columns we may have */
01178     if (!priv->seen_gconf_visibility)
01179     {
01180         columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
01181         g_list_foreach(columns, (GFunc)gnc_tree_view_update_visibility, view);
01182         g_list_free(columns);
01183     }
01184 
01185     LEAVE(" ");
01186 }
01187 
01199 static void
01200 gnc_tree_view_remove_gconf(GncTreeView *view)
01201 {
01202     GncTreeViewPrivate *priv;
01203     GtkTreeModel *model;
01204 
01205     ENTER(" ");
01206     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01207     if (!priv->gconf_section)
01208     {
01209         LEAVE("no gconf section");
01210         return;
01211     }
01212 
01213     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01214     if (priv->sort_column_changed_cb_id)
01215     {
01216         model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
01217         if (model)
01218         {
01219             DEBUG("removing sort_column_changed callback (# %ld)",
01220                   priv->sort_column_changed_cb_id);
01221             g_signal_handler_disconnect(GTK_TREE_SORTABLE(model),
01222                                         priv->sort_column_changed_cb_id);
01223             priv->sort_column_changed_cb_id = 0;
01224         }
01225     }
01226 
01227     if (priv->columns_changed_cb_id)
01228     {
01229         DEBUG("removing columns_changed callback (# %ld)",
01230               priv->columns_changed_cb_id);
01231         g_signal_handler_disconnect(view, priv->columns_changed_cb_id);
01232         priv->columns_changed_cb_id = 0;
01233     }
01234 
01235     if (priv->size_allocate_cb_id)
01236     {
01237         DEBUG("removing size_allocate callback (# %ld)",
01238               priv->size_allocate_cb_id);
01239         g_signal_handler_disconnect(view, priv->size_allocate_cb_id);
01240         priv->size_allocate_cb_id = 0;
01241     }
01242 
01243     DEBUG("removing gconf notification");
01244     gnc_gconf_remove_notification(G_OBJECT(view), priv->gconf_section,
01245                                   GNC_TREE_VIEW_NAME);
01246     g_free(priv->gconf_section);
01247     priv->gconf_section = NULL;
01248     LEAVE(" ");
01249 }
01250 
01260 void
01261 gnc_tree_view_set_gconf_section (GncTreeView *view,
01262                                  const gchar *section)
01263 {
01264     GncTreeViewPrivate *priv;
01265     GtkTreeModel *model;
01266     gulong id;
01267 
01268     g_return_if_fail(GNC_IS_TREE_VIEW(view));
01269 
01270     ENTER("view %p, section %s", view, section);
01271     gnc_tree_view_remove_gconf(view);
01272 
01273     if (!section)
01274     {
01275         LEAVE("cleared gconf section");
01276         return;
01277     }
01278 
01279     /* Catch changes in gconf. Propagate to view. */
01280     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01281     priv->gconf_section = g_strdup(section);
01282     gnc_gconf_add_notification(G_OBJECT(view), section,
01283                                gnc_tree_view_gconf_changed,
01284                                GNC_TREE_VIEW_NAME);
01285 
01286     /* Catch changes to the sort column. Propagate to gconf. The key can
01287      * be set before the model, so the code must handle that case. */
01288     model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
01289     if (model)
01290         priv->sort_column_changed_cb_id =
01291             g_signal_connect(GTK_TREE_SORTABLE(model), "sort-column-changed",
01292                              (GCallback)gtk_tree_view_sort_column_changed_cb, view);
01293 
01294     /* Catch changes to the column order. Propagate to gconf */
01295     id = g_signal_connect(view, "columns-changed",
01296                           (GCallback)gtk_tree_view_columns_changed_cb, NULL);
01297     priv->columns_changed_cb_id = id;
01298 
01299     /* Catch changes to the column width. Propagate to gconf */
01300     id = g_signal_connect(view, "size-allocate",
01301                           (GCallback)gtk_tree_view_size_allocate_cb, NULL);
01302     priv->size_allocate_cb_id = id;
01303 
01304     /* Force an update of the view with all items from gconf. */
01305     gnc_tree_view_gconf_force_update(view);
01306 
01307     /* Rebuild the column visibility menu */
01308     gnc_tree_view_build_column_menu(view);
01309     LEAVE("set gconf section");
01310 }
01311 
01319 const gchar *
01320 gnc_tree_view_get_gconf_section (GncTreeView *view)
01321 {
01322     GncTreeViewPrivate *priv;
01323 
01324     g_return_val_if_fail(GNC_IS_TREE_VIEW(view), NULL);
01325 
01326     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01327     return(priv->gconf_section);
01328 }
01329 
01330 
01333 /************************************************************/
01334 /*                   Column Selection Menu                  */
01335 /************************************************************/
01336 
01351 static void
01352 gnc_tree_view_menu_item_toggled (GtkCheckMenuItem *checkmenuitem,
01353                                  GncTreeView *view)
01354 {
01355     GncTreeViewPrivate *priv;
01356     gboolean value;
01357     gchar *key;
01358 
01359     g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(checkmenuitem));
01360     g_return_if_fail(GNC_IS_TREE_VIEW(view));
01361 
01362     ENTER("checkmenuitem %p, view %p", checkmenuitem, view);
01363     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01364     if (!priv->gconf_section)
01365     {
01366         LEAVE("no gconf section");
01367         return;
01368     }
01369 
01370     key = g_object_get_data(G_OBJECT(checkmenuitem), GCONF_KEY);
01371     value = gtk_check_menu_item_get_active(checkmenuitem);
01372     gnc_gconf_set_bool(priv->gconf_section, key, value, NULL);
01373     LEAVE("set gconf section %s, key %s, visible %d",
01374           priv->gconf_section, key, value);
01375 }
01376 
01392 static void
01393 gnc_tree_view_create_menu_item (GtkTreeViewColumn *column,
01394                                 GncTreeView *view)
01395 {
01396     GncTreeViewPrivate *priv;
01397     GtkWidget *widget;
01398     const gchar *column_name, *pref_name;
01399     gulong id;
01400     gchar *key;
01401 
01402     // ENTER("view %p, column %p", view, column);
01403     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01404     if (!priv->gconf_section)
01405     {
01406         // LEAVE("no gconf section");
01407         return;
01408     }
01409 
01410     pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
01411     if (!pref_name)
01412     {
01413         // LEAVE("column has no pref_name");
01414         return;
01415     }
01416 
01417     /* Create the menu if we don't have one already */
01418     if (!priv->column_menu)
01419     {
01420         priv->column_menu = gtk_menu_new();
01421         g_object_ref_sink(priv->column_menu);
01422     }
01423 
01424     /* Create the check menu item */
01425     column_name = g_object_get_data(G_OBJECT(column), REAL_TITLE);
01426     if (!column_name)
01427         column_name = gtk_tree_view_column_get_title(column);
01428     widget = gtk_check_menu_item_new_with_label(column_name);
01429     gtk_menu_shell_append(GTK_MENU_SHELL(priv->column_menu), widget);
01430 
01431     /* Should never be able to hide the first column */
01432     if (g_object_get_data(G_OBJECT(column), ALWAYS_VISIBLE))
01433     {
01434         g_object_set_data(G_OBJECT(widget), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
01435         gtk_widget_set_sensitive(widget, FALSE);
01436     }
01437 
01438     /* Set up the callback  */
01439     id = g_signal_connect(widget, "toggled",
01440                           (GCallback)gnc_tree_view_menu_item_toggled, view);
01441 
01442     /* Store data on the widget for callbacks */
01443     key = g_strdup_printf("%s_%s", pref_name, GCONF_KEY_VISIBLE);
01444     g_object_set_data_full(G_OBJECT(widget), GCONF_KEY, key, g_free);
01445     // LEAVE(" ");
01446 }
01447 
01459 static void
01460 gnc_tree_view_build_column_menu (GncTreeView *view)
01461 {
01462     GncTreeViewPrivate *priv;
01463     GList *column_list;
01464 
01465     g_return_if_fail(GNC_IS_TREE_VIEW(view));
01466 
01467     ENTER("view %p", view);
01468     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01469 
01470     /* Destroy any old menu */
01471     if (priv->column_menu)
01472     {
01473         gtk_widget_unref(priv->column_menu);
01474         priv->column_menu = NULL;
01475     }
01476 
01477     if (priv->show_column_menu && priv->gconf_section)
01478     {
01479         /* Show the menu popup button */
01480         if (priv->column_menu_column)
01481             gtk_tree_view_column_set_visible(priv->column_menu_column, TRUE);
01482 
01483         /* Now build a new menu */
01484         column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
01485         g_list_foreach(column_list, (GFunc)gnc_tree_view_create_menu_item, view);
01486         g_list_free(column_list);
01487     }
01488     else
01489     {
01490         /* Hide the menu popup button */
01491         if (priv->column_menu_column)
01492             gtk_tree_view_column_set_visible(priv->column_menu_column, FALSE);
01493     }
01494     LEAVE("menu: show %d, section %s", priv->show_column_menu,
01495           priv->gconf_section ? priv->gconf_section : "(null)");
01496 }
01497 
01513 static void
01514 gnc_tree_view_update_column_menu_item (GtkCheckMenuItem *checkmenuitem,
01515                                        GncTreeView *view)
01516 {
01517     GncTreeViewPrivate *priv;
01518     gchar *key;
01519     gboolean visible;
01520 
01521     g_return_if_fail(GTK_IS_CHECK_MENU_ITEM(checkmenuitem));
01522     g_return_if_fail(GNC_IS_TREE_VIEW(view));
01523 
01524     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01525     key = g_object_get_data(G_OBJECT(checkmenuitem), GCONF_KEY);
01526     if (g_object_get_data(G_OBJECT(checkmenuitem), ALWAYS_VISIBLE))
01527     {
01528         visible = TRUE;
01529     }
01530     else
01531     {
01532         visible = gnc_gconf_get_bool(priv->gconf_section, key, NULL);
01533     }
01534 
01535     g_signal_handlers_block_by_func(checkmenuitem,
01536                                     gnc_tree_view_menu_item_toggled, view);
01537     gtk_check_menu_item_set_active(checkmenuitem, visible);
01538     g_signal_handlers_unblock_by_func(checkmenuitem,
01539                                       gnc_tree_view_menu_item_toggled, view);
01540 }
01541 
01554 static void
01555 gnc_tree_view_select_column_cb (GtkTreeViewColumn *column,
01556                                 GncTreeView *view)
01557 {
01558     GncTreeViewPrivate *priv;
01559     GtkWidget *widget, *menu;
01560 
01561     g_return_if_fail(GTK_IS_TREE_VIEW_COLUMN(column));
01562     g_return_if_fail(GNC_IS_TREE_VIEW(view));
01563 
01564     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01565     menu = priv->column_menu;
01566     if (!menu)
01567         return;
01568 
01569     /* Synchronize the menu before display */
01570     gtk_container_foreach(GTK_CONTAINER(menu),
01571                           (GtkCallback)gnc_tree_view_update_column_menu_item,
01572                           view);
01573 
01574     /* Ensure all components are visible */
01575     gtk_widget_show_all(menu);
01576 
01577     /* Pop the menu up at the button */
01578     widget = gtk_tree_view_column_get_widget(column);
01579     gtk_menu_popup(GTK_MENU(priv->column_menu), NULL, GTK_WIDGET(menu),
01580                    NULL, NULL, 0, gtk_get_current_event_time());
01581 }
01582 
01583 
01584 void gnc_tree_view_expand_columns (GncTreeView *view,
01585                                    gchar *first_column_name,
01586                                    ...)
01587 {
01588     GncTreeViewPrivate *priv;
01589     GtkTreeViewColumn *column;
01590     gboolean hide_spacer;
01591     GList *columns, *tmp;
01592     gchar *name, *pref_name;
01593     va_list args;
01594 
01595     g_return_if_fail (GNC_IS_TREE_VIEW (view));
01596     ENTER(" ");
01597     va_start (args, first_column_name);
01598     priv = GNC_TREE_VIEW_GET_PRIVATE (view);
01599     name = first_column_name;
01600     hide_spacer = FALSE;
01601 
01602     /* First disable the expand property on all (non-infrastructure) columns. */
01603     columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
01604     for (tmp = columns; tmp; tmp = g_list_next(tmp))
01605     {
01606         column = tmp->data;
01607         pref_name = g_object_get_data(G_OBJECT(column), PREF_NAME);
01608         if (pref_name != NULL)
01609             gtk_tree_view_column_set_expand(column, FALSE);
01610     }
01611     g_list_free(columns);
01612 
01613     /* Now enable it on the requested columns. */
01614     while (name != NULL)
01615     {
01616         column = gnc_tree_view_find_column_by_name(view, name);
01617         if (column != NULL)
01618         {
01619             gtk_tree_view_column_set_expand(column, TRUE);
01620             hide_spacer = TRUE;
01621         }
01622         name = va_arg (args, gchar*);
01623     }
01624     va_end (args);
01625 
01626     gtk_tree_view_column_set_visible (priv->spacer_column, !hide_spacer);
01627     gtk_tree_view_column_set_visible (priv->selection_column, !hide_spacer);
01628 
01629     LEAVE(" ");
01630 }
01631 
01638 void
01639 gnc_tree_view_set_show_column_menu (GncTreeView *view,
01640                                     gboolean visible)
01641 {
01642     GncTreeViewPrivate *priv;
01643 
01644     g_return_if_fail(GNC_IS_TREE_VIEW(view));
01645 
01646     ENTER("view %p, show menu %d", view, visible);
01647     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01648     priv->show_column_menu = visible;
01649     gnc_tree_view_build_column_menu(view);
01650     LEAVE(" ");
01651 }
01652 
01659 gboolean
01660 gnc_tree_view_get_show_column_menu (GncTreeView *view)
01661 {
01662     GncTreeViewPrivate *priv;
01663 
01664     g_return_val_if_fail(GNC_IS_TREE_VIEW(view), FALSE);
01665 
01666     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01667     return(priv->show_column_menu);
01668 }
01669 
01672 /************************************************************/
01673 /*                    Tree View Creation                    */
01674 /************************************************************/
01675 
01685 void
01686 gnc_tree_view_set_model(GncTreeView *view, GtkTreeModel *model)
01687 {
01688     GncTreeViewPrivate *priv;
01689     GtkTreeModel *old_model;
01690 
01691     /* Remove existing callback */
01692     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01693     if (priv->sort_column_changed_cb_id)
01694     {
01695         old_model = gtk_tree_view_get_model (GTK_TREE_VIEW(view));
01696         g_signal_handler_disconnect (old_model, priv->sort_column_changed_cb_id);
01697         priv->sort_column_changed_cb_id = 0;
01698     }
01699 
01700     gtk_tree_view_set_model (GTK_TREE_VIEW(view), model);
01701 
01702     /* Maybe add a new callback */
01703     if (model && priv->gconf_section)
01704     {
01705         priv->sort_column_changed_cb_id =
01706             g_signal_connect(GTK_TREE_SORTABLE(model), "sort-column-changed",
01707                              (GCallback)gtk_tree_view_sort_column_changed_cb, view);
01708     }
01709 }
01710 
01711 static gint
01712 gnc_tree_view_count_visible_columns(GncTreeView *view)
01713 {
01714     GList *columns, *node;
01715     gint count = 0;
01716 
01717     columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
01718     for (node = columns; node; node = node->next)
01719     {
01720         GtkTreeViewColumn *col = GTK_TREE_VIEW_COLUMN(node->data);
01721 
01722         if (g_object_get_data(G_OBJECT(col), DEFAULT_VISIBLE) ||
01723                 g_object_get_data(G_OBJECT(col), ALWAYS_VISIBLE))
01724             count++;
01725     }
01726     g_list_free(columns);
01727     return count;
01728 }
01729 
01730 void
01731 gnc_tree_view_configure_columns (GncTreeView *view)
01732 {
01733     GncTreeViewPrivate *priv;
01734     GtkTreeViewColumn *column;
01735     GList *columns;
01736     gboolean hide_spacer;
01737 
01738     g_return_if_fail(GNC_IS_TREE_VIEW(view));
01739 
01740     ENTER(" ");
01741 
01742     /* Update the view and gconf */
01743     columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
01744     g_list_foreach(columns, (GFunc)gnc_tree_view_update_visibility, view);
01745     g_list_free(columns);
01746 
01747     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01748     if (priv->gconf_section)
01749         priv->seen_gconf_visibility = TRUE;
01750 
01751     /* If only the first column is visible, hide the spacer and make that
01752      * column expand. */
01753     hide_spacer = (gnc_tree_view_count_visible_columns(view) == 1);
01754     column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), 0);
01755     gtk_tree_view_column_set_expand(column, hide_spacer);
01756     gtk_tree_view_column_set_visible(priv->spacer_column, !hide_spacer);
01757     gtk_tree_view_column_set_visible(priv->selection_column, !hide_spacer);
01758 
01759     LEAVE(" ");
01760 }
01761 
01762 
01792 static void
01793 gnc_tree_view_column_properties (GncTreeView *view,
01794                                  GtkTreeViewColumn *column,
01795                                  const gchar *pref_name,
01796                                  gint data_column,
01797                                  gint default_width,
01798                                  gboolean resizable,
01799                                  GtkTreeIterCompareFunc column_sort_fn)
01800 {
01801     GncTreeViewPrivate *priv;
01802     GtkTreeModel *s_model;
01803     gboolean visible;
01804     int width = 0;
01805     gchar *key;
01806 
01807     /* Set data used by other functions */
01808     if (pref_name)
01809         g_object_set_data(G_OBJECT(column), PREF_NAME, (gpointer)pref_name);
01810     if (data_column == 0)
01811         g_object_set_data(G_OBJECT(column), ALWAYS_VISIBLE, GINT_TO_POINTER(1));
01812     g_object_set_data(G_OBJECT(column), MODEL_COLUMN,
01813                       GINT_TO_POINTER(data_column));
01814 
01815     /* Get visibility */
01816     visible = gnc_tree_view_column_visible(view, NULL, pref_name);
01817 
01818     /* Set column attributes (without the sizing) */
01819     g_object_set(G_OBJECT(column),
01820                  "visible",     visible,
01821                  "resizable",   resizable && pref_name != NULL,
01822                  "reorderable", pref_name != NULL,
01823                  NULL);
01824 
01825     /* Get width */
01826     if (default_width == 0)
01827     {
01828         /* Set the sizing column attributes */
01829         g_object_set(G_OBJECT(column),
01830                      "sizing",      GTK_TREE_VIEW_COLUMN_AUTOSIZE,
01831                      NULL);
01832     }
01833     else
01834     {
01835         priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01836         if (priv->gconf_section)
01837         {
01838             key = g_strdup_printf("%s_%s", pref_name, GCONF_KEY_WIDTH);
01839             width = gnc_gconf_get_int(priv->gconf_section, key, NULL);
01840             g_free(key);
01841         }
01842 
01843         /* If gconf comes back with a width of zero (or there is no gconf
01844          * width) the use the default width for the column.  Allow for
01845          * padding L and R of the displayed data. */
01846         if (width == 0)
01847             width = default_width + 10;
01848         if (width == 0)
01849             width = 10;
01850 
01851         /* Set the sizing column attributes (including fixed-width) */
01852         g_object_set(G_OBJECT(column),
01853                      "sizing",      GTK_TREE_VIEW_COLUMN_FIXED,
01854                      "fixed-width", width,
01855                      NULL);
01856     }
01857 
01858     s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
01859     if (GTK_IS_TREE_SORTABLE(s_model))
01860     {
01861         gtk_tree_view_column_set_sort_column_id (column, data_column);
01862         if (column_sort_fn)
01863         {
01864             gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(s_model),
01865                                              data_column, column_sort_fn,
01866                                              GINT_TO_POINTER(data_column),
01867                                              NULL /* destroy fn */);
01868         }
01869     }
01870 
01871     /* Add to the column selection menu */
01872     if (pref_name)
01873     {
01874         gnc_tree_view_create_menu_item(column, view);
01875     }
01876 }
01877 
01888 GtkTreeViewColumn *
01889 gnc_tree_view_add_toggle_column (GncTreeView *view,
01890                                  const gchar *column_title,
01891                                  const gchar *column_short_title,
01892                                  const gchar *pref_name,
01893                                  gint model_data_column,
01894                                  gint model_visibility_column,
01895                                  GtkTreeIterCompareFunc column_sort_fn,
01896                                  renderer_toggled toggle_edited_cb)
01897 {
01898     GncTreeViewPrivate *priv;
01899     GtkTreeViewColumn *column;
01900     GtkCellRenderer *renderer;
01901 
01902     g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
01903 
01904     priv = GNC_TREE_VIEW_GET_PRIVATE(view);
01905     renderer = gtk_cell_renderer_toggle_new ();
01906     column =
01907         gtk_tree_view_column_new_with_attributes (column_short_title,
01908                 renderer,
01909                 "active", model_data_column,
01910                 NULL);
01911 
01912     /* Add the full title to the object for menu creation */
01913     g_object_set_data_full(G_OBJECT(column), REAL_TITLE,
01914                            g_strdup(column_title), g_free);
01915     if (toggle_edited_cb)
01916         g_signal_connect(G_OBJECT(renderer), "toggled",
01917                          G_CALLBACK(toggle_edited_cb), view);
01918 
01919     if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
01920         gtk_tree_view_column_add_attribute (column, renderer,
01921                                             "visible", model_visibility_column);
01922 
01923 
01924     gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
01925                                      0, FALSE, column_sort_fn);
01926 
01927     gnc_tree_view_append_column (view, column);
01928 
01929     /* Also add the full title to the object as a tooltip */
01930     gtk_widget_set_tooltip_text(column->button, column_title);
01931 
01932     return column;
01933 }
01934 
01943 GtkTreeViewColumn *
01944 gnc_tree_view_add_text_column (GncTreeView *view,
01945                                const gchar *column_title,
01946                                const gchar *pref_name,
01947                                const gchar *stock_icon_name,
01948                                const gchar *sizing_text,
01949                                gint model_data_column,
01950                                gint model_visibility_column,
01951                                GtkTreeIterCompareFunc column_sort_fn)
01952 {
01953     GtkTreeViewColumn *column;
01954     GtkCellRenderer *renderer;
01955     PangoLayout* layout;
01956     int default_width, title_width;
01957 
01958     g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
01959 
01960     column = gtk_tree_view_column_new ();
01961     gtk_tree_view_column_set_title (column, column_title);
01962 
01963     /* Set up an icon renderer if requested */
01964     if (stock_icon_name)
01965     {
01966         renderer = gtk_cell_renderer_pixbuf_new ();
01967         g_object_set (renderer, "stock-id", stock_icon_name, NULL);
01968         gtk_tree_view_column_pack_start (column, renderer, FALSE);
01969     }
01970 
01971     /* Set up a text renderer and attributes */
01972     renderer = gtk_cell_renderer_text_new ();
01973     gtk_tree_view_column_pack_start (column, renderer, TRUE);
01974 
01975     /* Set renderer attributes controlled by the model */
01976     if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
01977         gtk_tree_view_column_add_attribute (column, renderer,
01978                                             "text", model_data_column);
01979     if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
01980         gtk_tree_view_column_add_attribute (column, renderer,
01981                                             "visible", model_visibility_column);
01982 
01983     /* Default size is the larger of the column title and the sizing text */
01984     layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
01985     pango_layout_get_pixel_size(layout, &title_width, NULL);
01986     g_object_unref(layout);
01987     layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
01988     pango_layout_get_pixel_size(layout, &default_width, NULL);
01989     g_object_unref(layout);
01990     default_width = MAX(default_width, title_width);
01991     if (default_width)
01992         default_width += 10; /* padding on either side */
01993     gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
01994                                      default_width, TRUE, column_sort_fn);
01995 
01996     gnc_tree_view_append_column (view, column);
01997     return column;
01998 }
01999 
02000 GtkTreeViewColumn *
02001 gnc_tree_view_add_combo_column (GncTreeView *view,
02002                                 const gchar *column_title,
02003                                 const gchar *pref_name,
02004                                 const gchar *sizing_text,
02005                                 gint model_data_column,
02006                                 gint model_visibility_column,
02007                                 GtkTreeModel *combo_tree_model,
02008                                 gint combo_model_text_column,
02009                                 GtkTreeIterCompareFunc column_sort_fn)
02010 {
02011     GtkTreeViewColumn *column;
02012     GtkCellRenderer *renderer;
02013     PangoLayout* layout;
02014     int default_width, title_width;
02015 
02016     g_return_val_if_fail (GNC_IS_TREE_VIEW(view), NULL);
02017 
02018     column = gtk_tree_view_column_new ();
02019     gtk_tree_view_column_set_title (column, gettext(column_title));
02020 
02021     /* Set up a renderer and attributes */
02022     renderer = gtk_cell_renderer_combo_new ();
02023     gtk_tree_view_column_pack_start (column, renderer, TRUE);
02024 
02025     /* Set renderer attributes controlled by the model */
02026     if (model_data_column != GNC_TREE_VIEW_COLUMN_DATA_NONE)
02027         gtk_tree_view_column_add_attribute (column, renderer,
02028                                             "text", model_data_column);
02029     if (model_visibility_column != GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS)
02030         gtk_tree_view_column_add_attribute (column, renderer,
02031                                             "visible", model_visibility_column);
02032 
02033     /* Default size is the larger of the column title and the sizing text */
02034     layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), column_title);
02035     pango_layout_get_pixel_size(layout, &title_width, NULL);
02036     g_object_unref(layout);
02037     layout = gtk_widget_create_pango_layout (GTK_WIDGET(view), sizing_text);
02038     pango_layout_get_pixel_size(layout, &default_width, NULL);
02039     g_object_unref(layout);
02040     default_width = MAX(default_width, title_width);
02041     if (default_width)
02042         default_width += 10; /* padding on either side */
02043 
02044     gnc_tree_view_column_properties (view, column, pref_name, model_data_column,
02045                                      default_width, TRUE, column_sort_fn);
02046 
02047     /* Stuff specific to combo */
02048     if (combo_tree_model)
02049     {
02050         g_object_set(G_OBJECT(renderer), "model", combo_tree_model,
02051                      "text-column", combo_model_text_column, NULL);
02052     }
02053     /* TODO: has-entry? */
02054 
02055     gnc_tree_view_append_column (view, column);
02056     return column;
02057 }
02058 
02059 GtkCellRenderer *
02060 gnc_tree_view_column_get_renderer(GtkTreeViewColumn *column)
02061 {
02062     GList *renderers;
02063     GtkCellRenderer *cr = NULL;
02064 
02065     g_return_val_if_fail(GTK_TREE_VIEW_COLUMN(column), NULL);
02066 
02067     /* Get the list of one renderer */
02068     renderers = gtk_tree_view_column_get_cell_renderers(column);
02069     if (g_list_length(renderers) > 0)
02070         cr = GTK_CELL_RENDERER(renderers->data);
02071     g_list_free(renderers);
02072 
02073     return cr;
02074 }
02075 
02086 GtkTreeViewColumn *
02087 gnc_tree_view_add_numeric_column (GncTreeView *view,
02088                                   const gchar *column_title,
02089                                   const gchar *pref_name,
02090                                   const gchar *sizing_text,
02091                                   gint model_data_column,
02092                                   gint model_color_column,
02093                                   gint model_visibility_column,
02094                                   GtkTreeIterCompareFunc column_sort_fn)
02095 {
02096     GtkTreeViewColumn *column;
02097     GtkCellRenderer *renderer;
02098 
02099     column = gnc_tree_view_add_text_column (view, column_title, pref_name,
02100                                             NULL, sizing_text, model_data_column,
02101                                             model_visibility_column,
02102                                             column_sort_fn);
02103 
02104     renderer = gnc_tree_view_column_get_renderer(column);
02105 
02106     /* Right align the column title and data */
02107     g_object_set(G_OBJECT(column), "alignment",   1.0, NULL);
02108     g_object_set(G_OBJECT(renderer), "xalign",   1.0, NULL);
02109 
02110     /* Change the text color */
02111     if (model_color_column != GNC_TREE_VIEW_COLUMN_COLOR_NONE)
02112         gtk_tree_view_column_add_attribute (column, renderer,
02113                                             "foreground", model_color_column);
02114 
02115     return column;
02116 }
02117 
02126 gint
02127 gnc_tree_view_append_column (GncTreeView *view,
02128                              GtkTreeViewColumn *column)
02129 {
02130     GList *columns;
02131     int n;
02132 
02133     /* There's no easy way to get this number. */
02134     columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(view));
02135     n = g_list_length(columns);
02136     g_list_free(columns);
02137 
02138     /* Ignore the initial two columns (the spacer and the selection menu) */
02139     if (n >= 2)
02140         n -= 2;
02141     return gtk_tree_view_insert_column (GTK_TREE_VIEW(view), column, n);
02142 }
02143 
02144 static gboolean
02145 get_column_next_to(GtkTreeView *tv, GtkTreeViewColumn **col, gboolean backward)
02146 {
02147     GList *cols, *node;
02148     GtkTreeViewColumn *c = NULL;
02149     gint seen = 0;
02150     gboolean wrapped = FALSE;
02151 
02152     cols = gtk_tree_view_get_columns(tv);
02153     g_return_val_if_fail(g_list_length(cols) > 0, FALSE);
02154 
02155     node = g_list_find(cols, *col);
02156     g_return_val_if_fail(node, FALSE);
02157     do
02158     {
02159         node = backward ? node->prev : node->next;
02160         if (!node)
02161         {
02162             wrapped = TRUE;
02163             node = backward ? g_list_last(cols) : cols;
02164         }
02165         c = GTK_TREE_VIEW_COLUMN(node->data);
02166         if (c && gtk_tree_view_column_get_visible(c))
02167             seen++;
02168         if (c == *col) break;
02169     }
02170     while (!seen);
02171 
02172     g_list_free(cols);
02173     *col = c;
02174     return wrapped;
02175 }
02176 
02177 gboolean
02178 gnc_tree_view_path_is_valid(GncTreeView *view, GtkTreePath *path)
02179 {
02180     GtkTreeView *tv = GTK_TREE_VIEW(view);
02181     GtkTreeModel *s_model;
02182     GtkTreeIter iter;
02183 
02184     s_model = gtk_tree_view_get_model(tv);
02185     return gtk_tree_model_get_iter(s_model, &iter, path);
02186 }
02187 
02188 void
02189 gnc_tree_view_keynav(GncTreeView *view, GtkTreeViewColumn **col,
02190                      GtkTreePath *path, GdkEventKey *event)
02191 {
02192     GtkTreeView *tv = GTK_TREE_VIEW(view);
02193     gint depth;
02194     gboolean shifted;
02195 
02196     if (event->type != GDK_KEY_PRESS) return;
02197 
02198     switch (event->keyval)
02199     {
02200     case GDK_Tab:
02201     case GDK_ISO_Left_Tab:
02202     case GDK_KP_Tab:
02203         shifted = event->state & GDK_SHIFT_MASK;
02204         if (get_column_next_to(tv, col, shifted))
02205         {
02206             /* This is the end (or beginning) of the line, buddy. */
02207             depth = gtk_tree_path_get_depth(path);
02208             if (shifted)
02209             {
02210                 if (!gtk_tree_path_prev(path) && depth > 1)
02211                 {
02212                     gtk_tree_path_up(path);
02213                 }
02214             }
02215             else if (gtk_tree_view_row_expanded(tv, path))
02216             {
02217                 gtk_tree_path_down(path);
02218             }
02219             else
02220             {
02221                 gtk_tree_path_next(path);
02222                 if (!gnc_tree_view_path_is_valid(view, path) && depth > 1)
02223                 {
02224                     gtk_tree_path_prev(path);
02225                     gtk_tree_path_up(path);
02226                     gtk_tree_path_next(path);
02227                 }
02228             }
02229         }
02230         break;
02231 
02232     case GDK_Return:
02233     case GDK_KP_Enter:
02234         if (gtk_tree_view_row_expanded(tv, path))
02235         {
02236             gtk_tree_path_down(path);
02237         }
02238         else
02239         {
02240             depth = gtk_tree_path_get_depth(path);
02241             gtk_tree_path_next(path);
02242             if (!gnc_tree_view_path_is_valid(view, path) && depth > 1)
02243             {
02244                 gtk_tree_path_prev(path);
02245                 gtk_tree_path_up(path);
02246                 gtk_tree_path_next(path);
02247             }
02248         }
02249         break;
02250     }
02251     return;
02252 }
02253 
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines