|
GnuCash 2.4.99
|
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 ¤t, 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 ¤t, &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
1.7.4