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