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(new FlowLayout(FlowLayout.CENTER));
080
081        applyResolutionAction = new ApplyResolutionAction();
082        JButton btn = new JButton(applyResolutionAction);
083        btn.setName("button.apply");
084        pnl.add(btn);
085
086        btn = new JButton(new CancelAction());
087        btn.setName("button.cancel");
088        pnl.add(btn);
089
090        btn = new JButton(new HelpAction());
091        btn.setName("button.help");
092        pnl.add(btn);
093
094        pnl.setBorder(BorderFactory.createLoweredBevelBorder());
095        return pnl;
096    }
097
098    private void registerListeners() {
099        resolver.addPropertyChangeListener(applyResolutionAction);
100    }
101
102    private void unregisterListeners() {
103        resolver.removePropertyChangeListener(applyResolutionAction);
104        resolver.unregisterListeners();
105    }
106
107    /**
108     * builds the GUI
109     */
110    protected void build() {
111        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
112        getContentPane().setLayout(new BorderLayout());
113
114        titleLabel = new JLabel();
115        titleLabel.setHorizontalAlignment(JLabel.CENTER);
116        getContentPane().add(titleLabel, BorderLayout.NORTH);
117
118        updateTitle();
119
120        resolver = new ConflictResolver();
121        resolver.setName("panel.conflictresolver");
122        getContentPane().add(resolver, BorderLayout.CENTER);
123        getContentPane().add(buildButtonRow(), BorderLayout.SOUTH);
124
125        resolver.addPropertyChangeListener(this);
126        HelpUtil.setHelpContext(this.getRootPane(), ht("Dialog/Conflict"));
127
128        registerListeners();
129    }
130
131    /**
132     * Constructs a new {@code ConflictResolutionDialog}.
133     * @param parent parent component
134     */
135    public ConflictResolutionDialog(Component parent) {
136        super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
137        build();
138        pack();
139        if (getInsets().top > 0) {
140            titleLabel.setVisible(false);
141        }
142    }
143
144    /**
145     * Replies the conflict resolver component.
146     * @return the conflict resolver component
147     */
148    public ConflictResolver getConflictResolver() {
149        return resolver;
150    }
151
152    /**
153     * Action for canceling conflict resolution
154     */
155    class CancelAction extends AbstractAction {
156        CancelAction() {
157            putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution and close the dialog"));
158            putValue(Action.NAME, tr("Cancel"));
159            putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
160            setEnabled(true);
161        }
162
163        @Override
164        public void actionPerformed(ActionEvent arg0) {
165            closeDialog();
166        }
167    }
168
169    /**
170     * Action for canceling conflict resolution
171     */
172    static class HelpAction extends AbstractAction {
173        HelpAction() {
174            putValue(Action.SHORT_DESCRIPTION, tr("Show help information"));
175            putValue(Action.NAME, tr("Help"));
176            putValue(Action.SMALL_ICON, ImageProvider.get("help"));
177            setEnabled(true);
178        }
179
180        @Override
181        public void actionPerformed(ActionEvent arg0) {
182            HelpBrowser.setUrlForHelpTopic(ht("/Dialog/Conflict"));
183        }
184    }
185
186    /**
187     * Action for applying resolved differences in a conflict
188     *
189     */
190    class ApplyResolutionAction extends AbstractAction implements PropertyChangeListener {
191        ApplyResolutionAction() {
192            putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts and close the dialog"));
193            putValue(Action.NAME, tr("Apply Resolution"));
194            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs", "conflict"));
195            updateEnabledState();
196        }
197
198        protected void updateEnabledState() {
199            setEnabled(resolver.isResolvedCompletely());
200        }
201
202        @Override
203        public void actionPerformed(ActionEvent arg0) {
204            if (!resolver.isResolvedCompletely()) {
205                Object[] options = {
206                        tr("Close anyway"),
207                        tr("Continue resolving")};
208                int ret = JOptionPane.showOptionDialog(Main.parent,
209                        tr("<html>You did not finish to merge the differences in this conflict.<br>"
210                                + "Conflict resolutions will not be applied unless all differences<br>"
211                                + "are resolved.<br>"
212                                + "Click <strong>{0}</strong> to close anyway.<strong> Already<br>"
213                                + "resolved differences will not be applied.</strong><br>"
214                                + "Click <strong>{1}</strong> to return to resolving conflicts.</html>",
215                                options[0].toString(), options[1].toString()
216                        ),
217                        tr("Conflict not resolved completely"),
218                        JOptionPane.YES_NO_OPTION,
219                        JOptionPane.WARNING_MESSAGE,
220                        null,
221                        options,
222                        options[1]
223                );
224                switch(ret) {
225                case JOptionPane.YES_OPTION:
226                    closeDialog();
227                    break;
228                default:
229                    return;
230                }
231            }
232            Command cmd = resolver.buildResolveCommand();
233            Main.main.undoRedo.add(cmd);
234            closeDialog();
235        }
236
237        @Override
238        public void propertyChange(PropertyChangeEvent evt) {
239            if (evt.getPropertyName().equals(ConflictResolver.RESOLVED_COMPLETELY_PROP)) {
240                updateEnabledState();
241            }
242        }
243    }
244
245    protected void updateTitle() {
246        updateTitle(null);
247    }
248
249    protected void updateTitle(OsmPrimitive my) {
250        if (my == null) {
251            setTitle(tr("Resolve conflicts"));
252        } else {
253            setTitle(tr("Resolve conflicts for ''{0}''", my.getDisplayName(DefaultNameFormatter.getInstance())));
254        }
255    }
256
257    @Override
258    public void setTitle(String title) {
259        super.setTitle(title);
260        if (titleLabel != null) {
261            titleLabel.setText(title);
262        }
263    }
264
265    @Override
266    public void propertyChange(PropertyChangeEvent evt) {
267        if (evt.getPropertyName().equals(ConflictResolver.MY_PRIMITIVE_PROP)) {
268            updateTitle((OsmPrimitive) evt.getNewValue());
269        }
270    }
271}