/*
* $Id: JXErrorPane.java,v 1.10 2009/01/02 13:26:04 rah003 Exp $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx;
import java.awt.Component;
import java.lang.reflect.InvocationTargetException;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.SwingUtilities;
import org.jdesktop.swingx.error.ErrorReporter;
import org.jdesktop.swingx.error.ErrorInfo;
import org.jdesktop.swingx.plaf.ErrorPaneUI;
import org.jdesktop.swingx.plaf.ErrorPaneAddon;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
/**
* <p>JXErrorPane is a common error component suitable for displaying errors,
* warnings, and exceptional application behavior to users.</p>
*
* <p>User interaction with the <code>JXErrorPane</code> includes the ability to
* view details associated with the error. This is the primary feature that differentiates
* <code>JXErrorPane</code> from <code>JOptionPane</code>. In addition,
* <code>JXErrorPane</code> specializes in handling unrecoverable errors. If you
* need an error dialog that allows the user to take some action to recover
* from an error (such as "Repair Disk", "Replace All", etc) then you should
* use <code>JOptionPane</code>.</p>
*
* <p>Data and application state associated with an error are encapsulated
* in the {@link org.jdesktop.swingx.error.ErrorInfo} class. The
* {@code JXErrorPane} displays the data contained in the {@code ErrorInfo}.
* In addition, {@code ErrorInfo} is passed to the
* {@link org.jdesktop.swingx.error.ErrorReporter} if the user decides to report
* the incident.</p>
*
* <h2>Basic Usage</h2>
* <p>Typically, the <code>JXErrorPane</code>
* is not created and displayed directly. Instead, one of the static showXXX methods
* are called that create and display the <code>JXErrorPane</code> in a
* <code>JDialog</code>, <code>JFrame</code>, or <code>JInternalFrame</code>.</p>
*
* <p>These static showXXX methods all follow the same pattern, namely (
* where XXX could be one of Dialog, Frame, or InternalFrame):
* <ul>
* <li><b>showXXX(Throwable e)</b>: This usage allows you to show a default error
* window, detailing the error</li>
* <li><b>showXXX(Component owner, ErrorInfo info)</b>: This usage shows an
* error dialog based on the given <code>ErrorInfo</code>. The component
* argument is the component over which the dialog should be centered.</li>
* <li><b>showXXX(Component owner, JXErrorPane pane)</b>: This usage shows
* an error dialog using the given error pane. This allows you to completely
* modify the pane (perhaps installing a custom UI delegate, etc) to present
* to the user</li>
* <li><b>createXXX(Component owner, JXErrorPane pane)</b>: Creates and returns
* a dialog for presenting the given <code>JXErrorPane</code>, but does not
* show it. This allows the developer to modify properties of the dialog
* prior to display</li>
* </ul></p>
*
* <p>Following are some examples and further discussion regarding some of these
* static methods. Example of the most basic usage:
* <pre><code>
* try {
* //do stuff.... something throws an exception in here
* } catch (Exception e) {
* JXErrorPane.showDialog(e);
* }
* </code></pre>. Alternatively there are <code>showFrame</code> and
* <code>showInternalFrame</code> variants of each of the <code>showDialog</code>
* methods described in this API.</p>
*
* <p>While this is the simplest usage, it is not the recommended approach for
* most errors since it yields the most difficult messages for users to understand.
* Instead it is recommended to provide a more useful message for users. For example:
* <pre><code>
* URL url = null;
* try {
* url = new URL(userSuppliedUrl);
* } catch (MalformedURLException e) {
* String msg = "The web resource you entered is not formatted"
* + " correctly.";
* String details = "<html>Web resources should begin with \"http://\""
* + " and cannot contain any spaces. Below are a few"
* + " more guidelines.<ul>"
* + getURLGuidelines()
* + "</ul></html>";
* JXErrorPane.showDialog(myWindow, "Unknown Resource", msg, details, e);
* return false;
* }
* </code></pre></p>
*
* <p>Before showing the <code>JXErrorPane</code> in a frame or dialog, you may modify
* the appearance and behavior of the <code>JXErrorPane</code> by setting one or more of its bean
* properties. For example, to modify the icon shown with a particular
* instance of a <code>JXErrorPane</code>, you might do the following:
* <pre><code>
* JXErrorPane pane = new JXErrorPane();
* pane.setErrorIcon(myErrorIcon);
* pane.setErrorInfo(new ErrorInfo("Fatal Error", exception));
* JXErrorPane.showDialog(null, pane);
* </code></pre></p>
*
* <p><code>JXErrorPane</code> may also be configured with a "Report" button which allows
* the user to send a bug report, typically through email. This is done through
* the pluggable {@link org.jdesktop.swingx.error.ErrorReporter} class. Simply instantiate
* some custom subclass of <code>ErrorReporter</code> and pass the instance into the
* {@link #setErrorReporter} method.</p>
*
* <p><code>JXErrorPane</code> can also be used for displaying fatal error messages to
* users. Fatal messages indicate a serious error in the application that cannot
* be corrected and that must result in the termination of the application.
* After the close of a fatal error dialog, the application should
* automatically exit. Fatal messages are identified by the <code>Level</code>
* of the <code>ErrorInfo</code> being
* {@link org.jdesktop.swingx.error.ErrorLevel}<code>.FATAL</code>.</p>
*
* <p>By default, when Fatal error dialogs are closed the application exits with
* a code of "1". In other words, <code>System.exit(1)</code>. If you wish to implement
* custom handling, you can replace the default fatal action in the <code>ActionMap</code>
* of the <code>JXErrorPane</code> instance. If you specify a custom fatal
* action, then the default action of calling
* System.exit will not occur. You are therefore responsible for shutting down
* the application.</p>
*
* <h2>UI Default Keys</h2>
* <p>TODO</p>
* JXErrorPane.errorIcon
* or, if not specified, JOptionPane.errorIcon
* JXErrorPane.warningIcon
* or, if not specified, JOptionPane.warningIcon
* JXErrorPane.details_contract_text (ignored on Mac OS X)
* JXErrorPane.details_expand_text (ignored on Mac OS X)
* JXErrorPane.mac.details_contract_text
* JXErrorPane.mac.details_expand_text
* Tree.expandedIcon (on Mac OS X)
* Tree.collapsedIcon (on Mac OS X)
*
* <h2>Customizing the Look and Feel</h2>
* <p>TODO</p>
*
*
* @status REVIEWED
*
* @author Richard Bair
* @author Alexander Zuev
* @author Shai Almog
* @author rah003
*/
public class JXErrorPane extends JComponent {
//---------------------------------------------------- static properties
/**
* Name of the Action used for reporting errors
*/
public static final String REPORT_ACTION_KEY = "report-action";
/**
* Name of the Action used for fatal errors
*/
public static final String FATAL_ACTION_KEY = "fatal-action";
/**
* UI Class ID
*/
public final static String uiClassID = "ErrorPaneUI";
/**
*/
static {
LookAndFeelAddons.contribute(new ErrorPaneAddon());
}
//-------------------------------------------------- instance properties
/**
* ErrorInfo that contains all the information prepared for
* reporting.
*/
private ErrorInfo errorInfo = new ErrorInfo("Error", "Normally this place contains problem description.\n You see this text because one of the following reasons:\n * Either it is a test\n * Developer have not provided error details\n * This error message was invoked unexpectedly and there are no more details available", null, null, null, null, null);
/**
* The Icon to use, regardless of the error message. The UI delegate is
* responsible for setting this icon, if the developer has not specified
* the icon.
*/
private Icon icon;
/**
* The delegate to use for reporting errors.
*/
private ErrorReporter reporter;
//--------------------------------------------------------- constructors
/**
* Create a new <code>JXErrorPane</code>.
*/
public JXErrorPane() {
super();
updateUI();
}
//------------------------------------------------------------- UI Logic
/**
* @inheritDoc
*/
public ErrorPaneUI getUI() {
return (ErrorPaneUI)ui;
}
/**
* Sets the look and feel (L&F) object that renders this component.
*
* @param ui
* the ErrorPaneUI L&F object
* @see javax.swing.UIDefaults#getUI
* @beaninfo bound: true hidden: true attribute: visualUpdate true
* description: The UI object that implements the Component's
* LookAndFeel.
*/
public void setUI(ErrorPaneUI ui) {
super.setUI(ui);
}
/**
* Returns the name of the L&F class that renders this component.
*
* @return the string {@link #uiClassID}
* @see javax.swing.JComponent#getUIClassID
* @see javax.swing.UIDefaults#getUI
*/
@Override
public String getUIClassID() {
return uiClassID;
}
/**
* Notification from the <code>UIManager</code> that the L&F has changed.
* Replaces the current UI object with the latest version from the
* <code>UIManager</code>.
*
* @see javax.swing.JComponent#updateUI
*/
@Override
public void updateUI() {
setUI((ErrorPaneUI) LookAndFeelAddons
.getUI(this, ErrorPaneUI.class));
}
//-------------------------------------------- public methods/properties
/**
* Sets the ErrorInfo for this dialog. ErrorInfo can't be null.
*
* @param info ErrorInfo that incorporates all the details about the error. Null value is not supported.
*/
public void setErrorInfo(ErrorInfo info) {
if (info == null) {
throw new NullPointerException("ErrorInfo can't be null. Provide valid ErrorInfo object.");
}
ErrorInfo old = this.errorInfo;
this.errorInfo = info;
firePropertyChange("errorInfo", old, this.errorInfo);
}
/**
* Gets the <code>JXErrorPane</code>'s <code>ErrorInfo</code>
*
* @return <code>ErrorInfo</code> assigned to this dialog
*/
public ErrorInfo getErrorInfo() {
return errorInfo;
}
/**
* Specifies the icon to use
*
* @param icon the Icon to use. May be null.
*/
public void setIcon(Icon icon) {
Icon old = this.icon;
this.icon = icon;
firePropertyChange("icon", old, this.icon);
}
/**
* Returns the Icon used
*
* @return the Icon
*/
public Icon getIcon() {
return icon;
}
/**
* Sets the {@link ErrorReporter} delegate to use. This delegate is called
* automatically when the report action is fired.
*
* @param reporter the ErrorReporter to use. If null, the report button will
* not be shown in the error dialog.
*/
public void setErrorReporter(ErrorReporter reporter) {
ErrorReporter old = getErrorReporter();
this.reporter = reporter;
firePropertyChange("errorReporter", old, getErrorReporter());
}
/**
* Gets the {@link ErrorReporter} delegate in use.
*
* @return the ErrorReporter. May be null.
*/
public ErrorReporter getErrorReporter() {
return reporter;
}
//------------------------------------------------------- static methods
/**
* <p>Constructs and shows the error dialog for the given exception. The
* exceptions message will be the errorMessage, and the stacktrace will form
* the details for the error dialog.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the dialog shown will be modal. Otherwise, this thread will
* block until the error dialog has been shown and hidden on the EDT.</p>
*
* @param e Exception that contains information about the error cause and stack trace
*/
public static void showDialog(Throwable e) {
ErrorInfo ii = new ErrorInfo(null, null, null, null, e, null, null);
showDialog(null, ii);
}
/**
* <p>Constructs and shows the error dialog, using the given
* <code>ErrorInfo</code> to initialize the view.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the dialog shown will be modal. Otherwise, this thread will
* block until the error dialog has been shown and hidden on the EDT.</p>
*
* @param owner Owner of this error dialog. Determines the Window in which the dialog
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param info <code>ErrorInfo</code> that incorporates all the information about the error
*/
public static void showDialog(Component owner, ErrorInfo info) {
JXErrorPane pane = new JXErrorPane();
pane.setErrorInfo(info);
showDialog(owner, pane);
}
/**
* <p>Constructs and shows the error dialog, using the given
* <code>JXErrorPane</code> for the view portion of the dialog.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the dialog shown will be modal. Otherwise, this thread will
* block until the error dialog has been shown and hidden on the EDT.</p>
*
* @param owner Owner of this error dialog. Determines the Window in which the dialog
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param pane <code>JXErrorPane</code> which will form the content area
* of the dialog.
*/
public static void showDialog(final Component owner, final JXErrorPane pane) {
Runnable r = new Runnable() {
public void run() {
JDialog dlg = createDialog(owner, pane);
dlg.setVisible(true);
}
};
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(r);
} catch (InvocationTargetException ex) {
ex.printStackTrace();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
} else {
r.run();
}
}
/**
* <p>Constructs and returns an error dialog, using the given
* <code>JXErrorPane</code> for the view portion of the dialog.</p>
*
* <p>This method may be called from any thread. It does not block. The
* caller is responsible for ensuring that the dialog is shown and manipulated
* on the AWT event dispatch thread. A common way to do this is to use
* <code>SwingUtilities.invokeAndWait</code> or
* <code>SwingUtilities.invokeLater()</code>.</p>
*
* @param owner Owner of this error dialog. Determines the Window in which the dialog
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param pane <code>JXErrorPane</code> which will form the content area
* of the dialog.
* @return a <code>JDialog</code> configured to display the error.
*/
public static JDialog createDialog(Component owner, JXErrorPane pane) {
JDialog window = pane.getUI().getErrorDialog(owner);
// If the owner is null applies orientation of the shared
// hidden window used as owner.
if(owner != null) {
pane.applyComponentOrientation(owner.getComponentOrientation());
} else {
pane.applyComponentOrientation(window.getComponentOrientation());
}
window.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
window.pack();
window.setLocationRelativeTo(owner);
return window;
}
/**
* <p>Constructs and shows the error frame for the given exception. The
* exceptions message will be the errorMessage, and the stacktrace will form
* the details for the error dialog.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the frame shown will be modal. Otherwise, this thread will
* block until the error frame has been shown and hidden on the EDT.</p>
*
* @param e Exception that contains information about the error cause and stack trace
*/
public static void showFrame(Throwable e) {
ErrorInfo ii = new ErrorInfo(null, null, null, null, e, null, null);
showFrame(null, ii);
}
/**
* <p>Constructs and shows the error frame, using the given
* <code>ErrorInfo</code> to initialize the view.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the frame shown will be modal. Otherwise, this thread will
* block until the error frame has been shown and hidden on the EDT.</p>
*
* @param owner Owner of this error frame. Determines the Window in which the frame
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param info <code>ErrorInfo</code> that incorporates all the information about the error
*/
public static void showFrame(Component owner, ErrorInfo info) {
JXErrorPane pane = new JXErrorPane();
pane.setErrorInfo(info);
showFrame(owner, pane);
}
/**
* <p>Constructs and shows the error frame, using the given
* <code>JXErrorPane</code> for the view portion of the frame.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the frame shown will be modal. Otherwise, this thread will
* block until the error frame has been shown and hidden on the EDT.</p>
*
* @param owner Owner of this error frame. Determines the Window in which the dialog
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param pane <code>JXErrorPane</code> which will form the content area
* of the frame.
*/
public static void showFrame(final Component owner, final JXErrorPane pane) {
Runnable r = new Runnable() {
public void run() {
JFrame window = createFrame(owner, pane);
window.setVisible(true);
}
};
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(r);
} catch (InvocationTargetException ex) {
ex.printStackTrace();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
} else {
r.run();
}
}
/**
* <p>Constructs and returns an error frame, using the given
* <code>JXErrorPane</code> for the view portion of the frame.</p>
*
* <p>This method may be called from any thread. It does not block. The
* caller is responsible for ensuring that the frame is shown and manipulated
* on the AWT event dispatch thread. A common way to do this is to use
* <code>SwingUtilities.invokeAndWait</code> or
* <code>SwingUtilities.invokeLater()</code>.</p>
*
* @param owner Owner of this error frame. Determines the Window in which the frame
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param pane <code>JXErrorPane</code> which will form the content area
* of the frame.
* @return a <code>JFrame</code> configured to display the error.
*/
public static JFrame createFrame(Component owner, JXErrorPane pane) {
JFrame window = pane.getUI().getErrorFrame(owner);
// If the owner is null applies orientation of the shared
// hidden window used as owner.
if(owner != null) {
pane.applyComponentOrientation(owner.getComponentOrientation());
} else {
pane.applyComponentOrientation(window.getComponentOrientation());
}
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
window.pack();
// window.setLocationRelativeTo(owner);
return window;
}
/**
* <p>Constructs and shows the error frame for the given exception. The
* exceptions message will be the errorMessage, and the stacktrace will form
* the details for the error dialog.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the frame shown will be modal. Otherwise, this thread will
* block until the error frame has been shown and hidden on the EDT.</p>
*
* @param e Exception that contains information about the error cause and stack trace
*/
public static void showInternalFrame(Throwable e) {
ErrorInfo ii = new ErrorInfo(null, null, null, null, e, null, null);
showInternalFrame(null, ii);
}
/**
* <p>Constructs and shows the error frame, using the given
* <code>ErrorInfo</code> to initialize the view.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the frame shown will be modal. Otherwise, this thread will
* block until the error frame has been shown and hidden on the EDT.</p>
*
* @param owner Owner of this error frame. Determines the Window in which the frame
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param info <code>ErrorInfo</code> that incorporates all the information about the error
*/
public static void showInternalFrame(Component owner, ErrorInfo info) {
JXErrorPane pane = new JXErrorPane();
pane.setErrorInfo(info);
showInternalFrame(owner, pane);
}
/**
* <p>Constructs and shows the error frame, using the given
* <code>JXErrorPane</code> for the view portion of the frame.</p>
*
* <p>This method may be called from any thread. It will actually show the error
* dialog on the AWT event dispatch thread. This method blocks. If called
* on the EDT, the frame shown will be modal. Otherwise, this thread will
* block until the error frame has been shown and hidden on the EDT.</p>
*
* @param owner Owner of this error frame. Determines the Window in which the dialog
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param pane <code>JXErrorPane</code> which will form the content area
* of the frame.
*/
public static void showInternalFrame(final Component owner, final JXErrorPane pane) {
Runnable r = new Runnable() {
public void run() {
JInternalFrame window = createInternalFrame(owner, pane);
window.setVisible(true);
}
};
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(r);
} catch (InvocationTargetException ex) {
ex.printStackTrace();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
} else {
r.run();
}
}
/**
* <p>Constructs and returns an error frame, using the given
* <code>JXErrorPane</code> for the view portion of the frame.</p>
*
* <p>This method may be called from any thread. It does not block. The
* caller is responsible for ensuring that the frame is shown and manipulated
* on the AWT event dispatch thread. A common way to do this is to use
* <code>SwingUtilities.invokeAndWait</code> or
* <code>SwingUtilities.invokeLater()</code>.</p>
*
* @param owner Owner of this error frame. Determines the Window in which the frame
* is displayed; if the <code>owner</code> has
* no <code>Window</code>, a default <code>Frame</code> is used
* @param pane <code>JXErrorPane</code> which will form the content area
* of the frame.
* @return a <code>JInternalFrame</code> configured to display the error.
*/
public static JInternalFrame createInternalFrame(Component owner, JXErrorPane pane) {
JInternalFrame window = pane.getUI().getErrorInternalFrame(owner);
// If the owner is null applies orientation of the shared
// hidden window used as owner.
if(owner != null) {
pane.applyComponentOrientation(owner.getComponentOrientation());
} else {
pane.applyComponentOrientation(window.getComponentOrientation());
}
window.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
window.pack();
//TODO!
// window.setLocationRelativeTo(owner);
return window;
}
}