/* * This file is part of NodeBox. * * Copyright (C) 2008 Frederik De Bleser (frederik@pandora.be) * * NodeBox is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NodeBox is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NodeBox. If not, see . */ package nodebox.client; import nodebox.node.NodeLibrary; import nodebox.node.NodeLibraryManager; import nodebox.versioncheck.Host; import nodebox.versioncheck.Updater; import nodebox.versioncheck.Version; import javax.swing.*; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.Preferences; public class Application implements Host { private static Application instance; private JFrame hiddenFrame; public static Application getInstance() { return instance; } private AtomicBoolean startingUp = new AtomicBoolean(true); private SwingWorker startupWorker; private Updater updater; private List documents = new ArrayList(); private NodeBoxDocument currentDocument; private NodeLibraryManager manager; private ProgressDialog startupDialog; private Version version; private List filesToLoad = Collections.synchronizedList(new ArrayList()); private Console console = null; public static final String NAME = "NodeBox"; private static Logger logger = Logger.getLogger("nodebox.client.Application"); private Application() { instance = this; initLastResortHandler(); initLookAndFeel(); } //// Application Load //// /** * Starts a SwingWorker that loads the application in the background. *

* Called in the event dispatch thread using invokeLater. */ private void run() { showProgressDialog(); startupWorker = new SwingWorker() { @Override protected Throwable doInBackground() throws Exception { try { publish("Starting NodeBox"); setNodeBoxVersion(); createNodeBoxDataDirectories(); applyPreferences(); registerForMacOSXEvents(); updater = new Updater(Application.this); updater.checkForUpdatesInBackground(); publish("Loading Python"); initPython(); } catch (RuntimeException ex) { return ex; } return null; } @Override protected void process(List strings) { final String firstString = strings.get(0); startupDialog.setMessage(firstString); } @Override protected void done() { startingUp.set(false); startupDialog.setVisible(false); // See if application startup has generated an exception. Throwable t; try { t = get(); } catch (Exception e) { t = e; } if (t != null) { ExceptionDialog ed = new ExceptionDialog(null, t); ed.setVisible(true); System.exit(-1); } if (documents.isEmpty() && filesToLoad.isEmpty()) { instance.createNewDocument(); } else { for (File f : filesToLoad) { openDocument(f); } } } }; startupWorker.execute(); } /** * Sets a handler for uncaught exceptions that pops up a message dialog with the exception. *

* Called from the constructor, in the main thread. */ private void initLastResortHandler() { Thread.currentThread().setUncaughtExceptionHandler(new LastResortHandler()); } /** * Initializes Swing's look and feel to the system native look and feel. * On Mac, uses the system menu bar. */ private void initLookAndFeel() { try { UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName()); } catch (Exception ignored) { } System.setProperty("apple.laf.useScreenMenuBar", "true"); } /** * Shows the progress dialog. *

* Called from the run() method (which is called in invokeLater). */ private void showProgressDialog() { startupDialog = new ProgressDialog(null, "Starting " + NAME); startupDialog.setVisible(true); } /** * Retrieves the NodeBox version number from the version.properties file and sets it in the app. * * @throws RuntimeException if we're not able to retrieve the version number. This is fatal. */ private void setNodeBoxVersion() throws RuntimeException { Properties properties = new Properties(); try { properties.load(new FileInputStream("version.properties")); version = new Version(properties.getProperty("nodebox.version")); } catch (IOException e) { throw new RuntimeException("Could not read NodeBox version file. Please re-install NodeBox.", e); } } /** * Creates the necessary directories used for storing user scripts and Python libraries. * * @throws RuntimeException if we can't create the user directories. This is fatal. */ private void createNodeBoxDataDirectories() throws RuntimeException { PlatformUtils.getUserDataDirectory().mkdir(); PlatformUtils.getUserScriptsDirectory().mkdir(); PlatformUtils.getUserPythonDirectory().mkdir(); } /** * Load the preferences and make them available to the Application object. */ private void applyPreferences() { Preferences preferences = Preferences.userNodeForPackage(this.getClass()); // There are no more preferences. Leaving this in for when there are. } /** * Register for special events available on the Mac, such as showing the about screen, * showing the preferences or double-clicking a file. * * @throws RuntimeException if the adapter methods could not be loaded. */ private void registerForMacOSXEvents() throws RuntimeException { if (!PlatformUtils.onMac()) return; try { // Generate and register the OSXAdapter, passing it a hash of all the methods we wish to // use as delegates for various com.apple.eawt.ApplicationListener methods OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("quit", (Class[]) null)); OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod("showAbout", (Class[]) null)); OSXAdapter.setPreferencesHandler(this, getClass().getDeclaredMethod("showPreferences", (Class[]) null)); OSXAdapter.setFileHandler(this, getClass().getDeclaredMethod("readFromFile", String.class)); } catch (Exception e) { throw new RuntimeException("Error while loading the OS X Adapter.", e); } // On the Mac, if all windows are closed the menu bar will be empty. // To solve this, we create an off-screen window with the same menu bar as visible windows. hiddenFrame = new JFrame(); hiddenFrame.setJMenuBar(new NodeBoxMenuBar()); hiddenFrame.setUndecorated(true); hiddenFrame.setSize(0, 0); hiddenFrame.setLocation(-100, -100); hiddenFrame.pack(); hiddenFrame.setVisible(true); } private void initPython() { manager = new NodeLibraryManager(); manager.addSearchPath(PlatformUtils.getApplicationScriptsDirectory()); manager.addSearchPath(PlatformUtils.getUserScriptsDirectory()); manager.lookForLibraries(); PythonUtils.initializePython(); } //// Application events //// public boolean quit() { // Because documents will disappear from the list once they are closed, // make a copy of the list. java.util.List documents = new ArrayList(getDocuments()); for (NodeBoxDocument d : documents) { if (!d.close()) return false; } System.exit(0); return true; } public void showAbout() { String javaVersion = System.getProperty("java.runtime.version"); JOptionPane.showMessageDialog(null, NAME + " version " + getVersion() + "\nJava " + javaVersion, NAME, JOptionPane.INFORMATION_MESSAGE); } public void showPreferences() { PreferencesDialog dialog = new PreferencesDialog(); dialog.setModal(true); dialog.setVisible(true); } public void showConsole() { java.awt.Dimension d = new java.awt.Dimension(400, 400); if (console == null) { console = new Console(); console.setPreferredSize(d); console.setSize(d); console.setLocationRelativeTo(getCurrentDocument()); console.setVisible(true); } console.setLocationRelativeTo(getCurrentDocument()); console.setVisible(true); for (NodeBoxDocument document : documents) { document.onConsoleVisibleEvent(true); } } public void hideConsole() { console.setVisible(false); onHideConsole(); } public void onHideConsole() { for (NodeBoxDocument document : documents) { document.onConsoleVisibleEvent(false); } } public boolean isConsoleOpened() { if (console == null) return false; return console.isVisible(); } public void readFromFile(String path) { // This method looks unused, but is actually called using reflection by the OS X adapter. // If the application is still starting up, don't open the document immediately but place it in a file loading queue. if (startingUp.get()) { filesToLoad.add(new File(path)); } else { openDocument(new File(path)); } } //// Document management //// public List getDocuments() { return documents; } public int getDocumentCount() { return documents.size(); } public void removeDocument(NodeBoxDocument document) { documents.remove(document); } public NodeBoxDocument createNewDocument() { NodeLibrary newLibrary = new NodeLibrary("untitled"); NodeBoxDocument doc = new NodeBoxDocument(newLibrary); addDocument(doc); return doc; } public boolean openDocument(File file) { // Check if the document is already open. String path; try { path = file.getCanonicalPath(); for (NodeBoxDocument doc : Application.getInstance().getDocuments()) { try { if (doc.getDocumentFile() == null) continue; if (doc.getDocumentFile().getCanonicalPath().equals(path)) { // The document is already open. Bring it to the front. doc.toFront(); doc.requestFocus(); NodeBoxMenuBar.addRecentFile(file); return true; } } catch (IOException e) { logger.log(Level.WARNING, "The document " + doc.getDocumentFile() + " refers to path with errors", e); } } } catch (IOException e) { logger.log(Level.WARNING, "The document " + file + " refers to path with errors", e); } try { NodeBoxDocument doc = new NodeBoxDocument(file); addDocument(doc); NodeBoxMenuBar.addRecentFile(file); return true; } catch (RuntimeException e) { logger.log(Level.SEVERE, "Error while loading " + file, e); ExceptionDialog d = new ExceptionDialog(null, e); d.setVisible(true); return false; } } private void addDocument(NodeBoxDocument doc) { doc.setVisible(true); doc.requestFocus(); documents.add(doc); currentDocument = doc; } public NodeBoxDocument getCurrentDocument() { return currentDocument; } void setCurrentDocument(NodeBoxDocument document) { currentDocument = document; } public NodeLibraryManager getManager() { return manager; } //// Host implementation //// public String getName() { return "NodeBox"; } public String getIconFile() { return "res/applogo.png"; } public Version getVersion() { return version; } public String getAppcastURL() { return "https://secure.nodebox.net/app/nodebox/appcast.xml"; } public Updater getUpdater() { return updater; } public static void main(String[] args) { final Application app = new Application(); SwingUtilities.invokeLater(new Runnable() { public void run() { app.run(); } }); } }