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}