Merge lp:~inkscape+alexander/inkscape/clones into lp:~inkscape.dev/inkscape/trunk

Proposed by Alexander Brock
Status: Superseded
Proposed branch: lp:~inkscape+alexander/inkscape/clones
Merge into: lp:~inkscape.dev/inkscape/trunk
Diff against target: 184 lines (+80/-11)
5 files modified
src/menus-skeleton.h (+1/-0)
src/object-set.h (+14/-2)
src/selection-chemistry.cpp (+59/-9)
src/verbs.cpp (+5/-0)
src/verbs.h (+1/-0)
To merge this branch: bzr merge lp:~inkscape+alexander/inkscape/clones
Reviewer Review Type Date Requested Status
Mc code, refactor Pending
Martin Owens Pending
Review via email: mp+310059@code.launchpad.net

This proposal supersedes a proposal from 2016-10-27.

This proposal has been superseded by a proposal from 2016-11-04.

Commit message

Add "Edit->Clone->Unlink Clones recursively" function (ObjectSet::unlinkRecursive)

Description of the change

I added "Edit->Clone->Unlink Clones recursively": It recurses into groups and unlinks every clone it finds, including clones used as clipping paths.

Mc... rewrote the function and I improved it further.

To post a comment you must log in.
Revision history for this message
Mc (mc...) wrote : Posted in a previous version of this proposal

Looks mostly fine, but I'm not a big fan of the _functions which seems to duplicate the code a bit and double number of functions. I'd prefer a ", bool flash = true" or something like that in the already present function and "if(doflash) desktop->messageStack()->flash(whatever)"

I'll re-read the main new function during the week-end and extensively test it.

Revision history for this message
Mc (mc...) wrote : Posted in a previous version of this proposal

Ok, so with the small change I did to the codebase, here's what I think should be done:

-> add a "bool skip_undo" flag to ::setMask(), ::unsetMask(), and ::unlink() similar to what's done in other methods in the file. (Also add it to your recursive method).

-> make a ObjectSet with no SPDesktop (just the SPDocument) containing the selection, so that it does not flash recursively.

-> make all the calls with the skip_undo flag on your ObjectSet.

review: Needs Resubmitting (code, refactor)
Revision history for this message
Alexander Brock (brock-alexander) wrote : Posted in a previous version of this proposal

> Ok, so with the small change I did to the codebase, here's what I think should
> be done:
>
> -> add a "bool skip_undo" flag to ::setMask(), ::unsetMask(), and ::unlink()
> similar to what's done in other methods in the file. (Also add it to your
> recursive method).
>
> -> make a ObjectSet with no SPDesktop (just the SPDocument) containing the
> selection, so that it does not flash recursively.
>
> -> make all the calls with the skip_undo flag on your ObjectSet.

I tried to merge your changes with my branch, failed and restarted from scratch.
I added an unlinkRecursive method to ObjectSet, added the bool skip_undo where I needed it and force-pushed the code to the old location (lp:~inkscape+alexander/inkscape/clones).

Revision history for this message
Mc (mc...) wrote : Posted in a previous version of this proposal

Good !
I did a merge proposal to your branch to propose a simpler version of the recursive function, though it can be improved and further simplified by putting the unclip/reclip business in the unlink() function where it probably belongs.
If you find the courage, you can also propose unit tests to test the function (see testfiles/src/object-set-test.cpp )

Revision history for this message
Tavmjong Bah (tavmjong-free) wrote :

Please get rid of gratuitous spacing differences. It makes it much harder to see what is really being changed.

Revision history for this message
Alexander Brock (brock-alexander) wrote :

@mc...: Thank you for rewriting the unlinkRecursive function. I checked out your version, tested it, found some problems and fixed them:

1.

unlinked = unlinked || tmp_set.unlinkRecursive(true);

From the C++ standard: "Unlike |, || guarantees left-to-right evaluation; moreover, the second operand is not evaluated if the first operand evaluates to true."

So if unlinked is already true then the unlinkRecursive function is not called, preventing groups of clones from beeing unlinked. I changed it:

unlinked = tmp_set.unlinkRecursive(true) || unlinked;

2.

if(SP_IS_GROUP(it)) {
......
} else if(dynamic_cast<SPUse*>(it) || dynamic_cast<SPText*>(it) || dynamic_cast<SPTRef*>(it)) {

This causes two problems: First, if an Object is a group masked (or clipped) by a clone that clone would not be unlinked.
Second, if the object is not SPUse, SPText or SPTRef it still is necessary to run the code, it might be clipped or masked by a clone of some object.
I removed the else if so the code runs unconditionally.

3.

unlinked = unlinked || tmp_set.unlink();

This causes calls to DocumentUndo::done (also see 1.), so I changed it:

unlinked = tmp_set.unlink(true) || unlinked;

15205. By Alexander Brock <email address hidden>

Add "Edit->Clone->Unlink Clones recursively" function (ObjectSet::unlinkRecursive)

15206. By Alexander Brock <email address hidden>

Preserve clips when unlinking clones

15207. By Alexander Brock <email address hidden>

Make ObjectSet::unlink() work for clones which are both clipped and masked

15208. By Alexander Brock <email address hidden>

Remove duplicate code from unlinkRecursive() and fix issue with clones of groups

15209. By Alexander Brock <email address hidden>

Fix selection issue with ObjectSet::unlinkRecursive() and add tests for it.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/menus-skeleton.h'
2--- src/menus-skeleton.h 2016-04-15 14:39:49 +0000
3+++ src/menus-skeleton.h 2016-11-04 14:19:19 +0000
4@@ -66,6 +66,7 @@
5 " <verb verb-id=\"EditClone\" />\n"
6 " <verb verb-id=\"DialogClonetiler\" />\n"
7 " <verb verb-id=\"EditUnlinkClone\" />\n"
8+" <verb verb-id=\"EditUnlinkCloneRecursive\" />\n"
9 " <verb verb-id=\"EditRelinkClone\" />\n"
10 " <verb verb-id=\"EditCloneSelectOriginal\" />\n"
11 " <verb verb-id=\"EditCloneOriginalPathLPE\" />\n"
12
13=== modified file 'src/object-set.h'
14--- src/object-set.h 2016-11-03 23:28:50 +0000
15+++ src/object-set.h 2016-11-04 14:19:19 +0000
16@@ -330,7 +330,19 @@
17 void deleteItems();
18 void duplicate(bool suppressDone = false, bool duplicateLayer = false);
19 void clone();
20- void unlink();
21+
22+ /**
23+ * @brief Unlink all directly selected clones.
24+ * @param skip_undo If this is set to true the call to DocumentUndo::done is omitted.
25+ * @return True if anything was unlinked, otherwise false.
26+ */
27+ bool unlink(const bool skip_undo = false);
28+ /**
29+ * @brief Recursively unlink any clones present in the current selection,
30+ * including clones which are used to clip other objects, groups of clones etc.
31+ * @return true if anything was unlinked, otherwise false.
32+ */
33+ bool unlinkRecursive(const bool skip_undo = false);
34 void relink();
35 void cloneOriginal();
36 void cloneOriginalPathLPE();
37@@ -376,7 +388,7 @@
38 void createBitmapCopy();
39 void setMask(bool apply_clip_path, bool apply_to_layer = false, bool skip_undo = false);
40 void editMask(bool clip);
41- void unsetMask(bool apply_clip_path);
42+ void unsetMask(const bool apply_clip_path, const bool skip_undo = false);
43 void setClipGroup();
44
45 // moves
46
47=== modified file 'src/selection-chemistry.cpp'
48--- src/selection-chemistry.cpp 2016-11-02 23:08:41 +0000
49+++ src/selection-chemistry.cpp 2016-11-04 14:19:19 +0000
50@@ -2653,12 +2653,12 @@
51 }
52
53
54-void ObjectSet::unlink()
55+bool ObjectSet::unlink(const bool skip_undo)
56 {
57 if (isEmpty()) {
58 if(desktop())
59 desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
60- return;
61+ return false;
62 }
63
64 // Get a copy of current selection.
65@@ -2715,8 +2715,56 @@
66 desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
67 }
68
69- DocumentUndo::done(document(), SP_VERB_EDIT_UNLINK_CLONE,
70- _("Unlink clone"));
71+ if (!skip_undo) {
72+ DocumentUndo::done(document(), SP_VERB_EDIT_UNLINK_CLONE,
73+ _("Unlink clone"));
74+ }
75+ return unlinked;
76+}
77+
78+bool ObjectSet::unlinkRecursive(const bool skip_undo) {
79+ if (isEmpty()){
80+ if (desktop())
81+ desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>clones</b> to unlink."));
82+ return false;
83+ }
84+ bool unlinked = false;
85+ ObjectSet tmp_set(document());
86+ std::vector<SPItem*> items_(items().begin(), items().end());
87+ for (auto& it:items_){
88+ if (SP_IS_GROUP(it)) {
89+ std::vector<SPObject*> c = it->childList(false);
90+ tmp_set.setList(c);
91+ unlinked = tmp_set.unlinkRecursive(true) || unlinked;
92+ }
93+ tmp_set.set(it);
94+ bool has_clip = false;
95+ bool has_mask = false;
96+ Inkscape::URIReference *clip = it->clip_ref;
97+ Inkscape::URIReference *mask = it->mask_ref;
98+ if ((NULL != clip) && (NULL != clip->getObject())) {
99+ tmp_set.unsetMask(true,true);
100+ has_clip = true;
101+ }
102+ if ((NULL != mask) && (NULL != mask->getObject())) {
103+ tmp_set.unsetMask(false,true);
104+ has_mask = true;
105+ }
106+ unlinked = tmp_set.unlink(true) || unlinked;
107+ if (has_mask)
108+ tmp_set.setMask(false,false,true);
109+ if (has_clip)
110+ tmp_set.setMask(true,false,true);
111+ }
112+ if (!unlinked) {
113+ if(desktop())
114+ desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No clones to unlink</b> in the selection."));
115+ }
116+ if (!skip_undo) {
117+ DocumentUndo::done(document(), SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE,
118+ _("Unlink clone recursively"));
119+ }
120+ return unlinked;
121 }
122
123 void ObjectSet::cloneOriginal()
124@@ -3960,7 +4008,7 @@
125 }
126 }
127
128-void ObjectSet::unsetMask(bool apply_clip_path) {
129+void ObjectSet::unsetMask(const bool apply_clip_path, const bool skip_undo) {
130 SPDocument *doc = document();
131 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
132
133@@ -4082,10 +4130,12 @@
134 // rebuild selection
135 addList(items_to_select);
136
137- if (apply_clip_path) {
138- DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_CLIPPATH, _("Release clipping path"));
139- } else {
140- DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_MASK, _("Release mask"));
141+ if (!skip_undo) {
142+ if (apply_clip_path) {
143+ DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_CLIPPATH, _("Release clipping path"));
144+ } else {
145+ DocumentUndo::done(doc, SP_VERB_OBJECT_UNSET_MASK, _("Release mask"));
146+ }
147 }
148 }
149
150
151=== modified file 'src/verbs.cpp'
152--- src/verbs.cpp 2016-11-02 22:42:43 +0000
153+++ src/verbs.cpp 2016-11-04 14:19:19 +0000
154@@ -1009,6 +1009,9 @@
155 case SP_VERB_EDIT_UNLINK_CLONE:
156 dt->selection->unlink();
157 break;
158+ case SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE:
159+ dt->selection->unlinkRecursive();
160+ break;
161 case SP_VERB_EDIT_RELINK_CLONE:
162 dt->selection->relink();
163 break;
164@@ -2530,6 +2533,8 @@
165 N_("Create a clone (a copy linked to the original) of selected object"), INKSCAPE_ICON("edit-clone")),
166 new EditVerb(SP_VERB_EDIT_UNLINK_CLONE, "EditUnlinkClone", N_("Unlin_k Clone"),
167 N_("Cut the selected clones' links to the originals, turning them into standalone objects"), INKSCAPE_ICON("edit-clone-unlink")),
168+ new EditVerb(SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE, "EditUnlinkCloneRecursive", N_("Unlink Clones _recursively"),
169+ N_("Unlink all clones in the selection, even if they are in groups."), INKSCAPE_ICON("edit-clone-unlink")),
170 new EditVerb(SP_VERB_EDIT_RELINK_CLONE, "EditRelinkClone", N_("Relink to Copied"),
171 N_("Relink the selected clones to the object currently on the clipboard"), NULL),
172 new EditVerb(SP_VERB_EDIT_CLONE_SELECT_ORIGINAL, "EditCloneSelectOriginal", N_("Select _Original"),
173
174=== modified file 'src/verbs.h'
175--- src/verbs.h 2016-10-08 06:16:53 +0000
176+++ src/verbs.h 2016-11-04 14:19:19 +0000
177@@ -83,6 +83,7 @@
178 SP_VERB_EDIT_DUPLICATE,
179 SP_VERB_EDIT_CLONE,
180 SP_VERB_EDIT_UNLINK_CLONE,
181+ SP_VERB_EDIT_UNLINK_CLONE_RECURSIVE,
182 SP_VERB_EDIT_RELINK_CLONE,
183 SP_VERB_EDIT_CLONE_SELECT_ORIGINAL,
184 SP_VERB_EDIT_CLONE_ORIGINAL_PATH_LPE,