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