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

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

see #12300 - Fix headless unit tests

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