[gtk/wip/chergert/textundo: 11/20] textview: add undo/redo support to GtkTextView



commit bd8a2e8bb914dc2bc4dbe915a891ff49d83b329b
Author: Christian Hergert <chergert redhat com>
Date:   Fri Nov 1 11:12:21 2019 -0700

    textview: add undo/redo support to GtkTextView
    
    This builds upon the GtkTextHistory helper to provide undo and redo support
    for the GtkTextView widget and GtkTextBuffer object.
    
    You can undo/redo using familiar shortcuts such as Primary+Z,
    Primary+Shift+Z, ad Primary+Y.
    
    Developers that wish to disable undo, should set the
    GtkTextBuffer:enable-undo property to FALSE.
    
    You can wrap irreversible actions
    gtk_text_buffer_begin_irreversible_action() and
    gtk_text_buffer_end_irreversible_action(). This will cause the undo stack
    to drop all undo/redo actions and the changes made between them will be
    the "initial state" of the buffer.
    
    Calling gtk_text_buffer_set_text() will do this automatically for you.

 docs/reference/gtk/gtk4-sections.txt |  12 +
 docs/reference/gtk/text_widget.sgml  |  10 +
 gtk/gtktextbuffer.c                  | 426 ++++++++++++++++++++++++++++++++++-
 gtk/gtktextbuffer.h                  |  29 ++-
 gtk/gtktextprivate.h                 |   2 +
 gtk/gtktextview.c                    |  40 ++++
 6 files changed, 509 insertions(+), 10 deletions(-)
---
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index 6a246c8e9a..1781176a9e 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -2876,6 +2876,18 @@ gtk_text_buffer_begin_user_action
 gtk_text_buffer_end_user_action
 gtk_text_buffer_add_selection_clipboard
 gtk_text_buffer_remove_selection_clipboard
+gtk_text_buffer_get_can_undo
+gtk_text_buffer_get_can_redo
+gtk_text_buffer_get_enable_undo
+gtk_text_buffer_set_enable_undo
+gtk_text_buffer_get_max_undo_levels
+gtk_text_buffer_set_max_undo_levels
+gtk_text_buffer_undo
+gtk_text_buffer_redo
+gtk_text_buffer_begin_irreversible_action
+gtk_text_buffer_end_irreversible_action
+gtk_text_buffer_begin_user_action
+gtk_text_buffer_end_user_action
 
 <SUBSECTION Standard>
 GTK_TEXT_BUFFER
diff --git a/docs/reference/gtk/text_widget.sgml b/docs/reference/gtk/text_widget.sgml
index 06caf7386f..e0efd6ccd8 100644
--- a/docs/reference/gtk/text_widget.sgml
+++ b/docs/reference/gtk/text_widget.sgml
@@ -112,6 +112,16 @@ multiple bytes in UTF-8, and the two-character sequence "\r\n" is also
 considered a line separator.
 </para>
 
+<para>
+Text buffers support undo and redo if gtk_text_buffer_set_undo_enabled()
+has been set to %TRUE. Use gtk_text_buffer_undo() or gtk_text_buffer_redo()
+to perform the necessary action. Note that these operations are ignored if
+the buffer is not editable. Developers may want some operations to not be
+undoable. To do this, wrap your changes in
+gtk_text_buffer_begin_irreversible_action() and
+gtk_text_buffer_end_irreversible_action().
+</para>
+
 </refsect1>
 
 
diff --git a/gtk/gtktextbuffer.c b/gtk/gtktextbuffer.c
index 5434696670..5ddb1e4b9f 100644
--- a/gtk/gtktextbuffer.c
+++ b/gtk/gtktextbuffer.c
@@ -30,6 +30,7 @@
 #include "gtkdnd.h"
 #include "gtkmarshalers.h"
 #include "gtktextbuffer.h"
+#include "gtktexthistoryprivate.h"
 #include "gtktextbufferprivate.h"
 #include "gtktextbtree.h"
 #include "gtktextiterprivate.h"
@@ -38,6 +39,8 @@
 #include "gtkprivate.h"
 #include "gtkintl.h"
 
+#define DEFAULT_MAX_UNDO 200
+
 /**
  * SECTION:gtktextbuffer
  * @Short_description: Stores attributed text for display in a GtkTextView
@@ -62,11 +65,15 @@ struct _GtkTextBufferPrivate
 
   GtkTextLogAttrCache *log_attr_cache;
 
+  GtkTextHistory *history;
+
   guint user_action_count;
 
   /* Whether the buffer has been modified since last save */
   guint modified : 1;
   guint has_selection : 1;
+  guint can_undo : 1;
+  guint can_redo : 1;
 };
 
 typedef struct _ClipboardRequest ClipboardRequest;
@@ -93,6 +100,8 @@ enum {
   BEGIN_USER_ACTION,
   END_USER_ACTION,
   PASTE_DONE,
+  UNDO,
+  REDO,
   LAST_SIGNAL
 };
 
@@ -108,6 +117,9 @@ enum {
   PROP_CURSOR_POSITION,
   PROP_COPY_TARGET_LIST,
   PROP_PASTE_TARGET_LIST,
+  PROP_CAN_UNDO,
+  PROP_CAN_REDO,
+  PROP_ENABLE_UNDO,
   LAST_PROP
 };
 
@@ -138,6 +150,8 @@ static void gtk_text_buffer_real_changed               (GtkTextBuffer     *buffe
 static void gtk_text_buffer_real_mark_set              (GtkTextBuffer     *buffer,
                                                         const GtkTextIter *iter,
                                                         GtkTextMark       *mark);
+static void gtk_text_buffer_real_undo                  (GtkTextBuffer     *buffer);
+static void gtk_text_buffer_real_redo                  (GtkTextBuffer     *buffer);
 
 static GtkTextBTree* get_btree (GtkTextBuffer *buffer);
 static void          free_log_attr_cache (GtkTextLogAttrCache *cache);
@@ -154,6 +168,24 @@ static void gtk_text_buffer_get_property (GObject         *object,
                                          GValue          *value,
                                          GParamSpec      *pspec);
 
+static void gtk_text_buffer_history_change_state (gpointer     funcs_data,
+                                                  gboolean     is_modified,
+                                                  gboolean     can_undo,
+                                                  gboolean     can_redo);
+static void gtk_text_buffer_history_insert       (gpointer     funcs_data,
+                                                  guint        begin,
+                                                  guint        end,
+                                                  const char  *text,
+                                                  guint        len);
+static void gtk_text_buffer_history_delete       (gpointer     funcs_data,
+                                                  guint        begin,
+                                                  guint        end,
+                                                  const char  *expected_text,
+                                                  guint        len);
+static void gtk_text_buffer_history_select       (gpointer     funcs_data,
+                                                  int          selection_insert,
+                                                  int          selection_bound);
+
 static guint signals[LAST_SIGNAL] = { 0 };
 static GParamSpec *text_buffer_props[LAST_PROP];
 
@@ -185,6 +217,13 @@ GType gtk_text_buffer_content_get_type (void) G_GNUC_CONST;
 
 G_DEFINE_TYPE (GtkTextBufferContent, gtk_text_buffer_content, GDK_TYPE_CONTENT_PROVIDER)
 
+static GtkTextHistoryFuncs history_funcs = {
+  gtk_text_buffer_history_change_state,
+  gtk_text_buffer_history_insert,
+  gtk_text_buffer_history_delete,
+  gtk_text_buffer_history_select,
+};
+
 static GdkContentFormats *
 gtk_text_buffer_content_ref_formats (GdkContentProvider *provider)
 {
@@ -403,6 +442,8 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
   klass->remove_tag = gtk_text_buffer_real_remove_tag;
   klass->changed = gtk_text_buffer_real_changed;
   klass->mark_set = gtk_text_buffer_real_mark_set;
+  klass->undo = gtk_text_buffer_real_undo;
+  klass->redo = gtk_text_buffer_real_redo;
 
   /* Construct */
   text_buffer_props[PROP_TAG_TABLE] =
@@ -439,6 +480,45 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
                             FALSE,
                             GTK_PARAM_READABLE);
 
+  /**
+   * GtkTextBuffer:can-undo:
+   *
+   * The :can-undo property denotes that the buffer can undo the last
+   * applied action.
+   */
+  text_buffer_props[PROP_CAN_UNDO] =
+    g_param_spec_boolean ("can-undo",
+                          P_("Can Undo"),
+                          P_("If the buffer can have the last action undone"),
+                          FALSE,
+                          GTK_PARAM_READABLE);
+
+  /**
+   * GtkTextBuffer:can-redo:
+   *
+   * The :can-redo property denotes that the buffer can reapply the
+   * last undone action.
+   */
+  text_buffer_props[PROP_CAN_REDO] =
+    g_param_spec_boolean ("can-redo",
+                          P_("Can Redo"),
+                          P_("If the buffer can have the last undone action reapplied"),
+                          FALSE,
+                          GTK_PARAM_READABLE);
+
+  /**
+   * GtkTextBuffer:enable-undo:
+   *
+   * The :enable-undo property denotes if support for undoing and redoing
+   * changes to the buffer is allowed.
+   */
+  text_buffer_props[PROP_ENABLE_UNDO] =
+    g_param_spec_boolean ("enable-undo",
+                          "Enable Undo",
+                          "Enable support for undo and redo in the text view",
+                          TRUE,
+                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
   /**
    * GtkTextBuffer:cursor-position:
    *
@@ -840,6 +920,34 @@ gtk_text_buffer_class_init (GtkTextBufferClass *klass)
                   1,
                   GDK_TYPE_CLIPBOARD);
 
+  /**
+   * GtkTextBuffer::redo:
+   * @buffer: a #GtkTextBuffer
+   *
+   * The "redo" signal is emitted when a request has been made to redo the
+   * previously undone operation.
+   */
+  signals[REDO] =
+    g_signal_new (I_("redo"),
+                  G_OBJECT_CLASS_TYPE (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkTextBufferClass, redo),
+                  NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+  /**
+   * GtkTextBuffer::undo:
+   * @buffer: a #GtkTextBuffer
+   *
+   * The "undo" signal is emitted when a request has been made to undo the
+   * previous operation or set of operations that have been grouped together.
+   */
+  signals[UNDO] =
+    g_signal_new (I_("undo"),
+                  G_OBJECT_CLASS_TYPE (object_class),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkTextBufferClass, undo),
+                  NULL, NULL, NULL, G_TYPE_NONE, 0);
+
   gtk_text_buffer_register_serializers ();
 }
 
@@ -848,6 +956,9 @@ gtk_text_buffer_init (GtkTextBuffer *buffer)
 {
   buffer->priv = gtk_text_buffer_get_instance_private (buffer);
   buffer->priv->tag_table = NULL;
+  buffer->priv->history = gtk_text_history_new (&history_funcs, buffer);
+
+  gtk_text_history_set_max_undo_levels (buffer->priv->history, DEFAULT_MAX_UNDO);
 }
 
 static void
@@ -891,6 +1002,10 @@ gtk_text_buffer_set_property (GObject         *object,
 
   switch (prop_id)
     {
+    case PROP_ENABLE_UNDO:
+      gtk_text_buffer_set_enable_undo (text_buffer, g_value_get_boolean (value));
+      break;
+
     case PROP_TAG_TABLE:
       set_table (text_buffer, g_value_get_object (value));
       break;
@@ -919,6 +1034,10 @@ gtk_text_buffer_get_property (GObject         *object,
 
   switch (prop_id)
     {
+    case PROP_ENABLE_UNDO:
+      g_value_set_boolean (value, gtk_text_buffer_get_enable_undo (text_buffer));
+      break;
+
     case PROP_TAG_TABLE:
       g_value_set_object (value, get_table (text_buffer));
       break;
@@ -946,6 +1065,14 @@ gtk_text_buffer_get_property (GObject         *object,
       g_value_set_int (value, gtk_text_iter_get_offset (&iter));
       break;
 
+    case PROP_CAN_UNDO:
+      g_value_set_boolean (value, gtk_text_buffer_get_can_undo (text_buffer));
+      break;
+
+    case PROP_CAN_REDO:
+      g_value_set_boolean (value, gtk_text_buffer_get_can_redo (text_buffer));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -981,6 +1108,8 @@ gtk_text_buffer_finalize (GObject *object)
 
   remove_all_selection_clipboards (buffer);
 
+  g_clear_object (&buffer->priv->history);
+
   if (priv->tag_table)
     {
       _gtk_text_tag_table_remove_buffer (priv->tag_table, buffer);
@@ -1058,6 +1187,8 @@ gtk_text_buffer_set_text (GtkTextBuffer *buffer,
   if (len < 0)
     len = strlen (text);
 
+  gtk_text_history_begin_irreversible_action (buffer->priv->history);
+
   gtk_text_buffer_get_bounds (buffer, &start, &end);
 
   gtk_text_buffer_delete (buffer, &start, &end);
@@ -1067,6 +1198,8 @@ gtk_text_buffer_set_text (GtkTextBuffer *buffer,
       gtk_text_buffer_get_iter_at_offset (buffer, &start, 0);
       gtk_text_buffer_insert (buffer, &start, text, len);
     }
+
+  gtk_text_history_end_irreversible_action (buffer->priv->history);
 }
 
  
@@ -1084,6 +1217,11 @@ gtk_text_buffer_real_insert_text (GtkTextBuffer *buffer,
   g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
   g_return_if_fail (iter != NULL);
   
+  gtk_text_history_text_inserted (buffer->priv->history,
+                                  gtk_text_iter_get_offset (iter),
+                                  text,
+                                  len);
+
   _gtk_text_btree_insert (iter, text, len);
 
   g_signal_emit (buffer, signals[CHANGED], 0);
@@ -1798,6 +1936,28 @@ gtk_text_buffer_real_delete_range (GtkTextBuffer *buffer,
   g_return_if_fail (start != NULL);
   g_return_if_fail (end != NULL);
 
+  if (gtk_text_history_get_enabled (buffer->priv->history))
+    {
+      GtkTextIter sel_begin, sel_end;
+      gchar *text;
+
+      if (gtk_text_buffer_get_selection_bounds (buffer, &sel_begin, &sel_end))
+        gtk_text_history_selection_changed (buffer->priv->history,
+                                            gtk_text_iter_get_offset (&sel_begin),
+                                            gtk_text_iter_get_offset (&sel_end));
+      else
+        gtk_text_history_selection_changed (buffer->priv->history,
+                                            gtk_text_iter_get_offset (&sel_begin),
+                                            -1);
+
+      text = gtk_text_iter_get_slice (start, end);
+      gtk_text_history_text_deleted (buffer->priv->history,
+                                     gtk_text_iter_get_offset (start),
+                                     gtk_text_iter_get_offset (end),
+                                     text, -1);
+      g_free (text);
+    }
+
   _gtk_text_btree_delete (start, end);
 
   /* may have deleted the selection... */
@@ -3274,17 +3434,14 @@ void
 gtk_text_buffer_set_modified (GtkTextBuffer *buffer,
                               gboolean       setting)
 {
-  gboolean fixed_setting;
-
   g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
 
-  fixed_setting = setting != FALSE;
+  setting = !!setting;
 
-  if (buffer->priv->modified == fixed_setting)
-    return;
-  else
+  if (buffer->priv->modified != setting)
     {
-      buffer->priv->modified = fixed_setting;
+      buffer->priv->modified = setting;
+      gtk_text_history_modified_changed (buffer->priv->history, setting);
       g_signal_emit (buffer, signals[MODIFIED_CHANGED], 0);
     }
 }
@@ -4723,3 +4880,258 @@ gtk_text_buffer_insert_markup (GtkTextBuffer *buffer,
   pango_attr_list_unref (attributes);
   g_free (text); 
 }
+
+static void
+gtk_text_buffer_real_undo (GtkTextBuffer *buffer)
+{
+  if (gtk_text_history_get_can_undo (buffer->priv->history))
+    gtk_text_history_undo (buffer->priv->history);
+}
+
+static void
+gtk_text_buffer_real_redo (GtkTextBuffer *buffer)
+{
+  if (gtk_text_history_get_can_redo (buffer->priv->history))
+    gtk_text_history_redo (buffer->priv->history);
+}
+
+gboolean
+gtk_text_buffer_get_can_undo (GtkTextBuffer *buffer)
+{
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+  return gtk_text_history_get_can_undo (buffer->priv->history);
+}
+
+gboolean
+gtk_text_buffer_get_can_redo (GtkTextBuffer *buffer)
+{
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+  return gtk_text_history_get_can_redo (buffer->priv->history);
+}
+
+static void
+gtk_text_buffer_history_change_state (gpointer funcs_data,
+                                      gboolean is_modified,
+                                      gboolean can_undo,
+                                      gboolean can_redo)
+{
+  GtkTextBuffer *buffer = funcs_data;
+
+  if (buffer->priv->can_undo != can_undo)
+    {
+      buffer->priv->can_undo = can_undo;
+      g_object_notify_by_pspec (G_OBJECT (buffer), text_buffer_props[PROP_CAN_UNDO]);
+    }
+
+  if (buffer->priv->can_redo != can_redo)
+    {
+      buffer->priv->can_redo = can_redo;
+      g_object_notify_by_pspec (G_OBJECT (buffer), text_buffer_props[PROP_CAN_REDO]);
+    }
+
+  if (buffer->priv->modified != is_modified)
+    gtk_text_buffer_set_modified (buffer, is_modified);
+}
+
+static void
+gtk_text_buffer_history_insert (gpointer    funcs_data,
+                                guint       begin,
+                                guint       end,
+                                const char *text,
+                                guint       len)
+{
+  GtkTextBuffer *buffer = funcs_data;
+  GtkTextIter iter;
+
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, begin);
+  gtk_text_buffer_insert (buffer, &iter, text, len);
+}
+
+static void
+gtk_text_buffer_history_delete (gpointer    funcs_data,
+                                guint       begin,
+                                guint       end,
+                                const char *expected_text,
+                                guint       len)
+{
+  GtkTextBuffer *buffer = funcs_data;
+  GtkTextIter iter;
+  GtkTextIter end_iter;
+
+  gtk_text_buffer_get_iter_at_offset (buffer, &iter, begin);
+  gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+  gtk_text_buffer_delete (buffer, &iter, &end_iter);
+}
+
+static void
+gtk_text_buffer_history_select (gpointer funcs_data,
+                                int      selection_insert,
+                                int      selection_bound)
+{
+  GtkTextBuffer *buffer = funcs_data;
+  GtkTextIter insert;
+  GtkTextIter bound;
+
+  if (selection_insert == -1 || selection_bound == -1)
+    return;
+
+  gtk_text_buffer_get_iter_at_offset (buffer, &insert, selection_insert);
+  gtk_text_buffer_get_iter_at_offset (buffer, &bound, selection_bound);
+  gtk_text_buffer_select_range (buffer, &insert, &bound);
+}
+
+/**
+ * gtk_text_buffer_undo:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Undoes the last undoable action on the buffer, if there is one.
+ */
+void
+gtk_text_buffer_undo (GtkTextBuffer *buffer)
+{
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  if (gtk_text_buffer_get_can_undo (buffer))
+    g_signal_emit (buffer, signals[UNDO], 0);
+}
+
+/**
+ * gtk_text_buffer_redo:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Redoes the next redoable action on the buffer, if there is one.
+ */
+void
+gtk_text_buffer_redo (GtkTextBuffer *buffer)
+{
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  if (gtk_text_buffer_get_can_redo (buffer))
+    g_signal_emit (buffer, signals[REDO], 0);
+}
+
+/**
+ * gtk_text_buffer_get_enable_undo:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Gets whether the buffer is saving modifications to the buffer to allow for
+ * undo and redo actions.
+ *
+ * See gtk_text_buffer_begin_irreversible_action() and
+ * gtk_text_buffer_end_irreversible_action() to create changes to the buffer
+ * that cannot be undone.
+ */
+gboolean
+gtk_text_buffer_get_enable_undo (GtkTextBuffer *buffer)
+{
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
+
+  return gtk_text_history_get_enabled (buffer->priv->history);
+}
+
+/**
+ * gtk_text_buffer_set_enable_undo:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Sets whether or not to enable undoable actions in the text buffer. If
+ * enabled, the user will be able to undo the last number of actions up to
+ * gtk_text_buffer_get_max_undo_levels().
+ *
+ * See gtk_text_buffer_begin_irreversible_action() and
+ * gtk_text_buffer_end_irreversible_action() to create changes to the buffer
+ * that cannot be undone.
+ */
+void
+gtk_text_buffer_set_enable_undo (GtkTextBuffer *buffer,
+                                 gboolean       enabled)
+{
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  if (enabled != gtk_text_history_get_enabled (buffer->priv->history))
+    {
+      gtk_text_history_set_enabled (buffer->priv->history, enabled);
+      g_object_notify_by_pspec (G_OBJECT (buffer),
+                                text_buffer_props[PROP_ENABLE_UNDO]);
+    }
+}
+
+/**
+ * gtk_text_buffer_begin_irreversible_action:
+ * @self: a #Gtktextbuffer
+ *
+ * Denotes the beginning of an action that may not be undone. This will cause
+ * any previous operations in the undo/redo queue to be cleared.
+ *
+ * This should be paired with a call to
+ * gtk_text_buffer_end_irreversible_action() after the irreversible action
+ * has completed.
+ *
+ * You may nest calls to gtk_text_buffer_begin_irreversible_action() and
+ * gtk_text_buffer_end_irreversible_action() pairs.
+ */
+void
+gtk_text_buffer_begin_irreversible_action (GtkTextBuffer *buffer)
+{
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  gtk_text_history_begin_irreversible_action (buffer->priv->history);
+}
+
+/**
+ * gtk_text_buffer_end_irreversible_action:
+ * @self: a #Gtktextbuffer
+ *
+ * Denotes the end of an action that may not be undone. This will cause
+ * any previous operations in the undo/redo queue to be cleared.
+ *
+ * This should be called after completing modifications to the text buffer
+ * after gtk_text_buffer_begin_irreversible_action() was called.
+ *
+ * You may nest calls to gtk_text_buffer_begin_irreversible_action() and
+ * gtk_text_buffer_end_irreversible_action() pairs.
+ */
+void
+gtk_text_buffer_end_irreversible_action (GtkTextBuffer *buffer)
+{
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  gtk_text_history_end_irreversible_action (buffer->priv->history);
+}
+
+/**
+ * gtk_text_buffer_get_max_undo_levels:
+ * @buffer: a #GtkTextBuffer
+ *
+ * Gets the maximum number of undo levels to perform. If 0, unlimited undo
+ * actions may be performed. Note that this may have a memory usage impact
+ * as it requires storing an additional copy of the inserted or removed text
+ * within the text buffer.
+ */
+guint
+gtk_text_buffer_get_max_undo_levels (GtkTextBuffer *buffer)
+{
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), 0);
+
+  return gtk_text_history_get_max_undo_levels (buffer->priv->history);
+}
+
+/**
+ * gtk_text_buffer_set_max_undo_levels:
+ * @buffer: a #GtkTextBuffer
+ * @max_undo_levels: the maximum number of undo actions to perform
+ *
+ * Sets the maximum number of undo levels to perform. If 0, unlimited undo
+ * actions may be performed. Note that this may have a memory usage impact
+ * as it requires storing an additional copy of the inserted or removed text
+ * within the text buffer.
+ */
+void
+gtk_text_buffer_set_max_undo_levels (GtkTextBuffer *buffer,
+                                     guint          max_undo_levels)
+{
+  g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
+
+  gtk_text_history_set_max_undo_levels (buffer->priv->history, max_undo_levels);
+}
diff --git a/gtk/gtktextbuffer.h b/gtk/gtktextbuffer.h
index 51668cbb6e..9517077fe6 100644
--- a/gtk/gtktextbuffer.h
+++ b/gtk/gtktextbuffer.h
@@ -146,6 +146,8 @@ struct _GtkTextBufferClass
 
   void (* paste_done)             (GtkTextBuffer      *buffer,
                                    GdkClipboard       *clipboard);
+  void (* undo)                   (GtkTextBuffer      *buffer);
+  void (* redo)                   (GtkTextBuffer      *buffer);
 
   /*< private >*/
 
@@ -451,11 +453,32 @@ gboolean        gtk_text_buffer_delete_selection        (GtkTextBuffer *buffer,
                                                          gboolean       interactive,
                                                          gboolean       default_editable);
 
-/* Called to specify atomic user actions, used to implement undo */
 GDK_AVAILABLE_IN_ALL
-void            gtk_text_buffer_begin_user_action       (GtkTextBuffer *buffer);
+gboolean        gtk_text_buffer_get_can_undo              (GtkTextBuffer *buffer);
 GDK_AVAILABLE_IN_ALL
-void            gtk_text_buffer_end_user_action         (GtkTextBuffer *buffer);
+gboolean        gtk_text_buffer_get_can_redo              (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+gboolean        gtk_text_buffer_get_enable_undo           (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_set_enable_undo           (GtkTextBuffer *buffer,
+                                                           gboolean       enable_undo);
+GDK_AVAILABLE_IN_ALL
+guint           gtk_text_buffer_get_max_undo_levels       (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_set_max_undo_levels       (GtkTextBuffer *buffer,
+                                                           guint          max_undo_levels);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_undo                      (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_redo                      (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_begin_irreversible_action (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_end_irreversible_action   (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_begin_user_action         (GtkTextBuffer *buffer);
+GDK_AVAILABLE_IN_ALL
+void            gtk_text_buffer_end_user_action           (GtkTextBuffer *buffer);
 
 
 G_END_DECLS
diff --git a/gtk/gtktextprivate.h b/gtk/gtktextprivate.h
index daeed71bce..500389824d 100644
--- a/gtk/gtktextprivate.h
+++ b/gtk/gtktextprivate.h
@@ -85,6 +85,8 @@ struct _GtkTextClass
   void (* paste_clipboard)    (GtkText         *self);
   void (* toggle_overwrite)   (GtkText         *self);
   void (* insert_emoji)       (GtkText         *self);
+  void (* undo)               (GtkText         *self);
+  void (* redo)               (GtkText         *self);
 };
 
 char *              gtk_text_get_display_text   (GtkText    *entry,
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index 766060aff7..0c1282c011 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -619,6 +619,13 @@ static void gtk_text_view_activate_misc_insert_emoji    (GtkWidget  *widget,
                                                          const char *action_name,
                                                          GVariant   *parameter);
 
+static void gtk_text_view_real_undo (GtkWidget   *widget,
+                                     const gchar *action_name,
+                                     GVariant    *parameter);
+static void gtk_text_view_real_redo (GtkWidget   *widget,
+                                     const gchar *action_name,
+                                     GVariant    *parameter);
+
 
 /* FIXME probably need the focus methods. */
 
@@ -1358,6 +1365,9 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
                   NULL,
                   G_TYPE_NONE, 0);
 
+  gtk_widget_class_install_action (widget_class, "text.undo", NULL, gtk_text_view_real_undo);
+  gtk_widget_class_install_action (widget_class, "text.redo", NULL, gtk_text_view_real_redo);
+
   /*
    * Key bindings
    */
@@ -1550,6 +1560,14 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_SHIFT_MASK,
                                 "paste-clipboard", 0);
 
+  /* Undo/Redo */
+  gtk_binding_entry_add_action (binding_set, GDK_KEY_z, GDK_CONTROL_MASK,
+                                "text.undo", NULL);
+  gtk_binding_entry_add_action (binding_set, GDK_KEY_y, GDK_CONTROL_MASK,
+                                "text.redo", NULL);
+  gtk_binding_entry_add_action (binding_set, GDK_KEY_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+                                "text.redo", NULL);
+
   /* Overwrite */
   gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, 0,
                                "toggle-overwrite", 0);
@@ -9835,3 +9853,25 @@ gtk_text_view_get_extra_menu (GtkTextView *text_view)
 
   return priv->extra_menu;
 }
+
+static void
+gtk_text_view_real_undo (GtkWidget   *widget,
+                         const gchar *action_name,
+                         GVariant    *parameters)
+{
+  GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+
+  if (gtk_text_view_get_editable (text_view))
+    gtk_text_buffer_undo (text_view->priv->buffer);
+}
+
+static void
+gtk_text_view_real_redo (GtkWidget   *widget,
+                         const gchar *action_name,
+                         GVariant    *parameters)
+{
+  GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+
+  if (gtk_text_view_get_editable (text_view))
+    gtk_text_buffer_redo (text_view->priv->buffer);
+}


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