Blame gtksourceview/gtksourceundomanagerdefault.c

Packit a7d494
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
Packit a7d494
/*
Packit a7d494
 * gtksourceundomanagerdefault.c
Packit a7d494
 * This file is part of GtkSourceView
Packit a7d494
 *
Packit a7d494
 * Copyright (C) 1998, 1999 - Alex Roberts, Evan Lawrence
Packit a7d494
 * Copyright (C) 2000, 2001 - Chema Celorio, Paolo Maggi
Packit a7d494
 * Copyright (C) 2002-2005  - Paolo Maggi
Packit a7d494
 * Copyright (C) 2014, 2015 - Sébastien Wilmet <swilmet@gnome.org>
Packit a7d494
 *
Packit a7d494
 * This library is free software; you can redistribute it and/or
Packit a7d494
 * modify it under the terms of the GNU Lesser General Public
Packit a7d494
 * License as published by the Free Software Foundation; either
Packit a7d494
 * version 2.1 of the License, or (at your option) any later version.
Packit a7d494
 *
Packit a7d494
 * This library is distributed in the hope that it will be useful,
Packit a7d494
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit a7d494
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit a7d494
 * Lesser General Public License for more details.
Packit a7d494
 *
Packit a7d494
 * You should have received a copy of the GNU Lesser General Public
Packit a7d494
 * License along with this library; if not, write to the Free Software
Packit a7d494
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Packit a7d494
 */
Packit a7d494
Packit a7d494
#include "gtksourceundomanagerdefault.h"
Packit a7d494
#include <string.h>
Packit a7d494
#include "gtksourceundomanager.h"
Packit a7d494
Packit a7d494
/* unlimited by default */
Packit a7d494
#define DEFAULT_MAX_UNDO_LEVELS -1
Packit a7d494
Packit a7d494
typedef struct _Action		Action;
Packit a7d494
typedef struct _ActionGroup	ActionGroup;
Packit a7d494
Packit a7d494
typedef enum _ActionType
Packit a7d494
{
Packit a7d494
	ACTION_TYPE_INSERT,
Packit a7d494
	ACTION_TYPE_DELETE
Packit a7d494
} ActionType;
Packit a7d494
Packit a7d494
/* A more precise deletion type. But currently it's only a guess, we are not
Packit a7d494
 * 100% sure of the deletion type. To be sure, we would need to listen to key
Packit a7d494
 * events on the GtkSourceView widget, which is more complicated than simply
Packit a7d494
 * listening to the insert-text and delete-range GtkTextBuffer signals.
Packit a7d494
 */
Packit a7d494
typedef enum _DeletionType
Packit a7d494
{
Packit a7d494
	DELETION_TYPE_SELECTION_DELETED,
Packit a7d494
	DELETION_TYPE_BACKSPACE_KEY,
Packit a7d494
	DELETION_TYPE_DELETE_KEY,
Packit a7d494
	DELETION_TYPE_PROGRAMMATICALLY
Packit a7d494
} DeletionType;
Packit a7d494
Packit a7d494
struct _Action
Packit a7d494
{
Packit a7d494
	ActionType type;
Packit a7d494
Packit a7d494
	/* Character offset for the start of @text in the GtkTextBuffer. */
Packit a7d494
	gint start;
Packit a7d494
Packit a7d494
	/* Character offset for the end of @text in the GtkTextBuffer. */
Packit a7d494
	gint end;
Packit a7d494
Packit a7d494
	/* Nul-terminated text.
Packit a7d494
	 * TODO A possible memory optimization is to store the text only when
Packit a7d494
	 * needed. For an insertion that is located in the history on the undo
Packit a7d494
	 * side, the text is not needed since it is already present in the
Packit a7d494
	 * buffer. The same for a deletion on the redo side. But the last action
Packit a7d494
	 * text is needed for the merging.
Packit a7d494
	 */
Packit a7d494
	gchar *text;
Packit a7d494
Packit a7d494
	/* Character offsets of the insert and selection bound marks.
Packit a7d494
	 * They are both -1 or they both match @start or @end.
Packit a7d494
	 * If the text cursor or the selected text is not related to the action,
Packit a7d494
	 * the selection is not stored (i.e. -1).
Packit a7d494
	 * If not -1, when undoing or redoing an action, the insert and
Packit a7d494
	 * selection bound marks are restored to where they were.
Packit a7d494
	 * For an insert, @selection_insert and @selection_bound must match
Packit a7d494
	 * @start, otherwise the selection or cursor position is unrelated to
Packit a7d494
	 * the insertion.
Packit a7d494
	 * For a deletion, if @selection_insert and @selection_bound are -1, it
Packit a7d494
	 * corresponds to DELETION_TYPE_PROGRAMMATICALLY. For all the other
Packit a7d494
	 * deletion types, the selection is stored.
Packit a7d494
	 */
Packit a7d494
	gint selection_insert;
Packit a7d494
	gint selection_bound;
Packit a7d494
};
Packit a7d494
Packit a7d494
struct _ActionGroup
Packit a7d494
{
Packit a7d494
	/* One or several Action's that forms a single undo or redo step. The
Packit a7d494
	 * most recent action is at the end of the list.
Packit a7d494
	 * In fact, actions can be grouped with
Packit a7d494
	 * gtk_text_buffer_begin_user_action() and
Packit a7d494
	 * gtk_text_buffer_end_user_action().
Packit a7d494
	 */
Packit a7d494
	GQueue *actions;
Packit a7d494
Packit a7d494
	/* If force_not_mergeable is FALSE, there are dynamic checks to see if
Packit a7d494
	 * the action group is mergeable. For example if the saved_location is
Packit a7d494
	 * just after the action group, the action group is not mergeable, so
Packit a7d494
	 * the saved_location isn't lost.
Packit a7d494
	 */
Packit a7d494
	guint force_not_mergeable : 1;
Packit a7d494
};
Packit a7d494
Packit a7d494
struct _GtkSourceUndoManagerDefaultPrivate
Packit a7d494
{
Packit a7d494
	/* Weak ref to the buffer. */
Packit a7d494
	GtkTextBuffer *buffer;
Packit a7d494
Packit a7d494
	/* List of ActionGroup's. The most recent ActionGroup is at the end of
Packit a7d494
	 * the list.
Packit a7d494
	 */
Packit a7d494
	GQueue *action_groups;
Packit a7d494
Packit a7d494
	/* Current location in 'action_groups', where we are located in the
Packit a7d494
	 * history. The redo steps are on the right of the pointer, and the undo
Packit a7d494
	 * steps are on the left. In other words, the next redo step is
Packit a7d494
	 * location->data. The next undo step is location->prev->data. But the
Packit a7d494
	 * location should not be seen as a node, it should be seen as a
Packit a7d494
	 * vertical bar between two nodes, like a GtkTextIter between two
Packit a7d494
	 * characters.
Packit a7d494
	 */
Packit a7d494
	GList *location;
Packit a7d494
Packit a7d494
	/* A new ActionGroup that is created when some text is inserted or
Packit a7d494
	 * deleted in the buffer. As long as a user action is running (when
Packit a7d494
	 * 'running_user_action' is TRUE) the new actions are inserted into
Packit a7d494
	 * 'new_action_group'. When the user action ends, we try to merge
Packit a7d494
	 * 'new_action_group' with the previous ActionGroup in 'action_groups'
Packit a7d494
	 * (the node on the left of 'location'). If the merging fails, a new
Packit a7d494
	 * node is inserted on the left of 'location'.
Packit a7d494
	 */
Packit a7d494
	ActionGroup *new_action_group;
Packit a7d494
Packit a7d494
	/* The number of nested calls to
Packit a7d494
	 * gtk_source_buffer_begin_not_undoable_action().
Packit a7d494
	 */
Packit a7d494
	guint running_not_undoable_actions;
Packit a7d494
Packit a7d494
	/* Max number of action groups. */
Packit a7d494
	gint max_undo_levels;
Packit a7d494
Packit a7d494
	/* The location in 'action_groups' where the buffer is saved. I.e. when
Packit a7d494
	 * gtk_text_buffer_set_modified (buffer, FALSE) was called for the last
Packit a7d494
	 * time.
Packit a7d494
	 * NULL is for the end of 'action_groups'.
Packit a7d494
	 * 'has_saved_location' is FALSE if the history doesn't contain a saved
Packit a7d494
	 * location.
Packit a7d494
	 */
Packit a7d494
	GList *saved_location;
Packit a7d494
	guint has_saved_location : 1;
Packit a7d494
Packit a7d494
	guint can_undo : 1;
Packit a7d494
	guint can_redo : 1;
Packit a7d494
Packit a7d494
	/* Whether we are between a begin-user-action and a end-user-action.
Packit a7d494
	 * Some operations, like undo and redo, are not allowed during a user
Packit a7d494
	 * action (it would screw up the history).
Packit a7d494
	 * At the beginning of a user action, a new action group is created. At
Packit a7d494
	 * the end of the user action, we try to merge the group with the
Packit a7d494
	 * previous one. So when an insertion or deletion occurs when
Packit a7d494
	 * running_user_action is TRUE, we don't need to create a new group. But
Packit a7d494
	 * when running_user_action is FALSE, we need to put the insertion or
Packit a7d494
	 * deletion into a new group and try to merge it directly with the
Packit a7d494
	 * previous group.
Packit a7d494
	 */
Packit a7d494
	guint running_user_action : 1;
Packit a7d494
};
Packit a7d494
Packit a7d494
enum
Packit a7d494
{
Packit a7d494
	PROP_0,
Packit a7d494
	PROP_BUFFER,
Packit a7d494
	PROP_MAX_UNDO_LEVELS
Packit a7d494
};
Packit a7d494
Packit a7d494
static void gtk_source_undo_manager_iface_init (GtkSourceUndoManagerIface *iface);
Packit a7d494
Packit a7d494
static gboolean action_merge (Action *action,
Packit a7d494
			      Action *new_action);
Packit a7d494
Packit a7d494
G_DEFINE_TYPE_WITH_CODE (GtkSourceUndoManagerDefault,
Packit a7d494
			 gtk_source_undo_manager_default,
Packit a7d494
			 G_TYPE_OBJECT,
Packit a7d494
			 G_ADD_PRIVATE (GtkSourceUndoManagerDefault)
Packit a7d494
                         G_IMPLEMENT_INTERFACE (GTK_SOURCE_TYPE_UNDO_MANAGER,
Packit a7d494
                                                gtk_source_undo_manager_iface_init))
Packit a7d494
Packit a7d494
/* Utilities functions */
Packit a7d494
Packit a7d494
static Action *
Packit a7d494
action_new (void)
Packit a7d494
{
Packit a7d494
	Action *action;
Packit a7d494
Packit a7d494
	action = g_slice_new0 (Action);
Packit a7d494
Packit a7d494
	action->selection_insert = -1;
Packit a7d494
	action->selection_bound = -1;
Packit a7d494
Packit a7d494
	return action;
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_free (Action *action)
Packit a7d494
{
Packit a7d494
	if (action != NULL)
Packit a7d494
	{
Packit a7d494
		g_free (action->text);
Packit a7d494
		g_slice_free (Action, action);
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static ActionGroup *
Packit a7d494
action_group_new (void)
Packit a7d494
{
Packit a7d494
	ActionGroup *group;
Packit a7d494
Packit a7d494
	group = g_slice_new (ActionGroup);
Packit a7d494
	group->actions = g_queue_new ();
Packit a7d494
	group->force_not_mergeable = FALSE;
Packit a7d494
Packit a7d494
	return group;
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_group_free (ActionGroup *group)
Packit a7d494
{
Packit a7d494
	if (group != NULL)
Packit a7d494
	{
Packit a7d494
		g_queue_free_full (group->actions, (GDestroyNotify) action_free);
Packit a7d494
		g_slice_free (ActionGroup, group);
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
update_can_undo_can_redo (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	gboolean can_undo;
Packit a7d494
	gboolean can_redo;
Packit a7d494
Packit a7d494
	if (manager->priv->running_user_action)
Packit a7d494
	{
Packit a7d494
		can_undo = FALSE;
Packit a7d494
		can_redo = FALSE;
Packit a7d494
	}
Packit a7d494
	else if (manager->priv->location != NULL)
Packit a7d494
	{
Packit a7d494
		can_undo = manager->priv->location->prev != NULL;
Packit a7d494
		can_redo = TRUE;
Packit a7d494
	}
Packit a7d494
	else
Packit a7d494
	{
Packit a7d494
		can_undo = manager->priv->action_groups->tail != NULL;
Packit a7d494
		can_redo = FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (manager->priv->can_undo != can_undo)
Packit a7d494
	{
Packit a7d494
		manager->priv->can_undo = can_undo;
Packit a7d494
		gtk_source_undo_manager_can_undo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (manager->priv->can_redo != can_redo)
Packit a7d494
	{
Packit a7d494
		manager->priv->can_redo = can_redo;
Packit a7d494
		gtk_source_undo_manager_can_redo_changed (GTK_SOURCE_UNDO_MANAGER (manager));
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
clear_all (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	GList *l;
Packit a7d494
Packit a7d494
	if (manager->priv->has_saved_location &&
Packit a7d494
	    manager->priv->saved_location != manager->priv->location)
Packit a7d494
	{
Packit a7d494
		manager->priv->has_saved_location = FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	for (l = manager->priv->action_groups->head; l != NULL; l = l->next)
Packit a7d494
	{
Packit a7d494
		ActionGroup *group = l->data;
Packit a7d494
		action_group_free (group);
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_queue_clear (manager->priv->action_groups);
Packit a7d494
	manager->priv->location = NULL;
Packit a7d494
	manager->priv->saved_location = NULL;
Packit a7d494
Packit a7d494
	action_group_free (manager->priv->new_action_group);
Packit a7d494
	manager->priv->new_action_group = NULL;
Packit a7d494
Packit a7d494
	update_can_undo_can_redo (manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
remove_last_action_group (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	ActionGroup *group;
Packit a7d494
Packit a7d494
	if (manager->priv->action_groups->length == 0)
Packit a7d494
	{
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (manager->priv->location == manager->priv->action_groups->tail)
Packit a7d494
	{
Packit a7d494
		manager->priv->location = NULL;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (manager->priv->has_saved_location)
Packit a7d494
	{
Packit a7d494
		if (manager->priv->saved_location == NULL)
Packit a7d494
		{
Packit a7d494
			manager->priv->has_saved_location = FALSE;
Packit a7d494
		}
Packit a7d494
		else if (manager->priv->saved_location == manager->priv->action_groups->tail)
Packit a7d494
		{
Packit a7d494
			manager->priv->saved_location = NULL;
Packit a7d494
		}
Packit a7d494
	}
Packit a7d494
Packit a7d494
	group = g_queue_pop_tail (manager->priv->action_groups);
Packit a7d494
	action_group_free (group);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
remove_first_action_group (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	GList *first_node;
Packit a7d494
	ActionGroup *group;
Packit a7d494
Packit a7d494
	first_node = manager->priv->action_groups->head;
Packit a7d494
Packit a7d494
	if (first_node == NULL)
Packit a7d494
	{
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (manager->priv->location == first_node)
Packit a7d494
	{
Packit a7d494
		manager->priv->location = first_node->next;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (manager->priv->has_saved_location &&
Packit a7d494
	    manager->priv->saved_location == first_node)
Packit a7d494
	{
Packit a7d494
		manager->priv->has_saved_location = FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	group = g_queue_pop_head (manager->priv->action_groups);
Packit a7d494
	action_group_free (group);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
check_history_size (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	if (manager->priv->max_undo_levels == -1)
Packit a7d494
	{
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (manager->priv->max_undo_levels == 0)
Packit a7d494
	{
Packit a7d494
		clear_all (manager);
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_return_if_fail (manager->priv->max_undo_levels > 0);
Packit a7d494
Packit a7d494
	while (manager->priv->action_groups->length > (guint)manager->priv->max_undo_levels)
Packit a7d494
	{
Packit a7d494
		/* Strip redo action groups first. */
Packit a7d494
		if (manager->priv->location != NULL)
Packit a7d494
		{
Packit a7d494
			remove_last_action_group (manager);
Packit a7d494
		}
Packit a7d494
		else
Packit a7d494
		{
Packit a7d494
			remove_first_action_group (manager);
Packit a7d494
		}
Packit a7d494
	}
Packit a7d494
Packit a7d494
	update_can_undo_can_redo (manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
remove_redo_action_groups (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	while (manager->priv->location != NULL)
Packit a7d494
	{
Packit a7d494
		remove_last_action_group (manager);
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Try to merge @new_group into @group. Returns TRUE if merged. It is up to the
Packit a7d494
 * caller to free @new_group.
Packit a7d494
 */
Packit a7d494
static gboolean
Packit a7d494
action_group_merge (ActionGroup *group,
Packit a7d494
		    ActionGroup *new_group)
Packit a7d494
{
Packit a7d494
	Action *action;
Packit a7d494
	Action *new_action;
Packit a7d494
Packit a7d494
	g_assert (group != NULL);
Packit a7d494
	g_assert (new_group != NULL);
Packit a7d494
Packit a7d494
	if (new_group->actions->length == 0)
Packit a7d494
	{
Packit a7d494
		return TRUE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (group->force_not_mergeable ||
Packit a7d494
	    new_group->force_not_mergeable ||
Packit a7d494
	    group->actions->length > 1 ||
Packit a7d494
	    new_group->actions->length > 1)
Packit a7d494
	{
Packit a7d494
		return FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	action = g_queue_peek_head (group->actions);
Packit a7d494
	new_action = g_queue_peek_head (new_group->actions);
Packit a7d494
Packit a7d494
	return action_merge (action, new_action);
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Try to merge the new action group with the previous one (the one located on
Packit a7d494
 * the left of priv->location). If the merge fails, a new node is inserted into
Packit a7d494
 * the history.
Packit a7d494
 */
Packit a7d494
static void
Packit a7d494
insert_new_action_group (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	GList *prev_node = NULL;
Packit a7d494
	ActionGroup *prev_group = NULL;
Packit a7d494
	ActionGroup *new_group = manager->priv->new_action_group;
Packit a7d494
	gboolean can_merge = TRUE;
Packit a7d494
Packit a7d494
	if (new_group == NULL || new_group->actions->length == 0)
Packit a7d494
	{
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	remove_redo_action_groups (manager);
Packit a7d494
	g_assert (manager->priv->location == NULL);
Packit a7d494
Packit a7d494
	prev_node = manager->priv->action_groups->tail;
Packit a7d494
Packit a7d494
	if (prev_node != NULL)
Packit a7d494
	{
Packit a7d494
		prev_group = prev_node->data;
Packit a7d494
Packit a7d494
		/* If the previous group is empty, it means that it was not correctly
Packit a7d494
		 * inserted into the history.
Packit a7d494
		 */
Packit a7d494
		g_assert_cmpuint (prev_group->actions->length, >, 0);
Packit a7d494
	}
Packit a7d494
Packit a7d494
	/* If the saved_location is equal to the current location, the two
Packit a7d494
	 * ActionGroups cannot be merged, to not lose the saved_location.
Packit a7d494
	 */
Packit a7d494
	if (manager->priv->has_saved_location &&
Packit a7d494
	    manager->priv->saved_location == manager->priv->location)
Packit a7d494
	{
Packit a7d494
		g_assert (manager->priv->saved_location == NULL);
Packit a7d494
		can_merge = FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (can_merge &&
Packit a7d494
	    prev_group != NULL &&
Packit a7d494
	    action_group_merge (prev_group, new_group))
Packit a7d494
	{
Packit a7d494
		/* new_group merged into prev_group */
Packit a7d494
		action_group_free (manager->priv->new_action_group);
Packit a7d494
		manager->priv->new_action_group = NULL;
Packit a7d494
Packit a7d494
		update_can_undo_can_redo (manager);
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_queue_push_tail (manager->priv->action_groups, new_group);
Packit a7d494
	manager->priv->new_action_group = NULL;
Packit a7d494
Packit a7d494
	if (manager->priv->has_saved_location &&
Packit a7d494
	    manager->priv->saved_location == NULL)
Packit a7d494
	{
Packit a7d494
		manager->priv->saved_location = manager->priv->action_groups->tail;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	/* "Archive" prev_group. It will never be mergeable again. If the user
Packit a7d494
	 * does some undo's to return to this location, a new action won't be
Packit a7d494
	 * merged with an "archived" action group.
Packit a7d494
	 */
Packit a7d494
	if (prev_group != NULL)
Packit a7d494
	{
Packit a7d494
		prev_group->force_not_mergeable = TRUE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	check_history_size (manager);
Packit a7d494
	update_can_undo_can_redo (manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
insert_action (GtkSourceUndoManagerDefault *manager,
Packit a7d494
	       Action                      *new_action)
Packit a7d494
{
Packit a7d494
	ActionGroup *new_group;
Packit a7d494
Packit a7d494
	g_assert (new_action != NULL);
Packit a7d494
Packit a7d494
	if (manager->priv->new_action_group == NULL)
Packit a7d494
	{
Packit a7d494
		manager->priv->new_action_group = action_group_new ();
Packit a7d494
	}
Packit a7d494
Packit a7d494
	new_group = manager->priv->new_action_group;
Packit a7d494
Packit a7d494
	/* Inside a group, don't try to merge the actions. It is needed to keep
Packit a7d494
	 * them separate so when undoing or redoing, the cursor position is set
Packit a7d494
	 * at the right place.
Packit a7d494
	 * For example with the search and replace, we replace all occurrences
Packit a7d494
	 * of 'a' by '' (i.e. delete all a's). The text "aaba" becomes "b". On
Packit a7d494
	 * undo, the cursor position should be placed at "a|aba", not "aa|ba"
Packit a7d494
	 * (but it's a detail).
Packit a7d494
	 */
Packit a7d494
	g_queue_push_tail (new_group->actions, new_action);
Packit a7d494
Packit a7d494
	/* An action is mergeable only for an insertion or deletion of a single
Packit a7d494
	 * character. If the text contains several characters, the new_action
Packit a7d494
	 * can for example come from a copy/paste.
Packit a7d494
	 */
Packit a7d494
	if (new_action->end - new_action->start > 1 ||
Packit a7d494
	    g_str_equal (new_action->text, "\n"))
Packit a7d494
	{
Packit a7d494
		new_group->force_not_mergeable = TRUE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (!manager->priv->running_user_action)
Packit a7d494
	{
Packit a7d494
		insert_new_action_group (manager);
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
delete_text (GtkTextBuffer *buffer,
Packit a7d494
	     gint           start,
Packit a7d494
	     gint           end)
Packit a7d494
{
Packit a7d494
	GtkTextIter start_iter;
Packit a7d494
	GtkTextIter end_iter;
Packit a7d494
Packit a7d494
	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
Packit a7d494
	gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
Packit a7d494
Packit a7d494
	gtk_text_buffer_begin_user_action (buffer);
Packit a7d494
	gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
Packit a7d494
	gtk_text_buffer_end_user_action (buffer);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
insert_text (GtkTextBuffer *buffer,
Packit a7d494
	     gint           offset,
Packit a7d494
	     const gchar   *text)
Packit a7d494
{
Packit a7d494
	GtkTextIter iter;
Packit a7d494
Packit a7d494
	gtk_text_buffer_get_iter_at_offset (buffer, &iter, offset);
Packit a7d494
Packit a7d494
	gtk_text_buffer_begin_user_action (buffer);
Packit a7d494
	gtk_text_buffer_insert (buffer, &iter, text, -1);
Packit a7d494
	gtk_text_buffer_end_user_action (buffer);
Packit a7d494
}
Packit a7d494
Packit a7d494
static gunichar
Packit a7d494
get_last_char (const gchar *text)
Packit a7d494
{
Packit a7d494
	gchar *pos;
Packit a7d494
Packit a7d494
	pos = g_utf8_find_prev_char (text, text + strlen (text));
Packit a7d494
Packit a7d494
	if (pos == NULL)
Packit a7d494
	{
Packit a7d494
		return '\0';
Packit a7d494
	}
Packit a7d494
Packit a7d494
	return g_utf8_get_char (pos);
Packit a7d494
}
Packit a7d494
Packit a7d494
/* ActionInsert implementation */
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_insert_undo (GtkTextBuffer *buffer,
Packit a7d494
		    Action        *action)
Packit a7d494
{
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
Packit a7d494
Packit a7d494
	delete_text (buffer, action->start, action->end);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_insert_redo (GtkTextBuffer *buffer,
Packit a7d494
		    Action        *action)
Packit a7d494
{
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
Packit a7d494
Packit a7d494
	insert_text (buffer, action->start, action->text);
Packit a7d494
}
Packit a7d494
Packit a7d494
static gboolean
Packit a7d494
action_insert_merge (Action *action,
Packit a7d494
		     Action *new_action)
Packit a7d494
{
Packit a7d494
	gint new_text_length;
Packit a7d494
	gunichar new_char;
Packit a7d494
	gunichar last_char;
Packit a7d494
	gchar *merged_text;
Packit a7d494
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
Packit a7d494
	g_assert_cmpint (new_action->type, ==, ACTION_TYPE_INSERT);
Packit a7d494
Packit a7d494
	new_text_length = new_action->end - new_action->start;
Packit a7d494
	g_assert_cmpint (new_text_length, ==, 1);
Packit a7d494
Packit a7d494
	new_char = g_utf8_get_char (new_action->text);
Packit a7d494
	g_assert (new_char != '\n');
Packit a7d494
Packit a7d494
	if (action->end != new_action->start)
Packit a7d494
	{
Packit a7d494
		return FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	last_char = get_last_char (action->text);
Packit a7d494
Packit a7d494
	/* If I type character by character the text "hello world", there will
Packit a7d494
	 * be two actions: "hello" and " world". If I click on undo, only
Packit a7d494
	 * "hello" remains, not the space. The space makes sense only when
Packit a7d494
	 * a second word is present.
Packit a7d494
	 * Note that the spaces or tabs at the beginning of a line (for code
Packit a7d494
	 * indentation) are removed with the first word of the line. For example
Packit a7d494
	 * if I type character by character "  return FALSE;", there are two
Packit a7d494
	 * actions: "  return" and " FALSE;". If I undo two times, maybe I still
Packit a7d494
	 * want the indentation. But with auto-indent, when we press Enter to
Packit a7d494
	 * create a newline, the indentation is part of the action that adds the
Packit a7d494
	 * newline, i.e. we have the three actions "\n  ", "return" and
Packit a7d494
	 * " FALSE;".
Packit a7d494
	 */
Packit a7d494
	if ((new_char == ' ' || new_char == '\t') &&
Packit a7d494
	    (last_char != ' ' && last_char != '\t'))
Packit a7d494
	{
Packit a7d494
		return FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	merged_text = g_strdup_printf ("%s%s", action->text, new_action->text);
Packit a7d494
Packit a7d494
	g_free (action->text);
Packit a7d494
	action->text = merged_text;
Packit a7d494
Packit a7d494
	action->end = new_action->end;
Packit a7d494
Packit a7d494
	/* No need to update the selection, action->start is not modified. */
Packit a7d494
	g_assert ((action->selection_insert == -1 &&
Packit a7d494
		   action->selection_bound == -1) ||
Packit a7d494
		  (action->selection_insert == action->start &&
Packit a7d494
		   action->selection_bound == action->start));
Packit a7d494
Packit a7d494
	return TRUE;
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_insert_restore_selection (GtkTextBuffer *buffer,
Packit a7d494
				 Action        *action,
Packit a7d494
				 gboolean       undo)
Packit a7d494
{
Packit a7d494
	GtkTextIter iter;
Packit a7d494
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_INSERT);
Packit a7d494
Packit a7d494
	/* No need to take into account action->selection_insert and
Packit a7d494
	 * action->selection_bound, because:
Packit a7d494
	 * - If they are both -1, we still want to place the cursor correctly,
Packit a7d494
	 *   as done below, because if the cursor is not moved the user won't
Packit a7d494
	 *   see the modification.
Packit a7d494
	 * - If they are set, their values are both action->start, so the undo
Packit a7d494
	 *   works as expeceted in this case. The redo is also the expected
Packit a7d494
	 *   behavior because after inserting a character the cursor is _after_
Packit a7d494
	 *   the character, not before.
Packit a7d494
	 */
Packit a7d494
Packit a7d494
	if (undo)
Packit a7d494
	{
Packit a7d494
		gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->start);
Packit a7d494
	}
Packit a7d494
	else /* redo */
Packit a7d494
	{
Packit a7d494
		gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->end);
Packit a7d494
	}
Packit a7d494
Packit a7d494
	gtk_text_buffer_place_cursor (buffer, &iter);
Packit a7d494
}
Packit a7d494
Packit a7d494
/* ActionDelete implementation */
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_delete_undo (GtkTextBuffer *buffer,
Packit a7d494
		    Action        *action)
Packit a7d494
{
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
Packit a7d494
Packit a7d494
	insert_text (buffer, action->start, action->text);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_delete_redo (GtkTextBuffer *buffer,
Packit a7d494
		    Action        *action)
Packit a7d494
{
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
Packit a7d494
Packit a7d494
	delete_text (buffer, action->start, action->end);
Packit a7d494
}
Packit a7d494
Packit a7d494
static DeletionType
Packit a7d494
get_deletion_type (Action *action)
Packit a7d494
{
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
Packit a7d494
Packit a7d494
	if (action->selection_insert == -1)
Packit a7d494
	{
Packit a7d494
		g_assert_cmpint (action->selection_bound, ==, -1);
Packit a7d494
		return DELETION_TYPE_PROGRAMMATICALLY;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (action->selection_insert == action->end &&
Packit a7d494
	    action->selection_bound == action->end)
Packit a7d494
	{
Packit a7d494
		return DELETION_TYPE_BACKSPACE_KEY;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	if (action->selection_insert == action->start &&
Packit a7d494
	    action->selection_bound == action->start)
Packit a7d494
	{
Packit a7d494
		return DELETION_TYPE_DELETE_KEY;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_assert (action->selection_insert == action->start ||
Packit a7d494
		  action->selection_insert == action->end);
Packit a7d494
	g_assert (action->selection_bound == action->start ||
Packit a7d494
		  action->selection_bound == action->end);
Packit a7d494
Packit a7d494
	return DELETION_TYPE_SELECTION_DELETED;
Packit a7d494
}
Packit a7d494
Packit a7d494
static gboolean
Packit a7d494
action_delete_merge (Action *action,
Packit a7d494
		     Action *new_action)
Packit a7d494
{
Packit a7d494
	gint new_text_length;
Packit a7d494
	gunichar new_char;
Packit a7d494
	DeletionType deletion_type;
Packit a7d494
	DeletionType new_deletion_type;
Packit a7d494
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
Packit a7d494
	g_assert_cmpint (new_action->type, ==, ACTION_TYPE_DELETE);
Packit a7d494
Packit a7d494
	new_text_length = new_action->end - new_action->start;
Packit a7d494
	g_assert_cmpint (new_text_length, ==, 1);
Packit a7d494
Packit a7d494
	new_char = g_utf8_get_char (new_action->text);
Packit a7d494
	g_assert (new_char != '\n');
Packit a7d494
Packit a7d494
	deletion_type = get_deletion_type (action);
Packit a7d494
	new_deletion_type = get_deletion_type (new_action);
Packit a7d494
Packit a7d494
	if (deletion_type != new_deletion_type)
Packit a7d494
	{
Packit a7d494
		return FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	switch (deletion_type)
Packit a7d494
	{
Packit a7d494
		/* If the user has selected some text and then has deleted it,
Packit a7d494
		 * it should be seen as a single action group, not mergeable.  A
Packit a7d494
		 * good reason for that is to correctly restore the selection.
Packit a7d494
		 */
Packit a7d494
		case DELETION_TYPE_SELECTION_DELETED:
Packit a7d494
			return FALSE;
Packit a7d494
Packit a7d494
		/* For memory use it would be better to take it into account,
Packit a7d494
		 * but the code is simpler like that.
Packit a7d494
		 */
Packit a7d494
		case DELETION_TYPE_PROGRAMMATICALLY:
Packit a7d494
			return FALSE;
Packit a7d494
Packit a7d494
		/* Two Backspaces or two Deletes must follow each other. In
Packit a7d494
		 * "abc", if the cursor is at offset 2 and I press the Backspace
Packit a7d494
		 * key, then move the cursor after 'c' and press Backspace
Packit a7d494
		 * again, the two deletes won't be merged, since there was a
Packit a7d494
		 * cursor movement in between.
Packit a7d494
		 */
Packit a7d494
Packit a7d494
		case DELETION_TYPE_DELETE_KEY:
Packit a7d494
			/* Not consecutive deletes. */
Packit a7d494
			if (action->start != new_action->start)
Packit a7d494
			{
Packit a7d494
				return FALSE;
Packit a7d494
			}
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		case DELETION_TYPE_BACKSPACE_KEY:
Packit a7d494
			/* Not consecutive backspaces. */
Packit a7d494
			if (action->start != new_action->end)
Packit a7d494
			{
Packit a7d494
				return FALSE;
Packit a7d494
			}
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		default:
Packit a7d494
			g_assert_not_reached ();
Packit a7d494
	}
Packit a7d494
Packit a7d494
	/* Delete key pressed several times. */
Packit a7d494
	if (action->start == new_action->start)
Packit a7d494
	{
Packit a7d494
		gunichar last_char;
Packit a7d494
		gchar *merged_text;
Packit a7d494
Packit a7d494
		last_char = get_last_char (action->text);
Packit a7d494
Packit a7d494
		/* Same as action_insert_merge(). */
Packit a7d494
		if ((new_char == ' ' || new_char == '\t') &&
Packit a7d494
		    (last_char != ' ' && last_char != '\t'))
Packit a7d494
		{
Packit a7d494
			return FALSE;
Packit a7d494
		}
Packit a7d494
Packit a7d494
		merged_text = g_strdup_printf ("%s%s", action->text, new_action->text);
Packit a7d494
Packit a7d494
		g_free (action->text);
Packit a7d494
		action->text = merged_text;
Packit a7d494
Packit a7d494
		action->end += new_text_length;
Packit a7d494
Packit a7d494
		/* No need to update the selection, action->start is not
Packit a7d494
		 * modified.
Packit a7d494
		 */
Packit a7d494
		g_assert_cmpint (action->selection_insert, ==, action->start);
Packit a7d494
		g_assert_cmpint (action->selection_bound, ==, action->start);
Packit a7d494
Packit a7d494
		return TRUE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	/* Backspace key pressed several times. */
Packit a7d494
	if (action->start == new_action->end)
Packit a7d494
	{
Packit a7d494
		gunichar last_char;
Packit a7d494
		gchar *merged_text;
Packit a7d494
Packit a7d494
		/* The last char deleted, but since it's with the Backspace key,
Packit a7d494
		 * it's the first char in action->text.
Packit a7d494
		 */
Packit a7d494
		last_char = g_utf8_get_char (action->text);
Packit a7d494
Packit a7d494
		/* Same as action_insert_merge(). */
Packit a7d494
		if ((new_char != ' ' && new_char != '\t') &&
Packit a7d494
		    (last_char == ' ' || last_char == '\t'))
Packit a7d494
		{
Packit a7d494
			return FALSE;
Packit a7d494
		}
Packit a7d494
Packit a7d494
		merged_text = g_strdup_printf ("%s%s", new_action->text, action->text);
Packit a7d494
Packit a7d494
		g_free (action->text);
Packit a7d494
		action->text = merged_text;
Packit a7d494
Packit a7d494
		action->start = new_action->start;
Packit a7d494
Packit a7d494
		/* No need to update the selection, action->end is not modified. */
Packit a7d494
		g_assert_cmpint (action->selection_insert, ==, action->end);
Packit a7d494
		g_assert_cmpint (action->selection_bound, ==, action->end);
Packit a7d494
Packit a7d494
		return TRUE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_assert_not_reached ();
Packit a7d494
	return FALSE;
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_delete_restore_selection (GtkTextBuffer *buffer,
Packit a7d494
				 Action        *action,
Packit a7d494
				 gboolean       undo)
Packit a7d494
{
Packit a7d494
Packit a7d494
	g_assert_cmpint (action->type, ==, ACTION_TYPE_DELETE);
Packit a7d494
Packit a7d494
	if (undo)
Packit a7d494
	{
Packit a7d494
		if (action->selection_insert == -1)
Packit a7d494
		{
Packit a7d494
			GtkTextIter iter;
Packit a7d494
Packit a7d494
			g_assert_cmpint (action->selection_bound, ==, -1);
Packit a7d494
Packit a7d494
			gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->end);
Packit a7d494
			gtk_text_buffer_place_cursor (buffer, &iter);
Packit a7d494
		}
Packit a7d494
		else
Packit a7d494
		{
Packit a7d494
			GtkTextIter insert_iter;
Packit a7d494
			GtkTextIter bound_iter;
Packit a7d494
Packit a7d494
			gtk_text_buffer_get_iter_at_offset (buffer,
Packit a7d494
							    &insert_iter,
Packit a7d494
							    action->selection_insert);
Packit a7d494
Packit a7d494
			gtk_text_buffer_get_iter_at_offset (buffer,
Packit a7d494
							    &bound_iter,
Packit a7d494
							    action->selection_bound);
Packit a7d494
Packit a7d494
			gtk_text_buffer_select_range (buffer, &insert_iter, &bound_iter);
Packit a7d494
		}
Packit a7d494
	}
Packit a7d494
	else /* redo */
Packit a7d494
	{
Packit a7d494
		GtkTextIter iter;
Packit a7d494
Packit a7d494
		gtk_text_buffer_get_iter_at_offset (buffer, &iter, action->start);
Packit a7d494
		gtk_text_buffer_place_cursor (buffer, &iter);
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Action interface.
Packit a7d494
 * The Action struct can be seen as an interface. All the explicit case analysis
Packit a7d494
 * on the action type are grouped in this code section. This can easily be
Packit a7d494
 * modified as an object-oriented architecture with polymorphism.
Packit a7d494
 */
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_undo (GtkTextBuffer *buffer,
Packit a7d494
	     Action        *action)
Packit a7d494
{
Packit a7d494
	g_assert (action != NULL);
Packit a7d494
Packit a7d494
	switch (action->type)
Packit a7d494
	{
Packit a7d494
		case ACTION_TYPE_INSERT:
Packit a7d494
			action_insert_undo (buffer, action);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		case ACTION_TYPE_DELETE:
Packit a7d494
			action_delete_undo (buffer, action);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		default:
Packit a7d494
			g_return_if_reached ();
Packit a7d494
			break;
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
action_redo (GtkTextBuffer *buffer,
Packit a7d494
	     Action        *action)
Packit a7d494
{
Packit a7d494
	g_assert (action != NULL);
Packit a7d494
Packit a7d494
	switch (action->type)
Packit a7d494
	{
Packit a7d494
		case ACTION_TYPE_INSERT:
Packit a7d494
			action_insert_redo (buffer, action);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		case ACTION_TYPE_DELETE:
Packit a7d494
			action_delete_redo (buffer, action);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		default:
Packit a7d494
			g_return_if_reached ();
Packit a7d494
			break;
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Try to merge @new_action into @action. Returns TRUE if merged. It is up to
Packit a7d494
 * the caller to free @new_action if needed.
Packit a7d494
 */
Packit a7d494
static gboolean
Packit a7d494
action_merge (Action *action,
Packit a7d494
	      Action *new_action)
Packit a7d494
{
Packit a7d494
	g_assert (action != NULL);
Packit a7d494
	g_assert (new_action != NULL);
Packit a7d494
Packit a7d494
	if (action->type != new_action->type)
Packit a7d494
	{
Packit a7d494
		return FALSE;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	switch (action->type)
Packit a7d494
	{
Packit a7d494
		case ACTION_TYPE_INSERT:
Packit a7d494
			return action_insert_merge (action, new_action);
Packit a7d494
Packit a7d494
		case ACTION_TYPE_DELETE:
Packit a7d494
			return action_delete_merge (action, new_action);
Packit a7d494
Packit a7d494
		default:
Packit a7d494
			g_return_val_if_reached (FALSE);
Packit a7d494
			break;
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Restore the selection (or cursor position) according to @action.
Packit a7d494
 * If @undo is TRUE, @action has just been undone. If @undo is FALSE, @action
Packit a7d494
 * has just been redone.
Packit a7d494
 */
Packit a7d494
static void
Packit a7d494
action_restore_selection (GtkTextBuffer *buffer,
Packit a7d494
			  Action        *action,
Packit a7d494
			  gboolean       undo)
Packit a7d494
{
Packit a7d494
	g_assert (action != NULL);
Packit a7d494
Packit a7d494
	switch (action->type)
Packit a7d494
	{
Packit a7d494
		case ACTION_TYPE_INSERT:
Packit a7d494
			action_insert_restore_selection (buffer, action, undo);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		case ACTION_TYPE_DELETE:
Packit a7d494
			action_delete_restore_selection (buffer, action, undo);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		default:
Packit a7d494
			g_return_if_reached ();
Packit a7d494
			break;
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Buffer signal handlers */
Packit a7d494
Packit a7d494
static void
Packit a7d494
set_selection_bounds (GtkTextBuffer *buffer,
Packit a7d494
		      Action        *action)
Packit a7d494
{
Packit a7d494
	GtkTextMark *insert_mark;
Packit a7d494
	GtkTextMark *bound_mark;
Packit a7d494
	GtkTextIter insert_iter;
Packit a7d494
	GtkTextIter bound_iter;
Packit a7d494
Packit a7d494
	insert_mark = gtk_text_buffer_get_insert (buffer);
Packit a7d494
	bound_mark = gtk_text_buffer_get_selection_bound (buffer);
Packit a7d494
Packit a7d494
	gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, insert_mark);
Packit a7d494
	gtk_text_buffer_get_iter_at_mark (buffer, &bound_iter, bound_mark);
Packit a7d494
Packit a7d494
	action->selection_insert = gtk_text_iter_get_offset (&insert_iter);
Packit a7d494
	action->selection_bound = gtk_text_iter_get_offset (&bound_iter);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
insert_text_cb (GtkTextBuffer               *buffer,
Packit a7d494
		GtkTextIter                 *location,
Packit a7d494
		const gchar                 *text,
Packit a7d494
		gint                         length,
Packit a7d494
		GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	Action *action = action_new ();
Packit a7d494
Packit a7d494
	action->type = ACTION_TYPE_INSERT;
Packit a7d494
	action->start = gtk_text_iter_get_offset (location);
Packit a7d494
	action->text = g_strndup (text, length);
Packit a7d494
	action->end = action->start + g_utf8_strlen (action->text, -1);
Packit a7d494
Packit a7d494
	set_selection_bounds (buffer, action);
Packit a7d494
Packit a7d494
	if (action->selection_insert != action->selection_bound ||
Packit a7d494
	    action->selection_insert != action->start)
Packit a7d494
	{
Packit a7d494
		action->selection_insert = -1;
Packit a7d494
		action->selection_bound = -1;
Packit a7d494
	}
Packit a7d494
	else
Packit a7d494
	{
Packit a7d494
		/* The insertion occurred at the cursor. */
Packit a7d494
		g_assert_cmpint (action->selection_insert, ==, action->start);
Packit a7d494
		g_assert_cmpint (action->selection_bound, ==, action->start);
Packit a7d494
	}
Packit a7d494
Packit a7d494
	insert_action (manager, action);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
delete_range_cb (GtkTextBuffer               *buffer,
Packit a7d494
		 GtkTextIter                 *start,
Packit a7d494
		 GtkTextIter                 *end,
Packit a7d494
		 GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	Action *action = action_new ();
Packit a7d494
Packit a7d494
	action->type = ACTION_TYPE_DELETE;
Packit a7d494
	action->start = gtk_text_iter_get_offset (start);
Packit a7d494
	action->end = gtk_text_iter_get_offset (end);
Packit a7d494
	action->text = gtk_text_buffer_get_slice (buffer, start, end, TRUE);
Packit a7d494
Packit a7d494
	g_assert_cmpint (action->start, <, action->end);
Packit a7d494
Packit a7d494
	set_selection_bounds (buffer, action);
Packit a7d494
Packit a7d494
	if ((action->selection_insert != action->start &&
Packit a7d494
	     action->selection_insert != action->end) ||
Packit a7d494
	    (action->selection_bound != action->start &&
Packit a7d494
	     action->selection_bound != action->end))
Packit a7d494
	{
Packit a7d494
		action->selection_insert = -1;
Packit a7d494
		action->selection_bound = -1;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	insert_action (manager, action);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
begin_user_action_cb (GtkTextBuffer               *buffer,
Packit a7d494
		      GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	manager->priv->running_user_action = TRUE;
Packit a7d494
	update_can_undo_can_redo (manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
end_user_action_cb (GtkTextBuffer               *buffer,
Packit a7d494
		    GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	insert_new_action_group (manager);
Packit a7d494
Packit a7d494
	manager->priv->running_user_action = FALSE;
Packit a7d494
	update_can_undo_can_redo (manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
modified_changed_cb (GtkTextBuffer               *buffer,
Packit a7d494
		     GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	if (gtk_text_buffer_get_modified (buffer))
Packit a7d494
	{
Packit a7d494
		/* It can happen for example when the file on disk has been
Packit a7d494
		 * deleted.
Packit a7d494
		 */
Packit a7d494
		if (manager->priv->has_saved_location &&
Packit a7d494
		    manager->priv->saved_location == manager->priv->location &&
Packit a7d494
		    (manager->priv->new_action_group == NULL ||
Packit a7d494
		     manager->priv->new_action_group->actions->length == 0))
Packit a7d494
		{
Packit a7d494
			manager->priv->has_saved_location = FALSE;
Packit a7d494
		}
Packit a7d494
	}
Packit a7d494
Packit a7d494
	/* saved */
Packit a7d494
	else
Packit a7d494
	{
Packit a7d494
		/* Saving a buffer during a user action is allowed, the user
Packit a7d494
		 * action is split.
Packit a7d494
		 * FIXME and/or a warning should be printed?
Packit a7d494
		 */
Packit a7d494
		if (manager->priv->running_user_action)
Packit a7d494
		{
Packit a7d494
			insert_new_action_group (manager);
Packit a7d494
		}
Packit a7d494
Packit a7d494
		manager->priv->saved_location = manager->priv->location;
Packit a7d494
		manager->priv->has_saved_location = TRUE;
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
block_signal_handlers (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	if (manager->priv->buffer == NULL)
Packit a7d494
	{
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_signal_handlers_block_by_func (manager->priv->buffer,
Packit a7d494
					 insert_text_cb,
Packit a7d494
					 manager);
Packit a7d494
Packit a7d494
	g_signal_handlers_block_by_func (manager->priv->buffer,
Packit a7d494
					 delete_range_cb,
Packit a7d494
					 manager);
Packit a7d494
Packit a7d494
	g_signal_handlers_block_by_func (manager->priv->buffer,
Packit a7d494
					 modified_changed_cb,
Packit a7d494
					 manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
unblock_signal_handlers (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	if (manager->priv->buffer == NULL)
Packit a7d494
	{
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_signal_handlers_unblock_by_func (manager->priv->buffer,
Packit a7d494
					   insert_text_cb,
Packit a7d494
					   manager);
Packit a7d494
Packit a7d494
	g_signal_handlers_unblock_by_func (manager->priv->buffer,
Packit a7d494
					   delete_range_cb,
Packit a7d494
					   manager);
Packit a7d494
Packit a7d494
	g_signal_handlers_unblock_by_func (manager->priv->buffer,
Packit a7d494
					   modified_changed_cb,
Packit a7d494
					   manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
set_buffer (GtkSourceUndoManagerDefault *manager,
Packit a7d494
            GtkTextBuffer               *buffer)
Packit a7d494
{
Packit a7d494
	g_assert (manager->priv->buffer == NULL);
Packit a7d494
Packit a7d494
	if (buffer == NULL)
Packit a7d494
	{
Packit a7d494
		return;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	manager->priv->buffer = buffer;
Packit a7d494
Packit a7d494
	g_object_add_weak_pointer (G_OBJECT (buffer),
Packit a7d494
				   (gpointer *)&manager->priv->buffer);
Packit a7d494
Packit a7d494
	g_signal_connect_object (buffer,
Packit a7d494
				 "insert-text",
Packit a7d494
				 G_CALLBACK (insert_text_cb),
Packit a7d494
				 manager,
Packit a7d494
				 0);
Packit a7d494
Packit a7d494
	g_signal_connect_object (buffer,
Packit a7d494
				 "delete-range",
Packit a7d494
				 G_CALLBACK (delete_range_cb),
Packit a7d494
				 manager,
Packit a7d494
				 0);
Packit a7d494
Packit a7d494
	g_signal_connect_object (buffer,
Packit a7d494
				 "begin-user-action",
Packit a7d494
				 G_CALLBACK (begin_user_action_cb),
Packit a7d494
				 manager,
Packit a7d494
				 0);
Packit a7d494
Packit a7d494
	g_signal_connect_object (buffer,
Packit a7d494
				 "end-user-action",
Packit a7d494
				 G_CALLBACK (end_user_action_cb),
Packit a7d494
				 manager,
Packit a7d494
				 0);
Packit a7d494
Packit a7d494
	g_signal_connect_object (buffer,
Packit a7d494
				 "modified-changed",
Packit a7d494
				 G_CALLBACK (modified_changed_cb),
Packit a7d494
				 manager,
Packit a7d494
				 0);
Packit a7d494
Packit a7d494
	modified_changed_cb (manager->priv->buffer, manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
/* GObject construction, destruction and properties */
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_default_set_property (GObject      *object,
Packit a7d494
                                              guint         prop_id,
Packit a7d494
                                              const GValue *value,
Packit a7d494
                                              GParamSpec   *pspec)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
Packit a7d494
Packit a7d494
	switch (prop_id)
Packit a7d494
	{
Packit a7d494
		case PROP_BUFFER:
Packit a7d494
			set_buffer (manager, g_value_get_object (value));
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		case PROP_MAX_UNDO_LEVELS:
Packit a7d494
			gtk_source_undo_manager_default_set_max_undo_levels (manager, g_value_get_int (value));
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		default:
Packit a7d494
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit a7d494
			break;
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_default_get_property (GObject    *object,
Packit a7d494
                                              guint       prop_id,
Packit a7d494
                                              GValue     *value,
Packit a7d494
                                              GParamSpec *pspec)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
Packit a7d494
Packit a7d494
	switch (prop_id)
Packit a7d494
	{
Packit a7d494
		case PROP_BUFFER:
Packit a7d494
			g_value_set_object (value, manager->priv->buffer);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		case PROP_MAX_UNDO_LEVELS:
Packit a7d494
			g_value_set_int (value, manager->priv->max_undo_levels);
Packit a7d494
			break;
Packit a7d494
Packit a7d494
		default:
Packit a7d494
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit a7d494
			break;
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_default_dispose (GObject *object)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
Packit a7d494
Packit a7d494
	if (manager->priv->buffer != NULL)
Packit a7d494
	{
Packit a7d494
		g_object_remove_weak_pointer (G_OBJECT (manager->priv->buffer),
Packit a7d494
					      (gpointer *)&manager->priv->buffer);
Packit a7d494
Packit a7d494
		manager->priv->buffer = NULL;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	G_OBJECT_CLASS (gtk_source_undo_manager_default_parent_class)->dispose (object);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_default_finalize (GObject *object)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (object);
Packit a7d494
Packit a7d494
	g_queue_free_full (manager->priv->action_groups,
Packit a7d494
			   (GDestroyNotify) action_group_free);
Packit a7d494
Packit a7d494
	action_group_free (manager->priv->new_action_group);
Packit a7d494
Packit a7d494
	G_OBJECT_CLASS (gtk_source_undo_manager_default_parent_class)->finalize (object);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_default_class_init (GtkSourceUndoManagerDefaultClass *klass)
Packit a7d494
{
Packit a7d494
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
Packit a7d494
Packit a7d494
	object_class->set_property = gtk_source_undo_manager_default_set_property;
Packit a7d494
	object_class->get_property = gtk_source_undo_manager_default_get_property;
Packit a7d494
	object_class->dispose = gtk_source_undo_manager_default_dispose;
Packit a7d494
	object_class->finalize = gtk_source_undo_manager_default_finalize;
Packit a7d494
Packit a7d494
	g_object_class_install_property (object_class,
Packit a7d494
	                                 PROP_BUFFER,
Packit a7d494
	                                 g_param_spec_object ("buffer",
Packit a7d494
	                                                      "Buffer",
Packit a7d494
	                                                      "The text buffer to add undo support on",
Packit a7d494
	                                                      GTK_TYPE_TEXT_BUFFER,
Packit a7d494
	                                                      G_PARAM_READWRITE |
Packit a7d494
							      G_PARAM_CONSTRUCT_ONLY |
Packit a7d494
							      G_PARAM_STATIC_STRINGS));
Packit a7d494
Packit a7d494
	g_object_class_install_property (object_class,
Packit a7d494
	                                 PROP_MAX_UNDO_LEVELS,
Packit a7d494
	                                 g_param_spec_int ("max-undo-levels",
Packit a7d494
	                                                   "Max Undo Levels",
Packit a7d494
	                                                   "Number of undo levels for the buffer",
Packit a7d494
	                                                   -1,
Packit a7d494
	                                                   G_MAXINT,
Packit a7d494
	                                                   DEFAULT_MAX_UNDO_LEVELS,
Packit a7d494
	                                                   G_PARAM_READWRITE |
Packit a7d494
							   G_PARAM_STATIC_STRINGS));
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_default_init (GtkSourceUndoManagerDefault *manager)
Packit a7d494
{
Packit a7d494
	manager->priv = gtk_source_undo_manager_default_get_instance_private (manager);
Packit a7d494
Packit a7d494
	manager->priv->action_groups = g_queue_new ();
Packit a7d494
	manager->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Interface implementation */
Packit a7d494
Packit a7d494
static gboolean
Packit a7d494
gtk_source_undo_manager_can_undo_impl (GtkSourceUndoManager *undo_manager)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
Packit a7d494
	return manager->priv->can_undo;
Packit a7d494
}
Packit a7d494
Packit a7d494
static gboolean
Packit a7d494
gtk_source_undo_manager_can_redo_impl (GtkSourceUndoManager *undo_manager)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
Packit a7d494
	return manager->priv->can_redo;
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
restore_modified_state (GtkSourceUndoManagerDefault *manager,
Packit a7d494
			GList                       *old_location,
Packit a7d494
			GList                       *new_location)
Packit a7d494
{
Packit a7d494
	if (manager->priv->has_saved_location)
Packit a7d494
	{
Packit a7d494
		if (old_location == manager->priv->saved_location)
Packit a7d494
		{
Packit a7d494
			gtk_text_buffer_set_modified (manager->priv->buffer, TRUE);
Packit a7d494
		}
Packit a7d494
		else if (new_location == manager->priv->saved_location)
Packit a7d494
		{
Packit a7d494
			gtk_text_buffer_set_modified (manager->priv->buffer, FALSE);
Packit a7d494
		}
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_undo_impl (GtkSourceUndoManager *undo_manager)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
Packit a7d494
	GList *old_location;
Packit a7d494
	GList *new_location;
Packit a7d494
	ActionGroup *group;
Packit a7d494
	Action *action;
Packit a7d494
	GList *l;
Packit a7d494
Packit a7d494
	g_return_if_fail (manager->priv->can_undo);
Packit a7d494
Packit a7d494
	old_location = manager->priv->location;
Packit a7d494
Packit a7d494
	if (old_location != NULL)
Packit a7d494
	{
Packit a7d494
		new_location = manager->priv->location->prev;
Packit a7d494
	}
Packit a7d494
	else
Packit a7d494
	{
Packit a7d494
		new_location = manager->priv->action_groups->tail;
Packit a7d494
	}
Packit a7d494
Packit a7d494
	g_assert (new_location != NULL);
Packit a7d494
Packit a7d494
	group = new_location->data;
Packit a7d494
	g_assert_cmpuint (group->actions->length, >, 0);
Packit a7d494
Packit a7d494
	block_signal_handlers (manager);
Packit a7d494
Packit a7d494
	for (l = group->actions->tail; l != NULL; l = l->prev)
Packit a7d494
	{
Packit a7d494
		action = l->data;
Packit a7d494
		action_undo (manager->priv->buffer, action);
Packit a7d494
	}
Packit a7d494
Packit a7d494
	restore_modified_state (manager, old_location, new_location);
Packit a7d494
Packit a7d494
	/* After an undo, place the cursor at the first action in the group. For
Packit a7d494
	 * a search and replace, it will be the first occurrence in the buffer.
Packit a7d494
	 */
Packit a7d494
	action = g_queue_peek_head (group->actions);
Packit a7d494
	action_restore_selection (manager->priv->buffer, action, TRUE);
Packit a7d494
Packit a7d494
	unblock_signal_handlers (manager);
Packit a7d494
Packit a7d494
	manager->priv->location = new_location;
Packit a7d494
	update_can_undo_can_redo (manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_redo_impl (GtkSourceUndoManager *undo_manager)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
Packit a7d494
	GList *old_location;
Packit a7d494
	GList *new_location;
Packit a7d494
	ActionGroup *group;
Packit a7d494
	GList *l;
Packit a7d494
Packit a7d494
	g_return_if_fail (manager->priv->can_redo);
Packit a7d494
Packit a7d494
	old_location = manager->priv->location;
Packit a7d494
	g_assert (old_location != NULL);
Packit a7d494
Packit a7d494
	new_location = old_location->next;
Packit a7d494
Packit a7d494
	group = old_location->data;
Packit a7d494
Packit a7d494
	block_signal_handlers (manager);
Packit a7d494
Packit a7d494
	for (l = group->actions->head; l != NULL; l = l->next)
Packit a7d494
	{
Packit a7d494
		Action *action = l->data;
Packit a7d494
		action_redo (manager->priv->buffer, action);
Packit a7d494
Packit a7d494
		/* For a redo, place the cursor at the first action in the
Packit a7d494
		 * group. For an undo the first action is also chosen, so when
Packit a7d494
		 * undoing/redoing a search and replace, the cursor position
Packit a7d494
		 * stays at the first occurrence and the user can see the
Packit a7d494
		 * replacement easily.
Packit a7d494
		 * For a redo, if we choose the last action in the group, when
Packit a7d494
		 * undoing/redoing a search and replace, the cursor position
Packit a7d494
		 * will jump between the first occurrence and the last
Packit a7d494
		 * occurrence. Staying at the same place is probably better.
Packit a7d494
		 */
Packit a7d494
		if (l == group->actions->head)
Packit a7d494
		{
Packit a7d494
			action_restore_selection (manager->priv->buffer, action, FALSE);
Packit a7d494
		}
Packit a7d494
	}
Packit a7d494
Packit a7d494
	restore_modified_state (manager, old_location, new_location);
Packit a7d494
Packit a7d494
	unblock_signal_handlers (manager);
Packit a7d494
Packit a7d494
	manager->priv->location = new_location;
Packit a7d494
	update_can_undo_can_redo (manager);
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_begin_not_undoable_action_impl (GtkSourceUndoManager *undo_manager)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
Packit a7d494
	manager->priv->running_not_undoable_actions++;
Packit a7d494
Packit a7d494
	if (manager->priv->running_not_undoable_actions == 1)
Packit a7d494
	{
Packit a7d494
		block_signal_handlers (manager);
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_end_not_undoable_action_impl (GtkSourceUndoManager *undo_manager)
Packit a7d494
{
Packit a7d494
	GtkSourceUndoManagerDefault *manager = GTK_SOURCE_UNDO_MANAGER_DEFAULT (undo_manager);
Packit a7d494
Packit a7d494
	g_return_if_fail (manager->priv->running_not_undoable_actions > 0);
Packit a7d494
Packit a7d494
	manager->priv->running_not_undoable_actions--;
Packit a7d494
Packit a7d494
	if (manager->priv->running_not_undoable_actions == 0)
Packit a7d494
	{
Packit a7d494
		unblock_signal_handlers (manager);
Packit a7d494
		clear_all (manager);
Packit a7d494
		modified_changed_cb (manager->priv->buffer, manager);
Packit a7d494
	}
Packit a7d494
}
Packit a7d494
Packit a7d494
static void
Packit a7d494
gtk_source_undo_manager_iface_init (GtkSourceUndoManagerIface *iface)
Packit a7d494
{
Packit a7d494
	iface->can_undo = gtk_source_undo_manager_can_undo_impl;
Packit a7d494
	iface->can_redo = gtk_source_undo_manager_can_redo_impl;
Packit a7d494
	iface->undo = gtk_source_undo_manager_undo_impl;
Packit a7d494
	iface->redo = gtk_source_undo_manager_redo_impl;
Packit a7d494
	iface->begin_not_undoable_action = gtk_source_undo_manager_begin_not_undoable_action_impl;
Packit a7d494
	iface->end_not_undoable_action = gtk_source_undo_manager_end_not_undoable_action_impl;
Packit a7d494
}
Packit a7d494
Packit a7d494
/* Public functions */
Packit a7d494
Packit a7d494
void
Packit a7d494
gtk_source_undo_manager_default_set_max_undo_levels (GtkSourceUndoManagerDefault *manager,
Packit a7d494
                                                     gint                         max_undo_levels)
Packit a7d494
{
Packit a7d494
	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER_DEFAULT (manager));
Packit a7d494
	g_return_if_fail (max_undo_levels >= -1);
Packit a7d494
Packit a7d494
	if (manager->priv->max_undo_levels != max_undo_levels)
Packit a7d494
	{
Packit a7d494
		if (max_undo_levels == 0)
Packit a7d494
		{
Packit a7d494
			/* disable the undo manager */
Packit a7d494
			block_signal_handlers (manager);
Packit a7d494
		}
Packit a7d494
		else if (manager->priv->max_undo_levels == 0)
Packit a7d494
		{
Packit a7d494
			unblock_signal_handlers (manager);
Packit a7d494
			modified_changed_cb (manager->priv->buffer, manager);
Packit a7d494
		}
Packit a7d494
Packit a7d494
		manager->priv->max_undo_levels = max_undo_levels;
Packit a7d494
		check_history_size (manager);
Packit a7d494
Packit a7d494
		g_object_notify (G_OBJECT (manager), "max-undo-levels");
Packit a7d494
	}
Packit a7d494
}