001: /*
002: * $Id: JXErrorDialog.java,v 1.6 2005/10/11 14:40:53 kizune Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
005: * California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or modify it under
008: * the terms of the GNU Lesser General Public License as published by the Free
009: * Software Foundation; either version 2.1 of the License, or (at your option)
010: * any later version.
011: *
012: * This library is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
014: * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
015: * details.
016: *
017: * You should have received a copy of the GNU Lesser General Public License
018: * along with this library; if not, write to the Free Software Foundation, Inc.,
019: * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package org.jdesktop.swingx;
023:
024: import java.awt.Container;
025: import java.awt.Dialog;
026: import java.awt.Dimension;
027: import java.awt.Frame;
028: import java.awt.GridBagConstraints;
029: import java.awt.GridBagLayout;
030: import java.awt.Insets;
031: import java.awt.Window;
032: import java.awt.event.ActionEvent;
033: import java.awt.event.ActionListener;
034: import java.io.PrintWriter;
035: import java.io.StringWriter;
036:
037: import javax.swing.AbstractAction;
038: import javax.swing.Action;
039: import javax.swing.Icon;
040: import javax.swing.JButton;
041: import javax.swing.JDialog;
042: import javax.swing.JLabel;
043: import javax.swing.JScrollPane;
044: import javax.swing.JTextArea;
045: import javax.swing.UIManager;
046:
047: /**
048: * Common Error Dialog, suitable for representing information about errors and
049: * exceptions happened in application. The common usage of the
050: * <code>JXErrorDialog</code> is to show collected data about the incident and
051: * probably ask customer for a feedback. The data about the incident consists
052: * from the title which will be displayed in the dialog header, short
053: * description of the problem that will be immediately seen after dialog is
054: * became visible, full description of the problem which will be visible after
055: * user clicks "Details" button and Throwable that contains stack trace and
056: * another usable information that may be displayed in the dialog.
057: * <p>
058: *
059: * To ask user for feedback extend abstract class <code>ErrorReporter</code>
060: * and set your reporter using <code>setReporter</code> method. Report button
061: * will be added to the dialog automatically.<br>
062: * See {@link MailErrorReporter MailErrorReporter} documentation for the example
063: * of error reporting usage.
064: * <p>
065: *
066: * For example, to show simple <code>JXErrorDialog</code> call <br>
067: * <code>JXErrorDialog.showDialog(null, "Application Error",
068: * "The application encountered the unexpected error,
069: * please contact developers")</code>
070: *
071: * @author Richard Bair
072: * @author Alexander Zuev
073: */
074: public class JXErrorDialog extends JDialog {
075: /**
076: *
077: */
078: private static final long serialVersionUID = -7946066280576376962L;
079:
080: /**
081: * Text representing expanding the details section of the dialog
082: */
083: private static final String DETAILS_EXPAND_TEXT = "Details >>";
084:
085: /**
086: * Text representing contracting the details section of the dialog
087: */
088: private static final String DETAILS_CONTRACT_TEXT = "Details <<";
089:
090: /**
091: * Text for the Ok button.
092: */
093: private static final String OK_BUTTON_TEXT = "Ok";
094:
095: /**
096: * Icon for the error dialog (stop sign, etc)
097: */
098: private static final Icon icon = UIManager
099: .getIcon("OptionPane.warningIcon");
100:
101: /**
102: * Text for the reportError button
103: */
104: private static final String REPORT_BUTTON_TEXT = "Report...";
105:
106: /**
107: * Error message label
108: */
109: private JLabel errorMessage;
110:
111: /**
112: * details text area
113: */
114: private JTextArea details;
115:
116: /**
117: * detail button
118: */
119: private JButton detailButton;
120:
121: /**
122: * details scroll pane
123: */
124: private JScrollPane detailsScrollPane;
125:
126: /**
127: * report an error button
128: */
129: private JButton reportButton;
130:
131: /**
132: * Error reporting engine assigned for error reporting for all error dialogs
133: */
134: private static ErrorReporter reporter;
135:
136: /**
137: * IncidentInfo that contains all the information prepared for reporting.
138: */
139: private IncidentInfo incidentInfo;
140:
141: /**
142: * Create a new ErrorDialog with the given Frame as the owner
143: *
144: * @param owner
145: * Owner of this error dialog.
146: */
147: public JXErrorDialog(Frame owner) {
148: super (owner, true);
149: initGui();
150: }
151:
152: /**
153: * Create a new ErrorDialog with the given Dialog as the owner
154: *
155: * @param owner
156: * Owner of this error dialog.
157: */
158: private JXErrorDialog(Dialog owner) {
159: super (owner, true);
160: initGui();
161: }
162:
163: /**
164: * initialize the gui.
165: */
166: private void initGui() {
167: // initialize the gui
168: GridBagLayout layout = new GridBagLayout();
169: this .getContentPane().setLayout(layout);
170:
171: GridBagConstraints gbc = new GridBagConstraints();
172: gbc.anchor = GridBagConstraints.CENTER;
173: gbc.fill = GridBagConstraints.NONE;
174: gbc.gridheight = 1;
175: gbc.insets = new Insets(22, 12, 11, 17);
176: this .getContentPane().add(new JLabel(icon), gbc);
177:
178: errorMessage = new JLabel();
179: gbc.anchor = GridBagConstraints.WEST;
180: gbc.fill = GridBagConstraints.BOTH;
181: gbc.gridheight = 1;
182: gbc.gridwidth = 2;
183: gbc.gridx = 1;
184: gbc.weightx = 1.0;
185: gbc.insets = new Insets(12, 0, 0, 11);
186: this .getContentPane().add(errorMessage, gbc);
187:
188: gbc.fill = GridBagConstraints.NONE;
189: gbc.gridx = 1;
190: gbc.gridy = 1;
191: gbc.gridwidth = 1;
192: gbc.weightx = 1.0;
193: gbc.weighty = 0.0;
194: gbc.anchor = GridBagConstraints.EAST;
195: gbc.insets = new Insets(12, 0, 11, 5);
196: JButton okButton = new JButton(OK_BUTTON_TEXT);
197: this .getContentPane().add(okButton, gbc);
198:
199: reportButton = new JButton(new ReportAction());
200: gbc.gridx = 2;
201: gbc.weightx = 0.0;
202: gbc.insets = new Insets(12, 0, 11, 5);
203: this .getContentPane().add(reportButton, gbc);
204: reportButton.setVisible(false); // not visible by default
205:
206: detailButton = new JButton(DETAILS_EXPAND_TEXT);
207: gbc.gridx = 3;
208: gbc.weightx = 0.0;
209: gbc.insets = new Insets(12, 0, 11, 11);
210: this .getContentPane().add(detailButton, gbc);
211:
212: details = new JTextArea(7, 60);
213: detailsScrollPane = new JScrollPane(details);
214: detailsScrollPane
215: .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
216: details.setEditable(false);
217: gbc.fill = GridBagConstraints.BOTH;
218: gbc.gridwidth = 4;
219: gbc.gridx = 0;
220: gbc.gridy = 2;
221: gbc.weighty = 1.0;
222: gbc.insets = new Insets(6, 11, 11, 11);
223: this .getContentPane().add(detailsScrollPane, gbc);
224:
225: /*
226: * Here i'm going to add invisible empty container to the bottom of the
227: * content pane to fix minimal width of the dialog. It's quite a hack,
228: * but i have not found anything better.
229: */
230: Dimension spPredictedSize = detailsScrollPane
231: .getPreferredSize();
232: Dimension newPanelSize = new Dimension(
233: spPredictedSize.width + 15, 0);
234: Container widthHolder = new Container();
235: widthHolder.setMinimumSize(newPanelSize);
236: widthHolder.setPreferredSize(newPanelSize);
237: widthHolder.setMaximumSize(newPanelSize);
238:
239: gbc.gridy = 3;
240: gbc.insets = new Insets(0, 11, 11, 0);
241: this .getContentPane().add(widthHolder, gbc);
242:
243: // make the buttons the same size
244: int buttonLength = detailButton.getPreferredSize().width;
245: int buttonHeight = detailButton.getPreferredSize().height;
246: Dimension buttonSize = new Dimension(buttonLength, buttonHeight);
247: okButton.setPreferredSize(buttonSize);
248: reportButton.setPreferredSize(buttonSize);
249: detailButton.setPreferredSize(buttonSize);
250:
251: // set the event handling
252: okButton.addActionListener(new OkClickEvent());
253: detailButton.addActionListener(new DetailsClickEvent());
254: }
255:
256: /**
257: * Set the details section of the error dialog. If the details are either
258: * null or an empty string, then hide the details button and hide the detail
259: * scroll pane. Otherwise, just set the details section.
260: *
261: * @param details
262: * Details to be shown in the detail section of the dialog. This
263: * can be null if you do not want to display the details section
264: * of the dialog.
265: */
266: private void setDetails(String details) {
267: if (details == null || details.equals("")) {
268: setDetailsVisible(false);
269: detailButton.setVisible(false);
270: } else {
271: this .details.setText(details);
272: setDetailsVisible(false);
273: detailButton.setVisible(true);
274: }
275: }
276:
277: /**
278: * Set the details section to be either visible or invisible. Set the text
279: * of the Details button accordingly.
280: *
281: * @param b
282: * if true details section will be visible
283: */
284: private void setDetailsVisible(boolean b) {
285: if (b) {
286: details.setCaretPosition(0);
287: detailsScrollPane.setVisible(true);
288: detailButton.setText(DETAILS_CONTRACT_TEXT);
289: } else {
290: detailsScrollPane.setVisible(false);
291: detailButton.setText(DETAILS_EXPAND_TEXT);
292: }
293:
294: pack();
295: }
296:
297: /**
298: * Set the error message for the dialog box
299: *
300: * @param errorMessage
301: * Message for the error dialog
302: */
303: private void setErrorMessage(String errorMessage) {
304: this .errorMessage.setText(errorMessage);
305: }
306:
307: /**
308: * Sets the IncidentInfo for this dialog
309: *
310: * @param info
311: * IncidentInfo that incorporates all the details about the error
312: */
313: private void setIncidentInfo(IncidentInfo info) {
314: this .incidentInfo = info;
315: this .reportButton.setVisible(getReporter() != null);
316: }
317:
318: /**
319: * Get curent dialog's IncidentInfo
320: *
321: * @return <code>IncidentInfo</code> assigned to this dialog
322: */
323: private IncidentInfo getIncidentInfo() {
324: return incidentInfo;
325: }
326:
327: /**
328: * Listener for Ok button click events
329: *
330: * @author Richard Bair
331: */
332: private final class OkClickEvent implements ActionListener {
333:
334: /*
335: * (non-Javadoc)
336: *
337: * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
338: */
339: public void actionPerformed(ActionEvent e) {
340: // close the window
341: setVisible(false);
342: }
343: }
344:
345: /**
346: * Listener for Details click events. Alternates whether the details section
347: * is visible or not.
348: *
349: * @author Richard Bair
350: */
351: private final class DetailsClickEvent implements ActionListener {
352:
353: /*
354: * (non-Javadoc)
355: *
356: * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
357: */
358: public void actionPerformed(ActionEvent e) {
359: setDetailsVisible(!detailsScrollPane.isVisible());
360: }
361: }
362:
363: /**
364: * Constructs and shows the error dialog for the given exception. The
365: * exceptions message will be the errorMessage, and the stacktrace will be
366: * the details.
367: *
368: * @param owner
369: * Owner of this error dialog.
370: * @param title
371: * Title of the error dialog
372: * @param e
373: * Exception that contains information about the error cause and
374: * stack trace
375: */
376: public static void showDialog(Window owner, String title,
377: Throwable e) {
378: IncidentInfo ii = new IncidentInfo(title, null, null, e);
379: showDialog(owner, ii);
380: }
381:
382: /**
383: * Show the error dialog.
384: *
385: * @param owner
386: * Owner of this error dialog
387: * @param title
388: * Title of the error dialog
389: * @param errorMessage
390: * Message for the error dialog
391: * @param details
392: * Details to be shown in the detail section of the dialog. This
393: * can be null if you do not want to display the details section
394: * of the dialog.
395: */
396: public static void showDialog(Window owner, String title,
397: String errorMessage, String details) {
398: IncidentInfo ii = new IncidentInfo(title, errorMessage, details);
399: showDialog(owner, ii);
400: }
401:
402: /**
403: * Show the error dialog.
404: *
405: * @param owner
406: * Owner of this error dialog.
407: * @param info
408: * <code>IncidentInfo</code> that incorporates all the
409: * information about the error
410: */
411: public static void showDialog(Window owner, IncidentInfo info) {
412: JXErrorDialog dlg;
413:
414: if (owner instanceof Dialog) {
415: dlg = new JXErrorDialog((Dialog) owner);
416: } else if (owner instanceof Frame) {
417: dlg = new JXErrorDialog((Frame) owner);
418: } else {
419: return;
420: }
421: dlg.setTitle(info.getHeader());
422: dlg.setErrorMessage(info.getBasicErrorMessage());
423: String details = info.getDetailedErrorMessage();
424: if (details == null) {
425: if (info.getErrorException() != null) {
426: StringWriter sw = new StringWriter();
427: PrintWriter pw = new PrintWriter(sw);
428: info.getErrorException().printStackTrace(pw);
429: details = sw.toString();
430: } else {
431: details = "";
432: }
433: }
434: dlg.setDetails(details);
435: dlg.setIncidentInfo(info);
436: dlg.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
437: dlg.pack();
438: dlg.setLocationRelativeTo(owner);
439: dlg.setVisible(true);
440: }
441:
442: /**
443: * Returns the current reporting engine that will be used to report a
444: * problem if user clicks on 'Report' button or <code>null</code> if no
445: * reporting engine set.
446: *
447: * @return reporting engine
448: */
449: public static ErrorReporter getReporter() {
450: return reporter;
451: }
452:
453: /**
454: * Set reporting engine which will handle error reporting if user clicks
455: * 'report' button.
456: *
457: * @param rep
458: * <code>ErrorReporter</code> to be used or <code>null</code>
459: * to turn reporting facility off.
460: */
461: public static void setReporter(ErrorReporter rep) {
462: reporter = rep;
463: }
464:
465: /**
466: * Action for report button
467: */
468: public class ReportAction extends AbstractAction {
469:
470: /**
471: *
472: */
473: private static final long serialVersionUID = -2846780396236072439L;
474:
475: public boolean isEnabled() {
476: return (getReporter() != null);
477: }
478:
479: public void actionPerformed(ActionEvent e) {
480: getReporter().reportIncident(getIncidentInfo());
481: }
482:
483: public Object getValue(String key) {
484: if (key.equals(Action.NAME)) {
485: if (getReporter() != null
486: && getReporter().getActionName() != null) {
487: return getReporter().getActionName();
488: } else {
489: return REPORT_BUTTON_TEXT;
490: }
491: }
492: return super.getValue(key);
493: }
494: }
495: }
|