source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/tags/PasteTagsConflictResolverDialog.java@ 3083

Last change on this file since 3083 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

  • Property svn:eol-style set to native
File size: 19.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.tags;
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.Component;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.Font;
12import java.awt.GridBagConstraints;
13import java.awt.GridBagLayout;
14import java.awt.Insets;
15import java.awt.event.ActionEvent;
16import java.beans.PropertyChangeEvent;
17import java.beans.PropertyChangeListener;
18import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.List;
21import java.util.Map;
22import java.util.logging.Logger;
23
24import javax.swing.AbstractAction;
25import javax.swing.Action;
26import javax.swing.ImageIcon;
27import javax.swing.JDialog;
28import javax.swing.JLabel;
29import javax.swing.JOptionPane;
30import javax.swing.JPanel;
31import javax.swing.JTabbedPane;
32import javax.swing.JTable;
33import javax.swing.UIManager;
34import javax.swing.table.DefaultTableColumnModel;
35import javax.swing.table.DefaultTableModel;
36import javax.swing.table.TableCellRenderer;
37import javax.swing.table.TableColumn;
38
39import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
40import org.openstreetmap.josm.data.osm.TagCollection;
41import org.openstreetmap.josm.gui.SideButton;
42import org.openstreetmap.josm.tools.ImageProvider;
43import org.openstreetmap.josm.tools.WindowGeometry;
44
45public class PasteTagsConflictResolverDialog extends JDialog implements PropertyChangeListener {
46 static private final Map<OsmPrimitiveType, String> PANE_TITLES;
47 static {
48 PANE_TITLES = new HashMap<OsmPrimitiveType, String>();
49 PANE_TITLES.put(OsmPrimitiveType.NODE, tr("Tags from nodes"));
50 PANE_TITLES.put(OsmPrimitiveType.WAY, tr("Tags from ways"));
51 PANE_TITLES.put(OsmPrimitiveType.RELATION, tr("Tags from relations"));
52 }
53
54 private enum Mode {
55 RESOLVING_ONE_TAGCOLLECTION_ONLY,
56 RESOLVING_TYPED_TAGCOLLECTIONS
57 }
58
59 private TagConflictResolver allPrimitivesResolver;
60 private Map<OsmPrimitiveType, TagConflictResolver> resolvers;
61 private JTabbedPane tpResolvers;
62 private Mode mode;
63 private boolean canceled = false;
64
65 private ImageIcon iconResolved;
66 private ImageIcon iconUnresolved;
67 private StatisticsTableModel statisticsModel;
68 private JPanel pnlTagResolver;
69
70 public PasteTagsConflictResolverDialog(Component owner) {
71 super(JOptionPane.getFrameForComponent(owner),true);
72 build();
73 iconResolved = ImageProvider.get("dialogs/conflict", "tagconflictresolved");
74 iconUnresolved = ImageProvider.get("dialogs/conflict", "tagconflictunresolved");
75 }
76
77 protected void build() {
78 setTitle(tr("Conflicts in pasted tags"));
79 allPrimitivesResolver = new TagConflictResolver();
80 resolvers = new HashMap<OsmPrimitiveType, TagConflictResolver>();
81 for (OsmPrimitiveType type: OsmPrimitiveType.values()) {
82 resolvers.put(type, new TagConflictResolver());
83 resolvers.get(type).getModel().addPropertyChangeListener(this);
84 }
85 tpResolvers = new JTabbedPane();
86 getContentPane().setLayout(new GridBagLayout());
87 mode = null;
88 GridBagConstraints gc = new GridBagConstraints();
89 gc.gridx = 0;
90 gc.gridy = 0;
91 gc.fill = GridBagConstraints.HORIZONTAL;
92 gc.weightx = 1.0;
93 gc.weighty = 0.0;
94 getContentPane().add(buildSourceAndTargetInfoPanel(), gc);
95 gc.gridx = 0;
96 gc.gridy = 1;
97 gc.fill = GridBagConstraints.BOTH;
98 gc.weightx = 1.0;
99 gc.weighty = 1.0;
100 getContentPane().add(pnlTagResolver = new JPanel(), gc);
101 gc.gridx = 0;
102 gc.gridy = 2;
103 gc.fill = GridBagConstraints.HORIZONTAL;
104 gc.weightx = 1.0;
105 gc.weighty = 0.0;
106 getContentPane().add(buildButtonPanel(), gc);
107 }
108
109 protected JPanel buildButtonPanel() {
110 JPanel pnl = new JPanel();
111 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
112
113 // -- apply button
114 ApplyAction applyAction = new ApplyAction();
115 allPrimitivesResolver.getModel().addPropertyChangeListener(applyAction);
116 for (OsmPrimitiveType type: resolvers.keySet()) {
117 resolvers.get(type).getModel().addPropertyChangeListener(applyAction);
118 }
119 pnl.add(new SideButton(applyAction));
120
121 // -- cancel button
122 CancelAction cancelAction = new CancelAction();
123 pnl.add(new SideButton(cancelAction));
124
125 return pnl;
126 }
127
128 protected JPanel buildSourceAndTargetInfoPanel() {
129 JPanel pnl = new JPanel();
130 pnl.setLayout(new BorderLayout());
131 statisticsModel = new StatisticsTableModel();
132 pnl.add(new StatisticsInfoTable(statisticsModel), BorderLayout.CENTER);
133 return pnl;
134 }
135
136 /**
137 * Initializes the conflict resolver for a specific type of primitives
138 *
139 * @param type the type of primitives
140 * @param tc the tags belonging to this type of primitives
141 * @param targetStatistics histogram of paste targets, number of primitives of each type in the paste target
142 */
143 protected void initResolver(OsmPrimitiveType type, TagCollection tc, Map<OsmPrimitiveType,Integer> targetStatistics) {
144 resolvers.get(type).getModel().populate(tc,tc.getKeysWithMultipleValues());
145 resolvers.get(type).getModel().prepareDefaultTagDecisions();
146 if (!tc.isEmpty() && targetStatistics.get(type) != null && targetStatistics.get(type) > 0) {
147 tpResolvers.add(PANE_TITLES.get(type), resolvers.get(type));
148 }
149 }
150
151 /**
152 * Populates the conflict resolver with one tag collection
153 *
154 * @param tagsForAllPrimitives the tag collection
155 * @param sourceStatistics histogram of tag source, number of primitives of each type in the source
156 * @param targetStatistics histogram of paste targets, number of primitives of each type in the paste target
157 */
158 public void populate(TagCollection tagsForAllPrimitives, Map<OsmPrimitiveType, Integer> sourceStatistics, Map<OsmPrimitiveType,Integer> targetStatistics) {
159 mode = Mode.RESOLVING_ONE_TAGCOLLECTION_ONLY;
160 tagsForAllPrimitives = tagsForAllPrimitives == null? new TagCollection() : tagsForAllPrimitives;
161 sourceStatistics = sourceStatistics == null ? new HashMap<OsmPrimitiveType, Integer>() :sourceStatistics;
162 targetStatistics = targetStatistics == null ? new HashMap<OsmPrimitiveType, Integer>() : targetStatistics;
163
164 // init the resolver
165 //
166 allPrimitivesResolver.getModel().populate(tagsForAllPrimitives,tagsForAllPrimitives.getKeysWithMultipleValues());
167 allPrimitivesResolver.getModel().prepareDefaultTagDecisions();
168
169 // prepare the dialog with one tag resolver
170 pnlTagResolver.setLayout(new BorderLayout());
171 pnlTagResolver.removeAll();
172 pnlTagResolver.add(allPrimitivesResolver, BorderLayout.CENTER);
173
174 statisticsModel.reset();
175 StatisticsInfo info = new StatisticsInfo();
176 info.numTags = tagsForAllPrimitives.getKeys().size();
177 for (OsmPrimitiveType type: sourceStatistics.keySet()) {
178 info.sourceInfo.put(type, sourceStatistics.get(type));
179 }
180 for (OsmPrimitiveType type: targetStatistics.keySet()) {
181 info.targetInfo.put(type, targetStatistics.get(type));
182 }
183 statisticsModel.append(info);
184 validate();
185 }
186
187 protected int getNumResolverTabs() {
188 return tpResolvers.getTabCount();
189 }
190
191 protected TagConflictResolver getResolver(int idx) {
192 return (TagConflictResolver)tpResolvers.getComponentAt(idx);
193 }
194
195 /**
196 * Populate the tag conflict resolver with tags for each type of primitives
197 *
198 * @param tagsForNodes the tags belonging to nodes in the paste source
199 * @param tagsForWays the tags belonging to way in the paste source
200 * @param tagsForRelations the tags belonging to relations in the paste source
201 * @param sourceStatistics histogram of tag source, number of primitives of each type in the source
202 * @param targetStatistics histogram of paste targets, number of primitives of each type in the paste target
203 */
204 public void populate(TagCollection tagsForNodes, TagCollection tagsForWays, TagCollection tagsForRelations, Map<OsmPrimitiveType,Integer> sourceStatistics, Map<OsmPrimitiveType, Integer> targetStatistics) {
205 tagsForNodes = (tagsForNodes == null) ? new TagCollection() : tagsForNodes;
206 tagsForWays = (tagsForWays == null) ? new TagCollection() : tagsForWays;
207 tagsForRelations = (tagsForRelations == null) ? new TagCollection() : tagsForRelations;
208 if (tagsForNodes.isEmpty() && tagsForWays.isEmpty() && tagsForRelations.isEmpty()) {
209 populate(null,null,null);
210 return;
211 }
212 tpResolvers.removeAll();
213 initResolver(OsmPrimitiveType.NODE,tagsForNodes, targetStatistics);
214 initResolver(OsmPrimitiveType.WAY,tagsForWays, targetStatistics);
215 initResolver(OsmPrimitiveType.RELATION,tagsForRelations, targetStatistics);
216
217 pnlTagResolver.setLayout(new BorderLayout());
218 pnlTagResolver.removeAll();
219 pnlTagResolver.add(tpResolvers, BorderLayout.CENTER);
220 mode = Mode.RESOLVING_TYPED_TAGCOLLECTIONS;
221 validate();
222 statisticsModel.reset();
223 if (!tagsForNodes.isEmpty()) {
224 StatisticsInfo info = new StatisticsInfo();
225 info.numTags = tagsForNodes.getKeys().size();
226 int numTargets = targetStatistics.get(OsmPrimitiveType.NODE) == null ? 0 : targetStatistics.get(OsmPrimitiveType.NODE);
227 if (numTargets > 0) {
228 info.sourceInfo.put(OsmPrimitiveType.NODE, sourceStatistics.get(OsmPrimitiveType.NODE));
229 info.targetInfo.put(OsmPrimitiveType.NODE, numTargets);
230 statisticsModel.append(info);
231 }
232 }
233 if (!tagsForWays.isEmpty()) {
234 StatisticsInfo info = new StatisticsInfo();
235 info.numTags = tagsForWays.getKeys().size();
236 int numTargets = targetStatistics.get(OsmPrimitiveType.WAY) == null ? 0 : targetStatistics.get(OsmPrimitiveType.WAY);
237 if (numTargets > 0) {
238 info.sourceInfo.put(OsmPrimitiveType.WAY, sourceStatistics.get(OsmPrimitiveType.WAY));
239 info.targetInfo.put(OsmPrimitiveType.WAY, numTargets);
240 statisticsModel.append(info);
241 }
242 }
243 if (!tagsForRelations.isEmpty()) {
244 StatisticsInfo info = new StatisticsInfo();
245 info.numTags = tagsForRelations.getKeys().size();
246 int numTargets = targetStatistics.get(OsmPrimitiveType.RELATION) == null ? 0 : targetStatistics.get(OsmPrimitiveType.RELATION);
247 if (numTargets > 0) {
248 info.sourceInfo.put(OsmPrimitiveType.RELATION, sourceStatistics.get(OsmPrimitiveType.RELATION));
249 info.targetInfo.put(OsmPrimitiveType.RELATION, numTargets);
250 statisticsModel.append(info);
251 }
252 }
253
254 for (int i =0; i < getNumResolverTabs(); i++) {
255 if (!getResolver(i).getModel().isResolvedCompletely()) {
256 tpResolvers.setSelectedIndex(i);
257 break;
258 }
259 }
260 }
261
262 protected void setCanceled(boolean canceled) {
263 this.canceled = canceled;
264 }
265
266 public boolean isCanceled() {
267 return this.canceled;
268 }
269
270 class CancelAction extends AbstractAction {
271
272 public CancelAction() {
273 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
274 putValue(Action.NAME, tr("Cancel"));
275 putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
276 setEnabled(true);
277 }
278
279 public void actionPerformed(ActionEvent arg0) {
280 setVisible(false);
281 setCanceled(true);
282 }
283 }
284
285 class ApplyAction extends AbstractAction implements PropertyChangeListener {
286
287 public ApplyAction() {
288 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
289 putValue(Action.NAME, tr("Apply"));
290 putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
291 updateEnabledState();
292 }
293
294 public void actionPerformed(ActionEvent arg0) {
295 setVisible(false);
296 }
297
298 protected void updateEnabledState() {
299 if (mode == null) {
300 setEnabled(false);
301 } else if (mode.equals(Mode.RESOLVING_ONE_TAGCOLLECTION_ONLY)) {
302 setEnabled(allPrimitivesResolver.getModel().isResolvedCompletely());
303 } else {
304 boolean enabled = true;
305 for (OsmPrimitiveType type: resolvers.keySet()) {
306 enabled &= resolvers.get(type).getModel().isResolvedCompletely();
307 }
308 setEnabled(enabled);
309 }
310 }
311
312 public void propertyChange(PropertyChangeEvent evt) {
313 if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
314 updateEnabledState();
315 }
316 }
317 }
318
319 @Override
320 public void setVisible(boolean visible) {
321 if (visible) {
322 new WindowGeometry(
323 getClass().getName() + ".geometry",
324 WindowGeometry.centerOnScreen(new Dimension(400,300))
325 ).applySafe(this);
326 } else {
327 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
328 }
329 super.setVisible(visible);
330 }
331
332 public TagCollection getResolution() {
333 return allPrimitivesResolver.getModel().getResolution();
334 }
335
336 public TagCollection getResolution(OsmPrimitiveType type) {
337 if (type == null) return null;
338 return resolvers.get(type).getModel().getResolution();
339 }
340
341 public void propertyChange(PropertyChangeEvent evt) {
342 if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
343 TagConflictResolverModel model = (TagConflictResolverModel)evt.getSource();
344 for (int i=0; i < tpResolvers.getTabCount();i++) {
345 TagConflictResolver resolver = (TagConflictResolver)tpResolvers.getComponentAt(i);
346 if (model == resolver.getModel()) {
347 tpResolvers.setIconAt(i,
348 (Boolean)evt.getNewValue() ? iconResolved : iconUnresolved
349
350 );
351 }
352 }
353 }
354 }
355
356 static public class StatisticsInfo {
357 public int numTags;
358 public Map<OsmPrimitiveType, Integer> sourceInfo;
359 public Map<OsmPrimitiveType, Integer> targetInfo;
360
361 public StatisticsInfo() {
362 sourceInfo = new HashMap<OsmPrimitiveType, Integer>();
363 targetInfo = new HashMap<OsmPrimitiveType, Integer>();
364 }
365 }
366
367 static private class StatisticsTableColumnModel extends DefaultTableColumnModel {
368 public StatisticsTableColumnModel() {
369 TableCellRenderer renderer = new StatisticsInfoRenderer();
370 TableColumn col = null;
371
372 // column 0 - Paste
373 col = new TableColumn(0);
374 col.setHeaderValue(tr("Paste ..."));
375 col.setResizable(true);
376 col.setCellRenderer(renderer);
377 addColumn(col);
378
379 // column 1 - From
380 col = new TableColumn(1);
381 col.setHeaderValue(tr("From ..."));
382 col.setResizable(true);
383 col.setCellRenderer(renderer);
384 addColumn(col);
385
386 // column 2 - To
387 col = new TableColumn(2);
388 col.setHeaderValue(tr("To ..."));
389 col.setResizable(true);
390 col.setCellRenderer(renderer);
391 addColumn(col);
392 }
393 }
394
395 static private class StatisticsTableModel extends DefaultTableModel {
396 private static final String[] HEADERS = new String[] {tr("Paste ..."), tr("From ..."), tr("To ...") };
397 private List<StatisticsInfo> data;
398
399 public StatisticsTableModel() {
400 data = new ArrayList<StatisticsInfo>();
401 }
402
403 @Override
404 public Object getValueAt(int row, int column) {
405 if (row == 0)
406 return HEADERS[column];
407 else if (row -1 < data.size())
408 return data.get(row -1);
409 else
410 return null;
411 }
412
413 @Override
414 public boolean isCellEditable(int row, int column) {
415 return false;
416 }
417
418 @Override
419 public int getRowCount() {
420 if (data == null) return 1;
421 return data.size() + 1;
422 }
423
424 public void reset() {
425 data.clear();
426 }
427
428 public void append(StatisticsInfo info) {
429 data.add(info);
430 fireTableDataChanged();
431 }
432 }
433
434 static private class StatisticsInfoRenderer extends JLabel implements TableCellRenderer {
435 @SuppressWarnings("unused")
436 static private final Logger logger = Logger.getLogger(StatisticsInfoRenderer.class.getName());
437
438 protected void reset() {
439 setIcon(null);
440 setText("");
441 setFont(UIManager.getFont("Table.font"));
442 }
443 protected void renderNumTags(StatisticsInfo info) {
444 if (info == null) return;
445 setText(trn("{0} tag", "{0} tags", info.numTags, info.numTags));
446 }
447
448 protected void renderStatistics(Map<OsmPrimitiveType, Integer> stat) {
449 if (stat == null) return;
450 if (stat.isEmpty()) return;
451 if (stat.size() == 1) {
452 setIcon(ImageProvider.get(stat.keySet().iterator().next()));
453 } else {
454 setIcon(ImageProvider.get("data", "object"));
455 }
456 String text = "";
457 for (OsmPrimitiveType type: stat.keySet()) {
458 int numPrimitives = stat.get(type) == null ? 0 : stat.get(type);
459 if (numPrimitives == 0) {
460 continue;
461 }
462 String msg = "";
463 switch(type) {
464 case NODE: msg = trn("{0} node", "{0} nodes", numPrimitives,numPrimitives); break;
465 case WAY: msg = trn("{0} way", "{0} ways", numPrimitives, numPrimitives); break;
466 case RELATION: msg = trn("{0} relation", "{0} relations", numPrimitives, numPrimitives); break;
467 }
468 text = text.equals("") ? msg : text + ", " + msg;
469 }
470 setText(text);
471 }
472
473 protected void renderFrom(StatisticsInfo info) {
474 renderStatistics(info.sourceInfo);
475 }
476
477 protected void renderTo(StatisticsInfo info) {
478 renderStatistics(info.targetInfo);
479 }
480
481 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
482 boolean hasFocus, int row, int column) {
483 reset();
484 if (row == 0) {
485 setFont(getFont().deriveFont(Font.BOLD));
486 setText((String)value);
487 } else {
488 StatisticsInfo info = (StatisticsInfo) value;
489
490 switch(column) {
491 case 0: renderNumTags(info); break;
492 case 1: renderFrom(info); break;
493 case 2: renderTo(info); break;
494 }
495 }
496 return this;
497 }
498 }
499
500 static private class StatisticsInfoTable extends JPanel {
501
502 private JTable infoTable;
503
504 protected void build(StatisticsTableModel model) {
505 infoTable = new JTable(model, new StatisticsTableColumnModel());
506 infoTable.setShowHorizontalLines(true);
507 infoTable.setShowVerticalLines(false);
508 infoTable.setEnabled(false);
509 setLayout(new BorderLayout());
510 add(infoTable, BorderLayout.CENTER);
511 }
512
513 public StatisticsInfoTable(StatisticsTableModel model) {
514 build(model);
515 }
516
517 @Override
518 public Insets getInsets() {
519 Insets insets = super.getInsets();
520 insets.bottom = 20;
521 return insets;
522 }
523 }
524}
Note: See TracBrowser for help on using the repository browser.