Code

Updated cases for attributes added in <color-profile> support
[inkscape.git] / src / xml / event.cpp
1 /*
2  * Repr transaction logging
3  *
4  * Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   MenTaLguY  <mental@rydia.net>
7  *
8  * Copyright (C) 2004-2005 MenTaLguY
9  * Copyright (C) 1999-2003 authors
10  * Copyright (C) 2001-2002 Ximian, Inc.
11  * g++ port Copyright (C) 2003 Nathan Hurst
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #include "event.h"
17 #include "event-fns.h"
18 #include "util/reverse-list.h"
19 #include "xml/document.h"
20 #include "xml/node-observer.h"
21 #include "debug/event-tracker.h"
22 #include "debug/simple-event.h"
24 using Inkscape::Util::List;
25 using Inkscape::Util::reverse_list;
27 int Inkscape::XML::Event::_next_serial=0;
29 using Inkscape::XML::Session;
31 void
32 sp_repr_begin_transaction (Inkscape::XML::Document *doc)
33 {
34         using Inkscape::Debug::SimpleEvent;
35         using Inkscape::Debug::EventTracker;
36         using Inkscape::Debug::Event;
38         EventTracker<SimpleEvent<Event::XML> > tracker("begin-transaction");
40         g_assert(doc != NULL);
41         Session *session=doc->session();
42         g_assert(session != NULL);
43         session->beginTransaction();
44 }
46 void
47 sp_repr_rollback (Inkscape::XML::Document *doc)
48 {
49         using Inkscape::Debug::SimpleEvent;
50         using Inkscape::Debug::EventTracker;
51         using Inkscape::Debug::Event;
53         EventTracker<SimpleEvent<Event::XML> > tracker("rollback");
55         g_assert(doc != NULL);
56         Session *session=doc->session();
57         g_assert(session != NULL);
58         session->rollback();
59 }
61 void
62 sp_repr_commit (Inkscape::XML::Document *doc)
63 {
64         using Inkscape::Debug::SimpleEvent;
65         using Inkscape::Debug::EventTracker;
66         using Inkscape::Debug::Event;
68         EventTracker<SimpleEvent<Event::XML> > tracker("commit");
70         g_assert(doc != NULL);
71         Session *session=doc->session();
72         g_assert(session != NULL);
73         session->commit();
74 }
76 Inkscape::XML::Event *
77 sp_repr_commit_undoable (Inkscape::XML::Document *doc)
78 {
79         using Inkscape::Debug::SimpleEvent;
80         using Inkscape::Debug::EventTracker;
81         using Inkscape::Debug::Event;
83         EventTracker<SimpleEvent<Event::XML> > tracker("commit");
85         g_assert(doc != NULL);
86         Session *session=doc->session();
87         g_assert(session != NULL);
88         return session->commitUndoable();
89 }
91 namespace {
93 class LogPerformer : public Inkscape::XML::NodeObserver {
94 public:
95         typedef Inkscape::XML::Node Node;
97         static LogPerformer &instance() {
98                 static LogPerformer singleton;
99                 return singleton;
100         }
102         void notifyChildAdded(Node &parent, Node &child, Node *ref) {
103                 parent.addChild(&child, ref);
104         }
106         void notifyChildRemoved(Node &parent, Node &child, Node *old_ref) {
107                 parent.removeChild(&child);
108         }
110         void notifyChildOrderChanged(Node &parent, Node &child,
111                                      Node *old_ref, Node *new_ref)
112         {
113                 parent.changeOrder(&child, new_ref);
114         }
116         void notifyAttributeChanged(Node &node, GQuark name,
117                                     Inkscape::Util::ptr_shared<char> old_value,
118                                     Inkscape::Util::ptr_shared<char> new_value)
119         {
120                 node.setAttribute(g_quark_to_string(name), new_value);
121         }
123         void notifyContentChanged(Node &node,
124                                   Inkscape::Util::ptr_shared<char> old_value,
125                                   Inkscape::Util::ptr_shared<char> new_value)
126         {
127                 node.setContent(new_value);
128         }
129 };
133 void Inkscape::XML::undo_log_to_observer(
134         Inkscape::XML::Event const *log,
135         Inkscape::XML::NodeObserver &observer
136 ) {
137         for ( Event const *action = log ; action ; action = action->next ) {
138                 action->undoOne(observer);
139         }
142 void
143 sp_repr_undo_log (Inkscape::XML::Event *log)
145         using Inkscape::Debug::SimpleEvent;
146         using Inkscape::Debug::EventTracker;
147         using Inkscape::Debug::Event;
149         EventTracker<SimpleEvent<Event::XML> > tracker("undo-log");
151         if (log) {
152                 g_assert(!log->repr->session()->inTransaction());
153         }
155         Inkscape::XML::undo_log_to_observer(log, LogPerformer::instance());
158 void Inkscape::XML::EventAdd::_undoOne(
159         Inkscape::XML::NodeObserver &observer
160 ) const {
161         observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
164 void Inkscape::XML::EventDel::_undoOne(
165         Inkscape::XML::NodeObserver &observer
166 ) const {
167         observer.notifyChildAdded(*this->repr, *this->child, this->ref);
170 void Inkscape::XML::EventChgAttr::_undoOne(
171         Inkscape::XML::NodeObserver &observer
172 ) const {
173         observer.notifyAttributeChanged(*this->repr, this->key, this->newval, this->oldval);
176 void Inkscape::XML::EventChgContent::_undoOne(
177         Inkscape::XML::NodeObserver &observer
178 ) const {
179         observer.notifyContentChanged(*this->repr, this->newval, this->oldval);
182 void Inkscape::XML::EventChgOrder::_undoOne(
183         Inkscape::XML::NodeObserver &observer
184 ) const {
185         observer.notifyChildOrderChanged(*this->repr, *this->child, this->newref, this->oldref);
188 void Inkscape::XML::replay_log_to_observer(
189         Inkscape::XML::Event const *log,
190         Inkscape::XML::NodeObserver &observer
191 ) {
192         List<Inkscape::XML::Event const &> reversed =
193           reverse_list<Inkscape::XML::Event::ConstIterator>(log, NULL);
194         for ( ; reversed ; ++reversed ) {
195                 reversed->replayOne(observer);
196         }
199 void
200 sp_repr_replay_log (Inkscape::XML::Event *log)
202         using Inkscape::Debug::SimpleEvent;
203         using Inkscape::Debug::EventTracker;
204         using Inkscape::Debug::Event;
206         EventTracker<SimpleEvent<Event::XML> > tracker("replay-log");
208         if (log) {
209                 // Nodes created by the whiteboard deserializer tend to not 
210                 // have an associated session.  This conditional hacks a way around that,
211                 // but what's the best way to fix the whiteboard code so that new nodes
212                 // will have an associated session? -- yipdw
213                 if (log->repr->session()) {
214                         g_assert(!log->repr->session()->inTransaction());
215                 }
216         }
218         Inkscape::XML::replay_log_to_observer(log, LogPerformer::instance());
221 void Inkscape::XML::EventAdd::_replayOne(
222         Inkscape::XML::NodeObserver &observer
223 ) const {
224         observer.notifyChildAdded(*this->repr, *this->child, this->ref);
227 void Inkscape::XML::EventDel::_replayOne(
228         Inkscape::XML::NodeObserver &observer
229 ) const {
230         observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
233 void Inkscape::XML::EventChgAttr::_replayOne(
234         Inkscape::XML::NodeObserver &observer
235 ) const {
236         observer.notifyAttributeChanged(*this->repr, this->key, this->oldval, this->newval);
239 void Inkscape::XML::EventChgContent::_replayOne(
240         Inkscape::XML::NodeObserver &observer
241 ) const {
242         observer.notifyContentChanged(*this->repr, this->oldval, this->newval);
245 void Inkscape::XML::EventChgOrder::_replayOne(
246         Inkscape::XML::NodeObserver &observer
247 ) const {
248         observer.notifyChildOrderChanged(*this->repr, *this->child, this->oldref, this->newref);
251 Inkscape::XML::Event *
252 sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b)
254         Inkscape::XML::Event *action;
255         Inkscape::XML::Event **prev_ptr;
257         if (!b) return a;
258         if (!a) return b;
260         /* find the earliest action in the second log */
261         /* (also noting the pointer that references it, so we can
262          *  replace it later) */
263         prev_ptr = &b;
264         for ( action = b ; action->next ; action = action->next ) {
265                 prev_ptr = &action->next;
266         }
268         /* add the first log after it */
269         action->next = a;
271         /* optimize the result */
272         *prev_ptr = action->optimizeOne();
274         return b;
277 void
278 sp_repr_free_log (Inkscape::XML::Event *log)
280         while (log) {
281                 Inkscape::XML::Event *action;
282                 action = log;
283                 log = action->next;
284                 delete action;
285         }
288 namespace {
290 template <typename T> struct ActionRelations;
292 template <>
293 struct ActionRelations<Inkscape::XML::EventAdd> {
294         typedef Inkscape::XML::EventDel Opposite;
295 };
297 template <>
298 struct ActionRelations<Inkscape::XML::EventDel> {
299         typedef Inkscape::XML::EventAdd Opposite;
300 };
302 template <typename A>
303 Inkscape::XML::Event *cancel_add_or_remove(A *action) {
304         typedef typename ActionRelations<A>::Opposite Opposite;
305         Opposite *opposite=dynamic_cast<Opposite *>(action->next);
307         if ( opposite && opposite->repr == action->repr &&
308              opposite->child == action->child &&
309              opposite->ref == action->ref )
310         {
311                 Inkscape::XML::Event *remaining=opposite->next;
313                 delete opposite;
314                 delete action;
316                 return remaining;
317         } else {
318                 return action;
319         }
324 Inkscape::XML::Event *Inkscape::XML::EventAdd::_optimizeOne() {
325         return cancel_add_or_remove(this);
328 Inkscape::XML::Event *Inkscape::XML::EventDel::_optimizeOne() {
329         return cancel_add_or_remove(this);
332 Inkscape::XML::Event *Inkscape::XML::EventChgAttr::_optimizeOne() {
333         Inkscape::XML::EventChgAttr *chg_attr=dynamic_cast<Inkscape::XML::EventChgAttr *>(this->next);
335         /* consecutive chgattrs on the same key can be combined */
336         if ( chg_attr && chg_attr->repr == this->repr &&
337              chg_attr->key == this->key )
338         {
339                 /* replace our oldval with the prior action's */
340                 this->oldval = chg_attr->oldval;
342                 /* discard the prior action */
343                 this->next = chg_attr->next;
344                 delete chg_attr;
345         }
347         return this;
350 Inkscape::XML::Event *Inkscape::XML::EventChgContent::_optimizeOne() {
351         Inkscape::XML::EventChgContent *chg_content=dynamic_cast<Inkscape::XML::EventChgContent *>(this->next);
353         /* consecutive content changes can be combined */
354         if ( chg_content && chg_content->repr == this->repr ) {
355                 /* replace our oldval with the prior action's */
356                 this->oldval = chg_content->oldval;
358                 /* get rid of the prior action*/
359                 this->next = chg_content->next;
360                 delete chg_content;
361         }
363         return this;
366 Inkscape::XML::Event *Inkscape::XML::EventChgOrder::_optimizeOne() {
367         Inkscape::XML::EventChgOrder *chg_order=dynamic_cast<Inkscape::XML::EventChgOrder *>(this->next);
369         /* consecutive chgorders for the same child may be combined or
370          * canceled out */
371         if ( chg_order && chg_order->repr == this->repr &&
372              chg_order->child == this->child )
373         {
374                 if ( chg_order->oldref == this->newref ) {
375                         /* cancel them out */
376                         Inkscape::XML::Event *after=chg_order->next;
378                         delete chg_order;
379                         delete this;
381                         return after;
382                 } else {
383                         /* combine them */
384                         this->oldref = chg_order->oldref;
386                         /* get rid of the other one */
387                         this->next = chg_order->next;
388                         delete chg_order;
390                         return this;
391                 }
392         } else {
393                 return this;
394         }
397 namespace {
399 class LogPrinter : public Inkscape::XML::NodeObserver {
400 public:
401         typedef Inkscape::XML::Node Node;
403         static LogPrinter &instance() {
404                 static LogPrinter singleton;
405                 return singleton;
406         }
408         static Glib::ustring node_to_string(Node const &node) {
409                 Glib::ustring result;
410                 char const *type_name=NULL;
411                 switch (node.type()) {
412                 case Inkscape::XML::DOCUMENT_NODE:
413                         type_name = "Document";
414                         break;
415                 case Inkscape::XML::ELEMENT_NODE:
416                         type_name = "Element";
417                         break;
418                 case Inkscape::XML::TEXT_NODE:
419                         type_name = "Text";
420                         break;
421                 case Inkscape::XML::COMMENT_NODE:
422                         type_name = "Comment";
423                         break;
424                 default:
425                         g_assert_not_reached();
426                 }
427                 char buffer[40];
428                 result.append("#<");
429                 result.append(type_name);
430                 result.append(":");
431                 snprintf(buffer, 40, "0x%p", &node);
432                 result.append(buffer);
433                 result.append(">");
435                 return result;
436         }
438         static Glib::ustring ref_to_string(Node *ref) {
439                 if (ref) {
440                         return node_to_string(*ref);
441                 } else {
442                         return "beginning";
443                 }
444         }
446         void notifyChildAdded(Node &parent, Node &child, Node *ref) {
447                 g_warning("Event: Added %s to %s after %s", node_to_string(parent).c_str(), node_to_string(child).c_str(), ref_to_string(ref).c_str());
448         }
450         void notifyChildRemoved(Node &parent, Node &child, Node *ref) {
451                 g_warning("Event: Removed %s from %s", node_to_string(parent).c_str(), node_to_string(child).c_str());
452         }
454         void notifyChildOrderChanged(Node &parent, Node &child,
455                                      Node *old_ref, Node *new_ref)
456         {
457                 g_warning("Event: Moved %s after %s in %s", node_to_string(child).c_str(), ref_to_string(new_ref).c_str(), node_to_string(parent).c_str());
458         }
460         void notifyAttributeChanged(Node &node, GQuark name,
461                                     Inkscape::Util::ptr_shared<char> old_value,
462                                     Inkscape::Util::ptr_shared<char> new_value)
463         {
464                 if (new_value) {
465                         g_warning("Event: Set attribute %s to \"%s\" on %s", g_quark_to_string(name), new_value.pointer(), node_to_string(node).c_str());
466                 } else {
467                         g_warning("Event: Unset attribute %s on %s", g_quark_to_string(name), node_to_string(node).c_str());
468                 }
469         }
471         void notifyContentChanged(Node &node,
472                                   Inkscape::Util::ptr_shared<char> old_value,
473                                   Inkscape::Util::ptr_shared<char> new_value)
474         {
475                 if (new_value) {
476                         g_warning("Event: Set content of %s to \"%s\"", node_to_string(node).c_str(), new_value.pointer());
477                 } else {
478                         g_warning("Event: Unset content of %s", node_to_string(node).c_str());
479                 }
480         }
481 };
485 void
486 sp_repr_debug_print_log(Inkscape::XML::Event const *log) {
487         Inkscape::XML::replay_log_to_observer(log, LogPrinter::instance());