[gtk/wip/otte/undo: 16/17] undo: Change semantics of gtk_undo_command_merge()



commit afb661e99265a9ac3885ef5ed7dade70f4ab7fef
Author: Benjamin Otte <otte redhat com>
Date:   Sun Aug 23 18:01:26 2015 +0200

    undo: Change semantics of gtk_undo_command_merge()
    
    Merges always succeed. The fallback implementation will create a (newly
    added) GtkUndoCommandChain with the 2 commands to be merged.
    
    The merge function may still return NULL. NULL should be returned if
    merging commands would create a no-op.
    This is implemented in the entry command undo. You can test it by
    entering a character and then typing backspace.

 gtk/Makefile.am                  |   2 +
 gtk/gtkentryundocommand.c        |   5 +-
 gtk/gtkundocommand.c             |   4 +-
 gtk/gtkundocommandchain.c        | 209 +++++++++++++++++++++++++++++++++++++++
 gtk/gtkundocommandchainprivate.h |  57 +++++++++++
 gtk/gtkundostack.c               |  19 ++--
 6 files changed, 286 insertions(+), 10 deletions(-)
---
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index d1b05a8cc3..17927696f0 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -530,6 +530,7 @@ gtk_private_h_sources =             \
        gtktooltipprivate.h     \
        gtktreedatalist.h       \
        gtktreeprivate.h        \
+       gtkundocommandchainprivate.h    \
        gtkundocommandprivate.h \
        gtkundostackprivate.h   \
        gtkundoundocommandprivate.h     \
@@ -866,6 +867,7 @@ gtk_base_c_sources =                \
        gtktreeviewcolumn.c     \
        gtktypebuiltins.c       \
        gtkundocommand.c        \
+       gtkundocommandchain.c   \
        gtkundostack.c          \
        gtkundoundocommand.c    \
        gtkvolumebutton.c       \
diff --git a/gtk/gtkentryundocommand.c b/gtk/gtkentryundocommand.c
index c88570caa9..1797a10e53 100644
--- a/gtk/gtkentryundocommand.c
+++ b/gtk/gtkentryundocommand.c
@@ -195,9 +195,12 @@ gtk_entry_undo_command_merge (GtkUndoCommand *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;
+    return GTK_UNDO_COMMAND_CLASS (gtk_entry_undo_command_parent_class)->merge (command, followup);
 
   if (command_priv->entry != followup_priv->entry)
+    return GTK_UNDO_COMMAND_CLASS (gtk_entry_undo_command_parent_class)->merge (command, followup);
+
+  if (g_str_equal (command_priv->before.text, followup_priv->after.text))
     return NULL;
 
   return gtk_entry_undo_command_new_from_snapshots (command_priv->entry,
diff --git a/gtk/gtkundocommand.c b/gtk/gtkundocommand.c
index b7fc36683d..01241454e2 100644
--- a/gtk/gtkundocommand.c
+++ b/gtk/gtkundocommand.c
@@ -23,6 +23,8 @@
 
 #include <glib/gi18n-lib.h>
 
+#include "gtkundocommandchainprivate.h"
+
 typedef struct _GtkUndoCommandPrivate GtkUndoCommandPrivate;
 struct _GtkUndoCommandPrivate {
   gint64 timestamp;
@@ -121,7 +123,7 @@ GtkUndoCommand *
 gtk_undo_command_real_merge (GtkUndoCommand *command,
                              GtkUndoCommand *followup)
 {
-  return NULL;
+  return gtk_undo_command_chain_new_merge (command, followup);
 }
 
 gboolean
diff --git a/gtk/gtkundocommandchain.c b/gtk/gtkundocommandchain.c
new file mode 100644
index 0000000000..54f9820a3e
--- /dev/null
+++ b/gtk/gtkundocommandchain.c
@@ -0,0 +1,209 @@
+/*
+ * 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 "gtkundocommandchainprivate.h"
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+typedef struct _GtkUndoCommandChainPrivate GtkUndoCommandChainPrivate;
+
+struct _GtkUndoCommandChainPrivate {
+  GtkUndoCommand **commands;
+  gsize            n_commands;
+};
+
+G_DEFINE_TYPE_WITH_CODE (GtkUndoCommandChain, gtk_undo_command_chain, GTK_TYPE_UNDO_COMMAND,
+                         G_ADD_PRIVATE (GtkUndoCommandChain))
+
+static gboolean
+gtk_undo_command_chain_undo (GtkUndoCommand *command)
+{
+  GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN 
(command));
+  gboolean result;
+  gsize i;
+
+  result = FALSE;
+
+  for (i = priv->n_commands; i --> 0; )
+    {
+      result |= gtk_undo_command_undo (priv->commands[i]);
+    }
+
+  return result;
+}
+
+gboolean
+gtk_undo_command_chain_redo (GtkUndoCommand *command)
+{
+  GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN 
(command));
+  gboolean result;
+  gsize i;
+
+  result = FALSE;
+
+  for (i = 0; i < priv->n_commands; i++)
+    {
+      result |= gtk_undo_command_redo (priv->commands[i]);
+    }
+
+  return result;
+}
+
+GtkUndoCommand *
+gtk_undo_command_chain_merge (GtkUndoCommand *command,
+                              GtkUndoCommand *followup)
+{
+  return gtk_undo_command_chain_new_merge (command, followup);
+}
+
+static void
+gtk_undo_command_chain_dispose (GObject *object)
+{
+  GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN 
(object));
+  gsize i;
+
+  for (i = 0; i < priv->n_commands; i++)
+    {
+      g_object_unref (priv->commands[i]);
+    }
+  g_free (priv->commands);
+  priv->n_commands = 0;
+
+  G_OBJECT_CLASS (gtk_undo_command_chain_parent_class)->dispose (object);
+}
+
+static void
+gtk_undo_command_chain_class_init (GtkUndoCommandChainClass *klass)
+{
+  GtkUndoCommandClass *undo_class = GTK_UNDO_COMMAND_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gtk_undo_command_chain_dispose;
+
+  undo_class->undo = gtk_undo_command_chain_undo;
+  undo_class->redo = gtk_undo_command_chain_redo;
+  undo_class->merge = gtk_undo_command_chain_merge;
+}
+
+static void
+gtk_undo_command_chain_init (GtkUndoCommandChain *command)
+{
+}
+
+/**
+ * gtk_undo_command_chain_new:
+ * @commands: (array length=n_commands): commands to chain
+ * @n_commands: number of commands
+ *
+ * Creates a new command that undoes or redoes all given @commands
+ * in order. The first command in the @commands array is the oldest
+ * command. So it is the first command to be executed during redo
+ * and the last command executed during undo.
+ *
+ * Returns: a new #GtkUndoCommand
+ */
+GtkUndoCommand *
+gtk_undo_command_chain_new (GtkUndoCommand **commands,
+                            gsize            n_commands)
+{
+  GtkUndoCommandChainPrivate *priv;
+  GtkUndoCommand *result;
+  char *title;
+  gsize i;
+
+  g_return_val_if_fail (commands != NULL, NULL);
+  g_return_val_if_fail (n_commands > 0, NULL);
+
+  title = g_strdup_printf (_("Execute %zu commands"), n_commands);
+  result = g_object_new (GTK_TYPE_UNDO_COMMAND_CHAIN,
+                         "timestamp", gtk_undo_command_get_timestamp (commands[n_commands - 1]),
+                         "title", title,
+                         NULL);
+  g_free (title);
+  priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN (result));
+
+  priv->commands = g_new (GtkUndoCommand *, n_commands);
+  priv->n_commands = n_commands;
+  for (i = 0; i < n_commands; i++)
+    {
+      priv->commands[i] = g_object_ref (commands[i]);
+    }
+
+  return result;
+}
+
+/**
+ * gtk_undo_command_chain_new_merge:
+ * @command: first command to merge
+ * @followup: followup command to merge
+ *
+ * Merges the two given command. This is a convenience function for use
+ * in GtkUndCommandClass::merge implementations as a fallback.
+ *
+ * Returns: A new command
+ */
+GtkUndoCommand *
+gtk_undo_command_chain_new_merge (GtkUndoCommand *command,
+                                  GtkUndoCommand *followup)
+{
+  GtkUndoCommand **commands, **first;
+  GtkUndoCommand *result;
+  gsize n_commands, n_first;
+
+  g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), NULL);
+  g_return_val_if_fail (GTK_IS_UNDO_COMMAND (followup), NULL);
+
+  if (GTK_IS_UNDO_COMMAND_CHAIN (command))
+    {
+      GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN 
(command));
+
+      n_commands = n_first = priv->n_commands;
+      first = priv->commands;
+    }
+  else
+    {
+      n_commands = n_first = 1;
+      first = &command;
+    }
+
+  if (GTK_IS_UNDO_COMMAND_CHAIN (followup))
+    {
+      GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN 
(followup));
+
+      n_commands += priv->n_commands;
+      commands = g_new (GtkUndoCommand *, n_commands);
+      memcpy (commands + n_first, priv->commands, sizeof (GtkUndoCommand *) * priv->n_commands);
+    }
+  else
+    {
+      n_commands++;
+      commands = g_new (GtkUndoCommand *, n_commands);
+      commands[n_first] = followup;
+    }
+  memcpy (commands, first, sizeof (GtkUndoCommand *) * n_first);
+
+  result = gtk_undo_command_chain_new (commands, n_commands);
+
+  g_free (commands);
+
+  return result;
+}
diff --git a/gtk/gtkundocommandchainprivate.h b/gtk/gtkundocommandchainprivate.h
new file mode 100644
index 0000000000..520ee9e423
--- /dev/null
+++ b/gtk/gtkundocommandchainprivate.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_UNDO_COMMAND_CHAIN_PRIVATE_H__
+#define __GTK_UNDO_COMMAND_CHAIN_PRIVATE_H__
+
+#include <gtk/gtkentry.h>
+#include <gtk/gtkundocommandprivate.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_UNDO_COMMAND_CHAIN           (gtk_undo_command_chain_get_type ())
+#define GTK_UNDO_COMMAND_CHAIN(obj)           (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_UNDO_COMMAND_CHAIN, 
GtkUndoCommandChain))
+#define GTK_UNDO_COMMAND_CHAIN_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_UNDO_COMMAND_CHAIN, 
GtkUndoCommandChainClass))
+#define GTK_IS_UNDO_COMMAND_CHAIN(obj)        (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_UNDO_COMMAND_CHAIN))
+#define GTK_IS_UNDO_COMMAND_CHAIN_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_UNDO_COMMAND_CHAIN))
+#define GTK_UNDO_COMMAND_CHAIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GTK_TYPE_UNDO_COMMAND_CHAIN, GtkUndoCommandChainClass))
+
+typedef struct _GtkUndoCommandChain           GtkUndoCommandChain;
+typedef struct _GtkUndoCommandChainClass      GtkUndoCommandChainClass;
+
+struct _GtkUndoCommandChain
+{
+  GtkUndoCommand parent;
+};
+
+struct _GtkUndoCommandChainClass
+{
+  GtkUndoCommandClass parent_class;
+};
+
+GType                   gtk_undo_command_chain_get_type         (void) G_GNUC_CONST;
+
+GtkUndoCommand *        gtk_undo_command_chain_new              (GtkUndoCommand        **commnds,
+                                                                 gsize                   n_commands);
+GtkUndoCommand *        gtk_undo_command_chain_new_merge        (GtkUndoCommand         *command,
+                                                                 GtkUndoCommand         *followup);
+
+G_END_DECLS
+
+#endif /* __GTK_UNDO_COMMAND_CHAIN_PRIVATE_H__ */
diff --git a/gtk/gtkundostack.c b/gtk/gtkundostack.c
index 5dea553ab9..4eed652e58 100644
--- a/gtk/gtkundostack.c
+++ b/gtk/gtkundostack.c
@@ -162,10 +162,15 @@ gtk_undo_stack_push_internal (GtkUndoStack   *stack,
   if (replace)
     g_sequence_remove (g_sequence_get_begin_iter (priv->commands));
 
-  g_object_ref (command);
-  g_sequence_prepend (priv->commands, command);
+  if (command)
+    {
+      g_object_ref (command);
+      g_sequence_prepend (priv->commands, command);
+    }
 
-  g_list_model_items_changed (G_LIST_MODEL (stack), 0, replace ? 1 : 0, 1);
+  g_list_model_items_changed (G_LIST_MODEL (stack), 0,
+                              replace ? 1 : 0,
+                              command ? 1 : 0);
 
   gtk_undo_stack_dump (stack);
 }
@@ -188,12 +193,10 @@ gtk_undo_stack_push (GtkUndoStack   *stack,
       if (gtk_undo_command_should_merge (previous, command))
         {
           merge = gtk_undo_command_merge (previous, command);
+          gtk_undo_stack_push_internal (stack, merge, TRUE);
           if (merge)
-            {
-              gtk_undo_stack_push_internal (stack, merge, TRUE);
-              g_object_unref (merge);
-              return;
-            }
+            g_object_unref (merge);
+          return;
         }
     }
 


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