/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 1999-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * 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; * version 2.1 of the License. * * 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. */ package org.geotoolkit.internal.swing; import java.awt.Dimension; import java.awt.BorderLayout; import java.awt.Container; import java.awt.Component; import java.awt.Dialog; import java.awt.Window; import javax.swing.Box; import javax.swing.JPanel; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JTextArea; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JDesktopPane; import javax.swing.JInternalFrame; import javax.swing.AbstractButton; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.EventQueue; import org.jdesktop.swingx.JXLabel; import org.geotoolkit.util.Exceptions; import org.apache.sis.util.Classes; import org.geotoolkit.resources.Vocabulary; /** * Class in charge of displaying any exception messages and eventually their traces. * The message will appear in a dialog box or in an internal window, depending on the * parent. <strong>Note:</strong> All methods in this class must be called in the * same thread as the <cite>Swing</cite> thread. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.18 * * @see Exceptions * @see org.jdesktop.swingx.JXErrorPane * * @since 2.0 * @module */ @SuppressWarnings("serial") public final class ExceptionMonitor extends JOptionPane implements ActionListener { /** * Width (in pixels) of the dialog box when it also displays the trace. */ private static final int WIDTH = 600; /** * Height (in pixels) of the dialog box when it also displays the trace. */ private static final int HEIGHT = 400; /** * Displayed dialog box. It will be a {@link JDialog} object or a * {@link JInternalFrame} object. */ private final Component dialog; /** * Exception to display in the dialog box. The method {@link Throwable#getLocalizedMessage} * will be called to obtain the message to display. */ private final Throwable exception; /** * Box which will contain the "message" part of the constructed dialog box. This box * will be expanded if the user asks to see the exception trace. It will arrange the * components using {@link BorderLayout}. */ private final Container message; /** * Component displaying the exception trace. Initially, this component will be null. * It will only be created if the trace is requested by the user. */ private Container trace; /** * Indicates whether the trace is currently visible. This field value * will be inverted each time the user presses the button "trace". */ private boolean traceVisible; /** * Button which makes the trace appear or disappear. */ private final AbstractButton traceButton; /** * Initial size of the dialog box {@link #dialog}. This information will be used to * return the box to its initial size when the trace disappears. */ private final Dimension initialSize; /** * Resources in the user's language. */ private final Vocabulary resources; /** * Constructs a pane which will display the specified error message. * * @param owner Parent Component of the dialog box to be created. * @param exception Exception we want to report. * @param message Message to display. * @param buttons Buttons to place under the message. These buttons * should be in the order "Debug", "Close". * @param resources Resources in the user's language. */ private ExceptionMonitor(final Component owner, final Throwable exception, final Container message, final AbstractButton[] buttons, final Vocabulary resources) { super(message, ERROR_MESSAGE, OK_CANCEL_OPTION, null, buttons); this.exception = exception; this.message = message; this.resources = resources; this.traceButton = buttons[0]; buttons[0].addActionListener(this); buttons[1].addActionListener(this); /* * Constructs the dialog box. Automatically detects if we can use InternalFrame or if * we should be happy with JDialog. The exception trace will not be written immediately. */ final String classname = Classes.getShortClassName(exception); final String title = resources.getString(Vocabulary.Keys.Error_1, classname); final JDesktopPane desktop = getDesktopPaneForComponent(owner); if (desktop != null) { final JInternalFrame dialog = createInternalFrame(desktop, title); desktop.setLayer(dialog, JDesktopPane.MODAL_LAYER.intValue()); dialog.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE); dialog.setResizable(false); dialog.pack(); this.dialog = dialog; } else { final JDialog dialog = createDialog(owner, title); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); dialog.setResizable(false); dialog.pack(); this.dialog = dialog; } initialSize = dialog.getSize(); } /** * Constructs and displays a dialog box which informs the user that an exception has * been produced. This method should be called in the same thread as the Swing thread. */ private static void showUnsafe(final Component owner, final Throwable exception, String message) { final Vocabulary resources = Vocabulary.getResources((owner != null) ? owner.getLocale() : null); if (message == null) { message = exception.getLocalizedMessage(); if (message == null) { final String classname = Classes.getShortClassName(exception); message = resources.getString(Vocabulary.Keys.NoDetails_1, classname); } } final JXLabel textArea = new JXLabel(message); textArea.setLineWrap(true); textArea.setMaxLineSpan(WIDTH); final JComponent messageBox = new JPanel(new BorderLayout()); messageBox.add(textArea, BorderLayout.NORTH); final ExceptionMonitor pane = new ExceptionMonitor(owner, exception, messageBox, new AbstractButton[] { new JButton(resources.getString(Vocabulary.Keys.Debug)), new JButton(resources.getString(Vocabulary.Keys.Close)) }, resources); pane.dialog.setVisible(true); } /** * Displays an error message for the specified exception. Note that this method can * be called from any thread (not necessarily the <cite>Swing</cite> thread). * * @param owner Component in which the exception is produced, or {@code null} if unknown. * @param exception Exception which has been thrown and is to be reported to the user. * @param message Message to display. If this parameter is null, then * {@link Exception#getLocalizedMessage} will be called to obtain the message. */ public static void show(final Component owner, final Throwable exception, final String message) { if (EventQueue.isDispatchThread()) { showUnsafe(owner, exception, message); } else { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { showUnsafe(owner, exception, message); } }); } } /** * Displays an error message for the specified exception. Note that this method can * be called from any thread (not necessarily the <cite>Swing</cite> thread). * * @param owner Component in which the exception is produced, or {@code null} if unknown. * @param exception Exception which has been thrown and is to be reported to the user. */ public static void show(final Component owner, final Throwable exception) { show(owner, exception, null); } /** * Displays the exception trace below the message. This method is called automatically * when the dialog box "Debug" button is pressed. If the exception trace still hasn't * been written, this method will construct the necessary components. * * @param event The event. */ @Override public void actionPerformed(final ActionEvent event) { if (event.getSource() != traceButton) { dispose(); return; } /* * Constructs the exception trace if it hasn't already been constructed. */ if (trace == null) { JComponent traceComponent = null; for (Throwable cause = exception; cause != null; cause = cause.getCause()) { final JTextArea text = new JTextArea(); text.setTabSize(4); text.setText(Exceptions.formatStackTrace(cause)); text.setEditable(false); text.setCaretPosition(0); final JScrollPane scroll = new JScrollPane(text); if (traceComponent != null) { if (!(traceComponent instanceof JTabbedPane)) { traceComponent.setOpaque(false); String classname = Classes.getShortClassName(exception); JTabbedPane tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); tabs.addTab(classname, traceComponent); traceComponent = tabs; } String classname = Classes.getShortClassName(cause); ((JTabbedPane) traceComponent).addTab(classname, scroll); } else { traceComponent = scroll; } } if (traceComponent == null) { // Should not happen return; } trace = Box.createVerticalBox(); trace.add(Box.createVerticalStrut(12)); trace.add(traceComponent); } /* * Inserts or hides the exception trace. Even if the trace is * hidden, it will not be destroyed if the user would like to * redisplay it. */ traceButton.setText(resources.getString(traceVisible ? Vocabulary.Keys.Debug : Vocabulary.Keys.Hide)); traceVisible = !traceVisible; if (dialog instanceof Dialog) { ((Dialog) dialog).setResizable(traceVisible); } else { ((JInternalFrame) dialog).setResizable(traceVisible); } int dx = dialog.getWidth(); int dy = dialog.getHeight(); if (traceVisible) { message.add(trace, BorderLayout.CENTER); dialog.setSize(WIDTH, HEIGHT); } else { message.remove(trace); dialog.setSize(initialSize); } dx -= dialog.getWidth(); dy -= dialog.getHeight(); dialog.setLocation(Math.max(0, dialog.getX() + dx/2), Math.max(0, dialog.getY() + dy/2)); dialog.validate(); } /** * Frees up the resources used by this dialog box. This method is called when the * user closes the dialog box which reported the exception. */ private void dispose() { if (dialog instanceof Window) { ((Window) dialog).dispose(); } else { ((JInternalFrame) dialog).dispose(); } } }