001/* An action for getting documentation. 002 003 Copyright (c) 2006-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 027 */ 028package ptolemy.vergil.basic; 029 030import java.awt.event.ActionEvent; 031import java.net.URL; 032import java.util.Iterator; 033import java.util.List; 034 035import javax.swing.JOptionPane; 036 037import ptolemy.actor.gui.Configuration; 038import ptolemy.actor.gui.Effigy; 039import ptolemy.actor.gui.Tableau; 040import ptolemy.data.expr.Parameter; 041import ptolemy.kernel.ComponentEntity; 042import ptolemy.kernel.attributes.VersionAttribute; 043import ptolemy.kernel.util.InternalErrorException; 044import ptolemy.kernel.util.KernelException; 045import ptolemy.kernel.util.KernelRuntimeException; 046import ptolemy.kernel.util.NamedObj; 047import ptolemy.kernel.util.StringAttribute; 048import ptolemy.util.MessageHandler; 049import ptolemy.vergil.actor.DocApplicationSpecializer; 050import ptolemy.vergil.actor.DocBuilderEffigy; 051import ptolemy.vergil.actor.DocBuilderTableau; 052import ptolemy.vergil.actor.DocEffigy; 053import ptolemy.vergil.actor.DocManager; 054import ptolemy.vergil.actor.DocTableau; 055import ptolemy.vergil.toolbox.FigureAction; 056 057/////////////////////////////////////////////////////////////////// 058//// GetDocumentationAction 059 060/** This is an action that accesses the documentation for a Ptolemy 061 object associated with a figure. Note that this base class does 062 not put this action in a menu, since some derived classes will 063 not want it. But by having it here, it is available to all 064 derived classes. 065 066 This class provides an action for removing instance-specific documentation. 067 068 @author Edward A. Lee 069 @version $Id$ 070 @since Ptolemy II 5.2 071 @Pt.ProposedRating Red (eal) 072 @Pt.AcceptedRating Red (johnr) 073 */ 074@SuppressWarnings("serial") 075public class GetDocumentationAction extends FigureAction { 076 077 /** Construct an instance and give a preference for whether the 078 * KeplerDocumentationAttribute or the docAttribute should be displayed 079 * if both exist. 080 * @param docPreference 0 for docAttribute, 1 for 081 * KeplerDocumentationAttribute 082 */ 083 public GetDocumentationAction(int docPreference) { 084 super("Get Documentation"); 085 _docPreference = docPreference; 086 } 087 088 /** Construct an instance of this action. */ 089 public GetDocumentationAction() { 090 super("Get Documentation"); 091 } 092 093 /////////////////////////////////////////////////////////////////// 094 //// public methods //// 095 096 /** Perform the action by opening documentation for the target. 097 * In the default situation, the documentation is in doc.codeDoc. 098 * However, if we have a custom application like HyVisual, 099 * VisualSense or Viptos, then we create the docs in 100 * doc.codeDoc<i>ApplicationName</i>.doc.codeDoc. However, this 101 * directory gets jar up and shipped with these apps when we ship 102 * windows installers and the docs are found at doc.codeDoc 103 * again. So, if _applicationName is set, we look in 104 * doc.codeDoc<i>_applicationName</i>.doc.codeDoc. If that is 105 * not found, we look in doc.codeDoc. If that is not found, 106 * we bring up {@link ptolemy.vergil.actor.DocBuilderGUI}. 107 */ 108 @Override 109 public void actionPerformed(ActionEvent e) { 110 super.actionPerformed(e); 111 112 if (_configuration == null) { 113 MessageHandler 114 .error("Cannot get documentation without a configuration."); 115 } 116 117 NamedObj target = getTarget(); 118 if (target == null) { 119 // Ignore and return. 120 return; 121 } 122 123 showDocumentation(target); 124 } 125 126 /** 127 * Show the documentation for a NamedObj. This does the same 128 * thing as the actionPerformed but without the action handler 129 * @param target The NamedObj that will have its documentation shown. 130 */ 131 public void showDocumentation(NamedObj target) { 132 if (_configuration == null) { 133 MessageHandler 134 .error("Cannot get documentation without a configuration."); 135 } 136 137 // If the object contains 138 // an attribute named of class DocAttribute or if there 139 // is a doc file for the object in the standard place, 140 // then use the DocViewer class to display the documentation. 141 // For backward compatibility, if neither of these is found, 142 // then we open the Javadoc file, if it is found. 143 List docAttributes = target.attributeList(DocAttribute.class); 144 //check for the KeplerDocumentation attribute 145 KeplerDocumentationAttribute keplerDocumentationAttribute = (KeplerDocumentationAttribute) target 146 .getAttribute("KeplerDocumentation"); 147 int docAttributeSize = docAttributes.size(); 148 149 if (docAttributes.size() != 0 && keplerDocumentationAttribute != null) { 150 //if there is both a docAttribute and a KeplerDocumentationAttribute 151 //use the preference passed in to the constructor 152 if (_docPreference == 0) { 153 keplerDocumentationAttribute = null; 154 } else if (_docPreference == 1) { 155 docAttributeSize = 0; 156 } 157 } 158 159 if (keplerDocumentationAttribute != null) { 160 //use the KeplerDocumentationAttribute 161 DocAttribute docAttribute = keplerDocumentationAttribute 162 .getDocAttribute(target); 163 if (docAttribute != null) { 164 _showDocAttributeTableau(docAttribute, target); 165 } else { 166 throw new InternalErrorException( 167 "Error building Kepler documentation"); 168 } 169 } else if (docAttributeSize != 0) { 170 // Have a doc attribute. Use that. 171 DocAttribute docAttribute = (DocAttribute) docAttributes 172 .get(docAttributes.size() - 1); 173 _showDocAttributeTableau(docAttribute, target); 174 } else { 175 // No doc attribute. Try for a doc file. 176 String className = target.getClass().getName(); 177 Effigy context = Configuration.findEffigy(target); 178 NamedObj container = target.getContainer(); 179 while (context == null && container != null) { 180 context = Configuration.findEffigy(container); 181 container = container.getContainer(); 182 } 183 /* This test is pointless, since it shows the doc anyway. 184 if (context == null) { 185 MessageHandler.error("Cannot find an effigy for " 186 + target.getFullName()); 187 } 188 */ 189 getDocumentation(_configuration, className, context); 190 } 191 } 192 193 /** Get the documentation for a particular class. 194 * <p>If the configuration has a parameter _docApplicationSpecializer 195 * and that parameter names a class that that implements the 196 * DocApplicationSpecializer interface, then we call 197 * docClassNameToURL(). 198 * 199 * <p>If the documentation is not found, pop up a dialog and ask the 200 * user if they would like to build the documentation, use the 201 * website documentation or cancel. The location of the website 202 * documentation is set by the _remoteDocumentationURLBase attribute 203 * in the configuration. That attribute, if present, should be a 204 * parameter that whose value is a string that represents the URL 205 * where the documentation may be found. If the 206 * _remoteDocumentationURLBase attribute is not set, then the 207 * location of the website documentation defaults to 208 * <code>http://ptolemy.eecs.berkeley.edu/ptolemyII/ptII/<i>Major.Version</i></code>, 209 * where <code><i>Major.Version</i></code> is the value returned by 210 * {@link ptolemy.kernel.attributes.VersionAttribute#majorCurrentVersion()}. 211 * 212 * @param configuration The configuration. 213 * @param className The dot separated fully qualified name of the class. 214 * @param context The context. 215 */ 216 public static void getDocumentation(Configuration configuration, 217 String className, Effigy context) { 218 try { 219 220 // Look for the PtDoc .xml file or the javadoc. 221 // Don't look for the source or the index. 222 URL toRead = DocManager.docClassNameToURL(configuration, className, 223 true, true, false, false); 224 if (toRead != null) { 225 _lastClassName = null; 226 if (toRead.toExternalForm().endsWith(".html")) { 227 // Sadly, Javadoc from Java 1.7 cannot be 228 // displayed using a JEditorPane, so we open 229 // javadoc in an external browser. To test this 230 // out, see 231 // http://docs.oracle.com/javase/tutorial/uiswing/components/editorpane.html#editorpane 232 // and modify the example so that it tries to view 233 // the Javadoc for Object. 234 toRead = new URL(toRead.toExternalForm() + "#in_browser"); 235 } 236 // Opening a remote URL can be slow, so we report to the status bar. 237 BasicGraphFrame basicGraphFrame = BasicGraphFrame 238 .getBasicGraphFrame(context); 239 240 if (basicGraphFrame != null) { 241 basicGraphFrame.report("Opening " + toRead); 242 } 243 configuration.openModel(null, toRead, toRead.toExternalForm()); 244 if (basicGraphFrame != null) { 245 basicGraphFrame 246 .report("Opened documentation for " + className); 247 } 248 } else { 249 Parameter docApplicationSpecializerParameter = (Parameter) configuration 250 .getAttribute("_docApplicationSpecializer", 251 Parameter.class); 252 if (docApplicationSpecializerParameter != null) { 253 //if there is a docApplicationSpecializer, let it handle the 254 //error instead of just throwing the exception 255 String docApplicationSpecializerClassName = docApplicationSpecializerParameter 256 .getExpression(); 257 Class docApplicationSpecializerClass = Class 258 .forName(docApplicationSpecializerClassName); 259 final DocApplicationSpecializer docApplicationSpecializer = (DocApplicationSpecializer) docApplicationSpecializerClass 260 .newInstance(); 261 docApplicationSpecializer 262 .handleDocumentationNotFound(className, context); 263 } else { 264 throw new Exception("Could not get find documentation for " 265 + className + "." 266 + (DocManager 267 .getRemoteDocumentationURLBase() != null 268 ? " Also tried looking on \"" 269 + DocManager 270 .getRemoteDocumentationURLBase() 271 + "\"." 272 : "")); 273 } 274 } 275 } catch (Exception ex) { 276 // Try to open the DocBuilderGUI 277 try { 278 Parameter remoteDocumentationURLBaseParameter = (Parameter) configuration 279 .getAttribute("_remoteDocumentationURLBase", 280 Parameter.class); 281 String tentativeRemoteDocumentationURLBase = null; 282 if (remoteDocumentationURLBaseParameter != null) { 283 tentativeRemoteDocumentationURLBase = remoteDocumentationURLBaseParameter 284 .getExpression(); 285 } else { 286 if (VersionAttribute.CURRENT_VERSION.getExpression() 287 .indexOf(".devel") != -1) { 288 tentativeRemoteDocumentationURLBase = "https://chess.eecs.berkeley.edu/ptexternal/src/ptII/"; 289 } else { 290 tentativeRemoteDocumentationURLBase = "http://ptolemy.eecs.berkeley.edu/ptolemyII/ptII" 291 + VersionAttribute.majorCurrentVersion() 292 + "/ptII/"; 293 } 294 } 295 // Pop up a query an prompt the user 296 String message = "The documentation for " + className 297 + " was not found.\n" 298 + (_lastClassName != null && DocManager 299 .getRemoteDocumentationURLBase() != null 300 ? " We looked in \"" + DocManager 301 .getRemoteDocumentationURLBase() 302 + "\" but did not find anything.\n" 303 : "") 304 + "You may\n" 305 + "1) Build the documentation, which requires " 306 + "configure and make, or\n" 307 + "2) Use the documentation from the website at \"" 308 + tentativeRemoteDocumentationURLBase + "\" or\n" 309 + "3) Cancel"; 310 Object[] options = { "Build", "Use Website", "Cancel" }; 311 int selected = JOptionPane.showOptionDialog(null, message, 312 "Choose Documentation Source", 313 JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, 314 null, options, options[0]); 315 switch (selected) { 316 case 2: 317 // Cancel 318 return; 319 case 1: 320 // Use Website 321 DocManager.setRemoteDocumentationURLBase( 322 tentativeRemoteDocumentationURLBase); 323 _lastClassName = className; 324 getDocumentation(configuration, className, context); 325 break; 326 case 0: 327 // Build 328 // Need to create an effigy and tableau. 329 ComponentEntity effigy = context 330 .getEntity("DocBuilderEffigy"); 331 if (effigy == null) { 332 try { 333 effigy = new DocBuilderEffigy(context, 334 "DocBuilderEffigy"); 335 } catch (KernelException exception) { 336 throw new InternalErrorException(exception); 337 } 338 } 339 if (!(effigy instanceof DocBuilderEffigy)) { 340 MessageHandler.error("Found an effigy named " 341 + "DocBuilderEffigy that " 342 + "is not an instance of DocBuilderEffigy!"); 343 } 344 //((DocEffigy) effigy).setDocAttribute(docAttribute); 345 ComponentEntity tableau = ((Effigy) effigy) 346 .getEntity("DocBuilderTableau"); 347 if (tableau == null) { 348 try { 349 tableau = new DocBuilderTableau( 350 (DocBuilderEffigy) effigy, 351 "DocBuilderTableau"); 352 ((DocBuilderTableau) tableau) 353 .setTitle("Documentation for " + className); 354 } catch (KernelException exception) { 355 throw new InternalErrorException(exception); 356 } 357 } 358 if (!(tableau instanceof DocBuilderTableau)) { 359 MessageHandler.error("Found a tableau named " 360 + "DocBuilderTableau that " 361 + "is not an instance of DocBuilderTableau!"); 362 } 363 // FIXME: Tell the user what to do here. 364 ((DocBuilderTableau) tableau).show(); 365 break; 366 default: 367 throw new InternalErrorException("Unknown return value \"" 368 + selected 369 + "\" from Choose Documentation Source window."); 370 //break; 371 } 372 } catch (Throwable throwable) { 373 MessageHandler.error("Cannot find documentation for " 374 + className + "\nTry Running \"make\" in ptII/doc." 375 + "\nor installing the documentation component.", 376 throwable); 377 } 378 } 379 } 380 381 /** Set the configuration. This is used 382 * to open files (such as documentation). The configuration is 383 * is important because it keeps track of which files are already 384 * open and ensures that there is only one editor operating on the 385 * file at any one time. 386 * @param configuration The configuration. 387 */ 388 public void setConfiguration(Configuration configuration) { 389 _configuration = configuration; 390 } 391 392 /** Set the effigy to be used if the effigy is not evident from the 393 * model being edited. This is used if you are showing the documentation 394 * from code that is not in a model. 395 * @param effigy the effigy to set. 396 */ 397 public void setEffigy(Effigy effigy) { 398 _effigy = effigy; 399 } 400 401 /////////////////////////////////////////////////////////////////// 402 //// protected variables //// 403 404 /** The configuration. */ 405 protected Configuration _configuration; 406 407 /////////////////////////////////////////////////////////////////// 408 //// private methods //// 409 410 /** 411 * Allow optional use of multiple documentation windows when the 412 * _multipleDocumentationAllowed attribute is found in the 413 * Configuration. 414 */ 415 private static boolean _isMultipleDocumentationAllowed() { 416 // FIXME: This is necessary for Kepler, but not for Ptolemy? 417 // Why? 418 boolean retVal = false; 419 List configsList = Configuration.configurations(); 420 Configuration config = null; 421 422 for (Iterator it = configsList.iterator(); it.hasNext();) { 423 config = (Configuration) it.next(); 424 if (config != null) { 425 break; 426 } 427 } 428 if (config == null) { 429 throw new KernelRuntimeException("Could not find " 430 + "configuration, list of configurations was " 431 + configsList.size() + " elements, all were null."); 432 } 433 // Look up the attribute (if it exists) 434 StringAttribute multipleDocumentationAllowed = (StringAttribute) config 435 .getAttribute("_multipleDocumentationAllowed"); 436 if (multipleDocumentationAllowed != null) { 437 retVal = Boolean 438 .parseBoolean(multipleDocumentationAllowed.getExpression()); 439 } 440 return retVal; 441 } 442 443 /** 444 * Find and show the tableau for a given DocAttribute. 445 * @param docAttribute the attribute to show 446 * @param target the target of the documentation viewing 447 */ 448 private void _showDocAttributeTableau(DocAttribute docAttribute, 449 NamedObj target) { 450 // Need to create an effigy and tableau. 451 ComponentEntity effigy = null; 452 Effigy context = Configuration.findEffigy(target); 453 if (_effigy == null) { 454 NamedObj container = target.getContainer(); 455 while (container != null && context == null) { 456 context = Configuration.findEffigy(container); 457 container = container.getContainer(); 458 } 459 if (context == null) { 460 MessageHandler.error( 461 "Cannot find an effigy for " + target.getFullName()); 462 return; 463 } 464 effigy = context.getEntity("DocEffigy"); 465 } else { 466 effigy = _effigy; 467 } 468 469 if (effigy == null) { 470 try { 471 effigy = new DocEffigy(context, "DocEffigy"); 472 } catch (KernelException exception) { 473 throw new InternalErrorException(exception); 474 } 475 } 476 if (!(effigy instanceof DocEffigy)) { 477 MessageHandler.error("Found an effigy named DocEffigy that " 478 + "is not an instance of DocEffigy!"); 479 } 480 ((DocEffigy) effigy).setDocAttribute(docAttribute); 481 ComponentEntity tableau = ((Effigy) effigy).getEntity("DocTableau"); 482 if (tableau == null) { 483 try { 484 tableau = new DocTableau((DocEffigy) effigy, "DocTableau"); 485 486 ((DocTableau) tableau) 487 .setTitle("Documentation for " + target.getFullName()); 488 } catch (KernelException exception) { 489 throw new InternalErrorException(exception); 490 } 491 } else { 492 if (_isMultipleDocumentationAllowed() || 493 // For some reason, the frame might be null. 494 (tableau instanceof Tableau) 495 && ((Tableau) tableau).getFrame() == null) { 496 try { 497 // FIXME: This is necessary for Kepler, but 498 // not for Ptolemy? Why? 499 500 // Create a new tableau with a unique name 501 tableau = new DocTableau((DocEffigy) effigy, 502 effigy.uniqueName("DocTableau")); 503 ((DocTableau) tableau).setTitle( 504 "Documentation for " + target.getFullName()); 505 } catch (KernelException exception) { 506 MessageHandler.error("Failed to display documentation for " 507 + "\" " + target.getFullName() + "\".", exception); 508 } 509 } 510 } 511 if (!(tableau instanceof DocTableau)) { 512 MessageHandler.error("Found a tableau named DocTableau that " 513 + "is not an instance of DocTableau!"); 514 } 515 ((DocTableau) tableau).show(); 516 } 517 518 /////////////////////////////////////////////////////////////////// 519 //// private variables //// 520 521 /** 522 * Defines a preference for whether to display kepler documentation or 523 * ptolemy documentation. This can be set in the constructor and it 524 * default to ptolemy. 0 is ptolemy, 1 is kepler. 525 */ 526 private int _docPreference = 0; 527 528 /** 529 * Defines the effigy to use if the effigy is not apparent from the model 530 */ 531 private Effigy _effigy = null; 532 533 /** The name of the last class for which we looked. If the user 534 * looks again for the same class and gets an error and 535 * remoteDocumentationURLBase is set, we print a little more information. 536 */ 537 private static String _lastClassName = null; 538}