Code

initial implementation of XML::Subtree API for tracking changes on a
[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     }
789     
790     *p = item->getCenter();
793 void
794 sp_item_invoke_print(SPItem *item, SPPrintContext *ctx)
796     if (!item->isHidden()) {
797         if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->print) {
798             if (!item->transform.test_identity()
799                 || SP_OBJECT_STYLE(item)->opacity.value != SP_SCALE24_MAX)
800             {
801                 sp_print_bind(ctx, item->transform, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value));
802                 ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx);
803                 sp_print_release(ctx);
804             } else {
805                 ((SPItemClass *) G_OBJECT_GET_CLASS(item))->print(item, ctx);
806             }
807         }
808     }
811 static gchar *
812 sp_item_private_description(SPItem *item)
814     return g_strdup(_("Object"));
817 /**
818  * Returns a string suitable for status bar, formatted in pango markup language.
819  *
820  * Must be freed by caller.
821  */
822 gchar *
823 sp_item_description(SPItem *item)
825     g_assert(item != NULL);
826     g_assert(SP_IS_ITEM(item));
828     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->description) {
829         gchar *s = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->description(item);
830         if (s && item->clip_ref->getObject()) {
831             gchar *snew = g_strdup_printf (_("%s; <i>clipped</i>"), s);
832             g_free (s);
833             s = snew;
834         }
835         if (s && item->mask_ref->getObject()) {
836             gchar *snew = g_strdup_printf (_("%s; <i>masked</i>"), s);
837             g_free (s);
838             s = snew;
839         }
840         return s;
841     }
843     g_assert_not_reached();
844     return NULL;
847 /**
848  * Allocates unique integer keys.
849  * \param numkeys Number of keys required.
850  * \return First allocated key; hence if the returned key is n
851  * you can use n, n + 1, ..., n + (numkeys - 1)
852  */
853 unsigned
854 sp_item_display_key_new(unsigned numkeys)
856     static unsigned dkey = 0;
858     dkey += numkeys;
860     return dkey - numkeys;
863 NRArenaItem *
864 sp_item_invoke_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags)
866     g_assert(item != NULL);
867     g_assert(SP_IS_ITEM(item));
868     g_assert(arena != NULL);
869     g_assert(NR_IS_ARENA(arena));
871     NRArenaItem *ai = NULL;
872     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->show) {
873         ai = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->show(item, arena, key, flags);
874     }
876     if (ai != NULL) {
877         item->display = sp_item_view_new_prepend(item->display, item, flags, key, ai);
878         nr_arena_item_set_transform(ai, item->transform);
879         nr_arena_item_set_opacity(ai, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(item)->opacity.value));
880         nr_arena_item_set_visible(ai, !item->isHidden());
881         nr_arena_item_set_sensitive(ai, item->sensitive);
882         if (item->clip_ref->getObject()) {
883             SPClipPath *cp = item->clip_ref->getObject();
885             if (!item->display->arenaitem->key) {
886                 NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3));
887             }
888             int clip_key = NR_ARENA_ITEM_GET_KEY(item->display->arenaitem);
890             // Show and set clip
891             NRArenaItem *ac = sp_clippath_show(cp, arena, clip_key);
892             nr_arena_item_set_clip(ai, ac);
893             nr_arena_item_unref(ac);
895             // Update bbox, in case the clip uses bbox units
896             NRRect bbox;
897             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
898             sp_clippath_set_bbox(SP_CLIPPATH(cp), clip_key, &bbox);
899             SP_OBJECT(cp)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
900         }
901         if (item->mask_ref->getObject()) {
902             SPMask *mask = item->mask_ref->getObject();
904             if (!item->display->arenaitem->key) {
905                 NR_ARENA_ITEM_SET_KEY(item->display->arenaitem, sp_item_display_key_new(3));
906             }
907             int mask_key = NR_ARENA_ITEM_GET_KEY(item->display->arenaitem);
909             // Show and set mask
910             NRArenaItem *ac = sp_mask_show(mask, arena, mask_key);
911             nr_arena_item_set_mask(ai, ac);
912             nr_arena_item_unref(ac);
914             // Update bbox, in case the mask uses bbox units
915             NRRect bbox;
916             sp_item_invoke_bbox(item, &bbox, NR::identity(), TRUE);
917             sp_mask_set_bbox(SP_MASK(mask), mask_key, &bbox);
918             SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
919         }
920         NR_ARENA_ITEM_SET_DATA(ai, item);
921     }
923     return ai;
926 void
927 sp_item_invoke_hide(SPItem *item, unsigned key)
929     g_assert(item != NULL);
930     g_assert(SP_IS_ITEM(item));
932     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide) {
933         ((SPItemClass *) G_OBJECT_GET_CLASS(item))->hide(item, key);
934     }
936     SPItemView *ref = NULL;
937     SPItemView *v = item->display;
938     while (v != NULL) {
939         SPItemView *next = v->next;
940         if (v->key == key) {
941             if (item->clip_ref->getObject()) {
942                 sp_clippath_hide(item->clip_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
943                 nr_arena_item_set_clip(v->arenaitem, NULL);
944             }
945             if (item->mask_ref->getObject()) {
946                 sp_mask_hide(item->mask_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
947                 nr_arena_item_set_mask(v->arenaitem, NULL);
948             }
949             if (!ref) {
950                 item->display = v->next;
951             } else {
952                 ref->next = v->next;
953             }
954             nr_arena_item_unparent(v->arenaitem);
955             nr_arena_item_unref(v->arenaitem);
956             g_free(v);
957         } else {
958             ref = v;
959         }
960         v = next;
961     }
964 // Adjusters
966 void
967 sp_item_adjust_pattern (SPItem *item, NR::Matrix const &postmul, bool set)
969     SPStyle *style = SP_OBJECT_STYLE (item);
971     if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
972         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (item);
973         if (SP_IS_PATTERN (server)) {
974             SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "fill");
975             sp_pattern_transform_multiply (pattern, postmul, set);
976         }
977     }
979     if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
980         SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (item);
981         if (SP_IS_PATTERN (server)) {
982             SPPattern *pattern = sp_pattern_clone_if_necessary (item, SP_PATTERN (server), "stroke");
983             sp_pattern_transform_multiply (pattern, postmul, set);
984         }
985     }
989 void
990 sp_item_adjust_gradient (SPItem *item, NR::Matrix const &postmul, bool set)
992     SPStyle *style = SP_OBJECT_STYLE (item);
994     if (style && (style->fill.type == SP_PAINT_TYPE_PAINTSERVER)) {
995         SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
996         if (SP_IS_GRADIENT (server)) {
998             /**
999              * \note Bbox units for a gradient are generally a bad idea because
1000              * with them, you cannot preserve the relative position of the
1001              * object and its gradient after rotation or skew. So now we
1002              * convert them to userspace units which are easy to keep in sync
1003              * just by adding the object's transform to gradientTransform.
1004              * \todo FIXME: convert back to bbox units after transforming with
1005              * the item, so as to preserve the original units.
1006              */
1007             SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "fill");
1009             sp_gradient_transform_multiply (gradient, postmul, set);
1010         }
1011     }
1013     if (style && (style->stroke.type == SP_PAINT_TYPE_PAINTSERVER)) {
1014         SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
1015         if (SP_IS_GRADIENT (server)) {
1016             SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), item, "stroke");
1017             sp_gradient_transform_multiply (gradient, postmul, set);
1018         }
1019     }
1022 void
1023 sp_item_adjust_stroke (SPItem *item, gdouble ex)
1025     SPStyle *style = SP_OBJECT_STYLE (item);
1027     if (style && style->stroke.type != SP_PAINT_TYPE_NONE && !NR_DF_TEST_CLOSE (ex, 1.0, NR_EPSILON)) {
1029         style->stroke_width.computed *= ex;
1030         style->stroke_width.set = TRUE;
1032         if (style->stroke_dash.n_dash != 0) {
1033             int i;
1034             for (i = 0; i < style->stroke_dash.n_dash; i++) {
1035                 style->stroke_dash.dash[i] *= ex;
1036             }
1037             style->stroke_dash.offset *= ex;
1038         }
1040         SP_OBJECT(item)->updateRepr();
1041     }
1044 /**
1045  * Find out the inverse of previous transform of an item (from its repr)
1046  */
1047 NR::Matrix
1048 sp_item_transform_repr (SPItem *item)
1050     NR::Matrix t_old(NR::identity());
1051     gchar const *t_attr = SP_OBJECT_REPR(item)->attribute("transform");
1052     if (t_attr) {
1053         NR::Matrix t;
1054         if (sp_svg_transform_read(t_attr, &t)) {
1055             t_old = t;
1056         }
1057     }
1059     return t_old;
1063 /**
1064  * Recursively scale stroke width in \a item and its children by \a expansion.
1065  */
1066 void
1067 sp_item_adjust_stroke_width_recursive(SPItem *item, double expansion)
1069     sp_item_adjust_stroke (item, expansion);
1071 // A clone's child is the ghost of its original - we must not touch it, skip recursion
1072     if (item && SP_IS_USE(item))
1073         return;
1075     for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1076         if (SP_IS_ITEM(o))
1077             sp_item_adjust_stroke_width_recursive(SP_ITEM(o), expansion);
1078     }
1081 /**
1082  * Recursively adjust rx and ry of rects.
1083  */
1084 void
1085 sp_item_adjust_rects_recursive(SPItem *item, NR::Matrix advertized_transform)
1087     if (SP_IS_RECT (item)) {
1088         sp_rect_compensate_rxry (SP_RECT(item), advertized_transform);
1089     }
1091     for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1092         if (SP_IS_ITEM(o))
1093             sp_item_adjust_rects_recursive(SP_ITEM(o), advertized_transform);
1094     }
1097 /**
1098  * Recursively compensate pattern or gradient transform.
1099  */
1100 void
1101 sp_item_adjust_paint_recursive (SPItem *item, NR::Matrix advertized_transform, NR::Matrix t_ancestors, bool is_pattern)
1103 // _Before_ full pattern/gradient transform: t_paint * t_item * t_ancestors
1104 // _After_ full pattern/gradient transform: t_paint_new * t_item * t_ancestors * advertised_transform
1105 // By equating these two expressions we get t_paint_new = t_paint * paint_delta, where:
1106     NR::Matrix t_item = sp_item_transform_repr (item);
1107     NR::Matrix paint_delta = t_item * t_ancestors * advertized_transform * t_ancestors.inverse() * t_item.inverse();
1109 // Within text, we do not fork gradients, and so must not recurse to avoid double compensation;
1110 // also we do not recurse into clones, because a clone's child is the ghost of its original - 
1111 // we must not touch it
1112     if (!(item && (SP_IS_TEXT(item) || SP_IS_USE(item)))) {
1113         for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1114             if (SP_IS_ITEM(o)) {
1115 // At the level of the transformed item, t_ancestors is identity;
1116 // below it, it is the accmmulated chain of transforms from this level to the top level
1117                 sp_item_adjust_paint_recursive (SP_ITEM(o), advertized_transform, t_item * t_ancestors, is_pattern);
1118             }
1119         }
1120     }
1122 // We recursed into children first, and are now adjusting this object second;
1123 // this is so that adjustments in a tree are done from leaves up to the root,
1124 // and paintservers on leaves inheriting their values from ancestors could adjust themselves properly
1125 // before ancestors themselves are adjusted, probably differently (bug 1286535)
1127     if (is_pattern)
1128         sp_item_adjust_pattern (item, paint_delta);
1129     else
1130         sp_item_adjust_gradient (item, paint_delta);
1134 /**
1135  * A temporary wrapper for the next function accepting the NRMatrix
1136  * instead of NR::Matrix
1137  */
1138 void
1139 sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NRMatrix const *transform, NR::Matrix const *adv)
1141     if (transform == NULL)
1142         sp_item_write_transform(item, repr, NR::identity(), adv);
1143     else
1144         sp_item_write_transform(item, repr, NR::Matrix (transform), adv);
1147 /**
1148  * Set a new transform on an object.
1149  *
1150  * Compensate for stroke scaling and gradient/pattern fill transform, if
1151  * necessary. Call the object's set_transform method if transforms are
1152  * stored optimized. Send _transformed_signal. Invoke _write method so that
1153  * the repr is updated with the new transform.
1154  */
1155 void
1156 sp_item_write_transform(SPItem *item, Inkscape::XML::Node *repr, NR::Matrix const &transform, NR::Matrix const *adv, bool compensate)
1158     g_return_if_fail(item != NULL);
1159     g_return_if_fail(SP_IS_ITEM(item));
1160     g_return_if_fail(repr != NULL);
1162     // calculate the relative transform, if not given by the adv attribute
1163     NR::Matrix advertized_transform;
1164     if (adv != NULL) {
1165         advertized_transform = *adv;
1166     } else {
1167         advertized_transform = sp_item_transform_repr (item).inverse() * transform;
1168     }
1169     
1170     if (compensate) {
1171         
1172          // recursively compensate for stroke scaling, depending on user preference
1173         if (prefs_get_int_attribute("options.transform", "stroke", 1) == 0) {
1174             double const expansion = 1. / NR::expansion(advertized_transform);
1175             sp_item_adjust_stroke_width_recursive(item, expansion);
1176         }
1177     
1178         // recursively compensate rx/ry of a rect if requested
1179         if (prefs_get_int_attribute("options.transform", "rectcorners", 1) == 0) {
1180             sp_item_adjust_rects_recursive(item, advertized_transform);
1181         }
1182     
1183         // recursively compensate pattern fill if it's not to be transformed
1184         if (prefs_get_int_attribute("options.transform", "pattern", 1) == 0) {
1185             sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), true);
1186         }
1187         /// \todo FIXME: add the same else branch as for gradients below, to convert patterns to userSpaceOnUse as well
1188         /// recursively compensate gradient fill if it's not to be transformed
1189         if (prefs_get_int_attribute("options.transform", "gradient", 1) == 0) {
1190             sp_item_adjust_paint_recursive (item, advertized_transform.inverse(), NR::identity(), false);
1191         } else {
1192             // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do
1193             // it here _before_ the new transform is set, so as to use the pre-transform bbox
1194             sp_item_adjust_paint_recursive (item, NR::identity(), NR::identity(), false);
1195         }          
1196         
1197     } // endif(compensate)
1199     gint preserve = prefs_get_int_attribute("options.preservetransform", "value", 0);
1200     NR::Matrix transform_attr (transform);
1201     if ( // run the object's set_transform (i.e. embed transform) only if:
1202          ((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform && // it does have a set_transform method
1203              !preserve && // user did not chose to preserve all transforms
1204              !item->clip_ref->getObject() && // the object does not have a clippath
1205              !item->mask_ref->getObject() && // the object does not have a mask
1206          !(!transform.is_translation() && SP_OBJECT_STYLE(item) && SP_OBJECT_STYLE(item)->getFilter()) 
1207              // the object does not have a filter, or the transform is translation (which is supposed to not affect filters)
1208         ) {
1209         transform_attr = ((SPItemClass *) G_OBJECT_GET_CLASS(item))->set_transform(item, transform);
1210     }
1211     sp_item_set_item_transform(item, transform_attr);
1213     // Note: updateRepr comes before emitting the transformed signal since
1214     // it causes clone SPUse's copy of the original object to brought up to
1215     // date with the original.  Otherwise, sp_use_bbox returns incorrect
1216     // values if called in code handling the transformed signal.
1217     SP_OBJECT(item)->updateRepr();
1219     // send the relative transform with a _transformed_signal
1220     item->_transformed_signal.emit(&advertized_transform, item);
1223 gint
1224 sp_item_event(SPItem *item, SPEvent *event)
1226     g_return_val_if_fail(item != NULL, FALSE);
1227     g_return_val_if_fail(SP_IS_ITEM(item), FALSE);
1228     g_return_val_if_fail(event != NULL, FALSE);
1230     if (((SPItemClass *) G_OBJECT_GET_CLASS(item))->event)
1231         return ((SPItemClass *) G_OBJECT_GET_CLASS(item))->event(item, event);
1233     return FALSE;
1236 /**
1237  * Sets item private transform (not propagated to repr), without compensating stroke widths,
1238  * gradients, patterns as sp_item_write_transform does.
1239  */
1240 void
1241 sp_item_set_item_transform(SPItem *item, NR::Matrix const &transform)
1243     g_return_if_fail(item != NULL);
1244     g_return_if_fail(SP_IS_ITEM(item));
1246     if (!matrix_equalp(transform, item->transform, NR_EPSILON)) {
1247         item->transform = transform;
1248         /* The SP_OBJECT_USER_MODIFIED_FLAG_B is used to mark the fact that it's only a
1249            transformation.  It's apparently not used anywhere else. */
1250         item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_USER_MODIFIED_FLAG_B);
1251         sp_item_rm_unsatisfied_cns(*item);
1252     }
1256 /**
1257  * \pre \a ancestor really is an ancestor (\>=) of \a object, or NULL.
1258  *   ("Ancestor (\>=)" here includes as far as \a object itself.)
1259  */
1260 NR::Matrix
1261 i2anc_affine(SPObject const *object, SPObject const *const ancestor) {
1262     NR::Matrix ret(NR::identity());
1263     g_return_val_if_fail(object != NULL, ret);
1265     /* stop at first non-renderable ancestor */
1266     while ( object != ancestor && SP_IS_ITEM(object) ) {
1267         if (SP_IS_ROOT(object)) {
1268             ret *= SP_ROOT(object)->c2p;
1269         }
1270         ret *= SP_ITEM(object)->transform;
1271         object = SP_OBJECT_PARENT(object);
1272     }
1273     return ret;
1276 NR::Matrix
1277 i2i_affine(SPObject const *src, SPObject const *dest) {
1278     g_return_val_if_fail(src != NULL && dest != NULL, NR::identity());
1279     SPObject const *ancestor = src->nearestCommonAncestor(dest);
1280     return i2anc_affine(src, ancestor) / i2anc_affine(dest, ancestor);
1283 NR::Matrix SPItem::getRelativeTransform(SPObject const *dest) const {
1284     return i2i_affine(this, dest);
1287 /**
1288  * Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
1289  * \pre (item != NULL) and SP_IS_ITEM(item).
1290  */
1291 NR::Matrix sp_item_i2doc_affine(SPItem const *item)
1293     return i2anc_affine(item, NULL);
1296 /**
1297  * Returns the accumulated transformation of the item and all its ancestors, but excluding root's viewport.
1298  * Used in path operations mostly.
1299  * \pre (item != NULL) and SP_IS_ITEM(item).
1300  */
1301 NR::Matrix sp_item_i2root_affine(SPItem const *item)
1303     g_assert(item != NULL);
1304     g_assert(SP_IS_ITEM(item));
1306     NR::Matrix ret(NR::identity());
1307     g_assert(ret.test_identity());
1308     while ( NULL != SP_OBJECT_PARENT(item) ) {
1309         ret *= item->transform;
1310         item = SP_ITEM(SP_OBJECT_PARENT(item));
1311     }
1312     g_assert(SP_IS_ROOT(item));
1314     ret *= item->transform;
1316     return ret;
1319 /* fixme: This is EVIL!!! */
1321 NR::Matrix sp_item_i2d_affine(SPItem const *item)
1323     g_assert(item != NULL);
1324     g_assert(SP_IS_ITEM(item));
1326     NR::Matrix const ret( sp_item_i2doc_affine(item)
1327                           * NR::scale(1, -1)
1328                           * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) );
1329     return ret;
1332 // same as i2d but with i2root instead of i2doc
1333 NR::Matrix sp_item_i2r_affine(SPItem const *item)
1335     g_assert(item != NULL);
1336     g_assert(SP_IS_ITEM(item));
1338     NR::Matrix const ret( sp_item_i2root_affine(item)
1339                           * NR::scale(1, -1)
1340                           * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item))) );
1341     return ret;
1344 /**
1345  * Converts a matrix \a m into the desktop coords of the \a item.
1346  * Will become a noop when we eliminate the coordinate flipping.
1347  */
1348 NR::Matrix matrix_to_desktop(NR::Matrix const m, SPItem const *item)
1350     NR::Matrix const ret(m
1351                          * NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item)))
1352                          * NR::scale(1, -1));
1353     return ret;
1356 /**
1357  * Converts a matrix \a m from the desktop coords of the \a item.
1358  * Will become a noop when we eliminate the coordinate flipping.
1359  */
1360 NR::Matrix matrix_from_desktop(NR::Matrix const m, SPItem const *item)
1362     NR::Matrix const ret(NR::scale(1, -1)
1363                          * NR::translate(0, sp_document_height(SP_OBJECT_DOCUMENT(item)))
1364                          * m);
1365     return ret;
1368 void sp_item_set_i2d_affine(SPItem *item, NR::Matrix const &i2dt)
1370     g_return_if_fail( item != NULL );
1371     g_return_if_fail( SP_IS_ITEM(item) );
1373     NR::Matrix dt2p; /* desktop to item parent transform */
1374     if (SP_OBJECT_PARENT(item)) {
1375         dt2p = sp_item_i2d_affine((SPItem *) SP_OBJECT_PARENT(item)).inverse();
1376     } else {
1377         dt2p = ( NR::translate(0, -sp_document_height(SP_OBJECT_DOCUMENT(item)))
1378                  * NR::scale(1, -1) );
1379     }
1381     NR::Matrix const i2p( i2dt * dt2p );
1382     sp_item_set_item_transform(item, i2p);
1386 NR::Matrix
1387 sp_item_dt2i_affine(SPItem const *item)
1389     /* fixme: Implement the right way (Lauris) */
1390     return sp_item_i2d_affine(item).inverse();
1393 /* Item views */
1395 static SPItemView *
1396 sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem)
1398     SPItemView *new_view;
1400     g_assert(item != NULL);
1401     g_assert(SP_IS_ITEM(item));
1402     g_assert(arenaitem != NULL);
1403     g_assert(NR_IS_ARENA_ITEM(arenaitem));
1405     new_view = g_new(SPItemView, 1);
1407     new_view->next = list;
1408     new_view->flags = flags;
1409     new_view->key = key;
1410     new_view->arenaitem = nr_arena_item_ref(arenaitem);
1412     return new_view;
1415 static SPItemView *
1416 sp_item_view_list_remove(SPItemView *list, SPItemView *view)
1418     if (view == list) {
1419         list = list->next;
1420     } else {
1421         SPItemView *prev;
1422         prev = list;
1423         while (prev->next != view) prev = prev->next;
1424         prev->next = view->next;
1425     }
1427     nr_arena_item_unref(view->arenaitem);
1428     g_free(view);
1430     return list;
1433 /**
1434  * Return the arenaitem corresponding to the given item in the display
1435  * with the given key
1436  */
1437 NRArenaItem *
1438 sp_item_get_arenaitem(SPItem *item, unsigned key)
1440     for ( SPItemView *iv = item->display ; iv ; iv = iv->next ) {
1441         if ( iv->key == key ) {
1442             return iv->arenaitem;
1443         }
1444     }
1446     return NULL;
1449 int
1450 sp_item_repr_compare_position(SPItem *first, SPItem *second)
1452     return sp_repr_compare_position(SP_OBJECT_REPR(first),
1453                                     SP_OBJECT_REPR(second));
1456 SPItem *
1457 sp_item_first_item_child (SPObject *obj)
1459     for ( SPObject *iter = sp_object_first_child(obj) ; iter ; iter = SP_OBJECT_NEXT(iter)) {
1460         if (SP_IS_ITEM (iter))
1461             return SP_ITEM (iter);
1462     }
1463     return NULL;
1467 /*
1468   Local Variables:
1469   mode:c++
1470   c-file-style:"stroustrup"
1471   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1472   indent-tabs-mode:nil
1473   fill-column:99
1474   End:
1475 */
1476 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :