[gtksourceview/wip/undo-redo-restore-selection: 38/38] UndoManager: restore restore selection (wip)



commit 6d3bb64181fb9089682bdd44cf7e62b82de45bd2
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Sat Oct 11 15:10:46 2014 +0200

    UndoManager: restore restore selection (wip)
    
    Restore the restore selection behavior (but enhanced).

 gtksourceview/gtksourceundomanagerdefault.c |  179 ++++++++++++++++++++++++---
 1 files changed, 163 insertions(+), 16 deletions(-)
---
diff --git a/gtksourceview/gtksourceundomanagerdefault.c b/gtksourceview/gtksourceundomanagerdefault.c
index 6d61fbf..aa4027f 100644
--- a/gtksourceview/gtksourceundomanagerdefault.c
+++ b/gtksourceview/gtksourceundomanagerdefault.c
@@ -39,6 +39,19 @@ typedef enum
        ACTION_TYPE_DELETE
 } ActionType;
 
+/* A more precise deletion type. But currently it's only a guess, we are not
+ * 100% sure of the deletion type. To be sure, we would need to listen to key
+ * events on the GtkSourceView widget, which is more complicated than simply
+ * listening to the insert-text and delete-range GtkTextBuffer signals.
+ */
+typedef enum
+{
+       DELETION_TYPE_SELECTION_DELETED,
+       DELETION_TYPE_BACKSPACE_KEY,
+       DELETION_TYPE_DELETE_KEY,
+       DELETION_TYPE_PROGRAMMATICALLY
+} DeletionType;
+
 struct _Action
 {
        ActionType type;
@@ -58,11 +71,21 @@ struct _Action
         */
        gchar *text;
 
-       /* Used only for a deletion. If forward is TRUE, the Delete key was
-        * probably used. If forward is FALSE, the Backspace key was probably
-        * used.
+       /* Character offsets of the insert and selection bound marks.
+        * They are both -1 or they both match @start or @end.
+        * If the text cursor or the selected text is not related to the action,
+        * the selection is not stored (i.e. -1).
+        * If not -1, when undoing or redoing an action, the insert and
+        * selection bound marks are restored to where they were.
+        * For an insert, @selection_insert and @selection_bound must match
+        * @start, otherwise the selection or cursor position is unrelated to
+        * the insertion.
+        * For a deletion, if @selection_insert and @selection_bound are -1, it
+        * corresponds to DELETION_TYPE_PROGRAMMATICALLY. For all the other
+        * deletion types, the selection is stored.
         */
-       guint forward : 1;
+       gint selection_insert;
+       gint selection_bound;
 };
 
 struct _ActionGroup
@@ -162,7 +185,14 @@ G_DEFINE_TYPE_WITH_CODE (GtkSourceUndoManagerDefault,
 static Action *
 action_new (void)
 {
-       return g_slice_new0 (Action);
+       Action *action;
+
+       action = g_slice_new0 (Action);
+
+       action->selection_insert = -1;
+       action->selection_bound = -1;
+
+       return action;
 }
 
 static void
@@ -706,12 +736,45 @@ action_delete_redo (GtkTextBuffer *buffer,
        delete_text (buffer, action->start, action->end);
 }
 
+static DeletionType
+get_deletion_type (Action *action)
+{
+       g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
+
+       if (action->selection_insert == -1)
+       {
+               g_assert_cmpint (action->selection_bound, ==, -1);
+               return DELETION_TYPE_PROGRAMMATICALLY;
+       }
+
+       if (action->selection_insert == action->end &&
+           action->selection_bound == action->end)
+       {
+               return DELETION_TYPE_BACKSPACE_KEY;
+       }
+
+       if (action->selection_insert == action->start &&
+           action->selection_bound == action->start)
+       {
+               return DELETION_TYPE_DELETE_KEY;
+       }
+
+       g_assert ((action->selection_insert == action->start ||
+                  action->selection_insert == action->end));
+       g_assert ((action->selection_bound == action->start ||
+                  action->selection_bound == action->end));
+
+       return DELETION_TYPE_SELECTION_DELETED;
+}
+
 static gboolean
 action_delete_merge (Action *action,
                     Action *new_action)
 {
        gint new_text_length;
        gunichar new_char;
+       DeletionType deletion_type;
+       DeletionType new_deletion_type;
 
        g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
        g_assert_cmpint (new_action->type, ==, ACTION_TYPE_DELETE);
@@ -722,21 +785,56 @@ action_delete_merge (Action *action,
        new_char = g_utf8_get_char (new_action->text);
        g_assert (new_char != '\n');
 
-       /* A Backspace can not be merged with a Delete.
-        * Two Backspaces or two Deletes must follow each other.
+       /* Two Backspaces or two Deletes must follow each other.
         * In "abc", if the cursor is at offset 2 and I press the Backspace key,
         * then move the cursor after 'c' and press Backspace again, the two
         * deletes won't be merged, since there was a cursor movement in
         * between.
         */
-       if ((action->forward != new_action->forward) ||
-           (action->forward && action->start != new_action->start) ||
-           (!action->forward && action->start != new_action->end))
+       deletion_type = get_deletion_type (action);
+       new_deletion_type = get_deletion_type (new_action);
+
+       if (deletion_type != new_deletion_type)
        {
                return FALSE;
        }
 
-       /* forward, Delete key pressed several times */
+       switch (deletion_type)
+       {
+               /* If the user has selected some text and then has deleted it,
+                * it should be seen as a single action group, not mergeable.  A
+                * good reason for that is to correctly restore the selection.
+                */
+               case DELETION_TYPE_SELECTION_DELETED:
+                       return FALSE;
+
+               /* For memory use it would be better to take it into account,
+                * but the code is simpler like that.
+                */
+               case DELETION_TYPE_PROGRAMMATICALLY:
+                       return FALSE;
+
+               case DELETION_TYPE_DELETE_KEY:
+                       /* Not consecutive deletes. */
+                       if (action->start != new_action->start)
+                       {
+                               return FALSE;
+                       }
+                       break;
+
+               case DELETION_TYPE_BACKSPACE_KEY:
+                       /* Not consecutive backspaces. */
+                       if (action->start != new_action->end)
+                       {
+                               return FALSE;
+                       }
+                       break;
+
+               default:
+                       g_assert_not_reached ();
+       }
+
+       /* Delete key pressed several times. */
        if (action->start == new_action->start)
        {
                gunichar last_char;
@@ -758,10 +856,16 @@ action_delete_merge (Action *action,
 
                action->end += new_text_length;
 
+               /* No need to update the selection, action->start is not
+                * modified.
+                */
+               g_assert_cmpint (action->selection_insert, ==, action->start);
+               g_assert_cmpint (action->selection_bound, ==, action->start);
+
                return TRUE;
        }
 
-       /* backward, Backspace key pressed several times */
+       /* Backspace key pressed several times. */
        if (action->start == new_action->end)
        {
                gunichar last_char;
@@ -786,6 +890,10 @@ action_delete_merge (Action *action,
 
                action->start = new_action->start;
 
+               /* No need to update the selection, action->end is not modified. */
+               g_assert_cmpint (action->selection_insert, ==, action->end);
+               g_assert_cmpint (action->selection_bound, ==, action->end);
+
                return TRUE;
        }
 
@@ -923,6 +1031,25 @@ action_set_cursor_position (GtkTextBuffer *buffer,
 /* Buffer signal handlers */
 
 static void
+set_selection_bounds (GtkTextBuffer *buffer,
+                     Action        *action)
+{
+       GtkTextMark *insert_mark;
+       GtkTextMark *selection_mark;
+       GtkTextIter insert_iter;
+       GtkTextIter selection_iter;
+
+       insert_mark = gtk_text_buffer_get_insert (buffer);
+       selection_mark = gtk_text_buffer_get_selection_bound (buffer);
+
+       gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, insert_mark);
+       gtk_text_buffer_get_iter_at_mark (buffer, &selection_iter, selection_mark);
+
+       action->selection_insert = gtk_text_iter_get_offset (&insert_iter);
+       action->selection_bound = gtk_text_iter_get_offset (&selection_iter);
+}
+
+static void
 insert_text_cb (GtkTextBuffer               *buffer,
                GtkTextIter                 *location,
                const gchar                 *text,
@@ -936,6 +1063,21 @@ insert_text_cb (GtkTextBuffer               *buffer,
        action->text = g_strndup (text, length);
        action->end = action->start + g_utf8_strlen (action->text, -1);
 
+       set_selection_bounds (buffer, action);
+
+       if (action->selection_insert != action->selection_bound ||
+           action->selection_insert != action->start)
+       {
+               action->selection_insert = -1;
+               action->selection_bound = -1;
+       }
+       else
+       {
+               /* The insertion occurred at the cursor. */
+               g_assert_cmpint (action->selection_insert, ==, action->start);
+               g_assert_cmpint (action->selection_bound, ==, action->start);
+       }
+
        insert_action (manager, action);
 }
 
@@ -946,7 +1088,6 @@ delete_range_cb (GtkTextBuffer               *buffer,
                 GtkSourceUndoManagerDefault *manager)
 {
        Action *action = action_new ();
-       GtkTextIter cursor_pos;
 
        action->type = ACTION_TYPE_DELETE;
        action->start = gtk_text_iter_get_offset (start);
@@ -955,10 +1096,16 @@ delete_range_cb (GtkTextBuffer               *buffer,
 
        g_assert_cmpint (action->start, <, action->end);
 
-       gtk_text_buffer_get_iter_at_mark (buffer, &cursor_pos,
-                                         gtk_text_buffer_get_insert (buffer));
+       set_selection_bounds (buffer, action);
 
-       action->forward = gtk_text_iter_equal (&cursor_pos, start);
+       if ((action->selection_insert != action->start &&
+            action->selection_insert != action->end) ||
+           (action->selection_bound != action->start &&
+            action->selection_bound != action->end))
+       {
+               action->selection_insert = -1;
+               action->selection_bound = -1;
+       }
 
        insert_action (manager, action);
 }


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