diff options
author | Milan Crha <mcrha@redhat.com> | 2014-01-11 00:18:49 +0800 |
---|---|---|
committer | Milan Crha <mcrha@redhat.com> | 2014-01-11 00:18:49 +0800 |
commit | 63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c (patch) | |
tree | 4af77bd941992bb173dac1e4480f51fb30fb5b1a /e-util | |
parent | 04ed82b0530ca7fa34008876b056378dff6b76fb (diff) | |
download | gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.gz gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.bz2 gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.lz gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.xz gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.tar.zst gsoc2013-evolution-63a1f0eab3e15e0d64e24bd5a2659a61347cfe9c.zip |
Bug #333184 - Add Undo support to component editors
Diffstat (limited to 'e-util')
-rw-r--r-- | e-util/Makefile.am | 2 | ||||
-rw-r--r-- | e-util/e-focus-tracker.c | 365 | ||||
-rw-r--r-- | e-util/e-focus-tracker.h | 8 | ||||
-rw-r--r-- | e-util/e-selectable.c | 26 | ||||
-rw-r--r-- | e-util/e-selectable.h | 4 | ||||
-rw-r--r-- | e-util/e-util.h | 1 | ||||
-rw-r--r-- | e-util/e-widget-undo.c | 891 | ||||
-rw-r--r-- | e-util/e-widget-undo.h | 48 |
8 files changed, 1325 insertions, 20 deletions
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 */ |