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

Last change on this file since 10604 was 10604, checked in by Don-vip, 8 years ago

fix #12478, fix #12565, fix #11114 - Use ​Swing Copy/Paste instead of CopyAction/PasteAction with custom buffer (patch by michael2402, modified) - gsoc-core

  • Property svn:eol-style set to native
File size: 33.3 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(new ActionListener() {
128 @Override
129 public void actionPerformed(ActionEvent e) {
130 SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory());
131 }
132 });
133
134 // the search button
135 final SideButton searchButton = new SideButton(actSearch);
136 searchButton.createArrow(new ActionListener() {
137 @Override
138 public void actionPerformed(ActionEvent e) {
139 SearchPopupMenu.launch(searchButton);
140 }
141 });
142
143 createLayout(lstPrimitives, true, Arrays.asList(new SideButton[] {
144 selectButton, searchButton, new SideButton(actShowHistory)
145 }));
146 }
147
148 /**
149 * Constructs a new {@code SelectionListDialog}.
150 */
151 public SelectionListDialog() {
152 super(tr("Selection"), "selectionlist", tr("Open a selection list window."),
153 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}",
154 tr("Current Selection")), KeyEvent.VK_T, Shortcut.ALT_SHIFT),
155 150, // default height
156 true // default is "show dialog"
157 );
158
159 buildContentPanel();
160 model.addListDataListener(new TitleUpdater());
161 model.addListDataListener(actZoomToJOSMSelection);
162
163 popupMenu = new ListPopupMenu(lstPrimitives);
164 popupMenuHandler = setupPopupMenuHandler();
165
166 lstPrimitives.addListSelectionListener(new ListSelectionListener() {
167 @Override
168 public void valueChanged(ListSelectionEvent e) {
169 actZoomToListSelection.valueChanged(e);
170 popupMenuHandler.setPrimitives(model.getSelected());
171 }
172 });
173
174 lstPrimitives.addMouseListener(new MouseEventHandler());
175
176 InputMapUtils.addEnterAction(lstPrimitives, actZoomToListSelection);
177 }
178
179 @Override
180 public void showNotify() {
181 SelectionEventManager.getInstance().addSelectionListener(actShowHistory, FireMode.IN_EDT_CONSOLIDATED);
182 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED);
183 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT);
184 Main.getLayerManager().addActiveLayerChangeListener(actSearch);
185 // editLayerChanged also gets the selection history of the level. Listener calls setJOSMSelection when fired.
186 Main.getLayerManager().addAndFireActiveLayerChangeListener(model);
187 actSearch.updateEnabledState();
188 }
189
190 @Override
191 public void hideNotify() {
192 Main.getLayerManager().removeActiveLayerChangeListener(actSearch);
193 Main.getLayerManager().removeActiveLayerChangeListener(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.getLayerManager().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 ActiveLayerChangeListener {
296 /**
297 * Constructs a new {@code SearchAction}.
298 */
299 SearchAction() {
300 putValue(NAME, tr("Search"));
301 putValue(SHORT_DESCRIPTION, tr("Search for objects"));
302 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true);
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.getLayerManager().getEditLayer() != null);
314 }
315
316 @Override
317 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
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.getLayerManager().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.isSelectionEmpty());
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 new ImageProvider("dialogs", "history").getResource().attachImageIcon(this, true);
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.isSelectionEmpty() || 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 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true);
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 new ImageProvider("dialogs/autoscale", "selection").getResource().attachImageIcon(this, true);
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.isSelectionEmpty());
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 ActiveLayerChangeListener, 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 default: throw new AssertionError();
512 }
513 }
514 return tr("Sel.: Rel.:{0} / Ways:{1} / Nodes:{2}", numRelations, numWays, numNodes);
515 }
516
517 /**
518 * Remembers a JOSM selection the history of JOSM selections
519 *
520 * @param selection the JOSM selection. Ignored if null or empty.
521 */
522 public void remember(Collection<? extends OsmPrimitive> selection) {
523 if (selection == null) return;
524 if (selection.isEmpty()) return;
525 if (history == null) return;
526 if (history.isEmpty()) {
527 history.add(selection);
528 return;
529 }
530 if (history.getFirst().equals(selection)) return;
531 history.addFirst(selection);
532 for (int i = 1; i < history.size(); ++i) {
533 if (history.get(i).equals(selection)) {
534 history.remove(i);
535 break;
536 }
537 }
538 int maxsize = Main.pref.getInteger("select.history-size", SELECTION_HISTORY_SIZE);
539 while (history.size() > maxsize) {
540 history.removeLast();
541 }
542 }
543
544 /**
545 * Replies the history of JOSM selections
546 *
547 * @return history of JOSM selections
548 */
549 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() {
550 return history;
551 }
552
553 @Override
554 public synchronized OsmPrimitive getElementAt(int index) {
555 return selection.get(index);
556 }
557
558 @Override
559 public synchronized int getSize() {
560 return selection.size();
561 }
562
563 /**
564 * Determines if no OSM primitives are currently selected.
565 * @return {@code true} if no OSM primitives are currently selected
566 * @since 10383
567 */
568 public boolean isSelectionEmpty() {
569 return selectionModel.isSelectionEmpty();
570 }
571
572 /**
573 * Replies the collection of OSM primitives currently selected in the view of this model
574 *
575 * @return choosen elements in the view
576 */
577 public synchronized Collection<OsmPrimitive> getSelected() {
578 Set<OsmPrimitive> sel = new HashSet<>();
579 for (int i = 0; i < getSize(); i++) {
580 if (selectionModel.isSelectedIndex(i)) {
581 sel.add(selection.get(i));
582 }
583 }
584 return sel;
585 }
586
587 /**
588 * Sets the OSM primitives to be selected in the view of this model
589 *
590 * @param sel the collection of primitives to select
591 */
592 public synchronized void setSelected(Collection<OsmPrimitive> sel) {
593 selectionModel.clearSelection();
594 if (sel == null) return;
595 for (OsmPrimitive p: sel) {
596 int i = selection.indexOf(p);
597 if (i >= 0) {
598 selectionModel.addSelectionInterval(i, i);
599 }
600 }
601 }
602
603 @Override
604 protected void fireContentsChanged(Object source, int index0, int index1) {
605 Collection<OsmPrimitive> sel = getSelected();
606 super.fireContentsChanged(source, index0, index1);
607 setSelected(sel);
608 }
609
610 /**
611 * Sets the collection of currently selected OSM objects
612 *
613 * @param selection the collection of currently selected OSM objects
614 */
615 public void setJOSMSelection(final Collection<? extends OsmPrimitive> selection) {
616 synchronized (this) {
617 this.selection.clear();
618 if (selection != null) {
619 this.selection.addAll(selection);
620 sort();
621 }
622 }
623 GuiHelper.runInEDTAndWait(new Runnable() {
624 @Override public void run() {
625 fireContentsChanged(this, 0, getSize());
626 if (selection != null) {
627 remember(selection);
628 if (selection.size() == 2) {
629 Iterator<? extends OsmPrimitive> it = selection.iterator();
630 OsmPrimitive n1 = it.next();
631 OsmPrimitive n2 = it.next();
632 // show distance between two selected nodes with coordinates
633 if (n1 instanceof Node && n2 instanceof Node) {
634 LatLon c1 = ((Node) n1).getCoor();
635 LatLon c2 = ((Node) n2).getCoor();
636 if (c1 != null && c2 != null) {
637 Main.map.statusLine.setDist(c1.greatCircleDistance(c2));
638 return;
639 }
640 }
641 }
642 Main.map.statusLine.setDist(
643 new SubclassFilteredCollection<OsmPrimitive, Way>(selection, OsmPrimitive.wayPredicate));
644 }
645 }
646 });
647 }
648
649 /**
650 * Triggers a refresh of the view for all primitives in {@code toUpdate}
651 * which are currently displayed in the view
652 *
653 * @param toUpdate the collection of primitives to update
654 */
655 public synchronized void update(Collection<? extends OsmPrimitive> toUpdate) {
656 if (toUpdate == null) return;
657 if (toUpdate.isEmpty()) return;
658 Collection<OsmPrimitive> sel = getSelected();
659 for (OsmPrimitive p: toUpdate) {
660 int i = selection.indexOf(p);
661 if (i >= 0) {
662 super.fireContentsChanged(this, i, i);
663 }
664 }
665 setSelected(sel);
666 }
667
668 /**
669 * Sorts the current elements in the selection
670 */
671 public synchronized void sort() {
672 if (this.selection.size() <= Main.pref.getInteger("selection.no_sort_above", 100000)) {
673 boolean quick = this.selection.size() > Main.pref.getInteger("selection.fast_sort_above", 10000);
674 Collections.sort(this.selection, new OsmPrimitiveComparator(quick, false));
675 }
676 }
677
678 /* ------------------------------------------------------------------------ */
679 /* interface ActiveLayerChangeListener */
680 /* ------------------------------------------------------------------------ */
681 @Override
682 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
683 DataSet newData = e.getSource().getEditDataSet();
684 if (newData == null) {
685 setJOSMSelection(null);
686 history = null;
687 } else {
688 history = newData.getSelectionHistory();
689 setJOSMSelection(newData.getAllSelected());
690 }
691 }
692
693 /* ------------------------------------------------------------------------ */
694 /* interface SelectionChangeListener */
695 /* ------------------------------------------------------------------------ */
696 @Override
697 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
698 setJOSMSelection(newSelection);
699 }
700
701 /* ------------------------------------------------------------------------ */
702 /* interface DataSetListener */
703 /* ------------------------------------------------------------------------ */
704 @Override
705 public void dataChanged(DataChangedEvent event) {
706 // refresh the whole list
707 fireContentsChanged(this, 0, getSize());
708 }
709
710 @Override
711 public void nodeMoved(NodeMovedEvent event) {
712 // may influence the display name of primitives, update the data
713 update(event.getPrimitives());
714 }
715
716 @Override
717 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
718 // may influence the display name of primitives, update the data
719 update(event.getPrimitives());
720 }
721
722 @Override
723 public void relationMembersChanged(RelationMembersChangedEvent event) {
724 // may influence the display name of primitives, update the data
725 update(event.getPrimitives());
726 }
727
728 @Override
729 public void tagsChanged(TagsChangedEvent event) {
730 // may influence the display name of primitives, update the data
731 update(event.getPrimitives());
732 }
733
734 @Override
735 public void wayNodesChanged(WayNodesChangedEvent event) {
736 // may influence the display name of primitives, update the data
737 update(event.getPrimitives());
738 }
739
740 @Override
741 public void primitivesAdded(PrimitivesAddedEvent event) {
742 /* ignored - handled by SelectionChangeListener */
743 }
744
745 @Override
746 public void primitivesRemoved(PrimitivesRemovedEvent event) {
747 /* ignored - handled by SelectionChangeListener*/
748 }
749 }
750
751 /**
752 * A specialized {@link JMenuItem} for presenting one entry of the search history
753 *
754 * @author Jan Peter Stotz
755 */
756 protected static class SearchMenuItem extends JMenuItem implements ActionListener {
757 protected final transient SearchSetting s;
758
759 public SearchMenuItem(SearchSetting s) {
760 super(Utils.shortenString(s.toString(),
761 org.openstreetmap.josm.actions.search.SearchAction.MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY));
762 this.s = s;
763 addActionListener(this);
764 }
765
766 @Override
767 public void actionPerformed(ActionEvent e) {
768 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s);
769 }
770 }
771
772 /**
773 * The popup menu for the search history entries
774 *
775 */
776 protected static class SearchPopupMenu extends JPopupMenu {
777 public static void launch(Component parent) {
778 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty())
779 return;
780 JPopupMenu menu = new SearchPopupMenu();
781 Rectangle r = parent.getBounds();
782 menu.show(parent, r.x, r.y + r.height);
783 }
784
785 /**
786 * Constructs a new {@code SearchPopupMenu}.
787 */
788 public SearchPopupMenu() {
789 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) {
790 add(new SearchMenuItem(ss));
791 }
792 }
793 }
794
795 /**
796 * A specialized {@link JMenuItem} for presenting one entry of the selection history
797 *
798 * @author Jan Peter Stotz
799 */
800 protected static class SelectionMenuItem extends JMenuItem implements ActionListener {
801 protected transient Collection<? extends OsmPrimitive> sel;
802
803 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) {
804 this.sel = sel;
805 int ways = 0;
806 int nodes = 0;
807 int relations = 0;
808 for (OsmPrimitive o : sel) {
809 if (!o.isSelectable()) continue; // skip unselectable primitives
810 if (o instanceof Way) {
811 ways++;
812 } else if (o instanceof Node) {
813 nodes++;
814 } else if (o instanceof Relation) {
815 relations++;
816 }
817 }
818 StringBuilder text = new StringBuilder();
819 if (ways != 0) {
820 text.append(text.length() > 0 ? ", " : "")
821 .append(trn("{0} way", "{0} ways", ways, ways));
822 }
823 if (nodes != 0) {
824 text.append(text.length() > 0 ? ", " : "")
825 .append(trn("{0} node", "{0} nodes", nodes, nodes));
826 }
827 if (relations != 0) {
828 text.append(text.length() > 0 ? ", " : "")
829 .append(trn("{0} relation", "{0} relations", relations, relations));
830 }
831 if (ways + nodes + relations == 0) {
832 text.append(tr("Unselectable now"));
833 this.sel = new ArrayList<>(); // empty selection
834 }
835 DefaultNameFormatter df = DefaultNameFormatter.getInstance();
836 if (ways + nodes + relations == 1) {
837 text.append(": ");
838 for (OsmPrimitive o : sel) {
839 text.append(o.getDisplayName(df));
840 }
841 setText(text.toString());
842 } else {
843 setText(tr("Selection: {0}", text));
844 }
845 addActionListener(this);
846 }
847
848 @Override
849 public void actionPerformed(ActionEvent e) {
850 Main.getLayerManager().getEditDataSet().setSelected(sel);
851 }
852 }
853
854 /**
855 * The popup menu for the JOSM selection history entries
856 */
857 protected static class SelectionHistoryPopup extends JPopupMenu {
858 public static void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) {
859 if (history == null || history.isEmpty()) return;
860 JPopupMenu menu = new SelectionHistoryPopup(history);
861 Rectangle r = parent.getBounds();
862 menu.show(parent, r.x, r.y + r.height);
863 }
864
865 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) {
866 for (Collection<? extends OsmPrimitive> sel : history) {
867 add(new SelectionMenuItem(sel));
868 }
869 }
870 }
871
872 /**
873 * A transfer handler class for drag-and-drop support.
874 */
875 protected class SelectionTransferHandler extends TransferHandler {
876
877 @Override
878 public int getSourceActions(JComponent c) {
879 return COPY;
880 }
881
882 @Override
883 protected Transferable createTransferable(JComponent c) {
884 return new PrimitiveTransferable(PrimitiveTransferData.getData(getSelectedPrimitives()));
885 }
886 }
887}
Note: See TracBrowser for help on using the repository browser.