Index: gtk/Makefile.am
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/Makefile.am,v
retrieving revision 1.294
diff -u -p -r1.294 Makefile.am
--- gtk/Makefile.am	7 Mar 2006 13:45:55 -0000	1.294
+++ gtk/Makefile.am	11 Mar 2006 10:44:11 -0000
@@ -322,6 +322,7 @@ gtk_private_h_sources =		\
 	gtkthemes.h		\
 	gtktreedatalist.h	\
 	gtktreeprivate.h	\
+	gtkundo.h		\
 	gtkwindow-decorate.h	\
 	gtktoggleactionprivate.h
 
@@ -522,6 +523,7 @@ gtk_c_sources =                 \
 	gtktypebuiltins.c	\
 	gtktypeutils.c		\
 	gtkuimanager.c		\
+	gtkundo.c		\
 	gtkvbbox.c		\
 	gtkvbox.c		\
 	gtkviewport.c		\
Index: gtk/gtkcomboboxentry.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkcomboboxentry.c,v
retrieving revision 1.29
diff -u -p -r1.29 gtkcomboboxentry.c
--- gtk/gtkcomboboxentry.c	1 Sep 2005 05:11:39 -0000	1.29
+++ gtk/gtkcomboboxentry.c	11 Mar 2006 10:44:15 -0000
@@ -137,6 +137,7 @@ gtk_combo_box_entry_init (GtkComboBoxEnt
   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: gtk/gtkentry.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkentry.c,v
retrieving revision 1.307
diff -u -p -r1.307 gtkentry.c
--- gtk/gtkentry.c	10 Mar 2006 10:30:35 -0000	1.307
+++ gtk/gtkentry.c	11 Mar 2006 10:44:25 -0000
@@ -55,6 +55,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"
@@ -76,10 +77,15 @@ typedef struct _GtkEntryPrivate GtkEntry
 
 #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 {
@@ -93,6 +99,8 @@ enum {
   COPY_CLIPBOARD,
   PASTE_CLIPBOARD,
   TOGGLE_OVERWRITE,
+  UNDO,
+  REDO,
   LAST_SIGNAL
 };
 
@@ -111,6 +119,7 @@ enum {
   PROP_SCROLL_OFFSET,
   PROP_TEXT,
   PROP_XALIGN,
+  PROP_ENABLE_UNDO,
   PROP_TRUNCATE_MULTILINE
 };
 
@@ -360,6 +369,20 @@ static void         disconnect_completio
 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;
 
@@ -489,7 +512,11 @@ gtk_entry_class_init (GtkEntryClass *cla
   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;
+
+  init_undo_actions ();
+
   quark_inner_border = g_quark_from_static_string ("gtk-entry-inner-border");
 
   g_object_class_install_property (gobject_class,
@@ -596,6 +623,14 @@ gtk_entry_class_init (GtkEntryClass *cla
 							"",
 							GTK_PARAM_READWRITE));
 
+  g_object_class_install_property (gobject_class,
+                                   PROP_ENABLE_UNDO,
+                                   g_param_spec_boolean ("enable-undo",
+							 "enable-undo",
+							 "enable-undo",
+							 TRUE,
+							 GTK_PARAM_READWRITE));
+
   /**
    * GtkEntry:xalign:
    *
@@ -729,6 +764,24 @@ gtk_entry_class_init (GtkEntryClass *cla
 		  _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
    */
@@ -876,6 +929,13 @@ gtk_entry_class_init (GtkEntryClass *cla
   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);
@@ -1001,6 +1061,10 @@ gtk_entry_set_property (GObject         
       entry->truncate_multiline = g_value_get_boolean (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:
@@ -1058,6 +1122,9 @@ gtk_entry_get_property (GObject         
     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;
     case PROP_TRUNCATE_MULTILINE:
       g_value_set_boolean (value, entry->truncate_multiline);
       break;
@@ -1090,6 +1157,9 @@ gtk_entry_init (GtkEntry *entry)
   entry->truncate_multiline = FALSE;
   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,
@@ -1181,6 +1251,8 @@ gtk_entry_finalize (GObject *object)
       entry->text = NULL;
     }
 
+  g_object_unref (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -2388,6 +2460,11 @@ gtk_entry_real_insert_text (GtkEditable 
 	}
     }
 
+  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);
@@ -2432,6 +2509,11 @@ gtk_entry_real_delete_text (GtkEditable 
       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);
@@ -2616,6 +2698,8 @@ gtk_entry_delete_from_cursor (GtkEntry  
   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);
@@ -2764,6 +2848,7 @@ gtk_entry_cut_clipboard (GtkEntry *entry
   gint start, end;
 
   gtk_entry_copy_clipboard (entry);
+  _gtk_undo_stack_new_group (GTK_ENTRY_GET_PRIVATE(entry)->undo_stack);
 
   if (entry->editable)
     {
@@ -2775,6 +2860,8 @@ gtk_entry_cut_clipboard (GtkEntry *entry
 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);
 }
@@ -4769,7 +4856,19 @@ popup_targets_received (GtkClipboard    
       gtk_menu_attach_to_widget (GTK_MENU (entry->popup_menu),
 				 GTK_WIDGET (entry),
 				 popup_menu_detach);
-      
+
+      if (gtk_entry_get_enable_undo (entry))
+        {
+          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",
@@ -5699,6 +5798,273 @@ gtk_entry_get_completion (GtkEntry *entr
 
   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: gtk/gtkentry.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkentry.h,v
retrieving revision 1.67
diff -u -p -r1.67 gtkentry.h
--- gtk/gtkentry.h	10 Mar 2006 10:30:36 -0000	1.67
+++ gtk/gtkentry.h	11 Mar 2006 10:44:25 -0000
@@ -135,9 +135,10 @@ struct _GtkEntryClass
   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);
 };
 
Index: gtk/gtkfilechooserentry.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkfilechooserentry.c,v
retrieving revision 1.33
diff -u -p -r1.33 gtkfilechooserentry.c
--- gtk/gtkfilechooserentry.c	19 Dec 2005 19:03:51 -0000	1.33
+++ gtk/gtkfilechooserentry.c	11 Mar 2006 10:44:25 -0000
@@ -197,6 +197,7 @@ gtk_file_chooser_entry_init (GtkFileChoo
   g_signal_connect (comp, "match-selected",
 		    G_CALLBACK (match_selected_callback), chooser_entry);
 
+  g_object_set (chooser_entry, "enable-undo", FALSE, NULL);
   gtk_entry_set_completion (GTK_ENTRY (chooser_entry), comp);
   g_object_unref (comp);
 
Index: gtk/gtkspinbutton.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkspinbutton.c,v
retrieving revision 1.125
diff -u -p -r1.125 gtkspinbutton.c
--- gtk/gtkspinbutton.c	30 Jan 2006 17:35:49 -0000	1.125
+++ gtk/gtkspinbutton.c	11 Mar 2006 10:44:30 -0000
@@ -503,6 +503,8 @@ gtk_spin_button_init (GtkSpinButton *spi
   spin_button->wrap = FALSE;
   spin_button->snap_to_ticks = FALSE;
 
+  g_object_set (spin_button, "enable-undo", FALSE, NULL);
+
   gtk_spin_button_set_adjustment (spin_button,
 	  (GtkAdjustment*) gtk_adjustment_new (0, 0, 0, 0, 0, 0));
 }
Index: gtk/gtktextbuffer.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktextbuffer.c,v
retrieving revision 1.110
diff -u -p -r1.110 gtktextbuffer.c
--- gtk/gtktextbuffer.c	7 Mar 2006 13:45:55 -0000	1.110
+++ gtk/gtktextbuffer.c	11 Mar 2006 10:44:38 -0000
@@ -38,6 +38,7 @@
 #include "gtktextbufferrichtext.h"
 #include "gtktextbtree.h"
 #include "gtktextiterprivate.h"
+#include "gtkundo.h"
 #include "gtkprivate.h"
 #include "gtkintl.h"
 #include "gtkalias.h"
@@ -56,6 +57,10 @@ struct _GtkTextBufferPrivate
   GtkTargetList  *paste_target_list;
   GtkTargetEntry *paste_target_entries;
   gint            n_paste_target_entries;
+
+  GtkUndoStack	 *undo_stack;
+  gpointer	  modifying_action; /* undo action which toggles modified state of the buffer */
+  int 		  move_cursor_to;   /* need to move cursor here after undo/redo */
 };
 
 
@@ -128,6 +133,21 @@ static void gtk_text_buffer_real_changed
 static void gtk_text_buffer_real_mark_set              (GtkTextBuffer     *buffer,
                                                         const GtkTextIter *iter,
                                                         GtkTextMark       *mark);
+static void gtk_text_buffer_real_begin_user_action     (GtkTextBuffer     *buffer);
+static void gtk_text_buffer_real_end_user_action       (GtkTextBuffer     *buffer);
+
+static guint INSERT_ACTION_TYPE;
+static guint DELETE_ACTION_TYPE;
+static void gtk_text_buffer_init_undo_actions		(void);
+static GtkUndoAction *insert_action_new			(GtkTextBuffer      *buffer,
+							 GtkTextIter        *pos,
+							 const char         *text,
+							 int                 length);
+static GtkUndoAction *delete_action_new			(GtkTextBuffer      *buffer,
+							 GtkTextIter        *start,
+							 GtkTextIter        *end);
+static void after_undo_redo				(GtkTextBuffer      *buffer);
+static GtkUndoStack *get_undo_stack			(GtkTextBuffer      *buffer);
 
 static GtkTextBTree* get_btree (GtkTextBuffer *buffer);
 static void          free_log_attr_cache (GtkTextLogAttrCache *cache);
@@ -201,6 +221,10 @@ gtk_text_buffer_class_init (GtkTextBuffe
   klass->remove_tag = gtk_text_buffer_real_remove_tag;
   klass->changed = gtk_text_buffer_real_changed;
   klass->mark_set = gtk_text_buffer_real_mark_set;
+  klass->begin_user_action = gtk_text_buffer_real_begin_user_action;
+  klass->end_user_action = gtk_text_buffer_real_end_user_action;
+
+  gtk_text_buffer_init_undo_actions ();
 
   /* Construct */
   g_object_class_install_property (object_class,
@@ -433,11 +457,26 @@ gtk_text_buffer_class_init (GtkTextBuffe
 static void
 gtk_text_buffer_init (GtkTextBuffer *buffer)
 {
+  GtkTextBufferPrivate *priv;
+
   buffer->clipboard_contents_buffers = NULL;
   buffer->tag_table = NULL;
 
   /* allow copying of arbiatray stuff in the internal rich text format */
   gtk_text_buffer_register_serialize_tagset (buffer, NULL);
+
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+  priv->undo_stack = _gtk_undo_stack_new (buffer);
+  priv->move_cursor_to = -1;
+
+  g_signal_connect_data (priv->undo_stack, "undo",
+			 G_CALLBACK (after_undo_redo),
+			 buffer, NULL,
+			 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+  g_signal_connect_data (priv->undo_stack, "redo",
+			 G_CALLBACK (after_undo_redo),
+			 buffer, NULL,
+			 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
 }
 
 static void
@@ -598,6 +637,9 @@ gtk_text_buffer_finalize (GObject *objec
 
   gtk_text_buffer_free_target_lists (buffer);
 
+  g_signal_handlers_disconnect_by_func (priv->undo_stack, after_undo_redo, buffer);
+  g_object_unref (priv->undo_stack);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -680,9 +722,19 @@ gtk_text_buffer_real_insert_text (GtkTex
                                   const gchar *text,
                                   gint len)
 {
+  GtkUndoStack *undo_stack;
+
   g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
   g_return_if_fail (iter != NULL);
   
+  undo_stack = get_undo_stack (buffer);
+
+  if (!_gtk_undo_stack_frozen (undo_stack))
+    {
+      GtkUndoAction *action = insert_action_new (buffer, iter, text, len);
+      _gtk_undo_stack_add_action (undo_stack, INSERT_ACTION_TYPE, action);
+    }
+		    
   _gtk_text_btree_insert (iter, text, len);
 
   g_signal_emit (buffer, signals[CHANGED], 0);
@@ -1394,10 +1446,19 @@ gtk_text_buffer_real_delete_range (GtkTe
                                    GtkTextIter   *start,
                                    GtkTextIter   *end)
 {
+  GtkUndoStack *undo_stack;
+
   g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
   g_return_if_fail (start != NULL);
   g_return_if_fail (end != NULL);
 
+  undo_stack = get_undo_stack (buffer);
+  if (!_gtk_undo_stack_frozen (undo_stack))
+    {
+      GtkUndoAction *action = delete_action_new (buffer, start, end);
+      _gtk_undo_stack_add_action (undo_stack, DELETE_ACTION_TYPE, action);
+    }
+
   _gtk_text_btree_delete (start, end);
 
   /* may have deleted the selection... */
@@ -4193,6 +4254,440 @@ _gtk_text_buffer_spew (GtkTextBuffer *bu
 {
   _gtk_text_btree_spew (get_btree (buffer));
 }
+
+
+/*************************************************************************/
+/* Undo/redo
+ */
+
+static GtkUndoStack *
+get_undo_stack (GtkTextBuffer *buffer)
+{
+    return GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->undo_stack;
+}
+
+gpointer 
+_gtk_text_buffer_get_undo_stack (GtkTextBuffer *buffer)
+{
+  g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+  return get_undo_stack (buffer);
+}
+
+
+static void
+gtk_text_buffer_real_begin_user_action (GtkTextBuffer *text_buffer)
+{
+  _gtk_undo_stack_start_group (get_undo_stack (text_buffer));
+}
+
+static void
+gtk_text_buffer_real_end_user_action (GtkTextBuffer *text_buffer)
+{
+  _gtk_undo_stack_end_group (get_undo_stack (text_buffer));
+}
+
+
+static void
+after_undo_redo (GtkTextBuffer *buffer)
+{
+  GtkTextIter iter;
+  GtkTextBufferPrivate *priv;
+
+  priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+  if (priv->move_cursor_to >= 0)
+    {
+      gtk_text_buffer_get_iter_at_offset (buffer, &iter, priv->move_cursor_to);
+      gtk_text_buffer_place_cursor (buffer, &iter);
+      priv->move_cursor_to = -1;
+    }
+}
+
+
+typedef enum {
+  ACTION_INSERT,
+  ACTION_DELETE
+} ActionType;
+
+typedef struct {
+  char *text;
+  guint mergeable   : 1;
+} EditAction;
+
+typedef struct {
+  EditAction edit;
+  int pos;
+  int length;
+  int chars;
+} InsertAction;
+
+typedef struct {
+  EditAction edit;
+  int start;
+  int end;
+  guint forward : 1;
+} DeleteAction;
+
+#define EDIT_ACTION(action_)           ((EditAction*)action_)
+
+static void     edit_action_destroy     (EditAction     *action,
+                                         GtkTextBuffer  *buffer);
+
+static void     insert_action_undo      (InsertAction   *action,
+                                         GtkTextBuffer  *buffer);
+static void     insert_action_redo      (InsertAction   *action,
+                                         GtkTextBuffer  *buffer);
+static gboolean insert_action_merge     (InsertAction   *action,
+                                         InsertAction   *what,
+                                         GtkTextBuffer  *buffer);
+
+static void     delete_action_undo      (DeleteAction   *action,
+                                         GtkTextBuffer  *buffer);
+static void     delete_action_redo      (DeleteAction   *action,
+                                         GtkTextBuffer  *buffer);
+static gboolean delete_action_merge     (DeleteAction   *action,
+                                         DeleteAction   *what,
+                                         GtkTextBuffer  *buffer);
+
+static GtkUndoActionClass InsertActionClass = {
+  (GtkUndoActionUndo) insert_action_undo,
+  (GtkUndoActionRedo) insert_action_redo,
+  (GtkUndoActionMerge) insert_action_merge,
+  (GtkUndoActionDestroy) edit_action_destroy
+};
+
+static GtkUndoActionClass DeleteActionClass = {
+  (GtkUndoActionUndo) delete_action_undo,
+  (GtkUndoActionRedo) delete_action_redo,
+  (GtkUndoActionMerge) delete_action_merge,
+  (GtkUndoActionDestroy) edit_action_destroy
+};
+
+
+static void
+gtk_text_buffer_init_undo_actions (void)
+{
+  INSERT_ACTION_TYPE = _gtk_undo_action_register (&InsertActionClass);
+  DELETE_ACTION_TYPE = _gtk_undo_action_register (&DeleteActionClass);
+}
+
+
+static EditAction *
+action_new (ActionType     type,
+	    GtkTextBuffer *buffer)
+{
+  EditAction *action;
+  gsize size = 0;
+  GtkUndoActionClass *klass = NULL;
+
+  switch (type)
+    {
+    case ACTION_INSERT:
+      size = sizeof (InsertAction);
+      klass = &InsertActionClass;
+      break;
+    case ACTION_DELETE:
+      size = sizeof (DeleteAction);
+      klass = &DeleteActionClass;
+      break;
+    }
+
+  action = g_malloc0 (size);
+
+  if (!gtk_text_buffer_get_modified (buffer))
+    GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->modifying_action = action;
+
+  return action;
+}
+
+
+static GtkUndoAction *
+insert_action_new (GtkTextBuffer    *buffer,
+		   GtkTextIter      *pos,
+		   const char       *text,
+		   int               length)
+{
+  InsertAction *action;
+  EditAction *edit_action;
+
+  if (length < 0)
+    length = strlen (text);
+
+  g_return_val_if_fail (length > 0, NULL);
+
+  edit_action = action_new (ACTION_INSERT, buffer);
+  action = (InsertAction*) edit_action;
+
+  action->pos = gtk_text_iter_get_offset (pos);
+  action->edit.text = g_strndup (text, length);
+  action->length = length;
+  action->chars = g_utf8_strlen (text, length);
+
+  if (action->chars > 1 || text[0] == '\n')
+    edit_action->mergeable = FALSE;
+  else
+    edit_action->mergeable = TRUE;
+
+  return (GtkUndoAction*) action;
+}
+
+
+static GtkUndoAction *
+delete_action_new (GtkTextBuffer      *buffer,
+		   GtkTextIter        *start,
+		   GtkTextIter        *end)
+{
+  GtkTextIter iter;
+  DeleteAction *action;
+  EditAction *edit_action;
+  int start_offset, end_offset;
+
+  start_offset = gtk_text_iter_get_offset (start);
+  end_offset = gtk_text_iter_get_offset (end);
+
+  g_return_val_if_fail (start_offset < end_offset, NULL);
+
+  edit_action = action_new (ACTION_DELETE, buffer);
+  action = (DeleteAction*) edit_action;
+
+  action->start = start_offset;
+  action->end = end_offset;
+  action->edit.text = gtk_text_buffer_get_slice (buffer, start, end, TRUE);
+
+  /* figure out if the user used the Delete or the Backspace key */
+  gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
+
+  if (gtk_text_iter_get_offset (&iter) <= action->start)
+    action->forward = TRUE;
+  else
+    action->forward = FALSE;
+
+  if (action->end - action->start > 1 || action->edit.text[0] == '\n')
+    edit_action->mergeable = FALSE;
+  else
+    edit_action->mergeable = TRUE;
+
+  return (GtkUndoAction*) action;
+}
+
+
+static void
+action_undo_or_redo (EditAction     *action,
+		     GtkTextBuffer  *buffer,
+		     gboolean        was_modified)
+{
+  GtkTextBufferPrivate *priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+  if (!was_modified)
+    {
+      g_assert (gtk_text_buffer_get_modified (buffer));
+      g_assert (priv->modifying_action == NULL);
+      priv->modifying_action = action;
+    }
+  else if (priv->modifying_action == action)
+    {
+      gtk_text_buffer_set_modified (buffer, FALSE);
+      priv->modifying_action = NULL;
+    }
+}
+
+
+static void
+insert_action_undo (InsertAction   *action,
+		    GtkTextBuffer  *buffer)
+{
+  GtkTextIter start, end;
+  gboolean was_modified;
+
+  was_modified = gtk_text_buffer_get_modified (buffer);
+
+  gtk_text_buffer_get_iter_at_offset (buffer, &start, action->pos);
+  gtk_text_buffer_get_iter_at_offset (buffer, &end,
+				      action->pos + action->chars);
+  gtk_text_buffer_delete (buffer, &start, &end);
+
+  GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->move_cursor_to = action->pos;
+
+  action_undo_or_redo (EDIT_ACTION (action), buffer, was_modified);
+}
+
+
+static void
+delete_action_undo (DeleteAction   *action,
+		    GtkTextBuffer  *buffer)
+{
+  GtkTextIter start;
+  gboolean was_modified;
+
+  was_modified = gtk_text_buffer_get_modified (buffer);
+
+  gtk_text_buffer_get_iter_at_offset (buffer, &start, action->start);
+  gtk_text_buffer_insert (buffer, &start, action->edit.text, -1);
+
+  if (action->forward)
+    GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->move_cursor_to = action->start;
+  else
+    GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->move_cursor_to = action->end;
+
+  action_undo_or_redo (EDIT_ACTION (action), buffer, was_modified);
+}
+
+
+static void
+insert_action_redo (InsertAction   *action,
+		    GtkTextBuffer  *buffer)
+{
+  GtkTextIter start;
+  gboolean was_modified;
+
+  was_modified = gtk_text_buffer_get_modified (buffer);
+
+  gtk_text_buffer_get_iter_at_offset (buffer, &start, action->pos);
+  gtk_text_buffer_insert (buffer, &start, action->edit.text, action->length);
+
+  GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->move_cursor_to = action->pos + action->length;
+
+  action_undo_or_redo (EDIT_ACTION (action), buffer, was_modified);
+}
+
+
+static void
+delete_action_redo (DeleteAction   *action,
+		    GtkTextBuffer  *buffer)
+{
+  GtkTextIter start, end;
+  gboolean was_modified;
+
+  was_modified = gtk_text_buffer_get_modified (buffer);
+
+  gtk_text_buffer_get_iter_at_offset (buffer, &start, action->start);
+  gtk_text_buffer_get_iter_at_offset (buffer, &end, action->end);
+  gtk_text_buffer_delete (buffer, &start, &end);
+
+  GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->move_cursor_to = action->start;
+
+  action_undo_or_redo (EDIT_ACTION (action), buffer, was_modified);
+}
+
+
+static void
+edit_action_destroy (EditAction    *action,
+		     GtkTextBuffer *buffer)
+{
+  if (action)
+    {
+      GtkTextBufferPrivate *priv = GTK_TEXT_BUFFER_GET_PRIVATE (buffer);
+    
+      if (priv->modifying_action == action)
+	priv->modifying_action = NULL;
+      g_free (action->text);
+      g_free (action);
+    }
+}
+
+
+static gboolean
+action_merge (EditAction     *last_action,
+	      EditAction     *action,
+	      GtkTextBuffer  *buffer)
+{
+  if (!last_action->mergeable)
+    return FALSE;
+
+  if (!action->mergeable || GTK_TEXT_BUFFER_GET_PRIVATE(buffer)->modifying_action == action)
+    {
+        last_action->mergeable = FALSE;
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+
+static gboolean
+insert_action_merge (InsertAction   *last_action,
+		     InsertAction   *action,
+		     GtkTextBuffer  *buffer)
+{
+  char *tmp;
+
+  if (!action_merge (EDIT_ACTION (last_action), EDIT_ACTION (action), buffer))
+    return FALSE;
+
+  if (action->pos != (last_action->pos + last_action->chars) ||
+      (action->edit.text[0] != ' ' && action->edit.text[0] != '\t' &&
+      (last_action->edit.text[last_action->length-1] == ' ' ||
+       last_action->edit.text[last_action->length-1] == '\t')))
+    {
+      EDIT_ACTION(last_action)->mergeable = FALSE;
+      return FALSE;
+    }
+
+  tmp = g_strconcat (last_action->edit.text, action->edit.text, NULL);
+  g_free (last_action->edit.text);
+  last_action->length += action->length;
+  last_action->edit.text = tmp;
+  last_action->chars += action->chars;
+
+  return TRUE;
+}
+
+
+static gboolean
+delete_action_merge (DeleteAction   *last_action,
+		     DeleteAction   *action,
+		     GtkTextBuffer  *buffer)
+{
+  char *tmp;
+
+  if (!action_merge (EDIT_ACTION (last_action), EDIT_ACTION (action), buffer))
+    return FALSE;
+
+  if (last_action->forward != action->forward ||
+      (last_action->start != action->start &&
+       last_action->start != action->end))
+    {
+      EDIT_ACTION(last_action)->mergeable = FALSE;
+      return FALSE;
+    }
+
+  if (last_action->start == action->start)
+    {
+      char *text_end = g_utf8_offset_to_pointer (last_action->edit.text,
+						 last_action->end - last_action->start - 1);
+
+      /* Deleted with the delete key */
+      if (action->edit.text[0] != ' ' && action->edit.text[0] != '\t' && 
+	  (*text_end == ' ' || *text_end  == '\t'))
+        {
+          EDIT_ACTION(last_action)->mergeable = FALSE;
+          return FALSE;
+        }
+
+      tmp = g_strconcat (last_action->edit.text, action->edit.text, NULL);
+      g_free (last_action->edit.text);
+      last_action->end += (action->end - action->start);
+      last_action->edit.text = tmp;
+    }
+  else
+    {
+      /* Deleted with the backspace key */
+      if (action->edit.text[0] != ' ' && action->edit.text[0] != '\t' &&
+	  (last_action->edit.text[0] == ' ' || last_action->edit.text[0] == '\t'))
+        {
+          EDIT_ACTION(last_action)->mergeable = FALSE;
+          return FALSE;
+        }
+
+      tmp = g_strconcat (action->edit.text, last_action->edit.text, NULL);
+      g_free (last_action->edit.text);
+      last_action->start = action->start;
+      last_action->edit.text = tmp;
+    }
+
+  return TRUE;
+}
+
 
 #define __GTK_TEXT_BUFFER_C__
 #include "gtkaliasdef.c"
Index: gtk/gtktextbuffer.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktextbuffer.h,v
retrieving revision 1.39
diff -u -p -r1.39 gtktextbuffer.h
--- gtk/gtktextbuffer.h	7 Mar 2006 13:45:55 -0000	1.39
+++ gtk/gtktextbuffer.h	11 Mar 2006 10:44:39 -0000
@@ -392,6 +392,8 @@ const PangoLogAttr* _gtk_text_buffer_get
 void _gtk_text_buffer_notify_will_remove_tag (GtkTextBuffer *buffer,
                                               GtkTextTag    *tag);
 
+gpointer _gtk_text_buffer_get_undo_stack (GtkTextBuffer *buffer);
+
 G_END_DECLS
 
 #endif
Index: gtk/gtktextview.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktextview.c,v
retrieving revision 1.306
diff -u -p -r1.306 gtktextview.c
--- gtk/gtktextview.c	7 Mar 2006 13:45:55 -0000	1.306
+++ gtk/gtktextview.c	11 Mar 2006 10:44:56 -0000
@@ -47,6 +47,7 @@
 #include "gtkprivate.h"
 #include "gtksizegroup.h"          /* FIXME http://bugzilla.gnome.org/show_bug.cgi?id=72258 */
 #include "gtktextutil.h"
+#include "gtkundo.h"
 #include "gtkwindow.h"
 #include "gtkalias.h"
 
@@ -125,6 +126,8 @@ enum
   TOGGLE_OVERWRITE,
   MOVE_FOCUS,
   MOVE_VIEWPORT,
+  UNDO,
+  REDO,
   SELECT_ALL,
   LAST_SIGNAL
 };
@@ -145,6 +148,8 @@ enum
   PROP_CURSOR_VISIBLE,
   PROP_BUFFER,
   PROP_OVERWRITE,
+  PROP_CAN_UNDO,
+  PROP_CAN_REDO,
   PROP_ACCEPTS_TAB,
   LAST_PROP
 };
@@ -537,6 +542,8 @@ gtk_text_view_class_init (GtkTextViewCla
   klass->toggle_overwrite = gtk_text_view_toggle_overwrite;
   klass->move_focus = gtk_text_view_move_focus;
   klass->set_scroll_adjustments = gtk_text_view_set_scroll_adjustments;
+  klass->undo = gtk_text_view_undo;
+  klass->redo = gtk_text_view_redo;
 
   /*
    * Properties
@@ -668,6 +675,22 @@ gtk_text_view_class_init (GtkTextViewCla
 							 TRUE,
 							 GTK_PARAM_READWRITE));
 
+  g_object_class_install_property (gobject_class,
+				   PROP_CAN_UNDO,
+				   g_param_spec_boolean ("can-undo",
+                                    			 "Can undo",
+                            				 "Whether undo stack is not empty",
+                            				 FALSE,
+                            				 G_PARAM_READABLE));
+
+  g_object_class_install_property (gobject_class,
+                        	   PROP_CAN_REDO,
+				   g_param_spec_boolean ("can-redo",
+                                    			 "Can redo",
+                            				 "Whether redo stack is not empty",
+                            				 FALSE,
+                            				 G_PARAM_READABLE));
+				                                       
   /*
    * Style properties
    */
@@ -848,7 +871,24 @@ gtk_text_view_class_init (GtkTextViewCla
 			     G_TYPE_NONE, 1,
 			     G_TYPE_BOOLEAN, TRUE);
 
-  
+  signals[UNDO] =
+    g_signal_new ("undo",
+		  G_OBJECT_CLASS_TYPE (klass),
+    		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+        	  G_STRUCT_OFFSET (GtkTextViewClass, undo),
+        	  g_signal_accumulator_true_handled, NULL,
+        	  _gtk_marshal_BOOLEAN__VOID,
+        	  G_TYPE_BOOLEAN, 0);
+
+  signals[REDO] =
+    g_signal_new ("redo",
+		  G_OBJECT_CLASS_TYPE (klass),
+    		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+        	  G_STRUCT_OFFSET (GtkTextViewClass, redo),
+        	  g_signal_accumulator_true_handled, NULL,
+        	  _gtk_marshal_BOOLEAN__VOID,
+        	  G_TYPE_BOOLEAN, 0);
+
   /*
    * Key bindings
    */
@@ -1039,6 +1079,12 @@ gtk_text_view_class_init (GtkTextViewCla
   gtk_binding_entry_add_signal (binding_set, GDK_KP_Tab, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
 				"move_focus", 1,
 				GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD);
+
+  /* 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);
 }
 
 static void
@@ -1139,6 +1185,13 @@ gtk_text_view_new_with_buffer (GtkTextBu
   return GTK_WIDGET (text_view);
 }
 
+static void
+proxy_property_notify (GtkTextView *view,
+		       GParamSpec  *pspec)
+{
+    g_object_notify (G_OBJECT (view), pspec->name);
+}
+
 /**
  * gtk_text_view_set_buffer:
  * @text_view: a #GtkTextView
@@ -1166,6 +1219,7 @@ gtk_text_view_set_buffer (GtkTextView   
       /* Destroy all anchored children */
       GSList *tmp_list;
       GSList *copy;
+      GtkUndoStack *undo_stack;
 
       copy = g_slist_copy (text_view->children);
       tmp_list = copy;
@@ -1199,6 +1253,9 @@ gtk_text_view_set_buffer (GtkTextView   
 							      GDK_SELECTION_PRIMARY);
 	  gtk_text_buffer_remove_selection_clipboard (text_view->buffer, clipboard);
 	}
+    
+      undo_stack = _gtk_text_buffer_get_undo_stack (text_view->buffer);
+      g_signal_handlers_disconnect_by_func (undo_stack, proxy_property_notify, text_view);
     }
 
   text_view->buffer = buffer;
@@ -1206,6 +1263,7 @@ gtk_text_view_set_buffer (GtkTextView   
   if (buffer != NULL)
     {
       GtkTextIter start;
+      GtkUndoStack *undo_stack;
 
       g_object_ref (buffer);
 
@@ -1239,6 +1297,14 @@ gtk_text_view_set_buffer (GtkTextView   
 							      GDK_SELECTION_PRIMARY);
 	  gtk_text_buffer_add_selection_clipboard (text_view->buffer, clipboard);
 	}
+    
+      undo_stack = _gtk_text_buffer_get_undo_stack (text_view->buffer);
+      g_signal_connect_swapped (undo_stack, "notify::can-undo", 
+    				G_CALLBACK (proxy_property_notify), 
+				text_view);
+      g_signal_connect_swapped (undo_stack, "notify::can-redo", 
+    				G_CALLBACK (proxy_property_notify), 
+				text_view);
     }
 
   g_object_notify (G_OBJECT (text_view), "buffer");
@@ -2751,7 +2817,15 @@ gtk_text_view_get_property (GObject     
     case PROP_ACCEPTS_TAB:
       g_value_set_boolean (value, text_view->accepts_tab);
       break;
-      
+
+    case PROP_CAN_UNDO:
+      g_value_set_boolean (value, gtk_text_view_can_undo (text_view));
+      break;
+			
+    case PROP_CAN_REDO:
+      g_value_set_boolean (value, gtk_text_view_can_redo (text_view));
+      break;
+							
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -6954,8 +7028,9 @@ static void
 activate_cb (GtkWidget   *menuitem,
 	     GtkTextView *text_view)
 {
+  gboolean dummy;
   const gchar *signal = g_object_get_data (G_OBJECT (menuitem), "gtk-signal");
-  g_signal_emit_by_name (text_view, signal);
+  g_signal_emit_by_name (text_view, signal, &dummy);
 }
 
 static void
@@ -7161,7 +7236,16 @@ popup_targets_received (GtkClipboard    
       gtk_menu_attach_to_widget (GTK_MENU (text_view->popup_menu),
 				 GTK_WIDGET (text_view),
 				 popup_menu_detach);
-      
+
+      append_action_signal (text_view, text_view->popup_menu, GTK_STOCK_UNDO, "undo",
+			    gtk_text_view_can_undo (text_view));
+      append_action_signal (text_view, text_view->popup_menu, GTK_STOCK_REDO, "redo",
+			    gtk_text_view_can_redo (text_view));
+
+      menuitem = gtk_separator_menu_item_new ();
+      gtk_widget_show (menuitem);
+      gtk_menu_shell_append (GTK_MENU_SHELL (text_view->popup_menu), menuitem);
+
       have_selection = gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
                                                              &sel_start, &sel_end);
       
@@ -8552,6 +8636,121 @@ gtk_text_view_move_visually (GtkTextView
   gtk_text_view_ensure_layout (text_view);
 
   return gtk_text_layout_move_iter_visually (text_view->layout, iter, count);
+}
+
+
+/***********************************************************************************/
+/* Undo/redo
+ */
+
+static GtkUndoStack *
+get_undo_stack (GtkTextView *text_view)
+{
+    return _gtk_text_buffer_get_undo_stack (text_view->buffer);
+}
+
+/**
+ * gtk_text_view_can_undo:
+ * @text_view: a #GtkTextView
+ *
+ * Tells whether undo stack is not empty, i.e. Undo operation is possible.
+ * This is useful for enabling/disabling Undo menu item.
+ *
+ * Return value: %TRUE if Undo operation is possible
+ **/
+gboolean
+gtk_text_view_can_undo (GtkTextView *text_view)
+{
+    g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
+    return _gtk_undo_stack_can_undo (get_undo_stack (text_view));
+}
+
+/**
+ * gtk_text_view_can_redo:
+ * @text_view: a #GtkTextView
+ *
+ * Tells whether redo stack is not empty, i.e. Redo operation is possible.
+ * This is useful for enabling/disabling Redo menu item.
+ *
+ * Return value: %TRUE if Redo operation is possible
+ **/
+gboolean
+gtk_text_view_can_redo (GtkTextView *text_view)
+{
+    g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), FALSE);
+    return _gtk_undo_stack_can_redo (get_undo_stack (text_view));
+}
+
+
+/**
+ * gtk_text_view_begin_not_undoable_action:
+ * @text_view: a #GtkTextView
+ *
+ * Clears undo/redo history and disables undo until gtk_text_view_end_not_undoable_action 
+ * is called. This is useful for example in a text editor when loading file.
+ *
+ * Calls to this function may be nested analogously to gtk_text_buffer_begin_user_action.
+ **/
+void
+gtk_text_view_begin_not_undoable_action (GtkTextView *text_view)
+{
+  g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
+  _gtk_undo_stack_freeze (get_undo_stack (text_view));
+  gtk_text_buffer_begin_user_action (get_buffer (text_view));
+}
+
+/**
+ * gtk_text_view_end_not_undoable_action:
+ * @text_view: a #GtkTextView
+ *
+ * Reverses the effect of gtk_text_view_begin_not_undoable_action.
+ **/
+void
+gtk_text_view_end_not_undoable_action (GtkTextView *text_view)
+{
+    g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
+    gtk_text_buffer_end_user_action (get_buffer (text_view));
+    _gtk_undo_stack_thaw (get_undo_stack (text_view));
+}
+
+
+gboolean
+gtk_text_view_redo (GtkTextView *view)
+{
+    g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), FALSE);
+
+    if (_gtk_undo_stack_can_redo (get_undo_stack (view)))
+    {
+        _gtk_undo_stack_redo (get_undo_stack (view));
+        gtk_text_view_scroll_to_mark (view,
+                                      gtk_text_buffer_get_insert (get_buffer (view)),
+                                      0, FALSE, 0, 0);
+        return TRUE;
+    }
+    else
+    {
+        return FALSE;
+    }
+}
+
+
+gboolean
+gtk_text_view_undo (GtkTextView *view)
+{
+    g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), FALSE);
+
+    if (_gtk_undo_stack_can_undo (get_undo_stack (view)))
+    {
+        _gtk_undo_stack_undo (get_undo_stack (view));
+        gtk_text_view_scroll_to_mark (view,
+                                      gtk_text_buffer_get_insert (get_buffer (view)),
+                                      0, FALSE, 0, 0);
+        return TRUE;
+    }
+    else
+    {
+        return FALSE;
+    }
 }
 
 #define __GTK_TEXT_VIEW_C__
Index: gtk/gtktextview.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktextview.h,v
retrieving revision 1.48
diff -u -p -r1.48 gtktextview.h
--- gtk/gtktextview.h	18 May 2005 17:10:46 -0000	1.48
+++ gtk/gtktextview.h	11 Mar 2006 10:44:57 -0000
@@ -199,15 +199,15 @@ struct _GtkTextViewClass
   void (* move_focus)       (GtkTextView     *text_view,
                              GtkDirectionType direction);  
   
-  
+  gboolean (* undo)         (GtkTextView *text_view);
+  gboolean (* redo)         (GtkTextView *text_view);
+
   /* Padding for future expansion */
   void (*_gtk_reserved1) (void);
   void (*_gtk_reserved2) (void);
   void (*_gtk_reserved3) (void);
   void (*_gtk_reserved4) (void);
   void (*_gtk_reserved5) (void);
-  void (*_gtk_reserved6) (void);
-  void (*_gtk_reserved7) (void);
 };
 
 GType          gtk_text_view_get_type              (void) G_GNUC_CONST;
@@ -359,6 +359,13 @@ PangoTabArray*   gtk_text_view_get_tabs 
 
 /* note that the return value of this changes with the theme */
 GtkTextAttributes* gtk_text_view_get_default_attributes (GtkTextView    *text_view);
+
+gboolean     	 gtk_text_view_can_redo			(GtkTextView    *text_view);
+gboolean	 gtk_text_view_can_undo                 (GtkTextView    *text_view);
+gboolean	 gtk_text_view_redo                     (GtkTextView    *text_view);
+gboolean	 gtk_text_view_undo                     (GtkTextView    *text_view);
+void		 gtk_text_view_begin_not_undoable_action(GtkTextView    *text_view);
+void		 gtk_text_view_end_not_undoable_action  (GtkTextView    *text_view);
 
 G_END_DECLS
 
