1 #define __SP_PATH_CHEMISTRY_C__
3 /*
4 * Here are handlers for modifying selections, specific to paths
5 *
6 * Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
10 *
11 * Copyright (C) 1999-2008 Authors
12 * Copyright (C) 2001-2002 Ximian, Inc.
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <cstring>
21 #include <string>
22 #include "xml/repr.h"
23 #include "svg/svg.h"
24 #include "display/curve.h"
25 #include <glib/gmem.h>
26 #include <glibmm/i18n.h>
27 #include "sp-path.h"
28 #include "sp-text.h"
29 #include "sp-flowtext.h"
30 #include "libnr/nr-path.h"
31 #include "text-editing.h"
32 #include "style.h"
33 #include "inkscape.h"
34 #include "desktop.h"
35 #include "document.h"
36 #include "message-stack.h"
37 #include "selection.h"
38 #include "desktop-handles.h"
39 #include "box3d.h"
41 #include "path-chemistry.h"
43 /* Helper functions for sp_selected_path_to_curves */
44 static void sp_selected_path_to_curves0(gboolean do_document_done, guint32 text_grouping_policy);
45 static bool sp_item_list_to_curves(const GSList *items, GSList **selected, GSList **to_select);
47 enum {
48 /* Not used yet. This is the placeholder of Lauris's idea. */
49 SP_TOCURVE_INTERACTIVE = 1 << 0,
50 SP_TOCURVE_GROUPING_BY_WORD = 1 << 1,
51 SP_TOCURVE_GROUPING_BY_LINE = 1 << 2,
52 SP_TOCURVE_GROUPING_BY_WHOLE = 1 << 3
53 };
55 void
56 sp_selected_path_combine(void)
57 {
58 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
59 Inkscape::Selection *selection = sp_desktop_selection(desktop);
61 if (g_slist_length((GSList *) selection->itemList()) < 2) {
62 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>at least two objects</b> to combine."));
63 return;
64 }
66 desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Combining paths..."));
67 // set "busy" cursor
68 desktop->setWaitingCursor();
70 GSList *items = g_slist_copy((GSList *) selection->itemList());
71 GSList *already_paths = NULL;
72 GSList *to_paths = NULL;
73 for (GSList *i = items; i != NULL; i = i->next) {
74 SPItem *item = (SPItem *) i->data;
75 if (SP_IS_PATH(item))
76 already_paths = g_slist_prepend(already_paths, item);
77 else
78 to_paths = g_slist_prepend(to_paths, item);
79 }
80 GSList *converted = NULL;
81 bool did = sp_item_list_to_curves(to_paths, &items, &converted);
82 items = g_slist_concat (items, converted);
84 items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position);
85 items = g_slist_reverse(items);
87 // remember the position, id, transform and style of the topmost path, they will be assigned to the combined one
88 gint position = 0;
89 char const *id = NULL;
90 char const *transform = NULL;
91 gchar *style = NULL;
92 gchar *path_effect = NULL;
94 SPCurve* curve = 0;
95 SPItem *first = NULL;
96 Inkscape::XML::Node *parent = NULL;
98 for (GSList *i = items; i != NULL; i = i->next) { // going from top to bottom
100 SPItem *item = (SPItem *) i->data;
101 if (!SP_IS_PATH(item))
102 continue;
103 did = true;
105 SPCurve *c = sp_path_get_curve_for_edit(SP_PATH(item));
106 if (first == NULL) { // this is the topmost path
107 first = item;
108 parent = SP_OBJECT_REPR(first)->parent();
109 position = SP_OBJECT_REPR(first)->position();
110 id = SP_OBJECT_REPR(first)->attribute("id");
111 transform = SP_OBJECT_REPR(first)->attribute("transform");
112 // FIXME: merge styles of combined objects instead of using the first one's style
113 style = g_strdup(SP_OBJECT_REPR(first)->attribute("style"));
114 path_effect = g_strdup(SP_OBJECT_REPR(first)->attribute("inkscape:path-effect"));
115 //c->transform(item->transform);
116 curve = c;
117 } else {
118 c->transform(item->getRelativeTransform(SP_OBJECT(first)));
119 curve->append(c, false);
120 c->unref();
121 }
123 // unless this is the topmost object,
124 if (item != first) {
125 // reduce position only if the same parent
126 if (SP_OBJECT_REPR(item)->parent() == parent)
127 position--;
128 // delete the object for real, so that its clones can take appropriate action
129 SP_OBJECT(item)->deleteObject();
130 }
131 }
133 g_slist_free(items);
135 if (did) {
136 selection->clear();
138 // delete the topmost one so that its clones don't get alerted; this object will be
139 // restored shortly, with the same id
140 SP_OBJECT(first)->deleteObject(false);
142 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
143 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
145 // restore id, transform, path effect, and style
146 repr->setAttribute("id", id);
147 if (transform) repr->setAttribute("transform", transform);
148 repr->setAttribute("style", style);
149 g_free(style);
151 // set path data corresponding to new curve
152 gchar *dstring = sp_svg_write_path(SP_CURVE_BPATH(curve));
153 curve->unref();
154 repr->setAttribute("d", dstring);
155 if (path_effect)
156 repr->setAttribute("inkscape:original-d", dstring);
157 g_free(dstring);
159 repr->setAttribute("inkscape:path-effect", path_effect);
160 g_free(path_effect);
162 // add the new group to the parent of the topmost
163 parent->appendChild(repr);
165 // move to the position of the topmost, reduced by the number of deleted items
166 repr->setPosition(position > 0 ? position : 0);
168 sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_COMBINE,
169 _("Combine"));
171 selection->set(repr);
173 Inkscape::GC::release(repr);
175 } else {
176 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No path(s)</b> to combine in the selection."));
177 }
179 desktop->clearWaitingCursor();
180 }
182 void
183 sp_selected_path_break_apart(void)
184 {
185 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
187 Inkscape::Selection *selection = sp_desktop_selection(desktop);
189 if (selection->isEmpty()) {
190 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to break apart."));
191 return;
192 }
194 desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Breaking apart paths..."));
195 // set "busy" cursor
196 desktop->setWaitingCursor();
198 bool did = false;
200 for (GSList *items = g_slist_copy((GSList *) selection->itemList());
201 items != NULL;
202 items = items->next) {
204 SPItem *item = (SPItem *) items->data;
206 if (!SP_IS_PATH(item))
207 continue;
209 SPPath *path = SP_PATH(item);
211 SPCurve *curve = sp_path_get_curve_for_edit(SP_PATH(path));
212 if (curve == NULL)
213 continue;
215 did = true;
217 Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
218 gint pos = SP_OBJECT_REPR(item)->position();
219 char const *id = SP_OBJECT_REPR(item)->attribute("id");
221 gchar *style = g_strdup(SP_OBJECT(item)->repr->attribute("style"));
222 gchar *path_effect = g_strdup(SP_OBJECT(item)->repr->attribute("inkscape:path-effect"));
224 NArtBpath *abp = nr_artpath_affine(SP_CURVE_BPATH(curve), (SP_ITEM(path))->transform);
226 curve->unref();
228 // it's going to resurrect as one of the pieces, so we delete without advertisement
229 SP_OBJECT(item)->deleteObject(false);
231 curve = SPCurve::new_from_bpath(abp);
232 g_assert(curve != NULL);
234 GSList *list = curve->split();
236 curve->unref();
238 GSList *reprs = NULL;
239 for (GSList *l = list; l != NULL; l = l->next) {
240 curve = (SPCurve *) l->data;
242 Inkscape::XML::Node *repr = parent->document()->createElement("svg:path");
243 repr->setAttribute("style", style);
245 gchar *str = sp_svg_write_path(SP_CURVE_BPATH(curve));
246 repr->setAttribute("d", str);
247 if (path_effect)
248 repr->setAttribute("inkscape:original-d", str);
249 g_free(str);
251 repr->setAttribute("inkscape:path-effect", path_effect);
253 // add the new repr to the parent
254 parent->appendChild(repr);
256 // move to the saved position
257 repr->setPosition(pos > 0 ? pos : 0);
259 // if it's the first one, restore id
260 if (l == list)
261 repr->setAttribute("id", id);
263 reprs = g_slist_prepend (reprs, repr);
265 Inkscape::GC::release(repr);
266 }
268 selection->setReprList(reprs);
270 g_slist_free(reprs);
271 g_slist_free(list);
272 g_free(style);
274 }
276 desktop->clearWaitingCursor();
278 if (did) {
279 sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_BREAK_APART,
280 _("Break apart"));
281 } else {
282 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No path(s)</b> to break apart in the selection."));
283 }
284 }
286 /* This function is an entry point from GUI */
287 void
288 sp_selected_path_to_curves(bool interactive)
289 {
290 if (interactive) {
291 sp_selected_path_to_curves0(TRUE, SP_TOCURVE_INTERACTIVE);
292 } else {
293 sp_selected_path_to_curves0(false, 0);
294 }
295 }
297 static void
298 sp_selected_path_to_curves0(gboolean interactive, guint32 /*text_grouping_policy*/)
299 {
300 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
302 Inkscape::Selection *selection = sp_desktop_selection(desktop);
304 if (selection->isEmpty()) {
305 if (interactive)
306 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to path."));
307 return;
308 }
310 bool did = false;
311 if (interactive) {
312 desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Converting objects to paths..."));
313 // set "busy" cursor
314 desktop->setWaitingCursor();
315 }
317 GSList *selected = g_slist_copy((GSList *) selection->itemList());
318 GSList *to_select = NULL;
319 selection->clear();
320 GSList *items = g_slist_copy(selected);
322 did = sp_item_list_to_curves(items, &selected, &to_select);
324 g_slist_free (items);
325 selection->setReprList(to_select);
326 selection->addList(selected);
327 g_slist_free (to_select);
328 g_slist_free (selected);
330 if (interactive) {
331 desktop->clearWaitingCursor();
332 if (did) {
333 sp_document_done(sp_desktop_document(desktop), SP_VERB_OBJECT_TO_CURVE,
334 _("Object to path"));
335 } else {
336 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No objects</b> to convert to path in the selection."));
337 return;
338 }
339 }
340 }
342 static bool
343 sp_item_list_to_curves(const GSList *items, GSList **selected, GSList **to_select)
344 {
345 bool did = false;
347 for (;
348 items != NULL;
349 items = items->next) {
351 SPItem *item = SP_ITEM(items->data);
353 if (SP_IS_PATH(item) && !SP_PATH(item)->original_curve) {
354 continue; // already a path, and no path effect
355 }
357 if (SP_IS_BOX3D(item)) {
358 // convert 3D box to ordinary group of paths; replace the old element in 'selected' with the new group
359 Inkscape::XML::Node *repr = SP_OBJECT_REPR(box3d_convert_to_group(SP_BOX3D(item)));
361 if (repr) {
362 *to_select = g_slist_prepend (*to_select, repr);
363 did = true;
364 *selected = g_slist_remove (*selected, item);
365 }
367 continue;
368 }
370 if (SP_IS_GROUP(item)) {
371 sp_lpe_item_remove_path_effect(SP_LPE_ITEM(item), true);
372 GSList *item_list = sp_item_group_item_list(SP_GROUP(item));
374 GSList *item_to_select = NULL;
375 GSList *item_selected = NULL;
377 if (sp_item_list_to_curves(item_list, &item_selected, &item_to_select))
378 did = true;
380 g_slist_free(item_list);
381 g_slist_free(item_to_select);
382 g_slist_free(item_selected);
384 continue;
385 }
387 Inkscape::XML::Node *repr = sp_selected_item_to_curved_repr(item, 0);
388 if (!repr)
389 continue;
391 did = true;
392 *selected = g_slist_remove (*selected, item);
394 // remember the position of the item
395 gint pos = SP_OBJECT_REPR(item)->position();
396 // remember parent
397 Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
398 // remember id
399 char const *id = SP_OBJECT_REPR(item)->attribute("id");
401 // It's going to resurrect, so we delete without notifying listeners.
402 SP_OBJECT(item)->deleteObject(false);
404 // restore id
405 repr->setAttribute("id", id);
406 // add the new repr to the parent
407 parent->appendChild(repr);
408 // move to the saved position
409 repr->setPosition(pos > 0 ? pos : 0);
411 /* Buglet: We don't re-add the (new version of the) object to the selection of any other
412 * desktops where it was previously selected. */
413 *to_select = g_slist_prepend (*to_select, repr);
414 Inkscape::GC::release(repr);
415 }
417 return did;
418 }
420 Inkscape::XML::Node *
421 sp_selected_item_to_curved_repr(SPItem *item, guint32 /*text_grouping_policy*/)
422 {
423 if (!item)
424 return NULL;
426 SPCurve *curve = NULL;
427 if (SP_IS_SHAPE(item)) {
428 curve = sp_shape_get_curve(SP_SHAPE(item));
429 } else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
430 curve = te_get_layout(item)->convertToCurves();
431 }
433 if (!curve)
434 return NULL;
436 // Prevent empty paths from being added to the document
437 // otherwise we end up with zomby markup in the SVG file
438 if(curve->_end <= 0)
439 {
440 curve->unref();
441 return NULL;
442 }
444 Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(item)->document();
445 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
446 /* Transformation */
447 repr->setAttribute("transform", SP_OBJECT_REPR(item)->attribute("transform"));
448 /* Style */
449 gchar *style_str = sp_style_write_difference(SP_OBJECT_STYLE(item),
450 SP_OBJECT_STYLE(SP_OBJECT_PARENT(item)));
451 repr->setAttribute("style", style_str);
452 g_free(style_str);
454 /* Mask */
455 gchar *mask_str = (gchar *) SP_OBJECT_REPR(item)->attribute("mask");
456 if ( mask_str )
457 repr->setAttribute("mask", mask_str);
459 /* Clip path */
460 gchar *clip_path_str = (gchar *) SP_OBJECT_REPR(item)->attribute("clip-path");
461 if ( clip_path_str )
462 repr->setAttribute("clip-path", clip_path_str);
464 /* Rotation center */
465 repr->setAttribute("inkscape:transform-center-x", SP_OBJECT_REPR(item)->attribute("inkscape:transform-center-x"), false);
466 repr->setAttribute("inkscape:transform-center-y", SP_OBJECT_REPR(item)->attribute("inkscape:transform-center-y"), false);
468 /* Definition */
469 gchar *def_str = sp_svg_write_path(SP_CURVE_BPATH(curve));
470 repr->setAttribute("d", def_str);
471 g_free(def_str);
472 curve->unref();
473 return repr;
474 }
477 // FIXME: THIS DOES NOT REVERSE THE NODETYPES ORDER!
478 void
479 sp_selected_path_reverse()
480 {
481 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
483 Inkscape::Selection *selection = sp_desktop_selection(desktop);
484 GSList *items = (GSList *) selection->itemList();
486 if (!items) {
487 sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to reverse."));
488 return;
489 }
492 // set "busy" cursor
493 desktop->setWaitingCursor();
495 bool did = false;
496 desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, _("Reversing paths..."));
498 for (GSList *i = items; i != NULL; i = i->next) {
500 if (!SP_IS_PATH(i->data))
501 continue;
503 did = true;
504 SPPath *path = SP_PATH(i->data);
506 SPCurve *rcurve = sp_path_get_curve_reference(path)->create_reverse();
508 gchar *str = sp_svg_write_path(SP_CURVE_BPATH(rcurve));
509 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(path)) ) {
510 SP_OBJECT_REPR(path)->setAttribute("inkscape:original-d", str);
511 } else {
512 SP_OBJECT_REPR(path)->setAttribute("d", str);
513 }
514 g_free(str);
516 rcurve->unref();
517 }
519 desktop->clearWaitingCursor();
521 if (did) {
522 sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_REVERSE,
523 _("Reverse path"));
524 } else {
525 sp_desktop_message_stack(desktop)->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to reverse in the selection."));
526 }
527 }
529 /*
530 Local Variables:
531 mode:c++
532 c-file-style:"stroustrup"
533 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
534 indent-tabs-mode:nil
535 fill-column:99
536 End:
537 */
538 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :