001/* A tableau representing a Web Browser window.
002
003 Copyright (c) 2002-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 */
027package ptolemy.actor.gui;
028
029import java.io.IOException;
030
031import ptolemy.kernel.util.IllegalActionException;
032import ptolemy.kernel.util.InvalidStateException;
033import ptolemy.kernel.util.KernelException;
034import ptolemy.kernel.util.NameDuplicationException;
035import ptolemy.kernel.util.Nameable;
036import ptolemy.kernel.util.NamedObj;
037
038///////////////////////////////////////////////////////////////////
039//// BrowserTableau
040
041/**
042 A tableau representing a web browser window.
043
044 There can be any number of instances of this class in an effigy.
045
046 @author Christopher Hylands
047 @version $Id$
048 @since Ptolemy II 2.0
049 @Pt.ProposedRating Yellow (cxh)
050 @Pt.AcceptedRating Red (cxh)
051 @see BrowserEffigy
052 @see BrowserLauncher
053 */
054public class BrowserTableau extends Tableau {
055    /** Construct a new tableau for the model represented by the given effigy.
056     *  @param container The container.
057     *  @param name The name.
058     *  @exception IllegalActionException If the container does not accept
059     *   this entity (this should not occur).
060     *  @exception NameDuplicationException If the name coincides with an
061     *   attribute already in the container.
062     */
063    public BrowserTableau(BrowserEffigy container, String name)
064            throws IllegalActionException, NameDuplicationException {
065        super(container, name);
066    }
067
068    ///////////////////////////////////////////////////////////////////
069    ////                         public methods                    ////
070
071    /** Make the tableau editable or uneditable.  Notice that this does
072     *  not change whether the effigy is modifiable, so other tableaux
073     *  on the same effigy may still modify the associated file.
074     *  @param flag False to make the tableau uneditable.
075     */
076    @Override
077    public void setEditable(boolean flag) {
078        super.setEditable(flag);
079    }
080
081    /** Make this tableau visible by calling
082     *        {@link BrowserLauncher#openURL(String)}
083     *  with URI from the effigy.  Most browsers are smart enough
084     *  so that if the browser is already displaying the URI, then
085     *  that window will be brought to the foreground.  We are limited
086     *  by the lack of communication between Java and the browser,
087     *  so this is the best we can do.
088     *  If the URI ends in "#in_browser", we strip it off before
089     *  passing the URI to the browser.  #in_browser is used by
090     *  {@link ptolemy.actor.gui.HTMLViewer} to force a hyperlink to be
091     *  opened in a browser.
092     */
093    @Override
094    public void show() {
095        // FIXME: Unfortunately, the _config.showAll() at the bottom
096        // of MoMLApplication.parseArgs() will end up calling this method
097        // a second time.
098        // FIXME: Probably the following could make better use of URI
099        // facilities (used to be URL based).
100        String url = ((Effigy) getContainer()).uri.getURI().toString();
101
102        try {
103            if (url.startsWith("jar:")) {
104                // If the URL begins with jar: then we are inside Web
105                // Start, or the Windows installer // and we should
106                // get the resource, and try to write the file to the
107                // place where it would appear in the classpath.
108                // For example,  if url is
109                // jar:file:/D:/ptII/doc/design.jar!/doc/design/design.pdf
110                // then we try to save the file as
111                // d:/ptII/doc/design.pdf
112                // if d:/ptII/doc is writable.
113                String temporaryURL = null;
114
115                try {
116                    // We try to save the resource in the classpath, but
117                    // if we fail, then we copy the resource to a temporary
118                    // location.
119                    // If we are successful, then note that the file
120                    // that we create is not deleted when we exit.
121                    temporaryURL = JNLPUtilities.saveJarURLInClassPath(url);
122                } catch (Exception ex) {
123                    // We print out the error and move on.
124                    // Eventually, this could be logged as a warning.
125                    System.out.println("Failed to save '" + url + "': " + ex);
126                    ex.printStackTrace();
127                }
128
129                if (temporaryURL != null) {
130                    url = temporaryURL;
131                } else {
132                    // For some reason we could not write the file, so
133                    // save the jar file as a temporary file in the default
134                    // platform dependent directory with the same suffix
135                    // as that of the jar URL.
136                    // In this case, the temporary file is deleted when
137                    // we exit.
138                    url = JNLPUtilities.saveJarURLAsTempFile(url, "tmp", null,
139                            null);
140                }
141            }
142
143            String inBrowser = "#in_browser";
144
145            if (url.endsWith(inBrowser)) {
146                // Strip off any trailing #in_browser, see HTMLViewer.
147                url = url.substring(0, url.length() - inBrowser.length());
148            }
149
150            inBrowser = "%23in_browser";
151            if (url.endsWith(inBrowser)) {
152                // Strip off any trailing #in_browser, see HTMLViewer.
153                url = url.substring(0, url.length() - inBrowser.length());
154            }
155
156            BrowserLauncher.openURL(url);
157
158            try {
159                // We set the container to null immediately because
160                // once we spawn the browser process, we have no
161                // way of communicating with it, so we have no way
162                // of knowing when the browser has been closed.
163                //
164                // FIXME: this effectively destroys the Tableau/Effigy model
165                // for BrowserTableaus, but there is not much to be done
166                // about it since we do not have a platform independent way
167                // of communicating with the browser that we invoke.
168                setContainer(null);
169            } catch (KernelException ex2) {
170                throw new InvalidStateException((Nameable) null, ex2,
171                        "setContainer(null) failed, url was " + url);
172            }
173        } catch (IOException ex) {
174            throw new InvalidStateException((Nameable) null, ex,
175                    "Failed to handle '" + url + "': ");
176        }
177    }
178
179    ///////////////////////////////////////////////////////////////////
180    ////                         inner classes                     ////
181
182    /** A factory that creates web browser tableaux for Ptolemy models.
183     */
184    public static class Factory extends TableauFactory {
185        /** Create a factory with the given name and container.
186         *  @param container The container entity.
187         *  @param name The name of the entity.
188         *  @exception IllegalActionException If the container is incompatible
189         *   with this attribute.
190         *  @exception NameDuplicationException If the name coincides with
191         *   an attribute already in the container.
192         */
193        public Factory(NamedObj container, String name)
194                throws IllegalActionException, NameDuplicationException {
195            super(container, name);
196        }
197
198        ///////////////////////////////////////////////////////////////////
199        ////                         public methods                    ////
200
201        /** If the specified effigy is a BrowserEffigy and it
202         *  already contains a tableau named
203         *  "browserTableau", then return that tableau; otherwise, create
204         *  a new instance of BrowserTableau in the specified
205         *  effigy, and name it "browserTableau" and return that tableau.
206         *  If the specified
207         *  effigy is not an instance of BrowserEffigy, then do not
208         *  create a tableau and return null.  It is the
209         *  responsibility of callers of this method to check the
210         *  return value and call show().
211         *  @param effigy The effigy.
212         *  @return A browser editor tableau, or null if one cannot be
213         *    found or created.
214         *  @exception Exception If the factory should be able to create a
215         *   tableau for the effigy, but something goes wrong.
216         */
217        @Override
218        public Tableau createTableau(Effigy effigy) throws Exception {
219            if (effigy instanceof BrowserEffigy) {
220                // First see whether the effigy already contains a
221                // BrowserTableau with the appropriate name.
222                BrowserTableau tableau = (BrowserTableau) effigy
223                        .getEntity("browserTableau");
224
225                if (tableau == null) {
226                    tableau = new BrowserTableau((BrowserEffigy) effigy,
227                            "browserTableau");
228                }
229
230                tableau.setEditable(effigy.isModifiable());
231                return tableau;
232            } else {
233                return null;
234            }
235        }
236    }
237}