Code

65d8b997c729130a31a37a8b2130f05e51052a29
[inkscape.git] / src / sp-item.cpp
1 #define __SP_ITEM_C__
3 /** \file
4  * Base class for visual SVG elements
5  */
6 /*
7  * Authors:
8  *   Lauris Kaplinski <lauris@kaplinski.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *   Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
11  *
12  * Copyright (C) 2001-2006 authors
13  * Copyright (C) 2001 Ximian, Inc.
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 /** \class SPItem
19  *
20  * SPItem is an abstract base class for all graphic (visible) SVG nodes. It
21  * is a subclass of SPObject, with great deal of specific functionality.
22  */
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
29 #include "sp-item.h"
30 #include "svg/svg.h"
31 #include "print.h"
32 #include "display/nr-arena.h"
33 #include "display/nr-arena-item.h"
34 #include "attributes.h"
35 #include "document.h"
36 #include "uri.h"
37 #include "inkscape.h"
38 #include "desktop-handles.h"
40 #include "style.h"
41 #include <glibmm/i18n.h>
42 #include "sp-root.h"
43 #include "sp-clippath.h"
44 #include "sp-mask.h"
45 #include "sp-rect.h"
46 #include "sp-use.h"
47 #include "sp-text.h"
48 #include "sp-item-rm-unsatisfied-cns.h"
49 #include "sp-pattern.h"
50 #include "sp-switch.h"
51 #include "gradient-chemistry.h"
52 #include "prefs-utils.h"
53 #include "conn-avoid-ref.h"
54 #include "conditions.h"
55 #include "sp-filter-reference.h"
56 #include "sp-guide.h"
58 #include "libnr/nr-matrix-div.h"
59 #include "libnr/nr-matrix-fns.h"
60 #include "libnr/nr-matrix-scale-ops.h"
61 #include "libnr/nr-matrix-translate-ops.h"
62 #include "libnr/nr-scale-translate-ops.h"
63 #include "libnr/nr-translate-scale-ops.h"
64 #include "libnr/nr-convert2geom.h"
65 #include "algorithms/find-last-if.h"
66 #include "util/reverse-list.h"
68 #include "xml/repr.h"
69 #include "extract-uri.h"
71 #include "live_effects/lpeobject.h"
72 #include "live_effects/effect.h"
74 #define noSP_ITEM_DEBUG_IDLE
76 static void sp_item_class_init(SPItemClass *klass);
77 static void sp_item_init(SPItem *item);
79 static void sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
80 static void sp_item_release(SPObject *object);
81 static void sp_item_set(SPObject *object, unsigned key, gchar const *value);
82 static void sp_item_update(SPObject *object, SPCtx *ctx, guint flags);
83 static Inkscape::XML::Node *sp_item_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
85 static gchar *sp_item_private_description(SPItem *item);
86 static void sp_item_private_snappoints(SPItem const *item, SnapPointsIter p);
88 static SPItemView *sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem);
89 static SPItemView *sp_item_view_list_remove(SPItemView *list, SPItemView *view);
91 static SPObjectClass *parent_class;
93 static void clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
94 static void mask_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
96 /**
97  * Registers SPItem class and returns its type number.
98  */
99 GType
100 sp_item_get_type(void)
102     static GType type = 0;
103     if (!type) {
104         GTypeInfo info = {
105             sizeof(SPItemClass),
106             NULL, NULL,
107             (GClassInitFunc) sp_item_class_init,
108             NULL, NULL,
109             sizeof(SPItem),
110             16,
111             (GInstanceInitFunc) sp_item_init,
112             NULL,   /* value_table */
113         };
114         type = g_type_register_static(SP_TYPE_OBJECT, "SPItem", &info, (GTypeFlags)0);
115     }
116     return type;
119 /**
120  * SPItem vtable initialization.
121  */
122 static void
123 sp_item_class_init(SPItemClass *klass)
125     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
127     parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT);
129     sp_object_class->build = sp_item_build;
130     sp_object_class->release = sp_item_release;
131     sp_object_class->set = sp_item_set;
132     sp_object_class->update = sp_item_update;
133     sp_object_class->write = sp_item_write;
135     klass->description = sp_item_private_description;
136     klass->snappoints = sp_item_private_snappoints;
139 /**
140  * Callback for SPItem object initialization.
141  */
142 static void
143 sp_item_init(SPItem *item)
145     item->init();
148 void SPItem::init() {
149     this->sensitive = TRUE;
151     this->transform_center_x = 0;
152     this->transform_center_y = 0;
154     this->_is_evaluated = true;
155     this->_evaluated_status = StatusUnknown;
157     this->transform = NR::identity();
159     this->display = NULL;
161     this->clip_ref = new SPClipPathReference(this);
162                 sigc::signal<void, SPObject *, SPObject *> cs1=this->clip_ref->changedSignal();
163                 sigc::slot2<void,SPObject*, SPObject *> sl1=sigc::bind(sigc::ptr_fun(clip_ref_changed), this);
164     _clip_ref_connection = cs1.connect(sl1);
166     this->mask_ref = new SPMaskReference(this);
167                 sigc::signal<void, SPObject *, SPObject *> cs2=this->mask_ref->changedSignal();
168                 sigc::slot2<void,SPObject*, SPObject *> sl2=sigc::bind(sigc::ptr_fun(mask_ref_changed), this);
169     _mask_ref_connection = cs2.connect(sl2);
171     this->avoidRef = new SPAvoidRef(this);
173     new (&this->_transformed_signal) sigc::signal<void, NR::Matrix const *, SPItem *>();
176 bool SPItem::isVisibleAndUnlocked() const {
177     return (!isHidden() && !isLocked());
180 bool SPItem::isVisibleAndUnlocked(unsigned display_key) const {
181     return (!isHidden(display_key) && !isLocked());
184 bool SPItem::isLocked() const {
185     for (SPObject *o = SP_OBJECT(this); o != NULL; o = SP_OBJECT_PARENT(o)) {
186         if (SP_IS_ITEM(o) && !(SP_ITEM(o)->sensitive))
187             return true;
188     }
189     return false;
192 void SPItem::setLocked(bool locked) {
193     SP_OBJECT_REPR(this)->setAttribute("sodipodi:insensitive",
194                      ( locked ? "1" : NULL ));
195     updateRepr();
198 bool SPItem::isHidden() const {
199     if (!isEvaluated())
200         return true;
201     return style->display.computed == SP_CSS_DISPLAY_NONE;
204 void SPItem::setHidden(bool hide) {
205     style->display.set = TRUE;
206     style->display.value = ( hide ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
207     style->display.computed = style->display.value;
208     style->display.inherit = FALSE;
209     updateRepr();
212 bool SPItem::isHidden(unsigned display_key) const {
213     if (!isEvaluated())
214         return true;
215     for ( SPItemView *view(display) ; view ; view = view->next ) {
216         if ( view->key == display_key ) {
217             g_assert(view->arenaitem != NULL);
218             for ( NRArenaItem *arenaitem = view->arenaitem ;
219                   arenaitem ; arenaitem = arenaitem->parent )
220             {
221                 if (!arenaitem->visible) {
222                     return true;
223                 }
224             }
225             return false;
226         }
227     }
228     return true;
231 void SPItem::setEvaluated(bool evaluated) {
232     _is_evaluated = evaluated;
233     _evaluated_status = StatusSet;
236 void SPItem::resetEvaluated() {
237     if ( StatusCalculated == _evaluated_status ) {
238         _evaluated_status = StatusUnknown;
239         bool oldValue = _is_evaluated;
240         if ( oldValue != isEvaluated() ) {
241             requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
242         }
243     } if ( StatusSet == _evaluated_status ) {
244         SPObject const *const parent = SP_OBJECT_PARENT(this);
245         if (SP_IS_SWITCH(parent)) {
246             SP_SWITCH(parent)->resetChildEvaluated();
247         }
248     }
251 bool SPItem::isEvaluated() const {
252     if ( StatusUnknown == _evaluated_status ) {
253         _is_evaluated = sp_item_evaluate(this);
254         _evaluated_status = StatusCalculated;
255     }
256     return _is_evaluated;
259 /**
260  * Returns something suitable for the `Hide' checkbox in the Object Properties dialog box.
261  *  Corresponds to setExplicitlyHidden.
262  */
263 bool
264 SPItem::isExplicitlyHidden() const
266     return (this->style->display.set
267             && this->style->display.value == SP_CSS_DISPLAY_NONE);
270 /**
271  * Sets the display CSS property to `hidden' if \a val is true,
272  * otherwise makes it unset
273  */
274 void
275 SPItem::setExplicitlyHidden(bool const val) {
276     this->style->display.set = val;
277     this->style->display.value = ( val ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
278     this->style->display.computed = this->style->display.value;
279     this->updateRepr();
282 /**
283  * Sets the transform_center_x and transform_center_y properties to retain the rotation centre
284  */
285 void
286 SPItem::setCenter(NR::Point object_centre) {
287     NR::Maybe<NR::Rect> bbox = getBounds(sp_item_i2d_affine(this));
288     if (bbox) {
289         transform_center_x = object_centre[NR::X] - bbox->midpoint()[NR::X];
290         if (fabs(transform_center_x) < 1e-5) // rounding error
291             transform_center_x = 0;
292         transform_center_y = object_centre[NR::Y] - bbox->midpoint()[NR::Y];
293         if (fabs(transform_center_y) < 1e-5) // rounding error
294             transform_center_y = 0;
295     }
298 void
299 SPItem::unsetCenter() {
300     transform_center_x = 0;
301     transform_center_y = 0;
304 bool SPItem::isCenterSet() {
305     return (transform_center_x != 0 || transform_center_y != 0);
308 NR::Point SPItem::getCenter() const {
309     NR::Maybe<NR::Rect> bbox = getBounds(sp_item_i2d_affine(this));
310     if (bbox) {
311         return bbox->midpoint() + NR::Point (this->transform_center_x, this->transform_center_y);
312     } else {
313         return NR::Point (0, 0); // something's wrong!
314     }
318 namespace {
320 bool is_item(SPObject const &object) {
321     return SP_IS_ITEM(&object);
326 void SPItem::raiseToTop() {
327     using Inkscape::Algorithms::find_last_if;
329     SPObject *topmost=find_last_if<SPObject::SiblingIterator>(
330         SP_OBJECT_NEXT(this), NULL, &is_item
331     );
332     if (topmost) {
333         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
334         sp_repr_parent(repr)->changeOrder(repr, SP_OBJECT_REPR(topmost));
335     }
338 void SPItem::raiseOne() {
339     SPObject *next_higher=std::find_if<SPObject::SiblingIterator>(
340         SP_OBJECT_NEXT(this), NULL, &is_item
341     );
342     if (next_higher) {
343         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
344         Inkscape::XML::Node *ref=SP_OBJECT_REPR(next_higher);
345         sp_repr_parent(repr)->changeOrder(repr, ref);
346     }
349 void SPItem::lowerOne() {
350     using Inkscape::Util::MutableList;
351     using Inkscape::Util::reverse_list;
353     MutableList<SPObject &> next_lower=std::find_if(
354         reverse_list<SPObject::SiblingIterator>(
355             SP_OBJECT_PARENT(this)->firstChild(), this
356         ),
357         MutableList<SPObject &>(),
358         &is_item
359     );
360     if (next_lower) {
361         ++next_lower;
362         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
363         Inkscape::XML::Node *ref=( next_lower ? SP_OBJECT_REPR(&*next_lower) : NULL );
364         sp_repr_parent(repr)->changeOrder(repr, ref);
365     }
368 void SPItem::lowerToBottom() {
369     using Inkscape::Algorithms::find_last_if;
370     using Inkscape::Util::MutableList;
371     using Inkscape::Util::reverse_list;
373     MutableList<SPObject &> bottom=find_last_if(
374         reverse_list<SPObject::SiblingIterator>(
375             SP_OBJECT_PARENT(this)->firstChild(), this
376         ),
377         MutableList<SPObject &>(),
378         &is_item
379     );
380     if (bottom) {
381         ++bottom;
382         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
383         Inkscape::XML::Node *ref=( bottom ? SP_OBJECT_REPR(&*bottom) : NULL );
384         sp_repr_parent(repr)->changeOrder(repr, ref);
385     }
388 static void
389 sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
391     sp_object_read_attr(object, "style");
392     sp_object_read_attr(object, "transform");
393     sp_object_read_attr(object, "clip-path");
394     sp_object_read_attr(object, "mask");
395     sp_object_read_attr(object, "sodipodi:insensitive");
396     sp_object_read_attr(object, "sodipodi:nonprintable");
397     sp_object_read_attr(object, "inkscape:transform-center-x");
398     sp_object_read_attr(object, "inkscape:transform-center-y");
399     sp_object_read_attr(object, "inkscape:connector-avoid");
401     if (((SPObjectClass *) (parent_class))->build) {
402         (* ((SPObjectClass *) (parent_class))->build)(object, document, repr);
403     }
406 static void
407 sp_item_release(SPObject *object)
409     SPItem *item = (SPItem *) object;
411     item->_clip_ref_connection.disconnect();
412     item->_mask_ref_connection.disconnect();
414     // Note: do this here before the clip_ref is deleted, since calling
415     // sp_document_ensure_up_to_date for triggered routing may reference
416     // the deleted clip_ref.
417     if (item->avoidRef) {
418         delete item->avoidRef;
419         item->avoidRef = NULL;
420     }
422     if (item->clip_ref) {
423         item->clip_ref->detach();
424         delete item->clip_ref;
425         item->clip_ref = NULL;
426     }
428     if (item->mask_ref) {
429         item->mask_ref->detach();
430         delete item->mask_ref;
431         item->mask_ref = NULL;
432     }
434     if (((SPObjectClass *) (parent_class))->release) {
435         ((SPObjectClass *) parent_class)->release(object);
436     }
438     while (item->display) {
439         nr_arena_item_unparent(item->display->arenaitem);
440         item->display = sp_item_view_list_remove(item->display, item->display);
441     }
443     item->_transformed_signal.~signal();
446 static void
447 sp_item_set(SPObject *object, unsigned key, gchar const *value)
449     SPItem *item = (SPItem *) object;
451     switch (key) {
452         case SP_ATTR_TRANSFORM: {
453             NR::Matrix t;
454             if (value && sp_svg_transform_read(value, &t)) {
455                 sp_item_set_item_transform(item, t);
456             } else {
457                 sp_item_set_item_transform(item, NR::identity());
458             }
459             break;
460         }
461         case SP_PROP_CLIP_PATH: {
462             gchar *uri = extract_uri(value);
463             if (uri) {
464                 try {
465                     item->clip_ref->attach(Inkscape::URI(uri));
466                 } catch (Inkscape::BadURIException &e) {
467                     g_warning("%s", e.what());
468                     item->clip_ref->detach();
469                 }
470                 g_free(uri);
471             } else {
472                 item->clip_ref->detach();
473             }
475             break;
476         }
477         case SP_PROP_MASK: {
478             gchar *uri = extract_uri(value);
479             if (uri) {
480                 try {
481                     item->mask_ref->attach(Inkscape::URI(uri));
482                 } catch (Inkscape::BadURIException &e) {
483                     g_warning("%s", e.what());
484                     item->mask_ref->detach();
485                 }
486                 g_free(uri);
487             } else {
488                 item->mask_ref->detach();
489             }
491             break;
492         }
493         case SP_ATTR_SODIPODI_INSENSITIVE:
494             item->sensitive = !value;
495             for (SPItemView *v = item->display; v != NULL; v = v->next) {
496                 nr_arena_item_set_sensitive(v->arenaitem, item->sensitive);
497             }
498             break;
499         case SP_ATTR_CONNECTOR_AVOID:
500             item->avoidRef->setAvoid(value);
501             break;
502         case SP_ATTR_TRANSFORM_CENTER_X:
503             if (value) {
504                 item->transform_center_x = g_strtod(value, NULL);
505             } else {
506                 item->transform_center_x = 0;
507             }
508             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
509             break;
510         case SP_ATTR_TRANSFORM_CENTER_Y:
511             if (value) {
512                 item->transform_center_y = g_strtod(value, NULL);
513             } else {
514                 item->transform_center_y = 0;
515             }
516             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
517             break;
518         case SP_PROP_SYSTEM_LANGUAGE:
519         case SP_PROP_REQUIRED_FEATURES:
520         case SP_PROP_REQUIRED_EXTENSIONS:
521             {
522                 item->resetEvaluated();
523                 // pass to default handler
524             }
525         default:
526             if (SP_ATTRIBUTE_IS_CSS(key)) {
527                 sp_style_read_from_object(object->style, object);
528                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
529             } else {
530                 if (((SPObjectClass *) (parent_class))->set) {
531                     (* ((SPObjectClass *) (parent_class))->set)(object, key, value);
532                 }
533             }
534             break;
535     }
538 static void
539 clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item)
541     if (old_clip) {
542         SPItemView *v;
543         /* Hide clippath */
544         for (v = item->display; v != NULL; v = v->next) {
545             sp_clippath_hide(SP_CLIPPATH(old_clip), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
546             nr_arena_item_set_clip(v->arenaitem, NULL);
547         }
548     }
549     if (SP_IS_CLIPPATH(clip)) {
550         NRRect bbox;
551         sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
552         for (SPItemView *v = item->display; v != NULL; v = v->next) {
553             if (!v->arenaitem->key) {
554                 NR_ARENA_ITEM_SET_KEY(v->arenaitem, sp_item_display_key_new(3));
555             }
556             NRArenaItem *ai = sp_clippath_show(SP_CLIPPATH(clip),
557                                                NR_ARENA_ITEM_ARENA(v->arenaitem),
558                                                NR_ARENA_ITEM_GET_KEY(v->arenaitem));
559             nr_arena_item_set_clip(v->arenaitem, ai);
560             nr_arena_item_unref(ai);
561             sp_clippath_set_bbox(SP_CLIPPATH(clip), NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
562             SP_OBJECT(clip)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
563         }
564     }
567 static void
568 mask_ref_changed(SPObject *old_mask, SPObject *mask, SPItem *item)
570     if (old_mask) {
571         /* Hide mask */
572         for (SPItemView *v = item->display; v != NULL; v = v->next) {
573             sp_mask_hide(SP_MASK(old_mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
574             nr_arena_item_set_mask(v->arenaitem, NULL);
575         }
576     }
577     if (SP_IS_MASK(mask)) {
578         NRRect bbox;
579         sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
580         for (SPItemView *v = item->display; v != NULL; v = v->next) {
581             if (!v->arenaitem->key) {
582                 NR_ARENA_ITEM_SET_KEY(v->arenaitem, sp_item_display_key_new(3));
583             }
584             NRArenaItem *ai = sp_mask_show(SP_MASK(mask),
585                                            NR_ARENA_ITEM_ARENA(v->arenaitem),
586                                            NR_ARENA_ITEM_GET_KEY(v->arenaitem));
587             nr_arena_item_set_mask(v->arenaitem, ai);
588             nr_arena_item_unref(ai);
589             sp_mask_set_bbox(SP_MASK(mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
590             SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
591         }
592     }
595 static void
596 sp_item_update(SPObject *object, SPCtx *ctx, guint flags)
598     SPItem *item = SP_ITEM(object);
600     if (((SPObjectClass *) (parent_class))->update)
601         (* ((SPObjectClass *) (parent_class))->update)(object, ctx, flags);
603     if (flags & (SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG)) {
604         if (flags & SP_OBJECT_MODIFIED_FLAG) {
605             for (SPItemView *v = item->display; v != NULL; v = v->next) {
606                 nr_arena_item_set_transform(v->arenaitem, item->transform);
607             }
608         }
610         SPClipPath *clip_path = item->clip_ref ? item->clip_ref->getObject() : NULL;
611         SPMask *mask = item->mask_ref ? item->mask_ref->getObject() : NULL;
613         if ( clip_path || mask ) {
614             NRRect bbox;
615             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
616             if (clip_path) {
617                 for (SPItemView *v = item->display; v != NULL; v = v->next) {
618                     sp_clippath_set_bbox(clip_path, NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
619                 }
620             }
621             if (mask) {
622                 for (SPItemView *v = item->display; v != NULL; v = v->next) {
623                     sp_mask_set_bbox(mask, NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
624                 }
625             }
626         }
628         if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
629             for (SPItemView *v = item->display; v != NULL; v = v->next) {
630                 nr_arena_item_set_opacity(v->arenaitem, SP_SCALE24_TO_FLOAT(object->style->opacity.value));
631                 nr_arena_item_set_visible(v->arenaitem, !item->isHidden());
632             }
633         }
634     }
636     /* Update bounding box data used by filters */
637     if (item->style->filter.set && item->display) {
638         NRRect item_bbox;
639         sp_item_invoke_bbox(item, &item_bbox, NR::identity(), TRUE, SPItem::GEOMETRIC_BBOX);
640         NR::Maybe<NR::Rect> i_bbox = item_bbox;
642         SPItemView *itemview = item->display;
643         do {
644             if (itemview->arenaitem)
645                 nr_arena_item_set_item_bbox(itemview->arenaitem, i_bbox);
646         } while ( (itemview = itemview->next) );
647     }
649     // Update libavoid with item geometry (for connector routing).
650     if (item->avoidRef)
651         item->avoidRef->handleSettingChange();
654 static Inkscape::XML::Node *
655 sp_item_write(SPObject *const object, Inkscape::XML::Node *repr, guint flags)
657     SPItem *item = SP_ITEM(object);
659     gchar *c = sp_svg_transform_write(item->transform);
660     repr->setAttribute("transform", c);
661     g_free(c);
663     if (flags & SP_OBJECT_WRITE_EXT) {
664         repr->setAttribute("sodipodi:insensitive", ( item->sensitive ? NULL : "true" ));
665         if (item->transform_center_x != 0)
666             sp_repr_set_svg_double (repr, "inkscape:transform-center-x", item->transform_center_x);
667         else
668             repr->setAttribute ("inkscape:transform-center-x", NULL);
669         if (item->transform_center_y != 0)
670             sp_repr_set_svg_double (repr, "inkscape:transform-center-y", item->transform_center_y);
671         else
672             repr->setAttribute ("inkscape:transform-center-y", NULL);
673     }
675     if (item->clip_ref->getObject()) {
676         const gchar *value = g_strdup_printf ("url(%s)", item->clip_ref->getURI()->toString());
677         repr->setAttribute ("clip-path", value);
678         g_free ((void *) value);
679     }
680     if (item->mask_ref->getObject()) {
681         const gchar *value = g_strdup_printf ("url(%s)", item->mask_ref->getURI()->toString());
682         repr->setAttribute ("mask", value);
683         g_free ((void *) value);
684     }
686     if (((SPObjectClass *) (parent_class))->write) {
687         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
688     }
690     return repr;
693 NR::Maybe<NR::Rect> SPItem::getBounds(NR::Matrix const &transform,
694                                       SPItem::BBoxType type,
695                                       unsigned int /*dkey*/) const
697     NR::Maybe<NR::Rect> r = NR::Nothing();
698     sp_item_invoke_bbox_full(this, &r, transform, type, TRUE);
699     return r;
702 void
703 sp_item_invoke_bbox(SPItem const *item, NR::Maybe<NR::Rect> *bbox, NR::Matrix const &transform, unsigned const clear, SPItem::BBoxType type)
705     sp_item_invoke_bbox_full(item, bbox, transform, type, clear);
708 // DEPRECATED to phase out the use of NRRect in favor of NR::Maybe<NR::Rect>
709 void
710 sp_item_invoke_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const clear, SPItem::BBoxType type)
712     sp_item_invoke_bbox_full(item, bbox, transform, type, clear);
715 /** Calls \a item's subclass' bounding box method; clips it by the bbox of clippath, if any; and
716  * unions the resulting bbox with \a bbox. If \a clear is true, empties \a bbox first. Passes the
717  * transform and the flags to the actual bbox methods. Note that many of subclasses (e.g. groups,
718  * clones), in turn, call this function in their bbox methods. */
719 void
720 sp_item_invoke_bbox_full(SPItem const *item, NR::Maybe<NR::Rect> *bbox, NR::Matrix const &transform, unsigned const flags, unsigned const clear)
722     g_assert(item != NULL);
723     g_assert(SP_IS_ITEM(item));
724     g_assert(bbox != NULL);
726     if (clear) {
727         *bbox = NR::Nothing();
728     }
730     // TODO: replace NRRect by NR::Rect, for all SPItemClasses, and for SP_CLIPPATH
732     NRRect temp_bbox;
733     temp_bbox.x0 = temp_bbox.y0 = NR_HUGE;
734     temp_bbox.x1 = temp_bbox.y1 = -NR_HUGE;
736     // call the subclass method
737     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox) {
738         ((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox(item, &temp_bbox, transform, flags);
739     }
741     // unless this is geometric bbox, crop the bbox by clip path, if any
742     if ((SPItem::BBoxType) flags != SPItem::GEOMETRIC_BBOX && item->clip_ref->getObject()) {
743         NRRect b;
744         sp_clippath_get_bbox(SP_CLIPPATH(item->clip_ref->getObject()), &b, transform, flags);
745         nr_rect_d_intersect (&temp_bbox, &temp_bbox, &b);
746     }
748     if (temp_bbox.x0 > temp_bbox.x1 || temp_bbox.y0 > temp_bbox.y1) {
749         // We'll assume here that when x0 > x1 or y0 > y1, the bbox is "nothing"
750         // However it has never been explicitely defined this way for NRRects
751         // (as opposed to NR::Maybe<NR::Rect>)
752         *bbox = NR::Nothing();
753         return;
754     }
756     if (temp_bbox.x0 == temp_bbox.y0 == NR_HUGE && temp_bbox.x1 == temp_bbox.y1 == -NR_HUGE) {
757         // The bbox hasn't been touched by the SPItemClass' bbox method
758         // or it has explicitely been set to be like this (e.g. in sp_shape_bbox)
759         *bbox = NR::Nothing();
760         return;
761     }
763     // Do not use temp_bbox.upgrade() here, because it uses a test that returns NR::Nothing
764     // for any rectangle with zero area. The geometrical bbox of for example a vertical line
765     // would therefore be translated into NR::Nothing (see bug https://bugs.launchpad.net/inkscape/+bug/168684)
766     NR::Maybe<NR::Rect> temp_bbox_new = NR::Rect(NR::Point(temp_bbox.x0, temp_bbox.y0), NR::Point(temp_bbox.x1, temp_bbox.y1));
768     *bbox = NR::union_bounds(*bbox, temp_bbox_new);
771 // DEPRECATED to phase out the use of NRRect in favor of NR::Maybe<NR::Rect>
772 /** Calls \a item's subclass' bounding box method; clips it by the bbox of clippath, if any; and
773  * unions the resulting bbox with \a bbox. If \a clear is true, empties \a bbox first. Passes the
774  * transform and the flags to the actual bbox methods. Note that many of subclasses (e.g. groups,
775  * clones), in turn, call this function in their bbox methods. */
776 void
777 sp_item_invoke_bbox_full(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags, unsigned const clear)
779     g_assert(item != NULL);
780     g_assert(SP_IS_ITEM(item));
781     g_assert(bbox != NULL);
783     if (clear) {
784         bbox->x0 = bbox->y0 = 1e18;
785         bbox->x1 = bbox->y1 = -1e18;
786     }
788     NRRect this_bbox;
789     this_bbox.x0 = this_bbox.y0 = 1e18;
790     this_bbox.x1 = this_bbox.y1 = -1e18;
792     // call the subclass method
793     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox) {
794         ((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox(item, &this_bbox, transform, flags);
795     }
797     // unless this is geometric bbox, crop the bbox by clip path, if any
798     if ((SPItem::BBoxType) flags != SPItem::GEOMETRIC_BBOX && item->clip_ref->getObject()) {
799         NRRect b;
800         sp_clippath_get_bbox(SP_CLIPPATH(item->clip_ref->getObject()), &b, transform, flags);
801         nr_rect_d_intersect (&this_bbox, &this_bbox, &b);
802     }
804     // if non-empty (with some tolerance - ?) union this_bbox with the bbox we've got passed
805     if ( fabs(this_bbox.x1-this_bbox.x0) > -0.00001 && fabs(this_bbox.y1-this_bbox.y0) > -0.00001 ) {
806         nr_rect_d_union (bbox, bbox, &this_bbox);
807     }
810 unsigned sp_item_pos_in_parent(SPItem *item)
812     g_assert(item != NULL);
813     g_assert(SP_IS_ITEM(item));
815     SPObject *parent = SP_OBJECT_PARENT(item);
816     g_assert(parent != NULL);
817     g_assert(SP_IS_OBJECT(parent));
819     SPObject *object = SP_OBJECT(item);
821     unsigned pos=0;
822     for ( SPObject *iter = sp_object_first_child(parent) ; iter ; iter = SP_OBJECT_NEXT(iter)) {
823         if ( iter == object ) {
824             return pos;
825         }
826         if (SP_IS_ITEM(iter)) {
827             pos++;
828         }
829     }
831     g_assert_not_reached();
832     return 0;
835 void
836 sp_item_bbox_desktop(SPItem *item, NRRect *bbox, SPItem::BBoxType type)
838     g_assert(item != NULL);
839     g_assert(SP_IS_ITEM(item));
840     g_assert(bbox != NULL);
842     sp_item_invoke_bbox(item, bbox, sp_item_i2d_affine(item), TRUE, type);
845 NR::Maybe<NR::Rect> sp_item_bbox_desktop(SPItem *item, SPItem::BBoxType type)
847     NR::Maybe<NR::Rect> rect = NR::Nothing();
848     sp_item_invoke_bbox(item, &rect, sp_item_i2d_affine(item), TRUE, type);
849     return rect;
852 static void sp_item_private_snappoints(SPItem const *item, SnapPointsIter p)
854     NR::Maybe<NR::Rect> bbox = item->getBounds(sp_item_i2d_affine(item));
855     /* Just the corners of the bounding box suffices given that we don't yet
856        support angled guide lines. */
858     if (bbox) {
859         NR::Point p1, p2;
860         p1 = bbox->min();
861         p2 = bbox->max();
862         *p = p1;
863         *p = NR::Point(p1[NR::X], p2[NR::Y]);
864         *p = p2;
865         *p = NR::Point(p1[NR::Y], p2[NR::X]);
866     }
869 void sp_item_snappoints(SPItem const *item, bool includeItemCenter, SnapPointsIter p)
871     g_assert (item != NULL);
872     g_assert (SP_IS_ITEM(item));
874     SPItemClass const &item_class = *(SPItemClass const *) G_OBJECT_GET_CLASS(item);
875     if (item_class.snappoints) {
876         item_class.snappoints(item, p);
877     }
879     if (includeItemCenter) {
880         *p = item->getCenter();
881     }
884 void
885 sp_item_invoke_print(SPItem *item, SPPrintContext *ctx)
887     if (!item->isHidden()) {
888         if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->print) {
889             if (!item->transform.test_identity()
890                 || SP_OBJECT_STYLE(item)->opacity.value != SP_SCALE24_MAX)
891             {
892                 sp_print_bind(ctx, item->transform, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value));
893                 ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx);
894                 sp_print_release(ctx);
895             } else {
896                 ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx);
897             }
898         }
899     }
902 static gchar *
903 sp_item_private_description(SPItem */*item*/)
905     return g_strdup(_("Object"));
908 /**
909  * Returns a string suitable for status bar, formatted in pango markup language.
910  *
911  * Must be freed by caller.
912  */
913 gchar *
914 sp_item_description(SPItem *item)
916     g_assert(item != NULL);
917     g_assert(SP_IS_ITEM(item));
919     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->description) {
920         gchar *s = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->description(item);
921         if (s && item->clip_ref->getObject()) {
922             gchar *snew = g_strdup_printf (_("%s; <i>clipped</i>"), s);
923             g_free (s);
924             s = snew;
925         }
926         if (s && item->mask_ref->getObject()) {
927             gchar *snew = g_strdup_printf (_("%s; <i>masked</i>"), s);
928             g_free (s);
929             s = snew;
930         }
931         return s;
932     }
934     g_assert_not_reached();
935     return NULL;
938 /**
939  * Allocates unique integer keys.
940  * \param numkeys Number of keys required.
941  * \return First allocated key; hence if the returned key is n
942  * you can use n, n + 1, ..., n + (numkeys - 1)
943  */
944 unsigned
945 sp_item_display_key_new(unsigned numkeys)
947     static unsigned dkey = 0;
949     dkey += numkeys;
951     return dkey - numkeys;
954 NRArenaItem *
955 sp_item_invoke_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags)
957     g_assert(item != NULL);
958     g_assert(SP_IS_ITEM(item));
959     g_assert(arena != NULL);
960     g_assert(NR_IS_ARENA(arena));
962     NRArenaItem *ai = NULL;
963     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->show) {
964         ai = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->show(item, arena, key, flags);
965     }
967     if (ai != NULL) {
968         item->display = sp_item_view_new_prepend(item->display, item, flags, key, ai);
969         nr_arena_item_set_transform(ai, item->transform);
970         nr_arena_item_set_opacity(ai, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value));
971         nr_arena_item_set_visible(ai, !item->isHidden());
972         nr_arena_item_set_sensitive(ai, item->sensitive);
973         if (item->clip_ref->getObject()) {
974             SPClipPath *cp = item->clip_ref->getObject();
976             if (!item->display->arenaitem->key) {
977                 NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3));
978             }
979             int clip_key = NR_ARENA_ITEM_GET_KEY(item->display->arenaitem);
981             // Show and set clip
982             NRArenaItem *ac = sp_clippath_show(cp, arena, clip_key);
983             nr_arena_item_set_clip(ai, ac);
984             nr_arena_item_unref(ac);
986             // Update bbox, in case the clip uses bbox units
987             NRRect bbox;
988             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
989             sp_clippath_set_bbox(SP_CLIPPATH(cp), clip_key, &bbox);
990             SP_OBJECT(cp)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
991         }
992         if (item->mask_ref->getObject()) {
993             SPMask *mask = item->mask_ref->getObject();
995             if (!item->display->arenaitem->key) {
996                 NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3));
997             }
998             int mask_key = NR_ARENA_ITEM_GET_KEY(item->display->arenaitem);
1000             // Show and set mask
1001             NRArenaItem *ac = sp_mask_show(mask, arena, mask_key);
1002             nr_arena_item_set_mask(ai, ac);
1003             nr_arena_item_unref(ac);
1005             // Update bbox, in case the mask uses bbox units
1006             NRRect bbox;
1007             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
1008             sp_mask_set_bbox(SP_MASK(mask), mask_key, &bbox);
1009             SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1010         }
1011         NR_ARENA_ITEM_SET_DATA(ai, item);
1012         NRRect item_bbox;
1013         sp_item_invoke_bbox(item, &item_bbox, NR::identity(), TRUE, SPItem::GEOMETRIC_BBOX);
1014         NR::Maybe<NR::Rect> i_bbox = item_bbox;
1015         nr_arena_item_set_item_bbox(ai, i_bbox);
1016     }
1018     return ai;
1021 void
1022 sp_item_invoke_hide(SPItem *item, unsigned key)
1024     g_assert(item != NULL);
1025     g_assert(SP_IS_ITEM(item));
1027     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide) {
1028         ((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide(item, key);
1029     }
1031     SPItemView *ref = NULL;
1032     SPItemView *v = item->display;
1033     while (v != NULL) {
1034         SPItemView *next = v->next;
1035         if (v->key == key) {
1036             if (item->clip_ref->getObject()) {
1037                 sp_clippath_hide(item->clip_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
1038                 nr_arena_item_set_clip(v->arenaitem, NULL);
1039             }
1040             if (item->mask_ref->getObject()) {
1041                 sp_mask_hide(item->mask_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
1042                 nr_arena_item_set_mask(v->arenaitem, NULL);
1043             }
1044             if (!ref) {
1045                 item->display = v->next;
1046             } else {
1047                 ref->next = v->next;
1048             }
1049             nr_arena_item_unparent(v->arenaitem);
1050             nr_arena_item_unref(v->arenaitem);
1051             g_free(v);
1052         } else {
1053             ref = v;
1054         }
1055         v = next;
1056     }
1059 // Adjusters
1061 void
1062 sp_item_adjust_pattern (SPItem *item, NR::Matrix const &postmul, bool set)
1064     SPStyle *style = SP_OBJECT_STYLE (item);
1066     if (style && (style->fill.isPaintserver())) {
1067         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
1068         if (SP_IS_PATTERN (server)) {
1069             SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "fill");
1070             sp_pattern_transform_multiply (pattern, postmul, set);
1071         }
1072     }
1074     if (style && (style->stroke.isPaintserver())) {
1075         SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
1076         if (SP_IS_PATTERN (server)) {
1077             SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "stroke");
1078             sp_pattern_transform_multiply (pattern, postmul, set);
1079         }
1080     }
1084 void
1085 sp_item_adjust_gradient (SPItem *item, NR::Matrix const &postmul, bool set)
1087     SPStyle *style = SP_OBJECT_STYLE (item);
1089     if (style && (style->fill.isPaintserver())) {
1090         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
1091         if (SP_IS_GRADIENT (server)) {
1093             /**
1094              * \note Bbox units for a gradient are generally a bad idea because
1095              * with them, you cannot preserve the relative position of the
1096              * object and its gradient after rotation or skew. So now we
1097              * convert them to userspace units which are easy to keep in sync
1098              * just by adding the object's transform to gradientTransform.
1099              * \todo FIXME: convert back to bbox units after transforming with
1100              * the item, so as to preserve the original units.
1101              */
1102             SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "fill");
1104             sp_gradient_transform_multiply (gradient, postmul, set);
1105         }
1106     }
1108     if (style && (style->stroke.isPaintserver())) {
1109         SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
1110         if (SP_IS_GRADIENT (server)) {
1111             SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "stroke");
1112             sp_gradient_transform_multiply (gradient, postmul, set);
1113         }
1114     }
1117 void
1118 sp_item_adjust_stroke (SPItem *item, gdouble ex)
1120     SPStyle *style = SP_OBJECT_STYLE (item);
1122     if (style && !style->stroke.isNone() && !NR_DF_TEST_CLOSE (ex, 1.0, NR_EPSILON)) {
1124         style->stroke_width.computed *= ex;
1125         style->stroke_width.set = TRUE;
1127         if (style->stroke_dash.n_dash != 0) {
1128             int i;
1129             for (i = 0; i < style->stroke_dash.n_dash; i++) {
1130                 style->stroke_dash.dash[i] *= ex;
1131             }
1132             style->stroke_dash.offset *= ex;
1133         }
1135         SP_OBJECT(item)->updateRepr();
1136     }
1139 /**
1140  * Find out the inverse of previous transform of an item (from its repr)
1141  */
1142 NR::Matrix
1143 sp_item_transform_repr (SPItem *item)
1145     NR::Matrix t_old(NR::identity());
1146     gchar const *t_attr = SP_OBJECT_REPR(item)->attribute("transform");
1147     if (t_attr) {
1148         NR::Matrix t;
1149         if (sp_svg_transform_read(t_attr, &t)) {
1150             t_old = t;
1151         }
1152     }
1154     return t_old;
1158 /**
1159  * Recursively scale stroke width in \a item and its children by \a expansion.
1160  */
1161 void
1162 sp_item_adjust_stroke_width_recursive(SPItem *item, double expansion)
1164     sp_item_adjust_stroke (item, expansion);
1166 // A clone's child is the ghost of its original - we must not touch it, skip recursion
1167     if (item && SP_IS_USE(item))
1168         return;
1170     for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1171         if (SP_IS_ITEM(o))
1172             sp_item_adjust_stroke_width_recursive(SP_ITEM(o), expansion);
1173     }
1176 /**
1177  * Recursively adjust rx and ry of rects.
1178  */
1179 void
1180 sp_item_adjust_rects_recursive(SPItem *item, NR::Matrix advertized_transform)
1182     if (SP_IS_RECT (item)) {
1183         sp_rect_compensate_rxry (SP_RECT(item), advertized_transform);
1184     }
1186     for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1187         if (SP_IS_ITEM(o))
1188             sp_item_adjust_rects_recursive(SP_ITEM(o), advertized_transform);
1189     }
1192 /**
1193  * Recursively compensate pattern or gradient transform.
1194  */
1195 void
1196 sp_item_adjust_paint_recursive (SPItem *item, NR::Matrix advertized_transform, NR::Matrix t_ancestors, bool is_pattern)
1198 // _Before_ full pattern/gradient transform: t_paint * t_item * t_ancestors
1199 // _After_ full pattern/gradient transform: t_paint_new * t_item * t_ancestors * advertised_transform
1200 // By equating these two expressions we get t_paint_new = t_paint * paint_delta, where:
1201     NR::Matrix t_item = sp_item_transform_repr (item);
1202     NR::Matrix paint_delta = t_item * t_ancestors * advertized_transform * t_ancestors.inverse() * t_item.inverse();
1204 // Within text, we do not fork gradients, and so must not recurse to avoid double compensation;
1205 // also we do not recurse into clones, because a clone's child is the ghost of its original -
1206 // we must not touch it
1207     if (!(item && (SP_IS_TEXT(item) || SP_IS_USE(item)))) {
1208         for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1209             if (SP_IS_ITEM(o)) {
1210 // At the level of the transformed item, t_ancestors is identity;
1211 // below it, it is the accmmulated chain of transforms from this level to the top level
1212                 sp_item_adjust_paint_recursive (SP_ITEM(o), advertized_transform, t_item * t_ancestors, is_pattern);
1213             }
1214         }
1215     }
1217 // We recursed into children first, and are now adjusting this object second;
1218 // this is so that adjustments in a tree are done from leaves up to the root,
1219 // and paintservers on leaves inheriting their values from ancestors could adjust themselves properly
1220 // before ancestors themselves are adjusted, probably differently (bug 1286535)
1222     if (is_pattern)
1223         sp_item_adjust_pattern (item, paint_delta);
1224     else
1225         sp_item_adjust_gradient (item, paint_delta);
1229 void
1230 sp_item_adjust_livepatheffect (SPItem *item, NR::Matrix const &postmul, bool set)
1232     if ( !SP_IS_SHAPE(item) )
1233         return;
1235     SPShape *shape = SP_SHAPE (item);
1236     if ( sp_shape_has_path_effect(shape) ) {
1237         LivePathEffectObject *lpeobj = sp_shape_get_livepatheffectobject(shape);
1238         LivePathEffectObject *new_lpeobj = lpeobj->fork_private_if_necessary();
1239         if (new_lpeobj != lpeobj) {
1240             sp_shape_set_path_effect(shape, new_lpeobj);
1241         }
1243         Inkscape::LivePathEffect::Effect * effect = sp_shape_get_livepatheffect(shape);
1244         if (effect) {
1245             effect->transform_multiply (to_2geom(postmul), set);
1246         }
1247     }
1250 /**
1251  * A temporary wrapper for the next function accepting the NRMatrix
1252  * instead of NR::Matrix
1253  */
1254 void
1255 sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NRMatrix const *transform, NR::Matrix const *adv)
1257     if (transform == NULL)
1258         sp_item_write_transform(item, repr, NR::identity(), adv);
1259     else
1260         sp_item_write_transform(item, repr, NR::Matrix (transform), adv);
1263 /**
1264  * Set a new transform on an object.
1265  *
1266  * Compensate for stroke scaling and gradient/pattern fill transform, if
1267  * necessary. Call the object's set_transform method if transforms are
1268  * stored optimized. Send _transformed_signal. Invoke _write method so that
1269  * the repr is updated with the new transform.
1270  */
1271 void
1272 sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NR::Matrix const &transform, NR::Matrix const *adv, bool compensate)
1274     g_return_if_fail(item != NULL);
1275     g_return_if_fail(SP_IS_ITEM(item));
1276     g_return_if_fail(repr != NULL);
1278     // calculate the relative transform, if not given by the adv attribute
1279     NR::Matrix advertized_transform;
1280     if (adv != NULL) {
1281         advertized_transform = *adv;
1282     } else {
1283         advertized_transform = sp_item_transform_repr (item).inverse() * transform;
1284     }
1286     if (compensate) {
1288          // recursively compensate for stroke scaling, depending on user preference
1289         if (prefs_get_int_attribute("options.transform", "stroke", 1) == 0) {
1290             double const expansion = 1. / NR::expansion(advertized_transform);
1291             sp_item_adjust_stroke_width_recursive(item, expansion);
1292         }
1294         // recursively compensate rx/ry of a rect if requested
1295         if (prefs_get_int_attribute("options.transform", "rectcorners", 1) == 0) {
1296             sp_item_adjust_rects_recursive(item, advertized_transform);
1297         }
1299         // recursively compensate pattern fill if it's not to be transformed
1300         if (prefs_get_int_attribute("options.transform", "pattern", 1) == 0) {
1301             sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), true);
1302         }
1303         /// \todo FIXME: add the same else branch as for gradients below, to convert patterns to userSpaceOnUse as well
1304         /// recursively compensate gradient fill if it's not to be transformed
1305         if (prefs_get_int_attribute("options.transform", "gradient", 1) == 0) {
1306             sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), false);
1307         } else {
1308             // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do
1309             // it here _before_ the new transform is set, so as to use the pre-transform bbox
1310             sp_item_adjust_paint_recursive (item, NR::identity(), NR::identity(), false);
1311         }
1313     } // endif(compensate)
1315     gint preserve = prefs_get_int_attribute("options.preservetransform", "value", 0);
1316     NR::Matrix transform_attr (transform);
1317     if ( // run the object's set_transform (i.e. embed transform) only if:
1318          ((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform && // it does have a set_transform method
1319              !preserve && // user did not chose to preserve all transforms
1320              !item->clip_ref->getObject() && // the object does not have a clippath
1321              !item->mask_ref->getObject() && // the object does not have a mask
1322          !(!transform.is_translation() && SP_OBJECT_STYLE(item) && SP_OBJECT_STYLE(item)->getFilter())
1323              // the object does not have a filter, or the transform is translation (which is supposed to not affect filters)
1324         ) {
1325         transform_attr = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform(item, transform);
1326     }
1327     sp_item_set_item_transform(item, transform_attr);
1329     // Note: updateRepr comes before emitting the transformed signal since
1330     // it causes clone SPUse's copy of the original object to brought up to
1331     // date with the original.  Otherwise, sp_use_bbox returns incorrect
1332     // values if called in code handling the transformed signal.
1333     SP_OBJECT(item)->updateRepr();
1335     // send the relative transform with a _transformed_signal
1336     item->_transformed_signal.emit(&advertized_transform, item);
1339 gint
1340 sp_item_event(SPItem *item, SPEvent *event)
1342     g_return_val_if_fail(item != NULL, FALSE);
1343     g_return_val_if_fail(SP_IS_ITEM(item), FALSE);
1344     g_return_val_if_fail(event != NULL, FALSE);
1346     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->event)
1347         return ((SPItemClass *) G_OBJECT_GET_CLASS(item))->event(item, event);
1349     return FALSE;
1352 /**
1353  * Sets item private transform (not propagated to repr), without compensating stroke widths,
1354  * gradients, patterns as sp_item_write_transform does.
1355  */
1356 void
1357 sp_item_set_item_transform(SPItem *item, NR::Matrix const &transform)
1359     g_return_if_fail(item != NULL);
1360     g_return_if_fail(SP_IS_ITEM(item));
1362     if (!matrix_equalp(transform, item->transform, NR_EPSILON)) {
1363         item->transform = transform;
1364         /* The SP_OBJECT_USER_MODIFIED_FLAG_B is used to mark the fact that it's only a
1365            transformation.  It's apparently not used anywhere else. */
1366         item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_USER_MODIFIED_FLAG_B);
1367         sp_item_rm_unsatisfied_cns(*item);
1368     }
1371 void
1372 sp_item_convert_item_to_guides(SPItem *item) {
1373     g_return_if_fail(item != NULL);
1374     g_return_if_fail(SP_IS_ITEM(item));
1376     /* Use derived method if present ... */
1377     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->convert_to_guides) {
1378         (*((SPItemClass *) G_OBJECT_GET_CLASS(item))->convert_to_guides)(item);
1379         return;
1380     }
1382     /* .. otherwise simply place the guides around the item's bounding box */
1384     sp_item_convert_to_guides(item);
1388 /**
1389  * \pre \a ancestor really is an ancestor (\>=) of \a object, or NULL.
1390  *   ("Ancestor (\>=)" here includes as far as \a object itself.)
1391  */
1392 NR::Matrix
1393 i2anc_affine(SPObject const *object, SPObject const *const ancestor) {
1394     NR::Matrix ret(NR::identity());
1395     g_return_val_if_fail(object != NULL, ret);
1397     /* stop at first non-renderable ancestor */
1398     while ( object != ancestor && SP_IS_ITEM(object) ) {
1399         if (SP_IS_ROOT(object)) {
1400             ret *= SP_ROOT(object)->c2p;
1401         }
1402         ret *= SP_ITEM(object)->transform;
1403         object = SP_OBJECT_PARENT(object);
1404     }
1405     return ret;
1408 NR::Matrix
1409 i2i_affine(SPObject const *src, SPObject const *dest) {
1410     g_return_val_if_fail(src != NULL && dest != NULL, NR::identity());
1411     SPObject const *ancestor = src->nearestCommonAncestor(dest);
1412     return i2anc_affine(src, ancestor) / i2anc_affine(dest, ancestor);
1415 NR::Matrix SPItem::getRelativeTransform(SPObject const *dest) const {
1416     return i2i_affine(this, dest);
1419 /**
1420  * Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
1421  * \pre (item != NULL) and SP_IS_ITEM(item).
1422  */
1423 NR::Matrix sp_item_i2doc_affine(SPItem const *item)
1425     return i2anc_affine(item, NULL);
1428 /**
1429  * Returns the accumulated transformation of the item and all its ancestors, but excluding root's viewport.
1430  * Used in path operations mostly.
1431  * \pre (item != NULL) and SP_IS_ITEM(item).
1432  */
1433 NR::Matrix sp_item_i2root_affine(SPItem const *item)
1435     g_assert(item != NULL);
1436     g_assert(SP_IS_ITEM(item));
1438     NR::Matrix ret(NR::identity());
1439     g_assert(ret.test_identity());
1440     while ( NULL != SP_OBJECT_PARENT(item) ) {
1441         ret *= item->transform;
1442         item = SP_ITEM(SP_OBJECT_PARENT(item));
1443     }
1444     g_assert(SP_IS_ROOT(item));
1446     ret *= item->transform;
1448     return ret;
1451 /* fixme: This is EVIL!!! */
1453 NR::Matrix sp_item_i2d_affine(SPItem const *item)
1455     g_assert(item != NULL);
1456     g_assert(SP_IS_ITEM(item));
1458     NR::Matrix const ret( sp_item_i2doc_affine(item)
1459                           * NR::scale(1, -1)
1460                           * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) );
1461     return ret;
1464 // same as i2d but with i2root instead of i2doc
1465 NR::Matrix sp_item_i2r_affine(SPItem const *item)
1467     g_assert(item != NULL);
1468     g_assert(SP_IS_ITEM(item));
1470     NR::Matrix const ret( sp_item_i2root_affine(item)
1471                           * NR::scale(1, -1)
1472                           * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) );
1473     return ret;
1476 /**
1477  * Converts a matrix \a m into the desktop coords of the \a item.
1478  * Will become a noop when we eliminate the coordinate flipping.
1479  */
1480 NR::Matrix matrix_to_desktop(NR::Matrix const m, SPItem const *item)
1482     NR::Matrix const ret(m
1483                          * NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item)))
1484                          * NR::scale(1, -1));
1485     return ret;
1488 /**
1489  * Converts a matrix \a m from the desktop coords of the \a item.
1490  * Will become a noop when we eliminate the coordinate flipping.
1491  */
1492 NR::Matrix matrix_from_desktop(NR::Matrix const m, SPItem const *item)
1494     NR::Matrix const ret(NR::scale(1, -1)
1495                          * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item)))
1496                          * m);
1497     return ret;
1500 void sp_item_set_i2d_affine(SPItem *item, NR::Matrix const &i2dt)
1502     g_return_if_fail( item != NULL );
1503     g_return_if_fail( SP_IS_ITEM(item) );
1505     NR::Matrix dt2p; /* desktop to item parent transform */
1506     if (SP_OBJECT_PARENT(item)) {
1507         dt2p = sp_item_i2d_affine((SPItem *) SP_OBJECT_PARENT(item)).inverse();
1508     } else {
1509         dt2p = ( NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item)))
1510                  * NR::scale(1, -1) );
1511     }
1513     NR::Matrix const i2p( i2dt * dt2p );
1514     sp_item_set_item_transform(item, i2p);
1518 NR::Matrix
1519 sp_item_dt2i_affine(SPItem const *item)
1521     /* fixme: Implement the right way (Lauris) */
1522     return sp_item_i2d_affine(item).inverse();
1525 /* Item views */
1527 static SPItemView *
1528 sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem)
1530     SPItemView *new_view;
1532     g_assert(item != NULL);
1533     g_assert(SP_IS_ITEM(item));
1534     g_assert(arenaitem != NULL);
1535     g_assert(NR_IS_ARENA_ITEM(arenaitem));
1537     new_view = g_new(SPItemView, 1);
1539     new_view->next = list;
1540     new_view->flags = flags;
1541     new_view->key = key;
1542     new_view->arenaitem = nr_arena_item_ref(arenaitem);
1544     return new_view;
1547 static SPItemView *
1548 sp_item_view_list_remove(SPItemView *list, SPItemView *view)
1550     if (view == list) {
1551         list = list->next;
1552     } else {
1553         SPItemView *prev;
1554         prev = list;
1555         while (prev->next != view) prev = prev->next;
1556         prev->next = view->next;
1557     }
1559     nr_arena_item_unref(view->arenaitem);
1560     g_free(view);
1562     return list;
1565 /**
1566  * Return the arenaitem corresponding to the given item in the display
1567  * with the given key
1568  */
1569 NRArenaItem *
1570 sp_item_get_arenaitem(SPItem *item, unsigned key)
1572     for ( SPItemView *iv = item->display ; iv ; iv = iv->next ) {
1573         if ( iv->key == key ) {
1574             return iv->arenaitem;
1575         }
1576     }
1578     return NULL;
1581 int
1582 sp_item_repr_compare_position(SPItem *first, SPItem *second)
1584     return sp_repr_compare_position(SP_OBJECT_REPR(first),
1585                                     SP_OBJECT_REPR(second));
1588 SPItem *
1589 sp_item_first_item_child (SPObject *obj)
1591     for ( SPObject *iter = sp_object_first_child(obj) ; iter ; iter = SP_OBJECT_NEXT(iter)) {
1592         if (SP_IS_ITEM (iter))
1593             return SP_ITEM (iter);
1594     }
1595     return NULL;
1598 void
1599 sp_item_convert_to_guides(SPItem *item) {
1600     SPDesktop *dt = inkscape_active_desktop();
1601     SPNamedView *nv = sp_desktop_namedview(dt);
1602     (void)nv;
1604     gchar const *prefs_bbox = prefs_get_string_attribute("tools", "bounding_box");
1605     SPItem::BBoxType bbox_type = (prefs_bbox != NULL && strcmp(prefs_bbox, "geometric")==0)? SPItem::GEOMETRIC_BBOX : SPItem::RENDERING_BBOX;
1607     NR::Maybe<NR::Rect> bbox = sp_item_bbox_desktop(item, bbox_type);
1608     if (!bbox) {
1609         g_warning ("Cannot determine item's bounding box during conversion to guides.\n");
1610         return;
1611     }
1613     std::list<std::pair<Geom::Point, Geom::Point> > pts;
1615     NR::Point A((*bbox).min());
1616     NR::Point C((*bbox).max());
1617     NR::Point B(A[NR::X], C[NR::Y]);
1618     NR::Point D(C[NR::X], A[NR::Y]);
1620     pts.push_back(std::make_pair(A.to_2geom(), B.to_2geom()));
1621     pts.push_back(std::make_pair(B.to_2geom(), C.to_2geom()));
1622     pts.push_back(std::make_pair(C.to_2geom(), D.to_2geom()));
1623     pts.push_back(std::make_pair(D.to_2geom(), A.to_2geom()));
1625     sp_guide_pt_pairs_to_guides(SP_OBJECT_DOCUMENT(item), pts);
1628 /*
1629   Local Variables:
1630   mode:c++
1631   c-file-style:"stroustrup"
1632   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1633   indent-tabs-mode:nil
1634   fill-column:99
1635   End:
1636 */
1637 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :