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() + "&nbsp; &nbsp; &nbsp; ("
753                    + target.getFullName() + ")";
754        } else {
755            if (manager.isInstanceDoc()) {
756                title = target.getName() + "&nbsp; &nbsp; &nbsp; (Instance of "
757                        + className + ")";
758            } else {
759                title = rootName + "&nbsp; &nbsp; &nbsp; (" + className + ")";
760            }
761        }
762        JEditorPane titlePane = new JEditorPane();
763        titlePane.setContentType("text/html");
764        titlePane.setEditable(false);
765        titlePane.setText(
766                _HTML_HEADER + "<H2>&nbsp; " + 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}