Index: Makefile.am
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/Makefile.am,v
retrieving revision 1.276.2.1
diff -u -r1.276.2.1 Makefile.am
--- Makefile.am	26 Oct 2005 18:13:22 -0000	1.276.2.1
+++ Makefile.am	23 Nov 2005 01:08:47 -0000
@@ -318,6 +318,7 @@
 	gtkthemes.h		\
 	gtktreedatalist.h	\
 	gtktreeprivate.h	\
+	gtkundo.h		\
 	gtkwindow-decorate.h	\
 	gtktoggleactionprivate.h
 
@@ -511,6 +512,7 @@
 	gtktypebuiltins.c	\
 	gtktypeutils.c		\
 	gtkuimanager.c		\
+	gtkundo.c		\
 	gtkvbbox.c		\
 	gtkvbox.c		\
 	gtkviewport.c		\
Index: gtkcomboboxentry.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkcomboboxentry.c,v
retrieving revision 1.27
diff -u -r1.27 gtkcomboboxentry.c
--- gtkcomboboxentry.c	6 Apr 2005 04:01:19 -0000	1.27
+++ gtkcomboboxentry.c	23 Nov 2005 01:08:52 -0000
@@ -137,6 +137,7 @@
   entry_box->priv->text_column = -1;
 
   entry_box->priv->entry = gtk_entry_new ();
+  g_object_set (entry_box->priv->entry, "enable-undo", FALSE, NULL);
   /* this flag is a hack to tell the entry to fill its allocation.
    */
   GTK_ENTRY (entry_box->priv->entry)->is_cell_renderer = TRUE;
Index: gtkentry.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkentry.c,v
retrieving revision 1.284.2.2
diff -u -r1.284.2.2 gtkentry.c
--- gtkentry.c	18 Nov 2005 19:30:41 -0000	1.284.2.2
+++ gtkentry.c	23 Nov 2005 01:09:43 -0000
@@ -54,6 +54,7 @@
 #include "gtkprivate.h"
 #include "gtkentryprivate.h"
 #include "gtkcelllayout.h"
+#include "gtkundo.h"
 #include "gtkalias.h"
 
 #define GTK_ENTRY_COMPLETION_KEY "gtk-entry-completion-key"
@@ -73,10 +74,15 @@
 
 #define GTK_ENTRY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ENTRY, GtkEntryPrivate))
 
+static guint INSERT_ACTION_TYPE;
+static guint DELETE_ACTION_TYPE;
+
 struct _GtkEntryPrivate 
 {
   gfloat xalign;
   gint insert_pos;
+  GtkUndoStack *undo_stack;
+  gboolean enable_undo;
 };
 
 enum {
@@ -90,6 +96,8 @@
   COPY_CLIPBOARD,
   PASTE_CLIPBOARD,
   TOGGLE_OVERWRITE,
+  UNDO,
+  REDO,
   LAST_SIGNAL
 };
 
@@ -106,7 +114,8 @@
   PROP_WIDTH_CHARS,
   PROP_SCROLL_OFFSET,
   PROP_TEXT,
-  PROP_XALIGN
+  PROP_XALIGN,
+  PROP_ENABLE_UNDO
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -353,6 +362,20 @@
 static void         connect_completion_signals         (GtkEntry           *entry,
 							GtkEntryCompletion *completion);
 
+static void         init_undo_actions                  (void);
+static gpointer     insert_action_new                  (const gchar        *text,
+                                                        gint                length,
+                                                        gint                chars,
+                                                        gint                position);
+static gpointer     delete_action_new                  (GtkEntry           *entry,
+                                                        gint                start_pos,
+                                                        gint                end_pos);
+static void         gtk_entry_undo                     (GtkEntry           *entry);
+static void         gtk_entry_redo                     (GtkEntry           *entry);
+static void         gtk_entry_set_enable_undo          (GtkEntry           *entry,
+                                                        gboolean            enable);
+static gboolean     gtk_entry_get_enable_undo          (GtkEntry           *entry);
+
 
 static GtkWidgetClass *parent_class = NULL;
 
@@ -439,6 +462,8 @@
   gtk_object_class = (GtkObjectClass *)class;
   parent_class = g_type_class_peek_parent (class);
 
+  init_undo_actions ();
+
   gobject_class->finalize = gtk_entry_finalize;
   gobject_class->set_property = gtk_entry_set_property;
   gobject_class->get_property = gtk_entry_get_property;
@@ -482,7 +507,9 @@
   class->paste_clipboard = gtk_entry_paste_clipboard;
   class->toggle_overwrite = gtk_entry_toggle_overwrite;
   class->activate = gtk_entry_real_activate;
-  
+  class->undo = gtk_entry_undo;
+  class->redo = gtk_entry_redo;
+
   g_object_class_install_property (gobject_class,
                                    PROP_CURSOR_POSITION,
                                    g_param_spec_int ("cursor-position",
@@ -697,6 +724,24 @@
 		  _gtk_marshal_VOID__VOID,
 		  G_TYPE_NONE, 0);
 
+  signals[UNDO] =
+    g_signal_new ("undo",
+		  G_OBJECT_CLASS_TYPE (gobject_class),
+		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		  G_STRUCT_OFFSET (GtkEntryClass, undo),
+		  NULL, NULL,
+		  _gtk_marshal_VOID__VOID,
+		  G_TYPE_NONE, 0);
+
+  signals[REDO] =
+    g_signal_new ("redo",
+		  G_OBJECT_CLASS_TYPE (gobject_class),
+		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		  G_STRUCT_OFFSET (GtkEntryClass, redo),
+		  NULL, NULL,
+		  _gtk_marshal_VOID__VOID,
+		  G_TYPE_NONE, 0);
+
   /*
    * Key bindings
    */
@@ -822,6 +867,13 @@
   gtk_binding_entry_add_signal (binding_set, GDK_Insert, GDK_SHIFT_MASK,
 				"paste_clipboard", 0);
 
+  /* Undo/redo */
+
+  gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK,
+				"undo", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+				"redo", 0);
+
   /* Overwrite */
   gtk_binding_entry_add_signal (binding_set, GDK_Insert, 0,
 				"toggle_overwrite", 0);
@@ -925,6 +977,10 @@
       gtk_entry_set_alignment (entry, g_value_get_float (value));
       break;
 
+    case PROP_ENABLE_UNDO:
+      gtk_entry_set_enable_undo (entry, g_value_get_boolean (value));
+      break;
+
     case PROP_SCROLL_OFFSET:
     case PROP_CURSOR_POSITION:
     default:
@@ -979,6 +1035,9 @@
     case PROP_XALIGN:
       g_value_set_float (value, gtk_entry_get_alignment (entry));
       break;
+    case PROP_ENABLE_UNDO:
+      g_value_set_boolean (value, gtk_entry_get_enable_undo (entry));
+      break;
       
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1007,6 +1066,9 @@
   entry->has_frame = TRUE;
   priv->xalign = 0.0;
 
+  priv->undo_stack = gtk_undo_stack_new (entry);
+  priv->enable_undo = TRUE;
+
   gtk_drag_dest_set (GTK_WIDGET (entry),
                      GTK_DEST_DEFAULT_HIGHLIGHT,
                      NULL, 0,
@@ -1098,6 +1160,8 @@
       entry->text = NULL;
     }
 
+  g_object_unref (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -2283,6 +2347,11 @@
 	}
     }
 
+  if (n_chars > 0 && entry->visible && gtk_entry_get_enable_undo (entry))
+    gtk_undo_stack_add_action (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack,
+                               INSERT_ACTION_TYPE,
+                               insert_action_new (new_text, new_text_length, n_chars, *position));
+
   index = g_utf8_offset_to_pointer (entry->text, *position) - entry->text;
 
   g_memmove (entry->text + index + new_text_length, entry->text + index, entry->n_bytes - index);
@@ -2327,6 +2396,11 @@
       gint current_pos;
       gint selection_bound;
 
+      if (entry->visible && gtk_entry_get_enable_undo (entry))
+        gtk_undo_stack_add_action (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack,
+                                   DELETE_ACTION_TYPE,
+                                   delete_action_new (entry, start_pos, end_pos));
+
       g_memmove (entry->text + start_index, entry->text + end_index, entry->n_bytes + 1 - end_index);
       entry->text_length -= (end_pos - start_pos);
       entry->n_bytes -= (end_index - start_index);
@@ -2511,6 +2585,8 @@
   if (!entry->editable)
     return;
 
+  gtk_undo_stack_new_group (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack);
+
   if (entry->selection_bound != entry->current_pos)
     {
       gtk_editable_delete_selection (editable);
@@ -2659,6 +2735,7 @@
   gint start, end;
 
   gtk_entry_copy_clipboard (entry);
+  gtk_undo_stack_new_group (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack);
 
   if (entry->editable)
     {
@@ -2670,6 +2747,8 @@
 static void
 gtk_entry_paste_clipboard (GtkEntry *entry)
 {
+  gtk_undo_stack_new_group (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack);
+
   if (entry->editable)
     gtk_entry_paste (entry, GDK_NONE);
 }
@@ -4578,7 +4657,16 @@
       gtk_menu_attach_to_widget (GTK_MENU (entry->popup_menu),
 				 GTK_WIDGET (entry),
 				 popup_menu_detach);
-      
+
+      append_action_signal (entry, entry->popup_menu, GTK_STOCK_UNDO, "undo",
+                            gtk_undo_stack_can_undo (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack));
+      append_action_signal (entry, entry->popup_menu, GTK_STOCK_REDO, "redo",
+                            gtk_undo_stack_can_redo (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack));
+
+      menuitem = gtk_separator_menu_item_new ();
+      gtk_widget_show (menuitem);
+      gtk_menu_shell_append (GTK_MENU_SHELL (entry->popup_menu), menuitem);
+
       append_action_signal (entry, entry->popup_menu, GTK_STOCK_CUT, "cut_clipboard",
 			    entry->editable && entry->current_pos != entry->selection_bound);
       append_action_signal (entry, entry->popup_menu, GTK_STOCK_COPY, "copy_clipboard",
@@ -5486,6 +5574,273 @@
 
   return completion;
 }
+
+
+/********************************************************************/
+/* Undo/redo
+ */
+
+typedef struct {
+    int   pos;
+    char *text;
+    int   length;
+    int   chars;
+} InsertAction;
+
+typedef struct {
+    int   start;
+    int   end;
+    char *text;
+    gboolean forward;
+} DeleteAction;
+
+static gpointer
+insert_action_new (const gchar        *text,
+                   gint                length,
+                   gint                chars,
+                   gint                position)
+{
+  InsertAction *action;
+
+  g_return_val_if_fail (length > 0, NULL);
+
+  action = g_new0 (InsertAction, 1);
+
+  action->pos = position;
+  action->text = g_strndup (text, length);
+  action->length = length;
+  action->chars = chars;
+
+  return action;
+}
+
+
+static gpointer
+delete_action_new (GtkEntry *entry,
+                   gint      start_pos,
+                   gint      end_pos)
+{
+  DeleteAction *action;
+
+  g_return_val_if_fail (start_pos < end_pos, NULL);
+
+  action = g_new0 (DeleteAction, 1);
+
+  action->start = start_pos;
+  action->end = end_pos;
+
+  action->text = gtk_editable_get_chars (GTK_EDITABLE (entry), start_pos, end_pos);
+
+  /* figure out if the user used the Delete or the Backspace key */
+  if (gtk_editable_get_position (GTK_EDITABLE (entry)) <= action->start)
+    action->forward = TRUE;
+  else
+    action->forward = FALSE;
+
+  return action;
+}
+
+
+static void
+insert_action_undo (InsertAction   *action,
+                    GtkEditable    *editable)
+{
+  gtk_editable_delete_text (editable, action->pos, action->pos + action->chars);
+  gtk_editable_set_position (editable, action->pos);
+}
+
+
+static void
+delete_action_undo (DeleteAction   *action,
+                    GtkEditable    *editable)
+{
+  int pos_here = action->start;
+
+  gtk_editable_insert_text (editable, action->text, -1, &pos_here);
+
+  if (action->forward)
+    gtk_editable_set_position (editable, action->start);
+  else
+    gtk_editable_set_position (editable, action->end);
+}
+
+
+static void
+insert_action_redo (InsertAction   *action,
+                    GtkEditable    *editable)
+{
+  int pos_here = action->pos;
+  gtk_editable_insert_text (editable, action->text, action->length, &pos_here);
+  gtk_editable_set_position (editable, action->pos + action->chars);
+}
+
+
+static void
+delete_action_redo (DeleteAction   *action,
+                    GtkEditable    *editable)
+{
+  gtk_editable_delete_text (editable, action->start, action->end);
+  gtk_editable_set_position (editable, action->start);
+}
+
+
+static void
+insert_action_destroy (InsertAction *action)
+{
+  if (action)
+  {
+    g_free (action->text);
+    g_free (action);
+  }
+}
+
+
+static void
+delete_action_destroy (DeleteAction *action)
+{
+  if (action)
+  {
+    g_free (action->text);
+    g_free (action);
+  }
+}
+
+
+static gboolean
+insert_action_merge (InsertAction   *last_action,
+                     InsertAction   *action)
+{
+  char *tmp;
+
+  if (action->pos != (last_action->pos + last_action->chars) ||
+      (action->text[0] != ' ' && action->text[0] != '\t' &&
+      (last_action->text[last_action->length-1] == ' ' ||
+      last_action->text[last_action->length-1] == '\t')))
+  {
+    return FALSE;
+  }
+
+  tmp = g_strconcat (last_action->text, action->text, NULL);
+  g_free (last_action->text);
+  last_action->length += action->length;
+  last_action->text = tmp;
+  last_action->chars += action->chars;
+
+  return TRUE;
+}
+
+
+static gboolean
+delete_action_merge (DeleteAction   *last_action,
+                     DeleteAction   *action)
+{
+  char *tmp;
+
+  if (last_action->forward != action->forward ||
+      (last_action->start != action->start &&
+      last_action->start != action->end))
+  {
+    return FALSE;
+  }
+
+  if (last_action->start == action->start)
+  {
+    char *text_end = g_utf8_offset_to_pointer (last_action->text,
+                                               last_action->end -
+                                                  last_action->start - 1);
+
+    /* Deleted with the delete key */
+    if (action->text[0] != ' ' && action->text[0] != '\t' &&
+        (*text_end == ' ' || *text_end  == '\t'))
+    {
+      return FALSE;
+    }
+
+    tmp = g_strconcat (last_action->text, action->text, NULL);
+    g_free (last_action->text);
+    last_action->end += (action->end - action->start);
+    last_action->text = tmp;
+  }
+  else
+  {
+    /* Deleted with the backspace key */
+    if (action->text[0] != ' ' && action->text[0] != '\t' &&
+        (last_action->text[0] == ' ' || last_action->text[0] == '\t'))
+    {
+      return FALSE;
+    }
+
+    tmp = g_strconcat (action->text, last_action->text, NULL);
+    g_free (last_action->text);
+    last_action->start = action->start;
+    last_action->text = tmp;
+  }
+
+  return TRUE;
+}
+
+
+static GtkUndoActionClass InsertActionClass = {
+    (GtkUndoActionUndo) insert_action_undo,
+    (GtkUndoActionRedo) insert_action_redo,
+    (GtkUndoActionMerge) insert_action_merge,
+    (GtkUndoActionDestroy) insert_action_destroy
+};
+
+static GtkUndoActionClass DeleteActionClass = {
+    (GtkUndoActionUndo) delete_action_undo,
+    (GtkUndoActionRedo) delete_action_redo,
+    (GtkUndoActionMerge) delete_action_merge,
+    (GtkUndoActionDestroy) delete_action_destroy
+};
+
+static void
+init_undo_actions (void)
+{
+    INSERT_ACTION_TYPE = gtk_undo_action_register (&InsertActionClass);
+    DELETE_ACTION_TYPE = gtk_undo_action_register (&DeleteActionClass);
+}
+
+
+static void
+gtk_entry_undo (GtkEntry *entry)
+{
+  GtkEntryPrivate *priv = GTK_ENTRY_GET_PRIVATE (entry);
+  if (priv->enable_undo && gtk_undo_stack_can_undo (priv->undo_stack))
+    gtk_undo_stack_undo (priv->undo_stack);
+}
+
+
+static void
+gtk_entry_redo (GtkEntry *entry)
+{
+  GtkEntryPrivate *priv = GTK_ENTRY_GET_PRIVATE (entry);
+  if (priv->enable_undo && gtk_undo_stack_can_redo (priv->undo_stack))
+    gtk_undo_stack_redo (priv->undo_stack);
+}
+
+
+static void
+gtk_entry_set_enable_undo (GtkEntry *entry,
+                           gboolean  enable)
+{
+  GtkEntryPrivate *priv = GTK_ENTRY_GET_PRIVATE (entry);
+
+  if ((enable && priv->enable_undo) || (!enable && !priv->enable_undo))
+    return;
+
+  priv->enable_undo = enable != 0;
+  gtk_undo_stack_clear (priv->undo_stack);
+}
+
+
+static gboolean
+gtk_entry_get_enable_undo (GtkEntry *entry)
+{
+  GtkEntryPrivate *priv = GTK_ENTRY_GET_PRIVATE (entry);
+  return priv->enable_undo;
+}
+
 
 #define __GTK_ENTRY_C__
 #include "gtkaliasdef.c"
Index: gtkentry.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkentry.h,v
retrieving revision 1.65
diff -u -r1.65 gtkentry.h
--- gtkentry.h	20 Mar 2005 07:01:21 -0000	1.65
+++ gtkentry.h	23 Nov 2005 01:09:46 -0000
@@ -140,9 +140,10 @@
   void (* paste_clipboard)    (GtkEntry       *entry);
   void (* toggle_overwrite)   (GtkEntry       *entry);
 
+  void (* undo)               (GtkEntry       *entry);
+  void (* redo)               (GtkEntry       *entry);
+
   /* Padding for future expansion */
-  void (*_gtk_reserved1) (void);
-  void (*_gtk_reserved2) (void);
   void (*_gtk_reserved3) (void);
 };
 
