| Index: third_party/undoview/undo_manager.c
|
| ===================================================================
|
| --- third_party/undoview/undo_manager.c (revision 0)
|
| +++ third_party/undoview/undo_manager.c (revision 0)
|
| @@ -0,0 +1,1110 @@
|
| +/*
|
| + * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
|
| + * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi
|
| + * Copyright (C) 2002-2005 Paolo Maggi
|
| + *
|
| + * This library is free software; you can redistribute it and/or
|
| + * modify it under the terms of the GNU Lesser General Public
|
| + * License as published by the Free Software Foundation; either
|
| + * version 2.1 of the License, or (at your option) any later version.
|
| + *
|
| + * This library 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
|
| + * Lesser General Public License for more details.
|
| +
|
| + * You should have received a copy of the GNU Lesser General Public
|
| + * License along with this library; if not, write to the Free Software
|
| + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
| + */
|
| +
|
| +#ifdef HAVE_CONFIG_H
|
| +#include <config.h>
|
| +#endif
|
| +
|
| +#include <glib.h>
|
| +#include <stdlib.h>
|
| +#include <string.h>
|
| +
|
| +#include "undo_manager.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
|
| +};
|
| +
|
| +#if !defined(NDEBUG)
|
| +static void
|
| +print_state(GtkSourceUndoManager* um)
|
| +{
|
| + fprintf(stderr, "\n***\n");
|
| + GList* actions = um->priv->actions;
|
| +
|
| + for (; actions; actions = g_list_next(actions)) {
|
| + GtkSourceUndoAction* act = actions->data;
|
| + fprintf(stderr, "* type = %s\n", act->action_type == GTK_SOURCE_UNDO_ACTION_DELETE ?
|
| + "delete" : "insert");
|
| +
|
| + fprintf(stderr, "\ttext = %s\n", act->action_type == GTK_SOURCE_UNDO_ACTION_DELETE
|
| + ? act->action.delete.text : act->action.insert.text);
|
| + fprintf(stderr, "\torder = %d\n", act->order_in_group);
|
| + }
|
| +
|
| + fprintf(stderr, "* next redo: %d\n", um->priv->next_redo);
|
| + fprintf(stderr, "* num of groups: %d\n", um->priv->num_of_groups);
|
| + fprintf(stderr, "* actions in group: %d\n", um->priv->actions_in_current_group);
|
| +}
|
| +#endif
|
| +
|
| +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,
|
| + g_cclosure_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,
|
| + g_cclosure_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_signal_handlers_disconnect_by_func(G_OBJECT(um->priv->document),
|
| + G_CALLBACK(gtk_source_undo_manager_modified_changed_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)
|
| + g_free(action->action.delete.text);
|
| + else
|
| + g_return_if_reached();
|
| +
|
| + g_free(action);
|
| +}
|
| +
|
| +static void
|
| +gtk_source_undo_manager_free_action_list(GtkSourceUndoManager *um) {
|
| + GList *l;
|
| +
|
| + l = um->priv->actions;
|
| +
|
| + while(l != NULL)
|
| + {
|
| + GtkSourceUndoAction *action = l->data;
|
| +
|
| + if(action->order_in_group == 1)
|
| + --um->priv->num_of_groups;
|
| + um->priv->actions_in_current_group = action->order_in_group - 1;
|
| +
|
| + if(action->modified)
|
| + um->priv->modified_action = INVALID;
|
| +
|
| + gtk_source_undo_action_free(action);
|
| +
|
| + l = g_list_next(l);
|
| + }
|
| +
|
| + 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;
|
| +
|
| + 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_strndup(undo_action->action.insert.text, undo_action->action.insert.length);
|
| + 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;
|
| +
|
| + if(um->priv->actions == NULL)
|
| + return;
|
| +
|
| + for(i = 0; i < n; i++)
|
| + {
|
| + GtkSourceUndoAction *action = g_list_first(um->priv->actions)->data;
|
| +
|
| + if(action->order_in_group == 1)
|
| + --um->priv->num_of_groups;
|
| + um->priv->actions_in_current_group = action->order_in_group - 1;
|
| +
|
| + if(action->modified)
|
| + um->priv->modified_action = INVALID;
|
| +
|
| + gtk_source_undo_action_free(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->order_in_group == 1)
|
| + --um->priv->num_of_groups;
|
| + um->priv->actions_in_current_group = undo_action->order_in_group - 1;
|
| +
|
| + if(undo_action->modified)
|
| + um->priv->modified_action = INVALID;
|
| +
|
| + gtk_source_undo_action_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 successful, %FALSE otherwise.
|
| + **/
|
| +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;
|
| +}
|
| +
|
|
|
| Property changes on: third_party/undoview/undo_manager.c
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|