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

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

see #12300 - Allow to drag from select list to relation members

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