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

Last change on this file since 10791 was 10791, checked in by simon04, 8 years ago

see #13319 - Use InputMapUtils where applicable (VK_ESCAPE)

  • Property svn:eol-style set to native
File size: 18.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.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.InputMapUtils;
44import org.openstreetmap.josm.tools.WindowGeometry;
45
46public class PasteTagsConflictResolverDialog extends JDialog implements PropertyChangeListener {
47 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 enum Mode {
56 RESOLVING_ONE_TAGCOLLECTION_ONLY,
57 RESOLVING_TYPED_TAGCOLLECTIONS
58 }
59
60 private final TagConflictResolver allPrimitivesResolver = new TagConflictResolver();
61 private final transient Map<OsmPrimitiveType, TagConflictResolver> resolvers = new EnumMap<>(OsmPrimitiveType.class);
62 private final JTabbedPane tpResolvers = new JTabbedPane();
63 private Mode mode;
64 private boolean canceled;
65
66 private final ImageIcon iconResolved = ImageProvider.get("dialogs/conflict", "tagconflictresolved");
67 private final ImageIcon iconUnresolved = ImageProvider.get("dialogs/conflict", "tagconflictunresolved");
68 private final StatisticsTableModel statisticsModel = new StatisticsTableModel();
69 private final JPanel pnlTagResolver = new JPanel(new BorderLayout());
70
71 /**
72 * Constructs a new {@code PasteTagsConflictResolverDialog}.
73 * @param owner parent component
74 */
75 public PasteTagsConflictResolverDialog(Component owner) {
76 super(GuiHelper.getFrameForComponent(owner), ModalityType.DOCUMENT_MODAL);
77 build();
78 }
79
80 protected final void build() {
81 setTitle(tr("Conflicts in pasted tags"));
82 for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
83 resolvers.put(type, new TagConflictResolver());
84 resolvers.get(type).getModel().addPropertyChangeListener(this);
85 }
86 getContentPane().setLayout(new GridBagLayout());
87 mode = null;
88 GridBagConstraints gc = new GridBagConstraints();
89 gc.gridx = 0;
90 gc.gridy = 0;
91 gc.fill = GridBagConstraints.HORIZONTAL;
92 gc.weightx = 1.0;
93 gc.weighty = 0.0;
94 getContentPane().add(buildSourceAndTargetInfoPanel(), gc);
95 gc.gridx = 0;
96 gc.gridy = 1;
97 gc.fill = GridBagConstraints.BOTH;
98 gc.weightx = 1.0;
99 gc.weighty = 1.0;
100 getContentPane().add(pnlTagResolver, gc);
101 gc.gridx = 0;
102 gc.gridy = 2;
103 gc.fill = GridBagConstraints.HORIZONTAL;
104 gc.weightx = 1.0;
105 gc.weighty = 0.0;
106 getContentPane().add(buildButtonPanel(), gc);
107 InputMapUtils.addEscapeAction(getRootPane(), new CancelAction());
108
109 }
110
111 protected JPanel buildButtonPanel() {
112 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
113
114 // -- apply button
115 ApplyAction applyAction = new ApplyAction();
116 allPrimitivesResolver.getModel().addPropertyChangeListener(applyAction);
117 for (TagConflictResolver r : resolvers.values()) {
118 r.getModel().addPropertyChangeListener(applyAction);
119 }
120 pnl.add(new SideButton(applyAction));
121
122 // -- cancel button
123 CancelAction cancelAction = new CancelAction();
124 pnl.add(new SideButton(cancelAction));
125
126 return pnl;
127 }
128
129 protected JPanel buildSourceAndTargetInfoPanel() {
130 JPanel pnl = new JPanel(new BorderLayout());
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,
158 Map<OsmPrimitiveType, Integer> targetStatistics) {
159 mode = Mode.RESOLVING_ONE_TAGCOLLECTION_ONLY;
160 tagsForAllPrimitives = tagsForAllPrimitives == null ? new TagCollection() : tagsForAllPrimitives;
161 sourceStatistics = sourceStatistics == null ? new HashMap<>() : sourceStatistics;
162 targetStatistics = targetStatistics == null ? new HashMap<>() : targetStatistics;
163
164 // init the resolver
165 //
166 allPrimitivesResolver.getModel().populate(tagsForAllPrimitives, tagsForAllPrimitives.getKeysWithMultipleValues());
167 allPrimitivesResolver.getModel().prepareDefaultTagDecisions();
168
169 // prepare the dialog with one tag resolver
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,
200 Map<OsmPrimitiveType, Integer> sourceStatistics, Map<OsmPrimitiveType, Integer> targetStatistics) {
201 tagsForNodes = (tagsForNodes == null) ? new TagCollection() : tagsForNodes;
202 tagsForWays = (tagsForWays == null) ? new TagCollection() : tagsForWays;
203 tagsForRelations = (tagsForRelations == null) ? new TagCollection() : tagsForRelations;
204 if (tagsForNodes.isEmpty() && tagsForWays.isEmpty() && tagsForRelations.isEmpty()) {
205 populate(null, null, null);
206 return;
207 }
208 tpResolvers.removeAll();
209 initResolver(OsmPrimitiveType.NODE, tagsForNodes, targetStatistics);
210 initResolver(OsmPrimitiveType.WAY, tagsForWays, targetStatistics);
211 initResolver(OsmPrimitiveType.RELATION, tagsForRelations, targetStatistics);
212
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 final class CancelAction extends AbstractAction {
266
267 private CancelAction() {
268 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
269 putValue(Action.NAME, tr("Cancel"));
270 new ImageProvider("cancel").getResource().attachImageIcon(this);
271 setEnabled(true);
272 }
273
274 @Override
275 public void actionPerformed(ActionEvent arg0) {
276 setVisible(false);
277 setCanceled(true);
278 }
279 }
280
281 final class ApplyAction extends AbstractAction implements PropertyChangeListener {
282
283 private ApplyAction() {
284 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
285 putValue(Action.NAME, tr("Apply"));
286 new ImageProvider("ok").getResource().attachImageIcon(this);
287 updateEnabledState();
288 }
289
290 @Override
291 public void actionPerformed(ActionEvent arg0) {
292 setVisible(false);
293 }
294
295 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 setEnabled(resolvers.values().stream().allMatch(val -> val.getModel().isResolvedCompletely()));
302 }
303 }
304
305 @Override
306 public void propertyChange(PropertyChangeEvent evt) {
307 if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
308 updateEnabledState();
309 }
310 }
311 }
312
313 @Override
314 public void setVisible(boolean visible) {
315 if (visible) {
316 new WindowGeometry(
317 getClass().getName() + ".geometry",
318 WindowGeometry.centerOnScreen(new Dimension(600, 400))
319 ).applySafe(this);
320 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
321 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
322 }
323 super.setVisible(visible);
324 }
325
326 /**
327 * Returns conflict resolution.
328 * @return conflict resolution
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 (Integer) evt.getNewValue() == 0 ? iconResolved : iconUnresolved
348 );
349 }
350 }
351 }
352 }
353
354 static final class StatisticsInfo {
355 int numTags;
356 final Map<OsmPrimitiveType, Integer> sourceInfo;
357 final Map<OsmPrimitiveType, Integer> targetInfo;
358
359 StatisticsInfo() {
360 sourceInfo = new EnumMap<>(OsmPrimitiveType.class);
361 targetInfo = new EnumMap<>(OsmPrimitiveType.class);
362 }
363 }
364
365 static final class StatisticsTableModel extends DefaultTableModel {
366 private static final String[] HEADERS = new String[] {tr("Paste ..."), tr("From ..."), tr("To ...") };
367 private final transient List<StatisticsInfo> data = new ArrayList<>();
368
369 @Override
370 public Object getValueAt(int row, int column) {
371 if (row == 0)
372 return HEADERS[column];
373 else if (row -1 < data.size())
374 return data.get(row -1);
375 else
376 return null;
377 }
378
379 @Override
380 public boolean isCellEditable(int row, int column) {
381 return false;
382 }
383
384 @Override
385 public int getRowCount() {
386 return data == null ? 1 : data.size() + 1;
387 }
388
389 void reset() {
390 data.clear();
391 }
392
393 void append(StatisticsInfo info) {
394 data.add(info);
395 fireTableDataChanged();
396 }
397 }
398
399 static final class StatisticsInfoRenderer extends JLabel implements TableCellRenderer {
400 private void reset() {
401 setIcon(null);
402 setText("");
403 setFont(UIManager.getFont("Table.font"));
404 }
405
406 private void renderNumTags(StatisticsInfo info) {
407 if (info == null) return;
408 setText(trn("{0} tag", "{0} tags", info.numTags, info.numTags));
409 }
410
411 private void renderStatistics(Map<OsmPrimitiveType, Integer> stat) {
412 if (stat == null) return;
413 if (stat.isEmpty()) return;
414 if (stat.size() == 1) {
415 setIcon(ImageProvider.get(stat.keySet().iterator().next()));
416 } else {
417 setIcon(ImageProvider.get("data", "object"));
418 }
419 StringBuilder text = new StringBuilder();
420 for (Entry<OsmPrimitiveType, Integer> entry: stat.entrySet()) {
421 OsmPrimitiveType type = entry.getKey();
422 int numPrimitives = entry.getValue() == null ? 0 : entry.getValue();
423 if (numPrimitives == 0) {
424 continue;
425 }
426 String msg;
427 switch(type) {
428 case NODE: msg = trn("{0} node", "{0} nodes", numPrimitives, numPrimitives); break;
429 case WAY: msg = trn("{0} way", "{0} ways", numPrimitives, numPrimitives); break;
430 case RELATION: msg = trn("{0} relation", "{0} relations", numPrimitives, numPrimitives); break;
431 default: throw new AssertionError();
432 }
433 if (text.length() > 0) {
434 text.append(", ");
435 }
436 text.append(msg);
437 }
438 setText(text.toString());
439 }
440
441 private void renderFrom(StatisticsInfo info) {
442 renderStatistics(info.sourceInfo);
443 }
444
445 private void renderTo(StatisticsInfo info) {
446 renderStatistics(info.targetInfo);
447 }
448
449 @Override
450 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
451 boolean hasFocus, int row, int column) {
452 reset();
453 if (value == null)
454 return this;
455
456 if (row == 0) {
457 setFont(getFont().deriveFont(Font.BOLD));
458 setText((String) value);
459 } else {
460 StatisticsInfo info = (StatisticsInfo) value;
461
462 switch(column) {
463 case 0: renderNumTags(info); break;
464 case 1: renderFrom(info); break;
465 case 2: renderTo(info); break;
466 default: // Do nothing
467 }
468 }
469 return this;
470 }
471 }
472
473 static final class StatisticsInfoTable extends JPanel {
474
475 StatisticsInfoTable(StatisticsTableModel model) {
476 JTable infoTable = new JTable(model,
477 new TagTableColumnModelBuilder(new StatisticsInfoRenderer(), tr("Paste ..."), tr("From ..."), tr("To ...")).build());
478 infoTable.setShowHorizontalLines(true);
479 infoTable.setShowVerticalLines(false);
480 infoTable.setEnabled(false);
481 setLayout(new BorderLayout());
482 add(infoTable, BorderLayout.CENTER);
483 }
484
485 @Override
486 public Insets getInsets() {
487 Insets insets = super.getInsets();
488 insets.bottom = 20;
489 return insets;
490 }
491 }
492}
Note: See TracBrowser for help on using the repository browser.