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

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

refactoring - global simplification of use of setLayout method - simply pass layout to JPanel constructor

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