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

Last change on this file since 10716 was 10716, checked in by simon04, 8 years ago

see #11390, see #12890 - Deprecate predicates in OsmPrimitive class

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