Code

Pot and Dutch translation update
[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 <string>
62 #include <cstring>
63 #include "xml/repr.h"
64 #include "document-private.h"
65 #include "inkscape.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  */
75 /**
76  * Set undo sensitivity.
77  *
78  * \note
79  *   Since undo sensitivity needs to be nested, setting undo sensitivity
80  *   should be done like this:
81  *\verbatim
82         bool saved = sp_document_get_undo_sensitive(document);
83         sp_document_set_undo_sensitive(document, false);
84         ... do stuff ...
85         sp_document_set_undo_sensitive(document, saved);  \endverbatim
86  */
87 void
88 sp_document_set_undo_sensitive (SPDocument *doc, bool sensitive)
89 {
90         g_assert (doc != NULL);
91         g_assert (doc->priv != NULL);
93         if ( sensitive == doc->priv->sensitive )
94                 return;
96         if (sensitive) {
97                 sp_repr_begin_transaction (doc->rdoc);
98         } else {
99                 doc->priv->partial = sp_repr_coalesce_log (
100                         doc->priv->partial,
101                         sp_repr_commit_undoable (doc->rdoc)
102                 );
103         }
105         doc->priv->sensitive = sensitive;
108 /*TODO: Throughout the inkscape code tree set/get_undo_sensitive are used for
109  * as is shown above.  Perhaps it makes sense to create new functions,
110  * undo_ignore, and undo_recall to replace the start and end parts of the above.
111  * The main complexity with this is that they have to nest, so you have to store
112  * the saved bools in a stack.  Perhaps this is why the above solution is better.
113  */
115 bool sp_document_get_undo_sensitive(SPDocument const *document) {
116         g_assert(document != NULL);
117         g_assert(document->priv != NULL);
119         return document->priv->sensitive;
122 void
123 sp_document_done (SPDocument *doc, const unsigned int event_type, Glib::ustring event_description)
125         sp_document_maybe_done (doc, NULL, event_type, event_description);
128 void sp_document_reset_key( Inkscape::Application * /*inkscape*/, SPDesktop * /*desktop*/, GtkObject *base )
130     SPDocument *doc = reinterpret_cast<SPDocument *>(base);
131     doc->actionkey.clear();
134 namespace {
136 using Inkscape::Debug::Event;
137 using Inkscape::Debug::SimpleEvent;
138 using Inkscape::Util::share_static_string;
139 using Inkscape::Debug::timestamp;
140 using Inkscape::Verb;
142 typedef SimpleEvent<Event::INTERACTION> InteractionEvent;
144 class CommitEvent : public InteractionEvent {
145 public:
147     CommitEvent(SPDocument *doc, const gchar *key, const unsigned int type)
148     : InteractionEvent(share_static_string("commit"))
149     {
150         _addProperty(share_static_string("timestamp"), timestamp());
151         gchar *serial = g_strdup_printf("%lu", doc->serial());
152         _addProperty(share_static_string("document"), serial);
153         g_free(serial);
154         Verb *verb = Verb::get(type);
155         if (verb) {
156             _addProperty(share_static_string("context"), verb->get_id());
157         }
158         if (key) {
159             _addProperty(share_static_string("merge-key"), key);
160         }
161     }
162 };
166 void
167 sp_document_maybe_done (SPDocument *doc, const gchar *key, const unsigned int event_type,
168                         Glib::ustring event_description)
170         g_assert (doc != NULL);
171         g_assert (doc->priv != NULL);
172         g_assert (doc->priv->sensitive);
173         if ( key && !*key ) {
174             g_warning("Blank undo key specified.");
175         }
177         Inkscape::Debug::EventTracker<CommitEvent> tracker(doc, key, event_type);
179         doc->collectOrphans();
181         sp_document_ensure_up_to_date (doc);
183         sp_document_clear_redo (doc);
185         Inkscape::XML::Event *log = sp_repr_coalesce_log (doc->priv->partial, sp_repr_commit_undoable (doc->rdoc));
186         doc->priv->partial = NULL;
188         if (!log) {
189                 sp_repr_begin_transaction (doc->rdoc);
190                 return;
191         }
193         if (key && !doc->actionkey.empty() && (doc->actionkey == key) && doc->priv->undo) {
194                 ((Inkscape::Event *)doc->priv->undo->data)->event =
195                     sp_repr_coalesce_log (((Inkscape::Event *)doc->priv->undo->data)->event, log);
196         } else {
197                 Inkscape::Event *event = new Inkscape::Event(log, event_type, event_description);
198                 doc->priv->undo = g_slist_prepend (doc->priv->undo, event);
199                 doc->priv->history_size++;
200                 doc->priv->undoStackObservers.notifyUndoCommitEvent(event);
201         }
203         if ( key ) {
204             doc->actionkey = key;
205         } else {
206             doc->actionkey.clear();
207         }
209         doc->virgin = FALSE;
210         doc->setModifiedSinceSave();
212         sp_repr_begin_transaction (doc->rdoc);
214   doc->priv->commit_signal.emit();
217 void
218 sp_document_cancel (SPDocument *doc)
220         g_assert (doc != NULL);
221         g_assert (doc->priv != NULL);
222         g_assert (doc->priv->sensitive);
224         sp_repr_rollback (doc->rdoc);
226         if (doc->priv->partial) {
227                 sp_repr_undo_log (doc->priv->partial);
228                 sp_repr_free_log (doc->priv->partial);
229                 doc->priv->partial = NULL;
230         }
232         sp_repr_begin_transaction (doc->rdoc);
235 static void finish_incomplete_transaction(SPDocument &doc) {
236         SPDocumentPrivate &priv=*doc.priv;
237         Inkscape::XML::Event *log=sp_repr_commit_undoable(doc.rdoc);
238         if (log || priv.partial) {
239                 g_warning ("Incomplete undo transaction:");
240                 priv.partial = sp_repr_coalesce_log(priv.partial, log);
241                 sp_repr_debug_print_log(priv.partial);
242                 Inkscape::Event *event = new Inkscape::Event(priv.partial);
243                 priv.undo = g_slist_prepend(priv.undo, event);
244                 priv.undoStackObservers.notifyUndoCommitEvent(event);
245                 priv.partial = NULL;
246         }
249 gboolean
250 sp_document_undo (SPDocument *doc)
252         using Inkscape::Debug::EventTracker;
253         using Inkscape::Debug::SimpleEvent;
255         gboolean ret;
257         EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("undo");
259         g_assert (doc != NULL);
260         g_assert (doc->priv != NULL);
261         g_assert (doc->priv->sensitive);
263         doc->priv->sensitive = FALSE;
264         doc->priv->seeking = true;
266         doc->actionkey.clear();
268         finish_incomplete_transaction(*doc);
270         if (doc->priv->undo) {
271                 Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data;
272                 doc->priv->undo = g_slist_remove (doc->priv->undo, log);
273                 sp_repr_undo_log (log->event);
274                 doc->priv->redo = g_slist_prepend (doc->priv->redo, log);
276                 doc->setModifiedSinceSave();
277                 doc->priv->undoStackObservers.notifyUndoEvent(log);
279                 ret = TRUE;
280         } else {
281                 ret = FALSE;
282         }
284         sp_repr_begin_transaction (doc->rdoc);
286         doc->priv->sensitive = TRUE;
287         doc->priv->seeking = false;
289         if (ret)
290                 inkscape_external_change();
292         return ret;
295 gboolean
296 sp_document_redo (SPDocument *doc)
298         using Inkscape::Debug::EventTracker;
299         using Inkscape::Debug::SimpleEvent;
301         gboolean ret;
303         EventTracker<SimpleEvent<Inkscape::Debug::Event::DOCUMENT> > tracker("redo");
305         g_assert (doc != NULL);
306         g_assert (doc->priv != NULL);
307         g_assert (doc->priv->sensitive);
309         doc->priv->sensitive = FALSE;
310         doc->priv->seeking = true;
312         doc->actionkey.clear();
314         finish_incomplete_transaction(*doc);
316         if (doc->priv->redo) {
317                 Inkscape::Event *log=(Inkscape::Event *)doc->priv->redo->data;
318                 doc->priv->redo = g_slist_remove (doc->priv->redo, log);
319                 sp_repr_replay_log (log->event);
320                 doc->priv->undo = g_slist_prepend (doc->priv->undo, log);
322                 doc->setModifiedSinceSave();
323                 doc->priv->undoStackObservers.notifyRedoEvent(log);
325                 ret = TRUE;
326         } else {
327                 ret = FALSE;
328         }
330         sp_repr_begin_transaction (doc->rdoc);
332         doc->priv->sensitive = TRUE;
333         doc->priv->seeking = false;
335         if (ret)
336                 inkscape_external_change();
338         return ret;
341 void
342 sp_document_clear_undo (SPDocument *doc)
344         if (doc->priv->undo)
345                 doc->priv->undoStackObservers.notifyClearUndoEvent();
347         while (doc->priv->undo) {
348                 GSList *current;
350                 current = doc->priv->undo;
351                 doc->priv->undo = current->next;
352                 doc->priv->history_size--;
354                 delete ((Inkscape::Event *) current->data);
355                 g_slist_free_1 (current);
356         }
359 void
360 sp_document_clear_redo (SPDocument *doc)
362         if (doc->priv->redo)
363                 doc->priv->undoStackObservers.notifyClearRedoEvent();
365         while (doc->priv->redo) {
366                 GSList *current;
368                 current = doc->priv->redo;
369                 doc->priv->redo = current->next;
370                 doc->priv->history_size--;
372                 delete ((Inkscape::Event *) current->data);
373                 g_slist_free_1 (current);
374         }
376 /*
377   Local Variables:
378   mode:c++
379   c-file-style:"stroustrup"
380   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
381   indent-tabs-mode:nil
382   fill-column:99
383   End:
384 */
385 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :