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