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

Last change on this file since 6084 was 6084, checked in by bastiK, 11 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

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