[evolution] Bug #333184 - Add Undo support to component editors



commit 63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c
Author: Milan Crha <mcrha redhat com>
Date:   Fri Jan 10 17:18:49 2014 +0100

    Bug #333184 - Add Undo support to component editors

 addressbook/gui/contact-editor/e-contact-editor.c |  875 ++++++++++++---------
 addressbook/gui/contact-editor/e-contact-editor.h |   50 +--
 calendar/gui/dialogs/comp-editor.c                |   27 +
 calendar/gui/dialogs/event-page.c                 |   11 +
 calendar/gui/dialogs/memo-page.c                  |    9 +
 calendar/gui/dialogs/task-page.c                  |   11 +
 e-util/Makefile.am                                |    2 +
 e-util/e-focus-tracker.c                          |  365 ++++++++-
 e-util/e-focus-tracker.h                          |    8 +
 e-util/e-selectable.c                             |   26 +
 e-util/e-selectable.h                             |    4 +
 e-util/e-util.h                                   |    1 +
 e-util/e-widget-undo.c                            |  891 +++++++++++++++++++++
 e-util/e-widget-undo.h                            |   48 ++
 po/POTFILES.in                                    |    1 +
 15 files changed, 1895 insertions(+), 434 deletions(-)
---
diff --git a/addressbook/gui/contact-editor/e-contact-editor.c 
b/addressbook/gui/contact-editor/e-contact-editor.c
index 58ba976..c67353c 100644
--- a/addressbook/gui/contact-editor/e-contact-editor.c
+++ b/addressbook/gui/contact-editor/e-contact-editor.c
@@ -76,6 +76,7 @@ static void   e_contact_editor_get_property   (GObject *object,
                                                 guint property_id,
                                                 GValue *value,
                                                 GParamSpec *pspec);
+static void    e_contact_editor_constructed    (GObject *object);
 static void    e_contact_editor_dispose        (GObject *object);
 static void    e_contact_editor_raise          (EABEditor *editor);
 static void    e_contact_editor_show           (EABEditor *editor);
@@ -198,8 +199,87 @@ static const gint email_default[] = { 0, 1, 2, 2 };
 #define STRING_IS_EMPTY(x)      (!(x) || !(*(x)))
 #define STRING_MAKE_NON_NULL(x) ((x) ? (x) : "")
 
+struct _EContactEditorPrivate
+{
+       /* item specific fields */
+       EBookClient *source_client;
+       EBookClient *target_client;
+       EContact *contact;
+
+       GtkBuilder *builder;
+       GtkWidget *app;
+
+       GtkWidget *file_selector;
+
+       EContactName *name;
+
+       /* Whether we are editing a new contact or an existing one */
+       guint is_new_contact : 1;
+
+       /* Whether an image is associated with a contact. */
+       guint image_set : 1;
+
+       /* Whether the contact has been changed since bringing up the contact editor */
+       guint changed : 1;
+
+       /* Wheter should check for contact to merge. Only when name or email are changed */
+       guint check_merge : 1;
+
+       /* Whether the contact editor will accept modifications, save */
+       guint target_editable : 1;
+
+       /* Whether an async wombat call is in progress */
+       guint in_async_call : 1;
+
+       /* Whether an image is changed */
+       guint image_changed : 1;
+
+       /* Whether to try to reduce space used */
+       guint compress_ui : 1;
+
+       GSList *writable_fields;
+
+       GSList *required_fields;
+
+       GCancellable *cancellable;
+
+       /* signal ids for "writable_status" */
+       gint target_editable_id;
+
+       GtkWidget *fullname_dialog;
+       GtkWidget *categories_dialog;
+
+       GtkUIManager *ui_manager;
+       EFocusTracker *focus_tracker;
+};
+
 G_DEFINE_TYPE (EContactEditor, e_contact_editor, EAB_TYPE_EDITOR)
 
+static GtkActionEntry undo_entries[] = {
+
+       { "undo-menu",
+         "Undo menu",
+         NULL,
+         NULL,
+         NULL,
+         NULL }, /* just a fake undo menu, to get shortcuts working */
+
+       { "undo",
+         GTK_STOCK_UNDO,
+         NULL,
+         "<Control>z",
+         N_("Undo"),
+         NULL }, /* Handled by EFocusTracker */
+
+       { "redo",
+         GTK_STOCK_REDO,
+         NULL,
+         "<Control>y",
+         N_("Redo"),
+         NULL } /* Handled by EFocusTracker */
+
+};
+
 static void
 connect_closure_free (ConnectClosure *connect_closure)
 {
@@ -284,10 +364,13 @@ e_contact_editor_class_init (EContactEditorClass *class)
        GObjectClass *object_class = G_OBJECT_CLASS (class);
        EABEditorClass *editor_class = EAB_EDITOR_CLASS (class);
 
+       g_type_class_add_private (class, sizeof (EContactEditorPrivate));
+
        parent_class = g_type_class_ref (EAB_TYPE_EDITOR);
 
        object_class->set_property = e_contact_editor_set_property;
        object_class->get_property = e_contact_editor_get_property;
+       object_class->constructed = e_contact_editor_constructed;
        object_class->dispose = e_contact_editor_dispose;
 
        editor_class->raise = e_contact_editor_raise;
@@ -395,7 +478,7 @@ is_field_supported (EContactEditor *editor,
        GSList      *fields, *iter;
        const gchar *field;
 
-       fields = editor->writable_fields;
+       fields = editor->priv->writable_fields;
        if (!fields)
                return FALSE;
 
@@ -535,12 +618,12 @@ file_as_get_style (EContactEditor *editor)
 {
        GtkEntry *file_as = GTK_ENTRY (
                gtk_bin_get_child (GTK_BIN (
-               e_builder_get_widget (editor->builder, "combo-file-as"))));
+               e_builder_get_widget (editor->priv->builder, "combo-file-as"))));
        GtkEntry *company_w = GTK_ENTRY (
-               e_builder_get_widget (editor->builder, "entry-company"));
+               e_builder_get_widget (editor->priv->builder, "entry-company"));
        gchar *filestring;
        gchar *trystring;
-       EContactName *name = editor->name;
+       EContactName *name = editor->priv->name;
        const gchar *company;
        gint i;
 
@@ -571,9 +654,9 @@ file_as_set_style (EContactEditor *editor,
        gint i;
        GList *strings = NULL;
        GtkComboBox *combo_file_as = GTK_COMBO_BOX (
-               e_builder_get_widget (editor->builder, "combo-file-as"));
+               e_builder_get_widget (editor->priv->builder, "combo-file-as"));
        GtkEntry *company_w = GTK_ENTRY (
-               e_builder_get_widget (editor->builder, "entry-company"));
+               e_builder_get_widget (editor->priv->builder, "entry-company"));
        const gchar *company;
 
        if (!(combo_file_as && GTK_IS_COMBO_BOX (combo_file_as)))
@@ -592,9 +675,9 @@ file_as_set_style (EContactEditor *editor,
        }
 
        for (i = 0; i < 6; i++) {
-               if (style_makes_sense (editor->name, company, i)) {
+               if (style_makes_sense (editor->priv->name, company, i)) {
                        gchar *u;
-                       u = name_to_style (editor->name, company, i);
+                       u = name_to_style (editor->priv->name, company, i);
                        if (!STRING_IS_EMPTY (u))
                                strings = g_list_append (strings, u);
                        else
@@ -622,7 +705,7 @@ file_as_set_style (EContactEditor *editor,
        g_list_free (strings);
 
        if (style != -1) {
-               string = name_to_style (editor->name, company, style);
+               string = name_to_style (editor->priv->name, company, style);
                set_entry_text (
                        editor, GTK_ENTRY (gtk_bin_get_child (
                        GTK_BIN (combo_file_as))), string);
@@ -638,17 +721,17 @@ name_entry_changed (GtkWidget *widget,
        const gchar *string;
 
        style = file_as_get_style (editor);
-       e_contact_name_free (editor->name);
+       e_contact_name_free (editor->priv->name);
        string = gtk_entry_get_text (GTK_ENTRY (widget));
-       editor->name = e_contact_name_from_string (string);
+       editor->priv->name = e_contact_name_from_string (string);
        file_as_set_style (editor, style);
 
-       editor->check_merge = TRUE;
+       editor->priv->check_merge = TRUE;
 
        sensitize_ok (editor);
        if (string && !*string)
                gtk_window_set_title (
-                       GTK_WINDOW (editor->app), _("Contact Editor"));
+                       GTK_WINDOW (editor->priv->app), _("Contact Editor"));
 }
 
 static void
@@ -665,12 +748,12 @@ file_as_combo_changed (GtkWidget *widget,
        if (string && *string) {
                gchar *title;
                title = g_strdup_printf (_("Contact Editor - %s"), string);
-               gtk_window_set_title (GTK_WINDOW (editor->app), title);
+               gtk_window_set_title (GTK_WINDOW (editor->priv->app), title);
                g_free (title);
        }
        else {
                gtk_window_set_title (
-                       GTK_WINDOW (editor->app), _("Contact Editor"));
+                       GTK_WINDOW (editor->priv->app), _("Contact Editor"));
        }
        sensitize_ok (editor);
 
@@ -698,15 +781,15 @@ fill_in_source_field (EContactEditor *editor)
 {
        GtkWidget *source_menu;
 
-       if (!editor->target_client)
+       if (!editor->priv->target_client)
                return;
 
        source_menu = e_builder_get_widget (
-               editor->builder, "client-combo-box");
+               editor->priv->builder, "client-combo-box");
 
        e_source_combo_box_set_active (
                E_SOURCE_COMBO_BOX (source_menu),
-               e_client_get_source (E_CLIENT (editor->target_client)));
+               e_client_get_source (E_CLIENT (editor->priv->target_client)));
 }
 
 static void
@@ -715,12 +798,12 @@ sensitize_ok (EContactEditor *ce)
        GtkWidget *widget;
        gboolean   allow_save;
        GtkWidget *entry_fullname =
-               e_builder_get_widget (ce->builder, "entry-fullname");
+               e_builder_get_widget (ce->priv->builder, "entry-fullname");
        GtkWidget *entry_file_as =
                gtk_bin_get_child (GTK_BIN (
-               e_builder_get_widget (ce->builder, "combo-file-as")));
+               e_builder_get_widget (ce->priv->builder, "combo-file-as")));
        GtkWidget *company_name =
-               e_builder_get_widget (ce->builder, "entry-company");
+               e_builder_get_widget (ce->priv->builder, "entry-company");
        const gchar *name_entry_string =
                gtk_entry_get_text (GTK_ENTRY (entry_fullname));
        const gchar *file_as_entry_string =
@@ -728,7 +811,7 @@ sensitize_ok (EContactEditor *ce)
        const gchar *company_name_string =
                gtk_entry_get_text (GTK_ENTRY (company_name));
 
-       allow_save = ce->target_editable && ce->changed;
+       allow_save = ce->priv->target_editable && ce->priv->changed;
 
        if (!strcmp (name_entry_string, "") ||
            !strcmp (file_as_entry_string, "")) {
@@ -738,7 +821,7 @@ sensitize_ok (EContactEditor *ce)
                else
                        allow_save = FALSE;
        }
-       widget = e_builder_get_widget (ce->builder, "button-ok");
+       widget = e_builder_get_widget (ce->priv->builder, "button-ok");
        gtk_widget_set_sensitive (widget, allow_save);
 }
 
@@ -746,12 +829,12 @@ static void
 object_changed (GObject *object,
                 EContactEditor *editor)
 {
-       if (!editor->target_editable) {
+       if (!editor->priv->target_editable) {
                g_warning ("non-editable contact editor has an editable field in it.");
                return;
        }
 
-       if (!editor->check_merge && GTK_IS_WIDGET (object)) {
+       if (!editor->priv->check_merge && GTK_IS_WIDGET (object)) {
                const gchar *widget_name;
 
                widget_name = gtk_widget_get_name (GTK_WIDGET (object));
@@ -761,11 +844,11 @@ object_changed (GObject *object,
                     (g_str_equal (widget_name, "nickname")) ||
                     (g_str_equal (widget_name, "file-as")) ||
                     (g_str_has_prefix (widget_name, "email-"))))
-                       editor->check_merge = TRUE;
+                       editor->priv->check_merge = TRUE;
        }
 
-       if (!editor->changed) {
-               editor->changed = TRUE;
+       if (!editor->priv->changed) {
+               editor->priv->changed = TRUE;
                sensitize_ok (editor);
        }
 }
@@ -774,8 +857,8 @@ static void
 image_chooser_changed (GtkWidget *widget,
                        EContactEditor *editor)
 {
-       editor->image_set = TRUE;
-       editor->image_changed = TRUE;
+       editor->priv->image_set = TRUE;
+       editor->priv->image_changed = TRUE;
 }
 
 static void
@@ -821,12 +904,12 @@ init_email_record_location (EContactEditor *editor,
        GtkListStore *store;
 
        widget_name = g_strdup_printf ("entry-email-%d", record);
-       email_entry = e_builder_get_widget (editor->builder, widget_name);
+       email_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("combobox-email-%d", record);
        location_combo_box = GTK_COMBO_BOX (
-               e_builder_get_widget (editor->builder, widget_name));
+               e_builder_get_widget (editor->priv->builder, widget_name));
        g_free (widget_name);
 
        store = GTK_LIST_STORE (gtk_combo_box_get_model (location_combo_box));
@@ -865,11 +948,11 @@ fill_in_email_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-email-%d", record);
-       location_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       location_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("entry-email-%d", record);
-       email_entry = e_builder_get_widget (editor->builder, widget_name);
+       email_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        set_combo_box_active (
@@ -890,11 +973,11 @@ extract_email_record (EContactEditor *editor,
        const gchar *text;
 
        widget_name = g_strdup_printf ("combobox-email-%d", record);
-       location_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       location_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("entry-email-%d", record);
-       email_entry = e_builder_get_widget (editor->builder, widget_name);
+       email_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        text = gtk_entry_get_text (GTK_ENTRY (email_entry));
@@ -1041,7 +1124,7 @@ alloc_ui_slot (EContactEditor *editor,
 
        if (preferred_slot >= 1) {
                widget_name = g_strdup_printf ("%s-%d", widget_base, preferred_slot);
-               widget = e_builder_get_widget (editor->builder, widget_name);
+               widget = e_builder_get_widget (editor->priv->builder, widget_name);
                entry_contents = gtk_entry_get_text (GTK_ENTRY (widget));
                g_free (widget_name);
 
@@ -1053,7 +1136,7 @@ alloc_ui_slot (EContactEditor *editor,
 
        for (i = 1; i <= num_slots; i++) {
                widget_name = g_strdup_printf ("%s-%d", widget_base, i);
-               widget = e_builder_get_widget (editor->builder, widget_name);
+               widget = e_builder_get_widget (editor->priv->builder, widget_name);
                entry_contents = gtk_entry_get_text (GTK_ENTRY (widget));
                g_free (widget_name);
 
@@ -1094,7 +1177,7 @@ fill_in_email (EContactEditor *editor)
        /* Fill in */
 
        email_attr_list = e_contact_get_attributes (
-               editor->contact, E_CONTACT_EMAIL);
+               editor->priv->contact, E_CONTACT_EMAIL);
 
        for (record_n = 1, l = email_attr_list;
                l && record_n <= EMAIL_SLOTS; l = g_list_next (l)) {
@@ -1157,7 +1240,7 @@ extract_email (EContactEditor *editor)
 
        /* Splice in the old attributes, minus the EMAIL_SLOTS first */
 
-       old_attr_list = e_contact_get_attributes (editor->contact, E_CONTACT_EMAIL);
+       old_attr_list = e_contact_get_attributes (editor->priv->contact, E_CONTACT_EMAIL);
        for (ll = old_attr_list, i = 1; ll && i <= EMAIL_SLOTS; i++) {
                e_vcard_attribute_free (ll->data);
                ll = g_list_delete_link (ll, ll);
@@ -1166,7 +1249,7 @@ extract_email (EContactEditor *editor)
        old_attr_list = ll;
        attr_list = g_list_concat (attr_list, old_attr_list);
 
-       e_contact_set_attributes (editor->contact, E_CONTACT_EMAIL, attr_list);
+       e_contact_set_attributes (editor->priv->contact, E_CONTACT_EMAIL, attr_list);
 
        free_attr_list (attr_list);
 }
@@ -1181,11 +1264,11 @@ sensitize_email_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-email-%d", record);
-       location_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       location_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("entry-email-%d", record);
-       email_entry = e_builder_get_widget (editor->builder, widget_name);
+       email_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        gtk_widget_set_sensitive (location_combo_box, enabled);
@@ -1200,7 +1283,7 @@ sensitize_email (EContactEditor *editor)
        for (i = 1; i <= EMAIL_SLOTS; i++) {
                gboolean enabled = TRUE;
 
-               if (!editor->target_editable)
+               if (!editor->priv->target_editable)
                        enabled = FALSE;
 
                if (E_CONTACT_FIRST_EMAIL_ID + i - 1 <= E_CONTACT_LAST_EMAIL_ID &&
@@ -1286,7 +1369,7 @@ set_arrow_image (EContactEditor *editor,
 {
        GtkWidget *arrow;
 
-       arrow  = e_builder_get_widget (editor->builder, arrow_widget);
+       arrow  = e_builder_get_widget (editor->priv->builder, arrow_widget);
        if (expanded)
                gtk_arrow_set (
                        GTK_ARROW (arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
@@ -1303,7 +1386,7 @@ expand_widget_list (EContactEditor *editor,
        gint i;
        for (i = 0; widget_names[i]; i++)
                gtk_widget_set_visible (
-                       e_builder_get_widget (editor->builder, widget_names[i]),
+                       e_builder_get_widget (editor->priv->builder, widget_names[i]),
                        expanded);
 }
 
@@ -1349,8 +1432,8 @@ expand_mail (EContactEditor *editor,
        expand_widget_list (editor, names, expanded);
 
        /* move 'use html mail' into position */
-       check = e_builder_get_widget (editor->builder, "checkbutton-htmlmail");
-       table = GTK_TABLE (e_builder_get_widget (editor->builder, "email-table"));
+       check = e_builder_get_widget (editor->priv->builder, "checkbutton-htmlmail");
+       table = GTK_TABLE (e_builder_get_widget (editor->priv->builder, "email-table"));
        if (check != NULL && table != NULL) {
                GtkWidget *parent;
 
@@ -1373,7 +1456,7 @@ init_email (EContactEditor *editor)
        for (i = 1; i <= EMAIL_SLOTS; i++)
                init_email_record_location (editor, i);
 
-       expand_mail (editor, !editor->compress_ui);
+       expand_mail (editor, !editor->priv->compress_ui);
 }
 
 static void
@@ -1387,11 +1470,11 @@ fill_in_phone_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-phone-%d", record);
-       phone_type_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       phone_type_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("entry-phone-%d", record);
-       phone_entry = e_builder_get_widget (editor->builder, widget_name);
+       phone_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        set_combo_box_active (
@@ -1414,11 +1497,11 @@ extract_phone_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-phone-%d", record);
-       phone_type_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       phone_type_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("entry-phone-%d", record);
-       phone_entry = e_builder_get_widget (editor->builder, widget_name);
+       phone_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        *phone      = g_strdup (gtk_entry_get_text (GTK_ENTRY (phone_entry)));
@@ -1440,7 +1523,7 @@ fill_in_phone (EContactEditor *editor)
 
        /* Fill in */
 
-       phone_attr_list = get_attributes_named (E_VCARD (editor->contact), "TEL");
+       phone_attr_list = get_attributes_named (E_VCARD (editor->priv->contact), "TEL");
 
        for (record_n = 1, l = phone_attr_list;
                l && record_n <= PHONE_SLOTS; l = g_list_next (l)) {
@@ -1507,7 +1590,7 @@ extract_phone (EContactEditor *editor)
 
        /* Splice in the old attributes, minus the PHONE_SLOTS first */
 
-       old_attr_list = get_attributes_named (E_VCARD (editor->contact), "TEL");
+       old_attr_list = get_attributes_named (E_VCARD (editor->priv->contact), "TEL");
        for (ll = old_attr_list, i = 1; ll && i <= PHONE_SLOTS; i++) {
                e_vcard_attribute_free (ll->data);
                ll = g_list_delete_link (ll, ll);
@@ -1516,7 +1599,7 @@ extract_phone (EContactEditor *editor)
        old_attr_list = ll;
        attr_list = g_list_concat (attr_list, old_attr_list);
 
-       set_attributes_named (E_VCARD (editor->contact), "TEL", attr_list);
+       set_attributes_named (E_VCARD (editor->priv->contact), "TEL", attr_list);
 
        free_attr_list (attr_list);
 }
@@ -1532,11 +1615,11 @@ init_phone_record_type (EContactEditor *editor,
        GtkListStore *store;
 
        widget_name = g_strdup_printf ("entry-phone-%d", record);
-       phone_entry = e_builder_get_widget (editor->builder, widget_name);
+       phone_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("combobox-phone-%d", record);
-       phone_type_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       phone_type_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        init_item_sensitiveable_combo_box (GTK_COMBO_BOX (phone_type_combo_box));
@@ -1618,11 +1701,11 @@ sensitize_phone_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-phone-%d", record);
-       phone_type_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       phone_type_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("entry-phone-%d", record);
-       phone_entry = e_builder_get_widget (editor->builder, widget_name);
+       phone_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        gtk_widget_set_sensitive (phone_type_combo_box, enabled);
@@ -1639,7 +1722,7 @@ sensitize_phone (EContactEditor *editor)
        for (i = 1; i <= PHONE_SLOTS; i++) {
                gboolean enabled = TRUE;
 
-               if (!editor->target_editable)
+               if (!editor->priv->target_editable)
                        enabled = FALSE;
 
                sensitize_phone_record (editor, i, enabled);
@@ -1658,7 +1741,7 @@ init_im_record_location (EContactEditor *editor,
        gchar *widget_name;
 
        widget_name = g_strdup_printf ("combobox-im-location-%d", record);
-       location_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       location_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        init_item_sensitiveable_combo_box (GTK_COMBO_BOX (location_combo_box));
@@ -1695,14 +1778,14 @@ init_im_record_service (EContactEditor *editor,
        gint       i;
 
        widget_name = g_strdup_printf ("entry-im-name-%d", record);
-       name_entry = e_builder_get_widget (editor->builder, widget_name);
+       name_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("combobox-im-service-%d", record);
-       service_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       service_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
-       if (editor->compress_ui && record > 2) {
+       if (editor->priv->compress_ui && record > 2) {
                gtk_widget_hide (name_entry);
                gtk_widget_hide (service_combo_box);
        }
@@ -1764,17 +1847,17 @@ fill_in_im_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-im-service-%d", record);
-       service_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       service_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
 #ifdef ENABLE_IM_LOCATION
        widget_name = g_strdup_printf ("combobox-im-location-%d", record);
-       location_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       location_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 #endif
 
        widget_name = g_strdup_printf ("entry-im-name-%d", record);
-       name_entry = e_builder_get_widget (editor->builder, widget_name);
+       name_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
 #ifdef ENABLE_IM_LOCATION
@@ -1805,7 +1888,7 @@ fill_in_im (EContactEditor *editor)
        /* Fill in */
 
        for (record_n = 1, i = 0; i < G_N_ELEMENTS (im_service); i++) {
-               im_attr_list = e_contact_get_attributes (editor->contact, im_service[i].field);
+               im_attr_list = e_contact_get_attributes (editor->priv->contact, im_service[i].field);
 
                for (l = im_attr_list; l && record_n <= IM_SLOTS; l = g_list_next (l)) {
                        EVCardAttribute *attr = l->data;
@@ -1847,17 +1930,17 @@ extract_im_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-im-service-%d", record);
-       service_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       service_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
 #ifdef ENABLE_IM_LOCATION
        widget_name = g_strdup_printf ("combobox-im-location-%d", record);
-       location_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       location_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 #endif
 
        widget_name = g_strdup_printf ("entry-im-name-%d", record);
-       name_entry = e_builder_get_widget (editor->builder, widget_name);
+       name_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        *name  = g_strdup (gtk_entry_get_text (GTK_ENTRY (name_entry)));
@@ -1916,7 +1999,7 @@ extract_im (EContactEditor *editor)
                /* Splice in the old attributes, minus the filled_in_slots first */
 
                old_service_attr_list = e_contact_get_attributes (
-                       editor->contact, im_service[i].field);
+                       editor->priv->contact, im_service[i].field);
                filled_in_slots = MIN (
                        remaining_slots,
                        g_list_length (old_service_attr_list));
@@ -1934,7 +2017,7 @@ extract_im (EContactEditor *editor)
                        service_attr_list[i], old_service_attr_list);
 
                e_contact_set_attributes (
-                       editor->contact,
+                       editor->priv->contact,
                        im_service[i].field,
                        service_attr_list[i]);
 
@@ -1984,17 +2067,17 @@ sensitize_im_record (EContactEditor *editor,
        gchar     *widget_name;
 
        widget_name = g_strdup_printf ("combobox-im-service-%d", record);
-       service_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       service_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
 #ifdef ENABLE_IM_LOCATION
        widget_name = g_strdup_printf ("combobox-im-location-%d", record);
-       location_combo_box = e_builder_get_widget (editor->builder, widget_name);
+       location_combo_box = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 #endif
 
        widget_name = g_strdup_printf ("entry-im-name-%d", record);
-       name_entry = e_builder_get_widget (editor->builder, widget_name);
+       name_entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        gtk_widget_set_sensitive (service_combo_box, enabled);
@@ -2012,7 +2095,7 @@ sensitize_im (EContactEditor *editor)
        gboolean enabled;
        gboolean no_ims_supported;
 
-       enabled = editor->target_editable;
+       enabled = editor->priv->target_editable;
        no_ims_supported = TRUE;
 
        for (i = 0; i < G_N_ELEMENTS (im_service); i++)
@@ -2034,10 +2117,10 @@ init_personal (EContactEditor *editor)
 {
        gtk_expander_set_expanded (
                GTK_EXPANDER (e_builder_get_widget (
-                       editor->builder, "expander-personal-misc")),
-               !editor->compress_ui);
+                       editor->priv->builder, "expander-personal-misc")),
+               !editor->priv->compress_ui);
 
-       expand_web (editor, !editor->compress_ui);
+       expand_web (editor, !editor->priv->compress_ui);
 }
 
 static void
@@ -2050,7 +2133,7 @@ init_address_textview (EContactEditor *editor,
 
        textview_name = g_strdup_printf (
                "textview-%s-address", address_name[record]);
-       textview = e_builder_get_widget (editor->builder, textview_name);
+       textview = e_builder_get_widget (editor->priv->builder, textview_name);
        g_free (textview_name);
 
        text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
@@ -2070,7 +2153,7 @@ init_address_field (EContactEditor *editor,
 
        entry_name = g_strdup_printf (
                "entry-%s-%s", address_name[record], widget_field_name);
-       entry = e_builder_get_widget (editor->builder, entry_name);
+       entry = e_builder_get_widget (editor->priv->builder, entry_name);
        g_free (entry_name);
 
        g_signal_connect (
@@ -2103,8 +2186,8 @@ init_address (EContactEditor *editor)
 
        gtk_expander_set_expanded (
                GTK_EXPANDER (e_builder_get_widget (
-                       editor->builder, "expander-address-other")),
-               !editor->compress_ui);
+                       editor->priv->builder, "expander-address-other")),
+               !editor->priv->compress_ui);
 }
 
 static void
@@ -2118,7 +2201,7 @@ fill_in_address_textview (EContactEditor *editor,
        GtkTextIter    iter_end, iter_start;
 
        textview_name = g_strdup_printf ("textview-%s-address", address_name[record]);
-       textview = e_builder_get_widget (editor->builder, textview_name);
+       textview = e_builder_get_widget (editor->priv->builder, textview_name);
        g_free (textview_name);
 
        text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
@@ -2146,7 +2229,7 @@ fill_in_address_label_textview (EContactEditor *editor,
 
        textview_name = g_strdup_printf (
                "textview-%s-address", address_name[record]);
-       textview = e_builder_get_widget (editor->builder, textview_name);
+       textview = e_builder_get_widget (editor->priv->builder, textview_name);
        g_free (textview_name);
 
        text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
@@ -2164,7 +2247,7 @@ fill_in_address_field (EContactEditor *editor,
 
        entry_name = g_strdup_printf (
                "entry-%s-%s", address_name[record], widget_field_name);
-       entry = e_builder_get_widget (editor->builder, entry_name);
+       entry = e_builder_get_widget (editor->priv->builder, entry_name);
        g_free (entry_name);
 
        set_entry_text (editor, GTK_ENTRY (entry), string);
@@ -2177,8 +2260,8 @@ fill_in_address_record (EContactEditor *editor,
        EContactAddress *address;
        gchar           *address_label;
 
-       address = e_contact_get (editor->contact, addresses[record]);
-       address_label = e_contact_get (editor->contact, address_labels[record]);
+       address = e_contact_get (editor->priv->contact, addresses[record]);
+       address_label = e_contact_get (editor->priv->contact, address_labels[record]);
 
        if (address &&
            (!STRING_IS_EMPTY (address->street)   ||
@@ -2223,7 +2306,7 @@ extract_address_textview (EContactEditor *editor,
        GtkTextIter    iter_1, iter_2;
 
        textview_name = g_strdup_printf ("textview-%s-address", address_name[record]);
-       textview = e_builder_get_widget (editor->builder, textview_name);
+       textview = e_builder_get_widget (editor->priv->builder, textview_name);
        g_free (textview_name);
 
        text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
@@ -2265,7 +2348,7 @@ extract_address_field (EContactEditor *editor,
 
        entry_name = g_strdup_printf (
                "entry-%s-%s", address_name[record], widget_field_name);
-       entry = e_builder_get_widget (editor->builder, entry_name);
+       entry = e_builder_get_widget (editor->priv->builder, entry_name);
        g_free (entry_name);
 
        return g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
@@ -2361,12 +2444,12 @@ extract_address_record (EContactEditor *editor,
            !STRING_IS_EMPTY (address->code)     ||
            !STRING_IS_EMPTY (address->po)       ||
            !STRING_IS_EMPTY (address->country)) {
-               e_contact_set (editor->contact, addresses[record], address);
-               set_address_label (editor->contact, address_labels[record], address);
+               e_contact_set (editor->priv->contact, addresses[record], address);
+               set_address_label (editor->priv->contact, address_labels[record], address);
        }
        else {
-               e_contact_set (editor->contact, addresses[record], NULL);
-               set_address_label (editor->contact, address_labels[record], NULL);
+               e_contact_set (editor->priv->contact, addresses[record], NULL);
+               set_address_label (editor->priv->contact, address_labels[record], NULL);
        }
 
        g_boxed_free (e_contact_address_get_type (), address);
@@ -2391,11 +2474,11 @@ sensitize_address_textview (EContactEditor *editor,
        GtkWidget     *label;
 
        widget_name = g_strdup_printf ("textview-%s-address", address_name[record]);
-       textview = e_builder_get_widget (editor->builder, widget_name);
+       textview = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf ("label-%s-address", address_name[record]);
-       label = e_builder_get_widget (editor->builder, widget_name);
+       label = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), enabled);
@@ -2414,12 +2497,12 @@ sensitize_address_field (EContactEditor *editor,
 
        widget_name = g_strdup_printf (
                "entry-%s-%s", address_name[record], widget_field_name);
-       entry = e_builder_get_widget (editor->builder, widget_name);
+       entry = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        widget_name = g_strdup_printf (
                "label-%s-%s", address_name[record], widget_field_name);
-       label = e_builder_get_widget (editor->builder, widget_name);
+       label = e_builder_get_widget (editor->priv->builder, widget_name);
        g_free (widget_name);
 
        gtk_editable_set_editable (GTK_EDITABLE (entry), enabled);
@@ -2447,7 +2530,7 @@ sensitize_address (EContactEditor *editor)
        for (i = 0; i < ADDRESS_SLOTS; i++) {
                gboolean enabled = TRUE;
 
-               if (!editor->target_editable ||
+               if (!editor->priv->target_editable ||
                    !(is_field_supported (editor, addresses[i]) ||
                      is_field_supported (editor, address_labels[i])))
                        enabled = FALSE;
@@ -2593,7 +2676,7 @@ fill_in_simple_field (EContactEditor *editor,
 {
        EContact *contact;
 
-       contact = editor->contact;
+       contact = editor->priv->contact;
 
        g_signal_handlers_block_matched (
                widget, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, editor);
@@ -2635,36 +2718,36 @@ fill_in_simple_field (EContactEditor *editor,
 
        } else if (E_IS_IMAGE_CHOOSER (widget)) {
                EContactPhoto *photo = e_contact_get (contact, field_id);
-               editor->image_set = FALSE;
+               editor->priv->image_set = FALSE;
                if (photo && photo->type == E_CONTACT_PHOTO_TYPE_INLINED) {
                        e_image_chooser_set_image_data (
                                E_IMAGE_CHOOSER (widget),
                                (gchar *) photo->data.inlined.data,
                                photo->data.inlined.length);
-                       editor->image_set = TRUE;
+                       editor->priv->image_set = TRUE;
                } else if (photo && photo->type == E_CONTACT_PHOTO_TYPE_URI) {
                        gchar *file_name = g_filename_from_uri (photo->data.uri, NULL, NULL);
                        if (file_name) {
                                e_image_chooser_set_from_file (
                                        E_IMAGE_CHOOSER (widget),
                                        file_name);
-                               editor->image_set = TRUE;
+                               editor->priv->image_set = TRUE;
                                g_free (file_name);
                        }
                }
 
-               if (!editor->image_set) {
+               if (!editor->priv->image_set) {
                        gchar *file_name;
 
                        file_name = e_icon_factory_get_icon_filename (
                                "avatar-default", GTK_ICON_SIZE_DIALOG);
                        e_image_chooser_set_from_file (
                                E_IMAGE_CHOOSER (widget), file_name);
-                       editor->image_set = FALSE;
+                       editor->priv->image_set = FALSE;
                        g_free (file_name);
                }
 
-               editor->image_changed = FALSE;
+               editor->priv->image_changed = FALSE;
                e_contact_photo_free (photo);
 
        } else if (GTK_IS_TOGGLE_BUTTON (widget)) {
@@ -2687,7 +2770,7 @@ extract_simple_field (EContactEditor *editor,
 {
        EContact *contact;
 
-       contact = editor->contact;
+       contact = editor->priv->contact;
 
        if (GTK_IS_ENTRY (widget)) {
                const gchar *text = gtk_entry_get_text (GTK_ENTRY (widget));
@@ -2765,9 +2848,9 @@ extract_simple_field (EContactEditor *editor,
                EContactPhoto photo;
                photo.type = E_CONTACT_PHOTO_TYPE_INLINED;
                photo.data.inlined.mime_type = NULL;
-               if (editor->image_changed) {
+               if (editor->priv->image_changed) {
                        gchar *img_buff = NULL;
-                       if (editor->image_set &&
+                       if (editor->priv->image_set &&
                            e_image_chooser_get_image_data (
                                        E_IMAGE_CHOOSER (widget),
                                        &img_buff, &photo.data.inlined.length)) {
@@ -2794,7 +2877,7 @@ extract_simple_field (EContactEditor *editor,
 
                                                prompt_response =
                                                        e_alert_run_dialog_for_args
-                                                       (GTK_WINDOW (editor->app),
+                                                       (GTK_WINDOW (editor->priv->app),
                                                         "addressbook:prompt-resize",
                                                         NULL);
 
@@ -2831,7 +2914,7 @@ extract_simple_field (EContactEditor *editor,
                                        }
                                        g_object_unref (pixbuf);
                                }
-                               editor->image_changed = FALSE;
+                               editor->priv->image_changed = FALSE;
                                g_object_unref (loader);
 
                                e_contact_set (contact, field_id, &photo);
@@ -2839,7 +2922,7 @@ extract_simple_field (EContactEditor *editor,
                                g_free (photo.data.inlined.data);
 
                        } else {
-                               editor->image_changed = FALSE;
+                               editor->priv->image_changed = FALSE;
                                e_contact_set (contact, E_CONTACT_PHOTO, NULL);
                        }
                }
@@ -2877,7 +2960,7 @@ init_simple (EContactEditor *editor)
 
        for (i = 0; i < G_N_ELEMENTS (simple_field_map); i++) {
                widget = e_builder_get_widget (
-                       editor->builder, simple_field_map[i].widget_name);
+                       editor->priv->builder, simple_field_map[i].widget_name);
                if (!widget)
                        continue;
 
@@ -2888,18 +2971,18 @@ init_simple (EContactEditor *editor)
 
        /* Update file_as */
 
-       widget = e_builder_get_widget (editor->builder, "entry-fullname");
+       widget = e_builder_get_widget (editor->priv->builder, "entry-fullname");
        g_signal_connect (
                widget, "changed",
                G_CALLBACK (name_entry_changed), editor);
 
-       widget = e_builder_get_widget (editor->builder, "combo-file-as");
+       widget = e_builder_get_widget (editor->priv->builder, "combo-file-as");
        gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (widget), 0);
        g_signal_connect (
                widget, "changed",
                G_CALLBACK (file_as_combo_changed), editor);
 
-       widget = e_builder_get_widget (editor->builder, "entry-company");
+       widget = e_builder_get_widget (editor->priv->builder, "entry-company");
        g_signal_connect (
                widget, "changed",
                G_CALLBACK (company_entry_changed), editor);
@@ -2920,7 +3003,7 @@ fill_in_simple (EContactEditor *editor)
                        continue;
 
                widget = e_builder_get_widget (
-                       editor->builder, simple_field_map[i].widget_name);
+                       editor->priv->builder, simple_field_map[i].widget_name);
                if (!widget)
                        continue;
 
@@ -2932,26 +3015,26 @@ fill_in_simple (EContactEditor *editor)
 
        /* Update broken-up name */
 
-       g_object_get (editor->contact, "name", &name, NULL);
+       g_object_get (editor->priv->contact, "name", &name, NULL);
 
-       if (editor->name)
-               e_contact_name_free (editor->name);
+       if (editor->priv->name)
+               e_contact_name_free (editor->priv->name);
 
-       editor->name = name;
+       editor->priv->name = name;
 
        /* Update the contact editor title */
 
-       filename = (gchar *) e_contact_get (editor->contact, E_CONTACT_FILE_AS);
+       filename = (gchar *) e_contact_get (editor->priv->contact, E_CONTACT_FILE_AS);
 
        if (filename) {
                gchar *title;
                title = g_strdup_printf (_("Contact Editor - %s"), filename);
-               gtk_window_set_title (GTK_WINDOW (editor->app), title);
+               gtk_window_set_title (GTK_WINDOW (editor->priv->app), title);
                g_free (title);
                g_free (filename);
        } else
                gtk_window_set_title (
-                       GTK_WINDOW (editor->app), _("Contact Editor"));
+                       GTK_WINDOW (editor->priv->app), _("Contact Editor"));
 
        /* Update file_as combo options */
 
@@ -2971,7 +3054,7 @@ extract_simple (EContactEditor *editor)
                        continue;
 
                widget = e_builder_get_widget (
-                       editor->builder, simple_field_map[i].widget_name);
+                       editor->priv->builder, simple_field_map[i].widget_name);
                if (!widget)
                        continue;
 
@@ -2981,7 +3064,7 @@ extract_simple (EContactEditor *editor)
 
        /* Special cases */
 
-       e_contact_set (editor->contact, E_CONTACT_NAME, editor->name);
+       e_contact_set (editor->priv->contact, E_CONTACT_NAME, editor->priv->name);
 }
 
 static void
@@ -2994,7 +3077,7 @@ sensitize_simple (EContactEditor *editor)
                gboolean   enabled = TRUE;
 
                widget = e_builder_get_widget (
-                       editor->builder, simple_field_map[i].widget_name);
+                       editor->priv->builder, simple_field_map[i].widget_name);
                if (!widget)
                        continue;
 
@@ -3003,7 +3086,7 @@ sensitize_simple (EContactEditor *editor)
                        enabled = FALSE;
 
                if (simple_field_map[i].desensitize_for_read_only &&
-                   !editor->target_editable)
+                   !editor->priv->target_editable)
                        enabled = FALSE;
 
                sensitize_simple_field (widget, enabled);
@@ -3063,7 +3146,7 @@ init_all (EContactEditor *editor)
        requisition.height = -1;
 
        for (ii = 0; ii < G_N_ELEMENTS (contents); ii++) {
-               widget = e_builder_get_widget (editor->builder, contents[ii]);
+               widget = e_builder_get_widget (editor->priv->builder, contents[ii]);
 
                gtk_widget_get_preferred_size (widget, NULL, &tab_req);
 
@@ -3080,7 +3163,7 @@ init_all (EContactEditor *editor)
                gint x = 0, y = 0, monitor, width, height;
 
                window = e_builder_get_widget (
-                       editor->builder, "contact editor");
+                       editor->priv->builder, "contact editor");
 
                gtk_widget_get_preferred_size (window, &tab_req, NULL);
                width = tab_req.width - 320 + 24;
@@ -3112,7 +3195,7 @@ init_all (EContactEditor *editor)
                                height + requisition.height);
        }
 
-       widget = e_builder_get_widget (editor->builder, "text-comments");
+       widget = e_builder_get_widget (editor->priv->builder, "text-comments");
        if (widget)
                e_spell_text_view_attach (GTK_TEXT_VIEW (widget));
 }
@@ -3153,7 +3236,7 @@ contact_editor_get_client_cb (GObject *source_object,
 
                e_source_combo_box_set_active (
                        E_SOURCE_COMBO_BOX (combo_box),
-                       e_client_get_source (E_CLIENT (closure->editor->target_client)));
+                       e_client_get_source (E_CLIENT (closure->editor->priv->target_client)));
 
                g_error_free (error);
                goto exit;
@@ -3181,14 +3264,14 @@ source_changed (EClientComboBox *combo_box,
                E_SOURCE_COMBO_BOX (combo_box));
        g_return_if_fail (source != NULL);
 
-       if (editor->cancellable != NULL) {
-               g_cancellable_cancel (editor->cancellable);
-               g_object_unref (editor->cancellable);
-               editor->cancellable = NULL;
+       if (editor->priv->cancellable != NULL) {
+               g_cancellable_cancel (editor->priv->cancellable);
+               g_object_unref (editor->priv->cancellable);
+               editor->priv->cancellable = NULL;
        }
 
-       target_source = e_client_get_source (E_CLIENT (editor->target_client));
-       source_source = e_client_get_source (E_CLIENT (editor->source_client));
+       target_source = e_client_get_source (E_CLIENT (editor->priv->target_client));
+       source_source = e_client_get_source (E_CLIENT (editor->priv->source_client));
 
        if (e_source_equal (target_source, source))
                goto exit;
@@ -3196,11 +3279,11 @@ source_changed (EClientComboBox *combo_box,
        if (e_source_equal (source_source, source)) {
                g_object_set (
                        editor, "target_client",
-                       editor->source_client, NULL);
+                       editor->priv->source_client, NULL);
                goto exit;
        }
 
-       editor->cancellable = g_cancellable_new ();
+       editor->priv->cancellable = g_cancellable_new ();
 
        closure = g_slice_new0 (ConnectClosure);
        closure->editor = g_object_ref (editor);
@@ -3208,7 +3291,7 @@ source_changed (EClientComboBox *combo_box,
 
        e_client_combo_box_get_client (
                combo_box, source,
-               editor->cancellable,
+               editor->priv->cancellable,
                contact_editor_get_client_cb,
                closure);
 
@@ -3234,7 +3317,7 @@ full_name_response (GtkDialog *dialog,
                style = file_as_get_style (editor);
 
                fname_widget = e_builder_get_widget (
-                       editor->builder, "entry-fullname");
+                       editor->priv->builder, "entry-fullname");
 
                if (GTK_IS_ENTRY (fname_widget)) {
                        GtkEntry *entry;
@@ -3249,14 +3332,14 @@ full_name_response (GtkDialog *dialog,
                        g_free (full_name);
                }
 
-               e_contact_name_free (editor->name);
-               editor->name = name;
+               e_contact_name_free (editor->priv->name);
+               editor->priv->name = name;
 
                file_as_set_style (editor, style);
        }
 
        gtk_widget_destroy (GTK_WIDGET (dialog));
-       editor->fullname_dialog = NULL;
+       editor->priv->fullname_dialog = NULL;
 }
 
 static gint
@@ -3277,17 +3360,17 @@ full_name_clicked (GtkWidget *button,
        GtkDialog *dialog;
        gboolean fullname_supported;
 
-       if (editor->fullname_dialog) {
-               gtk_window_present (GTK_WINDOW (editor->fullname_dialog));
+       if (editor->priv->fullname_dialog) {
+               gtk_window_present (GTK_WINDOW (editor->priv->fullname_dialog));
                return;
        }
 
-       dialog = GTK_DIALOG (e_contact_editor_fullname_new (editor->name));
+       dialog = GTK_DIALOG (e_contact_editor_fullname_new (editor->priv->name));
        fullname_supported = is_field_supported (editor, E_CONTACT_FULL_NAME);
 
        g_object_set (
                dialog, "editable",
-               fullname_supported & editor->target_editable, NULL);
+               fullname_supported & editor->priv->target_editable, NULL);
 
        g_signal_connect (
                dialog, "response",
@@ -3299,7 +3382,7 @@ full_name_clicked (GtkWidget *button,
                G_CALLBACK (full_name_editor_delete_event_cb), dialog);
 
        gtk_widget_show (GTK_WIDGET (dialog));
-       editor->fullname_dialog = GTK_WIDGET (dialog);
+       editor->priv->fullname_dialog = GTK_WIDGET (dialog);
 }
 
 static void
@@ -3310,7 +3393,7 @@ categories_response (GtkDialog *dialog,
        gchar *categories;
        GtkWidget *entry;
 
-       entry = e_builder_get_widget (editor->builder, "entry-categories");
+       entry = e_builder_get_widget (editor->priv->builder, "entry-categories");
 
        if (response == GTK_RESPONSE_OK) {
                categories = e_categories_dialog_get_categories (
@@ -3320,14 +3403,14 @@ categories_response (GtkDialog *dialog,
                                GTK_ENTRY (entry), categories);
                else
                        e_contact_set (
-                               editor->contact,
+                               editor->priv->contact,
                                E_CONTACT_CATEGORIES,
                                categories);
                g_free (categories);
        }
 
        gtk_widget_destroy (GTK_WIDGET (dialog));
-       editor->categories_dialog = NULL;
+       editor->priv->categories_dialog = NULL;
 }
 
 static void
@@ -3337,20 +3420,20 @@ categories_clicked (GtkWidget *button,
        gchar *categories = NULL;
        GtkDialog *dialog;
        GtkWindow *window;
-       GtkWidget *entry = e_builder_get_widget (editor->builder, "entry-categories");
+       GtkWidget *entry = e_builder_get_widget (editor->priv->builder, "entry-categories");
 
        if (entry && GTK_IS_ENTRY (entry))
                categories = g_strdup (gtk_entry_get_text (GTK_ENTRY (entry)));
-       else if (editor->contact)
-               categories = e_contact_get (editor->contact, E_CONTACT_CATEGORIES);
+       else if (editor->priv->contact)
+               categories = e_contact_get (editor->priv->contact, E_CONTACT_CATEGORIES);
 
-       if (editor->categories_dialog != NULL) {
-               gtk_window_present (GTK_WINDOW (editor->categories_dialog));
+       if (editor->priv->categories_dialog != NULL) {
+               gtk_window_present (GTK_WINDOW (editor->priv->categories_dialog));
                g_free (categories);
                return;
        }else if (!(dialog = GTK_DIALOG (e_categories_dialog_new (categories)))) {
                e_alert_run_dialog_for_args (
-                       GTK_WINDOW (editor->app),
+                       GTK_WINDOW (editor->priv->app),
                        "addressbook:edit-categories", NULL);
                g_free (categories);
                return;
@@ -3370,7 +3453,7 @@ categories_clicked (GtkWidget *button,
        gtk_widget_show (GTK_WIDGET (dialog));
        g_free (categories);
 
-       editor->categories_dialog = GTK_WIDGET (dialog);
+       editor->priv->categories_dialog = GTK_WIDGET (dialog);
 }
 
 static void
@@ -3380,12 +3463,12 @@ image_selected (EContactEditor *editor)
        GtkWidget *image_chooser;
 
        file_name = gtk_file_chooser_get_filename (
-               GTK_FILE_CHOOSER (editor->file_selector));
+               GTK_FILE_CHOOSER (editor->priv->file_selector));
 
        if (!file_name)
                return;
 
-       image_chooser = e_builder_get_widget (editor->builder, "image-chooser");
+       image_chooser = e_builder_get_widget (editor->priv->builder, "image-chooser");
 
        g_signal_handlers_block_by_func (
                image_chooser, image_chooser_changed, editor);
@@ -3394,8 +3477,8 @@ image_selected (EContactEditor *editor)
        g_signal_handlers_unblock_by_func (
                image_chooser, image_chooser_changed, editor);
 
-       editor->image_set = TRUE;
-       editor->image_changed = TRUE;
+       editor->priv->image_set = TRUE;
+       editor->priv->image_changed = TRUE;
        object_changed (G_OBJECT (image_chooser), editor);
 }
 
@@ -3406,7 +3489,7 @@ image_cleared (EContactEditor *editor)
        gchar     *file_name;
 
        image_chooser = e_builder_get_widget (
-               editor->builder, "image-chooser");
+               editor->priv->builder, "image-chooser");
 
        file_name = e_icon_factory_get_icon_filename (
                "avatar-default", GTK_ICON_SIZE_DIALOG);
@@ -3420,8 +3503,8 @@ image_cleared (EContactEditor *editor)
 
        g_free (file_name);
 
-       editor->image_set = FALSE;
-       editor->image_changed = TRUE;
+       editor->priv->image_set = FALSE;
+       editor->priv->image_changed = TRUE;
        object_changed (G_OBJECT (image_chooser), editor);
 }
 
@@ -3435,7 +3518,7 @@ file_chooser_response (GtkWidget *widget,
        else if (response == GTK_RESPONSE_NO)
                image_cleared (editor);
 
-       gtk_widget_hide (editor->file_selector);
+       gtk_widget_hide (editor->priv->file_selector);
 }
 
 static gboolean
@@ -3481,14 +3564,14 @@ static void
 image_clicked (GtkWidget *button,
                EContactEditor *editor)
 {
-       if (!editor->file_selector) {
+       if (!editor->priv->file_selector) {
                const gchar *title = _("Please select an image for this contact");
                const gchar *no_image = _("_No image");
                GtkImage *preview;
                GtkFileFilter *filter;
 
-               editor->file_selector = gtk_file_chooser_dialog_new (
-                       title, GTK_WINDOW (editor->app),
+               editor->priv->file_selector = gtk_file_chooser_dialog_new (
+                       title, GTK_WINDOW (editor->priv->app),
                        GTK_FILE_CHOOSER_ACTION_OPEN,
                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                        GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
@@ -3498,35 +3581,35 @@ image_clicked (GtkWidget *button,
                filter = gtk_file_filter_new ();
                gtk_file_filter_add_mime_type (filter, "image/*");
                gtk_file_chooser_set_filter (
-                       GTK_FILE_CHOOSER (editor->file_selector),
+                       GTK_FILE_CHOOSER (editor->priv->file_selector),
                        filter);
 
                preview = GTK_IMAGE (gtk_image_new ());
                gtk_file_chooser_set_preview_widget (
-                       GTK_FILE_CHOOSER (editor->file_selector),
+                       GTK_FILE_CHOOSER (editor->priv->file_selector),
                        GTK_WIDGET (preview));
                g_signal_connect (
-                       editor->file_selector, "update-preview",
+                       editor->priv->file_selector, "update-preview",
                        G_CALLBACK (update_preview_cb), preview);
 
                gtk_dialog_set_default_response (
-                       GTK_DIALOG (editor->file_selector),
+                       GTK_DIALOG (editor->priv->file_selector),
                        GTK_RESPONSE_ACCEPT);
 
                g_signal_connect (
-                       editor->file_selector, "response",
+                       editor->priv->file_selector, "response",
                        G_CALLBACK (file_chooser_response), editor);
 
                g_signal_connect_after (
-                       editor->file_selector, "delete-event",
+                       editor->priv->file_selector, "delete-event",
                        G_CALLBACK (file_selector_deleted),
-                       editor->file_selector);
+                       editor->priv->file_selector);
        }
 
        /* Display the dialog */
 
-       gtk_window_set_modal (GTK_WINDOW (editor->file_selector), TRUE);
-       gtk_window_present (GTK_WINDOW (editor->file_selector));
+       gtk_window_set_modal (GTK_WINDOW (editor->priv->file_selector), TRUE);
+       gtk_window_present (GTK_WINDOW (editor->priv->file_selector));
 }
 
 typedef struct {
@@ -3548,23 +3631,23 @@ contact_removed_cb (GObject *source_object,
 
        e_book_client_remove_contact_finish (book_client, result, &error);
 
-       gtk_widget_set_sensitive (ce->app, TRUE);
-       ce->in_async_call = FALSE;
+       gtk_widget_set_sensitive (ce->priv->app, TRUE);
+       ce->priv->in_async_call = FALSE;
 
-       e_contact_set (ce->contact, E_CONTACT_UID, ecs->new_id);
+       e_contact_set (ce->priv->contact, E_CONTACT_UID, ecs->new_id);
 
-       eab_editor_contact_deleted (EAB_EDITOR (ce), error, ce->contact);
+       eab_editor_contact_deleted (EAB_EDITOR (ce), error, ce->priv->contact);
 
-       ce->is_new_contact = FALSE;
+       ce->priv->is_new_contact = FALSE;
 
        if (should_close) {
                eab_editor_close (EAB_EDITOR (ce));
        } else {
-               ce->changed = FALSE;
+               ce->priv->changed = FALSE;
 
-               g_object_ref (ce->target_client);
-               g_object_unref (ce->source_client);
-               ce->source_client = ce->target_client;
+               g_object_ref (ce->priv->target_client);
+               g_object_unref (ce->priv->source_client);
+               ce->priv->source_client = ce->priv->target_client;
 
                sensitize_all (ce);
        }
@@ -3587,28 +3670,28 @@ contact_added_cb (EBookClient *book_client,
        EContactEditor *ce = ecs->ce;
        gboolean should_close = ecs->should_close;
 
-       if (ce->source_client != ce->target_client && !e_client_is_readonly (E_CLIENT (ce->source_client)) &&
-           !error && ce->is_new_contact == FALSE) {
+       if (ce->priv->source_client != ce->priv->target_client && !e_client_is_readonly (E_CLIENT 
(ce->priv->source_client)) &&
+           !error && ce->priv->is_new_contact == FALSE) {
                ecs->new_id = g_strdup (id);
                e_book_client_remove_contact (
-                       ce->source_client, ce->contact, NULL, contact_removed_cb, ecs);
+                       ce->priv->source_client, ce->priv->contact, NULL, contact_removed_cb, ecs);
                return;
        }
 
-       gtk_widget_set_sensitive (ce->app, TRUE);
-       ce->in_async_call = FALSE;
+       gtk_widget_set_sensitive (ce->priv->app, TRUE);
+       ce->priv->in_async_call = FALSE;
 
-       e_contact_set (ce->contact, E_CONTACT_UID, id);
+       e_contact_set (ce->priv->contact, E_CONTACT_UID, id);
 
-       eab_editor_contact_added (EAB_EDITOR (ce), error, ce->contact);
+       eab_editor_contact_added (EAB_EDITOR (ce), error, ce->priv->contact);
 
        if (!error) {
-               ce->is_new_contact = FALSE;
+               ce->priv->is_new_contact = FALSE;
 
                if (should_close) {
                        eab_editor_close (EAB_EDITOR (ce));
                } else {
-                       ce->changed = FALSE;
+                       ce->priv->changed = FALSE;
                        sensitize_all (ce);
                }
        }
@@ -3626,17 +3709,17 @@ contact_modified_cb (EBookClient *book_client,
        EContactEditor *ce = ecs->ce;
        gboolean should_close = ecs->should_close;
 
-       gtk_widget_set_sensitive (ce->app, TRUE);
-       ce->in_async_call = FALSE;
+       gtk_widget_set_sensitive (ce->priv->app, TRUE);
+       ce->priv->in_async_call = FALSE;
 
-       eab_editor_contact_modified (EAB_EDITOR (ce), error, ce->contact);
+       eab_editor_contact_modified (EAB_EDITOR (ce), error, ce->priv->contact);
 
        if (!error) {
                if (should_close) {
                        eab_editor_close (EAB_EDITOR (ce));
                }
                else {
-                       ce->changed = FALSE;
+                       ce->priv->changed = FALSE;
                        sensitize_all (ce);
                }
        }
@@ -3679,26 +3762,26 @@ real_save_contact (EContactEditor *ce,
 
        ecs->should_close = should_close;
 
-       gtk_widget_set_sensitive (ce->app, FALSE);
-       ce->in_async_call = TRUE;
+       gtk_widget_set_sensitive (ce->priv->app, FALSE);
+       ce->priv->in_async_call = TRUE;
 
-       if (ce->source_client != ce->target_client) {
+       if (ce->priv->source_client != ce->priv->target_client) {
                /* Two-step move; add to target, then remove from source */
                eab_merging_book_add_contact (
-                       registry, ce->target_client,
-                       ce->contact, contact_added_cb, ecs);
+                       registry, ce->priv->target_client,
+                       ce->priv->contact, contact_added_cb, ecs);
        } else {
-               if (ce->is_new_contact)
+               if (ce->priv->is_new_contact)
                        eab_merging_book_add_contact (
-                               registry, ce->target_client,
-                               ce->contact, contact_added_cb, ecs);
-               else if (ce->check_merge)
+                               registry, ce->priv->target_client,
+                               ce->priv->contact, contact_added_cb, ecs);
+               else if (ce->priv->check_merge)
                        eab_merging_book_modify_contact (
-                               registry, ce->target_client,
-                               ce->contact, contact_modified_cb, ecs);
+                               registry, ce->priv->target_client,
+                               ce->priv->contact, contact_modified_cb, ecs);
                else
                        e_book_client_modify_contact (
-                               ce->target_client, ce->contact, NULL,
+                               ce->priv->target_client, ce->priv->contact, NULL,
                                contact_modified_ready_cb, ecs);
        }
 }
@@ -3714,16 +3797,16 @@ save_contact (EContactEditor *ce,
        GtkWidget *entry_fullname, *entry_file_as, *company_name, *client_combo_box;
        ESource *active_source;
 
-       if (!ce->target_client)
+       if (!ce->priv->target_client)
                return;
 
-       client_combo_box = e_builder_get_widget (ce->builder, "client-combo-box");
+       client_combo_box = e_builder_get_widget (ce->priv->builder, "client-combo-box");
        active_source = e_source_combo_box_ref_active (E_SOURCE_COMBO_BOX (client_combo_box));
        g_return_if_fail (active_source != NULL);
 
-       if (!e_source_equal (e_client_get_source (E_CLIENT (ce->target_client)), active_source)) {
+       if (!e_source_equal (e_client_get_source (E_CLIENT (ce->priv->target_client)), active_source)) {
                e_alert_run_dialog_for_args (
-                               GTK_WINDOW (ce->app),
+                               GTK_WINDOW (ce->priv->app),
                                "addressbook:error-still-opening",
                                e_source_get_display_name (active_source),
                                NULL);
@@ -3733,18 +3816,18 @@ save_contact (EContactEditor *ce,
 
        g_object_unref (active_source);
 
-       if (ce->target_editable && e_client_is_readonly (E_CLIENT (ce->source_client))) {
+       if (ce->priv->target_editable && e_client_is_readonly (E_CLIENT (ce->priv->source_client))) {
                if (e_alert_run_dialog_for_args (
-                               GTK_WINDOW (ce->app),
+                               GTK_WINDOW (ce->priv->app),
                                "addressbook:prompt-move",
                                NULL) == GTK_RESPONSE_NO)
                        return;
        }
 
-       entry_fullname = e_builder_get_widget (ce->builder, "entry-fullname");
+       entry_fullname = e_builder_get_widget (ce->priv->builder, "entry-fullname");
        entry_file_as = gtk_bin_get_child (
-               GTK_BIN (e_builder_get_widget (ce->builder, "combo-file-as")));
-       company_name = e_builder_get_widget (ce->builder, "entry-company");
+               GTK_BIN (e_builder_get_widget (ce->priv->builder, "combo-file-as")));
+       company_name = e_builder_get_widget (ce->priv->builder, "entry-company");
        name_entry_string = gtk_entry_get_text (GTK_ENTRY (entry_fullname));
        file_as_entry_string = gtk_entry_get_text (GTK_ENTRY (entry_file_as));
        company_name_string = gtk_entry_get_text (GTK_ENTRY (company_name));
@@ -3763,11 +3846,11 @@ save_contact (EContactEditor *ce,
        extract_all (ce);
 
        if (!e_contact_editor_is_valid (EAB_EDITOR (ce))) {
-               uid = e_contact_get (ce->contact, E_CONTACT_UID);
-               g_object_unref (ce->contact);
-               ce->contact = e_contact_new ();
+               uid = e_contact_get (ce->priv->contact, E_CONTACT_UID);
+               g_object_unref (ce->priv->contact);
+               ce->priv->contact = e_contact_new ();
                if (uid) {
-                       e_contact_set (ce->contact, E_CONTACT_UID, uid);
+                       e_contact_set (ce->priv->contact, E_CONTACT_UID, uid);
                        g_free (uid);
                }
                return;
@@ -3789,9 +3872,9 @@ e_contact_editor_close (EABEditor *editor)
 {
        EContactEditor *ce = E_CONTACT_EDITOR (editor);
 
-       if (ce->app != NULL) {
-               gtk_widget_destroy (ce->app);
-               ce->app = NULL;
+       if (ce->priv->app != NULL) {
+               gtk_widget_destroy (ce->priv->app);
+               ce->priv->app = NULL;
                eab_editor_closed (editor);
        }
 }
@@ -3844,7 +3927,7 @@ e_contact_editor_is_valid (EABEditor *editor)
        GString *errmsg = g_string_new (_("The contact data is invalid:\n\n"));
        time_t bday, now = time (NULL);
 
-       widget = e_builder_get_widget (ce->builder, "dateedit-birthday");
+       widget = e_builder_get_widget (ce->priv->builder, "dateedit-birthday");
        if (!(e_date_edit_date_is_valid (E_DATE_EDIT (widget)))) {
                g_string_append_printf (
                        errmsg, _("'%s' has an invalid format"),
@@ -3860,7 +3943,7 @@ e_contact_editor_is_valid (EABEditor *editor)
                validation_error = TRUE;
        }
 
-       widget = e_builder_get_widget (ce->builder, "dateedit-anniversary");
+       widget = e_builder_get_widget (ce->priv->builder, "dateedit-anniversary");
        if (!(e_date_edit_date_is_valid (E_DATE_EDIT (widget)))) {
                g_string_append_printf (
                        errmsg, _("%s'%s' has an invalid format"),
@@ -3869,12 +3952,12 @@ e_contact_editor_is_valid (EABEditor *editor)
                validation_error = TRUE;
        }
 
-       for (iter = ce->required_fields; iter; iter = iter->next) {
+       for (iter = ce->priv->required_fields; iter; iter = iter->next) {
                const gchar *field_name = iter->data;
                EContactField  field_id = e_contact_field_id (field_name);
 
                if (is_non_string_field (field_id)) {
-                       if (e_contact_get_const (ce->contact, field_id) == NULL) {
+                       if (e_contact_get_const (ce->priv->contact, field_id) == NULL) {
                                g_string_append_printf (
                                        errmsg, _("%s'%s' is empty"),
                                        validation_error ? ",\n" : "",
@@ -3886,7 +3969,7 @@ e_contact_editor_is_valid (EABEditor *editor)
                } else {
                        const gchar *text;
 
-                       text = e_contact_get_const (ce->contact, field_id);
+                       text = e_contact_get_const (ce->priv->contact, field_id);
 
                        if (STRING_IS_EMPTY (text)) {
                                g_string_append_printf (
@@ -3903,7 +3986,7 @@ e_contact_editor_is_valid (EABEditor *editor)
        if (validation_error) {
                g_string_append (errmsg, ".");
                e_alert_run_dialog_for_args (
-                       GTK_WINDOW (ce->app),
+                       GTK_WINDOW (ce->priv->app),
                        "addressbook:generic-error",
                        _("Invalid contact."), errmsg->str, NULL);
                g_string_free (errmsg, TRUE);
@@ -3918,13 +4001,13 @@ e_contact_editor_is_valid (EABEditor *editor)
 static gboolean
 e_contact_editor_is_changed (EABEditor *editor)
 {
-       return E_CONTACT_EDITOR (editor)->changed;
+       return E_CONTACT_EDITOR (editor)->priv->changed;
 }
 
 static GtkWindow *
 e_contact_editor_get_window (EABEditor *editor)
 {
-       return GTK_WINDOW (E_CONTACT_EDITOR (editor)->app);
+       return GTK_WINDOW (E_CONTACT_EDITOR (editor)->priv->app);
 }
 
 static void
@@ -3952,11 +4035,11 @@ app_delete_event_cb (GtkWidget *widget,
        ce = E_CONTACT_EDITOR (data);
 
        /* if we're saving, don't allow the dialog to close */
-       if (ce->in_async_call)
+       if (ce->priv->in_async_call)
                return TRUE;
 
-       if (ce->changed) {
-               switch (eab_prompt_save_dialog (GTK_WINDOW (ce->app))) {
+       if (ce->priv->changed) {
+               switch (eab_prompt_save_dialog (GTK_WINDOW (ce->priv->app))) {
                        case GTK_RESPONSE_YES:
                                eab_editor_save_contact (EAB_EDITOR (ce), TRUE);
                                return TRUE;
@@ -4070,7 +4153,7 @@ expand_web_toggle (EContactEditor *ce)
 {
        GtkWidget *widget;
 
-       widget = e_builder_get_widget (ce->builder, "label-videourl");
+       widget = e_builder_get_widget (ce->priv->builder, "label-videourl");
        expand_web (ce, !gtk_widget_get_visible (widget));
 }
 
@@ -4080,7 +4163,7 @@ expand_phone_toggle (EContactEditor *ce)
        GtkWidget *phone_ext_table;
 
        phone_ext_table = e_builder_get_widget (
-               ce->builder, "table-phone-extended");
+               ce->priv->builder, "table-phone-extended");
        expand_phone (ce, !gtk_widget_get_visible (phone_ext_table));
 }
 
@@ -4089,11 +4172,25 @@ expand_mail_toggle (EContactEditor *ce)
 {
        GtkWidget *mail;
 
-       mail = e_builder_get_widget (ce->builder, "entry-email-4");
+       mail = e_builder_get_widget (ce->priv->builder, "entry-email-4");
        expand_mail (ce, !gtk_widget_get_visible (mail));
 }
 
 static void
+contact_editor_focus_widget_changed_cb (EFocusTracker *focus_tracker,
+                                       GParamSpec *param,
+                                       EContactEditor *editor)
+{
+       GtkWidget *widget;
+
+       widget = e_focus_tracker_get_focus (focus_tracker);
+
+       /* there is no problem to call the attach multiple times */
+       if (widget)
+               e_widget_undo_attach (widget, focus_tracker);
+}
+
+static void
 e_contact_editor_init (EContactEditor *e_contact_editor)
 {
        GtkBuilder *builder;
@@ -4103,34 +4200,37 @@ e_contact_editor_init (EContactEditor *e_contact_editor)
        GtkWidget *widget, *label;
        GtkEntryCompletion *completion;
 
+       e_contact_editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+               e_contact_editor, E_TYPE_CONTACT_EDITOR, EContactEditorPrivate);
+
        /* FIXME The shell should be obtained
         *       through a constructor property. */
        shell = e_shell_get_default ();
        client_cache = e_shell_get_client_cache (shell);
 
-       e_contact_editor->name = e_contact_name_new ();
+       e_contact_editor->priv->name = e_contact_name_new ();
 
-       e_contact_editor->contact = NULL;
-       e_contact_editor->changed = FALSE;
-       e_contact_editor->check_merge = FALSE;
-       e_contact_editor->image_set = FALSE;
-       e_contact_editor->image_changed = FALSE;
-       e_contact_editor->in_async_call = FALSE;
-       e_contact_editor->target_editable = TRUE;
-       e_contact_editor->fullname_dialog = NULL;
-       e_contact_editor->categories_dialog = NULL;
-       e_contact_editor->compress_ui = e_shell_get_express_mode (shell);
+       e_contact_editor->priv->contact = NULL;
+       e_contact_editor->priv->changed = FALSE;
+       e_contact_editor->priv->check_merge = FALSE;
+       e_contact_editor->priv->image_set = FALSE;
+       e_contact_editor->priv->image_changed = FALSE;
+       e_contact_editor->priv->in_async_call = FALSE;
+       e_contact_editor->priv->target_editable = TRUE;
+       e_contact_editor->priv->fullname_dialog = NULL;
+       e_contact_editor->priv->categories_dialog = NULL;
+       e_contact_editor->priv->compress_ui = e_shell_get_express_mode (shell);
 
        builder = gtk_builder_new ();
        e_load_ui_builder_definition (builder, "contact-editor.ui");
 
-       e_contact_editor->builder = builder;
+       e_contact_editor->priv->builder = builder;
 
        setup_tab_order (builder);
 
-       e_contact_editor->app =
+       e_contact_editor->priv->app =
                e_builder_get_widget (builder, "contact editor");
-       widget = e_contact_editor->app;
+       widget = e_contact_editor->priv->app;
 
        gtk_widget_ensure_style (widget);
        gtk_window_set_type_hint (
@@ -4143,68 +4243,68 @@ e_contact_editor_init (EContactEditor *e_contact_editor)
        init_all (e_contact_editor);
 
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-image");
+               e_contact_editor->priv->builder, "button-image");
        g_signal_connect (
                widget, "clicked",
                G_CALLBACK (image_clicked), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-fullname");
+               e_contact_editor->priv->builder, "button-fullname");
        g_signal_connect (
                widget, "clicked",
                G_CALLBACK (full_name_clicked), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-categories");
+               e_contact_editor->priv->builder, "button-categories");
        g_signal_connect (
                widget, "clicked",
                G_CALLBACK (categories_clicked), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "client-combo-box");
+               e_contact_editor->priv->builder, "client-combo-box");
        e_client_combo_box_set_client_cache (
                E_CLIENT_COMBO_BOX (widget), client_cache);
        g_signal_connect (
                widget, "changed",
                G_CALLBACK (source_changed), e_contact_editor);
        label = e_builder_get_widget (
-               e_contact_editor->builder, "where-label");
+               e_contact_editor->priv->builder, "where-label");
        gtk_label_set_mnemonic_widget (GTK_LABEL (label), widget);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-ok");
+               e_contact_editor->priv->builder, "button-ok");
        g_signal_connect (
                widget, "clicked",
                G_CALLBACK (file_save_and_close_cb), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-cancel");
+               e_contact_editor->priv->builder, "button-cancel");
        g_signal_connect (
                widget, "clicked",
                G_CALLBACK (file_cancel_cb), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-help");
+               e_contact_editor->priv->builder, "button-help");
        g_signal_connect (
                widget, "clicked",
                G_CALLBACK (show_help_cb), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-web-expand");
+               e_contact_editor->priv->builder, "button-web-expand");
        g_signal_connect_swapped (
                widget, "clicked",
                G_CALLBACK (expand_web_toggle), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-phone-expand");
+               e_contact_editor->priv->builder, "button-phone-expand");
        g_signal_connect_swapped (
                widget, "clicked",
                G_CALLBACK (expand_phone_toggle), e_contact_editor);
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "button-mail-expand");
+               e_contact_editor->priv->builder, "button-mail-expand");
        g_signal_connect_swapped (
                widget, "clicked",
                G_CALLBACK (expand_mail_toggle), e_contact_editor);
 
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "entry-fullname");
+               e_contact_editor->priv->builder, "entry-fullname");
        if (widget)
                gtk_widget_grab_focus (widget);
 
        widget = e_builder_get_widget (
-               e_contact_editor->builder, "entry-categories");
+               e_contact_editor->priv->builder, "entry-categories");
        completion = e_category_completion_new ();
        gtk_entry_set_completion (GTK_ENTRY (widget), completion);
        g_object_unref (completion);
@@ -4212,19 +4312,70 @@ e_contact_editor_init (EContactEditor *e_contact_editor)
        /* Connect to the deletion of the dialog */
 
        g_signal_connect (
-               e_contact_editor->app, "delete_event",
+               e_contact_editor->priv->app, "delete_event",
                G_CALLBACK (app_delete_event_cb), e_contact_editor);
 
        /* set the icon */
        gtk_window_set_icon_name (
-               GTK_WINDOW (e_contact_editor->app), "contact-editor");
+               GTK_WINDOW (e_contact_editor->priv->app), "contact-editor");
 
        /* show window */
-       gtk_widget_show (e_contact_editor->app);
+       gtk_widget_show (e_contact_editor->priv->app);
 
        gtk_application_add_window (
                GTK_APPLICATION (shell),
-               GTK_WINDOW (e_contact_editor->app));
+               GTK_WINDOW (e_contact_editor->priv->app));
+}
+
+static void
+e_contact_editor_constructed (GObject *object)
+{
+       const gchar *ui =
+               "<ui>"
+               "  <menubar name='undo-menubar'>"
+               "      <menu action='undo-menu'>"
+               "      <menuitem action='undo'/>"
+               "    <menuitem action='redo'/>"
+               "    </menu>"
+               "  </menubar>"
+               "</ui>";
+       EContactEditor *editor = E_CONTACT_EDITOR (object);
+       GtkActionGroup *action_group;
+       GtkAction *action;
+       GError *error = NULL;
+
+       editor->priv->focus_tracker = e_focus_tracker_new (GTK_WINDOW (editor->priv->app));
+       editor->priv->ui_manager = gtk_ui_manager_new ();
+
+       gtk_window_add_accel_group (
+               GTK_WINDOW (editor->priv->app),
+               gtk_ui_manager_get_accel_group (editor->priv->ui_manager));
+
+       g_signal_connect (editor->priv->focus_tracker, "notify::focus",
+               G_CALLBACK (contact_editor_focus_widget_changed_cb), editor);
+
+       action_group = gtk_action_group_new ("undo");
+       gtk_action_group_set_translation_domain (
+               action_group, GETTEXT_PACKAGE);
+       gtk_action_group_add_actions (
+               action_group, undo_entries,
+               G_N_ELEMENTS (undo_entries), editor);
+       gtk_ui_manager_insert_action_group (
+               editor->priv->ui_manager, action_group, 0);
+
+       action = gtk_action_group_get_action (action_group, "undo");
+       e_focus_tracker_set_undo_action (editor->priv->focus_tracker, action);
+
+       action = gtk_action_group_get_action (action_group, "redo");
+       e_focus_tracker_set_redo_action (editor->priv->focus_tracker, action);
+
+       g_object_unref (action_group);
+
+       gtk_ui_manager_add_ui_from_string (editor->priv->ui_manager, ui, -1, &error);
+       if (error != NULL) {
+               g_warning ("%s: %s", G_STRFUNC, error->message);
+               g_error_free (error);
+       }
 }
 
 static void
@@ -4232,53 +4383,45 @@ e_contact_editor_dispose (GObject *object)
 {
        EContactEditor *e_contact_editor = E_CONTACT_EDITOR (object);
 
-       if (e_contact_editor->file_selector != NULL) {
-               gtk_widget_destroy (e_contact_editor->file_selector);
-               e_contact_editor->file_selector = NULL;
+       if (e_contact_editor->priv->file_selector != NULL) {
+               gtk_widget_destroy (e_contact_editor->priv->file_selector);
+               e_contact_editor->priv->file_selector = NULL;
        }
 
        g_slist_free_full (
-               e_contact_editor->writable_fields,
+               e_contact_editor->priv->writable_fields,
                (GDestroyNotify) g_free);
-       e_contact_editor->writable_fields = NULL;
+       e_contact_editor->priv->writable_fields = NULL;
 
        g_slist_free_full (
-               e_contact_editor->required_fields,
+               e_contact_editor->priv->required_fields,
                (GDestroyNotify) g_free);
-       e_contact_editor->required_fields = NULL;
-
-       if (e_contact_editor->contact) {
-               g_object_unref (e_contact_editor->contact);
-               e_contact_editor->contact = NULL;
-       }
+       e_contact_editor->priv->required_fields = NULL;
 
-       if (e_contact_editor->source_client) {
-               g_object_unref (e_contact_editor->source_client);
-               e_contact_editor->source_client = NULL;
-       }
-
-       if (e_contact_editor->target_client) {
+       if (e_contact_editor->priv->target_client) {
                g_signal_handler_disconnect (
-                       e_contact_editor->target_client,
-                       e_contact_editor->target_editable_id);
-               g_object_unref (e_contact_editor->target_client);
-               e_contact_editor->target_client = NULL;
+                       e_contact_editor->priv->target_client,
+                       e_contact_editor->priv->target_editable_id);
        }
 
-       if (e_contact_editor->name) {
-               e_contact_name_free (e_contact_editor->name);
-               e_contact_editor->name = NULL;
+       if (e_contact_editor->priv->name) {
+               e_contact_name_free (e_contact_editor->priv->name);
+               e_contact_editor->priv->name = NULL;
        }
 
-       if (e_contact_editor->builder) {
-               g_object_unref (e_contact_editor->builder);
-               e_contact_editor->builder = NULL;
+       if (e_contact_editor->priv->focus_tracker) {
+               g_signal_handlers_disconnect_by_data (
+                       e_contact_editor->priv->focus_tracker,
+                       e_contact_editor);
        }
 
-       if (e_contact_editor->cancellable != NULL) {
-               g_object_unref (e_contact_editor->cancellable);
-               e_contact_editor->cancellable = NULL;
-       }
+       g_clear_object (&e_contact_editor->priv->contact);
+       g_clear_object (&e_contact_editor->priv->source_client);
+       g_clear_object (&e_contact_editor->priv->target_client);
+       g_clear_object (&e_contact_editor->priv->builder);
+       g_clear_object (&e_contact_editor->priv->ui_manager);
+       g_clear_object (&e_contact_editor->priv->cancellable);
+       g_clear_object (&e_contact_editor->priv->focus_tracker);
 
        /* Chain up to parent's dispose() method. */
        G_OBJECT_CLASS (parent_class)->dispose (object);
@@ -4405,13 +4548,13 @@ notify_readonly_cb (EBookClient *book_client,
        gint new_target_editable;
        gboolean changed = FALSE;
 
-       client = E_CLIENT (ce->target_client);
+       client = E_CLIENT (ce->priv->target_client);
        new_target_editable = !e_client_is_readonly (client);
 
-       if (ce->target_editable != new_target_editable)
+       if (ce->priv->target_editable != new_target_editable)
                changed = TRUE;
 
-       ce->target_editable = new_target_editable;
+       ce->priv->target_editable = new_target_editable;
 
        if (changed)
                sensitize_all (ce);
@@ -4435,37 +4578,37 @@ e_contact_editor_set_property (GObject *object,
 
                source_client = E_BOOK_CLIENT (g_value_get_object (value));
 
-               if (source_client == editor->source_client)
+               if (source_client == editor->priv->source_client)
                        break;
 
-               if (editor->source_client)
-                       g_object_unref (editor->source_client);
+               if (editor->priv->source_client)
+                       g_object_unref (editor->priv->source_client);
 
-               editor->source_client = source_client;
-               g_object_ref (editor->source_client);
+               editor->priv->source_client = source_client;
+               g_object_ref (editor->priv->source_client);
 
-               if (!editor->target_client) {
-                       editor->target_client = editor->source_client;
-                       g_object_ref (editor->target_client);
+               if (!editor->priv->target_client) {
+                       editor->priv->target_client = editor->priv->source_client;
+                       g_object_ref (editor->priv->target_client);
 
-                       editor->target_editable_id = g_signal_connect (
-                               editor->target_client, "notify::readonly",
+                       editor->priv->target_editable_id = g_signal_connect (
+                               editor->priv->target_client, "notify::readonly",
                                G_CALLBACK (notify_readonly_cb), editor);
 
                        e_client_get_backend_property (
-                               E_CLIENT (editor->target_client),
+                               E_CLIENT (editor->priv->target_client),
                                BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS,
                                NULL, supported_fields_cb, editor);
 
                        e_client_get_backend_property (
-                               E_CLIENT (editor->target_client),
+                               E_CLIENT (editor->priv->target_client),
                                BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS,
                                NULL, required_fields_cb, editor);
                }
 
-               writable = !e_client_is_readonly (E_CLIENT (editor->target_client));
-               if (writable != editor->target_editable) {
-                       editor->target_editable = writable;
+               writable = !e_client_is_readonly (E_CLIENT (editor->priv->target_client));
+               if (writable != editor->priv->target_editable) {
+                       editor->priv->target_editable = writable;
                        changed = TRUE;
                }
 
@@ -4482,40 +4625,40 @@ e_contact_editor_set_property (GObject *object,
 
                target_client = E_BOOK_CLIENT (g_value_get_object (value));
 
-               if (target_client == editor->target_client)
+               if (target_client == editor->priv->target_client)
                        break;
 
-               if (editor->target_client) {
+               if (editor->priv->target_client) {
                        g_signal_handler_disconnect (
-                               editor->target_client,
-                               editor->target_editable_id);
-                       g_object_unref (editor->target_client);
+                               editor->priv->target_client,
+                               editor->priv->target_editable_id);
+                       g_object_unref (editor->priv->target_client);
                }
 
-               editor->target_client = target_client;
-               g_object_ref (editor->target_client);
+               editor->priv->target_client = target_client;
+               g_object_ref (editor->priv->target_client);
 
-               editor->target_editable_id = g_signal_connect (
-                       editor->target_client, "notify::readonly",
+               editor->priv->target_editable_id = g_signal_connect (
+                       editor->priv->target_client, "notify::readonly",
                        G_CALLBACK (notify_readonly_cb), editor);
 
                e_client_get_backend_property (
-                       E_CLIENT (editor->target_client),
+                       E_CLIENT (editor->priv->target_client),
                        BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS,
                        NULL, supported_fields_cb, editor);
 
                e_client_get_backend_property (
-                       E_CLIENT (editor->target_client),
+                       E_CLIENT (editor->priv->target_client),
                        BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS,
                        NULL, required_fields_cb, editor);
 
-               if (!editor->is_new_contact)
-                       editor->changed = TRUE;
+               if (!editor->priv->is_new_contact)
+                       editor->priv->changed = TRUE;
 
-               writable = !e_client_is_readonly (E_CLIENT (editor->target_client));
+               writable = !e_client_is_readonly (E_CLIENT (editor->priv->target_client));
 
-               if (writable != editor->target_editable) {
-                       editor->target_editable = writable;
+               if (writable != editor->priv->target_editable) {
+                       editor->priv->target_editable = writable;
                        changed = TRUE;
                }
 
@@ -4526,23 +4669,23 @@ e_contact_editor_set_property (GObject *object,
        }
 
        case PROP_CONTACT:
-               if (editor->contact)
-                       g_object_unref (editor->contact);
-               editor->contact = e_contact_duplicate (
+               if (editor->priv->contact)
+                       g_object_unref (editor->priv->contact);
+               editor->priv->contact = e_contact_duplicate (
                        E_CONTACT (g_value_get_object (value)));
                fill_in_all (editor);
-               editor->changed = FALSE;
+               editor->priv->changed = FALSE;
                break;
 
        case PROP_IS_NEW_CONTACT:
-               editor->is_new_contact = g_value_get_boolean (value);
+               editor->priv->is_new_contact = g_value_get_boolean (value);
                break;
 
        case PROP_EDITABLE: {
                gboolean new_value = g_value_get_boolean (value);
-               gboolean changed = (editor->target_editable != new_value);
+               gboolean changed = (editor->priv->target_editable != new_value);
 
-               editor->target_editable = new_value;
+               editor->priv->target_editable = new_value;
 
                if (changed)
                        sensitize_all (editor);
@@ -4551,9 +4694,9 @@ e_contact_editor_set_property (GObject *object,
 
        case PROP_CHANGED: {
                gboolean new_value = g_value_get_boolean (value);
-               gboolean changed = (editor->changed != new_value);
+               gboolean changed = (editor->priv->changed != new_value);
 
-               editor->changed = new_value;
+               editor->priv->changed = new_value;
 
                if (changed)
                        sensitize_ok (editor);
@@ -4561,9 +4704,9 @@ e_contact_editor_set_property (GObject *object,
        }
        case PROP_WRITABLE_FIELDS:
                g_slist_free_full (
-                       editor->writable_fields,
+                       editor->priv->writable_fields,
                        (GDestroyNotify) g_free);
-               editor->writable_fields = g_slist_copy_deep (
+               editor->priv->writable_fields = g_slist_copy_deep (
                        g_value_get_pointer (value),
                        (GCopyFunc) g_strdup, NULL);
 
@@ -4571,9 +4714,9 @@ e_contact_editor_set_property (GObject *object,
                break;
        case PROP_REQUIRED_FIELDS:
                g_slist_free_full (
-                       editor->required_fields,
+                       editor->priv->required_fields,
                        (GDestroyNotify) g_free);
-               editor->required_fields = g_slist_copy_deep (
+               editor->priv->required_fields = g_slist_copy_deep (
                        g_value_get_pointer (value),
                        (GCopyFunc) g_strdup, NULL);
                break;
@@ -4595,38 +4738,38 @@ e_contact_editor_get_property (GObject *object,
 
        switch (property_id) {
        case PROP_SOURCE_CLIENT:
-               g_value_set_object (value, e_contact_editor->source_client);
+               g_value_set_object (value, e_contact_editor->priv->source_client);
                break;
 
        case PROP_TARGET_CLIENT:
-               g_value_set_object (value, e_contact_editor->target_client);
+               g_value_set_object (value, e_contact_editor->priv->target_client);
                break;
 
        case PROP_CONTACT:
                extract_all (e_contact_editor);
-               g_value_set_object (value, e_contact_editor->contact);
+               g_value_set_object (value, e_contact_editor->priv->contact);
                break;
 
        case PROP_IS_NEW_CONTACT:
                g_value_set_boolean (
-                       value, e_contact_editor->is_new_contact);
+                       value, e_contact_editor->priv->is_new_contact);
                break;
 
        case PROP_EDITABLE:
                g_value_set_boolean (
-                       value, e_contact_editor->target_editable);
+                       value, e_contact_editor->priv->target_editable);
                break;
 
        case PROP_CHANGED:
                g_value_set_boolean (
-                       value, e_contact_editor->changed);
+                       value, e_contact_editor->priv->changed);
                break;
 
        case PROP_WRITABLE_FIELDS:
-               g_value_set_pointer (value, e_contact_editor->writable_fields);
+               g_value_set_pointer (value, e_contact_editor->priv->writable_fields);
                break;
        case PROP_REQUIRED_FIELDS:
-               g_value_set_pointer (value, e_contact_editor->required_fields);
+               g_value_set_pointer (value, e_contact_editor->priv->required_fields);
                break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -4646,7 +4789,7 @@ e_contact_editor_raise (EABEditor *editor)
        EContactEditor *ce = E_CONTACT_EDITOR (editor);
        GdkWindow *window;
 
-       window = gtk_widget_get_window (ce->app);
+       window = gtk_widget_get_window (ce->priv->app);
 
        if (window != NULL)
                gdk_window_raise (window);
@@ -4662,5 +4805,5 @@ static void
 e_contact_editor_show (EABEditor *editor)
 {
        EContactEditor *ce = E_CONTACT_EDITOR (editor);
-       gtk_widget_show (ce->app);
+       gtk_widget_show (ce->priv->app);
 }
diff --git a/addressbook/gui/contact-editor/e-contact-editor.h 
b/addressbook/gui/contact-editor/e-contact-editor.h
index aa9448d..41f9087 100644
--- a/addressbook/gui/contact-editor/e-contact-editor.h
+++ b/addressbook/gui/contact-editor/e-contact-editor.h
@@ -45,58 +45,12 @@ G_BEGIN_DECLS
 
 typedef struct _EContactEditor       EContactEditor;
 typedef struct _EContactEditorClass  EContactEditorClass;
+typedef struct _EContactEditorPrivate EContactEditorPrivate;
 
 struct _EContactEditor
 {
        EABEditor object;
-
-       /* item specific fields */
-       EBookClient *source_client;
-       EBookClient *target_client;
-       EContact *contact;
-
-       GtkBuilder *builder;
-       GtkWidget *app;
-
-       GtkWidget *file_selector;
-
-       EContactName *name;
-
-       /* Whether we are editing a new contact or an existing one */
-       guint is_new_contact : 1;
-
-       /* Whether an image is associated with a contact. */
-       guint image_set : 1;
-
-       /* Whether the contact has been changed since bringing up the contact editor */
-       guint changed : 1;
-
-       /* Wheter should check for contact to merge. Only when name or email are changed */
-       guint check_merge : 1;
-
-       /* Whether the contact editor will accept modifications, save */
-       guint target_editable : 1;
-
-       /* Whether an async wombat call is in progress */
-       guint in_async_call : 1;
-
-       /* Whether an image is changed */
-       guint image_changed : 1;
-
-       /* Whether to try to reduce space used */
-       guint compress_ui : 1;
-
-       GSList *writable_fields;
-
-       GSList *required_fields;
-
-       GCancellable *cancellable;
-
-       /* signal ids for "writable_status" */
-       gint target_editable_id;
-
-       GtkWidget *fullname_dialog;
-       GtkWidget *categories_dialog;
+       EContactEditorPrivate *priv;
 };
 
 struct _EContactEditorClass
diff --git a/calendar/gui/dialogs/comp-editor.c b/calendar/gui/dialogs/comp-editor.c
index bbf4662..f635ab7 100644
--- a/calendar/gui/dialogs/comp-editor.c
+++ b/calendar/gui/dialogs/comp-editor.c
@@ -158,6 +158,9 @@ static const gchar *ui =
 "      <menuitem action='close'/>"
 "    </menu>"
 "    <menu action='edit-menu'>"
+"      <menuitem action='undo'/>"
+"      <menuitem action='redo'/>"
+"      <separator/>"
 "      <menuitem action='cut-clipboard'/>"
 "      <menuitem action='copy-clipboard'/>"
 "      <menuitem action='paste-clipboard'/>"
@@ -179,6 +182,9 @@ static const gchar *ui =
 "    <toolitem action='save'/>\n"
 "    <toolitem action='print'/>\n"
 "    <separator/>"
+"    <toolitem action='undo'/>"
+"    <toolitem action='redo'/>"
+"    <separator/>"
 "    <placeholder name='content'/>\n"
 "  </toolbar>"
 "</ui>";
@@ -1288,6 +1294,21 @@ static GtkActionEntry core_entries[] = {
          N_("Select all text"),
          NULL },  /* Handled by EFocusTracker */
 
+       { "undo",
+         GTK_STOCK_UNDO,
+         NULL,
+         "<Control>z",
+         N_("Undo"),
+         NULL },  /* Handled by EFocusTracker */
+
+       { "redo",
+         GTK_STOCK_REDO,
+         NULL,
+         "<Control>y",
+         N_("Redo"),
+         NULL },  /* Handled by EFocusTracker */
+
+
        /* Menus */
 
        { "classification-menu",
@@ -2199,6 +2220,12 @@ comp_editor_init (CompEditor *editor)
        action = comp_editor_get_action (editor, "select-all");
        e_focus_tracker_set_select_all_action (focus_tracker, action);
 
+       action = comp_editor_get_action (editor, "undo");
+       e_focus_tracker_set_undo_action (focus_tracker, action);
+
+       action = comp_editor_get_action (editor, "redo");
+       e_focus_tracker_set_redo_action (focus_tracker, action);
+
        priv->focus_tracker = focus_tracker;
 
        /* Fine Tuning */
diff --git a/calendar/gui/dialogs/event-page.c b/calendar/gui/dialogs/event-page.c
index 7f2e27e..0eafc08 100644
--- a/calendar/gui/dialogs/event-page.c
+++ b/calendar/gui/dialogs/event-page.c
@@ -1384,6 +1384,11 @@ event_page_fill_widgets (CompEditorPage *page,
 
        sensitize_widgets (epage);
 
+       e_widget_undo_reset (priv->summary);
+       e_widget_undo_reset (priv->location);
+       e_widget_undo_reset (priv->categories);
+       e_widget_undo_reset (priv->description);
+
        return validated;
 }
 
@@ -3585,6 +3590,7 @@ event_page_construct (EventPage *epage,
        EShell *shell;
        CompEditor *editor;
        ESourceRegistry *registry;
+       EFocusTracker *focus_tracker;
        GtkComboBox *combo_box;
        GtkListStore *list_store;
        GtkTreeModel *model;
@@ -3593,6 +3599,7 @@ event_page_construct (EventPage *epage,
 
        editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (epage));
        shell = comp_editor_get_shell (editor);
+       focus_tracker = comp_editor_get_focus_tracker (editor);
 
        priv = epage->priv;
        priv->meeting_store = g_object_ref (meeting_store);
@@ -3614,6 +3621,10 @@ event_page_construct (EventPage *epage,
        }
 
        e_spell_text_view_attach (GTK_TEXT_VIEW (priv->description));
+       e_widget_undo_attach (priv->summary, focus_tracker);
+       e_widget_undo_attach (priv->location, focus_tracker);
+       e_widget_undo_attach (priv->categories, focus_tracker);
+       e_widget_undo_attach (priv->description, focus_tracker);
 
        /* Create entry completion and attach it to the entry */
        priv->location_completion = gtk_entry_completion_new ();
diff --git a/calendar/gui/dialogs/memo-page.c b/calendar/gui/dialogs/memo-page.c
index 43b07c4..12126a6 100644
--- a/calendar/gui/dialogs/memo-page.c
+++ b/calendar/gui/dialogs/memo-page.c
@@ -374,6 +374,10 @@ memo_page_fill_widgets (CompEditorPage *page,
 
        sensitize_widgets (mpage);
 
+       e_widget_undo_reset (priv->summary_entry);
+       e_widget_undo_reset (priv->categories);
+       e_widget_undo_reset (priv->memo_content);
+
        return TRUE;
 }
 
@@ -1213,11 +1217,13 @@ memo_page_construct (MemoPage *mpage)
        CompEditor *editor;
        CompEditorFlags flags;
        ESourceRegistry *registry;
+       EFocusTracker *focus_tracker;
        EClientCache *client_cache;
 
        priv = mpage->priv;
 
        editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (mpage));
+       focus_tracker = comp_editor_get_focus_tracker (editor);
 
        flags = comp_editor_get_flags (editor);
        shell = comp_editor_get_shell (editor);
@@ -1241,6 +1247,9 @@ memo_page_construct (MemoPage *mpage)
        }
 
        e_spell_text_view_attach (GTK_TEXT_VIEW (priv->memo_content));
+       e_widget_undo_attach (priv->summary_entry, focus_tracker);
+       e_widget_undo_attach (priv->categories, focus_tracker);
+       e_widget_undo_attach (priv->memo_content, focus_tracker);
 
        if (flags & COMP_EDITOR_IS_SHARED) {
                GtkComboBox *combo_box;
diff --git a/calendar/gui/dialogs/task-page.c b/calendar/gui/dialogs/task-page.c
index 35bf3aa..1acc93f 100644
--- a/calendar/gui/dialogs/task-page.c
+++ b/calendar/gui/dialogs/task-page.c
@@ -921,6 +921,11 @@ task_page_fill_widgets (CompEditorPage *page,
 
        sensitize_widgets (tpage);
 
+       e_widget_undo_reset (priv->summary);
+       e_widget_undo_reset (priv->categories);
+       e_widget_undo_reset (priv->web_page_entry);
+       e_widget_undo_reset (priv->description);
+
        return TRUE;
 }
 
@@ -2652,6 +2657,7 @@ task_page_construct (TaskPage *tpage,
        EShell *shell;
        CompEditor *editor;
        ESourceRegistry *registry;
+       EFocusTracker *focus_tracker;
        TaskPagePrivate *priv;
        GtkComboBox *combo_box;
        GtkListStore *list_store;
@@ -2661,6 +2667,7 @@ task_page_construct (TaskPage *tpage,
 
        editor = comp_editor_page_get_editor (COMP_EDITOR_PAGE (tpage));
        shell = comp_editor_get_shell (editor);
+       focus_tracker = comp_editor_get_focus_tracker (editor);
 
        priv = tpage->priv;
        priv->meeting_store = g_object_ref (meeting_store);
@@ -2684,6 +2691,10 @@ task_page_construct (TaskPage *tpage,
        }
 
        e_spell_text_view_attach (GTK_TEXT_VIEW (priv->description));
+       e_widget_undo_attach (priv->summary, focus_tracker);
+       e_widget_undo_attach (priv->categories, focus_tracker);
+       e_widget_undo_attach (priv->web_page_entry, focus_tracker);
+       e_widget_undo_attach (priv->description, focus_tracker);
 
        combo_box = GTK_COMBO_BOX (priv->organizer);
        model = gtk_combo_box_get_model (combo_box);
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index cf13b39..e6321db 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -307,6 +307,7 @@ evolution_util_include_HEADERS =  \
        e-web-view-gtkhtml.h \
        e-web-view-preview.h \
        e-web-view.h \
+       e-widget-undo.h \
        e-xml-utils.h \
        ea-calendar-cell.h \
        ea-calendar-item.h \
@@ -548,6 +549,7 @@ libevolution_util_la_SOURCES = \
        e-web-view-gtkhtml.c \
        e-web-view-preview.c \
        e-web-view.c \
+       e-widget-undo.c \
        e-xml-utils.c \
        ea-calendar-cell.c \
        ea-calendar-item.c \
diff --git a/e-util/e-focus-tracker.c b/e-util/e-focus-tracker.c
index 958eda8..36d2421 100644
--- a/e-util/e-focus-tracker.c
+++ b/e-util/e-focus-tracker.c
@@ -27,6 +27,7 @@
 #include <glib/gi18n-lib.h>
 
 #include "e-selectable.h"
+#include "e-widget-undo.h"
 
 #define E_FOCUS_TRACKER_GET_PRIVATE(obj) \
        (G_TYPE_INSTANCE_GET_PRIVATE \
@@ -41,6 +42,8 @@ struct _EFocusTrackerPrivate {
        GtkAction *paste_clipboard;
        GtkAction *delete_selection;
        GtkAction *select_all;
+       GtkAction *undo;
+       GtkAction *redo;
 };
 
 enum {
@@ -51,7 +54,9 @@ enum {
        PROP_COPY_CLIPBOARD_ACTION,
        PROP_PASTE_CLIPBOARD_ACTION,
        PROP_DELETE_SELECTION_ACTION,
-       PROP_SELECT_ALL_ACTION
+       PROP_SELECT_ALL_ACTION,
+       PROP_UNDO_ACTION,
+       PROP_REDO_ACTION
 };
 
 G_DEFINE_TYPE (
@@ -83,6 +88,55 @@ focus_tracker_disable_actions (EFocusTracker *focus_tracker)
        action = e_focus_tracker_get_select_all_action (focus_tracker);
        if (action != NULL)
                gtk_action_set_sensitive (action, FALSE);
+
+       action = e_focus_tracker_get_undo_action (focus_tracker);
+       if (action != NULL)
+               gtk_action_set_sensitive (action, FALSE);
+
+       action = e_focus_tracker_get_redo_action (focus_tracker);
+       if (action != NULL)
+               gtk_action_set_sensitive (action, FALSE);
+}
+
+static void
+focus_tracker_update_undo_redo (EFocusTracker *focus_tracker,
+                               GtkWidget *widget,
+                               gboolean can_edit_text)
+{
+       GtkAction *action;
+       gboolean sensitive;
+
+       action = e_focus_tracker_get_undo_action (focus_tracker);
+       if (action != NULL) {
+               sensitive = can_edit_text && widget && e_widget_undo_has_undo (widget);
+               gtk_action_set_sensitive (action, sensitive);
+
+               if (sensitive) {
+                       gchar *description;
+
+                       description = e_widget_undo_describe_undo (widget);
+                       gtk_action_set_tooltip (action, description);
+                       g_free (description);
+               } else {
+                       gtk_action_set_tooltip (action, _("Undo"));
+               }
+       }
+
+       action = e_focus_tracker_get_redo_action (focus_tracker);
+       if (action != NULL) {
+               sensitive = can_edit_text && widget && e_widget_undo_has_redo (widget);
+               gtk_action_set_sensitive (action, sensitive);
+
+               if (sensitive) {
+                       gchar *description;
+
+                       description = e_widget_undo_describe_redo (widget);
+                       gtk_action_set_tooltip (action, description);
+                       g_free (description);
+               } else {
+                       gtk_action_set_tooltip (action, _("Redo"));
+               }
+       }
 }
 
 static void
@@ -140,6 +194,65 @@ focus_tracker_editable_update_actions (EFocusTracker *focus_tracker,
                gtk_action_set_sensitive (action, sensitive);
                gtk_action_set_tooltip (action, _("Select all text"));
        }
+
+       focus_tracker_update_undo_redo (focus_tracker, GTK_WIDGET (editable), can_edit_text);
+}
+
+
+static void
+focus_tracker_text_view_update_actions (EFocusTracker *focus_tracker,
+                                       GtkTextView *text_view,
+                                       GdkAtom *targets,
+                                       gint n_targets)
+{
+       GtkAction *action;
+       GtkTextBuffer *buffer;
+       gboolean can_edit_text;
+       gboolean clipboard_has_text;
+       gboolean text_is_selected;
+       gboolean sensitive;
+
+       buffer = gtk_text_view_get_buffer (text_view);
+       can_edit_text = gtk_text_view_get_editable (text_view);
+       clipboard_has_text = (targets != NULL) && gtk_targets_include_text (targets, n_targets);
+       text_is_selected = gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL);
+
+       action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+       if (action != NULL) {
+               sensitive = can_edit_text && text_is_selected;
+               gtk_action_set_sensitive (action, sensitive);
+               gtk_action_set_tooltip (action, _("Cut the selection"));
+       }
+
+       action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+       if (action != NULL) {
+               sensitive = text_is_selected;
+               gtk_action_set_sensitive (action, sensitive);
+               gtk_action_set_tooltip (action, _("Copy the selection"));
+       }
+
+       action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+       if (action != NULL) {
+               sensitive = can_edit_text && clipboard_has_text;
+               gtk_action_set_sensitive (action, sensitive);
+               gtk_action_set_tooltip (action, _("Paste the clipboard"));
+       }
+
+       action = e_focus_tracker_get_delete_selection_action (focus_tracker);
+       if (action != NULL) {
+               sensitive = can_edit_text && text_is_selected;
+               gtk_action_set_sensitive (action, sensitive);
+               gtk_action_set_tooltip (action, _("Delete the selection"));
+       }
+
+       action = e_focus_tracker_get_select_all_action (focus_tracker);
+       if (action != NULL) {
+               sensitive = TRUE;  /* always enabled */
+               gtk_action_set_sensitive (action, sensitive);
+               gtk_action_set_tooltip (action, _("Select all text"));
+       }
+
+       focus_tracker_update_undo_redo (focus_tracker, GTK_WIDGET (text_view), can_edit_text);
 }
 
 static void
@@ -181,6 +294,14 @@ focus_tracker_selectable_update_actions (EFocusTracker *focus_tracker,
        action = e_focus_tracker_get_select_all_action (focus_tracker);
        if (action != NULL && interface->select_all == NULL)
                gtk_action_set_sensitive (action, FALSE);
+
+       action = e_focus_tracker_get_undo_action (focus_tracker);
+       if (action != NULL && interface->undo == NULL)
+               gtk_action_set_sensitive (action, FALSE);
+
+       action = e_focus_tracker_get_redo_action (focus_tracker);
+       if (action != NULL && interface->redo == NULL)
+               gtk_action_set_sensitive (action, FALSE);
 }
 
 static void
@@ -196,16 +317,22 @@ focus_tracker_targets_received_cb (GtkClipboard *clipboard,
        if (focus == NULL)
                focus_tracker_disable_actions (focus_tracker);
 
+       else if (E_IS_SELECTABLE (focus))
+               focus_tracker_selectable_update_actions (
+                       focus_tracker, E_SELECTABLE (focus),
+                       targets, n_targets);
+
        else if (GTK_IS_EDITABLE (focus))
                focus_tracker_editable_update_actions (
                        focus_tracker, GTK_EDITABLE (focus),
                        targets, n_targets);
 
-       else if (E_IS_SELECTABLE (focus))
-               focus_tracker_selectable_update_actions (
-                       focus_tracker, E_SELECTABLE (focus),
+       else if (GTK_IS_TEXT_VIEW (focus))
+               focus_tracker_text_view_update_actions (
+                       focus_tracker, GTK_TEXT_VIEW (focus),
                        targets, n_targets);
 
+
        g_object_unref (focus_tracker);
 }
 
@@ -215,10 +342,13 @@ focus_tracker_set_focus_cb (GtkWindow *window,
                             EFocusTracker *focus_tracker)
 {
        while (focus != NULL) {
+               if (E_IS_SELECTABLE (focus))
+                       break;
+
                if (GTK_IS_EDITABLE (focus))
                        break;
 
-               if (E_IS_SELECTABLE (focus))
+               if (GTK_IS_TEXT_VIEW (focus))
                        break;
 
                focus = gtk_widget_get_parent (focus);
@@ -289,6 +419,18 @@ focus_tracker_set_property (GObject *object,
                                E_FOCUS_TRACKER (object),
                                g_value_get_object (value));
                        return;
+
+               case PROP_UNDO_ACTION:
+                       e_focus_tracker_set_undo_action (
+                               E_FOCUS_TRACKER (object),
+                               g_value_get_object (value));
+                       return;
+
+               case PROP_REDO_ACTION:
+                       e_focus_tracker_set_redo_action (
+                               E_FOCUS_TRACKER (object),
+                               g_value_get_object (value));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -349,6 +491,20 @@ focus_tracker_get_property (GObject *object,
                                e_focus_tracker_get_select_all_action (
                                E_FOCUS_TRACKER (object)));
                        return;
+
+               case PROP_UNDO_ACTION:
+                       g_value_set_object (
+                               value,
+                               e_focus_tracker_get_undo_action (
+                               E_FOCUS_TRACKER (object)));
+                       return;
+
+               case PROP_REDO_ACTION:
+                       g_value_set_object (
+                               value,
+                               e_focus_tracker_get_redo_action (
+                               E_FOCUS_TRACKER (object)));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -534,6 +690,26 @@ e_focus_tracker_class_init (EFocusTrackerClass *class)
                        NULL,
                        GTK_TYPE_ACTION,
                        G_PARAM_READWRITE));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_UNDO_ACTION,
+               g_param_spec_object (
+                       "undo-action",
+                       "Undo Action",
+                       NULL,
+                       GTK_TYPE_ACTION,
+                       G_PARAM_READWRITE));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_REDO_ACTION,
+               g_param_spec_object (
+                       "redo-action",
+                       "Redo Action",
+                       NULL,
+                       GTK_TYPE_ACTION,
+                       G_PARAM_READWRITE));
 }
 
 static void
@@ -787,6 +963,82 @@ e_focus_tracker_set_select_all_action (EFocusTracker *focus_tracker,
        g_object_notify (G_OBJECT (focus_tracker), "select-all-action");
 }
 
+GtkAction *
+e_focus_tracker_get_undo_action (EFocusTracker *focus_tracker)
+{
+       g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+       return focus_tracker->priv->undo;
+}
+
+void
+e_focus_tracker_set_undo_action (EFocusTracker *focus_tracker,
+                                GtkAction *undo)
+{
+       g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+       if (undo != NULL) {
+               g_return_if_fail (GTK_IS_ACTION (undo));
+               g_object_ref (undo);
+       }
+
+       if (focus_tracker->priv->undo != NULL) {
+               g_signal_handlers_disconnect_matched (
+                       focus_tracker->priv->undo,
+                       G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+                       focus_tracker);
+               g_object_unref (focus_tracker->priv->undo);
+       }
+
+       focus_tracker->priv->undo = undo;
+
+       if (undo != NULL)
+               g_signal_connect_swapped (
+                       undo, "activate",
+                       G_CALLBACK (e_focus_tracker_undo),
+                       focus_tracker);
+
+       g_object_notify (G_OBJECT (focus_tracker), "undo-action");
+}
+
+GtkAction *
+e_focus_tracker_get_redo_action (EFocusTracker *focus_tracker)
+{
+       g_return_val_if_fail (E_IS_FOCUS_TRACKER (focus_tracker), NULL);
+
+       return focus_tracker->priv->redo;
+}
+
+void
+e_focus_tracker_set_redo_action (EFocusTracker *focus_tracker,
+                                GtkAction *redo)
+{
+       g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+       if (redo != NULL) {
+               g_return_if_fail (GTK_IS_ACTION (redo));
+               g_object_ref (redo);
+       }
+
+       if (focus_tracker->priv->redo != NULL) {
+               g_signal_handlers_disconnect_matched (
+                       focus_tracker->priv->redo,
+                       G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
+                       focus_tracker);
+               g_object_unref (focus_tracker->priv->redo);
+       }
+
+       focus_tracker->priv->redo = redo;
+
+       if (redo != NULL)
+               g_signal_connect_swapped (
+                       redo, "activate",
+                       G_CALLBACK (e_focus_tracker_redo),
+                       focus_tracker);
+
+       g_object_notify (G_OBJECT (focus_tracker), "redo-action");
+}
+
 void
 e_focus_tracker_update_actions (EFocusTracker *focus_tracker)
 {
@@ -813,11 +1065,21 @@ e_focus_tracker_cut_clipboard (EFocusTracker *focus_tracker)
 
        focus = e_focus_tracker_get_focus (focus_tracker);
 
-       if (GTK_IS_EDITABLE (focus))
+       if (E_IS_SELECTABLE (focus))
+               e_selectable_cut_clipboard (E_SELECTABLE (focus));
+
+       else if (GTK_IS_EDITABLE (focus))
                gtk_editable_cut_clipboard (GTK_EDITABLE (focus));
 
-       else if (E_IS_SELECTABLE (focus))
-               e_selectable_cut_clipboard (E_SELECTABLE (focus));
+       else if (GTK_IS_TEXT_VIEW (focus)) {
+               GtkTextView *text_view = GTK_TEXT_VIEW (focus);
+               GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+               gboolean is_editable = gtk_text_view_get_editable (text_view);
+
+               gtk_text_buffer_cut_clipboard (buffer,
+                       gtk_widget_get_clipboard (focus, GDK_SELECTION_CLIPBOARD),
+                       is_editable);
+       }
 }
 
 void
@@ -829,11 +1091,18 @@ e_focus_tracker_copy_clipboard (EFocusTracker *focus_tracker)
 
        focus = e_focus_tracker_get_focus (focus_tracker);
 
-       if (GTK_IS_EDITABLE (focus))
+       if (E_IS_SELECTABLE (focus))
+               e_selectable_copy_clipboard (E_SELECTABLE (focus));
+
+       else if (GTK_IS_EDITABLE (focus))
                gtk_editable_copy_clipboard (GTK_EDITABLE (focus));
 
-       else if (E_IS_SELECTABLE (focus))
-               e_selectable_copy_clipboard (E_SELECTABLE (focus));
+       else if (GTK_IS_TEXT_VIEW (focus)) {
+               GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (focus));
+
+               gtk_text_buffer_copy_clipboard (buffer,
+                       gtk_widget_get_clipboard (focus, GDK_SELECTION_CLIPBOARD));
+       }
 }
 
 void
@@ -845,11 +1114,21 @@ e_focus_tracker_paste_clipboard (EFocusTracker *focus_tracker)
 
        focus = e_focus_tracker_get_focus (focus_tracker);
 
-       if (GTK_IS_EDITABLE (focus))
+       if (E_IS_SELECTABLE (focus))
+               e_selectable_paste_clipboard (E_SELECTABLE (focus));
+
+       else if (GTK_IS_EDITABLE (focus))
                gtk_editable_paste_clipboard (GTK_EDITABLE (focus));
 
-       else if (E_IS_SELECTABLE (focus))
-               e_selectable_paste_clipboard (E_SELECTABLE (focus));
+       else if (GTK_IS_TEXT_VIEW (focus)) {
+               GtkTextView *text_view = GTK_TEXT_VIEW (focus);
+               GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+               gboolean is_editable = gtk_text_view_get_editable (text_view);
+
+               gtk_text_buffer_paste_clipboard (buffer,
+                       gtk_widget_get_clipboard (focus, GDK_SELECTION_CLIPBOARD),
+                       NULL, is_editable);
+       }
 }
 
 void
@@ -861,11 +1140,19 @@ e_focus_tracker_delete_selection (EFocusTracker *focus_tracker)
 
        focus = e_focus_tracker_get_focus (focus_tracker);
 
-       if (GTK_IS_EDITABLE (focus))
+       if (E_IS_SELECTABLE (focus))
+               e_selectable_delete_selection (E_SELECTABLE (focus));
+
+       else if (GTK_IS_EDITABLE (focus))
                gtk_editable_delete_selection (GTK_EDITABLE (focus));
 
-       else if (E_IS_SELECTABLE (focus))
-               e_selectable_delete_selection (E_SELECTABLE (focus));
+       else if (GTK_IS_TEXT_VIEW (focus)) {
+               GtkTextView *text_view = GTK_TEXT_VIEW (focus);
+               GtkTextBuffer *buffer = gtk_text_view_get_buffer (text_view);
+               gboolean is_editable = gtk_text_view_get_editable (text_view);
+
+               gtk_text_buffer_delete_selection (buffer, TRUE, is_editable);
+       }
 }
 
 void
@@ -877,9 +1164,47 @@ e_focus_tracker_select_all (EFocusTracker *focus_tracker)
 
        focus = e_focus_tracker_get_focus (focus_tracker);
 
-       if (GTK_IS_EDITABLE (focus))
+       if (E_IS_SELECTABLE (focus))
+               e_selectable_select_all (E_SELECTABLE (focus));
+
+       else if (GTK_IS_EDITABLE (focus))
                gtk_editable_select_region (GTK_EDITABLE (focus), 0, -1);
 
-       else if (E_IS_SELECTABLE (focus))
-               e_selectable_select_all (E_SELECTABLE (focus));
+       else if (GTK_IS_TEXT_VIEW (focus)) {
+               GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (focus));
+               GtkTextIter start, end;
+
+               gtk_text_buffer_get_bounds (buffer, &start, &end);
+               gtk_text_buffer_select_range (buffer, &start, &end);
+       }
+}
+
+void
+e_focus_tracker_undo (EFocusTracker *focus_tracker)
+{
+       GtkWidget *focus;
+
+       g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+       focus = e_focus_tracker_get_focus (focus_tracker);
+
+       if (E_IS_SELECTABLE (focus))
+               e_selectable_undo (E_SELECTABLE (focus));
+       else
+               e_widget_undo_do_undo (focus);
+}
+
+void
+e_focus_tracker_redo (EFocusTracker *focus_tracker)
+{
+       GtkWidget *focus;
+
+       g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+       focus = e_focus_tracker_get_focus (focus_tracker);
+
+       if (E_IS_SELECTABLE (focus))
+               e_selectable_redo (E_SELECTABLE (focus));
+       else
+               e_widget_undo_do_redo (focus);
 }
diff --git a/e-util/e-focus-tracker.h b/e-util/e-focus-tracker.h
index b837e52..a4e5a4e 100644
--- a/e-util/e-focus-tracker.h
+++ b/e-util/e-focus-tracker.h
@@ -90,6 +90,12 @@ GtkAction *  e_focus_tracker_get_select_all_action
 void           e_focus_tracker_set_select_all_action
                                                (EFocusTracker *focus_tracker,
                                                 GtkAction *select_all);
+GtkAction *    e_focus_tracker_get_undo_action (EFocusTracker *focus_tracker);
+void           e_focus_tracker_set_undo_action (EFocusTracker *focus_tracker,
+                                                GtkAction *undo);
+GtkAction *    e_focus_tracker_get_redo_action (EFocusTracker *focus_tracker);
+void           e_focus_tracker_set_redo_action (EFocusTracker *focus_tracker,
+                                                GtkAction *redo);
 void           e_focus_tracker_update_actions  (EFocusTracker *focus_tracker);
 void           e_focus_tracker_cut_clipboard   (EFocusTracker *focus_tracker);
 void           e_focus_tracker_copy_clipboard  (EFocusTracker *focus_tracker);
@@ -97,6 +103,8 @@ void         e_focus_tracker_paste_clipboard (EFocusTracker *focus_tracker);
 void           e_focus_tracker_delete_selection
                                                (EFocusTracker *focus_tracker);
 void           e_focus_tracker_select_all      (EFocusTracker *focus_tracker);
+void           e_focus_tracker_undo            (EFocusTracker *focus_tracker);
+void           e_focus_tracker_redo            (EFocusTracker *focus_tracker);
 
 G_END_DECLS
 
diff --git a/e-util/e-selectable.c b/e-util/e-selectable.c
index d19adb8..6b011ee 100644
--- a/e-util/e-selectable.c
+++ b/e-util/e-selectable.c
@@ -134,6 +134,32 @@ e_selectable_select_all (ESelectable *selectable)
                interface->select_all (selectable);
 }
 
+void
+e_selectable_undo (ESelectable *selectable)
+{
+       ESelectableInterface *interface;
+
+       g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+       interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+       if (interface->undo != NULL)
+               interface->undo (selectable);
+}
+
+void
+e_selectable_redo (ESelectable *selectable)
+{
+       ESelectableInterface *interface;
+
+       g_return_if_fail (E_IS_SELECTABLE (selectable));
+
+       interface = E_SELECTABLE_GET_INTERFACE (selectable);
+
+       if (interface->redo != NULL)
+               interface->redo (selectable);
+}
+
 GtkTargetList *
 e_selectable_get_copy_target_list (ESelectable *selectable)
 {
diff --git a/e-util/e-selectable.h b/e-util/e-selectable.h
index c03cb3d..2d92941 100644
--- a/e-util/e-selectable.h
+++ b/e-util/e-selectable.h
@@ -62,6 +62,8 @@ struct _ESelectableInterface {
        void            (*paste_clipboard)      (ESelectable *selectable);
        void            (*delete_selection)     (ESelectable *selectable);
        void            (*select_all)           (ESelectable *selectable);
+       void            (*undo)                 (ESelectable *selectable);
+       void            (*redo)                 (ESelectable *selectable);
 };
 
 GType          e_selectable_get_type           (void) G_GNUC_CONST;
@@ -74,6 +76,8 @@ void          e_selectable_copy_clipboard     (ESelectable *selectable);
 void           e_selectable_paste_clipboard    (ESelectable *selectable);
 void           e_selectable_delete_selection   (ESelectable *selectable);
 void           e_selectable_select_all         (ESelectable *selectable);
+void           e_selectable_undo               (ESelectable *selectable);
+void           e_selectable_redo               (ESelectable *selectable);
 GtkTargetList *        e_selectable_get_copy_target_list
                                                (ESelectable *selectable);
 GtkTargetList *        e_selectable_get_paste_target_list
diff --git a/e-util/e-util.h b/e-util/e-util.h
index aa2521f..784858e 100644
--- a/e-util/e-util.h
+++ b/e-util/e-util.h
@@ -223,6 +223,7 @@
 #include <e-util/e-web-view-gtkhtml.h>
 #include <e-util/e-web-view-preview.h>
 #include <e-util/e-web-view.h>
+#include <e-util/e-widget-undo.h>
 #include <e-util/e-xml-utils.h>
 #include <e-util/ea-cell-table.h>
 #include <e-util/ea-factory.h>
diff --git a/e-util/e-widget-undo.c b/e-util/e-widget-undo.c
new file mode 100644
index 0000000..4cb933f
--- /dev/null
+++ b/e-util/e-widget-undo.c
@@ -0,0 +1,891 @@
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms 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 program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Authors:
+ *             Milan Crha <mcrha redhat com>
+ *
+ * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <string.h>
+
+#include "e-focus-tracker.h"
+#include "e-widget-undo.h"
+
+#define DEFAULT_MAX_UNDO_LEVEL 256
+#define UNDO_DATA_KEY "e-undo-data-ptr"
+
+/* calculates real index in EUndoData::undo_stack */
+#define REAL_INDEX(x) ((data->undo_from + (x) + 2 * data->undo_len) % data->undo_len)
+
+typedef enum {
+       E_UNDO_INSERT,
+       E_UNDO_DELETE
+} EUndoType;
+
+typedef enum {
+       E_UNDO_DO_UNDO,
+       E_UNDO_DO_REDO
+} EUndoDoType;
+
+typedef struct _EUndoInfo {
+       EUndoType type;
+       gchar *text;
+       gint position_start;
+       gint position_end; /* valid for delete type only */
+} EUndoInfo;
+
+typedef struct _EUndoData {
+       EUndoInfo **undo_stack; /* stack for undo, with max_undo_level elements, some are NULL */
+       gint undo_len; /* how many undo actions can be saved */
+       gint undo_from; /* where the first undo action begins */
+       gint n_undos; /* how many undo actions are saved;
+               [(undo_from + n_undos) % undo_len] is the next free undo item (or the first redo) */
+       gint n_redos; /* how many redo actions are saved */
+
+       EUndoInfo *current_info; /* the top undo action */
+
+       gulong insert_handler_id;
+       gulong delete_handler_id;
+} EUndoData;
+
+static void
+free_undo_info (gpointer ptr)
+{
+       EUndoInfo *info = ptr;
+
+       if (info) {
+               g_free (info->text);
+               g_free (info);
+       }
+}
+
+static void
+free_undo_data (gpointer ptr)
+{
+       EUndoData *data = ptr;
+
+       if (data) {
+               gint ii;
+
+               for (ii = 0; ii < data->undo_len; ii++) {
+                       free_undo_info (data->undo_stack[ii]);
+               }
+               g_free (data);
+       }
+}
+
+static void
+reset_redos (EUndoData *data)
+{
+       gint ii, index;
+
+       for (ii = 0; ii < data->n_redos; ii++) {
+               index = REAL_INDEX (data->n_undos + ii);
+
+               free_undo_info (data->undo_stack[index]);
+               data->undo_stack[index] = NULL;
+       }
+
+       data->n_redos = 0;
+}
+
+static void
+push_undo (EUndoData *data,
+          EUndoInfo *info)
+{
+       gint index;
+
+       reset_redos (data);
+
+       if (data->n_undos == data->undo_len) {
+               data->undo_from = (data->undo_from + 1) % data->undo_len;
+       } else {
+               data->n_undos++;
+       }
+
+       index = REAL_INDEX (data->n_undos - 1);
+       free_undo_info (data->undo_stack[index]);
+       data->undo_stack[index] = info;
+}
+
+static gboolean
+can_merge_insert_undos (EUndoInfo *current_info,
+                       const gchar *text,
+                       gint text_len,
+                       gint position)
+{
+       gint len;
+
+       /* allow only one letter merge */
+       if (!current_info || current_info->type != E_UNDO_INSERT ||
+           !text || text_len <= 0 || text_len > 1)
+               return FALSE;
+
+       if (text[0] == '\r' || text[0] == '\n')
+               return FALSE;
+
+       len = strlen (current_info->text);
+       if (position != current_info->position_start + len)
+               return FALSE;
+
+       if (g_ascii_isspace (text[0])) {
+               if (len <= 0 || !g_ascii_isspace (current_info->text[len - 1]))
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+push_insert_undo (GObject *object,
+                 const gchar *text,
+                 gint text_len,
+                 gint position)
+{
+       EUndoData *data;
+       EUndoInfo *info;
+
+       data = g_object_get_data (object, UNDO_DATA_KEY);
+       if (!data) {
+               g_warn_if_reached ();
+               return;
+       }
+
+       /* one letter long text, divide undos on spaces */
+       if (data->current_info &&
+           can_merge_insert_undos (data->current_info, text, text_len, position)) {
+               gchar *new_text;
+
+               new_text = g_strdup_printf ("%s%*s", data->current_info->text, text_len, text);
+               g_free (data->current_info->text);
+               data->current_info->text = new_text;
+
+               return;
+       }
+
+       info = g_new0 (EUndoInfo, 1);
+       info->type = E_UNDO_INSERT;
+       info->text = g_strndup (text, text_len);
+       info->position_start = position;
+
+       push_undo (data, info);
+
+       data->current_info = info;
+}
+
+static void
+push_delete_undo (GObject *object,
+                 gchar *text, /* takes ownership of the 'text' */
+                 gint position_start,
+                 gint position_end)
+{
+       EUndoData *data;
+       EUndoInfo *info;
+
+       data = g_object_get_data (object, UNDO_DATA_KEY);
+       if (!data) {
+               g_warn_if_reached ();
+               return;
+       }
+
+       if (data->current_info && data->current_info->type == E_UNDO_DELETE &&
+           position_end - position_start == 1 && !g_ascii_isspace (*text)) {
+               info = data->current_info;
+
+               if (info->position_start == position_start) {
+                       gchar *new_text;
+
+                       new_text = g_strconcat (info->text, text, NULL);
+                       g_free (info->text);
+                       info->text = new_text;
+                       g_free (text);
+
+                       info->position_end++;
+
+                       return;
+               } else if (data->current_info->position_start == position_end) {
+                       gchar *new_text;
+
+                       new_text = g_strconcat (text, info->text, NULL);
+                       g_free (info->text);
+                       info->text = new_text;
+                       g_free (text);
+
+                       info->position_start = position_start;
+
+                       return;
+               }
+       }
+
+       info = g_new0 (EUndoInfo, 1);
+       info->type = E_UNDO_DELETE;
+       info->text = text;
+       info->position_start = position_start;
+       info->position_end = position_end;
+
+       push_undo (data, info);
+
+       data->current_info = info;
+}
+
+static void
+editable_undo_insert_text_cb (GtkEditable *editable,
+                             gchar *text,
+                             gint text_length,
+                             gint *position,
+                             gpointer user_data)
+{
+       push_insert_undo (G_OBJECT (editable), text, text_length, *position);
+}
+
+static void
+editable_undo_delete_text_cb (GtkEditable *editable,
+                             gint start_pos,
+                             gint end_pos,
+                             gpointer user_data)
+{
+       push_delete_undo (G_OBJECT (editable), gtk_editable_get_chars (editable, start_pos, end_pos), 
start_pos, end_pos);
+}
+
+static void
+editable_undo_insert_text (GObject *object,
+                          const gchar *text,
+                          gint position)
+{
+       g_return_if_fail (GTK_IS_EDITABLE (object));
+
+       gtk_editable_insert_text (GTK_EDITABLE (object), text, -1, &position);
+}
+
+static void
+editable_undo_delete_text (GObject *object,
+                          gint position_start,
+                          gint position_end)
+{
+       g_return_if_fail (GTK_IS_EDITABLE (object));
+
+       gtk_editable_delete_text (GTK_EDITABLE (object), position_start, position_end);
+}
+
+static void
+text_buffer_undo_insert_text_cb (GtkTextBuffer *text_buffer,
+                                GtkTextIter *location,
+                                gchar *text,
+                                gint text_length,
+                                gpointer user_data)
+{
+       push_insert_undo (G_OBJECT (text_buffer), text, text_length, gtk_text_iter_get_offset (location));
+}
+
+static void
+text_buffer_undo_delete_range_cb (GtkTextBuffer *text_buffer,
+                                 GtkTextIter *start,
+                                 GtkTextIter *end,
+                                 gpointer user_data)
+{
+       push_delete_undo (G_OBJECT (text_buffer),
+               gtk_text_iter_get_text (start, end),
+               gtk_text_iter_get_offset (start),
+               gtk_text_iter_get_offset (end));
+}
+
+static void
+text_buffer_undo_insert_text (GObject *object,
+                             const gchar *text,
+                             gint position)
+{
+       GtkTextBuffer *text_buffer;
+       GtkTextIter iter;
+
+       g_return_if_fail (GTK_IS_TEXT_BUFFER (object));
+
+       text_buffer = GTK_TEXT_BUFFER (object);
+
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &iter, position);
+       gtk_text_buffer_insert (text_buffer, &iter, text, -1);
+}
+
+static void
+text_buffer_undo_delete_text (GObject *object,
+                             gint position_start,
+                             gint position_end)
+{
+       GtkTextBuffer *text_buffer;
+       GtkTextIter start_iter, end_iter;
+
+       g_return_if_fail (GTK_IS_TEXT_BUFFER (object));
+
+       text_buffer = GTK_TEXT_BUFFER (object);
+
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &start_iter, position_start);
+       gtk_text_buffer_get_iter_at_offset (text_buffer, &end_iter, position_end);
+       gtk_text_buffer_delete (text_buffer, &start_iter, &end_iter);
+}
+
+static void
+widget_undo_place_cursor_at (GObject *object,
+                            gint char_pos)
+{
+       if (GTK_IS_EDITABLE (object))
+               gtk_editable_set_position (GTK_EDITABLE (object), char_pos);
+       else if (GTK_IS_TEXT_BUFFER (object)) {
+               GtkTextBuffer *buffer;
+               GtkTextIter pos;
+
+               buffer = GTK_TEXT_BUFFER (object);
+
+               gtk_text_buffer_get_iter_at_offset (buffer, &pos, char_pos);
+               gtk_text_buffer_place_cursor (buffer, &pos);
+       }
+}
+
+static void
+undo_do_something (GObject *object,
+                  EUndoDoType todo,
+                  void (* insert_func) (GObject *object, const gchar *text, gint position),
+                  void (* delete_func) (GObject *object, gint position_start, gint position_end))
+{
+       EUndoData *data;
+       EUndoInfo *info = NULL;
+
+       data = g_object_get_data (object, UNDO_DATA_KEY);
+       if (!data)
+               return;
+
+       if (todo == E_UNDO_DO_UNDO && data->n_undos > 0) {
+               info = data->undo_stack[REAL_INDEX (data->n_undos - 1)];
+               data->n_undos--;
+               data->n_redos++;
+       } else if (todo == E_UNDO_DO_REDO && data->n_redos > 0) {
+               info = data->undo_stack[REAL_INDEX (data->n_undos)];
+               data->n_undos++;
+               data->n_redos--;
+       }
+
+       if (!info)
+               return;
+
+       g_signal_handler_block (object, data->insert_handler_id);
+       g_signal_handler_block (object, data->delete_handler_id);
+
+       if (info->type == E_UNDO_INSERT) {
+               if (todo == E_UNDO_DO_UNDO) {
+                       delete_func (object, info->position_start, info->position_start + g_utf8_strlen 
(info->text, -1));
+                       widget_undo_place_cursor_at (object, info->position_start);
+               } else {
+                       insert_func (object, info->text, info->position_start);
+                       widget_undo_place_cursor_at (object, info->position_start  + g_utf8_strlen 
(info->text, -1));
+               }
+       } else if (info->type == E_UNDO_DELETE) {
+               if (todo == E_UNDO_DO_UNDO) {
+                       insert_func (object, info->text, info->position_start);
+                       widget_undo_place_cursor_at (object, info->position_start  + g_utf8_strlen 
(info->text, -1));
+               } else {
+                       delete_func (object, info->position_start, info->position_end);
+                       widget_undo_place_cursor_at (object, info->position_start);
+               }
+       }
+
+       data->current_info = NULL;
+
+       g_signal_handler_unblock (object, data->delete_handler_id);
+       g_signal_handler_unblock (object, data->insert_handler_id);
+}
+
+static gchar *
+undo_describe_info (EUndoInfo *info,
+                   EUndoDoType undo_type)
+{
+       if (!info)
+               return NULL;
+
+       if (info->type == E_UNDO_INSERT) {
+               if (undo_type == E_UNDO_DO_UNDO)
+                       return g_strdup (_("Undo 'Insert text'"));
+               else
+                       return g_strdup (_("Redo 'Insert text'"));
+               /* if (strlen (info->text) > 15) {
+                       if (undo_type == E_UNDO_DO_UNDO)
+                               return g_strdup_printf (_("Undo 'Insert '%.12s...''"), info->text);
+                       else
+                               return g_strdup_printf (_("Redo 'Insert '%.12s...''"), info->text);
+               }
+
+               if (undo_type == E_UNDO_DO_UNDO)
+                       return g_strdup_printf (_("Undo 'Insert '%s''"), info->text);
+               else
+                       return g_strdup_printf (_("Redo 'Insert '%s''"), info->text); */
+       } else if (info->type == E_UNDO_DELETE) {
+               if (undo_type == E_UNDO_DO_UNDO)
+                       return g_strdup (_("Undo 'Delete text'"));
+               else
+                       return g_strdup (_("Redo 'Delete text'"));
+               /* if (strlen (info->text) > 15) {
+                       if (undo_type == E_UNDO_DO_UNDO)
+                               return g_strdup_printf (_("Undo 'Delete '%.12s...''"), info->text);
+                       else
+                               return g_strdup_printf (_("Redo 'Delete '%.12s...''"), info->text);
+               }
+
+               if (undo_type == E_UNDO_DO_UNDO)
+                       return g_strdup_printf (_("Undo 'Delete '%s''"), info->text);
+               else
+                       return g_strdup_printf (_("Redo 'Delete '%s''"), info->text); */
+       }
+
+       return NULL;
+}
+
+static gboolean
+undo_check_undo (GObject *object,
+                gchar **description)
+{
+       EUndoData *data;
+
+       data = g_object_get_data (object, UNDO_DATA_KEY);
+       if (!data)
+               return FALSE;
+
+       if (data->n_undos <= 0)
+               return FALSE;
+
+       if (description)
+               *description = undo_describe_info (data->undo_stack[REAL_INDEX (data->n_undos - 1)], 
E_UNDO_DO_UNDO);
+
+       return TRUE;
+}
+
+static gboolean
+undo_check_redo (GObject *object,
+                gchar **description)
+{
+       EUndoData *data;
+
+       data = g_object_get_data (object, UNDO_DATA_KEY);
+       if (!data)
+               return FALSE;
+
+       if (data->n_redos <= 0)
+               return FALSE;
+
+       if (description)
+               *description = undo_describe_info (data->undo_stack[REAL_INDEX (data->n_undos)], 
E_UNDO_DO_REDO);
+
+       return TRUE;
+}
+
+static void
+undo_reset (GObject *object)
+{
+       EUndoData *data;
+
+       data = g_object_get_data (object, UNDO_DATA_KEY);
+       if (!data)
+               return;
+
+       data->n_undos = 0;
+       data->n_redos = 0;
+}
+
+static void
+widget_undo_popup_activate_cb (GObject *menu_item,
+                              GtkWidget *widget)
+{
+       EUndoDoType undo_type = GPOINTER_TO_INT (g_object_get_data (menu_item, UNDO_DATA_KEY));
+
+       if (undo_type == E_UNDO_DO_UNDO)
+               e_widget_undo_do_undo (widget);
+       else
+               e_widget_undo_do_redo (widget);
+}
+
+static gboolean
+widget_undo_prepend_popup (GtkWidget *widget,
+                          GtkMenuShell *menu,
+                          EUndoDoType undo_type,
+                          gboolean already_added)
+{
+       gchar *description = NULL;
+       const gchar *icon_name = NULL;
+
+       if (undo_type == E_UNDO_DO_UNDO && e_widget_undo_has_undo (widget)) {
+               description = e_widget_undo_describe_undo (widget);
+               icon_name = "edit-undo";
+       } else if (undo_type == E_UNDO_DO_REDO && e_widget_undo_has_redo (widget)) {
+               description = e_widget_undo_describe_redo (widget);
+               icon_name = "edit-redo";
+       }
+
+       if (description) {
+               GtkWidget *item, *image;
+
+               if (!already_added) {
+                       item = gtk_separator_menu_item_new ();
+                       gtk_widget_show (item);
+                       gtk_menu_shell_prepend (menu, item);
+
+                       already_added = TRUE;
+               }
+
+               image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
+               item = gtk_image_menu_item_new_with_label (description);
+               gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+               gtk_widget_show (item);
+
+               g_object_set_data (G_OBJECT (item), UNDO_DATA_KEY, GINT_TO_POINTER (undo_type));
+               g_signal_connect (item, "activate", G_CALLBACK (widget_undo_popup_activate_cb), widget);
+
+               gtk_menu_shell_prepend (menu, item);
+
+               g_free (description);
+       }
+
+       return already_added;
+}
+
+static void
+widget_undo_populate_popup_cb (GtkWidget *widget,
+                              GtkWidget *popup,
+                              gpointer user_data)
+{
+       GtkMenuShell *menu;
+       gboolean added = FALSE;
+
+       if (!GTK_IS_MENU (popup))
+               return;
+
+       menu = GTK_MENU_SHELL (popup);
+
+       /* first redo, because prependend, thus undo gets before it */
+       if (e_widget_undo_has_redo (widget))
+               added = widget_undo_prepend_popup (widget, menu, E_UNDO_DO_REDO, added);
+
+       if (e_widget_undo_has_undo (widget))
+               widget_undo_prepend_popup (widget, menu, E_UNDO_DO_UNDO, added);
+}
+
+/**
+ * e_widget_undo_attach:
+ * @widget: a #GtkWidget, where to attach undo functionality
+ * @focus_tracker: an #EFocusTracker, can be %NULL
+ *
+ * The function does nothing, if the widget is not of a supported type
+ * for undo functionality, same as when the undo is already attached.
+ * It is ensured that the actions of the provided @focus_tracker are
+ * updated on change of the @widget.
+ *
+ * See @e_widget_undo_is_attached().
+ *
+ * Since: 3.12
+ **/
+void
+e_widget_undo_attach (GtkWidget *widget,
+                     EFocusTracker *focus_tracker)
+{
+       EUndoData *data;
+
+       if (e_widget_undo_is_attached (widget))
+               return;
+
+       if (GTK_IS_EDITABLE (widget)) {
+               data = g_new0 (EUndoData, 1);
+               data->undo_len = DEFAULT_MAX_UNDO_LEVEL;
+               data->undo_stack = g_new0 (EUndoInfo *, data->undo_len);
+
+               g_object_set_data_full (G_OBJECT (widget), UNDO_DATA_KEY, data, free_undo_data);
+
+               data->insert_handler_id = g_signal_connect (widget, "insert-text",
+                       G_CALLBACK (editable_undo_insert_text_cb), NULL);
+               data->delete_handler_id = g_signal_connect (widget, "delete-text",
+                       G_CALLBACK (editable_undo_delete_text_cb), NULL);
+
+               if (focus_tracker)
+                       g_signal_connect_swapped (widget, "changed",
+                               G_CALLBACK (e_focus_tracker_update_actions), focus_tracker);
+
+               if (GTK_IS_ENTRY (widget))
+                       g_signal_connect (widget, "populate-popup",
+                               G_CALLBACK (widget_undo_populate_popup_cb), NULL);
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               data = g_new0 (EUndoData, 1);
+               data->undo_len = DEFAULT_MAX_UNDO_LEVEL;
+               data->undo_stack = g_new0 (EUndoInfo *, data->undo_len);
+
+               g_object_set_data_full (G_OBJECT (text_buffer), UNDO_DATA_KEY, data, free_undo_data);
+
+               data->insert_handler_id = g_signal_connect (text_buffer, "insert-text",
+                       G_CALLBACK (text_buffer_undo_insert_text_cb), NULL);
+               data->delete_handler_id = g_signal_connect (text_buffer, "delete-range",
+                       G_CALLBACK (text_buffer_undo_delete_range_cb), NULL);
+
+               if (focus_tracker)
+                       g_signal_connect_swapped (text_buffer, "changed",
+                               G_CALLBACK (e_focus_tracker_update_actions), focus_tracker);
+
+               g_signal_connect (widget, "populate-popup",
+                       G_CALLBACK (widget_undo_populate_popup_cb), NULL);
+       }
+}
+
+/**
+ * e_widget_undo_is_attached:
+ * @widget: a #GtkWidget, where to test whether undo functionality is attached.
+ *
+ * Checks whether the given widget has already attached an undo
+ * functionality - it is done with @e_widget_undo_attach().
+ *
+ * Returns: Whether the given @widget has already attached undo functionality.
+ *
+ * Since: 3.12
+ **/
+gboolean
+e_widget_undo_is_attached (GtkWidget *widget)
+{
+       gboolean res = FALSE;
+
+       if (GTK_IS_EDITABLE (widget)) {
+               res = g_object_get_data (G_OBJECT (widget), UNDO_DATA_KEY) != NULL;
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               res = g_object_get_data (G_OBJECT (text_buffer), UNDO_DATA_KEY) != NULL;
+       }
+
+       return res;
+}
+
+/**
+ * e_widget_undo_has_undo:
+ * @widget: a #GtkWidget
+ *
+ * Returns: Whether the given @widget has any undo available.
+ *
+ * See: @e_widget_undo_describe_undo, @e_widget_undo_do_undo
+ *
+ * Since: 3.12
+ **/
+gboolean
+e_widget_undo_has_undo (GtkWidget *widget)
+{
+       if (GTK_IS_EDITABLE (widget)) {
+               return undo_check_undo (G_OBJECT (widget), NULL);
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               return undo_check_undo (G_OBJECT (text_buffer), NULL);
+       }
+
+       return FALSE;
+}
+
+/**
+ * e_widget_undo_has_redo:
+ * @widget: a #GtkWidget
+ *
+ * Returns: Whether the given @widget has any redo available.
+ *
+ * See: @e_widget_undo_describe_redo, @e_widget_undo_do_redo
+ *
+ * Since: 3.12
+ **/
+gboolean
+e_widget_undo_has_redo (GtkWidget *widget)
+{
+       if (GTK_IS_EDITABLE (widget)) {
+               return undo_check_redo (G_OBJECT (widget), NULL);
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               return undo_check_redo (G_OBJECT (text_buffer), NULL);
+       }
+
+       return FALSE;
+}
+
+/**
+ * e_widget_undo_describe_undo:
+ * @widget: a #GtkWidget
+ *
+ * Returns: (transfer full): Description of a top undo action available
+ *    for the @widget, %NULL when there is no undo action. Returned pointer,
+ *    if not %NULL, should be freed with g_free().
+ *
+ * See: @e_widget_undo_has_undo, @e_widget_undo_do_undo
+ *
+ * Since: 3.12
+ **/
+gchar *
+e_widget_undo_describe_undo (GtkWidget *widget)
+{
+       gchar *res = NULL;
+
+       if (GTK_IS_EDITABLE (widget)) {
+               if (!undo_check_undo (G_OBJECT (widget), &res)) {
+                       g_warn_if_fail (res == NULL);
+               }
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               if (!undo_check_undo (G_OBJECT (text_buffer), &res)) {
+                       g_warn_if_fail (res == NULL);
+               }
+       }
+
+       return res;
+}
+
+/**
+ * e_widget_undo_describe_redo:
+ * @widget: a #GtkWidget
+ *
+ * Returns: (transfer full): Description of a top redo action available
+ *    for the @widget, %NULL when there is no redo action. Returned pointer,
+ *    if not %NULL, should be freed with g_free().
+ *
+ * See: @e_widget_undo_has_redo, @e_widget_undo_do_redo
+ *
+ * Since: 3.12
+ **/
+gchar *
+e_widget_undo_describe_redo (GtkWidget *widget)
+{
+       gchar *res = NULL;
+
+       if (GTK_IS_EDITABLE (widget)) {
+               if (!undo_check_redo (G_OBJECT (widget), &res)) {
+                       g_warn_if_fail (res == NULL);
+               }
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               if (!undo_check_redo (G_OBJECT (text_buffer), &res)) {
+                       g_warn_if_fail (res == NULL);
+               }
+       }
+
+       return res;
+}
+
+/**
+ * e_widget_undo_do_undo:
+ * @widget: a #GtkWidget
+ *
+ * Applies the top undo action on the @widget, which also remembers
+ * a redo action. It does nothing if the widget doesn't have
+ * attached undo functionality (@e_widget_undo_attach()), neither
+ * when there is no undo action available.
+ *
+ * See: @e_widget_undo_attach, @e_widget_undo_has_undo, @e_widget_undo_describe_undo
+ *
+ * Since: 3.12
+ **/
+void
+e_widget_undo_do_undo (GtkWidget *widget)
+{
+       if (GTK_IS_EDITABLE (widget)) {
+               undo_do_something (G_OBJECT (widget),
+                       E_UNDO_DO_UNDO,
+                       editable_undo_insert_text,
+                       editable_undo_delete_text);
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               undo_do_something (G_OBJECT (text_buffer),
+                       E_UNDO_DO_UNDO,
+                       text_buffer_undo_insert_text,
+                       text_buffer_undo_delete_text);
+       }
+}
+
+/**
+ * e_widget_undo_do_redo:
+ * @widget: a #GtkWidget
+ *
+ * Applies the top redo action on the @widget, which also remembers
+ * an undo action. It does nothing if the widget doesn't have
+ * attached undo functionality (@e_widget_undo_attach()), neither
+ * when there is no redo action available.
+ *
+ * See: @e_widget_undo_attach, @e_widget_undo_has_redo, @e_widget_undo_describe_redo
+ *
+ * Since: 3.12
+ **/
+void
+e_widget_undo_do_redo (GtkWidget *widget)
+{
+       if (GTK_IS_EDITABLE (widget)) {
+               undo_do_something (G_OBJECT (widget),
+                       E_UNDO_DO_REDO,
+                       editable_undo_insert_text,
+                       editable_undo_delete_text);
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               undo_do_something (G_OBJECT (text_buffer),
+                       E_UNDO_DO_REDO,
+                       text_buffer_undo_insert_text,
+                       text_buffer_undo_delete_text);
+       }
+}
+
+/**
+ * e_widget_undo_reset:
+ * @widget: a #GtkWidget, on which might be attached undo functionality
+ *
+ * Resets undo and redo stack to empty on a widget with attached
+ * undo functionality. It does nothing, if the widget does not have
+ * the undo functionality attached (see @e_widget_undo_attach()).
+ *
+ * Since: 3.12
+ **/
+void
+e_widget_undo_reset (GtkWidget *widget)
+{
+       if (GTK_IS_EDITABLE (widget)) {
+               undo_reset (G_OBJECT (widget));
+       } else if (GTK_IS_TEXT_VIEW (widget)) {
+               GtkTextBuffer *text_buffer;
+
+               text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+
+               undo_reset (G_OBJECT (text_buffer));
+       }
+}
diff --git a/e-util/e-widget-undo.h b/e-util/e-widget-undo.h
new file mode 100644
index 0000000..848359b
--- /dev/null
+++ b/e-util/e-widget-undo.h
@@ -0,0 +1,48 @@
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms 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 program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Authors:
+ *             Milan Crha <mcrha redhat com>
+ *
+ * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_WIDGET_UNDO_H
+#define E_WIDGET_UNDO_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+struct _EFocusTracker;
+
+void           e_widget_undo_attach            (GtkWidget *widget,
+                                                struct _EFocusTracker *focus_tracker);
+gboolean       e_widget_undo_is_attached       (GtkWidget *widget);
+gboolean       e_widget_undo_has_undo          (GtkWidget *widget);
+gboolean       e_widget_undo_has_redo          (GtkWidget *widget);
+gchar *                e_widget_undo_describe_undo     (GtkWidget *widget);
+gchar *                e_widget_undo_describe_redo     (GtkWidget *widget);
+void           e_widget_undo_do_undo           (GtkWidget *widget);
+void           e_widget_undo_do_redo           (GtkWidget *widget);
+void           e_widget_undo_reset             (GtkWidget *widget);
+
+G_END_DECLS
+
+#endif /* E_WIDGET_UNDO_H */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2e8242b..3002afd 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -279,6 +279,7 @@ e-util/e-tree.c
 e-util/e-url-entry.c
 e-util/e-web-view-gtkhtml.c
 e-util/e-web-view.c
+e-util/e-widget-undo.c
 e-util/ea-calendar-item.c
 e-util/evolution-source-viewer.c
 e-util/filter.error.xml


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