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

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

performance - use of EnumMap / EnumSet

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