[gtksourceview/wip/undo-redo] UndoManager: new implementation based on GQueues (not finished)



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]