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

Last change on this file was 19050, checked in by taylor.smock, 34 hours ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

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