/* * $Id: JXErrorPane.java 3707 2010-07-08 19:19:25Z kschaefe $ * * 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.hdesktop.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.hdesktop.swingx.error.ErrorInfo; import org.hdesktop.swingx.error.ErrorReporter; import org.hdesktop.swingx.plaf.ErrorPaneAddon; import org.hdesktop.swingx.plaf.ErrorPaneUI; import org.hdesktop.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.hdesktop.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.hdesktop.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.hdesktop.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.hdesktop.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; } }