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

Last change on this file was 19187, checked in by taylor.smock, 11 months ago

Remove/fix unused icons

rocket.svg was only used for getting people to update from WebStart to
OpenWebStart. Since we no longer support Oracle WebStart, we can remove
rocket.svg.

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