From a8d7f4db568ca421bab7f4d2828fe2d3c17e6072 Mon Sep 17 00:00:00 2001 From: Krzysztof Kosi??ski Date: Thu, 27 Feb 2014 04:54:34 +0100 Subject: Partial fix for LP #1001756 (crash when ungrouping a selection containing both a group and its clone). There are still some problems with undo/redo (incomplete undo transaction somewhere). (bzr r13063) --- src/selection-chemistry.cpp | 103 ++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 32 deletions(-) (limited to 'src/selection-chemistry.cpp') diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index c3d37af72..ea95cecd8 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -53,6 +53,7 @@ SPCycleType SP_CYCLING = SP_CYCLE_FOCUS; #include "sp-ellipse.h" #include "sp-star.h" #include "sp-spiral.h" +#include "sp-switch.h" #include "sp-polyline.h" #include "sp-line.h" #include "text-editing.h" @@ -767,57 +768,95 @@ void sp_selection_group(Inkscape::Selection *selection, SPDesktop *desktop) Inkscape::GC::release(group); } +static gint clone_depth_descending(gconstpointer a, gconstpointer b) { + SPUse *use_a = static_cast(const_cast(a)); + SPUse *use_b = static_cast(const_cast(b)); + int depth_a = use_a->cloneDepth(); + int depth_b = use_b->cloneDepth(); + if (depth_a < depth_b) { + return 1; + } else if (depth_a == depth_b) { + return 0; + } else { + return -1; + } +} + void sp_selection_ungroup(Inkscape::Selection *selection, SPDesktop *desktop) { if (selection->isEmpty()) { selection_display_message(desktop, Inkscape::WARNING_MESSAGE, _("Select a group to ungroup.")); + } + + // first check whether there is anything to ungroup + GSList *old_select = const_cast(selection->itemList()); + GSList *new_select = NULL; + GSList *groups = NULL; + for (GSList *item = old_select; item; item = item->next) { + SPItem *obj = static_cast(item->data); + if (SP_IS_GROUP(obj) && !SP_IS_SWITCH(obj)) { + groups = g_slist_prepend(groups, obj); + } + } + + if (groups == NULL) { + selection_display_message(desktop, Inkscape::ERROR_MESSAGE, _("No groups to ungroup in the selection.")); + g_slist_free(groups); return; } - GSList *items = g_slist_copy(const_cast(selection->itemList())); + GSList *items = g_slist_copy(old_select); selection->clear(); - // Get a copy of current selection. - GSList *new_select = NULL; - bool ungrouped = false; - for (GSList *i = items; - i != NULL; - i = i->next) - { - SPItem *group = static_cast(i->data); + // If any of the clones refer to the groups, unlink them and replace them with successors + // in the items list. + GSList *clones_to_unlink = NULL; + for (GSList *item = items; item; item = item->next) { + SPUse *use = dynamic_cast(static_cast(item->data)); - // when ungrouping cloned groups with their originals, some objects that were selected may no more exist due to unlinking - if (!SP_IS_OBJECT(group) || !group->getRepr()) { - continue; + SPItem *original = use; + while (SP_IS_USE(original)) { + original = SP_USE(original)->get_original(); } - // This check reflects the g_return_if_fail in sp_item_group_ungroup and - // may be a redundent. It also allows ungrouping of 'a' tags and we dont - if (strcmp(group->getRepr()->name(), "svg:g") && strcmp(group->getRepr()->name(), "svg:switch") && - strcmp(group->getRepr()->name(), "svg:svg")) { - // keep the non-group item in the new selection - new_select = g_slist_append(new_select, group); - continue; + if (g_slist_find(groups, original) != NULL) { + clones_to_unlink = g_slist_prepend(clones_to_unlink, item->data); } - - GSList *children = NULL; - /* This is not strictly required, but is nicer to rely on group ::destroy (lauris) */ - sp_item_group_ungroup(SP_GROUP(group), &children, false); - ungrouped = true; - // Add ungrouped items to the new selection. - new_select = g_slist_concat(new_select, children); } - if (new_select) { // Set new selection. - selection->addList(new_select); - g_slist_free(new_select); + // Unlink clones beginning from those with highest clone depth. + // This way we can be sure than no additional automatic unlinking happens, + // and the items in the list remain valid + clones_to_unlink = g_slist_sort(clones_to_unlink, clone_depth_descending); + + for (GSList *item = clones_to_unlink; item; item = item->next) { + SPUse *use = static_cast(item->data); + GSList *items_node = g_slist_find(items, item->data); + items_node->data = use->unlink(); } - if (!ungrouped) { - selection_display_message(desktop, Inkscape::ERROR_MESSAGE, _("No groups to ungroup in the selection.")); + g_slist_free(clones_to_unlink); + + // do the actual work + for (GSList *item = items; item; item = item->next) { + SPItem *obj = static_cast(item->data); + + // do not ungroup switches + if (SP_IS_GROUP(obj) && !SP_IS_SWITCH(obj)) { + GSList *children = NULL; + sp_item_group_ungroup(SP_GROUP(obj), &children, false); + // add the items resulting from ungrouping to the selection + new_select = g_slist_concat(new_select, children); + item->data = NULL; // zero out the original pointer, which is no longer valid + } else { + // if not a group, keep in the selection + new_select = g_slist_append(new_select, item->data); + } } + selection->addList(new_select); + g_slist_free(new_select); g_slist_free(items); - + DocumentUndo::done(selection->layers()->getDocument(), SP_VERB_SELECTION_UNGROUP, _("Ungroup")); } -- cgit v1.2.3