source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/SelectionListDialog.java@ 12495

Last change on this file since 12495 was 12279, checked in by Don-vip, 7 years ago

sonar - squid:S3878 - Arrays should not be created for varargs parameters

  • Property svn:eol-style set to native
File size: 33.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Component;
8import java.awt.GraphicsEnvironment;
9import java.awt.Rectangle;
10import java.awt.datatransfer.Transferable;
11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.Comparator;
20import java.util.HashSet;
21import java.util.Iterator;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Set;
25
26import javax.swing.AbstractAction;
27import javax.swing.AbstractListModel;
28import javax.swing.DefaultListSelectionModel;
29import javax.swing.JComponent;
30import javax.swing.JList;
31import javax.swing.JMenuItem;
32import javax.swing.JPopupMenu;
33import javax.swing.ListSelectionModel;
34import javax.swing.TransferHandler;
35import javax.swing.event.ListDataEvent;
36import javax.swing.event.ListDataListener;
37import javax.swing.event.ListSelectionEvent;
38import javax.swing.event.ListSelectionListener;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.actions.AbstractSelectAction;
42import org.openstreetmap.josm.actions.AutoScaleAction;
43import org.openstreetmap.josm.actions.relation.DownloadSelectedIncompleteMembersAction;
44import org.openstreetmap.josm.actions.relation.EditRelationAction;
45import org.openstreetmap.josm.actions.relation.SelectInRelationListAction;
46import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting;
47import org.openstreetmap.josm.data.SelectionChangedListener;
48import org.openstreetmap.josm.data.coor.LatLon;
49import org.openstreetmap.josm.data.osm.DataSet;
50import org.openstreetmap.josm.data.osm.Node;
51import org.openstreetmap.josm.data.osm.OsmPrimitive;
52import org.openstreetmap.josm.data.osm.OsmPrimitiveComparator;
53import org.openstreetmap.josm.data.osm.Relation;
54import org.openstreetmap.josm.data.osm.Way;
55import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
56import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
57import org.openstreetmap.josm.data.osm.event.DataSetListener;
58import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
59import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
60import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
61import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
62import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
63import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
64import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
65import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
66import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
67import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
68import org.openstreetmap.josm.gui.DefaultNameFormatter;
69import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
70import org.openstreetmap.josm.gui.PopupMenuHandler;
71import org.openstreetmap.josm.gui.SideButton;
72import org.openstreetmap.josm.gui.datatransfer.PrimitiveTransferable;
73import org.openstreetmap.josm.gui.datatransfer.data.PrimitiveTransferData;
74import org.openstreetmap.josm.gui.history.HistoryBrowserDialogManager;
75import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
76import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
77import org.openstreetmap.josm.gui.layer.OsmDataLayer;
78import org.openstreetmap.josm.gui.util.GuiHelper;
79import org.openstreetmap.josm.gui.util.HighlightHelper;
80import org.openstreetmap.josm.gui.widgets.ListPopupMenu;
81import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
82import org.openstreetmap.josm.tools.ImageProvider;
83import org.openstreetmap.josm.tools.InputMapUtils;
84import org.openstreetmap.josm.tools.Shortcut;
85import org.openstreetmap.josm.tools.SubclassFilteredCollection;
86import org.openstreetmap.josm.tools.Utils;
87
88/**
89 * A small tool dialog for displaying the current selection.
90 * @since 8
91 */
92public class SelectionListDialog extends ToggleDialog {
93 private JList<OsmPrimitive> lstPrimitives;
94 private final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
95 private final SelectionListModel model = new SelectionListModel(selectionModel);
96
97 private final SelectAction actSelect = new SelectAction();
98 private final SearchAction actSearch = new SearchAction();
99 private final ShowHistoryAction actShowHistory = new ShowHistoryAction();
100 private final ZoomToJOSMSelectionAction actZoomToJOSMSelection = new ZoomToJOSMSelectionAction();
101 private final ZoomToListSelection actZoomToListSelection = new ZoomToListSelection();
102 private final SelectInRelationListAction actSetRelationSelection = new SelectInRelationListAction();
103 private final EditRelationAction actEditRelationSelection = new EditRelationAction();
104 private final DownloadSelectedIncompleteMembersAction actDownloadSelIncompleteMembers = new DownloadSelectedIncompleteMembersAction();
105
106 /** the popup menu and its handler */
107 private final ListPopupMenu popupMenu;
108 private final transient PopupMenuHandler popupMenuHandler;
109
110 /**
111 * Builds the content panel for this dialog
112 */
113 protected void buildContentPanel() {
114 lstPrimitives = new JList<>(model);
115 lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
116 lstPrimitives.setSelectionModel(selectionModel);
117 lstPrimitives.setCellRenderer(new OsmPrimitivRenderer());
118 lstPrimitives.setTransferHandler(new SelectionTransferHandler());
119 if (!GraphicsEnvironment.isHeadless()) {
120 lstPrimitives.setDragEnabled(true);
121 }
122
123 lstPrimitives.getSelectionModel().addListSelectionListener(actSelect);
124 lstPrimitives.getSelectionModel().addListSelectionListener(actShowHistory);
125
126 // the select action
127 final SideButton selectButton = new SideButton(actSelect);
128 selectButton.createArrow(e -> SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory()));
129
130 // the search button
131 final SideButton searchButton = new SideButton(actSearch);
132 searchButton.createArrow(e -> SearchPopupMenu.launch(searchButton));
133
134 createLayout(lstPrimitives, true, Arrays.asList(
135 selectButton, searchButton, new SideButton(actShowHistory)
136 ));
137 }
138
139 /**
140 * Constructs a new {@code SelectionListDialog}.
141 */
142 public SelectionListDialog() {
143 super(tr("Selection"), "selectionlist", tr("Open a selection list window."),
144 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}",
145 tr("Current Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT),
146 150, // default height
147 true // default is "show dialog"
148 );
149
150 buildContentPanel();
151 model.addListDataListener(new TitleUpdater());
152 model.addListDataListener(actZoomToJOSMSelection);
153
154 popupMenu = new ListPopupMenu(lstPrimitives);
155 popupMenuHandler = setupPopupMenuHandler();
156
157 lstPrimitives.addListSelectionListener(e -> {
158 actZoomToListSelection.valueChanged(e);
159 popupMenuHandler.setPrimitives(model.getSelected());
160 });
161
162 lstPrimitives.addMouseListener(new MouseEventHandler());
163
164 InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection);
165 }
166
167 @Override
168 public void showNotify() {
169 SelectionEventManager.getInstance().addSelectionListener(actShowHistory, FireMode.IN_EDT_CONSOLIDATED);
170 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED);
171 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT);
172 Main.getLayerManager().addActiveLayerChangeListener(actSearch);
173 // editLayerChanged also gets the selection history of the level. Listener calls setJOSMSelection when fired.
174 Main.getLayerManager().addAndFireActiveLayerChangeListener(model);
175 actSearch.updateEnabledState();
176 }
177
178 @Override
179 public void hideNotify() {
180 Main.getLayerManager().removeActiveLayerChangeListener(actSearch);
181 Main.getLayerManager().removeActiveLayerChangeListener(model);
182 SelectionEventManager.getInstance().removeSelectionListener(actShowHistory);
183 SelectionEventManager.getInstance().removeSelectionListener(model);
184 DatasetEventManager.getInstance().removeDatasetListener(model);
185 }
186
187 /**
188 * Responds to double clicks on the list of selected objects and launches the popup menu
189 */
190 class MouseEventHandler extends PopupMenuLauncher {
191 private final HighlightHelper helper = new HighlightHelper();
192 private final boolean highlightEnabled = Main.pref.getBoolean("draw.target-highlight", true);
193
194 MouseEventHandler() {
195 super(popupMenu);
196 }
197
198 @Override
199 public void mouseClicked(MouseEvent e) {
200 int idx = lstPrimitives.locationToIndex(e.getPoint());
201 if (idx < 0) return;
202 if (isDoubleClick(e)) {
203 OsmDataLayer layer = Main.getLayerManager().getEditLayer();
204 if (layer == null) return;
205 OsmPrimitive osm = model.getElementAt(idx);
206 Collection<OsmPrimitive> sel = layer.data.getSelected();
207 if (sel.size() != 1 || !sel.iterator().next().equals(osm)) {
208 // Select primitive if it's not the whole current selection
209 layer.data.setSelected(Collections.singleton(osm));
210 } else if (osm instanceof Relation) {
211 // else open relation editor if applicable
212 actEditRelationSelection.actionPerformed(null);
213 }
214 } else if (highlightEnabled && Main.isDisplayingMapView() && helper.highlightOnly(model.getElementAt(idx))) {
215 Main.map.mapView.repaint();
216 }
217 }
218
219 @Override
220 public void mouseExited(MouseEvent me) {
221 if (highlightEnabled) helper.clear();
222 super.mouseExited(me);
223 }
224 }
225
226 private PopupMenuHandler setupPopupMenuHandler() {
227 PopupMenuHandler handler = new PopupMenuHandler(popupMenu);
228 handler.addAction(actZoomToJOSMSelection);
229 handler.addAction(actZoomToListSelection);
230 handler.addSeparator();
231 handler.addAction(actSetRelationSelection);
232 handler.addAction(actEditRelationSelection);
233 handler.addSeparator();
234 handler.addAction(actDownloadSelIncompleteMembers);
235 return handler;
236 }
237
238 /**
239 * Replies the popup menu handler.
240 * @return The popup menu handler
241 */
242 public PopupMenuHandler getPopupMenuHandler() {
243 return popupMenuHandler;
244 }
245
246 /**
247 * Replies the selected OSM primitives.
248 * @return The selected OSM primitives
249 */
250 public Collection<OsmPrimitive> getSelectedPrimitives() {
251 return model.getSelected();
252 }
253
254 /**
255 * Updates the dialog title with a summary of the current JOSM selection
256 */
257 class TitleUpdater implements ListDataListener {
258 protected void updateTitle() {
259 setTitle(model.getJOSMSelectionSummary());
260 }
261
262 @Override
263 public void contentsChanged(ListDataEvent e) {
264 updateTitle();
265 }
266
267 @Override
268 public void intervalAdded(ListDataEvent e) {
269 updateTitle();
270 }
271
272 @Override
273 public void intervalRemoved(ListDataEvent e) {
274 updateTitle();
275 }
276 }
277
278 /**
279 * Launches the search dialog
280 */
281 static class SearchAction extends AbstractAction implements ActiveLayerChangeListener {
282 /**
283 * Constructs a new {@code SearchAction}.
284 */
285 SearchAction() {
286 putValue(NAME, tr("Search"));
287 putValue(SHORT_DESCRIPTION, tr("Search for objects"));
288 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true);
289 updateEnabledState();
290 }
291
292 @Override
293 public void actionPerformed(ActionEvent e) {
294 if (!isEnabled()) return;
295 org.openstreetmap.josm.actions.search.SearchAction.search();
296 }
297
298 protected void updateEnabledState() {
299 setEnabled(Main.getLayerManager().getEditLayer() != null);
300 }
301
302 @Override
303 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
304 updateEnabledState();
305 }
306 }
307
308 /**
309 * Sets the current JOSM selection to the OSM primitives selected in the list
310 * of this dialog
311 */
312 class SelectAction extends AbstractSelectAction implements ListSelectionListener {
313 /**
314 * Constructs a new {@code SelectAction}.
315 */
316 SelectAction() {
317 updateEnabledState();
318 }
319
320 @Override
321 public void actionPerformed(ActionEvent e) {
322 Collection<OsmPrimitive> sel = model.getSelected();
323 if (sel.isEmpty()) return;
324 OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
325 if (editLayer == null) return;
326 editLayer.data.setSelected(sel);
327 model.selectionModel.setSelectionInterval(0, sel.size()-1);
328 }
329
330 protected void updateEnabledState() {
331 setEnabled(!model.isSelectionEmpty());
332 }
333
334 @Override
335 public void valueChanged(ListSelectionEvent e) {
336 updateEnabledState();
337 }
338 }
339
340 /**
341 * The action for showing history information of the current history item.
342 */
343 class ShowHistoryAction extends AbstractAction implements ListSelectionListener, SelectionChangedListener {
344 /**
345 * Constructs a new {@code ShowHistoryAction}.
346 */
347 ShowHistoryAction() {
348 putValue(NAME, tr("History"));
349 putValue(SHORT_DESCRIPTION, tr("Display the history of the selected objects."));
350 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this, true);
351 updateEnabledState(model.getSize());
352 }
353
354 @Override
355 public void actionPerformed(ActionEvent e) {
356 Collection<OsmPrimitive> sel = model.getSelected();
357 if (sel.isEmpty() && model.getSize() != 1) {
358 return;
359 } else if (sel.isEmpty()) {
360 sel = Collections.singleton(model.getElementAt(0));
361 }
362 HistoryBrowserDialogManager.getInstance().showHistory(sel);
363 }
364
365 protected void updateEnabledState(int osmSelectionSize) {
366 // See #10830 - allow to click on history button is a single object is selected, even if not selected again in the list
367 setEnabled(!model.isSelectionEmpty() || osmSelectionSize == 1);
368 }
369
370 @Override
371 public void valueChanged(ListSelectionEvent e) {
372 updateEnabledState(model.getSize());
373 }
374
375 @Override
376 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
377 updateEnabledState(newSelection.size());
378 }
379 }
380
381 /**
382 * The action for zooming to the primitives in the current JOSM selection
383 *
384 */
385 class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener {
386
387 ZoomToJOSMSelectionAction() {
388 putValue(NAME, tr("Zoom to selection"));
389 putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
390 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true);
391 updateEnabledState();
392 }
393
394 @Override
395 public void actionPerformed(ActionEvent e) {
396 AutoScaleAction.autoScale("selection");
397 }
398
399 public void updateEnabledState() {
400 setEnabled(model.getSize() > 0);
401 }
402
403 @Override
404 public void contentsChanged(ListDataEvent e) {
405 updateEnabledState();
406 }
407
408 @Override
409 public void intervalAdded(ListDataEvent e) {
410 updateEnabledState();
411 }
412
413 @Override
414 public void intervalRemoved(ListDataEvent e) {
415 updateEnabledState();
416 }
417 }
418
419 /**
420 * The action for zooming to the primitives which are currently selected in
421 * the list displaying the JOSM selection
422 *
423 */
424 class ZoomToListSelection extends AbstractAction implements ListSelectionListener {
425 /**
426 * Constructs a new {@code ZoomToListSelection}.
427 */
428 ZoomToListSelection() {
429 putValue(NAME, tr("Zoom to selected element(s)"));
430 putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)"));
431 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true);
432 updateEnabledState();
433 }
434
435 @Override
436 public void actionPerformed(ActionEvent e) {
437 BoundingXYVisitor box = new BoundingXYVisitor();
438 Collection<OsmPrimitive> sel = model.getSelected();
439 if (sel.isEmpty()) return;
440 box.computeBoundingBox(sel);
441 if (box.getBounds() == null)
442 return;
443 box.enlargeBoundingBox();
444 Main.map.mapView.zoomTo(box);
445 }
446
447 protected void updateEnabledState() {
448 setEnabled(!model.isSelectionEmpty());
449 }
450
451 @Override
452 public void valueChanged(ListSelectionEvent e) {
453 updateEnabledState();
454 }
455 }
456
457 /**
458 * The list model for the list of OSM primitives in the current JOSM selection.
459 *
460 * The model also maintains a history of the last {@link SelectionListModel#SELECTION_HISTORY_SIZE}
461 * JOSM selection.
462 *
463 */
464 private static class SelectionListModel extends AbstractListModel<OsmPrimitive>
465 implements ActiveLayerChangeListener, SelectionChangedListener, DataSetListener {
466
467 private static final int SELECTION_HISTORY_SIZE = 10;
468
469 // Variable to store history from currentDataSet()
470 private LinkedList<Collection<? extends OsmPrimitive>> history;
471 private final transient List<OsmPrimitive> selection = new ArrayList<>();
472 private final DefaultListSelectionModel selectionModel;
473
474 /**
475 * Constructor
476 * @param selectionModel the selection model used in the list
477 */
478 SelectionListModel(DefaultListSelectionModel selectionModel) {
479 this.selectionModel = selectionModel;
480 }
481
482 /**
483 * Replies a summary of the current JOSM selection
484 *
485 * @return a summary of the current JOSM selection
486 */
487 public synchronized String getJOSMSelectionSummary() {
488 if (selection.isEmpty()) return tr("Selection");
489 int numNodes = 0;
490 int numWays = 0;
491 int numRelations = 0;
492 for (OsmPrimitive p: selection) {
493 switch(p.getType()) {
494 case NODE: numNodes++; break;
495 case WAY: numWays++; break;
496 case RELATION: numRelations++; break;
497 default: throw new AssertionError();
498 }
499 }
500 return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes);
501 }
502
503 /**
504 * Remembers a JOSM selection the history of JOSM selections
505 *
506 * @param selection the JOSM selection. Ignored if null or empty.
507 */
508 public void remember(Collection<? extends OsmPrimitive> selection) {
509 if (selection == null) return;
510 if (selection.isEmpty()) return;
511 if (history == null) return;
512 if (history.isEmpty()) {
513 history.add(selection);
514 return;
515 }
516 if (history.getFirst().equals(selection)) return;
517 history.addFirst(selection);
518 for (int i = 1; i < history.size(); ++i) {
519 if (history.get(i).equals(selection)) {
520 history.remove(i);
521 break;
522 }
523 }
524 int maxsize = Main.pref.getInteger("select.history-size", SELECTION_HISTORY_SIZE);
525 while (history.size() > maxsize) {
526 history.removeLast();
527 }
528 }
529
530 /**
531 * Replies the history of JOSM selections
532 *
533 * @return history of JOSM selections
534 */
535 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() {
536 return history;
537 }
538
539 @Override
540 public synchronized OsmPrimitive getElementAt(int index) {
541 return selection.get(index);
542 }
543
544 @Override
545 public synchronized int getSize() {
546 return selection.size();
547 }
548
549 /**
550 * Determines if no OSM primitives are currently selected.
551 * @return {@code true} if no OSM primitives are currently selected
552 * @since 10383
553 */
554 public boolean isSelectionEmpty() {
555 return selectionModel.isSelectionEmpty();
556 }
557
558 /**
559 * Replies the collection of OSM primitives currently selected in the view of this model
560 *
561 * @return choosen elements in the view
562 */
563 public synchronized Collection<OsmPrimitive> getSelected() {
564 Set<OsmPrimitive> sel = new HashSet<>();
565 for (int i = 0; i < getSize(); i++) {
566 if (selectionModel.isSelectedIndex(i)) {
567 sel.add(selection.get(i));
568 }
569 }
570 return sel;
571 }
572
573 /**
574 * Sets the OSM primitives to be selected in the view of this model
575 *
576 * @param sel the collection of primitives to select
577 */
578 public synchronized void setSelected(Collection<OsmPrimitive> sel) {
579 selectionModel.setValueIsAdjusting(true);
580 selectionModel.clearSelection();
581 if (sel != null) {
582 for (OsmPrimitive p: sel) {
583 int i = selection.indexOf(p);
584 if (i >= 0) {
585 selectionModel.addSelectionInterval(i, i);
586 }
587 }
588 }
589 selectionModel.setValueIsAdjusting(false);
590 }
591
592 @Override
593 protected void fireContentsChanged(Object source, int index0, int index1) {
594 Collection<OsmPrimitive> sel = getSelected();
595 super.fireContentsChanged(source, index0, index1);
596 setSelected(sel);
597 }
598
599 /**
600 * Sets the collection of currently selected OSM objects
601 *
602 * @param selection the collection of currently selected OSM objects
603 */
604 public void setJOSMSelection(final Collection<? extends OsmPrimitive> selection) {
605 synchronized (this) {
606 this.selection.clear();
607 if (selection != null) {
608 this.selection.addAll(selection);
609 sort();
610 }
611 }
612 GuiHelper.runInEDTAndWait(new Runnable() {
613 @Override public void run() {
614 fireContentsChanged(this, 0, getSize());
615 if (selection != null) {
616 remember(selection);
617 if (selection.size() == 2) {
618 Iterator<? extends OsmPrimitive> it = selection.iterator();
619 OsmPrimitive n1 = it.next();
620 OsmPrimitive n2 = it.next();
621 // show distance between two selected nodes with coordinates
622 if (n1 instanceof Node && n2 instanceof Node) {
623 LatLon c1 = ((Node) n1).getCoor();
624 LatLon c2 = ((Node) n2).getCoor();
625 if (c1 != null && c2 != null) {
626 Main.map.statusLine.setDist(c1.greatCircleDistance(c2));
627 return;
628 }
629 }
630 }
631 Main.map.statusLine.setDist(
632 new SubclassFilteredCollection<OsmPrimitive, Way>(selection, Way.class::isInstance));
633 }
634 }
635 });
636 }
637
638 /**
639 * Triggers a refresh of the view for all primitives in {@code toUpdate}
640 * which are currently displayed in the view
641 *
642 * @param toUpdate the collection of primitives to update
643 */
644 public synchronized void update(Collection<? extends OsmPrimitive> toUpdate) {
645 if (toUpdate == null) return;
646 if (toUpdate.isEmpty()) return;
647 Collection<OsmPrimitive> sel = getSelected();
648 for (OsmPrimitive p: toUpdate) {
649 int i = selection.indexOf(p);
650 if (i >= 0) {
651 super.fireContentsChanged(this, i, i);
652 }
653 }
654 setSelected(sel);
655 }
656
657 /**
658 * Sorts the current elements in the selection
659 */
660 public synchronized void sort() {
661 if (selection.size() <= Main.pref.getInteger("selection.no_sort_above", 100_000)) {
662 boolean quick = selection.size() > Main.pref.getInteger("selection.fast_sort_above", 10_000);
663 Comparator<OsmPrimitive> c = Main.pref.getBoolean("selection.sort_relations_before_ways", true)
664 ? OsmPrimitiveComparator.orderingRelationsWaysNodes()
665 : OsmPrimitiveComparator.orderingWaysRelationsNodes();
666 selection.sort(c.thenComparing(quick
667 ? OsmPrimitiveComparator.comparingUniqueId()
668 : OsmPrimitiveComparator.comparingNames()));
669 }
670 }
671
672 /* ------------------------------------------------------------------------ */
673 /* interface ActiveLayerChangeListener */
674 /* ------------------------------------------------------------------------ */
675 @Override
676 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
677 DataSet newData = e.getSource().getEditDataSet();
678 if (newData == null) {
679 setJOSMSelection(null);
680 history = null;
681 } else {
682 history = newData.getSelectionHistory();
683 setJOSMSelection(newData.getAllSelected());
684 }
685 }
686
687 /* ------------------------------------------------------------------------ */
688 /* interface SelectionChangeListener */
689 /* ------------------------------------------------------------------------ */
690 @Override
691 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
692 setJOSMSelection(newSelection);
693 }
694
695 /* ------------------------------------------------------------------------ */
696 /* interface DataSetListener */
697 /* ------------------------------------------------------------------------ */
698 @Override
699 public void dataChanged(DataChangedEvent event) {
700 // refresh the whole list
701 fireContentsChanged(this, 0, getSize());
702 }
703
704 @Override
705 public void nodeMoved(NodeMovedEvent event) {
706 // may influence the display name of primitives, update the data
707 update(event.getPrimitives());
708 }
709
710 @Override
711 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
712 // may influence the display name of primitives, update the data
713 update(event.getPrimitives());
714 }
715
716 @Override
717 public void relationMembersChanged(RelationMembersChangedEvent event) {
718 // may influence the display name of primitives, update the data
719 update(event.getPrimitives());
720 }
721
722 @Override
723 public void tagsChanged(TagsChangedEvent event) {
724 // may influence the display name of primitives, update the data
725 update(event.getPrimitives());
726 }
727
728 @Override
729 public void wayNodesChanged(WayNodesChangedEvent event) {
730 // may influence the display name of primitives, update the data
731 update(event.getPrimitives());
732 }
733
734 @Override
735 public void primitivesAdded(PrimitivesAddedEvent event) {
736 /* ignored - handled by SelectionChangeListener */
737 }
738
739 @Override
740 public void primitivesRemoved(PrimitivesRemovedEvent event) {
741 /* ignored - handled by SelectionChangeListener*/
742 }
743 }
744
745 /**
746 * A specialized {@link JMenuItem} for presenting one entry of the search history
747 *
748 * @author Jan Peter Stotz
749 */
750 protected static class SearchMenuItem extends JMenuItem implements ActionListener {
751 protected final transient SearchSetting s;
752
753 public SearchMenuItem(SearchSetting s) {
754 super(Utils.shortenString(s.toString(),
755 org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY));
756 this.s = s;
757 addActionListener(this);
758 }
759
760 @Override
761 public void actionPerformed(ActionEvent e) {
762 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s);
763 }
764 }
765
766 /**
767 * The popup menu for the search history entries
768 *
769 */
770 protected static class SearchPopupMenu extends JPopupMenu {
771 public static void launch(Component parent) {
772 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty())
773 return;
774 JPopupMenu menu = new SearchPopupMenu();
775 Rectangle r = parent.getBounds();
776 menu.show(parent, r.x, r.y + r.height);
777 }
778
779 /**
780 * Constructs a new {@code SearchPopupMenu}.
781 */
782 public SearchPopupMenu() {
783 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) {
784 add(new SearchMenuItem(ss));
785 }
786 }
787 }
788
789 /**
790 * A specialized {@link JMenuItem} for presenting one entry of the selection history
791 *
792 * @author Jan Peter Stotz
793 */
794 protected static class SelectionMenuItem extends JMenuItem implements ActionListener {
795 protected transient Collection<? extends OsmPrimitive> sel;
796
797 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) {
798 this.sel = sel;
799 int ways = 0;
800 int nodes = 0;
801 int relations = 0;
802 for (OsmPrimitive o : sel) {
803 if (!o.isSelectable()) continue; // skip unselectable primitives
804 if (o instanceof Way) {
805 ways++;
806 } else if (o instanceof Node) {
807 nodes++;
808 } else if (o instanceof Relation) {
809 relations++;
810 }
811 }
812 StringBuilder text = new StringBuilder();
813 if (ways != 0) {
814 text.append(text.length() > 0 ? ", " : "")
815 .append(trn("{0} way", "{0} ways", ways, ways));
816 }
817 if (nodes != 0) {
818 text.append(text.length() > 0 ? ", " : "")
819 .append(trn("{0} node", "{0} nodes", nodes, nodes));
820 }
821 if (relations != 0) {
822 text.append(text.length() > 0 ? ", " : "")
823 .append(trn("{0} relation", "{0} relations", relations, relations));
824 }
825 if (ways + nodes + relations == 0) {
826 text.append(tr("Unselectable now"));
827 this.sel = new ArrayList<>(); // empty selection
828 }
829 DefaultNameFormatter df = DefaultNameFormatter.getInstance();
830 if (ways + nodes + relations == 1) {
831 text.append(": ");
832 for (OsmPrimitive o : sel) {
833 text.append(o.getDisplayName(df));
834 }
835 setText(text.toString());
836 } else {
837 setText(tr("Selection: {0}", text));
838 }
839 addActionListener(this);
840 }
841
842 @Override
843 public void actionPerformed(ActionEvent e) {
844 Main.getLayerManager().getEditDataSet().setSelected(sel);
845 }
846 }
847
848 /**
849 * The popup menu for the JOSM selection history entries
850 */
851 protected static class SelectionHistoryPopup extends JPopupMenu {
852 public static void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) {
853 if (history == null || history.isEmpty()) return;
854 JPopupMenu menu = new SelectionHistoryPopup(history);
855 Rectangle r = parent.getBounds();
856 menu.show(parent, r.x, r.y + r.height);
857 }
858
859 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) {
860 for (Collection<? extends OsmPrimitive> sel : history) {
861 add(new SelectionMenuItem(sel));
862 }
863 }
864 }
865
866 /**
867 * A transfer handler class for drag-and-drop support.
868 */
869 protected class SelectionTransferHandler extends TransferHandler {
870
871 @Override
872 public int getSourceActions(JComponent c) {
873 return COPY;
874 }
875
876 @Override
877 protected Transferable createTransferable(JComponent c) {
878 return new PrimitiveTransferable(PrimitiveTransferData.getData(getSelectedPrimitives()));
879 }
880 }
881}
Note: See TracBrowser for help on using the repository browser.