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

Last change on this file since 15929 was 15707, checked in by simon04, 4 years ago

see #14465 -Tag2Link: consider all selected primitives/properties

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