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

Last change on this file since 14426 was 14273, checked in by stoecker, 6 years ago

fix typos - patch by naoliv - fix #16781 - Thanks a lot

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