source: osm/applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapAction.java@ 29778

Last change on this file since 29778 was 29778, checked in by akks, 11 years ago

[josm_plugins]: see #josm6355 - Move most of the other plugins to suitable menus, add Netbeans projects

File size: 20.8 KB
Line 
1package mergeoverlap;
2
3import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.combineTigerTags;
4import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.completeTagCollectionForEditing;
5import static org.openstreetmap.josm.gui.conflict.tags.TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.ArrayList;
11import java.util.Arrays;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.Iterator;
17import java.util.LinkedHashSet;
18import java.util.LinkedList;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import javax.swing.JOptionPane;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.actions.JosmAction;
27import org.openstreetmap.josm.actions.SplitWayAction;
28import org.openstreetmap.josm.actions.CombineWayAction.NodeGraph;
29import org.openstreetmap.josm.actions.SplitWayAction.SplitWayResult;
30import org.openstreetmap.josm.command.AddCommand;
31import org.openstreetmap.josm.command.ChangeCommand;
32import org.openstreetmap.josm.command.Command;
33import org.openstreetmap.josm.command.DeleteCommand;
34import org.openstreetmap.josm.command.SequenceCommand;
35import org.openstreetmap.josm.corrector.ReverseWayTagCorrector;
36import org.openstreetmap.josm.corrector.UserCancelException;
37import org.openstreetmap.josm.data.osm.Node;
38import org.openstreetmap.josm.data.osm.OsmPrimitive;
39import org.openstreetmap.josm.data.osm.Relation;
40import org.openstreetmap.josm.data.osm.RelationMember;
41import org.openstreetmap.josm.data.osm.TagCollection;
42import org.openstreetmap.josm.data.osm.Way;
43import org.openstreetmap.josm.gui.layer.OsmDataLayer;
44import org.openstreetmap.josm.tools.Pair;
45import org.openstreetmap.josm.tools.Shortcut;
46
47/**
48 * Merge overlapping part of ways.
49 */
50@SuppressWarnings("serial")
51public class MergeOverlapAction extends JosmAction {
52
53 public MergeOverlapAction() {
54 super(tr("Merge overlap"), "merge_overlap",
55 tr("Merge overlap of ways."),
56 Shortcut.registerShortcut("tools:mergeoverlap",tr("Tool: {0}", tr("Merge overlap")), KeyEvent.VK_O,
57 Shortcut.ALT_CTRL)
58 , true);
59 }
60
61 Map<Way, List<Relation>> relations = new HashMap<Way, List<Relation>>();
62 Map<Way, Way> oldWays = new HashMap<Way, Way>();
63 Map<Relation, Relation> newRelations = new HashMap<Relation, Relation>();
64 Set<Way> deletes = new HashSet<Way>();
65
66 /**
67 * The action button has been clicked
68 *
69 * @param e
70 * Action Event
71 */
72 @Override
73 public void actionPerformed(ActionEvent e) {
74
75 // List of selected ways
76 List<Way> ways = new ArrayList<Way>();
77 relations.clear();
78 newRelations.clear();
79
80 // For every selected way
81 for (OsmPrimitive osm : Main.main.getCurrentDataSet().getSelected()) {
82 if (osm instanceof Way && !osm.isDeleted()) {
83 Way way = (Way) osm;
84 ways.add(way);
85 List<Relation> rels = new ArrayList<Relation>();
86 for (Relation r : OsmPrimitive.getFilteredList(way
87 .getReferrers(), Relation.class)) {
88 rels.add(r);
89 }
90 relations.put(way, rels);
91 }
92 }
93
94 List<Way> sel = new ArrayList<Way>(ways);
95 Collection<Command> cmds = new LinkedList<Command>();
96
97 // *****
98 // split
99 // *****
100 for (Way way : ways) {
101 Set<Node> nodes = new HashSet<Node>();
102 for (Way opositWay : ways) {
103 if (way != opositWay) {
104 List<NodePos> nodesPos = new LinkedList<NodePos>();
105
106 int pos = 0;
107 for (Node node : way.getNodes()) {
108 int opositPos = 0;
109 for (Node opositNode : opositWay.getNodes()) {
110 if (node == opositNode) {
111 if (opositWay.isClosed()) {
112 opositPos %= opositWay.getNodesCount() - 1;
113 }
114 nodesPos.add(new NodePos(node, pos, opositPos));
115 break;
116 }
117 opositPos++;
118 }
119 pos++;
120 }
121
122 NodePos start = null;
123 NodePos end = null;
124 int increment = 0;
125
126 boolean hasFirst = false;
127 for (NodePos node : nodesPos) {
128 if (start == null) {
129 start = node;
130 } else {
131 if (end == null) {
132 if (follows(way, opositWay, start, node, 1)) {
133 end = node;
134 increment = +1;
135 } else if (follows(way, opositWay, start, node,
136 -1)) {
137 end = node;
138 increment = -1;
139 } else {
140 start = node;
141 end = null;
142 }
143 } else {
144 if (follows(way, opositWay, end, node,
145 increment)) {
146 end = node;
147 } else {
148 hasFirst = addNodes(start, end, way, nodes,
149 hasFirst);
150 start = node;
151 end = null;
152 }
153 }
154 }
155 }
156
157 if (start != null && end != null) {
158 hasFirst = addNodes(start, end, way, nodes, hasFirst);
159 start = null;
160 end = null;
161 }
162 }
163 }
164 if (!nodes.isEmpty() && !way.isClosed() || nodes.size() >= 2) {
165 List<List<Node>> wayChunks = SplitWayAction.buildSplitChunks(
166 way, new ArrayList<Node>(nodes));
167 SplitWayResult result = splitWay(getEditLayer(), way, wayChunks);
168
169 cmds.add(result.getCommand());
170 sel.remove(way);
171 sel.add(result.getOriginalWay());
172 sel.addAll(result.getNewWays());
173 List<Relation> rels = relations.remove(way);
174 relations.put(result.getOriginalWay(), rels);
175 for (Way w : result.getNewWays()) {
176 relations.put(w, rels);
177 }
178 }
179 }
180
181 // *****
182 // merge
183 // *****
184 ways = new ArrayList<Way>(sel);
185 while (!ways.isEmpty()) {
186 Way way = ways.get(0);
187 List<Way> combine = new ArrayList<Way>();
188 combine.add(way);
189 for (Way opositWay : ways) {
190 if (way != opositWay
191 && way.getNodesCount() == opositWay.getNodesCount()) {
192 boolean equals1 = true;
193 for (int i = 0; i < way.getNodesCount(); i++) {
194 if (way.getNode(i) != opositWay.getNode(i)) {
195 equals1 = false;
196 break;
197 }
198 }
199 boolean equals2 = true;
200 for (int i = 0; i < way.getNodesCount(); i++) {
201 if (way.getNode(i) != opositWay.getNode(way
202 .getNodesCount()
203 - i - 1)) {
204 equals2 = false;
205 break;
206 }
207 }
208 if (equals1 || equals2) {
209 combine.add(opositWay);
210 }
211 }
212 }
213 ways.removeAll(combine);
214 if (combine.size() > 1) {
215 sel.removeAll(combine);
216 // combine
217 Pair<Way, List<Command>> combineResult;
218 try {
219 combineResult = combineWaysWorker(combine);
220 } catch (UserCancelException e1) {
221 return;
222 }
223 sel.add(combineResult.a);
224 cmds.addAll(combineResult.b);
225 }
226 }
227
228 for (Relation old : newRelations.keySet()) {
229 cmds.add(new ChangeCommand(old, newRelations.get(old)));
230 }
231
232 List<Way> del = new LinkedList<Way>();
233 for (Way w : deletes) {
234 if (!w.isDeleted()) {
235 del.add(w);
236 }
237 }
238 if (!del.isEmpty()) {
239 cmds.add(new DeleteCommand(del));
240 }
241
242 // Commit
243 Main.main.undoRedo.add(new SequenceCommand(
244 tr("Merge Overlap (combine)"), cmds));
245 getCurrentDataSet().setSelected(sel);
246 Main.map.repaint();
247
248 relations.clear();
249 newRelations.clear();
250 oldWays.clear();
251 }
252
253 private class NodePos {
254 Node node;
255 int pos;
256 int opositPos;
257
258 NodePos(Node n, int p, int op) {
259 node = n;
260 pos = p;
261 opositPos = op;
262 }
263
264 @Override
265 public String toString() {
266 return "NodePos: " + pos + ", " + opositPos + ", " + node;
267 }
268 }
269
270 private boolean addNodes(NodePos start, NodePos end, Way way,
271 Set<Node> nodes, boolean hasFirst) {
272 if (way.isClosed()
273 || (start.node != way.getNode(0) && start.node != way
274 .getNode(way.getNodesCount() - 1))) {
275 hasFirst = hasFirst || start.node == way.getNode(0);
276 nodes.add(start.node);
277 }
278 if (way.isClosed()
279 || (end.node != way.getNode(0) && end.node != way.getNode(way
280 .getNodesCount() - 1))) {
281 if (hasFirst && (end.node == way.getNode(way.getNodesCount() - 1))) {
282 nodes.remove(way.getNode(0));
283 } else {
284 nodes.add(end.node);
285 }
286 }
287 return hasFirst;
288 }
289
290 private boolean follows(Way way1, Way way2, NodePos np1, NodePos np2,
291 int incr) {
292 if (way2.isClosed() && incr == 1
293 && np1.opositPos == way2.getNodesCount() - 2) {
294 return np2.pos == np1.pos + 1 && np2.opositPos == 0;
295 } else if (way2.isClosed() && incr == 1 && np1.opositPos == 0) {
296 return np2.pos == np1.pos && np2.opositPos == 0
297 || np2.pos == np1.pos + 1 && np2.opositPos == 1;
298 } else if (way2.isClosed() && incr == -1 && np1.opositPos == 0) {
299 return np2.pos == np1.pos && np2.opositPos == 0
300 || np2.pos == np1.pos + 1
301 && np2.opositPos == way2.getNodesCount() - 2;
302 } else {
303 return np2.pos == np1.pos + 1
304 && np2.opositPos == np1.opositPos + incr;
305 }
306 }
307
308 /**
309 * Splits a way
310 *
311 * @param layer
312 * @param way
313 * @param wayChunks
314 * @return
315 */
316 private SplitWayResult splitWay(OsmDataLayer layer, Way way,
317 List<List<Node>> wayChunks) {
318 // build a list of commands, and also a new selection list
319 Collection<Command> commandList = new ArrayList<Command>(wayChunks
320 .size());
321
322 Iterator<List<Node>> chunkIt = wayChunks.iterator();
323 Collection<String> nowarnroles = Main.pref.getCollection(
324 "way.split.roles.nowarn", Arrays.asList(new String[] { "outer",
325 "inner", "forward", "backward" }));
326
327 // First, change the original way
328 Way changedWay = new Way(way);
329 oldWays.put(changedWay, way);
330 changedWay.setNodes(chunkIt.next());
331 commandList.add(new ChangeCommand(way, changedWay));
332
333 List<Way> newWays = new ArrayList<Way>();
334 // Second, create new ways
335 while (chunkIt.hasNext()) {
336 Way wayToAdd = new Way();
337 wayToAdd.setKeys(way.getKeys());
338 newWays.add(wayToAdd);
339 wayToAdd.setNodes(chunkIt.next());
340 commandList.add(new AddCommand(layer, wayToAdd));
341 }
342 boolean warnmerole = false;
343 boolean warnme = false;
344 // now copy all relations to new way also
345
346 for (Relation r : getParentRelations(way)) {
347 if (!r.isUsable()) {
348 continue;
349 }
350 Relation c = null;
351 String type = r.get("type");
352 if (type == null) {
353 type = "";
354 }
355
356 int i_c = 0, i_r = 0;
357 List<RelationMember> relationMembers = r.getMembers();
358 for (RelationMember rm : relationMembers) {
359 if (rm.isWay() && rm.getMember() == way) {
360 boolean insert = true;
361 if ("restriction".equals(type)) {
362 /*
363 * this code assumes the restriction is correct. No real
364 * error checking done
365 */
366 String role = rm.getRole();
367 if ("from".equals(role) || "to".equals(role)) {
368 OsmPrimitive via = null;
369 for (RelationMember rmv : r.getMembers()) {
370 if ("via".equals(rmv.getRole())) {
371 via = rmv.getMember();
372 }
373 }
374 List<Node> nodes = new ArrayList<Node>();
375 if (via != null) {
376 if (via instanceof Node) {
377 nodes.add((Node) via);
378 } else if (via instanceof Way) {
379 nodes.add(((Way) via).lastNode());
380 nodes.add(((Way) via).firstNode());
381 }
382 }
383 Way res = null;
384 for (Node n : nodes) {
385 if (changedWay.isFirstLastNode(n)) {
386 res = way;
387 }
388 }
389 if (res == null) {
390 for (Way wayToAdd : newWays) {
391 for (Node n : nodes) {
392 if (wayToAdd.isFirstLastNode(n)) {
393 res = wayToAdd;
394 }
395 }
396 }
397 if (res != null) {
398 if (c == null) {
399 c = getNew(r);
400 }
401 c.addMember(new RelationMember(role, res));
402 c.removeMembersFor(way);
403 insert = false;
404 }
405 } else {
406 insert = false;
407 }
408 } else if (!"via".equals(role)) {
409 warnme = true;
410 }
411 } else if (!("route".equals(type))
412 && !("multipolygon".equals(type))) {
413 warnme = true;
414 }
415 if (c == null) {
416 c = getNew(r);
417 }
418
419 if (insert) {
420 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) {
421 warnmerole = true;
422 }
423
424 Boolean backwards = null;
425 int k = 1;
426 while (i_r - k >= 0 || i_r + k < relationMembers.size()) {
427 if ((i_r - k >= 0)
428 && relationMembers.get(i_r - k).isWay()) {
429 Way w = relationMembers.get(i_r - k).getWay();
430 if ((w.lastNode() == way.firstNode())
431 || w.firstNode() == way.firstNode()) {
432 backwards = false;
433 } else if ((w.firstNode() == way.lastNode())
434 || w.lastNode() == way.lastNode()) {
435 backwards = true;
436 }
437 break;
438 }
439 if ((i_r + k < relationMembers.size())
440 && relationMembers.get(i_r + k).isWay()) {
441 Way w = relationMembers.get(i_r + k).getWay();
442 if ((w.lastNode() == way.firstNode())
443 || w.firstNode() == way.firstNode()) {
444 backwards = true;
445 } else if ((w.firstNode() == way.lastNode())
446 || w.lastNode() == way.lastNode()) {
447 backwards = false;
448 }
449 break;
450 }
451 k++;
452 }
453
454 int j = i_c;
455 for (Way wayToAdd : newWays) {
456 RelationMember em = new RelationMember(
457 rm.getRole(), wayToAdd);
458 j++;
459 if ((backwards != null) && backwards) {
460 c.addMember(i_c, em);
461 } else {
462 c.addMember(j, em);
463 }
464 }
465 i_c = j;
466 }
467 }
468 i_c++;
469 i_r++;
470 }
471
472 if (c != null) {
473 // commandList.add(new ChangeCommand(layer, r, c));
474 newRelations.put(r, c);
475 }
476 }
477 if (warnmerole) {
478 JOptionPane
479 .showMessageDialog(
480 Main.parent,
481 tr("<html>A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
482 tr("Warning"), JOptionPane.WARNING_MESSAGE);
483 } else if (warnme) {
484 JOptionPane
485 .showMessageDialog(
486 Main.parent,
487 tr("<html>A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
488 tr("Warning"), JOptionPane.WARNING_MESSAGE);
489 }
490
491 return new SplitWayResult(
492 new SequenceCommand(tr("Split way"), commandList), null,
493 changedWay, newWays);
494 }
495
496 /**
497 * @param ways
498 * @return null if ways cannot be combined. Otherwise returns the combined
499 * ways and the commands to combine
500 * @throws UserCancelException
501 */
502 private Pair<Way, List<Command>> combineWaysWorker(Collection<Way> ways)
503 throws UserCancelException {
504
505 // prepare and clean the list of ways to combine
506 if (ways == null || ways.isEmpty())
507 return null;
508 ways.remove(null); // just in case - remove all null ways from the
509 // collection
510
511 // remove duplicates, preserving order
512 ways = new LinkedHashSet<Way>(ways);
513
514 // try to build a new way which includes all the combined ways
515 NodeGraph graph = NodeGraph.createUndirectedGraphFromNodeWays(ways);
516 List<Node> path = graph.buildSpanningPath();
517
518 // check whether any ways have been reversed in the process
519 // and build the collection of tags used by the ways to combine
520 TagCollection wayTags = TagCollection.unionOfAllPrimitives(ways);
521
522 List<Way> reversedWays = new LinkedList<Way>();
523 List<Way> unreversedWays = new LinkedList<Way>();
524 for (Way w : ways) {
525 if ((path.indexOf(w.getNode(0)) + 1) == path.lastIndexOf(w
526 .getNode(1))) {
527 unreversedWays.add(w);
528 } else {
529 reversedWays.add(w);
530 }
531 }
532 // reverse path if all ways have been reversed
533 if (unreversedWays.isEmpty()) {
534 Collections.reverse(path);
535 unreversedWays = reversedWays;
536 reversedWays = null;
537 }
538 if ((reversedWays != null) && !reversedWays.isEmpty()) {
539 // filter out ways that have no direction-dependent tags
540 unreversedWays = ReverseWayTagCorrector
541 .irreversibleWays(unreversedWays);
542 reversedWays = ReverseWayTagCorrector
543 .irreversibleWays(reversedWays);
544 // reverse path if there are more reversed than unreversed ways with
545 // direction-dependent tags
546 if (reversedWays.size() > unreversedWays.size()) {
547 Collections.reverse(path);
548 List<Way> tempWays = unreversedWays;
549 unreversedWays = reversedWays;
550 reversedWays = tempWays;
551 }
552 // if there are still reversed ways with direction-dependent tags,
553 // reverse their tags
554 if (!reversedWays.isEmpty()) {
555 List<Way> unreversedTagWays = new ArrayList<Way>(ways);
556 unreversedTagWays.removeAll(reversedWays);
557 ReverseWayTagCorrector reverseWayTagCorrector = new ReverseWayTagCorrector();
558 List<Way> reversedTagWays = new ArrayList<Way>();
559 Collection<Command> changePropertyCommands = null;
560 for (Way w : reversedWays) {
561 Way wnew = new Way(w);
562 reversedTagWays.add(wnew);
563 changePropertyCommands = reverseWayTagCorrector.execute(w,
564 wnew);
565 }
566 if ((changePropertyCommands != null)
567 && !changePropertyCommands.isEmpty()) {
568 for (Command c : changePropertyCommands) {
569 c.executeCommand();
570 }
571 }
572 wayTags = TagCollection.unionOfAllPrimitives(reversedTagWays);
573 wayTags.add(TagCollection
574 .unionOfAllPrimitives(unreversedTagWays));
575 }
576 }
577
578 // create the new way and apply the new node list
579 Way targetWay = getTargetWay(ways);
580 Way modifiedTargetWay = new Way(targetWay);
581 modifiedTargetWay.setNodes(path);
582
583 TagCollection completeWayTags = new TagCollection(wayTags);
584 combineTigerTags(completeWayTags);
585 normalizeTagCollectionBeforeEditing(completeWayTags, ways);
586 TagCollection tagsToEdit = new TagCollection(completeWayTags);
587 completeTagCollectionForEditing(tagsToEdit);
588
589 MyCombinePrimitiveResolverDialog dialog = MyCombinePrimitiveResolverDialog
590 .getInstance();
591 dialog.getTagConflictResolverModel().populate(tagsToEdit,
592 completeWayTags.getKeysWithMultipleValues());
593 dialog.setTargetPrimitive(targetWay);
594 Set<Relation> parentRelations = getParentRelations(ways);
595 dialog.getRelationMemberConflictResolverModel().populate(
596 parentRelations, ways, oldWays);
597 dialog.prepareDefaultDecisions();
598
599 // resolve tag conflicts if necessary
600 if (askForMergeTag(ways) || duplicateParentRelations(ways)) {
601 dialog.setVisible(true);
602 if (dialog.isCancelled())
603 throw new UserCancelException();
604 }
605
606 LinkedList<Command> cmds = new LinkedList<Command>();
607 deletes.addAll(ways);
608 deletes.remove(targetWay);
609
610 cmds.add(new ChangeCommand(targetWay, modifiedTargetWay));
611 cmds.addAll(dialog.buildWayResolutionCommands());
612 dialog.buildRelationCorrespondance(newRelations, oldWays);
613
614 return new Pair<Way, List<Command>>(targetWay, cmds);
615 }
616
617 private static Way getTargetWay(Collection<Way> combinedWays) {
618 // init with an arbitrary way
619 Way targetWay = combinedWays.iterator().next();
620
621 // look for the first way already existing on
622 // the server
623 for (Way w : combinedWays) {
624 targetWay = w;
625 if (!w.isNew()) {
626 break;
627 }
628 }
629 return targetWay;
630 }
631
632 /**
633 * @return has tag to be merged (=> ask)
634 */
635 private static boolean askForMergeTag(Collection<Way> ways) {
636 for (Way way : ways) {
637 for (Way oposite : ways) {
638 for (String key : way.getKeys().keySet()) {
639 if (!"source".equals(key) && oposite.hasKey(key)
640 && !way.get(key).equals(oposite.get(key))) {
641 return true;
642 }
643 }
644 }
645 }
646 return false;
647 }
648
649 /**
650 * @return has duplicate parent relation
651 */
652 private boolean duplicateParentRelations(Collection<Way> ways) {
653 Set<Relation> relations = new HashSet<Relation>();
654 for (Way w : ways) {
655 List<Relation> rs = getParentRelations(w);
656 for (Relation r : rs) {
657 if (relations.contains(r)) {
658 return true;
659 }
660 }
661 relations.addAll(rs);
662 }
663 return false;
664 }
665
666 /**
667 * Replies the set of referring relations
668 *
669 * @return the set of referring relations
670 */
671 private List<Relation> getParentRelations(Way way) {
672 List<Relation> rels = new ArrayList<Relation>();
673 for (Relation r : relations.get(way)) {
674 if (newRelations.containsKey(r)) {
675 rels.add(newRelations.get(r));
676 } else {
677 rels.add(r);
678 }
679 }
680 return rels;
681 }
682
683 private Relation getNew(Relation r) {
684 return getNew(r, newRelations);
685 }
686
687 public static Relation getNew(Relation r,
688 Map<Relation, Relation> newRelations) {
689 if (newRelations.containsValue(r)) {
690 return r;
691 } else {
692 Relation c = new Relation(r);
693 newRelations.put(r, c);
694 return c;
695 }
696 }
697
698 private Way getOld(Way r) {
699 return getOld(r, oldWays);
700 }
701
702 public static Way getOld(Way w, Map<Way, Way> oldWays) {
703 if (oldWays.containsKey(w)) {
704 return oldWays.get(w);
705 } else {
706 return w;
707 }
708 }
709
710 /**
711 * Replies the set of referring relations
712 *
713 * @return the set of referring relations
714 */
715 private Set<Relation> getParentRelations(Collection<Way> ways) {
716 HashSet<Relation> ret = new HashSet<Relation>();
717 for (Way w : ways) {
718 ret.addAll(getParentRelations(w));
719 }
720 return ret;
721 }
722
723 /** Enable this action only if something is selected */
724 @Override
725 protected void updateEnabledState() {
726 if (getCurrentDataSet() == null) {
727 setEnabled(false);
728 } else {
729 updateEnabledState(getCurrentDataSet().getSelected());
730 }
731 }
732
733 /** Enable this action only if something is selected */
734 @Override
735 protected void updateEnabledState(
736 Collection<? extends OsmPrimitive> selection) {
737 if (selection == null) {
738 setEnabled(false);
739 return;
740 }
741 for (OsmPrimitive primitive : selection) {
742 if (!(primitive instanceof Way) || primitive.isDeleted()) {
743 setEnabled(false);
744 return;
745 }
746 }
747 setEnabled(selection.size() >= 2);
748 }
749}
Note: See TracBrowser for help on using the repository browser.