1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of version 2 of the GNU Lesser General Public
7 * License as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 #include <glib/gi18n-lib.h>
23 #include <libedataserver/libedataserver.h>
25 #include "e-categories-selector.h"
26 #include "e-data-server-ui-marshal.h"
28 #define E_CATEGORIES_SELECTOR_GET_PRIVATE(obj) \
29 (G_TYPE_INSTANCE_GET_PRIVATE \
30 ((obj), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelectorPrivate))
32 struct _ECategoriesSelectorPrivate {
34 GHashTable *selected_categories;
36 gboolean ignore_category_changes;
57 static gint signals[LAST_SIGNAL] = {0};
61 e_categories_selector,
65 categories_selector_build_model (ECategoriesSelector *selector)
70 store = gtk_list_store_new (
71 N_COLUMNS, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING);
73 gtk_tree_sortable_set_sort_column_id (
74 GTK_TREE_SORTABLE (store),
75 COLUMN_CATEGORY, GTK_SORT_ASCENDING);
77 list = e_categories_get_list ();
78 for (iter = list; iter != NULL; iter = iter->next) {
79 const gchar *category_name = iter->data;
80 const gchar *filename;
81 GdkPixbuf *pixbuf = NULL;
85 /* Only add user-visible categories. */
86 if (!e_categories_is_searchable (category_name))
89 active = (g_hash_table_lookup (
90 selector->priv->selected_categories,
91 category_name) != NULL);
93 filename = e_categories_get_icon_file_for (category_name);
95 pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
97 gtk_list_store_append (store, &iter);
101 COLUMN_ACTIVE, active,
103 COLUMN_CATEGORY, category_name,
107 g_object_unref (pixbuf);
110 gtk_tree_view_set_model (
111 GTK_TREE_VIEW (selector), GTK_TREE_MODEL (store));
113 /* This has to be reset everytime we install a new model */
114 gtk_tree_view_set_search_column (
115 GTK_TREE_VIEW (selector), COLUMN_CATEGORY);
118 g_object_unref (store);
122 category_toggled_cb (GtkCellRenderer *renderer,
124 ECategoriesSelector *selector)
127 GtkTreePath *tree_path;
130 model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
131 g_return_if_fail (model);
133 tree_path = gtk_tree_path_new_from_string (path);
134 g_return_if_fail (tree_path);
136 if (gtk_tree_model_get_iter (model, &iter, tree_path)) {
140 gtk_tree_model_get (model, &iter,
141 COLUMN_ACTIVE, &active,
142 COLUMN_CATEGORY, &category, -1);
145 GTK_LIST_STORE (model), &iter,
146 COLUMN_ACTIVE, !active, -1);
149 g_hash_table_remove (
150 selector->priv->selected_categories, category);
152 g_hash_table_insert (
153 selector->priv->selected_categories,
154 g_strdup (category), g_strdup (category));
157 selector, signals[CATEGORY_CHECKED], 0,
163 gtk_tree_path_free (tree_path);
167 categories_selector_listener_cb (gpointer useless_pointer,
168 ECategoriesSelector *selector)
170 if (!selector->priv->ignore_category_changes)
171 categories_selector_build_model (selector);
175 categories_selector_key_press_event (ECategoriesSelector *selector,
178 if (event->keyval == GDK_KEY_Delete) {
179 e_categories_selector_delete_selection (selector);
187 categories_selector_selection_changed (GtkTreeSelection *selection,
188 ECategoriesSelector *selector)
190 g_signal_emit (selector, signals[SELECTION_CHANGED], 0, selection);
194 categories_selector_get_property (GObject *object,
199 switch (property_id) {
200 case PROP_ITEMS_CHECKABLE:
201 g_value_set_boolean (
203 e_categories_selector_get_items_checkable (
204 E_CATEGORIES_SELECTOR (object)));
208 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
212 categories_selector_set_property (GObject *object,
217 switch (property_id) {
218 case PROP_ITEMS_CHECKABLE:
219 e_categories_selector_set_items_checkable (
220 E_CATEGORIES_SELECTOR (object),
221 g_value_get_boolean (value));
225 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
229 categories_selector_dispose (GObject *object)
231 ECategoriesSelectorPrivate *priv;
233 priv = E_CATEGORIES_SELECTOR_GET_PRIVATE (object);
235 if (priv->selected_categories != NULL) {
236 g_hash_table_destroy (priv->selected_categories);
237 priv->selected_categories = NULL;
240 /* Chain up to parent's dispose() method.*/
241 G_OBJECT_CLASS (e_categories_selector_parent_class)->dispose (object);
245 categories_selector_finalize (GObject *object)
247 e_categories_unregister_change_listener (
248 G_CALLBACK (categories_selector_listener_cb), object);
252 e_categories_selector_class_init (ECategoriesSelectorClass *class)
254 GObjectClass *object_class;
256 g_type_class_add_private (class, sizeof (ECategoriesSelectorPrivate));
258 object_class = G_OBJECT_CLASS (class);
259 object_class->set_property = categories_selector_set_property;
260 object_class->get_property = categories_selector_get_property;
261 object_class->dispose = categories_selector_dispose;
262 object_class->finalize = categories_selector_finalize;
264 g_object_class_install_property (
266 PROP_ITEMS_CHECKABLE,
267 g_param_spec_boolean (
274 signals[CATEGORY_CHECKED] = g_signal_new (
276 G_TYPE_FROM_CLASS (class),
278 G_STRUCT_OFFSET (ECategoriesSelectorClass, category_checked),
280 e_data_server_ui_marshal_VOID__STRING_BOOLEAN,
285 signals[SELECTION_CHANGED] = g_signal_new (
287 G_TYPE_FROM_CLASS (class),
289 G_STRUCT_OFFSET (ECategoriesSelectorClass, selection_changed),
291 g_cclosure_marshal_VOID__OBJECT,
293 GTK_TYPE_TREE_SELECTION);
297 e_categories_selector_init (ECategoriesSelector *selector)
299 GtkCellRenderer *renderer;
300 GtkTreeViewColumn *column;
301 GtkTreeSelection *selection;
303 selector->priv = E_CATEGORIES_SELECTOR_GET_PRIVATE (selector);
305 selector->priv->checkable = TRUE;
306 selector->priv->selected_categories = g_hash_table_new_full (
307 (GHashFunc) g_str_hash,
308 (GEqualFunc) g_str_equal,
309 (GDestroyNotify) g_free,
310 (GDestroyNotify) g_free);
311 selector->priv->ignore_category_changes = FALSE;
313 renderer = gtk_cell_renderer_toggle_new ();
314 column = gtk_tree_view_column_new_with_attributes (
315 "?", renderer, "active", COLUMN_ACTIVE, NULL);
316 gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
320 G_CALLBACK (category_toggled_cb), selector);
322 renderer = gtk_cell_renderer_pixbuf_new ();
323 column = gtk_tree_view_column_new_with_attributes (
324 _("Icon"), renderer, "pixbuf", COLUMN_ICON, NULL);
325 gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
327 renderer = gtk_cell_renderer_text_new ();
328 column = gtk_tree_view_column_new_with_attributes (
329 _("Category"), renderer, "text", COLUMN_CATEGORY, NULL);
330 gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
332 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
334 selection, "changed",
335 G_CALLBACK (categories_selector_selection_changed), selector);
338 selector, "key-press-event",
339 G_CALLBACK (categories_selector_key_press_event), NULL);
341 e_categories_register_change_listener (
342 G_CALLBACK (categories_selector_listener_cb), selector);
344 categories_selector_build_model (selector);
348 * e_categories_selector_new:
353 e_categories_selector_new (void)
355 return g_object_new (
356 E_TYPE_CATEGORIES_SELECTOR,
357 "items-checkable", TRUE, NULL);
361 * e_categories_selector_get_items_checkable:
366 e_categories_selector_get_items_checkable (ECategoriesSelector *selector)
368 g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), TRUE);
370 return selector->priv->checkable;
374 * e_categories_selector_set_items_checkable:
379 e_categories_selector_set_items_checkable (ECategoriesSelector *selector,
382 GtkTreeViewColumn *column;
384 g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
386 selector->priv->checkable = checkable;
388 column = gtk_tree_view_get_column (
389 GTK_TREE_VIEW (selector), COLUMN_ACTIVE);
390 gtk_tree_view_column_set_visible (column, checkable);
392 g_object_notify (G_OBJECT (selector), "items-checkable");
396 * e_categories_selector_get_checked:
398 * Free returned pointer with g_free().
403 e_categories_selector_get_checked (ECategoriesSelector *selector)
406 GList *list, *category;
408 g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
410 str = g_string_new ("");
411 list = g_hash_table_get_values (selector->priv->selected_categories);
413 /* to get them always in the same order */
414 list = g_list_sort (list, (GCompareFunc) g_utf8_collate);
416 for (category = list; category != NULL; category = category->next) {
418 g_string_append_printf (
419 str, ",%s", (gchar *) category->data);
421 g_string_append (str, (gchar *) category->data);
426 return g_string_free (str, FALSE);
430 * e_categories_selector_set_checked:
435 e_categories_selector_set_checked (ECategoriesSelector *selector,
436 const gchar *categories)
443 g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
445 /* Clean up table of selected categories. */
446 g_hash_table_remove_all (selector->priv->selected_categories);
448 arr = g_strsplit (categories, ",", 0);
450 for (i = 0; arr[i] != NULL; i++) {
452 g_hash_table_insert (
453 selector->priv->selected_categories,
454 g_strdup (arr[i]), g_strdup (arr[i]));
459 model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
460 if (gtk_tree_model_get_iter_first (model, &iter)) {
462 gchar *category_name;
465 gtk_tree_model_get (model, &iter,
466 COLUMN_CATEGORY, &category_name,
468 found = (g_hash_table_lookup (
469 selector->priv->selected_categories,
470 category_name) != NULL);
472 GTK_LIST_STORE (model), &iter,
473 COLUMN_ACTIVE, found, -1);
475 g_free (category_name);
476 } while (gtk_tree_model_iter_next (model, &iter));
481 * e_categories_selector_delete_selection:
486 e_categories_selector_delete_selection (ECategoriesSelector *selector)
489 GtkTreeSelection *selection;
490 GList *selected, *item;
492 g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
494 model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
495 g_return_if_fail (model != NULL);
497 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
498 selected = gtk_tree_selection_get_selected_rows (selection, &model);
500 /* Remove categories in reverse order to avoid invalidating
501 * tree paths as we iterate over the list. Note, the list is
502 * probably already sorted but we sort again just to be safe. */
503 selected = g_list_reverse (g_list_sort (
504 selected, (GCompareFunc) gtk_tree_path_compare));
506 /* Prevent the model from being rebuilt every time we
507 * remove a category, since we're already modifying it. */
508 selector->priv->ignore_category_changes = TRUE;
510 for (item = selected; item != NULL; item = item->next) {
511 GtkTreePath *path = item->data;
515 gtk_tree_model_get_iter (model, &iter, path);
516 gtk_tree_model_get (model, &iter,
517 COLUMN_CATEGORY, &category, -1);
518 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
519 e_categories_remove (category);
523 selector->priv->ignore_category_changes = FALSE;
525 /* If we only remove one category, try to select another */
526 if (g_list_length (selected) == 1) {
527 GtkTreePath *path = selected->data;
529 gtk_tree_selection_select_path (selection, path);
530 if (!gtk_tree_selection_path_is_selected (selection, path))
531 if (gtk_tree_path_prev (path))
532 gtk_tree_selection_select_path (selection, path);
535 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
536 g_list_free (selected);
540 * e_categories_selector_get_selected:
542 * Free returned pointer with g_free().
547 e_categories_selector_get_selected (ECategoriesSelector *selector)
550 GtkTreeSelection *selection;
551 GList *selected, *item;
552 GString *str = g_string_new ("");
554 g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
556 model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
557 g_return_val_if_fail (model != NULL, NULL);
559 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
560 selected = gtk_tree_selection_get_selected_rows (selection, &model);
562 for (item = selected; item != NULL; item = item->next) {
563 GtkTreePath *path = item->data;
567 gtk_tree_model_get_iter (model, &iter, path);
568 gtk_tree_model_get (model, &iter,
569 COLUMN_CATEGORY, &category, -1);
571 g_string_assign (str, category);
573 g_string_append_printf (str, ",%s", category);
578 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
579 g_list_free (selected);
581 return g_string_free (str, FALSE);