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 "debug/timestamp.h"
67 #include "event.h"
69 bool SPDocument::isModified() const {
70 return rroot ? rroot->attribute("sodipodi:modified") != NULL : false;
71 }
72 void SPDocument::setModified(bool modified) {
73 if (rroot) {
74 rroot->setAttribute("sodipodi:modified", "true");
75 }
76 }
79 /*
80 * Undo & redo
81 */
82 /**
83 * Set undo sensitivity.
84 *
85 * \note
86 * Since undo sensitivity needs to be nested, setting undo sensitivity
87 * should be done like this:
88 *\verbatim
89 bool saved = sp_document_get_undo_sensitive(document);
90 sp_document_set_undo_sensitive(document, false);
91 ... do stuff ...
92 sp_document_set_undo_sensitive(document, saved); \endverbatim
93 */
94 void
95 sp_document_set_undo_sensitive (SPDocument *doc, bool sensitive)
96 {
97 g_assert (doc != NULL);
98 g_assert (doc->priv != NULL);
100 if ( sensitive == doc->priv->sensitive )
101 return;
103 if (sensitive) {
104 sp_repr_begin_transaction (doc->rdoc);
105 } else {
106 doc->priv->partial = sp_repr_coalesce_log (
107 doc->priv->partial,
108 sp_repr_commit_undoable (doc->rdoc)
109 );
110 }
112 doc->priv->sensitive = sensitive;
113 }
115 /*TODO: Throughout the inkscape code tree set/get_undo_sensitive are used for
116 * as is shown above. Perhaps it makes sense to create new functions,
117 * undo_ignore, and undo_recall to replace the start and end parts of the above.
118 * The main complexity with this is that they have to nest, so you have to store
119 * the saved bools in a stack. Perhaps this is why the above solution is better.
120 */
122 bool sp_document_get_undo_sensitive(SPDocument const *document) {
123 g_assert(document != NULL);
124 g_assert(document->priv != NULL);
126 return document->priv->sensitive;
127 }
129 void
130 sp_document_done (SPDocument *doc, const unsigned int event_type, Glib::ustring event_description)
131 {
132 sp_document_maybe_done (doc, NULL, event_type, event_description);
133 }
135 void
136 sp_document_reset_key (Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, GtkObject *base)
137 {
138 SPDocument *doc = (SPDocument *) base;
139 doc->actionkey = NULL;
140 }
142 namespace {
144 using Inkscape::Debug::Event;
145 using Inkscape::Debug::SimpleEvent;
146 using Inkscape::Util::share_static_string;
147 using Inkscape::Debug::timestamp;
148 using Inkscape::Verb;
150 typedef SimpleEvent<Event::INTERACTION> InteractionEvent;
152 class CommitEvent : public InteractionEvent {
153 public:
155 CommitEvent(SPDocument *doc, const gchar *key, const unsigned int type)
156 : InteractionEvent(share_static_string("commit"))
157 {
158 _addProperty(share_static_string("timestamp"), timestamp());
159 gchar *serial = g_strdup_printf("%lu", doc->serial());
160 _addProperty(share_static_string("document"), serial);
161 g_free(serial);
162 Verb *verb = Verb::get(type);
163 if (verb) {
164 _addProperty(share_static_string("context"), verb->get_id());
165 }
166 if (key) {
167 _addProperty(share_static_string("merge-key"), key);
168 }
169 }
170 };
172 }
174 void
175 sp_document_maybe_done (SPDocument *doc, const gchar *key, const unsigned int event_type,
176 Glib::ustring event_description)
177 {
178 g_assert (doc != NULL);
179 g_assert (doc->priv != NULL);
180 g_assert (doc->priv->sensitive);
182 Inkscape::Debug::EventTracker<CommitEvent> tracker(doc, key, event_type);
184 doc->collectOrphans();
186 sp_document_ensure_up_to_date (doc);
188 sp_document_clear_redo (doc);
190 Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
191 doc->priv->partial = NULL;
193 if (!log) {
194 sp_repr_begin_transaction (doc->rdoc);
195 return;
196 }
198 if (key && doc->actionkey && !strcmp (key, doc->actionkey) && doc->priv->undo) {
199 ((Inkscape::Event *)doc->priv->undo->data)->event =
200 sp_repr_coalesce_log (((Inkscape::Event *)doc->priv->undo->data)->event, log);
201 } else {
202 Inkscape::Event *event = new Inkscape::Event(log, event_type, event_description);
203 doc->priv->undo = g_slist_prepend (doc->priv->undo, event);
204 doc->priv->history_size++;
205 doc->priv->undoStackObservers.notifyUndoCommitEvent(event);
206 }
208 doc->actionkey = key;
210 doc->virgin = FALSE;
211 doc->setModified();
213 sp_repr_begin_transaction (doc->rdoc);
215 doc->priv->commit_signal.emit();
216 }
218 void
219 sp_document_cancel (SPDocument *doc)
220 {
221 g_assert (doc != NULL);
222 g_assert (doc->priv != NULL);
223 g_assert (doc->priv->sensitive);
225 sp_repr_rollback (doc->rdoc);
227 if (doc->priv->partial) {
228 sp_repr_undo_log (doc->priv->partial);
229 sp_repr_free_log (doc->priv->partial);
230 doc->priv->partial = NULL;
231 }
233 sp_repr_begin_transaction (doc->rdoc);
234 }
236 static void finish_incomplete_transaction(SPDocument &doc) {
237 SPDocumentPrivate &priv=*doc.priv;
238 Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
239 if (log || priv.partial) {
240 g_warning ("Incomplete undo transaction:");
241 priv.partial = sp_repr_coalesce_log(priv.partial, log);
242 sp_repr_debug_print_log(priv.partial);
243 Inkscape::Event *event = new Inkscape::Event(priv.partial);
244 priv.undo = g_slist_prepend(priv.undo, event);
245 priv.undoStackObservers.notifyUndoCommitEvent(event);
246 priv.partial = NULL;
247 }
248 }
250 gboolean
251 sp_document_undo (SPDocument *doc)
252 {
253 using Inkscape::Debug::EventTracker;
254 using Inkscape::Debug::SimpleEvent;
256 gboolean ret;
258 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
260 g_assert (doc != NULL);
261 g_assert (doc->priv != NULL);
262 g_assert (doc->priv->sensitive);
264 doc->priv->sensitive = FALSE;
265 doc->priv->seeking = true;
267 doc->actionkey = NULL;
269 finish_incomplete_transaction(*doc);
271 if (doc->priv->undo) {
272 Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
273 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
274 sp_repr_undo_log (log->event);
275 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
277 doc->setModified();
278 doc->priv->undoStackObservers.notifyUndoEvent(log);
280 ret = TRUE;
281 } else {
282 ret = FALSE;
283 }
285 sp_repr_begin_transaction (doc->rdoc);
287 doc->priv->sensitive = TRUE;
288 doc->priv->seeking = false;
290 if (ret)
291 inkscape_external_change();
293 return ret;
294 }
296 gboolean
297 sp_document_redo (SPDocument *doc)
298 {
299 using Inkscape::Debug::EventTracker;
300 using Inkscape::Debug::SimpleEvent;
302 gboolean ret;
304 EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
306 g_assert (doc != NULL);
307 g_assert (doc->priv != NULL);
308 g_assert (doc->priv->sensitive);
310 doc->priv->sensitive = FALSE;
311 doc->priv->seeking = true;
313 doc->actionkey = NULL;
315 finish_incomplete_transaction(*doc);
317 if (doc->priv->redo) {
318 Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
319 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
320 sp_repr_replay_log (log->event);
321 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
323 doc->setModified();
324 doc->priv->undoStackObservers.notifyRedoEvent(log);
326 ret = TRUE;
327 } else {
328 ret = FALSE;
329 }
331 sp_repr_begin_transaction (doc->rdoc);
333 doc->priv->sensitive = TRUE;
334 doc->priv->seeking = false;
336 if (ret)
337 inkscape_external_change();
339 return ret;
340 }
342 void
343 sp_document_clear_undo (SPDocument *doc)
344 {
345 if (doc->priv->undo)
346 doc->priv->undoStackObservers.notifyClearUndoEvent();
348 while (doc->priv->undo) {
349 GSList *current;
351 current = doc->priv->undo;
352 doc->priv->undo = current->next;
353 doc->priv->history_size--;
355 delete ((Inkscape::Event *) current->data);
356 g_slist_free_1 (current);
357 }
358 }
360 void
361 sp_document_clear_redo (SPDocument *doc)
362 {
363 if (doc->priv->redo)
364 doc->priv->undoStackObservers.notifyClearRedoEvent();
366 while (doc->priv->redo) {
367 GSList *current;
369 current = doc->priv->redo;
370 doc->priv->redo = current->next;
371 doc->priv->history_size--;
373 delete ((Inkscape::Event *) current->data);
374 g_slist_free_1 (current);
375 }
376 }
377 /*
378 Local Variables:
379 mode:c++
380 c-file-style:"stroustrup"
381 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
382 indent-tabs-mode:nil
383 fill-column:99
384 End:
385 */
386 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :