[gtk/wip/chergert/textundo: 1/8] textview: add undo/redo support to GtkTextView
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/chergert/textundo: 1/8] textview: add undo/redo support to GtkTextView
- Date: Fri, 1 Nov 2019 20:48:21 +0000 (UTC)
commit b470ac861deadf99c3b5ed05c0c3af360515ec6c
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 | 63 ++++++
gtk/gtktextview.h | 2 +
7 files changed, 534 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..7a0884fe6b 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 can be undone and redone 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..a6351b495f 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -314,6 +314,8 @@ enum
PREEDIT_CHANGED,
EXTEND_SELECTION,
INSERT_EMOJI,
+ UNDO,
+ REDO,
LAST_SIGNAL
};
@@ -619,6 +621,9 @@ static void gtk_text_view_activate_misc_insert_emoji (GtkWidget *widget,
const char *action_name,
GVariant *parameter);
+static void gtk_text_view_real_undo (GtkTextView *text_view);
+static void gtk_text_view_real_redo (GtkTextView *text_view);
+
/* FIXME probably need the focus methods. */
@@ -734,6 +739,8 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
klass->create_buffer = gtk_text_view_create_buffer;
klass->extend_selection = gtk_text_view_extend_selection;
klass->insert_emoji = gtk_text_view_insert_emoji;
+ klass->undo = gtk_text_view_real_undo;
+ klass->redo = gtk_text_view_real_redo;
/*
* Properties
@@ -1358,6 +1365,40 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
NULL,
G_TYPE_NONE, 0);
+ /**
+ * GtkTextView::undo:
+ * @text_view: the object which received the signal
+ *
+ * The ::undo signal is a
+ * [keybinding signal][GtkBindingSignal]
+ * which gets emitted to undo the last operation in the @text_view.
+ *
+ * The default binding for this signal is Ctrl-z.
+ */
+ signals[UNDO] =
+ g_signal_new (I_("undo"),
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkTextViewClass, undo),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ /**
+ * GtkTextView::redo:
+ * @text_view: the object which received the signal
+ *
+ * The ::redo signal is a
+ * [keybinding signal][GtkBindingSignal]
+ * which gets emitted to redo the last undone operation in the @text_view.
+ *
+ * The default binding for this signal is Ctrl-Shift-z.
+ */
+ signals[REDO] =
+ g_signal_new (I_("redo"),
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (GtkTextViewClass, redo),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
/*
* Key bindings
*/
@@ -1550,6 +1591,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_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK,
+ "undo", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_y, GDK_CONTROL_MASK,
+ "redo", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+ "redo", 0);
+
/* Overwrite */
gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, 0,
"toggle-overwrite", 0);
@@ -9835,3 +9884,17 @@ gtk_text_view_get_extra_menu (GtkTextView *text_view)
return priv->extra_menu;
}
+
+static void
+gtk_text_view_real_undo (GtkTextView *text_view)
+{
+ if (gtk_text_view_get_editable (text_view))
+ gtk_text_buffer_undo (text_view->priv->buffer);
+}
+
+static void
+gtk_text_view_real_redo (GtkTextView *text_view)
+{
+ if (gtk_text_view_get_editable (text_view))
+ gtk_text_buffer_redo (text_view->priv->buffer);
+}
diff --git a/gtk/gtktextview.h b/gtk/gtktextview.h
index d386aebf70..91a156ddc4 100644
--- a/gtk/gtktextview.h
+++ b/gtk/gtktextview.h
@@ -179,6 +179,8 @@ struct _GtkTextViewClass
GtkTextIter *start,
GtkTextIter *end);
void (* insert_emoji) (GtkTextView *text_view);
+ void (* undo) (GtkTextView *text_view);
+ void (* redo) (GtkTextView *text_view);
/*< private >*/
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]