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

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

cleanup color preferences a lot

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