/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.plugin.desktoputil; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.swing.*; import javax.swing.event.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.skin.*; /** * Implements a <tt>JDialog</tt> which displays an error message and, * optionally, a <tt>Throwable</tt> stack trace. <tt>ErrorDialog</tt> has an OK * button which dismisses the message and a link to display the * <tt>Throwable</tt> stack trace upon request if available. * * @author Yana Stamcheva * @author Adam Netocny * @author Lyubomir Marinov */ public class ErrorDialog extends SIPCommDialog implements ActionListener, HyperlinkListener, Skinnable { private static final long serialVersionUID = 1L; /** * The <tt>Logger</tt> used by the <tt>ErrorDialog</tt> class and its * instances for logging output. */ private static final Logger logger = Logger.getLogger(ErrorDialog.class); private JButton okButton = new JButton( DesktopUtilActivator.getResources().getI18NString("service.gui.OK")); private JLabel iconLabel = new JLabel(new ImageIcon( DesktopUtilActivator.getImage("service.gui.icons.ERROR_ICON"))); private StyledHTMLEditorPane htmlMsgEditorPane = new StyledHTMLEditorPane(); private JTextArea stackTraceTextArea = new JTextArea(); private JScrollPane stackTraceScrollPane = new JScrollPane(); private TransparentPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER)); private TransparentPanel infoMessagePanel = new TransparentPanel(); private TransparentPanel messagePanel = new TransparentPanel(new BorderLayout()); private TransparentPanel mainPanel = new TransparentPanel(new BorderLayout(10, 10)); /** * Load the "net.java.sip.communicator.SHOW_STACK_TRACE" property to * determine whether we should show stack trace in error dialogs. * Default is show. */ private static String showStackTraceDefaultProp = DesktopUtilActivator.getResources().getSettingsString( "net.java.sip.communicator.SHOW_STACK_TRACE"); /** * Should we show stack trace. */ private final static boolean showStackTrace = (showStackTraceDefaultProp != null) ? Boolean.parseBoolean(showStackTraceDefaultProp) : true; /** * The indicator which determines whether the details of the error are * currently shown. * <p> * The indicator is initially set to <tt>true</tt> because the constructor * {@link #ErrorDialog(Frame, String, String, Throwable)} calls * {@link #showOrHideDetails()} and thus <tt>ErrorDialog</tt> defaults to * not showing the details of the error. * </p> */ private boolean detailsShown = true; /** * The type of <tt>ErrorDialog</tt> which displays a warning instead of an * error. */ public static final int WARNING = 1; /** * The type of this <tt>ErrorDialog</tt> (e.g. {@link #WARNING}). The * default <tt>ErrorDialog</tt> displays an error. */ private int type = 0; /** * The maximum width that we allow message dialogs to have. */ private static final int MAX_MSG_PANE_WIDTH = 340; /** * The maximum height that we allow message dialogs to have. */ private static final int MAX_MSG_PANE_HEIGHT = 800; /** * Initializes a new <tt>ErrorDialog</tt> with a specific owner * <tt>Frame</tt>, title and message to be displayed. * * @param owner the dialog owner * @param title the title of the dialog * @param message the message to be displayed */ public ErrorDialog( Frame owner, String title, String message) { super(owner, false); this.mainPanel.setBorder( BorderFactory.createEmptyBorder(20, 20, 10, 20)); if (showStackTrace) { this.stackTraceScrollPane.setBorder(BorderFactory.createLineBorder( iconLabel.getForeground())); this.stackTraceScrollPane.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); } this.setTitle(title); this.infoMessagePanel.setLayout(new BorderLayout()); JEditorPane messageArea = new JEditorPane(); /* * Make JEditorPane respect our default font because we will be using it * to just display text. */ messageArea.putClientProperty( JEditorPane.HONOR_DISPLAY_PROPERTIES, true); messageArea.setOpaque(false); messageArea.setEditable(false); messageArea.setContentType("text/html"); messageArea.setText( "<html><body><p align=\"left\" >"+message+"</p></body></html>"); //try to reevaluate the preferred size of the message pane. //(this is definitely not a neat way to do it ... but it works). messageArea.setSize( new Dimension(MAX_MSG_PANE_WIDTH, MAX_MSG_PANE_HEIGHT)); messageArea.setPreferredSize( new Dimension( MAX_MSG_PANE_WIDTH, messageArea.getPreferredSize().height)); this.infoMessagePanel.add(messageArea, BorderLayout.CENTER); this.init(); } /** * Initializes a new <tt>ErrorDialog</tt> with a specific owner * <tt>Frame</tt>, title, error message to be displayed and the * <tt>Throwable</tt> associated with the error. * * @param owner the dialog owner * @param title the title of the dialog * @param message the message to be displayed * @param e the exception corresponding to the error */ public ErrorDialog( Frame owner, String title, String message, Throwable e) { this(owner, title, message); if (showStackTrace && e != null) { this.setTitle(title); this.htmlMsgEditorPane.setEditable(false); this.htmlMsgEditorPane.setOpaque(false); this.htmlMsgEditorPane.addHyperlinkListener(this); showOrHideDetails(); this.infoMessagePanel.add(htmlMsgEditorPane, BorderLayout.SOUTH); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); pw.close(); String stackTrace = sw.toString(); try { sw.close(); } catch (IOException ex) { //really shouldn't happen. but log anyway logger.error("Failed to close a StringWriter. ", ex); } this.stackTraceTextArea.setText(stackTrace); } } /** * Initializes a new <tt>ErrorDialog</tt> with a specific owner * <tt>Frame</tt>, title and message to be displayed and of a specific type. * * @param owner the dialog owner * @param title the title of the error dialog * @param message the message to be displayed * @param type the dialog type */ public ErrorDialog( Frame owner, String title, String message, int type) { this(owner, title, message); if(type == WARNING) { iconLabel.setIcon(new ImageIcon( DesktopUtilActivator.getImage("service.gui.icons.WARNING_ICON"))); this.type = type; } } /** * Initializes this dialog. */ private void init() { this.getRootPane().setDefaultButton(okButton); this.stackTraceScrollPane.getViewport().add(stackTraceTextArea); this.stackTraceScrollPane.setPreferredSize( new Dimension(this.getWidth(), 100)); this.buttonsPanel.add(okButton); this.okButton.addActionListener(this); this.mainPanel.add(iconLabel, BorderLayout.WEST); this.messagePanel.add(infoMessagePanel, BorderLayout.NORTH); this.mainPanel.add(messagePanel, BorderLayout.CENTER); this.mainPanel.add(buttonsPanel, BorderLayout.SOUTH); this.getContentPane().add(mainPanel); } /** * Shows if previously hidden or hides if previously shown the details of * the error. Called when the "more" link is clicked. */ public void showOrHideDetails() { String startDivTag = "<div id=\"message\">"; String endDivTag = "</div>"; String msgString; detailsShown = !detailsShown; if(detailsShown) { msgString = startDivTag + " <p align=\"right\"><a href=\"\"><< Hide info</a></p>" + endDivTag; this.messagePanel.add(stackTraceScrollPane, BorderLayout.CENTER); } else { msgString = startDivTag + " <p align=\"right\"><a href=\"\">More info >></a></p>" + endDivTag; this.messagePanel.remove(stackTraceScrollPane); } htmlMsgEditorPane.setText(msgString); this.messagePanel.revalidate(); this.messagePanel.repaint(); // restore default values for preferred size, // as we have resized its components let it calculate // that size setPreferredSize(null); this.pack(); } /** * Shows the dialog. */ public void showDialog() { this.pack(); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); this.setLocation(screenSize.width/2 - this.getWidth()/2, screenSize.height/2 - this.getHeight()/2); this.setVisible(true); this.toFront(); } /** * Handles the <tt>ActionEvent</tt>. Depending on the user choice sets * the return code to the appropriate value. * * @param e the <tt>ActionEvent</tt> instance that has just been fired. */ public void actionPerformed(ActionEvent e) { JButton button = (JButton) e.getSource(); if(button.equals(okButton)) this.dispose(); } /** * Close the ErrorDialog. This function is invoked when user * presses the Escape key. * * @param isEscaped Specifies whether the close was triggered by pressing * the escape key. */ @Override protected void close(boolean isEscaped) { this.okButton.doClick(); } /** * Update the ErrorDialog when the user clicks on the hyperlink. * * @param e The event generated by the click on the hyperlink. */ public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) showOrHideDetails(); } /** * Reloads icon. */ public void loadSkin() { String icon = (type == WARNING) ? "service.gui.icons.WARNING_ICON" : "service.gui.icons.ERROR_ICON"; iconLabel.setIcon(new ImageIcon(DesktopUtilActivator.getImage(icon))); } }