package chatty.gui.components;
import chatty.Chatty;
import chatty.Helper;
import chatty.util.DateTime;
import chatty.util.LogUtil;
import chatty.util.MiscUtil;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.LinkedList;
import java.util.logging.LogRecord;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
/**
*
* @author tduva
*/
public class ErrorMessage extends JDialog {
public static final int CONTINUE = 0;
public static final int QUIT = 1;
private static final int ERROR_LIMIT = 20;
private static final String TOP_MESSAGE = "<html><body width='300px'>An unexpected error has occured.";
private static final String MESSAGE = "<html><body width='300px'>"
+ "You can help to fix "
+ "the error by [url:https://docs.google.com/forms/d/1pCc1xcWHOK1JPetQNc_N3boijnm3srcPH8PcfyVYG_U/viewform reporting it]."
+ " Please include a copy of the report message below and a short description of what you were "
+ "doing when the error occured.<br /><br />"
+ "Alternatively to the report page, you can also send a report "
+ "via [url:http://www.twitch.tv/message/compose?to=tduva Twitch], "
+ "[url:https://twitter.com/ChattyClient Twitter] or "
+ "[url:mailto:chattyclient@gmail.com E-Mail]. "
+ "You can use something like [url:http://pastebin.com Pastebin.com] "
+ "to send the text as a link.";
private static final String MINIMIZED_MESSAGE = "<html><body width='300px'>"
+ "An unexpected error has occured. ([local:maximize Show more..])";
private static final int MINIMIZE_AFTER = 30*1000;
private final LinkedList<String> errors = new LinkedList<>();
private int errorCount;
private final GridBagConstraints contentGbc;
private final LinkLabel topMessage;
private final LinkLabel message;
private final LinkLabel minimizedMessage;
private final JButton continueProgram = new JButton("Continue");
private final JButton quitProgram = new JButton("Quit Program");
private final JButton copyText = new JButton("Copy to clipboard");
private final JTextArea debugMessage = new JTextArea();
private final Frame parent;
private final JPanel normal;
private final JPanel minimized;
private Timer minimizeTimer;
private long timeOpened;
private int result;
public ErrorMessage(Frame parent, LinkLabelListener linkLabelListener) {
super(parent);
this.parent = parent;
setTitle("Error");
setAlwaysOnTop(true);
setAutoRequestFocus(false);
setModal(true);
debugMessage.setLineWrap(false);
debugMessage.setEditable(false);
message = new LinkLabel(MESSAGE, linkLabelListener);
minimizeTimer = new Timer(5000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
update();
}
});
LinkLabelListener localListener = new LinkLabelListener() {
@Override
public void linkClicked(String type, String ref) {
if (ref.equals("minimize")) {
minimize();
} else if (ref.equals("maximize")) {
maximize();
}
}
};
topMessage = new LinkLabel(TOP_MESSAGE, localListener);
addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
stopTimer();
}
});
setLayout(new GridBagLayout());
contentGbc = makeGbc(0, 0, 2, 1);
contentGbc.fill = GridBagConstraints.BOTH;
contentGbc.weightx = 1;
contentGbc.weighty = 1;
contentGbc.insets = new Insets(0, 0, 0, 0);
minimized = new JPanel();
minimizedMessage = new LinkLabel(MINIMIZED_MESSAGE, localListener);
minimized.add(minimizedMessage);
normal = new JPanel(new GridBagLayout());
GridBagConstraints gbc;
ImageIcon icon = new ImageIcon(ErrorMessage.class.getResource("dialog-warning.png"));
setIconImage(icon.getImage());
gbc = makeGbc(0, 0, 2, 1);
gbc.insets.bottom = 0;
normal.add(topMessage, gbc);
gbc = makeGbc(0, 1, 2, 1);
normal.add(message, gbc);
gbc = makeGbc(1, 2, 1, 1);
copyText.setMargin(new Insets(0,10,-1,10));
gbc.insets = new Insets(5, 0, 0, 10);
gbc.anchor = GridBagConstraints.EAST;
normal.add(copyText, gbc);
gbc = makeGbc(0, 3, 2, 1);
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
JScrollPane scroll = new JScrollPane(debugMessage);
scroll.setPreferredSize(new Dimension(300,100));
normal.add(scroll, gbc);
gbc = makeGbc(0, 1, 1, 1);
gbc.anchor = GridBagConstraints.EAST;
gbc.insets = new Insets(10, 0, 10, 0);
gbc.weightx = 0.5;
add(continueProgram, gbc);
gbc = makeGbc(1, 1, 1, 1);
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(10, 8, 10, 0);
gbc.weightx = 0.5;
add(quitProgram, gbc);
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == continueProgram) {
close(CONTINUE);
} else if (e.getSource() == quitProgram) {
close(QUIT);
} else if (e.getSource() == copyText) {
MiscUtil.copyToClipboard(debugMessage.getText());
}
}
};
continueProgram.addActionListener(actionListener);
quitProgram.addActionListener(actionListener);
copyText.addActionListener(actionListener);
add(normal, contentGbc);
finishDialog();
}
public int show(LogRecord error, LinkedList<LogRecord> previous) {
if (errorCount >= ERROR_LIMIT) {
setTitle(errorCount+" Errors (stopped recording)");
} else {
addError(error, previous);
}
if (!isVisible()) {
setFocusableWindowState(false);
maximize();
setLocationRelativeTo(parent);
}
result = CONTINUE;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setFocusableWindowState(true);
debugMessage.scrollRectToVisible(new Rectangle());
}
});
timeOpened = System.currentTimeMillis();
if (!isFocused()) {
stopTimer();
minimizeTimer.restart();
}
setVisible(true);
return result;
}
private void addError(LogRecord error, LinkedList<LogRecord> previous) {
errorCount++;
String errorText = makeErrorText(error, previous);
errors.add(errorText);
if (errorCount == 1) {
setTitle("Error");
debugMessage.setText(String.format("Error Report // %s / %s / %s / %s\n\n",
DateTime.fullDateTime(),
Chatty.chattyVersion(),
Helper.systemInfo(),
LogUtil.getMemoryUsage()));
} else {
setTitle(errorCount+" Errors");
}
debugMessage.append(errorText);
}
private String makeErrorText(LogRecord error, LinkedList<LogRecord> previous) {
StringBuilder b = new StringBuilder();
for (LogRecord r : previous) {
// Should never be null, but since this may contain null and it
// apparently happened before..
if (r != null) {
b.append(DateTime.format(r.getMillis()));
b.append(" ");
b.append(r.getMessage());
b.append("\n");
}
}
b.append(DateTime.format(error.getMillis()));
b.append(" Unhandled Exception:\n");
b.append(error.getMessage());
b.append("\n\n");
return b.toString();
}
private void finishDialog() {
setMinimumSize(null);
pack();
setMinimumSize(getPreferredSize());
}
private void minimize() {
remove(normal);
add(minimized, contentGbc);
finishDialog();
}
private void maximize() {
remove(minimized);
add(normal, contentGbc);
finishDialog();
}
private void update() {
if (isVisible()) {
//System.out.println("update");
long timePassed = System.currentTimeMillis() - timeOpened;
long leftToMinimize = (MINIMIZE_AFTER - timePassed) / 1000;
if (leftToMinimize <= 0) {
minimize();
stopTimer();
} else {
topMessage.setText(TOP_MESSAGE+" ([local:minimize Minimize dialog] in "+leftToMinimize+"s)");
}
}
}
private void stopTimer() {
if (minimizeTimer != null) {
minimizeTimer.stop();
topMessage.setText(TOP_MESSAGE+" ("+"[local:minimize Minimize dialog]"+")");
}
}
private void close(int action) {
result = action;
errors.clear();
errorCount = 0;
setVisible(false);
}
private GridBagConstraints makeGbc(int x, int y, int w, int h) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.gridwidth = w;
gbc.gridheight = h;
gbc.insets = new Insets(10,10,10,10);
return gbc;
}
}