aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMilan Crha <mcrha@redhat.com>2014-01-11 00:18:49 +0800
committerMilan Crha <mcrha@redhat.com>2014-01-11 00:18:49 +0800
commit63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c (patch)
tree4af77bd941992bb173dac1e4480f51fb30fb5b1a
parent04ed82b0530ca7fa34008876b056378dff6b76fb (diff)
downloadgsoc2013-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.c875
-rw-r--r--addressbook/gui/contact-editor/e-contact-editor.h50
-rw-r--r--calendar/gui/dialogs/comp-editor.c27
-rw-r--r--calendar/gui/dialogs/event-page.c11
-rw-r--r--calendar/gui/dialogs/memo-page.c9
-rw-r--r--calendar/gui/dialogs/task-page.c11
-rw-r--r--e-util/Makefile.am2
-rw-r--r--e-util/e-focus-tracker.c365
-rw-r--r--e-util/e-focus-tracker.h8
-rw-r--r--e-util/e-selectable.c26
-rw-r--r--e-util/e-selectable.h4
-rw-r--r--e-util/e-util.h1
-rw-r--r--e-util/e-widget-undo.c891
-rw-r--r--e-util/e-widget-undo.h48
-rw-r--r--po/POTFILES.in1
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