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

Last change on this file since 810 was 810, checked in by stoecker, 16 years ago

RelationEditor now selects members on entry, fix minor problem in Lambert projection

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