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}