001/* Attribute specifying a title for a model or component in a model.
002
003 Copyright (c) 2011-2018 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 */
028
029package ptolemy.vergil.basic.export.web;
030
031import java.awt.Color;
032import java.awt.Font;
033import java.awt.GraphicsEnvironment;
034import java.util.Collection;
035
036import javax.swing.SwingConstants;
037
038import ptolemy.actor.gui.ColorAttribute;
039import ptolemy.data.BooleanToken;
040import ptolemy.data.IntToken;
041import ptolemy.data.expr.Parameter;
042import ptolemy.data.expr.SingletonParameter;
043import ptolemy.data.expr.StringParameter;
044import ptolemy.data.type.BaseType;
045import ptolemy.kernel.util.Attribute;
046import ptolemy.kernel.util.ConfigurableAttribute;
047import ptolemy.kernel.util.IllegalActionException;
048import ptolemy.kernel.util.NameDuplicationException;
049import ptolemy.kernel.util.NamedObj;
050import ptolemy.kernel.util.Settable;
051import ptolemy.kernel.util.SingletonAttribute;
052import ptolemy.kernel.util.Workspace;
053import ptolemy.vergil.icon.TextIcon;
054import ptolemy.vergil.toolbox.VisibleParameterEditorFactory;
055
056///////////////////////////////////////////////////////////////////
057//// Title
058/**
059 * Attribute specifying a title for a model or a component in a model.
060 * This attribute provides a visual title in the model,
061 * rendered more suitably for a title than a normal annotation.
062 * Moreover, if you export to web, this is used as the title for the
063 * containing component and for any exported web page.
064 * By default, the title is not shown on the web page except
065 * as part of the image of the model. If you wish for the title
066 * to also be shown in the HTML text before the image, then set
067 * the <i>showTitleInHTML</i> parameter to true.
068 *
069 * @author Edward A. Lee
070 * @version $Id$
071 * @since Ptolemy II 10.0
072 * @Pt.ProposedRating Red (cxh)
073 * @Pt.AcceptedRating Red (cxh)
074 */
075public class Title extends StringParameter implements WebExportable {
076
077    /** Create an instance of this parameter.
078     *  @param container The container.
079     *  @param name The name.
080     *  @exception IllegalActionException If the superclass throws it.
081     *  @exception NameDuplicationException If the superclass throws it.
082     */
083    public Title(NamedObj container, String name)
084            throws IllegalActionException, NameDuplicationException {
085        super(container, name);
086
087        // Unfortunately, much of this class is copied from AbstractTextAttribute,
088        // but we have no choice because we need it to be a StringParameter.
089
090        _icon = new TextIcon(this, "_icon");
091        _icon.setPersistent(false);
092        _icon.setIconText("T");
093
094        showTitleInHTML = new Parameter(this, "showTitleInHTML");
095        showTitleInHTML.setExpression("false");
096        showTitleInHTML.setTypeEquals(BaseType.BOOLEAN);
097
098        textSize = new Parameter(this, "textSize");
099        textSize.setExpression("24");
100        textSize.setTypeEquals(BaseType.INT);
101        textSize.addChoice("9");
102        textSize.addChoice("10");
103        textSize.addChoice("11");
104        textSize.addChoice("12");
105        textSize.addChoice("14");
106        textSize.addChoice("18");
107        textSize.addChoice("24");
108        textSize.addChoice("32");
109
110        textColor = new ColorAttribute(this, "textColor");
111        textColor.setExpression("{0.0, 0.0, 0.0, 1.0}");
112
113        // Get font family names from the Font class in Java.
114        // This includes logical font names, per Font class in Java:
115        // Dialog, DialogInput, Monospaced, Serif, SansSerif, or Symbol.
116        fontFamily = new StringParameter(this, "fontFamily");
117        fontFamily.setExpression("SansSerif");
118
119        String[] families = GraphicsEnvironment.getLocalGraphicsEnvironment()
120                .getAvailableFontFamilyNames();
121
122        for (String familie : families) {
123            fontFamily.addChoice(familie);
124        }
125
126        bold = new Parameter(this, "bold");
127        bold.setExpression("false");
128        bold.setTypeEquals(BaseType.BOOLEAN);
129
130        italic = new Parameter(this, "italic");
131        italic.setExpression("false");
132        italic.setTypeEquals(BaseType.BOOLEAN);
133
134        center = new Parameter(this, "center");
135        center.setToken(BooleanToken.FALSE);
136        center.setTypeEquals(BaseType.BOOLEAN);
137
138        // Hide the name.
139        SingletonParameter hide = new SingletonParameter(this, "_hideName");
140        hide.setToken(BooleanToken.TRUE);
141        hide.setVisibility(Settable.EXPERT);
142
143        // No need to display any parameters when the "_showParameters"
144        // preference asks for such display because presumably all the
145        // parameters are reflected in the visual display already.
146        Parameter hideAllParameters = new Parameter(this, "_hideAllParameters");
147        hideAllParameters.setVisibility(Settable.EXPERT);
148        hideAllParameters.setExpression("true");
149
150        // The following ensures that double click edits the text of the title.
151        new VisibleParameterEditorFactory(this, "_editorFactory");
152
153        // Add a small icon.
154        ConfigurableAttribute smallIcon = new ConfigurableAttribute(this,
155                "_smallIconDescription");
156        try {
157            smallIcon.configure(null, null,
158                    "<svg><text x=\"20\" style=\"font-size:14; font-family:SansSerif; fill:blue\" y=\"20\">title</text></svg>");
159        } catch (Exception e) {
160            // Show exception on the console. Should not occur.
161            e.printStackTrace();
162        }
163    }
164
165    ///////////////////////////////////////////////////////////////////
166    ////                         parameters                        ////
167
168    /** A boolean indicating whether the font should be bold.
169     *  This defaults to false.
170     */
171    public Parameter bold;
172
173    /** A boolean parameter that controls whether the origin of the text is
174     *  center (if true) or north-west.
175     */
176    public Parameter center;
177
178    /** The font family. This is a string that defaults to "SansSerif".
179     */
180    public StringParameter fontFamily;
181
182    /** A boolean indicating whether the font should be italic.
183     *  This defaults to false.
184     */
185    public Parameter italic;
186
187    /** If set to true, then the title given by this parameter
188     *  will be shown in the HTML prior to the image of the model
189     *  (as well as in the image of the model, if it is visible
190     *  when the export to web occurs). This is a boolean that
191     *  defaults to false.
192     */
193    public Parameter showTitleInHTML;
194
195    /** The text color.  This is a string representing an array with
196     *  four elements, red, green, blue, and alpha, where alpha is
197     *  transparency. The default is "{0.0, 0.0, 0.0, 1.0}", which
198     *  represents an opaque black.
199     */
200    public ColorAttribute textColor;
201
202    /** The text size.  This is an int that defaults to 14.
203     */
204    public Parameter textSize;
205
206    ///////////////////////////////////////////////////////////////////
207    ////                         public methods                    ////
208
209    /** React to a changes in the attributes by changing the icon.
210     *  @param attribute The attribute that changed.
211     *  @exception IllegalActionException If the change is not acceptable
212     *   to this container (should not be thrown).
213     */
214    @Override
215    public void attributeChanged(Attribute attribute)
216            throws IllegalActionException {
217        if (attribute == center) {
218            if (((BooleanToken) center.getToken()).booleanValue()) {
219                _icon.setAnchor(SwingConstants.CENTER);
220            } else {
221                _icon.setAnchor(SwingConstants.NORTH_WEST);
222            }
223        } else if ((attribute == fontFamily || attribute == textSize
224                || attribute == bold || attribute == italic)
225                && !_inAttributeChanged) {
226            try {
227                // Prevent redundant actions here... When we evaluate the
228                // _other_ attribute here (whichever one did _not_ trigger
229                // this call, it will likely trigger another call to
230                // attributeChanged(), which will result in this action
231                // being performed twice.
232                _inAttributeChanged = true;
233
234                int sizeValue = ((IntToken) textSize.getToken()).intValue();
235                String familyValue = fontFamily.stringValue();
236                int styleValue = Font.PLAIN;
237
238                if (((BooleanToken) bold.getToken()).booleanValue()) {
239                    styleValue = styleValue | Font.BOLD;
240                }
241
242                if (((BooleanToken) italic.getToken()).booleanValue()) {
243                    styleValue = styleValue | Font.ITALIC;
244                }
245
246                Font fontValue = new Font(familyValue, styleValue, sizeValue);
247                _icon.setFont(fontValue);
248            } finally {
249                _inAttributeChanged = false;
250            }
251        } else if (attribute == textColor) {
252            Color colorValue = textColor.asColor();
253            _icon.setTextColor(colorValue);
254        } else {
255            super.attributeChanged(attribute);
256        }
257    }
258
259    /** Clone the object into the specified workspace.
260     *  @param workspace The workspace for the new object.
261     *  @return A new AbstractTextAttribute.
262     *  @exception CloneNotSupportedException If any of the attributes
263     *   cannot be cloned.
264     */
265    @Override
266    public Object clone(Workspace workspace) throws CloneNotSupportedException {
267        Title result = (Title) super.clone(workspace);
268        result._icon = (TextIcon) result.getAttribute("_icon");
269        return result;
270    }
271
272    /** A title is of type text/html.
273     *
274     * @return The string text/html
275     */
276    @Override
277    public String getMimeType() {
278        return "text/html";
279    }
280
281    /** Return true, since new title content should overwrite old title content.
282     *
283     * @return True, since new title content should overwrite old title content.
284     */
285    @Override
286    public boolean isOverwriteable() {
287        return true;
288    }
289
290    /** Move this object to the first position in the list
291     *  of attributes of the container. This overrides the base
292     *  class to create  an attribute named "_renderFirst" and to
293     *  remove an attribute named "_renderLast", if it is present.
294     *  This attribute is recognized by vergil, which then renders this
295     *  attribute before entities, connections, and other attributes.
296     *  This method gets write access on workspace
297     *  and increments the version.
298     *  @return The index of the specified object prior to moving it,
299     *   or -1 if it is not moved.
300     *  @exception IllegalActionException If this object has
301     *   no container.
302     */
303    @Override
304    public int moveToFirst() throws IllegalActionException {
305        try {
306            new SingletonAttribute(this, "_renderFirst");
307            Attribute renderLast = getAttribute("_renderLast");
308
309            if (renderLast != null) {
310                renderLast.setContainer(null);
311            }
312        } catch (NameDuplicationException e) {
313            // Ignore.  This will result in a rendering error,
314            // but that is better than trashing user data.
315        }
316
317        return super.moveToFirst();
318    }
319
320    /** Move this object to the last position in the list
321     *  of attributes of the container. This overrides the base
322     *  class to create  an attribute named "_renderLast" and to
323     *  remove an attribute named "_renderFirst" if it is present.
324     *  This attribute is recognized by vergil, which then renders this
325     *  attribute after entities, connections, and other attributes.
326     *  This method gets write access on workspace
327     *  and increments the version.
328     *  @return The index of the specified object prior to moving it,
329     *   or -1 if it is not moved.
330     *  @exception IllegalActionException If this object has
331     *   no container.
332     */
333    @Override
334    public int moveToLast() throws IllegalActionException {
335        try {
336            new SingletonAttribute(this, "_renderLast");
337            Attribute renderFirst = getAttribute("_renderFirst");
338
339            if (renderFirst != null) {
340                renderFirst.setContainer(null);
341            }
342        } catch (NameDuplicationException e) {
343            // Ignore.  This will result in a rendering error,
344            // but that is better than trashing user data.
345        }
346
347        return super.moveToLast();
348    }
349
350    /** Return a title for the model.  The title can be returned as an attribute
351     *  or an element, but not a document.
352     *
353     *  @param exporter  The web exporter to which to write content.
354     *  @exception IllegalActionException If there is a problem creating
355     *  the content or setting the attribute.
356     */
357    @Override
358    public void provideContent(WebExporter exporter)
359            throws IllegalActionException {
360        // Provide a WebElement containing the title.  Title does not
361        // provide any WebAttributes.
362        //_provideElements(exporter);
363        _provideAttributes(exporter);
364    }
365
366    /** Provide a title for this object to the specified web exporter.
367     *
368     *  @param exporter The WebExporter to add content to
369     *  @exception IllegalActionException If something is wrong with the
370     *   specification of the content.
371     */
372    protected void _provideAttributes(WebExporter exporter)
373            throws IllegalActionException {
374
375        // FIXME:  Refactor so we don't need this method
376        exporter.setTitle(stringValue(),
377                ((BooleanToken) showTitleInHTML.getToken()).booleanValue());
378
379        // Create a WebAttribute for title and add to exporter.
380        // Content should only be added once (onceOnly -> true).
381        /* title attribute is now used for displaying parameter table.
382        WebAttribute webAttribute = WebAttribute.createWebAttribute(
383                getContainer(), "titleWebAttribute", "title");
384        webAttribute.setExpression(stringValue());
385        exporter.defineAttribute(webAttribute, true);
386         */
387    }
388
389    /** Provide the <title> </title> element to the specified web exporter.
390     *  This element should be included in the <head> section.
391     *
392     *  @param exporter The WebExporter to add content to
393     *  @exception IllegalActionException If something is wrong with the
394     *   specification of the content.
395     */
396
397    /*
398    protected void _provideElements(WebExporter exporter)
399        throws IllegalActionException {
400
401        // Create a WebElement for title and add to exporter.
402        // Content should only be added once (onceOnly -> true).
403        WebElement webElement = WebElement.
404            createWebElement(getContainer(), "titleWebElement", "title");
405        webElement.setParent(WebElement.HEAD);
406        webElement.setExpression(stringValue());
407        exporter.defineElement(webElement, true);
408    }
409     */
410
411    /** Override the base class to set the text to be displayed
412     *  in the icon.
413     */
414    @Override
415    public Collection validate() throws IllegalActionException {
416        Collection result = super.validate();
417        _icon.setText(stringValue());
418        return result;
419    }
420
421    ///////////////////////////////////////////////////////////////////
422    ////                       protected members                   ////
423
424    /** The text icon. */
425    protected TextIcon _icon;
426
427    ///////////////////////////////////////////////////////////////////
428    ////                         private variables                 ////
429    private boolean _inAttributeChanged = false;
430}