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