b3d5c5ffe03ecc5cd2f5e1eabec411ba476331d2
1 #define __SP_TREF_CPP__
3 /** \file
4 * SVG <tref> implementation - All character data within the referenced
5 * element, including character data enclosed within additional markup,
6 * will be rendered.
7 *
8 * This file was created based on skeleton.cpp
9 */
10 /*
11 * Authors:
12 * Gail Banaszkiewicz <Gail.Banaszkiewicz@gmail.com>
13 *
14 * Copyright (C) 2007 Gail Banaszkiewicz
15 *
16 * Released under GNU GPL, read the file 'COPYING' for more information
17 */
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
23 #include <glibmm/i18n.h>
25 #include "attributes.h"
26 #include "document.h"
27 #include "sp-object-repr.h"
28 #include "sp-text.h"
29 #include "sp-tspan.h"
30 #include "sp-tref.h"
31 #include "style.h"
32 #include "text-editing.h"
33 #include "uri.h"
35 #include "display/nr-arena-group.h"
36 #include "libnr/nr-matrix-fns.h"
37 #include "xml/node.h"
38 #include "xml/repr.h"
41 //#define DEBUG_TREF
42 #ifdef DEBUG_TREF
43 # define debug(f, a...) { g_message("%s(%d) %s:", \
44 __FILE__,__LINE__,__FUNCTION__); \
45 g_message(f, ## a); \
46 g_message("\n"); \
47 }
48 #else
49 # define debug(f, a...) /**/
50 #endif
53 static void build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString);
55 /* TRef base class */
57 static void sp_tref_class_init(SPTRefClass *tref_class);
58 static void sp_tref_init(SPTRef *tref);
59 static void sp_tref_finalize(GObject *obj);
61 static void sp_tref_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
62 static void sp_tref_release(SPObject *object);
63 static void sp_tref_set(SPObject *object, unsigned int key, gchar const *value);
64 static void sp_tref_update(SPObject *object, SPCtx *ctx, guint flags);
65 static void sp_tref_modified(SPObject *object, guint flags);
66 static Inkscape::XML::Node *sp_tref_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
68 static void sp_tref_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags);
69 static gchar *sp_tref_description(SPItem *item);
71 static void sp_tref_href_changed(SPObject *old_ref, SPObject *ref, SPTRef *tref);
72 static void sp_tref_delete_self(SPObject *deleted, SPTRef *self);
74 static SPObjectClass *tref_parent_class;
76 GType
77 sp_tref_get_type()
78 {
79 static GType tref_type = 0;
81 if (!tref_type) {
82 GTypeInfo tref_info = {
83 sizeof(SPTRefClass),
84 NULL, NULL,
85 (GClassInitFunc) sp_tref_class_init,
86 NULL, NULL,
87 sizeof(SPTRef),
88 16,
89 (GInstanceInitFunc) sp_tref_init,
90 NULL, /* value_table */
91 };
92 tref_type = g_type_register_static(SP_TYPE_ITEM, "SPTRef", &tref_info, (GTypeFlags)0);
93 }
94 return tref_type;
95 }
97 static void
98 sp_tref_class_init(SPTRefClass *tref_class)
99 {
100 GObjectClass *gobject_class = (GObjectClass *) tref_class;
101 SPObjectClass *sp_object_class = (SPObjectClass *)tref_class;
103 tref_parent_class = (SPObjectClass*)g_type_class_peek_parent(tref_class);
105 sp_object_class->build = sp_tref_build;
106 sp_object_class->release = sp_tref_release;
107 sp_object_class->write = sp_tref_write;
108 sp_object_class->set = sp_tref_set;
109 sp_object_class->update = sp_tref_update;
110 sp_object_class->modified = sp_tref_modified;
112 gobject_class->finalize = sp_tref_finalize;
114 SPItemClass *item_class = (SPItemClass *) tref_class;
116 item_class->bbox = sp_tref_bbox;
117 item_class->description = sp_tref_description;
118 }
120 static void
121 sp_tref_init(SPTRef *tref)
122 {
123 new (&tref->attributes) TextTagAttributes;
125 tref->href = NULL;
126 tref->uriOriginalRef = new SPTRefReference(SP_OBJECT(tref));
127 new (&tref->_delete_connection) sigc::connection();
128 new (&tref->_changed_connection) sigc::connection();
130 tref->_changed_connection =
131 tref->uriOriginalRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_tref_href_changed), tref));
132 }
135 static void
136 sp_tref_finalize(GObject *obj)
137 {
138 SPTRef *tref = (SPTRef *) obj;
140 delete tref->uriOriginalRef;
142 tref->_delete_connection.~connection();
143 tref->_changed_connection.~connection();
144 }
147 /**
148 * Reads the Inkscape::XML::Node, and initializes SPTRef variables.
149 */
150 static void
151 sp_tref_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
152 {
153 if (((SPObjectClass *) tref_parent_class)->build) {
154 ((SPObjectClass *) tref_parent_class)->build(object, document, repr);
155 }
157 sp_object_read_attr(object, "xlink:href");
158 sp_object_read_attr(object, "x");
159 sp_object_read_attr(object, "y");
160 sp_object_read_attr(object, "dx");
161 sp_object_read_attr(object, "dy");
162 sp_object_read_attr(object, "rotate");
163 }
165 /**
166 * Drops any allocated memory.
167 */
168 static void
169 sp_tref_release(SPObject *object)
170 {
171 SPTRef *tref = SP_TREF(object);
173 tref->attributes.~TextTagAttributes();
175 tref->_delete_connection.disconnect();
176 tref->_changed_connection.disconnect();
178 g_free(tref->href);
179 tref->href = NULL;
181 tref->uriOriginalRef->detach();
183 if (((SPObjectClass *) tref_parent_class)->release)
184 ((SPObjectClass *) tref_parent_class)->release(object);
185 }
187 /**
188 * Sets a specific value in the SPTRef.
189 */
190 static void
191 sp_tref_set(SPObject *object, unsigned int key, gchar const *value)
192 {
193 debug("0x%p %s(%u): '%s'",object,
194 sp_attribute_name(key),key,value ? value : "<no value>");
196 SPTRef *tref = SP_TREF(object);
198 if (tref->attributes.readSingleAttribute(key, value)) { // x, y, dx, dy, rotate
199 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
200 } else if (key == SP_ATTR_XLINK_HREF) { // xlink:href
201 if ( !value ) {
202 // No value
203 g_free(tref->href);
204 tref->href = NULL;
205 tref->uriOriginalRef->detach();
206 } else if ((tref->href && strcmp(value, tref->href) != 0) || (!tref->href)) {
208 // Value has changed
210 if ( tref->href ) {
211 g_free(tref->href);
212 tref->href = NULL;
213 }
215 tref->href = g_strdup(value);
217 try {
218 tref->uriOriginalRef->attach(Inkscape::URI(value));
219 tref->uriOriginalRef->updateObserver();
220 } catch ( Inkscape::BadURIException &e ) {
221 g_warning("%s", e.what());
222 tref->uriOriginalRef->detach();
223 }
225 // No matter what happened, an update should be in order
226 SP_OBJECT(tref)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
227 }
229 } else { // default
230 if (((SPObjectClass *) tref_parent_class)->set) {
231 ((SPObjectClass *) tref_parent_class)->set(object, key, value);
232 }
233 }
236 }
238 /**
239 * Receives update notifications. Code based on sp_use_update and sp_tspan_update.
240 */
241 static void
242 sp_tref_update(SPObject *object, SPCtx *ctx, guint flags)
243 {
244 debug("0x%p",object);
246 SPTRef *tref = SP_TREF(object);
248 if (((SPObjectClass *) tref_parent_class)->update) {
249 ((SPObjectClass *) tref_parent_class)->update(object, ctx, flags);
250 }
252 if (flags & SP_OBJECT_MODIFIED_FLAG) {
253 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
254 }
256 flags &= SP_OBJECT_MODIFIED_CASCADE;
258 SPObject *child = tref->stringChild;
259 if (child) {
260 if ( flags || ( child->uflags & SP_OBJECT_MODIFIED_FLAG )) {
261 child->updateDisplay(ctx, flags);
262 }
263 }
266 }
268 static void
269 sp_tref_modified(SPObject *object, guint flags)
270 {
271 SPTRef *tref_obj = SP_TREF(object);
273 if (flags & SP_OBJECT_MODIFIED_FLAG) {
274 flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
275 }
277 flags &= SP_OBJECT_MODIFIED_CASCADE;
279 SPObject *child = tref_obj->stringChild;
280 if (child) {
281 g_object_ref(G_OBJECT(child));
282 if (flags || (child->mflags & SP_OBJECT_MODIFIED_FLAG)) {
283 child->emitModified(flags);
284 }
285 g_object_unref(G_OBJECT(child));
286 }
287 }
289 /**
290 * Writes its settings to an incoming repr object, if any.
291 */
292 static Inkscape::XML::Node *
293 sp_tref_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
294 {
295 debug("0x%p",object);
297 SPTRef *tref = SP_TREF(object);
299 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
300 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
301 repr = xml_doc->createElement("svg:tref");
302 }
304 tref->attributes.writeTo(repr);
306 if (tref->uriOriginalRef->getURI()) {
307 gchar *uri_string = tref->uriOriginalRef->getURI()->toString();
308 debug("uri_string=%s", uri_string);
309 repr->setAttribute("xlink:href", uri_string);
310 g_free(uri_string);
311 }
313 if (((SPObjectClass *) tref_parent_class)->write) {
314 ((SPObjectClass *) tref_parent_class)->write(object, repr, flags);
315 }
317 return repr;
318 }
320 /**
321 * The code for this function is swiped from the tspan bbox code, since tref should work pretty much the same way
322 */
323 static void
324 sp_tref_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const /*flags*/)
325 {
326 // find out the ancestor text which holds our layout
327 SPObject *parent_text = SP_OBJECT(item);
328 for (; parent_text != NULL && !SP_IS_TEXT(parent_text); parent_text = SP_OBJECT_PARENT (parent_text));
329 if (parent_text == NULL) return;
331 // get the bbox of our portion of the layout
332 SP_TEXT(parent_text)->layout.getBoundingBox(
333 bbox, transform, sp_text_get_length_upto(parent_text, item), sp_text_get_length_upto(item, NULL) - 1);
335 // Add stroke width
336 SPStyle* style=SP_OBJECT_STYLE (item);
337 if (!style->stroke.isNone()) {
338 double const scale = expansion(transform);
339 if ( fabs(style->stroke_width.computed * scale) > 0.01 ) { // sinon c'est 0=oon veut pas de bord
340 double const width = MAX(0.125, style->stroke_width.computed * scale);
341 if ( fabs(bbox->x1 - bbox->x0) > -0.00001 && fabs(bbox->y1 - bbox->y0) > -0.00001 ) {
342 bbox->x0-=0.5*width;
343 bbox->x1+=0.5*width;
344 bbox->y0-=0.5*width;
345 bbox->y1+=0.5*width;
346 }
347 }
348 }
349 }
352 static gchar *
353 sp_tref_description(SPItem *item)
354 {
355 SPTRef *tref = SP_TREF(item);
357 if (tref && tref->getObjectReferredTo()) {
358 char *child_desc = sp_item_description(SP_ITEM(tref->getObjectReferredTo()));
359 char *ret = g_strdup_printf(_("<b>Cloned character data</b> from: %s"), child_desc);
360 g_free(child_desc);
361 return ret;
362 } else {
363 return g_strdup(_("<b>Orphaned cloned character data</b>"));
364 }
365 }
368 /* For the sigc::connection changes (i.e. when the object being refered to changes) */
369 static void
370 sp_tref_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPTRef *tref)
371 {
372 if (tref)
373 {
374 // Save a pointer to the original object being referred to
375 SPObject *refRoot = tref->getObjectReferredTo();
377 tref->_delete_connection.disconnect();
379 if (tref->stringChild) {
380 sp_object_detach(SP_OBJECT(tref), tref->stringChild);
381 tref->stringChild = NULL;
382 }
384 // Ensure that we are referring to a legitimate object
385 if (tref->href && refRoot && sp_tref_reference_allowed(tref, refRoot)) {
387 // Update the text being referred to (will create a new string child)
388 sp_tref_update_text(tref);
390 // Restore the delete connection now that we're done messing with stuff
391 tref->_delete_connection = SP_OBJECT(refRoot)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_tref_delete_self), tref));
392 }
394 }
395 }
398 /**
399 * Delete the tref object
400 */
401 static void
402 sp_tref_delete_self(SPObject */*deleted*/, SPTRef *self)
403 {
404 SP_OBJECT(self)->deleteObject();
405 }
407 /**
408 * Return the object referred to via the URI reference
409 */
410 SPObject * SPTRef::getObjectReferredTo(void)
411 {
412 SPObject *referredObject = NULL;
414 if (uriOriginalRef) {
415 referredObject = SP_OBJECT(uriOriginalRef->getObject());
416 }
418 return referredObject;
419 }
422 /**
423 * Returns true when the given tref is allowed to refer to a particular object
424 */
425 bool
426 sp_tref_reference_allowed(SPTRef *tref, SPObject *possible_ref)
427 {
428 bool allowed = false;
430 if (tref && possible_ref) {
431 if (tref != possible_ref) {
432 bool ancestor = false;
433 for (SPObject *obj = tref; obj; obj = SP_OBJECT_PARENT(obj)) {
434 if (possible_ref == obj) {
435 ancestor = true;
436 break;
437 }
438 }
439 allowed = !ancestor;
440 }
441 }
443 return allowed;
444 }
447 /**
448 * Returns true if a tref is fully contained in the confines of the given
449 * iterators and layout (or if there is no tref).
450 */
451 bool
452 sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start,
453 SPObject *end_item, Glib::ustring::iterator &end)
454 {
455 bool fully_contained = false;
457 if (start_item && end_item) {
459 // If neither the beginning or the end is a tref then we return true (whether there
460 // is a tref in the innards or not, because if there is one then it must be totally
461 // contained)
462 if (!(SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
463 && !(SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
464 fully_contained = true;
465 }
467 // Both the beginning and end are trefs; but in this case, the string iterators
468 // must be at the right places
469 else if ((SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
470 && (SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
471 if (start == SP_STRING(start_item)->string.begin()
472 && end == SP_STRING(start_item)->string.end()) {
473 fully_contained = true;
474 }
475 }
477 // If the beginning is a string that is a child of a tref, the iterator has to be
478 // at the beginning of the item
479 else if ((SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
480 && !(SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
481 if (start == SP_STRING(start_item)->string.begin()) {
482 fully_contained = true;
483 }
484 }
486 // Same, but the for the end
487 else if (!(SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
488 && (SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
489 if (end == SP_STRING(start_item)->string.end()) {
490 fully_contained = true;
491 }
492 }
493 }
495 return fully_contained;
496 }
499 void
500 sp_tref_update_text(SPTRef *tref)
501 {
502 if (tref) {
503 // Get the character data that will be used with this tref
504 Glib::ustring charData = "";
505 build_string_from_root(SP_OBJECT_REPR(tref->getObjectReferredTo()), &charData);
507 if (tref->stringChild) {
508 sp_object_detach(SP_OBJECT(tref), tref->stringChild);
509 tref->stringChild = NULL;
510 }
512 // Create the node and SPString to be the tref's child
513 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(tref));
515 Inkscape::XML::Node *newStringRepr = xml_doc->createTextNode(charData.c_str());
516 tref->stringChild = SP_OBJECT(g_object_new(sp_repr_type_lookup(newStringRepr), NULL));
518 // Add this SPString as a child of the tref
519 sp_object_attach(SP_OBJECT(tref), tref->stringChild, tref->lastChild());
520 sp_object_unref(tref->stringChild, NULL);
521 sp_object_invoke_build(tref->stringChild, SP_OBJECT(tref)->document, newStringRepr, TRUE);
523 Inkscape::GC::release(newStringRepr);
524 }
525 }
529 /**
530 * Using depth-first search, build up a string by concatenating all SPStrings
531 * found in the tree starting at the root
532 */
533 static void
534 build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString)
535 {
536 if (root && retString) {
538 // Stop and concatenate when a SPString is found
539 if (root->type() == Inkscape::XML::TEXT_NODE) {
540 *retString += (root->content());
542 debug("%s", retString->c_str());
544 // Otherwise, continue searching down the tree (with the assumption that no children nodes
545 // of a SPString are actually legal)
546 } else {
547 Inkscape::XML::Node *childNode;
548 for (childNode = root->firstChild(); childNode; childNode = childNode->next()) {
549 build_string_from_root(childNode, retString);
550 }
551 }
552 }
553 }
555 /**
556 * This function will create a new tspan element with the same attributes as
557 * the tref had and add the same text as a child. The tref is replaced in the
558 * tree with the new tspan.
559 * The code is based partially on sp_use_unlink
560 */
561 SPObject *
562 sp_tref_convert_to_tspan(SPObject *obj)
563 {
564 SPObject * new_tspan = NULL;
566 ////////////////////
567 // BASE CASE
568 ////////////////////
569 if (SP_IS_TREF(obj)) {
571 SPTRef *tref = SP_TREF(obj);
573 if (tref && tref->stringChild) {
574 Inkscape::XML::Node *tref_repr = SP_OBJECT_REPR(tref);
575 Inkscape::XML::Node *tref_parent = sp_repr_parent(tref_repr);
577 SPDocument *document = SP_OBJECT(tref)->document;
578 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
580 Inkscape::XML::Node *new_tspan_repr = xml_doc->createElement("svg:tspan");
582 // Add the new tspan element just after the current tref
583 tref_parent->addChild(new_tspan_repr, tref_repr);
584 Inkscape::GC::release(new_tspan_repr);
586 new_tspan = document->getObjectByRepr(new_tspan_repr);
588 // Create a new string child for the tspan
589 Inkscape::XML::Node *new_string_repr = SP_OBJECT_REPR(tref->stringChild)->duplicate(xml_doc);
590 new_tspan_repr->addChild(new_string_repr, NULL);
592 //SPObject * new_string_child = document->getObjectByRepr(new_string_repr);
594 // Merge style from the tref
595 SPStyle *new_tspan_sty = SP_OBJECT_STYLE(new_tspan);
596 SPStyle const *tref_sty = SP_OBJECT_STYLE(tref);
597 sp_style_merge_from_dying_parent(new_tspan_sty, tref_sty);
598 sp_style_merge_from_parent(new_tspan_sty, new_tspan->parent->style);
601 SP_OBJECT(new_tspan)->updateRepr();
603 // Hold onto our SPObject and repr for now.
604 sp_object_ref(SP_OBJECT(tref), NULL);
605 Inkscape::GC::anchor(tref_repr);
607 // Remove ourselves, not propagating delete events to avoid a
608 // chain-reaction with other elements that might reference us.
609 SP_OBJECT(tref)->deleteObject(false);
611 // Give the copy our old id and let go of our old repr.
612 new_tspan_repr->setAttribute("id", tref_repr->attribute("id"));
613 Inkscape::GC::release(tref_repr);
615 // Establish the succession and let go of our object.
616 SP_OBJECT(tref)->setSuccessor(new_tspan);
617 sp_object_unref(SP_OBJECT(tref), NULL);
618 }
619 }
620 ////////////////////
621 // RECURSIVE CASE
622 ////////////////////
623 else {
624 GSList *l = NULL;
625 for (SPObject *child = sp_object_first_child(obj) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
626 sp_object_ref (SP_OBJECT (child), obj);
627 l = g_slist_prepend (l, child);
628 }
629 l = g_slist_reverse (l);
630 while (l) {
631 SPObject *child = SP_OBJECT (l->data);
632 l = g_slist_remove (l, child);
634 // Note that there may be more than one conversion happening here, so if it's not a
635 // tref being passed into this function, the returned value can't be specifically known
636 new_tspan = sp_tref_convert_to_tspan(child);
638 sp_object_unref (SP_OBJECT (child), obj);
639 }
640 }
642 return new_tspan;
643 }
646 /*
647 Local Variables:
648 mode:c++
649 c-file-style:"stroustrup"
650 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
651 indent-tabs-mode:nil
652 fill-column:99
653 End:
654 */
655 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :