001    /*
002     * $Id: JXErrorDialog.java,v 1.22 2006/03/18 22:52:10 rbair Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
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     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    
024    import java.awt.Component;
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.datatransfer.StringSelection;
033    import java.awt.datatransfer.Transferable;
034    import java.awt.event.ActionEvent;
035    import java.awt.event.ActionListener;
036    import java.util.Enumeration;
037    import java.util.ResourceBundle;
038    import java.util.logging.Level;
039    
040    import javax.swing.AbstractAction;
041    import javax.swing.Action;
042    import javax.swing.Icon;
043    import javax.swing.JButton;
044    import javax.swing.JComponent;
045    import javax.swing.JDialog;
046    import javax.swing.JEditorPane;
047    import javax.swing.JLabel;
048    import javax.swing.JScrollPane;
049    import javax.swing.TransferHandler;
050    import javax.swing.UIManager;
051    import javax.swing.plaf.basic.BasicHTML;
052    import org.jdesktop.swingx.util.WindowUtils;
053    
054    /**
055     * <p>Common Error Dialog, suitable for representing information about
056     * errors and exceptions happened in application. The common usage of the
057     * <code>JXErrorDialog</code> is to show collected data about the incident and
058     * probably ask customer for a feedback. The data about the incident consists
059     * from the title which will be displayed in the dialog header, short
060     * description of the problem that will be immediately seen after dialog is
061     * became visible, full description of the problem which will be visible after
062     * user clicks "Details" button and Throwable that contains stack trace and
063     * another usable information that may be displayed in the dialog.</p>
064     *
065     * <p>There are two basic ways to use the JXErrorDialog. The first is to call one
066     * of the standard static methods for displaying the error dialog. Here is a
067     * basic example:<br/>
068     * <pre><code>
069     *      try {
070     *          //do some work
071     *          //some exception is thrown
072     *      } catch (Exception e) {
073     *          JXErrorDialog.showDialog(this, "Critical Error",
074     *                  "&lt;html&gt;&lt;body&gt;Some &lt;b&gt;critical error&lt;/b&gt; has occured." +
075     *                  " You may need to restart this application. Message #SC21." +
076     *                  "&lt;/body&gt;&lt;/html&gt;", e);
077     *      }
078     * </code></pre></p>
079     *
080     * <p>For the vast majority of use cases, the static methods should be sufficient and
081     * are recommended. However, for those few use cases requireing customizations, you
082     * can create and display a JXErrorDialog manually.</p>
083     *
084     * <p>To ask user for feedback extend abstract class <code>ErrorReporter</code> and
085     * set your reporter using <code>setReporter</code> method. Report button will
086     * be added to the dialog automatically.<br>
087     * See {@link MailErrorReporter MailErrorReporter} documentation for the
088     * example of error reporting usage.</p>
089     *
090     * <p>For example, to show simple <code>JXErrorDialog</code> call <br>
091     * <code>JXErrorDialog.showDialog(null, "Application Error",
092     *   "The application encountered the unexpected error,
093     *    please contact developers")</code></p>
094     *
095     * <p>Internationalization is handled via a resource bundle or via the UIManager
096     * bidi orientation (usefull for right to left languages) is determined in the
097     * same way as the JOptionPane where the orientation of the parent component is
098     * picked. So when showDialog(Component cmp, ...) is invoked the component
099     * orientation of the error dialog will match the component orientation of cmp.</p>
100     *
101     * @author Richard Bair
102     * @author Alexander Zuev
103     * @author Shai Almog
104     */
105    public class JXErrorDialog extends JDialog {
106        //---------------------------------------------------- static properties
107        /**
108         * Used as a prefix when pulling data out of UIManager for i18n
109         */
110        private static String CLASS_NAME;
111        /**
112         * Icon for the error dialog (stop sign, etc)
113         */
114        private static Icon DEFAULT_ERROR_ICON = UIManager.getIcon("OptionPane.errorIcon");
115        /**
116         * Icon for the error dialog (stop sign, etc)
117         */
118        private static Icon DEFAULT_WARNING_ICON = UIManager.getIcon("OptionPane.warningIcon");
119        /**
120         * Error reporting engine assigned for error reporting for all error dialogs
121         */
122        private static ErrorReporter DEFAULT_REPORTER;
123        
124        //-------------------------------------------------- instance properties
125        
126        /**
127         * Error message text area
128         */
129        private JEditorPane errorMessage;
130        /**
131         * details text area
132         */
133        private JXEditorPane details;
134        /**
135         * detail button
136         */
137        private EqualSizeJButton detailButton;
138        /**
139         * details panel
140         */
141        private JXPanel detailsPanel;
142        /**
143         * label used to display the warning/error icon
144         */
145        private JLabel iconLabel;
146        /**
147         * report an error button
148         */
149        private EqualSizeJButton reportButton;
150        /**
151         * IncidentInfo that contains all the information prepared for
152         * reporting.
153         */
154        private IncidentInfo incidentInfo;
155        /**
156         * The Action that will be executed to report an error/warning. If null,
157         * then a default ReportAction will be used.
158         */
159        private Action reportAction;
160        /**
161         * The ErrorReporter to use. It defaults to the global default reporter
162         * specified via the static methods, but may be overridden
163         */
164        private ErrorReporter reporter = DEFAULT_REPORTER;
165        /**
166         * The Icon to use if the error message is indeed an Error, as specified
167         * by IncidentInfo.getErrorLevel == Level.SEVERE
168         */
169        private Icon errorIcon = DEFAULT_ERROR_ICON;
170        /**
171         * The Icon to use if the error message is not an Error
172         * (IncidentInfo.getErrorLevel != Level.SEVERE)
173         */
174        private Icon warningIcon = DEFAULT_WARNING_ICON;
175        
176        //------------------------------------------------------ private helpers
177        /**
178         * The height of the window when collapsed. This value is stashed when the
179         * dialog is expanded
180         */
181        private int collapsedHeight = 0;
182        /**
183         * The height of the window when last expanded. This value is stashed when
184         * the dialog is collapsed
185         */
186        private int expandedHeight = 0;
187        
188        //------------------------------------------------- static configuration
189        
190        /**
191         * Creates initialize the UIManager with localized strings
192         */
193        static {
194            // Popuplate UIDefaults with the localizable Strings we will use
195            // in the Login panel.
196            CLASS_NAME = JXErrorDialog.class.getCanonicalName();
197            String lookup;
198            ResourceBundle res = ResourceBundle.getBundle("org.jdesktop.swingx.plaf.resources.ErrorDialog");
199            Enumeration<String> keys = res.getKeys();
200            while (keys.hasMoreElements()) {
201                String key = keys.nextElement();
202                lookup = CLASS_NAME + "." + key;
203                if (UIManager.getString(lookup) == null) {
204                    UIManager.put(lookup, res.getString(key));
205                }
206            }
207        }
208        
209        //--------------------------------------------------------- constructors
210        
211        /**
212         * Create a new ErrorDialog with the given Frame as the owner
213         * @param owner Owner of this error dialog.
214         */
215        public JXErrorDialog(Frame owner) {
216            super(owner, true);
217            initGui();
218        }
219        
220        /**
221         * Create a new ErrorDialog with the given Dialog as the owner
222         * @param owner Owner of this error dialog.
223         */
224        public JXErrorDialog(Dialog owner) {
225            super(owner, true);
226            initGui();
227        }
228        
229        //-------------------------------------------- public methods/properties
230        
231        /**
232         * Sets the IncidentInfo for this dialog
233         *
234         * @param info IncidentInfo that incorporates all the details about the error
235         */
236        public void setIncidentInfo(IncidentInfo info) {
237            IncidentInfo old = this.incidentInfo;
238            this.incidentInfo = info;
239            firePropertyChange("incidentInfo", old, this.incidentInfo);
240            reinit();
241        }
242        
243        /**
244         * Get curent dialog's IncidentInfo
245         *
246         * @return <code>IncidentInfo</code> assigned to this dialog
247         */
248        public IncidentInfo getIncidentInfo() {
249            return incidentInfo;
250        }
251        
252        /**
253         * Sets the ErrorReporter to use with this instance of JXErrorDialog.
254         * If not specified, the default error reporter is used (as specified
255         * by the setDefaultErrorReporter() static method).
256         *
257         * @param rep if null, the default error reporter is used
258         */
259        public void setErrorReporter(ErrorReporter rep) {
260            ErrorReporter old = this.reporter;
261            this.reporter = rep == null ? DEFAULT_REPORTER : rep;
262            firePropertyChange("errorReporter", old, this.reporter);
263            reinit();
264        }
265        
266        /**
267         * Returns the error reporter in use with this instance of JXErrorDialog.
268         * If not specified, the default error reporter is returned
269         *
270         * @return the ErrorReporter in use. May be null
271         */
272        public ErrorReporter getErrorReporter() {
273            return reporter;
274        }
275        
276        /**
277         * Specifies the icon to use if the IncidentInfo is Level.SEVERE
278         *
279         * @param icon the Icon to use. If null, the default error icon will be used
280         */
281        public void setErrorIcon(Icon icon) {
282            Icon old = this.errorIcon;
283            this.errorIcon = icon == null ? DEFAULT_ERROR_ICON : icon;
284            firePropertyChange("errorIcon", old, this.errorIcon);
285            reinit();
286        }
287        
288        /**
289         * Returns the Icon in use if the IncidentInfo is Level.SEVERE
290         *
291         * @return the Icon
292         */
293        public Icon getErrorIcon() {
294            return errorIcon;
295        }
296        
297        /**
298         * Specifies the icon to use if the IncidentInfo is not Level.SEVERE
299         *
300         * @param icon the Icon to use. If null, the default warning icon will be used
301         */
302        public void setWarningIcon(Icon icon) {
303            Icon old = this.warningIcon;
304            this.warningIcon = icon == null ? DEFAULT_WARNING_ICON : icon;
305            firePropertyChange("warningIcon", old, this.warningIcon);
306            reinit();
307        }
308        
309        /**
310         * Returns the Icon in use if the IncidentInfo is not Level.SEVERE
311         *
312         * @return the Icon
313         */
314        public Icon getWarningIcon() {
315            return warningIcon;
316        }
317        
318        /**
319         * Specify the Action that will be executed to report an error/warning. If null,
320         * then a default ReportAction will be used.
321         *
322         * @param action The Action to execute if the user attempts to report a problem
323         */
324        public void setReportAction(Action action) {
325            Action old = this.reportAction;
326            this.reportAction = action == null ? new ReportAction() : action;
327            firePropertyChange("reportAction", old, this.reportAction);
328            reportButton.setAction(this.reportAction);
329        }
330        
331        /**
332         * @return the Action that is executed if the user attempts to report a problem
333         */
334        public Action getReportAction() {
335            return reportAction;
336        }
337        
338        //----------------------------------------------- private helper methods
339        /**
340         * initialize the gui.
341         */
342        private void initGui() {
343            //initialize the gui
344            GridBagLayout layout = new GridBagLayout();
345            this.getContentPane().setLayout(layout);
346            
347            GridBagConstraints gbc = new GridBagConstraints();
348            gbc.anchor = GridBagConstraints.NORTH;
349            gbc.fill = GridBagConstraints.NONE;
350            gbc.gridheight = 1;
351            gbc.insets = new Insets(22, 12, 11, 17);
352            iconLabel = new JLabel(DEFAULT_ERROR_ICON);
353            this.getContentPane().add(iconLabel, gbc);
354            
355            errorMessage = new JEditorPane();
356            errorMessage.setEditable( false );
357            errorMessage.setContentType("text/html");
358            errorMessage.setOpaque( false );
359            errorMessage.putClientProperty(JXEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
360            gbc = new GridBagConstraints();
361            gbc.anchor = GridBagConstraints.LINE_START;
362            gbc.fill = GridBagConstraints.BOTH;
363            gbc.gridheight = 1;
364            gbc.gridwidth = 3;
365            gbc.gridx = 1;
366            gbc.weightx = 0.0;
367            gbc.weighty = 0.00001; //ensures that when details is hidden, it get all
368            //the extra space, but none when details is shown
369            //(unless you have a REALLY BIG MONITOR
370            gbc.insets = new Insets(24, 0, 0, 11);
371            this.getContentPane().add(errorMessage, gbc);
372            
373            gbc = new GridBagConstraints();
374            gbc.fill = GridBagConstraints.NONE;
375            gbc.gridx = 1;
376            gbc.gridy = 1;
377            gbc.gridwidth = 1;
378            gbc.weightx = 1.0;
379            gbc.weighty = 0.0;
380            gbc.anchor = GridBagConstraints.LINE_END;
381            gbc.insets = new Insets(12, 0, 11, 5);
382            EqualSizeJButton okButton = new EqualSizeJButton(UIManager.getString(CLASS_NAME + ".ok_button_text"));
383            this.getContentPane().add(okButton, gbc);
384            
385            reportAction = new ReportAction();
386            reportButton = new EqualSizeJButton(reportAction);
387            gbc = new GridBagConstraints();
388            gbc.gridx = 2;
389            gbc.weightx = 0.0;
390            gbc.insets = new Insets(12, 0, 11, 5);
391            this.getContentPane().add(reportButton, gbc);
392            reportButton.setVisible(false); // not visible by default
393            
394            detailButton = new EqualSizeJButton(UIManager.getString(CLASS_NAME + ".details_expand_text"));
395            gbc = new GridBagConstraints();
396            gbc.gridx = 3;
397            gbc.weightx = 0.0;
398            gbc.insets = new Insets(12, 0, 11, 11);
399            this.getContentPane().add(detailButton, gbc);
400            
401            details = new JXEditorPane();
402            details.setContentType("text/html");
403            details.putClientProperty(JXEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
404            details.setTransferHandler(new DetailsTransferHandler());
405            JScrollPane detailsScrollPane = new JScrollPane(details);
406            detailsScrollPane.setPreferredSize(new Dimension(10, 250));
407            details.setEditable(false);
408            detailsPanel = new JXPanel(new GridBagLayout());
409            detailsPanel.add(detailsScrollPane, new GridBagConstraints(0,0,1,1,1.0,1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(6,11,11,11),0,0));
410            gbc = new GridBagConstraints();
411            gbc.fill = GridBagConstraints.BOTH;
412            gbc.gridwidth = 4;
413            gbc.gridx = 0;
414            gbc.gridy = 2;
415            gbc.weighty = 1.0;
416            this.getContentPane().add(detailsPanel, gbc);
417            
418            JButton button = new JButton(UIManager.getString(CLASS_NAME + ".copy_to_clipboard_button_text"));
419            button.addActionListener(new ActionListener() {
420                public void actionPerformed(ActionEvent ae) {
421                    details.copy();
422                }
423            });
424            gbc = new GridBagConstraints();
425            gbc.anchor = GridBagConstraints.LINE_END;
426            gbc.fill = GridBagConstraints.NONE;
427            gbc.gridwidth = 1;
428            gbc.gridx = 0;
429            gbc.gridy = 1;
430            gbc.weighty = 0.0;
431            gbc.weightx = 1.0;
432            gbc.insets = new Insets(6, 11, 11, 11);
433            detailsPanel.add(button, gbc);
434            
435            
436            //make the buttons the same size
437            EqualSizeJButton[] buttons = new EqualSizeJButton[] {
438                detailButton, okButton, reportButton };
439            okButton.setGroup(buttons);
440            reportButton.setGroup(buttons);
441            detailButton.setGroup(buttons);
442            
443            okButton.setMinimumSize(okButton.getPreferredSize());
444            reportButton.setMinimumSize(reportButton.getPreferredSize());
445            detailButton.setMinimumSize(detailButton.getPreferredSize());
446            
447            //set the event handling
448            okButton.addActionListener(new OkClickEvent());
449            detailButton.addActionListener(new DetailsClickEvent());
450        }
451        
452        /**
453         * Set the details section of the error dialog.  If the details are either
454         * null or an empty string, then hide the details button and hide the detail
455         * scroll pane.  Otherwise, just set the details section.
456         * @param details  Details to be shown in the detail section of the dialog.
457         * This can be null if you do not want to display the details section of the
458         * dialog.
459         */
460        private void setDetails(String details) {
461            if (details == null || details.equals("")) {
462                setDetailsVisible(false);
463                detailButton.setVisible(false);
464            } else {
465                this.details.setText(details);
466                setDetailsVisible(false);
467                detailButton.setVisible(true);
468            }
469        }
470        
471        /**
472         * Set the details section to be either visible or invisible.  Set the
473         * text of the Details button accordingly.
474         * @param b if true details section will be visible
475         */
476        private void setDetailsVisible(boolean b) {
477            if (b) {
478                collapsedHeight = getHeight();
479                setSize(getWidth(), expandedHeight == 0 ? collapsedHeight + 300 : expandedHeight);
480                detailsPanel.setVisible(true);
481                detailButton.setText(UIManager.getString(CLASS_NAME + ".details_contract_text"));
482                detailsPanel.applyComponentOrientation(detailButton.getComponentOrientation());
483                
484                // workaround for bidi bug, if the text is not set "again" and the component orientation has changed
485                // then the text won't be aligned correctly. To reproduce this (in JDK 1.5) show two dialogs in one
486                // use LTOR orientation and in the second use RTOL orientation and press "details" in both.
487                // Text in the text box should be aligned to right/left respectively, without this line this doesn't
488                // occure I assume because bidi properties are tested when the text is set and are not updated later
489                // on when setComponentOrientation is invoked.
490                details.setText(details.getText());
491                details.setCaretPosition(0);
492            } else {
493                expandedHeight = getHeight();
494                detailsPanel.setVisible(false);
495                detailButton.setText(UIManager.getString(CLASS_NAME + ".details_expand_text"));
496                // Trick to force errorMessage JTextArea to resize according
497                // to its columns property.
498                errorMessage.setSize( 0, 0 );
499                errorMessage.setSize( errorMessage.getPreferredSize() );
500                setSize(getWidth(), collapsedHeight);
501            }
502            
503            repaint();
504        }
505        
506        /**
507         * Set the error message for the dialog box
508         * @param errorMessage Message for the error dialog
509         */
510        private void setErrorMessage(String errorMessage) {
511            if(BasicHTML.isHTMLString(errorMessage)) {
512                this.errorMessage.setContentType("text/html");
513            } else {
514                this.errorMessage.setContentType("text/plain");
515            }
516            this.errorMessage.setText(errorMessage);
517        }
518        
519        /**
520         * Reconfigures the dialog if settings have changed, such as the
521         * IncidentInfo, errorIcon, warningIcon, etc
522         */
523        private void reinit() {
524            reportButton.setVisible(getErrorReporter() != null);
525            if (incidentInfo == null) {
526                iconLabel.setIcon(DEFAULT_ERROR_ICON);
527                setTitle("");
528                setErrorMessage("");
529                setDetails("");
530            } else {
531                iconLabel.setIcon(incidentInfo.getErrorLevel() == Level.SEVERE ?
532                    errorIcon : warningIcon);
533                setTitle(incidentInfo.getHeader());
534                setErrorMessage(incidentInfo.getBasicErrorMessage());
535                String details = incidentInfo.getDetailedErrorMessage();
536                if(details == null) {
537                    if(incidentInfo.getErrorException() != null) {
538                        //convert the stacktrace into a more pleasent bit of HTML
539                        StringBuffer html = new StringBuffer("<html>");
540                        html.append("<h2>" + incidentInfo.getHeader() + "</h2>");
541                        html.append("<HR size='1' noshade>");
542                        html.append("<div></div>");
543                        html.append("<b>Message:</b>");
544                        html.append("<pre>");
545                        html.append("    " + incidentInfo.getErrorException().toString());
546                        html.append("</pre>");
547                        html.append("<b>Level:</b>");
548                        html.append("<pre>");
549                        html.append("    " + incidentInfo.getErrorLevel());
550                        html.append("</pre>");
551                        html.append("<b>Stack Trace:</b>");
552                        html.append("<pre>");
553                        for (StackTraceElement el : incidentInfo.getErrorException().getStackTrace()) {
554                            html.append("    " + el.toString() + "\n");
555                        }
556                        html.append("</pre></html>");
557                        details = html.toString();
558                    } else {
559                        details = "";
560                    }
561                }
562                setDetails(details);
563            }
564            
565            //set the preferred width of the message area
566            //the preferred width should not exceed 500 pixels, or be less than 300 pixels
567            Dimension prefSize = errorMessage.getPreferredSize();
568            prefSize.width = Math.min(500, prefSize.width);
569            prefSize.width = Math.max(300, prefSize.width);
570            errorMessage.setSize(prefSize);
571            prefSize.height = errorMessage.getPreferredSize().height;
572            errorMessage.setPreferredSize(prefSize);
573        }
574        
575        //------------------------------------------------------- static methods
576        
577        /**
578         * Constructs and shows the error dialog for the given exception.  The exceptions message will be the
579         * errorMessage, and the stacktrace will be the details.
580         * @param owner Owner of this error dialog. Determines the Window in which the dialog
581         *      is displayed; if the <code>owner</code> has
582         *      no <code>Window</code>, a default <code>Frame</code> is used
583         * @param title Title of the error dialog
584         * @param e Exception that contains information about the error cause and stack trace
585         */
586        public static void showDialog(Component owner, String title, Throwable e) {
587            IncidentInfo ii = new IncidentInfo(title, null, null, e);
588            showDialog(owner, ii);
589        }
590        
591        /**
592         * Constructs and shows the error dialog for the given exception.  The exceptions message is specified,
593         * and the stacktrace will be the details.
594         * @param owner Owner of this error dialog. Determines the Window in which the dialog
595         *      is displayed; if the <code>owner</code> has
596         *      no <code>Window</code>, a default <code>Frame</code> is used
597         * @param title Title of the error dialog
598         * @param errorMessage Message for the error dialog
599         * @param e Exception that contains information about the error cause and stack trace
600         */
601        public static void showDialog(Component owner, String title, String errorMessage, Throwable e) {
602            IncidentInfo ii = new IncidentInfo(title, errorMessage, null, e);
603            showDialog(owner, ii);
604        }
605        
606        /**
607         * Show the error dialog.
608         * @param owner Owner of this error dialog. Determines the Window in which the dialog
609         *      is displayed; if the <code>owner</code> has
610         *      no <code>Window</code>, a default <code>Frame</code> is used
611         * @param title Title of the error dialog
612         * @param errorMessage Message for the error dialog
613         * @param details Details to be shown in the detail section of the dialog.  This can be null
614         * if you do not want to display the details section of the dialog.
615         */
616        public static void showDialog(Component owner, String title, String errorMessage, String details) {
617            IncidentInfo ii = new IncidentInfo(title, errorMessage, details);
618            showDialog(owner, ii);
619        }
620        
621        /**
622         * Show the error dialog.
623         * @param owner Owner of this error dialog. Determines the Window in which the dialog
624         *      is displayed; if the <code>owner</code> has
625         *      no <code>Window</code>, a default <code>Frame</code> is used
626         * @param title Title of the error dialog
627         * @param errorMessage Message for the error dialog
628         */
629        public static void showDialog(Component owner, String title, String errorMessage) {
630            IncidentInfo ii = new IncidentInfo(title, errorMessage, (String)null);
631            showDialog(owner, ii);
632        }
633        
634        /**
635         * Show the error dialog.
636         * @param owner Owner of this error dialog. Determines the Window in which the dialog
637         *      is displayed; if the <code>owner</code> has
638         *      no <code>Window</code>, a default <code>Frame</code> is used
639         * @param info <code>IncidentInfo</code> that incorporates all the information about the error
640         */
641        public static void showDialog(Component owner, IncidentInfo info) {
642            JXErrorDialog dlg;
643            
644            Window window = WindowUtils.findWindow(owner);
645            
646            if (window instanceof Dialog) {
647                dlg = new JXErrorDialog((Dialog)window);
648            } else {
649                dlg = new JXErrorDialog((Frame)window);
650            }
651            dlg.setIncidentInfo(info);
652            // If the owner is null applies orientation of the shared
653            // hidden window used as owner.
654            if(owner != null)
655                dlg.applyComponentOrientation(owner.getComponentOrientation());
656            else
657                dlg.applyComponentOrientation(window.getComponentOrientation());
658            dlg.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
659            dlg.pack();
660            dlg.setLocationRelativeTo(owner);
661            dlg.setVisible(true);
662        }
663        
664        /**
665         * Returns the current reporting engine that will be used to report a problem if
666         * user clicks on 'Report' button or <code>null</code> if no reporting engine set.
667         *
668         * @return reporting engine
669         * @deprecated Use <code>getDefaultErrorReporter</code> instead
670         */
671        public static ErrorReporter getReporter() {
672            return DEFAULT_REPORTER;
673        }
674        
675        /**
676         * Set reporting engine which will handle error reporting if user clicks 'report' button.
677         *
678         * @param rep <code>ErrorReporter</code> to be used or <code>null</code> to turn reporting facility off.
679         * @deprecated Use <code>setDefaultErrorReporter</code> instead
680         */
681        public static void setReporter(ErrorReporter rep) {
682            DEFAULT_REPORTER = rep;
683        }
684        
685        /**
686         * Returns the current reporting engine that will be used to report a problem if
687         * user clicks on 'Report' button or <code>null</code> if no reporting engine set.
688         *
689         * @return reporting engine
690         */
691        public static ErrorReporter getDefaultErrorReporter() {
692            return DEFAULT_REPORTER;
693        }
694        
695        /**
696         * Set reporting engine which will handle error reporting if user clicks 'report' button.
697         *
698         * @param rep <code>ErrorReporter</code> to be used or <code>null</code> to turn reporting facility off.
699         */
700        public static void setDefaultErrorReporter(ErrorReporter rep) {
701            DEFAULT_REPORTER = rep;
702        }
703        
704        /**
705         * Set the Icon to use as the default error icon for JXErrorDialog
706         * instances. This icon is used whenever the IncidentInfo for a JXErrorDialog
707         * has an errorLevel of Level.SEVERE
708         *
709         * @param icon the Icon to use as the default error icon
710         */
711        public static void setDefaultErrorIcon(Icon icon) {
712            DEFAULT_ERROR_ICON = icon;
713        }
714        
715        /**
716         * @return the default error icon
717         */
718        public static Icon getDefaultErrorIcon() {
719            return DEFAULT_ERROR_ICON;
720        }
721        
722        /**
723         * Set the Icon to use as the default warning icon for JXErrorDialog
724         * instances. This icon is used whenever the IncidentInfo for a JXErrorDialog
725         * has an errorLevel that is not Level.SEVERE
726         *
727         * @param icon the Icon to use as the default warning icon
728         */
729        public static void setDefaultWarningIcon(Icon icon) {
730            DEFAULT_WARNING_ICON = icon;
731        }
732        
733        /**
734         * @return the default warning icon
735         */
736        public static Icon getDefaultWarningIcon() {
737            return DEFAULT_WARNING_ICON;
738        }
739        
740        //------------------------------------------------ actions/inner classes
741        
742        /**
743         * Listener for Ok button click events
744         * @author Richard Bair
745         */
746        private final class OkClickEvent implements ActionListener {
747            
748            /* (non-Javadoc)
749             * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
750             */
751            public void actionPerformed(ActionEvent e) {
752                //close the window
753                setVisible(false);
754                dispose();
755            }
756        }
757        
758        /**
759         * Listener for Details click events.  Alternates whether the details section
760         * is visible or not.
761         * @author Richard Bair
762         */
763        private final class DetailsClickEvent implements ActionListener {
764            
765            /* (non-Javadoc)
766             * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
767             */
768            public void actionPerformed(ActionEvent e) {
769                setDetailsVisible(!detailsPanel.isVisible());
770            }
771        }
772        
773        /**
774         * Action for report button
775         */
776        public class ReportAction extends AbstractAction {
777            
778            public boolean isEnabled() {
779                return (getErrorReporter() != null);
780            }
781            
782            public void actionPerformed(ActionEvent e) {
783                getErrorReporter().reportIncident(getIncidentInfo());
784            }
785            
786            public Object getValue(String key) {
787                if(key == Action.NAME) {
788                    if(getErrorReporter() != null && getErrorReporter().getActionName() != null) {
789                        return getErrorReporter().getActionName();
790                    } else {
791                        return UIManager.getString(CLASS_NAME + ".report_button_text");
792                    }
793                }
794                return super.getValue(key);
795            }
796        }
797        
798        /**
799         * This is a button that maintains the size of the largest button in the button
800         * group by returning the largest size from the getPreferredSize method.
801         * This is better than using setPreferredSize since this will work regardless
802         * of changes to the text of the button and its language.
803         */
804        private static class EqualSizeJButton extends JButton {
805            public EqualSizeJButton() {
806            }
807            
808            public EqualSizeJButton(String text) {
809                super(text);
810            }
811            
812            public EqualSizeJButton(Action a) {
813                super(a);
814            }
815            
816            /**
817             * Buttons whose size should be taken into consideration
818             */
819            private EqualSizeJButton[] group;
820            
821            public void setGroup(EqualSizeJButton[] group) {
822                this.group = group;
823            }
824            
825            /**
826             * Returns the actual preferred size on a different instance of this button
827             */
828            private Dimension getRealPreferredSize() {
829                return super.getPreferredSize();
830            }
831            
832            /**
833             * If the <code>preferredSize</code> has been set to a
834             * non-<code>null</code> value just returns it.
835             * If the UI delegate's <code>getPreferredSize</code>
836             * method returns a non <code>null</code> value then return that;
837             * otherwise defer to the component's layout manager.
838             *
839             * @return the value of the <code>preferredSize</code> property
840             * @see #setPreferredSize
841             * @see ComponentUI
842             */
843            public Dimension getPreferredSize() {
844                int width = 0;
845                int height = 0;
846                for(int iter = 0 ; iter < group.length ; iter++) {
847                    Dimension size = group[iter].getRealPreferredSize();
848                    width = Math.max(size.width, width);
849                    height = Math.max(size.height, height);
850                }
851                
852                return new Dimension(width, height);
853            }
854            
855        }
856        
857        /**
858         * Returns the text as non-HTML in a COPY operation, and disabled CUT/PASTE
859         * operations for the Details pane.
860         */
861        private final class DetailsTransferHandler extends TransferHandler {
862            protected Transferable createTransferable(JComponent c) {
863                String text = details.getSelectedText();
864                if (text == null || text.equals("")) {
865                    details.selectAll();
866                    text = details.getSelectedText();
867                    details.select(-1, -1);
868                }
869                return new StringSelection(text);
870            }
871            
872            public int getSourceActions(JComponent c) {
873                return TransferHandler.COPY;
874            }
875            
876        }
877    }