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

Last change on this file since 8510 was 8510, checked in by Don-vip, 9 years ago

checkstyle: enable relevant whitespace checks and fix them

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