001/*
002 * Copyright (c) 2004-2011 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2015-08-24 22:44:14 +0000 (Mon, 24 Aug 2015) $' 
007 * '$Revision: 33630 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.gui.kar;
031
032import java.awt.Color;
033import java.awt.Toolkit;
034import java.awt.event.ActionEvent;
035import java.io.File;
036import java.net.URL;
037
038import javax.swing.Action;
039import javax.swing.ImageIcon;
040import javax.swing.JFileChooser;
041import javax.swing.JOptionPane;
042import javax.swing.KeyStroke;
043
044import org.apache.commons.logging.Log;
045import org.apache.commons.logging.LogFactory;
046import org.kepler.gui.KeplerGraphFrame;
047import org.kepler.kar.KARFile;
048
049import diva.gui.GUIUtilities;
050import ptolemy.actor.gui.Configuration;
051import ptolemy.actor.gui.Tableau;
052import ptolemy.actor.gui.TableauFrame;
053import ptolemy.gui.ExtensionFilenameFilter;
054import ptolemy.gui.JFileChooserBugFix;
055import ptolemy.gui.PtFileChooser;
056import ptolemy.kernel.util.InternalErrorException;
057import ptolemy.kernel.util.Nameable;
058import ptolemy.vergil.basic.BasicGraphFrame;
059import ptolemy.vergil.toolbox.FigureAction;
060
061/**
062 * This action opens a kar file to the system. It is called from File -> Open.
063 *
064 * @author Ben Leinfelder, Christopher Brooks
065 * @since 05/28/2009
066 */
067public class OpenArchiveAction extends FigureAction
068{
069
070    private static String DISPLAY_NAME = "Open";
071    private static String TOOLTIP = "Open a KAR file archive.";
072    private static ImageIcon LARGE_ICON = null;
073    private static KeyStroke ACCELERATOR_KEYSTROKE = KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O,
074            Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
075
076    // //////////////////////////////////////////////////////////////////////////////
077
078    private TableauFrame parent;
079
080    private final static Log log = LogFactory.getLog(OpenArchiveAction.class);
081    private static final boolean isDebugging = log.isDebugEnabled();
082
083    private File archiveFileToOpen = null;
084    private boolean updateHistoryAndLastDirectory = true;
085
086    private ActionEvent actionEvent = null;
087    
088    /** The exception generated when trying to open the KAR. */
089    private Exception _openException = null;
090    
091    /**
092     * Constructor
093     *
094     * @param parent the "frame" (derived from ptolemy.gui.Top) where the menu is
095     *               being added.
096     */
097    public OpenArchiveAction(TableauFrame parent)
098    {
099        super("Open...");
100
101        if (parent == null)
102        {
103            IllegalArgumentException iae = new IllegalArgumentException(
104                    "OpenArchiveAction constructor received NULL argument for TableauFrame");
105            iae.fillInStackTrace();
106            throw iae;
107        }
108        this.parent = parent;
109
110        this.putValue(Action.NAME, DISPLAY_NAME);
111        this.putValue(GUIUtilities.LARGE_ICON, LARGE_ICON);
112        this.putValue("tooltip", TOOLTIP);
113        this.putValue(GUIUtilities.ACCELERATOR_KEY, ACCELERATOR_KEYSTROKE);
114    }
115
116    /**
117     * Explicitly set the Archive file that the action will open. If not file is
118     * set a File chooser dialog is displayed to the user.
119     *
120     * @param archiveFile
121     */
122    public void setArchiveFileToOpen(File archiveFile)
123    {
124        archiveFileToOpen = archiveFile;
125    }
126    
127    /**
128     * Change whether or not to update Kepler's Recent Files menu, and 
129     * last directory setting.
130     * Default is true.
131     *
132     * @param updateHistoryAndLastDirectory
133     */
134    public void updateHistoryAndLastDirectory(boolean updateHistoryAndLastDirectory)
135    {
136        this.updateHistoryAndLastDirectory = updateHistoryAndLastDirectory;
137    }
138
139    /**
140     * Attempt to open the KAR. Any exception that is generated can be
141     * retrieved with getOpenException().
142     *
143     * @param e ActionEvent
144     */
145    public void actionPerformed(ActionEvent e)
146    {
147
148        // must call this first...
149        super.actionPerformed(e);
150        actionEvent = e;
151        // ...before calling this:
152        // NamedObj target = super.getTarget();
153
154        File karFile = null;
155        if (archiveFileToOpen != null)
156        {
157            karFile = archiveFileToOpen;
158        }
159        else
160        {
161            // Create a file filter that accepts .kar files.
162            ExtensionFilenameFilter filter = new ExtensionFilenameFilter(new String[]{"kar", "xml", "moml"});
163            
164                        // Avoid white boxes in file chooser, see
165                        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=3801
166                        JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix();
167                        Color background = null;
168                        PtFileChooser chooser = null;
169
170                        try {
171                                background = jFileChooserBugFix.saveBackground();
172                                chooser = new PtFileChooser(parent, "Open",
173                                                JFileChooser.OPEN_DIALOG);
174                                if(parent instanceof BasicGraphFrame && updateHistoryAndLastDirectory) {
175                                        chooser.setCurrentDirectory(((BasicGraphFrame)parent).getLastDirectory());
176                                }
177                                chooser.addChoosableFileFilter(filter);
178
179                                int returnVal = chooser.showDialog(parent, "Open");
180                                if (returnVal == JFileChooser.APPROVE_OPTION) {
181                                        // process the given file
182                                        karFile = chooser.getSelectedFile();
183                                }
184                        } finally {
185                                jFileChooserBugFix.restoreBackground(background);
186                        }
187            
188        }
189        if (karFile != null)
190        {
191            String karFileName = karFile.getName().toLowerCase();
192            if ( karFileName.endsWith(".kar") )
193            {
194                //System.out.println("DEBUG: Opening KAR");
195                try
196                {
197                    openKAR(karFile, false, updateHistoryAndLastDirectory);
198                }
199                catch (Exception exc)
200                {
201                        _openException = exc;
202                }
203            }
204            else if( karFileName.endsWith(".xml") || karFileName.endsWith(".moml") )
205            {
206                //System.out.println("DEBUG: Opening XML");
207                _open(karFile);
208            }
209        }
210    }
211
212    /** Return any exception generated while trying to open the KAR. */
213    public Exception getOpenException() {
214        return _openException;
215    }
216
217    /**
218     * Process the new kar file into the actor library.
219     *
220     * @param karFile the file to process
221     * @param forceOpen
222     * @parem updateHistory update recently opened history menu. Default true.
223     */
224    public void openKAR(File karFile, boolean forceOpen, boolean updateHistory) throws Exception
225    {
226        updateHistoryAndLastDirectory(updateHistory);
227        
228        if (isDebugging)
229            log.debug("openKAR(" + karFile.toString() + ")");
230
231        // Read in the KAR file
232        KARFile karf = new KARFile(karFile);
233
234        if (forceOpen || karf.isOpenable())
235        {
236            boolean opened = karf.openKARContents(parent, forceOpen);
237            log.debug("called openKARContents. opened:"+opened);
238
239            if (!opened)
240            {
241                JOptionPane.showMessageDialog(null, "The contents of this KAR are not openable.");
242            }
243            else
244            {
245                // update the history menu
246                if(parent instanceof KeplerGraphFrame && updateHistoryAndLastDirectory) {
247                        ((KeplerGraphFrame)parent).updateHistory(karFile.getAbsolutePath());
248                }
249                                if(parent instanceof BasicGraphFrame && updateHistoryAndLastDirectory) {
250                                        ((BasicGraphFrame)parent).setLastDirectory(karFile.getParentFile());
251                                }
252            }
253
254        }
255        else
256        {
257            if (!karf.areAllModuleDependenciesSatisfied())
258            {
259                log.debug("KAR file module dependencies are not satisfied, calling ImportModuleDependenciesAction");
260
261                //module-dependencies aren't satisfied, invoke ImportModuleDependenciesAction
262                ImportModuleDependenciesAction imda = new ImportModuleDependenciesAction(parent);
263                imda.setArchiveFile(karFile);
264                imda.actionPerformed(actionEvent);
265            }
266            else
267            {
268                JOptionPane.showMessageDialog(null, "This KAR is not openable.");
269            }
270
271        }
272
273        karf.close();
274
275    }
276
277
278    /**
279     * Open a file dialog to identify a file to be opened, and then call
280     * _read() to open the file.
281     */
282    protected void _open(File file)
283    {
284
285        try
286        {
287            // NOTE: It would be nice if it were possible to enter
288            // a URL in the file chooser, but Java's file chooser does
289            // not permit this, regrettably.  So we have a separate
290            // menu item for this.
291
292            // Report on the time it takes to open the model.
293            long startTime = System.currentTimeMillis();
294            _read(file.toURI().toURL());
295            long endTime = System.currentTimeMillis();
296            if (endTime > startTime + 10000)
297            {
298                // Only print the time if it is more than 10
299                // seconds See also PtolemyEffigy.  Perhaps
300                // this code should be in PtolemyEffigy, but
301                // if it is here, we get the time it takes to
302                // read any file, not just a Ptolemy model.
303                System.out.println("Opened " + file + " in "
304                        + (System.currentTimeMillis() - startTime)
305                        + " ms.");
306            }
307            // update the history menu
308            if(parent instanceof KeplerGraphFrame && updateHistoryAndLastDirectory) {
309                ((KeplerGraphFrame)parent).updateHistory(file.getAbsolutePath());
310            }
311                        if(parent instanceof BasicGraphFrame && updateHistoryAndLastDirectory) {
312                                ((BasicGraphFrame)parent).setLastDirectory(file.getParentFile());
313                        }
314        }
315        catch (Error error)
316        {
317            // Be sure to catch Error here so that if we throw an
318            // Error, then we will report it to with a window.
319            try
320            {
321                throw new RuntimeException(error);
322            }
323            catch (Exception ex2)
324            {
325                ex2.printStackTrace();
326            }
327        }
328        catch (Exception ex)
329        {
330            // NOTE: The XML parser can only throw an
331            // XmlException.  It signals that it is a user
332            // cancellation with the special string pattern
333            // "*** Canceled." in the message.
334
335            if ((ex.getMessage() != null)
336                    && !ex.getMessage().startsWith("*** Canceled."))
337            {
338                // No need to report a CancelException, since
339                // it results from the user clicking a
340                // "Cancel" button.
341                ex.printStackTrace();
342            }
343        }
344    }
345
346    /**
347     * Read the specified URL.  This delegates to the ModelDirectory
348     * to ensure that the preferred tableau of the model is opened, and
349     * that a model is not opened more than once.
350     *
351     * @param url The URL to read.
352     * @throws Exception If the URL cannot be read, or if there is no
353     *                   tableau.
354     */
355    protected void _read(URL url) throws Exception
356    {
357        Tableau _tableau = parent.getTableau();
358        if (_tableau == null)
359        {
360            throw new Exception("No associated Tableau!"
361                    + " Can't open a file.");
362        }
363
364        // NOTE: Used to use for the first argument the following, but
365        // it seems to not work for relative file references:
366        // new URL("file", null, _directory.getAbsolutePath()
367        Nameable configuration = _tableau.toplevel();
368
369        if (configuration instanceof Configuration)
370        {
371            ((Configuration) configuration).openModel(url, url, url
372                    .toExternalForm());
373        }
374        else
375        {
376            throw new InternalErrorException(
377                    "Expected top-level to be a Configuration: "
378                            + _tableau.toplevel().getFullName());
379        }
380    }
381    
382    /** True if a message about failing to find last directory was printed. */
383    private static boolean _printedFailedLastDirectoryMessage = false;
384}