medit/moo/mooedit/gtksourceview/gtksourceundomanager.c

1143 lines
30 KiB
C
Raw Blame History

/* -*- 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.
*/
/*
* This file is from gtksourceview-1.4.1. Modified by muntyan
* Sep 08 2005: replaced gtksourceview_marshal* with _moo_marshal*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include "gtksourceundomanager.h"
#include "mooutils/moomarshals.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,
_moo_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,
_moo_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_manager_free_action_list (GtkSourceUndoManager *um)
{
gint n, len;
g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
g_return_if_fail (um->priv != NULL);
if (um->priv->actions == NULL)
return;
len = g_list_length (um->priv->actions);
for (n = 0; n < len; n++)
{
GtkSourceUndoAction *undo_action =
(GtkSourceUndoAction *)(g_list_nth_data (um->priv->actions, n));
if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
g_free (undo_action->action.insert.text);
else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
g_free (undo_action->action.delete.text);
else
g_return_if_fail (FALSE);
if (undo_action->order_in_group == 1)
--um->priv->num_of_groups;
if (undo_action->modified)
um->priv->modified_action = INVALID;
g_free (undo_action);
}
g_list_free (um->priv->actions);
um->priv->actions = NULL;
}
static void
gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer,
GtkTextIter *pos,
const gchar *text,
gint length,
GtkSourceUndoManager *um)
{
GtkSourceUndoAction undo_action;
if (um->priv->running_not_undoable_actions > 0)
return;
g_return_if_fail (strlen (text) >= (guint)length);
undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
undo_action.action.insert.pos = gtk_text_iter_get_offset (pos);
undo_action.action.insert.text = (gchar*) text;
undo_action.action.insert.length = length;
undo_action.action.insert.chars = g_utf8_strlen (text, length);
if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
undo_action.mergeable = FALSE;
else
undo_action.mergeable = TRUE;
undo_action.modified = FALSE;
gtk_source_undo_manager_add_action (um, &undo_action);
}
static void
gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer,
GtkTextIter *start,
GtkTextIter *end,
GtkSourceUndoManager *um)
{
GtkSourceUndoAction undo_action;
GtkTextIter insert_iter;
if (um->priv->running_not_undoable_actions > 0)
return;
undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE;
gtk_text_iter_order (start, end);
undo_action.action.delete.start = gtk_text_iter_get_offset (start);
undo_action.action.delete.end = gtk_text_iter_get_offset (end);
undo_action.action.delete.text = get_chars (
buffer,
undo_action.action.delete.start,
undo_action.action.delete.end);
/* figure out if the user used the Delete or the Backspace key */
gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter,
gtk_text_buffer_get_insert (buffer));
if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start)
undo_action.action.delete.forward = TRUE;
else
undo_action.action.delete.forward = FALSE;
if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
(g_utf8_get_char (undo_action.action.delete.text ) == '\n'))
undo_action.mergeable = FALSE;
else
undo_action.mergeable = TRUE;
undo_action.modified = FALSE;
gtk_source_undo_manager_add_action (um, &undo_action);
g_free (undo_action.action.delete.text);
}
static void
gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um)
{
g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
g_return_if_fail (um->priv != NULL);
if (um->priv->running_not_undoable_actions > 0)
return;
um->priv->actions_in_current_group = 0;
}
static void
gtk_source_undo_manager_add_action (GtkSourceUndoManager *um,
const GtkSourceUndoAction *undo_action)
{
GtkSourceUndoAction* action;
if (um->priv->next_redo >= 0)
{
gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1);
}
um->priv->next_redo = -1;
if (!gtk_source_undo_manager_merge_action (um, undo_action))
{
action = g_new (GtkSourceUndoAction, 1);
*action = *undo_action;
if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
action->action.insert.text = g_strdup (undo_action->action.insert.text);
else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
action->action.delete.text = g_strdup (undo_action->action.delete.text);
else
{
g_free (action);
g_return_if_reached ();
}
++um->priv->actions_in_current_group;
action->order_in_group = um->priv->actions_in_current_group;
if (action->order_in_group == 1)
++um->priv->num_of_groups;
um->priv->actions = g_list_prepend (um->priv->actions, action);
}
gtk_source_undo_manager_check_list_size (um);
if (!um->priv->can_undo)
{
um->priv->can_undo = TRUE;
g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
}
if (um->priv->can_redo)
{
um->priv->can_redo = FALSE;
g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
}
}
static void
gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um,
gint n)
{
gint i;
g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
g_return_if_fail (um->priv != NULL);
if (um->priv->actions == NULL)
return;
for (i = 0; i < n; i++)
{
GtkSourceUndoAction *undo_action =
(GtkSourceUndoAction *)(g_list_first (um->priv->actions)->data);
if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
g_free (undo_action->action.insert.text);
else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
g_free (undo_action->action.delete.text);
else
g_return_if_fail (FALSE);
if (undo_action->order_in_group == 1)
--um->priv->num_of_groups;
if (undo_action->modified)
um->priv->modified_action = INVALID;
g_free (undo_action);
um->priv->actions = g_list_delete_link (um->priv->actions, um->priv->actions);
if (um->priv->actions == NULL)
return;
}
}
static void
gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um)
{
gint undo_levels;
g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
g_return_if_fail (um->priv != NULL);
undo_levels = gtk_source_undo_manager_get_max_undo_levels (um);
if (undo_levels < 1)
return;
if (um->priv->num_of_groups > undo_levels)
{
GtkSourceUndoAction *undo_action;
GList *last;
last = g_list_last (um->priv->actions);
undo_action = (GtkSourceUndoAction*) last->data;
do
{
GList *tmp;
if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
g_free (undo_action->action.insert.text);
else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
g_free (undo_action->action.delete.text);
else
g_return_if_fail (FALSE);
if (undo_action->order_in_group == 1)
--um->priv->num_of_groups;
if (undo_action->modified)
um->priv->modified_action = INVALID;
g_free (undo_action);
tmp = g_list_previous (last);
um->priv->actions = g_list_delete_link (um->priv->actions, last);
last = tmp;
g_return_if_fail (last != NULL);
undo_action = (GtkSourceUndoAction*) last->data;
} while ((undo_action->order_in_group > 1) ||
(um->priv->num_of_groups > undo_levels));
}
}
/**
* gtk_source_undo_manager_merge_action:
* @um: a #GtkSourceUndoManager.
* @undo_action: a #GtkSourceUndoAction.
*
* This function tries to merge the undo action at the top of
* the stack with a new undo action. So when we undo for example
* typing, we can undo the whole word and not each letter by itself.
*
* Return Value: %TRUE is merge was sucessful, %FALSE otherwise.<2E>
**/
static gboolean
gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um,
const GtkSourceUndoAction *undo_action)
{
GtkSourceUndoAction *last_action;
g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
g_return_val_if_fail (um->priv != NULL, FALSE);
if (um->priv->actions == NULL)
return FALSE;
last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0);
if (!last_action->mergeable)
return FALSE;
if ((!undo_action->mergeable) ||
(undo_action->action_type != last_action->action_type))
{
last_action->mergeable = FALSE;
return FALSE;
}
if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
{
if ((last_action->action.delete.forward != undo_action->action.delete.forward) ||
((last_action->action.delete.start != undo_action->action.delete.start) &&
(last_action->action.delete.start != undo_action->action.delete.end)))
{
last_action->mergeable = FALSE;
return FALSE;
}
if (last_action->action.delete.start == undo_action->action.delete.start)
{
gchar *str;
#define L (last_action->action.delete.end - last_action->action.delete.start - 1)
#define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
/* Deleted with the delete key */
if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
(g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
(g_utf8_get_char_at (last_action->action.delete.text, L) == '\t')))
{
last_action->mergeable = FALSE;
return FALSE;
}
str = g_strdup_printf ("%s%s", last_action->action.delete.text,
undo_action->action.delete.text);
g_free (last_action->action.delete.text);
last_action->action.delete.end += (undo_action->action.delete.end -
undo_action->action.delete.start);
last_action->action.delete.text = str;
}
else
{
gchar *str;
/* Deleted with the backspace key */
if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
(g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
(g_utf8_get_char (last_action->action.delete.text) == '\t')))
{
last_action->mergeable = FALSE;
return FALSE;
}
str = g_strdup_printf ("%s%s", undo_action->action.delete.text,
last_action->action.delete.text);
g_free (last_action->action.delete.text);
last_action->action.delete.start = undo_action->action.delete.start;
last_action->action.delete.text = str;
}
}
else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
{
gchar* str;
#define I (last_action->action.insert.chars - 1)
if ((undo_action->action.insert.pos !=
(last_action->action.insert.pos + last_action->action.insert.chars)) ||
((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
(g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
(g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
)
{
last_action->mergeable = FALSE;
return FALSE;
}
str = g_strdup_printf ("%s%s", last_action->action.insert.text,
undo_action->action.insert.text);
g_free (last_action->action.insert.text);
last_action->action.insert.length += undo_action->action.insert.length;
last_action->action.insert.text = str;
last_action->action.insert.chars += undo_action->action.insert.chars;
}
else
/* Unknown action inside undo merge encountered */
g_return_val_if_reached (TRUE);
return TRUE;
}
gint
gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um)
{
g_return_val_if_fail (um != NULL, 0);
g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0);
return um->priv->max_undo_levels;
}
void
gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um,
gint max_undo_levels)
{
gint old_levels;
g_return_if_fail (um != NULL);
g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
old_levels = um->priv->max_undo_levels;
um->priv->max_undo_levels = max_undo_levels;
if (max_undo_levels < 1)
return;
if (old_levels > max_undo_levels)
{
/* strip redo actions first */
while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels))
{
gtk_source_undo_manager_free_first_n_actions (um, 1);
um->priv->next_redo--;
}
/* now remove undo actions if necessary */
gtk_source_undo_manager_check_list_size (um);
/* emit "can_undo" and/or "can_redo" if appropiate */
if (um->priv->next_redo < 0 && um->priv->can_redo)
{
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->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);
}
}
}
static void
gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer,
GtkSourceUndoManager *um)
{
GtkSourceUndoAction *action;
GList *list;
g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
g_return_if_fail (um->priv != NULL);
if (um->priv->actions == NULL)
return;
list = g_list_nth (um->priv->actions, um->priv->next_redo + 1);
if (list != NULL)
action = (GtkSourceUndoAction*) list->data;
else
action = NULL;
if (gtk_text_buffer_get_modified (buffer) == FALSE)
{
if (action != NULL)
action->mergeable = FALSE;
if (um->priv->modified_action != NULL)
{
if (um->priv->modified_action != INVALID)
um->priv->modified_action->modified = FALSE;
um->priv->modified_action = NULL;
}
return;
}
if (action == NULL)
{
g_return_if_fail (um->priv->running_not_undoable_actions > 0);
return;
}
/* gtk_text_buffer_get_modified (buffer) == TRUE */
g_return_if_fail (um->priv->modified_action == NULL);
if (action->order_in_group > 1)
um->priv->modified_undoing_group = TRUE;
while (action->order_in_group > 1)
{
list = g_list_next (list);
g_return_if_fail (list != NULL);
action = (GtkSourceUndoAction*) list->data;
g_return_if_fail (action != NULL);
}
action->modified = TRUE;
um->priv->modified_action = action;
}