Mercurial > pidgin.yaz
view pidgin/gtksourceundomanager.c @ 27325:ecdc58da4cdd
Clear the notifications via a signal instead of pidgin_close_notify.
If you have the detailed new mail window open when you sign off all accounts
with mail notifications, the window won't be closed, which is kinda weird.
Let's first see if this is on the right track before I go overboard.
Refs #9345. Sulabh, please test this!
author | Paul Aurich <paul@darkrain42.org> |
---|---|
date | Thu, 02 Jul 2009 04:15:21 +0000 |
parents | 584063555949 |
children | f6017a90c1b8 |
line wrap: on
line source
/* -*- 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02111-1301, USA. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <glib.h> #include <stdlib.h> #include <string.h> #include "gtksourceundomanager.h" #include "gtksourceview-marshal.h" #define DEFAULT_MAX_UNDO_LEVELS 25 typedef struct _GtkSourceUndoAction GtkSourceUndoAction; typedef struct _GtkSourceUndoInsertAction GtkSourceUndoInsertAction; typedef struct _GtkSourceUndoDeleteAction GtkSourceUndoDeleteAction; typedef struct _GtkSourceUndoInsertAnchorAction GtkSourceUndoInsertAnchorAction; typedef enum { 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 { gint pos; gchar *text; gint length; gint chars; }; struct _GtkSourceUndoDeleteAction { gint start; gint end; gchar *text; gboolean forward; }; struct _GtkSourceUndoInsertAnchorAction { gint pos; GtkTextChildAnchor *anchor; }; struct _GtkSourceUndoAction { GtkSourceUndoActionType action_type; union { GtkSourceUndoInsertAction insert; GtkSourceUndoDeleteAction delete; GtkSourceUndoInsertAnchorAction insert_anchor; } 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_insert_anchor_handler (GtkTextBuffer *buffer, GtkTextIter *pos, GtkTextChildAnchor *anchor, GtkSourceUndoManager *um); static void gtk_source_undo_manager_delete_range_handler (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end, GtkSourceUndoManager *um); static void gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um); static void gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um); static void gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um); static void gtk_source_undo_manager_add_action (GtkSourceUndoManager *um, const GtkSourceUndoAction *undo_action); static void gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager *um, gint n); static void gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um); static gboolean gtk_source_undo_manager_merge_action (GtkSourceUndoManager *um, const GtkSourceUndoAction *undo_action); static GObjectClass *parent_class = NULL; static guint undo_manager_signals [LAST_SIGNAL] = { 0 }; GType gtk_source_undo_manager_get_type (void) { static GType undo_manager_type = 0; if (undo_manager_type == 0) { static const GTypeInfo our_info = { sizeof (GtkSourceUndoManagerClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) gtk_source_undo_manager_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (GtkSourceUndoManager), 0, /* n_preallocs */ (GInstanceInitFunc) gtk_source_undo_manager_init, NULL /* value_table */ }; undo_manager_type = g_type_register_static (G_TYPE_OBJECT, "GtkSourceUndoManager", &our_info, 0); } return undo_manager_type; } static void gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = gtk_source_undo_manager_finalize; klass->can_undo = NULL; klass->can_redo = NULL; undo_manager_signals[CAN_UNDO] = g_signal_new ("can_undo", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo), NULL, NULL, gtksourceview_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); undo_manager_signals[CAN_REDO] = g_signal_new ("can_redo", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo), NULL, NULL, gtksourceview_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); } static void gtk_source_undo_manager_init (GtkSourceUndoManager *um) { um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1); um->priv->actions = NULL; um->priv->next_redo = 0; um->priv->can_undo = FALSE; um->priv->can_redo = FALSE; um->priv->running_not_undoable_actions = 0; um->priv->num_of_groups = 0; um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS; um->priv->modified_action = NULL; um->priv->modified_undoing_group = FALSE; } static void gtk_source_undo_manager_finalize (GObject *object) { GtkSourceUndoManager *um; g_return_if_fail (object != NULL); g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object)); um = GTK_SOURCE_UNDO_MANAGER (object); g_return_if_fail (um->priv != NULL); if (um->priv->actions != NULL) { gtk_source_undo_manager_free_action_list (um); } g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), G_CALLBACK (gtk_source_undo_manager_delete_range_handler), um); g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), G_CALLBACK (gtk_source_undo_manager_insert_text_handler), um); g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document), G_CALLBACK (gtk_source_undo_manager_insert_anchor_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), "insert_child_anchor", G_CALLBACK (gtk_source_undo_manager_insert_anchor_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 insert_anchor (GtkTextBuffer *buffer, gint pos, GtkTextChildAnchor *anchor) { GtkTextIter iter; gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos); gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor); } 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; case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR: delete_text ( um->priv->document, undo_action->action.insert_anchor.pos, undo_action->action.insert_anchor.pos + 1); undo_action->action.insert_anchor.anchor->segment = NULL; /* XXX: This may be a bug in GTK+ */ 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; case GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR: set_cursor ( um->priv->document, undo_action->action.insert_anchor.pos); insert_anchor ( um->priv->document, undo_action->action.insert_anchor.pos, undo_action->action.insert_anchor.anchor); 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 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) g_object_unref(action->action.insert_anchor.anchor); 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; 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_insert_anchor_handler (GtkTextBuffer *buffer, GtkTextIter *pos, GtkTextChildAnchor *anchor, GtkSourceUndoManager *um) { GtkSourceUndoAction undo_action; if (um->priv->running_not_undoable_actions > 0) return; 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); } 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 if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) { /* Nothing needs to be done */ } 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; 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; 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 if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT_ANCHOR) { /* Nothing needs to be done */ } 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; }