1 /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 #include <config.h>
3 
4 #include "treeview.h"
5 
6 typedef struct
7 {
8     GSettings  *settings;
9     gboolean    store_column_order;
10     GHashTable *excluded_columns;
11 } GsmTreeViewPrivate;
12 
13 G_DEFINE_TYPE_WITH_PRIVATE (GsmTreeView, gsm_tree_view, GTK_TYPE_TREE_VIEW)
14 
15 static void
16 gsm_tree_view_finalize (GObject *object)
17 {
18     GsmTreeViewPrivate *priv = gsm_tree_view_get_instance_private (GSM_TREE_VIEW (object));
19 
20     g_hash_table_destroy (priv->excluded_columns);
21     priv->excluded_columns = NULL;
22 
23     G_OBJECT_CLASS (gsm_tree_view_parent_class)->finalize (object);
24 }
25 
26 static void
27 gsm_tree_view_class_init (GsmTreeViewClass *klass)
28 {
29     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
30 
31     gobject_class->finalize = gsm_tree_view_finalize;
32 }
33 
34 static void
35 gsm_tree_view_init (GsmTreeView *self)
36 {
37     GsmTreeViewPrivate *priv = gsm_tree_view_get_instance_private (self);
38 
39     priv->excluded_columns = g_hash_table_new (g_direct_hash, g_direct_equal);
40 }
41 
42 void
43 gsm_tree_view_save_state (GsmTreeView *tree_view)
44 {
45     GsmTreeViewPrivate *priv;
46 
47     g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
48 
49     priv = gsm_tree_view_get_instance_private (tree_view);
50     GtkTreeModel *model;
51     gint sort_col;
52     GtkSortType sort_type;
53 
54     model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
55     g_settings_delay (priv->settings);
56     if (gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (model),
57                                               &sort_col,
58                                               &sort_type)) {
59         g_settings_set_int (priv->settings, "sort-col", sort_col);
60         g_settings_set_int (priv->settings, "sort-order", sort_type);
61     }
62 
63     if (priv->store_column_order) {
64         GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree_view));
65         GList *iter;
66         GVariantBuilder builder;
67 
68         g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
69 
70         for (iter = columns; iter != NULL; iter = iter->next) {
71             gint id = gtk_tree_view_column_get_sort_column_id (GTK_TREE_VIEW_COLUMN (iter->data));
72             g_variant_builder_add (&builder, "i", id);
73         }
74 
75         g_settings_set_value (priv->settings, "columns-order",
76                               g_variant_builder_end (&builder));
77 
78         g_list_free (columns);
79     }
80 
81     g_settings_apply (priv->settings);
82 }
83 
84 GtkTreeViewColumn *
85 gsm_tree_view_get_column_from_id (GsmTreeView *tree_view, gint sort_id)
86 {
87     GList *columns;
88     GList *iter;
89     GtkTreeViewColumn *col = NULL;
90 
91     g_return_val_if_fail (GSM_IS_TREE_VIEW (tree_view), NULL);
92 
93     columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree_view));
94 
95     for (iter = columns; iter != NULL; iter = iter->next) {
96         col = GTK_TREE_VIEW_COLUMN (iter->data);
97         if (gtk_tree_view_column_get_sort_column_id (col) == sort_id)
98             break;
99     }
100 
101     g_list_free (columns);
102 
103     return col;
104 }
105 
106 static gboolean
107 cb_column_header_clicked (GtkTreeViewColumn *column, GdkEventButton *event, gpointer data)
108 {
109     GtkMenu *menu = GTK_MENU (data);
110 
111     if (event->button == GDK_BUTTON_SECONDARY) {
112         gtk_menu_popup_at_pointer (menu, (GdkEvent*)event);
113         return TRUE;
114     }
115 
116     return FALSE;
117 }
118 
119 void
120 gsm_tree_view_load_state (GsmTreeView *tree_view)
121 {
122     GsmTreeViewPrivate *priv;
123     GtkTreeModel *model;
124     gint sort_col;
125     GtkSortType sort_type;
126 
127     g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
128 
129     priv = gsm_tree_view_get_instance_private (tree_view);
130     model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
131 
132     sort_col = g_settings_get_int (priv->settings, "sort-col");
133     sort_type = g_settings_get_int (priv->settings, "sort-order");
134 
135     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
136                                           sort_col,
137                                           sort_type);
138 
139     if (priv->store_column_order) {
140         GtkMenu *header_menu = GTK_MENU (gtk_menu_new ());
141         GList *columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (tree_view));
142         GList *iter;
143         GVariantIter *var_iter;
144         GtkTreeViewColumn *col, *last;
145         gint sort_id;
146 
147         for (iter = columns; iter != NULL; iter = iter->next) {
148             const char *title;
149             char *key;
150             GtkButton *button;
151             GtkCheckMenuItem *column_item;
152 
153             col = GTK_TREE_VIEW_COLUMN (iter->data);
154             sort_id = gtk_tree_view_column_get_sort_column_id (col);
155 
156             if (priv->excluded_columns &&
157                 g_hash_table_contains (priv->excluded_columns, GINT_TO_POINTER (sort_id))) {
158                 gtk_tree_view_column_set_visible (col, FALSE);
159                 continue;
160             }
161 
162             title = gtk_tree_view_column_get_title (col);
163 
164             button = GTK_BUTTON (gtk_tree_view_column_get_button (col));
165             g_signal_connect (button, "button-press-event",
166                               G_CALLBACK (cb_column_header_clicked),
167                               header_menu);
168 
169             column_item = GTK_CHECK_MENU_ITEM (gtk_check_menu_item_new_with_label (title));
170             g_object_bind_property (col, "visible",
171                                     column_item, "active",
172                                     G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
173 
174             gtk_menu_shell_append (GTK_MENU_SHELL (header_menu), GTK_WIDGET (column_item));
175 
176             key = g_strdup_printf ("col-%d-width", sort_id);
177             gtk_tree_view_column_set_fixed_width (col, g_settings_get_int (priv->settings, key));
178             gtk_tree_view_column_set_min_width (col, 30);
179             g_free (key);
180 
181             key = g_strdup_printf ("col-%d-visible", sort_id);
182             gtk_tree_view_column_set_visible (col, g_settings_get_boolean (priv->settings, key));
183             g_free (key);
184         }
185 
186         g_list_free (columns);
187 
188         gtk_widget_show_all (GTK_WIDGET (header_menu));
189 
190         g_settings_get (priv->settings, "columns-order", "ai", &var_iter);
191         last = NULL;
192         while (g_variant_iter_loop (var_iter, "i", &sort_id)) {
193             col = gsm_tree_view_get_column_from_id (tree_view, sort_id);
194 
195             if (col != NULL && col != last) {
196                 gtk_tree_view_move_column_after (GTK_TREE_VIEW (tree_view),
197                                                  col, last);
198                 last = col;
199             }
200         }
201         g_variant_iter_free (var_iter);
202     }
203 }
204 
205 void
206 gsm_tree_view_add_excluded_column (GsmTreeView *tree_view, gint column_id)
207 {
208     GsmTreeViewPrivate *priv;
209 
210     g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
211 
212     priv = gsm_tree_view_get_instance_private (tree_view);
213     g_hash_table_add (priv->excluded_columns, GINT_TO_POINTER (column_id));
214 }
215 
216 static guint timeout_id = 0;
217 static GtkTreeViewColumn *current_column;
218 
219 static gboolean
220 save_column_state (gpointer data)
221 {
222     GSettings *settings = G_SETTINGS (data);
223     gint column_id = gtk_tree_view_column_get_sort_column_id (current_column);
224     gint width = gtk_tree_view_column_get_width (current_column);
225     gboolean visible = gtk_tree_view_column_get_visible (current_column);
226 
227     gchar *key;
228     g_settings_delay (settings);
229 
230     key = g_strdup_printf ("col-%d-width", column_id);
231     g_settings_set_int (settings, key, width);
232     g_free (key);
233 
234     key = g_strdup_printf ("col-%d-visible", column_id);
235     g_settings_set_boolean (settings, key, visible);
236     g_free (key);
237     timeout_id = 0;
238     g_settings_apply (settings);
239 
240     return FALSE;
241 }
242 
243 static void
244 cb_update_column_state (GObject *object, GParamSpec *pspec, gpointer data)
245 {
246     GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN (object);
247 
248     current_column = column;
249 
250     if (timeout_id > 0)
251         g_source_remove (timeout_id);
252 
253     timeout_id = g_timeout_add_seconds (1, save_column_state, data);
254 }
255 
256 void
257 gsm_tree_view_append_and_bind_column (GsmTreeView *tree_view, GtkTreeViewColumn *column)
258 {
259     GsmTreeViewPrivate *priv;
260 
261     g_return_if_fail (GSM_IS_TREE_VIEW (tree_view));
262     g_return_if_fail (GTK_IS_TREE_VIEW_COLUMN (column));
263 
264     priv = gsm_tree_view_get_instance_private (tree_view);
265 
266     gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view),
267                                  column);
268 
269     g_signal_connect (column, "notify::fixed-width",
270                       G_CALLBACK (cb_update_column_state),
271                       priv->settings);
272 
273     g_signal_connect (column, "notify::visible",
274                       G_CALLBACK (cb_update_column_state),
275                       priv->settings);
276 }
277 
278 
279 GsmTreeView *
280 gsm_tree_view_new (GSettings *settings, gboolean store_column_order)
281 {
282     GsmTreeView *self = g_object_new (GSM_TYPE_TREE_VIEW, NULL);
283     GsmTreeViewPrivate *priv = gsm_tree_view_get_instance_private (self);
284 
285     priv->settings = settings;
286     priv->store_column_order = store_column_order;
287 
288     return self;
289 }
290