cc130042e52eb7da0e474360d4da92e51f120deb
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 void
30 sp_repr_begin_transaction (Inkscape::XML::Document *doc)
31 {
32 using Inkscape::Debug::SimpleEvent;
33 using Inkscape::Debug::EventTracker;
34 using Inkscape::Debug::Event;
36 EventTracker<SimpleEvent<Event::XML> > tracker("begin-transaction");
38 g_assert(doc != NULL);
39 doc->beginTransaction();
40 }
42 void
43 sp_repr_rollback (Inkscape::XML::Document *doc)
44 {
45 using Inkscape::Debug::SimpleEvent;
46 using Inkscape::Debug::EventTracker;
47 using Inkscape::Debug::Event;
49 EventTracker<SimpleEvent<Event::XML> > tracker("rollback");
51 g_assert(doc != NULL);
52 doc->rollback();
53 }
55 void
56 sp_repr_commit (Inkscape::XML::Document *doc)
57 {
58 using Inkscape::Debug::SimpleEvent;
59 using Inkscape::Debug::EventTracker;
60 using Inkscape::Debug::Event;
62 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
64 g_assert(doc != NULL);
65 doc->commit();
66 }
68 Inkscape::XML::Event *
69 sp_repr_commit_undoable (Inkscape::XML::Document *doc)
70 {
71 using Inkscape::Debug::SimpleEvent;
72 using Inkscape::Debug::EventTracker;
73 using Inkscape::Debug::Event;
75 EventTracker<SimpleEvent<Event::XML> > tracker("commit");
77 g_assert(doc != NULL);
78 return doc->commitUndoable();
79 }
81 namespace {
83 class LogPerformer : public Inkscape::XML::NodeObserver {
84 public:
85 typedef Inkscape::XML::Node Node;
87 static LogPerformer &instance() {
88 static LogPerformer singleton;
89 return singleton;
90 }
92 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
93 parent.addChild(&child, ref);
94 }
96 void notifyChildRemoved(Node &parent, Node &child, Node *old_ref) {
97 parent.removeChild(&child);
98 }
100 void notifyChildOrderChanged(Node &parent, Node &child,
101 Node *old_ref, Node *new_ref)
102 {
103 parent.changeOrder(&child, new_ref);
104 }
106 void notifyAttributeChanged(Node &node, GQuark name,
107 Inkscape::Util::ptr_shared<char> old_value,
108 Inkscape::Util::ptr_shared<char> new_value)
109 {
110 node.setAttribute(g_quark_to_string(name), new_value);
111 }
113 void notifyContentChanged(Node &node,
114 Inkscape::Util::ptr_shared<char> old_value,
115 Inkscape::Util::ptr_shared<char> new_value)
116 {
117 node.setContent(new_value);
118 }
119 };
121 }
123 void Inkscape::XML::undo_log_to_observer(
124 Inkscape::XML::Event const *log,
125 Inkscape::XML::NodeObserver &observer
126 ) {
127 for ( Event const *action = log ; action ; action = action->next ) {
128 action->undoOne(observer);
129 }
130 }
132 void
133 sp_repr_undo_log (Inkscape::XML::Event *log)
134 {
135 using Inkscape::Debug::SimpleEvent;
136 using Inkscape::Debug::EventTracker;
137 using Inkscape::Debug::Event;
139 EventTracker<SimpleEvent<Event::XML> > tracker("undo-log");
141 if (log) {
142 g_assert(!log->repr->document()->inTransaction());
143 }
145 Inkscape::XML::undo_log_to_observer(log, LogPerformer::instance());
146 }
148 void Inkscape::XML::EventAdd::_undoOne(
149 Inkscape::XML::NodeObserver &observer
150 ) const {
151 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
152 }
154 void Inkscape::XML::EventDel::_undoOne(
155 Inkscape::XML::NodeObserver &observer
156 ) const {
157 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
158 }
160 void Inkscape::XML::EventChgAttr::_undoOne(
161 Inkscape::XML::NodeObserver &observer
162 ) const {
163 observer.notifyAttributeChanged(*this->repr, this->key, this->newval, this->oldval);
164 }
166 void Inkscape::XML::EventChgContent::_undoOne(
167 Inkscape::XML::NodeObserver &observer
168 ) const {
169 observer.notifyContentChanged(*this->repr, this->newval, this->oldval);
170 }
172 void Inkscape::XML::EventChgOrder::_undoOne(
173 Inkscape::XML::NodeObserver &observer
174 ) const {
175 observer.notifyChildOrderChanged(*this->repr, *this->child, this->newref, this->oldref);
176 }
178 void Inkscape::XML::replay_log_to_observer(
179 Inkscape::XML::Event const *log,
180 Inkscape::XML::NodeObserver &observer
181 ) {
182 List<Inkscape::XML::Event const &> reversed =
183 reverse_list<Inkscape::XML::Event::ConstIterator>(log, NULL);
184 for ( ; reversed ; ++reversed ) {
185 reversed->replayOne(observer);
186 }
187 }
189 void
190 sp_repr_replay_log (Inkscape::XML::Event *log)
191 {
192 using Inkscape::Debug::SimpleEvent;
193 using Inkscape::Debug::EventTracker;
194 using Inkscape::Debug::Event;
196 EventTracker<SimpleEvent<Event::XML> > tracker("replay-log");
198 if (log) {
199 if (log->repr->document()) {
200 g_assert(!log->repr->document()->inTransaction());
201 }
202 }
204 Inkscape::XML::replay_log_to_observer(log, LogPerformer::instance());
205 }
207 void Inkscape::XML::EventAdd::_replayOne(
208 Inkscape::XML::NodeObserver &observer
209 ) const {
210 observer.notifyChildAdded(*this->repr, *this->child, this->ref);
211 }
213 void Inkscape::XML::EventDel::_replayOne(
214 Inkscape::XML::NodeObserver &observer
215 ) const {
216 observer.notifyChildRemoved(*this->repr, *this->child, this->ref);
217 }
219 void Inkscape::XML::EventChgAttr::_replayOne(
220 Inkscape::XML::NodeObserver &observer
221 ) const {
222 observer.notifyAttributeChanged(*this->repr, this->key, this->oldval, this->newval);
223 }
225 void Inkscape::XML::EventChgContent::_replayOne(
226 Inkscape::XML::NodeObserver &observer
227 ) const {
228 observer.notifyContentChanged(*this->repr, this->oldval, this->newval);
229 }
231 void Inkscape::XML::EventChgOrder::_replayOne(
232 Inkscape::XML::NodeObserver &observer
233 ) const {
234 observer.notifyChildOrderChanged(*this->repr, *this->child, this->oldref, this->newref);
235 }
237 Inkscape::XML::Event *
238 sp_repr_coalesce_log (Inkscape::XML::Event *a, Inkscape::XML::Event *b)
239 {
240 Inkscape::XML::Event *action;
241 Inkscape::XML::Event **prev_ptr;
243 if (!b) return a;
244 if (!a) return b;
246 /* find the earliest action in the second log */
247 /* (also noting the pointer that references it, so we can
248 * replace it later) */
249 prev_ptr = &b;
250 for ( action = b ; action->next ; action = action->next ) {
251 prev_ptr = &action->next;
252 }
254 /* add the first log after it */
255 action->next = a;
257 /* optimize the result */
258 *prev_ptr = action->optimizeOne();
260 return b;
261 }
263 void
264 sp_repr_free_log (Inkscape::XML::Event *log)
265 {
266 while (log) {
267 Inkscape::XML::Event *action;
268 action = log;
269 log = action->next;
270 delete action;
271 }
272 }
274 namespace {
276 template <typename T> struct ActionRelations;
278 template <>
279 struct ActionRelations<Inkscape::XML::EventAdd> {
280 typedef Inkscape::XML::EventDel Opposite;
281 };
283 template <>
284 struct ActionRelations<Inkscape::XML::EventDel> {
285 typedef Inkscape::XML::EventAdd Opposite;
286 };
288 template <typename A>
289 Inkscape::XML::Event *cancel_add_or_remove(A *action) {
290 typedef typename ActionRelations<A>::Opposite Opposite;
291 Opposite *opposite=dynamic_cast<Opposite *>(action->next);
293 if ( opposite && opposite->repr == action->repr &&
294 opposite->child == action->child &&
295 opposite->ref == action->ref )
296 {
297 Inkscape::XML::Event *remaining=opposite->next;
299 delete opposite;
300 delete action;
302 return remaining;
303 } else {
304 return action;
305 }
306 }
308 }
310 Inkscape::XML::Event *Inkscape::XML::EventAdd::_optimizeOne() {
311 return cancel_add_or_remove(this);
312 }
314 Inkscape::XML::Event *Inkscape::XML::EventDel::_optimizeOne() {
315 return cancel_add_or_remove(this);
316 }
318 Inkscape::XML::Event *Inkscape::XML::EventChgAttr::_optimizeOne() {
319 Inkscape::XML::EventChgAttr *chg_attr=dynamic_cast<Inkscape::XML::EventChgAttr *>(this->next);
321 /* consecutive chgattrs on the same key can be combined */
322 if ( chg_attr && chg_attr->repr == this->repr &&
323 chg_attr->key == this->key )
324 {
325 /* replace our oldval with the prior action's */
326 this->oldval = chg_attr->oldval;
328 /* discard the prior action */
329 this->next = chg_attr->next;
330 delete chg_attr;
331 }
333 return this;
334 }
336 Inkscape::XML::Event *Inkscape::XML::EventChgContent::_optimizeOne() {
337 Inkscape::XML::EventChgContent *chg_content=dynamic_cast<Inkscape::XML::EventChgContent *>(this->next);
339 /* consecutive content changes can be combined */
340 if ( chg_content && chg_content->repr == this->repr ) {
341 /* replace our oldval with the prior action's */
342 this->oldval = chg_content->oldval;
344 /* get rid of the prior action*/
345 this->next = chg_content->next;
346 delete chg_content;
347 }
349 return this;
350 }
352 Inkscape::XML::Event *Inkscape::XML::EventChgOrder::_optimizeOne() {
353 Inkscape::XML::EventChgOrder *chg_order=dynamic_cast<Inkscape::XML::EventChgOrder *>(this->next);
355 /* consecutive chgorders for the same child may be combined or
356 * canceled out */
357 if ( chg_order && chg_order->repr == this->repr &&
358 chg_order->child == this->child )
359 {
360 if ( chg_order->oldref == this->newref ) {
361 /* cancel them out */
362 Inkscape::XML::Event *after=chg_order->next;
364 delete chg_order;
365 delete this;
367 return after;
368 } else {
369 /* combine them */
370 this->oldref = chg_order->oldref;
372 /* get rid of the other one */
373 this->next = chg_order->next;
374 delete chg_order;
376 return this;
377 }
378 } else {
379 return this;
380 }
381 }
383 namespace {
385 class LogPrinter : public Inkscape::XML::NodeObserver {
386 public:
387 typedef Inkscape::XML::Node Node;
389 static LogPrinter &instance() {
390 static LogPrinter singleton;
391 return singleton;
392 }
394 static Glib::ustring node_to_string(Node const &node) {
395 Glib::ustring result;
396 char const *type_name=NULL;
397 switch (node.type()) {
398 case Inkscape::XML::DOCUMENT_NODE:
399 type_name = "Document";
400 break;
401 case Inkscape::XML::ELEMENT_NODE:
402 type_name = "Element";
403 break;
404 case Inkscape::XML::TEXT_NODE:
405 type_name = "Text";
406 break;
407 case Inkscape::XML::COMMENT_NODE:
408 type_name = "Comment";
409 break;
410 default:
411 g_assert_not_reached();
412 }
413 char buffer[40];
414 result.append("#<");
415 result.append(type_name);
416 result.append(":");
417 snprintf(buffer, 40, "0x%p", &node);
418 result.append(buffer);
419 result.append(">");
421 return result;
422 }
424 static Glib::ustring ref_to_string(Node *ref) {
425 if (ref) {
426 return node_to_string(*ref);
427 } else {
428 return "beginning";
429 }
430 }
432 void notifyChildAdded(Node &parent, Node &child, Node *ref) {
433 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());
434 }
436 void notifyChildRemoved(Node &parent, Node &child, Node *ref) {
437 g_warning("Event: Removed %s from %s", node_to_string(parent).c_str(), node_to_string(child).c_str());
438 }
440 void notifyChildOrderChanged(Node &parent, Node &child,
441 Node *old_ref, Node *new_ref)
442 {
443 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());
444 }
446 void notifyAttributeChanged(Node &node, GQuark name,
447 Inkscape::Util::ptr_shared<char> old_value,
448 Inkscape::Util::ptr_shared<char> new_value)
449 {
450 if (new_value) {
451 g_warning("Event: Set attribute %s to \"%s\" on %s", g_quark_to_string(name), new_value.pointer(), node_to_string(node).c_str());
452 } else {
453 g_warning("Event: Unset attribute %s on %s", g_quark_to_string(name), node_to_string(node).c_str());
454 }
455 }
457 void notifyContentChanged(Node &node,
458 Inkscape::Util::ptr_shared<char> old_value,
459 Inkscape::Util::ptr_shared<char> new_value)
460 {
461 if (new_value) {
462 g_warning("Event: Set content of %s to \"%s\"", node_to_string(node).c_str(), new_value.pointer());
463 } else {
464 g_warning("Event: Unset content of %s", node_to_string(node).c_str());
465 }
466 }
467 };
469 }
471 void
472 sp_repr_debug_print_log(Inkscape::XML::Event const *log) {
473 Inkscape::XML::replay_log_to_observer(log, LogPrinter::instance());
474 }