/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
package org.beanfabrics.util;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.UIManager;
/**
* The <code>ErrorPane</code> is a panel that shows a message beside an error
* icon and - when checking the checkbox - the stack trace of a
* {@link Throwable}. TODO This class makes no sense in SWT environment. Any
* solution?
*
* @author Max Gensthaler
*/
@SuppressWarnings("serial")
public class ErrorPane extends JPanel {
private static JDialog dialog; // TODO why is this a static member instance? do we need to store the reference as a member?
private JLabel messageLabel;
private JCheckBox stackTraceCheckBox;
private JScrollPane stackTraceScrollPane;
private JTextArea stackTraceTextArea;
private final Object message;
private final Throwable cause;
private JLabel errorIconLabel;
private JPanel stackTracePanel;
/**
* Main method to test this class.
*/
public static void main(String[] args) {
try {
recursiveMethod(0);
} catch (Exception e) {
showErrorDialog("This is a test.", e);
}
}
private static void recursiveMethod(int counter) {
if (counter >= 20) {
throw new RuntimeException("This is a test exception.");
}
recursiveMethod(counter + 1);
}
/**
* Constructs a new instance of this class with no message and no
* {@link Throwable}.
*/
public ErrorPane() {
this(null, null);
}
/**
* Constructs a new instance of this class with the given message and no
* {@link Throwable}.
*
* @param message message to display
*/
public ErrorPane(Object message) {
this(message, null);
}
/**
* Constructs a new instance of this class with the given message and the
* given {@link Throwable}.
*
* @param message message to display
* @param cause <code>Throwable</code> to display the stack trace of
*/
public ErrorPane(Object message, Throwable cause) {
this.message = message;
this.cause = cause;
this.setLayout(new GridBagLayout());
this.add(getErrorIconLabel(), new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.NONE, new Insets(10, 10, 10, 10), 0, 0));
this.add(getMessageLabel(), new GridBagConstraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(10, 0, 10, 10), 0, 0));
this.add(getStackTracePanel(), new GridBagConstraints(0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 10, 0, 10), 0, 0));
}
private JPanel getStackTracePanel() {
if (stackTracePanel == null) {
stackTracePanel = new JPanel();
stackTracePanel.setLayout(new BorderLayout());
if (cause != null) {
stackTracePanel.add(getStackTraceCheckBox(), BorderLayout.NORTH);
stackTracePanel.add(getStackTraceScrollPane(), BorderLayout.CENTER);
}
}
return stackTracePanel;
}
private JLabel getErrorIconLabel() {
if (errorIconLabel == null) {
Icon errorIcon = UIManager.getIcon("OptionPane.errorIcon");
errorIconLabel = new JLabel(errorIcon);
}
return errorIconLabel;
}
private JLabel getMessageLabel() {
if (messageLabel == null) {
messageLabel = new JLabel();
messageLabel.setText(message.toString());
}
return messageLabel;
}
private JCheckBox getStackTraceCheckBox() {
if (stackTraceCheckBox == null) {
Action action = new AbstractAction() {
public void actionPerformed(ActionEvent evt) {
Object source = evt.getSource();
if (source instanceof JCheckBox) {
JCheckBox checkBox = (JCheckBox)source;
JScrollPane scrollPane = ErrorPane.this.getStackTraceScrollPane();
scrollPane.setVisible(checkBox.isSelected());
if (checkBox.isSelected()) {
scrollPane.requestFocus();
}
if (dialog != null) {
dialog.pack();
}
getStackTracePanel().revalidate();
}
}
};
stackTraceCheckBox = new JCheckBox(action);
stackTraceCheckBox.setText("show details");
}
return stackTraceCheckBox;
}
private JScrollPane getStackTraceScrollPane() {
if (stackTraceScrollPane == null) {
stackTraceScrollPane = new JScrollPane();
stackTraceScrollPane.setViewportView(getStackTraceTextArea());
stackTraceScrollPane.setPreferredSize(new Dimension(500, 200));
stackTraceScrollPane.setVisible(getStackTraceCheckBox().isSelected());
}
return stackTraceScrollPane;
}
private JTextArea getStackTraceTextArea() {
if (stackTraceTextArea == null) {
stackTraceTextArea = new JTextArea();
stackTraceTextArea.setEditable(false);
if (cause != null) {
Writer writer = new StringWriter();
cause.printStackTrace(new PrintWriter(writer));
stackTraceTextArea.setText(writer.toString());
}
}
return stackTraceTextArea;
}
/**
* Show a {@link JDialog} with the given message.
*
* @param message message to display
*/
public static void showErrorDialog(Object message) {
showErrorDialog(null, "Error", message, null);
}
/**
* Show a {@link JDialog} with the given message and the given
* {@link Throwable}.
*
* @param message message to display, may be <code>null</code>
* @param cause <code>Throwable</code> to display the stack trace of, may be
* <code>null</code>
*/
public static void showErrorDialog(Object message, Throwable cause) {
showErrorDialog(null, "Error", message, cause);
}
/**
* Show a {@link JDialog} which has the given parent component and the given
* title, showing the given message and the given {@link Throwable}.
*
* @param parentComponent parent component of the dialog
* @param title title of the dialog
* @param message message to show in the dialog, may be <code>null</code>
* @param cause Throwable to show the stack trace of in the dialog, may be
* <code>null</code>
*/
public static void showErrorDialog(Component parentComponent, String title, Object message, Throwable cause) {
showErrorDialog(parentComponent, false, title, message, cause);
}
/**
* Show a {@link JDialog} which has the given parent component, the given
* modality and the given title, showing the given message and the given
* {@link Throwable}.
*
* @param parentComponent parent component of the dialog
* @param modal specifies whether the dialog should be modal
* @param title title of the dialog
* @param message message to show in the dialog, may be <code>null</code>
* @param cause Throwable to show the stack trace of in the dialog, may be
* <code>null</code>
*/
public static void showErrorDialog(Component parentComponent, boolean modal, String title, Object message, Throwable cause) {
dialog = createDialog(parentComponent, modal, title, message, cause);
dialog.pack();
dialog.setLocationRelativeTo(parentComponent);
dialog.setVisible(true);
}
private static JDialog createDialog(Component owner, boolean modal, String title, Object message, Throwable cause) {
Window window = getWindowForComponent(owner);
if (window instanceof Frame) {
dialog = new ErrorPaneDialog((Frame)window, title, message, cause);
} else if (window instanceof Dialog) {
dialog = new ErrorPaneDialog((Dialog)window, title, message, cause);
} else {
// TODO perhaps we should try to find the topmost frame before using the fallback below
// fallback: window is null. Use a dummy frame as parent.
dialog = new ErrorPaneDialog(new Frame(), title, message, cause);
}
dialog.setModal(modal);
return dialog;
}
private static Window getWindowForComponent(Component component) {
if (component != null) {
for (Component parent; (parent = component.getParent()) != null;) {
if (parent instanceof Window) {
return (Window)parent;
}
}
}
return null;
}
static class ErrorPaneDialog extends JDialog {
private JPanel contentPane;
private JPanel buttonPanel;
private JButton okButton;
public ErrorPaneDialog(Frame owner, String title, Object message, Throwable cause) {
super(owner, title, true);
init(message, cause);
registerCloseOnEsc();
}
public ErrorPaneDialog(Dialog owner, String title, Object message, Throwable cause) {
super(owner, title, true);
init(message, cause);
registerCloseOnEsc();
}
private void registerCloseOnEsc() {
KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE");
getRootPane().getActionMap().put("ESCAPE", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
dispose();
}
});
}
private void init(Object message, Throwable cause) {
this.setContentPane(getDialogContentPane(message, cause));
this.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
@Override
public Component getInitialComponent(Window window) {
return ErrorPaneDialog.this.getOkButton();
}
});
}
private JPanel getDialogContentPane(Object message, Throwable cause) {
if (contentPane == null) {
contentPane = new JPanel();
contentPane.setLayout(new BorderLayout());
contentPane.add(new ErrorPane(message, cause), BorderLayout.CENTER);
contentPane.add(getButtonPanel(), BorderLayout.SOUTH);
}
return contentPane;
}
private JPanel getButtonPanel() {
if (buttonPanel == null) {
buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
buttonPanel.add(getOkButton());
}
return buttonPanel;
}
private JButton getOkButton() {
if (okButton == null) {
okButton = new JButton();
okButton.setText("OK");
okButton.setMnemonic('O');
okButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ErrorPane.dialog.setVisible(false);
ErrorPane.dialog.dispose();
}
});
}
return okButton;
}
}
}