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;
103 }
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;
117 }
119 void
120 sp_document_done (SPDocument *doc, const unsigned int event_type, Glib::ustring event_description)
121 {
122 sp_document_maybe_done (doc, NULL, event_type, event_description);
123 }
125 void
126 sp_document_reset_key (Inkscape::Application *inkscape, SPDesktop *desktop, GtkObject *base)
127 {
128 SPDocument *doc = (SPDocument *) base;
129 doc->actionkey = NULL;
130 }
132 void
133 sp_document_maybe_done (SPDocument *doc, const gchar *key, const unsigned int event_type,
134 Glib::ustring event_description)
135 {
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();
175 }
177 void
178 sp_document_cancel (SPDocument *doc)
179 {
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);
193 }
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 Inkscape::Event *event = new Inkscape::Event(priv.partial);
205 priv.undo = g_slist_prepend(priv.undo, event);
206 priv.undoStackObservers.notifyUndoCommitEvent(event);
207 priv.partial = NULL;
208 }
209 }
211 }
213 gboolean
214 sp_document_undo (SPDocument *doc)
215 {
216 using Inkscape::Debug::EventTracker;
217 using Inkscape::Debug::SimpleEvent;
219 gboolean ret;
221 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
223 g_assert (doc != NULL);
224 g_assert (doc->priv != NULL);
225 g_assert (doc->priv->sensitive);
227 doc->priv->sensitive = FALSE;
228 doc->priv->seeking = true;
230 doc->actionkey = NULL;
232 finish_incomplete_transaction(*doc);
234 if (doc->priv->undo) {
235 Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
236 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
237 sp_repr_undo_log (log->event);
238 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
240 doc->rroot->setAttribute("sodipodi:modified", "true");
241 doc->priv->undoStackObservers.notifyUndoEvent(log);
243 ret = TRUE;
244 } else {
245 ret = FALSE;
246 }
248 sp_repr_begin_transaction (doc->rdoc);
250 doc->priv->sensitive = TRUE;
251 doc->priv->seeking = false;
253 if (ret)
254 inkscape_external_change();
256 return ret;
257 }
259 gboolean
260 sp_document_redo (SPDocument *doc)
261 {
262 using Inkscape::Debug::EventTracker;
263 using Inkscape::Debug::SimpleEvent;
265 gboolean ret;
267 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
269 g_assert (doc != NULL);
270 g_assert (doc->priv != NULL);
271 g_assert (doc->priv->sensitive);
273 doc->priv->sensitive = FALSE;
274 doc->priv->seeking = true;
276 doc->actionkey = NULL;
278 finish_incomplete_transaction(*doc);
280 if (doc->priv->redo) {
281 Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
282 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
283 sp_repr_replay_log (log->event);
284 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
286 doc->rroot->setAttribute("sodipodi:modified", "true");
287 doc->priv->undoStackObservers.notifyRedoEvent(log);
289 ret = TRUE;
290 } else {
291 ret = FALSE;
292 }
294 sp_repr_begin_transaction (doc->rdoc);
296 doc->priv->sensitive = TRUE;
297 doc->priv->seeking = false;
299 if (ret)
300 inkscape_external_change();
302 return ret;
303 }
305 void
306 sp_document_clear_undo (SPDocument *doc)
307 {
308 if (doc->priv->undo)
309 doc->priv->undoStackObservers.notifyClearUndoEvent();
311 while (doc->priv->undo) {
312 GSList *current;
314 current = doc->priv->undo;
315 doc->priv->undo = current->next;
316 doc->priv->history_size--;
318 delete ((Inkscape::Event *) current->data);
319 g_slist_free_1 (current);
320 }
321 }
323 void
324 sp_document_clear_redo (SPDocument *doc)
325 {
326 if (doc->priv->redo)
327 doc->priv->undoStackObservers.notifyClearRedoEvent();
329 while (doc->priv->redo) {
330 GSList *current;
332 current = doc->priv->redo;
333 doc->priv->redo = current->next;
334 doc->priv->history_size--;
336 delete ((Inkscape::Event *) current->data);
337 g_slist_free_1 (current);
338 }
339 }
340 /*
341 Local Variables:
342 mode:c++
343 c-file-style:"stroustrup"
344 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
345 indent-tabs-mode:nil
346 fill-column:99
347 End:
348 */
349 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :