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}