001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.bugreport;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.GridBagConstraints;
008import java.awt.GridBagLayout;
009import java.awt.event.ActionEvent;
010
011import javax.swing.AbstractAction;
012import javax.swing.BorderFactory;
013import javax.swing.Icon;
014import javax.swing.JButton;
015import javax.swing.JCheckBox;
016import javax.swing.JDialog;
017import javax.swing.JLabel;
018import javax.swing.JOptionPane;
019import javax.swing.JPanel;
020import javax.swing.UIManager;
021
022import org.openstreetmap.josm.actions.ExpertToggleAction;
023import org.openstreetmap.josm.gui.MainApplication;
024import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference;
025import org.openstreetmap.josm.gui.util.GuiHelper;
026import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
027import org.openstreetmap.josm.gui.widgets.UrlLabel;
028import org.openstreetmap.josm.plugins.PluginDownloadTask;
029import org.openstreetmap.josm.plugins.PluginHandler;
030import org.openstreetmap.josm.spi.preferences.Config;
031import org.openstreetmap.josm.tools.GBC;
032import org.openstreetmap.josm.tools.ImageProvider;
033import org.openstreetmap.josm.tools.InputMapUtils;
034import org.openstreetmap.josm.tools.bugreport.BugReport;
035import org.openstreetmap.josm.tools.bugreport.BugReportQueue.SuppressionMode;
036import org.openstreetmap.josm.tools.bugreport.BugReportSender;
037import org.openstreetmap.josm.tools.bugreport.ReportedException;
038
039/**
040 * This is a dialog that can be used to display a bug report.
041 * <p>
042 * It displays the bug to the user and asks the user to submit a bug report.
043 * @author Michael Zangl
044 * @since 10649
045 */
046public class BugReportDialog extends JDialog {
047    private static final int MAX_MESSAGE_SIZE = 500;
048    // This is explicitly not an ExtendedDialog - we still want to be able to display bug reports if there are problems with preferences/..
049    private final JPanel content = new JPanel(new GridBagLayout());
050    private final BugReport report;
051    private final DebugTextDisplay textPanel;
052    private JCheckBox cbSuppressSingle;
053    private JCheckBox cbSuppressAll;
054
055    /**
056     * Create a new dialog.
057     * @param report The report to display the dialog for.
058     */
059    public BugReportDialog(BugReport report) {
060        super(MainApplication.getMainFrame(), tr("You have encountered a bug in JOSM"));
061        this.report = report;
062        textPanel = new DebugTextDisplay(report);
063        setContentPane(content);
064
065        addMessageSection();
066
067        addUpToDateSection();
068        // TODO: Notify user about plugin updates, then remove that notification that is displayed before this dialog is displayed.
069
070        addCreateTicketSection();
071
072        if (ExpertToggleAction.isExpert()) {
073            addDebugTextSection();
074        }
075
076        addIgnoreButton();
077
078        pack();
079        setModal(true);
080        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
081
082        InputMapUtils.addEscapeAction(getRootPane(), new AbstractAction() {
083            @Override
084            public void actionPerformed(ActionEvent e) {
085                closeDialog();
086            }
087        });
088    }
089
090    /**
091     * The message informing the user what happened.
092     */
093    private void addMessageSection() {
094        String message = tr(
095                "An unexpected exception occurred.\n" + "This is always a coding error. If you are running the latest "
096                        + "version of JOSM, please consider being kind and file a bug report.");
097        Icon icon = UIManager.getIcon("OptionPane.errorIcon");
098
099        JPanel panel = new JPanel(new GridBagLayout());
100
101        panel.add(new JLabel(icon), GBC.std().insets(0, 0, 10, 0));
102        JMultilineLabel messageLabel = new JMultilineLabel(message);
103        messageLabel.setMaxWidth(MAX_MESSAGE_SIZE);
104        panel.add(messageLabel, GBC.eol().fill());
105        content.add(panel, GBC.eop().fill(GBC.HORIZONTAL).insets(20, 10, 10, 10));
106    }
107
108    private void addDebugTextSection() {
109        JPanel panel = new JPanel(new GridBagLayout());
110        addBorder(panel, tr("Debug information"));
111        panel.add(textPanel, GBC.eop().fill());
112
113        panel.add(new JLabel(tr("Manually report at:")+' '), GBC.std());
114        panel.add(new UrlLabel(Config.getUrls().getJOSMWebsite() + "/newticket"), GBC.std().fill(GBC.HORIZONTAL));
115        JButton copy = new JButton("Copy to clipboard");
116        copy.addActionListener(e -> textPanel.copyToClipboard());
117        panel.add(copy, GBC.eol().anchor(GBC.EAST));
118        content.add(panel, GBC.eop().fill());
119    }
120
121    private void addUpToDateSection() {
122        JPanel panel = new JosmUpdatePanel();
123        addBorder(panel, tr("Is JOSM up to date?"));
124        content.add(panel, GBC.eop().fill(GBC.HORIZONTAL));
125    }
126
127    private void addCreateTicketSection() {
128        JPanel panel = new JPanel(new GridBagLayout());
129        addBorder(panel, tr("Send bug report"));
130
131        JMultilineLabel helpText = new JMultilineLabel(
132                tr("If you are running the latest version of JOSM and the plugins, "
133                        + "please file a bug report in our bugtracker.\n"
134                        + "There the error information should already be "
135                        + "filled in for you. Please include information on how to reproduce "
136                        + "the error and try to supply as much detail as possible."));
137        helpText.setMaxWidth(MAX_MESSAGE_SIZE);
138        panel.add(helpText, GBC.eop().fill(GridBagConstraints.HORIZONTAL));
139
140        Component settings = GBC.glue(0, 0);
141        if (ExpertToggleAction.isExpert()) {
142            // The default settings should be fine in most situations.
143            settings = new BugReportSettingsPanel(report);
144        }
145        panel.add(settings);
146
147        JButton sendBugReportButton = new JButton(tr("Report bug"), ImageProvider.getIfAvailable("bug"));
148        sendBugReportButton.addActionListener(e -> sendBug());
149        panel.add(sendBugReportButton, GBC.eol().insets(0, 0, 0, 0).anchor(GBC.SOUTHEAST));
150        content.add(panel, GBC.eop().fill(GBC.HORIZONTAL));
151    }
152
153    private static void addBorder(JPanel panel, String title) {
154        panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(title), BorderFactory
155                .createEmptyBorder(5, 5, 5, 5)));
156    }
157
158    private void addIgnoreButton() {
159        JPanel panel = new JPanel(new GridBagLayout());
160        cbSuppressSingle = new JCheckBox(tr("Suppress this error for this session."));
161        cbSuppressSingle.setVisible(false);
162        panel.add(cbSuppressSingle, GBC.std(0, 0).fill(GBC.HORIZONTAL));
163        cbSuppressAll = new JCheckBox(tr("Suppress further error dialogs for this session."));
164        cbSuppressAll.setVisible(false);
165        panel.add(cbSuppressAll, GBC.std(0, 1).fill(GBC.HORIZONTAL));
166        JButton ignore = new JButton(tr("Ignore this error."));
167        ignore.addActionListener(e -> closeDialog());
168        panel.add(ignore, GBC.std(1, 0).span(1, 2).anchor(GBC.CENTER));
169        content.add(panel, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 10, 10));
170    }
171
172    /**
173     * Shows or hides the suppress errors button
174     * @param showSuppress <code>true</code> to show the suppress errors checkbox.
175     */
176    public void setShowSuppress(boolean showSuppress) {
177        cbSuppressSingle.setVisible(showSuppress);
178        pack();
179    }
180
181    /**
182     * Shows or hides the suppress all errors button
183     * @param showSuppress <code>true</code> to show the suppress errors checkbox.
184     * @since 10819
185     */
186    public void setShowSuppressAll(boolean showSuppress) {
187        cbSuppressAll.setVisible(showSuppress);
188        pack();
189    }
190
191    /**
192     * Check if the checkbox to suppress further errors was selected
193     * @return <code>true</code> if the user wishes to suppress errors.
194     */
195    public SuppressionMode shouldSuppressFurtherErrors() {
196        if (cbSuppressAll.isSelected()) {
197            return SuppressionMode.ALL;
198        } else if (cbSuppressSingle.isSelected()) {
199            return SuppressionMode.SAME;
200        } else {
201            return SuppressionMode.NONE;
202        }
203    }
204
205    private void closeDialog() {
206        setVisible(false);
207    }
208
209    private void sendBug() {
210        BugReportSender.reportBug(textPanel.getCodeText());
211    }
212
213    /**
214     * Show the bug report for a given exception
215     * @param e The exception to display
216     * @param exceptionCounter A counter of how many exceptions have already been worked on
217     * @return The new suppression status
218     * @since 10819
219     */
220    public static SuppressionMode showFor(ReportedException e, int exceptionCounter) {
221        if (e.isOutOfMemory()) {
222            // do not translate the string, as translation may raise an exception
223            JOptionPane.showMessageDialog(MainApplication.getMainFrame(), "JOSM is out of memory. " +
224                    "Strange things may happen.\nPlease restart JOSM with the -Xmx###M option,\n" +
225                    "where ### is the number of MB assigned to JOSM (e.g. 256).\n" +
226                    "Currently, " + Runtime.getRuntime().maxMemory()/1024/1024 + " MB are available to JOSM.",
227                    "Error",
228                    JOptionPane.ERROR_MESSAGE
229                    );
230            return SuppressionMode.NONE;
231        } else {
232            return GuiHelper.runInEDTAndWaitAndReturn(() -> {
233                PluginDownloadTask downloadTask = PluginHandler.updateOrdisablePluginAfterException(e);
234                if (downloadTask != null) {
235                    // Ask for restart to install new plugin
236                    PluginPreference.notifyDownloadResults(
237                            MainApplication.getMainFrame(), downloadTask, !downloadTask.getDownloadedPlugins().isEmpty());
238                    return SuppressionMode.NONE;
239                }
240
241                BugReport report = new BugReport(e);
242                BugReportDialog dialog = new BugReportDialog(report);
243                dialog.setShowSuppress(exceptionCounter > 0);
244                dialog.setShowSuppressAll(exceptionCounter > 1);
245                dialog.setVisible(true);
246                return dialog.shouldSuppressFurtherErrors();
247            });
248        }
249    }
250}