1 /** \file
2 * Base class for visual SVG elements
3 */
4 /*
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
9 *
10 * Copyright (C) 2001-2006 authors
11 * Copyright (C) 2001 Ximian, Inc.
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 /** \class SPItem
17 *
18 * SPItem is an abstract base class for all graphic (visible) SVG nodes. It
19 * is a subclass of SPObject, with great deal of specific functionality.
20 */
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
27 #include "sp-item.h"
28 #include "svg/svg.h"
29 #include "print.h"
30 #include "display/nr-arena.h"
31 #include "display/nr-arena-item.h"
32 #include "attributes.h"
33 #include "document.h"
34 #include "uri.h"
35 #include "inkscape.h"
36 #include "desktop.h"
37 #include "desktop-handles.h"
39 #include "style.h"
40 #include <glibmm/i18n.h>
41 #include "sp-root.h"
42 #include "sp-clippath.h"
43 #include "sp-mask.h"
44 #include "sp-rect.h"
45 #include "sp-use.h"
46 #include "sp-text.h"
47 #include "sp-item-rm-unsatisfied-cns.h"
48 #include "sp-pattern.h"
49 #include "sp-switch.h"
50 #include "sp-guide-constraint.h"
51 #include "gradient-chemistry.h"
52 #include "preferences.h"
53 #include "conn-avoid-ref.h"
54 #include "conditions.h"
55 #include "sp-filter-reference.h"
56 #include "filter-chemistry.h"
57 #include "sp-guide.h"
58 #include "sp-title.h"
59 #include "sp-desc.h"
61 #include "libnr/nr-matrix-fns.h"
62 #include "libnr/nr-matrix-scale-ops.h"
63 #include "libnr/nr-matrix-translate-ops.h"
64 #include "libnr/nr-scale-translate-ops.h"
65 #include "libnr/nr-translate-scale-ops.h"
66 #include "libnr/nr-convert2geom.h"
67 #include "util/find-last-if.h"
68 #include "util/reverse-list.h"
69 #include <2geom/rect.h>
70 #include <2geom/matrix.h>
71 #include <2geom/transforms.h>
73 #include "xml/repr.h"
74 #include "extract-uri.h"
75 #include "helper/geom.h"
77 #include "live_effects/lpeobject.h"
78 #include "live_effects/effect.h"
79 #include "live_effects/lpeobject-reference.h"
81 #define noSP_ITEM_DEBUG_IDLE
83 //static void sp_item_class_init(SPItemClass *klass);
84 //static void sp_item_init(SPItem *item);
86 //static void sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
87 //static void sp_item_release(SPObject *object);
88 //static void sp_item_set(SPObject *object, unsigned key, gchar const *value);
89 //static void sp_item_update(SPObject *object, SPCtx *ctx, guint flags);
90 //static Inkscape::XML::Node *sp_item_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
92 //static gchar *sp_item_private_description(SPItem *item);
93 //static void sp_item_private_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
95 //static SPItemView *sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem);
96 //static SPItemView *sp_item_view_list_remove(SPItemView *list, SPItemView *view);
98 //static SPObjectClass *parent_class;
100 //static void clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
101 //static void mask_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item);
103 SPObjectClass * SPItemClass::static_parent_class=0;
105 /**
106 * Registers SPItem class and returns its type number.
107 */
108 GType
109 SPItem::getType(void)
110 {
111 static GType type = 0;
112 if (!type) {
113 GTypeInfo info = {
114 sizeof(SPItemClass),
115 NULL, NULL,
116 (GClassInitFunc) SPItemClass::sp_item_class_init,
117 NULL, NULL,
118 sizeof(SPItem),
119 16,
120 (GInstanceInitFunc) sp_item_init,
121 NULL, /* value_table */
122 };
123 type = g_type_register_static(SP_TYPE_OBJECT, "SPItem", &info, (GTypeFlags)0);
124 }
125 return type;
126 }
128 /**
129 * SPItem vtable initialization.
130 */
131 void
132 SPItemClass::sp_item_class_init(SPItemClass *klass)
133 {
134 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
136 static_parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT);
138 sp_object_class->build = SPItem::sp_item_build;
139 sp_object_class->release = SPItem::sp_item_release;
140 sp_object_class->set = SPItem::sp_item_set;
141 sp_object_class->update = SPItem::sp_item_update;
142 sp_object_class->write = SPItem::sp_item_write;
144 klass->description = SPItem::sp_item_private_description;
145 klass->snappoints = SPItem::sp_item_private_snappoints;
146 }
148 /**
149 * Callback for SPItem object initialization.
150 */
151 void
152 SPItem::sp_item_init(SPItem *item)
153 {
154 item->init();
155 }
157 void SPItem::init() {
158 this->sensitive = TRUE;
160 this->transform_center_x = 0;
161 this->transform_center_y = 0;
163 this->_is_evaluated = true;
164 this->_evaluated_status = StatusUnknown;
166 this->transform = Geom::identity();
168 this->display = NULL;
170 this->clip_ref = new SPClipPathReference(this);
171 sigc::signal<void, SPObject *, SPObject *> cs1=this->clip_ref->changedSignal();
172 sigc::slot2<void,SPObject*, SPObject *> sl1=sigc::bind(sigc::ptr_fun(clip_ref_changed), this);
173 _clip_ref_connection = cs1.connect(sl1);
175 this->mask_ref = new SPMaskReference(this);
176 sigc::signal<void, SPObject *, SPObject *> cs2=this->mask_ref->changedSignal();
177 sigc::slot2<void,SPObject*, SPObject *> sl2=sigc::bind(sigc::ptr_fun(mask_ref_changed), this);
178 _mask_ref_connection = cs2.connect(sl2);
180 this->avoidRef = new SPAvoidRef(this);
182 new (&this->constraints) std::vector<SPGuideConstraint>();
184 new (&this->_transformed_signal) sigc::signal<void, Geom::Matrix const *, SPItem *>();
185 }
187 bool SPItem::isVisibleAndUnlocked() const {
188 return (!isHidden() && !isLocked());
189 }
191 bool SPItem::isVisibleAndUnlocked(unsigned display_key) const {
192 return (!isHidden(display_key) && !isLocked());
193 }
195 bool SPItem::isLocked() const {
196 for (SPObject *o = SP_OBJECT(this); o != NULL; o = SP_OBJECT_PARENT(o)) {
197 if (SP_IS_ITEM(o) && !(SP_ITEM(o)->sensitive))
198 return true;
199 }
200 return false;
201 }
203 void SPItem::setLocked(bool locked) {
204 SP_OBJECT_REPR(this)->setAttribute("sodipodi:insensitive",
205 ( locked ? "1" : NULL ));
206 updateRepr();
207 }
209 bool SPItem::isHidden() const {
210 if (!isEvaluated())
211 return true;
212 return style->display.computed == SP_CSS_DISPLAY_NONE;
213 }
215 void SPItem::setHidden(bool hide) {
216 style->display.set = TRUE;
217 style->display.value = ( hide ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
218 style->display.computed = style->display.value;
219 style->display.inherit = FALSE;
220 updateRepr();
221 }
223 bool SPItem::isHidden(unsigned display_key) const {
224 if (!isEvaluated())
225 return true;
226 for ( SPItemView *view(display) ; view ; view = view->next ) {
227 if ( view->key == display_key ) {
228 g_assert(view->arenaitem != NULL);
229 for ( NRArenaItem *arenaitem = view->arenaitem ;
230 arenaitem ; arenaitem = arenaitem->parent )
231 {
232 if (!arenaitem->visible) {
233 return true;
234 }
235 }
236 return false;
237 }
238 }
239 return true;
240 }
242 void SPItem::setEvaluated(bool evaluated) {
243 _is_evaluated = evaluated;
244 _evaluated_status = StatusSet;
245 }
247 void SPItem::resetEvaluated() {
248 if ( StatusCalculated == _evaluated_status ) {
249 _evaluated_status = StatusUnknown;
250 bool oldValue = _is_evaluated;
251 if ( oldValue != isEvaluated() ) {
252 requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
253 }
254 } if ( StatusSet == _evaluated_status ) {
255 SPObject const *const parent = SP_OBJECT_PARENT(this);
256 if (SP_IS_SWITCH(parent)) {
257 SP_SWITCH(parent)->resetChildEvaluated();
258 }
259 }
260 }
262 bool SPItem::isEvaluated() const {
263 if ( StatusUnknown == _evaluated_status ) {
264 _is_evaluated = sp_item_evaluate(this);
265 _evaluated_status = StatusCalculated;
266 }
267 return _is_evaluated;
268 }
270 /**
271 * Returns something suitable for the `Hide' checkbox in the Object Properties dialog box.
272 * Corresponds to setExplicitlyHidden.
273 */
274 bool
275 SPItem::isExplicitlyHidden() const
276 {
277 return (this->style->display.set
278 && this->style->display.value == SP_CSS_DISPLAY_NONE);
279 }
281 /**
282 * Sets the display CSS property to `hidden' if \a val is true,
283 * otherwise makes it unset
284 */
285 void
286 SPItem::setExplicitlyHidden(bool const val) {
287 this->style->display.set = val;
288 this->style->display.value = ( val ? SP_CSS_DISPLAY_NONE : SP_CSS_DISPLAY_INLINE );
289 this->style->display.computed = this->style->display.value;
290 this->updateRepr();
291 }
293 /**
294 * Sets the transform_center_x and transform_center_y properties to retain the rotation centre
295 */
296 void
297 SPItem::setCenter(Geom::Point object_centre) {
298 // for getBounds() to work
299 SP_OBJECT_DOCUMENT(this)->ensure_up_to_date();
301 Geom::OptRect bbox = getBounds(i2d_affine());
302 if (bbox) {
303 transform_center_x = object_centre[Geom::X] - bbox->midpoint()[Geom::X];
304 if (fabs(transform_center_x) < 1e-5) // rounding error
305 transform_center_x = 0;
306 transform_center_y = object_centre[Geom::Y] - bbox->midpoint()[Geom::Y];
307 if (fabs(transform_center_y) < 1e-5) // rounding error
308 transform_center_y = 0;
309 }
310 }
312 void
313 SPItem::unsetCenter() {
314 transform_center_x = 0;
315 transform_center_y = 0;
316 }
318 bool SPItem::isCenterSet() {
319 return (transform_center_x != 0 || transform_center_y != 0);
320 }
322 Geom::Point SPItem::getCenter() const {
323 // for getBounds() to work
324 SP_OBJECT_DOCUMENT(this)->ensure_up_to_date();
326 Geom::OptRect bbox = getBounds(i2d_affine());
327 if (bbox) {
328 return to_2geom(bbox->midpoint()) + Geom::Point (this->transform_center_x, this->transform_center_y);
329 } else {
330 return Geom::Point (0, 0); // something's wrong!
331 }
332 }
335 namespace {
337 bool is_item(SPObject const &object) {
338 return SP_IS_ITEM(&object);
339 }
341 }
343 void SPItem::raiseToTop() {
344 using Inkscape::Algorithms::find_last_if;
346 SPObject *topmost=find_last_if<SPObject::SiblingIterator>(
347 SP_OBJECT_NEXT(this), NULL, &is_item
348 );
349 if (topmost) {
350 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
351 sp_repr_parent(repr)->changeOrder(repr, SP_OBJECT_REPR(topmost));
352 }
353 }
355 void SPItem::raiseOne() {
356 SPObject *next_higher=std::find_if<SPObject::SiblingIterator>(
357 SP_OBJECT_NEXT(this), NULL, &is_item
358 );
359 if (next_higher) {
360 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
361 Inkscape::XML::Node *ref=SP_OBJECT_REPR(next_higher);
362 sp_repr_parent(repr)->changeOrder(repr, ref);
363 }
364 }
366 void SPItem::lowerOne() {
367 using Inkscape::Util::MutableList;
368 using Inkscape::Util::reverse_list;
370 MutableList<SPObject &> next_lower=std::find_if(
371 reverse_list<SPObject::SiblingIterator>(
372 SP_OBJECT_PARENT(this)->firstChild(), this
373 ),
374 MutableList<SPObject &>(),
375 &is_item
376 );
377 if (next_lower) {
378 ++next_lower;
379 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
380 Inkscape::XML::Node *ref=( next_lower ? SP_OBJECT_REPR(&*next_lower) : NULL );
381 sp_repr_parent(repr)->changeOrder(repr, ref);
382 }
383 }
385 void SPItem::lowerToBottom() {
386 using Inkscape::Algorithms::find_last_if;
387 using Inkscape::Util::MutableList;
388 using Inkscape::Util::reverse_list;
390 MutableList<SPObject &> bottom=find_last_if(
391 reverse_list<SPObject::SiblingIterator>(
392 SP_OBJECT_PARENT(this)->firstChild(), this
393 ),
394 MutableList<SPObject &>(),
395 &is_item
396 );
397 if (bottom) {
398 ++bottom;
399 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
400 Inkscape::XML::Node *ref=( bottom ? SP_OBJECT_REPR(&*bottom) : NULL );
401 sp_repr_parent(repr)->changeOrder(repr, ref);
402 }
403 }
405 void
406 SPItem::sp_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
407 {
408 object->readAttr( "style");
409 object->readAttr( "transform");
410 object->readAttr( "clip-path");
411 object->readAttr( "mask");
412 object->readAttr( "sodipodi:insensitive");
413 object->readAttr( "sodipodi:nonprintable");
414 object->readAttr( "inkscape:transform-center-x");
415 object->readAttr( "inkscape:transform-center-y");
416 object->readAttr( "inkscape:connector-avoid");
417 object->readAttr( "inkscape:connection-points");
419 if (((SPObjectClass *) (SPItemClass::static_parent_class))->build) {
420 (* ((SPObjectClass *) (SPItemClass::static_parent_class))->build)(object, document, repr);
421 }
422 }
424 void
425 SPItem::sp_item_release(SPObject *object)
426 {
427 SPItem *item = (SPItem *) object;
429 item->_clip_ref_connection.disconnect();
430 item->_mask_ref_connection.disconnect();
432 // Note: do this here before the clip_ref is deleted, since calling
433 // sp_document_ensure_up_to_date for triggered routing may reference
434 // the deleted clip_ref.
435 if (item->avoidRef) {
436 delete item->avoidRef;
437 item->avoidRef = NULL;
438 }
440 if (item->clip_ref) {
441 item->clip_ref->detach();
442 delete item->clip_ref;
443 item->clip_ref = NULL;
444 }
446 if (item->mask_ref) {
447 item->mask_ref->detach();
448 delete item->mask_ref;
449 item->mask_ref = NULL;
450 }
452 if (((SPObjectClass *) (SPItemClass::static_parent_class))->release) {
453 ((SPObjectClass *) SPItemClass::static_parent_class)->release(object);
454 }
456 while (item->display) {
457 nr_arena_item_unparent(item->display->arenaitem);
458 item->display = sp_item_view_list_remove(item->display, item->display);
459 }
461 item->_transformed_signal.~signal();
462 }
464 void
465 SPItem::sp_item_set(SPObject *object, unsigned key, gchar const *value)
466 {
467 SPItem *item = (SPItem *) object;
469 switch (key) {
470 case SP_ATTR_TRANSFORM: {
471 Geom::Matrix t;
472 if (value && sp_svg_transform_read(value, &t)) {
473 item->set_item_transform(t);
474 } else {
475 item->set_item_transform(Geom::identity());
476 }
477 break;
478 }
479 case SP_PROP_CLIP_PATH: {
480 gchar *uri = extract_uri(value);
481 if (uri) {
482 try {
483 item->clip_ref->attach(Inkscape::URI(uri));
484 } catch (Inkscape::BadURIException &e) {
485 g_warning("%s", e.what());
486 item->clip_ref->detach();
487 }
488 g_free(uri);
489 } else {
490 item->clip_ref->detach();
491 }
493 break;
494 }
495 case SP_PROP_MASK: {
496 gchar *uri = extract_uri(value);
497 if (uri) {
498 try {
499 item->mask_ref->attach(Inkscape::URI(uri));
500 } catch (Inkscape::BadURIException &e) {
501 g_warning("%s", e.what());
502 item->mask_ref->detach();
503 }
504 g_free(uri);
505 } else {
506 item->mask_ref->detach();
507 }
509 break;
510 }
511 case SP_ATTR_SODIPODI_INSENSITIVE:
512 item->sensitive = !value;
513 for (SPItemView *v = item->display; v != NULL; v = v->next) {
514 nr_arena_item_set_sensitive(v->arenaitem, item->sensitive);
515 }
516 break;
517 case SP_ATTR_CONNECTOR_AVOID:
518 item->avoidRef->setAvoid(value);
519 break;
520 case SP_ATTR_CONNECTION_POINTS:
521 item->avoidRef->setConnectionPoints(value);
522 break;
523 case SP_ATTR_TRANSFORM_CENTER_X:
524 if (value) {
525 item->transform_center_x = g_strtod(value, NULL);
526 } else {
527 item->transform_center_x = 0;
528 }
529 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
530 break;
531 case SP_ATTR_TRANSFORM_CENTER_Y:
532 if (value) {
533 item->transform_center_y = g_strtod(value, NULL);
534 } else {
535 item->transform_center_y = 0;
536 }
537 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
538 break;
539 case SP_PROP_SYSTEM_LANGUAGE:
540 case SP_PROP_REQUIRED_FEATURES:
541 case SP_PROP_REQUIRED_EXTENSIONS:
542 {
543 item->resetEvaluated();
544 // pass to default handler
545 }
546 default:
547 if (SP_ATTRIBUTE_IS_CSS(key)) {
548 sp_style_read_from_object(object->style, object);
549 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
550 } else {
551 if (((SPObjectClass *) (SPItemClass::static_parent_class))->set) {
552 (* ((SPObjectClass *) (SPItemClass::static_parent_class))->set)(object, key, value);
553 }
554 }
555 break;
556 }
557 }
559 void
560 SPItem::clip_ref_changed(SPObject *old_clip, SPObject *clip, SPItem *item)
561 {
562 if (old_clip) {
563 SPItemView *v;
564 /* Hide clippath */
565 for (v = item->display; v != NULL; v = v->next) {
566 SP_CLIPPATH(old_clip)->sp_clippath_hide(NR_ARENA_ITEM_GET_KEY(v->arenaitem));
567 nr_arena_item_set_clip(v->arenaitem, NULL);
568 }
569 }
570 if (SP_IS_CLIPPATH(clip)) {
571 NRRect bbox;
572 item->invoke_bbox( &bbox, Geom::identity(), TRUE);
573 for (SPItemView *v = item->display; v != NULL; v = v->next) {
574 if (!v->arenaitem->key) {
575 NR_ARENA_ITEM_SET_KEY(v->arenaitem, SPItem::display_key_new(3));
576 }
577 NRArenaItem *ai = SP_CLIPPATH(clip)->sp_clippath_show(
578 NR_ARENA_ITEM_ARENA(v->arenaitem),
579 NR_ARENA_ITEM_GET_KEY(v->arenaitem));
580 nr_arena_item_set_clip(v->arenaitem, ai);
581 nr_arena_item_unref(ai);
582 SP_CLIPPATH(clip)->sp_clippath_set_bbox(NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
583 SP_OBJECT(clip)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
584 }
585 }
586 }
588 void
589 SPItem::mask_ref_changed(SPObject *old_mask, SPObject *mask, SPItem *item)
590 {
591 if (old_mask) {
592 /* Hide mask */
593 for (SPItemView *v = item->display; v != NULL; v = v->next) {
594 sp_mask_hide(SP_MASK(old_mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
595 nr_arena_item_set_mask(v->arenaitem, NULL);
596 }
597 }
598 if (SP_IS_MASK(mask)) {
599 NRRect bbox;
600 item->invoke_bbox( &bbox, Geom::identity(), TRUE);
601 for (SPItemView *v = item->display; v != NULL; v = v->next) {
602 if (!v->arenaitem->key) {
603 NR_ARENA_ITEM_SET_KEY(v->arenaitem, SPItem::display_key_new(3));
604 }
605 NRArenaItem *ai = sp_mask_show(SP_MASK(mask),
606 NR_ARENA_ITEM_ARENA(v->arenaitem),
607 NR_ARENA_ITEM_GET_KEY(v->arenaitem));
608 nr_arena_item_set_mask(v->arenaitem, ai);
609 nr_arena_item_unref(ai);
610 sp_mask_set_bbox(SP_MASK(mask), NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
611 SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
612 }
613 }
614 }
616 void
617 SPItem::sp_item_update(SPObject *object, SPCtx *ctx, guint flags)
618 {
619 SPItem *item = SP_ITEM(object);
621 if (((SPObjectClass *) (SPItemClass::static_parent_class))->update)
622 (* ((SPObjectClass *) (SPItemClass::static_parent_class))->update)(object, ctx, flags);
624 if (flags & (SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG)) {
625 if (flags & SP_OBJECT_MODIFIED_FLAG) {
626 for (SPItemView *v = item->display; v != NULL; v = v->next) {
627 nr_arena_item_set_transform(v->arenaitem, item->transform);
628 }
629 }
631 SPClipPath *clip_path = item->clip_ref ? item->clip_ref->getObject() : NULL;
632 SPMask *mask = item->mask_ref ? item->mask_ref->getObject() : NULL;
634 if ( clip_path || mask ) {
635 NRRect bbox;
636 item->invoke_bbox( &bbox, Geom::identity(), TRUE);
637 if (clip_path) {
638 for (SPItemView *v = item->display; v != NULL; v = v->next) {
639 clip_path->sp_clippath_set_bbox(NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
640 }
641 }
642 if (mask) {
643 for (SPItemView *v = item->display; v != NULL; v = v->next) {
644 sp_mask_set_bbox(mask, NR_ARENA_ITEM_GET_KEY(v->arenaitem), &bbox);
645 }
646 }
647 }
649 if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
650 for (SPItemView *v = item->display; v != NULL; v = v->next) {
651 nr_arena_item_set_opacity(v->arenaitem, SP_SCALE24_TO_FLOAT(object->style->opacity.value));
652 nr_arena_item_set_visible(v->arenaitem, !item->isHidden());
653 }
654 }
655 }
657 /* Update bounding box data used by filters */
658 if (item->style->filter.set && item->display) {
659 Geom::OptRect item_bbox;
660 item->invoke_bbox( item_bbox, Geom::identity(), TRUE, SPItem::GEOMETRIC_BBOX);
662 SPItemView *itemview = item->display;
663 do {
664 if (itemview->arenaitem)
665 nr_arena_item_set_item_bbox(itemview->arenaitem, item_bbox);
666 } while ( (itemview = itemview->next) );
667 }
669 // Update libavoid with item geometry (for connector routing).
670 if (item->avoidRef)
671 item->avoidRef->handleSettingChange();
672 }
674 Inkscape::XML::Node *
675 SPItem::sp_item_write(SPObject *const object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
676 {
677 SPObject *child;
678 SPItem *item = SP_ITEM(object);
680 // in the case of SP_OBJECT_WRITE_BUILD, the item should always be newly created,
681 // so we need to add any children from the underlying object to the new repr
682 if (flags & SP_OBJECT_WRITE_BUILD) {
683 Inkscape::XML::Node *crepr;
684 GSList *l;
685 l = NULL;
686 for (child = object->first_child(); child != NULL; child = SP_OBJECT_NEXT(child) ) {
687 if (!SP_IS_TITLE(child) && !SP_IS_DESC(child)) continue;
688 crepr = child->updateRepr(xml_doc, NULL, flags);
689 if (crepr) l = g_slist_prepend (l, crepr);
690 }
691 while (l) {
692 repr->addChild((Inkscape::XML::Node *) l->data, NULL);
693 Inkscape::GC::release((Inkscape::XML::Node *) l->data);
694 l = g_slist_remove (l, l->data);
695 }
696 } else {
697 for (child = object->first_child() ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
698 if (!SP_IS_TITLE(child) && !SP_IS_DESC(child)) continue;
699 child->updateRepr(flags);
700 }
701 }
703 gchar *c = sp_svg_transform_write(item->transform);
704 repr->setAttribute("transform", c);
705 g_free(c);
707 if (flags & SP_OBJECT_WRITE_EXT) {
708 repr->setAttribute("sodipodi:insensitive", ( item->sensitive ? NULL : "true" ));
709 if (item->transform_center_x != 0)
710 sp_repr_set_svg_double (repr, "inkscape:transform-center-x", item->transform_center_x);
711 else
712 repr->setAttribute ("inkscape:transform-center-x", NULL);
713 if (item->transform_center_y != 0)
714 sp_repr_set_svg_double (repr, "inkscape:transform-center-y", item->transform_center_y);
715 else
716 repr->setAttribute ("inkscape:transform-center-y", NULL);
717 }
719 if (item->clip_ref->getObject()) {
720 const gchar *value = g_strdup_printf ("url(%s)", item->clip_ref->getURI()->toString());
721 repr->setAttribute ("clip-path", value);
722 g_free ((void *) value);
723 }
724 if (item->mask_ref->getObject()) {
725 const gchar *value = g_strdup_printf ("url(%s)", item->mask_ref->getURI()->toString());
726 repr->setAttribute ("mask", value);
727 g_free ((void *) value);
728 }
730 if (((SPObjectClass *) (SPItemClass::static_parent_class))->write) {
731 ((SPObjectClass *) (SPItemClass::static_parent_class))->write(object, xml_doc, repr, flags);
732 }
734 return repr;
735 }
737 /**
738 * \return There is no guarantee that the return value will contain a rectangle.
739 If this item does not have a boundingbox, it might well be empty.
740 */
741 Geom::OptRect SPItem::getBounds(Geom::Matrix const &transform,
742 SPItem::BBoxType type,
743 unsigned int /*dkey*/) const
744 {
745 Geom::OptRect r;
746 SP_ITEM(this)->invoke_bbox_full( r, transform, type, TRUE);
747 return r;
748 }
750 void
751 SPItem::invoke_bbox( Geom::OptRect &bbox, Geom::Matrix const &transform, unsigned const clear, SPItem::BBoxType type)
752 {
753 invoke_bbox_full( bbox, transform, type, clear);
754 }
756 // DEPRECATED to phase out the use of NRRect in favor of Geom::OptRect
757 void
758 SPItem::invoke_bbox( NRRect *bbox, Geom::Matrix const &transform, unsigned const clear, SPItem::BBoxType type)
759 {
760 invoke_bbox_full( bbox, transform, type, clear);
761 }
763 /** Calls \a item's subclass' bounding box method; clips it by the bbox of clippath, if any; and
764 * unions the resulting bbox with \a bbox. If \a clear is true, empties \a bbox first. Passes the
765 * transform and the flags to the actual bbox methods. Note that many of subclasses (e.g. groups,
766 * clones), in turn, call this function in their bbox methods.
767 * \retval bbox Note that there is no guarantee that bbox will contain a rectangle when the
768 * function returns. If this item does not have a boundingbox, this might well be empty.
769 */
770 void
771 SPItem::invoke_bbox_full( Geom::OptRect &bbox, Geom::Matrix const &transform, unsigned const flags, unsigned const clear)
772 {
773 //g_assert(this != NULL);
774 //g_assert(SP_IS_ITEM(this));
776 if (clear) {
777 bbox = Geom::OptRect();
778 }
780 // TODO: replace NRRect by Geom::Rect, for all SPItemClasses, and for SP_CLIPPATH
782 NRRect temp_bbox;
783 temp_bbox.x0 = temp_bbox.y0 = NR_HUGE;
784 temp_bbox.x1 = temp_bbox.y1 = -NR_HUGE;
786 // call the subclass method
787 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->bbox) {
788 ((SPItemClass *) G_OBJECT_GET_CLASS(this))->bbox(this, &temp_bbox, transform, flags);
789 }
791 // unless this is geometric bbox, extend by filter area and crop the bbox by clip path, if any
792 if ((SPItem::BBoxType) flags != SPItem::GEOMETRIC_BBOX) {
793 if (SP_OBJECT_STYLE(this) && SP_OBJECT_STYLE(this)->filter.href) {
794 SPObject *filter = SP_OBJECT_STYLE(this)->getFilter();
795 if (filter && SP_IS_FILTER(filter)) {
796 // default filer area per the SVG spec:
797 double x = -0.1;
798 double y = -0.1;
799 double w = 1.2;
800 double h = 1.2;
802 // if area is explicitly set, override:
803 if (SP_FILTER(filter)->x._set)
804 x = SP_FILTER(filter)->x.computed;
805 if (SP_FILTER(filter)->y._set)
806 y = SP_FILTER(filter)->y.computed;
807 if (SP_FILTER(filter)->width._set)
808 w = SP_FILTER(filter)->width.computed;
809 if (SP_FILTER(filter)->height._set)
810 h = SP_FILTER(filter)->height.computed;
812 double dx0 = 0;
813 double dx1 = 0;
814 double dy0 = 0;
815 double dy1 = 0;
816 if (filter_is_single_gaussian_blur(SP_FILTER(filter))) {
817 // if this is a single blur, use 2.4*radius
818 // which may be smaller than the default area;
819 // see set_filter_area for why it's 2.4
820 double r = get_single_gaussian_blur_radius (SP_FILTER(filter));
821 dx0 = -2.4 * r;
822 dx1 = 2.4 * r;
823 dy0 = -2.4 * r;
824 dy1 = 2.4 * r;
825 } else {
826 // otherwise, calculate expansion from relative to absolute units:
827 dx0 = x * (temp_bbox.x1 - temp_bbox.x0);
828 dx1 = (w + x - 1) * (temp_bbox.x1 - temp_bbox.x0);
829 dy0 = y * (temp_bbox.y1 - temp_bbox.y0);
830 dy1 = (h + y - 1) * (temp_bbox.y1 - temp_bbox.y0);
831 }
833 // transform the expansions by the item's transform:
834 Geom::Matrix i2d(i2d_affine ());
835 dx0 *= i2d.expansionX();
836 dx1 *= i2d.expansionX();
837 dy0 *= i2d.expansionY();
838 dy1 *= i2d.expansionY();
840 // expand the bbox
841 temp_bbox.x0 += dx0;
842 temp_bbox.x1 += dx1;
843 temp_bbox.y0 += dy0;
844 temp_bbox.y1 += dy1;
845 }
846 }
847 if (this->clip_ref->getObject()) {
848 NRRect b;
849 SP_CLIPPATH(this->clip_ref->getObject())->sp_clippath_get_bbox(&b, transform, flags);
850 nr_rect_d_intersect (&temp_bbox, &temp_bbox, &b);
851 }
852 }
854 if (temp_bbox.x0 > temp_bbox.x1 || temp_bbox.y0 > temp_bbox.y1) {
855 // Either the bbox hasn't been touched by the SPItemClass' bbox method
856 // (it still has its initial values, see above: x0 = y0 = NR_HUGE and x1 = y1 = -NR_HUGE)
857 // or it has explicitely been set to be like this (e.g. in sp_shape_bbox)
859 // When x0 > x1 or y0 > y1, the bbox is considered to be "nothing", although it has not been
860 // explicitely defined this way for NRRects (as opposed to Geom::OptRect)
861 // So union bbox with nothing = do nothing, just return
862 return;
863 }
865 // Do not use temp_bbox.upgrade() here, because it uses a test that returns an empty Geom::OptRect()
866 // for any rectangle with zero area. The geometrical bbox of for example a vertical line
867 // would therefore be translated into empty Geom::OptRect() (see bug https://bugs.launchpad.net/inkscape/+bug/168684)
868 Geom::OptRect temp_bbox_new = Geom::Rect(Geom::Point(temp_bbox.x0, temp_bbox.y0), Geom::Point(temp_bbox.x1, temp_bbox.y1));
870 bbox = Geom::unify(bbox, temp_bbox_new);
871 }
873 // DEPRECATED to phase out the use of NRRect in favor of Geom::OptRect
874 /** Calls \a item's subclass' bounding box method; clips it by the bbox of clippath, if any; and
875 * unions the resulting bbox with \a bbox. If \a clear is true, empties \a bbox first. Passes the
876 * transform and the flags to the actual bbox methods. Note that many of subclasses (e.g. groups,
877 * clones), in turn, call this function in their bbox methods. */
878 void
879 SPItem::invoke_bbox_full( NRRect *bbox, Geom::Matrix const &transform, unsigned const flags, unsigned const clear)
880 {
881 //g_assert(this != NULL);
882 //g_assert(SP_IS_ITEM(this));
883 g_assert(bbox != NULL);
885 if (clear) {
886 bbox->x0 = bbox->y0 = 1e18;
887 bbox->x1 = bbox->y1 = -1e18;
888 }
890 NRRect this_bbox;
891 this_bbox.x0 = this_bbox.y0 = 1e18;
892 this_bbox.x1 = this_bbox.y1 = -1e18;
894 // call the subclass method
895 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->bbox) {
896 ((SPItemClass *) G_OBJECT_GET_CLASS(this))->bbox(this, &this_bbox, transform, flags);
897 }
899 // unless this is geometric bbox, crop the bbox by clip path, if any
900 if ((SPItem::BBoxType) flags != SPItem::GEOMETRIC_BBOX && this->clip_ref->getObject()) {
901 NRRect b;
902 SP_CLIPPATH(this->clip_ref->getObject())->sp_clippath_get_bbox(&b, transform, flags);
903 nr_rect_d_intersect (&this_bbox, &this_bbox, &b);
904 }
906 // if non-empty (with some tolerance - ?) union this_bbox with the bbox we've got passed
907 if ( fabs(this_bbox.x1-this_bbox.x0) > -0.00001 && fabs(this_bbox.y1-this_bbox.y0) > -0.00001 ) {
908 nr_rect_d_union (bbox, bbox, &this_bbox);
909 }
910 }
912 unsigned SPItem::pos_in_parent()
913 {
914 //g_assert(this != NULL);
915 //g_assert(SP_IS_ITEM(this));
917 SPObject *parent = SP_OBJECT_PARENT(this);
918 g_assert(parent != NULL);
919 g_assert(SP_IS_OBJECT(parent));
921 SPObject *object = SP_OBJECT(this);
923 unsigned pos=0;
924 for ( SPObject *iter = parent->first_child() ; iter ; iter = SP_OBJECT_NEXT(iter)) {
925 if ( iter == object ) {
926 return pos;
927 }
928 if (SP_IS_ITEM(iter)) {
929 pos++;
930 }
931 }
933 g_assert_not_reached();
934 return 0;
935 }
937 void
938 SPItem::getBboxDesktop(NRRect *bbox, SPItem::BBoxType type)
939 {
940 //g_assert(item != NULL);
941 //g_assert(SP_IS_ITEM(item));
942 g_assert(bbox != NULL);
944 invoke_bbox( bbox, i2d_affine(), TRUE, type);
945 }
947 Geom::OptRect SPItem::getBboxDesktop(SPItem::BBoxType type)
948 {
949 Geom::OptRect rect = Geom::OptRect();
950 invoke_bbox( rect, i2d_affine(), TRUE, type);
951 return rect;
952 }
954 void SPItem::sp_item_private_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/)
955 {
956 /* This will only be called if the derived class doesn't override this.
957 * see for example sp_genericellipse_snappoints in sp-ellipse.cpp
958 * We don't know what shape we could be dealing with here, so we'll just
959 * return the corners of the bounding box */
961 Geom::OptRect bbox = item->getBounds(item->i2d_affine());
963 if (bbox) {
964 Geom::Point p1, p2;
965 p1 = bbox->min();
966 p2 = bbox->max();
967 p.push_back(Inkscape::SnapCandidatePoint(p1, Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER));
968 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(p1[Geom::X], p2[Geom::Y]), Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER));
969 p.push_back(Inkscape::SnapCandidatePoint(p2, Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER));
970 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(p2[Geom::X], p1[Geom::Y]), Inkscape::SNAPSOURCE_BBOX_CORNER, Inkscape::SNAPTARGET_BBOX_CORNER));
971 }
973 }
975 void SPItem::getSnappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const
976 {
977 //g_assert (this != NULL);
978 //g_assert (SP_IS_ITEM(this));
980 // Get the snappoints of the item
981 SPItemClass const &item_class = *(SPItemClass const *) G_OBJECT_GET_CLASS(this);
982 if (item_class.snappoints) {
983 item_class.snappoints(this, p, snapprefs);
984 }
986 // Get the snappoints at the item's center
987 if (snapprefs != NULL && snapprefs->getIncludeItemCenter()) {
988 p.push_back(Inkscape::SnapCandidatePoint(getCenter(), Inkscape::SNAPSOURCE_ROTATION_CENTER, Inkscape::SNAPTARGET_ROTATION_CENTER));
989 }
991 // Get the snappoints of clipping paths and mask, if any
992 std::list<SPObject const *> clips_and_masks;
994 clips_and_masks.push_back(SP_OBJECT(clip_ref->getObject()));
995 clips_and_masks.push_back(SP_OBJECT(mask_ref->getObject()));
997 SPDesktop *desktop = inkscape_active_desktop();
998 for (std::list<SPObject const *>::const_iterator o = clips_and_masks.begin(); o != clips_and_masks.end(); o++) {
999 if (*o) {
1000 // obj is a group object, the children are the actual clippers
1001 for (SPObject *child = (*o)->children ; child ; child = child->next) {
1002 if (SP_IS_ITEM(child)) {
1003 std::vector<Inkscape::SnapCandidatePoint> p_clip_or_mask;
1004 // Please note the recursive call here!
1005 SP_ITEM(child)->getSnappoints(p_clip_or_mask, snapprefs);
1006 // Take into account the transformation of the item being clipped or masked
1007 for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator p_orig = p_clip_or_mask.begin(); p_orig != p_clip_or_mask.end(); p_orig++) {
1008 // All snappoints are in desktop coordinates, but the item's transformation is
1009 // in document coordinates. Hence the awkward construction below
1010 Geom::Point pt = desktop->dt2doc((*p_orig).getPoint()) * i2d_affine();
1011 p.push_back(Inkscape::SnapCandidatePoint(pt, (*p_orig).getSourceType(), (*p_orig).getTargetType()));
1012 }
1013 }
1014 }
1015 }
1016 }
1017 }
1019 void
1020 SPItem::invoke_print(SPPrintContext *ctx)
1021 {
1022 if (!this->isHidden()) {
1023 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->print) {
1024 if (!this->transform.isIdentity()
1025 || SP_OBJECT_STYLE(this)->opacity.value != SP_SCALE24_MAX)
1026 {
1027 sp_print_bind(ctx, this->transform, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(this)->opacity.value));
1028 ((SPItemClass *) G_OBJECT_GET_CLASS(this))->print(this, ctx);
1029 sp_print_release(ctx);
1030 } else {
1031 ((SPItemClass *) G_OBJECT_GET_CLASS(this))->print(this, ctx);
1032 }
1033 }
1034 }
1035 }
1037 gchar *
1038 SPItem::sp_item_private_description(SPItem */*item*/)
1039 {
1040 return g_strdup(_("Object"));
1041 }
1043 /**
1044 * Returns a string suitable for status bar, formatted in pango markup language.
1045 *
1046 * Must be freed by caller.
1047 */
1048 gchar *
1049 SPItem::description()
1050 {
1051 //g_assert(this != NULL);
1052 //g_assert(SP_IS_ITEM(this));
1054 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->description) {
1055 gchar *s = ((SPItemClass *) G_OBJECT_GET_CLASS(this))->description(this);
1056 if (s && clip_ref->getObject()) {
1057 gchar *snew = g_strdup_printf (_("%s; <i>clipped</i>"), s);
1058 g_free (s);
1059 s = snew;
1060 }
1061 if (s && mask_ref->getObject()) {
1062 gchar *snew = g_strdup_printf (_("%s; <i>masked</i>"), s);
1063 g_free (s);
1064 s = snew;
1065 }
1066 if (SP_OBJECT_STYLE(this) && SP_OBJECT_STYLE(this)->filter.href && SP_OBJECT_STYLE(this)->filter.href->getObject()) {
1067 const gchar *label = SP_OBJECT_STYLE(this)->filter.href->getObject()->label();
1068 gchar *snew;
1069 if (label) {
1070 snew = g_strdup_printf (_("%s; <i>filtered (%s)</i>"), s, _(label));
1071 } else {
1072 snew = g_strdup_printf (_("%s; <i>filtered</i>"), s);
1073 }
1074 g_free (s);
1075 s = snew;
1076 }
1077 return s;
1078 }
1080 g_assert_not_reached();
1081 return NULL;
1082 }
1084 /**
1085 * Allocates unique integer keys.
1086 * \param numkeys Number of keys required.
1087 * \return First allocated key; hence if the returned key is n
1088 * you can use n, n + 1, ..., n + (numkeys - 1)
1089 */
1090 unsigned
1091 SPItem::display_key_new(unsigned numkeys)
1092 {
1093 static unsigned dkey = 0;
1095 dkey += numkeys;
1097 return dkey - numkeys;
1098 }
1100 NRArenaItem *
1101 SPItem::invoke_show(NRArena *arena, unsigned key, unsigned flags)
1102 {
1103 //g_assert(this != NULL);
1104 //g_assert(SP_IS_ITEM(this));
1105 g_assert(arena != NULL);
1106 g_assert(NR_IS_ARENA(arena));
1108 NRArenaItem *ai = NULL;
1109 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->show) {
1110 ai = ((SPItemClass *) G_OBJECT_GET_CLASS(this))->show(this, arena, key, flags);
1111 }
1113 if (ai != NULL) {
1114 display = sp_item_view_new_prepend(display, this, flags, key, ai);
1115 nr_arena_item_set_transform(ai, transform);
1116 nr_arena_item_set_opacity(ai, SP_SCALE24_TO_FLOAT(SP_OBJECT_STYLE(this)->opacity.value));
1117 nr_arena_item_set_visible(ai, !isHidden());
1118 nr_arena_item_set_sensitive(ai, sensitive);
1119 if (clip_ref->getObject()) {
1120 SPClipPath *cp = clip_ref->getObject();
1122 if (!display->arenaitem->key) {
1123 NR_ARENA_ITEM_SET_KEY(display->arenaitem, display_key_new(3));
1124 }
1125 int clip_key = NR_ARENA_ITEM_GET_KEY(display->arenaitem);
1127 // Show and set clip
1128 NRArenaItem *ac = cp->sp_clippath_show(arena, clip_key);
1129 nr_arena_item_set_clip(ai, ac);
1130 nr_arena_item_unref(ac);
1132 // Update bbox, in case the clip uses bbox units
1133 NRRect bbox;
1134 invoke_bbox( &bbox, Geom::identity(), TRUE);
1135 SP_CLIPPATH(cp)->sp_clippath_set_bbox(clip_key, &bbox);
1136 SP_OBJECT(cp)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1137 }
1138 if (mask_ref->getObject()) {
1139 SPMask *mask = mask_ref->getObject();
1141 if (!display->arenaitem->key) {
1142 NR_ARENA_ITEM_SET_KEY(display->arenaitem, display_key_new(3));
1143 }
1144 int mask_key = NR_ARENA_ITEM_GET_KEY(display->arenaitem);
1146 // Show and set mask
1147 NRArenaItem *ac = sp_mask_show(mask, arena, mask_key);
1148 nr_arena_item_set_mask(ai, ac);
1149 nr_arena_item_unref(ac);
1151 // Update bbox, in case the mask uses bbox units
1152 NRRect bbox;
1153 invoke_bbox( &bbox, Geom::identity(), TRUE);
1154 sp_mask_set_bbox(SP_MASK(mask), mask_key, &bbox);
1155 SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1156 }
1157 NR_ARENA_ITEM_SET_DATA(ai, this);
1158 Geom::OptRect item_bbox;
1159 invoke_bbox( item_bbox, Geom::identity(), TRUE, SPItem::GEOMETRIC_BBOX);
1160 nr_arena_item_set_item_bbox(ai, item_bbox);
1161 }
1163 return ai;
1164 }
1166 void
1167 SPItem::invoke_hide(unsigned key)
1168 {
1169 //g_assert(this != NULL);
1170 //g_assert(SP_IS_ITEM(this));
1172 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->hide) {
1173 ((SPItemClass *) G_OBJECT_GET_CLASS(this))->hide(this, key);
1174 }
1176 SPItemView *ref = NULL;
1177 SPItemView *v = display;
1178 while (v != NULL) {
1179 SPItemView *next = v->next;
1180 if (v->key == key) {
1181 if (clip_ref->getObject()) {
1182 (clip_ref->getObject())->sp_clippath_hide(NR_ARENA_ITEM_GET_KEY(v->arenaitem));
1183 nr_arena_item_set_clip(v->arenaitem, NULL);
1184 }
1185 if (mask_ref->getObject()) {
1186 sp_mask_hide(mask_ref->getObject(), NR_ARENA_ITEM_GET_KEY(v->arenaitem));
1187 nr_arena_item_set_mask(v->arenaitem, NULL);
1188 }
1189 if (!ref) {
1190 display = v->next;
1191 } else {
1192 ref->next = v->next;
1193 }
1194 nr_arena_item_unparent(v->arenaitem);
1195 nr_arena_item_unref(v->arenaitem);
1196 g_free(v);
1197 } else {
1198 ref = v;
1199 }
1200 v = next;
1201 }
1202 }
1204 // Adjusters
1206 void
1207 SPItem::adjust_pattern (Geom::Matrix const &postmul, bool set)
1208 {
1209 SPStyle *style = SP_OBJECT_STYLE (this);
1211 if (style && (style->fill.isPaintserver())) {
1212 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER (this);
1213 if (SP_IS_PATTERN (server)) {
1214 SPPattern *pattern = sp_pattern_clone_if_necessary (this, SP_PATTERN (server), "fill");
1215 sp_pattern_transform_multiply (pattern, postmul, set);
1216 }
1217 }
1219 if (style && (style->stroke.isPaintserver())) {
1220 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER (this);
1221 if (SP_IS_PATTERN (server)) {
1222 SPPattern *pattern = sp_pattern_clone_if_necessary (this, SP_PATTERN (server), "stroke");
1223 sp_pattern_transform_multiply (pattern, postmul, set);
1224 }
1225 }
1227 }
1229 void
1230 SPItem::adjust_gradient (Geom::Matrix const &postmul, bool set)
1231 {
1232 SPStyle *style = SP_OBJECT_STYLE (this);
1234 if (style && (style->fill.isPaintserver())) {
1235 SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(this);
1236 if (SP_IS_GRADIENT (server)) {
1238 /**
1239 * \note Bbox units for a gradient are generally a bad idea because
1240 * with them, you cannot preserve the relative position of the
1241 * object and its gradient after rotation or skew. So now we
1242 * convert them to userspace units which are easy to keep in sync
1243 * just by adding the object's transform to gradientTransform.
1244 * \todo FIXME: convert back to bbox units after transforming with
1245 * the item, so as to preserve the original units.
1246 */
1247 SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), this, "fill");
1249 sp_gradient_transform_multiply (gradient, postmul, set);
1250 }
1251 }
1253 if (style && (style->stroke.isPaintserver())) {
1254 SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(this);
1255 if (SP_IS_GRADIENT (server)) {
1256 SPGradient *gradient = sp_gradient_convert_to_userspace (SP_GRADIENT (server), this, "stroke");
1257 sp_gradient_transform_multiply (gradient, postmul, set);
1258 }
1259 }
1260 }
1262 void
1263 SPItem::adjust_stroke (gdouble ex)
1264 {
1265 SPStyle *style = SP_OBJECT_STYLE (this);
1267 if (style && !style->stroke.isNone() && !NR_DF_TEST_CLOSE (ex, 1.0, NR_EPSILON)) {
1269 style->stroke_width.computed *= ex;
1270 style->stroke_width.set = TRUE;
1272 if (style->stroke_dash.n_dash != 0) {
1273 int i;
1274 for (i = 0; i < style->stroke_dash.n_dash; i++) {
1275 style->stroke_dash.dash[i] *= ex;
1276 }
1277 style->stroke_dash.offset *= ex;
1278 }
1280 SP_OBJECT(this)->updateRepr();
1281 }
1282 }
1284 /**
1285 * Find out the inverse of previous transform of an item (from its repr)
1286 */
1287 Geom::Matrix
1288 sp_item_transform_repr (SPItem *item)
1289 {
1290 Geom::Matrix t_old(Geom::identity());
1291 gchar const *t_attr = SP_OBJECT_REPR(item)->attribute("transform");
1292 if (t_attr) {
1293 Geom::Matrix t;
1294 if (sp_svg_transform_read(t_attr, &t)) {
1295 t_old = t;
1296 }
1297 }
1299 return t_old;
1300 }
1303 /**
1304 * Recursively scale stroke width in \a item and its children by \a expansion.
1305 */
1306 void
1307 SPItem::adjust_stroke_width_recursive(double expansion)
1308 {
1309 adjust_stroke (expansion);
1311 // A clone's child is the ghost of its original - we must not touch it, skip recursion
1312 if (this && SP_IS_USE(this))
1313 return;
1315 for (SPObject *o = SP_OBJECT(this)->children; o != NULL; o = o->next) {
1316 if (SP_IS_ITEM(o))
1317 SP_ITEM(o)->adjust_stroke_width_recursive(expansion);
1318 }
1319 }
1321 /**
1322 * Recursively adjust rx and ry of rects.
1323 */
1324 void
1325 sp_item_adjust_rects_recursive(SPItem *item, Geom::Matrix advertized_transform)
1326 {
1327 if (SP_IS_RECT (item)) {
1328 sp_rect_compensate_rxry (SP_RECT(item), advertized_transform);
1329 }
1331 for (SPObject *o = SP_OBJECT(item)->children; o != NULL; o = o->next) {
1332 if (SP_IS_ITEM(o))
1333 sp_item_adjust_rects_recursive(SP_ITEM(o), advertized_transform);
1334 }
1335 }
1337 /**
1338 * Recursively compensate pattern or gradient transform.
1339 */
1340 void
1341 SPItem::adjust_paint_recursive (Geom::Matrix advertized_transform, Geom::Matrix t_ancestors, bool is_pattern)
1342 {
1343 // _Before_ full pattern/gradient transform: t_paint * t_item * t_ancestors
1344 // _After_ full pattern/gradient transform: t_paint_new * t_item * t_ancestors * advertised_transform
1345 // By equating these two expressions we get t_paint_new = t_paint * paint_delta, where:
1346 Geom::Matrix t_item = sp_item_transform_repr (this);
1347 Geom::Matrix paint_delta = t_item * t_ancestors * advertized_transform * t_ancestors.inverse() * t_item.inverse();
1349 // Within text, we do not fork gradients, and so must not recurse to avoid double compensation;
1350 // also we do not recurse into clones, because a clone's child is the ghost of its original -
1351 // we must not touch it
1352 if (!(this && (SP_IS_TEXT(this) || SP_IS_USE(this)))) {
1353 for (SPObject *o = SP_OBJECT(this)->children; o != NULL; o = o->next) {
1354 if (SP_IS_ITEM(o)) {
1355 // At the level of the transformed item, t_ancestors is identity;
1356 // below it, it is the accmmulated chain of transforms from this level to the top level
1357 SP_ITEM(o)->adjust_paint_recursive (advertized_transform, t_item * t_ancestors, is_pattern);
1358 }
1359 }
1360 }
1362 // We recursed into children first, and are now adjusting this object second;
1363 // this is so that adjustments in a tree are done from leaves up to the root,
1364 // and paintservers on leaves inheriting their values from ancestors could adjust themselves properly
1365 // before ancestors themselves are adjusted, probably differently (bug 1286535)
1367 if (is_pattern)
1368 adjust_pattern (paint_delta);
1369 else
1370 adjust_gradient (paint_delta);
1372 }
1374 void
1375 SPItem::adjust_livepatheffect (Geom::Matrix const &postmul, bool set)
1376 {
1377 if ( !SP_IS_LPE_ITEM(this) )
1378 return;
1380 SPLPEItem *lpeitem = SP_LPE_ITEM (this);
1381 if ( sp_lpe_item_has_path_effect(lpeitem) ) {
1382 sp_lpe_item_fork_path_effects_if_necessary(lpeitem);
1384 // now that all LPEs are forked_if_necessary, we can apply the transform
1385 PathEffectList effect_list = sp_lpe_item_get_effect_list(lpeitem);
1386 for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); it++)
1387 {
1388 LivePathEffectObject *lpeobj = (*it)->lpeobject;
1389 if (lpeobj && lpeobj->get_lpe()) {
1390 Inkscape::LivePathEffect::Effect * effect = lpeobj->get_lpe();
1391 effect->transform_multiply(postmul, set);
1392 }
1393 }
1394 }
1395 }
1397 /**
1398 * Set a new transform on an object.
1399 *
1400 * Compensate for stroke scaling and gradient/pattern fill transform, if
1401 * necessary. Call the object's set_transform method if transforms are
1402 * stored optimized. Send _transformed_signal. Invoke _write method so that
1403 * the repr is updated with the new transform.
1404 */
1405 void
1406 SPItem::doWriteTransform(Inkscape::XML::Node *repr, Geom::Matrix const &transform, Geom::Matrix const *adv, bool compensate)
1407 {
1408 //g_return_if_fail(this != NULL);
1409 //g_return_if_fail(SP_IS_ITEM(this));
1410 g_return_if_fail(repr != NULL);
1412 // calculate the relative transform, if not given by the adv attribute
1413 Geom::Matrix advertized_transform;
1414 if (adv != NULL) {
1415 advertized_transform = *adv;
1416 } else {
1417 advertized_transform = sp_item_transform_repr (this).inverse() * transform;
1418 }
1420 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1421 if (compensate) {
1423 // recursively compensate for stroke scaling, depending on user preference
1424 if (!prefs->getBool("/options/transform/stroke", true)) {
1425 double const expansion = 1. / advertized_transform.descrim();
1426 adjust_stroke_width_recursive(expansion);
1427 }
1429 // recursively compensate rx/ry of a rect if requested
1430 if (!prefs->getBool("/options/transform/rectcorners", true)) {
1431 sp_item_adjust_rects_recursive(this, advertized_transform);
1432 }
1434 // recursively compensate pattern fill if it's not to be transformed
1435 if (!prefs->getBool("/options/transform/pattern", true)) {
1436 adjust_paint_recursive (advertized_transform.inverse(), Geom::identity(), true);
1437 }
1438 /// \todo FIXME: add the same else branch as for gradients below, to convert patterns to userSpaceOnUse as well
1439 /// recursively compensate gradient fill if it's not to be transformed
1440 if (!prefs->getBool("/options/transform/gradient", true)) {
1441 adjust_paint_recursive (advertized_transform.inverse(), Geom::identity(), false);
1442 } else {
1443 // this converts the gradient/pattern fill/stroke, if any, to userSpaceOnUse; we need to do
1444 // it here _before_ the new transform is set, so as to use the pre-transform bbox
1445 adjust_paint_recursive (Geom::identity(), Geom::identity(), false);
1446 }
1448 } // endif(compensate)
1450 gint preserve = prefs->getBool("/options/preservetransform/value", 0);
1451 Geom::Matrix transform_attr (transform);
1452 if ( // run the object's set_transform (i.e. embed transform) only if:
1453 ((SPItemClass *) G_OBJECT_GET_CLASS(this))->set_transform && // it does have a set_transform method
1454 !preserve && // user did not chose to preserve all transforms
1455 !clip_ref->getObject() && // the object does not have a clippath
1456 !mask_ref->getObject() && // the object does not have a mask
1457 !(!transform.isTranslation() && SP_OBJECT_STYLE(this) && SP_OBJECT_STYLE(this)->getFilter())
1458 // the object does not have a filter, or the transform is translation (which is supposed to not affect filters)
1459 ) {
1460 transform_attr = ((SPItemClass *) G_OBJECT_GET_CLASS(this))->set_transform(this, transform);
1461 }
1462 set_item_transform(transform_attr);
1464 // Note: updateRepr comes before emitting the transformed signal since
1465 // it causes clone SPUse's copy of the original object to brought up to
1466 // date with the original. Otherwise, sp_use_bbox returns incorrect
1467 // values if called in code handling the transformed signal.
1468 SP_OBJECT(this)->updateRepr();
1470 // send the relative transform with a _transformed_signal
1471 _transformed_signal.emit(&advertized_transform, this);
1472 }
1474 gint
1475 SPItem::emitEvent(SPEvent &event)
1476 {
1477 //g_return_val_if_fail(this != NULL, FALSE);
1478 //g_return_val_if_fail(SP_IS_ITEM(this), FALSE);
1479 //g_return_val_if_fail((&event) != NULL, FALSE);
1481 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->event)
1482 return ((SPItemClass *) G_OBJECT_GET_CLASS(this))->event(this, &event);
1484 return FALSE;
1485 }
1487 /**
1488 * Sets item private transform (not propagated to repr), without compensating stroke widths,
1489 * gradients, patterns as sp_item_write_transform does.
1490 */
1491 void
1492 SPItem::set_item_transform(Geom::Matrix const &transform_matrix)
1493 {
1494 g_return_if_fail(this != NULL);
1495 g_return_if_fail(SP_IS_ITEM(this));
1497 if (!matrix_equalp(transform_matrix, transform, NR_EPSILON)) {
1498 transform = transform_matrix;
1499 /* The SP_OBJECT_USER_MODIFIED_FLAG_B is used to mark the fact that it's only a
1500 transformation. It's apparently not used anywhere else. */
1501 requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_USER_MODIFIED_FLAG_B);
1502 sp_item_rm_unsatisfied_cns(*this);
1503 }
1504 }
1506 void
1507 SPItem::convert_item_to_guides() {
1508 g_return_if_fail(this != NULL);
1509 g_return_if_fail(SP_IS_ITEM(this));
1511 /* Use derived method if present ... */
1512 if (((SPItemClass *) G_OBJECT_GET_CLASS(this))->convert_to_guides) {
1513 (*((SPItemClass *) G_OBJECT_GET_CLASS(this))->convert_to_guides)(this);
1514 return;
1515 }
1517 /* .. otherwise simply place the guides around the item's bounding box */
1519 convert_to_guides();
1520 }
1523 /**
1524 * \pre \a ancestor really is an ancestor (\>=) of \a object, or NULL.
1525 * ("Ancestor (\>=)" here includes as far as \a object itself.)
1526 */
1527 Geom::Matrix
1528 i2anc_affine(SPObject const *object, SPObject const *const ancestor) {
1529 Geom::Matrix ret(Geom::identity());
1530 g_return_val_if_fail(object != NULL, ret);
1532 /* stop at first non-renderable ancestor */
1533 while ( object != ancestor && SP_IS_ITEM(object) ) {
1534 if (SP_IS_ROOT(object)) {
1535 ret *= SP_ROOT(object)->c2p;
1536 } else {
1537 ret *= SP_ITEM(object)->transform;
1538 }
1539 object = SP_OBJECT_PARENT(object);
1540 }
1541 return ret;
1542 }
1544 Geom::Matrix
1545 i2i_affine(SPObject const *src, SPObject const *dest) {
1546 g_return_val_if_fail(src != NULL && dest != NULL, Geom::identity());
1547 SPObject const *ancestor = src->nearestCommonAncestor(dest);
1548 return i2anc_affine(src, ancestor) * i2anc_affine(dest, ancestor).inverse();
1549 }
1551 Geom::Matrix SPItem::getRelativeTransform(SPObject const *dest) const {
1552 return i2i_affine(this, dest);
1553 }
1555 /**
1556 * Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
1557 * \pre (item != NULL) and SP_IS_ITEM(item).
1558 */
1559 Geom::Matrix SPItem::i2doc_affine() const
1560 {
1561 return i2anc_affine(this, NULL);
1562 }
1564 /**
1565 * Returns the transformation from item to desktop coords
1566 */
1567 Geom::Matrix SPItem::i2d_affine() const
1568 {
1569 //g_assert(item != NULL);
1570 //g_assert(SP_IS_ITEM(item));
1572 Geom::Matrix const ret( i2doc_affine()
1573 * Geom::Scale(1, -1)
1574 * Geom::Translate(0, SP_OBJECT_DOCUMENT(this)->getHeight()) );
1575 return ret;
1576 }
1578 void SPItem::set_i2d_affine(Geom::Matrix const &i2dt)
1579 {
1580 //g_return_if_fail( item != NULL );
1581 //g_return_if_fail( SP_IS_ITEM(item) );
1583 Geom::Matrix dt2p; /* desktop to item parent transform */
1584 if (SP_OBJECT_PARENT(this)) {
1585 dt2p = static_cast<SPItem *>(SP_OBJECT_PARENT(this))->i2d_affine().inverse();
1586 } else {
1587 dt2p = ( Geom::Translate(0, -SP_OBJECT_DOCUMENT(this)->getHeight())
1588 * Geom::Scale(1, -1) );
1589 }
1591 Geom::Matrix const i2p( i2dt * dt2p );
1592 set_item_transform(i2p);
1593 }
1596 /**
1597 * should rather be named "sp_item_d2i_affine" to match "sp_item_i2d_affine" (or vice versa)
1598 */
1599 Geom::Matrix
1600 SPItem::dt2i_affine() const
1601 {
1602 /* fixme: Implement the right way (Lauris) */
1603 return i2d_affine().inverse();
1604 }
1606 /* Item views */
1608 SPItemView *
1609 SPItem::sp_item_view_new_prepend(SPItemView *list, SPItem *item, unsigned flags, unsigned key, NRArenaItem *arenaitem)
1610 {
1611 SPItemView *new_view;
1613 g_assert(item != NULL);
1614 g_assert(SP_IS_ITEM(item));
1615 g_assert(arenaitem != NULL);
1616 g_assert(NR_IS_ARENA_ITEM(arenaitem));
1618 new_view = g_new(SPItemView, 1);
1620 new_view->next = list;
1621 new_view->flags = flags;
1622 new_view->key = key;
1623 new_view->arenaitem = arenaitem;
1625 return new_view;
1626 }
1628 SPItemView *
1629 SPItem::sp_item_view_list_remove(SPItemView *list, SPItemView *view)
1630 {
1631 if (view == list) {
1632 list = list->next;
1633 } else {
1634 SPItemView *prev;
1635 prev = list;
1636 while (prev->next != view) prev = prev->next;
1637 prev->next = view->next;
1638 }
1640 nr_arena_item_unref(view->arenaitem);
1641 g_free(view);
1643 return list;
1644 }
1646 /**
1647 * Return the arenaitem corresponding to the given item in the display
1648 * with the given key
1649 */
1650 NRArenaItem *
1651 SPItem::get_arenaitem(unsigned key)
1652 {
1653 for ( SPItemView *iv = display ; iv ; iv = iv->next ) {
1654 if ( iv->key == key ) {
1655 return iv->arenaitem;
1656 }
1657 }
1659 return NULL;
1660 }
1662 int
1663 sp_item_repr_compare_position(SPItem *first, SPItem *second)
1664 {
1665 return sp_repr_compare_position(SP_OBJECT_REPR(first),
1666 SP_OBJECT_REPR(second));
1667 }
1669 SPItem *
1670 sp_item_first_item_child (SPObject *obj)
1671 {
1672 for ( SPObject *iter = obj->first_child() ; iter ; iter = SP_OBJECT_NEXT(iter)) {
1673 if (SP_IS_ITEM (iter))
1674 return SP_ITEM (iter);
1675 }
1676 return NULL;
1677 }
1679 void
1680 SPItem::convert_to_guides() {
1681 SPDesktop *dt = inkscape_active_desktop();
1682 SPNamedView *nv = sp_desktop_namedview(dt);
1683 (void)nv;
1685 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1686 int prefs_bbox = prefs->getInt("/tools/bounding_box", 0);
1687 SPItem::BBoxType bbox_type = (prefs_bbox ==0)?
1688 SPItem::APPROXIMATE_BBOX : SPItem::GEOMETRIC_BBOX;
1690 Geom::OptRect bbox = getBboxDesktop(bbox_type);
1691 if (!bbox) {
1692 g_warning ("Cannot determine item's bounding box during conversion to guides.\n");
1693 return;
1694 }
1696 std::list<std::pair<Geom::Point, Geom::Point> > pts;
1698 Geom::Point A((*bbox).min());
1699 Geom::Point C((*bbox).max());
1700 Geom::Point B(A[Geom::X], C[Geom::Y]);
1701 Geom::Point D(C[Geom::X], A[Geom::Y]);
1703 pts.push_back(std::make_pair(A, B));
1704 pts.push_back(std::make_pair(B, C));
1705 pts.push_back(std::make_pair(C, D));
1706 pts.push_back(std::make_pair(D, A));
1708 sp_guide_pt_pairs_to_guides(dt, pts);
1709 }
1711 /*
1712 Local Variables:
1713 mode:c++
1714 c-file-style:"stroustrup"
1715 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1716 indent-tabs-mode:nil
1717 fill-column:99
1718 End:
1719 */
1720 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :