Code

better way to deal with undo+id collisions
[inkscape.git] / src / document-undo.cpp
1 #define __SP_DOCUMENT_UNDO_C__
3 /** \file
4  * Undo/Redo stack implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   MenTaLguY <mental@rydia.net>
9  *
10  * Copyright (C) 2007  MenTaLguY <mental@rydia.net>
11  * Copyright (C) 1999-2003 authors
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  *
14  * Released under GNU GPL, read the file 'COPYING' for more information
15  *
16  * Using the split document model gives sodipodi a very simple and clean
17  * undo implementation. Whenever mutation occurs in the XML tree,
18  * SPObject invokes one of the five corresponding handlers of its
19  * container document. This writes down a generic description of the
20  * given action, and appends it to the recent action list, kept by the
21  * document. There will be as many action records as there are mutation
22  * events, which are all kept and processed together in the undo
23  * stack. Two methods exist to indicate that the given action is completed:
24  *
25  * \verbatim
26    void sp_document_done (SPDocument *document);
27    void sp_document_maybe_done (SPDocument *document, const unsigned char *key) \endverbatim
28  *
29  * Both move the recent action list into the undo stack and clear the
30  * list afterwards.  While the first method does an unconditional push,
31  * the second one first checks the key of the most recent stack entry. If
32  * the keys are identical, the current action list is appended to the
33  * existing stack entry, instead of pushing it onto its own.  This
34  * behaviour can be used to collect multi-step actions (like winding the
35  * Gtk spinbutton) from the UI into a single undoable step.
36  *
37  * For controls implemented by Sodipodi itself, implementing undo as a
38  * single step is usually done in a more efficent way. Most controls have
39  * the abstract model of grab, drag, release, and change user
40  * action. During the grab phase, all modifications are done to the
41  * SPObject directly - i.e. they do not change XML tree, and thus do not
42  * generate undo actions either.  Only at the release phase (normally
43  * associated with releasing the mousebutton), changes are written back
44  * to the XML tree, thus generating only a single set of undo actions.
45  * (Lauris Kaplinski)
46  */
48 #ifdef HAVE_CONFIG_H
49 # include "config.h"
50 #endif
54 #if HAVE_STRING_H
55 #endif
58 #if HAVE_STDLIB_H
59 #endif
61 #include "xml/repr.h"
62 #include "document-private.h"
63 #include "inkscape.h"
64 #include "debug/event-tracker.h"
65 #include "debug/simple-event.h"
66 #include "event.h"
69 /*
70  * Undo & redo
71  */
72 /**
73  * Set undo sensitivity.
74  *
75  * \note
76  *   Since undo sensitivity needs to be nested, setting undo sensitivity
77  *   should be done like this:
78  *\verbatim
79         bool saved = sp_document_get_undo_sensitive(document);
80         sp_document_set_undo_sensitive(document, false);
81         ... do stuff ...
82         sp_document_set_undo_sensitive(document, saved);  \endverbatim
83  */
84 void
85 sp_document_set_undo_sensitive (SPDocument *doc, bool sensitive)
86 {
87         g_assert (doc != NULL);
88         g_assert (doc->priv != NULL);
90         if ( sensitive == doc->priv->sensitive )
91                 return;
93         if (sensitive) {
94                 sp_repr_begin_transaction (doc->rdoc);
95         } else {
96                 doc->priv->partial = sp_repr_coalesce_log (
97                         doc->priv->partial,
98                         sp_repr_commit_undoable (doc->rdoc)
99                 );
100         }
102         doc->priv->sensitive = sensitive;
105 /*TODO: Throughout the inkscape code tree set/get_undo_sensitive are used for
106  * as is shown above.  Perhaps it makes sense to create new functions,
107  * undo_ignore, and undo_recall to replace the start and end parts of the above.
108  * The main complexity with this is that they have to nest, so you have to store
109  * the saved bools in a stack.  Perhaps this is why the above solution is better.
110  */
112 bool sp_document_get_undo_sensitive(SPDocument const *document) {
113         g_assert(document != NULL);
114         g_assert(document->priv != NULL);
116         return document->priv->sensitive;
119 void
120 sp_document_done (SPDocument *doc, const unsigned int event_type, Glib::ustring event_description)
122         sp_document_maybe_done (doc, NULL, event_type, event_description);
125 void
126 sp_document_reset_key (Inkscape::Application *inkscape, SPDesktop *desktop, GtkObject *base)
128         SPDocument *doc = (SPDocument *) base;
129         doc->actionkey = NULL;
132 void
133 sp_document_maybe_done (SPDocument *doc, const gchar *key, const unsigned int event_type,
134                         Glib::ustring event_description)
136         g_assert (doc != NULL);
137         g_assert (doc->priv != NULL);
138         g_assert (doc->priv->sensitive);
140         doc->collectOrphans();
142         sp_document_ensure_up_to_date (doc);
144         sp_document_clear_redo (doc);
146         Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
147         doc->priv->partial = NULL;
149         if (!log) {
150                 sp_repr_begin_transaction (doc->rdoc);
151                 return;
152         }
154         if (key && doc->actionkey && !strcmp (key, doc->actionkey) && doc->priv->undo) {
155                 doc->priv->undo->data = 
156                     new Inkscape::Event(sp_repr_coalesce_log (((Inkscape::Event *)
157                                                                doc->priv->undo->data)->event, log));
158         } else {
159                 Inkscape::Event *event = new Inkscape::Event(log, event_type, event_description);
160                 doc->priv->undo = g_slist_prepend (doc->priv->undo, event);
161                 doc->priv->history_size++;
162                 doc->priv->undoStackObservers.notifyUndoCommitEvent(event);
163         }
165         doc->actionkey = key;
167         doc->virgin = FALSE;
168         if (!doc->rroot->attribute("sodipodi:modified")) {
169                 doc->rroot->setAttribute("sodipodi:modified", "true");
170         }
172         sp_repr_begin_transaction (doc->rdoc);
174   doc->priv->commit_signal.emit();
177 void
178 sp_document_cancel (SPDocument *doc)
180         g_assert (doc != NULL);
181         g_assert (doc->priv != NULL);
182         g_assert (doc->priv->sensitive);
184         sp_repr_rollback (doc->rdoc);
186         if (doc->priv->partial) {
187                 sp_repr_undo_log (doc->priv->partial);
188                 sp_repr_free_log (doc->priv->partial);
189                 doc->priv->partial = NULL;
190         }
192         sp_repr_begin_transaction (doc->rdoc);
195 namespace {
197 void finish_incomplete_transaction(SPDocument &doc) {
198         SPDocumentPrivate &priv=*doc.priv;
199         Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
200         if (log || priv.partial) {
201                 g_warning ("Incomplete undo transaction:");
202                 priv.partial = sp_repr_coalesce_log(priv.partial, log);
203                 sp_repr_debug_print_log(priv.partial);
204                 priv.undo = g_slist_prepend(priv.undo, priv.partial);
205                 priv.partial = NULL;
206         }
211 gboolean
212 sp_document_undo (SPDocument *doc)
214         using Inkscape::Debug::EventTracker;
215         using Inkscape::Debug::SimpleEvent;
217         gboolean ret;
219         EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
221         g_assert (doc != NULL);
222         g_assert (doc->priv != NULL);
223         g_assert (doc->priv->sensitive);
225         doc->priv->sensitive = FALSE;
226         doc->priv->seeking = true;
228         doc->actionkey = NULL;
230         finish_incomplete_transaction(*doc);
232         if (doc->priv->undo) {
233                 Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
234                 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
235                 sp_repr_undo_log (log->event);
236                 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
238                 doc->rroot->setAttribute("sodipodi:modified", "true");
239                 doc->priv->undoStackObservers.notifyUndoEvent(log);
241                 ret = TRUE;
242         } else {
243                 ret = FALSE;
244         }
246         sp_repr_begin_transaction (doc->rdoc);
248         doc->priv->sensitive = TRUE;
249         doc->priv->seeking = false;
251         if (ret)
252                 inkscape_external_change();
254         return ret;
257 gboolean
258 sp_document_redo (SPDocument *doc)
260         using Inkscape::Debug::EventTracker;
261         using Inkscape::Debug::SimpleEvent;
263         gboolean ret;
265         EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
267         g_assert (doc != NULL);
268         g_assert (doc->priv != NULL);
269         g_assert (doc->priv->sensitive);
271         doc->priv->sensitive = FALSE;
272         doc->priv->seeking = true;
274         doc->actionkey = NULL;
276         finish_incomplete_transaction(*doc);
278         if (doc->priv->redo) {
279                 Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
280                 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
281                 sp_repr_replay_log (log->event);
282                 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
284                 doc->rroot->setAttribute("sodipodi:modified", "true");
285                 doc->priv->undoStackObservers.notifyRedoEvent(log);
287                 ret = TRUE;
288         } else {
289                 ret = FALSE;
290         }
292         sp_repr_begin_transaction (doc->rdoc);
294         doc->priv->sensitive = TRUE;
295         doc->priv->seeking = false;
297         if (ret)
298                 inkscape_external_change();
300         return ret;
303 void
304 sp_document_clear_undo (SPDocument *doc)
306         while (doc->priv->undo) {
307                 GSList *current;
309                 current = doc->priv->undo;
310                 doc->priv->undo = current->next;
311                 doc->priv->history_size--;
313                 delete ((Inkscape::Event *) current->data);
314                 g_slist_free_1 (current);
315         }
318 void
319 sp_document_clear_redo (SPDocument *doc)
321         while (doc->priv->redo) {
322                 GSList *current;
324                 current = doc->priv->redo;
325                 doc->priv->redo = current->next;
326                 doc->priv->history_size--;
328                 delete ((Inkscape::Event *) current->data);
329                 g_slist_free_1 (current);
330         }
332 /*
333   Local Variables:
334   mode:c++
335   c-file-style:"stroustrup"
336   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
337   indent-tabs-mode:nil
338   fill-column:99
339   End:
340 */
341 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :