// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.tools.bugreport;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Component;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.ExpertToggleAction;
import org.openstreetmap.josm.gui.preferences.plugin.PluginPreference;
import org.openstreetmap.josm.gui.util.GuiHelper;
import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
import org.openstreetmap.josm.gui.widgets.UrlLabel;
import org.openstreetmap.josm.plugins.PluginDownloadTask;
import org.openstreetmap.josm.plugins.PluginHandler;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.InputMapUtils;
import org.openstreetmap.josm.tools.bugreport.BugReportQueue.SuppressionMode;
/**
* This is a dialog that can be used to display a bug report.
* <p>
* It displays the bug to the user and asks the user to submit a bug report.
* @author Michael Zangl
* @since 10649
*/
public class BugReportDialog extends JDialog {
private static final int MAX_MESSAGE_SIZE = 500;
// This is explicitly not an ExtendedDialog - we still want to be able to display bug reports if there are problems with preferences/..
private final JPanel content = new JPanel(new GridBagLayout());
private final BugReport report;
private final DebugTextDisplay textPanel;
private JCheckBox cbSuppressSingle;
private JCheckBox cbSuppressAll;
/**
* Create a new dialog.
* @param report The report to display the dialog for.
*/
public BugReportDialog(BugReport report) {
super(findParent(), tr("You have encountered a bug in JOSM"));
this.report = report;
textPanel = new DebugTextDisplay(report);
setContentPane(content);
addMessageSection();
addUpToDateSection();
// TODO: Notify user about plugin updates, then remove that notification that is displayed before this dialog is displayed.
addCreateTicketSection();
if (ExpertToggleAction.isExpert()) {
addDebugTextSection();
}
addIgnoreButton();
pack();
setModal(true);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
InputMapUtils.addEscapeAction(getRootPane(), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
closeDialog();
}
});
}
/**
* The message informing the user what happened.
*/
private void addMessageSection() {
String message = tr(
"An unexpected exception occurred.\n" + "This is always a coding error. If you are running the latest "
+ "version of JOSM, please consider being kind and file a bug report.");
Icon icon = UIManager.getIcon("OptionPane.errorIcon");
JPanel panel = new JPanel(new GridBagLayout());
panel.add(new JLabel(icon), GBC.std().insets(0, 0, 10, 0));
JMultilineLabel messageLabel = new JMultilineLabel(message);
messageLabel.setMaxWidth(MAX_MESSAGE_SIZE);
panel.add(messageLabel, GBC.eol().fill());
content.add(panel, GBC.eop().fill(GBC.HORIZONTAL).insets(20, 10, 10, 10));
}
private void addDebugTextSection() {
JPanel panel = new JPanel(new GridBagLayout());
addBorder(panel, tr("Debug information"));
panel.add(textPanel, GBC.eop().fill());
panel.add(new JLabel(tr("Manually report at:")+' '), GBC.std());
panel.add(new UrlLabel(Main.getJOSMWebsite() + "/newticket"), GBC.std().fill(GBC.HORIZONTAL));
JButton copy = new JButton("Copy to clipboard");
copy.addActionListener(e -> textPanel.copyToClipboard());
panel.add(copy, GBC.eol().anchor(GBC.EAST));
content.add(panel, GBC.eop().fill());
}
private void addUpToDateSection() {
JPanel panel = new JosmUpdatePanel();
addBorder(panel, tr("Is JOSM up to date?"));
content.add(panel, GBC.eop().fill(GBC.HORIZONTAL));
}
private void addCreateTicketSection() {
JPanel panel = new JPanel(new GridBagLayout());
addBorder(panel, tr("Send bug report"));
JMultilineLabel helpText = new JMultilineLabel(
tr("If you are running the latest version of JOSM and the plugins, "
+ "please file a bug report in our bugtracker.\n"
+ "There the error information should already be "
+ "filled in for you. Please include information on how to reproduce "
+ "the error and try to supply as much detail as possible."));
helpText.setMaxWidth(MAX_MESSAGE_SIZE);
panel.add(helpText, GBC.eop().fill(GridBagConstraints.HORIZONTAL));
Component settings = GBC.glue(0, 0);
if (ExpertToggleAction.isExpert()) {
// The default settings should be fine in most situations.
settings = new BugReportSettingsPanel(report);
}
panel.add(settings);
JButton sendBugReportButton = new JButton(tr("Report Bug"), ImageProvider.get("bug"));
sendBugReportButton.addActionListener(e -> sendBug());
panel.add(sendBugReportButton, GBC.eol().insets(0, 0, 0, 0).anchor(GBC.SOUTHEAST));
content.add(panel, GBC.eop().fill(GBC.HORIZONTAL));
}
private static void addBorder(JPanel panel, String title) {
panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder(title), BorderFactory
.createEmptyBorder(5, 5, 5, 5)));
}
private void addIgnoreButton() {
JPanel panel = new JPanel(new GridBagLayout());
cbSuppressSingle = new JCheckBox(tr("Suppress this error for this session."));
cbSuppressSingle.setVisible(false);
panel.add(cbSuppressSingle, GBC.std(0, 0).fill(GBC.HORIZONTAL));
cbSuppressAll = new JCheckBox(tr("Suppress further error dialogs for this session."));
cbSuppressAll.setVisible(false);
panel.add(cbSuppressAll, GBC.std(0, 1).fill(GBC.HORIZONTAL));
JButton ignore = new JButton(tr("Ignore this error."));
ignore.addActionListener(e -> closeDialog());
panel.add(ignore, GBC.std(1, 0).span(1, 2).anchor(GBC.CENTER));
content.add(panel, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 0, 10, 10));
}
/**
* Shows or hides the suppress errors button
* @param showSuppress <code>true</code> to show the suppress errors checkbox.
*/
public void setShowSuppress(boolean showSuppress) {
cbSuppressSingle.setVisible(showSuppress);
pack();
}
/**
* Shows or hides the suppress all errors button
* @param showSuppress <code>true</code> to show the suppress errors checkbox.
* @since 10819
*/
public void setShowSuppressAll(boolean showSuppress) {
cbSuppressAll.setVisible(showSuppress);
pack();
}
/**
* Check if the checkbox to suppress further errors was selected
* @return <code>true</code> if the user wishes to suppress errors.
*/
public SuppressionMode shouldSuppressFurtherErrors() {
if (cbSuppressAll.isSelected()) {
return SuppressionMode.ALL;
} else if (cbSuppressSingle.isSelected()) {
return SuppressionMode.SAME;
} else {
return SuppressionMode.NONE;
}
}
private void closeDialog() {
setVisible(false);
}
private void sendBug() {
BugReportSender.reportBug(textPanel.getCodeText());
}
/**
* A safe way to find a matching parent frame.
* @return The parent frame.
*/
private static Frame findParent() {
Component current = Main.parent;
try {
// avoid cycles/invalid hirarchies
for (int i = 0; i < 20 && current != null; i++) {
if (current instanceof Frame) {
return (Frame) current;
}
current = current.getParent();
}
} catch (RuntimeException e) { // NOPMD
BugReport.intercept(e).put("current", current).warn();
}
return null;
}
/**
* Show the bug report for a given exception
* @param e The exception to display
* @param exceptionCounter A counter of how many exceptions have already been worked on
* @return The new suppression status
* @since 10819
*/
public static SuppressionMode showFor(ReportedException e, int exceptionCounter) {
if (e.isOutOfMemory()) {
// do not translate the string, as translation may raise an exception
JOptionPane.showMessageDialog(Main.parent, "JOSM is out of memory. " +
"Strange things may happen.\nPlease restart JOSM with the -Xmx###M option,\n" +
"where ### is the number of MB assigned to JOSM (e.g. 256).\n" +
"Currently, " + Runtime.getRuntime().maxMemory()/1024/1024 + " MB are available to JOSM.",
"Error",
JOptionPane.ERROR_MESSAGE
);
return SuppressionMode.NONE;
} else {
return GuiHelper.runInEDTAndWaitAndReturn(() -> {
PluginDownloadTask downloadTask = PluginHandler.updateOrdisablePluginAfterException(e);
if (downloadTask != null) {
// Ask for restart to install new plugin
PluginPreference.notifyDownloadResults(
Main.parent, downloadTask, !downloadTask.getDownloadedPlugins().isEmpty());
return SuppressionMode.NONE;
}
BugReport report = new BugReport(e);
BugReportDialog dialog = new BugReportDialog(report);
dialog.setShowSuppress(exceptionCounter > 0);
dialog.setShowSuppressAll(exceptionCounter > 1);
dialog.setVisible(true);
return dialog.shouldSuppressFurtherErrors();
});
}
}
}