Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / document-undo.cpp
1 /** \file
2  * Undo/Redo stack implementation
3  *
4  * Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   MenTaLguY <mental@rydia.net>
7  *   Abhishek Sharma
8  *
9  * Copyright (C) 2007  MenTaLguY <mental@rydia.net>
10  * Copyright (C) 1999-2003 authors
11  * Copyright (C) 2001-2002 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  *
15  * Using the split document model gives sodipodi a very simple and clean
16  * undo implementation. Whenever mutation occurs in the XML tree,
17  * SPObject invokes one of the five corresponding handlers of its
18  * container document. This writes down a generic description of the
19  * given action, and appends it to the recent action list, kept by the
20  * document. There will be as many action records as there are mutation
21  * events, which are all kept and processed together in the undo
22  * stack. Two methods exist to indicate that the given action is completed:
23  *
24  * \verbatim
25    void sp_document_done( SPDocument *document );
26    void sp_document_maybe_done( SPDocument *document, const unsigned char *key ) \endverbatim
27  *
28  * Both move the recent action list into the undo stack and clear the
29  * list afterwards.  While the first method does an unconditional push,
30  * the second one first checks the key of the most recent stack entry. If
31  * the keys are identical, the current action list is appended to the
32  * existing stack entry, instead of pushing it onto its own.  This
33  * behaviour can be used to collect multi-step actions (like winding the
34  * Gtk spinbutton) from the UI into a single undoable step.
35  *
36  * For controls implemented by Sodipodi itself, implementing undo as a
37  * single step is usually done in a more efficent way. Most controls have
38  * the abstract model of grab, drag, release, and change user
39  * action. During the grab phase, all modifications are done to the
40  * SPObject directly - i.e. they do not change XML tree, and thus do not
41  * generate undo actions either.  Only at the release phase (normally
42  * associated with releasing the mousebutton), changes are written back
43  * to the XML tree, thus generating only a single set of undo actions.
44  * (Lauris Kaplinski)
45  */
47 #ifdef HAVE_CONFIG_H
48 # include "config.h"
49 #endif
53 #if HAVE_STRING_H
54 #endif
57 #if HAVE_STDLIB_H
58 #endif
60 #include <string>
61 #include <cstring>
62 #include "xml/repr.h"
63 #include "document-private.h"
64 #include "inkscape.h"
65 #include "document-undo.h"
66 #include "debug/event-tracker.h"
67 #include "debug/simple-event.h"
68 #include "debug/timestamp.h"
69 #include "event.h"
72 /*
73  * Undo & redo
74  */
76 void Inkscape::DocumentUndo::setUndoSensitive(SPDocument *doc, bool sensitive)
77 {
78         g_assert (doc != NULL);
79         g_assert (doc->priv != NULL);
81         if ( sensitive == doc->priv->sensitive )
82                 return;
84         if (sensitive) {
85                 sp_repr_begin_transaction (doc->rdoc);
86         } else {
87                 doc->priv->partial = sp_repr_coalesce_log (
88                         doc->priv->partial,
89                         sp_repr_commit_undoable (doc->rdoc)
90                 );
91         }
93         doc->priv->sensitive = sensitive;
94 }
96 /*TODO: Throughout the inkscape code tree set/get_undo_sensitive are used for
97  * as is shown above.  Perhaps it makes sense to create new functions,
98  * undo_ignore, and undo_recall to replace the start and end parts of the above.
99  * The main complexity with this is that they have to nest, so you have to store
100  * the saved bools in a stack.  Perhaps this is why the above solution is better.
101  */
103 bool Inkscape::DocumentUndo::getUndoSensitive(SPDocument const *document) {
104         g_assert(document != NULL);
105         g_assert(document->priv != NULL);
107         return document->priv->sensitive;
110 void Inkscape::DocumentUndo::done(SPDocument *doc, const unsigned int event_type, Glib::ustring const &event_description)
112     maybeDone(doc, NULL, event_type, event_description);
115 void Inkscape::DocumentUndo::resetKey( Inkscape::Application * /*inkscape*/, SPDesktop * /*desktop*/, GtkObject *base )
117     SPDocument *doc = reinterpret_cast<SPDocument *>(base);
118     doc->actionkey.clear();
121 namespace {
123 using Inkscape::Debug::Event;
124 using Inkscape::Debug::SimpleEvent;
125 using Inkscape::Util::share_static_string;
126 using Inkscape::Debug::timestamp;
127 using Inkscape::Verb;
129 typedef SimpleEvent<Event::INTERACTION> InteractionEvent;
131 class CommitEvent : public InteractionEvent {
132 public:
134     CommitEvent(SPDocument *doc, const gchar *key, const unsigned int type)
135     : InteractionEvent(share_static_string("commit"))
136     {
137         _addProperty(share_static_string("timestamp"), timestamp());
138         gchar *serial = g_strdup_printf("%lu", doc->serial());
139         _addProperty(share_static_string("document"), serial);
140         g_free(serial);
141         Verb *verb = Verb::get(type);
142         if (verb) {
143             _addProperty(share_static_string("context"), verb->get_id());
144         }
145         if (key) {
146             _addProperty(share_static_string("merge-key"), key);
147         }
148     }
149 };
153 void Inkscape::DocumentUndo::maybeDone(SPDocument *doc, const gchar *key, const unsigned int event_type,
154                                        Glib::ustring const &event_description)
156         g_assert (doc != NULL);
157         g_assert (doc->priv != NULL);
158         g_assert (doc->priv->sensitive);
159         if ( key && !*key ) {
160             g_warning("Blank undo key specified.");
161         }
163         Inkscape::Debug::EventTracker<CommitEvent> tracker(doc, key, event_type);
165         doc->collectOrphans();
167         doc->ensureUpToDate();
169         DocumentUndo::clearRedo(doc);
171         Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
172         doc->priv->partial = NULL;
174         if (!log) {
175                 sp_repr_begin_transaction (doc->rdoc);
176                 return;
177         }
179         if (key && !doc->actionkey.empty() && (doc->actionkey == key) && doc->priv->undo) {
180                 ((Inkscape::Event *)doc->priv->undo->data)->event =
181                     sp_repr_coalesce_log (((Inkscape::Event *)doc->priv->undo->data)->event, log);
182         } else {
183                 Inkscape::Event *event = new Inkscape::Event(log, event_type, event_description);
184                 doc->priv->undo = g_slist_prepend (doc->priv->undo, event);
185                 doc->priv->history_size++;
186                 doc->priv->undoStackObservers.notifyUndoCommitEvent(event);
187         }
189         if ( key ) {
190             doc->actionkey = key;
191         } else {
192             doc->actionkey.clear();
193         }
195         doc->virgin = FALSE;
196         doc->setModifiedSinceSave();
198         sp_repr_begin_transaction (doc->rdoc);
200   doc->priv->commit_signal.emit();
203 void Inkscape::DocumentUndo::cancel(SPDocument *doc)
205         g_assert (doc != NULL);
206         g_assert (doc->priv != NULL);
207         g_assert (doc->priv->sensitive);
209         sp_repr_rollback (doc->rdoc);
211         if (doc->priv->partial) {
212                 sp_repr_undo_log (doc->priv->partial);
213                 sp_repr_free_log (doc->priv->partial);
214                 doc->priv->partial = NULL;
215         }
217         sp_repr_begin_transaction (doc->rdoc);
220 static void finish_incomplete_transaction(SPDocument &doc) {
221         SPDocumentPrivate &priv=*doc.priv;
222         Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
223         if (log || priv.partial) {
224                 g_warning ("Incomplete undo transaction:");
225                 priv.partial = sp_repr_coalesce_log(priv.partial, log);
226                 sp_repr_debug_print_log(priv.partial);
227                 Inkscape::Event *event = new Inkscape::Event(priv.partial);
228                 priv.undo = g_slist_prepend(priv.undo, event);
229                 priv.undoStackObservers.notifyUndoCommitEvent(event);
230                 priv.partial = NULL;
231         }
234 gboolean Inkscape::DocumentUndo::undo(SPDocument *doc)
236         using Inkscape::Debug::EventTracker;
237         using Inkscape::Debug::SimpleEvent;
239         gboolean ret;
241         EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
243         g_assert (doc != NULL);
244         g_assert (doc->priv != NULL);
245         g_assert (doc->priv->sensitive);
247         doc->priv->sensitive = FALSE;
248         doc->priv->seeking = true;
250         doc->actionkey.clear();
252         finish_incomplete_transaction(*doc);
254         if (doc->priv->undo) {
255                 Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
256                 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
257                 sp_repr_undo_log (log->event);
258                 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
260                 doc->setModifiedSinceSave();
261                 doc->priv->undoStackObservers.notifyUndoEvent(log);
263                 ret = TRUE;
264         } else {
265                 ret = FALSE;
266         }
268         sp_repr_begin_transaction (doc->rdoc);
270         doc->priv->sensitive = TRUE;
271         doc->priv->seeking = false;
273         if (ret)
274                 inkscape_external_change();
276         return ret;
279 gboolean Inkscape::DocumentUndo::redo(SPDocument *doc)
281         using Inkscape::Debug::EventTracker;
282         using Inkscape::Debug::SimpleEvent;
284         gboolean ret;
286         EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
288         g_assert (doc != NULL);
289         g_assert (doc->priv != NULL);
290         g_assert (doc->priv->sensitive);
292         doc->priv->sensitive = FALSE;
293         doc->priv->seeking = true;
295         doc->actionkey.clear();
297         finish_incomplete_transaction(*doc);
299         if (doc->priv->redo) {
300                 Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
301                 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
302                 sp_repr_replay_log (log->event);
303                 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
305                 doc->setModifiedSinceSave();
306                 doc->priv->undoStackObservers.notifyRedoEvent(log);
308                 ret = TRUE;
309         } else {
310                 ret = FALSE;
311         }
313         sp_repr_begin_transaction (doc->rdoc);
315         doc->priv->sensitive = TRUE;
316         doc->priv->seeking = false;
318         if (ret)
319                 inkscape_external_change();
321         return ret;
324 void Inkscape::DocumentUndo::clearUndo(SPDocument *doc)
326         if (doc->priv->undo)
327                 doc->priv->undoStackObservers.notifyClearUndoEvent();
329         while (doc->priv->undo) {
330                 GSList *current;
332                 current = doc->priv->undo;
333                 doc->priv->undo = current->next;
334                 doc->priv->history_size--;
336                 delete ((Inkscape::Event *) current->data);
337                 g_slist_free_1 (current);
338         }
341 void Inkscape::DocumentUndo::clearRedo(SPDocument *doc)
343         if (doc->priv->redo)
344                 doc->priv->undoStackObservers.notifyClearRedoEvent();
346         while (doc->priv->redo) {
347                 GSList *current;
349                 current = doc->priv->redo;
350                 doc->priv->redo = current->next;
351                 doc->priv->history_size--;
353                 delete ((Inkscape::Event *) current->data);
354                 g_slist_free_1 (current);
355         }
357 /*
358   Local Variables:
359   mode:c++
360   c-file-style:"stroustrup"
361   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
362   indent-tabs-mode:nil
363   fill-column:99
364   End:
365 */
366 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :