Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / libedataserverui / e-categories-dialog.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * Copyright (C) 2005 Novell, Inc.
4  *
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.
8  *
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.
13  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <string.h>
25 #include <glib/gi18n-lib.h>
26 #include <gtk/gtkbox.h>
27 #include <gtk/gtkbutton.h>
28 #include <gtk/gtkcellrenderertext.h>
29 #include <gtk/gtkcellrenderertoggle.h>
30 #include <gtk/gtkcellrendererpixbuf.h>
31 #include <gtk/gtkentry.h>
32 #include <gtk/gtkfilechooserbutton.h>
33 #include <gtk/gtkliststore.h>
34 #include <gtk/gtkmain.h>
35 #include <gtk/gtkmessagedialog.h>
36 #include <gtk/gtktable.h>
37 #include <gtk/gtkstock.h>
38 #include <gtk/gtktreeselection.h>
39 #include <gtk/gtktreeview.h>
40 #include <glade/glade-xml.h>
41 #include "libedataserver/e-categories.h"
42 #include "libedataserver/libedataserver-private.h"
43 #include "e-categories-dialog.h"
44
45 enum {
46         COLUMN_ACTIVE,
47         COLUMN_ICON,
48         COLUMN_CATEGORY,
49         N_COLUMNS
50 };
51
52 struct _ECategoriesDialogPrivate {
53         GladeXML *gui;
54         GtkWidget *categories_entry;
55         GtkWidget *categories_list;
56         GtkWidget *new_button;
57         GtkWidget *edit_button;
58         GtkWidget *delete_button;
59
60         GHashTable *selected_categories;
61 };
62
63 static GObjectClass *parent_class = NULL;
64
65 /* Category properties dialog */
66
67 typedef struct {
68         ECategoriesDialog *parent;
69         GladeXML *gui;
70         GtkWidget *the_dialog;
71         GtkWidget *category_name;
72         GtkWidget *category_color;
73         GtkWidget *category_icon;
74 } CategoryPropertiesDialog;
75
76 static CategoryPropertiesDialog *
77 load_properties_dialog (ECategoriesDialog *parent)
78 {
79         CategoryPropertiesDialog *prop_dialog;
80         char *gladefile;
81
82         prop_dialog = g_new0 (CategoryPropertiesDialog, 1);
83
84         gladefile = g_build_filename (E_DATA_SERVER_UI_GLADEDIR,
85                                       "e-categories-dialog.glade",
86                                       NULL);
87         prop_dialog->gui = glade_xml_new (gladefile, "properties-dialog", GETTEXT_PACKAGE);
88         g_free (gladefile);
89
90         if (!prop_dialog->gui) {
91                 g_free (prop_dialog);
92                 return NULL;
93         }
94
95         prop_dialog->parent = parent;
96
97         prop_dialog->the_dialog = glade_xml_get_widget (prop_dialog->gui, "properties-dialog");
98         gtk_window_set_transient_for (GTK_WINDOW (prop_dialog->the_dialog), GTK_WINDOW (parent));
99
100         prop_dialog->category_name = glade_xml_get_widget (prop_dialog->gui, "category-name");
101         prop_dialog->category_color = glade_xml_get_widget (prop_dialog->gui, "category-color");
102         prop_dialog->category_icon = glade_xml_get_widget (prop_dialog->gui, "category-icon");
103
104         return prop_dialog;
105 }
106
107 static void
108 free_properties_dialog (CategoryPropertiesDialog *prop_dialog)
109 {
110         if (prop_dialog->the_dialog) {
111                 gtk_widget_destroy (prop_dialog->the_dialog);
112                 prop_dialog->the_dialog = NULL;
113         }
114
115         if (prop_dialog->gui) {
116                 g_object_unref (prop_dialog->gui);
117                 prop_dialog->gui = NULL;
118         }
119
120         g_free (prop_dialog);
121 }
122
123 /* GObject methods */
124
125 G_DEFINE_TYPE (ECategoriesDialog, e_categories_dialog, GTK_TYPE_DIALOG)
126
127 static void
128 e_categories_dialog_dispose (GObject *object)
129 {
130         ECategoriesDialogPrivate *priv = E_CATEGORIES_DIALOG (object)->priv;
131
132         if (priv->gui) {
133                 g_object_unref (priv->gui);
134                 priv->gui = NULL;
135         }
136
137         if (priv->selected_categories) {
138                 g_hash_table_destroy (priv->selected_categories);
139                 priv->selected_categories = NULL;
140         }
141
142         (* G_OBJECT_CLASS (parent_class)->dispose) (object);
143 }
144
145 static void
146 e_categories_dialog_finalize (GObject *object)
147 {
148         ECategoriesDialogPrivate *priv = E_CATEGORIES_DIALOG (object)->priv;
149
150         g_free (priv);
151         E_CATEGORIES_DIALOG (object)->priv = NULL;
152
153         (* G_OBJECT_CLASS (parent_class)->finalize) (object);
154 }
155
156 static void
157 e_categories_dialog_class_init (ECategoriesDialogClass *klass)
158 {
159         GObjectClass *object_class = G_OBJECT_CLASS (klass);
160
161         object_class->dispose = e_categories_dialog_dispose;
162         object_class->finalize = e_categories_dialog_finalize;
163
164         parent_class = g_type_class_peek_parent (klass);
165 }
166
167 static void
168 add_comma_sep_categories (gpointer key, gpointer value, gpointer user_data)
169 {
170         GString **str = user_data;
171
172         if (strlen ((*str)->str) > 0)
173                 *str = g_string_append (*str, ",");
174
175         *str = g_string_append (*str, (const char *) key);
176 }
177
178 static void
179 category_toggled_cb (GtkCellRenderer *renderer, const gchar *path, gpointer user_data)
180 {
181         ECategoriesDialogPrivate *priv;
182         GtkTreeIter iter;
183         GtkTreeModel *model;
184         ECategoriesDialog *dialog = user_data;
185
186         priv = dialog->priv;
187         model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->categories_list));
188
189         if (gtk_tree_model_get_iter_from_string (model, &iter, path)) {
190                 gboolean place_bool;
191                 gchar *place_string;
192                 GString *str;
193
194                 gtk_tree_model_get (model, &iter,
195                                     COLUMN_ACTIVE, &place_bool,
196                                     COLUMN_CATEGORY, &place_string,
197                                     -1);
198                 if (place_bool) {
199                         g_hash_table_remove (priv->selected_categories, place_string);
200                         gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, FALSE, -1);
201                 } else {
202                         g_hash_table_insert (priv->selected_categories, g_strdup (place_string), g_strdup (place_string));
203                         gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, TRUE, -1);
204                 }
205
206                 str = g_string_new ("");
207                 g_hash_table_foreach (priv->selected_categories, (GHFunc) add_comma_sep_categories, &str);
208                 gtk_entry_set_text (GTK_ENTRY (priv->categories_entry), str->str);
209
210                 /* free memory */
211                 g_string_free (str, TRUE);
212                 g_free (place_string);
213
214                 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
215         }
216 }
217
218 static void
219 entry_changed_cb (GtkEditable *editable, gpointer user_data)
220 {
221         ECategoriesDialog *dialog = user_data;
222
223         gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
224 }
225
226 static char *
227 check_category_name (const char *name)
228 {
229         GString *str = NULL;
230         char *p = (char *) name;
231
232         str = g_string_new ("");
233         while (*p) {
234                 switch (*p) {
235                 case ',' :
236                         break;
237                 default :
238                         str = g_string_append_c (str, *p);
239                 }
240                 p++;
241         }
242
243         p = str->str;
244         g_string_free (str, FALSE);
245
246         return p;
247 }
248
249 static void
250 new_button_clicked_cb (GtkButton *button, gpointer user_data)
251 {
252         ECategoriesDialog *dialog;
253         CategoryPropertiesDialog *prop_dialog;
254
255         dialog = user_data;
256
257         prop_dialog = load_properties_dialog (dialog);
258         if (!prop_dialog)
259                 return;
260
261         do {
262                 if (gtk_dialog_run (GTK_DIALOG (prop_dialog->the_dialog)) == GTK_RESPONSE_OK) {
263                         const char *category_name;
264                         char *correct_category_name;
265                         GtkTreeIter iter;
266
267                         category_name = gtk_entry_get_text (GTK_ENTRY (prop_dialog->category_name));
268                         correct_category_name = check_category_name (category_name);
269
270                         if (e_categories_exist (correct_category_name)) {
271                                 GtkWidget *error_dialog;
272
273                                 error_dialog = gtk_message_dialog_new (
274                                         GTK_WINDOW (prop_dialog->the_dialog),
275                                         0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
276                                         _("There is already a category '%s' in the configuration. Please use another name"),
277                                         category_name);
278
279                                 gtk_dialog_run (GTK_DIALOG (error_dialog));
280                                 gtk_widget_destroy (error_dialog);
281                                 g_free (correct_category_name);
282                         } else {
283                                 gchar *category_icon;
284                                 GdkPixbuf *icon = NULL;
285                                 GtkListStore *list_store = GTK_LIST_STORE (
286                                                                 gtk_tree_view_get_model (GTK_TREE_VIEW (prop_dialog->parent->priv->categories_list)));
287                                 /* FIXME: get color */
288                                 category_icon = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (prop_dialog->category_icon));
289                                 if (category_icon)
290                                         icon = gdk_pixbuf_new_from_file (category_icon, NULL);
291
292                                 e_categories_add (correct_category_name, NULL, category_icon ? category_icon : NULL, TRUE);
293
294                                 gtk_list_store_append (list_store, &iter);
295                                 gtk_list_store_set (list_store, &iter,
296                                                     COLUMN_ACTIVE, FALSE,
297                                                     COLUMN_ICON, icon,
298                                                     COLUMN_CATEGORY,correct_category_name,
299                                                     -1);
300
301                                 if (icon)
302                                         g_object_unref (icon);
303                                 if (category_icon)
304                                         g_free (category_icon);
305                                 g_free (correct_category_name);
306
307                                 break;
308                         }
309                 } else
310                         break;
311         } while (TRUE);
312
313         free_properties_dialog (prop_dialog);
314 }
315
316 static void
317 edit_button_clicked_cb (GtkButton *button, gpointer user_data)
318 {
319         ECategoriesDialog *dialog;
320         ECategoriesDialogPrivate *priv;
321         CategoryPropertiesDialog *prop_dialog;
322         GtkTreeIter iter;
323         GtkTreeModel *model;
324         char *category_name;
325
326         dialog = user_data;
327         priv = dialog->priv;
328
329         /* get the currently selected item */
330         model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->categories_list));
331
332         if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->categories_list)),
333                                               NULL, &iter))
334                 return;
335
336         /* load the properties dialog */
337         prop_dialog = load_properties_dialog (dialog);
338         if (!prop_dialog)
339                 return;
340
341         gtk_tree_model_get (model, &iter, COLUMN_CATEGORY, &category_name, -1);
342         gtk_entry_set_text (GTK_ENTRY (prop_dialog->category_name), category_name);
343         gtk_widget_set_sensitive (prop_dialog->category_name, FALSE);
344         gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (prop_dialog->category_icon), 
345                                        e_categories_get_icon_file_for (category_name));
346
347         if (gtk_dialog_run (GTK_DIALOG (prop_dialog->the_dialog)) == GTK_RESPONSE_OK) {
348                 gchar *category_icon;
349
350                 /* FIXME: get color */
351                 category_icon = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (prop_dialog->category_icon));
352
353                 if (category_icon) {
354                         GdkPixbuf *icon = NULL;
355                         e_categories_set_icon_file_for (category_name, category_icon);
356                         icon = gdk_pixbuf_new_from_file (category_icon, NULL);
357                         if (icon) {
358                                 gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ICON, icon, -1);
359                                 g_object_unref (icon);
360                         }
361
362                         g_free (category_icon);
363                 }
364                 gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
365         }
366
367         g_free (category_name);
368         free_properties_dialog (prop_dialog);
369 }
370
371 static void
372 delete_button_clicked_cb (GtkButton *button, gpointer user_data)
373 {
374         ECategoriesDialog *dialog;
375         ECategoriesDialogPrivate *priv;
376         GtkTreeIter iter;
377         GtkTreeModel *model;
378         char *category_name;
379
380         dialog = user_data;
381         priv = dialog->priv;
382
383         /* get the currently selected item */
384         model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->categories_list));
385
386         if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->categories_list)),
387                                               NULL, &iter))
388                 return;
389
390         gtk_tree_model_get (model, &iter, COLUMN_CATEGORY, &category_name, -1);
391         e_categories_remove (category_name);
392         gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
393 }
394
395 static void
396 e_categories_dialog_init (ECategoriesDialog *dialog)
397 {
398         ECategoriesDialogPrivate *priv;
399         GList *cat_list;
400         GtkCellRenderer *renderer;
401         GtkTreeViewColumn *column;
402         GtkListStore *model;
403         GtkWidget *main_widget;
404         char *gladefile;
405
406         priv = g_new0 (ECategoriesDialogPrivate, 1);
407         priv->selected_categories = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
408         dialog->priv = priv;
409
410         /* load the UI from our Glade file */
411         gladefile = g_build_filename (E_DATA_SERVER_UI_GLADEDIR,
412                                       "e-categories-dialog.glade",
413                                       NULL);
414         priv->gui = glade_xml_new (gladefile, "table-categories", GETTEXT_PACKAGE);
415         g_free (gladefile);
416
417         if (!priv->gui) {
418                 g_warning (G_STRLOC ": can't load e-categories-dialog.glade file");
419                 return;
420         }
421
422         main_widget = glade_xml_get_widget (priv->gui, "table-categories");
423         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), main_widget, TRUE, TRUE, 0);
424
425         priv->categories_entry = glade_xml_get_widget (priv->gui, "entry-categories");
426         priv->categories_list = glade_xml_get_widget (priv->gui, "categories-list");
427
428         priv->new_button = glade_xml_get_widget (priv->gui, "button-new");
429         g_signal_connect (G_OBJECT (priv->new_button), "clicked", G_CALLBACK (new_button_clicked_cb), dialog);
430         priv->edit_button = glade_xml_get_widget (priv->gui, "button-edit");
431         g_signal_connect (G_OBJECT (priv->edit_button), "clicked", G_CALLBACK (edit_button_clicked_cb), dialog);
432         priv->delete_button = glade_xml_get_widget (priv->gui, "button-delete");
433         g_signal_connect (G_OBJECT (priv->delete_button), "clicked", G_CALLBACK (delete_button_clicked_cb), dialog);
434
435         gtk_dialog_add_buttons (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
436                                 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
437         gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
438         gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
439         gtk_window_set_title (GTK_WINDOW (dialog), _("Categories"));
440
441         /* set up the categories list */
442         model = gtk_list_store_new (N_COLUMNS, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING);
443         cat_list = e_categories_get_list ();
444         while (cat_list != NULL) {
445                 GtkTreeIter iter;
446
447                 /* only add categories that are user-visible */
448                 if (e_categories_is_searchable ((const char *) cat_list->data)) {
449                         const gchar *icon_file;
450                         GdkPixbuf *icon = NULL;
451                         icon_file = e_categories_get_icon_file_for ((const char *) cat_list->data);
452                         if (icon_file)
453                                 icon = gdk_pixbuf_new_from_file (icon_file, NULL);
454                         gtk_list_store_append (model, &iter);
455                         gtk_list_store_set (model, &iter, 
456                                             COLUMN_ACTIVE, FALSE,
457                                             COLUMN_ICON,   icon,
458                                             COLUMN_CATEGORY,  cat_list->data,
459                                             -1);
460                         if (icon)
461                                 g_object_unref (icon);
462                 }
463
464                 cat_list = g_list_remove (cat_list, cat_list->data);
465         }
466         gtk_tree_view_set_model (GTK_TREE_VIEW (priv->categories_list), GTK_TREE_MODEL (model));
467
468         renderer = gtk_cell_renderer_toggle_new ();
469         g_signal_connect (G_OBJECT (renderer), "toggled", G_CALLBACK (category_toggled_cb), dialog);
470         column = gtk_tree_view_column_new_with_attributes ("?", renderer,
471                                                            "active", COLUMN_ACTIVE, NULL);
472         gtk_tree_view_append_column (GTK_TREE_VIEW (priv->categories_list), column);
473
474         renderer = gtk_cell_renderer_pixbuf_new ();
475         column = gtk_tree_view_column_new_with_attributes (_("Icon"), renderer,
476                                                            "pixbuf", COLUMN_ICON, NULL);
477         gtk_tree_view_append_column (GTK_TREE_VIEW (priv->categories_list), column);
478
479         renderer = gtk_cell_renderer_text_new ();
480         column = gtk_tree_view_column_new_with_attributes (_("Category"), renderer,
481                                                            "text", COLUMN_CATEGORY, NULL);
482         gtk_tree_view_append_column (GTK_TREE_VIEW (priv->categories_list), column);
483
484         /* free memory */
485         g_object_unref (model);
486 }
487
488 /**
489  * e_categories_dialog_new:
490  * @initial_category_list: Comma-separated list of initial categories.
491  *
492  * Creates a new #ECategoriesDialog widget.
493  *
494  * Return value: A pointer to the newly created #ECategoriesDialog widget.
495  **/
496 GtkWidget *
497 e_categories_dialog_new (const char *initial_category_list)
498 {
499         ECategoriesDialog *dialog;
500
501         dialog = E_CATEGORIES_DIALOG (g_object_new (E_TYPE_CATEGORIES_DIALOG, NULL));
502         if (initial_category_list)
503                 e_categories_dialog_set_categories (dialog, initial_category_list);
504
505         g_signal_connect (G_OBJECT (dialog->priv->categories_entry), "changed", 
506                           G_CALLBACK (entry_changed_cb), dialog);
507
508         return GTK_WIDGET (dialog);
509 }
510
511 /**
512  * e_categories_dialog_get_categories:
513  * @dialog: An #ECategoriesDialog widget.
514  *
515  * Gets a comma-separated list of the categories currently selected on the dialog.
516  *
517  * Return value: comma-separated list of categories.
518  **/
519 const char *
520 e_categories_dialog_get_categories (ECategoriesDialog *dialog)
521 {
522         ECategoriesDialogPrivate *priv;
523
524         g_return_val_if_fail (E_IS_CATEGORIES_DIALOG (dialog), NULL);
525
526         priv = dialog->priv;
527
528         return gtk_entry_get_text (GTK_ENTRY (priv->categories_entry));
529 }
530
531 /**
532  * e_categories_dialog_set_categories:
533  * @dialog: An #ECategoriesDialog widget.
534  * @categories: Comma-separated list of categories.
535  *
536  * Sets the list of categories selected on the dialog.
537  **/
538 void
539 e_categories_dialog_set_categories (ECategoriesDialog *dialog, const char *categories)
540 {
541         ECategoriesDialogPrivate *priv;
542         gchar **arr;
543         GtkTreeIter iter;
544         GtkTreeModel *model;
545
546         g_return_if_fail (E_IS_CATEGORIES_DIALOG (dialog));
547
548         priv = dialog->priv;
549
550         /* clean up the table of selected categories */
551         g_hash_table_foreach_remove (priv->selected_categories, (GHRFunc) gtk_true, NULL);
552
553         arr = g_strsplit (categories, ",", 0);
554         if (arr) {
555                 int i = 0;
556                 while (arr[i] != NULL) {
557                         g_hash_table_insert (priv->selected_categories, g_strdup (arr[i]), g_strdup (arr[i]));
558                         i++;
559                 }
560
561                 g_strfreev (arr);
562         }
563
564         /* set the widgets */
565         gtk_entry_set_text (GTK_ENTRY (priv->categories_entry), categories);
566
567         model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->categories_list));
568         if (gtk_tree_model_get_iter_first (model, &iter)) {
569                 do {
570                         char *place_string;
571
572                         gtk_tree_model_get (model, &iter, COLUMN_CATEGORY, &place_string, -1);
573                         if (g_hash_table_lookup (priv->selected_categories, place_string))
574                                 gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, TRUE, -1);
575                         else
576                                 gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, FALSE, -1);
577
578                         g_free (place_string);
579                 } while (gtk_tree_model_iter_next (model, &iter));
580         }
581 }