001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.BorderLayout;
008import java.awt.Component;
009import java.awt.event.ActionEvent;
010import java.beans.PropertyChangeEvent;
011import java.beans.PropertyChangeListener;
012
013import javax.swing.AbstractAction;
014import javax.swing.Action;
015import javax.swing.JLabel;
016import javax.swing.JOptionPane;
017import javax.swing.JPanel;
018
019import org.openstreetmap.josm.data.UndoRedoHandler;
020import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
021import org.openstreetmap.josm.data.osm.OsmPrimitive;
022import org.openstreetmap.josm.gui.ExtendedDialog;
023import org.openstreetmap.josm.gui.MainApplication;
024import org.openstreetmap.josm.gui.conflict.pair.ConflictResolver;
025import org.openstreetmap.josm.gui.help.HelpBrowser;
026import org.openstreetmap.josm.gui.help.HelpUtil;
027import org.openstreetmap.josm.tools.ImageProvider;
028
029/**
030 * This is an extended dialog for resolving conflict between {@link OsmPrimitive}s.
031 * @since 1622
032 */
033public class ConflictResolutionDialog extends ExtendedDialog implements PropertyChangeListener {
034    /** the conflict resolver component */
035    private final ConflictResolver resolver = new ConflictResolver();
036    private final JLabel titleLabel = new JLabel("", null, JLabel.CENTER);
037
038    private final ApplyResolutionAction applyResolutionAction = new ApplyResolutionAction();
039
040    private boolean isRegistered;
041
042    /**
043     * Constructs a new {@code ConflictResolutionDialog}.
044     * @param parent parent component
045     */
046    public ConflictResolutionDialog(Component parent) {
047        // We define our own actions, but need to give a hint about number of buttons
048        super(parent, tr("Resolve conflicts"), null, null, null);
049        setDefaultButton(1);
050        setCancelButton(2);
051        build();
052        pack();
053        if (getInsets().top > 0) {
054            titleLabel.setVisible(false);
055        }
056    }
057
058    @Override
059    public void removeNotify() {
060        super.removeNotify();
061        unregisterListeners();
062    }
063
064    @Override
065    public void addNotify() {
066        super.addNotify();
067        registerListeners();
068    }
069
070    private synchronized void registerListeners() {
071        if (!isRegistered) {
072            resolver.addPropertyChangeListener(applyResolutionAction);
073            resolver.registerListeners();
074            isRegistered = true;
075        }
076    }
077
078    private synchronized void unregisterListeners() {
079        // See #13479 - See https://bugs.openjdk.java.net/browse/JDK-4387314
080        // Owner window keep a list of owned windows, and does not remove the references when the child is disposed.
081        // There's no easy way to remove ourselves from this list, so we must keep track of register state
082        if (isRegistered) {
083            resolver.removePropertyChangeListener(applyResolutionAction);
084            resolver.unregisterListeners();
085            isRegistered = false;
086        }
087    }
088
089    /**
090     * builds the GUI
091     */
092    protected void build() {
093        JPanel p = new JPanel(new BorderLayout());
094
095        p.add(titleLabel, BorderLayout.NORTH);
096
097        updateTitle();
098
099        resolver.setName("panel.conflictresolver");
100        p.add(resolver, BorderLayout.CENTER);
101
102        resolver.addPropertyChangeListener(this);
103        HelpUtil.setHelpContext(this.getRootPane(), ht("Dialog/Conflict"));
104
105        setContent(p, false);
106    }
107
108    @Override
109    protected Action createButtonAction(int i) {
110        switch (i) {
111            case 0: return applyResolutionAction;
112            case 1: return new CancelAction();
113            case 2: return new HelpAction();
114            default: return super.createButtonAction(i);
115        }
116    }
117
118    /**
119     * Replies the conflict resolver component.
120     * @return the conflict resolver component
121     */
122    public ConflictResolver getConflictResolver() {
123        return resolver;
124    }
125
126    /**
127     * Action for canceling conflict resolution
128     */
129    class CancelAction extends AbstractAction {
130        CancelAction() {
131            putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution and close the dialog"));
132            putValue(Action.NAME, tr("Cancel"));
133            new ImageProvider("cancel").getResource().attachImageIcon(this);
134            setEnabled(true);
135        }
136
137        @Override
138        public void actionPerformed(ActionEvent evt) {
139            buttonAction(2, evt);
140        }
141    }
142
143    /**
144     * Action for canceling conflict resolution
145     */
146    static class HelpAction extends AbstractAction {
147        HelpAction() {
148            putValue(Action.SHORT_DESCRIPTION, tr("Show help information"));
149            putValue(Action.NAME, tr("Help"));
150            new ImageProvider("help").getResource().attachImageIcon(this);
151            setEnabled(true);
152        }
153
154        @Override
155        public void actionPerformed(ActionEvent evt) {
156            HelpBrowser.setUrlForHelpTopic(ht("/Dialog/Conflict"));
157        }
158    }
159
160    /**
161     * Action for applying resolved differences in a conflict
162     *
163     */
164    class ApplyResolutionAction extends AbstractAction implements PropertyChangeListener {
165        ApplyResolutionAction() {
166            putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts and close the dialog"));
167            putValue(Action.NAME, tr("Apply Resolution"));
168            new ImageProvider("dialogs", "conflict").getResource().attachImageIcon(this);
169            updateEnabledState();
170        }
171
172        protected void updateEnabledState() {
173            setEnabled(resolver.isResolvedCompletely());
174        }
175
176        @Override
177        public void actionPerformed(ActionEvent evt) {
178            if (!resolver.isResolvedCompletely()) {
179                Object[] options = {
180                        tr("Close anyway"),
181                        tr("Continue resolving")};
182                int ret = JOptionPane.showOptionDialog(MainApplication.getMainFrame(),
183                        tr("<html>You did not finish to merge the differences in this conflict.<br>"
184                                + "Conflict resolutions will not be applied unless all differences<br>"
185                                + "are resolved.<br>"
186                                + "Click <strong>{0}</strong> to close anyway.<strong> Already<br>"
187                                + "resolved differences will not be applied.</strong><br>"
188                                + "Click <strong>{1}</strong> to return to resolving conflicts.</html>",
189                                options[0].toString(), options[1].toString()
190                        ),
191                        tr("Conflict not resolved completely"),
192                        JOptionPane.YES_NO_OPTION,
193                        JOptionPane.WARNING_MESSAGE,
194                        null,
195                        options,
196                        options[1]
197                );
198                switch(ret) {
199                case JOptionPane.YES_OPTION:
200                    buttonAction(1, evt);
201                    break;
202                default:
203                    return;
204                }
205            }
206            UndoRedoHandler.getInstance().add(resolver.buildResolveCommand());
207            buttonAction(1, evt);
208        }
209
210        @Override
211        public void propertyChange(PropertyChangeEvent evt) {
212            if (evt.getPropertyName().equals(ConflictResolver.RESOLVED_COMPLETELY_PROP)) {
213                updateEnabledState();
214            }
215        }
216    }
217
218    protected void updateTitle() {
219        updateTitle(null);
220    }
221
222    protected void updateTitle(OsmPrimitive my) {
223        if (my == null) {
224            setTitle(tr("Resolve conflicts"));
225        } else {
226            setTitle(tr("Resolve conflicts for ''{0}''", my.getDisplayName(DefaultNameFormatter.getInstance())));
227        }
228    }
229
230    @Override
231    public void setTitle(String title) {
232        super.setTitle(title);
233        if (titleLabel != null) {
234            titleLabel.setText(title);
235        }
236    }
237
238    @Override
239    public void propertyChange(PropertyChangeEvent evt) {
240        if (evt.getPropertyName().equals(ConflictResolver.MY_PRIMITIVE_PROP)) {
241            updateTitle((OsmPrimitive) evt.getNewValue());
242        }
243    }
244}