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.Dimension;
010import java.awt.FlowLayout;
011import java.awt.event.ActionEvent;
012import java.beans.PropertyChangeEvent;
013import java.beans.PropertyChangeListener;
014
015import javax.swing.AbstractAction;
016import javax.swing.Action;
017import javax.swing.BorderFactory;
018import javax.swing.JButton;
019import javax.swing.JDialog;
020import javax.swing.JLabel;
021import javax.swing.JOptionPane;
022import javax.swing.JPanel;
023import javax.swing.WindowConstants;
024
025import org.openstreetmap.josm.Main;
026import org.openstreetmap.josm.command.Command;
027import org.openstreetmap.josm.data.osm.OsmPrimitive;
028import org.openstreetmap.josm.gui.DefaultNameFormatter;
029import org.openstreetmap.josm.gui.conflict.pair.ConflictResolver;
030import org.openstreetmap.josm.gui.help.HelpBrowser;
031import org.openstreetmap.josm.gui.help.HelpUtil;
032import org.openstreetmap.josm.tools.ImageProvider;
033import org.openstreetmap.josm.tools.WindowGeometry;
034
035/**
036 * This is an extended dialog for resolving conflict between {@link OsmPrimitive}s.
037 * @since 1622
038 */
039public class ConflictResolutionDialog extends JDialog implements PropertyChangeListener {
040    /** the conflict resolver component */
041    private ConflictResolver resolver;
042    private JLabel titleLabel;
043
044    private ApplyResolutionAction applyResolutionAction;
045
046    @Override
047    public void removeNotify() {
048        super.removeNotify();
049        unregisterListeners();
050    }
051
052    @Override
053    public void setVisible(boolean isVisible) {
054        String geom = getClass().getName() + ".geometry";
055        if (isVisible) {
056            toFront();
057            new WindowGeometry(geom, WindowGeometry.centerInWindow(Main.parent,
058                new Dimension(600, 400))).applySafe(this);
059        } else {
060            if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
061                new WindowGeometry(this).remember(geom);
062            }
063            unregisterListeners();
064        }
065        super.setVisible(isVisible);
066    }
067
068    private void closeDialog() {
069        setVisible(false);
070        dispose();
071    }
072
073    /**
074     * builds the sub panel with the control buttons
075     *
076     * @return the panel
077     */
078    protected JPanel buildButtonRow() {
079        JPanel pnl = new JPanel();
080        pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
081
082        applyResolutionAction = new ApplyResolutionAction();
083        JButton btn = new JButton(applyResolutionAction);
084        btn.setName("button.apply");
085        pnl.add(btn);
086
087        btn = new JButton(new CancelAction());
088        btn.setName("button.cancel");
089        pnl.add(btn);
090
091        btn = new JButton(new HelpAction());
092        btn.setName("button.help");
093        pnl.add(btn);
094
095        pnl.setBorder(BorderFactory.createLoweredBevelBorder());
096        return pnl;
097    }
098
099    private void registerListeners() {
100        resolver.addPropertyChangeListener(applyResolutionAction);
101    }
102
103    private void unregisterListeners() {
104        resolver.removePropertyChangeListener(applyResolutionAction);
105        resolver.unregisterListeners();
106    }
107
108    /**
109     * builds the GUI
110     */
111    protected void build() {
112        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
113        getContentPane().setLayout(new BorderLayout());
114
115        titleLabel = new JLabel();
116        titleLabel.setHorizontalAlignment(JLabel.CENTER);
117        getContentPane().add(titleLabel, BorderLayout.NORTH);
118
119        updateTitle();
120
121        resolver = new ConflictResolver();
122        resolver.setName("panel.conflictresolver");
123        getContentPane().add(resolver, BorderLayout.CENTER);
124        getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
125
126        resolver.addPropertyChangeListener(this);
127        HelpUtil.setHelpContext(this.getRootPane(), ht("Dialog/Conflict"));
128
129        registerListeners();
130    }
131
132    /**
133     * Constructs a new {@code ConflictResolutionDialog}.
134     * @param parent parent component
135     */
136    public ConflictResolutionDialog(Component parent) {
137        super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
138        build();
139        pack();
140        if (getInsets().top > 0) {
141            titleLabel.setVisible(false);
142        }
143    }
144
145    /**
146     * Replies the conflict resolver component.
147     * @return the conflict resolver component
148     */
149    public ConflictResolver getConflictResolver() {
150        return resolver;
151    }
152
153    /**
154     * Action for canceling conflict resolution
155     */
156    class CancelAction extends AbstractAction {
157        CancelAction() {
158            putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution and close the dialog"));
159            putValue(Action.NAME, tr("Cancel"));
160            putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
161            setEnabled(true);
162        }
163
164        @Override
165        public void actionPerformed(ActionEvent arg0) {
166            closeDialog();
167        }
168    }
169
170    /**
171     * Action for canceling conflict resolution
172     */
173    static class HelpAction extends AbstractAction {
174        HelpAction() {
175            putValue(Action.SHORT_DESCRIPTION, tr("Show help information"));
176            putValue(Action.NAME, tr("Help"));
177            putValue(Action.SMALL_ICON, ImageProvider.get("help"));
178            setEnabled(true);
179        }
180
181        @Override
182        public void actionPerformed(ActionEvent arg0) {
183            HelpBrowser.setUrlForHelpTopic(ht("/Dialog/Conflict"));
184        }
185    }
186
187    /**
188     * Action for applying resolved differences in a conflict
189     *
190     */
191    class ApplyResolutionAction extends AbstractAction implements PropertyChangeListener {
192        ApplyResolutionAction() {
193            putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts and close the dialog"));
194            putValue(Action.NAME, tr("Apply Resolution"));
195            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs", "conflict"));
196            updateEnabledState();
197        }
198
199        protected void updateEnabledState() {
200            setEnabled(resolver.isResolvedCompletely());
201        }
202
203        @Override
204        public void actionPerformed(ActionEvent arg0) {
205            if (!resolver.isResolvedCompletely()) {
206                Object[] options = {
207                        tr("Close anyway"),
208                        tr("Continue resolving")};
209                int ret = JOptionPane.showOptionDialog(Main.parent,
210                        tr("<html>You did not finish to merge the differences in this conflict.<br>"
211                                + "Conflict resolutions will not be applied unless all differences<br>"
212                                + "are resolved.<br>"
213                                + "Click <strong>{0}</strong> to close anyway.<strong> Already<br>"
214                                + "resolved differences will not be applied.</strong><br>"
215                                + "Click <strong>{1}</strong> to return to resolving conflicts.</html>",
216                                options[0].toString(), options[1].toString()
217                        ),
218                        tr("Conflict not resolved completely"),
219                        JOptionPane.YES_NO_OPTION,
220                        JOptionPane.WARNING_MESSAGE,
221                        null,
222                        options,
223                        options[1]
224                );
225                switch(ret) {
226                case JOptionPane.YES_OPTION:
227                    closeDialog();
228                    break;
229                default:
230                    return;
231                }
232            }
233            Command cmd = resolver.buildResolveCommand();
234            Main.main.undoRedo.add(cmd);
235            closeDialog();
236        }
237
238        @Override
239        public void propertyChange(PropertyChangeEvent evt) {
240            if (evt.getPropertyName().equals(ConflictResolver.RESOLVED_COMPLETELY_PROP)) {
241                updateEnabledState();
242            }
243        }
244    }
245
246    protected void updateTitle() {
247        updateTitle(null);
248    }
249
250    protected void updateTitle(OsmPrimitive my) {
251        if (my == null) {
252            setTitle(tr("Resolve conflicts"));
253        } else {
254            setTitle(tr("Resolve conflicts for ''{0}''", my.getDisplayName(DefaultNameFormatter.getInstance())));
255        }
256    }
257
258    @Override
259    public void setTitle(String title) {
260        super.setTitle(title);
261        if (titleLabel != null) {
262            titleLabel.setText(title);
263        }
264    }
265
266    @Override
267    public void propertyChange(PropertyChangeEvent evt) {
268        if (evt.getPropertyName().equals(ConflictResolver.MY_PRIMITIVE_PROP)) {
269            updateTitle((OsmPrimitive) evt.getNewValue());
270        }
271    }
272}