[evolution-data-server] Bug 332497 - Add Edit -> Available Categories



commit 11044cd0ebb4fff492a207750050111d895d3bce
Author: Dan Vrátil <dvratil redhat com>
Date:   Thu May 5 10:01:43 2011 -0400

    Bug 332497 - Add Edit -> Available Categories
    
    Split ECategoriesDialog into smaller widgets:
    
        ECategoriesEditor
        ECategoriesSelector
        ECategoryEditor

 .../libedataserverui/libedataserverui-docs.sgml    |    3 +
 .../libedataserverui/libedataserverui-sections.txt |   66 ++
 .../libedataserverui/libedataserverui.types        |    6 +
 .../libedataserverui/tmpl/e-categories-editor.sgml |   87 ++
 .../tmpl/e-categories-selector.sgml                |  114 +++
 .../libedataserverui/tmpl/e-category-editor.sgml   |   58 ++
 libedataserverui/Makefile.am                       |    6 +
 libedataserverui/e-categories-dialog.c             |  823 +-------------------
 libedataserverui/e-categories-editor.c             |  427 ++++++++++
 libedataserverui/e-categories-editor.h             |   76 ++
 libedataserverui/e-categories-selector.c           |  570 ++++++++++++++
 libedataserverui/e-categories-selector.h           |   85 ++
 libedataserverui/e-category-editor.c               |  348 +++++++++
 libedataserverui/e-category-editor.h               |   69 ++
 libedataserverui/e-data-server-ui-marshal.list     |    1 +
 15 files changed, 1953 insertions(+), 786 deletions(-)
---
diff --git a/docs/reference/libedataserverui/libedataserverui-docs.sgml b/docs/reference/libedataserverui/libedataserverui-docs.sgml
index 130697f..3242839 100644
--- a/docs/reference/libedataserverui/libedataserverui-docs.sgml
+++ b/docs/reference/libedataserverui/libedataserverui-docs.sgml
@@ -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"/>
diff --git a/docs/reference/libedataserverui/libedataserverui-sections.txt b/docs/reference/libedataserverui/libedataserverui-sections.txt
index 6165102..e703f1c 100644
--- a/docs/reference/libedataserverui/libedataserverui-sections.txt
+++ b/docs/reference/libedataserverui/libedataserverui-sections.txt
@@ -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
diff --git a/docs/reference/libedataserverui/libedataserverui.types b/docs/reference/libedataserverui/libedataserverui.types
index 04eae99..0041710 100644
--- a/docs/reference/libedataserverui/libedataserverui.types
+++ b/docs/reference/libedataserverui/libedataserverui.types
@@ -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
index 0000000..1671364
--- /dev/null
+++ b/docs/reference/libedataserverui/tmpl/e-categories-editor.sgml
@@ -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
index 0000000..f0c1dc2
--- /dev/null
+++ b/docs/reference/libedataserverui/tmpl/e-categories-selector.sgml
@@ -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
index 0000000..8ef7cf3
--- /dev/null
+++ b/docs/reference/libedataserverui/tmpl/e-category-editor.sgml
@@ -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: 
+
+
diff --git a/libedataserverui/Makefile.am b/libedataserverui/Makefile.am
index e20028d..af00b8a 100644
--- a/libedataserverui/Makefile.am
+++ b/libedataserverui/Makefile.am
@@ -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		\
diff --git a/libedataserverui/e-categories-dialog.c b/libedataserverui/e-categories-dialog.c
index 6e3b360..0be5698 100644
--- a/libedataserverui/e-categories-dialog.c
+++ b/libedataserverui/e-categories-dialog.c
@@ -28,756 +28,63 @@
 #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
index 0000000..7a4e208
--- /dev/null
+++ b/libedataserverui/e-categories-editor.c
@@ -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
index 0000000..2789c0d
--- /dev/null
+++ b/libedataserverui/e-categories-editor.h
@@ -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
index 0000000..addd266
--- /dev/null
+++ b/libedataserverui/e-categories-selector.c
@@ -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
index 0000000..75dbfa5
--- /dev/null
+++ b/libedataserverui/e-categories-selector.h
@@ -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
index 0000000..958a578
--- /dev/null
+++ b/libedataserverui/e-category-editor.c
@@ -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
index 0000000..19c706b
--- /dev/null
+++ b/libedataserverui/e-category-editor.h
@@ -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 */
diff --git a/libedataserverui/e-data-server-ui-marshal.list b/libedataserverui/e-data-server-ui-marshal.list
index 333b042..5b7fbe9 100644
--- a/libedataserverui/e-data-server-ui-marshal.list
+++ b/libedataserverui/e-data-server-ui-marshal.list
@@ -1,2 +1,3 @@
 BOOLEAN:OBJECT,BOXED
 BOOLEAN:BOXED,OBJECT,FLAGS,UINT
+NONE:STRING,BOOLEAN



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]