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

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

Checkstyle 6.19: enable SingleSpaceSeparator and fix violations

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