001/* Update annotations
002
003 Copyright (c) 2008-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.vergil.kernel.attributes;
029
030import ptolemy.kernel.util.Attribute;
031import ptolemy.kernel.util.Location;
032import ptolemy.kernel.util.NamedObj;
033import ptolemy.moml.MoMLParser;
034import ptolemy.moml.filter.MoMLFilterSimple;
035
036///////////////////////////////////////////////////////////////////
037//// UpdateAnnotations
038
039/**
040 Update the annotations.
041 <p>When this class is registered with the MoMLParser.addMoMLFilter()
042 method, it will cause MoMLParser to add a update annotations from
043 the older style:
044 <pre>
045 &lt;property name="annotation" class="ptolemy.kernel.util.Attribute"&gt;
046    &lt;property name="_hideName" class="ptolemy.kernel.util.SingletonAttribute"&gt;
047    &lt;/property&gt;
048    &lt;property name="_iconDescription" class="ptolemy.kernel.util.SingletonConfigurableAttribute"&gt;
049          &lt;configure&gt;&lt;svg&gt;&lt;text x="20" y="20" style="font-size:14; font-family:SansSerif; fill:blue"&gt;Create a state machine here (and ports, if needed) and
050create refinements for the states. Each refinement needs a director.
051For hybrid system models, use the CTEmbeddedDirector.&lt;/text&gt;&lt;/svg&gt;&lt;/configure&gt;
052    &lt;/property&gt;
053    &lt;property name="_location" class="ptolemy.kernel.util.Location" value="75.0, 65.0"&gt;
054    &lt;/property&gt;
055    &lt;property name="_controllerFactory" class="ptolemy.vergil.basic.NodeControllerFactory"&gt;
056    &lt;/property&gt;
057    &lt;property name="_editorFactory" class="ptolemy.vergil.toolbox.AnnotationEditorFactory"&gt;
058    &lt;/property&gt;
059 &lt;/property&gt;
060 </pre>
061 to
062 <pre>
063
064    &lt;property name="annotation" class="ptolemy.vergil.kernel.attributes.TextAttribute"&gt;
065        &lt;property name="text" class="ptolemy.kernel.util.StringAttribute" value="Create a state machine here (and ports, if needed) and&amp;#10;create refinements for the states. Each refinement needs a director.&amp;#10;For hybrid system models, use the CTEmbeddedDirector.&lt;/text&gt;&lt;/svg&gt;&lt;/configure&gt;
066        &lt;/property&gt;
067        &lt;property name="_location" class="ptolemy.kernel.util.Location" value="[75.0, 65.0]"&gt;
068        &lt;/property&gt;
069    &lt;/property&gt;
070 </pre>
071
072 @author Christopher Brooks
073 @version $Id$
074 @since Ptolemy II 8.0
075 @Pt.ProposedRating Red (cxh)
076 @Pt.AcceptedRating Red (cxh)
077 */
078public class UpdateAnnotations extends MoMLFilterSimple {
079
080    /** Update annotations by removing old annotations and replacing
081     *  them with new annotation. If the attributeName is "name" and
082     *  attributeValue begins with with "annotation", then replace the
083     *  property with a TextAttribute
084     *
085     *  @param container  The container for this attribute.
086     *   in this method.
087     *  @param element The XML element name.
088     *  @param attributeName The name of the attribute.
089     *  @param attributeValue The value of the attribute.
090     *  @param xmlFile The file currently being parsed.
091     *  @return the value of the attributeValue argument.
092     */
093    @Override
094    public String filterAttributeValue(NamedObj container, String element,
095            String attributeName, String attributeValue, String xmlFile) {
096        //System.out.println("filterAttributeValue: " + container + "\t"
097        //       +  attributeName + "\t" + attributeValue);
098
099        if (attributeValue == null) {
100            // attributeValue == null is fairly common, so we check for
101            // that first
102            return null;
103        }
104
105        if (attributeName.equals("name")) {
106            if (attributeValue.startsWith("annotation")
107                    || attributeValue.contains("annotation")
108                            && attributeValue.contains(":")) {
109
110                // We found a line like
111                // <property name="annotation1"
112                //           class="ptolemy.kernel.util.Attribute">
113                _currentlyProcessingAnnotation = true;
114                _currentAnnotationContainerFullName = container.getFullName();
115                _currentAnnotationFullName = container.getFullName() + "."
116                        + attributeValue;
117                _currentlyProcessingLocation = false;
118            } else {
119                if (_currentlyProcessingAnnotation) {
120                    if (attributeValue.equals("_location")) {
121                        // We are processing a location
122                        _currentlyProcessingLocation = true;
123                    } else {
124                        // Saw another name, done processing location
125                        _currentlyProcessingLocation = false;
126                    }
127                }
128            }
129        } else if (_currentlyProcessingAnnotation) {
130            if (attributeName.equals("class")
131                    && attributeValue.equals(
132                            "ptolemy.vergil.kernel.attributes.TextAttribute")
133                    && container.getFullName()
134                            .equals(_currentAnnotationContainerFullName)) {
135                // We have an annotation, but it is a TextAttribute, so we are done.
136                _reset();
137                return attributeValue;
138            }
139            if (_currentlyProcessingLocation && attributeName.equals("value")) {
140                // Found the location
141                _currentlyProcessingLocation = false;
142            }
143        }
144        if (_currentlyProcessingAnnotation) {
145            if (container != null) {
146                if (!container.getFullName()
147                        .equals(_currentAnnotationFullName)) {
148                    if (_currentAnnotationFullName == null
149                            || (!_currentAnnotationFullName
150                                    .startsWith(container.getFullName())
151                                    && !container.getFullName().startsWith(
152                                            _currentAnnotationFullName))) {
153                        // We found another class in a different container
154                        // while handling an annotation.
155                        _reset();
156                    }
157                }
158            }
159        }
160
161        return attributeValue;
162    }
163
164    /** Make modifications to the specified container, which is
165     *  defined in a MoML element with the specified name.
166     *  @param container The object created by this element.
167     *  @param elementName The element name.
168     *  @param currentCharData The character data, which appears
169     *   only in the doc and configure elements
170     *  @param xmlFile The file currently being parsed.
171     *  @exception Exception if there is a problem substituting
172     *  in the new value.
173     */
174    @Override
175    public void filterEndElement(NamedObj container, String elementName,
176            StringBuffer currentCharData, String xmlFile) throws Exception {
177
178        if (!_currentlyProcessingAnnotation) {
179            return;
180        }
181
182        // Useful for debugging
183        //         System.out.println("filterEndElement: " + container + "\t"
184        //                 +  elementName
185        //                           + " container.fn: " +  container.getFullName()
186        //                           + " _cafn: " + _currentAnnotationFullName
187        //                           + " _cacfn: " + _currentAnnotationContainerFullName
188        //                           + "\n" + currentCharData);
189
190        // We have three cases:
191        // 1) We have a configure, so we create the TextAttribute and
192        //    populate it.
193        // 2) We have a Location, so we possibly create the TextAttribute
194        //    and we add a Location
195        // 3) We are at the end of the annotation, so we delete the
196        //    old annotation
197
198        if (elementName.equals("configure")) {
199            // 1) We have a configure, so we create the TextAttribute and
200            //    populate it.
201            Attribute currentAttribute = (Attribute) container;
202            NamedObj parentContainer = currentAttribute.getContainer();
203            if (currentAttribute.getName().equals("_smallIconDescription")) {
204                // Skip the smallIconDescription it is probably -A-
205                return;
206            }
207            if (!(parentContainer instanceof Attribute)) {
208                // ptolemy.domains.modal.modal.ModalController cannot be cast to ptolemy.kernel.util.Attribute
209                return;
210            }
211
212            if (_textAttribute == null) {
213                //System.out.println("UpdateAnnotation: create TextAttribute 1");
214                NamedObj grandparentContainer = currentAttribute.getContainer()
215                        .getContainer();
216                //((Attribute)parentContainer).setContainer(null);
217
218                // Use a new name instead of annotation and avoid the HideAnnotationNames filter
219                _textAttribute = new TextAttribute(grandparentContainer,
220                        grandparentContainer.uniqueName("AnnotationUpdated"));
221                //                        grandparentContainer.uniqueName(_currentAnnotationName));
222            }
223
224            // Clean up the character data: remove svg and text tags
225            String charData = currentCharData.toString().trim();
226            if (charData.startsWith("<svg>")) {
227                charData = charData.substring(5).trim();
228            }
229            if (charData.endsWith("</svg>")) {
230                charData = charData.substring(0, charData.length() - 6).trim();
231            }
232            if (charData.endsWith("</text>")) {
233                charData = charData.substring(0, charData.length() - 7).trim();
234            }
235
236            // Map colors
237            if (charData.contains(" fill:")) {
238                if (charData.contains(" fill:black")) {
239                    _textAttribute.textColor
240                            .setExpression("{0.0, 0.0, 0.0, 1.0}");
241                }
242                if (charData.contains(" fill:darkgray")
243                        || charData.contains(" fill:gray")) {
244                    _textAttribute.textColor
245                            .setExpression("{0.2, 0.2, 0.2, 1.0}");
246                }
247                if (charData.contains(" fill:green")) {
248                    _textAttribute.textColor
249                            .setExpression("{0.0, 1.0, 0.0, 1.0}");
250                }
251                if (charData.contains(" fill:red")) {
252                    _textAttribute.textColor
253                            .setExpression("{1.0, 0.0, 0.0, 1.0}");
254                }
255            }
256
257            // Map font sizes
258            if (charData.contains("font-size:")) {
259                if (charData.contains("font-size:12")) {
260                    _textAttribute.textSize.setExpression("12");
261                }
262                if (charData.contains("font-size:16")) {
263                    _textAttribute.textSize.setExpression("16");
264                }
265                if (charData.contains("font-size:18")) {
266                    _textAttribute.textSize.setExpression("18");
267                }
268            }
269
270            charData = charData.replaceAll("<text.*[^>]>", "");
271            //System.out.println("UpdateAnnotation: Setting TextAttribute");
272            _textAttribute.text.setExpression(charData);
273        }
274
275        if (container instanceof Location) {
276            // 2) We have a Location, so we possibly create the TextAttribute
277            //    and we add a Location.
278            Attribute currentAttribute = (Attribute) container;
279            if (_textAttribute == null) {
280                //System.out.println("UpdateAnnotation: create TextAttribute 2");
281                //NamedObj parentContainer = currentAttribute.getContainer();
282                NamedObj grandparentContainer = currentAttribute.getContainer()
283                        .getContainer();
284                //((Attribute)parentContainer).setContainer(null);
285                // Use a new name instead of annotation and avoid the HideAnnotationNames filter
286                _textAttribute = new TextAttribute(grandparentContainer,
287                        grandparentContainer.uniqueName("AnnotationUpdated"));
288            }
289            Location location = new Location(_textAttribute, "_location");
290
291            Location oldLocation = (Location) container;
292            oldLocation.validate();
293            //System.out.println("UpdateAnnotation: setting Location " + oldLocation.getExpression());
294            double[] xyLocation = oldLocation.getLocation();
295
296            xyLocation[0] += 15.0;
297            location.setLocation(xyLocation);
298
299            location.validate();
300        }
301
302        if (container != null
303                && container.getFullName().equals(_currentAnnotationFullName)
304                && _textAttribute != null) {
305            // 3) We are at the end of the annotation, so we delete the
306            //    old annotation by setting its container to null.
307            //System.out.println("UpdateAnnotation: closing up " + _textAttribute.getContainer().getFullName() + "\n" + _textAttribute.exportMoML());
308            //NamedObj top = _textAttribute.toplevel();
309            Attribute currentAttribute = (Attribute) container;
310            //String name = currentAttribute.getName();
311            currentAttribute.setContainer(null);
312            MoMLParser.setModified(true);
313            _reset();
314            //System.out.println("UpdateAnnotation: after reset" + top.exportMoML());
315        }
316    }
317
318    /** Return a string that describes what the filter does.
319     *  @return the description of the filter that ends with a newline.
320     */
321    @Override
322    public String toString() {
323        return getClass().getName() + ": Update annotation to new style\n";
324    }
325
326    ///////////////////////////////////////////////////////////////////
327    ////                         private variables                 ////
328
329    /** Reset the internal state of the filter. */
330    private void _reset() {
331        _currentlyProcessingAnnotation = false;
332        _currentAnnotationContainerFullName = null;
333        _currentAnnotationFullName = null;
334        _currentlyProcessingLocation = false;
335        _textAttribute = null;
336    }
337
338    ///////////////////////////////////////////////////////////////////
339    ////                         private variables                 ////
340    // True if we are currently processing an annotation.
341    private boolean _currentlyProcessingAnnotation = false;
342
343    // The the full name of the container of the annotation we are
344    // currently processing.
345    private String _currentAnnotationContainerFullName;
346
347    // The the full name of the annotation we are currently processing.
348    private String _currentAnnotationFullName;
349
350    // True if we are currently processing a location inside an annotation.
351    private boolean _currentlyProcessingLocation = false;
352
353    // The TextAttribute that is being created.
354    private TextAttribute _textAttribute;
355}