source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/RelationEditor.java@ 1303

Last change on this file since 1303 was 1303, checked in by ulfl, 16 years ago

fix compilation with JAVA 1.5

  • Property svn:eol-style set to native
File size: 23.4 KB
Line 
1package org.openstreetmap.josm.gui.dialogs;
2
3import static org.openstreetmap.josm.tools.I18n.marktr;
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Dimension;
8import java.awt.GridBagLayout;
9import java.awt.GridLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.ActionListener;
12import java.awt.event.KeyEvent;
13import java.beans.PropertyChangeEvent;
14import java.beans.PropertyChangeListener;
15import java.io.IOException;
16import java.text.Collator;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.Map.Entry;
23
24import javax.swing.BoxLayout;
25import javax.swing.JButton;
26import javax.swing.JFrame;
27import javax.swing.JLabel;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31import javax.swing.JTabbedPane;
32import javax.swing.JTable;
33import javax.swing.ListSelectionModel;
34import javax.swing.event.ListSelectionEvent;
35import javax.swing.event.ListSelectionListener;
36import javax.swing.event.TableModelEvent;
37import javax.swing.event.TableModelListener;
38import javax.swing.table.DefaultTableModel;
39
40import org.openstreetmap.josm.Main;
41import org.openstreetmap.josm.command.AddCommand;
42import org.openstreetmap.josm.command.ChangeCommand;
43import org.openstreetmap.josm.data.osm.DataSet;
44import org.openstreetmap.josm.data.osm.DataSource;
45import org.openstreetmap.josm.data.osm.OsmPrimitive;
46import org.openstreetmap.josm.data.osm.Relation;
47import org.openstreetmap.josm.data.osm.RelationMember;
48import org.openstreetmap.josm.data.osm.visitor.MergeVisitor;
49import org.openstreetmap.josm.gui.OsmPrimitivRenderer;
50import org.openstreetmap.josm.io.OsmServerObjectReader;
51import org.openstreetmap.josm.tools.GBC;
52import org.openstreetmap.josm.tools.ImageProvider;
53import org.xml.sax.SAXException;
54
55/**
56 * This dialog is for editing relations.
57 *
58 * In the basic form, it provides two tables, one with the relation tags
59 * and one with the relation members. (Relation tags can be edited through
60 * the normal properties dialog as well, if you manage to get a relation
61 * selected!)
62 *
63 * @author Frederik Ramm <frederik@remote.org>
64 *
65 */
66public class RelationEditor extends JFrame {
67
68 /**
69 * The relation that this editor is working on, and the clone made for
70 * editing.
71 */
72 private final Relation relation;
73 private final Relation clone;
74 private JLabel status;
75
76 /**
77 * True if the relation is ordered (API 0.6). False for API 0.5.
78 */
79 boolean ordered;
80
81 /**
82 * The property data.
83 */
84 private final DefaultTableModel propertyData = new DefaultTableModel() {
85 @Override public boolean isCellEditable(int row, int column) {
86 return true;
87 }
88 @Override public Class<?> getColumnClass(int columnIndex) {
89 return String.class;
90 }
91 };
92
93 /**
94 * The membership data.
95 */
96 private final DefaultTableModel memberData = new DefaultTableModel() {
97 @Override public boolean isCellEditable(int row, int column) {
98 return column == 0;
99 }
100 @Override public Class<?> getColumnClass(int columnIndex) {
101 return columnIndex == 1 ? OsmPrimitive.class : String.class;
102 }
103 };
104
105 /**
106 * The properties and membership lists.
107 */
108 private final JTable propertyTable = new JTable(propertyData);
109 private final JTable memberTable = new JTable(memberData);
110
111 // =================== FIXME FIXME FIXME =====================
112 // As soon as API 0.5 is dead, drop all the collation stuff from here ...
113
114 /**
115 * Collator for sorting the roles and entries of the member table.
116 */
117 private static final Collator collator;
118 static {
119 collator = Collator.getInstance();
120 collator.setStrength(Collator.PRIMARY);
121 }
122
123 /**
124 * Compare role strings.
125 */
126 private static int compareRole(String s1, String s2) {
127 int last1 = s1.lastIndexOf('_');
128 if (last1 > 0) {
129 int last2 = s2.lastIndexOf('_');
130 if (last2 == last1) {
131 String prefix1 = s1.substring(0, last1);
132 String prefix2 = s2.substring(0, last2);
133
134 if (prefix1.equalsIgnoreCase(prefix2)) {
135 // Both roles have the same prefix, now determine the
136 // suffix.
137 String suffix1 = s1.substring(last1 + 1, s1.length());
138 String suffix2 = s2.substring(last2 + 1, s2.length());
139
140 if (suffix1.matches("\\d+") && suffix2.matches("\\d+")) {
141 // Suffix is an number -> compare it.
142 int i1 = Integer.parseInt(suffix1);
143 int i2 = Integer.parseInt(suffix2);
144
145 return i1 - i2;
146 }
147 }
148 }
149 }
150 if(s1.length() == 0 && s2.length() != 0)
151 return 1;
152 else if(s2.length() == 0 && s1.length() != 0)
153 return -1;
154
155 // Default handling if the role name is nothing like "stop_xx"
156 return collator.compare(s1, s2);
157 }
158
159
160 /**
161 * Compare two OsmPrimitives.
162 */
163 private static int compareMembers(OsmPrimitive o1, OsmPrimitive o2) {
164 return collator.compare(o1.getName(), o2.getName());
165 }
166
167 private final Comparator<RelationMember> memberComparator = new Comparator<RelationMember>() {
168 public int compare(RelationMember r1, RelationMember r2) {
169 int roleResult = compareRole(r1.role, r2.role);
170
171 if (roleResult == 0)
172 roleResult = compareMembers(r1.member, r2.member);
173
174 return roleResult;
175 }
176 };
177
178 // =================== FIXME FIXME FIXME =====================
179 // ... until here, and also get rid of the "Collections.sort..." below.
180
181 /**
182 * Creates a new relation editor for the given relation. The relation
183 * will be saved if the user selects "ok" in the editor.
184 *
185 * If no relation is given, will create an editor for a new relation.
186 *
187 * @param relation relation to edit, or null to create a new one.
188 */
189 public RelationEditor(Relation relation)
190 {
191 this(relation, null);
192 }
193
194 /**
195 * Creates a new relation editor for the given relation. The relation
196 * will be saved if the user selects "ok" in the editor.
197 *
198 * If no relation is given, will create an editor for a new relation.
199 *
200 * @param relation relation to edit, or null to create a new one.
201 */
202 public RelationEditor(Relation relation, Collection<RelationMember> selectedMembers )
203 {
204 super(relation == null ? tr("Create new relation") :
205 relation.id == 0 ? tr ("Edit new relation") :
206 tr("Edit relation #{0}", relation.id));
207 this.relation = relation;
208
209 ordered = Main.pref.get("osm-server.version", "0.5").equals("0.6");
210
211 if (relation == null) {
212 // create a new relation
213 this.clone = new Relation();
214 } else {
215 // edit an existing relation
216 this.clone = new Relation(relation);
217 if (!ordered) Collections.sort(this.clone.members, memberComparator);
218 }
219
220 getContentPane().setLayout(new BorderLayout());
221 JTabbedPane tabPane = new JTabbedPane();
222 getContentPane().add(tabPane, BorderLayout.CENTER);
223
224 // (ab)use JOptionPane to make this look familiar;
225 // hook up with JOptionPane's property change event
226 // to detect button click
227 final JOptionPane okcancel = new JOptionPane("",
228 JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION, null);
229 getContentPane().add(okcancel, BorderLayout.SOUTH);
230
231 okcancel.addPropertyChangeListener(new PropertyChangeListener() {
232 public void propertyChange(PropertyChangeEvent event) {
233 if (event.getPropertyName().equals(JOptionPane.VALUE_PROPERTY) && event.getNewValue() != null) {
234 if ((Integer)event.getNewValue() == JOptionPane.OK_OPTION) {
235 // clicked ok!
236 if (RelationEditor.this.relation == null) {
237 Main.main.undoRedo.add(new AddCommand(clone));
238 DataSet.fireSelectionChanged(Main.ds.getSelected());
239 } else if (!RelationEditor.this.relation.realEqual(clone, true)) {
240 Main.main.undoRedo.add(new ChangeCommand(RelationEditor.this.relation, clone));
241 DataSet.fireSelectionChanged(Main.ds.getSelected());
242 }
243 }
244 setVisible(false);
245 }
246 }
247 });
248
249 JLabel help = new JLabel("<html><em>"+
250 tr("This is the basic relation editor which allows you to change the relation's tags " +
251 "as well as the members. In addition to this we should have a smart editor that " +
252 "detects the type of relationship and limits your choices in a sensible way.")+"</em></html>");
253 getContentPane().add(help, BorderLayout.NORTH);
254 try { setAlwaysOnTop(true); } catch (SecurityException sx) {}
255
256 // Basic Editor panel has two blocks;
257 // a tag table at the top and a membership list below.
258
259 // setting up the properties table
260
261 propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
262 propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
263 propertyData.addTableModelListener(new TableModelListener() {
264 public void tableChanged(TableModelEvent tme) {
265 if (tme.getType() == TableModelEvent.UPDATE) {
266 int row = tme.getFirstRow();
267
268 if (!(tme.getColumn() == 0 && row == propertyData.getRowCount() -1)) {
269 clone.entrySet().clear();
270 for (int i = 0; i < propertyData.getRowCount(); i++) {
271 String key = propertyData.getValueAt(i, 0).toString();
272 String value = propertyData.getValueAt(i, 1).toString();
273 if (key.length() > 0 && value.length() > 0) clone.put(key, value);
274 }
275 refreshTables();
276 }
277 }
278 }
279 });
280 ListSelectionModel lsm2 = propertyTable.getSelectionModel();
281 lsm2.addListSelectionListener(new ListSelectionListener() {
282 public void valueChanged(ListSelectionEvent e) {
283 Main.ds.setSelected(RelationEditor.this.relation);
284 }
285 });
286 propertyTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
287
288 // setting up the member table
289
290 memberData.setColumnIdentifiers(new String[]{tr("Role"),tr("Occupied By")});
291 memberTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
292 memberTable.getColumnModel().getColumn(1).setCellRenderer(new OsmPrimitivRenderer());
293 memberData.addTableModelListener(new TableModelListener() {
294 public void tableChanged(TableModelEvent tme) {
295 if (tme.getType() == TableModelEvent.UPDATE && tme.getColumn() == 0) {
296 int row = tme.getFirstRow();
297 clone.members.get(row).role = memberData.getValueAt(row, 0).toString();
298 }
299 }
300 });
301 ListSelectionModel lsm = memberTable.getSelectionModel();
302 lsm.addListSelectionListener(new ListSelectionListener() {
303 @Override public void valueChanged(ListSelectionEvent e) {
304 ArrayList<OsmPrimitive> sel;
305 int cnt = memberTable.getSelectedRowCount();
306 if(cnt > 0)
307 {
308 sel = new ArrayList<OsmPrimitive>(cnt);
309 for (int i : memberTable.getSelectedRows())
310 sel.add((OsmPrimitive)memberTable.getValueAt(i, 1));
311 }
312 else
313 {
314 cnt = memberTable.getRowCount();
315 sel = new ArrayList<OsmPrimitive>(cnt);
316 for (int i = 0; i < cnt; ++i)
317 sel.add((OsmPrimitive)memberTable.getValueAt(i, 1));
318 }
319 Main.ds.setSelected(sel);
320 }
321 });
322 memberTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
323
324 // combine both tables and wrap them in a scrollPane
325 JPanel bothTables = new JPanel();
326 bothTables.setLayout(new GridBagLayout());
327 bothTables.add(new JLabel(tr("Tags (empty value deletes tag)")), GBC.eol().fill(GBC.HORIZONTAL));
328 bothTables.add(new JScrollPane(propertyTable), GBC.eop().fill(GBC.BOTH));
329 bothTables.add(status = new JLabel(tr("Members")), GBC.eol().fill(GBC.HORIZONTAL));
330 if (ordered) {
331 JPanel upDownPanel = new JPanel();
332 upDownPanel.setLayout(new BoxLayout(upDownPanel, BoxLayout.Y_AXIS));
333
334 upDownPanel.add(createButton(null, "moveup", tr("Move the currently selected members up"),
335 KeyEvent.VK_U, new ActionListener() {
336 public void actionPerformed(ActionEvent e) {
337 moveMembers(-1);
338 }
339 }));
340 upDownPanel.add(createButton(null, "movedown", tr("Move the currently selected members down"),
341 KeyEvent.VK_N, new ActionListener() {
342 public void actionPerformed(ActionEvent e) {
343 moveMembers(1);
344 }
345 }));
346
347
348 bothTables.add(new JScrollPane(memberTable), GBC.std().fill(GBC.BOTH));
349 bothTables.add(upDownPanel, GBC.eol().fill(GBC.VERTICAL));
350 } else {
351 bothTables.add(new JScrollPane(memberTable), GBC.eol().fill(GBC.BOTH));
352 }
353
354 JPanel buttonPanel = new JPanel(new GridLayout(1,3));
355
356 buttonPanel.add(createButton(marktr("Add Selected"),"addselected",
357 tr("Add all currently selected objects as members"), KeyEvent.VK_A, new ActionListener() {
358 public void actionPerformed(ActionEvent e) {
359 addSelected();
360 }
361 }));
362
363 buttonPanel.add(createButton(marktr("Delete Selected"),"deleteselected",
364 tr("Delete all currently selected objects from relation"), KeyEvent.VK_R, new ActionListener() {
365 public void actionPerformed(ActionEvent e) {
366 deleteSelected();
367 }
368 }));
369
370 buttonPanel.add(createButton(marktr("Delete"),"delete",
371 tr("Remove the member in the current table row from this relation"), KeyEvent.VK_D, new ActionListener() {
372 public void actionPerformed(ActionEvent e) {
373 int[] rows = memberTable.getSelectedRows();
374 RelationMember mem = new RelationMember();
375 for (int row : rows) {
376 mem.role = memberTable.getValueAt(row, 0).toString();
377 mem.member = (OsmPrimitive) memberTable.getValueAt(row, 1);
378 clone.members.remove(mem);
379 }
380 refreshTables();
381 }
382 }));
383
384 /*buttonPanel.add(createButton(marktr("Select"),"select",
385 tr("Highlight the member from the current table row as JOSM's selection"), KeyEvent.VK_S, new ActionListener() {
386 public void actionPerformed(ActionEvent e) {
387 ArrayList<OsmPrimitive> sel;
388 int cnt = memberTable.getSelectedRowCount();
389 if(cnt > 0)
390 {
391 sel = new ArrayList<OsmPrimitive>(cnt);
392 for (int i : memberTable.getSelectedRows())
393 sel.add((OsmPrimitive)memberTable.getValueAt(i, 1));
394 }
395 else
396 {
397 cnt = memberTable.getRowCount();
398 sel = new ArrayList<OsmPrimitive>(cnt);
399 for (int i = 0; i < cnt; ++i)
400 sel.add((OsmPrimitive)memberTable.getValueAt(i, 1));
401 }
402 Main.ds.setSelected(sel);
403 }
404 }));*/
405 buttonPanel.add(createButton(marktr("Download Members"),"downloadincomplete",
406 tr("Download all incomplete ways and nodes in relation"), KeyEvent.VK_L, new ActionListener() {
407 public void actionPerformed(ActionEvent e) {
408 downloadRelationMembers();
409 refreshTables();
410 }
411 }));
412
413 bothTables.add(buttonPanel, GBC.eop().fill(GBC.HORIZONTAL));
414
415 tabPane.add(bothTables, "Basic");
416
417 refreshTables();
418
419 if (selectedMembers != null) {
420 boolean scrolled = false;
421 for (int i = 0; i < memberData.getRowCount(); i++) {
422 for (RelationMember m : selectedMembers) {
423 if (m.member == memberData.getValueAt(i, 1)
424 && m.role.equals(memberData.getValueAt(i, 0))) {
425 memberTable.addRowSelectionInterval(i, i);
426 if (!scrolled) {
427 // Ensure that the first member is visible
428 memberTable.scrollRectToVisible(memberTable.getCellRect(i, 0, true));
429 scrolled = true;
430 }
431 break;
432 }
433 }
434
435 }
436 }
437
438 setSize(new Dimension(600, 500));
439 setLocationRelativeTo(Main.parent);
440 }
441
442 private void refreshTables() {
443 // re-load property data
444
445 propertyData.setRowCount(0);
446 for (Entry<String, String> e : clone.entrySet()) {
447 propertyData.addRow(new Object[]{e.getKey(), e.getValue()});
448 }
449 propertyData.addRow(new Object[]{"", ""});
450
451 // re-load membership data
452
453 memberData.setRowCount(0);
454 for (RelationMember em : clone.members) {
455 memberData.addRow(new Object[]{em.role, em.member});
456 }
457 status.setText(tr("Members: {0}", clone.members.size()));
458 }
459
460 private JButton createButton(String name, String iconName, String tooltip, int mnemonic, ActionListener actionListener) {
461 JButton b = new JButton(tr(name), ImageProvider.get("dialogs", iconName));
462 b.setActionCommand(name);
463 b.addActionListener(actionListener);
464 b.setToolTipText(tooltip);
465 b.setMnemonic(mnemonic);
466 b.putClientProperty("help", "Dialog/Properties/"+name);
467 return b;
468 }
469
470 private void addSelected() {
471 for (OsmPrimitive p : Main.ds.getSelected()) {
472 boolean skip = false;
473 // ordered relations may have the same member multiple times.
474 // TODO: visual indication of the fact that one is there more than once?
475 if (!ordered)
476 {
477 for (RelationMember rm : clone.members) {
478 if (rm.member == p || p == relation)
479 {
480 skip = true;
481 break;
482 }
483 }
484 }
485 if (!skip)
486 {
487 RelationMember em = new RelationMember();
488 em.member = p;
489 em.role = "";
490 // when working with ordered relations, we make an effort to
491 // add the element before the first selected member.
492 int[] rows = memberTable.getSelectedRows();
493 if (ordered && rows.length > 0) {
494 clone.members.add(rows[0], em);
495 } else {
496 clone.members.add(em);
497 }
498 }
499 }
500 refreshTables();
501 }
502
503 private void deleteSelected() {
504 for (OsmPrimitive p : Main.ds.getSelected()) {
505 Relation c = new Relation(clone);
506 for (RelationMember rm : c.members) {
507 if (rm.member == p)
508 {
509 RelationMember mem = new RelationMember();
510 mem.role = rm.role;
511 mem.member = rm.member;
512 clone.members.remove(mem);
513 }
514 }
515 }
516 refreshTables();
517 }
518
519 private void moveMembers(int direction) {
520 int[] rows = memberTable.getSelectedRows();
521 if (rows.length == 0) return;
522
523 // check if user attempted to move anything beyond the boundary of the list
524 if (rows[0] + direction < 0) return;
525 if (rows[rows.length-1] + direction >= clone.members.size()) return;
526
527 RelationMember m[] = new RelationMember[clone.members.size()];
528
529 // first move all selected rows from the member list into a new array,
530 // displaced by the move amount
531 for (Integer i: rows) {
532 m[i+direction] = clone.members.get(i);
533 clone.members.set(i, null);
534 }
535
536 // now fill the empty spots in the destination array with the remaining
537 // elements.
538 int i = 0;
539 for (RelationMember rm : clone.members) {
540 if (rm != null) {
541 while (m[i] != null) i++;
542 m[i++] = rm;
543 }
544 }
545
546 // and write the array back into the member list.
547 clone.members.clear();
548 clone.members.addAll(Arrays.asList(m));
549 refreshTables();
550 ListSelectionModel lsm = memberTable.getSelectionModel();
551 lsm.setValueIsAdjusting(true);
552 for (Integer j: rows) {
553 lsm.addSelectionInterval(j + direction, j + direction);
554 }
555 lsm.setValueIsAdjusting(false);
556 }
557
558 private void downloadRelationMembers() {
559 boolean download = false;
560 for (RelationMember member : clone.members) {
561 if (member.member.incomplete) {
562 download = true;
563 break;
564 }
565 }
566 if (download) {
567 OsmServerObjectReader reader = new OsmServerObjectReader(clone.id, OsmServerObjectReader.TYPE_REL, true);
568 try {
569 DataSet dataSet = reader.parseOsm();
570 if (dataSet != null) {
571 final MergeVisitor visitor = new MergeVisitor(Main.main
572 .editLayer().data, dataSet);
573 for (final OsmPrimitive osm : dataSet.allPrimitives())
574 osm.visit(visitor);
575 visitor.fixReferences();
576
577 // copy the merged layer's data source info
578 for (DataSource src : dataSet.dataSources)
579 Main.main.editLayer().data.dataSources.add(src);
580 Main.main.editLayer().fireDataChange();
581
582 if (visitor.conflicts.isEmpty())
583 return;
584 final ConflictDialog dlg = Main.map.conflictDialog;
585 dlg.add(visitor.conflicts);
586 JOptionPane.showMessageDialog(Main.parent,
587 tr("There were conflicts during import."));
588 if (!dlg.isVisible())
589 dlg.action
590 .actionPerformed(new ActionEvent(this, 0, ""));
591 }
592
593 } catch (SAXException e) {
594 e.printStackTrace();
595 JOptionPane.showMessageDialog(this,tr("Error parsing server response.")+": "+e.getMessage(),
596 tr("Error"), JOptionPane.ERROR_MESSAGE);
597 } catch (IOException e) {
598 e.printStackTrace();
599 JOptionPane.showMessageDialog(this,tr("Cannot connect to server.")+": "+e.getMessage(),
600 tr("Error"), JOptionPane.ERROR_MESSAGE);
601 }
602 }
603 }
604}
Note: See TracBrowser for help on using the repository browser.