[gtk/wip/otte/undo: 2/17] entry: Add undo stack support



commit fd9c18f022a15f2b53b984234e7ae4ab19325be0
Author: Benjamin Otte <otte redhat com>
Date:   Tue Aug 4 03:02:47 2015 +0200

    entry: Add undo stack support
    
    So far, undo commands are just recorded, no support for actually undoing
    them exists.

 gtk/Makefile.am                  |   2 +
 gtk/gtkentry.c                   |  90 +++++++++++++++++++--
 gtk/gtkentryprivate.h            |   9 +++
 gtk/gtkentryundocommand.c        | 164 +++++++++++++++++++++++++++++++++++++++
 gtk/gtkentryundocommandprivate.h |  57 ++++++++++++++
 5 files changed, 317 insertions(+), 5 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index f75dfc4736..6d404e4d58 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -427,6 +427,7 @@ gtk_private_h_sources =             \
        gtkdialogprivate.h      \
        gtkdndprivate.h         \
        gtkentryprivate.h       \
+       gtkentryundocommandprivate.h    \
        gtkeventcontrollerprivate.h     \
        gtkfilechooserembed.h   \
        gtkfilechooserentry.h   \
@@ -676,6 +677,7 @@ gtk_base_c_sources =                \
        gtkentry.c              \
        gtkentrybuffer.c        \
        gtkentrycompletion.c    \
+       gtkentryundocommand.c   \
        gtkeventbox.c           \
        gtkeventcontroller.c    \
        gtkexpander.c           \
diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index a7d04fbb82..8c43b8ae07 100644
--- a/gtk/gtkentry.c
+++ b/gtk/gtkentry.c
@@ -38,6 +38,7 @@
 #include "gtkdnd.h"
 #include "gtkentry.h"
 #include "gtkentrybuffer.h"
+#include "gtkentryundocommandprivate.h"
 #include "gtkiconhelperprivate.h"
 #include "gtkimcontextsimple.h"
 #include "gtkimmulticontext.h"
@@ -65,6 +66,7 @@
 #include "gtkwidgetprivate.h"
 #include "gtkstylecontextprivate.h"
 #include "gtktexthandleprivate.h"
+#include "gtkundostackprivate.h"
 #include "gtkpopover.h"
 #include "gtktoolbar.h"
 #include "gtkmagnifierprivate.h"
@@ -150,7 +152,8 @@ struct _GtkEntryPrivate
   PangoAttrList         *attrs;
   PangoTabArray         *tabs;
 
-  gchar        *im_module;
+  gchar                 *im_module;
+  GtkUndoStack          *undo_stack;
 
   gdouble       progress_fraction;
   gdouble       progress_pulse_fraction;
@@ -210,6 +213,7 @@ struct _GtkEntryPrivate
   guint         overwrite_mode          : 1;
   guint         visible                 : 1;
 
+  GtkEntryUndoMode undo_mode            : 2;
   guint         activates_default       : 1;
   guint         cache_includes_preedit  : 1;
   guint         caps_lock_warning       : 1;
@@ -2737,6 +2741,8 @@ gtk_entry_init (GtkEntry *entry)
   g_signal_connect (priv->im_context, "delete-surrounding",
                    G_CALLBACK (gtk_entry_delete_surrounding_cb), entry);
 
+  priv->undo_stack = gtk_undo_stack_new ();
+
   context = gtk_widget_get_style_context (GTK_WIDGET (entry));
   gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
 
@@ -3027,6 +3033,8 @@ gtk_entry_finalize (GObject *object)
 
   g_object_unref (priv->im_context);
 
+  g_object_unref (priv->undo_stack);
+
   if (priv->blink_timeout)
     g_source_remove (priv->blink_timeout);
 
@@ -5359,22 +5367,50 @@ 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,
                             gint         end_pos)
 {
+  GtkEntry *entry = GTK_ENTRY (editable);
+  GtkEntryPrivate *priv = entry->priv;
+
   /*
    * The actual deletion from the buffer. This will end up firing the
    * following signal handlers: buffer_deleted_text(), buffer_notify_display_text(),
    * buffer_notify_text(), buffer_notify_length()
    */
 
-  begin_change (GTK_ENTRY (editable));
+  begin_change (entry);
 
-  gtk_entry_buffer_delete_text (get_buffer (GTK_ENTRY (editable)), start_pos, end_pos - start_pos);
+  if (priv->undo_mode == GTK_ENTRY_UNDO_RECORD)
+    {
+      char *deleted_text = gtk_entry_get_chars (GTK_EDITABLE (entry), start_pos, end_pos);
 
-  end_change (GTK_ENTRY (editable));
+      gtk_entry_record_undo_command (entry,
+                                     start_pos,
+                                     deleted_text,
+                                     NULL);
+
+      g_free (deleted_text);
+    }
+
+  gtk_entry_buffer_delete_text (get_buffer (entry), start_pos, end_pos - start_pos);
+
+  end_change (entry);
 }
 
 /* GtkEntryBuffer signal handlers
@@ -5401,6 +5437,11 @@ buffer_inserted_text (GtkEntryBuffer *buffer,
 
   gtk_entry_set_positions (entry, current_pos, selection_bound);
 
+  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)
     {
@@ -5453,6 +5494,9 @@ buffer_deleted_text (GtkEntryBuffer *buffer,
   /* We might have deleted the selection */
   gtk_entry_update_primary_selection (entry);
 
+  if (priv->undo_mode == GTK_ENTRY_UNDO_RESET)
+    gtk_undo_stack_clear (priv->undo_stack);
+
   /* Disable the password hint if one exists. */
   if (!priv->visible)
     {
@@ -5764,6 +5808,7 @@ gtk_entry_backspace (GtkEntry *entry)
 {
   GtkEntryPrivate *priv = entry->priv;
   GtkEditable *editable = GTK_EDITABLE (entry);
+  GtkEntryUndoMode old_mode;
   gint prev_pos;
 
   gtk_entry_reset_im_context (entry);
@@ -5776,7 +5821,9 @@ gtk_entry_backspace (GtkEntry *entry)
 
   if (priv->selection_bound != priv->current_pos)
     {
+      old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_RECORD);
       gtk_editable_delete_selection (editable);
+      gtk_entry_set_undo_mode (entry, old_mode);
       return;
     }
 
@@ -5788,6 +5835,8 @@ gtk_entry_backspace (GtkEntry *entry)
       PangoLogAttr *log_attrs;
       gint n_attrs;
 
+      old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_RECORD);
+
       pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
 
       /* Deleting parts of characters */
@@ -5822,6 +5871,8 @@ gtk_entry_backspace (GtkEntry *entry)
        {
           gtk_editable_delete_text (editable, prev_pos, priv->current_pos);
        }
+
+      gtk_entry_set_undo_mode (entry, old_mode);
       
       g_free (log_attrs);
     }
@@ -5862,6 +5913,7 @@ gtk_entry_cut_clipboard (GtkEntry *entry)
 {
   GtkEntryPrivate *priv = entry->priv;
   GtkEditable *editable = GTK_EDITABLE (entry);
+  GtkEntryUndoMode old_mode;
   gint start, end;
 
   if (!priv->visible)
@@ -5875,7 +5927,11 @@ gtk_entry_cut_clipboard (GtkEntry *entry)
   if (priv->editable)
     {
       if (gtk_editable_get_selection_bounds (editable, &start, &end))
-       gtk_editable_delete_text (editable, start, end);
+        {
+          old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_RECORD);
+         gtk_editable_delete_text (editable, start, end);
+          gtk_entry_set_undo_mode (entry, old_mode);
+        }
     }
   else
     {
@@ -6044,12 +6100,17 @@ gtk_entry_delete_surrounding_cb (GtkIMContext *slave,
                                 GtkEntry     *entry)
 {
   GtkEntryPrivate *priv = entry->priv;
+  GtkEntryUndoMode old_mode;
+
+  old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_RECORD);
 
   if (priv->editable)
     gtk_editable_delete_text (GTK_EDITABLE (entry),
                               priv->current_pos + offset,
                               priv->current_pos + offset + n_chars);
 
+  gtk_entry_set_undo_mode (entry, old_mode);
+
   return TRUE;
 }
 
@@ -6063,12 +6124,15 @@ gtk_entry_enter_text (GtkEntry       *entry,
 {
   GtkEntryPrivate *priv = entry->priv;
   GtkEditable *editable = GTK_EDITABLE (entry);
+  GtkEntryUndoMode old_mode;
   gint tmp_pos;
   gboolean old_need_im_reset;
 
   old_need_im_reset = priv->need_im_reset;
   priv->need_im_reset = FALSE;
 
+  old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_RECORD);
+
   if (gtk_editable_get_selection_bounds (editable, NULL, NULL))
     gtk_editable_delete_selection (editable);
   else
@@ -6081,6 +6145,8 @@ gtk_entry_enter_text (GtkEntry       *entry,
   gtk_editable_insert_text (editable, str, strlen (str), &tmp_pos);
   gtk_editable_set_position (editable, tmp_pos);
 
+  gtk_entry_set_undo_mode (entry, old_mode);
+
   priv->need_im_reset = old_need_im_reset;
 }
 
@@ -7329,6 +7395,7 @@ paste_received (GtkClipboard *clipboard,
   GtkEntry *entry = GTK_ENTRY (data);
   GtkEditable *editable = GTK_EDITABLE (entry);
   GtkEntryPrivate *priv = entry->priv;
+  GtkEntryUndoMode old_mode;
   guint button;
 
   button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (priv->multipress_gesture));
@@ -7368,12 +7435,14 @@ paste_received (GtkClipboard *clipboard,
        }
 
       begin_change (entry);
+      old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_RECORD);
       if (gtk_editable_get_selection_bounds (editable, &start, &end))
         gtk_editable_delete_text (editable, start, end);
 
       pos = priv->current_pos;
       gtk_editable_insert_text (editable, text, length, &pos);
       gtk_editable_set_position (editable, pos);
+      gtk_entry_set_undo_mode (entry, old_mode);
       end_change (entry);
 
       if (completion &&
@@ -10956,6 +11025,17 @@ keymap_state_changed (GdkKeymap *keymap,
     remove_capslock_feedback (entry);
 }
 
+GtkEntryUndoMode
+gtk_entry_set_undo_mode (GtkEntry         *entry,
+                         GtkEntryUndoMode  mode)
+{
+  GtkEntryUndoMode result = entry->priv->undo_mode;
+
+  entry->priv->undo_mode = mode;
+
+  return result;
+}
+
 /*
  * _gtk_entry_set_is_cell_renderer:
  * @entry: a #GtkEntry
diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h
index 6d85d87739..3cebcd479f 100644
--- a/gtk/gtkentryprivate.h
+++ b/gtk/gtkentryprivate.h
@@ -26,6 +26,12 @@
 
 G_BEGIN_DECLS
 
+typedef enum {
+  GTK_ENTRY_UNDO_RESET,
+  GTK_ENTRY_UNDO_RECORD,
+  GTK_ENTRY_UNDO_REPLAY
+} GtkEntryUndoMode;
+
 struct _GtkEntryCompletionPrivate
 {
   GtkWidget *entry;
@@ -79,6 +85,9 @@ void     _gtk_entry_completion_popdown      (GtkEntryCompletion *completion);
 void     _gtk_entry_completion_connect      (GtkEntryCompletion *completion,
                                              GtkEntry           *entry);
 void     _gtk_entry_completion_disconnect   (GtkEntryCompletion *completion);
+GtkEntryUndoMode
+         gtk_entry_set_undo_mode            (GtkEntry            *entry,
+                                             GtkEntryUndoMode     mode);
 
 gchar*   _gtk_entry_get_display_text       (GtkEntry *entry,
                                             gint      start_pos,
diff --git a/gtk/gtkentryundocommand.c b/gtk/gtkentryundocommand.c
new file mode 100644
index 0000000000..e9fa703aca
--- /dev/null
+++ b/gtk/gtkentryundocommand.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright © 2015 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkentryundocommandprivate.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "gtkentry.h"
+#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 */
+};
+
+G_DEFINE_TYPE_WITH_CODE (GtkEntryUndoCommand, gtk_entry_undo_command, GTK_TYPE_UNDO_COMMAND,
+                         G_ADD_PRIVATE (GtkEntryUndoCommand))
+
+static gboolean
+gtk_entry_undo_command_run (GtkEntry   *entry,
+                            int         position,
+                            int         chars_to_delete,
+                            const char *text_to_insert)
+{
+  GtkEntryUndoMode old_mode;
+
+  if (entry == NULL)
+    return FALSE;
+
+  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_undo_mode (entry, old_mode);
+
+  return TRUE;
+}
+
+static gboolean
+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);
+}
+
+gboolean
+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);
+}
+
+GtkUndoCommand *
+gtk_entry_undo_command_merge (GtkUndoCommand *command,
+                              GtkUndoCommand *followup)
+{
+  return NULL;
+}
+
+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);
+  else
+    return g_strdup (_("Did nothing"));
+}
+
+static void
+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);
+
+  G_OBJECT_CLASS (gtk_entry_undo_command_parent_class)->finalize (object);
+}
+
+static void
+gtk_entry_undo_command_class_init (GtkEntryUndoCommandClass *klass)
+{
+  GtkUndoCommandClass *undo_class = GTK_UNDO_COMMAND_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_entry_undo_command_finalize;
+
+  undo_class->undo = gtk_entry_undo_command_undo;
+  undo_class->redo = gtk_entry_undo_command_redo;
+  undo_class->merge = gtk_entry_undo_command_merge;
+  undo_class->describe = gtk_entry_undo_command_describe;
+}
+
+static void
+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)
+{
+  GtkEntryUndoCommand *result;
+  GtkEntryUndoCommandPrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL);
+
+  result = g_object_new (GTK_TYPE_ENTRY_UNDO_COMMAND, NULL);
+
+  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);
+
+  return GTK_UNDO_COMMAND (result);
+}
+
diff --git a/gtk/gtkentryundocommandprivate.h b/gtk/gtkentryundocommandprivate.h
new file mode 100644
index 0000000000..23b69a648e
--- /dev/null
+++ b/gtk/gtkentryundocommandprivate.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2015 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_ENTRY_UNDO_COMMAND_PRIVATE_H__
+#define __GTK_ENTRY_UNDO_COMMAND_PRIVATE_H__
+
+#include <gtk/gtkentry.h>
+#include <gtk/gtkundocommandprivate.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_ENTRY_UNDO_COMMAND           (gtk_entry_undo_command_get_type ())
+#define GTK_ENTRY_UNDO_COMMAND(obj)           (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_ENTRY_UNDO_COMMAND, 
GtkEntryUndoCommand))
+#define GTK_ENTRY_UNDO_COMMAND_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_ENTRY_UNDO_COMMAND, 
GtkEntryUndoCommandClass))
+#define GTK_IS_ENTRY_UNDO_COMMAND(obj)        (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_ENTRY_UNDO_COMMAND))
+#define GTK_IS_ENTRY_UNDO_COMMAND_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_ENTRY_UNDO_COMMAND))
+#define GTK_ENTRY_UNDO_COMMAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GTK_TYPE_ENTRY_UNDO_COMMAND, GtkEntryUndoCommandClass))
+
+typedef struct _GtkEntryUndoCommand           GtkEntryUndoCommand;
+typedef struct _GtkEntryUndoCommandClass      GtkEntryUndoCommandClass;
+
+struct _GtkEntryUndoCommand
+{
+  GtkUndoCommand parent;
+};
+
+struct _GtkEntryUndoCommandClass
+{
+  GtkUndoCommandClass parent_class;
+};
+
+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);
+
+G_END_DECLS
+
+#endif /* __GTK_ENTRY_UNDO_COMMAND_PRIVATE_H__ */


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