001/* A viewer for actor documentation. 002 003 Copyright (c) 2006-2016 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.actor; 029 030import java.awt.Container; 031import java.awt.Dimension; 032import java.awt.Font; 033import java.awt.event.ActionEvent; 034import java.awt.event.ActionListener; 035import java.awt.event.KeyEvent; 036import java.awt.geom.AffineTransform; 037import java.awt.geom.Rectangle2D; 038import java.net.URL; 039import java.util.Iterator; 040import java.util.List; 041import java.util.Vector; 042 043import javax.swing.BorderFactory; 044import javax.swing.Box; 045import javax.swing.BoxLayout; 046import javax.swing.JEditorPane; 047import javax.swing.JMenu; 048import javax.swing.JMenuItem; 049import javax.swing.JPanel; 050import javax.swing.JScrollPane; 051import javax.swing.JSplitPane; 052import javax.swing.SwingUtilities; 053import javax.swing.border.EtchedBorder; 054import javax.swing.event.HyperlinkEvent; 055 056import diva.canvas.CanvasUtilities; 057import diva.canvas.JCanvas; 058import diva.canvas.toolbox.LabelFigure; 059import diva.graph.GraphPane; 060import diva.graph.GraphViewEvent; 061import diva.graph.JGraph; 062import ptolemy.actor.IOPort; 063import ptolemy.actor.gui.Configuration; 064import ptolemy.actor.gui.Effigy; 065import ptolemy.actor.gui.HTMLViewer; 066import ptolemy.actor.gui.Tableau; 067import ptolemy.actor.parameters.ParameterPort; 068import ptolemy.actor.parameters.PortParameter; 069import ptolemy.data.BooleanToken; 070import ptolemy.data.Token; 071import ptolemy.data.expr.FileParameter; 072import ptolemy.data.expr.Parameter; 073import ptolemy.data.expr.SingletonParameter; 074import ptolemy.data.expr.StringParameter; 075import ptolemy.kernel.CompositeEntity; 076import ptolemy.kernel.Entity; 077import ptolemy.kernel.InstantiableNamedObj; 078import ptolemy.kernel.Port; 079import ptolemy.kernel.util.Attribute; 080import ptolemy.kernel.util.ChangeRequest; 081import ptolemy.kernel.util.IllegalActionException; 082import ptolemy.kernel.util.Instantiable; 083import ptolemy.kernel.util.InternalErrorException; 084import ptolemy.kernel.util.NameDuplicationException; 085import ptolemy.kernel.util.NamedObj; 086import ptolemy.kernel.util.Settable; 087import ptolemy.kernel.util.StringAttribute; 088import ptolemy.moml.MoMLChangeRequest; 089import ptolemy.util.MessageHandler; 090import ptolemy.vergil.basic.BasicGraphFrame; 091import ptolemy.vergil.basic.DocAttribute; 092 093/////////////////////////////////////////////////////////////////// 094//// DocViewer 095 096/** 097 This class defines a specialized window for displaying Ptolemy II actor 098 documentation. The three versions of the constructor offer mechanisms 099 to display documentation for a particular actor instance or a specified 100 actor class name, or to display a specified documentation file. 101 The documentation file is expected to be an XML file using the 102 DocML schema, as defined in the DocManager class. 103 104 @author Edward A. Lee 105 @version $Id$ 106 @since Ptolemy II 5.2 107 @see DocManager 108 @Pt.ProposedRating Yellow (eal) 109 @Pt.AcceptedRating Red (cxh) 110 */ 111@SuppressWarnings("serial") 112public class DocViewer extends HTMLViewer { 113 114 /** Construct a documentation viewer for the specified target. 115 * @param target The object to get documentation for. 116 * @param configuration The configuration in charge of this viewer. 117 */ 118 public DocViewer(NamedObj target, Configuration configuration) { 119 super(); 120 try { 121 _init(target, configuration, target.getClassName(), null); 122 } catch (ClassNotFoundException e) { 123 // Should not happen. 124 throw new InternalErrorException("Unexpected exception"); 125 } 126 } 127 128 /** Construct a documentation viewer for the specified class name. 129 * @param className The class name to get documentation for. 130 * @param configuration The configuration in charge of this viewer. 131 * @exception ClassNotFoundException If the class cannot be found. 132 */ 133 public DocViewer(String className, Configuration configuration) 134 throws ClassNotFoundException { 135 super(); 136 _init(null, configuration, className, null); 137 } 138 139 /** Construct a documentation viewer for the specified documentation file. 140 * @param url The URL at which to find the documentation. 141 * @param configuration The configuration in charge of this viewer. 142 */ 143 public DocViewer(URL url, Configuration configuration) { 144 super(); 145 try { 146 _init(null, configuration, null, url); 147 } catch (Throwable throwable) { 148 // Should not happen. 149 throw new InternalErrorException(null, throwable, 150 "Unexpected exception initializing viewer for " + url); 151 } 152 } 153 154 /////////////////////////////////////////////////////////////////// 155 //// public methods //// 156 157 /** Get the configuration specified in the constructor. 158 * @return The configuration controlling this frame, or null 159 * if there isn't one. 160 */ 161 @Override 162 public Configuration getConfiguration() { 163 return _configuration; 164 } 165 166 /** Override the base class to react to links of the form 167 * #parentClass. 168 * @param event The hyperlink event. 169 */ 170 @Override 171 public void hyperlinkUpdate(HyperlinkEvent event) { 172 if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED 173 && event.getDescription().equals("#parentClass")) { 174 // This should only occur if DocManager has already checked that the following will work. 175 // Nonetheless, we look for exceptions and report them. 176 try { 177 NamedObj parent = (NamedObj) ((Instantiable) _target) 178 .getParent(); 179 List docAttributes = parent.attributeList(DocAttribute.class); 180 DocAttribute attribute = (DocAttribute) docAttributes 181 .get(docAttributes.size() - 1); 182 Effigy effigy = getEffigy(); 183 DocEffigy newEffigy = new DocEffigy( 184 (CompositeEntity) effigy.getContainer(), 185 effigy.getContainer().uniqueName("parentClass")); 186 newEffigy.setDocAttribute(attribute); 187 DocTableau tableau = new DocTableau(newEffigy, "docTableau"); 188 tableau.show(); 189 } catch (Exception e) { 190 MessageHandler.error("Error following hyperlink", e); 191 } 192 } else { 193 super.hyperlinkUpdate(event); 194 } 195 } 196 197 /////////////////////////////////////////////////////////////////// 198 //// protected methods //// 199 200 /** Override the base class to do nothing. 201 * The main content pane is added after the top content. 202 */ 203 @Override 204 protected void _addMainPane() { 205 } 206 207 /** Add a Build menu item. 208 */ 209 @Override 210 protected void _addMenus() { 211 super._addMenus(); 212 213 Tableau tableau = getTableau(); 214 if (tableau != null) { 215 Effigy tableauContainer = (Effigy) tableau.getContainer(); 216 if (tableauContainer != null) { 217 JMenu buildMenu = new JMenu("Build"); 218 buildMenu.setMnemonic(KeyEvent.VK_B); 219 _menubar.add(buildMenu); 220 221 BuildMenuListener buildMenuListener = new BuildMenuListener(); 222 String name = "Build docs"; 223 JMenuItem item = new JMenuItem(name); 224 item.setActionCommand(name); 225 item.setMnemonic(name.charAt(0)); 226 item.addActionListener(buildMenuListener); 227 buildMenu.add(item); 228 } 229 } 230 } 231 232 /** Display the help file given by the configuration, or if there is 233 * none, then the file specified by the public variable helpFile. 234 * To specify a default help file in the configuration, create 235 * a FileParameter named "_helpDocViewer" whose value is the name of the 236 * file. If the specified file fails to open, then invoke the 237 * _help() method of the superclass. 238 * @see FileParameter 239 */ 240 @Override 241 protected void _help() { 242 try { 243 Configuration configuration = getConfiguration(); 244 FileParameter helpAttribute = (FileParameter) configuration 245 .getAttribute("_helpDocViewer", FileParameter.class); 246 URL doc; 247 248 if (helpAttribute != null) { 249 doc = helpAttribute.asURL(); 250 } else { 251 doc = getClass().getClassLoader().getResource(helpFile); 252 } 253 254 configuration.openModel(null, doc, doc.toExternalForm()); 255 } catch (Exception ex) { 256 super._help(); 257 } 258 } 259 260 /** Override the base class to do nothing. 261 * @param width The width. 262 * @param height The width. 263 */ 264 @Override 265 protected void _setScrollerSize(final int width, final int height) { 266 } 267 268 /////////////////////////////////////////////////////////////////// 269 //// private methods //// 270 271 /** Adjust the icon display for the specified target. 272 * @param sample The instance whose icon is displayed. 273 * @param container The container of the sample instance. 274 * @param graphPane The graph pane in which it is displayed. 275 * @param jgraph The jgraph. 276 * @exception IllegalActionException 277 * @exception NameDuplicationException 278 */ 279 private void _adjustIconDisplay(final NamedObj sample, 280 final CompositeEntity container, final GraphPane graphPane, 281 final JGraph jgraph) 282 throws IllegalActionException, NameDuplicationException { 283 // Now make appropriate modifications. 284 // First, if the object has ports, add parameters to the ports 285 // to display them. 286 if (sample instanceof Entity) { 287 Iterator ports = ((Entity) sample).portList().iterator(); 288 while (ports.hasNext()) { 289 Port port = (Port) ports.next(); 290 SingletonParameter show = new SingletonParameter(port, 291 "_showName"); 292 show.setExpression("true"); 293 } 294 } 295 // Next, set options to display parameter values. 296 StringParameter show = new StringParameter(container, 297 "_showParameters"); 298 show.setExpression("All"); 299 300 // Defer this to get it to happen after rendering. 301 Runnable defer = new Runnable() { 302 @Override 303 public void run() { 304 Rectangle2D bounds = graphPane.getForegroundLayer() 305 .getLayerBounds(); 306 if (!bounds.isEmpty()) { 307 Dimension size = jgraph.getSize(); 308 Rectangle2D viewSize = new Rectangle2D.Double(_PADDING, 309 _PADDING, size.getWidth() - 2 * _PADDING, 310 size.getHeight() - 2 * _PADDING); 311 AffineTransform newTransform = CanvasUtilities 312 .computeFitTransform(bounds, viewSize); 313 JCanvas canvas = graphPane.getCanvas(); 314 canvas.getCanvasPane().setTransform(newTransform); 315 } 316 } 317 }; 318 SwingUtilities.invokeLater(defer); 319 } 320 321 /** Return HTML that colorizes the rating text. 322 * @param rating The rating text, such as "Red (mrptolemy)" 323 * @return HTML, such as "<td bgcolor="#FF0000">Red (mrptolemy)</td>" 324 */ 325 private String _colorizeRating(String rating) { 326 String color = "#FFFFFF"; 327 if (rating.startsWith("Red")) { 328 color = "#FF0000"; 329 } else if (rating.startsWith("Yellow")) { 330 color = "#AAAA00"; 331 } else if (rating.startsWith("Green")) { 332 color = "#00FF00"; 333 } else if (rating.startsWith("Blue")) { 334 color = "#0000FF"; 335 } 336 //return "<td bgcolor=\"" + color + "\">" + rating + "</td>"; 337 return "<td><font color=\"" + color + "\">" + rating + "</font></td>"; 338 } 339 340 /** Return a string with parameter table entries. 341 * @param target The target. 342 * @param manager The manager. 343 * @return Parameter table entries, or null if there are no parameters. 344 */ 345 private String _getParameterEntries(NamedObj target, DocManager manager) { 346 //check for exclusion attributes in the configuration 347 //exclusion attributes can exclude params from the documentation 348 //by their name. an exclusion can be "exact" or "contains". an "exact" 349 //exclusion requires the name on the exclusion list to exactly match 350 //the name of the param. a "contains" exclusion just requires that the 351 //name of exclusion is contained in the name of the exclusion. 352 Configuration config = getConfiguration(); 353 Iterator itt = config 354 .attributeList(ptolemy.kernel.util.StringAttribute.class) 355 .iterator(); 356 Vector exclusions = new Vector(); 357 while (itt.hasNext()) { 358 NamedObj att = (NamedObj) itt.next(); 359 360 if (att.getName().indexOf("docViewerExclude") != -1) { 361 String value = ((StringAttribute) att).getExpression(); 362 exclusions.addElement(value); 363 } 364 } 365 366 StringBuffer parameters = new StringBuffer(); 367 parameters.append(_tr); 368 parameters.append(_tdColSpan); 369 parameters.append("<h2>Parameters</h2>"); 370 parameters.append(_tde); 371 parameters.append(_tre); 372 boolean foundOne = false; 373 Iterator attributes = target.attributeList(Settable.class).iterator(); 374 while (attributes.hasNext()) { 375 Settable parameter = (Settable) attributes.next(); 376 if (_isHidden((NamedObj) parameter)) { 377 continue; 378 } 379 if (parameter instanceof PortParameter) { 380 // Skip this one. 381 continue; 382 } 383 384 String parameterName = parameter.getName(); 385 //check to see if this param is on the exclusion list 386 for (int i = 0; i < exclusions.size(); i++) { 387 String exclusion = (String) exclusions.elementAt(i); 388 String type = exclusion.substring(0, exclusion.indexOf(":")); 389 exclusion = exclusion.substring(exclusion.indexOf(":") + 1, 390 exclusion.length()); 391 if (type.equals("contains")) { 392 if (parameterName.indexOf(exclusion) != -1) { 393 parameter.setVisibility(Settable.NONE); 394 } 395 } else if (type.equals("exact")) { 396 if (parameterName.equals(exclusion)) { 397 parameter.setVisibility(Settable.NONE); 398 } 399 } 400 } 401 402 String doc = manager.getPropertyDoc(parameter.getName()); 403 if (doc == null) { 404 doc = "No description."; 405 // See if the next tier has documentation. 406 DocManager nextTier = manager.getNextTier(); 407 if (nextTier != null) { 408 String nextDoc = nextTier 409 .getPropertyDoc(parameter.getName()); 410 if (nextDoc != null) { 411 doc = nextDoc; 412 } 413 } 414 } 415 if (parameter.getVisibility() == Settable.FULL) { 416 parameters.append(_tr); 417 parameters.append(_td); 418 parameters.append("<i>" + parameter.getDisplayName() + "</i>"); 419 parameters.append(_tde); 420 parameters.append(_td); 421 parameters.append(doc); 422 parameters.append(_tde); 423 parameters.append(_tre); 424 foundOne = true; 425 } 426 } 427 if (foundOne) { 428 return parameters.toString(); 429 } else { 430 return null; 431 } 432 } 433 434 /** Return a string with port table entries. 435 * @param target The target. 436 * @param manager The manager. 437 * @return Port table entries, or null if there are no ports. 438 */ 439 private String _getPortEntries(NamedObj target, DocManager manager) { 440 if (!(target instanceof Entity)) { 441 return null; 442 } 443 StringBuffer result = new StringBuffer(); 444 boolean foundOne = false; 445 boolean foundInput = false; 446 boolean foundOutput = false; 447 boolean foundInputOutput = false; 448 boolean foundNeither = false; 449 StringBuffer inputPorts = new StringBuffer(); 450 StringBuffer outputPorts = new StringBuffer(); 451 StringBuffer inputOutputPorts = new StringBuffer(); 452 StringBuffer neitherPorts = new StringBuffer(); 453 Iterator ports = ((Entity) target).portList().iterator(); 454 while (ports.hasNext()) { 455 Port port = (Port) ports.next(); 456 if (_isHidden(port)) { 457 continue; 458 } 459 if (port instanceof ParameterPort) { 460 // Skip this one. 461 continue; 462 } 463 String portName = "<i>" + port.getDisplayName() + "</i>"; 464 String doc = manager.getPortDoc(port.getName()); 465 if (doc == null) { 466 doc = "No port description."; 467 // See if the next tier has documentation. 468 DocManager nextTier = manager.getNextTier(); 469 if (nextTier != null) { 470 String nextDoc = nextTier.getPortDoc(port.getName()); 471 if (nextDoc != null) { 472 doc = nextDoc; 473 } 474 } 475 } 476 if (port instanceof IOPort) { 477 if (((IOPort) port).isInput() && !((IOPort) port).isOutput()) { 478 inputPorts.append(_tr); 479 inputPorts.append(_td); 480 inputPorts.append(portName); 481 inputPorts.append(_tde); 482 inputPorts.append(_td); 483 inputPorts.append(doc); 484 inputPorts.append(_tde); 485 inputPorts.append(_tre); 486 foundInput = true; 487 foundOne = true; 488 } else if (((IOPort) port).isOutput() 489 && !((IOPort) port).isInput()) { 490 outputPorts.append(_tr); 491 outputPorts.append(_td); 492 outputPorts.append(portName); 493 outputPorts.append(_tde); 494 outputPorts.append(_td); 495 outputPorts.append(doc); 496 outputPorts.append(_tde); 497 outputPorts.append(_tre); 498 foundOutput = true; 499 foundOne = true; 500 } else if (((IOPort) port).isOutput() 501 && ((IOPort) port).isInput()) { 502 inputOutputPorts.append(_tr); 503 inputOutputPorts.append(_td); 504 inputOutputPorts.append(portName); 505 inputOutputPorts.append(_tde); 506 inputOutputPorts.append(_td); 507 inputOutputPorts.append(doc); 508 inputOutputPorts.append(_tde); 509 inputOutputPorts.append(_tre); 510 foundInputOutput = true; 511 foundOne = true; 512 } else { 513 neitherPorts.append(_tr); 514 neitherPorts.append(_td); 515 neitherPorts.append(portName); 516 neitherPorts.append(_tde); 517 neitherPorts.append(_td); 518 neitherPorts.append(doc); 519 neitherPorts.append(_tde); 520 neitherPorts.append(_tre); 521 foundNeither = true; 522 foundOne = true; 523 } 524 } else { 525 neitherPorts.append(_tr); 526 neitherPorts.append(_td); 527 neitherPorts.append(portName); 528 neitherPorts.append(_tde); 529 neitherPorts.append(_td); 530 neitherPorts.append(doc); 531 neitherPorts.append(_tde); 532 neitherPorts.append(_tre); 533 foundNeither = true; 534 foundOne = true; 535 } 536 } 537 if (foundInput) { 538 result.append(_tr); 539 result.append(_tdColSpan); 540 result.append("<h2>Input Ports</h2>"); 541 result.append(_tde); 542 result.append(_tre); 543 result.append(inputPorts); 544 } 545 if (foundOutput) { 546 result.append(_tr); 547 result.append(_tdColSpan); 548 result.append("<h2>Output Ports</h2>"); 549 result.append(_tde); 550 result.append(_tre); 551 result.append(outputPorts); 552 } 553 if (foundInputOutput) { 554 result.append(_tr); 555 result.append(_tdColSpan); 556 result.append("<h2>Input/Output Ports</h2>"); 557 result.append(_tde); 558 result.append(_tre); 559 result.append(inputOutputPorts); 560 } 561 if (foundNeither) { 562 result.append(_tr); 563 result.append(_tdColSpan); 564 result.append("<h2>Ports (Neither Input nor Output)</h2>"); 565 result.append(_tde); 566 result.append(_tre); 567 result.append(neitherPorts); 568 } 569 if (foundOne) { 570 return result.toString(); 571 } else { 572 return null; 573 } 574 } 575 576 /** Return a string with port-parameter table entries. 577 * @param target The target. 578 * @param manager The manager. 579 * @return Port-parameter table entries, or null if there are no port-parameters. 580 */ 581 private String _getPortParameterEntries(NamedObj target, 582 DocManager manager) { 583 StringBuffer parameters = new StringBuffer(); 584 parameters.append(_tr); 585 parameters.append(_tdColSpan); 586 parameters.append("<h2>Port-Parameters</h2>"); 587 parameters.append(_tde); 588 parameters.append(_tre); 589 boolean foundOne = false; 590 Iterator attributes = target.attributeList(PortParameter.class) 591 .iterator(); 592 while (attributes.hasNext()) { 593 Settable parameter = (Settable) attributes.next(); 594 if (_isHidden((NamedObj) parameter)) { 595 continue; 596 } 597 String doc = manager.getPropertyDoc(parameter.getName()); 598 if (doc == null) { 599 doc = "No description."; 600 // See if the next tier has documentation. 601 DocManager nextTier = manager.getNextTier(); 602 if (nextTier != null) { 603 String nextDoc = nextTier 604 .getPropertyDoc(parameter.getName()); 605 if (nextDoc != null) { 606 doc = nextDoc; 607 } 608 } 609 } 610 if (parameter.getVisibility() == Settable.FULL) { 611 parameters.append(_tr); 612 parameters.append(_td); 613 parameters.append("<i>" + parameter.getDisplayName() + "</i>"); 614 parameters.append(_tde); 615 parameters.append(_td); 616 parameters.append(doc); 617 parameters.append(_tde); 618 parameters.append(_tre); 619 foundOne = true; 620 } 621 } 622 if (foundOne) { 623 return parameters.toString(); 624 } else { 625 return null; 626 } 627 } 628 629 /** Append to the specified buffer any locally defined base classes 630 * that are needed to define the specified target. 631 * @param target The target whose parent may need to be included. 632 * @param buffer The buffer to append the definition to. 633 */ 634 private void _includeClassDefinitions(NamedObj target, 635 StringBuffer buffer) { 636 if (target instanceof Instantiable) { 637 NamedObj parent = (NamedObj) ((Instantiable) target).getParent(); 638 if (parent != null && target.toplevel().deepContains(parent)) { 639 // Parent is locally defined. Include its definition. 640 // First recursively take care of the parent. 641 if (parent instanceof Instantiable) { 642 NamedObj parentsParent = (NamedObj) ((Instantiable) parent) 643 .getParent(); 644 if (parentsParent != null 645 && parent.toplevel().deepContains(parentsParent)) { 646 _includeClassDefinitions(parent, buffer); 647 } 648 } 649 buffer.append(parent.exportMoML()); 650 // Add an attribute to hide it. 651 buffer.append("<"); 652 buffer.append(parent.getElementName()); 653 buffer.append(" name=\""); 654 buffer.append(parent.getName()); 655 buffer.append( 656 "\"><property name=\"_hide\" class=\"ptolemy.data.expr.ExpertParameter\" value=\"true\"/></"); 657 buffer.append(parent.getElementName()); 658 buffer.append(">"); 659 } 660 } 661 } 662 663 /** Construct a documentation viewer for the specified target, 664 * class name, or URL. Normally, one of these three arguments 665 * will be non-null. 666 * @param target The object to get documentation for, or null 667 * to base this on the specified class name. 668 * @param configuration The configuration in charge of this viewer. 669 * @param className The class name of the target, or null if a target 670 * is given. 671 * @param url The URL from which to read the doc file, or null to 672 * infer it from the target or className. 673 */ 674 private void _init(final NamedObj target, Configuration configuration, 675 String className, URL url) throws ClassNotFoundException { 676 _configuration = configuration; 677 _target = target; 678 679 // Override the default value of the help file as defined in 680 // TableauFrame. This is the name of the default file to open 681 // when Help is invoked. This file should be relative to the 682 // home installation directory. 683 helpFile = "ptolemy/vergil/actor/docViewerHelp.htm"; 684 685 // We handle the applicationName specially so that we open 686 // only the docs for the app we are running. 687 try { 688 StringAttribute applicationNameAttribute = (StringAttribute) configuration 689 .getAttribute("_applicationName", StringAttribute.class); 690 691 if (applicationNameAttribute != null) { 692 _applicationName = applicationNameAttribute.getExpression(); 693 } 694 } catch (Throwable throwable) { 695 // Ignore and use the default applicationName: "", 696 // which means we look in doc.codeDoc. 697 } 698 699 // Start by creating a doc manager. 700 final DocManager manager; 701 if (target != null) { 702 manager = new DocManager(_configuration, target); 703 } else if (className != null) { 704 manager = new DocManager(_configuration, Class.forName(className)); 705 } else if (url != null) { 706 manager = new DocManager(_configuration, url); 707 } else { 708 throw new InternalErrorException( 709 "Need to specify one of target, className, or url!"); 710 } 711 className = manager.getClassName(); 712 final String rootName; 713 int lastPeriod = className.lastIndexOf("."); 714 if (lastPeriod >= 0) { 715 rootName = className.substring(lastPeriod + 1); 716 } else { 717 rootName = className; 718 } 719 // Need to set the base for relative URL references. 720 // If the url argument is given, then use that. 721 // Otherwise, set it to the directory in which the 722 // Javadoc file is normally be found. 723 if (url != null) { 724 setBase(url); 725 } else { 726 String javaDocDirectory = "doc.codeDoc" + _applicationName + "." 727 + className; 728 int lastDot = javaDocDirectory.lastIndexOf("."); 729 javaDocDirectory = javaDocDirectory.substring(0, lastDot); 730 URL base = getClass().getClassLoader() 731 .getResource(javaDocDirectory.replace('.', '/') + "/"); 732 setBase(base); 733 } 734 735 // Spacer at the top. 736 Container contentPane = getContentPane(); 737 Dimension horizontalSpace = new Dimension(_SPACING, 0); 738 Dimension verticalSpace = new Dimension(0, _SPACING); 739 contentPane.add(Box.createRigidArea(verticalSpace)); 740 741 // Panel for title. 742 JPanel titlePanel = new JPanel(); 743 titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.X_AXIS)); 744 contentPane.add(titlePanel); 745 746 // Create a title area. 747 String title = className; 748 // The instance has its own documentation. 749 if (target instanceof InstantiableNamedObj 750 && ((InstantiableNamedObj) target).isClassDefinition()) { 751 // FIXME: getFullName() isn't right here. How to get the full class name? 752 title = target.getName() + " (" 753 + target.getFullName() + ")"; 754 } else { 755 if (manager.isInstanceDoc()) { 756 title = target.getName() + " (Instance of " 757 + className + ")"; 758 } else { 759 title = rootName + " (" + className + ")"; 760 } 761 } 762 JEditorPane titlePane = new JEditorPane(); 763 titlePane.setContentType("text/html"); 764 titlePane.setEditable(false); 765 titlePane.setText( 766 _HTML_HEADER + "<H2> " + title + "</H2>" + _HTML_TAIL); 767 // Set the view to the start of the text. 768 titlePane.getCaret().setDot(0); 769 Dimension titleSize = new Dimension( 770 _DESCRIPTION_WIDTH + _ICON_WINDOW_WIDTH + _SPACING, 40); 771 titlePane.setPreferredSize(titleSize); 772 // Set the min and max size so that resizing the window does not expand the title. 773 titlePane.setMinimumSize(titleSize); 774 titlePane.setMaximumSize(titleSize); 775 titlePane.setSize(titleSize); 776 titlePane.setBorder( 777 BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 778 titlePanel.add(Box.createRigidArea(horizontalSpace)); 779 titlePanel.add(titlePane); 780 titlePanel.add(Box.createRigidArea(horizontalSpace)); 781 782 // Panel for icon and description. 783 //JPanel descriptionPanel = new JPanel(); 784 785 //descriptionPanel.setLayout(new BoxLayout(descriptionPanel, 786 // BoxLayout.X_AXIS)); 787 contentPane.add(Box.createRigidArea(verticalSpace)); 788 789 // Use a JSplitPane below. 790 //contentPane.add(descriptionPanel); 791 792 //descriptionPanel.add(Box.createRigidArea(horizontalSpace)); 793 794 // Construct a blank composite actor into which to put 795 // an instance of the actor. 796 _iconContainer = new CompositeEntity(); 797 final ActorEditorGraphController controller = new ActorEditorGraphController(); 798 controller.setConfiguration(getConfiguration()); 799 // Create a modified graph model with alternative error reporting. 800 ActorGraphModel graphModel = new ActorGraphModel(_iconContainer) { 801 /** Override the base class to give a useful message. 802 * @param change The change that has failed. 803 * @param exception The exception that was thrown. 804 */ 805 @Override 806 public void changeFailed(ChangeRequest change, 807 Exception exception) { 808 if (_graphPane == null) { 809 super.changeFailed(change, exception); 810 return; 811 } 812 LabelFigure newFigure = new LabelFigure("No icon available", 813 _font); 814 _graphPane.getForegroundLayer().add(newFigure); 815 CanvasUtilities.translateTo(newFigure, 100.0, 100.0); 816 controller.dispatch(new GraphViewEvent(this, 817 GraphViewEvent.NODE_DRAWN, newFigure)); 818 } 819 }; 820 _graphPane = new GraphPane(controller, graphModel); 821 _jgraph = new JGraph(_graphPane); 822 _jgraph.setBorder( 823 BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 824 // The icon window was of fixed size until we added the JSplitPane. 825 //_jgraph.setMinimumSize(new Dimension(_ICON_WINDOW_WIDTH, 826 // _ICON_WINDOW_HEIGHT)); 827 //_jgraph.setMaximumSize(new Dimension(_ICON_WINDOW_WIDTH, 828 // _ICON_WINDOW_HEIGHT)); 829 _jgraph.setPreferredSize( 830 new Dimension(_ICON_WINDOW_WIDTH, _ICON_WINDOW_HEIGHT)); 831 _jgraph.setSize(_ICON_WINDOW_WIDTH, _ICON_WINDOW_HEIGHT); 832 _jgraph.setBackground(BasicGraphFrame.BACKGROUND_COLOR); 833 //descriptionPanel.add(_jgraph); 834 //descriptionPanel.add(Box.createRigidArea(horizontalSpace)); 835 // Create a pane in which to display the description. 836 final JEditorPane descriptionPane = new JEditorPane(); 837 descriptionPane.addHyperlinkListener(this); 838 descriptionPane.setContentType("text/html"); 839 descriptionPane.setEditable(false); 840 841 JScrollPane scroller = new JScrollPane(descriptionPane); 842 scroller.setPreferredSize( 843 new Dimension(_DESCRIPTION_WIDTH, _ICON_WINDOW_HEIGHT)); 844 scroller.setBorder( 845 BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 846 //descriptionPanel.add(scroller); 847 //descriptionPanel.add(Box.createRigidArea(horizontalSpace)); 848 849 JSplitPane descriptionSplitPane = new JSplitPane( 850 JSplitPane.HORIZONTAL_SPLIT, _jgraph, scroller); 851 descriptionSplitPane.setDividerLocation(_ICON_WINDOW_WIDTH); 852 // Add the main content pane now. 853 JPanel middle = new JPanel(); 854 middle.setLayout(new BoxLayout(middle, BoxLayout.X_AXIS)); 855 contentPane.add(Box.createRigidArea(verticalSpace)); 856 857 // See JSplitPane below. 858 //contentPane.add(middle); 859 860 _scroller = new JScrollPane(pane); 861 // Default, which can be overridden by calling setSize(). 862 _scroller.setPreferredSize( 863 new Dimension(_MAIN_WINDOW_WIDTH, _MAIN_WINDOW_HEIGHT)); 864 _scroller.setBorder( 865 BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 866 middle.add(Box.createRigidArea(horizontalSpace)); 867 middle.add(_scroller); 868 middle.add(Box.createRigidArea(horizontalSpace)); 869 870 // Use a JSplitPane here. See Kepler component documentation layout needs improvement 871 // https://projects.ecoinformatics.org/ecoinfo/issues/5720 872 JSplitPane upperSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, 873 descriptionSplitPane, middle); 874 // descriptionPanel, middle); 875 upperSplitPane.setPreferredSize( 876 new Dimension(_AUTHOR_WINDOW_WIDTH + _SEE_ALSO_WIDTH + _SPACING, 877 _ICON_WINDOW_HEIGHT + _MAIN_WINDOW_HEIGHT)); 878 upperSplitPane.setOneTouchExpandable(true); 879 upperSplitPane.setDividerLocation(_ICON_WINDOW_HEIGHT); 880 881 // Add a panel for the icon + description and middle 882 JPanel descriptionMiddlePanel = new JPanel(); 883 descriptionMiddlePanel.add(upperSplitPane); 884 descriptionMiddlePanel.setPreferredSize( 885 new Dimension(_AUTHOR_WINDOW_WIDTH + _SEE_ALSO_WIDTH + _SPACING, 886 _ICON_WINDOW_HEIGHT + _MAIN_WINDOW_HEIGHT)); 887 descriptionMiddlePanel.setMinimumSize( 888 new Dimension(_AUTHOR_WINDOW_WIDTH + _SEE_ALSO_WIDTH + _SPACING, 889 _MAIN_WINDOW_HEIGHT)); 890 descriptionMiddlePanel.setLayout( 891 new BoxLayout(descriptionMiddlePanel, BoxLayout.X_AXIS)); 892 893 //contentPane.add(upperSplitPane); 894 contentPane.add(descriptionMiddlePanel); 895 896 // Panel for added sections at the bottom. 897 JPanel bottom = new JPanel(); 898 bottom.setLayout(new BoxLayout(bottom, BoxLayout.X_AXIS)); 899 contentPane.add(Box.createRigidArea(verticalSpace)); 900 contentPane.add(bottom); 901 contentPane.add(Box.createRigidArea(verticalSpace)); 902 bottom.add(Box.createRigidArea(horizontalSpace)); 903 // Pane for author, etc. 904 JEditorPane authorPane = new JEditorPane(); 905 authorPane.addHyperlinkListener(this); 906 authorPane.setContentType("text/html"); 907 authorPane.setEditable(false); 908 JScrollPane authorScroller = new JScrollPane(authorPane); 909 Dimension authorSize = new Dimension(_AUTHOR_WINDOW_WIDTH, 910 _BOTTOM_HEIGHT); 911 authorScroller.setPreferredSize(authorSize); 912 authorScroller.setSize(authorSize); 913 authorScroller.setBorder( 914 BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 915 bottom.add(authorScroller); 916 bottom.add(Box.createRigidArea(horizontalSpace)); 917 // Pane for "see also" information. 918 JEditorPane seeAlsoPane = new JEditorPane(); 919 seeAlsoPane.addHyperlinkListener(this); 920 seeAlsoPane.setContentType("text/html"); 921 seeAlsoPane.setEditable(false); 922 JScrollPane seeAlsoScroller = new JScrollPane(seeAlsoPane); 923 Dimension seeAlsoSize = new Dimension(_SEE_ALSO_WIDTH, _BOTTOM_HEIGHT); 924 seeAlsoScroller.setPreferredSize(seeAlsoSize); 925 seeAlsoScroller.setSize(seeAlsoSize); 926 seeAlsoScroller.setBorder( 927 BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); 928 bottom.add(seeAlsoScroller); 929 bottom.add(Box.createRigidArea(horizontalSpace)); 930 931 ////////////////////////////////////////////////////// 932 // Create the content. 933 934 // Now generate the body of the documentation. 935 StringBuffer html = new StringBuffer(); 936 html.append(_HTML_HEADER); 937 String description = manager.getDescription(); 938 html.append(description); 939 html.append(_HTML_TAIL); 940 descriptionPane.setText(html.toString()); 941 // Set the view to the start of the text. 942 descriptionPane.getCaret().setDot(0); 943 944 // Create an instance to display. 945 // Note that this will display something that looks just 946 // like the object we are asking about, including any customizations 947 // that are applicable only to this instance. 948 // If the target is given, then export MoML from it to use. 949 // Otherwise, use the class name. 950 String moml = null; 951 if (target != null) { 952 StringBuffer buffer = new StringBuffer("<group>"); 953 // If the target is an instance of a locally defined class, 954 // then we need to include the class as well. 955 _includeClassDefinitions(target, buffer); 956 957 // Need to use a 958 buffer.append(target.exportMoMLPlain()); 959 // Have to override the hide attribute in the derived class. 960 buffer.append("<"); 961 buffer.append(target.getElementName()); 962 buffer.append(" name=\""); 963 buffer.append(target.getName()); 964 buffer.append( 965 "\"><property name=\"_hide\" class=\"ptolemy.data.expr.ExpertParameter\" value=\"false\"/></"); 966 buffer.append(target.getElementName()); 967 buffer.append(">"); 968 969 buffer.append("</group>"); 970 moml = buffer.toString(); 971 } else if (!manager.hadException()) { 972 // NOTE: This will not work if a URL was specified and the parse failed. 973 // No target is given. Try to create an instance from the class name. 974 // This is a bit tricky, as we have to know what subclass of NamedObj 975 // it is, and whether it has an appropriate constructor. 976 if (manager.isTargetInstantiableAttribute()) { 977 // To make it visible, need to include a location attribute. 978 moml = "<property name=\"" + rootName + "\" class=\"" 979 + className + "\">" 980 + "<property name=\"_location\" class=\"ptolemy.kernel.util.Location\" value=\"{50, 50}\"/>" 981 + "</property>"; 982 } else if (manager.isTargetInstantiableEntity()) { 983 moml = "<entity name=\"" + rootName + "\" class=\"" + className 984 + "\"/>"; 985 } else if (manager.isTargetInstantiablePort()) { 986 // NOTE: The port has to be an input or an output or it can't be rendered. 987 // Since we aren't dealing with a specific instance, we make it an input. 988 moml = "<port name=\"" + rootName + "\" class=\"" + className 989 + "\">" + "<property name=\"input\"/></port>"; 990 } 991 } 992 if (moml != null) { 993 MoMLChangeRequest request = new MoMLChangeRequest(this, 994 _iconContainer, moml) { 995 @Override 996 protected void _execute() throws Exception { 997 super._execute(); 998 NamedObj sample = null; 999 String name = rootName; 1000 if (target != null) { 1001 name = target.getName(); 1002 } 1003 if (manager.isTargetInstantiableAttribute()) { 1004 sample = _iconContainer.getAttribute(name); 1005 } else if (manager.isTargetInstantiableEntity()) { 1006 sample = _iconContainer.getEntity(name); 1007 } else if (manager.isTargetInstantiablePort()) { 1008 sample = _iconContainer.getPort(name); 1009 } 1010 if (sample != null) { 1011 _populatePortsAndParametersTable(sample, manager); 1012 _adjustIconDisplay(sample, _iconContainer, _graphPane, 1013 _jgraph); 1014 } 1015 } 1016 }; 1017 _iconContainer.requestChange(request); 1018 } 1019 1020 if (target != null) { 1021 _populatePortsAndParametersTable(target, manager); 1022 } 1023 1024 // Populate the author window. 1025 StringBuffer info = new StringBuffer(); 1026 info.append(_HTML_HEADER); 1027 // Author(s) 1028 info.append(_tableOpening); 1029 info.append(_tr); 1030 info.append(_td20); 1031 info.append("<i>Author:</i> "); 1032 info.append(_tde); 1033 info.append(_td); 1034 info.append(manager.getAuthor()); 1035 if (manager.isInstanceDoc()) { 1036 DocManager nextTier = manager.getNextTier(); 1037 if (nextTier != null) { 1038 String nextTierAuthor = nextTier.getAuthor(); 1039 if (!nextTierAuthor.equals("No author given")) { 1040 info.append(" (<i>Class author:</i> "); 1041 info.append(nextTierAuthor); 1042 } 1043 } 1044 } 1045 info.append(_tde); 1046 info.append(_tre); 1047 // Version 1048 String version = manager.getVersion(); 1049 if (version != null) { 1050 info.append(_tr); 1051 info.append(_td20); 1052 info.append("<i>Version:</i> "); 1053 info.append(_tde); 1054 info.append(_td); 1055 info.append(version); 1056 info.append(_tde); 1057 info.append(_tre); 1058 } 1059 // Since 1060 String since = manager.getSince(); 1061 if (since != null) { 1062 info.append(_tr); 1063 info.append(_td20); 1064 info.append("<i>Since:</i> "); 1065 info.append(_tde); 1066 info.append(_td); 1067 info.append(since); 1068 info.append(_tde); 1069 info.append(_tre); 1070 } 1071 // Rating 1072 String rating = manager.getAcceptedRating(); 1073 if (rating != null) { 1074 info.append(_tr); 1075 info.append(_td20); 1076 info.append("<i>Rating:</i> "); 1077 info.append(_tde); 1078 info.append(_colorizeRating(rating)); 1079 info.append(_tre); 1080 } 1081 // End of table 1082 info.append(_tableClosing); 1083 info.append(_HTML_TAIL); 1084 authorPane.setText(info.toString()); 1085 // Set the view to the start of the text. 1086 authorPane.getCaret().setDot(0); 1087 1088 // Populate the "See Also" window. 1089 seeAlsoPane.setText(_HTML_HEADER + manager.getSeeAlso() + _HTML_TAIL); 1090 // Set the view to the start of the text. 1091 seeAlsoPane.getCaret().setDot(0); 1092 } 1093 1094 /** Return true if the specified object is intended to be hidden. 1095 * @param object The object. 1096 * @param name The property name. 1097 * @return True if the property is set. 1098 */ 1099 private boolean _isHidden(NamedObj object) { 1100 Attribute attribute = object.getAttribute("_hide"); 1101 1102 if (attribute == null) { 1103 return false; 1104 } 1105 1106 if (attribute instanceof Parameter) { 1107 try { 1108 Token token = ((Parameter) attribute).getToken(); 1109 1110 if (token instanceof BooleanToken) { 1111 if (!((BooleanToken) token).booleanValue()) { 1112 return false; 1113 } 1114 } 1115 } catch (IllegalActionException e) { 1116 // Ignore, using default of true. 1117 } 1118 } 1119 1120 return true; 1121 } 1122 1123 /** Populate the window displaying ports and parameters. 1124 * @param target The target object whose ports and parameters 1125 * will be described. 1126 * @param manager The doc manager. 1127 */ 1128 private void _populatePortsAndParametersTable(NamedObj target, 1129 DocManager manager) { 1130 // Create tables to contain the information about parameters and ports. 1131 // Start with parameters. 1132 boolean foundOne = false; 1133 StringBuffer table = new StringBuffer(); 1134 String parameterTableEntries = _getParameterEntries(target, manager); 1135 if (parameterTableEntries != null) { 1136 foundOne = true; 1137 table.append(parameterTableEntries); 1138 } 1139 // Next do the port-parameters. 1140 String portParameterTableEntries = _getPortParameterEntries(target, 1141 manager); 1142 if (portParameterTableEntries != null) { 1143 foundOne = true; 1144 table.append(portParameterTableEntries); 1145 } 1146 // Next do the ports. 1147 String portTableEntries = _getPortEntries(target, manager); 1148 if (portTableEntries != null) { 1149 foundOne = true; 1150 table.append(portTableEntries); 1151 } 1152 // Finally, insert all. 1153 StringBuffer info = new StringBuffer(); 1154 info.append(_HTML_HEADER); 1155 if (foundOne) { 1156 info.append(_tableOpening); 1157 info.append(table); 1158 info.append(_tableClosing); 1159 } else { 1160 info.append("No ports or parameters."); 1161 } 1162 info.append(_HTML_TAIL); 1163 1164 setText(info.toString()); 1165 // Set the view to the start of the text. 1166 pane.getCaret().setDot(0); 1167 } 1168 1169 /////////////////////////////////////////////////////////////////// 1170 //// private inner class //// 1171 1172 /** Listener for build menu commands. */ 1173 private class BuildMenuListener implements ActionListener { 1174 @Override 1175 public void actionPerformed(ActionEvent e) { 1176 try { 1177 Effigy effigy = (Effigy) getTableau().getContainer(); 1178 Tableau tableau = new DocBuilderTableau(effigy, 1179 "DocBuilderTableau"); 1180 tableau.show(); 1181 } catch (Throwable throwable) { 1182 MessageHandler.error("Cannot create build", throwable); 1183 } 1184 } 1185 } 1186 1187 /////////////////////////////////////////////////////////////////// 1188 //// private variables //// 1189 1190 /** The name of the application, usually from the _applicationName 1191 * StringAttribute in configuration.xml. 1192 * If the value is the empty string, then use the default 1193 * documentation in doc/codeDoc. 1194 */ 1195 private String _applicationName = ""; 1196 1197 /** Author window width. */ 1198 private static int _AUTHOR_WINDOW_WIDTH = 350; 1199 1200 /** The configuration specified in the constructor. */ 1201 private Configuration _configuration; 1202 1203 /** Bottom window height. */ 1204 private static int _BOTTOM_HEIGHT = 150; 1205 1206 /** Width of the description pane. */ 1207 private static int _DESCRIPTION_WIDTH = 500; 1208 1209 /** The font to use for No icon available message. */ 1210 private Font _font = new Font("SansSerif", Font.PLAIN, 14); 1211 1212 /** The graph pane. */ 1213 private GraphPane _graphPane; 1214 1215 /** HTML Header information. */ 1216 private static String _HTML_HEADER = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"" 1217 + "\"http://www.w3.org/TR/html4/loose.dtd\">" + "\n<html>\n<head>\n" 1218 + "<title>Ptolemy II Documentation</title>" 1219 + "<STYLE TYPE=\"text/css\">\n" + "<!--\n" 1220 + "h1, h2, h3, td, tr, body, p {font-family: Arial, Helvetica, sans-serif;}\n" 1221 + "-->\n" + "</STYLE>" + "</head><body>"; 1222 1223 private static String _HTML_TAIL = "</body></html>"; 1224 1225 /** The composite entity containing the icon. */ 1226 private CompositeEntity _iconContainer; 1227 1228 /** Icon window width. */ 1229 private static int _ICON_WINDOW_HEIGHT = 200; 1230 1231 /** Icon window width. */ 1232 private static int _ICON_WINDOW_WIDTH = 200; 1233 1234 /** The jgraph. */ 1235 private JGraph _jgraph; 1236 1237 /** Main window height. */ 1238 private static int _MAIN_WINDOW_HEIGHT = 250; 1239 1240 /** Main window width. */ 1241 private static int _MAIN_WINDOW_WIDTH = 700; 1242 1243 /** Padding in icon window. */ 1244 private static int _PADDING = 10; 1245 1246 /** Width of the see also pane. */ 1247 private static int _SEE_ALSO_WIDTH = 350; 1248 1249 /** Spacing between subwindows. */ 1250 private static int _SPACING = 5; 1251 1252 /** The target given in the constructor, if any. */ 1253 private NamedObj _target; 1254 1255 private static String _tr = "<tr valign=top>\n"; 1256 1257 private static String _tre = "</tr>\n"; 1258 1259 private static String _td = "<td>"; 1260 1261 private static String _td20 = "<td width=20%>"; 1262 1263 private static String _tdColSpan = "<td colspan=2>"; 1264 1265 private static String _tde = "</td>"; 1266 1267 private static String _tableOpening = "<table cellspacing=2 cellpadding=2>\n"; 1268 1269 private static String _tableClosing = "</table>"; 1270}