/* dia-undo.c * Copyright (C) 2003 Arjan Molenaar * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "dia-undo.h" #include "dia-undo-manager.h" #define PRIV(undo) ((DiaUndoPriv*) ((DiaUndo*) undo)->_priv) typedef struct _DiaUndoPriv DiaUndoPriv; typedef struct _DiaTrandaction DiaTransaction; struct _DiaUndoPriv { gboolean in_undo; guint stack_depth; GSList *undo_stack; GSList *redo_stack; DiaTransaction *current_transaction; }; struct _DiaTrandaction { GList *entries; }; static DiaTransaction* dia_transaction_new () { DiaTransaction *t = g_new (DiaTransaction, 1); t->entries = NULL; return t; } static void dia_transaction_destroy (DiaTransaction *t) { GList *l; g_return_if_fail (t != NULL); for (l = t->entries; l != NULL; l = g_list_next (l)) dia_undo_action_destroy (l->data); g_list_free (t->entries); t->entries = NULL; } static void dia_transaction_add (DiaTransaction *t, DiaUndoAction *entry) { g_return_if_fail (t != NULL); g_return_if_fail (entry != NULL); t->entries = g_list_prepend (t->entries, entry); } /** * dia_transaction_undo: * @t: * * Apply the entries in a transaction. Last entry first. **/ static void dia_transaction_undo (DiaTransaction *t) { GList *l; g_return_if_fail (t != NULL); for (l = t->entries; l != NULL; l = g_list_next (l)) dia_undo_action_undo (l->data); } /** * dia_transaction_redo: * @t: * * Redo acrions. The order is the opposite of the undo action. **/ static void dia_transaction_redo (DiaTransaction *t) { GList *l; g_return_if_fail (t != NULL); for (l = g_list_last (t->entries); l != NULL; l = g_list_previous (l)) dia_undo_action_redo (l->data); } static void dia_undo_class_init (DiaUndoClass *klass); static void dia_undo_undo_manager_init (DiaUndoManagerIface *undo_manager); static void dia_undo_init (DiaUndo *item); static void dia_undo_dispose (GObject *object); static void dia_undo_begin_transaction (DiaUndoManager *undo_manager); static void dia_undo_commit_transaction (DiaUndoManager *undo_manager); static void dia_undo_discard_transaction (DiaUndoManager *undo_manager); static void dia_undo_undo_transaction (DiaUndoManager *undo_manager); static void dia_undo_redo_transaction (DiaUndoManager *undo_manager); static gboolean dia_undo_in_transaction (DiaUndoManager *undo_manager); static gboolean dia_undo_can_undo (DiaUndoManager *undo_manager); static gboolean dia_undo_can_redo (DiaUndoManager *undo_manager); static void dia_undo_add_undo_action (DiaUndoManager *undo_manager, DiaUndoAction *entry); static GObjectClass *parent_class = NULL; GType dia_undo_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (DiaUndoClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) dia_undo_class_init, (GClassFinalizeFunc) NULL, (gconstpointer) NULL, /* class_data */ sizeof (DiaUndo), (guint16) 0, /* n_preallocs */ (GInstanceInitFunc) dia_undo_init, }; static const GInterfaceInfo undo_manager_info = { (GInterfaceInitFunc) dia_undo_undo_manager_init, NULL, NULL, }; object_type = g_type_register_static (G_TYPE_OBJECT, "DiaUndo", &object_info, 0); g_type_add_interface_static (object_type, DIA_TYPE_UNDO_MANAGER, &undo_manager_info); } return object_type; } static void dia_undo_class_init (DiaUndoClass *klass) { GObjectClass *object_class; object_class = (GObjectClass*) klass; parent_class = g_type_class_peek_parent (klass); object_class->dispose = dia_undo_dispose; } static void dia_undo_undo_manager_init (DiaUndoManagerIface *undo_manager) { undo_manager->in_transaction = dia_undo_in_transaction; undo_manager->can_undo = dia_undo_can_undo; undo_manager->can_redo = dia_undo_can_redo; undo_manager->begin_transaction = dia_undo_begin_transaction; undo_manager->commit_transaction = dia_undo_commit_transaction; undo_manager->discard_transaction = dia_undo_discard_transaction; undo_manager->add_undo_action = dia_undo_add_undo_action; undo_manager->undo_transaction = dia_undo_undo_transaction; undo_manager->redo_transaction = dia_undo_redo_transaction; } static void dia_undo_init (DiaUndo *undo) { undo->_priv = g_new0 (DiaUndoPriv, 1); PRIV (undo)->stack_depth = 10; } static void dia_undo_dispose (GObject *object) { DiaUndo *undo = (DiaUndo*) object; if (PRIV (undo)) { dia_undo_clear_undo_stack (undo); dia_undo_clear_redo_stack (undo); g_free (undo->_priv); undo->_priv = NULL; } parent_class->dispose (object); } static void clip_stack_depth (GSList *stack, int depth) { if (depth < 1) return; if (g_slist_length (stack) > depth) { GSList *last = g_slist_last (stack); dia_transaction_destroy (last->data); // TODO: remove_link() or delete_link()? g_slist_remove_link (stack, last); } } /** * dia_undo_begin_transaction: * @undo_manager: * * Begin a new undo transaction. Data on the redo stack is discarded. * The comment provided is not freed (should change to GValue?). **/ static void dia_undo_begin_transaction (DiaUndoManager *undo_manager) { DiaUndo *undo; g_return_if_fail (DIA_IS_UNDO (undo_manager)); g_return_if_fail (DIA_UNDO (undo_manager)->_priv != NULL); g_return_if_fail (PRIV (undo_manager)->in_undo == FALSE); undo = DIA_UNDO (undo_manager); if (PRIV (undo)->current_transaction) { g_warning ("Already in a transaction"); return; } PRIV (undo)->current_transaction = dia_transaction_new (); //g_message (G_STRLOC": Opening new transaction %p", PRIV (undo)->current_transaction); /* As soon as we begin a new transaction, the data on the redo_stack * is no longer usable. */ dia_undo_clear_redo_stack (undo); } static void dia_undo_commit_transaction (DiaUndoManager *undo_manager) { DiaUndo *undo; g_return_if_fail (DIA_IS_UNDO (undo_manager)); g_return_if_fail (DIA_UNDO (undo_manager)->_priv != NULL); undo = DIA_UNDO (undo_manager); if (!PRIV (undo)->current_transaction) { g_warning ("No transaction to commit"); return; } /* Only save transactions that do contain data */ if (PRIV (undo)->current_transaction->entries) PRIV (undo)->undo_stack = g_slist_prepend (PRIV (undo)->undo_stack, PRIV (undo)->current_transaction); else dia_transaction_destroy (PRIV (undo)->current_transaction); clip_stack_depth (PRIV (undo)->undo_stack, PRIV (undo)->stack_depth); //g_message (G_STRLOC": Transaction commited %p", PRIV (undo)->current_transaction); PRIV (undo)->current_transaction = NULL; } static void dia_undo_discard_transaction (DiaUndoManager *undo_manager) { DiaUndo *undo; g_return_if_fail (DIA_IS_UNDO (undo_manager)); g_return_if_fail (DIA_UNDO (undo_manager)->_priv != NULL); undo = DIA_UNDO (undo_manager); if (!PRIV (undo)->current_transaction) { g_warning ("No transaction to discard"); return; } dia_transaction_destroy (PRIV (undo)->current_transaction); //g_message (G_STRLOC": Transaction discarded %p", PRIV (undo)->current_transaction); PRIV (undo)->current_transaction = NULL; } static void dia_undo_undo_transaction (DiaUndoManager *undo_manager) { DiaUndo *undo; g_return_if_fail (DIA_IS_UNDO (undo_manager)); g_return_if_fail (DIA_UNDO (undo_manager)->_priv != NULL); g_return_if_fail (PRIV (undo_manager)->in_undo == FALSE); undo = DIA_UNDO (undo_manager); /* Make sure the current transaction is properly stored: */ if (PRIV (undo)->current_transaction) dia_undo_manager_commit_transaction (undo_manager); if (PRIV (undo)->undo_stack) { DiaTransaction *last = PRIV (undo)->undo_stack->data; PRIV (undo)->in_undo = TRUE; PRIV (undo)->undo_stack = g_slist_remove (PRIV (undo)->undo_stack, last); dia_transaction_undo (last); PRIV (undo)->redo_stack = g_slist_prepend (PRIV (undo)->redo_stack, last); clip_stack_depth (PRIV (undo)->redo_stack, PRIV (undo)->stack_depth); PRIV (undo)->in_undo = FALSE; } } static void dia_undo_redo_transaction (DiaUndoManager *undo_manager) { DiaUndo *undo; g_return_if_fail (DIA_IS_UNDO (undo_manager)); g_return_if_fail (DIA_UNDO (undo_manager)->_priv != NULL); g_return_if_fail (PRIV (undo_manager)->in_undo == FALSE); g_return_if_fail (PRIV (undo_manager)->current_transaction == NULL); undo = DIA_UNDO (undo_manager); if (PRIV (undo)->redo_stack) { DiaTransaction *last = PRIV (undo)->redo_stack->data; PRIV (undo)->in_undo = TRUE; PRIV (undo)->redo_stack = g_slist_remove (PRIV (undo)->redo_stack, last); dia_transaction_redo (last); PRIV (undo)->undo_stack = g_slist_prepend (PRIV (undo)->undo_stack, last); clip_stack_depth (PRIV (undo)->undo_stack, PRIV (undo)->stack_depth); PRIV (undo)->in_undo = FALSE; } } static gboolean dia_undo_in_transaction (DiaUndoManager *undo_manager) { g_return_val_if_fail (DIA_IS_UNDO (undo_manager), FALSE); g_return_val_if_fail (DIA_UNDO (undo_manager)->_priv != NULL, FALSE); return (PRIV (undo_manager)->current_transaction != NULL); } static gboolean dia_undo_can_undo (DiaUndoManager *undo_manager) { g_return_val_if_fail (DIA_IS_UNDO (undo_manager), FALSE); g_return_val_if_fail (DIA_UNDO (undo_manager)->_priv != NULL, FALSE); return (PRIV (undo_manager)->undo_stack != NULL); } static gboolean dia_undo_can_redo (DiaUndoManager *undo_manager) { g_return_val_if_fail (DIA_IS_UNDO (undo_manager), FALSE); g_return_val_if_fail (DIA_UNDO (undo_manager)->_priv != NULL, FALSE); return (PRIV (undo_manager)->redo_stack != NULL); } static void dia_undo_add_undo_action (DiaUndoManager *undo_manager, DiaUndoAction *entry) { DiaUndo *undo; g_return_if_fail (DIA_IS_UNDO (undo_manager)); g_return_if_fail (DIA_UNDO (undo_manager)->_priv != NULL); undo = DIA_UNDO (undo_manager); if (!PRIV (undo)->current_transaction) { g_warning ("No transaction"); dia_undo_action_destroy (entry); return; } dia_transaction_add (PRIV (undo)->current_transaction, entry); } void dia_undo_clear_undo_stack (DiaUndo *undo) { GSList *l; g_return_if_fail (DIA_IS_UNDO (undo)); g_return_if_fail (DIA_UNDO (undo)->_priv != NULL); for (l = PRIV (undo)->undo_stack; l != NULL; l = g_slist_next (l)) dia_transaction_destroy (l->data); g_slist_free (PRIV (undo)->undo_stack); PRIV (undo)->undo_stack = NULL; } void dia_undo_clear_redo_stack (DiaUndo *undo) { GSList *l; g_return_if_fail (DIA_IS_UNDO (undo)); g_return_if_fail (DIA_UNDO (undo)->_priv != NULL); for (l = PRIV (undo)->redo_stack; l != NULL; l = g_slist_next (l)) dia_transaction_destroy (l->data); g_slist_free (PRIV (undo)->redo_stack); PRIV (undo)->redo_stack = NULL; } guint dia_undo_get_depth (DiaUndo *undo) { g_return_val_if_fail (DIA_IS_UNDO (undo), 0); g_return_val_if_fail (DIA_UNDO (undo)->_priv != NULL, 0); return g_slist_length (PRIV (undo)->undo_stack); } guint dia_undo_get_redo_depth (DiaUndo *undo) { g_return_val_if_fail (DIA_IS_UNDO (undo), 0); g_return_val_if_fail (DIA_UNDO (undo)->_priv != NULL, 0); return g_slist_length (PRIV (undo)->redo_stack); } void dia_undo_set_max_depth (DiaUndo *undo, guint depth) { g_return_if_fail (DIA_IS_UNDO (undo)); g_return_if_fail (DIA_UNDO (undo)->_priv != NULL); PRIV (undo)->stack_depth = depth; clip_stack_depth (PRIV (undo)->undo_stack, PRIV (undo)->stack_depth); clip_stack_depth (PRIV (undo)->redo_stack, PRIV (undo)->stack_depth); } guint dia_undo_get_max_depth (DiaUndo *undo) { g_return_val_if_fail (DIA_IS_UNDO (undo), 0); g_return_val_if_fail (DIA_UNDO (undo)->_priv != NULL, 0); return PRIV (undo)->stack_depth; }