pidgin/gtksourceundomanager.c

changeset 18545
5af898e91ec2
parent 18148
b1844c3fcbbb
child 19710
c63486a2bf79
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksourceundomanager.c	Sun Jul 01 00:55:03 2007 +0000
@@ -0,0 +1,1123 @@
+/* -*- 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,
+        		NULL		/* value_table */
+      		};
+
+      		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
+	{	
+		undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1);
+		g_return_if_fail (undo_action != NULL);
+
+		/* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */
+		g_return_if_fail ((undo_action->order_in_group <= 1) ||
+				  ((undo_action->order_in_group > 1) && !undo_action->modified));
+
+		if (undo_action->order_in_group <= 1)
+		{
+			/* Set modified to TRUE only if the buffer did not change its state from
+			 * "not modified" to "modified" undoing an action (with order_in_group > 1) 
+			 * in current group. */
+			modified = (undo_action->modified && !um->priv->modified_undoing_group);
+		}
+
+		switch (undo_action->action_type)
+		{
+			case GTK_SOURCE_UNDO_ACTION_DELETE:
+				insert_text (
+					um->priv->document, 
+					undo_action->action.delete.start, 
+					undo_action->action.delete.text,
+					strlen (undo_action->action.delete.text));
+
+				if (undo_action->action.delete.forward)
+					set_cursor (
+						um->priv->document, 
+						undo_action->action.delete.start);
+				else
+					set_cursor (
+						um->priv->document, 
+						undo_action->action.delete.end);
+
+				break;
+				
+			case GTK_SOURCE_UNDO_ACTION_INSERT:
+				delete_text (
+					um->priv->document, 
+					undo_action->action.insert.pos, 
+					undo_action->action.insert.pos + 
+						undo_action->action.insert.chars); 
+
+				set_cursor (
+					um->priv->document, 
+					undo_action->action.insert.pos);
+				break;
+
+			default:
+				/* Unknown action type. */
+				g_return_if_reached ();
+		}
+
+		++um->priv->next_redo;
+
+	} while (undo_action->order_in_group > 1);
+
+	if (modified)
+	{
+		--um->priv->next_redo;
+		gtk_text_buffer_set_modified (um->priv->document, FALSE);
+		++um->priv->next_redo;
+	}
+
+	gtk_source_undo_manager_end_not_undoable_action_internal (um);
+	
+	um->priv->modified_undoing_group = FALSE;
+
+	if (!um->priv->can_redo)
+	{
+		um->priv->can_redo = TRUE;
+		g_signal_emit (G_OBJECT (um), 
+			       undo_manager_signals [CAN_REDO], 
+			       0, 
+			       TRUE);
+	}
+
+	if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
+	{
+		um->priv->can_undo = FALSE;
+		g_signal_emit (G_OBJECT (um), 
+			       undo_manager_signals [CAN_UNDO], 
+			       0, 
+			       FALSE);
+	}
+}
+
+void 
+gtk_source_undo_manager_redo (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_redo);
+	
+	undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
+	g_return_if_fail (undo_action != NULL);
+
+	gtk_source_undo_manager_begin_not_undoable_action (um);
+
+	do
+	{
+		if (undo_action->modified)
+		{
+			g_return_if_fail (undo_action->order_in_group <= 1);
+			modified = TRUE;
+		}
+
+		--um->priv->next_redo;
+	
+		switch (undo_action->action_type)
+		{
+			case GTK_SOURCE_UNDO_ACTION_DELETE:
+				delete_text (
+					um->priv->document, 
+					undo_action->action.delete.start, 
+					undo_action->action.delete.end); 
+
+				set_cursor (
+					um->priv->document,
+					undo_action->action.delete.start);
+
+				break;
+				
+			case GTK_SOURCE_UNDO_ACTION_INSERT:
+				set_cursor (
+					um->priv->document,
+					undo_action->action.insert.pos);
+
+				insert_text (
+					um->priv->document, 
+					undo_action->action.insert.pos, 
+					undo_action->action.insert.text,
+					undo_action->action.insert.length);
+
+				break;
+
+			default:
+				/* Unknown action type */
+				++um->priv->next_redo;
+				g_return_if_reached ();
+		}
+
+		if (um->priv->next_redo < 0)
+			undo_action = NULL;
+		else
+			undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
+			
+	} while ((undo_action != NULL) && (undo_action->order_in_group > 1));
+
+	if (modified)
+	{
+		++um->priv->next_redo;
+		gtk_text_buffer_set_modified (um->priv->document, FALSE);
+		--um->priv->next_redo;
+	}
+
+	gtk_source_undo_manager_end_not_undoable_action_internal (um);
+
+	if (um->priv->next_redo < 0)
+	{
+		um->priv->can_redo = FALSE;
+		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
+	}
+
+	if (!um->priv->can_undo)
+	{
+		um->priv->can_undo = TRUE;
+		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
+	}
+}
+
+static void
+gtk_source_undo_action_free (GtkSourceUndoAction *action)
+{
+	if (action == NULL)
+		return;
+
+	if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
+		g_free (action->action.insert.text);
+	else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)