source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/RelationListDialog.java@ 2348

Last change on this file since 2348 was 2348, checked in by Gubaer, 14 years ago

applied #3780: patch by hansendc: Shift selection is broken

  • Property svn:eol-style set to native
File size: 22.7 KB
Line 
1package org.openstreetmap.josm.gui.dialogs;
2
3import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.GridLayout;
8import java.awt.Point;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.awt.event.MouseAdapter;
12import java.awt.event.MouseEvent;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Comparator;
18import java.util.HashSet;
19import java.util.List;
20import java.util.logging.Logger;
21
22import javax.swing.AbstractAction;
23import javax.swing.AbstractListModel;
24import javax.swing.DefaultListSelectionModel;
25import javax.swing.JList;
26import javax.swing.JPanel;
27import javax.swing.JPopupMenu;
28import javax.swing.JScrollPane;
29import javax.swing.KeyStroke;
30import javax.swing.ListSelectionModel;
31import javax.swing.SwingUtilities;
32import javax.swing.event.ListSelectionEvent;
33import javax.swing.event.ListSelectionListener;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.data.osm.DataSet;
37import org.openstreetmap.josm.data.osm.NameFormatter;
38import org.openstreetmap.josm.data.osm.OsmPrimitive;
39import org.openstreetmap.josm.data.osm.Relation;
40import org.openstreetmap.josm.data.osm.RelationMember;
41import org.openstreetmap.josm.gui.DefaultNameFormatter;
42import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
43import org.openstreetmap.josm.gui.SideButton;
44import org.openstreetmap.josm.gui.dialogs.relation.GenericRelationEditor;
45import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
46import org.openstreetmap.josm.gui.layer.DataChangeListener;
47import org.openstreetmap.josm.gui.layer.Layer;
48import org.openstreetmap.josm.gui.layer.OsmDataLayer;
49import org.openstreetmap.josm.gui.layer.Layer.LayerChangeListener;
50import org.openstreetmap.josm.tools.GBC;
51import org.openstreetmap.josm.tools.ImageProvider;
52import org.openstreetmap.josm.tools.Shortcut;
53
54/**
55 * A dialog showing all known relations, with buttons to add, edit, and
56 * delete them.
57 *
58 * We don't have such dialogs for nodes, segments, and ways, because those
59 * objects are visible on the map and can be selected there. Relations are not.
60 */
61public class RelationListDialog extends ToggleDialog implements LayerChangeListener, DataChangeListener {
62 private static final Logger logger = Logger.getLogger(RelationListDialog.class.getName());
63
64 /** The display list. */
65 private JList displaylist;
66 /** the list model used */
67 private RelationListModel model;
68
69 /** the edit action */
70 private EditAction editAction;
71 /** the delete action */
72 private DeleteAction deleteAction;
73 /** the popup menu */
74 private RelationDialogPopupMenu popupMenu;
75
76
77 /**
78 * constructor
79 */
80 public RelationListDialog() {
81 super(tr("Relations"), "relationlist", tr("Open a list of all relations."),
82 Shortcut.registerShortcut("subwindow:relations", tr("Toggle: {0}", tr("Relations")), KeyEvent.VK_R, Shortcut.GROUP_LAYER), 150);
83
84 // create the list of relations
85 //
86 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
87 model = new RelationListModel(selectionModel);
88 displaylist = new JList(model);
89 displaylist.setSelectionModel(selectionModel);
90 displaylist.setCellRenderer(new OsmPrimitivRenderer());
91 displaylist.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
92 displaylist.addMouseListener(new MouseEventHandler());
93 add(new JScrollPane(displaylist), BorderLayout.CENTER);
94
95 // create the panel with buttons
96 //
97 JPanel buttonPanel = new JPanel(new GridLayout(1,3));
98
99 // the new action
100 //
101 NewAction newAction = new NewAction();
102 Layer.listeners.add(newAction);
103 buttonPanel.add(new SideButton(newAction), GBC.std());
104
105 // the edit action
106 //
107 editAction = new EditAction();
108 displaylist.addListSelectionListener(editAction);
109 buttonPanel.add(new SideButton(editAction), GBC.std());
110
111 // the duplicate action
112 //
113 DuplicateAction duplicateAction = new DuplicateAction();
114 displaylist.addListSelectionListener(duplicateAction);
115 buttonPanel.add(new SideButton(duplicateAction), GBC.std());
116
117 // the delete action
118 //
119 deleteAction = new DeleteAction();
120 displaylist.addListSelectionListener(deleteAction);
121 buttonPanel.add(new SideButton(deleteAction), GBC.eol());
122
123 // the select action
124 //
125 SelectAction selectAction = new SelectAction();
126 displaylist.addListSelectionListener(selectAction);
127 buttonPanel.add(new SideButton(selectAction), GBC.eol());
128
129 add(buttonPanel, BorderLayout.SOUTH);
130 displaylist.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "deleteRelation");
131 displaylist.getActionMap().put("deleteRelation", deleteAction);
132
133 popupMenu = new RelationDialogPopupMenu();
134
135 // register as layer listener
136 //
137 Layer.listeners.add(this);
138 }
139
140 @Override public void setVisible(boolean b) {
141 super.setVisible(b);
142 if (b) {
143 updateList();
144 }
145 }
146
147 protected int getNumRelations() {
148 if (Main.main.getCurrentDataSet() == null) return 0;
149 return Main.main.getCurrentDataSet().relations.size();
150 }
151
152 /**
153 * Replies the list of complete, non-deleted relations in the dataset <code>ds</code>,
154 * sorted by display name.
155 *
156 * @param ds the dataset
157 * @return the list of relations
158 */
159 protected ArrayList<Relation> getDisplayedRelationsInSortOrder(DataSet ds) {
160 ArrayList<Relation> relations = new ArrayList<Relation>(ds.relations.size());
161 for (Relation r: ds.relations ){
162 if (!r.isUsable() || !r.isVisible()) {
163 continue;
164 }
165 relations.add(r);
166 }
167
168 Collections.sort(
169 relations,
170 new Comparator<Relation>() {
171 NameFormatter formatter = DefaultNameFormatter.getInstance();
172 public int compare(Relation r1, Relation r2) {
173 return r1.getDisplayName(formatter).compareTo(r2.getDisplayName(formatter));
174 }
175 }
176 );
177 return relations;
178 }
179
180 public void updateList() {
181 if (Main.main.getCurrentDataSet() == null) {
182 model.setRelations(null);
183 return;
184 }
185 Relation[] selected = getAllSelected();
186
187 model.setRelations(getDisplayedRelationsInSortOrder(Main.main.getCurrentDataSet()));
188 if(model.getSize() > 0) {
189 setTitle(tr("Relations: {0}", model.getSize()));
190 } else {
191 setTitle(tr("Relations"));
192 }
193 selectRelations(selected);
194 }
195
196 public void activeLayerChange(Layer a, Layer b) {
197 updateList();
198 }
199
200 public void layerRemoved(Layer a) {
201 if (a instanceof OsmDataLayer) {
202 ((OsmDataLayer)a).listenerDataChanged.remove(this);
203 }
204 updateList();
205 }
206
207 public void layerAdded(Layer a) {
208 if (a instanceof OsmDataLayer) {
209 ((OsmDataLayer)a).listenerDataChanged.add(this);
210 }
211 }
212
213 public void dataChanged(OsmDataLayer l) {
214 updateList();
215 }
216
217 /**
218 * Returns the currently selected relation, or null.
219 *
220 * @return the currently selected relation, or null
221 */
222 public Relation getCurrentRelation() {
223 return (Relation) displaylist.getSelectedValue();
224 }
225
226 /**
227 * Adds a selection listener to the relation list.
228 *
229 * @param listener the listener to add
230 */
231 public void addListSelectionListener(ListSelectionListener listener) {
232 displaylist.addListSelectionListener(listener);
233 }
234
235 /**
236 * Removes a selection listener from the relation list.
237 *
238 * @param listener the listener to remove
239 */
240 public void removeListSelectionListener(ListSelectionListener listener) {
241 displaylist.removeListSelectionListener(listener);
242 }
243
244 /**
245 * @return The selected relation in the list
246 */
247 private Relation getSelected() {
248 if(model.getSize() == 1) {
249 displaylist.setSelectedIndex(0);
250 }
251 return (Relation) displaylist.getSelectedValue();
252 }
253
254 /**
255 * @return All selected relations in the list, possibly empty List
256 */
257 private Relation[] getAllSelected() {
258 return Arrays.asList(displaylist.getSelectedValues()).toArray(new Relation[0]);
259 }
260
261 /**
262 * Selects the relation <code>relation</code> in the list of relations.
263 *
264 * @param relation the relation
265 */
266 public void selectRelation(Relation relation) {
267 selectRelations(new Relation[] {relation});
268 }
269
270 /**
271 * Selects the relations <code>relations</code> in the list of relations.
272 *
273 * @param relations the relations (may be empty)
274 */
275 public void selectRelations(Relation[] relations) {
276 List<Integer> sel = new ArrayList<Integer>();
277 for (Relation r : relations) {
278 if (r == null) {
279 continue;
280 }
281 int idx = model.getIndexOfRelation(r);
282 if (idx != -1) {
283 sel.add(idx);
284 }
285 }
286 if (sel.isEmpty()) {
287 displaylist.clearSelection();
288 return;
289 } else {
290 int fst = Collections.min(sel);
291 displaylist.scrollRectToVisible(displaylist.getCellBounds(fst, fst));
292 }
293
294 int[] aSel = new int[sel.size()]; //FIXME: how to cast Integer[] -> int[] ?
295 for (int i=0; i<sel.size(); ++i) {
296 aSel[i] = sel.get(i);
297 }
298
299 displaylist.setSelectedIndices(aSel);
300 }
301
302 class MouseEventHandler extends MouseAdapter {
303 protected void setCurrentRelationAsSelection() {
304 Main.main.getCurrentDataSet().setSelected((Relation)displaylist.getSelectedValue());
305 }
306
307 protected void editCurrentRelation() {
308 new EditAction().launchEditor(getSelected());
309 }
310
311 @Override public void mouseClicked(MouseEvent e) {
312 if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) {
313 if (e.isControlDown()) {
314 editCurrentRelation();
315 } else {
316 setCurrentRelationAsSelection();
317 }
318 }
319 }
320 private void openPopup(MouseEvent e) {
321 Point p = e.getPoint();
322 int index = displaylist.locationToIndex(p);
323 if (index < 0) return;
324 if (!displaylist.getCellBounds(index, index).contains(e.getPoint()))
325 return;
326 if (! displaylist.isSelectedIndex(index)) {
327 displaylist.setSelectedIndex(index);
328 }
329 popupMenu.show(RelationListDialog.this, p.x, p.y-3);
330 }
331 @Override public void mousePressed(MouseEvent e) {
332 if (e.isPopupTrigger()) {
333 openPopup(e);
334 }
335 }
336 @Override public void mouseReleased(MouseEvent e) {
337 if (e.isPopupTrigger()) {
338 openPopup(e);
339 }
340 }
341 }
342
343 /**
344 * The edit action
345 *
346 */
347 class EditAction extends AbstractAction implements ListSelectionListener{
348 public EditAction() {
349 putValue(SHORT_DESCRIPTION,tr( "Open an editor for the selected relation"));
350 //putValue(NAME, tr("Edit"));
351 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
352 setEnabled(false);
353 }
354 protected Collection<RelationMember> getMembersForCurrentSelection(Relation r) {
355 Collection<RelationMember> members = new HashSet<RelationMember>();
356 Collection<OsmPrimitive> selection = Main.map.mapView.getEditLayer().data.getSelected();
357 for (RelationMember member: r.getMembers()) {
358 if (selection.contains(member.getMember())) {
359 members.add(member);
360 }
361 }
362 return members;
363 }
364
365 public void launchEditor(Relation toEdit) {
366 if (toEdit == null)
367 return;
368 RelationEditor.getEditor(Main.map.mapView.getEditLayer(),toEdit, getMembersForCurrentSelection(toEdit)).setVisible(true);
369 }
370
371 public void actionPerformed(ActionEvent e) {
372 if (!isEnabled())
373 return;
374 launchEditor(getSelected());
375 }
376
377 public void valueChanged(ListSelectionEvent e) {
378 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length == 1);
379 }
380 }
381
382 /**
383 * The delete action
384 *
385 */
386 class DeleteAction extends AbstractAction implements ListSelectionListener {
387 class AbortException extends Exception {}
388
389 public DeleteAction() {
390 putValue(SHORT_DESCRIPTION,tr("Delete the selected relation"));
391 //putValue(NAME, tr("Delete"));
392 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
393 setEnabled(false);
394 }
395
396 protected void deleteRelation(Relation toDelete) {
397 if (toDelete == null)
398 return;
399 org.openstreetmap.josm.actions.mapmode.DeleteAction.deleteRelation(
400 Main.main.getEditLayer(),
401 toDelete
402 );
403 }
404
405 public void actionPerformed(ActionEvent e) {
406 if (!isEnabled()) return;
407 int [] idx = displaylist.getSelectedIndices();
408 ArrayList<Relation> toDelete = new ArrayList<Relation>(idx.length);
409 for (int i: idx) {
410 toDelete.add(model.getRelation(i));
411 }
412 for (Relation r: toDelete) {
413 deleteRelation(r);
414 }
415 }
416
417 public void valueChanged(ListSelectionEvent e) {
418 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0);
419 }
420 }
421
422 /**
423 * The edit action
424 *
425 */
426 class NewAction extends AbstractAction implements LayerChangeListener{
427 public NewAction() {
428 putValue(SHORT_DESCRIPTION,tr("Create a new relation"));
429 //putValue(NAME, tr("New"));
430 putValue(SMALL_ICON, ImageProvider.get("dialogs", "addrelation"));
431 setEnabled(false);
432 }
433
434 public void run() {
435 RelationEditor.getEditor(Main.map.mapView.getEditLayer(),null, null).setVisible(true);
436 }
437
438 public void actionPerformed(ActionEvent e) {
439 run();
440 }
441
442 protected void updateEnabledState() {
443 setEnabled(Main.main != null && Main.main.getEditLayer() != null);
444 }
445
446 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
447 updateEnabledState();
448 }
449
450 public void layerAdded(Layer newLayer) {
451 updateEnabledState();
452 }
453
454 public void layerRemoved(Layer oldLayer) {
455 updateEnabledState();
456 }
457 }
458
459 /**
460 * Creates a new relation with a copy of the current editor state
461 *
462 */
463 class DuplicateAction extends AbstractAction implements ListSelectionListener {
464 public DuplicateAction() {
465 putValue(SHORT_DESCRIPTION, tr("Create a copy of this relation and open it in another editor window"));
466 putValue(SMALL_ICON, ImageProvider.get("duplicate"));
467 //putValue(NAME, tr("Duplicate"));
468 updateEnabledState();
469 }
470
471 public void launchEditorForDuplicate(Relation original) {
472 Relation copy = new Relation(original.getId());
473 copy.cloneFrom(original);
474 copy.clearOsmId();
475 copy.setModified(true);
476 RelationEditor editor = RelationEditor.getEditor(
477 Main.main.getEditLayer(),
478 copy,
479 null /* no selected members */
480 );
481 editor.setVisible(true);
482 }
483
484 public void actionPerformed(ActionEvent e) {
485 if (!isEnabled())
486 return;
487 launchEditorForDuplicate(getSelected());
488 }
489
490 protected void updateEnabledState() {
491 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length == 1);
492 }
493
494 public void valueChanged(ListSelectionEvent e) {
495 updateEnabledState();
496 }
497 }
498
499 /**
500 * Sets the current selection to the list of relations selected in this dialog
501 *
502 */
503 class SelectAction extends AbstractAction implements ListSelectionListener{
504 public SelectAction() {
505 putValue(SHORT_DESCRIPTION,tr("Set the current selection to the list of selected relations"));
506 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
507 setEnabled(false);
508 }
509
510 public void actionPerformed(ActionEvent e) {
511 if (!isEnabled()) return;
512 int [] idx = displaylist.getSelectedIndices();
513 if (idx == null || idx.length == 0) return;
514 ArrayList<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(idx.length);
515 for (int i: idx) {
516 selection.add(model.getRelation(i));
517 }
518 Main.map.mapView.getEditLayer().data.setSelected(selection);
519 Main.map.mapView.getEditLayer().data.fireSelectionChanged();
520 }
521
522 public void valueChanged(ListSelectionEvent e) {
523 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0);
524 }
525 }
526
527 /**
528 * Sets the current selection to the list of relations selected in this dialog
529 *
530 */
531 class SelectMembersAction extends AbstractAction implements ListSelectionListener{
532 public SelectMembersAction() {
533 putValue(SHORT_DESCRIPTION,tr("Select the members of all selected relations"));
534 putValue(SMALL_ICON, ImageProvider.get("selectall"));
535 putValue(NAME, tr("Select members"));
536 updateEnabledState();
537 }
538
539 public void actionPerformed(ActionEvent e) {
540 if (!isEnabled()) return;
541 List<Relation> relations = model.getSelectedRelations();
542 HashSet<OsmPrimitive> members = new HashSet<OsmPrimitive>();
543 for(Relation r: relations) {
544 members.addAll(r.getMemberPrimitives());
545 }
546 Main.map.mapView.getEditLayer().data.setSelected(members);
547 }
548
549 protected void updateEnabledState() {
550 setEnabled(displaylist.getSelectedIndices() != null && displaylist.getSelectedIndices().length > 0);
551 }
552
553 public void valueChanged(ListSelectionEvent e) {
554 updateEnabledState();
555 }
556 }
557
558
559 class DownloadMembersAction extends AbstractAction implements ListSelectionListener{
560
561 public DownloadMembersAction() {
562 putValue(SHORT_DESCRIPTION,tr("Download all members of the selected relations"));
563 putValue(NAME, tr("Download members"));
564 putValue(SMALL_ICON, ImageProvider.get("dialogs", "downloadincomplete"));
565 putValue("help", ht("/Dialog/RelationList#DownloadMembers"));
566 updateEnabledState();
567 }
568
569 protected void updateEnabledState() {
570 setEnabled(! model.getSelectedNonNewRelations().isEmpty());
571 }
572
573 public void valueChanged(ListSelectionEvent e) {
574 updateEnabledState();
575 }
576
577 public void actionPerformed(ActionEvent e) {
578 List<Relation> relations = model.getSelectedNonNewRelations();
579 if (relations.isEmpty())
580 return;
581 Main.worker.submit(new GenericRelationEditor.DownloadTask(
582 model.getSelectedNonNewRelations(),
583 Main.map.mapView.getEditLayer(), null));
584 }
585 }
586
587 private static class RelationListModel extends AbstractListModel {
588 private ArrayList<Relation> relations;
589 private DefaultListSelectionModel selectionModel;
590
591 public RelationListModel(DefaultListSelectionModel selectionModel) {
592 this.selectionModel = selectionModel;
593 }
594
595 public ArrayList<Relation> getRelations() {
596 return relations;
597 }
598
599 public Relation getRelation(int idx) {
600 return relations.get(idx);
601 }
602
603 public void setRelations(ArrayList<Relation> relations) {
604 this.relations = relations;
605 fireIntervalAdded(this, 0, getSize());
606 }
607
608 public Object getElementAt(int index) {
609 if (relations == null) return null;
610 return relations.get(index);
611 }
612
613 public int getSize() {
614 if (relations == null) return 0;
615 return relations.size();
616 }
617
618 public int getIndexOfRelation(Relation relation) {
619 if (relation == null) return -1;
620 return relations.indexOf(relation);
621 }
622
623 /**
624 * Replies the list of selected, non-new relations. Empty list,
625 * if there are no selected, non-new relations.
626 *
627 * @return the list of selected, non-new relations.
628 */
629 public List<Relation> getSelectedNonNewRelations() {
630 ArrayList<Relation> ret = new ArrayList<Relation>();
631 for (int i=0; i<getSize();i++) {
632 if (!selectionModel.isSelectedIndex(i)) {
633 continue;
634 }
635 if (relations.get(i).isNew()) {
636 continue;
637 }
638 ret.add(relations.get(i));
639 }
640 return ret;
641 }
642
643 /**
644 * Replies the list of selected relations. Empty list,
645 * if there are no selected relations.
646 *
647 * @return the list of selected, non-new relations.
648 */
649 public List<Relation> getSelectedRelations() {
650 ArrayList<Relation> ret = new ArrayList<Relation>();
651 for (int i=0; i<getSize();i++) {
652 if (!selectionModel.isSelectedIndex(i)) {
653 continue;
654 }
655 ret.add(relations.get(i));
656 }
657 return ret;
658 }
659 }
660
661 class RelationDialogPopupMenu extends JPopupMenu {
662
663 protected void build() {
664 // -- download members action
665 //
666 DownloadMembersAction downloadMembersAction = new DownloadMembersAction();
667 displaylist.addListSelectionListener(downloadMembersAction);
668 add(downloadMembersAction);
669
670 // -- select members action
671 //
672 SelectMembersAction selectMembersAction = new SelectMembersAction();
673 displaylist.addListSelectionListener(selectMembersAction);
674 add(selectMembersAction);
675 }
676
677 public RelationDialogPopupMenu() {
678 build();
679 }
680 }
681}
Note: See TracBrowser for help on using the repository browser.