Bug 332497 - Add Edit -> Available Categories
authorDan Vrátil <dvratil@redhat.com>
Thu, 5 May 2011 14:01:43 +0000 (10:01 -0400)
committerMatthew Barnes <mbarnes@redhat.com>
Thu, 5 May 2011 14:26:19 +0000 (10:26 -0400)
Split ECategoriesDialog into smaller widgets:

    ECategoriesEditor
    ECategoriesSelector
    ECategoryEditor

15 files changed:
docs/reference/libedataserverui/libedataserverui-docs.sgml
docs/reference/libedataserverui/libedataserverui-sections.txt
docs/reference/libedataserverui/libedataserverui.types
docs/reference/libedataserverui/tmpl/e-categories-editor.sgml [new file with mode: 0644]
docs/reference/libedataserverui/tmpl/e-categories-selector.sgml [new file with mode: 0644]
docs/reference/libedataserverui/tmpl/e-category-editor.sgml [new file with mode: 0644]
libedataserverui/Makefile.am
libedataserverui/e-categories-dialog.c
libedataserverui/e-categories-editor.c [new file with mode: 0644]
libedataserverui/e-categories-editor.h [new file with mode: 0644]
libedataserverui/e-categories-selector.c [new file with mode: 0644]
libedataserverui/e-categories-selector.h [new file with mode: 0644]
libedataserverui/e-category-editor.c [new file with mode: 0644]
libedataserverui/e-category-editor.h [new file with mode: 0644]
libedataserverui/e-data-server-ui-marshal.list

index 130697f..3242839 100644 (file)
@@ -12,6 +12,9 @@
     <title>Evolution-Data-Server Manual: Graphical Utilities (libedataserverui)</title>
     <xi:include href="xml/e-book-auth-util.xml"/>
     <xi:include href="xml/e-categories-dialog.xml"/>
+    <xi:include href="xml/e-categories-editor.xml"/>
+    <xi:include href="xml/e-categories-selector.xml"/>
+    <xi:include href="xml/e-category-editor.xml"/>
     <xi:include href="xml/e-category-completion.xml"/>
     <xi:include href="xml/e-cell-renderer-color.xml"/>
     <xi:include href="xml/e-contact-store.xml"/>
index 6165102..e703f1c 100644 (file)
@@ -26,6 +26,52 @@ e_categories_dialog_get_type
 </SECTION>
 
 <SECTION>
+<FILE>e-categories-editor</FILE>
+<TITLE>ECategoriesEditor</TITLE>
+ECategoriesEditor
+e_categories_editor_new
+e_categories_editor_get_categories
+e_categories_editor_set_categories
+e_categories_editor_get_entry_visible
+e_categories_editor_set_entry_visible
+<SUBSECTION Standard>
+E_CATEGORIES_EDITOR
+E_IS_CATEGORIES_EDITOR
+E_TYPE_CATEGORIES_EDITOR
+E_CATEGORIES_EDITOR_CLASS
+E_IS_CATEGORIES_EDITOR_CLASS
+E_CATEGORIES_EDITOR_GET_CLASS
+ECategoriesEditorClass
+<SUBSECTION Private>
+ECategoriesEditorPrivate
+e_categories_editor_get_type
+</SECTION>
+
+<SECTION>
+<FILE>e-categories-selector</FILE>
+<TITLE>ECategoriesSelector</TITLE>
+ECategoriesSelector
+e_categories_selector_new
+e_categories_selector_get_checked
+e_categories_selector_set_checked
+e_categories_selector_get_items_checkable
+e_categories_selector_set_items_checkable
+e_categories_selector_delete_selection
+e_categories_selector_get_selected
+<SUBSECTION Standard>
+E_CATEGORIES_SELECTOR
+E_IS_CATEGORIES_SELECTOR
+E_TYPE_CATEGORIES_SELECTOR
+E_CATEGORIES_SELECTOR_CLASS
+E_IS_CATEGORIES_SELECTOR_CLASS
+E_CATEGORIES_SELECTOR_GET_CLASS
+ECategoriesSelectorClass
+<SUBSECTION Private>
+ECategoriesSelectorPrivate
+e_categories_selector_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-category-completion</FILE>
 <TITLE>ECategoryCompletion</TITLE>
 ECategoryCompletion
@@ -44,6 +90,26 @@ e_category_completion_get_type
 </SECTION>
 
 <SECTION>
+<FILE>e-category-editor</FILE>
+<TITLE>ECategoryEditor</TITLE>
+ECategoryEditor
+e_category_editor_new
+e_category_editor_create_category
+e_category_editor_edit_category
+<SUBSECTION Standard>
+E_CATEGORY_EDITOR
+E_IS_CATEGORY_EDITOR
+E_TYPE_CATEGORY_EDITOR
+E_CATEGORY_EDITOR_CLASS
+E_IS_CATEGORY_EDITOR_CLASS
+E_CATEGORY_EDITOR_GET_CLASS
+ECategoryEditorClass
+<SUBSECTION Private>
+ECategoryEditorPrivate
+e_category_editor_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-cell-renderer-color</FILE>
 <TITLE>ECellRendererColor</TITLE>
 ECellRendererColor
index 04eae99..0041710 100644 (file)
@@ -1,4 +1,7 @@
 #include <libedataserverui/e-categories-dialog.h>
+#include <libedataserverui/e-categories-editor.h>
+#include <libedataserverui/e-categories-selector.h>
+#include <libedataserverui/e-category-editor.h>
 #include <libedataserverui/e-contact-store.h>
 #include <libedataserverui/e-destination-store.h>
 #include <libedataserverui/e-name-selector.h>
@@ -12,6 +15,9 @@
 #include <libedataserverui/e-tree-model-generator.h>
 
 e_categories_dialog_get_type
+e_categories_editor_get_type
+e_categories_selector_get_type
+e_category_editor_get_type
 e_contact_store_get_type
 e_destination_store_get_type
 e_name_selector_get_type
diff --git a/docs/reference/libedataserverui/tmpl/e-categories-editor.sgml b/docs/reference/libedataserverui/tmpl/e-categories-editor.sgml
new file mode 100644 (file)
index 0000000..1671364
--- /dev/null
@@ -0,0 +1,87 @@
+<!-- ##### SECTION Title ##### -->
+ECategoriesEditor
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### SECTION Image ##### -->
+
+
+<!-- ##### STRUCT ECategoriesEditor ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SIGNAL ECategoriesEditor::entry-changed ##### -->
+<para>
+
+</para>
+
+@ecategorieseditor: the object which received the signal.
+
+<!-- ##### ARG ECategoriesEditor:entry-visible ##### -->
+<para>
+
+</para>
+
+<!-- ##### FUNCTION e_categories_editor_new ##### -->
+<para>
+
+</para>
+
+@void: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_categories_editor_get_categories ##### -->
+<para>
+
+</para>
+
+@editor: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_categories_editor_set_categories ##### -->
+<para>
+
+</para>
+
+@editor: 
+@categories: 
+
+
+<!-- ##### FUNCTION e_categories_editor_get_entry_visible ##### -->
+<para>
+
+</para>
+
+@editor: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_categories_editor_set_entry_visible ##### -->
+<para>
+
+</para>
+
+@editor: 
+@entry_visible: 
+
+
diff --git a/docs/reference/libedataserverui/tmpl/e-categories-selector.sgml b/docs/reference/libedataserverui/tmpl/e-categories-selector.sgml
new file mode 100644 (file)
index 0000000..f0c1dc2
--- /dev/null
@@ -0,0 +1,114 @@
+<!-- ##### SECTION Title ##### -->
+ECategoriesSelector
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### SECTION Image ##### -->
+
+
+<!-- ##### STRUCT ECategoriesSelector ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SIGNAL ECategoriesSelector::category-checked ##### -->
+<para>
+
+</para>
+
+@ecategoriesselector: the object which received the signal.
+@arg1: 
+@arg2: 
+
+<!-- ##### SIGNAL ECategoriesSelector::selection-changed ##### -->
+<para>
+
+</para>
+
+@ecategoriesselector: the object which received the signal.
+@arg1: 
+
+<!-- ##### ARG ECategoriesSelector:items-checkable ##### -->
+<para>
+
+</para>
+
+<!-- ##### FUNCTION e_categories_selector_new ##### -->
+<para>
+
+</para>
+
+@void: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_categories_selector_get_checked ##### -->
+<para>
+
+</para>
+
+@selector: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_categories_selector_set_checked ##### -->
+<para>
+
+</para>
+
+@selector: 
+@categories: 
+
+
+<!-- ##### FUNCTION e_categories_selector_get_items_checkable ##### -->
+<para>
+
+</para>
+
+@selector: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_categories_selector_set_items_checkable ##### -->
+<para>
+
+</para>
+
+@selectr: 
+@checkable: 
+
+
+<!-- ##### FUNCTION e_categories_selector_delete_selection ##### -->
+<para>
+
+</para>
+
+@selector: 
+
+
+<!-- ##### FUNCTION e_categories_selector_get_selected ##### -->
+<para>
+
+</para>
+
+@selector: 
+@Returns: 
+
+
diff --git a/docs/reference/libedataserverui/tmpl/e-category-editor.sgml b/docs/reference/libedataserverui/tmpl/e-category-editor.sgml
new file mode 100644 (file)
index 0000000..8ef7cf3
--- /dev/null
@@ -0,0 +1,58 @@
+<!-- ##### SECTION Title ##### -->
+ECategoryEditor
+
+<!-- ##### SECTION Short_Description ##### -->
+
+
+<!-- ##### SECTION Long_Description ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SECTION See_Also ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### SECTION Stability_Level ##### -->
+
+
+<!-- ##### SECTION Image ##### -->
+
+
+<!-- ##### STRUCT ECategoryEditor ##### -->
+<para>
+
+</para>
+
+
+<!-- ##### FUNCTION e_category_editor_new ##### -->
+<para>
+
+</para>
+
+@void: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_category_editor_create_category ##### -->
+<para>
+
+</para>
+
+@editor: 
+@Returns: 
+
+
+<!-- ##### FUNCTION e_category_editor_edit_category ##### -->
+<para>
+
+</para>
+
+@editor: 
+@category: 
+@Returns: 
+
+
index e20028d..af00b8a 100644 (file)
@@ -17,7 +17,10 @@ lib_LTLIBRARIES = libedataserverui-3.0.la
 
 libedataserveruiinclude_HEADERS =      \
        e-categories-dialog.h           \
+       e-categories-editor.h           \
+       e-categories-selector.h         \
        e-category-completion.h         \
+       e-category-editor.h             \
        e-destination-store.h           \
        e-book-auth-util.h              \
        e-contact-store.h               \
@@ -44,7 +47,10 @@ endif
 libedataserverui_3_0_la_SOURCES =      \
        $(MARSHAL_GENERATED)            \
        e-categories-dialog.c           \
+       e-categories-editor.c           \
+       e-categories-selector.c         \
        e-category-completion.c         \
+       e-category-editor.c             \
        e-destination-store.c           \
        e-book-auth-util.c              \
        e-contact-store.c               \
index 6e3b360..0be5698 100644 (file)
 #include "libedataserver/e-categories.h"
 #include "libedataserver/libedataserver-private.h"
 #include "e-categories-dialog.h"
+#include "e-categories-editor.h"
+#include "e-categories-selector.h"
 #include "e-category-completion.h"
+#include "e-category-editor.h"
 
 G_DEFINE_TYPE (ECategoriesDialog, e_categories_dialog, GTK_TYPE_DIALOG)
 
 struct _ECategoriesDialogPrivate {
-       GtkWidget *categories_entry;
-       GtkWidget *categories_list;
-       GtkWidget *new_button;
-       GtkWidget *edit_button;
-       GtkWidget *delete_button;
-
-       GHashTable *selected_categories;
-
-       guint ignore_category_changes : 1;
-};
-
-enum {
-       COLUMN_ACTIVE,
-       COLUMN_ICON,
-       COLUMN_CATEGORY,
-       N_COLUMNS
+       GtkWidget *categories_editor;
 };
 
-static gpointer parent_class;
-
-/* Category properties dialog */
-
-typedef struct {
-       ECategoriesDialog *parent;
-       GtkWidget *the_dialog;
-       GtkWidget *category_name;
-       GtkWidget *category_icon;
-} CategoryPropertiesDialog;
-
-static void
-update_preview (GtkFileChooser *chooser, gpointer user_data)
-{
-       GtkImage *image;
-       gchar *filename;
-
-       g_return_if_fail (chooser != NULL);
-
-       image = GTK_IMAGE (gtk_file_chooser_get_preview_widget (chooser));
-       g_return_if_fail (image != NULL);
-
-       filename = gtk_file_chooser_get_preview_filename (chooser);
-
-       gtk_image_set_from_file (image, filename);
-       gtk_file_chooser_set_preview_widget_active (chooser, filename != NULL);
-
-       g_free (filename);
-}
-
-static void
-file_chooser_response (GtkDialog *dialog, gint response_id, GtkFileChooser *button)
-{
-       g_return_if_fail (button != NULL);
-
-       if (response_id == GTK_RESPONSE_NO) {
-               gtk_file_chooser_unselect_all (button);
-       }
-}
-
-static void
-category_name_changed_cb (GtkEntry *category_name_entry, CategoryPropertiesDialog *prop_dialog)
-{
-       gchar *name;
-
-       g_return_if_fail (prop_dialog != NULL);
-       g_return_if_fail (prop_dialog->the_dialog != NULL);
-       g_return_if_fail (category_name_entry != NULL);
-
-       name = g_strdup (gtk_entry_get_text (category_name_entry));
-       if (name)
-               name = g_strstrip (name);
-
-       gtk_dialog_set_response_sensitive (GTK_DIALOG (prop_dialog->the_dialog), GTK_RESPONSE_OK, name && *name);
-
-       g_free (name);
-}
-
-static CategoryPropertiesDialog *
-create_properties_dialog (ECategoriesDialog *parent)
-{
-       CategoryPropertiesDialog *prop_dialog;
-       GtkWidget *properties_dialog;
-       GtkWidget *dialog_content;
-       GtkWidget *dialog_action_area;
-       GtkWidget *table_category_properties;
-       GtkWidget *label4;
-       GtkWidget *label6;
-       GtkWidget *category_name;
-       GtkWidget *cancelbutton1;
-       GtkWidget *okbutton1;
-
-       properties_dialog = gtk_dialog_new ();
-       gtk_window_set_title (GTK_WINDOW (properties_dialog), _("Category Properties"));
-       gtk_window_set_type_hint (GTK_WINDOW (properties_dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
-
-       dialog_content = gtk_dialog_get_content_area (GTK_DIALOG (properties_dialog));
-
-       table_category_properties = gtk_table_new (3, 2, FALSE);
-       gtk_box_pack_start (GTK_BOX (dialog_content), table_category_properties, TRUE, TRUE, 0);
-       gtk_container_set_border_width (GTK_CONTAINER (table_category_properties), 12);
-       gtk_table_set_row_spacings (GTK_TABLE (table_category_properties), 6);
-       gtk_table_set_col_spacings (GTK_TABLE (table_category_properties), 6);
-
-       label4 = gtk_label_new_with_mnemonic (_("Category _Name"));
-       gtk_table_attach (GTK_TABLE (table_category_properties), label4, 0, 1, 0, 1,
-                         (GtkAttachOptions) (GTK_FILL),
-                         (GtkAttachOptions) (0), 0, 0);
-       gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5);
-
-       label6 = gtk_label_new_with_mnemonic (_("Category _Icon"));
-       gtk_table_attach (GTK_TABLE (table_category_properties), label6, 0, 1, 2, 3,
-                         (GtkAttachOptions) (GTK_FILL),
-                         (GtkAttachOptions) (0), 0, 0);
-       gtk_misc_set_alignment (GTK_MISC (label6), 0, 0.5);
-
-       category_name = gtk_entry_new ();
-       gtk_table_attach (GTK_TABLE (table_category_properties), category_name, 1, 2, 0, 1,
-                         (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
-                         (GtkAttachOptions) (0), 0, 0);
-
-       dialog_action_area = gtk_dialog_get_action_area (GTK_DIALOG (properties_dialog));
-       gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area), GTK_BUTTONBOX_END);
-
-       cancelbutton1 = gtk_button_new_from_stock ("gtk-cancel");
-       gtk_dialog_add_action_widget (GTK_DIALOG (properties_dialog), cancelbutton1, GTK_RESPONSE_CANCEL);
-       gtk_widget_set_can_default (cancelbutton1, TRUE);
-
-       okbutton1 = gtk_button_new_from_stock ("gtk-ok");
-       gtk_dialog_add_action_widget (GTK_DIALOG (properties_dialog), okbutton1, GTK_RESPONSE_OK);
-       gtk_widget_set_can_default (okbutton1, TRUE);
-
-       gtk_label_set_mnemonic_widget (GTK_LABEL (label4), category_name);
-
-       gtk_widget_show_all (dialog_content);
-
-       prop_dialog = g_new0 (CategoryPropertiesDialog, 1);
-       prop_dialog->parent = parent;
-
-       prop_dialog->the_dialog = properties_dialog;
-       gtk_window_set_transient_for (GTK_WINDOW (prop_dialog->the_dialog), GTK_WINDOW (parent));
-
-       prop_dialog->category_name = category_name;
-       g_signal_connect (prop_dialog->category_name, "changed", G_CALLBACK (category_name_changed_cb), prop_dialog);
-       category_name_changed_cb (GTK_ENTRY (prop_dialog->category_name), prop_dialog);
-
-       if (table_category_properties) {
-               GtkFileChooser *chooser;
-               GtkWidget *dialog, *button;
-               GtkWidget *image = gtk_image_new ();
-
-               dialog = gtk_file_chooser_dialog_new ( _("Category Icon"),
-                       NULL,
-                       GTK_FILE_CHOOSER_ACTION_OPEN,
-                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-                       NULL);
-
-               button = gtk_button_new_with_mnemonic (_("_No Image"));
-               gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON));
-               gtk_widget_show (button);
-               gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, GTK_RESPONSE_NO);
-               gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT);
-               button = NULL;
-
-               chooser = GTK_FILE_CHOOSER (gtk_file_chooser_button_new_with_dialog (dialog));
-
-               gtk_file_chooser_set_local_only (chooser, TRUE);
-
-               g_signal_connect (dialog, "response", (GCallback) file_chooser_response, chooser);
-
-               prop_dialog->category_icon = GTK_WIDGET (chooser);
-               gtk_widget_show (prop_dialog->category_icon);
-               gtk_table_attach (GTK_TABLE (table_category_properties), prop_dialog->category_icon, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
-
-               gtk_widget_show (image);
-
-               gtk_file_chooser_set_preview_widget (chooser, image);
-               gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
-
-               g_signal_connect (G_OBJECT (chooser), "update-preview", (GCallback) update_preview, NULL);
-       }
-
-       return prop_dialog;
-}
-
 static void
-free_properties_dialog (CategoryPropertiesDialog *prop_dialog)
+entry_changed_cb (GtkEntry *entry,
+                  ECategoriesDialog *dialog)
 {
-       if (prop_dialog->the_dialog) {
-               gtk_widget_destroy (prop_dialog->the_dialog);
-               prop_dialog->the_dialog = NULL;
-       }
-
-       g_free (prop_dialog);
-}
-
-static void
-categories_dialog_build_model (ECategoriesDialog *dialog)
-{
-       GtkTreeView *tree_view;
-       GtkListStore *store;
-       GList *list, *iter;
-
-       store = gtk_list_store_new (
-               N_COLUMNS, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING);
-
-       gtk_tree_sortable_set_sort_column_id (
-               GTK_TREE_SORTABLE (store),
-               COLUMN_CATEGORY, GTK_SORT_ASCENDING);
-
-       list = e_categories_get_list ();
-       for (iter = list; iter != NULL; iter = iter->next) {
-               const gchar *category_name = iter->data;
-               const gchar *filename;
-               GdkPixbuf *pixbuf = NULL;
-               GtkTreeIter iter;
-               gboolean active;
-
-               /* Only add user-visible categories. */
-               if (!e_categories_is_searchable (category_name))
-                       continue;
-
-               active = (g_hash_table_lookup (
-                       dialog->priv->selected_categories,
-                       category_name) != NULL);
-
-               filename = e_categories_get_icon_file_for (category_name);
-               if (filename != NULL)
-                       pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
-
-               gtk_list_store_append (store, &iter);
-
-               gtk_list_store_set (
-                       store, &iter,
-                       COLUMN_ACTIVE, active,
-                       COLUMN_ICON, pixbuf,
-                       COLUMN_CATEGORY, category_name,
-                       -1);
-
-               if (pixbuf != NULL)
-                       g_object_unref (pixbuf);
-       }
-
-       tree_view = GTK_TREE_VIEW (dialog->priv->categories_list);
-       gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store));
-
-       /* This has to be reset everytime we install a new model. */
-       gtk_tree_view_set_search_column (tree_view, COLUMN_CATEGORY);
-
-       g_list_free (list);
-       g_object_unref (store);
-}
-
-static void
-categories_dialog_listener_cb (gpointer useless_pointer,
-                               ECategoriesDialog *dialog)
-{
-       /* Don't rebuild the model if we're in
-        * the middle of changing it ourselves. */
-       if (dialog->priv->ignore_category_changes)
-               return;
-
-       categories_dialog_build_model (dialog);
-}
-
-static void
-add_comma_sep_categories (gpointer key, gpointer value, gpointer user_data)
-{
-       GString **str = user_data;
-
-       if (strlen ((*str)->str) > 0)
-               *str = g_string_append (*str, ",");
-
-       *str = g_string_append (*str, (const gchar *) key);
-}
-
-static void
-category_toggled_cb (GtkCellRenderer *renderer, const gchar *path, gpointer user_data)
-{
-       ECategoriesDialogPrivate *priv;
-       GtkTreeIter iter;
-       GtkTreeModel *model;
-       ECategoriesDialog *dialog = user_data;
-
-       priv = dialog->priv;
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->categories_list));
-
-       if (gtk_tree_model_get_iter_from_string (model, &iter, path)) {
-               gboolean place_bool;
-               gchar *place_string;
-               GString *str;
-
-               gtk_tree_model_get (model, &iter,
-                                   COLUMN_ACTIVE, &place_bool,
-                                   COLUMN_CATEGORY, &place_string,
-                                   -1);
-               if (place_bool) {
-                       g_hash_table_remove (priv->selected_categories, place_string);
-                       gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, FALSE, -1);
-               } else {
-                       g_hash_table_insert (priv->selected_categories, g_strdup (place_string), g_strdup (place_string));
-                       gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, TRUE, -1);
-               }
-
-               str = g_string_new ("");
-               g_hash_table_foreach (priv->selected_categories, (GHFunc) add_comma_sep_categories, &str);
-               gtk_entry_set_text (GTK_ENTRY (priv->categories_entry), str->str);
-
-               /* free memory */
-               g_string_free (str, TRUE);
-               g_free (place_string);
-
-               gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
-       }
-}
-
-static void
-entry_changed_cb (GtkEditable *editable, gpointer user_data)
-{
-       ECategoriesDialog *dialog = user_data;
-
-       gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
-}
-
-static gchar *
-check_category_name (const gchar *name)
-{
-       GString *str = NULL;
-       gchar *p = (gchar *) name;
-
-       str = g_string_new ("");
-       while (*p) {
-               switch (*p) {
-               case ',' :
-                       break;
-               default :
-                       str = g_string_append_c (str, *p);
-               }
-               p++;
-       }
-
-       p = g_strstrip (g_string_free (str, FALSE));
-
-       return p;
-}
-
-static void
-new_button_clicked_cb (GtkButton *button, gpointer user_data)
-{
-       ECategoriesDialog *dialog;
-       CategoryPropertiesDialog *prop_dialog;
-
-       dialog = user_data;
-
-       prop_dialog = create_properties_dialog (dialog);
-       if (!prop_dialog)
-               return;
-
-       do {
-               if (gtk_dialog_run (GTK_DIALOG (prop_dialog->the_dialog)) == GTK_RESPONSE_OK) {
-                       const gchar *category_name;
-                       gchar *correct_category_name;
-
-                       category_name = gtk_entry_get_text (GTK_ENTRY (prop_dialog->category_name));
-                       correct_category_name = check_category_name (category_name);
-
-                       if (e_categories_exist (correct_category_name)) {
-                               GtkWidget *error_dialog;
-
-                               error_dialog = gtk_message_dialog_new (
-                                       GTK_WINDOW (prop_dialog->the_dialog),
-                                       0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
-                                       _("There is already a category '%s' in the configuration. Please use another name"),
-                                       category_name);
-
-                               gtk_dialog_run (GTK_DIALOG (error_dialog));
-                               gtk_widget_destroy (error_dialog);
-                               g_free (correct_category_name);
-                       } else {
-                               gchar *category_icon;
-
-                               category_icon = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (prop_dialog->category_icon));
-
-                               e_categories_add (correct_category_name, NULL, category_icon, TRUE);
-
-                               g_free (category_icon);
-                               g_free (correct_category_name);
-
-                               break;
-                       }
-               } else
-                       break;
-       } while (TRUE);
-
-       free_properties_dialog (prop_dialog);
-}
-
-static void
-edit_button_clicked_cb (GtkButton *button, gpointer user_data)
-{
-       ECategoriesDialog *dialog;
-       CategoryPropertiesDialog *prop_dialog;
-       GtkTreeSelection *selection;
-       GtkTreeIter iter;
-       GtkTreeModel *model;
-       GtkTreeView *tree_view;
-       GList *selected;
-       gchar *category_name;
-       const gchar *icon_file;
-
-       dialog = user_data;
-
-       tree_view = GTK_TREE_VIEW (dialog->priv->categories_list);
-       selection = gtk_tree_view_get_selection (tree_view);
-       selected = gtk_tree_selection_get_selected_rows (selection, &model);
-       g_return_if_fail (g_list_length (selected) == 1);
-
-       /* load the properties dialog */
-       prop_dialog = create_properties_dialog (dialog);
-       if (!prop_dialog)
-               return;
-
-       gtk_tree_model_get_iter (model, &iter, selected->data);
-       gtk_tree_model_get (model, &iter, COLUMN_CATEGORY, &category_name, -1);
-       gtk_entry_set_text (GTK_ENTRY (prop_dialog->category_name), category_name);
-       gtk_widget_set_sensitive (prop_dialog->category_name, FALSE);
-
-       icon_file = e_categories_get_icon_file_for (category_name);
-       if (icon_file)
-               gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (prop_dialog->category_icon), icon_file);
-
-       if (gtk_dialog_run (GTK_DIALOG (prop_dialog->the_dialog)) == GTK_RESPONSE_OK) {
-               gchar *category_icon;
-
-               category_icon = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (prop_dialog->category_icon));
-
-               e_categories_set_icon_file_for (category_name, category_icon);
-
-               if (category_icon) {
-                       GdkPixbuf *icon = NULL;
-
-                       icon = gdk_pixbuf_new_from_file (category_icon, NULL);
-                       if (icon) {
-                               gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ICON, icon, -1);
-                               g_object_unref (icon);
-                       }
-
-                       g_free (category_icon);
-               } else {
-                       gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ICON, NULL, -1);
-               }
-
-               gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
-       }
-
-       g_free (category_name);
-       free_properties_dialog (prop_dialog);
-
-       g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
-       g_list_free (selected);
-}
-
-static void
-categories_dialog_delete_cb (ECategoriesDialog *dialog)
-{
-       GtkTreeSelection *selection;
-       GtkTreeView *tree_view;
-       GtkTreeModel *model;
-       GList *selected, *item;
-
-       tree_view = GTK_TREE_VIEW (dialog->priv->categories_list);
-       selection = gtk_tree_view_get_selection (tree_view);
-       selected = gtk_tree_selection_get_selected_rows (selection, &model);
-
-       /* Remove categories in reverse order to avoid invalidating
-        * tree paths as we iterate over the list.  Note, the list is
-        * probably already sorted but we sort again just to be safe. */
-       selected = g_list_reverse (g_list_sort (
-               selected, (GCompareFunc) gtk_tree_path_compare));
-
-       /* Prevent the model from being rebuilt every time we
-        * remove a category, since we're already modifying it. */
-       dialog->priv->ignore_category_changes = TRUE;
-
-       for (item = selected; item != NULL; item = item->next) {
-               GtkTreePath *path = item->data;
-               GtkTreeIter iter;
-               gchar *category;
-               gint column_id;
-
-               column_id = COLUMN_CATEGORY;
-               gtk_tree_model_get_iter (model, &iter, path);
-               gtk_tree_model_get (model, &iter, column_id, &category, -1);
-               gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
-               e_categories_remove (category);
-               g_free (category);
-       }
-
-       dialog->priv->ignore_category_changes = FALSE;
-
-       /* If we only removed one category, try to select another. */
-       if (g_list_length (selected) == 1) {
-               GtkTreePath *path = selected->data;
-
-               gtk_tree_selection_select_path (selection, path);
-               if (!gtk_tree_selection_path_is_selected (selection, path))
-                       if (gtk_tree_path_prev (path))
-                               gtk_tree_selection_select_path (selection, path);
-       }
-
-       g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
-       g_list_free (selected);
-}
-
-static void
-categories_dialog_selection_changed_cb (ECategoriesDialog *dialog,
-                                        GtkTreeSelection *selection)
-{
-       GtkWidget *widget;
-       gint n_rows;
-
-       n_rows = gtk_tree_selection_count_selected_rows (selection);
-
-       widget = dialog->priv->edit_button;
-       gtk_widget_set_sensitive (widget, n_rows == 1);
-
-       widget = dialog->priv->delete_button;
-       gtk_widget_set_sensitive (widget, n_rows >= 1);
-}
-
-static gboolean
-categories_dialog_key_press_event (ECategoriesDialog *dialog,
-                                   GdkEventKey *event)
-{
-       GtkButton *button;
-
-       button = GTK_BUTTON (dialog->priv->delete_button);
-
-       if (event->keyval == GDK_KEY_Delete) {
-               gtk_button_clicked (button);
-               return TRUE;
-       }
-
-       return FALSE;
-}
-
-static void
-categories_dialog_dispose (GObject *object)
-{
-       ECategoriesDialogPrivate *priv;
-
-       priv = E_CATEGORIES_DIALOG (object)->priv;
-
-       g_hash_table_remove_all (priv->selected_categories);
-
-       /* Chain up to parent's dispose() method. */
-       G_OBJECT_CLASS (parent_class)->dispose (object);
-}
-
-static void
-categories_dialog_finalize (GObject *object)
-{
-       ECategoriesDialogPrivate *priv;
-
-       priv = E_CATEGORIES_DIALOG (object)->priv;
-
-       e_categories_unregister_change_listener (
-               G_CALLBACK (categories_dialog_listener_cb), object);
-
-       g_hash_table_destroy (priv->selected_categories);
-
-       /* Chain up to parent's finalize() method. */
-       G_OBJECT_CLASS (parent_class)->finalize (object);
+       gtk_dialog_set_response_sensitive (
+               GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE);
 }
 
 static void
 e_categories_dialog_class_init (ECategoriesDialogClass *class)
 {
-       GObjectClass *object_class;
-
-       parent_class = g_type_class_peek_parent (class);
        g_type_class_add_private (class, sizeof (ECategoriesDialogPrivate));
-
-       object_class = G_OBJECT_CLASS (class);
-       object_class->dispose = categories_dialog_dispose;
-       object_class->finalize = categories_dialog_finalize;
 }
 
 static void
 e_categories_dialog_init (ECategoriesDialog *dialog)
 {
-       GtkCellRenderer *renderer;
-       GtkEntryCompletion *completion;
-       GtkTreeViewColumn *column;
-       GtkTreeSelection *selection;
-       GtkTreeView *tree_view;
        GtkWidget *dialog_content;
-       GtkWidget *table_categories;
-       GtkWidget *entry_categories;
-       GtkWidget *label_header;
-       GtkWidget *label2;
-       GtkWidget *scrolledwindow1;
-       GtkWidget *categories_list;
-       GtkWidget *hbuttonbox1;
-       GtkWidget *button_new;
-       GtkWidget *button_edit;
-       GtkWidget *alignment1;
-       GtkWidget *hbox1;
-       GtkWidget *image1;
-       GtkWidget *label3;
-       GtkWidget *button_delete;
-
-       gtk_window_set_default_size (GTK_WINDOW (dialog), -1, 400);
-
-       dialog_content = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
-
-       table_categories = gtk_table_new (5, 1, FALSE);
-       gtk_box_pack_start (GTK_BOX (dialog_content), table_categories, TRUE, TRUE, 0);
-       gtk_container_set_border_width (GTK_CONTAINER (table_categories), 12);
-       gtk_table_set_row_spacings (GTK_TABLE (table_categories), 6);
-       gtk_table_set_col_spacings (GTK_TABLE (table_categories), 6);
-
-       entry_categories = gtk_entry_new ();
-       gtk_table_attach (GTK_TABLE (table_categories), entry_categories, 0, 1, 1, 2,
-                         (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
-                         (GtkAttachOptions) (0), 0, 0);
-
-       label_header = gtk_label_new_with_mnemonic (_("Currently _used categories:"));
-       gtk_table_attach (GTK_TABLE (table_categories), label_header, 0, 1, 0, 1,
-                         (GtkAttachOptions) (GTK_FILL),
-                         (GtkAttachOptions) (0), 0, 0);
-       gtk_label_set_justify (GTK_LABEL (label_header), GTK_JUSTIFY_CENTER);
-       gtk_misc_set_alignment (GTK_MISC (label_header), 0, 0.5);
-
-       label2 = gtk_label_new_with_mnemonic (_("_Available Categories:"));
-       gtk_table_attach (GTK_TABLE (table_categories), label2, 0, 1, 2, 3,
-                         (GtkAttachOptions) (GTK_FILL),
-                         (GtkAttachOptions) (0), 0, 0);
-       gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER);
-       gtk_misc_set_alignment (GTK_MISC (label2), 0, 0.5);
-
-       scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
-       gtk_table_attach (GTK_TABLE (table_categories), scrolledwindow1, 0, 1, 3, 4,
-                         (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
-                         (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), 0, 0);
-       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
-       gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_SHADOW_IN);
-
-       categories_list = gtk_tree_view_new ();
-       gtk_container_add (GTK_CONTAINER (scrolledwindow1), categories_list);
-       gtk_widget_set_size_request (categories_list, -1, 350);
-       gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (categories_list), FALSE);
-       gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (categories_list), TRUE);
-
-       hbuttonbox1 = gtk_hbutton_box_new ();
-       gtk_table_attach (GTK_TABLE (table_categories), hbuttonbox1, 0, 1, 4, 5,
-                         (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
-                         (GtkAttachOptions) (GTK_SHRINK), 0, 0);
-       gtk_box_set_spacing (GTK_BOX (hbuttonbox1), 6);
-
-       button_new = gtk_button_new_from_stock ("gtk-new");
-       gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_new);
-       gtk_widget_set_can_default (button_new, TRUE);
-
-       button_edit = gtk_button_new ();
-       gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_edit);
-       gtk_widget_set_can_default (button_edit, TRUE);
-
-       alignment1 = gtk_alignment_new (0.5, 0.5, 0, 0);
-       gtk_container_add (GTK_CONTAINER (button_edit), alignment1);
-
-       hbox1 = gtk_hbox_new (FALSE, 2);
-       gtk_container_add (GTK_CONTAINER (alignment1), hbox1);
-
-       image1 = gtk_image_new_from_stock ("gtk-properties", GTK_ICON_SIZE_BUTTON);
-       gtk_box_pack_start (GTK_BOX (hbox1), image1, FALSE, FALSE, 0);
-
-       label3 = gtk_label_new_with_mnemonic (_("_Edit"));
-       gtk_box_pack_start (GTK_BOX (hbox1), label3, FALSE, FALSE, 0);
-
-       button_delete = gtk_button_new_from_stock ("gtk-delete");
-       gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_delete);
-       gtk_widget_set_can_default (button_delete, TRUE);
-
-       gtk_label_set_mnemonic_widget (GTK_LABEL (label_header), entry_categories);
-       gtk_label_set_mnemonic_widget (GTK_LABEL (label2), categories_list);
+       GtkWidget *categories_editor;
 
        dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (
                dialog, E_TYPE_CATEGORIES_DIALOG, ECategoriesDialogPrivate);
-       dialog->priv->selected_categories = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-
-       dialog->priv->categories_entry = entry_categories;
-       dialog->priv->categories_list = categories_list;
-
-       tree_view = GTK_TREE_VIEW (dialog->priv->categories_list);
-       selection = gtk_tree_view_get_selection (tree_view);
-       gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
-
-       g_signal_connect_swapped (
-               selection, "changed",
-               G_CALLBACK (categories_dialog_selection_changed_cb), dialog);
 
-       g_signal_connect_swapped (
-               dialog->priv->categories_list, "key-press-event",
-               G_CALLBACK (categories_dialog_key_press_event), dialog);
+       categories_editor = e_categories_editor_new ();
+       dialog->priv->categories_editor = categories_editor;
 
-       completion = e_category_completion_new ();
-       gtk_entry_set_completion (GTK_ENTRY (dialog->priv->categories_entry), completion);
-       g_object_unref (completion);
-
-       dialog->priv->new_button = button_new;
-       g_signal_connect (G_OBJECT (dialog->priv->new_button), "clicked", G_CALLBACK (new_button_clicked_cb), dialog);
-       dialog->priv->edit_button = button_edit;
-       g_signal_connect (G_OBJECT (dialog->priv->edit_button), "clicked", G_CALLBACK (edit_button_clicked_cb), dialog);
-       dialog->priv->delete_button = button_delete;
-       g_signal_connect_swapped (
-               G_OBJECT (dialog->priv->delete_button), "clicked",
-               G_CALLBACK (categories_dialog_delete_cb), dialog);
-
-       gtk_dialog_add_buttons (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-                               GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+       dialog_content = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+       gtk_container_set_border_width (GTK_CONTAINER (dialog), 12);
+       gtk_box_pack_start (
+               GTK_BOX (dialog_content), categories_editor, TRUE, TRUE, 0);
+       gtk_box_set_spacing (GTK_BOX (dialog_content), 12);
+
+       g_signal_connect (
+               categories_editor, "entry-changed",
+               G_CALLBACK (entry_changed_cb), dialog);
+
+       gtk_dialog_add_buttons (
+               GTK_DIALOG (dialog),
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+               GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
        gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
-       gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
+       gtk_dialog_set_response_sensitive (
+               GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE);
        gtk_window_set_title (GTK_WINDOW (dialog), _("Categories"));
 
-       gtk_widget_show_all (dialog_content);
-
-       renderer = gtk_cell_renderer_toggle_new ();
-       g_signal_connect (G_OBJECT (renderer), "toggled", G_CALLBACK (category_toggled_cb), dialog);
-       column = gtk_tree_view_column_new_with_attributes ("?", renderer,
-                                                          "active", COLUMN_ACTIVE, NULL);
-       gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->priv->categories_list), column);
-
-       renderer = gtk_cell_renderer_pixbuf_new ();
-       column = gtk_tree_view_column_new_with_attributes (_("Icon"), renderer,
-                                                          "pixbuf", COLUMN_ICON, NULL);
-       gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->priv->categories_list), column);
-
-       renderer = gtk_cell_renderer_text_new ();
-       column = gtk_tree_view_column_new_with_attributes (_("Category"), renderer,
-                                                          "text", COLUMN_CATEGORY, NULL);
-       gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->priv->categories_list), column);
-
-       categories_dialog_build_model (dialog);
-
-       e_categories_register_change_listener (
-               G_CALLBACK (categories_dialog_listener_cb), dialog);
+       gtk_widget_show_all (categories_editor);
 }
 
 /**
@@ -794,13 +101,11 @@ e_categories_dialog_new (const gchar *categories)
 {
        ECategoriesDialog *dialog;
 
-       dialog = E_CATEGORIES_DIALOG (g_object_new (E_TYPE_CATEGORIES_DIALOG, NULL));
+       dialog = g_object_new (E_TYPE_CATEGORIES_DIALOG, NULL);
+
        if (categories)
                e_categories_dialog_set_categories (dialog, categories);
 
-       g_signal_connect (G_OBJECT (dialog->priv->categories_entry), "changed",
-                         G_CALLBACK (entry_changed_cb), dialog);
-
        return GTK_WIDGET (dialog);
 }
 
@@ -816,30 +121,14 @@ e_categories_dialog_new (const gchar *categories)
 const gchar *
 e_categories_dialog_get_categories (ECategoriesDialog *dialog)
 {
-       GtkEntry *entry;
-       const gchar *text;
+       const gchar *categories;
 
        g_return_val_if_fail (E_IS_CATEGORIES_DIALOG (dialog), NULL);
 
-       entry = GTK_ENTRY (dialog->priv->categories_entry);
+       categories = e_categories_editor_get_categories (
+               E_CATEGORIES_EDITOR (dialog->priv->categories_editor));
 
-       text = gtk_entry_get_text (entry);
-       if (text) {
-               gint len = strlen (text), old_len = len;
-
-               while (len > 0 && (text[len -1] == ' ' || text[len - 1] == ','))
-                       len--;
-
-               if (old_len != len) {
-                       gchar *tmp = g_strndup (text, len);
-
-                       gtk_entry_set_text (entry, tmp);
-
-                       g_free (tmp);
-               }
-       }
-
-       return gtk_entry_get_text (entry);
+       return categories;
 }
 
 /**
@@ -853,47 +142,9 @@ void
 e_categories_dialog_set_categories (ECategoriesDialog *dialog,
                                     const gchar *categories)
 {
-       ECategoriesDialogPrivate *priv;
-       gchar **arr;
-       GtkTreeIter iter;
-       GtkTreeModel *model;
-
        g_return_if_fail (E_IS_CATEGORIES_DIALOG (dialog));
 
-       priv = dialog->priv;
-
-       /* clean up the table of selected categories */
-       g_hash_table_foreach_remove (priv->selected_categories, (GHRFunc) gtk_true, NULL);
-
-       arr = g_strsplit (categories, ",", 0);
-       if (arr) {
-               gint i = 0;
-               while (arr[i] != NULL) {
-                       arr[i] = g_strstrip (arr[i]);
-
-                       if (arr[i][0])
-                               g_hash_table_insert (priv->selected_categories, g_strdup (arr[i]), g_strdup (arr[i]));
-                       i++;
-               }
-
-               g_strfreev (arr);
-       }
-
-       /* set the widgets */
-       gtk_entry_set_text (GTK_ENTRY (priv->categories_entry), categories);
-
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->categories_list));
-       if (gtk_tree_model_get_iter_first (model, &iter)) {
-               do {
-                       gchar *place_string;
-
-                       gtk_tree_model_get (model, &iter, COLUMN_CATEGORY, &place_string, -1);
-                       if (g_hash_table_lookup (priv->selected_categories, place_string))
-                               gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, TRUE, -1);
-                       else
-                               gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_ACTIVE, FALSE, -1);
-
-                       g_free (place_string);
-               } while (gtk_tree_model_iter_next (model, &iter));
-       }
+       e_categories_editor_set_categories (
+               E_CATEGORIES_EDITOR (dialog->priv->categories_editor),
+               categories);
 }
diff --git a/libedataserverui/e-categories-editor.c b/libedataserverui/e-categories-editor.c
new file mode 100644 (file)
index 0000000..7a4e208
--- /dev/null
@@ -0,0 +1,427 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n-lib.h>
+#include "libedataserver/e-categories.h"
+#include "libedataserver/libedataserver-private.h"
+#include "e-categories-editor.h"
+#include "e-categories-selector.h"
+#include "e-category-completion.h"
+#include "e-category-editor.h"
+
+G_DEFINE_TYPE (ECategoriesEditor, e_categories_editor, GTK_TYPE_TABLE)
+
+struct _ECategoriesEditorPrivate {
+       ECategoriesSelector *categories_list;
+       GtkWidget *categories_entry;
+       GtkWidget *categories_entry_label;
+       GtkWidget *new_button;
+       GtkWidget *edit_button;
+       GtkWidget *delete_button;
+
+       guint ignore_category_changes : 1;
+};
+
+enum {
+       COLUMN_ACTIVE,
+       COLUMN_ICON,
+       COLUMN_CATEGORY,
+       N_COLUMNS
+};
+
+enum {
+       PROP_0,
+       PROP_ENTRY_VISIBLE
+};
+
+enum {
+       ENTRY_CHANGED,
+       LAST_SIGNAL
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+static void
+entry_changed_cb (GtkEntry *entry,
+                  ECategoriesEditor *editor)
+{
+       g_signal_emit (editor, signals[ENTRY_CHANGED], 0);
+}
+
+
+static void
+categories_editor_selection_changed_cb (ECategoriesEditor *editor,
+                                        GtkTreeSelection *selection)
+{
+       GtkWidget *widget;
+       gint n_rows;
+
+       n_rows = gtk_tree_selection_count_selected_rows (selection);
+
+       widget = editor->priv->edit_button;
+       gtk_widget_set_sensitive (widget, n_rows == 1);
+
+       widget = editor->priv->delete_button;
+       gtk_widget_set_sensitive (widget, n_rows >= 1);
+}
+
+static void
+category_checked_cb (ECategoriesSelector *selector,
+                     const gchar *category,
+                     const gboolean checked,
+                     ECategoriesEditor *editor)
+{
+       GtkEntry *entry;
+       const gchar *categories;
+
+       entry = GTK_ENTRY (editor->priv->categories_entry);
+       categories = e_categories_selector_get_checked (selector);
+
+       gtk_entry_set_text (entry, categories);
+}
+
+static void
+new_button_clicked_cb (GtkButton *button,
+                       ECategoriesEditor *editor)
+{
+       ECategoryEditor *cat_editor = e_category_editor_new();
+
+       e_category_editor_create_category (cat_editor);
+
+       gtk_widget_destroy (GTK_WIDGET (cat_editor));
+}
+
+static void
+edit_button_clicked_cb (GtkButton *button,
+                        ECategoriesEditor *editor)
+{
+       ECategoryEditor *cat_editor = e_category_editor_new();
+       const gchar *category;
+
+       category = e_categories_selector_get_selected (
+               editor->priv->categories_list);
+
+       e_category_editor_edit_category (cat_editor, category);
+
+       gtk_widget_destroy (GTK_WIDGET (cat_editor));
+}
+
+static void
+categories_editor_set_property (GObject *object,
+                                guint property_id,
+                                const GValue *value,
+                                GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ENTRY_VISIBLE:
+                       e_categories_editor_set_entry_visible (
+                               E_CATEGORIES_EDITOR (object),
+                               g_value_get_boolean (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+categories_editor_get_property (GObject *object,
+                                guint property_id,
+                                GValue *value,
+                                GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ENTRY_VISIBLE:
+                       g_value_set_boolean (
+                               value, e_categories_editor_get_entry_visible (
+                               E_CATEGORIES_EDITOR (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_categories_editor_class_init (ECategoriesEditorClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (ECategoriesEditorPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = categories_editor_set_property;
+       object_class->get_property = categories_editor_get_property;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_ENTRY_VISIBLE,
+               g_param_spec_boolean (
+                       "entry-visible",
+                       NULL,
+                       NULL,
+                       TRUE,
+                       G_PARAM_READWRITE));
+
+       signals[ENTRY_CHANGED] = g_signal_new (
+               "entry-changed",
+               G_TYPE_FROM_CLASS (class),
+               G_SIGNAL_RUN_FIRST,
+               G_STRUCT_OFFSET (ECategoriesEditorClass, entry_changed),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__VOID,
+               G_TYPE_NONE, 0);
+}
+
+static void
+e_categories_editor_init (ECategoriesEditor *editor)
+{
+       GtkEntryCompletion *completion;
+       GtkWidget *entry_categories;
+       GtkWidget *label_header;
+       GtkWidget *label2;
+       GtkWidget *scrolledwindow1;
+       GtkWidget *categories_list;
+       GtkWidget *hbuttonbox1;
+       GtkWidget *button_new;
+       GtkWidget *button_edit;
+       GtkWidget *button_delete;
+
+       gtk_widget_set_size_request (GTK_WIDGET (editor), -1, 400);
+
+       gtk_table_resize (GTK_TABLE (editor), 3, 2);
+       gtk_table_set_row_spacings (GTK_TABLE (editor), 6);
+       gtk_table_set_col_spacings (GTK_TABLE (editor), 6);
+
+       entry_categories = gtk_entry_new ();
+       gtk_table_attach (
+               GTK_TABLE (editor),
+               entry_categories, 0, 1, 1, 2,
+               (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
+               (GtkAttachOptions) (0), 0, 0);
+
+       label_header = gtk_label_new_with_mnemonic (
+               _("Currently _used categories:"));
+       gtk_table_attach (
+               GTK_TABLE (editor),
+               label_header, 0, 1, 0, 1,
+               (GtkAttachOptions) (GTK_FILL),
+               (GtkAttachOptions) (0), 0, 0);
+       gtk_label_set_justify (GTK_LABEL (label_header), GTK_JUSTIFY_CENTER);
+       gtk_misc_set_alignment (GTK_MISC (label_header), 0, 0.5);
+
+       label2 = gtk_label_new_with_mnemonic (_("_Available Categories:"));
+       gtk_table_attach (
+               GTK_TABLE (editor),
+               label2, 0, 1, 2, 3,
+               (GtkAttachOptions) (GTK_FILL),
+               (GtkAttachOptions) (0), 0, 0);
+       gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER);
+       gtk_misc_set_alignment (GTK_MISC (label2), 0, 0.5);
+
+       scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
+       gtk_table_attach (
+               GTK_TABLE (editor),
+               scrolledwindow1, 0, 1, 3, 4,
+               (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
+               (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), 0, 0);
+       gtk_scrolled_window_set_policy (
+               GTK_SCROLLED_WINDOW (scrolledwindow1),
+               GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+       gtk_scrolled_window_set_shadow_type (
+               GTK_SCROLLED_WINDOW (scrolledwindow1), GTK_SHADOW_IN);
+
+       categories_list = GTK_WIDGET (e_categories_selector_new ());
+       gtk_container_add (GTK_CONTAINER (scrolledwindow1), categories_list);
+       gtk_widget_set_size_request (categories_list, -1, 350);
+       gtk_tree_view_set_headers_visible (
+               GTK_TREE_VIEW (categories_list), FALSE);
+       gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (categories_list), TRUE);
+       g_signal_connect (G_OBJECT (categories_list), "category-checked",
+               G_CALLBACK (category_checked_cb), editor);
+
+       hbuttonbox1 = gtk_hbutton_box_new ();
+       gtk_table_attach (
+               GTK_TABLE (editor),
+               hbuttonbox1, 0, 1, 4, 5,
+               (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL),
+               (GtkAttachOptions) (GTK_SHRINK), 0, 0);
+       gtk_box_set_spacing (GTK_BOX (hbuttonbox1), 6);
+
+       button_new = gtk_button_new_from_stock (GTK_STOCK_NEW);
+       gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_new);
+       gtk_widget_set_can_default (button_new, TRUE);
+
+       button_edit = gtk_button_new_from_stock (GTK_STOCK_EDIT);
+       gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_edit);
+       gtk_widget_set_can_default (button_edit, TRUE);
+
+       button_delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
+       gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_delete);
+       gtk_widget_set_can_default (button_delete, TRUE);
+
+       gtk_label_set_mnemonic_widget (
+               GTK_LABEL (label_header), entry_categories);
+       gtk_label_set_mnemonic_widget (
+               GTK_LABEL (label2), categories_list);
+
+       editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+               editor, E_TYPE_CATEGORIES_EDITOR, ECategoriesEditorPrivate);
+
+       editor->priv->categories_list = E_CATEGORIES_SELECTOR (categories_list);
+       editor->priv->categories_entry = entry_categories;
+       editor->priv->categories_entry_label = label_header;
+
+       g_signal_connect_swapped (
+               editor->priv->categories_list, "selection-changed",
+               G_CALLBACK (categories_editor_selection_changed_cb), editor);
+
+       completion = e_category_completion_new ();
+       gtk_entry_set_completion (
+               GTK_ENTRY (editor->priv->categories_entry), completion);
+       g_object_unref (completion);
+
+       editor->priv->new_button = button_new;
+       g_signal_connect (
+               editor->priv->new_button, "clicked",
+               G_CALLBACK (new_button_clicked_cb), editor);
+
+       editor->priv->edit_button = button_edit;
+       g_signal_connect (
+               editor->priv->edit_button, "clicked",
+               G_CALLBACK (edit_button_clicked_cb), editor);
+
+       editor->priv->delete_button = button_delete;
+       g_signal_connect_swapped (
+               editor->priv->delete_button, "clicked",
+               G_CALLBACK (e_categories_selector_delete_selection),
+               editor->priv->categories_list);
+
+       g_signal_connect (
+               editor->priv->categories_entry, "changed",
+               G_CALLBACK (entry_changed_cb), editor);
+
+       gtk_widget_show_all (GTK_WIDGET (editor));
+}
+
+/**
+ * e_categories_editor_new:
+ *
+ * Creates a new #ECategoriesEditor widget.
+ *
+ * Returns: a new #ECategoriesEditor
+ *
+ * Since: 3.2
+ **/
+GtkWidget *
+e_categories_editor_new (void)
+{
+       return g_object_new (E_TYPE_CATEGORIES_EDITOR, NULL);
+}
+
+/**
+ * e_categories_editor_get_categories:
+ * @editor: an #ECategoriesEditor
+ *
+ * Gets a comma-separated list of the categories currently selected
+ * in the editor.
+ *
+ * Returns: a comma-separated list of categories
+ *
+ * Since: 3.2
+ **/
+const gchar *
+e_categories_editor_get_categories (ECategoriesEditor *editor)
+{
+       ECategoriesSelector *categories_list;
+
+       g_return_val_if_fail (E_IS_CATEGORIES_EDITOR (editor), NULL);
+
+       categories_list = editor->priv->categories_list;
+
+       return e_categories_selector_get_checked (categories_list);
+}
+
+/**
+ * e_categories_editor_set_categories:
+ * @dialog: an #ECategoriesEditor
+ * @categories: comma-separated list of categories
+ *
+ * Sets the list of categories selected on the editor.
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_editor_set_categories (ECategoriesEditor *editor,
+                                    const gchar *categories)
+{
+       ECategoriesSelector *categories_list;
+
+       g_return_if_fail (E_IS_CATEGORIES_EDITOR (editor));
+
+       categories_list = editor->priv->categories_list;
+
+       e_categories_selector_set_checked (categories_list, categories);
+       category_checked_cb (categories_list, NULL, FALSE, editor);
+}
+
+/**
+ * e_categories_editor_get_entry_visible:
+ * @editor: an #ECategoriesEditor
+ *
+ * Return the visibility of the category input entry.
+ *
+ * Returns: whether the entry is visible
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_categories_editor_get_entry_visible (ECategoriesEditor *editor)
+{
+       g_return_val_if_fail (E_IS_CATEGORIES_EDITOR (editor), TRUE);
+
+       return gtk_widget_get_visible (editor->priv->categories_entry);
+}
+
+/**
+ * e_categories_editor_set_entry_visible:
+ * @editor: an #ECategoriesEditor
+ * @entry_visible: whether to make the entry visible
+ *
+ * Sets the visibility of the category input entry.
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_editor_set_entry_visible (ECategoriesEditor *editor,
+                                       gboolean entry_visible)
+{
+       g_return_if_fail (E_IS_CATEGORIES_EDITOR (editor));
+
+       gtk_widget_set_visible (
+               editor->priv->categories_entry, entry_visible);
+       gtk_widget_set_visible (
+               editor->priv->categories_entry_label, entry_visible);
+       e_categories_selector_set_items_checkable (
+               editor->priv->categories_list, entry_visible);
+
+       g_object_notify (G_OBJECT (editor), "entry-visible");
+}
diff --git a/libedataserverui/e-categories-editor.h b/libedataserverui/e-categories-editor.h
new file mode 100644 (file)
index 0000000..2789c0d
--- /dev/null
@@ -0,0 +1,76 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef E_CATEGORIES_EDITOR_H
+#define E_CATEGORIES_EDITOR_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORIES_EDITOR \
+       (e_categories_editor_get_type ())
+#define E_CATEGORIES_EDITOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_CATEGORIES_EDITOR, ECategoriesEditor))
+#define E_CATEGORIES_EDITOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_CATEGORIES_EDITOR, ECategoriesEditorClass))
+#define E_IS_CATEGORIES_EDITOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_CATEGORIES_EDITOR))
+#define E_IS_CATEGORIES_EDITOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_CATEGORIES_EDITOR))
+#define E_CATEGORIES_EDITOR_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_CATEGORIES_EDITOR, ECategoriesEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoriesEditor ECategoriesEditor;
+typedef struct _ECategoriesEditorClass ECategoriesEditorClass;
+typedef struct _ECategoriesEditorPrivate ECategoriesEditorPrivate;
+
+struct _ECategoriesEditor {
+       GtkTable parent;
+       ECategoriesEditorPrivate *priv;
+};
+
+struct _ECategoriesEditorClass {
+       GtkTableClass parent_class;
+
+       void            (*entry_changed)        (GtkEntry *entry);
+};
+
+GType          e_categories_editor_get_type    (void);
+GtkWidget *    e_categories_editor_new         (void);
+const gchar *  e_categories_editor_get_categories
+                                               (ECategoriesEditor *editor);
+void           e_categories_editor_set_categories
+                                               (ECategoriesEditor *editor,
+                                                const gchar *categories);
+gboolean       e_categories_editor_get_entry_visible
+                                               (ECategoriesEditor *editor);
+void           e_categories_editor_set_entry_visible
+                                               (ECategoriesEditor *editor,
+                                                gboolean entry_visible);
+
+G_END_DECLS
+
+#endif /* E_CATEGORIES_EDITOR_H */
diff --git a/libedataserverui/e-categories-selector.c b/libedataserverui/e-categories-selector.c
new file mode 100644 (file)
index 0000000..addd266
--- /dev/null
@@ -0,0 +1,570 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+#include "libedataserver/e-categories.h"
+#include "e-categories-selector.h"
+#include "e-data-server-ui-marshal.h"
+
+G_DEFINE_TYPE (
+       ECategoriesSelector,
+       e_categories_selector,
+       GTK_TYPE_TREE_VIEW)
+
+struct _ECategoriesSelectorPrivate {
+       gboolean checkable;
+       GHashTable *selected_categories;
+
+       gboolean ignore_category_changes;
+};
+
+enum {
+       PROP_0,
+       PROP_ITEMS_CHECKABLE
+};
+
+enum {
+       CATEGORY_CHECKED,
+       SELECTION_CHANGED,
+       LAST_SIGNAL
+};
+
+enum {
+       COLUMN_ACTIVE,
+       COLUMN_ICON,
+       COLUMN_CATEGORY,
+       N_COLUMNS
+};
+
+static gint signals[LAST_SIGNAL] = {0};
+
+static void
+categories_selector_build_model (ECategoriesSelector *selector)
+{
+       GtkListStore *store;
+       GList *list, *iter;
+
+       store = gtk_list_store_new (
+               N_COLUMNS, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING);
+
+       gtk_tree_sortable_set_sort_column_id (
+               GTK_TREE_SORTABLE (store),
+               COLUMN_CATEGORY, GTK_SORT_ASCENDING);
+
+       list = e_categories_get_list ();
+       for (iter = list; iter != NULL; iter = iter->next) {
+               const gchar *category_name = iter->data;
+               const gchar *filename;
+               GdkPixbuf *pixbuf = NULL;
+               GtkTreeIter iter;
+               gboolean active;
+
+               /* Only add user-visible categories. */
+               if (!e_categories_is_searchable (category_name))
+                       continue;
+
+               active = (g_hash_table_lookup (
+                               selector->priv->selected_categories,
+                               category_name) != NULL);
+
+               filename = e_categories_get_icon_file_for (category_name);
+               if (filename != NULL)
+                       pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
+
+               gtk_list_store_append (store, &iter);
+
+               gtk_list_store_set (
+                       store, &iter,
+                       COLUMN_ACTIVE, active,
+                       COLUMN_ICON, pixbuf,
+                       COLUMN_CATEGORY, category_name,
+                       -1);
+
+               if (pixbuf != NULL)
+                       g_object_unref (pixbuf);
+       }
+
+       gtk_tree_view_set_model (
+               GTK_TREE_VIEW (selector), GTK_TREE_MODEL (store));
+
+       /* This has to be reset everytime we install a new model */
+       gtk_tree_view_set_search_column (
+               GTK_TREE_VIEW (selector), COLUMN_CATEGORY);
+
+       g_list_free (list);
+       g_object_unref (store);
+}
+
+static void
+category_toggled_cb (GtkCellRenderer *renderer,
+                     const gchar *path,
+                     ECategoriesSelector *selector)
+{
+       GtkTreeModel *model;
+       GtkTreePath *tree_path;
+       GtkTreeIter iter;
+
+       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       g_return_if_fail (model);
+
+       tree_path = gtk_tree_path_new_from_string (path);
+       g_return_if_fail (tree_path);
+
+       if (gtk_tree_model_get_iter (model, &iter, tree_path)) {
+               gchar *category;
+               gboolean active;
+
+               gtk_tree_model_get (model, &iter,
+                       COLUMN_ACTIVE, &active,
+                       COLUMN_CATEGORY, &category, -1);
+
+               gtk_list_store_set (
+                       GTK_LIST_STORE (model), &iter,
+                       COLUMN_ACTIVE, !active, -1);
+
+               if (active)
+                       g_hash_table_remove (
+                               selector->priv->selected_categories, category);
+               else
+                       g_hash_table_insert (
+                               selector->priv->selected_categories,
+                               g_strdup (category), g_strdup (category));
+
+               g_signal_emit (
+                       selector, signals[CATEGORY_CHECKED], 0,
+                       category, !active);
+
+               g_free (category);
+       }
+
+       gtk_tree_path_free (tree_path);
+}
+
+static void
+categories_selector_listener_cb (gpointer useless_pointer,
+                                 ECategoriesSelector *selector)
+{
+       if (!selector->priv->ignore_category_changes)
+               categories_selector_build_model (selector);
+}
+
+static gboolean
+categories_selector_key_press_event (ECategoriesSelector *selector,
+                                     GdkEventKey *event)
+{
+       if (event->keyval == GDK_KEY_Delete) {
+               e_categories_selector_delete_selection (selector);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+categories_selector_selection_changed (GtkTreeSelection *selection,
+                                       ECategoriesSelector *selector)
+{
+       g_signal_emit (selector, signals[SELECTION_CHANGED], 0, selection);
+}
+
+static void
+categories_selector_get_property (GObject *object,
+                                  guint property_id,
+                                  GValue *value,
+                                  GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ITEMS_CHECKABLE:
+                       g_value_set_boolean (
+                               value,
+                               e_categories_selector_get_items_checkable (
+                               E_CATEGORIES_SELECTOR (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+categories_selector_set_property (GObject *object,
+                                  guint property_id,
+                                  const GValue *value,
+                                  GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_ITEMS_CHECKABLE:
+                       e_categories_selector_set_items_checkable (
+                               E_CATEGORIES_SELECTOR (object),
+                               g_value_get_boolean (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+categories_selector_dispose (GObject *object)
+{
+       ECategoriesSelectorPrivate *priv;
+
+       priv = E_CATEGORIES_SELECTOR (object)->priv;
+
+       if (priv->selected_categories != NULL) {
+               g_hash_table_destroy (priv->selected_categories);
+               priv->selected_categories = NULL;
+       }
+
+       /* Chain up to parent's dispose() method.*/
+       G_OBJECT_CLASS (e_categories_selector_parent_class)->dispose (object);
+}
+
+static void
+categories_selector_finalize (GObject *object)
+{
+       e_categories_unregister_change_listener (
+               G_CALLBACK (categories_selector_listener_cb), object);
+}
+
+static void
+e_categories_selector_class_init (ECategoriesSelectorClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (ECategoriesSelectorPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = categories_selector_set_property;
+       object_class->get_property = categories_selector_get_property;
+       object_class->dispose = categories_selector_dispose;
+       object_class->finalize = categories_selector_finalize;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_ITEMS_CHECKABLE,
+               g_param_spec_boolean (
+                       "items-checkable",
+                       NULL,
+                       NULL,
+                       TRUE,
+                       G_PARAM_READWRITE));
+
+       signals[CATEGORY_CHECKED] = g_signal_new (
+               "category-checked",
+               G_TYPE_FROM_CLASS (class),
+               G_SIGNAL_RUN_FIRST,
+               G_STRUCT_OFFSET (ECategoriesSelectorClass, category_checked),
+               NULL, NULL,
+               e_data_server_ui_marshal_VOID__STRING_BOOLEAN,
+               G_TYPE_NONE, 2,
+               G_TYPE_STRING,
+               G_TYPE_BOOLEAN);
+
+       signals[SELECTION_CHANGED] = g_signal_new (
+               "selection-changed",
+               G_TYPE_FROM_CLASS (class),
+               G_SIGNAL_RUN_FIRST,
+               G_STRUCT_OFFSET (ECategoriesSelectorClass, selection_changed),
+               NULL, NULL,
+               g_cclosure_marshal_VOID__OBJECT,
+               G_TYPE_NONE, 1,
+               GTK_TYPE_TREE_SELECTION);
+}
+
+static void
+e_categories_selector_init (ECategoriesSelector *selector)
+{
+       GtkCellRenderer *renderer;
+       GtkTreeViewColumn *column;
+       GtkTreeSelection *selection;
+
+       selector->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+               selector, E_TYPE_CATEGORIES_SELECTOR,
+               ECategoriesSelectorPrivate);
+
+       selector->priv->checkable = TRUE;
+       selector->priv->selected_categories = g_hash_table_new_full (
+               (GHashFunc) g_str_hash,
+               (GEqualFunc) g_str_equal,
+               (GDestroyNotify) g_free,
+               (GDestroyNotify) g_free);
+       selector->priv->ignore_category_changes = FALSE;
+
+       renderer = gtk_cell_renderer_toggle_new ();
+       column = gtk_tree_view_column_new_with_attributes (
+               "?", renderer, "active", COLUMN_ACTIVE, NULL);
+       gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
+
+       g_signal_connect (
+               renderer, "toggled",
+               G_CALLBACK (category_toggled_cb), selector);
+
+       renderer = gtk_cell_renderer_pixbuf_new ();
+       column = gtk_tree_view_column_new_with_attributes (
+               _("Icon"), renderer, "pixbuf", COLUMN_ICON, NULL);
+       gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
+
+       renderer = gtk_cell_renderer_text_new ();
+       column = gtk_tree_view_column_new_with_attributes (
+               _("Category"), renderer, "text", COLUMN_CATEGORY, NULL);
+       gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
+       g_signal_connect (
+               selection, "changed",
+               G_CALLBACK (categories_selector_selection_changed), selector);
+
+       g_signal_connect (
+               selector, "key-press-event",
+               G_CALLBACK(categories_selector_key_press_event), NULL);
+
+       e_categories_register_change_listener (
+               G_CALLBACK (categories_selector_listener_cb), selector);
+
+       categories_selector_build_model (selector);
+}
+
+/**
+ * e_categories_selector_new:
+ *
+ * Since: 3.2
+ **/
+GtkWidget *
+e_categories_selector_new (void)
+{
+       return g_object_new (
+               E_TYPE_CATEGORIES_SELECTOR,
+               "items-checkable", TRUE, NULL);
+}
+
+/**
+ * e_categories_selector_get_items_checkable:
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_categories_selector_get_items_checkable (ECategoriesSelector *selector)
+{
+       g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), TRUE);
+
+       return selector->priv->checkable;
+}
+
+/**
+ * e_categories_selector_set_items_checkable:
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_selector_set_items_checkable (ECategoriesSelector *selector,
+                                           gboolean checkable)
+{
+       GtkTreeViewColumn *column;
+
+       g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
+
+       selector->priv->checkable = checkable;
+
+       column = gtk_tree_view_get_column (
+               GTK_TREE_VIEW (selector), COLUMN_ACTIVE);
+       gtk_tree_view_column_set_visible (column, checkable);
+
+       g_object_notify (G_OBJECT (selector), "items-checkable");
+}
+
+/**
+ * e_categories_selector_get_checked:
+ *
+ * Since: 3.2
+ **/
+const gchar *
+e_categories_selector_get_checked (ECategoriesSelector *selector)
+{
+       GString *str = g_string_new ("");
+       GList *list, *category;
+
+       g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
+
+       list = g_hash_table_get_values (selector->priv->selected_categories);
+
+       for (category = list; category != NULL; category = category->next) {
+               if (str->len > 0)
+                       g_string_append_printf (
+                               str, ",%s", (gchar *) category->data);
+               else
+                       g_string_append (str, (gchar *) category->data);
+       }
+
+       g_list_free (list);
+
+       return g_string_free (str, FALSE);
+}
+
+/**
+ * e_categories_selector_set_checked:
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_selector_set_checked (ECategoriesSelector *selector,
+                                   const gchar *categories)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+       gchar **arr;
+       gint i;
+
+       g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
+
+       /* Clean up table of selected categories. */
+       g_hash_table_remove_all (selector->priv->selected_categories);
+
+       arr = g_strsplit (categories, ",", 0);
+       if (arr) {
+               for (i = 0; arr[i] != NULL; i++) {
+                       g_strstrip (arr[i]);
+                       g_hash_table_insert (
+                               selector->priv->selected_categories,
+                               g_strdup (arr[i]), g_strdup (arr[i]));
+               }
+               g_strfreev (arr);
+       }
+
+       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       if (gtk_tree_model_get_iter_first (model, &iter)) {
+               do {
+                       gchar *category_name;
+                       gboolean found;
+
+                       gtk_tree_model_get (model, &iter,
+                               COLUMN_CATEGORY, &category_name,
+                               -1);
+                       found = (g_hash_table_lookup (
+                               selector->priv->selected_categories,
+                               category_name) != NULL);
+                       gtk_list_store_set (
+                               GTK_LIST_STORE (model), &iter,
+                               COLUMN_ACTIVE, found, -1);
+
+                       g_free (category_name);
+               } while (gtk_tree_model_iter_next (model, &iter));
+       }
+}
+
+/**
+ * e_categories_selector_delete_selection:
+ *
+ * Since: 3.2
+ **/
+void
+e_categories_selector_delete_selection (ECategoriesSelector *selector)
+{
+       GtkTreeModel *model;
+       GtkTreeSelection *selection;
+       GList *selected, *item;
+
+       g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
+
+       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       g_return_if_fail (model != NULL);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
+       selected = gtk_tree_selection_get_selected_rows (selection, &model);
+
+       /* Remove categories in reverse order to avoid invalidating
+        * tree paths as we iterate over the list. Note, the list is
+        * probably already sorted but we sort again just to be safe. */
+       selected = g_list_reverse (g_list_sort (
+               selected, (GCompareFunc) gtk_tree_path_compare));
+
+       /* Prevent the model from being rebuilt every time we
+          remove a category, since we're already modifying it. */
+       selector->priv->ignore_category_changes = TRUE;
+
+       for (item = selected; item != NULL; item = item->next) {
+               GtkTreePath *path = item->data;
+               GtkTreeIter iter;
+               gchar *category;
+
+               gtk_tree_model_get_iter (model, &iter, path);
+               gtk_tree_model_get (model, &iter,
+                       COLUMN_CATEGORY, &category, -1);
+               gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+               e_categories_remove (category);
+               g_free (category);
+       }
+
+       selector->priv->ignore_category_changes = FALSE;
+
+       /* If we only remove one category, try to select another */
+       if (g_list_length (selected) == 1) {
+               GtkTreePath *path = selected->data;
+
+               gtk_tree_selection_select_path (selection, path);
+               if (!gtk_tree_selection_path_is_selected (selection, path))
+                       if (gtk_tree_path_prev (path))
+                               gtk_tree_selection_select_path (selection, path);
+       }
+
+       g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+       g_list_free (selected);
+}
+
+/**
+ * e_categories_selector_get_selected:
+ *
+ * Since: 3.2
+ **/
+const gchar *
+e_categories_selector_get_selected (ECategoriesSelector *selector)
+{
+       GtkTreeModel *model;
+       GtkTreeSelection *selection;
+       GList *selected, *item;
+       GString *str = g_string_new ("");
+
+       g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
+
+       model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
+       g_return_val_if_fail (model != NULL, NULL);
+
+       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
+       selected = gtk_tree_selection_get_selected_rows (selection, &model);
+
+       for (item = selected; item != NULL; item = item->next) {
+               GtkTreePath *path = item->data;
+               GtkTreeIter iter;
+               gchar *category;
+
+               gtk_tree_model_get_iter (model, &iter, path);
+               gtk_tree_model_get (model, &iter,
+                       COLUMN_CATEGORY, &category, -1);
+               if (str->len == 0)
+                       g_string_assign (str, category);
+               else
+                       g_string_append_printf (str, ",%s", category);
+
+               g_free (category);
+       }
+
+       g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+       g_list_free (selected);
+
+       return g_string_free (str, FALSE);
+}
diff --git a/libedataserverui/e-categories-selector.h b/libedataserverui/e-categories-selector.h
new file mode 100644 (file)
index 0000000..75dbfa5
--- /dev/null
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef E_CATEGORIES_SELECTOR_H
+#define E_CATEGORIES_SELECTOR_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORIES_SELECTOR \
+       (e_categories_selector_get_type ())
+#define E_CATEGORIES_SELECTOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelector))
+#define E_CATEGORIES_SELECTOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelectorClass))
+#define E_IS_CATEGORIES_SELECTOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_CATEGORIES_SELECTOR))
+#define E_IS_CATEGORIES_SELECTOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_CATEGORIES_SELECTOR))
+#define E_CATEGORIES_SELECTOR_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_CATEGORIES_SELECTOR, ECategoriesSelectorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoriesSelector ECategoriesSelector;
+typedef struct _ECategoriesSelectorClass ECategoriesSelectorClass;
+typedef struct _ECategoriesSelectorPrivate ECategoriesSelectorPrivate;
+
+struct _ECategoriesSelector {
+       GtkTreeView parent;
+       ECategoriesSelectorPrivate *priv;
+};
+
+struct _ECategoriesSelectorClass {
+       GtkTreeViewClass parent_class;
+
+       void            (*category_checked)     (ECategoriesSelector *selector,
+                                                const gchar *category,
+                                                gboolean checked);
+
+       void            (*selection_changed)    (ECategoriesSelector *selector,
+                                                GtkTreeSelection *selection);
+};
+
+GType          e_categories_selector_get_type  (void);
+GtkWidget *    e_categories_selector_new       (void);
+const gchar *  e_categories_selector_get_checked
+                                               (ECategoriesSelector *selector);
+void           e_categories_selector_set_checked
+                                               (ECategoriesSelector *selector,
+                                                const gchar *categories);
+gboolean       e_categories_selector_get_items_checkable
+                                               (ECategoriesSelector *selector);
+void           e_categories_selector_set_items_checkable
+                                               (ECategoriesSelector *selectr,
+                                                gboolean checkable);
+void           e_categories_selector_delete_selection
+                                               (ECategoriesSelector *selector);
+const gchar *  e_categories_selector_get_selected
+                                               (ECategoriesSelector *selector);
+
+G_END_DECLS
+
+#endif /* E_CATEGORIES_SELECTOR_H */
diff --git a/libedataserverui/e-category-editor.c b/libedataserverui/e-category-editor.c
new file mode 100644 (file)
index 0000000..958a578
--- /dev/null
@@ -0,0 +1,348 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+#include "libedataserver/e-categories.h"
+#include "e-category-editor.h"
+
+G_DEFINE_TYPE (ECategoryEditor, e_category_editor, GTK_TYPE_DIALOG)
+
+struct _ECategoryEditorPrivate {
+       GtkWidget *category_name;
+       GtkWidget *category_icon;
+};
+
+static void
+update_preview (GtkFileChooser *chooser,
+                gpointer user_data)
+{
+       GtkImage *image;
+       gchar *filename;
+
+       g_return_if_fail (chooser != NULL);
+
+       image = GTK_IMAGE (gtk_file_chooser_get_preview_widget (chooser));
+       g_return_if_fail (image != NULL);
+
+       filename = gtk_file_chooser_get_preview_filename (chooser);
+
+       gtk_image_set_from_file (image, filename);
+       gtk_file_chooser_set_preview_widget_active (chooser, filename != NULL);
+
+       g_free (filename);
+}
+
+static void
+file_chooser_response (GtkDialog *dialog,
+                       gint response_id,
+                       GtkFileChooser *button)
+{
+       g_return_if_fail (button != NULL);
+
+       if (response_id == GTK_RESPONSE_NO)
+               gtk_file_chooser_unselect_all (button);
+}
+
+static void
+category_editor_category_name_changed (GtkEntry *category_name_entry,
+                                       ECategoryEditor *editor)
+{
+       gchar *name;
+
+       g_return_if_fail (editor != NULL);
+       g_return_if_fail (category_name_entry != NULL);
+
+       name = g_strdup (gtk_entry_get_text (category_name_entry));
+       if (name != NULL)
+               name = g_strstrip (name);
+
+       gtk_dialog_set_response_sensitive (
+               GTK_DIALOG (editor), GTK_RESPONSE_OK, name && *name);
+
+       g_free (name);
+}
+
+static gchar *
+check_category_name (const gchar *name)
+{
+       GString *str = NULL;
+       gchar *p = (gchar *) name;
+
+       str = g_string_new ("");
+       while (*p) {
+               switch (*p) {
+                       case ',':
+                               break;
+                       default:
+                               str = g_string_append_c (str, *p);
+               }
+               p++;
+       }
+
+       p = g_strstrip (g_string_free (str, FALSE));
+
+       return p;
+}
+
+static void
+e_category_editor_class_init (ECategoryEditorClass *class)
+{
+       g_type_class_add_private (class, sizeof (ECategoryEditorPrivate));
+}
+
+static void
+e_category_editor_init (ECategoryEditor *editor)
+{
+       GtkWidget *dialog_content;
+       GtkWidget *dialog_action_area;
+       GtkWidget *table_category_properties;
+       GtkWidget *label_name;
+       GtkWidget *label_icon;
+       GtkWidget *category_name;
+       GtkWidget *chooser_button;
+       GtkWidget *no_image_button;
+       GtkWidget *chooser_dialog;
+       GtkWidget *preview;
+
+       editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+               editor, E_TYPE_CATEGORY_EDITOR, ECategoryEditorPrivate);
+
+       chooser_dialog = gtk_file_chooser_dialog_new (
+               _("Category Icon"),
+               NULL, GTK_FILE_CHOOSER_ACTION_OPEN,
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
+
+       no_image_button = gtk_button_new_with_mnemonic (_("_No Image"));
+       gtk_button_set_image (
+               GTK_BUTTON (no_image_button),
+               gtk_image_new_from_stock (
+               GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON));
+       gtk_dialog_add_action_widget (
+               GTK_DIALOG (chooser_dialog),
+               no_image_button, GTK_RESPONSE_NO);
+       gtk_dialog_add_button (
+               GTK_DIALOG (chooser_dialog),
+               GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT);
+       gtk_file_chooser_set_local_only (
+               GTK_FILE_CHOOSER (chooser_dialog), TRUE);
+       gtk_widget_show (no_image_button);
+
+       g_signal_connect (
+               chooser_dialog, "update-preview",
+               G_CALLBACK (update_preview), NULL);
+
+       preview = gtk_image_new();
+       gtk_file_chooser_set_preview_widget (
+               GTK_FILE_CHOOSER (chooser_dialog), preview);
+       gtk_file_chooser_set_preview_widget_active (
+               GTK_FILE_CHOOSER (chooser_dialog), TRUE);
+       gtk_widget_show_all (preview);
+
+       dialog_content = gtk_dialog_get_content_area (GTK_DIALOG (editor));
+
+       table_category_properties = gtk_table_new (3, 2, FALSE);
+       gtk_box_pack_start (
+               GTK_BOX (dialog_content),
+               table_category_properties, TRUE, TRUE, 0);
+       gtk_container_set_border_width (
+               GTK_CONTAINER (table_category_properties), 12);
+       gtk_table_set_row_spacings (GTK_TABLE (table_category_properties), 6);
+       gtk_table_set_col_spacings (GTK_TABLE (table_category_properties), 6);
+
+       label_name = gtk_label_new_with_mnemonic (_("Category _Name"));
+       gtk_misc_set_alignment (GTK_MISC (label_name), 0, 0.5);
+       gtk_table_attach (
+               GTK_TABLE (table_category_properties),
+               label_name, 0, 1, 0, 1,
+               (GtkAttachOptions) GTK_FILL,
+               (GtkAttachOptions) 0, 0, 0);
+
+       label_icon = gtk_label_new_with_mnemonic (_("Category _Icon"));
+       gtk_misc_set_alignment (GTK_MISC (label_icon), 0, 0.5);
+       gtk_table_attach (
+               GTK_TABLE (table_category_properties),
+               label_icon, 0, 1, 2, 3,
+               (GtkAttachOptions) GTK_FILL,
+               (GtkAttachOptions) 0, 0, 0);
+
+       category_name = gtk_entry_new ();
+       gtk_label_set_mnemonic_widget (GTK_LABEL (label_name), category_name);
+       gtk_table_attach (
+               GTK_TABLE (table_category_properties),
+               category_name, 1, 2, 0, 1,
+               (GtkAttachOptions) GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+               (GtkAttachOptions) 0, 0, 0);
+       editor->priv->category_name = category_name;
+
+       chooser_button = GTK_WIDGET (
+               gtk_file_chooser_button_new_with_dialog (chooser_dialog));
+       gtk_label_set_mnemonic_widget (GTK_LABEL (label_icon), chooser_button);
+       gtk_table_attach (
+               GTK_TABLE (table_category_properties),
+               chooser_button, 1, 2, 2, 3,
+               (GtkAttachOptions) GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+               (GtkAttachOptions) 0, 0, 0);
+       editor->priv->category_icon = chooser_button;
+
+       g_signal_connect (
+               chooser_dialog, "response",
+               G_CALLBACK (file_chooser_response), chooser_button);
+
+       dialog_action_area = gtk_dialog_get_action_area (GTK_DIALOG (editor));
+       gtk_button_box_set_layout (
+               GTK_BUTTON_BOX (dialog_action_area), GTK_BUTTONBOX_END);
+
+       gtk_dialog_add_buttons (
+               GTK_DIALOG (editor),
+               GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+               GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
+       gtk_dialog_set_default_response (GTK_DIALOG (editor), GTK_RESPONSE_OK);
+       gtk_window_set_title (GTK_WINDOW (editor), _("Category Properties"));
+       gtk_window_set_type_hint (
+               GTK_WINDOW (editor), GDK_WINDOW_TYPE_HINT_DIALOG);
+
+       gtk_widget_show_all (dialog_content);
+
+       g_signal_connect (
+               category_name, "changed",
+               G_CALLBACK (category_editor_category_name_changed), editor);
+
+       category_editor_category_name_changed (
+               GTK_ENTRY (category_name), editor);
+}
+
+/**
+ * e_categort_editor_new:
+ *
+ * Creates a new #ECategoryEditor widget.
+ *
+ * Returns: a new #ECategoryEditor
+ *
+ * Since: 3.2
+ **/
+ECategoryEditor *
+e_category_editor_new ()
+{
+       return g_object_new (E_TYPE_CATEGORY_EDITOR, NULL);
+}
+
+/**
+ * e_category_editor_create_category:
+ *
+ * Since: 3.2
+ **/
+const gchar *
+e_category_editor_create_category (ECategoryEditor *editor)
+{
+       GtkEntry *entry;
+       GtkFileChooser *file_chooser;
+
+       g_return_val_if_fail (E_IS_CATEGORY_EDITOR (editor), NULL);
+
+       entry = GTK_ENTRY (editor->priv->category_name);
+       file_chooser = GTK_FILE_CHOOSER (editor->priv->category_icon);
+
+       do {
+               const gchar *category_name;
+               const gchar *correct_category_name;
+
+               if (gtk_dialog_run (GTK_DIALOG (editor)) != GTK_RESPONSE_OK)
+                       return NULL;
+
+               category_name = gtk_entry_get_text (entry);
+               correct_category_name = check_category_name (category_name);
+
+               if (e_categories_exist (correct_category_name)) {
+                       GtkWidget *error_dialog;
+
+                       error_dialog = gtk_message_dialog_new (
+                               GTK_WINDOW (editor),
+                               0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+                               _("There is already a category '%s' in the "
+                                 "configuration. Please use another name"),
+                               category_name);
+
+                       gtk_dialog_run (GTK_DIALOG (error_dialog));
+                       gtk_widget_destroy (error_dialog);
+
+                       /* Now we loop and run the dialog again. */
+
+               } else {
+                       gchar *category_icon;
+
+                       category_icon =
+                               gtk_file_chooser_get_filename (file_chooser);
+                       e_categories_add (
+                               correct_category_name, NULL,
+                               category_icon, TRUE);
+                       g_free (category_icon);
+
+                       return correct_category_name;
+               }
+
+       } while (TRUE);
+}
+
+/**
+ * e_category_editor_edit_category:
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_category_editor_edit_category (ECategoryEditor *editor,
+                                 const gchar *category)
+{
+       GtkFileChooser *file_chooser;
+       const gchar *icon_file;
+
+       g_return_val_if_fail (E_IS_CATEGORY_EDITOR (editor), FALSE);
+       g_return_val_if_fail (category != NULL, FALSE);
+
+       file_chooser = GTK_FILE_CHOOSER (editor->priv->category_icon);
+
+       gtk_entry_set_text (GTK_ENTRY (editor->priv->category_name), category);
+       gtk_widget_set_sensitive (editor->priv->category_name, FALSE);
+
+       icon_file = e_categories_get_icon_file_for (category);
+       if (icon_file) {
+               gtk_file_chooser_set_filename (file_chooser, icon_file);
+               update_preview (file_chooser, NULL);
+       }
+
+       if (gtk_dialog_run (GTK_DIALOG (editor)) == GTK_RESPONSE_OK) {
+               gchar *category_icon;
+
+               category_icon = gtk_file_chooser_get_filename (file_chooser);
+               e_categories_set_icon_file_for (category, category_icon);
+
+               gtk_dialog_set_response_sensitive (
+                       GTK_DIALOG (editor), GTK_RESPONSE_OK, TRUE);
+
+               g_free (category_icon);
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
diff --git a/libedataserverui/e-category-editor.h b/libedataserverui/e-category-editor.h
new file mode 100644 (file)
index 0000000..19c706b
--- /dev/null
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef E_CATEGORY_EDITOR_H
+#define E_CATEGORY_EDITOR_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CATEGORY_EDITOR \
+       (e_category_editor_get_type ())
+#define E_CATEGORY_EDITOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_CATEGORY_EDITOR, ECategoryEditor))
+#define E_CATEGORY_EDITOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_CATEGORY_EDITOR, ECategoryEditorClass))
+#define E_IS_CATEGORY_EDITOR(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_CATEGORY_EDITOR))
+#define E_IS_CATEGORY_EDITOR_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_CATEGORY_EDITOR))
+#define E_CATEGORY_EDITOR_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_CATEGORY_EDITOR, ECategoryEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECategoryEditor ECategoryEditor;
+typedef struct _ECategoryEditorClass ECategoryEditorClass;
+typedef struct _ECategoryEditorPrivate ECategoryEditorPrivate;
+
+struct _ECategoryEditor {
+       GtkDialog parent;
+       ECategoryEditorPrivate *priv;
+};
+
+struct _ECategoryEditorClass {
+       GtkDialogClass parent_class;
+};
+
+GType          e_category_editor_get_type      (void);
+ECategoryEditor *
+               e_category_editor_new           (void);
+const gchar *  e_category_editor_create_category
+                                               (ECategoryEditor *editor);
+gboolean       e_category_editor_edit_category (ECategoryEditor *editor,
+                                                const gchar *category);
+
+G_END_DECLS
+
+#endif /* E_CATEGORY_EDITOR_H */