org.jdesktop.swingx.plaf.basic.BasicErrorPaneUI Maven / Gradle / Ivy
Show all versions of swingx-all Show documentation
/*
* $Id: BasicErrorPaneUI.java 3927 2011-02-22 16:34:11Z kleopatra $
*
* 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.plaf.basic;
import org.jdesktop.swingx.JXEditorPane;
import org.jdesktop.swingx.JXErrorPane;
import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.error.ErrorInfo;
import org.jdesktop.swingx.error.ErrorLevel;
import org.jdesktop.swingx.error.ErrorReporter;
import org.jdesktop.swingx.plaf.ErrorPaneUI;
import org.jdesktop.swingx.plaf.UIManagerExt;
import org.jdesktop.swingx.util.WindowUtils;
import javax.swing.*;
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 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;
/**
* Base implementation of the JXErrorPane
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
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) {
JXErrorPane pane = (JXErrorPane) c;
AbstractActionExt reportAction = new AbstractActionExt() {
@Override
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 installListeners
*/
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 = 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 = {(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;
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 static 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.isEmpty()) {
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
StringBuilder html = new StringBuilder("");
html.append("" + escapeXml(errorInfo.getTitle()) + "
");
html.append("
");
html.append("");
html.append("Message:");
html.append("");
html.append(" " + escapeXml(errorInfo.getErrorException().toString()));
html.append("
");
html.append("Level:");
html.append("");
html.append(" " + errorInfo.getErrorLevel());
html.append("
");
html.append("Stack Trace:");
Throwable ex = errorInfo.getErrorException();
while (ex != null) {
html.append("" + ex.getMessage() + "
");
html.append("");
for (StackTraceElement el : ex.getStackTrace()) {
html.append(" " + el.toString().replace("", "<init>") + "\n");
}
html.append("
");
ex = ex.getCause();
}
html.append("");
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 final 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
*/
@Override
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)
*/
@Override
public void actionPerformed(ActionEvent e) {
setDetailsVisible(!detailsPanel.isVisible());
}
}
private final class ResizeWindow implements ActionListener {
private final Window w;
private ResizeWindow(Window w) {
if (w == null) {
throw new NullPointerException();
}
this.w = w;
}
@Override
public void actionPerformed(ActionEvent ae) {
Dimension contentSize;
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 {
EqualSizeJButton(String text) {
super(text);
}
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 preferredSize
has been set to a
* non-null
value just returns it.
* If the UI delegate's getPreferredSize
* method returns a non null
value then return that;
* otherwise defer to the component's layout manager.
*
* @return the value of the preferredSize
property
* @see #setPreferredSize
* @see ComponentUI
*/
@Override
public Dimension getPreferredSize() {
int width = 0;
int height = 0;
for (EqualSizeJButton btn : group) {
Dimension size = btn.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 final 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.isEmpty()) {
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 {
JXErrorDialog(Frame parent, JXErrorPane p) {
super(parent, true);
init(p);
}
JXErrorDialog(Dialog parent, JXErrorPane p) {
super(parent, true);
init(p);
}
private 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 {
JXErrorFrame(JXErrorPane p) {
setTitle(p.getErrorInfo().getTitle());
initWindow(this, p);
}
}
private final class JXInternalErrorFrame extends JInternalFrame {
JXInternalErrorFrame(JXErrorPane p) {
setTitle(p.getErrorInfo().getTitle());
setLayout(new BorderLayout());
add(p, BorderLayout.CENTER);
Action closeAction = new AbstractAction() {
@Override
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);
}
}
/**
* 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(Window w, JXErrorPane pane) {
w.setLayout(new BorderLayout());
w.add(pane, BorderLayout.CENTER);
Action closeAction = new CloseAction(w);
closeButton.addActionListener(closeAction);
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) {
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) {
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 {
@Override
public void propertyChange(PropertyChangeEvent evt) {
reinit();
}
}
/**
* Lays out the BasicErrorPaneUI components.
*/
private final class ErrorPaneLayout implements LayoutManager {
private final JEditorPane dummy = new JEditorPane();
@Override
public void addLayoutComponent(String name, Component comp) {
}
@Override
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.
*/
@Override
public Dimension preferredLayoutSize(Container parent) {
int prefWidth = parent.getWidth();
int prefHeight = parent.getHeight();
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);
}
@Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
@Override
public void layoutContainer(Container parent) {
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.replace(">", ">");
}
}