From b85a07b87428d92e70ae9faab4a57a1bdc88e880 Mon Sep 17 00:00:00 2001 From: myk Date: Wed, 20 Feb 2013 03:01:13 +0000 Subject: [PATCH] combine ErrorViewer and BugzReporter into a new combined dialog with forum-launching capabilities --- src/main/java/forge/error/BugReporter.java | 320 ++++++++++++++++++++ src/main/java/forge/gui/toolbox/FLabel.java | 13 +- 2 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 src/main/java/forge/error/BugReporter.java diff --git a/src/main/java/forge/error/BugReporter.java b/src/main/java/forge/error/BugReporter.java new file mode 100644 index 00000000000..706fd326373 --- /dev/null +++ b/src/main/java/forge/error/BugReporter.java @@ -0,0 +1,320 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.error; + +import java.awt.Desktop; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URI; +import java.util.ArrayList; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.KeyStroke; + +import org.apache.commons.lang3.StringUtils; + +import net.miginfocom.swing.MigLayout; +import forge.Singletons; +import forge.gui.WrapLayout; +import forge.gui.toolbox.FHyperlink; +import forge.gui.toolbox.FLabel; +import forge.model.BuildInfo; +import forge.properties.ForgeProps; +import forge.properties.NewConstants; + +/** + * The class ErrorViewer. Enables showing and saving error messages that + * occurred in forge. + * + * @author Clemens Koza + * @version V1.0 02.08.2009 + */ +public class BugReporter { + /** + * Shows exception information in a format ready to post to the forum as a crash report. Uses the exception's message + * as the reason if message is null. + */ + public static void reportException(final Throwable ex, final String message) { + if (ex == null) { + return; + } + if (message != null) { + System.err.println(message); + } + ex.printStackTrace(); + + StringBuilder sb = new StringBuilder(); + sb.append("Description: [describe what you were doing when the crash occurred]\n\n"); + _buildSpoilerHeader(sb, ex.getClass().getSimpleName()); + sb.append(null == message ? ex.getMessage() : message).append("\n\n"); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + sb.append(sw.toString()); + + _buildSpoilerFooter(sb); + + _showDialog("Report a crash", sb.toString(), false); + } + + /** + * Alias for reportException(ex, null). + */ + public static void reportException(final Throwable ex) { + reportException(ex, null); + } + + /** + * Alias for reportException(ex, String.format(format, args)). + */ + public static void reportException(final Throwable ex, final String format, final Object... args) { + reportException(ex, String.format(format, args)); + } + + /** + * Shows a forum post template for reporting a bug. + */ + public static void reportBug(String details) { + StringBuilder sb = new StringBuilder(); + sb.append("Description: [describe the problem]\n\n"); + _buildSpoilerHeader(sb, "General bug report"); + if (null != details && !details.isEmpty()) { + sb.append("\n\n"); + sb.append(details); + } + _buildSpoilerFooter(sb); + + _showDialog("Report a bug", sb.toString(), false); + } + + /** + * Shows thread stack information in a format ready to post to the forum. + */ + public static void reportThreadStacks(final String message) { + StringBuilder sb = new StringBuilder(); + sb.append("Description: [describe what you were doing at the time]\n\n"); + _buildSpoilerHeader(sb, "Thread stack dump"); + if (null != message) { + sb.append(message); + } + sb.append("\n\n"); + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + final Map traces = Thread.getAllStackTraces(); + for (final Entry e : traces.entrySet()) { + pw.println(); + pw.printf("%s (%s):%n", e.getKey().getName(), e.getKey().getId()); + for (final StackTraceElement el : e.getValue()) { + pw.println(el); + } + } + + sb.append(sw.toString()); + _buildSpoilerFooter(sb); + _showDialog("Thread stack dump", sb.toString(), false); + } + + /** + * Alias for reportThreadStacks(String.format(format, args)) + */ + public static void reportThreadStacks(final String format, final Object... args) { + reportThreadStacks(String.format(format, args)); + } + + private static StringBuilder _buildSpoilerHeader(StringBuilder sb, String reportTitle) { + sb.append("[spoiler=").append(reportTitle).append("][code]"); + sb.append("\nForge Version: ").append(Singletons.getModel().getBuildInfo().toPrettyString()); + sb.append("\nOperating System: ").append(System.getProperty("os.name")) + .append(" ").append(System.getProperty("os.version")) + .append(" ").append(System.getProperty("os.arch")); + sb.append("\nJava Version: ").append(System.getProperty("java.version")) + .append(" ").append(System.getProperty("java.vendor")); + return sb; + } + + private static StringBuilder _buildSpoilerFooter(StringBuilder sb) { + sb.append("\n[/code][/spoiler]"); + return sb; + } + + private static void _showDialog(String title, String text, boolean showExitAppBtn) { + JTextArea area = new JTextArea(text); + area.setFont(new Font("Monospaced", Font.PLAIN, 10)); + area.setEditable(false); + area.setLineWrap(true); + area.setWrapStyleWord(true); + + String helpText = "A template for a post in the bug reports forum topic is shown below. Just select 'Copy and go to forum' " + + "and the template will be copied to your system clipboard and the forum page will open in your browser. " + + "Then all you have to do is paste the text into a forum post and edit the description line."; + String helpUrlLabel = "Reporting bugs in Forge is very important. We sincerely thank you for your time." + + " For help writing a solid bug report, please see:"; + String helpUrl = "http://www.slightlymagic.net/forum/viewtopic.php?f=26&p=109925#p109925"; + JPanel helpPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 4, 2)); + for (String word : helpUrlLabel.split(" ")) { + helpPanel.add(new FLabel.Builder().text("" + word + "").useSkinColors(false).build()); + } + helpPanel.add(new FHyperlink.Builder().url(helpUrl).text("this post").useSkinColors(false).build()); + + JPanel p = new JPanel(new MigLayout("wrap")); + p.add(new FLabel.Builder().text(helpText).useSkinColors(false).build(), "gap 5"); + p.add(helpPanel, "w 600"); + p.add(new JScrollPane(area), "w 100%, h 100%, gaptop 5"); + + // determine proper forum URL + BuildInfo bi = Singletons.getModel().getBuildInfo(); + String forgeVersion = bi.getVersion(); + final String url; + if (StringUtils.containsIgnoreCase(forgeVersion, "svn") + || StringUtils.containsIgnoreCase(forgeVersion, "snapshot")) { + url = "http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=6333&start=54564487645#bottom"; + } else { + url = "http://www.slightlymagic.net/forum/viewforum.php?f=26"; + } + + // Button is not modified, String gets the automatic listener to hide + // the dialog + ArrayList options = new ArrayList(); + options.add(new JButton(new _CopyAndGo(url, area))); + options.add(new JButton(new _SaveAction(area))); + options.add("Close"); + if (showExitAppBtn) { + options.add(new JButton(new _ExitAction())); + } + + JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, + JOptionPane.DEFAULT_OPTION, null, options.toArray(), options.get(0)); + JDialog dlg = pane.createDialog(null, title); + dlg.setSize(600, 500); + dlg.setResizable(true); + dlg.setLocationRelativeTo(null); + dlg.setVisible(true); + dlg.dispose(); + } + + @SuppressWarnings("serial") + private static class _CopyAndGo extends AbstractAction { + private final String url; + private final JTextArea text; + + public _CopyAndGo(String url, JTextArea text) { + super("Copy and go to forum"); + this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + + this.url = url; + this.text = text; + } + + @Override + public void actionPerformed(final ActionEvent e) { + try { + // copy text to clipboard + StringSelection ss = new StringSelection(text.getText()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null); + + // browse to url + Desktop.getDesktop().browse(new URI(url)); + } catch (Exception ex) { + JOptionPane.showMessageDialog(null, + "Sorry, a problem occurred while opening the forum in your default browser.", + "A problem occured", JOptionPane.ERROR_MESSAGE); + } + } + } + + @SuppressWarnings("serial") + private static class _SaveAction extends AbstractAction { + private static JFileChooser c; + private final JTextArea area; + + public _SaveAction(final JTextArea areaParam) { + super("Save to file"); + this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + this.area = areaParam; + } + + @Override + public void actionPerformed(final ActionEvent e) { + if (c == null) { + c = new JFileChooser(); + } + + File f; + long curTime = System.currentTimeMillis(); + for (int i = 0;; i++) { + final String name = String.format("%TF-%02d.txt", curTime, i); + f = new File(name); + if (!f.exists()) { + break; + } + } + + c.setSelectedFile(f); + c.showSaveDialog(null); + f = c.getSelectedFile(); + + try { + final BufferedWriter bw = new BufferedWriter(new FileWriter(f)); + bw.write(this.area.getText()); + bw.close(); + } catch (final IOException ex) { + JOptionPane.showMessageDialog(area.getTopLevelAncestor(), + ForgeProps.getLocalized(NewConstants.Lang.ErrorViewer.ERRORS.SAVE_MESSAGE), + "Error saving file", JOptionPane.ERROR_MESSAGE); + } + } + } + + @SuppressWarnings("serial") + private static class _ExitAction extends AbstractAction { + public _ExitAction() { + super("Exit application"); + this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + } + + @Override + public void actionPerformed(final ActionEvent e) { + System.exit(0); + } + } + + // disable instantiation + private BugReporter() { } +} diff --git a/src/main/java/forge/gui/toolbox/FLabel.java b/src/main/java/forge/gui/toolbox/FLabel.java index 4f05e4679f7..a7b1a2e86a3 100644 --- a/src/main/java/forge/gui/toolbox/FLabel.java +++ b/src/main/java/forge/gui/toolbox/FLabel.java @@ -72,6 +72,7 @@ public class FLabel extends JLabel implements ILocalRepaint { private boolean bldIconInBackground = false; private boolean bldIconScaleAuto = true; protected boolean bldReactOnMouseDown = false; + private boolean bldUseSkinColors = true; protected String bldText, bldToolTip; private ImageIcon bldIcon; @@ -124,6 +125,10 @@ public class FLabel extends JLabel implements ILocalRepaint { public Builder reactOnMouseDown(final boolean b0) { this.bldReactOnMouseDown = b0; return this; } public Builder reactOnMouseDown() { reactOnMouseDown(true); return this; } + /**@param b0   boolean that controls whether the text uses skin colors + * @return {@link forge.gui.toolbox.Builder} */ + public Builder useSkinColors(final boolean b0) { bldUseSkinColors = b0; return this; } + /**@param c0   {@link forge.Command} to execute if clicked * @return {@link forge.gui.toolbox.Builder} */ public Builder cmdClick(final Command c0) { this.bldCmd = c0; return this; } @@ -217,9 +222,11 @@ public class FLabel extends JLabel implements ILocalRepaint { }); } - // Non-custom display properties - this.setForeground(clrText); - this.setBackground(clrMain); + if (b0.bldUseSkinColors) { + // Non-custom display properties + this.setForeground(clrText); + this.setBackground(clrMain); + } // Resize adapter this.removeComponentListener(cadResize);