/* * $Id: BasicErrorPaneUI.java 3753 2010-08-12 02:41:30Z 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.plaf.basic; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Point; import java.awt.Window; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.border.EmptyBorder; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicHTML; import javax.swing.text.JTextComponent; import javax.swing.text.StyledEditorKit; import javax.swing.text.html.HTMLEditorKit; import org.hdesktop.swingx.JXEditorPane; import org.hdesktop.swingx.JXErrorPane; import org.hdesktop.swingx.action.AbstractActionExt; import org.hdesktop.swingx.error.ErrorInfo; import org.hdesktop.swingx.error.ErrorLevel; import org.hdesktop.swingx.error.ErrorReporter; import org.hdesktop.swingx.plaf.ErrorPaneUI; import org.hdesktop.swingx.plaf.UIManagerExt; import org.hdesktop.swingx.util.WindowUtils; /** * Base implementation of the <code>JXErrorPane</code> UI. * * @author rbair * @author rah003 */ public class BasicErrorPaneUI extends ErrorPaneUI { /** * Used as a prefix when pulling data out of UIManager for i18n */ protected static final String CLASS_NAME = "JXErrorPane"; /** * The error pane this UI is for */ protected JXErrorPane pane; /** * Error message text area */ protected JEditorPane errorMessage; /** * Error message text scroll pane wrapper. */ protected JScrollPane errorScrollPane; /** * details text area */ protected JXEditorPane details; /** * detail button */ protected AbstractButton detailButton; /** * ok/close button */ protected JButton closeButton; /** * label used to display the warning/error icon */ protected JLabel iconLabel; /** * report an error button */ protected AbstractButton reportButton; /** * details panel */ protected JPanel detailsPanel; protected JScrollPane detailsScrollPane; protected JButton copyToClipboardButton; /** * Property change listener for the error pane ensures that the pane's UI * is reinitialized. */ protected PropertyChangeListener errorPaneListener; /** * Action listener for the detail button. */ protected ActionListener detailListener; /** * Action listener for the copy to clipboard button. */ protected ActionListener copyToClipboardListener; //------------------------------------------------------ private helpers /** * The height of the window when collapsed. This value is stashed when the * dialog is expanded */ private int collapsedHeight = 0; /** * The height of the window when last expanded. This value is stashed when * the dialog is collapsed */ private int expandedHeight = 0; //---------------------------------------------------------- constructor /** * {@inheritDoc} */ public static ComponentUI createUI(JComponent c) { return new BasicErrorPaneUI(); } /** * {@inheritDoc} */ @Override public void installUI(JComponent c) { super.installUI(c); this.pane = (JXErrorPane)c; installDefaults(); installComponents(); installListeners(); //if the report action needs to be defined, do so Action a = c.getActionMap().get(JXErrorPane.REPORT_ACTION_KEY); if (a == null) { final JXErrorPane pane = (JXErrorPane)c; AbstractActionExt reportAction = new AbstractActionExt() { public void actionPerformed(ActionEvent e) { ErrorReporter reporter = pane.getErrorReporter(); if (reporter != null) { reporter.reportError(pane.getErrorInfo()); } } }; configureReportAction(reportAction); c.getActionMap().put(JXErrorPane.REPORT_ACTION_KEY, reportAction); } } /** * {@inheritDoc} */ @Override public void uninstallUI(JComponent c) { super.uninstallUI(c); uninstallListeners(); uninstallComponents(); uninstallDefaults(); } /** * Installs the default colors, and default font into the Error Pane */ protected void installDefaults() { } /** * Uninstalls the default colors, and default font into the Error Pane. */ protected void uninstallDefaults() { LookAndFeel.uninstallBorder(pane); } /** * Create and install the listeners for the Error Pane. * This method is called when the UI is installed. */ protected void installListeners() { //add a listener to the pane so I can reinit() whenever the //bean properties change (particularly error info) errorPaneListener = new ErrorPaneListener(); pane.addPropertyChangeListener(errorPaneListener); } /** * Remove the installed listeners from the Error Pane. * The number and types of listeners removed and in this method should be * the same that was added in <code>installListeners</code> */ protected void uninstallListeners() { //remove the property change listener from the pane pane.removePropertyChangeListener(errorPaneListener); } // =============================== // begin Sub-Component Management // /** * Creates and initializes the components which make up the * aggregate combo box. This method is called as part of the UI * installation process. */ protected void installComponents() { iconLabel = new JLabel(pane.getIcon()); errorMessage = new JEditorPane(); errorMessage.setEditable(false); errorMessage.setContentType("text/html"); errorMessage.setEditorKitForContentType("text/plain", new StyledEditorKit()); errorMessage.setEditorKitForContentType("text/html", new HTMLEditorKit()); errorMessage.setOpaque(false); errorMessage.putClientProperty(JXEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); closeButton = new JButton(UIManagerExt.getString( CLASS_NAME + ".ok_button_text", errorMessage.getLocale())); reportButton = new EqualSizeJButton(pane.getActionMap().get(JXErrorPane.REPORT_ACTION_KEY)); detailButton = new EqualSizeJButton(UIManagerExt.getString( CLASS_NAME + ".details_expand_text", errorMessage.getLocale())); details = new JXEditorPane(); details.setContentType("text/html"); details.putClientProperty(JXEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE); details.setTransferHandler(createDetailsTransferHandler(details)); detailsScrollPane = new JScrollPane(details); detailsScrollPane.setPreferredSize(new Dimension(10, 250)); details.setEditable(false); detailsPanel = new JPanel(); detailsPanel.setVisible(false); copyToClipboardButton = new JButton(UIManagerExt.getString( CLASS_NAME + ".copy_to_clipboard_button_text", errorMessage.getLocale())); copyToClipboardListener = new ActionListener() { public void actionPerformed(ActionEvent ae) { details.copy(); } }; copyToClipboardButton.addActionListener(copyToClipboardListener); detailsPanel.setLayout(createDetailPanelLayout()); detailsPanel.add(detailsScrollPane); detailsPanel.add(copyToClipboardButton); // Create error scroll pane. Make sure this happens before call to createErrorPaneLayout() in case any extending // class wants to manipulate the component there. errorScrollPane = new JScrollPane(errorMessage); errorScrollPane.setBorder(new EmptyBorder(0,0,5,0)); errorScrollPane.setOpaque(false); errorScrollPane.getViewport().setOpaque(false); //initialize the gui. Most of this code is similar between Mac and PC, but //where they differ protected methods have been written allowing the //mac implementation to alter the layout of the dialog. pane.setLayout(createErrorPaneLayout()); //An empty border which constitutes the padding from the edge of the //dialog to the content. All content that butts against this border should //not be padded. Insets borderInsets = new Insets(16, 24, 16, 17); pane.setBorder(BorderFactory.createEmptyBorder(borderInsets.top, borderInsets.left, borderInsets.bottom, borderInsets.right)); //add the JLabel responsible for displaying the icon. //TODO: in the future, replace this usage of a JLabel with a JXImagePane, //which may add additional "coolness" such as allowing the user to drag //the image off the dialog onto the desktop. This kind of coolness is common //in the mac world. pane.add(iconLabel); pane.add(errorScrollPane); pane.add(closeButton); pane.add(reportButton); reportButton.setVisible(false); // not visible by default pane.add(detailButton); pane.add(detailsPanel); //make the buttons the same size EqualSizeJButton[] buttons = new EqualSizeJButton[] { (EqualSizeJButton)detailButton, (EqualSizeJButton)reportButton }; ((EqualSizeJButton)reportButton).setGroup(buttons); ((EqualSizeJButton)detailButton).setGroup(buttons); reportButton.setMinimumSize(reportButton.getPreferredSize()); detailButton.setMinimumSize(detailButton.getPreferredSize()); //set the event handling detailListener = new DetailsClickEvent(); detailButton.addActionListener(detailListener); } /** * The aggregate components which compise the combo box are * unregistered and uninitialized. This method is called as part of the * UI uninstallation process. */ protected void uninstallComponents() { iconLabel = null; errorMessage = null; closeButton = null; reportButton = null; detailButton.removeActionListener(detailListener); detailButton = null; details.setTransferHandler(null); details = null; detailsScrollPane.removeAll(); detailsScrollPane = null; detailsPanel.setLayout(null); detailsPanel.removeAll(); detailsPanel = null; copyToClipboardButton.removeActionListener(copyToClipboardListener); copyToClipboardButton = null; pane.removeAll(); pane.setLayout(null); pane.setBorder(null); } // // end Sub-Component Management // =============================== /** * @inheritDoc */ @Override public JFrame getErrorFrame(Component owner) { reinit(); expandedHeight = 0; collapsedHeight = 0; JXErrorFrame frame = new JXErrorFrame(pane); centerWindow(frame, owner); return frame; } /** * @inheritDoc */ @Override public JDialog getErrorDialog(Component owner) { reinit(); expandedHeight = 0; collapsedHeight = 0; Window w = WindowUtils.findWindow(owner); JXErrorDialog dlg = null; if (w instanceof Dialog) { dlg = new JXErrorDialog((Dialog)w, pane); } else if (w instanceof Frame) { dlg = new JXErrorDialog((Frame)w, pane); } else { // default fallback to null dlg = new JXErrorDialog(JOptionPane.getRootFrame(), pane); } centerWindow(dlg, owner); return dlg; } /** * @inheritDoc */ @Override public JInternalFrame getErrorInternalFrame(Component owner) { reinit(); expandedHeight = 0; collapsedHeight = 0; JXInternalErrorFrame frame = new JXInternalErrorFrame(pane); centerWindow(frame, owner); return frame; } /** * Create and return the LayoutManager to use with the error pane. */ protected LayoutManager createErrorPaneLayout() { return new ErrorPaneLayout(); } protected LayoutManager createDetailPanelLayout() { GridBagLayout layout = new GridBagLayout(); layout.addLayoutComponent(detailsScrollPane, new GridBagConstraints(0,0,1,1,1.0,1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(6,0,0,0),0,0)); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.LINE_END; gbc.fill = GridBagConstraints.NONE; gbc.gridwidth = 1; gbc.gridx = 0; gbc.gridy = 1; gbc.weighty = 0.0; gbc.weightx = 1.0; gbc.insets = new Insets(6, 0, 6, 0); layout.addLayoutComponent(copyToClipboardButton, gbc); return layout; } @Override public Dimension calculatePreferredSize() { //TODO returns a Dimension that is either X wide, or as wide as necessary //to show the title. It is Y high. return new Dimension(iconLabel.getPreferredSize().width + errorMessage.getPreferredSize().width, 206); } protected int getDetailsHeight() { return 300; } protected void configureReportAction(AbstractActionExt reportAction) { reportAction.setName(UIManagerExt.getString(CLASS_NAME + ".report_button_text", pane.getLocale())); } //----------------------------------------------- private helper methods /** * Creates and returns a TransferHandler which can be used to copy the details * from the details component. It also disallows pasting into the component, or * cutting from the component. * * @return a TransferHandler for the details area */ private TransferHandler createDetailsTransferHandler(JTextComponent detailComponent) { return new DetailsTransferHandler(detailComponent); } /** * @return the default error icon */ protected Icon getDefaultErrorIcon() { try { Icon icon = UIManager.getIcon(CLASS_NAME + ".errorIcon"); return icon == null ? UIManager.getIcon("OptionPane.errorIcon") : icon; } catch (Exception e) { return null; } } /** * @return the default warning icon */ protected Icon getDefaultWarningIcon() { try { Icon icon = UIManager.getIcon(CLASS_NAME + ".warningIcon"); return icon == null ? UIManager.getIcon("OptionPane.warningIcon") : icon; } catch (Exception e) { return null; } } /** * Set the details section of the error dialog. If the details are either * null or an empty string, then hide the details button and hide the detail * scroll pane. Otherwise, just set the details section. * * @param details Details to be shown in the detail section of the dialog. * This can be null if you do not want to display the details section of the * dialog. */ private void setDetails(String details) { if (details == null || details.equals("")) { detailButton.setVisible(false); } else { this.details.setText(details); detailButton.setVisible(true); } } protected void configureDetailsButton(boolean expanded) { if (expanded) { detailButton.setText(UIManagerExt.getString( CLASS_NAME + ".details_contract_text", detailButton.getLocale())); } else { detailButton.setText(UIManagerExt.getString( CLASS_NAME + ".details_expand_text", detailButton.getLocale())); } } /** * Set the details section to be either visible or invisible. Set the * text of the Details button accordingly. * @param b if true details section will be visible */ private void setDetailsVisible(boolean b) { if (b) { collapsedHeight = pane.getHeight(); pane.setSize(pane.getWidth(), expandedHeight == 0 ? collapsedHeight + getDetailsHeight() : expandedHeight); detailsPanel.setVisible(true); configureDetailsButton(true); detailsPanel.applyComponentOrientation(detailButton.getComponentOrientation()); // workaround for bidi bug, if the text is not set "again" and the component orientation has changed // then the text won't be aligned correctly. To reproduce this (in JDK 1.5) show two dialogs in one // use LTOR orientation and in the second use RTOL orientation and press "details" in both. // Text in the text box should be aligned to right/left respectively, without this line this doesn't // occure I assume because bidi properties are tested when the text is set and are not updated later // on when setComponentOrientation is invoked. details.setText(details.getText()); details.setCaretPosition(0); } else if (collapsedHeight != 0) { //only collapse if the dialog has been expanded expandedHeight = pane.getHeight(); detailsPanel.setVisible(false); configureDetailsButton(false); // Trick to force errorMessage JTextArea to resize according // to its columns property. errorMessage.setSize(0, 0); errorMessage.setSize(errorMessage.getPreferredSize()); pane.setSize(pane.getWidth(), collapsedHeight); } else { detailsPanel.setVisible(false); } pane.doLayout(); } /** * Set the error message for the dialog box * @param errorMessage Message for the error dialog */ private void setErrorMessage(String errorMessage) { if(BasicHTML.isHTMLString(errorMessage)) { this.errorMessage.setContentType("text/html"); } else { this.errorMessage.setContentType("text/plain"); } this.errorMessage.setText(errorMessage); this.errorMessage.setCaretPosition(0); } /** * Reconfigures the dialog if settings have changed, such as the * errorInfo, errorIcon, warningIcon, etc */ protected void reinit() { setDetailsVisible(false); Action reportAction = pane.getActionMap().get(JXErrorPane.REPORT_ACTION_KEY); reportButton.setAction(reportAction); reportButton.setVisible(reportAction != null && reportAction.isEnabled() && pane.getErrorReporter() != null); reportButton.setEnabled(reportButton.isVisible()); ErrorInfo errorInfo = pane.getErrorInfo(); if (errorInfo == null) { iconLabel.setIcon(pane.getIcon()); setErrorMessage(""); closeButton.setText(UIManagerExt.getString( CLASS_NAME + ".ok_button_text", closeButton.getLocale())); setDetails(""); //TODO Does this ever happen? It seems like if errorInfo is null and //this is called, it would be an IllegalStateException. } else { //change the "closeButton"'s text to either the default "ok"/"close" text //or to the "fatal" text depending on the error level of the incident info if (errorInfo.getErrorLevel() == ErrorLevel.FATAL) { closeButton.setText(UIManagerExt.getString( CLASS_NAME + ".fatal_button_text", closeButton.getLocale())); } else { closeButton.setText(UIManagerExt.getString( CLASS_NAME + ".ok_button_text", closeButton.getLocale())); } //if the icon for the pane has not been specified by the developer, //then set it to the default icon based on the error level Icon icon = pane.getIcon(); if (icon == null || icon instanceof UIResource) { if (errorInfo.getErrorLevel().intValue() <= Level.WARNING.intValue()) { icon = getDefaultWarningIcon(); } else { icon = getDefaultErrorIcon(); } } iconLabel.setIcon(icon); setErrorMessage(errorInfo.getBasicErrorMessage()); String details = errorInfo.getDetailedErrorMessage(); if(details == null) { details = getDetailsAsHTML(errorInfo); } setDetails(details); } } /** * Creates and returns HTML representing the details of this incident info. This * method is only called if the details needs to be generated: ie: the detailed * error message property of the incident info is null. */ protected String getDetailsAsHTML(ErrorInfo errorInfo) { if(errorInfo.getErrorException() != null) { //convert the stacktrace into a more pleasent bit of HTML StringBuffer html = new StringBuffer("<html>"); html.append("<h2>" + escapeXml(errorInfo.getTitle()) + "</h2>"); html.append("<HR size='1' noshade>"); html.append("<div></div>"); html.append("<b>Message:</b>"); html.append("<pre>"); html.append(" " + escapeXml(errorInfo.getErrorException().toString())); html.append("</pre>"); html.append("<b>Level:</b>"); html.append("<pre>"); html.append(" " + errorInfo.getErrorLevel()); html.append("</pre>"); html.append("<b>Stack Trace:</b>"); Throwable ex = errorInfo.getErrorException(); while(ex != null) { html.append("<h4>"+ex.getMessage()+"</h4>"); html.append("<pre>"); for (StackTraceElement el : ex.getStackTrace()) { html.append(" " + el.toString().replace("<init>", "<init>") + "\n"); } html.append("</pre>"); ex = ex.getCause(); } html.append("</html>"); return html.toString(); } else { return null; } } //------------------------------------------------ actions/inner classes /** * Default action for closing the JXErrorPane's enclosing window * (JDialog, JFrame, or JInternalFrame) */ private static final class CloseAction extends AbstractAction { private Window w; /** * @param w cannot be null */ private CloseAction(Window w) { if (w == null) { throw new NullPointerException("Window cannot be null"); } this.w = w; } /** * @inheritDoc */ public void actionPerformed(ActionEvent e) { w.setVisible(false); w.dispose(); } } /** * Listener for Details click events. Alternates whether the details section * is visible or not. * * @author rbair */ private final class DetailsClickEvent implements ActionListener { /* (non-Javadoc) * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ public void actionPerformed(ActionEvent e) { setDetailsVisible(!detailsPanel.isVisible()); } } private final class ResizeWindow implements ActionListener { private Window w; private ResizeWindow(Window w) { if (w == null) { throw new NullPointerException(); } this.w = w; } public void actionPerformed(ActionEvent ae) { Dimension contentSize = null; if (w instanceof JDialog) { contentSize = ((JDialog)w).getContentPane().getSize(); } else { contentSize = ((JFrame)w).getContentPane().getSize(); } Dimension dialogSize = w.getSize(); int ydiff = dialogSize.height - contentSize.height; Dimension paneSize = pane.getSize(); w.setSize(new Dimension(dialogSize.width, paneSize.height + ydiff)); w.validate(); w.repaint(); } } /** * This is a button that maintains the size of the largest button in the button * group by returning the largest size from the getPreferredSize method. * This is better than using setPreferredSize since this will work regardless * of changes to the text of the button and its language. */ private static final class EqualSizeJButton extends JButton { public EqualSizeJButton() { } public EqualSizeJButton(String text) { super(text); } public EqualSizeJButton(Action a) { super(a); } /** * Buttons whose size should be taken into consideration */ private EqualSizeJButton[] group; public void setGroup(EqualSizeJButton[] group) { this.group = group; } /** * Returns the actual preferred size on a different instance of this button */ private Dimension getRealPreferredSize() { return super.getPreferredSize(); } /** * If the <code>preferredSize</code> has been set to a * non-<code>null</code> value just returns it. * If the UI delegate's <code>getPreferredSize</code> * method returns a non <code>null</code> value then return that; * otherwise defer to the component's layout manager. * * @return the value of the <code>preferredSize</code> property * @see #setPreferredSize * @see ComponentUI */ @Override public Dimension getPreferredSize() { int width = 0; int height = 0; for(int iter = 0 ; iter < group.length ; iter++) { Dimension size = group[iter].getRealPreferredSize(); width = Math.max(size.width, width); height = Math.max(size.height, height); } return new Dimension(width, height); } } /** * Returns the text as non-HTML in a COPY operation, and disabled CUT/PASTE * operations for the Details pane. */ private static final class DetailsTransferHandler extends TransferHandler { private JTextComponent details; private DetailsTransferHandler(JTextComponent detailComponent) { if (detailComponent == null) { throw new NullPointerException("detail component cannot be null"); } this.details = detailComponent; } @Override protected Transferable createTransferable(JComponent c) { String text = details.getSelectedText(); if (text == null || text.equals("")) { details.selectAll(); text = details.getSelectedText(); details.select(-1, -1); } return new StringSelection(text); } @Override public int getSourceActions(JComponent c) { return TransferHandler.COPY; } } private final class JXErrorDialog extends JDialog { public JXErrorDialog(Frame parent, JXErrorPane p) { super(parent, true); init(p); } public JXErrorDialog(Dialog parent, JXErrorPane p) { super(parent, true); init(p); } protected void init(JXErrorPane p) { // FYI: info can be null setTitle(p.getErrorInfo() == null ? null : p.getErrorInfo().getTitle()); initWindow(this, p); } } private final class JXErrorFrame extends JFrame { public JXErrorFrame(JXErrorPane p) { setTitle(p.getErrorInfo().getTitle()); initWindow(this, p); } } private final class JXInternalErrorFrame extends JInternalFrame { public JXInternalErrorFrame(JXErrorPane p) { setTitle(p.getErrorInfo().getTitle()); setLayout(new BorderLayout()); add(p, BorderLayout.CENTER); final Action closeAction = new AbstractAction() { public void actionPerformed(ActionEvent evt) { setVisible(false); dispose(); } }; closeButton.addActionListener(closeAction); addComponentListener(new ComponentAdapter() { @Override public void componentHidden(ComponentEvent e) { //remove the action listener closeButton.removeActionListener(closeAction); exitIfFatal(); } }); getRootPane().setDefaultButton(closeButton); setResizable(false); setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE); KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW); //setPreferredSize(calculatePreferredDialogSize()); } } /** * Utility method for initializing a Window for displaying a JXErrorPane. * This is particularly useful because the differences between JFrame and * JDialog are so minor. * removed. */ private void initWindow(final Window w, final JXErrorPane pane) { w.setLayout(new BorderLayout()); w.add(pane, BorderLayout.CENTER); final Action closeAction = new CloseAction(w); closeButton.addActionListener(closeAction); final ResizeWindow resizeListener = new ResizeWindow(w); //make sure this action listener is last (or, oddly, the first in the list) ActionListener[] list = detailButton.getActionListeners(); for (ActionListener a : list) { detailButton.removeActionListener(a); } detailButton.addActionListener(resizeListener); for (ActionListener a : list) { detailButton.addActionListener(a); } if (w instanceof JFrame) { final JFrame f = (JFrame)w; f.getRootPane().setDefaultButton(closeButton); f.setResizable(true); f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); f.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW); } else if (w instanceof JDialog) { final JDialog d = (JDialog)w; d.getRootPane().setDefaultButton(closeButton); d.setResizable(true); d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); d.getRootPane().registerKeyboardAction(closeAction, ks, JComponent.WHEN_IN_FOCUSED_WINDOW); } w.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { //remove the action listener closeButton.removeActionListener(closeAction); detailButton.removeActionListener(resizeListener); exitIfFatal(); } }); w.pack(); } private void exitIfFatal() { ErrorInfo info = pane.getErrorInfo(); // FYI: info can be null if (info != null && info.getErrorLevel() == ErrorLevel.FATAL) { Action fatalAction = pane.getActionMap().get(JXErrorPane.FATAL_ACTION_KEY); if (fatalAction == null) { System.exit(1); } else { ActionEvent ae = new ActionEvent(closeButton, -1, "fatal"); fatalAction.actionPerformed(ae); } } } private final class ErrorPaneListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { reinit(); } } /** * Lays out the BasicErrorPaneUI components. */ private final class ErrorPaneLayout implements LayoutManager { private JEditorPane dummy = new JEditorPane(); public void addLayoutComponent(String name, Component comp) {} public void removeLayoutComponent(Component comp) {} /** * The preferred size is: * The width of the parent container * The height necessary to show the entire message text * (as long as said height does not go off the screen) * plus the buttons * * The preferred height changes depending on whether the details * are visible, or not. */ public Dimension preferredLayoutSize(Container parent) { int prefWidth = parent.getWidth(); int prefHeight = parent.getHeight(); final Insets insets = parent.getInsets(); int pw = detailButton.isVisible() ? detailButton.getPreferredSize().width : 0; pw += detailButton.isVisible() ? detailButton.getPreferredSize().width : 0; pw += reportButton.isVisible() ? (5 + reportButton.getPreferredSize().width) : 0; pw += closeButton.isVisible() ? (5 + closeButton.getPreferredSize().width) : 0; prefWidth = Math.max(prefWidth, pw) + insets.left + insets.right; if (errorMessage != null) { //set a temp editor to a certain size, just to determine what its //pref height is dummy.setContentType(errorMessage.getContentType()); dummy.setEditorKit(errorMessage.getEditorKit()); dummy.setText(errorMessage.getText()); dummy.setSize(prefWidth, 20); int errorMessagePrefHeight = dummy.getPreferredSize().height; prefHeight = //the greater of the error message height or the icon height Math.max(errorMessagePrefHeight, iconLabel.getPreferredSize().height) + //the space between the error message and the button 10 + //the button preferred height closeButton.getPreferredSize().height; if (detailsPanel.isVisible()) { prefHeight += getDetailsHeight(); } } if (iconLabel != null && iconLabel.getIcon() != null) { prefWidth += iconLabel.getIcon().getIconWidth(); prefHeight += 10; // top of icon is positioned 10px above the text } return new Dimension( prefWidth + insets.left + insets.right, prefHeight + insets.top + insets.bottom); } public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } public void layoutContainer(Container parent) { final Insets insets = parent.getInsets(); int x = insets.left; int y = insets.top; //place the icon if (iconLabel != null) { Dimension dim = iconLabel.getPreferredSize(); iconLabel.setBounds(x, y, dim.width, dim.height); x += dim.width + 17; int leftEdge = x; //place the error message dummy.setContentType(errorMessage.getContentType()); dummy.setText(errorMessage.getText()); dummy.setSize(parent.getWidth() - leftEdge - insets.right, 20); dim = dummy.getPreferredSize(); int spx = x; int spy = y; Dimension spDim = new Dimension (parent.getWidth() - leftEdge - insets.right, dim.height); y += dim.height + 10; int rightEdge = parent.getWidth() - insets.right; x = rightEdge; dim = detailButton.getPreferredSize(); //all buttons should be the same height! int buttonY = y + 5; if (detailButton.isVisible()) { dim = detailButton.getPreferredSize(); x -= dim.width; detailButton.setBounds(x, buttonY, dim.width, dim.height); } if (detailButton.isVisible()) { detailButton.setBounds(x, buttonY, dim.width, dim.height); } errorScrollPane.setBounds(spx, spy, spDim.width, buttonY - spy); if (reportButton.isVisible()) { dim = reportButton.getPreferredSize(); x -= dim.width; x -= 5; reportButton.setBounds(x, buttonY, dim.width, dim.height); } dim = closeButton.getPreferredSize(); x -= dim.width; x -= 5; closeButton.setBounds(x, buttonY, dim.width, dim.height); //if the dialog is expanded... if (detailsPanel.isVisible()) { //layout the details y = buttonY + dim.height + 6; x = leftEdge; int width = rightEdge - x; detailsPanel.setBounds(x, y, width, parent.getHeight() - (y + insets.bottom) ); } } } } private static void centerWindow(Window w, Component owner) { //center based on the owner component, if it is not null //otherwise, center based on the center of the screen if (owner != null) { Point p = owner.getLocation(); p.x += owner.getWidth()/2; p.y += owner.getHeight()/2; SwingUtilities.convertPointToScreen(p, owner); w.setLocation(p); } else { w.setLocation(WindowUtils.getPointForCentering(w)); } } private static void centerWindow(JInternalFrame w, Component owner) { //center based on the owner component, if it is not null //otherwise, center based on the center of the screen if (owner != null) { Point p = owner.getLocation(); p.x += owner.getWidth()/2; p.y += owner.getHeight()/2; SwingUtilities.convertPointToScreen(p, owner); w.setLocation(p); } else { w.setLocation(WindowUtils.getPointForCentering(w)); } } /** * Converts the incoming string to an escaped output string. This method * is far from perfect, only escaping <, > and & characters */ private static String escapeXml(String input) { String s = input == null ? "" : input.replace("&", "&"); s = s.replace("<", "<"); return s = s.replace(">", ">"); } }