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