[gtksourceview/wip/undo-redo] UndoManager: new implementation based on GQueues (not finished)
- From: Sébastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/undo-redo] UndoManager: new implementation based on GQueues (not finished)
- Date: Sun, 31 Aug 2014 21:14:41 +0000 (UTC)
commit 274e0a25630e05c0437bf17b03a026b2695627ac
Author: Sébastien Wilmet <swilmet gnome org>
Date: Sun Aug 31 17:40:40 2014 +0200
UndoManager: new implementation based on GQueues (not finished)
gtksourceview/gtksourceundomanagerdefault.c | 485 ++++++++++++++++++++++++++-
1 files changed, 476 insertions(+), 9 deletions(-)
---
diff --git a/gtksourceview/gtksourceundomanagerdefault.c b/gtksourceview/gtksourceundomanagerdefault.c
index f843613..17f6983 100644
--- a/gtksourceview/gtksourceundomanagerdefault.c
+++ b/gtksourceview/gtksourceundomanagerdefault.c
@@ -43,7 +43,16 @@ typedef enum
struct _ActionInsert
{
- gint pos;
+ /* The character offest where the insertion occurred. */
+ gint start;
+
+ /* When the text is inserted, the character offset of the end of 'text'.
+ * It is used to undo the insertion by deleting the text between 'start'
+ * and 'end'.
+ */
+ gint end;
+
+ /* nul-terminated text */
gchar *text;
};
@@ -134,6 +143,308 @@ G_DEFINE_TYPE_WITH_CODE (GtkSourceUndoManagerDefault,
G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_UNDO_MANAGER,
gtk_source_undo_manager_iface_init))
+/* Common Action functions */
+
+static void
+delete_text (GtkTextBuffer *buffer,
+ gint start,
+ gint end)
+{
+ GtkTextIter start_iter;
+ GtkTextIter end_iter;
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+ gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+ gtk_text_buffer_begin_user_action (buffer);
+ gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
+ gtk_text_buffer_end_user_action (buffer);
+}
+
+static void
+insert_text (GtkTextBuffer *buffer,
+ gint offset,
+ const gchar *text)
+{
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset);
+
+ gtk_text_buffer_begin_user_action (buffer);
+ gtk_text_buffer_insert (buffer, &iter, text, -1);
+ gtk_text_buffer_end_user_action (buffer);
+}
+
+/* ActionInsert implementation */
+
+static Action *
+action_insert_new (void)
+{
+ Action *action;
+
+ action = g_slice_new0 (Action);
+ action->action_type = ACTION_TYPE_INSERT;
+
+ return action;
+}
+
+static void
+action_insert_free (Action *action)
+{
+ g_assert (action->action_type == ACTION_TYPE_INSERT);
+
+ g_free (action->action.insert.text);
+ g_slice_free (Action, action);
+}
+
+static void
+action_insert_undo (GtkTextBuffer *buffer,
+ Action *action)
+{
+ g_assert (action->action_type == ACTION_TYPE_INSERT);
+
+ delete_text (buffer,
+ action->action.insert.start,
+ action->action.insert.end);
+}
+
+static void
+action_insert_redo (GtkTextBuffer *buffer,
+ Action *action)
+{
+ g_assert (action->action_type == ACTION_TYPE_INSERT);
+
+ insert_text (buffer,
+ action->action.insert.start,
+ action->action.insert.text);
+}
+
+/* ActionDelete implementation */
+
+static Action *
+action_delete_new (void)
+{
+ Action *action;
+
+ action = g_slice_new0 (Action);
+ action->action_type = ACTION_TYPE_DELETE;
+
+ return action;
+}
+
+static void
+action_delete_free (Action *action)
+{
+ g_assert (action->action_type == ACTION_TYPE_DELETE);
+
+ g_free (action->action.delete.text);
+ g_slice_free (Action, action);
+}
+
+static void
+action_delete_undo (GtkTextBuffer *buffer,
+ Action *action)
+{
+ g_assert (action->action_type == ACTION_TYPE_DELETE);
+
+ insert_text (buffer,
+ action->action.delete.start,
+ action->action.delete.text);
+}
+
+static void
+action_delete_redo (GtkTextBuffer *buffer,
+ Action *action)
+{
+ g_assert (action->action_type == ACTION_TYPE_DELETE);
+
+ delete_text (buffer,
+ action->action.delete.start,
+ action->action.delete.end);
+}
+
+/* Action functions. The Action struct can be seen as an interface. All the
+ * switch (action->action_type) are grouped in this section.
+ */
+
+static void
+action_free (Action *action)
+{
+ if (action == NULL)
+ {
+ return;
+ }
+
+ switch (action->action_type)
+ {
+ case ACTION_TYPE_INSERT:
+ action_insert_free (action);
+ break;
+
+ case ACTION_TYPE_DELETE:
+ action_delete_free (action);
+ break;
+
+ default:
+ g_return_if_reached ();
+ break;
+ }
+}
+
+static void
+action_undo (GtkTextBuffer *buffer,
+ Action *action)
+{
+ g_assert (action != NULL);
+
+ switch (action->action_type)
+ {
+ case ACTION_TYPE_INSERT:
+ action_insert_undo (buffer, action);
+ break;
+
+ case ACTION_TYPE_DELETE:
+ action_delete_undo (buffer, action);
+ break;
+
+ default:
+ g_return_if_reached ();
+ break;
+ }
+}
+
+static void
+action_redo (GtkTextBuffer *buffer,
+ Action *action)
+{
+ g_assert (action != NULL);
+
+ switch (action->action_type)
+ {
+ case ACTION_TYPE_INSERT:
+ action_insert_redo (buffer, action);
+ break;
+
+ case ACTION_TYPE_DELETE:
+ action_delete_redo (buffer, action);
+ break;
+
+ default:
+ g_return_if_reached ();
+ break;
+ }
+}
+
+/* Utilities functions */
+
+static ActionGroup *
+action_group_new (void)
+{
+ ActionGroup *group;
+
+ group = g_slice_new (ActionGroup);
+ group->actions = g_queue_new ();
+
+ return group;
+}
+
+static void
+action_group_free (ActionGroup *group)
+{
+ if (group != NULL)
+ {
+ g_queue_free_full (group->actions, (GDestroyNotify) action_free);
+ g_slice_free (ActionGroup, group);
+ }
+}
+
+static void
+update_can_undo_can_redo (GtkSourceUndoManagerDefault *manager)
+{
+ gboolean can_undo;
+ gboolean can_redo;
+
+ if (manager->priv->location != NULL)
+ {
+ can_undo = manager->priv->location->prev != NULL;
+ can_redo = TRUE;
+ }
+ else
+ {
+ can_undo = manager->priv->action_groups->tail != NULL;
+ can_redo = FALSE;
+ }
+
+ if (manager->priv->can_undo != can_undo)
+ {
+ manager->priv->can_undo = can_undo;
+ gtk_source_undo_manager_can_undo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
+ }
+
+ if (manager->priv->can_redo != can_redo)
+ {
+ manager->priv->can_redo = can_redo;
+ gtk_source_undo_manager_can_redo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
+ }
+}
+
+static void
+remove_last_action_group (GtkSourceUndoManagerDefault *manager)
+{
+ ActionGroup *group;
+
+ if (manager->priv->action_groups->length == 0)
+ {
+ return;
+ }
+
+ if (manager->priv->location == manager->priv->action_groups->tail)
+ {
+ manager->priv->location = NULL;
+ }
+
+ if (manager->priv->has_saved_location)
+ {
+ if (manager->priv->saved_location == NULL)
+ {
+ manager->priv->has_saved_location = FALSE;
+ }
+ else if (manager->priv->saved_location == manager->priv->action_groups->tail)
+ {
+ manager->priv->saved_location = NULL;
+ }
+ }
+
+ group = g_queue_pop_tail (manager->priv->action_groups);
+ action_group_free (group);
+}
+
+static void
+remove_redo_action_groups (GtkSourceUndoManagerDefault *manager)
+{
+ while (manager->priv->location != NULL)
+ {
+ remove_last_action_group (manager);
+ }
+}
+
+static void
+insert_action (GtkSourceUndoManagerDefault *manager,
+ Action *new_action)
+{
+ ActionGroup *group;
+
+ remove_redo_action_groups (manager);
+ g_assert (manager->priv->location == NULL);
+
+ group = action_group_new ();
+ g_queue_push_tail (group->actions, new_action);
+
+ g_queue_push_tail (manager->priv->action_groups, group);
+
+ update_can_undo_can_redo (manager);
+}
+
/* Buffer signal handlers */
static void
@@ -143,6 +454,20 @@ insert_text_cb (GtkTextBuffer *buffer,
gint length,
GtkSourceUndoManagerDefault *manager)
{
+ Action *action = action_insert_new ();
+ gint start;
+ gchar *text_copy;
+
+ start = gtk_text_iter_get_offset (location);
+ action->action.insert.start = start;
+
+ /* text_copy is nul-terminated. */
+ text_copy = g_strndup (text, length);
+ action->action.insert.text = text_copy;
+
+ action->action.insert.end = start + g_utf8_strlen (text_copy, -1);
+
+ insert_action (manager, action);
}
static void
@@ -151,6 +476,13 @@ delete_range_cb (GtkTextBuffer *buffer,
GtkTextIter *end,
GtkSourceUndoManagerDefault *manager)
{
+ Action *action = action_delete_new ();
+
+ action->action.delete.start = gtk_text_iter_get_offset (start);
+ action->action.delete.end = gtk_text_iter_get_offset (end);
+ action->action.insert.text = gtk_text_buffer_get_slice (buffer, start, end, TRUE);
+
+ insert_action (manager, action);
}
static void
@@ -166,6 +498,38 @@ modified_changed_cb (GtkTextBuffer *buffer,
}
static void
+block_signal_handlers (GtkSourceUndoManagerDefault *manager)
+{
+ g_signal_handlers_block_by_func (manager->priv->buffer,
+ insert_text_cb,
+ manager);
+
+ g_signal_handlers_block_by_func (manager->priv->buffer,
+ delete_range_cb,
+ manager);
+
+ g_signal_handlers_block_by_func (manager->priv->buffer,
+ begin_user_action_cb,
+ manager);
+}
+
+static void
+unblock_signal_handlers (GtkSourceUndoManagerDefault *manager)
+{
+ g_signal_handlers_unblock_by_func (manager->priv->buffer,
+ insert_text_cb,
+ manager);
+
+ g_signal_handlers_unblock_by_func (manager->priv->buffer,
+ delete_range_cb,
+ manager);
+
+ g_signal_handlers_unblock_by_func (manager->priv->buffer,
+ begin_user_action_cb,
+ manager);
+}
+
+static void
set_buffer (GtkSourceUndoManagerDefault *manager,
GtkTextBuffer *buffer)
{
@@ -275,6 +639,11 @@ gtk_source_undo_manager_default_dispose (GObject *object)
static void
gtk_source_undo_manager_default_finalize (GObject *object)
{
+ GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
+
+ g_queue_free_full (manager->priv->action_groups,
+ (GDestroyNotify) action_group_free);
+
G_OBJECT_CLASS (gtk_source_undo_manager_default_parent_class)->finalize (object);
}
@@ -311,40 +680,138 @@ static void
gtk_source_undo_manager_default_init (GtkSourceUndoManagerDefault *manager)
{
manager->priv = gtk_source_undo_manager_default_get_instance_private (manager);
+
+ manager->priv->action_groups = g_queue_new ();
}
/* Interface implementation */
static gboolean
-gtk_source_undo_manager_can_undo_impl (GtkSourceUndoManager *manager)
+gtk_source_undo_manager_can_undo_impl (GtkSourceUndoManager *undo_manager)
{
- return GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager)->priv->can_undo;
+ GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
+ return manager->priv->can_undo;
}
static gboolean
-gtk_source_undo_manager_can_redo_impl (GtkSourceUndoManager *manager)
+gtk_source_undo_manager_can_redo_impl (GtkSourceUndoManager *undo_manager)
{
- return GTK_SOURCE_UNDO_MANAGER_DEFAULT (manager)->priv->can_redo;
+ GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
+ return manager->priv->can_redo;
+}
+
+static void
+gtk_source_undo_manager_undo_impl (GtkSourceUndoManager *undo_manager)
+{
+ GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
+ GList *prev_node;
+ ActionGroup *group;
+ GList *l;
+
+ g_return_if_fail (manager->priv->can_undo);
+
+ if (manager->priv->location != NULL)
+ {
+ prev_node = manager->priv->location->prev;
+ }
+ else
+ {
+ prev_node = manager->priv->action_groups->tail;
+ }
+
+ g_assert (prev_node != NULL);
+ group = prev_node->data;
+
+ block_signal_handlers (manager);
+
+ for (l = group->actions->tail; l != NULL; l = l->prev)
+ {
+ Action *action = l->data;
+ action_undo (manager->priv->buffer, action);
+ }
+
+ unblock_signal_handlers (manager);
+
+ manager->priv->location = prev_node;
+ update_can_undo_can_redo (manager);
}
static void
-gtk_source_undo_manager_undo_impl (GtkSourceUndoManager *manager)
+gtk_source_undo_manager_redo_impl (GtkSourceUndoManager *undo_manager)
{
+ GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
+ ActionGroup *group;
+ GList *l;
+
+ g_return_if_fail (manager->priv->can_redo);
+ g_assert (manager->priv->location != NULL);
+
+ group = manager->priv->location->data;
+
+ block_signal_handlers (manager);
+
+ for (l = group->actions->head; l != NULL; l = l->next)
+ {
+ Action *action = l->data;
+ action_redo (manager->priv->buffer, action);
+ }
+
+ unblock_signal_handlers (manager);
+
+ manager->priv->location = manager->priv->location->next;
+ update_can_undo_can_redo (manager);
}
static void
-gtk_source_undo_manager_redo_impl (GtkSourceUndoManager *manager)
+gtk_source_undo_manager_begin_not_undoable_action_impl (GtkSourceUndoManager *undo_manager)
{
+ GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
+ manager->priv->running_not_undoable_actions++;
+
+ if (manager->priv->running_not_undoable_actions == 1)
+ {
+ block_signal_handlers (manager);
+ }
}
static void
-gtk_source_undo_manager_begin_not_undoable_action_impl (GtkSourceUndoManager *manager)
+clear_all (GtkSourceUndoManagerDefault *manager)
{
+ GList *l;
+
+ if (manager->priv->has_saved_location &&
+ manager->priv->saved_location != manager->priv->location)
+ {
+ manager->priv->has_saved_location = FALSE;
+ }
+
+ for (l = manager->priv->action_groups->head; l != NULL; l = l->next)
+ {
+ ActionGroup *group = l->data;
+ action_group_free (group);
+ }
+
+ g_queue_clear (manager->priv->action_groups);
+ manager->priv->location = NULL;
+ manager->priv->saved_location = NULL;
+
+ update_can_undo_can_redo (manager);
}
static void
-gtk_source_undo_manager_end_not_undoable_action_impl (GtkSourceUndoManager *manager)
+gtk_source_undo_manager_end_not_undoable_action_impl (GtkSourceUndoManager *undo_manager)
{
+ GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
+
+ g_return_if_fail (manager->priv->running_not_undoable_actions > 0);
+
+ manager->priv->running_not_undoable_actions--;
+
+ if (manager->priv->running_not_undoable_actions == 0)
+ {
+ unblock_signal_handlers (manager);
+ clear_all (manager);
+ }
}
static void
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]