xpad-hackers team mailing list archive
-
xpad-hackers team
-
Mailing list archive
-
Message #00016
Re: undo for xpad
On Wed, Mar 31, 2010 at 11:56 PM, Michael Terry <mike@xxxxxxxxxxx> wrote:
> On 30 March 2010 20:01, Sergei Riaguzov <riaguzov@xxxxxxxxx> wrote:
> > I've added undo/redo for xpad. I am attaching the diff (to apply it use
> > 'patch -p1 -i /patch/to/xpad_undo.diff' in xpad/src directory).
>
> Neat! This looks like a nice, non-trivial patch.
>
So can I submit this? Is it possible (do I have rights)? I've also fixed
some types in comments so I attach an updated diff. Who should submit it?
PS Also you can see that there is no "greying" of Redo/Undo menu elements
when there is nothing to Undo/Redo but I think this is so unimportant that
is not worth fixing.
>
> > Is there a chance for this code to appear in xpad? There is even a bug
> for
> > this: https://bugs.launchpad.net/xpad/+bug/370919. Well, actually I
> > submitted it in the first place.
>
> Hrm. I haven't touched xpad in a long time. I would like to see xpad
> improve, but I have very little interest or time to maintain xpad
> anymore.
>
No problem, I can spend some time on:
1. Adding search by Ctrl-F and menu item to search pads for text
2. Find out why no dispose/finalize functions are being called by pressing
Quit
3. Merge trunk with stable branch
4. Ask Debian/Fedora/Ubuntu/Gentoo/FreeBSD/.. xpad mantainers to update
their packages
I just need info on 3 and 4 procedures. Who should be submitting code?
Should I just send patches to you or send them to you and also submiPS: I am
using xpad in a non common manner - I am always having one pad where I keep
a lot of stuff like different links, command line options of utilities and
so on, so this pad is actually big and loosing all it's data accidentally
and not being able to restore it with Undo and lack of search is really
confusing.so I can spend time on it.
diff -Ndru o/xpad-pad.c p/xpad-pad.c
--- o/xpad-pad.c 2010-03-28 08:43:51.000000000 +0300
+++ p/xpad-pad.c 2010-03-31 02:47:15.000000000 +0300
@@ -163,13 +163,16 @@
pad = GTK_WIDGET (g_object_new (XPAD_TYPE_PAD, "group", group, NULL));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (XPAD_PAD (pad)->priv->textview));
-
+
+ xpad_text_buffer_freeze_undo (XPAD_TEXT_BUFFER (buffer));
g_signal_handlers_block_by_func (buffer, xpad_pad_text_changed, pad);
xpad_text_buffer_set_text_with_tags (XPAD_TEXT_BUFFER (buffer), content ? content : "");
g_free (content);
g_signal_handlers_unblock_by_func (buffer, xpad_pad_text_changed, pad);
+ xpad_text_buffer_thaw_undo (XPAD_TEXT_BUFFER (buffer));
+
xpad_pad_text_changed(XPAD_PAD(pad), buffer);
}
@@ -1062,12 +1065,15 @@
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview));
+ xpad_text_buffer_freeze_undo (XPAD_TEXT_BUFFER (buffer));
g_signal_handlers_block_by_func (buffer, xpad_pad_text_changed, pad);
xpad_text_buffer_set_text_with_tags (XPAD_TEXT_BUFFER (buffer), content ? content : "");
g_free (content);
g_signal_handlers_unblock_by_func (buffer, xpad_pad_text_changed, pad);
+ xpad_text_buffer_thaw_undo (XPAD_TEXT_BUFFER (buffer));
+
xpad_pad_text_changed(pad, buffer);
}
@@ -1305,6 +1311,26 @@
}
static void
+menu_undo (XpadPad *pad)
+{
+ g_return_if_fail (pad->priv->textview);
+ XpadTextBuffer *buffer = NULL;
+ buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
+ g_return_if_fail (buffer);
+ xpad_text_buffer_undo (buffer);
+}
+
+static void
+menu_redo (XpadPad *pad)
+{
+ g_return_if_fail (pad->priv->textview);
+ XpadTextBuffer *buffer = NULL;
+ buffer = XPAD_TEXT_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (pad->priv->textview)));
+ g_return_if_fail (buffer);
+ xpad_text_buffer_redo (buffer);
+}
+
+static void
menu_show_all (XpadPad *pad)
{
GSList *pads, *i;
@@ -1476,6 +1502,15 @@
gtk_widget_show (item);\
}
+#define MENU_ADD_STOCK_WITH_ACCEL(stock, callback, key, mask) {\
+ item = gtk_image_menu_item_new_from_stock (stock, accel_group);\
+ g_signal_connect_swapped (item, "activate", G_CALLBACK (callback), pad);\
+ if (key)\
+ gtk_widget_add_accelerator(item, "activate", accel_group, key, mask, GTK_ACCEL_VISIBLE);\
+ gtk_container_add (GTK_CONTAINER (menu), item);\
+ gtk_widget_show (item);\
+ }
+
#define MENU_ADD_CHECK(mnemonic, active, callback) {\
item = gtk_check_menu_item_new_with_mnemonic (mnemonic);\
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), active);\
@@ -1521,10 +1556,20 @@
menu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
+
+ MENU_ADD_STOCK_WITH_ACCEL (GTK_STOCK_UNDO, menu_undo, GDK_Z, GDK_CONTROL_MASK);
+ g_object_set_data (G_OBJECT (uppermenu), "undo", item);
+
+ MENU_ADD_STOCK_WITH_ACCEL (GTK_STOCK_REDO, menu_redo, GDK_R, GDK_CONTROL_MASK);
+ g_object_set_data (G_OBJECT (uppermenu), "redo", item);
+
+ MENU_ADD_SEP();
MENU_ADD_STOCK (GTK_STOCK_PASTE, menu_paste);
g_object_set_data (G_OBJECT (uppermenu), "paste", item);
+
MENU_ADD_SEP ();
+
MENU_ADD_STOCK (GTK_STOCK_PREFERENCES, xpad_pad_open_preferences);
diff -Ndru o/xpad-text-buffer.c p/xpad-text-buffer.c
--- o/xpad-text-buffer.c 2008-09-28 04:44:10.000000000 +0300
+++ p/xpad-text-buffer.c 2010-03-31 02:44:02.000000000 +0300
@@ -17,8 +17,16 @@
*/
#include "xpad-text-buffer.h"
+#include "xpad-undo.h"
G_DEFINE_TYPE(XpadTextBuffer, xpad_text_buffer, GTK_TYPE_TEXT_BUFFER)
+#define XPAD_TEXT_BUFFER_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), XPAD_TYPE_TEXT_BUFFER, XpadTextBufferPrivate))
+
+struct XpadTextBufferPrivate
+{
+ /* undo */
+ XpadUndo *undo;
+};
/* Unicode chars in the Private Use Area. */
static gunichar TAG_CHAR = 0xe000;
@@ -37,13 +45,31 @@
}
static void
+xpad_text_buffer_finalize (GObject *object)
+{
+ XpadTextBuffer *text_buffer = XPAD_TEXT_BUFFER (object);
+
+ g_free (text_buffer->priv->undo);
+
+ G_OBJECT_CLASS (xpad_text_buffer_parent_class)->finalize (object);
+}
+
+static void
xpad_text_buffer_class_init (XpadTextBufferClass *klass)
{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = xpad_text_buffer_finalize;
+
+ g_type_class_add_private (gobject_class, sizeof (XpadTextBufferPrivate));
}
static void
xpad_text_buffer_init (XpadTextBuffer *buffer)
{
+ buffer->priv = XPAD_TEXT_BUFFER_GET_PRIVATE (buffer);
+
+ buffer->priv->undo = xpad_undo_new (buffer);
}
void
@@ -164,6 +190,33 @@
return text;
}
+void
+xpad_text_buffer_insert_text (XpadTextBuffer *buffer, gint pos, const gchar *text, gint len)
+{
+ GtkTextBuffer *parent = (GtkTextBuffer*) buffer;
+ GtkTextIter iter;
+ gtk_text_buffer_get_iter_at_offset (parent, &iter, pos);
+ gtk_text_buffer_insert (parent, &iter, text, len);
+}
+
+void
+xpad_text_buffer_delete_range (XpadTextBuffer *buffer, gint start, gint end)
+{
+ GtkTextBuffer *parent = (GtkTextBuffer*) buffer;
+
+ GtkTextIter start_iter;
+ GtkTextIter end_iter;
+
+ gtk_text_buffer_get_iter_at_offset (parent, &start_iter, start);
+
+ if (end < 0)
+ gtk_text_buffer_get_end_iter (parent, &end_iter);
+ else
+ gtk_text_buffer_get_iter_at_offset (parent, &end_iter, end);
+
+ gtk_text_buffer_delete (parent, &start_iter, &end_iter);
+}
+
static GtkTextTagTable *
create_tag_table (void)
{
@@ -218,3 +271,26 @@
return table;
}
+
+void
+xpad_text_buffer_undo (XpadTextBuffer *buffer)
+{
+ xpad_undo_exec_undo (buffer->priv->undo);
+}
+
+void
+xpad_text_buffer_redo (XpadTextBuffer *buffer)
+{
+ xpad_undo_exec_redo (buffer->priv->undo);
+}
+
+void xpad_text_buffer_freeze_undo (XpadTextBuffer *buffer)
+{
+ xpad_undo_freeze (buffer->priv->undo);
+}
+
+void xpad_text_buffer_thaw_undo (XpadTextBuffer *buffer)
+{
+ xpad_undo_thaw (buffer->priv->undo);
+}
+
diff -Ndru o/xpad-text-buffer.h p/xpad-text-buffer.h
--- o/xpad-text-buffer.h 2008-09-28 04:44:10.000000000 +0300
+++ p/xpad-text-buffer.h 2010-03-31 02:43:22.000000000 +0300
@@ -31,11 +31,15 @@
#define XPAD_TEXT_BUFFER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), XPAD_TYPE_TEXT_BUFFER, XpadTextBufferClass))
typedef struct XpadTextBufferClass XpadTextBufferClass;
+typedef struct XpadTextBufferPrivate XpadTextBufferPrivate;
typedef struct XpadTextBuffer XpadTextBuffer;
struct XpadTextBuffer
{
GtkTextBuffer parent;
+
+ /* private */
+ XpadTextBufferPrivate *priv;
};
struct XpadTextBufferClass
@@ -50,6 +54,14 @@
void xpad_text_buffer_set_text_with_tags (XpadTextBuffer *buffer, const gchar *text);
gchar *xpad_text_buffer_get_text_with_tags (XpadTextBuffer *buffer);
+void xpad_text_buffer_insert_text (XpadTextBuffer *buffer, gint pos, const gchar *text, gint len);
+void xpad_text_buffer_delete_range (XpadTextBuffer *buffer, gint start, gint end);
+
+void xpad_text_buffer_undo (XpadTextBuffer *buffer);
+void xpad_text_buffer_redo (XpadTextBuffer *buffer);
+void xpad_text_buffer_freeze_undo (XpadTextBuffer *buffer);
+void xpad_text_buffer_thaw_undo (XpadTextBuffer *buffer);
+
G_END_DECLS
#endif /* __XPAD_TEXT_BUFFER_H__ */
diff -Ndru o/xpad-undo.c p/xpad-undo.c
--- o/xpad-undo.c 1970-01-01 03:00:00.000000000 +0300
+++ p/xpad-undo.c 2010-03-31 06:27:10.000000000 +0300
@@ -0,0 +1,452 @@
+/*
+
+Copyright (c) 2001-2007 Michael Terry
+Copyright (c) 2010 Sergei Riaguzov
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+
+#include "../config.h"
+#include <stdlib.h>
+#include <glib.h>
+#include <glib/glist.h>
+#include "xpad-undo.h"
+#include "xpad-text-buffer.h"
+
+G_DEFINE_TYPE(XpadUndo, xpad_undo, G_TYPE_OBJECT)
+#define XPAD_UNDO_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), XPAD_TYPE_UNDO, XpadUndoPrivate))
+
+static GObject* xpad_undo_constructor(GType gtype, guint n_properties, GObjectConstructParam *properties);
+static void xpad_undo_dispose (GObject *object);
+static void xpad_undo_finalize (GObject *object);
+static void xpad_undo_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void xpad_undo_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+
+enum UserActionType
+{
+ USER_ACTION_INSERT_TEXT,
+ USER_ACTION_DELETE_RANGE
+};
+
+typedef struct
+{
+ enum UserActionType action_type;
+ gint start;
+ gint end;
+ gchar *text;
+ gint len_in_bytes;
+ gint n_utf8_chars;
+ gboolean merged;
+} UserAction;
+
+static GList* xpad_undo_remove_action_elem (GList *curr);
+static void xpad_undo_clear_redo_history (XpadUndo *undo);
+static void xpad_undo_clear_history (XpadUndo *undo);
+static void xpad_undo_begin_user_action (GtkTextBuffer *buffer, XpadUndo *undo);
+static void xpad_undo_end_user_action (GtkTextBuffer *buffer, XpadUndo *undo);
+static void xpad_undo_insert_text (GtkTextBuffer *buffer, GtkTextIter *location, gchar *text, gint len, XpadUndo *undo);
+static void xpad_undo_delete_range (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, XpadUndo *undo);
+
+struct XpadUndoPrivate
+{
+ XpadTextBuffer *buffer;
+ // We always redo the next element in the list
+ // but undo the current one. We insert this guard
+ // with NULL data in the beginning to ease
+ // coding all of this
+ GList *history_start;
+ GList *history_curr;
+ guint user_action;
+ gboolean frozen;
+};
+
+enum
+{
+ PROP_0,
+ PROP_BUFFER,
+ LAST_PROP
+};
+
+XpadUndo*
+xpad_undo_new (XpadTextBuffer *buffer)
+{
+ return XPAD_UNDO (g_object_new (XPAD_TYPE_UNDO, "buffer", buffer, NULL));
+}
+
+static void
+xpad_undo_class_init (XpadUndoClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ // TODO: neither dispose nor finalize
+ // neither in here nor in xpad-pad.c are
+ // actually called, have to fix cleaning up
+ gobject_class->dispose = xpad_undo_dispose;
+ gobject_class->finalize = xpad_undo_finalize;
+ gobject_class->set_property = xpad_undo_set_property;
+ gobject_class->get_property = xpad_undo_get_property;
+ gobject_class->constructor = xpad_undo_constructor;
+
+ g_object_class_install_property (gobject_class,
+ PROP_BUFFER,
+ g_param_spec_pointer ("buffer",
+ "Pad Buffer",
+ "Pad buffer connected to this undo",
+ G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (gobject_class, sizeof (XpadUndoPrivate));
+}
+
+
+static void
+xpad_undo_init (XpadUndo *undo)
+{
+ undo->priv = XPAD_UNDO_GET_PRIVATE (undo);
+
+ undo->priv->buffer = NULL;
+ undo->priv->history_start = g_list_append (NULL, NULL);
+ undo->priv->history_curr = undo->priv->history_start;
+ undo->priv->user_action = 0;
+ undo->priv->frozen = FALSE;
+}
+
+static GObject*
+xpad_undo_constructor(GType gtype, guint n_properties, GObjectConstructParam *properties)
+{
+ GObjectClass *parent_class = G_OBJECT_CLASS (xpad_undo_parent_class);
+ GObject *obj = parent_class->constructor (gtype, n_properties, properties);
+
+ XpadUndo *undo = XPAD_UNDO (obj);
+
+ // Assert user passed buffer as construct parameter
+ gint i;
+ gboolean found_buffer = FALSE;
+ for (i = 0; i < n_properties; i++)
+ {
+ if (!g_strcmp0 (properties[i].pspec->name, "buffer") &&
+ G_VALUE_HOLDS_POINTER (properties[i].value) &&
+ G_IS_OBJECT (g_value_get_pointer (properties[i].value)))
+ {
+ found_buffer = TRUE;
+ }
+ }
+
+ if (!found_buffer)
+ {
+ undo->priv->buffer = NULL;
+ g_warning("GtkTextBuffer is not passed to XpadUndo constructor, undo will not work!\n");
+ }
+ else
+ {
+ // Set up signals
+ g_signal_connect (G_OBJECT (undo->priv->buffer), "insert-text", G_CALLBACK (xpad_undo_insert_text), undo);
+ g_signal_connect (G_OBJECT (undo->priv->buffer), "delete-range", G_CALLBACK (xpad_undo_delete_range), undo);
+ g_signal_connect (G_OBJECT (undo->priv->buffer), "begin-user-action", G_CALLBACK (xpad_undo_begin_user_action), undo);
+ g_signal_connect (G_OBJECT (undo->priv->buffer), "end-user-action", G_CALLBACK (xpad_undo_end_user_action), undo);
+ }
+
+ return obj;
+}
+
+static void
+xpad_undo_dispose (GObject *object)
+{
+ XpadUndo *undo = XPAD_UNDO (object);
+
+ if (undo->priv->buffer && G_IS_OBJECT (undo->priv->buffer))
+ {
+ g_object_unref (undo->priv->buffer);
+ undo->priv->buffer = NULL;
+ }
+
+ G_OBJECT_CLASS (xpad_undo_parent_class)->dispose (object);
+}
+
+static void
+xpad_undo_finalize (GObject *object)
+{
+ XpadUndo *undo = XPAD_UNDO (object);
+
+ // remove all elements except for the left guard
+ xpad_undo_clear_history (undo);
+
+ // remove left guard
+ g_list_free (undo->priv->history_start);
+
+ G_OBJECT_CLASS (xpad_undo_parent_class)->finalize (object);
+}
+
+static void
+xpad_undo_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ XpadUndo *undo = XPAD_UNDO (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUFFER:
+ if (undo->priv->buffer && G_IS_OBJECT (undo->priv->buffer))
+ g_object_unref (undo->priv->buffer);
+ if (G_VALUE_HOLDS_POINTER (value) && G_IS_OBJECT (g_value_get_pointer (value)))
+ {
+ undo->priv->buffer = g_value_get_pointer (value);
+ g_object_ref (undo->priv->buffer);
+ }
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+xpad_undo_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ XpadUndo *undo = XPAD_UNDO (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUFFER:
+ g_value_set_pointer (value, undo->priv->buffer);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+xpad_undo_begin_user_action (GtkTextBuffer *buffer, XpadUndo *undo)
+{
+ undo->priv->user_action++;
+}
+
+static void
+xpad_undo_end_user_action (GtkTextBuffer *buffer, XpadUndo *undo)
+{
+ if (undo->priv->user_action > 0)
+ undo->priv->user_action--;
+}
+
+// Removes current element and returns a pointer
+// to the previous
+static GList*
+xpad_undo_remove_action_elem (GList *curr)
+{
+ if (curr->data)
+ {
+ UserAction *action = curr->data;
+ g_free (action->text);
+ g_free (action);
+ if (curr->prev)
+ curr->prev->next = curr->next;
+ if (curr->next)
+ curr->next->prev = curr->prev;
+ GList *new_curr = curr->prev;
+ curr->prev = NULL;
+ curr->next = NULL;
+ g_list_free (curr);
+ curr = new_curr;
+ }
+ return curr;
+}
+
+// Redo is impossible after text insertion/deletion,
+// only right after Undo (another Redo),
+// so we have to remove every history entry
+// after the current one if there is any
+static void
+xpad_undo_clear_redo_history (XpadUndo *undo)
+{
+ while (undo->priv->history_curr->next)
+ xpad_undo_remove_action_elem (undo->priv->history_curr->next);
+}
+
+static void xpad_undo_clear_history (XpadUndo *undo)
+{
+ while (undo->priv->history_start->next)
+ xpad_undo_remove_action_elem (undo->priv->history_start->next);
+ undo->priv->history_curr = undo->priv->history_start;
+}
+
+static void
+xpad_undo_insert_text (GtkTextBuffer *buffer, GtkTextIter *location, gchar *text, gint len, XpadUndo *undo)
+{
+ if (undo->priv->frozen)
+ return;
+
+ if (undo->priv->user_action)
+ {
+ xpad_undo_clear_redo_history (undo);
+
+ gint pos = gtk_text_iter_get_offset (location);
+ gint n_utf8_chars = g_utf8_strlen (text, len);
+
+ // Merge similar actions
+ // This is how Undo works in most editors, if there is a series of
+ // 1-letter insertions - they are merge for Undo
+ if (undo->priv->history_curr->data)
+ {
+ UserAction *prev_action = undo->priv->history_curr->data;
+
+ if (prev_action->action_type == USER_ACTION_INSERT_TEXT)
+ {
+ // series of 1-letter insertions
+ if ((n_utf8_chars == 1 && prev_action->n_utf8_chars == 1) || (n_utf8_chars == 1 && prev_action->merged))
+ {
+ // if there was a space stop merging unless that was a series of spaces
+ if ((!g_unichar_isspace (prev_action->text[0]) && !g_ascii_isspace (text[0])) ||
+ (g_unichar_isspace (prev_action->text[0]) && g_unichar_isspace (text[0])))
+ {
+ gchar *joined_str = g_strjoin(NULL, prev_action->text, text, NULL);
+ g_free (prev_action->text);
+ prev_action->text = joined_str;
+ prev_action->len_in_bytes += len;
+ prev_action->end += prev_action->len_in_bytes;
+ prev_action->n_utf8_chars = prev_action->n_utf8_chars + n_utf8_chars;
+ prev_action->merged = TRUE;
+ return;
+ }
+ }
+ }
+ }
+
+ UserAction *action = g_new (UserAction, 1);
+ action->action_type = USER_ACTION_INSERT_TEXT;
+ action->text = g_strdup (text);
+ action->start = pos;
+ action->end = pos + len;
+ action->len_in_bytes = abs (action->end - action->start);
+ action->n_utf8_chars = n_utf8_chars;
+ action->merged = FALSE;
+
+ // since each operation clears redo we know that there
+ // is nothing after history_curr at this point so we
+ // insert right after it. history_start won't change
+ // since it is a left guard - not NULL
+ GList *dummy_start = g_list_append (undo->priv->history_curr, action); // supress warning, we have left guard for start
+ undo->priv->history_curr = g_list_next (undo->priv->history_curr);
+ }
+}
+
+static void
+xpad_undo_delete_range (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, XpadUndo *undo)
+{
+ if (undo->priv->frozen)
+ return;
+
+ if (undo->priv->user_action)
+ {
+ xpad_undo_clear_redo_history (undo);
+
+ gchar *text = gtk_text_iter_get_text (start, end);
+ gint start_offset = gtk_text_iter_get_offset (start);
+ gint end_offset = gtk_text_iter_get_offset (end);
+ gint len = abs (end_offset - start_offset);
+ gint n_utf8_chars = g_utf8_strlen (text, len);
+
+ UserAction *action = g_new (UserAction, 1);
+ action->action_type = USER_ACTION_DELETE_RANGE;
+ action->text = g_strdup (text);
+ action->start = start_offset;
+ action->end = end_offset;
+ action->len_in_bytes = len;
+ action->n_utf8_chars = n_utf8_chars;
+ action->merged = FALSE;
+
+ GList *dummy_start = g_list_append (undo->priv->history_curr, action); // supress warning, we have left guard for start
+ undo->priv->history_curr = g_list_next (undo->priv->history_curr);
+ }
+}
+
+void xpad_undo_exec_undo (XpadUndo *undo)
+{
+ if (undo->priv->frozen)
+ return;
+
+ // No undo without buffer
+ if (undo->priv->buffer == NULL || !G_IS_OBJECT (undo->priv->buffer))
+ return;
+
+ // Undo current element if it is not a start of the list
+ if (undo->priv->history_curr->data)
+ {
+ UserAction *action = undo->priv->history_curr->data;
+
+ switch (action->action_type)
+ {
+ case USER_ACTION_INSERT_TEXT:
+ xpad_text_buffer_delete_range (undo->priv->buffer,
+ action->start,
+ action->end);
+ break;
+ case USER_ACTION_DELETE_RANGE:
+ xpad_text_buffer_insert_text (undo->priv->buffer,
+ action->start,
+ action->text,
+ action->len_in_bytes);
+ break;
+ }
+
+ undo->priv->history_curr = g_list_previous (undo->priv->history_curr);
+ }
+}
+
+void xpad_undo_exec_redo (XpadUndo *undo)
+{
+ if (undo->priv->frozen)
+ return;
+
+ // No undo without buffer
+ if (undo->priv->buffer == NULL || !G_IS_OBJECT (undo->priv->buffer))
+ return;
+
+ // Redo next element if there is any
+ if (undo->priv->history_curr->next)
+ {
+ UserAction *action = undo->priv->history_curr->next->data;
+
+ switch (action->action_type)
+ {
+ case USER_ACTION_DELETE_RANGE:
+ xpad_text_buffer_delete_range (undo->priv->buffer,
+ action->start,
+ action->end);
+ break;
+ case USER_ACTION_INSERT_TEXT:
+ xpad_text_buffer_insert_text (undo->priv->buffer,
+ action->start,
+ action->text,
+ action->len_in_bytes);
+ break;
+ }
+
+ undo->priv->history_curr = g_list_next (undo->priv->history_curr);
+ }
+}
+
+void xpad_undo_freeze (XpadUndo *undo)
+{
+ undo->priv->frozen = TRUE;
+}
+
+void xpad_undo_thaw (XpadUndo *undo)
+{
+ undo->priv->frozen = FALSE;
+}
+
diff -Ndru o/xpad-undo.h p/xpad-undo.h
--- o/xpad-undo.h 1970-01-01 03:00:00.000000000 +0300
+++ p/xpad-undo.h 2010-03-31 02:41:00.000000000 +0300
@@ -0,0 +1,64 @@
+/*
+
+Copyright (c) 2001-2007 Michael Terry
+Copyright (c) 2010 Sergei Riaguzov
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+*/
+
+#ifndef __XPAD_UNDO_H__
+#define __XPAD_UNDO_H__
+
+#include <gtk/gtk.h>
+#include "xpad-text-buffer.h"
+
+G_BEGIN_DECLS
+
+#define XPAD_TYPE_UNDO (xpad_undo_get_type ())
+#define XPAD_UNDO(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), XPAD_TYPE_UNDO, XpadUndo))
+#define XPAD_UNDO_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), XPAD_TYPE_UNDO, XpadUndoClass))
+#define XPAD_IS_UNDO(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), XPAD_TYPE_UNDO))
+#define XPAD_IS_UNDO_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), XPAD_TYPE_UNDO))
+#define XPAD_UNDO_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), XPAD_TYPE_UNDO, XpadUndoClass))
+
+typedef struct XpadUndoClass XpadUndoClass;
+typedef struct XpadUndoPrivate XpadUndoPrivate;
+typedef struct XpadUndo XpadUndo;
+
+struct XpadUndo
+{
+ GObject parent;
+
+ /* private */
+ XpadUndoPrivate *priv;
+};
+
+struct XpadUndoClass
+{
+ GObjectClass parent_class;
+};
+
+GType xpad_undo_get_type (void);
+
+XpadUndo* xpad_undo_new (XpadTextBuffer *buffer);
+void xpad_undo_exec_undo (XpadUndo *undo);
+void xpad_undo_exec_redo (XpadUndo *undo);
+void xpad_undo_freeze (XpadUndo *undo);
+void xpad_undo_thaw (XpadUndo *undo);
+
+G_END_DECLS
+
+#endif /* __XPAD_UNDO_H__ */
Follow ups
References