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

Last change on this file since 12841 was 12841, checked in by bastiK, 7 years ago

see #15229 - fix deprecations caused by [12840]

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