source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/pair/properties/PropertiesMerger.java @ 10210

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

see #11924 - Java 9 - replace calls to deprecated classes java.util.Observable / java.util.Observer by a new class ChangeNotifier + swing's ChangeListener

  • 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.pair.properties;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.GridBagConstraints;
7import java.awt.GridBagLayout;
8import java.awt.Insets;
9import java.awt.event.ActionEvent;
10import java.text.DecimalFormat;
11import java.util.List;
12
13import javax.swing.AbstractAction;
14import javax.swing.Action;
15import javax.swing.BorderFactory;
16import javax.swing.JButton;
17import javax.swing.JLabel;
18import javax.swing.JPanel;
19import javax.swing.event.ChangeEvent;
20import javax.swing.event.ChangeListener;
21
22import org.openstreetmap.josm.data.conflict.Conflict;
23import org.openstreetmap.josm.data.coor.LatLon;
24import org.openstreetmap.josm.data.osm.OsmPrimitive;
25import org.openstreetmap.josm.gui.DefaultNameFormatter;
26import org.openstreetmap.josm.gui.conflict.ConflictColors;
27import org.openstreetmap.josm.gui.conflict.pair.IConflictResolver;
28import org.openstreetmap.josm.gui.conflict.pair.MergeDecisionType;
29import org.openstreetmap.josm.gui.history.VersionInfoPanel;
30import org.openstreetmap.josm.tools.ImageProvider;
31
32/**
33 * This class represents a UI component for resolving conflicts in some properties of {@link OsmPrimitive}.
34 * @since 1654
35 */
36public class PropertiesMerger extends JPanel implements ChangeListener, IConflictResolver {
37    private static final DecimalFormat COORD_FORMATTER = new DecimalFormat("###0.0000000");
38
39    private final JLabel lblMyCoordinates = buildValueLabel("label.mycoordinates");
40    private final JLabel lblMergedCoordinates = buildValueLabel("label.mergedcoordinates");
41    private final JLabel lblTheirCoordinates = buildValueLabel("label.theircoordinates");
42
43    private final JLabel lblMyDeletedState = buildValueLabel("label.mydeletedstate");
44    private final JLabel lblMergedDeletedState = buildValueLabel("label.mergeddeletedstate");
45    private final JLabel lblTheirDeletedState = buildValueLabel("label.theirdeletedstate");
46
47    private final JLabel lblMyReferrers = buildValueLabel("label.myreferrers");
48    private final JLabel lblTheirReferrers = buildValueLabel("label.theirreferrers");
49
50    private final transient PropertiesMergeModel model;
51    private final VersionInfoPanel mineVersionInfo = new VersionInfoPanel();
52    private final VersionInfoPanel theirVersionInfo = new VersionInfoPanel();
53
54    /**
55     * Constructs a new {@code PropertiesMerger}.
56     */
57    public PropertiesMerger() {
58        model = new PropertiesMergeModel();
59        model.addChangeListener(this);
60        build();
61    }
62
63    protected static JLabel buildValueLabel(String name) {
64        JLabel lbl = new JLabel();
65        lbl.setName(name);
66        lbl.setHorizontalAlignment(JLabel.CENTER);
67        lbl.setOpaque(true);
68        lbl.setBorder(BorderFactory.createLoweredBevelBorder());
69        return lbl;
70    }
71
72    protected void buildHeaderRow() {
73        GridBagConstraints gc = new GridBagConstraints();
74
75        gc.gridx = 1;
76        gc.gridy = 0;
77        gc.gridwidth = 1;
78        gc.gridheight = 1;
79        gc.fill = GridBagConstraints.NONE;
80        gc.anchor = GridBagConstraints.CENTER;
81        gc.weightx = 0.0;
82        gc.weighty = 0.0;
83        gc.insets = new Insets(10, 0, 0, 0);
84        JLabel lblMyVersion = new JLabel(tr("My version"));
85        lblMyVersion.setToolTipText(tr("Properties in my dataset, i.e. the local dataset"));
86        lblMyVersion.setLabelFor(mineVersionInfo);
87        add(lblMyVersion, gc);
88
89        gc.gridx = 3;
90        JLabel lblMergedVersion = new JLabel(tr("Merged version"));
91        lblMergedVersion.setToolTipText(
92                tr("Properties in the merged element. They will replace properties in my elements when merge decisions are applied."));
93        add(lblMergedVersion, gc);
94
95        gc.gridx = 5;
96        JLabel lblTheirVersion = new JLabel(tr("Their version"));
97        lblTheirVersion.setToolTipText(tr("Properties in their dataset, i.e. the server dataset"));
98        lblMyVersion.setLabelFor(theirVersionInfo);
99        add(lblTheirVersion, gc);
100
101        gc.gridx = 1;
102        gc.gridy = 1;
103        gc.fill = GridBagConstraints.HORIZONTAL;
104        gc.anchor = GridBagConstraints.LINE_START;
105        gc.insets = new Insets(0, 0, 20, 0);
106        add(mineVersionInfo, gc);
107
108        gc.gridx = 5;
109        add(theirVersionInfo, gc);
110    }
111
112    protected void buildCoordinateConflictRows() {
113        GridBagConstraints gc = new GridBagConstraints();
114
115        gc.gridx = 0;
116        gc.gridy = 2;
117        gc.gridwidth = 1;
118        gc.gridheight = 1;
119        gc.fill = GridBagConstraints.HORIZONTAL;
120        gc.anchor = GridBagConstraints.LINE_START;
121        gc.weightx = 0.0;
122        gc.weighty = 0.0;
123        gc.insets = new Insets(0, 5, 0, 5);
124        add(new JLabel(tr("Coordinates:")), gc);
125
126        gc.gridx = 1;
127        gc.fill = GridBagConstraints.BOTH;
128        gc.anchor = GridBagConstraints.CENTER;
129        gc.weightx = 0.33;
130        gc.weighty = 0.0;
131        add(lblMyCoordinates, gc);
132
133        gc.gridx = 2;
134        gc.fill = GridBagConstraints.NONE;
135        gc.anchor = GridBagConstraints.CENTER;
136        gc.weightx = 0.0;
137        gc.weighty = 0.0;
138        KeepMyCoordinatesAction actKeepMyCoordinates = new KeepMyCoordinatesAction();
139        model.addChangeListener(actKeepMyCoordinates);
140        JButton btnKeepMyCoordinates = new JButton(actKeepMyCoordinates);
141        btnKeepMyCoordinates.setName("button.keepmycoordinates");
142        add(btnKeepMyCoordinates, gc);
143
144        gc.gridx = 3;
145        gc.fill = GridBagConstraints.BOTH;
146        gc.anchor = GridBagConstraints.CENTER;
147        gc.weightx = 0.33;
148        gc.weighty = 0.0;
149        add(lblMergedCoordinates, gc);
150
151        gc.gridx = 4;
152        gc.fill = GridBagConstraints.NONE;
153        gc.anchor = GridBagConstraints.CENTER;
154        gc.weightx = 0.0;
155        gc.weighty = 0.0;
156        KeepTheirCoordinatesAction actKeepTheirCoordinates = new KeepTheirCoordinatesAction();
157        model.addChangeListener(actKeepTheirCoordinates);
158        JButton btnKeepTheirCoordinates = new JButton(actKeepTheirCoordinates);
159        add(btnKeepTheirCoordinates, gc);
160
161        gc.gridx = 5;
162        gc.fill = GridBagConstraints.BOTH;
163        gc.anchor = GridBagConstraints.CENTER;
164        gc.weightx = 0.33;
165        gc.weighty = 0.0;
166        add(lblTheirCoordinates, gc);
167
168        // ---------------------------------------------------
169        gc.gridx = 3;
170        gc.gridy = 3;
171        gc.fill = GridBagConstraints.NONE;
172        gc.anchor = GridBagConstraints.CENTER;
173        gc.weightx = 0.0;
174        gc.weighty = 0.0;
175        UndecideCoordinateConflictAction actUndecideCoordinates = new UndecideCoordinateConflictAction();
176        model.addChangeListener(actUndecideCoordinates);
177        JButton btnUndecideCoordinates = new JButton(actUndecideCoordinates);
178        add(btnUndecideCoordinates, gc);
179    }
180
181    protected void buildDeletedStateConflictRows() {
182        GridBagConstraints gc = new GridBagConstraints();
183
184        gc.gridx = 0;
185        gc.gridy = 4;
186        gc.gridwidth = 1;
187        gc.gridheight = 1;
188        gc.fill = GridBagConstraints.BOTH;
189        gc.anchor = GridBagConstraints.LINE_START;
190        gc.weightx = 0.0;
191        gc.weighty = 0.0;
192        gc.insets = new Insets(0, 5, 0, 5);
193        add(new JLabel(tr("Deleted State:")), gc);
194
195        gc.gridx = 1;
196        gc.fill = GridBagConstraints.BOTH;
197        gc.anchor = GridBagConstraints.CENTER;
198        gc.weightx = 0.33;
199        gc.weighty = 0.0;
200        add(lblMyDeletedState, gc);
201
202        gc.gridx = 2;
203        gc.fill = GridBagConstraints.NONE;
204        gc.anchor = GridBagConstraints.CENTER;
205        gc.weightx = 0.0;
206        gc.weighty = 0.0;
207        KeepMyDeletedStateAction actKeepMyDeletedState = new KeepMyDeletedStateAction();
208        model.addChangeListener(actKeepMyDeletedState);
209        JButton btnKeepMyDeletedState = new JButton(actKeepMyDeletedState);
210        btnKeepMyDeletedState.setName("button.keepmydeletedstate");
211        add(btnKeepMyDeletedState, gc);
212
213        gc.gridx = 3;
214        gc.fill = GridBagConstraints.BOTH;
215        gc.anchor = GridBagConstraints.CENTER;
216        gc.weightx = 0.33;
217        gc.weighty = 0.0;
218        add(lblMergedDeletedState, gc);
219
220        gc.gridx = 4;
221        gc.fill = GridBagConstraints.NONE;
222        gc.anchor = GridBagConstraints.CENTER;
223        gc.weightx = 0.0;
224        gc.weighty = 0.0;
225        KeepTheirDeletedStateAction actKeepTheirDeletedState = new KeepTheirDeletedStateAction();
226        model.addChangeListener(actKeepTheirDeletedState);
227        JButton btnKeepTheirDeletedState = new JButton(actKeepTheirDeletedState);
228        btnKeepTheirDeletedState.setName("button.keeptheirdeletedstate");
229        add(btnKeepTheirDeletedState, gc);
230
231        gc.gridx = 5;
232        gc.fill = GridBagConstraints.BOTH;
233        gc.anchor = GridBagConstraints.CENTER;
234        gc.weightx = 0.33;
235        gc.weighty = 0.0;
236        add(lblTheirDeletedState, gc);
237
238        // ---------------------------------------------------
239        gc.gridx = 3;
240        gc.gridy = 5;
241        gc.fill = GridBagConstraints.NONE;
242        gc.anchor = GridBagConstraints.CENTER;
243        gc.weightx = 0.0;
244        gc.weighty = 0.0;
245        UndecideDeletedStateConflictAction actUndecideDeletedState = new UndecideDeletedStateConflictAction();
246        model.addChangeListener(actUndecideDeletedState);
247        JButton btnUndecideDeletedState = new JButton(actUndecideDeletedState);
248        btnUndecideDeletedState.setName("button.undecidedeletedstate");
249        add(btnUndecideDeletedState, gc);
250    }
251
252    protected void buildReferrersRow() {
253        GridBagConstraints gc = new GridBagConstraints();
254
255        gc.gridx = 0;
256        gc.gridy = 7;
257        gc.gridwidth = 1;
258        gc.gridheight = 1;
259        gc.fill = GridBagConstraints.BOTH;
260        gc.anchor = GridBagConstraints.LINE_START;
261        gc.weightx = 0.0;
262        gc.weighty = 0.0;
263        gc.insets = new Insets(0, 5, 0, 5);
264        add(new JLabel(tr("Referenced by:")), gc);
265
266        gc.gridx = 1;
267        gc.gridy = 7;
268        gc.fill = GridBagConstraints.BOTH;
269        gc.anchor = GridBagConstraints.CENTER;
270        gc.weightx = 0.33;
271        gc.weighty = 0.0;
272        add(lblMyReferrers, gc);
273
274        gc.gridx = 5;
275        gc.gridy = 7;
276        gc.fill = GridBagConstraints.BOTH;
277        gc.anchor = GridBagConstraints.CENTER;
278        gc.weightx = 0.33;
279        gc.weighty = 0.0;
280        add(lblTheirReferrers, gc);
281    }
282
283    protected final void build() {
284        setLayout(new GridBagLayout());
285        buildHeaderRow();
286        buildCoordinateConflictRows();
287        buildDeletedStateConflictRows();
288        buildReferrersRow();
289    }
290
291    protected static String coordToString(LatLon coord) {
292        if (coord == null)
293            return tr("(none)");
294        StringBuilder sb = new StringBuilder();
295        sb.append('(')
296        .append(COORD_FORMATTER.format(coord.lat()))
297        .append(',')
298        .append(COORD_FORMATTER.format(coord.lon()))
299        .append(')');
300        return sb.toString();
301    }
302
303    protected static String deletedStateToString(Boolean deleted) {
304        if (deleted == null)
305            return tr("(none)");
306        if (deleted)
307            return tr("deleted");
308        else
309            return tr("not deleted");
310    }
311
312    protected static String referrersToString(List<OsmPrimitive> referrers) {
313        if (referrers.isEmpty())
314            return tr("(none)");
315        StringBuilder str = new StringBuilder("<html>");
316        for (OsmPrimitive r: referrers) {
317            str.append(r.getDisplayName(DefaultNameFormatter.getInstance())).append("<br>");
318        }
319        str.append("</html>");
320        return str.toString();
321    }
322
323    protected void updateCoordinates() {
324        lblMyCoordinates.setText(coordToString(model.getMyCoords()));
325        lblMergedCoordinates.setText(coordToString(model.getMergedCoords()));
326        lblTheirCoordinates.setText(coordToString(model.getTheirCoords()));
327        if (!model.hasCoordConflict()) {
328            lblMyCoordinates.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
329            lblMergedCoordinates.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
330            lblTheirCoordinates.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
331        } else {
332            if (!model.isDecidedCoord()) {
333                lblMyCoordinates.setBackground(ConflictColors.BGCOLOR_UNDECIDED.get());
334                lblMergedCoordinates.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
335                lblTheirCoordinates.setBackground(ConflictColors.BGCOLOR_UNDECIDED.get());
336            } else {
337                lblMyCoordinates.setBackground(
338                        model.isCoordMergeDecision(MergeDecisionType.KEEP_MINE)
339                        ? ConflictColors.BGCOLOR_DECIDED.get() : ConflictColors.BGCOLOR_NO_CONFLICT.get()
340                );
341                lblMergedCoordinates.setBackground(ConflictColors.BGCOLOR_DECIDED.get());
342                lblTheirCoordinates.setBackground(
343                        model.isCoordMergeDecision(MergeDecisionType.KEEP_THEIR)
344                        ? ConflictColors.BGCOLOR_DECIDED.get() : ConflictColors.BGCOLOR_NO_CONFLICT.get()
345                );
346            }
347        }
348    }
349
350    protected void updateDeletedState() {
351        lblMyDeletedState.setText(deletedStateToString(model.getMyDeletedState()));
352        lblMergedDeletedState.setText(deletedStateToString(model.getMergedDeletedState()));
353        lblTheirDeletedState.setText(deletedStateToString(model.getTheirDeletedState()));
354
355        if (!model.hasDeletedStateConflict()) {
356            lblMyDeletedState.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
357            lblMergedDeletedState.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
358            lblTheirDeletedState.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
359        } else {
360            if (!model.isDecidedDeletedState()) {
361                lblMyDeletedState.setBackground(ConflictColors.BGCOLOR_UNDECIDED.get());
362                lblMergedDeletedState.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
363                lblTheirDeletedState.setBackground(ConflictColors.BGCOLOR_UNDECIDED.get());
364            } else {
365                lblMyDeletedState.setBackground(
366                        model.isDeletedStateDecision(MergeDecisionType.KEEP_MINE)
367                        ? ConflictColors.BGCOLOR_DECIDED.get() : ConflictColors.BGCOLOR_NO_CONFLICT.get()
368                );
369                lblMergedDeletedState.setBackground(ConflictColors.BGCOLOR_DECIDED.get());
370                lblTheirDeletedState.setBackground(
371                        model.isDeletedStateDecision(MergeDecisionType.KEEP_THEIR)
372                        ? ConflictColors.BGCOLOR_DECIDED.get() : ConflictColors.BGCOLOR_NO_CONFLICT.get()
373                );
374            }
375        }
376    }
377
378    protected void updateReferrers() {
379        lblMyReferrers.setText(referrersToString(model.getMyReferrers()));
380        lblMyReferrers.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
381        lblTheirReferrers.setText(referrersToString(model.getTheirReferrers()));
382        lblTheirReferrers.setBackground(ConflictColors.BGCOLOR_NO_CONFLICT.get());
383    }
384
385    @Override
386    public void stateChanged(ChangeEvent e) {
387        updateCoordinates();
388        updateDeletedState();
389        updateReferrers();
390    }
391
392    /**
393     * Returns properties merge model.
394     * @return properties merge model
395     */
396    public PropertiesMergeModel getModel() {
397        return model;
398    }
399
400    class KeepMyCoordinatesAction extends AbstractAction implements ChangeListener {
401        KeepMyCoordinatesAction() {
402            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine"));
403            putValue(Action.SHORT_DESCRIPTION, tr("Keep my coordinates"));
404        }
405
406        @Override
407        public void actionPerformed(ActionEvent e) {
408            model.decideCoordsConflict(MergeDecisionType.KEEP_MINE);
409        }
410
411        @Override
412        public void stateChanged(ChangeEvent e) {
413            setEnabled(model.hasCoordConflict() && !model.isDecidedCoord() && model.getMyCoords() != null);
414        }
415    }
416
417    class KeepTheirCoordinatesAction extends AbstractAction implements ChangeListener {
418        KeepTheirCoordinatesAction() {
419            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir"));
420            putValue(Action.SHORT_DESCRIPTION, tr("Keep their coordinates"));
421        }
422
423        @Override
424        public void actionPerformed(ActionEvent e) {
425            model.decideCoordsConflict(MergeDecisionType.KEEP_THEIR);
426        }
427
428        @Override
429        public void stateChanged(ChangeEvent e) {
430            setEnabled(model.hasCoordConflict() && !model.isDecidedCoord() && model.getTheirCoords() != null);
431        }
432    }
433
434    class UndecideCoordinateConflictAction extends AbstractAction implements ChangeListener {
435        UndecideCoordinateConflictAction() {
436            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide"));
437            putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between different coordinates"));
438        }
439
440        @Override
441        public void actionPerformed(ActionEvent e) {
442            model.decideCoordsConflict(MergeDecisionType.UNDECIDED);
443        }
444
445        @Override
446        public void stateChanged(ChangeEvent e) {
447            setEnabled(model.hasCoordConflict() && model.isDecidedCoord());
448        }
449    }
450
451    class KeepMyDeletedStateAction extends AbstractAction implements ChangeListener {
452        KeepMyDeletedStateAction() {
453            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeepmine"));
454            putValue(Action.SHORT_DESCRIPTION, tr("Keep my deleted state"));
455        }
456
457        @Override
458        public void actionPerformed(ActionEvent e) {
459            model.decideDeletedStateConflict(MergeDecisionType.KEEP_MINE);
460        }
461
462        @Override
463        public void stateChanged(ChangeEvent e) {
464            setEnabled(model.hasDeletedStateConflict() && !model.isDecidedDeletedState());
465        }
466    }
467
468    class KeepTheirDeletedStateAction extends AbstractAction implements ChangeListener {
469        KeepTheirDeletedStateAction() {
470            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagkeeptheir"));
471            putValue(Action.SHORT_DESCRIPTION, tr("Keep their deleted state"));
472        }
473
474        @Override
475        public void actionPerformed(ActionEvent e) {
476            model.decideDeletedStateConflict(MergeDecisionType.KEEP_THEIR);
477        }
478
479        @Override
480        public void stateChanged(ChangeEvent e) {
481            setEnabled(model.hasDeletedStateConflict() && !model.isDecidedDeletedState());
482        }
483    }
484
485    class UndecideDeletedStateConflictAction extends AbstractAction implements ChangeListener {
486        UndecideDeletedStateConflictAction() {
487            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs/conflict", "tagundecide"));
488            putValue(Action.SHORT_DESCRIPTION, tr("Undecide conflict between deleted state"));
489        }
490
491        @Override
492        public void actionPerformed(ActionEvent e) {
493            model.decideDeletedStateConflict(MergeDecisionType.UNDECIDED);
494        }
495
496        @Override
497        public void stateChanged(ChangeEvent e) {
498            setEnabled(model.hasDeletedStateConflict() && model.isDecidedDeletedState());
499        }
500    }
501
502    @Override
503    public void deletePrimitive(boolean deleted) {
504        if (deleted) {
505            if (model.getMergedCoords() == null) {
506                model.decideCoordsConflict(MergeDecisionType.KEEP_MINE);
507            }
508        } else {
509            model.decideCoordsConflict(MergeDecisionType.UNDECIDED);
510        }
511    }
512
513    @Override
514    public void populate(Conflict<? extends OsmPrimitive> conflict) {
515        model.populate(conflict);
516        mineVersionInfo.update(conflict.getMy(), true);
517        theirVersionInfo.update(conflict.getTheir(), false);
518    }
519
520    @Override
521    public void decideRemaining(MergeDecisionType decision) {
522        if (!model.isDecidedCoord()) {
523            model.decideDeletedStateConflict(decision);
524        }
525        if (!model.isDecidedCoord()) {
526            model.decideCoordsConflict(decision);
527        }
528    }
529}
Note: See TracBrowser for help on using the repository browser.