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) 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 "xml/repr.h"
61 #include "document-private.h"
62 #include "inkscape.h"
63 #include "debug/event-tracker.h"
64 #include "debug/simple-event.h"
67 /*
68 * Undo & redo
69 */
70 /**
71 * Set undo sensitivity.
72 *
73 * \note
74 * Since undo sensitivity needs to be nested, setting undo sensitivity
75 * should be done like this:
76 *\verbatim
77 gboolean saved = sp_document_get_undo_sensitive(document);
78 sp_document_set_undo_sensitive(document, FALSE);
79 ... do stuff ...
80 sp_document_set_undo_sensitive(document, saved); \endverbatim
81 */
82 void
83 sp_document_set_undo_sensitive (SPDocument *doc, gboolean sensitive)
84 {
85 g_assert (doc != NULL);
86 g_assert (doc->priv != NULL);
88 if ( !(sensitive) == !(doc->priv->sensitive) )
89 return;
91 if (sensitive) {
92 sp_repr_begin_transaction (doc->rdoc);
93 } else {
94 doc->priv->partial = sp_repr_coalesce_log (
95 doc->priv->partial,
96 sp_repr_commit_undoable (doc->rdoc)
97 );
98 }
100 doc->priv->sensitive = !!sensitive;
101 }
103 gboolean sp_document_get_undo_sensitive(SPDocument const *document) {
104 g_assert(document != NULL);
105 g_assert(document->priv != NULL);
107 return document->priv->sensitive;
108 }
110 void
111 sp_document_done (SPDocument *doc)
112 {
113 sp_document_maybe_done (doc, NULL);
114 }
116 void
117 sp_document_reset_key (Inkscape::Application *inkscape, SPDesktop *desktop, GtkObject *base)
118 {
119 SPDocument *doc = (SPDocument *) base;
120 doc->actionkey = NULL;
121 }
123 void
124 sp_document_maybe_done (SPDocument *doc, const gchar *key)
125 {
126 g_assert (doc != NULL);
127 g_assert (doc->priv != NULL);
128 g_assert (doc->priv->sensitive);
130 doc->collectOrphans();
132 sp_document_ensure_up_to_date (doc);
134 sp_document_clear_redo (doc);
136 Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
137 doc->priv->partial = NULL;
139 if (!log) {
140 sp_repr_begin_transaction (doc->rdoc);
141 return;
142 }
144 if (key && doc->actionkey && !strcmp (key, doc->actionkey) && doc->priv->undo) {
145 doc->priv->undo->data = sp_repr_coalesce_log ((Inkscape::XML::Event *)doc->priv->undo->data, log);
146 } else {
147 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
148 doc->priv->history_size++;
149 doc->priv->undoStackObservers.notifyUndoCommitEvent(log);
150 }
152 doc->actionkey = key;
154 doc->virgin = FALSE;
155 if (!doc->rroot->attribute("sodipodi:modified")) {
156 doc->rroot->setAttribute("sodipodi:modified", "true");
157 }
159 sp_repr_begin_transaction (doc->rdoc);
160 }
162 void
163 sp_document_cancel (SPDocument *doc)
164 {
165 g_assert (doc != NULL);
166 g_assert (doc->priv != NULL);
167 g_assert (doc->priv->sensitive);
169 sp_repr_rollback (doc->rdoc);
171 if (doc->priv->partial) {
172 sp_repr_undo_log (doc->priv->partial);
173 sp_repr_free_log (doc->priv->partial);
174 doc->priv->partial = NULL;
175 }
177 sp_repr_begin_transaction (doc->rdoc);
178 }
180 namespace {
182 void finish_incomplete_transaction(SPDocument &doc) {
183 SPDocumentPrivate &priv=*doc.priv;
184 Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
185 if (log || priv.partial) {
186 g_warning ("Incomplete undo transaction:");
187 priv.partial = sp_repr_coalesce_log(priv.partial, log);
188 sp_repr_debug_print_log(priv.partial);
189 priv.undo = g_slist_prepend(priv.undo, priv.partial);
190 priv.partial = NULL;
191 }
192 }
194 }
196 gboolean
197 sp_document_undo (SPDocument *doc)
198 {
199 using Inkscape::Debug::EventTracker;
200 using Inkscape::Debug::SimpleEvent;
202 gboolean ret;
204 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
206 g_assert (doc != NULL);
207 g_assert (doc->priv != NULL);
208 g_assert (doc->priv->sensitive);
210 doc->priv->sensitive = FALSE;
212 doc->actionkey = NULL;
214 finish_incomplete_transaction(*doc);
216 if (doc->priv->undo) {
217 Inkscape::XML::Event *log=(Inkscape::XML::Event *)doc->priv->undo->data;
218 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
219 sp_repr_undo_log (log);
220 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
222 doc->rroot->setAttribute("sodipodi:modified", "true");
223 doc->priv->undoStackObservers.notifyUndoEvent(log);
225 ret = TRUE;
226 } else {
227 ret = FALSE;
228 }
230 sp_repr_begin_transaction (doc->rdoc);
232 doc->priv->sensitive = TRUE;
234 if (ret)
235 inkscape_external_change();
237 return ret;
238 }
240 gboolean
241 sp_document_redo (SPDocument *doc)
242 {
243 using Inkscape::Debug::EventTracker;
244 using Inkscape::Debug::SimpleEvent;
246 gboolean ret;
248 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
250 g_assert (doc != NULL);
251 g_assert (doc->priv != NULL);
252 g_assert (doc->priv->sensitive);
254 doc->priv->sensitive = FALSE;
256 doc->actionkey = NULL;
258 finish_incomplete_transaction(*doc);
260 if (doc->priv->redo) {
261 Inkscape::XML::Event *log=(Inkscape::XML::Event *)doc->priv->redo->data;
262 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
263 sp_repr_replay_log (log);
264 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
266 doc->rroot->setAttribute("sodipodi:modified", "true");
267 doc->priv->undoStackObservers.notifyRedoEvent(log);
269 ret = TRUE;
270 } else {
271 ret = FALSE;
272 }
274 sp_repr_begin_transaction (doc->rdoc);
276 doc->priv->sensitive = TRUE;
278 if (ret)
279 inkscape_external_change();
281 return ret;
282 }
284 void
285 sp_document_clear_undo (SPDocument *doc)
286 {
287 while (doc->priv->undo) {
288 GSList *current;
290 current = doc->priv->undo;
291 doc->priv->undo = current->next;
292 doc->priv->history_size--;
294 sp_repr_free_log ((Inkscape::XML::Event *)current->data);
295 g_slist_free_1 (current);
296 }
297 }
299 void
300 sp_document_clear_redo (SPDocument *doc)
301 {
302 while (doc->priv->redo) {
303 GSList *current;
305 current = doc->priv->redo;
306 doc->priv->redo = current->next;
307 doc->priv->history_size--;
309 sp_repr_free_log ((Inkscape::XML::Event *)current->data);
310 g_slist_free_1 (current);
311 }
312 }
313 /*
314 Local Variables:
315 mode:c++
316 c-file-style:"stroustrup"
317 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
318 indent-tabs-mode:nil
319 fill-column:99
320 End:
321 */
322 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :