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 <glib.h> // g_assert()
17 #include <cstdio>
19 #include "event.h"
20 #include "event-fns.h"
21 #include "util/reverse-list.h"
22 #include "xml/document.h"
23 #include "xml/node-observer.h"
24 #include "debug/event-tracker.h"
25 #include "debug/simple-event.h"
27 using Inkscape::Util::List;
28 using Inkscape::Util::reverse_list;
30 int Inkscape::XML::Event::_next_serial=0;
32 void
33 sp_repr_begin_transaction (Inkscape::XML::Document *doc)
34 {
35 using Inkscape::Debug::SimpleEvent;
36 using Inkscape::Debug::EventTracker;
37 using Inkscape::Debug::Event;
39 EventTracker<SimpleEvent<Event::XML> > tracker("begin-transaction");
41 g_assert(doc != NULL);
42 doc->beginTransaction();
43 }
45 void
46 sp_repr_rollback (Inkscape::XML::Document *doc)
47 {
48 using Inkscape::Debug::SimpleEvent;
49 using Inkscape::Debug::EventTracker;
50 using Inkscape::Debug::Event;
52 EventTracker<SimpleEvent<Event::XML> > tracker("rollback");
54 g_assert(doc != NULL);
55 doc->rollback();
56 }
58 void
59 sp_repr_commit (Inkscape::XML::Document *doc)
60 {
61 using Inkscape::Debug::SimpleEvent;
62 using Inkscape::Debug::EventTracker;
63 using Inkscape::Debug::Event;
65 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
67 g_assert(doc != NULL);
68 doc->commit();
69 }
71 Inkscape::XML::Event *
72 sp_repr_commit_undoable (Inkscape::XML::Document *doc)
73 {
74 using Inkscape::Debug::SimpleEvent;
75 using Inkscape::Debug::EventTracker;
76 using Inkscape::Debug::Event;
78 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
80 g_assert(doc != NULL);
81 return doc->commitUndoable();
82 }
84 namespace {
86 class LogPerformer : public Inkscape::XML::NodeObserver {
87 public:
88 typedef Inkscape::XML::Node Node;
90 static LogPerformer &instance() {
91 static LogPerformer singleton;
92 return singleton;
93 }
95 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
96 parent.addChild(&child, ref);
97 }
99 void notifyChildRemoved(Node &parent, Node &child, Node */*old_ref*/) {
100 parent.removeChild(&child);
101 }
103 void notifyChildOrderChanged(Node &parent, Node &child,
104 Node */*old_ref*/, Node *new_ref)
105 {
106 parent.changeOrder(&child, new_ref);
107 }
109 void notifyAttributeChanged(Node &node, GQuark name,
110 Inkscape::Util::ptr_shared<char> /*old_value*/,
111 Inkscape::Util::ptr_shared<char> new_value)
112 {
113 node.setAttribute(g_quark_to_string(name), new_value);
114 }
116 void notifyContentChanged(Node &node,
117 Inkscape::Util::ptr_shared<char> /*old_value*/,
118 Inkscape::Util::ptr_shared<char> new_value)
119 {
120 node.setContent(new_value);
121 }
122 };
124 }
126 void Inkscape::XML::undo_log_to_observer(
127 Inkscape::XML::Event const *log,
128 Inkscape::XML::NodeObserver &observer
129 ) {
130 for ( Event const *action = log ; action ; action = action->next ) {
131 action->undoOne(observer);
132 }
133 }
135 void
136 sp_repr_undo_log (Inkscape::XML::Event *log)
137 {
138 using Inkscape::Debug::SimpleEvent;
139 using Inkscape::Debug::EventTracker;
140 using Inkscape::Debug::Event;
142 EventTracker<SimpleEvent<Event::XML> > tracker("undo-log");
144 if (log && log->repr) {
145 g_assert(!log->repr->document()->inTransaction());
146 }
148 Inkscape::XML::undo_log_to_observer(log, LogPerformer::instance());
149 }
151 void Inkscape::XML::EventAdd::_undoOne(
152 Inkscape::XML::NodeObserver &observer
153 ) const {
154 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
155 }
157 void Inkscape::XML::EventDel::_undoOne(
158 Inkscape::XML::NodeObserver &observer
159 ) const {
160 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
161 }
163 void Inkscape::XML::EventChgAttr::_undoOne(
164 Inkscape::XML::NodeObserver &observer
165 ) const {
166 observer.notifyAttributeChanged(*this->repr, this->key, this->newval, this->oldval);
167 }
169 void Inkscape::XML::EventChgContent::_undoOne(
170 Inkscape::XML::NodeObserver &observer
171 ) const {
172 observer.notifyContentChanged(*this->repr, this->newval, this->oldval);
173 }
175 void Inkscape::XML::EventChgOrder::_undoOne(
176 Inkscape::XML::NodeObserver &observer
177 ) const {
178 observer.notifyChildOrderChanged(*this->repr, *this->child, this->newref, this->oldref);
179 }
181 void Inkscape::XML::replay_log_to_observer(
182 Inkscape::XML::Event const *log,
183 Inkscape::XML::NodeObserver &observer
184 ) {
185 List<Inkscape::XML::Event const &> reversed =
186 reverse_list<Inkscape::XML::Event::ConstIterator>(log, NULL);
187 for ( ; reversed ; ++reversed ) {
188 reversed->replayOne(observer);
189 }
190 }
192 void
193 sp_repr_replay_log (Inkscape::XML::Event *log)
194 {
195 using Inkscape::Debug::SimpleEvent;
196 using Inkscape::Debug::EventTracker;
197 using Inkscape::Debug::Event;
199 EventTracker<SimpleEvent<Event::XML> > tracker("replay-log");
201 if (log) {
202 if (log->repr->document()) {
203 g_assert(!log->repr->document()->inTransaction());
204 }
205 }
207 Inkscape::XML::replay_log_to_observer(log, LogPerformer::instance());
208 }
210 void Inkscape::XML::EventAdd::_replayOne(
211 Inkscape::XML::NodeObserver &observer
212 ) const {
213 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
214 }
216 void Inkscape::XML::EventDel::_replayOne(
217 Inkscape::XML::NodeObserver &observer
218 ) const {
219 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
220 }
222 void Inkscape::XML::EventChgAttr::_replayOne(
223 Inkscape::XML::NodeObserver &observer
224 ) const {
225 observer.notifyAttributeChanged(*this->repr, this->key, this->oldval, this->newval);
226 }
228 void Inkscape::XML::EventChgContent::_replayOne(
229 Inkscape::XML::NodeObserver &observer
230 ) const {
231 observer.notifyContentChanged(*this->repr, this->oldval, this->newval);
232 }
234 void Inkscape::XML::EventChgOrder::_replayOne(
235 Inkscape::XML::NodeObserver &observer
236 ) const {
237 observer.notifyChildOrderChanged(*this->repr, *this->child, this->oldref, this->newref);
238 }
240 Inkscape::XML::Event *
241 sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b)
242 {
243 Inkscape::XML::Event *action;
244 Inkscape::XML::Event **prev_ptr;
246 if (!b) return a;
247 if (!a) return b;
249 /* find the earliest action in the second log */
250 /* (also noting the pointer that references it, so we can
251 * replace it later) */
252 prev_ptr = &b;
253 for ( action = b ; action->next ; action = action->next ) {
254 prev_ptr = &action->next;
255 }
257 /* add the first log after it */
258 action->next = a;
260 /* optimize the result */
261 *prev_ptr = action->optimizeOne();
263 return b;
264 }
266 void
267 sp_repr_free_log (Inkscape::XML::Event *log)
268 {
269 while (log) {
270 Inkscape::XML::Event *action;
271 action = log;
272 log = action->next;
273 delete action;
274 }
275 }
277 namespace {
279 template <typename T> struct ActionRelations;
281 template <>
282 struct ActionRelations<Inkscape::XML::EventAdd> {
283 typedef Inkscape::XML::EventDel Opposite;
284 };
286 template <>
287 struct ActionRelations<Inkscape::XML::EventDel> {
288 typedef Inkscape::XML::EventAdd Opposite;
289 };
291 template <typename A>
292 Inkscape::XML::Event *cancel_add_or_remove(A *action) {
293 typedef typename ActionRelations<A>::Opposite Opposite;
294 Opposite *opposite=dynamic_cast<Opposite *>(action->next);
296 if ( opposite && opposite->repr == action->repr &&
297 opposite->child == action->child &&
298 opposite->ref == action->ref )
299 {
300 Inkscape::XML::Event *remaining=opposite->next;
302 delete opposite;
303 delete action;
305 return remaining;
306 } else {
307 return action;
308 }
309 }
311 }
313 Inkscape::XML::Event *Inkscape::XML::EventAdd::_optimizeOne() {
314 return cancel_add_or_remove(this);
315 }
317 Inkscape::XML::Event *Inkscape::XML::EventDel::_optimizeOne() {
318 return cancel_add_or_remove(this);
319 }
321 Inkscape::XML::Event *Inkscape::XML::EventChgAttr::_optimizeOne() {
322 Inkscape::XML::EventChgAttr *chg_attr=dynamic_cast<Inkscape::XML::EventChgAttr *>(this->next);
324 /* consecutive chgattrs on the same key can be combined */
325 if ( chg_attr && chg_attr->repr == this->repr &&
326 chg_attr->key == this->key )
327 {
328 /* replace our oldval with the prior action's */
329 this->oldval = chg_attr->oldval;
331 /* discard the prior action */
332 this->next = chg_attr->next;
333 delete chg_attr;
334 }
336 return this;
337 }
339 Inkscape::XML::Event *Inkscape::XML::EventChgContent::_optimizeOne() {
340 Inkscape::XML::EventChgContent *chg_content=dynamic_cast<Inkscape::XML::EventChgContent *>(this->next);
342 /* consecutive content changes can be combined */
343 if ( chg_content && chg_content->repr == this->repr ) {
344 /* replace our oldval with the prior action's */
345 this->oldval = chg_content->oldval;
347 /* get rid of the prior action*/
348 this->next = chg_content->next;
349 delete chg_content;
350 }
352 return this;
353 }
355 Inkscape::XML::Event *Inkscape::XML::EventChgOrder::_optimizeOne() {
356 Inkscape::XML::EventChgOrder *chg_order=dynamic_cast<Inkscape::XML::EventChgOrder *>(this->next);
358 /* consecutive chgorders for the same child may be combined or
359 * canceled out */
360 if ( chg_order && chg_order->repr == this->repr &&
361 chg_order->child == this->child )
362 {
363 if ( chg_order->oldref == this->newref ) {
364 /* cancel them out */
365 Inkscape::XML::Event *after=chg_order->next;
367 delete chg_order;
368 delete this;
370 return after;
371 } else {
372 /* combine them */
373 this->oldref = chg_order->oldref;
375 /* get rid of the other one */
376 this->next = chg_order->next;
377 delete chg_order;
379 return this;
380 }
381 } else {
382 return this;
383 }
384 }
386 namespace {
388 class LogPrinter : public Inkscape::XML::NodeObserver {
389 public:
390 typedef Inkscape::XML::Node Node;
392 static LogPrinter &instance() {
393 static LogPrinter singleton;
394 return singleton;
395 }
397 static Glib::ustring node_to_string(Node const &node) {
398 Glib::ustring result;
399 char const *type_name=NULL;
400 switch (node.type()) {
401 case Inkscape::XML::DOCUMENT_NODE:
402 type_name = "Document";
403 break;
404 case Inkscape::XML::ELEMENT_NODE:
405 type_name = "Element";
406 break;
407 case Inkscape::XML::TEXT_NODE:
408 type_name = "Text";
409 break;
410 case Inkscape::XML::COMMENT_NODE:
411 type_name = "Comment";
412 break;
413 default:
414 g_assert_not_reached();
415 }
416 char buffer[40];
417 result.append("#<");
418 result.append(type_name);
419 result.append(":");
420 snprintf(buffer, 40, "0x%p", &node);
421 result.append(buffer);
422 result.append(">");
424 return result;
425 }
427 static Glib::ustring ref_to_string(Node *ref) {
428 if (ref) {
429 return node_to_string(*ref);
430 } else {
431 return "beginning";
432 }
433 }
435 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
436 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());
437 }
439 void notifyChildRemoved(Node &parent, Node &child, Node */*ref*/) {
440 g_warning("Event: Removed %s from %s", node_to_string(parent).c_str(), node_to_string(child).c_str());
441 }
443 void notifyChildOrderChanged(Node &parent, Node &child,
444 Node */*old_ref*/, Node *new_ref)
445 {
446 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());
447 }
449 void notifyAttributeChanged(Node &node, GQuark name,
450 Inkscape::Util::ptr_shared<char> /*old_value*/,
451 Inkscape::Util::ptr_shared<char> new_value)
452 {
453 if (new_value) {
454 g_warning("Event: Set attribute %s to \"%s\" on %s", g_quark_to_string(name), new_value.pointer(), node_to_string(node).c_str());
455 } else {
456 g_warning("Event: Unset attribute %s on %s", g_quark_to_string(name), node_to_string(node).c_str());
457 }
458 }
460 void notifyContentChanged(Node &node,
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 content of %s to \"%s\"", node_to_string(node).c_str(), new_value.pointer());
466 } else {
467 g_warning("Event: Unset content of %s", node_to_string(node).c_str());
468 }
469 }
470 };
472 }
474 void
475 sp_repr_debug_print_log(Inkscape::XML::Event const *log) {
476 Inkscape::XML::replay_log_to_observer(log, LogPrinter::instance());
477 }