Code

switch SPStyle to using SPFilterReference for filters; sp_style_new now requires...
[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"
38 #include "style.h"
39 #include <glibmm/i18n.h>
40 #include "sp-root.h"
41 #include "sp-clippath.h"
42 #include "sp-mask.h"
43 #include "sp-rect.h"
44 #include "sp-use.h"
45 #include "sp-text.h"
46 #include "sp-item-rm-unsatisfied-cns.h"
47 #include "sp-pattern.h"
48 #include "sp-switch.h"
49 #include "gradient-chemistry.h"
50 #include "prefs-utils.h"
51 #include "conn-avoid-ref.h"
52 #include "conditions.h"
53 #include "sp-filter-reference.h"
55 #include "libnr/nr-matrix-div.h"
56 #include "libnr/nr-matrix-fns.h"
57 #include "libnr/nr-matrix-scale-ops.h"
58 #include "libnr/nr-matrix-translate-ops.h"
59 #include "libnr/nr-scale-translate-ops.h"
60 #include "libnr/nr-translate-scale-ops.h"
61 #include "algorithms/find-last-if.h"
62 #include "util/reverse-list.h"
64 #include "xml/repr.h"
66 #define noSP_ITEM_DEBUG_IDLE
68 static void sp_item_class_init(SPItemClass *klass);
69 static void sp_item_init(SPItem *item);
71 static void sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
72 static void sp_item_release(SPObject *object);
73 static void sp_item_set(SPObject *object, unsigned key, gchar const *value);
74 static void sp_item_update(SPObject *object, SPCtx *ctx, guint flags);
75 static Inkscape::XML::Node *sp_item_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
77 static gchar *sp_item_private_description(SPItem *item);
78 static void sp_item_private_snappoints(SPItem const *item, SnapPointsIter p);
80 static SPItemView *sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem);
81 static SPItemView *sp_item_view_list_remove(SPItemView *list, SPItemView *view);
83 static SPObjectClass *parent_class;
85 static void clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
86 static void mask_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
88 /**
89  * Registers SPItem class and returns its type number.
90  */
91 GType
92 sp_item_get_type(void)
93 {
94     static GType type = 0;
95     if (!type) {
96         GTypeInfo info = {
97             sizeof(SPItemClass),
98             NULL, NULL,
99             (GClassInitFunc) sp_item_class_init,
100             NULL, NULL,
101             sizeof(SPItem),
102             16,
103             (GInstanceInitFunc) sp_item_init,
104             NULL,   /* value_table */
105         };
106         type = g_type_register_static(SP_TYPE_OBJECT, "SPItem", &info, (GTypeFlags)0);
107     }
108     return type;
111 /**
112  * SPItem vtable initialization.
113  */
114 static void
115 sp_item_class_init(SPItemClass *klass)
117     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
119     parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT);
121     sp_object_class->build = sp_item_build;
122     sp_object_class->release = sp_item_release;
123     sp_object_class->set = sp_item_set;
124     sp_object_class->update = sp_item_update;
125     sp_object_class->write = sp_item_write;
127     klass->description = sp_item_private_description;
128     klass->snappoints = sp_item_private_snappoints;
131 /**
132  * Callback for SPItem object initialization.
133  */
134 static void
135 sp_item_init(SPItem *item)
137     item->init();
140 void SPItem::init() {
141     this->sensitive = TRUE;
143     this->transform_center_x = 0;
144     this->transform_center_y = 0;
146     this->_is_evaluated = true;
147     this->_evaluated_status = StatusUnknown;
149     this->transform = NR::identity();
151     this->display = NULL;
153     this->clip_ref = new SPClipPathReference(this);
154                 sigc::signal<void, SPObject *, SPObject *> cs1=this->clip_ref->changedSignal();
155                 sigc::slot2<void,SPObject*, SPObject *> sl1=sigc::bind(sigc::ptr_fun(clip_ref_changed), this);
156     _clip_ref_connection = cs1.connect(sl1);
158     this->mask_ref = new SPMaskReference(this);
159                 sigc::signal<void, SPObject *, SPObject *> cs2=this->mask_ref->changedSignal();
160                 sigc::slot2<void,SPObject*, SPObject *> sl2=sigc::bind(sigc::ptr_fun(mask_ref_changed), this);
161     _mask_ref_connection = cs2.connect(sl2);
163     this->avoidRef = new SPAvoidRef(this);
165     new (&this->_transformed_signal) sigc::signal<void, NR::Matrix const *, SPItem *>();
168 bool SPItem::isVisibleAndUnlocked() const {
169     return (!isHidden() && !isLocked());
172 bool SPItem::isVisibleAndUnlocked(unsigned display_key) const {
173     return (!isHidden(display_key) && !isLocked());
176 bool SPItem::isLocked() const {
177     for (SPObject *o = SP_OBJECT(this); o != NULL; o = SP_OBJECT_PARENT(o)) {
178         if (SP_IS_ITEM(o) && !(SP_ITEM(o)->sensitive))
179             return true;
180     }
181     return false;
184 void SPItem::setLocked(bool locked) {
185     SP_OBJECT_REPR(this)->setAttribute("sodipodi:insensitive",
186                      ( locked ? "1" : NULL ));
187     updateRepr();
190 bool SPItem::isHidden() const {
191     if (!isEvaluated())
192         return true;
193     return style->display.computed == SP_CSS_DISPLAY_NONE;
196 void SPItem::setHidden(bool hide) {
197     style->display.set = TRUE;
198     style->display.value = ( hide ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
199     style->display.computed = style->display.value;
200     style->display.inherit = FALSE;
201     updateRepr();
204 bool SPItem::isHidden(unsigned display_key) const {
205     if (!isEvaluated())
206         return true;
207     for ( SPItemView *view(display) ; view ; view = view->next ) {
208         if ( view->key == display_key ) {
209             g_assert(view->arenaitem != NULL);
210             for ( NRArenaItem *arenaitem = view->arenaitem ;
211                   arenaitem ; arenaitem = arenaitem->parent )
212             {
213                 if (!arenaitem->visible) {
214                     return true;
215                 }
216             }
217             return false;
218         }
219     }
220     return true;
223 void SPItem::setEvaluated(bool evaluated) {
224     _is_evaluated = evaluated;
225     _evaluated_status = StatusSet;
228 void SPItem::resetEvaluated() {
229     if ( StatusCalculated == _evaluated_status ) {
230         _evaluated_status = StatusUnknown;
231         bool oldValue = _is_evaluated;
232         if ( oldValue != isEvaluated() ) {
233             requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
234         }
235     } if ( StatusSet == _evaluated_status ) {
236         SPObject const *const parent = SP_OBJECT_PARENT(this);
237         if (SP_IS_SWITCH(parent)) {
238             SP_SWITCH(parent)->resetChildEvaluated();
239         }
240     }
243 bool SPItem::isEvaluated() const {
244     if ( StatusUnknown == _evaluated_status ) {
245         _is_evaluated = sp_item_evaluate(this);
246         _evaluated_status = StatusCalculated;
247     }
248     return _is_evaluated;
251 /**
252  * Returns something suitable for the `Hide' checkbox in the Object Properties dialog box.
253  *  Corresponds to setExplicitlyHidden.
254  */
255 bool
256 SPItem::isExplicitlyHidden() const
258     return (this->style->display.set
259             && this->style->display.value == SP_CSS_DISPLAY_NONE);
262 /**
263  * Sets the display CSS property to `hidden' if \a val is true,
264  * otherwise makes it unset
265  */
266 void
267 SPItem::setExplicitlyHidden(bool const val) {
268     this->style->display.set = val;
269     this->style->display.value = ( val ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
270     this->style->display.computed = this->style->display.value;
271     this->updateRepr();
274 /**
275  * Sets the transform_center_x and transform_center_y properties to retain the rotation centre
276  */
277 void
278 SPItem::setCenter(NR::Point object_centre) {
279     NR::Maybe<NR::Rect> bbox = getBounds(sp_item_i2d_affine(this));
280     if (bbox) {
281         transform_center_x = object_centre[NR::X] - bbox->midpoint()[NR::X];
282         if (fabs(transform_center_x) < 1e-5) // rounding error
283             transform_center_x = 0;
284         transform_center_y = object_centre[NR::Y] - bbox->midpoint()[NR::Y];
285         if (fabs(transform_center_y) < 1e-5) // rounding error
286             transform_center_y = 0;
287     }
290 void
291 SPItem::unsetCenter() {
292     transform_center_x = 0;
293     transform_center_y = 0;
296 bool SPItem::isCenterSet() {
297     return (transform_center_x != 0 || transform_center_y != 0);
300 NR::Point SPItem::getCenter() const {
301     NR::Maybe<NR::Rect> bbox = getBounds(sp_item_i2d_affine(this));
302     if (bbox) {
303         return bbox->midpoint() + NR::Point (this->transform_center_x, this->transform_center_y);
304     } else {
305         return NR::Point (0, 0); // something's wrong!
306     }
310 namespace {
312 bool is_item(SPObject const &object) {
313     return SP_IS_ITEM(&object);
318 void SPItem::raiseToTop() {
319     using Inkscape::Algorithms::find_last_if;
321     SPObject *topmost=find_last_if<SPObject::SiblingIterator>(
322         SP_OBJECT_NEXT(this), NULL, &is_item
323     );
324     if (topmost) {
325         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
326         sp_repr_parent(repr)->changeOrder(repr, SP_OBJECT_REPR(topmost));
327     }
330 void SPItem::raiseOne() {
331     SPObject *next_higher=std::find_if<SPObject::SiblingIterator>(
332         SP_OBJECT_NEXT(this), NULL, &is_item
333     );
334     if (next_higher) {
335         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
336         Inkscape::XML::Node *ref=SP_OBJECT_REPR(next_higher);
337         sp_repr_parent(repr)->changeOrder(repr, ref);
338     }
341 void SPItem::lowerOne() {
342     using Inkscape::Util::MutableList;
343     using Inkscape::Util::reverse_list;
345     MutableList<SPObject &> next_lower=std::find_if(
346         reverse_list<SPObject::SiblingIterator>(
347             SP_OBJECT_PARENT(this)->firstChild(), this
348         ),
349         MutableList<SPObject &>(),
350         &is_item
351     );
352     if (next_lower) {
353         ++next_lower;
354         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
355         Inkscape::XML::Node *ref=( next_lower ? SP_OBJECT_REPR(&*next_lower) : NULL );
356         sp_repr_parent(repr)->changeOrder(repr, ref);
357     }
360 void SPItem::lowerToBottom() {
361     using Inkscape::Algorithms::find_last_if;
362     using Inkscape::Util::MutableList;
363     using Inkscape::Util::reverse_list;
365     MutableList<SPObject &> bottom=find_last_if(
366         reverse_list<SPObject::SiblingIterator>(
367             SP_OBJECT_PARENT(this)->firstChild(), this
368         ),
369         MutableList<SPObject &>(),
370         &is_item
371     );
372     if (bottom) {
373         ++bottom;
374         Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
375         Inkscape::XML::Node *ref=( bottom ? SP_OBJECT_REPR(&*bottom) : NULL );
376         sp_repr_parent(repr)->changeOrder(repr, ref);
377     }
380 static void
381 sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
383     sp_object_read_attr(object, "style");
384     sp_object_read_attr(object, "transform");
385     sp_object_read_attr(object, "clip-path");
386     sp_object_read_attr(object, "mask");
387     sp_object_read_attr(object, "sodipodi:insensitive");
388     sp_object_read_attr(object, "sodipodi:nonprintable");
389     sp_object_read_attr(object, "inkscape:transform-center-x");
390     sp_object_read_attr(object, "inkscape:transform-center-y");
391     sp_object_read_attr(object, "inkscape:connector-avoid");
393     if (((SPObjectClass *) (parent_class))->build) {
394         (* ((SPObjectClass *) (parent_class))->build)(object, document, repr);
395     }
398 static void
399 sp_item_release(SPObject *object)
401     SPItem *item = (SPItem *) object;
403     item->_clip_ref_connection.disconnect();
404     item->_mask_ref_connection.disconnect();
406     if (item->clip_ref) {
407         item->clip_ref->detach();
408         delete item->clip_ref;
409         item->clip_ref = NULL;
410     }
412     if (item->mask_ref) {
413         item->mask_ref->detach();
414         delete item->mask_ref;
415         item->mask_ref = NULL;
416     }
418     if (item->avoidRef) {
419         delete item->avoidRef;
420         item->avoidRef = NULL;
421     }
423     if (((SPObjectClass *) (parent_class))->release) {
424         ((SPObjectClass *) parent_class)->release(object);
425     }
427     while (item->display) {
428         nr_arena_item_unparent(item->display->arenaitem);
429         item->display = sp_item_view_list_remove(item->display, item->display);
430     }
432     item->_transformed_signal.~signal();
435 static void
436 sp_item_set(SPObject *object, unsigned key, gchar const *value)
438     SPItem *item = (SPItem *) object;
440     switch (key) {
441         case SP_ATTR_TRANSFORM: {
442             NR::Matrix t;
443             if (value && sp_svg_transform_read(value, &t)) {
444                 sp_item_set_item_transform(item, t);
445             } else {
446                 sp_item_set_item_transform(item, NR::identity());
447             }
448             break;
449         }
450         case SP_PROP_CLIP_PATH: {
451             gchar *uri = Inkscape::parse_css_url(value);
452             if (uri) {
453                 try {
454                     item->clip_ref->attach(Inkscape::URI(uri));
455                 } catch (Inkscape::BadURIException &e) {
456                     g_warning("%s", e.what());
457                     item->clip_ref->detach();
458                 }
459                 g_free(uri);
460             } else {
461                 item->clip_ref->detach();
462             }
464             break;
465         }
466         case SP_PROP_MASK: {
467             gchar *uri=Inkscape::parse_css_url(value);
468             if (uri) {
469                 try {
470                     item->mask_ref->attach(Inkscape::URI(uri));
471                 } catch (Inkscape::BadURIException &e) {
472                     g_warning("%s", e.what());
473                     item->mask_ref->detach();
474                 }
475                 g_free(uri);
476             } else {
477                 item->mask_ref->detach();
478             }
480             break;
481         }
482         case SP_ATTR_SODIPODI_INSENSITIVE:
483             item->sensitive = !value;
484             for (SPItemView *v = item->display; v != NULL; v = v->next) {
485                 nr_arena_item_set_sensitive(v->arenaitem, item->sensitive);
486             }
487             break;
488         case SP_ATTR_CONNECTOR_AVOID:
489             item->avoidRef->setAvoid(value);
490             break;
491         case SP_ATTR_TRANSFORM_CENTER_X:
492             if (value) {
493                 item->transform_center_x = g_strtod(value, NULL);
494             } else {
495                 item->transform_center_x = 0;
496             }
497             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
498             break;
499         case SP_ATTR_TRANSFORM_CENTER_Y:
500             if (value) {
501                 item->transform_center_y = g_strtod(value, NULL);
502             } else {
503                 item->transform_center_y = 0;
504             }
505             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
506             break;
507         case SP_PROP_SYSTEM_LANGUAGE:
508         case SP_PROP_REQUIRED_FEATURES:
509         case SP_PROP_REQUIRED_EXTENSIONS:
510             {
511                 item->resetEvaluated();
512                 // pass to default handler
513             }
514         default:
515             if (SP_ATTRIBUTE_IS_CSS(key)) {
516                 sp_style_read_from_object(object->style, object);
517                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
518             } else {
519                 if (((SPObjectClass *) (parent_class))->set) {
520                     (* ((SPObjectClass *) (parent_class))->set)(object, key, value);
521                 }
522             }
523             break;
524     }
527 static void
528 clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item)
530     if (old_clip) {
531         SPItemView *v;
532         /* Hide clippath */
533         for (v = item->display; v != NULL; v = v->next) {
534             sp_clippath_hide(SP_CLIPPATH(old_clip), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
535             nr_arena_item_set_clip(v->arenaitem, NULL);
536         }
537     }
538     if (SP_IS_CLIPPATH(clip)) {
539         NRRect bbox;
540         sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
541         for (SPItemView *v = item->display; v != NULL; v = v->next) {
542             if (!v->arenaitem->key) {
543                 NR_ARENA_ITEM_SET_KEY(v->arenaitem, sp_item_display_key_new(3));
544             }
545             NRArenaItem *ai = sp_clippath_show(SP_CLIPPATH(clip),
546                                                NR_ARENA_ITEM_ARENA(v->arenaitem),
547                                                NR_ARENA_ITEM_GET_KEY(v->arenaitem));
548             nr_arena_item_set_clip(v->arenaitem, ai);
549             nr_arena_item_unref(ai);
550             sp_clippath_set_bbox(SP_CLIPPATH(clip), NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
551             SP_OBJECT(clip)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
552         }
553     }
556 static void
557 mask_ref_changed(SPObject *old_mask, SPObject *mask, SPItem *item)
559     if (old_mask) {
560         /* Hide mask */
561         for (SPItemView *v = item->display; v != NULL; v = v->next) {
562             sp_mask_hide(SP_MASK(old_mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
563             nr_arena_item_set_mask(v->arenaitem, NULL);
564         }
565     }
566     if (SP_IS_MASK(mask)) {
567         NRRect bbox;
568         sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
569         for (SPItemView *v = item->display; v != NULL; v = v->next) {
570             if (!v->arenaitem->key) {
571                 NR_ARENA_ITEM_SET_KEY(v->arenaitem, sp_item_display_key_new(3));
572             }
573             NRArenaItem *ai = sp_mask_show(SP_MASK(mask),
574                                            NR_ARENA_ITEM_ARENA(v->arenaitem),
575                                            NR_ARENA_ITEM_GET_KEY(v->arenaitem));
576             nr_arena_item_set_mask(v->arenaitem, ai);
577             nr_arena_item_unref(ai);
578             sp_mask_set_bbox(SP_MASK(mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
579             SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
580         }
581     }
584 static void
585 sp_item_update(SPObject *object, SPCtx *ctx, guint flags)
587     SPItem *item = SP_ITEM(object);
589     if (((SPObjectClass *) (parent_class))->update)
590         (* ((SPObjectClass *) (parent_class))->update)(object, ctx, flags);
592     if (flags & (SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG)) {
593         if (flags & SP_OBJECT_MODIFIED_FLAG) {
594             for (SPItemView *v = item->display; v != NULL; v = v->next) {
595                 nr_arena_item_set_transform(v->arenaitem, item->transform);
596             }
597         }
599         SPClipPath *clip_path = item->clip_ref->getObject();
600         SPMask *mask = item->mask_ref->getObject();
602         if ( clip_path || mask ) {
603             NRRect bbox;
604             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
605             if (clip_path) {
606                 for (SPItemView *v = item->display; v != NULL; v = v->next) {
607                     sp_clippath_set_bbox(clip_path, NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
608                 }
609             }
610             if (mask) {
611                 for (SPItemView *v = item->display; v != NULL; v = v->next) {
612                     sp_mask_set_bbox(mask, NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
613                 }
614             }
615         }
617         if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
618             for (SPItemView *v = item->display; v != NULL; v = v->next) {
619                 nr_arena_item_set_opacity(v->arenaitem, SP_SCALE24_TO_FLOAT(object->style->opacity.value));
620                 nr_arena_item_set_visible(v->arenaitem, !item->isHidden());
621             }
622         }
623     }
625     // Update libavoid with item geometry (for connector routing).
626     item->avoidRef->handleSettingChange();
629 static Inkscape::XML::Node *
630 sp_item_write(SPObject *const object, Inkscape::XML::Node *repr, guint flags)
632     SPItem *item = SP_ITEM(object);
634     gchar *c = sp_svg_transform_write(item->transform);
635     repr->setAttribute("transform", c);
636     g_free(c);
638     if (flags & SP_OBJECT_WRITE_EXT) {
639         repr->setAttribute("sodipodi:insensitive", ( item->sensitive ? NULL : "true" ));
640         if (item->transform_center_x != 0)
641             sp_repr_set_svg_double (repr, "inkscape:transform-center-x", item->transform_center_x);
642         else
643             repr->setAttribute ("inkscape:transform-center-x", NULL);
644         if (item->transform_center_y != 0)
645             sp_repr_set_svg_double (repr, "inkscape:transform-center-y", item->transform_center_y);
646         else
647             repr->setAttribute ("inkscape:transform-center-y", NULL);
648     }
650     if (item->clip_ref->getObject()) {
651         const gchar *value = g_strdup_printf ("url(%s)", item->clip_ref->getURI()->toString());
652         repr->setAttribute ("clip-path", value);
653         g_free ((void *) value);
654     }
655     if (item->mask_ref->getObject()) {
656         const gchar *value = g_strdup_printf ("url(%s)", item->mask_ref->getURI()->toString());
657         repr->setAttribute ("mask", value);
658         g_free ((void *) value);
659     }
661     if (((SPObjectClass *) (parent_class))->write) {
662         ((SPObjectClass *) (parent_class))->write(object, repr, flags);
663     }
665     return repr;
668 NR::Maybe<NR::Rect> SPItem::getBounds(NR::Matrix const &transform,
669                                       SPItem::BBoxType type,
670                                       unsigned int dkey) const
672     NRRect r;
673     sp_item_invoke_bbox_full(this, &r, transform, type, TRUE);
674     return r;
677 void
678 sp_item_invoke_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const clear, SPItem::BBoxType type)
680     sp_item_invoke_bbox_full(item, bbox, transform, type, clear);
683 /** Calls \a item's subclass' bounding box method; clips it by the bbox of clippath, if any; and
684  * unions the resulting bbox with \a bbox. If \a clear is true, empties \a bbox first. Passes the
685  * transform and the flags to the actual bbox methods. Note that many of subclasses (e.g. groups,
686  * clones), in turn, call this function in their bbox methods. */
687 void
688 sp_item_invoke_bbox_full(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags, unsigned const clear)
690     g_assert(item != NULL);
691     g_assert(SP_IS_ITEM(item));
692     g_assert(bbox != NULL);
694     if (clear) {
695         bbox->x0 = bbox->y0 = 1e18;
696         bbox->x1 = bbox->y1 = -1e18;
697     }
699     NRRect this_bbox;
700     this_bbox.x0 = this_bbox.y0 = 1e18;
701     this_bbox.x1 = this_bbox.y1 = -1e18;
703     // call the subclass method
704     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox) {
705         ((SPItemClass *) G_OBJECT_GET_CLASS(item))->bbox(item, &this_bbox, transform, flags);
706     }
708     // unless this is geometric bbox, crop the bbox by clip path, if any
709     if ((SPItem::BBoxType) flags != SPItem::GEOMETRIC_BBOX && item->clip_ref->getObject()) {
710         NRRect b;
711         sp_clippath_get_bbox(SP_CLIPPATH(item->clip_ref->getObject()), &b, transform, flags);
712         nr_rect_d_intersect (&this_bbox, &this_bbox, &b);
713     }
715     // if non-empty (with some tolerance - ?) union this_bbox with the bbox we've got passed
716     if ( fabs(this_bbox.x1-this_bbox.x0) > -0.00001 && fabs(this_bbox.y1-this_bbox.y0) > -0.00001 ) {
717         nr_rect_d_union (bbox, bbox, &this_bbox);
718     }
721 unsigned sp_item_pos_in_parent(SPItem *item)
723     g_assert(item != NULL);
724     g_assert(SP_IS_ITEM(item));
726     SPObject *parent = SP_OBJECT_PARENT(item);
727     g_assert(parent != NULL);
728     g_assert(SP_IS_OBJECT(parent));
730     SPObject *object = SP_OBJECT(item);
732     unsigned pos=0;
733     for ( SPObject *iter = sp_object_first_child(parent) ; iter ; iter = SP_OBJECT_NEXT(iter)) {
734         if ( iter == object ) {
735             return pos;
736         }
737         if (SP_IS_ITEM(iter)) {
738             pos++;
739         }
740     }
742     g_assert_not_reached();
743     return 0;
746 void
747 sp_item_bbox_desktop(SPItem *item, NRRect *bbox, SPItem::BBoxType type)
749     g_assert(item != NULL);
750     g_assert(SP_IS_ITEM(item));
751     g_assert(bbox != NULL);
753     sp_item_invoke_bbox(item, bbox, sp_item_i2d_affine(item), TRUE, type);
756 NR::Maybe<NR::Rect> sp_item_bbox_desktop(SPItem *item, SPItem::BBoxType type)
758     NRRect ret;
759     sp_item_invoke_bbox(item, &ret, sp_item_i2d_affine(item), TRUE, type);
760     return ret.upgrade();
763 static void sp_item_private_snappoints(SPItem const *item, SnapPointsIter p)
765     NR::Maybe<NR::Rect> bbox = item->getBounds(sp_item_i2d_affine(item));
766     /* Just the corners of the bounding box suffices given that we don't yet
767        support angled guide lines. */
769     if (bbox) {
770         NR::Point p1, p2;
771         p1 = bbox->min();
772         p2 = bbox->max();
773         *p = p1;
774         *p = NR::Point(p1[NR::X], p2[NR::Y]);
775         *p = p2;
776         *p = NR::Point(p1[NR::Y], p2[NR::X]);
777     }
780 void sp_item_snappoints(SPItem const *item, SnapPointsIter p)
782     g_assert (item != NULL);
783     g_assert (SP_IS_ITEM(item));
785     SPItemClass const &item_class = *(SPItemClass const *) G_OBJECT_GET_CLASS(item);
786     if (item_class.snappoints) {
787         item_class.snappoints(item, p);
788     }
791 void
792 sp_item_invoke_print(SPItem *item, SPPrintContext *ctx)
794     if (!item->isHidden()) {
795         if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->print) {
796             if (!item->transform.test_identity()
797                 || SP_OBJECT_STYLE(item)->opacity.value != SP_SCALE24_MAX)
798             {
799                 sp_print_bind(ctx, item->transform, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value));
800                 ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx);
801                 sp_print_release(ctx);
802             } else {
803                 ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx);
804             }
805         }
806     }
809 static gchar *
810 sp_item_private_description(SPItem *item)
812     return g_strdup(_("Object"));
815 /**
816  * Returns a string suitable for status bar, formatted in pango markup language.
817  *
818  * Must be freed by caller.
819  */
820 gchar *
821 sp_item_description(SPItem *item)
823     g_assert(item != NULL);
824     g_assert(SP_IS_ITEM(item));
826     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->description) {
827         gchar *s = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->description(item);
828         if (s && item->clip_ref->getObject()) {
829             gchar *snew = g_strdup_printf (_("%s; <i>clipped</i>"), s);
830             g_free (s);
831             s = snew;
832         }
833         if (s && item->mask_ref->getObject()) {
834             gchar *snew = g_strdup_printf (_("%s; <i>masked</i>"), s);
835             g_free (s);
836             s = snew;
837         }
838         return s;
839     }
841     g_assert_not_reached();
842     return NULL;
845 /**
846  * Allocates unique integer keys.
847  * \param numkeys Number of keys required.
848  * \return First allocated key; hence if the returned key is n
849  * you can use n, n + 1, ..., n + (numkeys - 1)
850  */
851 unsigned
852 sp_item_display_key_new(unsigned numkeys)
854     static unsigned dkey = 0;
856     dkey += numkeys;
858     return dkey - numkeys;
861 NRArenaItem *
862 sp_item_invoke_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags)
864     g_assert(item != NULL);
865     g_assert(SP_IS_ITEM(item));
866     g_assert(arena != NULL);
867     g_assert(NR_IS_ARENA(arena));
869     NRArenaItem *ai = NULL;
870     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->show) {
871         ai = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->show(item, arena, key, flags);
872     }
874     if (ai != NULL) {
875         item->display = sp_item_view_new_prepend(item->display, item, flags, key, ai);
876         nr_arena_item_set_transform(ai, item->transform);
877         nr_arena_item_set_opacity(ai, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value));
878         nr_arena_item_set_visible(ai, !item->isHidden());
879         nr_arena_item_set_sensitive(ai, item->sensitive);
880         if (item->clip_ref->getObject()) {
881             SPClipPath *cp = item->clip_ref->getObject();
883             if (!item->display->arenaitem->key) {
884                 NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3));
885             }
886             int clip_key = NR_ARENA_ITEM_GET_KEY(item->display->arenaitem);
888             // Show and set clip
889             NRArenaItem *ac = sp_clippath_show(cp, arena, clip_key);
890             nr_arena_item_set_clip(ai, ac);
891             nr_arena_item_unref(ac);
893             // Update bbox, in case the clip uses bbox units
894             NRRect bbox;
895             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
896             sp_clippath_set_bbox(SP_CLIPPATH(cp), clip_key, &bbox);
897             SP_OBJECT(cp)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
898         }
899         if (item->mask_ref->getObject()) {
900             SPMask *mask = item->mask_ref->getObject();
902             if (!item->display->arenaitem->key) {
903                 NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3));
904             }
905             int mask_key = NR_ARENA_ITEM_GET_KEY(item->display->arenaitem);
907             // Show and set mask
908             NRArenaItem *ac = sp_mask_show(mask, arena, mask_key);
909             nr_arena_item_set_mask(ai, ac);
910             nr_arena_item_unref(ac);
912             // Update bbox, in case the mask uses bbox units
913             NRRect bbox;
914             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
915             sp_mask_set_bbox(SP_MASK(mask), mask_key, &bbox);
916             SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
917         }
918         NR_ARENA_ITEM_SET_DATA(ai, item);
919     }
921     return ai;
924 void
925 sp_item_invoke_hide(SPItem *item, unsigned key)
927     g_assert(item != NULL);
928     g_assert(SP_IS_ITEM(item));
930     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide) {
931         ((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide(item, key);
932     }
934     SPItemView *ref = NULL;
935     SPItemView *v = item->display;
936     while (v != NULL) {
937         SPItemView *next = v->next;
938         if (v->key == key) {
939             if (item->clip_ref->getObject()) {
940                 sp_clippath_hide(item->clip_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
941                 nr_arena_item_set_clip(v->arenaitem, NULL);
942             }
943             if (item->mask_ref->getObject()) {
944                 sp_mask_hide(item->mask_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
945                 nr_arena_item_set_mask(v->arenaitem, NULL);
946             }
947             if (!ref) {
948                 item->display = v->next;
949             } else {
950                 ref->next = v->next;
951             }
952             nr_arena_item_unparent(v->arenaitem);
953             nr_arena_item_unref(v->arenaitem);
954             g_free(v);
955         } else {
956             ref = v;
957         }
958         v = next;
959     }
962 // Adjusters
964 void
965 sp_item_adjust_pattern (SPItem *item, NR::Matrix const &postmul, bool set)
967     SPStyle *style = SP_OBJECT_STYLE (item);
969     if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
970         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
971         if (SP_IS_PATTERN (server)) {
972             SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "fill");
973             sp_pattern_transform_multiply (pattern, postmul, set);
974         }
975     }
977     if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
978         SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
979         if (SP_IS_PATTERN (server)) {
980             SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "stroke");
981             sp_pattern_transform_multiply (pattern, postmul, set);
982         }
983     }
987 void
988 sp_item_adjust_gradient (SPItem *item, NR::Matrix const &postmul, bool set)
990     SPStyle *style = SP_OBJECT_STYLE (item);
992     if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
993         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
994         if (SP_IS_GRADIENT (server)) {
996             /**
997              * \note Bbox units for a gradient are generally a bad idea because
998              * with them, you cannot preserve the relative position of the
999              * object and its gradient after rotation or skew. So now we
1000              * convert them to userspace units which are easy to keep in sync
1001              * just by adding the object's transform to gradientTransform.
1002              * \todo FIXME: convert back to bbox units after transforming with
1003              * the item, so as to preserve the original units.
1004              */
1005             SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "fill");
1007             sp_gradient_transform_multiply (gradient, postmul, set);
1008         }
1009     }
1011     if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1012         SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
1013         if (SP_IS_GRADIENT (server)) {
1014             SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "stroke");
1015             sp_gradient_transform_multiply (gradient, postmul, set);
1016         }
1017     }
1020 void
1021 sp_item_adjust_stroke (SPItem *item, gdouble ex)
1023     SPStyle *style = SP_OBJECT_STYLE (item);
1025     if (style && style->stroke.type != SP_PAINT_TYPE_NONE && !NR_DF_TEST_CLOSE (ex, 1.0, NR_EPSILON)) {
1027         style->stroke_width.computed *= ex;
1028         style->stroke_width.set = TRUE;
1030         if (style->stroke_dash.n_dash != 0) {
1031             int i;
1032             for (i = 0; i < style->stroke_dash.n_dash; i++) {
1033                 style->stroke_dash.dash[i] *= ex;
1034             }
1035             style->stroke_dash.offset *= ex;
1036         }
1038         SP_OBJECT(item)->updateRepr();
1039     }
1042 /**
1043  * Find out the inverse of previous transform of an item (from its repr)
1044  */
1045 NR::Matrix
1046 sp_item_transform_repr (SPItem *item)
1048     NR::Matrix t_old(NR::identity());
1049     gchar const *t_attr = SP_OBJECT_REPR(item)->attribute("transform");
1050     if (t_attr) {
1051         NR::Matrix t;
1052         if (sp_svg_transform_read(t_attr, &t)) {
1053             t_old = t;
1054         }
1055     }
1057     return t_old;
1061 /**
1062  * Recursively scale stroke width in \a item and its children by \a expansion.
1063  */
1064 void
1065 sp_item_adjust_stroke_width_recursive(SPItem *item, double expansion)
1067     sp_item_adjust_stroke (item, expansion);
1069 // A clone's child is the ghost of its original - we must not touch it, skip recursion
1070     if (item && SP_IS_USE(item))
1071         return;
1073     for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1074         if (SP_IS_ITEM(o))
1075             sp_item_adjust_stroke_width_recursive(SP_ITEM(o), expansion);
1076     }
1079 /**
1080  * Recursively adjust rx and ry of rects.
1081  */
1082 void
1083 sp_item_adjust_rects_recursive(SPItem *item, NR::Matrix advertized_transform)
1085     if (SP_IS_RECT (item)) {
1086         sp_rect_compensate_rxry (SP_RECT(item), advertized_transform);
1087     }
1089     for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1090         if (SP_IS_ITEM(o))
1091             sp_item_adjust_rects_recursive(SP_ITEM(o), advertized_transform);
1092     }
1095 /**
1096  * Recursively compensate pattern or gradient transform.
1097  */
1098 void
1099 sp_item_adjust_paint_recursive (SPItem *item, NR::Matrix advertized_transform, NR::Matrix t_ancestors, bool is_pattern)
1101 // _Before_ full pattern/gradient transform: t_paint * t_item * t_ancestors
1102 // _After_ full pattern/gradient transform: t_paint_new * t_item * t_ancestors * advertised_transform
1103 // By equating these two expressions we get t_paint_new = t_paint * paint_delta, where:
1104     NR::Matrix t_item = sp_item_transform_repr (item);
1105     NR::Matrix paint_delta = t_item * t_ancestors * advertized_transform * t_ancestors.inverse() * t_item.inverse();
1107 // Within text, we do not fork gradients, and so must not recurse to avoid double compensation;
1108 // also we do not recurse into clones, because a clone's child is the ghost of its original - 
1109 // we must not touch it
1110     if (!(item && (SP_IS_TEXT(item) || SP_IS_USE(item)))) {
1111         for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1112             if (SP_IS_ITEM(o)) {
1113 // At the level of the transformed item, t_ancestors is identity;
1114 // below it, it is the accmmulated chain of transforms from this level to the top level
1115                 sp_item_adjust_paint_recursive (SP_ITEM(o), advertized_transform, t_item * t_ancestors, is_pattern);
1116             }
1117         }
1118     }
1120 // We recursed into children first, and are now adjusting this object second;
1121 // this is so that adjustments in a tree are done from leaves up to the root,
1122 // and paintservers on leaves inheriting their values from ancestors could adjust themselves properly
1123 // before ancestors themselves are adjusted, probably differently (bug 1286535)
1125     if (is_pattern)
1126         sp_item_adjust_pattern (item, paint_delta);
1127     else
1128         sp_item_adjust_gradient (item, paint_delta);
1132 /**
1133  * A temporary wrapper for the next function accepting the NRMatrix
1134  * instead of NR::Matrix
1135  */
1136 void
1137 sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NRMatrix const *transform, NR::Matrix const *adv)
1139     if (transform == NULL)
1140         sp_item_write_transform(item, repr, NR::identity(), adv);
1141     else
1142         sp_item_write_transform(item, repr, NR::Matrix (transform), adv);
1145 /**
1146  * Set a new transform on an object.
1147  *
1148  * Compensate for stroke scaling and gradient/pattern fill transform, if
1149  * necessary. Call the object's set_transform method if transforms are
1150  * stored optimized. Send _transformed_signal. Invoke _write method so that
1151  * the repr is updated with the new transform.
1152  */
1153 void
1154 sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NR::Matrix const &transform, NR::Matrix const *adv, bool compensate)
1156     g_return_if_fail(item != NULL);
1157     g_return_if_fail(SP_IS_ITEM(item));
1158     g_return_if_fail(repr != NULL);
1160     // calculate the relative transform, if not given by the adv attribute
1161     NR::Matrix advertized_transform;
1162     if (adv != NULL) {
1163         advertized_transform = *adv;
1164     } else {
1165         advertized_transform = sp_item_transform_repr (item).inverse() * transform;
1166     }
1167     
1168     if (compensate) {
1169         
1170          // recursively compensate for stroke scaling, depending on user preference
1171         if (prefs_get_int_attribute("options.transform", "stroke", 1) == 0) {
1172             double const expansion = 1. / NR::expansion(advertized_transform);
1173             sp_item_adjust_stroke_width_recursive(item, expansion);
1174         }
1175     
1176         // recursively compensate rx/ry of a rect if requested
1177         if (prefs_get_int_attribute("options.transform", "rectcorners", 1) == 0) {
1178             sp_item_adjust_rects_recursive(item, advertized_transform);
1179         }
1180     
1181         // recursively compensate pattern fill if it's not to be transformed
1182         if (prefs_get_int_attribute("options.transform", "pattern", 1) == 0) {
1183             sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), true);
1184         }
1185         /// \todo FIXME: add the same else branch as for gradients below, to convert patterns to userSpaceOnUse as well
1186         /// recursively compensate gradient fill if it's not to be transformed
1187         if (prefs_get_int_attribute("options.transform", "gradient", 1) == 0) {
1188             sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), false);
1189         } else {
1190             // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do
1191             // it here _before_ the new transform is set, so as to use the pre-transform bbox
1192             sp_item_adjust_paint_recursive (item, NR::identity(), NR::identity(), false);
1193         }          
1194         
1195     } // endif(compensate)
1197     gint preserve = prefs_get_int_attribute("options.preservetransform", "value", 0);
1198     NR::Matrix transform_attr (transform);
1199     if ( // run the object's set_transform (i.e. embed transform) only if:
1200          ((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform && // it does have a set_transform method
1201              !preserve && // user did not chose to preserve all transforms
1202              !item->clip_ref->getObject() && // the object does not have a clippath
1203              !item->mask_ref->getObject() && // the object does not have a mask
1204          !(!transform.is_translation() && SP_OBJECT_STYLE(item) && SP_OBJECT_STYLE(item)->filter.href->getObject()) 
1205              // the object does not have a filter, or the transform is translation (which is supposed to not affect filters)
1206         ) {
1207         transform_attr = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform(item, transform);
1208     }
1209     sp_item_set_item_transform(item, transform_attr);
1211     // Note: updateRepr comes before emitting the transformed signal since
1212     // it causes clone SPUse's copy of the original object to brought up to
1213     // date with the original.  Otherwise, sp_use_bbox returns incorrect
1214     // values if called in code handling the transformed signal.
1215     SP_OBJECT(item)->updateRepr();
1217     // send the relative transform with a _transformed_signal
1218     item->_transformed_signal.emit(&advertized_transform, item);
1221 gint
1222 sp_item_event(SPItem *item, SPEvent *event)
1224     g_return_val_if_fail(item != NULL, FALSE);
1225     g_return_val_if_fail(SP_IS_ITEM(item), FALSE);
1226     g_return_val_if_fail(event != NULL, FALSE);
1228     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->event)
1229         return ((SPItemClass *) G_OBJECT_GET_CLASS(item))->event(item, event);
1231     return FALSE;
1234 /**
1235  * Sets item private transform (not propagated to repr), without compensating stroke widths,
1236  * gradients, patterns as sp_item_write_transform does.
1237  */
1238 void
1239 sp_item_set_item_transform(SPItem *item, NR::Matrix const &transform)
1241     g_return_if_fail(item != NULL);
1242     g_return_if_fail(SP_IS_ITEM(item));
1244     if (!matrix_equalp(transform, item->transform, NR_EPSILON)) {
1245         item->transform = transform;
1246         /* The SP_OBJECT_USER_MODIFIED_FLAG_B is used to mark the fact that it's only a
1247            transformation.  It's apparently not used anywhere else. */
1248         item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_USER_MODIFIED_FLAG_B);
1249         sp_item_rm_unsatisfied_cns(*item);
1250     }
1254 /**
1255  * \pre \a ancestor really is an ancestor (\>=) of \a object, or NULL.
1256  *   ("Ancestor (\>=)" here includes as far as \a object itself.)
1257  */
1258 NR::Matrix
1259 i2anc_affine(SPObject const *object, SPObject const *const ancestor) {
1260     NR::Matrix ret(NR::identity());
1261     g_return_val_if_fail(object != NULL, ret);
1263     /* stop at first non-renderable ancestor */
1264     while ( object != ancestor && SP_IS_ITEM(object) ) {
1265         if (SP_IS_ROOT(object)) {
1266             ret *= SP_ROOT(object)->c2p;
1267         }
1268         ret *= SP_ITEM(object)->transform;
1269         object = SP_OBJECT_PARENT(object);
1270     }
1271     return ret;
1274 NR::Matrix
1275 i2i_affine(SPObject const *src, SPObject const *dest) {
1276     g_return_val_if_fail(src != NULL && dest != NULL, NR::identity());
1277     SPObject const *ancestor = src->nearestCommonAncestor(dest);
1278     return i2anc_affine(src, ancestor) / i2anc_affine(dest, ancestor);
1281 NR::Matrix SPItem::getRelativeTransform(SPObject const *dest) const {
1282     return i2i_affine(this, dest);
1285 /**
1286  * Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
1287  * \pre (item != NULL) and SP_IS_ITEM(item).
1288  */
1289 NR::Matrix sp_item_i2doc_affine(SPItem const *item)
1291     return i2anc_affine(item, NULL);
1294 /**
1295  * Returns the accumulated transformation of the item and all its ancestors, but excluding root's viewport.
1296  * Used in path operations mostly.
1297  * \pre (item != NULL) and SP_IS_ITEM(item).
1298  */
1299 NR::Matrix sp_item_i2root_affine(SPItem const *item)
1301     g_assert(item != NULL);
1302     g_assert(SP_IS_ITEM(item));
1304     NR::Matrix ret(NR::identity());
1305     g_assert(ret.test_identity());
1306     while ( NULL != SP_OBJECT_PARENT(item) ) {
1307         ret *= item->transform;
1308         item = SP_ITEM(SP_OBJECT_PARENT(item));
1309     }
1310     g_assert(SP_IS_ROOT(item));
1312     ret *= item->transform;
1314     return ret;
1317 /* fixme: This is EVIL!!! */
1319 NR::Matrix sp_item_i2d_affine(SPItem const *item)
1321     g_assert(item != NULL);
1322     g_assert(SP_IS_ITEM(item));
1324     NR::Matrix const ret( sp_item_i2doc_affine(item)
1325                           * NR::scale(1, -1)
1326                           * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) );
1327     return ret;
1330 // same as i2d but with i2root instead of i2doc
1331 NR::Matrix sp_item_i2r_affine(SPItem const *item)
1333     g_assert(item != NULL);
1334     g_assert(SP_IS_ITEM(item));
1336     NR::Matrix const ret( sp_item_i2root_affine(item)
1337                           * NR::scale(1, -1)
1338                           * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) );
1339     return ret;
1342 /**
1343  * Converts a matrix \a m into the desktop coords of the \a item.
1344  * Will become a noop when we eliminate the coordinate flipping.
1345  */
1346 NR::Matrix matrix_to_desktop(NR::Matrix const m, SPItem const *item)
1348     NR::Matrix const ret(m
1349                          * NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item)))
1350                          * NR::scale(1, -1));
1351     return ret;
1354 /**
1355  * Converts a matrix \a m from the desktop coords of the \a item.
1356  * Will become a noop when we eliminate the coordinate flipping.
1357  */
1358 NR::Matrix matrix_from_desktop(NR::Matrix const m, SPItem const *item)
1360     NR::Matrix const ret(NR::scale(1, -1)
1361                          * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item)))
1362                          * m);
1363     return ret;
1366 void sp_item_set_i2d_affine(SPItem *item, NR::Matrix const &i2dt)
1368     g_return_if_fail( item != NULL );
1369     g_return_if_fail( SP_IS_ITEM(item) );
1371     NR::Matrix dt2p; /* desktop to item parent transform */
1372     if (SP_OBJECT_PARENT(item)) {
1373         dt2p = sp_item_i2d_affine((SPItem *) SP_OBJECT_PARENT(item)).inverse();
1374     } else {
1375         dt2p = ( NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item)))
1376                  * NR::scale(1, -1) );
1377     }
1379     NR::Matrix const i2p( i2dt * dt2p );
1380     sp_item_set_item_transform(item, i2p);
1384 NR::Matrix
1385 sp_item_dt2i_affine(SPItem const *item)
1387     /* fixme: Implement the right way (Lauris) */
1388     return sp_item_i2d_affine(item).inverse();
1391 /* Item views */
1393 static SPItemView *
1394 sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem)
1396     SPItemView *new_view;
1398     g_assert(item != NULL);
1399     g_assert(SP_IS_ITEM(item));
1400     g_assert(arenaitem != NULL);
1401     g_assert(NR_IS_ARENA_ITEM(arenaitem));
1403     new_view = g_new(SPItemView, 1);
1405     new_view->next = list;
1406     new_view->flags = flags;
1407     new_view->key = key;
1408     new_view->arenaitem = nr_arena_item_ref(arenaitem);
1410     return new_view;
1413 static SPItemView *
1414 sp_item_view_list_remove(SPItemView *list, SPItemView *view)
1416     if (view == list) {
1417         list = list->next;
1418     } else {
1419         SPItemView *prev;
1420         prev = list;
1421         while (prev->next != view) prev = prev->next;
1422         prev->next = view->next;
1423     }
1425     nr_arena_item_unref(view->arenaitem);
1426     g_free(view);
1428     return list;
1431 /**
1432  * Return the arenaitem corresponding to the given item in the display
1433  * with the given key
1434  */
1435 NRArenaItem *
1436 sp_item_get_arenaitem(SPItem *item, unsigned key)
1438     for ( SPItemView *iv = item->display ; iv ; iv = iv->next ) {
1439         if ( iv->key == key ) {
1440             return iv->arenaitem;
1441         }
1442     }
1444     return NULL;
1447 int
1448 sp_item_repr_compare_position(SPItem *first, SPItem *second)
1450     return sp_repr_compare_position(SP_OBJECT_REPR(first),
1451                                     SP_OBJECT_REPR(second));
1454 SPItem *
1455 sp_item_first_item_child (SPObject *obj)
1457     for ( SPObject *iter = sp_object_first_child(obj) ; iter ; iter = SP_OBJECT_NEXT(iter)) {
1458         if (SP_IS_ITEM (iter))
1459             return SP_ITEM (iter);
1460     }
1461     return NULL;
1465 /*
1466   Local Variables:
1467   mode:c++
1468   c-file-style:"stroustrup"
1469   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1470   indent-tabs-mode:nil
1471   fill-column:99
1472   End:
1473 */
1474 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :