diff options
author | Milan Crha <mcrha@redhat.com> | 2014-01-11 00:18:49 +0800 |
---|---|---|
committer | Milan Crha <mcrha@redhat.com> | 2014-01-11 00:18:49 +0800 |
commit | 63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c (patch) | |
tree | 4af77bd941992bb173dac1e4480f51fb30fb5b1a | |
parent | 04ed82b0530ca7fa34008876b056378dff6b76fb (diff) | |
download | gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.gz gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.bz2 gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.lz gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.xz gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.zst gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.zip |
Bug #333184 - Add Undo support to component editors
-rw-r--r-- | addressbook/gui/contact-editor/e-contact-editor.c | 875 | ||||
-rw-r--r-- | addressbook/gui/contact-editor/e-contact-editor.h | 50 | ||||
-rw-r--r-- | calendar/gui/dialogs/comp-editor.c | 27 | ||||
-rw-r--r-- | calendar/gui/dialogs/event-page.c | 11 | ||||
-rw-r--r-- | calendar/gui/dialogs/memo-page.c | 9 | ||||
-rw-r--r-- | calendar/gui/dialogs/task-page.c | 11 | ||||
-rw-r--r-- | e-util/Makefile.am | 2 | ||||
-rw-r--r-- | e-util/e-focus-tracker.c | 365 | ||||
-rw-r--r-- | e-util/e-focus-tracker.h | 8 | ||||
-rw-r--r-- | e-util/e-selectable.c | 26 | ||||
-rw-r--r-- | e-util/e-selectable.h | 4 | ||||
-rw-r--r-- | e-util/e-util.h | 1 | ||||
-rw-r--r-- | e-util/e-widget-undo.c | 891 | ||||
-rw-r--r-- | e-util/e-widget-undo.h | 48 | ||||
-rw-r--r-- | 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 58ba9760c9..c67353c244 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 aa9448d43b..41f90870c9 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 bbf4662275..f635ab739a 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 7f2e27e538..0eafc08a9b 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 43b07c4afb..12126a6fc0 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 35bf3aa5af..1acc93f538 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 cf13b394d6..e6321db999 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 958eda87a6..36d2421711 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 b837e52997..a4e5a4e210 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 d19adb8304..6b011ee33b 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 c03cb3da2c..2d92941986 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 aa2521f35a..784858e99f 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 0000000000..4cb933f544 --- /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 0000000000..848359be93 --- /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 2e8242b69d..3002afd6b7 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 |