Undo/Redo in GtkImHtml from GtkSourceView. This may not be adaquate enough for us. release-2.1.0

Fri, 25 May 2007 21:53:49 +0000

author
Sean Egan <seanegan@pidgin.im>
date
Fri, 25 May 2007 21:53:49 +0000
branch
release-2.1.0
changeset 17880
5e73968467e0
parent 17385
bf46968ab031
child 18082
de4d785ab7ad

Undo/Redo in GtkImHtml from GtkSourceView. This may not be adaquate enough for us.

pidgin/Makefile.am file | annotate | diff | comparison | revisions
pidgin/gtkimhtml.c file | annotate | diff | comparison | revisions
pidgin/gtkimhtml.h file | annotate | diff | comparison | revisions
pidgin/gtksourceundomanager.c file | annotate | diff | comparison | revisions
pidgin/gtksourceundomanager.h file | annotate | diff | comparison | revisions
pidgin/gtksourceview-marshal.c file | annotate | diff | comparison | revisions
pidgin/gtksourceview-marshal.h file | annotate | diff | comparison | revisions
--- a/pidgin/Makefile.am	Fri May 25 19:05:47 2007 +0000
+++ b/pidgin/Makefile.am	Fri May 25 21:53:49 2007 +0000
@@ -109,6 +109,8 @@
 	gtksession.c \
 	gtksound.c \
 	gtksourceiter.c \
+	gtksourceundomanager.c \
+	gtksourceview-marshal.c \
 	gtkstatusbox.c \
 	gtkthemes.c \
 	gtkutils.c \
@@ -156,6 +158,8 @@
 	gtksession.h \
 	gtksound.h \
 	gtksourceiter.h \
+	gtksourceundomanager.h \
+	gtksourceview-marshal.h \
 	gtkstatusbox.h \
 	pidginstock.h \
 	gtkthemes.h \
--- a/pidgin/gtkimhtml.c	Fri May 25 19:05:47 2007 +0000
+++ b/pidgin/gtkimhtml.c	Fri May 25 21:53:49 2007 +0000
@@ -31,6 +31,8 @@
 #include "util.h"
 #include "gtkimhtml.h"
 #include "gtksourceiter.h"
+#include "gtksourceundomanager.h"
+#include "gtksourceview-marshal.h"
 #include <gtk/gtk.h>
 #include <glib/gerror.h>
 #include <gdk/gdkkeysyms.h>
@@ -136,6 +138,8 @@
 	CLEAR_FORMAT,
 	UPDATE_FORMAT,
 	MESSAGE_SEND,
+	UNDO,
+	REDO,
 	LAST_SIGNAL
 };
 static guint signals [LAST_SIGNAL] = { 0 };
@@ -1126,6 +1130,23 @@
 	return FALSE;
 }
 
+static void
+gtk_imhtml_undo(GtkIMHtml *imhtml) {
+	g_return_if_fail(GTK_IS_IMHTML(imhtml));
+	g_return_if_fail(imhtml->editable);
+	
+	gtk_source_undo_manager_undo(imhtml->undo_manager);
+}
+
+static void
+gtk_imhtml_redo(GtkIMHtml *imhtml) {
+	g_return_if_fail(GTK_IS_IMHTML(imhtml));
+	g_return_if_fail(imhtml->editable);
+	
+	gtk_source_undo_manager_redo(imhtml->undo_manager);
+
+}
+
 static gboolean imhtml_message_send(GtkIMHtml *imhtml)
 {
 	return FALSE;
@@ -1209,6 +1230,7 @@
 	g_queue_free(imhtml->animations);
 	g_free(imhtml->protocol_name);
 	g_free(imhtml->search_string);
+	g_object_unref(imhtml->undo_manager);
 	G_OBJECT_CLASS(parent_class)->finalize (object);
 }
 
@@ -1272,10 +1294,32 @@
 					     NULL,
 					     0, g_cclosure_marshal_VOID__VOID,
 					     G_TYPE_NONE, 0);
+        signals [UNDO] = g_signal_new ("undo",
+                        		      G_TYPE_FROM_CLASS (klass),
+		                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                		              G_STRUCT_OFFSET (GtkIMHtmlClass, undo),
+		                              NULL,
+		                              NULL,
+                		              gtksourceview_marshal_VOID__VOID,
+		                              G_TYPE_NONE,
+		                              0);
+        signals [REDO] = g_signal_new ("redo",
+                        		      G_TYPE_FROM_CLASS (klass),
+		                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		                              G_STRUCT_OFFSET (GtkIMHtmlClass, redo),
+		                              NULL,
+		                              NULL,
+		                              gtksourceview_marshal_VOID__VOID,
+		                              G_TYPE_NONE,
+		                              0);
+
+
 
 	klass->toggle_format = imhtml_toggle_format;
 	klass->message_send = imhtml_message_send;
 	klass->clear_format = imhtml_clear_formatting;
+	klass->undo = gtk_imhtml_undo;
+	klass->redo = gtk_imhtml_redo;
 
 	gobject_class->finalize = gtk_imhtml_finalize;
 	widget_class->drag_motion = gtk_text_view_drag_motion;
@@ -1300,12 +1344,17 @@
 	gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0);
 	gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0);
 	gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0);
+        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);
+        gtk_binding_entry_add_signal (binding_set, GDK_F14, 0, "undo", 0);
+
 }
 
 static void gtk_imhtml_init (GtkIMHtml *imhtml)
 {
 	GtkTextIter iter;
 	imhtml->text_buffer = gtk_text_buffer_new(NULL);
+	imhtml->undo_manager = gtk_source_undo_manager_new(imhtml->text_buffer);
 	gtk_text_buffer_get_end_iter (imhtml->text_buffer, &iter);
 	gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer);
 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
--- a/pidgin/gtkimhtml.h	Fri May 25 19:05:47 2007 +0000
+++ b/pidgin/gtkimhtml.h	Fri May 25 21:53:49 2007 +0000
@@ -27,6 +27,7 @@
 #include <gtk/gtktextview.h>
 #include <gtk/gtktooltips.h>
 #include <gtk/gtkimage.h>
+#include "gtksourceundomanager.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -126,6 +127,7 @@
 
 	GSList *im_images;
 	GtkIMHtmlFuncs *funcs;
+	GtkSourceUndoManager *undo_manager;
 };
 
 struct _GtkIMHtmlClass {
@@ -137,6 +139,8 @@
 	void (*clear_format)(GtkIMHtml *);
 	void (*update_format)(GtkIMHtml *);
 	gboolean (*message_send)(GtkIMHtml *);
+	void (*undo)(GtkIMHtml *);
+	void (*redo)(GtkIMHtml *);
 };
 
 struct _GtkIMHtmlFontDetail {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksourceundomanager.c	Fri May 25 21:53:49 2007 +0000
@@ -0,0 +1,1122 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gtksourceundomanager.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi 
+ * Copyright (C) 2002-2005  Paolo Maggi 
+ *
+ * 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 2 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gtksourceundomanager.h"
+#include "gtksourceview-marshal.h"
+
+
+#define DEFAULT_MAX_UNDO_LEVELS		25
+
+
+typedef struct _GtkSourceUndoAction  			GtkSourceUndoAction;
+typedef struct _GtkSourceUndoInsertAction		GtkSourceUndoInsertAction;
+typedef struct _GtkSourceUndoDeleteAction		GtkSourceUndoDeleteAction;
+
+typedef enum {
+	GTK_SOURCE_UNDO_ACTION_INSERT,
+	GTK_SOURCE_UNDO_ACTION_DELETE
+} GtkSourceUndoActionType;
+
+/* 
+ * We use offsets instead of GtkTextIters because the last ones
+ * require to much memory in this context without giving us any advantage.
+ */ 
+
+struct _GtkSourceUndoInsertAction
+{
+	gint   pos; 
+	gchar *text;
+	gint   length;
+	gint   chars;
+};
+
+struct _GtkSourceUndoDeleteAction
+{
+	gint   start;
+	gint   end;
+	gchar *text;
+	gboolean forward;
+};
+
+struct _GtkSourceUndoAction
+{
+	GtkSourceUndoActionType action_type;
+	
+	union {
+		GtkSourceUndoInsertAction  insert;
+		GtkSourceUndoDeleteAction  delete;
+	} action;
+
+	gint order_in_group;
+
+	/* It is TRUE whether the action can be merged with the following action. */
+	guint mergeable : 1;
+
+	/* It is TRUE whether the action is marked as "modified".
+	 * An action is marked as "modified" if it changed the 
+	 * state of the buffer from "not modified" to "modified". Only the first
+	 * action of a group can be marked as modified.
+	 * There can be a single action marked as "modified" in the actions list.
+	 */
+	guint modified  : 1;
+};
+
+/* INVALID is a pointer to an invalid action */
+#define INVALID ((void *) "IA")
+
+struct _GtkSourceUndoManagerPrivate
+{
+	GtkTextBuffer	*document;
+	
+	GList*		 actions;
+	gint 		 next_redo;	
+
+	gint 		 actions_in_current_group;
+	
+	gint		 running_not_undoable_actions;
+
+	gint		 num_of_groups;
+
+	gint		 max_undo_levels;
+	
+	guint	 	 can_undo : 1;
+	guint		 can_redo : 1;
+	
+	/* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1),
+	 * the state of the buffer changed from "not modified" to "modified".
+	 */
+	guint	 	 modified_undoing_group : 1;	
+
+	/* Pointer to the action (in the action list) marked as "modified".
+	 * It is NULL when no action is marked as "modified". 
+	 * It is INVALID when the action marked as "modified" has been removed 
+	 * from the action list (freeing the list or resizing it) */
+	GtkSourceUndoAction *modified_action;
+};
+
+enum {
+	CAN_UNDO,
+	CAN_REDO,
+	LAST_SIGNAL
+};
+
+static void gtk_source_undo_manager_class_init 			(GtkSourceUndoManagerClass 	*klass);
+static void gtk_source_undo_manager_init 			(GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_finalize 			(GObject 			*object);
+
+static void gtk_source_undo_manager_insert_text_handler 	(GtkTextBuffer 			*buffer, 
+							 	 GtkTextIter 			*pos,
+		                             		 	 const 	gchar 			*text, 
+							 	 gint 				 length, 
+							 	 GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_delete_range_handler 	(GtkTextBuffer 			*buffer, 
+							 	 GtkTextIter 			*start,
+                        		      		 	 GtkTextIter 			*end,
+							 	 GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_begin_user_action_handler 	(GtkTextBuffer 			*buffer, 
+								 GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_modified_changed_handler	(GtkTextBuffer                  *buffer,
+								 GtkSourceUndoManager           *um);
+
+static void gtk_source_undo_manager_free_action_list 		(GtkSourceUndoManager 		*um);
+
+static void gtk_source_undo_manager_add_action 			(GtkSourceUndoManager 		*um, 
+		                                         	 const GtkSourceUndoAction 	*undo_action);
+static void gtk_source_undo_manager_free_first_n_actions 	(GtkSourceUndoManager 		*um, 
+								 gint 				 n);
+static void gtk_source_undo_manager_check_list_size 		(GtkSourceUndoManager 		*um);
+
+static gboolean gtk_source_undo_manager_merge_action 		(GtkSourceUndoManager 		*um, 
+		                                        	 const GtkSourceUndoAction 	*undo_action);
+
+static GObjectClass 	*parent_class 				= NULL;
+static guint 		undo_manager_signals [LAST_SIGNAL] 	= { 0 };
+
+GType
+gtk_source_undo_manager_get_type (void)
+{
+	static GType undo_manager_type = 0;
+
+  	if (undo_manager_type == 0)
+    	{
+      		static const GTypeInfo our_info =
+      		{
+        		sizeof (GtkSourceUndoManagerClass),
+        		NULL,		/* base_init */
+        		NULL,		/* base_finalize */
+        		(GClassInitFunc) gtk_source_undo_manager_class_init,
+        		NULL,           /* class_finalize */
+        		NULL,           /* class_data */
+        		sizeof (GtkSourceUndoManager),
+        		0,              /* n_preallocs */
+        		(GInstanceInitFunc) gtk_source_undo_manager_init
+      		};
+
+      		undo_manager_type = g_type_register_static (G_TYPE_OBJECT,
+                					    "GtkSourceUndoManager",
+							    &our_info,
+							    0);
+    	}
+
+	return undo_manager_type;
+}
+
+static void
+gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  	parent_class = g_type_class_peek_parent (klass);
+
+  	object_class->finalize = gtk_source_undo_manager_finalize;
+
+        klass->can_undo 	= NULL;
+	klass->can_redo 	= NULL;
+	
+	undo_manager_signals[CAN_UNDO] =
+   		g_signal_new ("can_undo",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo),
+			      NULL, NULL,
+			      gtksourceview_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_BOOLEAN);
+
+	undo_manager_signals[CAN_REDO] =
+   		g_signal_new ("can_redo",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo),
+			      NULL, NULL,
+			      gtksourceview_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_BOOLEAN);
+}
+
+static void
+gtk_source_undo_manager_init (GtkSourceUndoManager *um)
+{
+	um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1);
+
+	um->priv->actions = NULL;
+	um->priv->next_redo = 0;
+
+	um->priv->can_undo = FALSE;
+	um->priv->can_redo = FALSE;
+
+	um->priv->running_not_undoable_actions = 0;
+
+	um->priv->num_of_groups = 0;
+
+	um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
+
+	um->priv->modified_action = NULL;
+
+	um->priv->modified_undoing_group = FALSE;
+}
+
+static void
+gtk_source_undo_manager_finalize (GObject *object)
+{
+	GtkSourceUndoManager *um;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object));
+	
+   	um = GTK_SOURCE_UNDO_MANAGER (object);
+
+	g_return_if_fail (um->priv != NULL);
+
+	if (um->priv->actions != NULL)
+	{
+		gtk_source_undo_manager_free_action_list (um);
+	}
+
+	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
+			  G_CALLBACK (gtk_source_undo_manager_delete_range_handler), 
+			  um);
+
+	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
+			  G_CALLBACK (gtk_source_undo_manager_insert_text_handler), 
+			  um);
+	
+	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
+			  G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), 
+			  um);
+
+	g_free (um->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+GtkSourceUndoManager*
+gtk_source_undo_manager_new (GtkTextBuffer* buffer)
+{
+ 	GtkSourceUndoManager *um;
+
+	um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER, NULL));
+
+	g_return_val_if_fail (um->priv != NULL, NULL);
+  	um->priv->document = buffer;
+
+	g_signal_connect (G_OBJECT (buffer), "insert_text",
+			  G_CALLBACK (gtk_source_undo_manager_insert_text_handler), 
+			  um);
+
+	g_signal_connect (G_OBJECT (buffer), "delete_range",
+			  G_CALLBACK (gtk_source_undo_manager_delete_range_handler), 
+			  um);
+
+	g_signal_connect (G_OBJECT (buffer), "begin_user_action",
+			  G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), 
+			  um);
+
+	g_signal_connect (G_OBJECT (buffer), "modified_changed",
+			  G_CALLBACK (gtk_source_undo_manager_modified_changed_handler),
+			  um);
+	return um;
+}
+
+void 
+gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um)
+{
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	++um->priv->running_not_undoable_actions;
+}
+
+static void 
+gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um)
+{
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	g_return_if_fail (um->priv->running_not_undoable_actions > 0);
+	
+	--um->priv->running_not_undoable_actions;
+}
+
+void 
+gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um)
+{
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	gtk_source_undo_manager_end_not_undoable_action_internal (um);
+
+	if (um->priv->running_not_undoable_actions == 0)
+	{	
+		gtk_source_undo_manager_free_action_list (um);
+	
+		um->priv->next_redo = -1;	
+
+		if (um->priv->can_undo)
+		{
+			um->priv->can_undo = FALSE;
+			g_signal_emit (G_OBJECT (um), 
+				       undo_manager_signals [CAN_UNDO], 
+				       0, 
+				       FALSE);
+		}
+
+		if (um->priv->can_redo)
+		{
+			um->priv->can_redo = FALSE;
+			g_signal_emit (G_OBJECT (um), 
+				       undo_manager_signals [CAN_REDO], 
+				       0, 
+				       FALSE);
+		}
+	}
+}
+
+gboolean
+gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um)
+{
+	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
+	g_return_val_if_fail (um->priv != NULL, FALSE);
+
+	return um->priv->can_undo;
+}
+
+gboolean 
+gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um)
+{
+	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
+	g_return_val_if_fail (um->priv != NULL, FALSE);
+
+	return um->priv->can_redo;
+}
+
+static void
+set_cursor (GtkTextBuffer *buffer, gint cursor)
+{
+	GtkTextIter iter;
+	
+	/* Place the cursor at the requested position */
+	gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor);
+	gtk_text_buffer_place_cursor (buffer, &iter);
+}
+
+static void 
+insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len)
+{
+	GtkTextIter iter;
+	
+	gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
+	gtk_text_buffer_insert (buffer, &iter, text, len);
+}
+
+static void 
+delete_text (GtkTextBuffer *buffer, gint start, gint end)
+{
+	GtkTextIter start_iter;
+	GtkTextIter end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+
+	if (end < 0)
+		gtk_text_buffer_get_end_iter (buffer, &end_iter);
+	else
+		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+	gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
+}
+
+static gchar*
+get_chars (GtkTextBuffer *buffer, gint start, gint end)
+{
+	GtkTextIter start_iter;
+	GtkTextIter end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+
+	if (end < 0)
+		gtk_text_buffer_get_end_iter (buffer, &end_iter);
+	else
+		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+	return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE);
+}
+
+void 
+gtk_source_undo_manager_undo (GtkSourceUndoManager *um)
+{
+	GtkSourceUndoAction *undo_action;
+	gboolean modified = FALSE;
+
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+	g_return_if_fail (um->priv->can_undo);
+	
+	um->priv->modified_undoing_group = FALSE;
+
+	gtk_source_undo_manager_begin_not_undoable_action (um);
+
+	do
+	{