/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02111-1301, USA. #include "gtksourceundomanager.h" #include "gtksourceview-marshal.h" #define DEFAULT_MAX_UNDO_LEVELS 25 typedef struct _GtkSourceUndoAction GtkSourceUndoAction; typedef struct _GtkSourceUndoInsertAction GtkSourceUndoInsertAction; typedef struct _GtkSourceUndoDeleteAction GtkSourceUndoDeleteAction; typedef struct _GtkSourceUndoInsertAnchorAction GtkSourceUndoInsertAnchorAction; GTK_SOURCE_UNDO_ACTION_INSERT, GTK_SOURCE_UNDO_ACTION_DELETE, GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR, } 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 struct _GtkSourceUndoDeleteAction struct _GtkSourceUndoInsertAnchorAction GtkTextChildAnchor *anchor; struct _GtkSourceUndoAction GtkSourceUndoActionType action_type; GtkSourceUndoInsertAction insert; GtkSourceUndoDeleteAction delete; GtkSourceUndoInsertAnchorAction insert_anchor; /* It is TRUE whether the action can be merged with the following action. */ /* 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. struct _GtkSourceUndoManagerPrivate gint actions_in_current_group; gint running_not_undoable_actions; /* 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". */ GtkSourceUndoAction *modified_action; 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, GtkSourceUndoManager *um); static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer, GtkTextChildAnchor *anchor, GtkSourceUndoManager *um); static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, 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, 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 }; 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_finalize */ (GClassInitFunc) gtk_source_undo_manager_class_init, NULL, /* class_finalize */ sizeof (GtkSourceUndoManager), (GInstanceInitFunc) gtk_source_undo_manager_init, undo_manager_type = g_type_register_static (G_TYPE_OBJECT, return undo_manager_type; 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; undo_manager_signals[CAN_UNDO] = g_signal_new ("can_undo", G_OBJECT_CLASS_TYPE (object_class), G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo), gtksourceview_marshal_VOID__BOOLEAN, undo_manager_signals[CAN_REDO] = g_signal_new ("can_redo", G_OBJECT_CLASS_TYPE (object_class), G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo), gtksourceview_marshal_VOID__BOOLEAN, gtk_source_undo_manager_init (GtkSourceUndoManager *um) um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1); um->priv->actions = NULL; 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; 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), g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), G_CALLBACK (gtk_source_undo_manager_insert_text_handler), g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler), g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), G_OBJECT_CLASS (parent_class)->finalize (object); 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), g_signal_connect (G_OBJECT (buffer), "insert_child_anchor", G_CALLBACK (gtk_source_undo_manager_insert_anchor_handler), g_signal_connect (G_OBJECT (buffer), "delete_range", G_CALLBACK (gtk_source_undo_manager_delete_range_handler), g_signal_connect (G_OBJECT (buffer), "begin_user_action", G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), g_signal_connect (G_OBJECT (buffer), "modified_changed", G_CALLBACK (gtk_source_undo_manager_modified_changed_handler), 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; 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; 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; um->priv->can_undo = FALSE; g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], um->priv->can_redo = FALSE; g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 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; 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; set_cursor (GtkTextBuffer *buffer, gint cursor) /* Place the cursor at the requested position */ gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor); gtk_text_buffer_place_cursor (buffer, &iter); insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len) gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); gtk_text_buffer_insert (buffer, &iter, text, len); insert_anchor (GtkTextBuffer *buffer, gint pos, GtkTextChildAnchor *anchor) gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor); delete_text (GtkTextBuffer *buffer, gint start, gint end) gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); gtk_text_buffer_get_end_iter (buffer, &end_iter); gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); gtk_text_buffer_delete (buffer, &start_iter, &end_iter); get_chars (GtkTextBuffer *buffer, gint start, gint end) gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start); gtk_text_buffer_get_end_iter (buffer, &end_iter); gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end); return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE); 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); 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) modified = (undo_action->modified && !um->priv->modified_undoing_group); switch (undo_action->action_type) case GTK_SOURCE_UNDO_ACTION_DELETE: undo_action->action.delete.start, undo_action->action.delete.text, strlen (undo_action->action.delete.text)); if (undo_action->action.delete.forward) undo_action->action.delete.start); undo_action->action.delete.end); case GTK_SOURCE_UNDO_ACTION_INSERT: undo_action->action.insert.pos, undo_action->action.insert.pos + undo_action->action.insert.chars); undo_action->action.insert.pos); case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR: undo_action->action.insert_anchor.pos, undo_action->action.insert_anchor.pos + 1); #if !GTK_CHECK_VERSION(3,0,0) undo_action->action.insert_anchor.anchor->segment = NULL; /* XXX: This may be a bug in GTK+ */ /* Unknown action type. */ } while (undo_action->order_in_group > 1); gtk_text_buffer_set_modified (um->priv->document, FALSE); gtk_source_undo_manager_end_not_undoable_action_internal (um); um->priv->modified_undoing_group = FALSE; um->priv->can_redo = TRUE; g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 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], 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); if (undo_action->modified) g_return_if_fail (undo_action->order_in_group <= 1); switch (undo_action->action_type) case GTK_SOURCE_UNDO_ACTION_DELETE: undo_action->action.delete.start, undo_action->action.delete.end); undo_action->action.delete.start); case GTK_SOURCE_UNDO_ACTION_INSERT: undo_action->action.insert.pos); undo_action->action.insert.pos, undo_action->action.insert.text, undo_action->action.insert.length); case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR: undo_action->action.insert_anchor.pos); undo_action->action.insert_anchor.pos, undo_action->action.insert_anchor.anchor); /* Unknown action type */ if (um->priv->next_redo < 0) undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo); } while ((undo_action != NULL) && (undo_action->order_in_group > 1)); gtk_text_buffer_set_modified (um->priv->document, FALSE); 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); um->priv->can_undo = TRUE; g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE); gtk_source_undo_action_free (GtkSourceUndoAction *action) 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 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) g_object_unref(action->action.insert_anchor.anchor); gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um) GtkSourceUndoAction *action = l->data; if (action->order_in_group == 1) --um->priv->num_of_groups; um->priv->modified_action = NULL; gtk_source_undo_action_free (action); g_list_free (um->priv->actions); um->priv->actions = NULL; gtk_source_undo_manager_insert_text_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um) GtkSourceUndoAction undo_action; if (um->priv->running_not_undoable_actions > 0) 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; undo_action.mergeable = TRUE; undo_action.modified = FALSE; gtk_source_undo_manager_add_action (um, &undo_action); static void gtk_source_undo_manager_insert_anchor_handler (GtkTextBuffer *buffer, GtkTextChildAnchor *anchor, GtkSourceUndoManager *um) GtkSourceUndoAction undo_action; if (um->priv->running_not_undoable_actions > 0) undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR; undo_action.action.insert_anchor.pos = gtk_text_iter_get_offset (pos); undo_action.action.insert_anchor.anchor = g_object_ref (anchor); undo_action.mergeable = FALSE; undo_action.modified = FALSE; gtk_source_undo_manager_add_action (um, &undo_action); gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um) GtkSourceUndoAction undo_action; if (um->priv->running_not_undoable_actions > 0) 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 ( 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; 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; undo_action.mergeable = TRUE; undo_action.modified = FALSE; gtk_source_undo_manager_add_action (um, &undo_action); g_free (undo_action.action.delete.text); 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) um->priv->actions_in_current_group = 0; 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); 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 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) /* Nothing needs to be done */ ++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); um->priv->can_undo = TRUE; g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE); um->priv->can_redo = FALSE; g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE); gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, if (um->priv->actions == NULL) GtkSourceUndoAction *action = g_list_first (um->priv->actions)->data; if (action->order_in_group == 1) --um->priv->num_of_groups; um->priv->modified_action = NULL; gtk_source_undo_action_free (action); um->priv->actions = g_list_delete_link (um->priv->actions, if (um->priv->actions == NULL) gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um) 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 (um->priv->num_of_groups > undo_levels) GtkSourceUndoAction *undo_action; last = g_list_last (um->priv->actions); undo_action = (GtkSourceUndoAction*) last->data; if (undo_action->order_in_group == 1) --um->priv->num_of_groups; if (undo_action->modified) um->priv->modified_action = NULL; gtk_source_undo_action_free (undo_action); tmp = g_list_previous (last); um->priv->actions = g_list_delete_link (um->priv->actions, last); 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. 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) last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0); if (!last_action->mergeable) if ((!undo_action->mergeable) || (undo_action->action_type != last_action->action_type)) last_action->mergeable = 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; if (last_action->action.delete.start == undo_action->action.delete.start) #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; 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; /* 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; 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) #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; 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 if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) /* Nothing needs to be done */ /* Unknown action inside undo merge encountered */ g_return_val_if_reached (TRUE); 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; gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager *um, 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 (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); /* 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); gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um) GtkSourceUndoAction *action; g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um)); g_return_if_fail (um->priv != NULL); if (um->priv->actions == NULL) list = g_list_nth (um->priv->actions, um->priv->next_redo + 1); action = (GtkSourceUndoAction*) list->data; if (gtk_text_buffer_get_modified (buffer) == FALSE) action->mergeable = FALSE; if (um->priv->modified_action != NULL) um->priv->modified_action->modified = FALSE; um->priv->modified_action = NULL; g_return_if_fail (um->priv->running_not_undoable_actions > 0); /* 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); um->priv->modified_action = action;