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

Last change on this file since 19050 was 19050, checked in by taylor.smock, 6 weeks 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.