001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.gui.swing;
018:
019: // Graphics and geometry
020: import java.awt.Rectangle;
021: import java.awt.Dimension;
022: import java.awt.Graphics2D;
023:
024: // User interface
025: import java.awt.BorderLayout;
026: import java.awt.Container;
027: import java.awt.Component;
028: import java.awt.Dialog;
029: import java.awt.Window;
030: import javax.swing.JPanel;
031: import javax.swing.JLabel;
032: import javax.swing.JButton;
033: import javax.swing.JDialog;
034: import javax.swing.JTextArea;
035: import javax.swing.JComponent;
036: import javax.swing.LookAndFeel;
037: import javax.swing.JOptionPane;
038: import javax.swing.JScrollPane;
039: import javax.swing.JTabbedPane;
040: import javax.swing.JDesktopPane;
041: import javax.swing.BorderFactory;
042: import javax.swing.JInternalFrame;
043: import javax.swing.AbstractButton;
044:
045: // Events
046: import java.awt.event.ActionListener;
047: import java.awt.event.ActionEvent;
048: import java.awt.EventQueue;
049:
050: // Miscellaneous
051: import java.lang.reflect.Constructor;
052: import java.lang.reflect.InvocationTargetException;
053:
054: // Resources
055: import org.geotools.util.logging.Logging;
056: import org.geotools.resources.Utilities;
057: import org.geotools.resources.i18n.Vocabulary;
058: import org.geotools.resources.i18n.VocabularyKeys;
059: import org.geotools.resources.GraphicsUtilities;
060:
061: /**
062: * Utility which enables exception messages to be displayed in a <cite>Swing</cite> component. The
063: * standard {@link java.lang.Exception} class contains methods which write the exception to the
064: * error console. This {@code ExceptionMonitor} class adds static methods which make the message,
065: * and eventually the exception trace, appear in a viewer component.
066: *
067: * <p> </p>
068: * <p align="center"><img src="doc-files/ExceptionMonitor.png"></p>
069: * <p> </p>
070: *
071: * @since 2.0
072: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/ExceptionMonitor.java $
073: * @version $Id: ExceptionMonitor.java 27848 2007-11-12 13:10:32Z desruisseaux $
074: * @author Martin Desruisseaux
075: */
076: public final class ExceptionMonitor {
077: /**
078: * The creation of {@code ExceptionMonitor} class objects is forbidden.
079: */
080: private ExceptionMonitor() {
081: }
082:
083: /**
084: * Displays an error message for the specified exception. Note that this method can
085: * be called from any thread (not necessarily the <cite>Swing</cite> thread).
086: *
087: * @param owner Component in which the exception is produced, or {@code null} if unknown.
088: * @param exception Exception which has been thrown and is to be reported to the user.
089: */
090: public static void show(final Component owner,
091: final Throwable exception) {
092: show(owner, exception, null);
093: }
094:
095: /**
096: * Displays an error message for the specified exception. Note that this method can
097: * be called from any thread (not necessarily the <cite>Swing</cite> thread).
098: *
099: * @param owner Component in which the exception is produced, or {@code null} if unknown.
100: * @param exception Exception which has been thrown and is to be reported to the user.
101: * @param message Message to display. If this parameter is null, then
102: * {@link Exception#getLocalizedMessage} will be called to obtain the message.
103: */
104: public static void show(final Component owner,
105: final Throwable exception, final String message) {
106: if (EventQueue.isDispatchThread()) {
107: Pane.show(owner, exception, message);
108: } else {
109: final Runnable monitor = new Runnable() {
110: public void run() {
111: Pane.show(owner, exception, message);
112: }
113: };
114: try {
115: EventQueue.invokeAndWait(monitor);
116: } catch (InterruptedException error) {
117: // Some doesn't want to let us sleep. Back to work.
118: } catch (InvocationTargetException error) {
119: final Throwable e = error.getTargetException();
120: if (e instanceof RuntimeException)
121: throw (RuntimeException) e;
122: if (e instanceof Error)
123: throw (Error) e;
124: Logging.unexpectedException("org.geotools.gui",
125: ExceptionMonitor.class, "show", e);
126: }
127: }
128: }
129:
130: /**
131: * Writes the specified exception trace in the specified graphics context. This method is
132: * useful when an exception has occurred inside a {@link Component#paint} method and we want
133: * to write it rather than leaving an empty window.
134: *
135: * @param graphics Graphics context in which to write exception. The graphics context should
136: * be in its initial state (default affine transform, default colour, etc...)
137: * @param widgetBounds Size of the trace which was being drawn.
138: * @param exception Exception whose trace we want to write.
139: */
140: public static void paintStackTrace(final Graphics2D graphics,
141: final Rectangle widgetBounds, final Throwable exception) {
142: GraphicsUtilities.paintStackTrace(graphics, widgetBounds,
143: exception);
144: }
145:
146: /**
147: * Class in charge of displaying any exception messages and eventually their traces.
148: * The message will appear in a dialog box or in an internal window, depending on the
149: * parent. <strong>Note:</strong> All methods in this class must be called in the
150: * same thread as the <cite>Swing</cite> thread.
151: *
152: * @version $Id: ExceptionMonitor.java 27848 2007-11-12 13:10:32Z desruisseaux $
153: * @author Martin Desruisseaux
154: */
155: private static final class Pane extends JOptionPane implements
156: ActionListener {
157: /**
158: * Default width (in number of columns) of the component which displays
159: * the exception message or trace.
160: */
161: private static final int WIDTH = 40;
162:
163: /**
164: * Minimum height (in pixels) of the dialog box when it also displays the trace.
165: */
166: private static final int HEIGHT = 300;
167:
168: /**
169: * Displayed dialog box. It will be a {@link JDialog} object or a
170: * {@link JInternalFrame} object.
171: */
172: private final Component dialog;
173:
174: /**
175: * Exception to display in the dialog box. The method {@link Throwable#getLocalizedMessage}
176: * will be called to obtain the message to display.
177: */
178: private final Throwable exception;
179:
180: /**
181: * Box which will contain the "message" part of the constructed dialog box. This box
182: * will be expanded if the user asks to see the exception trace. It will arrange the
183: * components using {@link BorderLayout}.
184: */
185: private final Container message;
186:
187: /**
188: * Component displaying the exception trace. Initially, this component will be null.
189: * It will only be created if the trace is requested by the user.
190: */
191: private Component trace;
192:
193: /**
194: * Indicates whether the trace is currently visible. This field's value
195: * will be inverted each time the user presses the button "trace".
196: */
197: private boolean traceVisible;
198:
199: /**
200: * Button which makes the trace appear or disappear.
201: */
202: private final AbstractButton traceButton;
203:
204: /**
205: * Initial size of the dialog box {@link #dialog}. This information will be used to
206: * return the box to its initial size when the trace disappears.
207: */
208: private final Dimension initialSize;
209:
210: /**
211: * Resources in the user's language.
212: */
213: private final Vocabulary resources;
214:
215: /**
216: * Constructs a pane which will display the specified error message.
217: *
218: * @param owner Parent Component of the dialog box to be created.
219: * @param exception Exception we want to report.
220: * @param message Message to display.
221: * @param buttons Buttons to place under the message. These buttons
222: * should be in the order "Debug", "Close".
223: * @param resources Resources in the user's language.
224: */
225: private Pane(final Component owner, final Throwable exception,
226: final Container message,
227: final AbstractButton[] buttons,
228: final Vocabulary resources) {
229: super (message, ERROR_MESSAGE, OK_CANCEL_OPTION, null,
230: buttons);
231: this .exception = exception;
232: this .message = message;
233: this .resources = resources;
234: this .traceButton = buttons[0];
235: buttons[0].addActionListener(this );
236: buttons[1].addActionListener(this );
237: /*
238: * Constructs the dialog box. Automatically detects if we can use InternalFrame or if
239: * we should be happy with JDialog. The exception trace will not be written immediately.
240: */
241: final String classname = Utilities
242: .getShortClassName(exception);
243: final String title = resources.getString(
244: VocabularyKeys.ERROR_$1, classname);
245: final JDesktopPane desktop = getDesktopPaneForComponent(owner);
246: if (desktop != null) {
247: final JInternalFrame dialog = createInternalFrame(
248: desktop, title);
249: desktop.setLayer(dialog, JDesktopPane.MODAL_LAYER
250: .intValue());
251: dialog
252: .setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
253: dialog.setResizable(false);
254: dialog.pack();
255: this .dialog = dialog;
256: } else {
257: final JDialog dialog = createDialog(owner, title);
258: dialog
259: .setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
260: dialog.setResizable(false);
261: dialog.pack();
262: this .dialog = dialog;
263: }
264: initialSize = dialog.getSize();
265: }
266:
267: /**
268: * Constructs and displays a dialog box which informs the user that an
269: * exception has been produced. This method should be called in the
270: * same thread as the Swing thread.
271: */
272: public static void show(final Component owner,
273: final Throwable exception, String message) {
274: final Vocabulary resources = Vocabulary
275: .getResources((owner != null) ? owner.getLocale()
276: : null);
277: if (message == null) {
278: message = exception.getLocalizedMessage();
279: if (message == null) {
280: final String classname = Utilities
281: .getShortClassName(exception);
282: message = resources.getString(
283: VocabularyKeys.NO_DETAILS_$1, classname);
284: }
285: }
286: final JTextArea textArea = new JTextArea(message, 1, WIDTH);
287: textArea.setEditable(false);
288: textArea.setLineWrap(true);
289: textArea.setWrapStyleWord(true);
290: textArea.setBackground(null);
291: textArea.setBorder(null); // Certain L&Fs have a border.
292: /*
293: * Constructs the rest of the dialog box. The title bar will
294: * contain the name of the exception class.
295: */
296: final JComponent messageBox = new JPanel(new BorderLayout());
297: messageBox.add(textArea, BorderLayout.NORTH);
298: final Pane pane = new Pane(
299: owner,
300: exception,
301: messageBox,
302: new AbstractButton[] {
303: new JButton(resources
304: .getString(VocabularyKeys.DEBUG)),
305: new JButton(resources
306: .getString(VocabularyKeys.CLOSE)) },
307: resources);
308: pane.dialog.setVisible(true);
309: }
310:
311: /**
312: * Displays the exception trace below the message. This method is
313: * called automatically when the dialog box's "Debug" button is
314: * pressed. If the exception trace still hasn't been written, this
315: * method will construct the necessary components once and for all.
316: */
317: public void actionPerformed(final ActionEvent event) {
318: if (event.getSource() != traceButton) {
319: dispose();
320: return;
321: }
322: /*
323: * Constructs the exception trace once and for all if it hasn't
324: * already been constructed.
325: */
326: if (trace == null) {
327: JComponent traceComponent = null;
328: for (Throwable cause = exception; cause != null; cause = cause
329: .getCause()) {
330: final JTextArea text = new JTextArea(1, WIDTH);
331: text.setTabSize(4);
332: text.setText(GraphicsUtilities
333: .printStackTrace(cause));
334: text.setEditable(false);
335: final JScrollPane scroll = new JScrollPane(text);
336: if (traceComponent != null) {
337: if (!(traceComponent instanceof JTabbedPane)) {
338: String classname = Utilities
339: .getShortClassName(exception);
340: JTabbedPane tabs = new JTabbedPane(
341: JTabbedPane.TOP,
342: JTabbedPane.SCROLL_TAB_LAYOUT);
343: tabs.addTab(classname, traceComponent);
344: traceComponent = tabs;
345: }
346: String classname = Utilities
347: .getShortClassName(cause);
348: ((JTabbedPane) traceComponent).addTab(
349: classname, scroll);
350: } else {
351: traceComponent = scroll;
352: }
353: }
354: if (traceComponent == null) {
355: // Should not happen
356: return;
357: }
358: traceComponent.setBorder(BorderFactory
359: .createCompoundBorder(BorderFactory
360: .createEmptyBorder(12, 0, 0, 0),
361: traceComponent.getBorder()));
362: trace = traceComponent;
363: }
364: /*
365: * Inserts or hides the exception trace. Even if the trace is
366: * hidden, it will not be destroyed if the user would like to
367: * redisplay it.
368: */
369: traceButton.setText(resources
370: .format(traceVisible ? VocabularyKeys.DEBUG
371: : VocabularyKeys.HIDE));
372: traceVisible = !traceVisible;
373: if (dialog instanceof Dialog) {
374: ((Dialog) dialog).setResizable(traceVisible);
375: } else {
376: ((JInternalFrame) dialog).setResizable(traceVisible);
377: }
378: if (traceVisible) {
379: message.add(trace, BorderLayout.CENTER);
380: dialog.setSize(initialSize.width, HEIGHT);
381: } else {
382: message.remove(trace);
383: dialog.setSize(initialSize);
384: }
385: dialog.validate();
386: }
387:
388: /**
389: * Frees up the resources used by this dialog box. This method is
390: * called when the user closes the dialog box which reported the
391: * exception.
392: */
393: private void dispose() {
394: if (dialog instanceof Window) {
395: ((Window) dialog).dispose();
396: } else {
397: ((JInternalFrame) dialog).dispose();
398: }
399: }
400: }
401:
402: /**
403: * Display a dummy exception. This method is provided only as an easy
404: * way to test the dialog appearance from the command line.
405: */
406: public static void main(final String[] args) {
407: show(null, new Exception());
408: }
409: }
|