1 /** \file
2 * Base class for live path effect items
3 */
4 /*
5 * Authors:
6 * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
7 * Bastien Bouclet <bgkweb@gmail.com>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 2008 authors
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
19 #include "live_effects/effect.h"
20 #include "live_effects/lpe-path_length.h"
21 #include "live_effects/lpeobject.h"
22 #include "live_effects/lpeobject-reference.h"
24 #include "sp-path.h"
25 #include "sp-item-group.h"
26 #include "streq.h"
27 #include "macros.h"
28 #include "attributes.h"
29 #include "sp-lpe-item.h"
30 #include "xml/repr.h"
31 #include "uri.h"
32 #include "message-stack.h"
33 #include "inkscape.h"
34 #include "desktop.h"
35 #include "shape-editor.h"
37 #include <algorithm>
39 /* LPEItem base class */
41 static void sp_lpe_item_class_init(SPLPEItemClass *klass);
42 static void sp_lpe_item_init(SPLPEItem *lpe_item);
43 static void sp_lpe_item_finalize(GObject *object);
45 static void sp_lpe_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
46 static void sp_lpe_item_release(SPObject *object);
47 static void sp_lpe_item_set(SPObject *object, unsigned int key, gchar const *value);
48 static void sp_lpe_item_update(SPObject *object, SPCtx *ctx, guint flags);
49 static void sp_lpe_item_modified (SPObject *object, unsigned int flags);
50 static Inkscape::XML::Node *sp_lpe_item_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags);
52 static void sp_lpe_item_child_added (SPObject * object, Inkscape::XML::Node * child, Inkscape::XML::Node * ref);
53 static void sp_lpe_item_remove_child (SPObject * object, Inkscape::XML::Node * child);
55 static void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable);
57 static void lpeobject_ref_modified(SPObject *href, guint flags, SPLPEItem *lpeitem);
59 static void sp_lpe_item_create_original_path_recursive(SPLPEItem *lpeitem);
60 static void sp_lpe_item_cleanup_original_path_recursive(SPLPEItem *lpeitem);
61 typedef std::list<std::string> HRefList;
62 static std::string patheffectlist_write_svg(PathEffectList const & list);
63 static std::string hreflist_write_svg(HRefList const & list);
65 static SPItemClass *parent_class;
67 GType
68 sp_lpe_item_get_type()
69 {
70 static GType lpe_item_type = 0;
72 if (!lpe_item_type) {
73 GTypeInfo lpe_item_info = {
74 sizeof(SPLPEItemClass),
75 NULL, NULL,
76 (GClassInitFunc) sp_lpe_item_class_init,
77 NULL, NULL,
78 sizeof(SPLPEItem),
79 16,
80 (GInstanceInitFunc) sp_lpe_item_init,
81 NULL, /* value_table */
82 };
83 lpe_item_type = g_type_register_static(SP_TYPE_ITEM, "SPLPEItem", &lpe_item_info, (GTypeFlags)0);
84 }
85 return lpe_item_type;
86 }
88 static void
89 sp_lpe_item_class_init(SPLPEItemClass *klass)
90 {
91 GObjectClass *gobject_class;
92 SPObjectClass *sp_object_class;
94 gobject_class = (GObjectClass *) klass;
95 sp_object_class = (SPObjectClass *) klass;
96 parent_class = (SPItemClass *)g_type_class_peek_parent (klass);
98 gobject_class->finalize = sp_lpe_item_finalize;
100 sp_object_class->build = sp_lpe_item_build;
101 sp_object_class->release = sp_lpe_item_release;
102 sp_object_class->set = sp_lpe_item_set;
103 sp_object_class->update = sp_lpe_item_update;
104 sp_object_class->modified = sp_lpe_item_modified;
105 sp_object_class->write = sp_lpe_item_write;
106 sp_object_class->child_added = sp_lpe_item_child_added;
107 sp_object_class->remove_child = sp_lpe_item_remove_child;
109 klass->update_patheffect = NULL;
110 }
112 static void
113 sp_lpe_item_init(SPLPEItem *lpeitem)
114 {
115 lpeitem->path_effects_enabled = 1;
117 lpeitem->path_effect_list = new PathEffectList();
118 lpeitem->current_path_effect = NULL;
120 lpeitem->lpe_modified_connection_list = new std::list<sigc::connection>();
121 }
123 static void
124 sp_lpe_item_finalize(GObject *object)
125 {
126 if (((GObjectClass *) (parent_class))->finalize) {
127 (* ((GObjectClass *) (parent_class))->finalize)(object);
128 }
129 }
131 /**
132 * Reads the Inkscape::XML::Node, and initializes SPLPEItem variables. For this to get called,
133 * our name must be associated with a repr via "sp_object_type_register". Best done through
134 * sp-object-repr.cpp's repr_name_entries array.
135 */
136 static void
137 sp_lpe_item_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
138 {
139 object->readAttr( "inkscape:path-effect" );
141 if (((SPObjectClass *) parent_class)->build) {
142 ((SPObjectClass *) parent_class)->build(object, document, repr);
143 }
144 }
146 /**
147 * Drops any allocated memory.
148 */
149 static void
150 sp_lpe_item_release(SPObject *object)
151 {
152 SPLPEItem *lpeitem = (SPLPEItem *) object;
154 // disconnect all modified listeners:
155 for (std::list<sigc::connection>::iterator mod_it = lpeitem->lpe_modified_connection_list->begin();
156 mod_it != lpeitem->lpe_modified_connection_list->end(); ++mod_it)
157 {
158 mod_it->disconnect();
159 }
160 delete lpeitem->lpe_modified_connection_list;
161 lpeitem->lpe_modified_connection_list = NULL;
163 PathEffectList::iterator it = lpeitem->path_effect_list->begin();
164 while ( it != lpeitem->path_effect_list->end() ) {
165 // unlink and delete all references in the list
166 (*it)->unlink();
167 delete *it;
168 it = lpeitem->path_effect_list->erase(it);
169 }
170 // delete the list itself
171 delete lpeitem->path_effect_list;
172 lpeitem->path_effect_list = NULL;
174 if (((SPObjectClass *) parent_class)->release)
175 ((SPObjectClass *) parent_class)->release(object);
176 }
178 /**
179 * Sets a specific value in the SPLPEItem.
180 */
181 static void
182 sp_lpe_item_set(SPObject *object, unsigned int key, gchar const *value)
183 {
184 SPLPEItem *lpeitem = (SPLPEItem *) object;
186 switch (key) {
187 case SP_ATTR_INKSCAPE_PATH_EFFECT:
188 {
189 lpeitem->current_path_effect = NULL;
191 // Disable the path effects while populating the LPE list
192 sp_lpe_item_enable_path_effects(lpeitem, false);
194 // disconnect all modified listeners:
195 for ( std::list<sigc::connection>::iterator mod_it = lpeitem->lpe_modified_connection_list->begin();
196 mod_it != lpeitem->lpe_modified_connection_list->end();
197 ++mod_it)
198 {
199 mod_it->disconnect();
200 }
201 lpeitem->lpe_modified_connection_list->clear();
202 // Clear the path effect list
203 PathEffectList::iterator it = lpeitem->path_effect_list->begin();
204 while ( it != lpeitem->path_effect_list->end() )
205 {
206 (*it)->unlink();
207 delete *it;
208 it = lpeitem->path_effect_list->erase(it);
209 }
211 // Parse the contents of "value" to rebuild the path effect reference list
212 if ( value ) {
213 std::istringstream iss(value);
214 std::string href;
215 while (std::getline(iss, href, ';'))
216 {
217 Inkscape::LivePathEffect::LPEObjectReference *path_effect_ref = new Inkscape::LivePathEffect::LPEObjectReference(object);
218 try {
219 path_effect_ref->link(href.c_str());
220 } catch (Inkscape::BadURIException e) {
221 g_warning("BadURIException when trying to find LPE: %s", e.what());
222 path_effect_ref->unlink();
223 delete path_effect_ref;
224 path_effect_ref = NULL;
225 }
227 lpeitem->path_effect_list->push_back(path_effect_ref);
228 if ( path_effect_ref->lpeobject && path_effect_ref->lpeobject->get_lpe() ) {
229 // connect modified-listener
230 lpeitem->lpe_modified_connection_list->push_back(
231 path_effect_ref->lpeobject->connectModified(sigc::bind(sigc::ptr_fun(&lpeobject_ref_modified), lpeitem)) );
232 } else {
233 // something has gone wrong in finding the right patheffect.
234 g_warning("Unknown LPE type specified, LPE stack effectively disabled");
235 // keep the effect in the lpestack, so the whole stack is effectively disabled but maintained
236 }
237 }
238 }
240 sp_lpe_item_enable_path_effects(lpeitem, true);
241 }
242 break;
243 default:
244 if (((SPObjectClass *) parent_class)->set) {
245 ((SPObjectClass *) parent_class)->set(object, key, value);
246 }
247 break;
248 }
249 }
251 /**
252 * Receives update notifications.
253 */
254 static void
255 sp_lpe_item_update(SPObject *object, SPCtx *ctx, guint flags)
256 {
257 if (((SPObjectClass *) parent_class)->update) {
258 ((SPObjectClass *) parent_class)->update(object, ctx, flags);
259 }
261 // update the helperpaths of all LPEs applied to the item
262 // TODO: re-add for the new node tool
263 }
265 /**
266 * Sets modified flag for all sub-item views.
267 */
268 static void
269 sp_lpe_item_modified (SPObject *object, unsigned int flags)
270 {
271 if (SP_IS_GROUP(object) && (flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_USER_MODIFIED_FLAG_B)) {
272 sp_lpe_item_update_patheffect(SP_LPE_ITEM(object), true, true);
273 }
275 if (((SPObjectClass *) (parent_class))->modified) {
276 (* ((SPObjectClass *) (parent_class))->modified) (object, flags);
277 }
278 }
280 /**
281 * Writes its settings to an incoming repr object, if any.
282 */
283 static Inkscape::XML::Node *
284 sp_lpe_item_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
285 {
286 SPLPEItem *lpeitem = (SPLPEItem *) object;
288 if (flags & SP_OBJECT_WRITE_EXT) {
289 if ( sp_lpe_item_has_path_effect(lpeitem) ) {
290 std::string href = patheffectlist_write_svg(*lpeitem->path_effect_list);
291 repr->setAttribute("inkscape:path-effect", href.c_str());
292 } else {
293 repr->setAttribute("inkscape:path-effect", NULL);
294 }
295 }
297 if (((SPObjectClass *)(parent_class))->write) {
298 ((SPObjectClass *)(parent_class))->write(object, xml_doc, repr, flags);
299 }
301 return repr;
302 }
304 /**
305 * returns true when LPE was successful.
306 */
307 bool sp_lpe_item_perform_path_effect(SPLPEItem *lpeitem, SPCurve *curve) {
308 if (!lpeitem) return false;
309 if (!curve) return false;
311 if (sp_lpe_item_has_path_effect(lpeitem) && sp_lpe_item_path_effects_enabled(lpeitem)) {
312 for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it)
313 {
314 LivePathEffectObject *lpeobj = (*it)->lpeobject;
315 if (!lpeobj) {
316 /** \todo Investigate the cause of this.
317 * For example, this happens when copy pasting an object with LPE applied. Probably because the object is pasted while the effect is not yet pasted to defs, and cannot be found.
318 */
319 g_warning("sp_lpe_item_perform_path_effect - NULL lpeobj in list!");
320 return false;
321 }
322 Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
323 if (!lpe) {
324 /** \todo Investigate the cause of this.
325 * Not sure, but I think this can happen when an unknown effect type is specified...
326 */
327 g_warning("sp_lpe_item_perform_path_effect - lpeobj with invalid lpe in the stack!");
328 return false;
329 }
331 if (lpe->isVisible()) {
332 if (lpe->acceptsNumClicks() > 0 && !lpe->isReady()) {
333 // if the effect expects mouse input before being applied and the input is not finished
334 // yet, we don't alter the path
335 return false;
336 }
338 // Groups have their doBeforeEffect called elsewhere
339 if (!SP_IS_GROUP(lpeitem)) {
340 lpe->doBeforeEffect(lpeitem);
341 }
343 try {
344 lpe->doEffect(curve);
345 }
346 catch (std::exception & e) {
347 g_warning("Exception during LPE %s execution. \n %s", lpe->getName().c_str(), e.what());
348 if (SP_ACTIVE_DESKTOP && SP_ACTIVE_DESKTOP->messageStack()) {
349 SP_ACTIVE_DESKTOP->messageStack()->flash( Inkscape::WARNING_MESSAGE,
350 _("An exception occurred during execution of the Path Effect.") );
351 }
352 return false;
353 }
354 }
355 }
356 }
358 return true;
359 }
361 /**
362 * Calls any registered handlers for the update_patheffect action
363 */
364 void
365 sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write)
366 {
367 #ifdef SHAPE_VERBOSE
368 g_message("sp_lpe_item_update_patheffect: %p\n", lpeitem);
369 #endif
370 g_return_if_fail (lpeitem != NULL);
371 g_return_if_fail (SP_IS_LPE_ITEM (lpeitem));
373 if (!sp_lpe_item_path_effects_enabled(lpeitem))
374 return;
376 // TODO: hack! this will be removed when path length measuring is reimplemented in a better way
377 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
378 std::list<Inkscape::LivePathEffect::LPEObjectReference *>::iterator i;
379 for (i = lpelist.begin(); i != lpelist.end(); ++i) {
380 if ((*i)->lpeobject) {
381 Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->get_lpe();
382 if (dynamic_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)) {
383 if (!lpe->isVisible()) {
384 // we manually disable text for LPEPathLength
385 // use static_cast, because we already checked for the right type above
386 static_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)->hideCanvasText();
387 }
388 }
389 }
390 }
392 SPLPEItem *top;
394 if (wholetree) {
395 SPObject *prev_parent = lpeitem;
396 SPObject *parent = prev_parent->parent;
397 while (parent && SP_IS_LPE_ITEM(parent) && sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(parent))) {
398 prev_parent = parent;
399 parent = prev_parent->parent;
400 }
401 top = SP_LPE_ITEM(prev_parent);
402 }
403 else {
404 top = lpeitem;
405 }
407 if (SP_LPE_ITEM_CLASS (G_OBJECT_GET_CLASS (top))->update_patheffect) {
408 SP_LPE_ITEM_CLASS (G_OBJECT_GET_CLASS (top))->update_patheffect (top, write);
409 }
410 }
412 /**
413 * Gets called when any of the lpestack's lpeobject repr contents change: i.e. parameter change in any of the stacked LPEs
414 */
415 static void
416 lpeobject_ref_modified(SPObject */*href*/, guint /*flags*/, SPLPEItem *lpeitem)
417 {
418 #ifdef SHAPE_VERBOSE
419 g_message("lpeobject_ref_modified");
420 #endif
421 sp_lpe_item_update_patheffect (lpeitem, true, true);
422 }
424 static void
425 sp_lpe_item_create_original_path_recursive(SPLPEItem *lpeitem)
426 {
427 if (SP_IS_GROUP(lpeitem)) {
428 GSList const *item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
429 for ( GSList const *iter = item_list; iter; iter = iter->next ) {
430 SPObject *subitem = static_cast<SPObject *>(iter->data);
431 if (SP_IS_LPE_ITEM(subitem)) {
432 sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(subitem));
433 }
434 }
435 }
436 else if (SP_IS_PATH(lpeitem)) {
437 Inkscape::XML::Node *pathrepr = SP_OBJECT_REPR(lpeitem);
438 if ( !pathrepr->attribute("inkscape:original-d") ) {
439 pathrepr->setAttribute("inkscape:original-d", pathrepr->attribute("d"));
440 }
441 }
442 }
444 static void
445 sp_lpe_item_cleanup_original_path_recursive(SPLPEItem *lpeitem)
446 {
447 if (SP_IS_GROUP(lpeitem)) {
448 GSList const *item_list = sp_item_group_item_list(SP_GROUP(lpeitem));
449 for ( GSList const *iter = item_list; iter; iter = iter->next ) {
450 SPObject *subitem = static_cast<SPObject *>(iter->data);
451 if (SP_IS_LPE_ITEM(subitem)) {
452 sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(subitem));
453 }
454 }
455 }
456 else if (SP_IS_PATH(lpeitem)) {
457 Inkscape::XML::Node *repr = SP_OBJECT_REPR(lpeitem);
458 if (!sp_lpe_item_has_path_effect_recursive(lpeitem)
459 && repr->attribute("inkscape:original-d")) {
460 repr->setAttribute("d", repr->attribute("inkscape:original-d"));
461 repr->setAttribute("inkscape:original-d", NULL);
462 }
463 else {
464 sp_lpe_item_update_patheffect(lpeitem, true, true);
465 }
466 }
467 }
469 void sp_lpe_item_add_path_effect(SPLPEItem *lpeitem, gchar *value, bool reset)
470 {
471 if (value) {
472 // Apply the path effects here because in the casse of a group, lpe->resetDefaults
473 // needs that all the subitems have their effects applied
474 sp_lpe_item_update_patheffect(lpeitem, false, true);
476 // Disable the path effects while preparing the new lpe
477 sp_lpe_item_enable_path_effects(lpeitem, false);
479 // Add the new reference to the list of LPE references
480 HRefList hreflist;
481 for (PathEffectList::const_iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it)
482 {
483 hreflist.push_back( std::string((*it)->lpeobject_href) );
484 }
485 hreflist.push_back( std::string(value) );
486 std::string hrefs = hreflist_write_svg(hreflist);
488 SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", hrefs.c_str());
490 // make sure there is an original-d for paths!!!
491 sp_lpe_item_create_original_path_recursive(lpeitem);
493 LivePathEffectObject *lpeobj = lpeitem->path_effect_list->back()->lpeobject;
494 if (lpeobj && lpeobj->get_lpe()) {
495 Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe();
496 // Ask the path effect to reset itself if it doesn't have parameters yet
497 if (reset) {
498 // has to be called when all the subitems have their lpes applied
499 lpe->resetDefaults(lpeitem);
500 }
502 // perform this once when the effect is applied
503 lpe->doOnApply(SP_LPE_ITEM(lpeitem));
505 // indicate that all necessary preparations are done and the effect can be performed
506 lpe->setReady();
507 }
509 //Enable the path effects now that everything is ready to apply the new path effect
510 sp_lpe_item_enable_path_effects(lpeitem, true);
512 // Apply the path effect
513 sp_lpe_item_update_patheffect(lpeitem, true, true);
514 }
515 }
517 void sp_lpe_item_add_path_effect(SPLPEItem *lpeitem, LivePathEffectObject * new_lpeobj)
518 {
519 const gchar * repr_id = SP_OBJECT_REPR(new_lpeobj)->attribute("id");
520 gchar *hrefstr = g_strdup_printf("#%s", repr_id);
521 sp_lpe_item_add_path_effect(lpeitem, hrefstr, false);
522 g_free(hrefstr);
523 }
525 void sp_lpe_item_remove_current_path_effect(SPLPEItem *lpeitem, bool keep_paths)
526 {
527 Inkscape::LivePathEffect::LPEObjectReference* lperef = sp_lpe_item_get_current_lpereference(lpeitem);
528 if (!lperef)
529 return;
531 PathEffectList new_list = *lpeitem->path_effect_list;
532 new_list.remove(lperef); //current lpe ref is always our 'own' pointer from the path_effect_list
533 std::string r = patheffectlist_write_svg(new_list);
535 if (!r.empty()) {
536 SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", r.c_str());
537 } else {
538 SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", NULL);
539 }
541 if (!keep_paths) {
542 sp_lpe_item_cleanup_original_path_recursive(lpeitem);
543 }
544 }
546 void sp_lpe_item_remove_all_path_effects(SPLPEItem *lpeitem, bool keep_paths)
547 {
548 SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", NULL);
550 if (!keep_paths) {
551 sp_lpe_item_cleanup_original_path_recursive(lpeitem);
552 }
553 }
555 void sp_lpe_item_down_current_path_effect(SPLPEItem *lpeitem)
556 {
557 Inkscape::LivePathEffect::LPEObjectReference* lperef = sp_lpe_item_get_current_lpereference(lpeitem);
558 if (!lperef)
559 return;
561 PathEffectList new_list = *lpeitem->path_effect_list;
562 PathEffectList::iterator cur_it = find( new_list.begin(), new_list.end(), lperef );
563 if (cur_it != new_list.end()) {
564 PathEffectList::iterator down_it = cur_it;
565 down_it++;
566 if (down_it != new_list.end()) { // perhaps current effect is already last effect
567 std::iter_swap(cur_it, down_it);
568 }
569 }
570 std::string r = patheffectlist_write_svg(new_list);
571 SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", r.c_str());
573 sp_lpe_item_cleanup_original_path_recursive(lpeitem);
574 }
576 void sp_lpe_item_up_current_path_effect(SPLPEItem *lpeitem)
577 {
578 Inkscape::LivePathEffect::LPEObjectReference* lperef = sp_lpe_item_get_current_lpereference(lpeitem);
579 if (!lperef)
580 return;
582 PathEffectList new_list = *lpeitem->path_effect_list;
583 PathEffectList::iterator cur_it = find( new_list.begin(), new_list.end(), lperef );
584 if (cur_it != new_list.end() && cur_it != new_list.begin()) {
585 PathEffectList::iterator up_it = cur_it;
586 up_it--;
587 std::iter_swap(cur_it, up_it);
588 }
589 std::string r = patheffectlist_write_svg(new_list);
591 SP_OBJECT_REPR(lpeitem)->setAttribute("inkscape:path-effect", r.c_str());
593 sp_lpe_item_cleanup_original_path_recursive(lpeitem);
594 }
596 /** used for shapes so they can see if they should also disable shape calculation and read from d= */
597 bool sp_lpe_item_has_broken_path_effect(SPLPEItem *lpeitem)
598 {
599 if (lpeitem->path_effect_list->empty())
600 return false;
602 // go through the list; if some are unknown or invalid, return true
603 PathEffectList effect_list = sp_lpe_item_get_effect_list(lpeitem);
604 for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); it++)
605 {
606 LivePathEffectObject *lpeobj = (*it)->lpeobject;
607 if (!lpeobj || !lpeobj->get_lpe())
608 return true;
609 }
611 return false;
612 }
615 bool sp_lpe_item_has_path_effect(SPLPEItem *lpeitem)
616 {
617 if (lpeitem->path_effect_list->empty())
618 return false;
620 // go through the list; if some are unknown or invalid, we are not an LPE item!
621 PathEffectList effect_list = sp_lpe_item_get_effect_list(lpeitem);
622 for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); it++)
623 {
624 LivePathEffectObject *lpeobj = (*it)->lpeobject;
625 if (!lpeobj || !lpeobj->get_lpe())
626 return false;
627 }
629 return true;
630 }
632 bool sp_lpe_item_has_path_effect_recursive(SPLPEItem *lpeitem)
633 {
634 SPObject *parent = lpeitem->parent;
635 if (parent && SP_IS_LPE_ITEM(parent)) {
636 return sp_lpe_item_has_path_effect(lpeitem) || sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(parent));
637 }
638 else {
639 return sp_lpe_item_has_path_effect(lpeitem);
640 }
641 }
643 Inkscape::LivePathEffect::Effect*
644 sp_lpe_item_has_path_effect_of_type(SPLPEItem *lpeitem, int type)
645 {
646 std::list<Inkscape::LivePathEffect::LPEObjectReference *>::iterator i;
647 for (i = lpeitem->path_effect_list->begin(); i != lpeitem->path_effect_list->end(); ++i) {
648 Inkscape::LivePathEffect::Effect* lpe = (*i)->lpeobject->get_lpe();
649 if (lpe && (lpe->effectType() == type)) {
650 return lpe;
651 }
652 }
653 return NULL;
654 }
656 /* Return false if the item is not a path or already has a shape applied */
657 bool sp_lpe_item_can_accept_freehand_shape(SPLPEItem *lpeitem)
658 {
659 if (!SP_IS_PATH(lpeitem))
660 return false;
662 if (sp_lpe_item_has_path_effect_of_type(lpeitem, Inkscape::LivePathEffect::FREEHAND_SHAPE))
663 return false;
665 return true;
666 }
668 void sp_lpe_item_edit_next_param_oncanvas(SPLPEItem *lpeitem, SPDesktop *dt)
669 {
670 Inkscape::LivePathEffect::LPEObjectReference *lperef = sp_lpe_item_get_current_lpereference(lpeitem);
671 if (lperef && lperef->lpeobject && lperef->lpeobject->get_lpe()) {
672 lperef->lpeobject->get_lpe()->editNextParamOncanvas(SP_ITEM(lpeitem), dt);
673 }
674 }
676 static void
677 sp_lpe_item_child_added (SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
678 {
679 if (((SPObjectClass *) (parent_class))->child_added)
680 (* ((SPObjectClass *) (parent_class))->child_added) (object, child, ref);
682 if (SP_IS_LPE_ITEM(object) && sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(object))) {
683 SPObject *ochild = object->get_child_by_repr(child);
684 if ( ochild && SP_IS_LPE_ITEM(ochild) ) {
685 sp_lpe_item_create_original_path_recursive(SP_LPE_ITEM(ochild));
686 }
687 }
688 }
690 static void
691 sp_lpe_item_remove_child (SPObject * object, Inkscape::XML::Node * child)
692 {
693 if (SP_IS_LPE_ITEM(object) && sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(object))) {
694 SPObject *ochild = object->get_child_by_repr(child);
695 if ( ochild && SP_IS_LPE_ITEM(ochild) ) {
696 sp_lpe_item_cleanup_original_path_recursive(SP_LPE_ITEM(ochild));
697 }
698 }
700 if (((SPObjectClass *) (parent_class))->remove_child)
701 (* ((SPObjectClass *) (parent_class))->remove_child) (object, child);
702 }
704 static std::string patheffectlist_write_svg(PathEffectList const & list)
705 {
706 HRefList hreflist;
707 for (PathEffectList::const_iterator it = list.begin(); it != list.end(); ++it)
708 {
709 hreflist.push_back( std::string((*it)->lpeobject_href) );
710 }
711 return hreflist_write_svg(hreflist);
712 }
714 /**
715 * THE function that should be used to generate any patheffectlist string.
716 * one of the methods to change the effect list:
717 * - create temporary href list
718 * - populate the templist with the effects from the old list that you want to have and their order
719 * - call this function with temp list as param
720 */
721 static std::string hreflist_write_svg(HRefList const & list)
722 {
723 std::string r;
724 bool semicolon_first = false;
725 for (HRefList::const_iterator it = list.begin(); it != list.end(); ++it)
726 {
727 if (semicolon_first) {
728 r += ';';
729 }
730 semicolon_first = true;
732 r += (*it);
733 }
734 return r;
735 }
737 // Return a copy of the effect list
738 PathEffectList sp_lpe_item_get_effect_list(SPLPEItem *lpeitem)
739 {
740 return *lpeitem->path_effect_list;
741 }
743 Inkscape::LivePathEffect::LPEObjectReference* sp_lpe_item_get_current_lpereference(SPLPEItem *lpeitem)
744 {
745 if (!lpeitem->current_path_effect && !lpeitem->path_effect_list->empty()) {
746 sp_lpe_item_set_current_path_effect(lpeitem, lpeitem->path_effect_list->back());
747 }
749 return lpeitem->current_path_effect;
750 }
752 Inkscape::LivePathEffect::Effect* sp_lpe_item_get_current_lpe(SPLPEItem *lpeitem)
753 {
754 Inkscape::LivePathEffect::LPEObjectReference* lperef = sp_lpe_item_get_current_lpereference(lpeitem);
756 if (lperef && lperef->lpeobject)
757 return lperef->lpeobject->get_lpe();
758 else
759 return NULL;
760 }
762 bool sp_lpe_item_set_current_path_effect(SPLPEItem *lpeitem, Inkscape::LivePathEffect::LPEObjectReference* lperef)
763 {
764 for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); it++) {
765 if ((*it)->lpeobject_repr == lperef->lpeobject_repr) {
766 lpeitem->current_path_effect = (*it); // current_path_effect should always be a pointer from the path_effect_list !
767 return true;
768 }
769 }
771 return false;
772 }
774 /**
775 * Writes a new "inkscape:path-effect" string to xml, where the old_lpeobjects are substituted by the new ones.
776 * Note that this method messes up the item's \c PathEffectList.
777 */
778 void SPLPEItem::replacePathEffects( std::vector<LivePathEffectObject const *> const old_lpeobjs,
779 std::vector<LivePathEffectObject const *> const new_lpeobjs )
780 {
781 HRefList hreflist;
782 for (PathEffectList::const_iterator it = this->path_effect_list->begin(); it != this->path_effect_list->end(); ++it)
783 {
784 LivePathEffectObject const * current_lpeobj = (*it)->lpeobject;
785 std::vector<LivePathEffectObject const *>::const_iterator found_it(std::find(old_lpeobjs.begin(), old_lpeobjs.end(), current_lpeobj));
786 if ( found_it != old_lpeobjs.end() ) {
787 std::vector<LivePathEffectObject const *>::difference_type found_index = std::distance (old_lpeobjs.begin(), found_it);
788 const gchar * repr_id = SP_OBJECT_REPR(new_lpeobjs[found_index])->attribute("id");
789 gchar *hrefstr = g_strdup_printf("#%s", repr_id);
790 hreflist.push_back( std::string(hrefstr) );
791 g_free(hrefstr);
792 }
793 else {
794 hreflist.push_back( std::string((*it)->lpeobject_href) );
795 }
796 }
797 std::string r = hreflist_write_svg(hreflist);
798 SP_OBJECT_REPR(this)->setAttribute("inkscape:path-effect", r.c_str());
799 }
801 /**
802 * Check all effects in the stack if they are used by other items, and fork them if so.
803 * It is not recommended to fork the effects by yourself calling LivePathEffectObject::fork_private_if_necessary,
804 * use this method instead.
805 * Returns true if one or more effects were forked; returns false if nothing was done.
806 */
807 bool sp_lpe_item_fork_path_effects_if_necessary(SPLPEItem *lpeitem, unsigned int nr_of_allowed_users)
808 {
809 bool forked = false;
811 if ( sp_lpe_item_has_path_effect(lpeitem) ) {
812 // If one of the path effects is used by 2 or more items, fork it
813 // so that each object has its own independent copy of the effect.
814 // Note: replacing path effects messes up the path effect list
816 // Clones of the LPEItem will increase the refcount of the lpeobjects.
817 // Therefore, nr_of_allowed_users should be increased with the number of clones (i.e. refs to the lpeitem)
818 nr_of_allowed_users += SP_OBJECT(lpeitem)->hrefcount;
820 std::vector<LivePathEffectObject const *> old_lpeobjs, new_lpeobjs;
821 PathEffectList effect_list = sp_lpe_item_get_effect_list(lpeitem);
822 for (PathEffectList::iterator it = effect_list.begin(); it != effect_list.end(); it++)
823 {
824 LivePathEffectObject *lpeobj = (*it)->lpeobject;
825 if (lpeobj) {
826 LivePathEffectObject *forked_lpeobj = lpeobj->fork_private_if_necessary(nr_of_allowed_users);
827 if (forked_lpeobj != lpeobj) {
828 forked = true;
829 old_lpeobjs.push_back(lpeobj);
830 new_lpeobjs.push_back(forked_lpeobj);
831 }
832 }
833 }
835 if (forked) {
836 lpeitem->replacePathEffects(old_lpeobjs, new_lpeobjs);
837 }
838 }
840 return forked;
841 }
843 // Enable or disable the path effects of the item.
844 // The counter allows nested calls
845 static void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable)
846 {
847 if (enable) {
848 lpeitem->path_effects_enabled++;
849 }
850 else {
851 lpeitem->path_effects_enabled--;
852 }
853 }
855 // Are the path effects enabled on this item ?
856 bool sp_lpe_item_path_effects_enabled(SPLPEItem *lpeitem)
857 {
858 return lpeitem->path_effects_enabled > 0;
859 }
861 /*
862 Local Variables:
863 mode:c++
864 c-file-style:"stroustrup"
865 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
866 indent-tabs-mode:nil
867 fill-column:99
868 End:
869 */
870 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :