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

Last change on this file since 4077 was 4068, checked in by jttt, 13 years ago

Fix #6290 "Copy" does not work when selecting objects from list

  • Property svn:eol-style set to native
File size: 36.7 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
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.BorderLayout;
8import java.awt.Color;
9import java.awt.Component;
10import java.awt.GridLayout;
11import java.awt.Rectangle;
12import java.awt.event.ActionEvent;
13import java.awt.event.ActionListener;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseAdapter;
16import java.awt.event.MouseEvent;
17import java.util.ArrayList;
18import java.util.Collection;
19import java.util.Collections;
20import java.util.Comparator;
21import java.util.HashMap;
22import java.util.HashSet;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Set;
26import java.util.logging.Logger;
27
28import javax.swing.AbstractAction;
29import javax.swing.AbstractListModel;
30import javax.swing.BorderFactory;
31import javax.swing.DefaultListSelectionModel;
32import javax.swing.JButton;
33import javax.swing.JList;
34import javax.swing.JMenuItem;
35import javax.swing.JPanel;
36import javax.swing.JPopupMenu;
37import javax.swing.JScrollPane;
38import javax.swing.ListSelectionModel;
39import javax.swing.SwingConstants;
40import javax.swing.SwingUtilities;
41import javax.swing.event.ListDataEvent;
42import javax.swing.event.ListDataListener;
43import javax.swing.event.ListSelectionEvent;
44import javax.swing.event.ListSelectionListener;
45import javax.swing.plaf.basic.BasicArrowButton;
46
47import org.openstreetmap.josm.Main;
48import org.openstreetmap.josm.actions.AutoScaleAction;
49import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting;
50import org.openstreetmap.josm.data.SelectionChangedListener;
51import org.openstreetmap.josm.data.osm.Node;
52import org.openstreetmap.josm.data.osm.OsmPrimitive;
53import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
54import org.openstreetmap.josm.data.osm.Relation;
55import org.openstreetmap.josm.data.osm.RelationMember;
56import org.openstreetmap.josm.data.osm.Way;
57import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
58import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
59import org.openstreetmap.josm.data.osm.event.DataSetListener;
60import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
61import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
62import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
63import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
64import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
65import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
66import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
67import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
68import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
69import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
70import org.openstreetmap.josm.gui.DefaultNameFormatter;
71import org.openstreetmap.josm.gui.MapView;
72import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener;
73import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
74import org.openstreetmap.josm.gui.SideButton;
75import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
76import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
77import org.openstreetmap.josm.gui.layer.OsmDataLayer;
78import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
79import org.openstreetmap.josm.tools.ImageProvider;
80import org.openstreetmap.josm.tools.Shortcut;
81
82/**
83 * A small tool dialog for displaying the current selection.
84 *
85 */
86public class SelectionListDialog extends ToggleDialog {
87 @SuppressWarnings("unused")
88 static private final Logger logger = Logger.getLogger(SelectionListDialog.class.getName());
89
90 private JList lstPrimitives;
91 private SelectionListModel model;
92
93 private SelectAction actSelect;
94 private SearchAction actSearch;
95 private ZoomToJOSMSelectionAction actZoomToJOSMSelection;
96 private ZoomToListSelection actZoomToListSelection;
97 private SetRelationSelection actSetRelationSelection;
98 private EditRelationSelection actEditRelationSelection;
99 private DownloadSelectedIncompleteMembersAction actDownloadSelectedIncompleteMembers;
100 private InspectAction actInspect;
101
102 /**
103 * Builds the panel with the list of selected OSM primitives
104 *
105 * @return the panel with the list of selected OSM primitives
106 */
107 protected JPanel buildListPanel() {
108 JPanel pnl = new JPanel(new BorderLayout());
109 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
110 model = new SelectionListModel(selectionModel);
111 lstPrimitives = new JList(model);
112 lstPrimitives.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
113 lstPrimitives.setSelectionModel(selectionModel);
114 lstPrimitives.setCellRenderer(new OsmPrimitivRenderer());
115 lstPrimitives.setTransferHandler(null); // Fix #6290. Drag & Drop is not supported anyway and Copy/Paste is better propagated to main window
116 pnl.add(new JScrollPane(lstPrimitives), BorderLayout.CENTER);
117
118 return pnl;
119 }
120
121 /**
122 * Builds the row of action buttons at the bottom of this dialog
123 *
124 * @return the panel
125 */
126 protected JPanel buildActionPanel() {
127 JPanel pnl = new JPanel(new GridLayout(1,2));
128
129 // the select action
130 final JButton selectButton = new SideButton(actSelect = new SelectAction());
131 lstPrimitives.getSelectionModel().addListSelectionListener(actSelect);
132 pnl.add(selectButton);
133 BasicArrowButton selectionHistoryMenuButton = createArrowButton(selectButton);
134 selectionHistoryMenuButton.addActionListener(new ActionListener() {
135 public void actionPerformed(ActionEvent e) {
136 SelectionHistoryPopup.launch(selectButton, model.getSelectionHistory());
137 }
138 });
139
140 // the search button
141 final JButton searchButton = new SideButton(actSearch = new SearchAction());
142 pnl.add(searchButton);
143
144 BasicArrowButton searchHistoryMenuButton = createArrowButton(searchButton);
145 searchHistoryMenuButton.addActionListener(new ActionListener() {
146 public void actionPerformed(ActionEvent e) {
147 SearchPopupMenu.launch(searchButton);
148 }
149 });
150
151 return pnl;
152 }
153
154 /**
155 * Builds the content panel for this dialog
156 *
157 * @return the content panel
158 */
159 protected JPanel buildContentPanel() {
160 JPanel pnl = new JPanel(new BorderLayout());
161 pnl.add(buildListPanel(), BorderLayout.CENTER);
162 pnl.add(buildActionPanel(), BorderLayout.SOUTH);
163 return pnl;
164 }
165
166 public SelectionListDialog() {
167 super(tr("Current Selection"), "selectionlist", tr("Open a selection list window."),
168 Shortcut.registerShortcut("subwindow:selection", tr("Toggle: {0}", tr("Current Selection")), KeyEvent.VK_T, Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT),
169 150, // default height
170 true // default is "show dialog"
171 );
172
173 add(buildContentPanel(), BorderLayout.CENTER);
174 model.addListDataListener(new TitleUpdater());
175 actZoomToJOSMSelection = new ZoomToJOSMSelectionAction();
176 model.addListDataListener(actZoomToJOSMSelection);
177
178 actZoomToListSelection = new ZoomToListSelection();
179 lstPrimitives.getSelectionModel().addListSelectionListener(actZoomToListSelection);
180
181 actSetRelationSelection = new SetRelationSelection();
182 lstPrimitives.getSelectionModel().addListSelectionListener(actSetRelationSelection);
183
184 actEditRelationSelection = new EditRelationSelection();
185 lstPrimitives.getSelectionModel().addListSelectionListener(actEditRelationSelection);
186
187 actDownloadSelectedIncompleteMembers = new DownloadSelectedIncompleteMembersAction();
188 lstPrimitives.getSelectionModel().addListSelectionListener(actDownloadSelectedIncompleteMembers);
189
190 actInspect = new InspectAction();
191 lstPrimitives.getSelectionModel().addListSelectionListener(actInspect);
192
193 lstPrimitives.addMouseListener(new SelectionPopupMenuLauncher());
194 lstPrimitives.addMouseListener(new DblClickHandler());
195 }
196
197 @Override
198 public void showNotify() {
199 MapView.addEditLayerChangeListener(model);
200 SelectionEventManager.getInstance().addSelectionListener(model, FireMode.IN_EDT_CONSOLIDATED);
201 DatasetEventManager.getInstance().addDatasetListener(model, FireMode.IN_EDT);
202 MapView.addEditLayerChangeListener(actSearch);
203 // editLayerChanged also gets the selection history of the level
204 model.editLayerChanged(null, Main.map.mapView.getEditLayer());
205 if (Main.map.mapView.getEditLayer() != null) {
206 model.setJOSMSelection(Main.map.mapView.getEditLayer().data.getSelected());
207 }
208 actSearch.updateEnabledState();
209 }
210
211 @Override
212 public void hideNotify() {
213 MapView.removeEditLayerChangeListener(actSearch);
214 MapView.removeEditLayerChangeListener(model);
215 SelectionEventManager.getInstance().removeSelectionListener(model);
216 DatasetEventManager.getInstance().removeDatasetListener(model);
217 }
218
219 private BasicArrowButton createArrowButton(JButton parentButton) {
220 BasicArrowButton arrowButton = new BasicArrowButton(SwingConstants.SOUTH, null, null, Color.BLACK, null);
221 arrowButton.setBorder(BorderFactory.createEmptyBorder());
222 parentButton.setLayout(new BorderLayout());
223 parentButton.add(arrowButton, BorderLayout.EAST);
224 return arrowButton;
225 }
226
227
228 /**
229 * Responds to double clicks on the list of selected objects
230 */
231 class DblClickHandler extends MouseAdapter {
232 @Override
233 public void mouseClicked(MouseEvent e) {
234 if (e.getClickCount() < 2 || ! SwingUtilities.isLeftMouseButton(e)) return;
235 int idx = lstPrimitives.locationToIndex(e.getPoint());
236 if (idx < 0) return;
237 OsmDataLayer layer = Main.main.getEditLayer();
238 if(layer == null) return;
239 layer.data.setSelected(Collections.singleton((OsmPrimitive)model.getElementAt(idx)));
240 }
241 }
242
243 /**
244 * The popup menu launcher
245 */
246 class SelectionPopupMenuLauncher extends PopupMenuLauncher {
247 private SelectionPopup popup = new SelectionPopup();
248
249 @Override
250 public void launch(MouseEvent evt) {
251 if (model.getSelected().isEmpty()) {
252 int idx = lstPrimitives.locationToIndex(evt.getPoint());
253 if (idx < 0) return;
254 model.setSelected(Collections.singleton((OsmPrimitive)model.getElementAt(idx)));
255 }
256 popup.show(lstPrimitives, evt.getX(), evt.getY());
257 }
258 }
259
260 /**
261 * The popup menu for the selection list
262 */
263 class SelectionPopup extends JPopupMenu {
264 public SelectionPopup() {
265 add(actZoomToJOSMSelection);
266 add(actZoomToListSelection);
267 addSeparator();
268 add(actSetRelationSelection);
269 add(actEditRelationSelection);
270 addSeparator();
271 add(actDownloadSelectedIncompleteMembers);
272 addSeparator();
273 add(actInspect);
274 }
275 }
276
277 /**
278 * Updates the dialog title with a summary of the current JOSM selection
279 */
280 class TitleUpdater implements ListDataListener {
281 protected void updateTitle() {
282 setTitle(model.getJOSMSelectionSummary());
283 }
284
285 public void contentsChanged(ListDataEvent e) {
286 updateTitle();
287 }
288
289 public void intervalAdded(ListDataEvent e) {
290 updateTitle();
291 }
292
293 public void intervalRemoved(ListDataEvent e) {
294 updateTitle();
295 }
296 }
297
298 /**
299 * Launches the search dialog
300 */
301 static class SearchAction extends AbstractAction implements EditLayerChangeListener {
302 public SearchAction() {
303 putValue(NAME, tr("Search"));
304 putValue(SHORT_DESCRIPTION, tr("Search for objects"));
305 putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
306 updateEnabledState();
307 }
308
309 public void actionPerformed(ActionEvent e) {
310 if (!isEnabled()) return;
311 org.openstreetmap.josm.actions.search.SearchAction.search();
312 }
313
314 public void updateEnabledState() {
315 setEnabled(Main.main != null && Main.main.getEditLayer() != null);
316 }
317
318 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
319 updateEnabledState();
320 }
321 }
322
323 /**
324 * Sets the current JOSM selection to the OSM primitives selected in the list
325 * of this dialog
326 */
327 class SelectAction extends AbstractAction implements ListSelectionListener {
328 public SelectAction() {
329 putValue(NAME, tr("Select"));
330 putValue(SHORT_DESCRIPTION, tr("Set the selected elements on the map to the selected items in the list above."));
331 putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
332 updateEnabledState();
333 }
334
335 public void actionPerformed(ActionEvent e) {
336 Collection<OsmPrimitive> sel = model.getSelected();
337 if (sel.isEmpty())return;
338 if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) return;
339 Main.map.mapView.getEditLayer().data.setSelected(sel);
340 }
341
342 public void updateEnabledState() {
343 setEnabled(!model.getSelected().isEmpty());
344 }
345
346 public void valueChanged(ListSelectionEvent e) {
347 updateEnabledState();
348 }
349 }
350
351 /**
352 * The action for zooming to the primitives in the current JOSM selection
353 *
354 */
355 class ZoomToJOSMSelectionAction extends AbstractAction implements ListDataListener {
356
357 public ZoomToJOSMSelectionAction() {
358 putValue(NAME,tr("Zoom to selection"));
359 putValue(SHORT_DESCRIPTION, tr("Zoom to selection"));
360 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
361 updateEnabledState();
362 }
363
364 public void actionPerformed(ActionEvent e) {
365 AutoScaleAction.autoScale("selection");
366 }
367
368 public void updateEnabledState() {
369 setEnabled(model.getSize() > 0);
370 }
371
372 public void contentsChanged(ListDataEvent e) {
373 updateEnabledState();
374 }
375
376 public void intervalAdded(ListDataEvent e) {
377 updateEnabledState();
378 }
379
380 public void intervalRemoved(ListDataEvent e) {
381 updateEnabledState();
382 }
383 }
384
385 /**
386 * The action for zooming to the primitives which are currently selected in
387 * the list displaying the JOSM selection
388 *
389 */
390 class ZoomToListSelection extends AbstractAction implements ListSelectionListener{
391 public ZoomToListSelection() {
392 putValue(NAME, tr("Zoom to selected element(s)"));
393 putValue(SHORT_DESCRIPTION, tr("Zoom to selected element(s)"));
394 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale", "selection"));
395 updateEnabledState();
396 }
397
398 public void actionPerformed(ActionEvent e) {
399 BoundingXYVisitor box = new BoundingXYVisitor();
400 Collection<OsmPrimitive> sel = model.getSelected();
401 if (sel.isEmpty()) return;
402 box.computeBoundingBox(sel);
403 if (box.getBounds() == null)
404 return;
405 box.enlargeBoundingBox();
406 Main.map.mapView.recalculateCenterScale(box);
407 }
408
409 public void updateEnabledState() {
410 setEnabled(!model.getSelected().isEmpty());
411 }
412
413 public void valueChanged(ListSelectionEvent e) {
414 updateEnabledState();
415 }
416 }
417
418 /**
419 * The action for setting and editing a relation in relation list dialog
420 *
421 */
422 class EditRelationSelection extends SetRelationSelection {
423 public EditRelationSelection() {
424 putValue(NAME, tr("Call editor for relation"));
425 putValue(SHORT_DESCRIPTION, tr("Call relation editor for selected relation"));
426 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
427 updateEnabledState();
428 }
429
430 @Override
431 public void actionPerformed(ActionEvent e) {
432 Relation relation = (Relation)model.getSelected().toArray()[0];
433 Collection<RelationMember> members = new HashSet<RelationMember>();
434 Collection<OsmPrimitive> selection = model.getAllElements();
435 for (RelationMember member: relation.getMembers()) {
436 if (selection.contains(member.getMember())) {
437 members.add(member);
438 }
439 }
440 Main.map.relationListDialog.selectRelation(relation);
441 RelationEditor.getEditor(Main.map.mapView.getEditLayer(), relation,
442 members).setVisible(true);
443 }
444 }
445
446 /**
447 * The action for setting a relation in relation list dialog
448 *
449 */
450 class SetRelationSelection extends AbstractAction implements ListSelectionListener{
451 public SetRelationSelection() {
452 putValue(NAME, tr("Select in relation list"));
453 putValue(SHORT_DESCRIPTION, tr("Select relation in relation list."));
454 putValue(SMALL_ICON, ImageProvider.get("dialogs", "selectionlist"));
455 updateEnabledState();
456 }
457
458 public void actionPerformed(ActionEvent e) {
459 Relation relation = (Relation)model.getSelected().toArray()[0];
460 Main.map.relationListDialog.selectRelation(relation);
461 }
462
463 public void updateEnabledState() {
464 Object[] sel = model.getSelected().toArray();
465 setEnabled(sel.length == 1 && sel[0] instanceof Relation);
466 }
467
468 public void valueChanged(ListSelectionEvent e) {
469 updateEnabledState();
470 }
471 }
472
473 /**
474 * The list model for the list of OSM primitives in the current JOSM selection.
475 *
476 * The model also maintains a history of the last {@see SelectionListModel#SELECTION_HISTORY_SIZE}
477 * JOSM selection.
478 *
479 */
480 static private class SelectionListModel extends AbstractListModel implements EditLayerChangeListener, SelectionChangedListener, DataSetListener{
481
482 private static final int SELECTION_HISTORY_SIZE = 10;
483
484 // Variable to store history from currentDataSet()
485 private LinkedList<Collection<? extends OsmPrimitive>> history;
486 private final List<OsmPrimitive> selection = new ArrayList<OsmPrimitive>();
487 private DefaultListSelectionModel selectionModel;
488
489 /**
490 * Constructor
491 * @param selectionModel the selection model used in the list
492 */
493 public SelectionListModel(DefaultListSelectionModel selectionModel) {
494 this.selectionModel = selectionModel;
495 }
496
497 /**
498 * Replies a summary of the current JOSM selection
499 *
500 * @return a summary of the current JOSM selection
501 */
502 public String getJOSMSelectionSummary() {
503 if (selection.isEmpty()) return tr("Selection");
504 int numNodes = 0;
505 int numWays = 0;
506 int numRelations = 0;
507 for (OsmPrimitive p: selection) {
508 switch(p.getType()) {
509 case NODE: numNodes++; break;
510 case WAY: numWays++; break;
511 case RELATION: numRelations++; break;
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
548 */
549 public List<Collection<? extends OsmPrimitive>> getSelectionHistory() {
550 return history;
551 }
552
553 public Object getElementAt(int index) {
554 return selection.get(index);
555 }
556
557 public int getSize() {
558 return selection.size();
559 }
560
561 /**
562 * Replies the collection of OSM primitives currently selected in the view
563 * of this model
564 *
565 * @return
566 */
567 public Collection<OsmPrimitive> getSelected() {
568 Set<OsmPrimitive> sel = new HashSet<OsmPrimitive>();
569 for(int i=0; i< getSize();i++) {
570 if (selectionModel.isSelectedIndex(i)) {
571 sel.add(selection.get(i));
572 }
573 }
574 return sel;
575 }
576
577 /**
578 * Replies the collection of OSM primitives in the view
579 * of this model
580 *
581 * @return
582 */
583 public Collection<OsmPrimitive> getAllElements() {
584 return selection;
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 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(Collection<? extends OsmPrimitive> selection) {
616 this.selection.clear();
617 if (selection == null) {
618 fireContentsChanged(this, 0, getSize());
619 return;
620 }
621 this.selection.addAll(selection);
622 sort();
623 fireContentsChanged(this, 0, getSize());
624 remember(selection);
625 }
626
627 /**
628 * Triggers a refresh of the view for all primitives in {@code toUpdate}
629 * which are currently displayed in the view
630 *
631 * @param toUpdate the collection of primitives to update
632 */
633 public void update(Collection<? extends OsmPrimitive> toUpdate) {
634 if (toUpdate == null) return;
635 if (toUpdate.isEmpty()) return;
636 Collection<OsmPrimitive> sel = getSelected();
637 for (OsmPrimitive p: toUpdate){
638 int i = selection.indexOf(p);
639 if (i >= 0) {
640 super.fireContentsChanged(this, i,i);
641 }
642 }
643 setSelected(sel);
644 }
645
646 /**
647 * Replies the list of selected relations with incomplete members
648 *
649 * @return the list of selected relations with incomplete members
650 */
651 public List<Relation> getSelectedRelationsWithIncompleteMembers() {
652 List<Relation> ret = new LinkedList<Relation>();
653 for(int i=0; i<getSize(); i++) {
654 if (!selectionModel.isSelectedIndex(i)) {
655 continue;
656 }
657 OsmPrimitive p = selection.get(i);
658 if (! (p instanceof Relation)) {
659 continue;
660 }
661 if (p.isNew()) {
662 continue;
663 }
664 Relation r = (Relation)p;
665 if (r.hasIncompleteMembers()) {
666 ret.add(r);
667 }
668 }
669 return ret;
670 }
671
672 /**
673 * Sorts the current elements in the selection
674 */
675 public void sort() {
676 if (this.selection.size()>Main.pref.getInteger("selection.no_sort_above",100000)) return;
677 if (this.selection.size()>Main.pref.getInteger("selection.fast_sort_above",10000)) {
678 Collections.sort(this.selection, new OsmPrimitiveQuickComparator());
679 } else {
680 Collections.sort(this.selection, new OsmPrimitiveComparator());
681 }
682 }
683
684 /* ------------------------------------------------------------------------ */
685 /* interface EditLayerChangeListener */
686 /* ------------------------------------------------------------------------ */
687 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
688 if (newLayer == null) {
689 setJOSMSelection(null);
690 history = null;
691 } else {
692 history = newLayer.data.getSelectionHistory();
693 setJOSMSelection(newLayer.data.getSelected());
694 }
695 }
696
697 /* ------------------------------------------------------------------------ */
698 /* interface SelectionChangeListener */
699 /* ------------------------------------------------------------------------ */
700 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
701 setJOSMSelection(newSelection);
702 }
703
704 /* ------------------------------------------------------------------------ */
705 /* interface DataSetListener */
706 /* ------------------------------------------------------------------------ */
707 public void dataChanged(DataChangedEvent event) {
708 // refresh the whole list
709 fireContentsChanged(this, 0, getSize());
710 }
711
712 public void nodeMoved(NodeMovedEvent event) {
713 // may influence the display name of primitives, update the data
714 update(event.getPrimitives());
715 }
716
717 public void otherDatasetChange(AbstractDatasetChangedEvent event) {
718 // may influence the display name of primitives, update the data
719 update(event.getPrimitives());
720 }
721
722 public void relationMembersChanged(RelationMembersChangedEvent event) {
723 // may influence the display name of primitives, update the data
724 update(event.getPrimitives());
725 }
726
727 public void tagsChanged(TagsChangedEvent event) {
728 // may influence the display name of primitives, update the data
729 update(event.getPrimitives());
730 }
731
732 public void wayNodesChanged(WayNodesChangedEvent event) {
733 // may influence the display name of primitives, update the data
734 update(event.getPrimitives());
735 }
736
737 public void primtivesAdded(PrimitivesAddedEvent event) {/* ignored - handled by SelectionChangeListener */}
738 public void primtivesRemoved(PrimitivesRemovedEvent event) {/* ignored - handled by SelectionChangeListener*/}
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 final protected SearchSetting s;
748
749 public SearchMenuItem(SearchSetting s) {
750 super(s.toString());
751 this.s = s;
752 addActionListener(this);
753 }
754
755 public void actionPerformed(ActionEvent e) {
756 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(s);
757 }
758 }
759
760 /**
761 * The popup menu for the search history entries
762 *
763 */
764 protected static class SearchPopupMenu extends JPopupMenu {
765 static public void launch(Component parent) {
766 if (org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory().isEmpty())
767 return;
768 JPopupMenu menu = new SearchPopupMenu();
769 Rectangle r = parent.getBounds();
770 menu.show(parent, r.x, r.y + r.height);
771 }
772
773 public SearchPopupMenu() {
774 for (SearchSetting ss: org.openstreetmap.josm.actions.search.SearchAction.getSearchHistory()) {
775 add(new SearchMenuItem(ss));
776 }
777 }
778 }
779
780 /**
781 * A specialized {@link JMenuItem} for presenting one entry of the selection history
782 *
783 * @author Jan Peter Stotz
784 */
785 protected static class SelectionMenuItem extends JMenuItem implements ActionListener {
786 final private DefaultNameFormatter df = DefaultNameFormatter.getInstance();
787 protected Collection<? extends OsmPrimitive> sel;
788
789 public SelectionMenuItem(Collection<? extends OsmPrimitive> sel) {
790 super();
791 this.sel = sel;
792 int ways = 0;
793 int nodes = 0;
794 int relations = 0;
795 for (OsmPrimitive o : sel) {
796 if (o instanceof Way) {
797 ways++;
798 } else if (o instanceof Node) {
799 nodes++;
800 } else if (o instanceof Relation) {
801 relations++;
802 }
803 }
804 StringBuffer text = new StringBuffer();
805 if(ways != 0) {
806 text.append(text.length() > 0 ? ", " : "")
807 .append(trn("{0} way", "{0} ways", ways, ways));
808 }
809 if(nodes != 0) {
810 text.append(text.length() > 0 ? ", " : "")
811 .append(trn("{0} node", "{0} nodes", nodes, nodes));
812 }
813 if(relations != 0) {
814 text.append(text.length() > 0 ? ", " : "")
815 .append(trn("{0} relation", "{0} relations", relations, relations));
816 }
817 if(ways + nodes + relations == 1)
818 {
819 text.append(": ");
820 for(OsmPrimitive o : sel) {
821 text.append(o.getDisplayName(df));
822 }
823 setText(text.toString());
824 } else {
825 setText(tr("Selection: {0}", text));
826 }
827 addActionListener(this);
828 }
829
830 public void actionPerformed(ActionEvent e) {
831 Main.main.getCurrentDataSet().setSelected(sel);
832 }
833 }
834
835 /**
836 * The popup menue for the JOSM selection history entries
837 *
838 */
839 protected static class SelectionHistoryPopup extends JPopupMenu {
840 static public void launch(Component parent, Collection<Collection<? extends OsmPrimitive>> history) {
841 if (history == null || history.isEmpty()) return;
842 JPopupMenu menu = new SelectionHistoryPopup(history);
843 Rectangle r = parent.getBounds();
844 menu.show(parent, r.x, r.y + r.height);
845 }
846
847 public SelectionHistoryPopup(Collection<Collection<? extends OsmPrimitive>> history) {
848 for (Collection<? extends OsmPrimitive> sel : history) {
849 add(new SelectionMenuItem(sel));
850 }
851 }
852 }
853
854 /**
855 * Action for downloading incomplete members of selected relations
856 *
857 */
858 class DownloadSelectedIncompleteMembersAction extends AbstractAction implements ListSelectionListener {
859 public DownloadSelectedIncompleteMembersAction() {
860 putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations"));
861 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
862 putValue(NAME, tr("Download incomplete members"));
863 updateEnabledState();
864 }
865
866 public Set<OsmPrimitive> buildSetOfIncompleteMembers(List<Relation> rels) {
867 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
868 for(Relation r: rels) {
869 ret.addAll(r.getIncompleteMembers());
870 }
871 return ret;
872 }
873
874 public void actionPerformed(ActionEvent e) {
875 if (!isEnabled())
876 return;
877 List<Relation> rels = model.getSelectedRelationsWithIncompleteMembers();
878 if (rels.isEmpty()) return;
879 Main.worker.submit(new DownloadRelationMemberTask(
880 rels,
881 buildSetOfIncompleteMembers(rels),
882 Main.map.mapView.getEditLayer()
883 ));
884 }
885
886 protected void updateEnabledState() {
887 setEnabled(!model.getSelectedRelationsWithIncompleteMembers().isEmpty());
888 }
889
890 public void valueChanged(ListSelectionEvent e) {
891 updateEnabledState();
892 }
893 }
894
895 class InspectAction extends AbstractAction implements ListSelectionListener {
896 public InspectAction() {
897 putValue(SHORT_DESCRIPTION, tr("Get detailed information on the internal state of the objects."));
898 putValue(NAME, tr("Inspect"));
899 updateEnabledState();
900 }
901
902 public void actionPerformed(ActionEvent e) {
903 Collection<OsmPrimitive> sel = model.getSelected();
904 if (sel.isEmpty()) return;
905 InspectPrimitiveDialog inspectDialog = new InspectPrimitiveDialog(sel);
906 inspectDialog.showDialog();
907 }
908
909 public void updateEnabledState() {
910 setEnabled(!model.getSelected().isEmpty());
911 }
912
913 public void valueChanged(ListSelectionEvent e) {
914 updateEnabledState();
915 }
916 }
917
918 /** Comparator, comparing by type and objects display names */
919 static private class OsmPrimitiveComparator implements Comparator<OsmPrimitive> {
920 final private HashMap<OsmPrimitive, String> cache= new HashMap<OsmPrimitive, String>();
921 final private DefaultNameFormatter df = DefaultNameFormatter.getInstance();
922
923 private String cachedName(OsmPrimitive p) {
924 String name = cache.get(p);
925 if (name == null) {
926 name = p.getDisplayName(df);
927 cache.put(p, name);
928 }
929 return name;
930 }
931
932 private int compareName(OsmPrimitive a, OsmPrimitive b) {
933 String an = cachedName(a);
934 String bn = cachedName(b);
935 // make sure display names starting with digits are the end of the
936 // list
937 if (Character.isDigit(an.charAt(0)) && Character.isDigit(bn.charAt(0)))
938 return an.compareTo(bn);
939 else if (Character.isDigit(an.charAt(0)) && !Character.isDigit(bn.charAt(0)))
940 return 1;
941 else if (!Character.isDigit(an.charAt(0)) && Character.isDigit(bn.charAt(0)))
942 return -1;
943 return an.compareTo(bn);
944 }
945
946 private int compareType(OsmPrimitive a, OsmPrimitive b) {
947 // show ways before relations, then nodes
948 if (a.getType().equals(OsmPrimitiveType.WAY)) return -1;
949 if (a.getType().equals(OsmPrimitiveType.NODE)) return 1;
950 // a is a relation
951 if (b.getType().equals(OsmPrimitiveType.WAY)) return 1;
952 // b is a node
953 return -1;
954 }
955
956 public int compare(OsmPrimitive a, OsmPrimitive b) {
957 if (a.getType().equals(b.getType()))
958 return compareName(a, b);
959 return compareType(a, b);
960 }
961 }
962
963 /** Quicker comparator, comparing just by type and ID's */
964 static private class OsmPrimitiveQuickComparator implements Comparator<OsmPrimitive> {
965
966 private int compareId(OsmPrimitive a, OsmPrimitive b) {
967 long id_a=a.getUniqueId();
968 long id_b=b.getUniqueId();
969 if (id_a<id_b) return -1;
970 if (id_a>id_b) return 1;
971 return 0;
972 }
973
974 private int compareType(OsmPrimitive a, OsmPrimitive b) {
975 // show ways before relations, then nodes
976 if (a.getType().equals(OsmPrimitiveType.WAY)) return -1;
977 if (a.getType().equals(OsmPrimitiveType.NODE)) return 1;
978 // a is a relation
979 if (b.getType().equals(OsmPrimitiveType.WAY)) return 1;
980 // b is a node
981 return -1;
982 }
983
984 public int compare(OsmPrimitive a, OsmPrimitive b) {
985 if (a.getType().equals(b.getType()))
986 return compareId(a, b);
987 return compareType(a, b);
988 }
989 }
990
991}
Note: See TracBrowser for help on using the repository browser.