Code

1f1cb4351022319c5ee56ad360063fe801303c35
[inkscape.git] / src / sp-tref.cpp
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;
111     
112     gobject_class->finalize = sp_tref_finalize;
113     
114     SPItemClass *item_class = (SPItemClass *) tref_class;
115     
116     item_class->bbox = sp_tref_bbox;
117     item_class->description = sp_tref_description;
120 static void
121 sp_tref_init(SPTRef *tref)
123     new (&tref->attributes) TextTagAttributes;
124     
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();
129     
130     tref->_changed_connection = 
131         tref->uriOriginalRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_tref_href_changed), tref));
135 static void
136 sp_tref_finalize(GObject *obj)
138     SPTRef *tref = (SPTRef *) obj;
140     delete tref->uriOriginalRef;
142     tref->_delete_connection.~connection();
143     tref->_changed_connection.~connection();
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)
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");
165 /**
166  * Drops any allocated memory.
167  */
168 static void
169 sp_tref_release(SPObject *object)
171     SPTRef *tref = SP_TREF(object);
172     
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);
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)
193     debug("0x%p %s(%u): '%s'",object,
194             sp_attribute_name(key),key,value ? value : "<no value>");
195             
196     SPTRef *tref = SP_TREF(object);
197     
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)) {
207             
208             // Value has changed
209             
210             if ( tref->href ) {
211                 g_free(tref->href);
212                 tref->href = NULL;
213             }
214             
215             tref->href = g_strdup(value);
216             
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             }
224             
225             // No matter what happened, an update should be in order
226             SP_OBJECT(tref)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
227         }
228     
229     } else { // default
230         if (((SPObjectClass *) tref_parent_class)->set) {
231             ((SPObjectClass *) tref_parent_class)->set(object, key, value);
232         }
233     }
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)
244     debug("0x%p",object);
245     
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     }
255     
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     }
265     
268 static void
269 sp_tref_modified(SPObject *object, guint flags)
271     SPTRef *tref_obj = SP_TREF(object);
273     if (flags & SP_OBJECT_MODIFIED_FLAG) {
274         flags |= SP_OBJECT_PARENT_MODIFIED_FLAG;
275     }
276     
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     }
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)
295     debug("0x%p",object);
296     
297     SPTRef *tref = SP_TREF(object);
298     
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     }
303     
304     tref->attributes.writeTo(repr);
305     
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;
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*/)
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     }
352 static gchar *
353 sp_tref_description(SPItem *item)
355     SPTRef *tref = SP_TREF(item);
356     
357     SPObject *referred = tref->getObjectReferredTo();
358     
359     if (tref && tref->getObjectReferredTo()) {
360         char *child_desc;
361         
362         if (SP_IS_ITEM(referred)) {
363             child_desc = sp_item_description(SP_ITEM(referred));
364         } else {
365             child_desc = "";
366         }
368         char *ret = g_strdup_printf(
369                 _("<b>Cloned character data</b>%s%s"),
370                 (SP_IS_ITEM(referred) ? _(" from ") : ""),
371                 child_desc);
372         g_free(child_desc);
373         return ret;
374     } else {
375         return g_strdup(_("<b>Orphaned cloned character data</b>"));
376     }
380 /* For the sigc::connection changes (i.e. when the object being refered to changes) */
381 static void
382 sp_tref_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPTRef *tref)
384     if (tref)
385     {
386         // Save a pointer to the original object being referred to
387         SPObject *refRoot = tref->getObjectReferredTo();
388         
389         tref->_delete_connection.disconnect();
390         
391         if (tref->stringChild) {
392             sp_object_detach(SP_OBJECT(tref), tref->stringChild);
393             tref->stringChild = NULL;
394         }
395         
396         // Ensure that we are referring to a legitimate object
397         if (tref->href && refRoot && sp_tref_reference_allowed(tref, refRoot)) {
398         
399             // Update the text being referred to (will create a new string child)
400             sp_tref_update_text(tref);  
401             
402             // Restore the delete connection now that we're done messing with stuff
403             tref->_delete_connection = SP_OBJECT(refRoot)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_tref_delete_self), tref));          
404         }
405             
406     }    
410 /**
411  * Delete the tref object
412  */
413 static void
414 sp_tref_delete_self(SPObject */*deleted*/, SPTRef *self)
416     SP_OBJECT(self)->deleteObject();
419 /**
420  * Return the object referred to via the URI reference
421  */
422 SPObject * SPTRef::getObjectReferredTo(void)
424     SPObject *referredObject = NULL;
425     
426     if (uriOriginalRef) {
427         referredObject = SP_OBJECT(uriOriginalRef->getObject());
428     }
429     
430     return referredObject;   
434 /**
435  * Returns true when the given tref is allowed to refer to a particular object
436  */
437 bool
438 sp_tref_reference_allowed(SPTRef *tref, SPObject *possible_ref)
440     bool allowed = false;
441     
442     if (tref && possible_ref) {
443         if (tref != possible_ref) {
444             bool ancestor = false;
445             for (SPObject *obj = tref; obj; obj = SP_OBJECT_PARENT(obj)) {
446                 if (possible_ref == obj) {
447                     ancestor = true;
448                     break;
449                 }    
450             }
451             allowed = !ancestor;
452         }
453     }
454     
455     return allowed;
459 /**
460  * Returns true if a tref is fully contained in the confines of the given
461  * iterators and layout (or if there is no tref).
462  */
463 bool
464 sp_tref_fully_contained(SPObject *start_item, Glib::ustring::iterator &start, 
465                              SPObject *end_item, Glib::ustring::iterator &end)
467     bool fully_contained = false;
468     
469     if (start_item && end_item) {
470         
471         // If neither the beginning or the end is a tref then we return true (whether there
472         // is a tref in the innards or not, because if there is one then it must be totally
473         // contained)
474         if (!(SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
475                 && !(SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
476             fully_contained = true;
477         }
478         
479         // Both the beginning and end are trefs; but in this case, the string iterators
480         // must be at the right places
481         else if ((SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
482                 && (SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
483             if (start == SP_STRING(start_item)->string.begin()
484                     && end == SP_STRING(start_item)->string.end()) {
485                 fully_contained = true;
486             }
487         }
488         
489         // If the beginning is a string that is a child of a tref, the iterator has to be
490         // at the beginning of the item
491         else if ((SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
492                     && !(SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
493             if (start == SP_STRING(start_item)->string.begin()) {
494                 fully_contained = true;
495             }
496         }
497         
498         // Same, but the for the end
499         else if (!(SP_IS_STRING(start_item) && SP_IS_TREF(SP_OBJECT_PARENT(start_item)))
500                     && (SP_IS_STRING(end_item) && SP_IS_TREF(SP_OBJECT_PARENT(end_item)))) {
501             if (end == SP_STRING(start_item)->string.end()) {
502                 fully_contained = true;
503             }
504         }
505     }
506     
507     return fully_contained;
511 void
512 sp_tref_update_text(SPTRef *tref)
514     if (tref) {
515         // Get the character data that will be used with this tref
516         Glib::ustring charData = "";
517         build_string_from_root(SP_OBJECT_REPR(tref->getObjectReferredTo()), &charData);
518         
519         if (tref->stringChild) {
520             sp_object_detach(SP_OBJECT(tref), tref->stringChild);
521             tref->stringChild = NULL;
522         }
523         
524         // Create the node and SPString to be the tref's child
525         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(tref));
526         
527         Inkscape::XML::Node *newStringRepr = xml_doc->createTextNode(charData.c_str());
528         tref->stringChild = SP_OBJECT(g_object_new(sp_repr_type_lookup(newStringRepr), NULL));
529         
530         // Add this SPString as a child of the tref
531         sp_object_attach(SP_OBJECT(tref), tref->stringChild, tref->lastChild());
532         sp_object_unref(tref->stringChild, NULL);
533         sp_object_invoke_build(tref->stringChild, SP_OBJECT(tref)->document, newStringRepr, TRUE);
534         
535         Inkscape::GC::release(newStringRepr);
536     }
541 /**
542  * Using depth-first search, build up a string by concatenating all SPStrings
543  * found in the tree starting at the root
544  */
545 static void
546 build_string_from_root(Inkscape::XML::Node *root, Glib::ustring *retString)
548     if (root && retString) {
549         
550         // Stop and concatenate when a SPString is found
551         if (root->type() == Inkscape::XML::TEXT_NODE) {
552             *retString += (root->content());
553             
554             debug("%s", retString->c_str());
555         
556         // Otherwise, continue searching down the tree (with the assumption that no children nodes
557         // of a SPString are actually legal)
558         } else {
559             Inkscape::XML::Node *childNode;
560             for (childNode = root->firstChild(); childNode; childNode = childNode->next()) {
561                 build_string_from_root(childNode, retString);
562             }
563         }
564     } 
567 /**
568  * This function will create a new tspan element with the same attributes as
569  * the tref had and add the same text as a child.  The tref is replaced in the
570  * tree with the new tspan.
571  * The code is based partially on sp_use_unlink
572  */
573 SPObject *
574 sp_tref_convert_to_tspan(SPObject *obj)
576     SPObject * new_tspan = NULL;
577     
578     ////////////////////
579     // BASE CASE
580     ////////////////////
581     if (SP_IS_TREF(obj)) {
582         
583         SPTRef *tref = SP_TREF(obj);
584     
585         if (tref && tref->stringChild) {
586             Inkscape::XML::Node *tref_repr = SP_OBJECT_REPR(tref);
587             Inkscape::XML::Node *tref_parent = sp_repr_parent(tref_repr);
588             
589             SPDocument *document = SP_OBJECT(tref)->document;
590             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
591             
592             Inkscape::XML::Node *new_tspan_repr = xml_doc->createElement("svg:tspan");
593             
594             // Add the new tspan element just after the current tref
595             tref_parent->addChild(new_tspan_repr, tref_repr);
596             Inkscape::GC::release(new_tspan_repr);
597             
598             new_tspan = document->getObjectByRepr(new_tspan_repr);
599             
600             // Create a new string child for the tspan
601             Inkscape::XML::Node *new_string_repr = SP_OBJECT_REPR(tref->stringChild)->duplicate(xml_doc);
602             new_tspan_repr->addChild(new_string_repr, NULL);    
603             
604             //SPObject * new_string_child = document->getObjectByRepr(new_string_repr);
605     
606             // Merge style from the tref
607             SPStyle *new_tspan_sty = SP_OBJECT_STYLE(new_tspan);
608             SPStyle const *tref_sty = SP_OBJECT_STYLE(tref);
609             sp_style_merge_from_dying_parent(new_tspan_sty, tref_sty);
610             sp_style_merge_from_parent(new_tspan_sty, new_tspan->parent->style);
611             
612             
613             SP_OBJECT(new_tspan)->updateRepr();
614             
615             // Hold onto our SPObject and repr for now.
616             sp_object_ref(SP_OBJECT(tref), NULL);
617             Inkscape::GC::anchor(tref_repr);
618             
619             // Remove ourselves, not propagating delete events to avoid a
620             // chain-reaction with other elements that might reference us.
621             SP_OBJECT(tref)->deleteObject(false);
622             
623             // Give the copy our old id and let go of our old repr.
624             new_tspan_repr->setAttribute("id", tref_repr->attribute("id"));
625             Inkscape::GC::release(tref_repr);
626             
627             // Establish the succession and let go of our object.
628             SP_OBJECT(tref)->setSuccessor(new_tspan);
629             sp_object_unref(SP_OBJECT(tref), NULL);
630         }
631     }
632     ////////////////////
633     // RECURSIVE CASE
634     ////////////////////
635     else {
636         GSList *l = NULL;
637         for (SPObject *child = sp_object_first_child(obj) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
638             sp_object_ref (SP_OBJECT (child), obj);
639             l = g_slist_prepend (l, child);
640         }
641         l = g_slist_reverse (l);
642         while (l) {
643             SPObject *child = SP_OBJECT (l->data);
644             l = g_slist_remove (l, child);
645             
646             // Note that there may be more than one conversion happening here, so if it's not a
647             // tref being passed into this function, the returned value can't be specifically known
648             new_tspan = sp_tref_convert_to_tspan(child);
649             
650             sp_object_unref (SP_OBJECT (child), obj);
651         }
652     }
653     
654     return new_tspan;
658 /*
659   Local Variables:
660   mode:c++
661   c-file-style:"stroustrup"
662   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
663   indent-tabs-mode:nil
664   fill-column:99
665   End:
666 */
667 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :