[gtk/wip/otte/undo: 6/17] entry: Rework undo



commit 8e8ee8142ee67597d35af0dd2568ed54a0261cb8
Author: Benjamin Otte <otte redhat com>
Date:   Sun Aug 16 03:09:09 2015 +0200

    entry: Rework undo
    
    Instead of storing the actions that were performed, store the before
    and after state of the entry.
    
    This also allows for a very easy way to do merges.

 gtk/gtkentry.c                   |  64 ++++++++------
 gtk/gtkentryundocommand.c        | 186 ++++++++++++++++++++++++++++-----------
 gtk/gtkentryundocommandprivate.h |  17 +++-
 3 files changed, 185 insertions(+), 82 deletions(-)
---
diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index 25fda154c9..438d0e07e5 100644
--- a/gtk/gtkentry.c
+++ b/gtk/gtkentry.c
@@ -5371,6 +5371,17 @@ gtk_entry_remove_password_hint (gpointer data)
   return FALSE;
 }
 
+static void
+gtk_entry_record_undo_command (GtkEntry               *entry,
+                               const GtkEntrySnapshot *before)
+{
+  GtkUndoCommand *command;
+
+  command = gtk_entry_undo_command_new (entry, before);
+  gtk_undo_stack_push (entry->priv->undo_stack, command);
+  g_object_unref (command);
+}
+
 /* Default signal handlers
  */
 static void
@@ -5379,6 +5390,9 @@ gtk_entry_real_insert_text (GtkEditable *editable,
                            gint         new_text_length,
                            gint        *position)
 {
+  GtkEntry *entry = GTK_ENTRY (editable);
+  GtkEntryPrivate *priv = entry->priv;
+  GtkEntrySnapshot snapshot = { NULL, };
   guint n_inserted;
   gint n_chars;
 
@@ -5389,11 +5403,20 @@ gtk_entry_real_insert_text (GtkEditable *editable,
    * following signal handlers: buffer_inserted_text(), buffer_notify_display_text(),
    * buffer_notify_text(), buffer_notify_length()
    */
-  begin_change (GTK_ENTRY (editable));
+  if (priv->undo_mode == GTK_ENTRY_UNDO_RECORD)
+    gtk_entry_snapshot_init_from_entry (&snapshot, entry);
 
-  n_inserted = gtk_entry_buffer_insert_text (get_buffer (GTK_ENTRY (editable)), *position, new_text, 
n_chars);
+  begin_change (entry);
 
-  end_change (GTK_ENTRY (editable));
+  n_inserted = gtk_entry_buffer_insert_text (get_buffer (entry), *position, new_text, n_chars);
+
+  end_change (entry);
+
+  if (priv->undo_mode == GTK_ENTRY_UNDO_RECORD)
+    {
+      gtk_entry_record_undo_command (entry, &snapshot);
+      gtk_entry_snapshot_clear (&snapshot);
+    }
 
   if (n_inserted != n_chars)
       gtk_widget_error_bell (GTK_WIDGET (editable));
@@ -5401,19 +5424,6 @@ gtk_entry_real_insert_text (GtkEditable *editable,
   *position += n_inserted;
 }
 
-static void
-gtk_entry_record_undo_command (GtkEntry   *entry,
-                               int         start_pos,
-                               const char *deleted_text,
-                               const char *inserted_text)
-{
-  GtkUndoCommand *command;
-
-  command = gtk_entry_undo_command_new (entry, start_pos, deleted_text, inserted_text);
-  gtk_undo_stack_push (entry->priv->undo_stack, command);
-  g_object_unref (command);
-}
-
 static void
 gtk_entry_real_delete_text (GtkEditable *editable,
                             gint         start_pos,
@@ -5421,6 +5431,7 @@ gtk_entry_real_delete_text (GtkEditable *editable,
 {
   GtkEntry *entry = GTK_ENTRY (editable);
   GtkEntryPrivate *priv = entry->priv;
+  GtkEntrySnapshot snapshot = { NULL, };
 
   /*
    * The actual deletion from the buffer. This will end up firing the
@@ -5428,23 +5439,20 @@ gtk_entry_real_delete_text (GtkEditable *editable,
    * buffer_notify_text(), buffer_notify_length()
    */
 
-  begin_change (entry);
-
   if (priv->undo_mode == GTK_ENTRY_UNDO_RECORD)
-    {
-      char *deleted_text = gtk_entry_get_chars (GTK_EDITABLE (entry), start_pos, end_pos);
-
-      gtk_entry_record_undo_command (entry,
-                                     start_pos,
-                                     deleted_text,
-                                     NULL);
+    gtk_entry_snapshot_init_from_entry (&snapshot, entry);
 
-      g_free (deleted_text);
-    }
+  begin_change (entry);
 
   gtk_entry_buffer_delete_text (get_buffer (entry), start_pos, end_pos - start_pos);
 
   end_change (entry);
+
+  if (priv->undo_mode == GTK_ENTRY_UNDO_RECORD)
+    {
+      gtk_entry_record_undo_command (entry, &snapshot);
+      gtk_entry_snapshot_clear (&snapshot);
+    }
 }
 
 /* GtkEntryBuffer signal handlers
@@ -5473,8 +5481,6 @@ buffer_inserted_text (GtkEntryBuffer *buffer,
 
   if (priv->undo_mode == GTK_ENTRY_UNDO_RESET)
     gtk_undo_stack_clear (priv->undo_stack);
-  else if (priv->undo_mode == GTK_ENTRY_UNDO_RECORD)
-    gtk_entry_record_undo_command (entry, position, NULL, chars);
 
   /* Calculate the password hint if it needs to be displayed. */
   if (n_chars == 1 && !priv->visible)
diff --git a/gtk/gtkentryundocommand.c b/gtk/gtkentryundocommand.c
index e9fa703aca..6a902ea38d 100644
--- a/gtk/gtkentryundocommand.c
+++ b/gtk/gtkentryundocommand.c
@@ -27,21 +27,50 @@
 #include "gtkentryprivate.h"
 
 typedef struct _GtkEntryUndoCommandPrivate GtkEntryUndoCommandPrivate;
+
 struct _GtkEntryUndoCommandPrivate {
   GtkEntry *entry;              /* entry we're operating on or NULL if entry was deleted */
-  gsize char_offset;            /* offset in characters into entry's text */
-  char *text_deleted;           /* text that will be deleted if we *redo* this command */
-  char *text_entered;           /* text that will be entered if we *redo* this command */
+  GtkEntrySnapshot before;      /* what we undo to */
+  GtkEntrySnapshot after;       /* what we redo to */
 };
 
 G_DEFINE_TYPE_WITH_CODE (GtkEntryUndoCommand, gtk_entry_undo_command, GTK_TYPE_UNDO_COMMAND,
                          G_ADD_PRIVATE (GtkEntryUndoCommand))
 
+void
+gtk_entry_snapshot_init_from_entry (GtkEntrySnapshot *snapshot,
+                                    GtkEntry         *entry)
+{
+  gint start, end;
+
+  snapshot->text = g_strdup (gtk_entry_get_text (entry));
+  gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end);
+  snapshot->cursor = gtk_editable_get_position (GTK_EDITABLE (entry));
+  if (start == snapshot->cursor)
+    snapshot->selection_start = end;
+  else
+    snapshot->selection_start = start;
+}
+
+void
+gtk_entry_snapshot_clear (GtkEntrySnapshot *snapshot)
+{
+  g_free (snapshot->text);
+  snapshot->text = NULL;
+}
+
+static void
+gtk_entry_snapshot_copy (GtkEntrySnapshot       *target,
+                         const GtkEntrySnapshot *source)
+{
+  target->text = g_strdup (source->text);
+  target->cursor = source->cursor;
+  target->selection_start = source->selection_start;
+}
+
 static gboolean
-gtk_entry_undo_command_run (GtkEntry   *entry,
-                            int         position,
-                            int         chars_to_delete,
-                            const char *text_to_insert)
+gtk_entry_undo_command_run (GtkEntry               *entry,
+                            const GtkEntrySnapshot *snapshot)
 {
   GtkEntryUndoMode old_mode;
 
@@ -50,18 +79,8 @@ gtk_entry_undo_command_run (GtkEntry   *entry,
 
   old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_REPLAY);
 
-  if (chars_to_delete)
-    gtk_editable_delete_text (GTK_EDITABLE (entry),
-                              position,
-                              position + chars_to_delete);
-
-  if (text_to_insert)
-    gtk_editable_insert_text (GTK_EDITABLE (entry),
-                              text_to_insert,
-                              -1,
-                              &position);
-
-  gtk_editable_set_position (GTK_EDITABLE (entry), position);
+  gtk_entry_set_text (entry, snapshot->text);
+  gtk_editable_select_region (GTK_EDITABLE (entry), snapshot->cursor, snapshot->selection_start);
 
   gtk_entry_set_undo_mode (entry, old_mode);
 
@@ -73,10 +92,7 @@ gtk_entry_undo_command_undo (GtkUndoCommand *command)
 {
   GtkEntryUndoCommandPrivate *priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND 
(command));
 
-  return gtk_entry_undo_command_run (priv->entry,
-                                     priv->char_offset,
-                                     priv->text_entered ? g_utf8_strlen (priv->text_entered, -1) : 0,
-                                     priv->text_deleted);
+  return gtk_entry_undo_command_run (priv->entry, &priv->before);
 }
 
 gboolean
@@ -84,30 +100,105 @@ gtk_entry_undo_command_redo (GtkUndoCommand *command)
 {
   GtkEntryUndoCommandPrivate *priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND 
(command));
 
-  return gtk_entry_undo_command_run (priv->entry,
-                                     priv->char_offset,
-                                     priv->text_deleted ? g_utf8_strlen (priv->text_deleted, -1) : 0,
-                                     priv->text_entered);
+  return gtk_entry_undo_command_run (priv->entry, &priv->after);
+}
+
+static GtkUndoCommand *
+gtk_entry_undo_command_new_from_snapshots (GtkEntry               *entry,
+                                           const GtkEntrySnapshot *before,
+                                           const GtkEntrySnapshot *after)
+{
+  GtkEntryUndoCommand *command;
+  GtkEntryUndoCommandPrivate *priv;
+
+  command = g_object_new (GTK_TYPE_ENTRY_UNDO_COMMAND, NULL);
+  priv = gtk_entry_undo_command_get_instance_private (command);
+
+  priv->entry = entry;
+  gtk_entry_snapshot_copy (&priv->before, before);
+  gtk_entry_snapshot_copy (&priv->after, after);
+
+  return GTK_UNDO_COMMAND (command);
 }
 
 GtkUndoCommand *
 gtk_entry_undo_command_merge (GtkUndoCommand *command,
                               GtkUndoCommand *followup)
 {
-  return NULL;
+  GtkEntryUndoCommandPrivate *command_priv = gtk_entry_undo_command_get_instance_private 
(GTK_ENTRY_UNDO_COMMAND (command));
+  GtkEntryUndoCommandPrivate *followup_priv = gtk_entry_undo_command_get_instance_private 
(GTK_ENTRY_UNDO_COMMAND (followup));
+
+  if (!GTK_IS_ENTRY_UNDO_COMMAND (followup))
+    return NULL;
+
+  if (command_priv->entry != followup_priv->entry)
+    return NULL;
+
+  if (!g_str_equal (command_priv->after.text, followup_priv->before.text))
+    return NULL;
+
+  /* We don't insist on cursor positions being equal here, someone
+   * might ie. move the cursor to correct a typo
+   */
+  return gtk_entry_undo_command_new_from_snapshots (command_priv->entry, &command_priv->before, 
&followup_priv->after);
+}
+
+static guint
+get_prefix_len (const char *str1,
+                const char *str2)
+{
+  guint i;
+
+  for (i = 0; str1[i] == str2[i] && str1[i] != 0; i++)
+    {
+      /* nothing to do here */
+    }
+
+  return i;
+}
+
+static guint
+get_suffix_len (const char *str1,
+                guint       len1,
+                const char *str2,
+                guint       len2)
+{
+  const char *cur1, *cur2;
+  guint i, max_len;
+
+  cur1 = str1 + len1 - 1;
+  cur2 = str2 + len2 - 1;
+  max_len = MIN (len1, len2);
+
+  for (i = 0; *cur1 == *cur2 && i < max_len; i++)
+    {
+      cur1--;
+      cur2--;
+    }
+
+  return i;
 }
 
 char *
 gtk_entry_undo_command_describe (GtkUndoCommand *command)
 {
   GtkEntryUndoCommandPrivate *priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND 
(command));
-
-  if (priv->text_entered)
-    return g_strdup_printf (_("Entered `%s'"), priv->text_entered);
-  else if (priv->text_deleted)
-    return g_strdup_printf (_("Deleted `%s'"), priv->text_deleted);
+  guint before_len, after_len, prefix_len, suffix_len;
+
+  before_len = strlen (priv->before.text);
+  after_len = strlen (priv->after.text);
+  prefix_len = get_prefix_len (priv->before.text, priv->after.text);
+  suffix_len = get_suffix_len (priv->before.text, before_len,
+                               priv->after.text, after_len);
+
+  if (before_len == after_len && before_len == prefix_len)
+    return g_strdup (_("No changes")); /* huh? */
+  else if (prefix_len + suffix_len == before_len)
+    return g_strdup_printf (_("Entered `%*s'"), after_len - prefix_len - suffix_len, priv->after.text + 
prefix_len);
+  else if (prefix_len + suffix_len == after_len)
+    return g_strdup_printf (_("Deleted `%*s'"), before_len - prefix_len - suffix_len, priv->before.text + 
prefix_len);
   else
-    return g_strdup (_("Did nothing"));
+    return g_strdup (_("Text changed"));
 }
 
 static void
@@ -115,8 +206,8 @@ gtk_entry_undo_command_finalize (GObject *object)
 {
   GtkEntryUndoCommandPrivate *priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND 
(object));
 
-  g_free (priv->text_deleted);
-  g_free (priv->text_entered);
+  gtk_entry_snapshot_clear (&priv->before);
+  gtk_entry_snapshot_clear (&priv->after);
 
   G_OBJECT_CLASS (gtk_entry_undo_command_parent_class)->finalize (object);
 }
@@ -141,24 +232,21 @@ gtk_entry_undo_command_init (GtkEntryUndoCommand *command)
 }
 
 GtkUndoCommand *
-gtk_entry_undo_command_new (GtkEntry   *entry,
-                            int         position,
-                            const char *text_deleted,
-                            const char *text_entered)
+gtk_entry_undo_command_new (GtkEntry               *entry,
+                            const GtkEntrySnapshot *before)
 {
-  GtkEntryUndoCommand *result;
-  GtkEntryUndoCommandPrivate *priv;
+  GtkEntrySnapshot after;
+  GtkUndoCommand *result;
 
   g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL);
+  g_return_val_if_fail (before != NULL, NULL);
 
-  result = g_object_new (GTK_TYPE_ENTRY_UNDO_COMMAND, NULL);
+  gtk_entry_snapshot_init_from_entry (&after, entry);
 
-  priv = gtk_entry_undo_command_get_instance_private (result);
-  priv->entry = entry;
-  priv->char_offset = position;
-  priv->text_deleted = g_strdup (text_deleted);
-  priv->text_entered = g_strdup (text_entered);
+  result = gtk_entry_undo_command_new_from_snapshots (entry, before, &after);
+  
+  gtk_entry_snapshot_clear (&after);
 
-  return GTK_UNDO_COMMAND (result);
+  return result;
 }
 
diff --git a/gtk/gtkentryundocommandprivate.h b/gtk/gtkentryundocommandprivate.h
index 23b69a648e..267c35284b 100644
--- a/gtk/gtkentryundocommandprivate.h
+++ b/gtk/gtkentryundocommandprivate.h
@@ -34,6 +34,13 @@ G_BEGIN_DECLS
 
 typedef struct _GtkEntryUndoCommand           GtkEntryUndoCommand;
 typedef struct _GtkEntryUndoCommandClass      GtkEntryUndoCommandClass;
+typedef struct _GtkEntrySnapshot GtkEntrySnapshot;
+
+struct _GtkEntrySnapshot {
+  char *text;                   /* text of the whole entry */
+  guint cursor;                 /* cursor position */
+  guint selection_start;        /* selection start. Equal to cursor if no selection */
+};
 
 struct _GtkEntryUndoCommand
 {
@@ -47,10 +54,12 @@ struct _GtkEntryUndoCommandClass
 
 GType                   gtk_entry_undo_command_get_type         (void) G_GNUC_CONST;
 
-GtkUndoCommand *        gtk_entry_undo_command_new              (GtkEntry       *entry,
-                                                                 int             position,
-                                                                 const char     *text_deleted,
-                                                                 const char     *text_entered);
+GtkUndoCommand *        gtk_entry_undo_command_new              (GtkEntry               *entry,
+                                                                 const GtkEntrySnapshot *before);
+
+void                    gtk_entry_snapshot_init_from_entry      (GtkEntrySnapshot       *snapshot,
+                                                                 GtkEntry               *entry);
+void                    gtk_entry_snapshot_clear                (GtkEntrySnapshot       *snapshot);
 
 G_END_DECLS
 


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