001/* Interface for parameters that provide web export content. 002 003 Copyright (c) 2011-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 */ 028 029package ptolemy.vergil.basic.export.web; 030 031import java.awt.Frame; 032import java.awt.print.PrinterException; 033import java.io.File; 034import java.io.FileOutputStream; 035import java.io.IOException; 036import java.io.OutputStream; 037import java.util.HashMap; 038import java.util.HashSet; 039import java.util.List; 040import java.util.Map; 041import java.util.Set; 042 043import ptolemy.actor.TypedActor; 044import ptolemy.actor.gui.Configuration; 045import ptolemy.actor.gui.Effigy; 046import ptolemy.actor.gui.PtolemyEffigy; 047import ptolemy.actor.gui.Tableau; 048import ptolemy.domains.modal.kernel.FSMActor; 049import ptolemy.domains.modal.kernel.State; 050import ptolemy.domains.modal.modal.ModalModel; 051import ptolemy.gui.ImageExportable; 052import ptolemy.kernel.util.IllegalActionException; 053import ptolemy.kernel.util.Instantiable; 054import ptolemy.kernel.util.NameDuplicationException; 055import ptolemy.kernel.util.NamedObj; 056import ptolemy.util.StringUtilities; 057import ptolemy.vergil.basic.ExportParameters; 058import ptolemy.vergil.basic.HTMLExportable; 059 060/////////////////////////////////////////////////////////////////// 061//// LinkToOpenTableaux 062/** 063 * A parameter specifying default hyperlink to associate 064 * with icons in model. Putting this into a model causes a hyperlink 065 * to be associated with each icon (as specified by the <i>include</i> 066 * and <i>instancesOf</i> parameters) that is associated to an 067 * open Tableau. 068 * If the the frame associated with the tableau implements 069 * HTMLExportable, then this is an ordinary link to 070 * the HTML exported by the frame. If it instead 071 * implements ImageExportable, then this a link that 072 * brings up the image in a lightbox. 073 * <p> 074 * This parameter is designed to be included in a Configuration file 075 * to specify global default behavior for export to Web. Just put 076 * it in the top level of the Configuration, and this hyperlink 077 * will be provided by default. 078 * <p> 079 * Note that this class works closely with 080 * {@link ptolemy.vergil.basic.export.html.ExportHTMLAction}. 081 * It will not work if the {@link WebExporter} provided to its 082 * methods is not an instance of ExportHTMLAction. 083 * 084 * @author Edward A. Lee 085 * @version $Id$ 086 * @since Ptolemy II 10.0 087 * @Pt.ProposedRating Red (cxh) 088 * @Pt.AcceptedRating Red (cxh) 089 */ 090public class LinkToOpenTableaux extends DefaultIconLink { 091 092 /** Create an instance of this parameter. 093 * @param container The container. 094 * @param name The name. 095 * @exception IllegalActionException If the superclass throws it. 096 * @exception NameDuplicationException If the superclass throws it. 097 */ 098 public LinkToOpenTableaux(NamedObj container, String name) 099 throws IllegalActionException, NameDuplicationException { 100 super(container, name); 101 102 _exportedClassDefinitions = null; 103 } 104 105 /////////////////////////////////////////////////////////////////// 106 //// public methods //// 107 108 /** Provide content to the specified web exporter to be 109 * included in a web page for the container of this object. 110 * This overrides the base class to ensure that each class 111 * definition is exported only once. 112 * @exception IllegalActionException If a subclass throws it. 113 */ 114 @Override 115 public void provideContent(WebExporter exporter) 116 throws IllegalActionException { 117 try { 118 _exportedClassDefinitions = new HashSet<NamedObj>(); 119 super.provideContent(exporter); 120 } finally { 121 _exportedClassDefinitions = null; 122 } 123 } 124 125 /////////////////////////////////////////////////////////////////// 126 //// protected methods //// 127 128 /** Override the base class to generate a web page or an image 129 * file for the specified object, if appropriate, and to provide 130 * the href, target, and class attributes to the area attribute 131 * associated with the object. 132 * @param exporter The exporter. 133 * @param object The Ptolemy II object. 134 * @exception IllegalActionException If evaluating parameters fails. 135 */ 136 @Override 137 protected void _provideEachAttribute(WebExporter exporter, NamedObj object) 138 throws IllegalActionException { 139 WebAttribute webAttribute; 140 141 // Create a table of effigies associated with any 142 // open submodel or plot. 143 Map<NamedObj, PtolemyEffigy> openEffigies = new HashMap<NamedObj, PtolemyEffigy>(); 144 Tableau myTableau = exporter.getFrame().getTableau(); 145 Effigy myEffigy = (Effigy) myTableau.getContainer(); 146 List<PtolemyEffigy> effigies = myEffigy.entityList(PtolemyEffigy.class); 147 for (PtolemyEffigy effigy : effigies) { 148 // The following will, for example, associate a plotter with effigy 149 // for the open plot. 150 openEffigies.put(effigy.getModel(), effigy); 151 } 152 // In order to ensure that all open windows have hyperlinks 153 // to them, fix up the openEffies data structure so that if 154 // the container of a plotter (for example) is not associated 155 // with an open effigy, then we attempt to associate it with 156 // the container instead, or the container's container, until 157 // we get to the top level. 158 Map<NamedObj, PtolemyEffigy> containerAssociations = new HashMap<NamedObj, PtolemyEffigy>(); 159 if (openEffigies.size() > 0) { 160 for (NamedObj component : openEffigies.keySet()) { 161 if (component == null) { 162 // This is caused by $PTII/bin/ptinvoke 163 // ptolemy.vergil.basic.export.ExportModel -force 164 // htm -run -openComposites -whiteBackground 165 // ptolemy/actor/gt/demo/ModelExecution/ModelExecution.xml 166 // $PTII/ptolemy/actor/gt/demo/ModelExecution/ModelExecution 167 System.out.println( 168 "Warning: LinkToOpenTableaux._provideEachAttribute() " 169 + object.getFullName() 170 + ", an open effigy was null?"); 171 } else { 172 NamedObj container = component.getContainer(); 173 while (container != null) { 174 if (openEffigies.get(container) != null) { 175 // The container of a plotter (say) has 176 // an open effigy, so there will be a link 177 // to the plot. 178 // Exporting 179 // ptolemy/data/ontologies/demo/CarTracking/CarTracking.xml 180 // was hanging in a tight loop here. 181 container = container.getContainer(); 182 continue; 183 } 184 // The container of the plotter (say) does 185 // not have an open effigy. Associate this 186 // container and then try the container's container. 187 containerAssociations.put(container, 188 openEffigies.get(component)); 189 container = container.getContainer(); 190 } 191 } 192 } 193 } 194 openEffigies.putAll(containerAssociations); 195 196 PtolemyEffigy effigy = openEffigies.get(object); 197 // The hierarchy of effigies does not always follow the model hierarchy 198 // (e.g., a PlotEffigy will be contained by the top-level effigy for the 199 // model for some reason), so if the effigy is null, we search 200 // nonetheless for an effigy. 201 if (effigy == null) { 202 Effigy candidate = Configuration.findEffigy(object); 203 if (candidate instanceof PtolemyEffigy) { 204 effigy = (PtolemyEffigy) candidate; 205 } 206 } 207 try { 208 if (effigy != null) { 209 // _linkTo() recursively calls writeHTML(); 210 _linkTo(exporter, effigy, object, object, 211 exporter.getExportParameters()); 212 } else { 213 // If the object is a State, we still have work to do. 214 if (object instanceof State) { 215 // In a ModalModel, object is a State 216 // inside the _Controller. But the effigy is stored 217 // under the refinements of that state, which have the 218 // same container as the _Controller. 219 try { 220 TypedActor[] refinements = ((State) object) 221 .getRefinement(); 222 // FIXME: There may be more 223 // than one refinement. How to open all of them? 224 // We have only one link. For now, just open the first one. 225 if (refinements != null && refinements.length > 0) { 226 effigy = openEffigies.get(refinements[0]); 227 if (effigy != null) { 228 // _linkTo() recursively calls writeHTML(); 229 _linkTo(exporter, effigy, object, 230 (NamedObj) refinements[0], 231 exporter.getExportParameters()); 232 } 233 } 234 } catch (IllegalActionException e) { 235 // Ignore errors here. Just don't export this refinement. 236 } 237 } else if (object instanceof Instantiable) { 238 // There is no open effigy, but the object might 239 // be an instance of a class where the class definition 240 // is open. Look for that. 241 Instantiable parent = ((Instantiable) object).getParent(); 242 if (parent instanceof NamedObj) { 243 // Avoid doing the export of a class definition 244 // multiple times. 245 if (_exportedClassDefinitions.contains(parent)) { 246 // Have already exported the class definition. Just 247 // need to add the hyperlink. 248 webAttribute = WebAttribute.createWebAttribute( 249 object, "hrefWebAttribute", "href"); 250 webAttribute.setExpression( 251 parent.getName() + "/index.html"); 252 exporter.defineAttribute(webAttribute, true); 253 } else { 254 // Have not exported the class definition. Do so now. 255 _exportedClassDefinitions.add((NamedObj) parent); 256 Effigy classEffigy = Configuration 257 .findEffigy((NamedObj) parent); 258 if (classEffigy instanceof PtolemyEffigy) { 259 // _linkTo() recursively calls writeHTML(); 260 _linkTo(exporter, (PtolemyEffigy) classEffigy, 261 object, (NamedObj) parent, 262 exporter.getExportParameters()); 263 } 264 } 265 } 266 } 267 } 268 } catch (Throwable throwable) { 269 throw new IllegalActionException(this, throwable, 270 "Failed to generate sub-web-page. "); 271 } 272 } 273 274 /////////////////////////////////////////////////////////////////// 275 //// private methods //// 276 277 /** Return the title of the specified object. If it contains a parameter 278 * of class {@link Title}, then return the title specified by that class. 279 * Otherwise, if the object is an instance of FSMActor contained by 280 * a ModalModel, then return the 281 * name of its container, not the name of the FSMActor. 282 * Otherwise, return the name of the object. 283 * @param object The object. 284 * @return A title for the object. 285 * @exception IllegalActionException If accessing the title attribute fails.. 286 */ 287 private static String _getTitleText(NamedObj object) 288 throws IllegalActionException { 289 // If the object contains an IconLink parameter, then use that instead 290 // of the default. If it has more than one, then just use the first one. 291 List<Title> links = object.attributeList(Title.class); 292 if (links != null && links.size() > 0) { 293 return links.get(0).stringValue(); 294 } 295 if (object instanceof FSMActor) { 296 NamedObj container = object.getContainer(); 297 if (container instanceof ModalModel) { 298 return container.getName(); 299 } 300 } 301 return object.getName(); 302 } 303 304 /** For the specified effigy, define the relevant href, target, 305 * and class area attributes 306 * if the effigy has any open tableaux and those have frames 307 * that implement either HTMLExportable or ImageExportable. 308 * As a side effect, this may generate HTML or image 309 * files or subdirectories in the directory given in the specified 310 * parameters. 311 * @param exporter The exporter. 312 * @param effigy The effigy. 313 * @param sourceObject The source Ptolemy II object (link from). 314 * @param destinationObject The destination object (link to, same as sourceObject, 315 * or alternatively, a class definition for sourceObject). 316 * @param parameters The parameters of the web export that requires this link. 317 * @exception IOException If unable to create required HTML files. 318 * @exception PrinterException If unable to create required HTML files. 319 * @exception IllegalActionException If something goes wrong. 320 */ 321 private void _linkTo(WebExporter exporter, PtolemyEffigy effigy, 322 NamedObj sourceObject, NamedObj destinationObject, 323 ExportParameters parameters) 324 throws IOException, PrinterException, IllegalActionException { 325 File gifFile; 326 WebAttribute webAttribute; 327 WebElement webElement; 328 // Look for any open tableaux for the object. 329 List<Tableau> tableaux = effigy.entityList(Tableau.class); 330 331 // ThreadedComposite extends MirrorComposite. 332 // ThreadedComposites do not have a top level tableau, they 333 // contain an effigy that contains a tableau. 334 335 // To replicate: 336 // $PTII/bin/ptinvoke ptolemy.vergil.basic.export.ExportModel -force htm -run -openComposites -timeOut 30000 -whiteBackground ptolemy/actor/lib/hoc/demo/ThreadedComposite/MulticoreExecution.xml $PTII/ptolemy/actor/lib/hoc/demo/ThreadedComposite/MulticoreExecution 337 338 if (tableaux.size() == 0) { 339 List<PtolemyEffigy> effigies = effigy 340 .entityList(PtolemyEffigy.class); 341 if (effigies != null && effigies.size() > 0) { 342 tableaux = effigies.get(0).entityList(Tableau.class); 343 } 344 } 345 // If there are multiple tableaux open, use only the first one. 346 if (tableaux.size() > 0) { 347 // The ddf IfThenElse model has a composite called +1/-1 Gain, 348 // which is not a legal file name, so we sanitize it. 349 String name = StringUtilities 350 .sanitizeName(destinationObject.getName()); 351 Frame frame = tableaux.get(0).getFrame(); 352 // If it's a composite actor, export HTML. 353 if (frame instanceof HTMLExportable) { 354 File directory = parameters.directoryToExportTo; 355 File subDirectory = new File(directory, name); 356 if (subDirectory.exists()) { 357 if (!subDirectory.isDirectory()) { 358 // Move file out of the way. 359 File backupFile = new File(directory, name + ".bak"); 360 if (!subDirectory.renameTo(backupFile)) { 361 throw new IOException( 362 "Failed to rename \"" + subDirectory 363 + "\" to \"" + backupFile + "\""); 364 } 365 } 366 } else if (!subDirectory.mkdir()) { 367 throw new IOException( 368 "Unable to create directory " + subDirectory); 369 } 370 ExportParameters newParameters = new ExportParameters( 371 subDirectory, parameters); 372 // The null argument causes the write to occur to an index.html 373 // file. 374 ((HTMLExportable) frame).writeHTML(newParameters, null); 375 webAttribute = WebAttribute.createWebAttribute(sourceObject, 376 "hrefWebAttribute", "href"); 377 webAttribute.setExpression(name + "/index.html"); 378 exporter.defineAttribute(webAttribute, true); 379 380 // Add to table of contents file if we are using the Ptolemy 381 // website infrastructure. 382 boolean usePtWebsite = Boolean.valueOf(StringUtilities 383 .getProperty("ptolemy.ptII.exportHTML.usePtWebsite")); 384 if (usePtWebsite) { 385 String destinationTitle = LinkToOpenTableaux 386 ._getTitleText(destinationObject); 387 if (destinationTitle.length() > 16) { 388 //Truncate the text so that it does not overflow the toc. 389 destinationTitle = destinationTitle.substring(0, 16) 390 + "."; 391 } 392 webElement = WebElement.createWebElement(destinationObject, 393 "tocContents", "tocContents"); 394 webElement.setExpression( 395 " <li><a href=\"" + name + "/index.html" + "\">" 396 + destinationTitle + "</a></li>"); 397 exporter.defineElement(webElement, false); 398 } 399 } else if (frame instanceof ImageExportable) { 400 gifFile = new File(parameters.directoryToExportTo, 401 name + ".gif"); 402 if (parameters.deleteFilesOnExit) { 403 gifFile.deleteOnExit(); 404 } 405 OutputStream gifOut = new FileOutputStream(gifFile); 406 try { 407 ((ImageExportable) frame).writeImage(gifOut, "gif"); 408 } finally { 409 gifOut.close(); 410 } 411 // Strangely, the class has to be "iframe". 412 // I don't understand why it can't be "lightbox". 413 webAttribute = WebAttribute.createWebAttribute(sourceObject, 414 "hrefWebAttribute", "href"); 415 webAttribute.setExpression(name + ".gif"); 416 exporter.defineAttribute(webAttribute, true); 417 418 webAttribute = WebAttribute.appendToWebAttribute(sourceObject, 419 "classWebAttribute", "class", "iframe"); 420 421 exporter.defineAttribute(webAttribute, true); 422 } 423 } 424 } 425 426 /////////////////////////////////////////////////////////////////// 427 //// private methods //// 428 429 /** A set of class definitions for which an export has already occurred. */ 430 private Set<NamedObj> _exportedClassDefinitions; 431}