#define __SP_PATH_CHEMISTRY_C__ /* * Here are handlers for modifying selections, specific to paths * * Authors: * Lauris Kaplinski <lauris@kaplinski.com> * * Copyright (C) 1999-2002 Authors * Copyright (C) 2001-2002 Ximian, Inc. * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <string.h> #include "libnr/nr-macros.h" #include "xml/repr.h" #include "svg/svg.h" #include "display/curve.h" #include <glibmm/i18n.h> #include "sp-object.h" #include "sp-path.h" #include "sp-text.h" #include "style.h" #include "inkscape.h" #include "document.h" #include "selection.h" #include "desktop-handles.h" #include "path-chemistry.h" #include "desktop.h" /* Helper functions for sp_selected_path_to_curves */ static void sp_selected_path_to_curves0 (gboolean do_document_done, guint32 text_grouping_policy); static SPRepr * sp_selected_item_to_curved_repr(SPItem * item, guint32 text_grouping_policy); enum { /* Not used yet. This is the placeholder of Lauris's idea. */ SP_TOCURVE_INTERACTIVE = 1 << 0, SP_TOCURVE_GROUPING_BY_WORD = 1 << 1, SP_TOCURVE_GROUPING_BY_LINE = 1 << 2, SP_TOCURVE_GROUPING_BY_WHOLE = 1 << 3 }; void sp_selected_path_combine (void) { SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (!SP_IS_DESKTOP(desktop)) return; SPSelection *selection = SP_DT_SELECTION (desktop); GSList *items = (GSList *) selection->itemList(); if (g_slist_length (items) < 2) { desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>at least two objects</b> to combine.")); return; } for (GSList *i = items; i != NULL; i = i->next) { SPItem *item = (SPItem *) i->data; if (!SP_IS_SHAPE (item) && !SP_IS_TEXT(item)) { desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("At least one of the objects is <b>not a path</b>, cannot combine.")); return; } } SPRepr *parent = SP_OBJECT_REPR ((SPItem *) items->data)->parent(); for (GSList *i = items; i != NULL; i = i->next) { if ( SP_OBJECT_REPR ((SPItem *) i->data)->parent() != parent ) { desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You cannot combine objects from <b>different groups</b> or <b>layers</b>.")); return; } } sp_selected_path_to_curves0 (FALSE, 0); items = (GSList *) selection->itemList(); items = g_slist_copy (items); items = g_slist_sort (items, (GCompareFunc) sp_item_repr_compare_position); // remember the position of the topmost object gint topmost = sp_repr_position (SP_OBJECT_REPR ((SPItem *) g_slist_last(items)->data)); // remember the id of the bottomost object const char *id = sp_repr_attr (SP_OBJECT_REPR ((SPItem *) items->data), "id"); // FIXME: merge styles of combined objects instead of using the first one's style gchar *style = g_strdup (sp_repr_attr (SP_OBJECT_REPR ((SPItem *) items->data), "style")); GString *dstring = g_string_new(""); for (GSList *i = items; i != NULL; i = i->next) { SPPath *path = (SPPath *) i->data; SPCurve *c = sp_shape_get_curve (SP_SHAPE (path)); NArtBpath *abp = nr_artpath_affine(c->bpath, SP_ITEM(path)->transform); sp_curve_unref (c); gchar *str = sp_svg_write_path (abp); nr_free (abp); dstring = g_string_append(dstring, str); g_free (str); // if this is the bottommost object, if (!strcmp (sp_repr_attr (SP_OBJECT_REPR (path), "id"), id)) { // delete it so that its clones don't get alerted; this object will be restored shortly, with the same id SP_OBJECT (path)->deleteObject(false); } else { // delete the object for real, so that its clones can take appropriate action SP_OBJECT (path)->deleteObject(); } topmost --; } g_slist_free (items); SPRepr *repr = sp_repr_new ("svg:path"); // restore id sp_repr_set_attr (repr, "id", id); sp_repr_set_attr (repr, "style", style); g_free (style); sp_repr_set_attr (repr, "d", dstring->str); g_string_free (dstring, TRUE); // add the new group to the group members' common parent sp_repr_append_child (parent, repr); // move to the position of the topmost, reduced by the number of deleted items sp_repr_set_position_absolute (repr, topmost > 0 ? topmost + 1 : 0); sp_document_done (SP_DT_DOCUMENT (desktop)); selection->setRepr(repr); sp_repr_unref (repr); } void sp_selected_path_break_apart (void) { SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (!SP_IS_DESKTOP(desktop)) return; SPSelection *selection = SP_DT_SELECTION (desktop); if (selection->isEmpty()) { desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to break apart.")); return; } bool did = false; for (GSList *items = g_slist_copy((GSList *) selection->itemList()); items != NULL; items = items->next) { SPItem *item = (SPItem *) items->data; if (!SP_IS_PATH (item)) continue; SPPath *path = SP_PATH (item); SPCurve *curve = sp_shape_get_curve (SP_SHAPE (path)); if (curve == NULL) continue; did = true; SPRepr *parent = SP_OBJECT_REPR (item)->parent(); gint pos = sp_repr_position (SP_OBJECT_REPR (item)); const char *id = sp_repr_attr (SP_OBJECT_REPR (item), "id"); gchar *style = g_strdup (sp_repr_attr (SP_OBJECT (item)->repr, "style")); NArtBpath *abp = nr_artpath_affine (curve->bpath, sp_item_i2root_affine (SP_ITEM (path))); sp_curve_unref (curve); // it's going to resurrect as one of the pieces, so we delete without advertisement SP_OBJECT (item)->deleteObject(false); curve = sp_curve_new_from_bpath (abp); g_assert (curve != NULL); GSList *list = sp_curve_split (curve); sp_curve_unref (curve); for (GSList *l = g_slist_reverse(list); l != NULL; l = l->next) { curve = (SPCurve *) l->data; SPRepr *repr = sp_repr_new ("svg:path"); sp_repr_set_attr (repr, "style", style); gchar *str = sp_svg_write_path (curve->bpath); sp_repr_set_attr (repr, "d", str); g_free (str); // add the new repr to the parent sp_repr_append_child (parent, repr); // move to the saved position sp_repr_set_position_absolute (repr, pos > 0 ? pos : 0); // if it's the first one, restore id if (l == list) sp_repr_set_attr (repr, "id", id); selection->addRepr(repr); sp_repr_unref (repr); } g_slist_free (list); g_free (style); } if (did) { sp_document_done (SP_DT_DOCUMENT (desktop)); } else { desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No path(s)</b> to break apart in the selection.")); return; } } /* This function is an entry point from GUI */ void sp_selected_path_to_curves (void) { sp_selected_path_to_curves0(TRUE, SP_TOCURVE_INTERACTIVE); } static void sp_selected_path_to_curves0 (gboolean interactive, guint32 text_grouping_policy) { SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (!SP_IS_DESKTOP(desktop)) return; SPSelection *selection = SP_DT_SELECTION (desktop); if (selection->isEmpty()) { if (interactive) desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>object(s)</b> to convert to path.")); return; } bool did = false; for (GSList *items = g_slist_copy((GSList *) selection->itemList()); items != NULL; items = items->next) { SPItem *item = SP_ITEM (items->data); SPRepr *repr = sp_selected_item_to_curved_repr (item, 0); if (!repr) continue; did = true; // remember the position of the item gint pos = sp_repr_position (SP_OBJECT_REPR (item)); // remember parent SPRepr *parent = SP_OBJECT_REPR (item)->parent(); // remember id const char *id = sp_repr_attr (SP_OBJECT_REPR (item), "id"); selection->removeItem (item); // it's going to resurrect, so we delete without advertisement SP_OBJECT (item)->deleteObject(false); // restore id sp_repr_set_attr (repr, "id", id); // add the new repr to the parent sp_repr_append_child (parent, repr); // move to the saved position sp_repr_set_position_absolute (repr, pos > 0 ? pos : 0); selection->addRepr(repr); sp_repr_unref(repr); } if (interactive) { if (did) { sp_document_done (SP_DT_DOCUMENT (desktop)); } else { desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No objects</b> to convert to path in the selection.")); return; } } } static SPRepr * sp_selected_item_to_curved_repr(SPItem * item, guint32 text_grouping_policy) { if (!item) return NULL; SPCurve *curve = NULL; if (SP_IS_SHAPE (item)) { curve = sp_shape_get_curve (SP_SHAPE (item)); } else if (SP_IS_TEXT (item)) { curve = sp_text_normalized_bpath (SP_TEXT (item)); } if (!curve) return NULL; SPRepr *repr = sp_repr_new ("svg:path"); /* Transformation */ sp_repr_set_attr (repr, "transform", sp_repr_attr (SP_OBJECT_REPR (item), "transform")); /* Style */ gchar *style_str = sp_style_write_difference (SP_OBJECT_STYLE (item), SP_OBJECT_STYLE (SP_OBJECT_PARENT (item))); sp_repr_set_attr (repr, "style", style_str); g_free (style_str); /* Definition */ gchar *def_str = sp_svg_write_path (curve->bpath); sp_repr_set_attr (repr, "d", def_str); g_free (def_str); sp_curve_unref (curve); return repr; } void sp_path_cleanup (SPPath *path) { if (strcmp (sp_repr_name (SP_OBJECT_REPR (path)), "svg:path")) return; SPStyle *style = SP_OBJECT_STYLE (path); if (style->fill.type == SP_PAINT_TYPE_NONE) return; SPCurve *curve = sp_shape_get_curve (SP_SHAPE (path)); if (!curve) return; GSList *c = sp_curve_split (curve); sp_curve_unref (curve); gboolean dropped = FALSE; GSList *curves = NULL; while (c) { curve = (SPCurve *) c->data; if (curve->closed) { curves = g_slist_prepend (curves, curve); } else { dropped = TRUE; sp_curve_unref (curve); } c = g_slist_remove (c, c->data); } curves = g_slist_reverse (curves); curve = sp_curve_concat (curves); while (curves) { sp_curve_unref ((SPCurve *) curves->data); curves = g_slist_remove (curves, curves->data); } if (sp_curve_is_empty (curve)) { SP_OBJECT (path)->deleteObject(); } else if (dropped) { gchar *svgpath; svgpath = sp_svg_write_path (curve->bpath); sp_repr_set_attr (SP_OBJECT_REPR (path), "d", svgpath); g_free (svgpath); } sp_curve_unref (curve); } void sp_selected_path_reverse () { SPDesktop *desktop = SP_ACTIVE_DESKTOP; if (!SP_IS_DESKTOP(desktop)) return; SPSelection *selection = SP_DT_SELECTION (desktop); GSList *items = (GSList *) selection->itemList(); if (g_slist_length (items) == 0) { desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to reverse.")); return; } bool did = false; for (GSList *i = items; i != NULL; i = i->next) { if (!SP_IS_SHAPE (items->data)) continue; did = true; SPShape *shape = SP_SHAPE (items->data); SPCurve *rcurve = sp_curve_reverse (shape->curve); char *str = sp_svg_write_path (rcurve->bpath); sp_repr_set_attr (SP_OBJECT_REPR (shape), "d", str); sp_curve_unref (rcurve); } if (did) { sp_document_done (SP_DT_DOCUMENT (desktop)); } else { desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to reverse in the selection.")); } }