001/* Singleton class for displaying exceptions, errors, warnings, and messages.
002
003 Copyright (c) 1999-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026 */
027package ptolemy.gui;
028
029import java.awt.EventQueue;
030import java.awt.HeadlessException;
031
032import javax.swing.JOptionPane;
033import javax.swing.SwingUtilities;
034
035import ptolemy.util.CancelException;
036import ptolemy.util.MessageHandler;
037import ptolemy.util.StringUtilities;
038
039///////////////////////////////////////////////////////////////////
040//// GraphicalMessageHandler
041
042/**
043 This is a message handler that reports errors in a graphical dialog box.
044 When an applet or application starts up, it should call setContext()
045 to specify a component with respect to which the display window
046 should be created.  This ensures that if the application is iconified
047 or deiconified, that the display window goes with it. If the context
048 is not specified, then the display window is centered on the screen,
049 but iconifying and deiconifying may not work as desired.
050 <p>
051 Unlike the base class, this class defers messages to be invoked in the
052 Swing thread if they are not already called from within the Swing thread.
053
054 <p>Note that to display a window with an error message, this graphical
055 handler must be registered by calling
056 {@link ptolemy.util.MessageHandler#setMessageHandler(MessageHandler)}.
057 For example:
058 <pre>
059 GraphicalMessageHandler handler = new GraphicalMessageHandler();
060 GraphicalMessageHandler.setMessageHandler(handler);
061 GraphicalMessageHandler.error("My error", new Exception("My Exception"));
062 </pre>
063 If setMessageHandler() is not called, then the error() call will
064 use the default handler and possibly display the message on standard error.
065
066 <p>This class is based on (and contains code from) the diva GUIUtilities
067 class.
068
069 @author  Edward A. Lee, Steve Neuendorffer, John Reekie, and Elaine Cheong
070 @version $Id$
071 @since Ptolemy II 1.0
072 @Pt.ProposedRating Yellow (eal)
073 @Pt.AcceptedRating Red (reviewmoderator)
074 */
075public class GraphicalMessageHandler extends UndeferredGraphicalMessageHandler {
076
077    ///////////////////////////////////////////////////////////////////
078    ////                         protected methods                 ////
079
080    /** Show the specified error message.
081     *  This is deferred to execute in the swing event thread if it is
082     *  called outside that thread.
083     *  @param info The message.
084     */
085    @Override
086    protected void _error(final String info) {
087        Runnable doMessage = new Runnable() {
088            @Override
089            public void run() {
090                GraphicalMessageHandler.super._error(info);
091            }
092        };
093
094        Top.deferIfNecessary(doMessage);
095    }
096
097    /** Show the specified message and throwable information.
098     *  If the throwable is an instance of CancelException, then it
099     *  is not shown.  By default, only the message of the throwable
100     *  is thrown.  The stack trace information is only shown if the
101     *  user clicks on the "Display Stack Trace" button.
102     *  This is deferred to execute in the swing event thread if it is
103     *  called outside that thread.
104     *
105     *  @param info The message.
106     *  @param throwable The throwable.
107     */
108    @Override
109    protected void _error(final String info, final Throwable throwable) {
110        Runnable doMessage = new Runnable() {
111            @Override
112            public void run() {
113                GraphicalMessageHandler.super._error(info, throwable);
114            }
115        };
116
117        try {
118            Top.deferIfNecessary(doMessage);
119        } catch (HeadlessException headless) {
120            System.err.println(
121                    "HeadlessException: " + info + "Original Throwable was:\n");
122            throwable.printStackTrace();
123            System.err.println("\n\nHeadlessException was:\n");
124            headless.printStackTrace();
125        }
126    }
127
128    /** Show the specified message in a modal dialog.
129     *  This is deferred to execute in the swing event thread if it is
130     *  called outside that thread.
131     *  @param info The message.
132     */
133    @Override
134    protected void _message(final String info) {
135        Runnable doMessage = new Runnable() {
136            @Override
137            public void run() {
138                GraphicalMessageHandler.super._message(info);
139            }
140        };
141
142        Top.deferIfNecessary(doMessage);
143    }
144
145    /** Show the specified message in a modal dialog.  If the user
146     *  clicks on the "Cancel" button, then throw an exception.
147     *  This gives the user the option of not continuing the
148     *  execution, something that is particularly useful if continuing
149     *  execution will result in repeated warnings.
150     *  NOTE: If this is called outside the swing event thread, then
151     *  no cancel button is presented and no CancelException will be
152     *  thrown.  This is because the displaying of the message must
153     *  be deferred to the swing event thread, according to the swing
154     *  architecture, or we could get deadlock or rendering problems.
155     *  @param info The message.
156     *  @exception ptolemy.util.CancelException If the user clicks on the
157     * "Cancel" button.
158     */
159    @Override
160    protected void _warning(final String info) throws CancelException {
161        if (isNonInteractive()) {
162            System.out.println("Running nightly build or in batch mode.  "
163                    + "A warning dialog would have been displayed, but instead we are printing:\n"
164                    + info);
165            return;
166        }
167
168        // In swing, updates to showing graphics must be done in the
169        // event thread.  If we are in the event thread, then proceed.
170        // Otherwise, defer.
171        if (EventQueue.isDispatchThread()) {
172            super._warning(info);
173        } else {
174            Runnable doWarning = new Runnable() {
175                @Override
176                public void run() {
177                    Object[] options = { "OK" };
178                    Object[] message = new Object[1];
179
180                    // If the message lines are longer than 80 characters, we split it
181                    // into shorter new line separated strings.
182                    // Running vergil on a HSIF .xml file will create a line longer
183                    // than 80 characters
184                    message[0] = StringUtilities.ellipsis(info,
185                            StringUtilities.ELLIPSIS_LENGTH_LONG);
186
187                    // Show the MODAL dialog
188                    /*int selected =*/JOptionPane.showOptionDialog(getContext(),
189                            message, "Warning", JOptionPane.YES_NO_OPTION,
190                            JOptionPane.WARNING_MESSAGE, null, options,
191                            options[0]);
192                }
193            };
194
195            Top.deferIfNecessary(doWarning);
196        }
197    }
198
199    /** Show the specified message and throwable information
200     *  in a modal dialog.  If the user
201     *  clicks on the "Cancel" button, then throw an exception.
202     *  This gives the user the option of not continuing the
203     *  execution, something that is particularly useful if continuing
204     *  execution will result in repeated warnings.
205     *  By default, only the message of the throwable
206     *  is shown.  The stack trace information is only shown if the
207     *  user clicks on the "Display Stack Trace" button.
208     *  NOTE: If this is called outside the swing event thread, then
209     *  no cancel button is presented and no CancelException will be
210     *  thrown.  This is because the displaying of the message must
211     *  be deferred to the swing event thread, according to the swing
212     *  architecture, or we could get deadlock or rendering problems.
213     *  @param info The message.
214     *  @param throwable The throwable.
215     *  @exception ptolemy.util.CancelException If the user clicks on the
216     *  "Cancel" button.
217     */
218    @Override
219    protected void _warning(final String info, final Throwable throwable)
220            throws CancelException {
221        if (isNonInteractive()) {
222            System.out.println("Running nightly build or in batch mode.  "
223                    + "A warning dialog would have been displayed, but instead we are printing:\n"
224                    + info + ": " + throwable.getMessage() + " " + throwable);
225            return;
226        }
227        // In swing, updates to showing graphics must be done in the
228        // event thread.  If we are in the event thread, then proceed.
229        // Otherwise, defer.
230        if (EventQueue.isDispatchThread()) {
231            super._warning(info, throwable);
232        } else {
233            Runnable doWarning = new Runnable() {
234                @Override
235                public void run() {
236                    Object[] message = new Object[1];
237                    message[0] = StringUtilities.ellipsis(info,
238                            StringUtilities.ELLIPSIS_LENGTH_LONG);
239
240                    Object[] options = { "OK", "Display Stack Trace" };
241
242                    // Show the MODAL dialog
243                    int selected = JOptionPane.showOptionDialog(getContext(),
244                            message, "Warning", JOptionPane.YES_NO_OPTION,
245                            JOptionPane.WARNING_MESSAGE, null, options,
246                            options[0]);
247
248                    if (selected == 1) {
249                        _showStackTrace(throwable, info);
250                    } else if (selected == 2) {
251                        // Derived classes
252                        _showNameable(throwable);
253                    }
254                }
255            };
256
257            Top.deferIfNecessary(doWarning);
258        }
259    }
260
261    /** Ask the user a yes/no question, and return true if the answer
262     *  is yes.
263     *
264     *  @param question The yes/no question.
265     *  @return True if the answer is yes.
266     */
267    @Override
268    protected boolean _yesNoQuestion(final String question) {
269        // In swing, updates to showing graphics must be done in the
270        // event thread.  If we are in the event thread, then proceed.
271        // Otherwise, invoke and wait.
272        if (EventQueue.isDispatchThread()) {
273            return super._yesNoQuestion(question);
274        } else {
275            // Place to store results from doYesNoCancel thread.
276            // results[0] is the return value ("Yes" or "No").
277            final Boolean[] result = new Boolean[1];
278
279            Runnable doYesNo = new Runnable() {
280                @Override
281                public void run() {
282                    Object[] message = new Object[1];
283                    message[0] = StringUtilities.ellipsis(question,
284                            StringUtilities.ELLIPSIS_LENGTH_LONG);
285
286                    Object[] options = { "Yes", "No" };
287
288                    // Show the MODAL dialog
289                    int selected = JOptionPane.showOptionDialog(getContext(),
290                            message, "Warning", JOptionPane.YES_NO_OPTION,
291                            JOptionPane.WARNING_MESSAGE, null, options,
292                            options[0]);
293
294                    if (selected == 0) {
295                        result[0] = Boolean.TRUE;
296                    } else {
297                        result[0] = Boolean.FALSE;
298                    }
299                }
300            };
301
302            try {
303                // Note: usually we use invokeLater() (see
304                // Top.deferIfNecessary()).  However, here, we need
305                // the return value.
306                SwingUtilities.invokeAndWait(doYesNo);
307            } catch (Exception ex) {
308                // do nothing.
309            }
310
311            return result[0].booleanValue();
312        }
313    }
314
315    /** Ask the user a question with three possible answers;
316     *  return true if the answer is the first one and false if
317     *  the answer is the second one; throw an exception if the
318     *  user selects the third one. The default (selected by return
319     *  and escape) is the third (the cancel option).
320     *  @param question The question.
321     *  @param trueOption The option for which to return true.
322     *  @param falseOption The option for which to return false.
323     *  @param exceptionOption The option for which to throw an exception.
324     *  @return True if the answer is the first option, false if it is the second.
325     *  @exception ptolemy.util.CancelException If the user selects the third option.
326     */
327    @Override
328    protected boolean _yesNoCancelQuestion(final String question,
329            final String trueOption, final String falseOption,
330            final String exceptionOption) throws ptolemy.util.CancelException {
331        // In swing, updates to showing graphics must be done in the
332        // event thread.  If we are in the event thread, then proceed.
333        // Otherwise, invoke and wait.
334        if (EventQueue.isDispatchThread()) {
335            return super._yesNoCancelQuestion(question, trueOption, falseOption,
336                    exceptionOption);
337        } else {
338            // Place to store results from doYesNoCancel thread.
339            // results[0] is the return value ("Yes" or "No").
340            // results[1] is the error value ("Cancel").
341            final Boolean[] results = new Boolean[2];
342
343            Runnable doYesNoCancel = new Runnable() {
344                @Override
345                public void run() {
346                    Object[] message = new Object[1];
347                    message[0] = StringUtilities.ellipsis(question,
348                            StringUtilities.ELLIPSIS_LENGTH_LONG);
349
350                    Object[] options = { trueOption, falseOption,
351                            exceptionOption };
352
353                    // Show the MODAL dialog
354                    int selected = JOptionPane.showOptionDialog(getContext(),
355                            message, "Warning",
356                            JOptionPane.YES_NO_CANCEL_OPTION,
357                            JOptionPane.WARNING_MESSAGE, null, options,
358                            options[0]);
359
360                    if (selected == 0) {
361                        results[0] = Boolean.TRUE;
362                    } else if (selected == 1) {
363                        results[0] = Boolean.FALSE;
364                    } else {
365                        results[1] = Boolean.TRUE;
366                    }
367                }
368            };
369
370            try {
371                // Note: usually we use invokeLater() (see
372                // Top.deferIfNecessary()).  However, here, we need
373                // the return value.
374                SwingUtilities.invokeAndWait(doYesNoCancel);
375            } catch (Exception ex) {
376                // do nothing.
377                System.out.println(
378                        "Internal warning:? GraphicalMessageHandler modal threw an exception: "
379                                + ex);
380            }
381
382            if (results[1] != null && results[1].booleanValue()) {
383                throw new ptolemy.util.CancelException();
384            }
385
386            return results[0].booleanValue();
387        }
388    }
389}