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