document-undo.cpp revision 153bb1867986d6db392e2cfa711ad6231fce8abe
493N/A/*
0N/A * Undo/Redo stack implementation
970N/A *
0N/A * Authors:
0N/A * Lauris Kaplinski <lauris@kaplinski.com>
919N/A * MenTaLguY <mental@rydia.net>
919N/A * Abhishek Sharma
919N/A *
919N/A * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
919N/A * Copyright (C) 1999-2003 authors
0N/A * Copyright (C) 2001-2002 Ximian, Inc.
919N/A *
919N/A * Released under GNU GPL, read the file 'COPYING' for more information
919N/A *
0N/A * Using the split document model gives sodipodi a very simple and clean
919N/A * undo implementation. Whenever mutation occurs in the XML tree,
919N/A * SPObject invokes one of the five corresponding handlers of its
919N/A * container document. This writes down a generic description of the
919N/A * given action, and appends it to the recent action list, kept by the
919N/A * document. There will be as many action records as there are mutation
919N/A * events, which are all kept and processed together in the undo
919N/A * stack. Two methods exist to indicate that the given action is completed:
0N/A *
0N/A * \verbatim
0N/A void sp_document_done( SPDocument *document );
0N/A void sp_document_maybe_done( SPDocument *document, const unsigned char *key ) \endverbatim
0N/A *
0N/A * Both move the recent action list into the undo stack and clear the
970N/A * list afterwards. While the first method does an unconditional push,
970N/A * the second one first checks the key of the most recent stack entry. If
970N/A * the keys are identical, the current action list is appended to the
0N/A * existing stack entry, instead of pushing it onto its own. This
0N/A * behaviour can be used to collect multi-step actions (like winding the
0N/A * Gtk spinbutton) from the UI into a single undoable step.
0N/A *
970N/A * For controls implemented by Sodipodi itself, implementing undo as a
493N/A * single step is usually done in a more efficent way. Most controls have
970N/A * the abstract model of grab, drag, release, and change user
0N/A * action. During the grab phase, all modifications are done to the
1003N/A * SPObject directly - i.e. they do not change XML tree, and thus do not
1003N/A * generate undo actions either. Only at the release phase (normally
1003N/A * associated with releasing the mousebutton), changes are written back
1003N/A * to the XML tree, thus generating only a single set of undo actions.
1003N/A * (Lauris Kaplinski)
1003N/A */
1003N/A
970N/A#ifdef HAVE_CONFIG_H
970N/A# include "config.h"
0N/A#endif
970N/A
970N/A
731N/A
970N/A#if HAVE_STRING_H
970N/A#endif
970N/A
970N/A
561N/A#if HAVE_STDLIB_H
493N/A#endif
970N/A
970N/A#include <string>
970N/A#include <cstring>
970N/A#include "xml/repr.h"
970N/A#include "document-private.h"
0N/A#include "inkscape.h"
0N/A#include "document-undo.h"
0N/A#include "debug/event-tracker.h"
970N/A#include "debug/simple-event.h"
970N/A#include "debug/timestamp.h"
970N/A#include "event.h"
0N/A
970N/A
970N/A/*
970N/A * Undo & redo
970N/A */
0N/A
970N/Avoid Inkscape::DocumentUndo::setUndoSensitive(SPDocument *doc, bool sensitive)
970N/A{
493N/A g_assert (doc != NULL);
970N/A g_assert (doc->priv != NULL);
29N/A
0N/A if ( sensitive == doc->priv->sensitive )
970N/A return;
970N/A
970N/A if (sensitive) {
970N/A sp_repr_begin_transaction (doc->rdoc);
970N/A } else {
970N/A doc->priv->partial = sp_repr_coalesce_log (
970N/A doc->priv->partial,
970N/A sp_repr_commit_undoable (doc->rdoc)
970N/A );
970N/A }
970N/A
970N/A doc->priv->sensitive = sensitive;
970N/A}
970N/A
970N/A/*TODO: Throughout the inkscape code tree set/get_undo_sensitive are used for
970N/A * as is shown above. Perhaps it makes sense to create new functions,
970N/A * undo_ignore, and undo_recall to replace the start and end parts of the above.
970N/A * The main complexity with this is that they have to nest, so you have to store
970N/A * the saved bools in a stack. Perhaps this is why the above solution is better.
493N/A */
bool Inkscape::DocumentUndo::getUndoSensitive(SPDocument const *document) {
g_assert(document != NULL);
g_assert(document->priv != NULL);
return document->priv->sensitive;
}
void Inkscape::DocumentUndo::done(SPDocument *doc, const unsigned int event_type, Glib::ustring const &event_description)
{
maybeDone(doc, NULL, event_type, event_description);
}
void Inkscape::DocumentUndo::resetKey( Inkscape::Application * /*inkscape*/, SPDesktop * /*desktop*/, GtkObject *base )
{
SPDocument *doc = reinterpret_cast<SPDocument *>(base);
doc->actionkey.clear();
}
namespace {
using Inkscape::Debug::Event;
using Inkscape::Debug::SimpleEvent;
using Inkscape::Util::share_static_string;
using Inkscape::Debug::timestamp;
using Inkscape::Verb;
typedef SimpleEvent<Event::INTERACTION> InteractionEvent;
class CommitEvent : public InteractionEvent {
public:
CommitEvent(SPDocument *doc, const gchar *key, const unsigned int type)
: InteractionEvent(share_static_string("commit"))
{
_addProperty(share_static_string("timestamp"), timestamp());
gchar *serial = g_strdup_printf("%lu", doc->serial());
_addProperty(share_static_string("document"), serial);
g_free(serial);
Verb *verb = Verb::get(type);
if (verb) {
_addProperty(share_static_string("context"), verb->get_id());
}
if (key) {
_addProperty(share_static_string("merge-key"), key);
}
}
};
}
void Inkscape::DocumentUndo::maybeDone(SPDocument *doc, const gchar *key, const unsigned int event_type,
Glib::ustring const &event_description)
{
g_assert (doc != NULL);
g_assert (doc->priv != NULL);
g_assert (doc->priv->sensitive);
if ( key && !*key ) {
g_warning("Blank undo key specified.");
}
Inkscape::Debug::EventTracker<CommitEvent> tracker(doc, key, event_type);
doc->collectOrphans();
doc->ensureUpToDate();
DocumentUndo::clearRedo(doc);
Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
doc->priv->partial = NULL;
if (!log) {
sp_repr_begin_transaction (doc->rdoc);
return;
}
if (key && !doc->actionkey.empty() && (doc->actionkey == key) && doc->priv->undo) {
((Inkscape::Event *)doc->priv->undo->data)->event =
sp_repr_coalesce_log (((Inkscape::Event *)doc->priv->undo->data)->event, log);
} else {
Inkscape::Event *event = new Inkscape::Event(log, event_type, event_description);
doc->priv->undo = g_slist_prepend (doc->priv->undo, event);
doc->priv->history_size++;
doc->priv->undoStackObservers.notifyUndoCommitEvent(event);
}
if ( key ) {
doc->actionkey = key;
} else {
doc->actionkey.clear();
}
doc->virgin = FALSE;
doc->setModifiedSinceSave();
sp_repr_begin_transaction (doc->rdoc);
doc->priv->commit_signal.emit();
}
void Inkscape::DocumentUndo::cancel(SPDocument *doc)
{
g_assert (doc != NULL);
g_assert (doc->priv != NULL);
g_assert (doc->priv->sensitive);
sp_repr_rollback (doc->rdoc);
if (doc->priv->partial) {
sp_repr_undo_log (doc->priv->partial);
sp_repr_free_log (doc->priv->partial);
doc->priv->partial = NULL;
}
sp_repr_begin_transaction (doc->rdoc);
}
static void finish_incomplete_transaction(SPDocument &doc) {
SPDocumentPrivate &priv=*doc.priv;
Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
if (log || priv.partial) {
g_warning ("Incomplete undo transaction:");
priv.partial = sp_repr_coalesce_log(priv.partial, log);
sp_repr_debug_print_log(priv.partial);
Inkscape::Event *event = new Inkscape::Event(priv.partial);
priv.undo = g_slist_prepend(priv.undo, event);
priv.undoStackObservers.notifyUndoCommitEvent(event);
priv.partial = NULL;
}
}
gboolean Inkscape::DocumentUndo::undo(SPDocument *doc)
{
using Inkscape::Debug::EventTracker;
using Inkscape::Debug::SimpleEvent;
gboolean ret;
EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
g_assert (doc != NULL);
g_assert (doc->priv != NULL);
g_assert (doc->priv->sensitive);
doc->priv->sensitive = FALSE;
doc->priv->seeking = true;
doc->actionkey.clear();
finish_incomplete_transaction(*doc);
if (doc->priv->undo) {
Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
doc->priv->undo = g_slist_remove (doc->priv->undo, log);
sp_repr_undo_log (log->event);
doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
doc->setModifiedSinceSave();
doc->priv->undoStackObservers.notifyUndoEvent(log);
ret = TRUE;
} else {
ret = FALSE;
}
sp_repr_begin_transaction (doc->rdoc);
doc->priv->sensitive = TRUE;
doc->priv->seeking = false;
if (ret)
inkscape_external_change();
return ret;
}
gboolean Inkscape::DocumentUndo::redo(SPDocument *doc)
{
using Inkscape::Debug::EventTracker;
using Inkscape::Debug::SimpleEvent;
gboolean ret;
EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
g_assert (doc != NULL);
g_assert (doc->priv != NULL);
g_assert (doc->priv->sensitive);
doc->priv->sensitive = FALSE;
doc->priv->seeking = true;
doc->actionkey.clear();
finish_incomplete_transaction(*doc);
if (doc->priv->redo) {
Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
doc->priv->redo = g_slist_remove (doc->priv->redo, log);
sp_repr_replay_log (log->event);
doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
doc->setModifiedSinceSave();
doc->priv->undoStackObservers.notifyRedoEvent(log);
ret = TRUE;
} else {
ret = FALSE;
}
sp_repr_begin_transaction (doc->rdoc);
doc->priv->sensitive = TRUE;
doc->priv->seeking = false;
if (ret)
inkscape_external_change();
return ret;
}
void Inkscape::DocumentUndo::clearUndo(SPDocument *doc)
{
if (doc->priv->undo)
doc->priv->undoStackObservers.notifyClearUndoEvent();
while (doc->priv->undo) {
GSList *current;
current = doc->priv->undo;
doc->priv->undo = current->next;
doc->priv->history_size--;
delete ((Inkscape::Event *) current->data);
g_slist_free_1 (current);
}
}
void Inkscape::DocumentUndo::clearRedo(SPDocument *doc)
{
if (doc->priv->redo)
doc->priv->undoStackObservers.notifyClearRedoEvent();
while (doc->priv->redo) {
GSList *current;
current = doc->priv->redo;
doc->priv->redo = current->next;
doc->priv->history_size--;
delete ((Inkscape::Event *) current->data);
g_slist_free_1 (current);
}
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :