Package processing.app

Source Code of processing.app.Base

/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Processing project - http://processing.org

  Copyright (c) 2004-10 Ben Fry and Casey Reas
  Copyright (c) 2001-04 Massachusetts Institute of Technology

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License version 2
  as published by the Free Software Foundation.

  This program 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 this program; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package processing.app;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.net.*;

import javax.swing.*;

import processing.app.debug.Compiler;
import processing.app.debug.Target;
import processing.core.*;


/**
* The base class for the main processing application.
* Primary role of this class is for platform identification and
* general interaction with the system (launching URLs, loading
* files and images, etc) that comes from that.
*/
public class Base {
  public static final int REVISION = 101;
  static String VERSION_NAME = "0101";
  /** Set true if this a proper release rather than a numbered revision. */
  static public boolean RELEASE = false;
  /** True if heavy debugging error/log messages are enabled */
  static public boolean DEBUG = false;

  static HashMap<Integer, String> platformNames = new HashMap<Integer, String>();
  static {
    platformNames.put(PConstants.WINDOWS, "windows");
    platformNames.put(PConstants.MACOSX, "macosx");
    platformNames.put(PConstants.LINUX, "linux");
  }

  static HashMap<String, Integer> platformIndices = new HashMap<String, Integer>();
  static {
    platformIndices.put("windows", PConstants.WINDOWS);
    platformIndices.put("macosx", PConstants.MACOSX);
    platformIndices.put("linux", PConstants.LINUX);
  }
  static Platform platform;

  static private boolean commandLine;

  // A single instance of the preferences window
  Preferences preferencesFrame;

  // set to true after the first time the menu is built.
  // so that the errors while building don't show up again.
  boolean builtOnce;

  static File buildFolder;

  // these are static because they're used by Sketch
  static private File examplesFolder;
  static private File librariesFolder;
  static private File toolsFolder;
  static private File hardwareFolder;
  static private File coresFolder;
  static private File coreLibrariesFolder;

  static HashSet<File> libraries;

  // maps imported packages to their library folder
  static HashMap<String, File> importToLibraryTable;
 
  static public HashMap<String, Target> targetsTable;
 
  static public HashMap<String, File> coresTable;

  // Location for untitled items
  static File untitledFolder;

  java.util.List<Editor> editors =
    Collections.synchronizedList(new ArrayList<Editor>());
  Editor activeEditor;
  // a lone file menu to be used when all sketch windows are closed
  static public JMenu defaultFileMenu;


  static public void main(final String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          createAndShowGUI(args);
        }
    });
  }


  static private void createAndShowGUI(String[] args) {
    try {
      File versionFile = getContentFile("lib/version.txt");
      if (versionFile.exists()) {
        String version = PApplet.loadStrings(versionFile)[0];
        if (!version.equals(VERSION_NAME)) {
          VERSION_NAME = version;
          RELEASE = true;
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    initPlatform();

    // Use native popups so they don't look so crappy on osx
    JPopupMenu.setDefaultLightWeightPopupEnabled(false);

    // Don't put anything above this line that might make GUI,
    // because the platform has to be inited properly first.

    // run static initialization that grabs all the prefs
    Preferences.init(null);

    // setup the theme coloring fun
    Theme.init();

    // Set the look and feel before opening the window
    try {
      platform.setLookAndFeel();
    } catch (Exception e) {
      String mess = e.getMessage();
      if (mess.indexOf("ch.randelshofer.quaqua.QuaquaLookAndFeel") == -1) {
        System.err.println("Non-fatal error while setting the Look & Feel.");
        System.err.println("The error message follows, however Wiring should run fine.");
        System.err.println(mess);
      }
    }

    // Create a location for untitled sketches
    untitledFolder = createTempFolder("untitled");
    untitledFolder.deleteOnExit();

    new Base(args);
  }


  static protected void setCommandLine() {
    commandLine = true;
  }


  static protected boolean isCommandLine() {
    return commandLine;
  }


  static public void initPlatform() {
    try {
      Class<?> platformClass = Class.forName("processing.app.Platform");
      if (Base.isMacOS()) {
        platformClass = Class.forName("processing.app.macosx.Platform");
      } else if (Base.isWindows()) {
        platformClass = Class.forName("processing.app.windows.Platform");
      } else if (Base.isLinux()) {
        platformClass = Class.forName("processing.app.linux.Platform");
      }
      platform = (Platform) platformClass.newInstance();
    } catch (Exception e) {
      Base.showError("Problem Setting the Platform",
                     "An unknown error occurred while trying to load\n" +
                     "platform-specific code for your machine.", e);
    }
  }

  public Base(String[] args) {
    // Get paths for the libraries and examples in the Processing folder
    examplesFolder = getContentFile("examples");
    librariesFolder = getContentFile("libraries");
    toolsFolder = getContentFile("tools");

    // Put this after loading the examples, so that building the default file
    // menu works on Mac OS X (since  it needs examplesFolder to be set).
    platform.init(this);

    // Get the sketchbook path, and make sure it's set properly
    String sketchbookPath = Preferences.get("sketchbook.path");

    // If a value is at least set, first check to see if the folder exists.
    // If it doesn't, warn the user that the sketchbook folder is being reset.
    if (sketchbookPath != null) {
      File skechbookFolder = new File(sketchbookPath);
      if (!skechbookFolder.exists()) {
        Base.showWarning("Sketchbook folder disappeared",
                         "The sketchbook folder no longer exists.\n" +
                         "Wiring will switch to the default sketchbook\n" +
                         "location, and create a new sketchbook folder if\n" +
                         "necessary. Wiring will then stop talking about\n" +
                         "himself in the third person.", null);
        sketchbookPath = null;
      }
    }

    // If not path is set, get the default sketchbook folder for this platform
    if (sketchbookPath == null) {
      File defaultFolder = getDefaultSketchbookFolder();
      Preferences.set("sketchbook.path", defaultFolder.getAbsolutePath());
      if (!defaultFolder.exists()) {
        defaultFolder.mkdirs();
      }
    }

    targetsTable = new HashMap<String, Target>();
    loadHardware(getHardwareFolder());
    loadHardware(getSketchbookHardwareFolder());
       
    if (coreAvailable()) {
      coresTable = new HashMap<String, File>();
      loadCores(getCoresFolder());
      loadCores(getSketchbookCoresFolder());
     
      coreLibrariesFolder = getCoreLibrariesFolder();
    }
// Check if there were previously opened sketches to be restored
    boolean opened = restoreSketches();

    // Check if any files were passed in on the command line
    for (int i = 0; i < args.length; i++) {
      String path = args[i];
      // Fix a problem with systems that use a non-ASCII languages. Paths are
      // being passed in with 8.3 syntax, which makes the sketch loader code
      // unhappy, since the sketch folder naming doesn't match up correctly.
      // http://dev.processing.org/bugs/show_bug.cgi?id=1089
      if (isWindows()) {
        try {
          File file = new File(args[i]);
          path = file.getCanonicalPath();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (handleOpen(path) != null) {
        opened = true;
      }
    }

    // Create a new empty window (will be replaced with any files to be opened)
    if (!opened) {
      handleNew();
    }

    // check for updates
    if (Preferences.getBoolean("update.check")) {
      new UpdateCheck(this);
    }
  }


  /**
   * Post-constructor setup for the editor area. Loads the last
   * sketch that was used (if any), and restores other Editor settings.
   * The complement to "storePreferences", this is called when the
   * application is first launched.
   */
  protected boolean restoreSketches() {
    if (!Preferences.getBoolean("last.sketch.restore")) {
      return false;
    }

    // figure out window placement
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    boolean windowPositionValid = true;

    if (Preferences.get("last.screen.height") != null) {
      // if screen size has changed, the window coordinates no longer
      // make sense, so don't use them unless they're identical
      int screenW = Preferences.getInteger("last.screen.width");
      int screenH = Preferences.getInteger("last.screen.height");

      if ((screen.width != screenW) || (screen.height != screenH)) {
        windowPositionValid = false;
      }
    } else {
      windowPositionValid = false;
    }

    // Save the sketch path and window placement for each open sketch
    int count = Preferences.getInteger("last.sketch.count");
    int opened = 0;
    for (int i = 0; i < count; i++) {
      String path = Preferences.get("last.sketch" + i + ".path");
      int[] location;
      if (windowPositionValid) {
        String locationStr = Preferences.get("last.sketch" + i + ".location");
        location = PApplet.parseInt(PApplet.split(locationStr, ','));
      } else {
        location = nextEditorLocation();
      }
      // If file did not exist, null will be returned for the Editor
      if (handleOpen(path, location) != null) {
        opened++;
      }
    }
    return (opened > 0);
  }


  /**
   * Store list of sketches that are currently open.
   * Called when the application is quitting and documents are still open.
   */
  protected void storeSketches() {
    // Save the width and height of the screen
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    Preferences.setInteger("last.screen.width", screen.width);
    Preferences.setInteger("last.screen.height", screen.height);

    String untitledPath = untitledFolder.getAbsolutePath();

    // Save the sketch path and window placement for each open sketch
    int index = 0;
    for (Editor editor : editors) {
      String path = editor.getSketch().getMainFilePath();
      // In case of a crash, save untitled sketches if they contain changes.
      // (Added this for release 0158, may not be a good idea.)
      if (path.startsWith(untitledPath) &&
          !editor.getSketch().isModified()) {
        continue;
      }
      Preferences.set("last.sketch" + index + ".path", path);

      int[] location = editor.getPlacement();
      String locationStr = PApplet.join(PApplet.str(location), ",");
      Preferences.set("last.sketch" + index + ".location", locationStr);
      index++;
    }
    Preferences.setInteger("last.sketch.count", index);
  }


  // If a sketch is untitled on quit, may need to store the new name
  // rather than the location from the temp folder.
  protected void storeSketchPath(Editor editor, int index) {
    String path = editor.getSketch().getMainFilePath();
    String untitledPath = untitledFolder.getAbsolutePath();
    if (path.startsWith(untitledPath)) {
      path = "";
    }
    Preferences.set("last.sketch" + index + ".path", path);
  }


  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


  /** Command on Mac OS X, Ctrl on Windows and Linux */
  static final int SHORTCUT_KEY_MASK =
    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
  /** Command-W on Mac OS X, Ctrl-W on Windows and Linux */
  static final KeyStroke WINDOW_CLOSE_KEYSTROKE =
    KeyStroke.getKeyStroke('W', SHORTCUT_KEY_MASK);
  /** Command-Option on Mac OS X, Ctrl-Alt on Windows and Linux */
  static final int SHORTCUT_ALT_KEY_MASK = ActionEvent.ALT_MASK |
    Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();


  /**
   * A software engineer, somewhere, needs to have his abstraction
   * taken away. In some countries they jail or beat people for crafting
   * the sort of API that would require a five line helper function
   * just to set the shortcut key for a menu item.
   */
  static public JMenuItem newJMenuItem(String title, int what) {
    JMenuItem menuItem = new JMenuItem(title);
    int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
    return menuItem;
  }


  /**
   * Like newJMenuItem() but adds shift as a modifier for the shortcut.
   */
  static public JMenuItem newJMenuItemShift(String title, int what) {
    JMenuItem menuItem = new JMenuItem(title);
    int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    modifiers |= ActionEvent.SHIFT_MASK;
    menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
    return menuItem;
  }


  /**
   * Same as newJMenuItem(), but adds the ALT (on Linux and Windows)
   * or OPTION (on Mac OS X) key as a modifier.
   */
  static public JMenuItem newJMenuItemAlt(String title, int what) {
    JMenuItem menuItem = new JMenuItem(title);
    menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_ALT_KEY_MASK));
    return menuItem;
  }


  static public JCheckBoxMenuItem newJCheckBoxMenuItem(String title, int what) {
    JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(title);
    int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
    return menuItem;
  }


  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


  static JMenu sketchbookMenu;
  static JMenu recentMenu;
  static JMenu examplesMenu;
  static JMenu importMenu;

 
  protected void rebuildWindowMenu(JMenu menu) {
    JMenuItem item;
    menu.removeAll();
    for (Editor editor : editors) {
      item = new JMenuItem(editor.getSketch().getName());
      item.setActionCommand(editor.getSketch().getMainFilePath());
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handleWindow(e.getActionCommand());
        }
      });
      menu.add(item);
    }     
  }
 
 
  public JMenu buildWindowMenu() {
    JMenuItem item;
    JMenu windowMenu = new JMenu("Window");
    for (Editor editor : editors) {
      item = new JMenuItem(editor.getSketch().getName());
      item.setActionCommand(editor.getSketch().getMainFilePath());
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handleWindow(e.getActionCommand());
        }
      });
      windowMenu.add(item);
    }     
    return windowMenu;
  }
 

  public JMenu buildFileMenu(final Editor editor) {
    JMenuItem item;
    JMenu fileMenu = new JMenu("File");

    item = newJMenuItem("New", 'N');
    item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handleNew();
        }
      });
    fileMenu.add(item);

    item = newJMenuItem("Open...", 'O');
    item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handleOpenPrompt();
        }
      });
    fileMenu.add(item);

    if (recentMenu == null) {
      recentMenu = new JMenu("Open Recent");
      rebuildRecentMenu(recentMenu);
    }
    fileMenu.add(recentMenu);
   
    if (sketchbookMenu == null) {
      sketchbookMenu = new JMenu("Sketchbook");
      rebuildSketchbookMenu(sketchbookMenu);
    }
    fileMenu.add(sketchbookMenu);

    item = newJMenuItem("Close", 'W');
    if (editor != null) {
    item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handleClose(editor);
        }
      });
    } else {
      item.setEnabled(false);
    }
    fileMenu.add(item);

    item = newJMenuItem("Save", 'S');
    if (editor != null) {
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          editor.handleSave(false);
        }
      });
      editor.setSaveItem(item);
    } else {
      item.setEnabled(false);
    }
    fileMenu.add(item);

    item = newJMenuItemShift("Save As...", 'S');
    if (editor != null) {
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          editor.handleSaveAs();
        }
      });
      editor.setSaveAsItem(item);
    } else {
      item.setEnabled(false);
    }
    fileMenu.add(item);

    item = newJMenuItem("Upload to Wiring hardware", 'U');
    if (editor != null) {
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          editor.handleExport(false);
        }
      });
    } else {
      item.setEnabled(false);
    }
    fileMenu.add(item);
    fileMenu.addSeparator();

    item = newJMenuItemShift("Page Setup", 'P');
    if (editor != null) {
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          editor.handlePageSetup();
        }
      });
    } else {
      item.setEnabled(false);
    }
    fileMenu.add(item);

    item = newJMenuItem("Print", 'P');
    if (editor != null) {
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          editor.handlePrint();
        }
      });
    } else {
      item.setEnabled(false);
    }
    fileMenu.add(item);

    // Mac OS X already has its own preferences and quit menu.
    // That's right! Think different, b*tches!
    if (!Base.isMacOS()) {
      fileMenu.addSeparator();

      item = newJMenuItem("Preferences", ',');
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handlePrefs();
        }
      });
      fileMenu.add(item);

      fileMenu.addSeparator();

      item = newJMenuItem("Quit", 'Q');
      item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handleQuit();
        }
      });
      fileMenu.add(item);
    }
    return fileMenu;
  }
 

  // .................................................................


  // Because of variations in native windowing systems, no guarantees about
  // changes to the focused and active Windows can be made. Developers must
  // never assume that this Window is the focused or active Window until this
  // Window receives a WINDOW_GAINED_FOCUS or WINDOW_ACTIVATED event.
  protected void handleActivated(Editor whichEditor) {
    activeEditor = whichEditor;

    // set the current window to be the console that's getting output
    EditorConsole.setEditor(activeEditor);
  }


  protected int[] nextEditorLocation() {
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    int defaultWidth = Preferences.getInteger("editor.window.width.default");
    int defaultHeight = Preferences.getInteger("editor.window.height.default");

    if (activeEditor == null) {
      // If no current active editor, use default placement
      return new int[] {
          (screen.width - defaultWidth) / 2,
          (screen.height - defaultHeight) / 2,
          defaultWidth, defaultHeight, 0
      };

    } else {
      // With a currently active editor, open the new window
      // using the same dimensions, but offset slightly.
      synchronized (editors) {
        final int OVER = 50;
        Editor lastOpened = editors.get(editors.size() - 1);
        int[] location = lastOpened.getPlacement();
        // Just in case the bounds for that window are bad
        location[0] += OVER;
        location[1] += OVER;

        if (location[0] == OVER ||
            location[2] == OVER ||
            location[0] + location[2] > screen.width ||
            location[1] + location[3] > screen.height) {
          // Warp the next window to a randomish location on screen.
          return new int[] {
              (int) (Math.random() * (screen.width - defaultWidth)),
              (int) (Math.random() * (screen.height - defaultHeight)),
              defaultWidth, defaultHeight, 0
          };
        }

        return location;
      }
    }
  }


  // .................................................................


  boolean breakTime = false;
  String[] months = {
    "jan", "feb", "mar", "apr", "may", "jun",
    "jul", "aug", "sep", "oct", "nov", "dec"
  };

  /**
   * Handle creating a sketch folder, return its base .pde file
   * or null if the operation was canceled.
   * @param shift whether shift is pressed, which will invert prompt setting
   * @param noPrompt disable prompt, no matter the setting
   */
  protected String createNewUntitled() throws IOException {
    File newbieDir = null;
    String newbieName = null;

    // In 0126, untitled sketches will begin in the temp folder,
    // and then moved to a new location because Save will default to Save As.
    File sketchbookDir = getSketchbookFolder();
    File newbieParentDir = untitledFolder;

    String prefix = Preferences.get("editor.untitled.prefix");

    // Use a generic name like sketch_031008a, the date plus a char
    int index = 0;
    String format = Preferences.get("editor.untitled.suffix");
    String suffix = null;
    if (format == null) {
      Calendar cal = Calendar.getInstance();
      int day = cal.get(Calendar.DAY_OF_MONTH)// 1..31
      int month = cal.get(Calendar.MONTH)// 0..11
      suffix = months[month] + PApplet.nf(day, 2);
    } else {
      SimpleDateFormat formatter = new SimpleDateFormat(format);
      suffix = formatter.format(new Date());
    }
    do {
      if (index == 26) {
        // In 0159, avoid running past z by sending people outdoors.
        if (!breakTime) {
          Base.showWarning("Time for a Break",
                           "You've reached the limit for auto naming of new sketches\n" +
                           "for the day. How about going for a walk instead?", null);
          breakTime = true;
        } else {
          Base.showWarning("Sunshine",
                           "No really, time for some fresh air for you.", null);
        }
        return null;
      }
      newbieName = prefix + suffix + ((char) ('a' + index));
      // Also sanitize the name since it might do strange things on
      // non-English systems that don't use this sort of date format.
      // http://code.google.com/p/processing/issues/detail?id=283
      newbieName = Sketch.sanitizeName(newbieName);
      newbieDir = new File(newbieParentDir, newbieName);
      index++;
      // Make sure it's not in the temp folder *and* it's not in the sketchbook
    } while (newbieDir.exists() || new File(sketchbookDir, newbieName).exists());

    // Make the directory for the new sketch
    newbieDir.mkdirs();

    // Make an empty pde file
    File newbieFile = new File(newbieDir, newbieName + ".pde");
    new FileOutputStream(newbieFile)// create the file
    return newbieFile.getAbsolutePath();
  }


  /**
   * Create a new untitled document in a new sketch window.
   */
  public void handleNew() {
    try {
      String path = createNewUntitled();
      if (path != null) {
        Editor editor = handleOpen(path);
        editor.untitled = true;
        StringBuffer buffer = new StringBuffer();
        buffer.append("void setup()\n{\n\n}\n\n");
        buffer.append("void loop()\n{\n\n}");
        buffer.append(editor.getText());
        editor.setText(buffer.toString());
        editor.setSelection(0, 0)// scroll to start
        editor.getSketch().setModified(false);
      }

    } catch (IOException e) {
      if (activeEditor != null) {
        activeEditor.statusError(e);
      }
    }
  }


  /**
   * Replace the sketch in the current window with a new untitled document.
   */
  public void handleNewReplace() {
    if (!activeEditor.checkModified()) {
      return// sketch was modified, and user canceled
    }
    // Close the running window, avoid window boogers with multiple sketches
    activeEditor.internalCloseRunner();

    // Actually replace things
    handleNewReplaceImpl();
  }


  protected void handleNewReplaceImpl() {
    try {
      String path = createNewUntitled();
      if (path != null) {
        activeEditor.handleOpenInternal(path);
        activeEditor.untitled = true;
        StringBuffer buffer = new StringBuffer();
        buffer.append("void setup()\n{\n\n}\n\n");
        buffer.append("void loop()\n{\n\n}");
        buffer.append(activeEditor.getText());
        activeEditor.setText(buffer.toString());
        activeEditor.setSelection(0, 0)// scroll to start
        activeEditor.getSketch().setModified(false);

      }

    } catch (IOException e) {
      activeEditor.statusError(e);
    }
  }


  /**
   * Open a sketch, replacing the sketch in the current window.
   * @param path Location of the primary pde file for the sketch.
   */
  public void handleOpenReplace(String path) {
    if (!activeEditor.checkModified()) {
      return// sketch was modified, and user canceled
    }
    // Close the running window, avoid window boogers with multiple sketches
    activeEditor.internalCloseRunner();

    boolean loaded = activeEditor.handleOpenInternal(path);
    if (!loaded) {
      // replace the document without checking if that's ok
      handleNewReplaceImpl();
    }
  }


  /**
   * Prompt for a sketch to open, and open it in a new window.
   */
  public void handleOpenPrompt() {
    // get the frontmost window frame for placing file dialog
    FileDialog fd = new FileDialog(activeEditor,
                                   "Open a Wiring sketch...",
                                   FileDialog.LOAD);

    // Only show .pde files as eligible bachelors
    fd.setFilenameFilter(new FilenameFilter() {
        public boolean accept(File dir, String name) {
          // TODO this doesn't seem to ever be used. AWESOME.
          return name.toLowerCase().endsWith(".pde")
              || name.toLowerCase().endsWith(".ino");
        }
      });

    fd.setVisible(true);

    String directory = fd.getDirectory();
    String filename = fd.getFile();

    // User canceled selection
    if (filename == null) return;

    File inputFile = new File(directory, filename);
    handleOpen(inputFile.getAbsolutePath());
  }


  protected void handleWindow(String window) {
    for (Editor editor : editors) {
      if (editor.getSketch().getMainFilePath().equals(window)) {
        editor.toFront();
      }
    }
  }
 

  /**
   * Open a sketch in a new window.
   * @param path Path to the pde file for the sketch in question
   * @return the Editor object, so that properties (like 'untitled')
   *         can be set by the caller
   */
  public Editor handleOpen(String path) {
    return handleOpen(path, nextEditorLocation());
  }


  protected Editor handleOpen(String path, int[] location) {
    File file = new File(path);
    if (!file.exists()) return null;

    // Cycle through open windows to make sure that it's not already open.
    for (Editor editor : editors) {
      if (editor.getSketch().getMainFilePath().equals(path)) {
        editor.toFront();
        return editor;
      }
    }

    Editor editor = new Editor(this, path, location);

    // Make sure that the sketch actually loaded
    if (editor.getSketch() == null) {
      return null// Just walk away quietly
    }

    editors.add(editor);
    // now that we're ready, show the window
    // (don't do earlier, cuz we might move it based on a window being closed)
    editor.setVisible(true);
    int maximum = Preferences.getInteger("recent.sketch.maximum");
    if (!path.startsWith(untitledFolder.getAbsolutePath())) {
      boolean needShuffle = true;
      for (int i=0; i<maximum; i++) {
        String savedPath = Preferences.get("recent.sketch" + i + ".path");
        if(savedPath != null) {
          if(savedPath.length()>0) {           
            if (savedPath.equals(path)) {
              for (int j=i; j<(maximum-1); j++) {
                String updatePath = Preferences.get("recent.sketch"+(j+1)+ ".path");
                if(updatePath != null) {
                  if(updatePath.length() >= 0) {
                    Preferences.set("recent.sketch"+(j)+ ".path", updatePath);
                  }
                } else {
                  Preferences.set("recent.sketch"+(j)+ ".path", "");
                }
              }
              break;
            }
          }
        }
      }
      if (needShuffle) {
        //shuffle every item down the list
        for (int i=maximum-1; i>0; i--) {
          String savedPath = Preferences.get("recent.sketch"+(i-1)+ ".path");
          if(savedPath != null) {
            if(savedPath.length()>=0) {
              Preferences.set("recent.sketch"+i+ ".path", savedPath);
            }
          } else {
            Preferences.set("recent.sketch"+i+ ".path", "");
          }
        }
      }
      Preferences.set("recent.sketch0.path",path);
      Preferences.setInteger("recent.sketch.maximum", Math.min(maximum, 10));
      Preferences.save();
    }
   
   
    return editor;
  }


  /**
   * Close a sketch as specified by its editor window.
   * @param editor Editor object of the sketch to be closed.
   * @return true if succeeded in closing, false if canceled.
   */
  public boolean handleClose(Editor editor) {
    if (!editor.checkModified()) {
      return false;
    }

    // Close the running window, avoid window boogers with multiple sketches
    editor.internalCloseRunner();

    if (editors.size() == 1) {
      if (Base.isMacOS()) {
          Object[] options = { "OK", "Cancel" };
          String prompt =
            "<html> " +
            "<head> <style type=\"text/css\">"+
            "b { font: 13pt \"Lucida Grande\" }"+
            "p { font: 11pt \"Lucida Grande\"; margin-top: 8px }"+
            "</style> </head>" +
            "<b>Are you sure you want to Quit?</b>" +
            "<p>Closing the last open sketch will quit Wiring.";

          int result = JOptionPane.showOptionDialog(editor,
            prompt,
            "Quit",
            JOptionPane.YES_NO_OPTION,
            JOptionPane.QUESTION_MESSAGE,
            null,
            options,
            options[0]);
          if (result == JOptionPane.NO_OPTION ||
              result == JOptionPane.CLOSED_OPTION) {
            return false;
          }
      }

      // This will store the sketch count as zero
      editors.remove(editor);
      Editor.serialMonitor.closeSerialPort();
      storeSketches();

      // Save out the current prefs state
      Preferences.save();

        System.exit(0);
    } else {
      // More than one editor window open,
      // proceed with closing the current window.
      editor.setVisible(false);
      editor.dispose();
      editors.remove(editor);
    }
    return true;
  }


  /**
   * Handler for File &rarr; Quit.
   * @return false if canceled, true otherwise.
   */
  public boolean handleQuit() {
    // If quit is canceled, this will be replaced anyway
    // by a later handleQuit() that is not canceled.
    storeSketches();
    Editor.serialMonitor.closeSerialPort();

    if (handleQuitEach()) {
      // make sure running sketches close before quitting
      for (Editor editor : editors) {
        editor.internalCloseRunner();
      }
      // Save out the current prefs state
      Preferences.save();

      if (!Base.isMacOS()) {
        // If this was fired from the menu or an AppleEvent (the Finder),
        // then Mac OS X will send the terminate signal itself.
        System.exit(0);
      }
      return true;
    }
    return false;
  }


  /**
   * Attempt to close each open sketch in preparation for quitting.
   * @return false if canceled along the way
   */
  protected boolean handleQuitEach() {
    int index = 0;
    for (Editor editor : editors) {
      if (editor.checkModified()) {
        // Update to the new/final sketch path for this fella
        storeSketchPath(editor, index);
        index++;

      } else {
        return false;
      }
    }
    return true;
  }


  // .................................................................
  /**
   * Asynchronous version of menu rebuild to be used on save and rename
   * to prevent the interface from locking up until the menus are done.
   */
  protected void rebuildRecentMenu() {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        rebuildRecentMenu(recentMenu);
      }
    });
  }


  protected void rebuildRecentMenu(JMenu menu) {
    JMenuItem item;
    menu.removeAll();
    int maximum = Preferences.getInteger("recent.sketch.maximum");

    ActionListener listener = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        String path = e.getActionCommand();
        if (new File(path).exists()) {
          handleOpen(path);
        } else {
          showWarning("Sketch Does Not Exist",
                      "The selected sketch no longer exists.\n" +
                      "You may need to restart Wiring to update\n" +
                      "the sketchbook menu.", null);
        }
      }
    };
    for (int i = 0; i < maximum; i++) {
      String path = Preferences.get("recent.sketch" + i + ".path");
      if(path != null) {
        if(path.length()>0) {
          File entry = new File(path);
          if(entry.exists()) {
            item = new JMenuItem(path);
            item.addActionListener(listener);
            item.setActionCommand(entry.getAbsolutePath());
            menu.add(item);
          }
        }
      }
    }
  } 
 
  /**
   * Asynchronous version of menu rebuild to be used on save and rename
   * to prevent the interface from locking up until the menus are done.
   */
  protected void rebuildSketchbookMenus() {
    EventQueue.invokeLater(new Runnable() {
      public void run() {
        rebuildSketchbookMenu(sketchbookMenu);
        rebuildToolbarMenu(Editor.toolbarMenu);
      }
    });
  }


  protected void rebuildToolbarMenu(JMenu menu) {
    JMenuItem item;
    menu.removeAll();

    // Add the single "Open" item
    item = newJMenuItem("Open...", 'O');
    item.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          handleOpenPrompt();
        }
      });
    menu.add(item);
    menu.addSeparator();

    // Add a list of all sketches and subfolders
    try {
      boolean sketches = addSketches(menu, getSketchbookFolder(), true);
      if (sketches) menu.addSeparator();
    } catch (IOException e) {
      e.printStackTrace();
    }

  // Add each of the subfolders of examples directly to the menu
    try {
      boolean found = addSketches(menu, examplesFolder, true);
      if (found) menu.addSeparator();
      found = addSketches(menu, getSketchbookLibrariesFolder(), true);
      if (found) menu.addSeparator();
      found = addSketches(menu, librariesFolder, true);
      if(coreAvailable()) {
        if (found) menu.addSeparator();
          addSketches(menu, coreLibrariesFolder, true);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }    
  }


  protected void rebuildSketchbookMenu(JMenu menu) {
    try {
      menu.removeAll();
      addSketches(menu, getSketchbookFolder(), false);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }


  public void rebuildImportMenu() {
    importMenu.removeAll();

    // reset the set of libraries
    libraries = new HashSet<File>();

    // reset the table mapping imports to libraries
    importToLibraryTable = new HashMap<String, File>();
    boolean previousLibrariesFound = false;
   
    int separatorIndex = importMenu.getItemCount();
   
    // Add libraries found in the current cores/XXX folder
    if (coreAvailable())
      try {
        File coreLibraries = getCoreLibrariesFolder();
        JMenu corelibs = new JMenu("Core");
        if (coreLibraries != null) {
          boolean found = addLibraries(corelibs, coreLibraries);
          if (found) {
            corelibs.setEnabled(true);
            importMenu.insert(corelibs, separatorIndex);
            if(previousLibrariesFound)
              importMenu.insertSeparator(separatorIndex);
            previousLibrariesFound = true;
          }
        }
      } catch (IOException e) {
        e.printStackTrace();
      }   
   
    separatorIndex = importMenu.getItemCount();
    // Add from the "libraries" subfolder in the Wiring directory
    try {
      JMenu commonLibs = new JMenu("Cross-platform");
      boolean found = addLibraries(commonLibs, librariesFolder);
      if (found) {
        commonLibs.setEnabled(true);
        importMenu.insert(commonLibs, separatorIndex);
        if (previousLibrariesFound)
          importMenu.insertSeparator(separatorIndex);
        previousLibrariesFound = true;
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
   
    // Add libraries found in the sketchbook folder
    separatorIndex = importMenu.getItemCount();
    try {
      File sketchbookLibraries = getSketchbookLibrariesFolder();
      JMenu contrib = new JMenu("Contributed");
      boolean found = addLibraries(contrib, sketchbookLibraries);
      if (found) {
        contrib.setEnabled(true);
        importMenu.insert(contrib, separatorIndex);
        if(previousLibrariesFound)
          importMenu.insertSeparator(separatorIndex);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
   
  }

  public void rebuildExamplesMenu() {
    try {
      int separatorIndex = examplesMenu.getItemCount();
     
      examplesMenu.removeAll();
      boolean found = addSketches(examplesMenu, examplesFolder, false);
      separatorIndex = examplesMenu.getItemCount();
      int librariesSeparatorIndex = separatorIndex;
      boolean librariesExamplesFound = false;
     
      if (coreAvailable()) {
        separatorIndex = examplesMenu.getItemCount();
        JMenu coreExamples = new JMenu("Core");
        found = addSketches(coreExamples, getCoreLibrariesFolder(), false);
        if (found) {
          coreExamples.setEnabled(true);
          examplesMenu.insert(coreExamples, separatorIndex);
          examplesMenu.insertSeparator(separatorIndex);
          librariesExamplesFound = true;
        }       
      }
      separatorIndex = examplesMenu.getItemCount();
      JMenu librariesExamples = new JMenu("Cross-platform");
      found = addSketches(librariesExamples, librariesFolder, false);
      if (found) {
        librariesExamples.setEnabled(true);
        examplesMenu.insert(librariesExamples, separatorIndex);
        examplesMenu.insertSeparator(separatorIndex);
        librariesExamplesFound = true;
      }
      separatorIndex = examplesMenu.getItemCount();
      JMenu contributedExamples = new JMenu("Contributed");
      found = addSketches(contributedExamples, getSketchbookLibrariesFolder(), false);
      if (found) {
        contributedExamples.setEnabled(true);
        examplesMenu.insert(contributedExamples, separatorIndex);
        examplesMenu.insertSeparator(separatorIndex);
        librariesExamplesFound = true;
      }
      if (librariesExamplesFound) {
        JMenuItem librariesMenu = new JMenuItem("Libraries");
        librariesMenu.setEnabled(false);
        librariesMenu.setFont(librariesMenu.getFont().deriveFont(Font.BOLD));
        librariesMenu.setFont(librariesMenu.getFont().deriveFont(Font.ITALIC));
        examplesMenu.insert(librariesMenu, librariesSeparatorIndex);
        examplesMenu.insertSeparator(librariesSeparatorIndex);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

 
  public void rebuildExamplesMenu(JMenu menu) {
    try {
      menu.removeAll();
      boolean found = addSketches(menu, examplesFolder, false);
      if (found) menu.addSeparator();
      found = addSketches(menu, getSketchbookLibrariesFolder(), false);
      if (found) menu.addSeparator();
      found = addSketches(menu, librariesFolder, false);
      if (found) menu.addSeparator();
      if (coreAvailable())
        addSketches(menu, getCoreLibrariesFolder(), false);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

 
  public void onBoardOrPortChange() {
    for (Editor editor : editors) {
      editor.onBoardOrPortChange();
   
  }
 
 
  public void rebuildBoardsMenu(JMenu menu) {
    targetsTable = new HashMap<String, Target>();
    loadHardware(getHardwareFolder());
    loadHardware(getSketchbookHardwareFolder());
   
    if (coreAvailable()) {
      coresTable = new HashMap<String, File>();
      loadCores(getCoresFolder());
      loadCores(getSketchbookCoresFolder());
     
      coreLibrariesFolder = getCoreLibrariesFolder();
    }
   
    menu.removeAll();     
    ButtonGroup group = new ButtonGroup();
    for (Target target : targetsTable.values()) {
      JMenu targetSubMenu = new JMenu(target.getName());
      for (String board : target.getBoards().keySet()) {
        AbstractAction action =
          new AbstractAction(target.getBoards().get(board).get("name")) {
            public void actionPerformed(ActionEvent actionevent) {
              Preferences.set("target", (String) getValue("target"));
              Preferences.set("board", (String) getValue("board"));
             
              onBoardOrPortChange();
              rebuildExamplesMenu();
              rebuildImportMenu();
            }
          };
        action.putValue("target", target.getName());
        action.putValue("board", board);
        JMenuItem item = new JRadioButtonMenuItem(action);
        if (target.getName().equals(Preferences.get("target")) &&
            board.equals(Preferences.get("board"))) {
          item.setSelected(true);
        }
        group.add(item);
        targetSubMenu.add(item);
      }
      menu.add(targetSubMenu);
    }
  }
 
  public void rebuildBurnBootloaderMenu(JMenu menu) {
    menu.removeAll();     
    Map<String, String> boardPreferences =  Base.getBoardPreferences();
   
    for (Target target : targetsTable.values()) {
      if (target.getName().equals(Preferences.get("target"))) {
        JMenuItem targetItem = new JMenuItem(target.getName()+" : "+boardPreferences.get("name"));
        targetItem.setEnabled(false);
        targetItem.setFont(targetItem.getFont().deriveFont(Font.BOLD));
        targetItem.setFont(targetItem.getFont().deriveFont(Font.ITALIC));
        menu.add(targetItem);
        for (String programmer : target.getProgrammers().keySet()) {
          AbstractAction action =
            new AbstractAction(
              "w/ " + target.getProgrammers().get(programmer).get("name")) {
              public void actionPerformed(ActionEvent actionevent) {
                activeEditor.handleBurnBootloader((String) getValue("target"),
                                                  (String) getValue("programmer"));
              }
            };
          action.putValue("target", target.getName());
          action.putValue("programmer", programmer);
          JMenuItem item = new JMenuItem(action);
          menu.add(item);
        }
      }
    } 
  } 
   


  /**
   * Scan a folder recursively, and add any sketches found to the menu
   * specified. Set the openReplaces parameter to true when opening the sketch
   * should replace the sketch in the current window, or false when the
   * sketch should open in a new window.
   */
  protected boolean addSketches(JMenu menu, File folder,
                                final boolean replaceExisting) throws IOException {
    // skip .DS_Store files, etc (this shouldn't actually be necessary)
    if (!folder.isDirectory()) return false;

    String[] list = folder.list();
    // If a bad folder or unreadable or whatever, this will come back null
    if (list == null) return false;

    // Alphabetize list, since it's not always alpha order
    Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);

    ActionListener listener = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          String path = e.getActionCommand();
          if (new File(path).exists()) {
            boolean replace = replaceExisting;
            if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0) {
              replace = !replace;
            }
            if (replace) {
              handleOpenReplace(path);
            } else {
              handleOpen(path);
            }
          } else {
            showWarning("Sketch Does Not Exist",
                        "The selected sketch no longer exists.\n" +
                        "You may need to restart Wiring to update\n" +
                        "the sketchbook menu.", null);
          }
        }
      };

    boolean ifound = false;

    for (int i = 0; i < list.length; i++) {
      if ((list[i].charAt(0) == '.') ||
          list[i].equals("CVS")) continue;

      File subfolder = new File(folder, list[i]);
      if (!subfolder.isDirectory()) continue;

      File entry = new File(subfolder, list[i] + ".pde");
      if (!entry.exists() && (new File(subfolder, list[i] + ".ino")).exists()) {
        entry = new File(subfolder, list[i] + ".ino");
      }
      // if a .pde file of the same prefix as the folder exists..
      if (entry.exists()) {
        if (!Sketch.isSanitaryName(list[i])) {
          if (!builtOnce) {
            String complaining =
              "The sketch \"" + list[i] + "\" cannot be used.\n" +
              "Sketch names must contain only basic letters and numbers\n" +
              "(ASCII-only with no spaces, " +
              "and it cannot start with a number).\n" +
              "To get rid of this message, remove the sketch from\n" +
              entry.getAbsolutePath();
            Base.showMessage("Ignoring sketch with bad name", complaining);
          }
          continue;
        }

        JMenuItem item = new JMenuItem(list[i]);
        item.addActionListener(listener);
        item.setActionCommand(entry.getAbsolutePath());
        menu.add(item);
        ifound = true;

      } else {
        // not a sketch folder, but maybe a subfolder containing sketches
        JMenu submenu = new JMenu(list[i]);
        // needs to be separate var otherwise would set ifound to false
        boolean found = addSketches(submenu, subfolder, replaceExisting); //, false);
        if (found) {
          menu.add(submenu);
          ifound = true;
        }
      }
    }
    return ifound;  // actually ignored, but..
  }

  protected boolean addLibraries(JMenu menu, File folder) throws IOException {
    if (!folder.isDirectory()) return false;

    String list[] = folder.list(new FilenameFilter() {
      public boolean accept(File dir, String name) {
        // skip .DS_Store files, .svn folders, etc
        if (name.charAt(0) == '.') return false;
        if (name.equals("CVS")) return false;
        return (new File(dir, name).isDirectory());
      }
    });
    // if a bad folder or something like that, this might come back null
    if (list == null) return false;

    // alphabetize list, since it's not always alpha order
    Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);

    ActionListener listener = new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          activeEditor.getSketch().importLibrary(e.getActionCommand());
        }
      };

    boolean ifound = false;

    for (String potentialName : list) {
      File subfolder = new File(folder, potentialName);

      String sanityCheck = Sketch.sanitizeName(potentialName);
      if (!sanityCheck.equals(potentialName)) {
        String mess =
          "The library \"" + potentialName + "\" cannot be used.\n" +
          "Library names must contain only basic letters and numbers.\n" +
          "(ASCII only and no spaces, and it cannot start with a number)";
        Base.showMessage("Ignoring bad library name", mess);
        continue;
      }


      String libraryName = potentialName;
      libraries.add(subfolder);
      String packages[] =
        Compiler.headerListFromIncludePath(subfolder.getAbsolutePath());
      for (String pkg : packages) {
        importToLibraryTable.put(pkg, subfolder);
      }

      JMenuItem item = new JMenuItem(libraryName);
      item.addActionListener(listener);
      item.setActionCommand(subfolder.getAbsolutePath());
      menu.add(item);
      ifound = true;

// XXX: DAM: should recurse here so that library folders can be nested
    }
    return ifound;
  }


  protected void loadHardware(File folder) {
    if (!folder.isDirectory()) return;
   
    String list[] = folder.list(new FilenameFilter() {
      public boolean accept(File dir, String name) {
        // skip .DS_Store files, .svn folders, etc
        if (name.charAt(0) == '.') return false;
        if (name.equals("CVS")) return false;
        return (new File(dir, name).isDirectory());
      }
    });
    // if a bad folder or something like that, this might come back null
    if (list == null) return;

    // alphabetize list, since it's not always alpha order
    // replaced hella slow bubble sort with this feller for 0093
    Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
   
    for (String target : list) {
      File subfolder = new File(folder, target);
      targetsTable.put(target, new Target(target, subfolder));
    }
  }

 
  protected void loadCores(File folder) {
    if (!folder.isDirectory()) return;
   
    String list[] = folder.list(new FilenameFilter() {
      public boolean accept(File dir, String name) {
        // skip .DS_Store files, .svn folders, etc
        if (name.charAt(0) == '.') return false;
        if (name.equals("CVS")) return false;
        return (new File(dir, name).isDirectory());
      }
    });
    // if a bad folder or something like that, this might come back null
    if (list == null) return;
   
    // alphabetize list, since it's not always alpha order
    // replaced hella slow bubble sort with this feller for 0093
    Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
   
    for (String target : list) {
      File subfolder = new File(folder, target);
      coresTable.put(target, subfolder);
    }
  }
 

  // .................................................................


  /**
   * Show the About box.
   */
  public void handleAbout() {
    final Image image = Base.getLibImage("about.jpg", activeEditor);
    final Window window = new Window(activeEditor) {
        public void paint(Graphics g) {
          g.drawImage(image, 0, 0, null);

          Graphics2D g2 = (Graphics2D) g;
          g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                              RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);

          g.setFont(new Font("SansSerif", Font.PLAIN, 11));
          g.setColor(Color.white);
          g.drawString("Version "+ "1.0" + " Build " +Base.REVISION, 34, 30);
        }
      };
    window.addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
          window.dispose();
        }
      });
    int w = image.getWidth(activeEditor);
    int h = image.getHeight(activeEditor);
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    window.setBounds((screen.width-w)/2, (screen.height-h)/2, w, h);
    window.setVisible(true);
  }


  /**
   * Show the Preferences window.
   */
  public void handlePrefs() {
    if (preferencesFrame == null) preferencesFrame = new Preferences();
    preferencesFrame.showFrame(activeEditor);
  }


  // ...................................................................


  static public Platform getPlatform() {
    return platform;
  }


  static public String getPlatformName() {
    String osname = System.getProperty("os.name");

    if (osname.indexOf("Mac") != -1) {
      return "macosx";

    } else if (osname.indexOf("Windows") != -1) {
      return "windows";

    } else if (osname.equals("Linux")) {  // true for the ibm vm
      return "linux";

    } else {
      return "other";
    }
  }


  /**
   * Map a platform constant to its name.
   * @param which PConstants.WINDOWS, PConstants.MACOSX, PConstants.LINUX
   * @return one of "windows", "macosx", or "linux"
   */
  static public String getPlatformName(int which) {
    return platformNames.get(which);
  }


  static public int getPlatformIndex(String what) {
    Integer entry = platformIndices.get(what);
    return (entry == null) ? -1 : entry.intValue();
  }


  /**
   * returns true if Processing is running on a Mac OS X machine.
   */
  static public boolean isMacOS() {
    return System.getProperty("os.name").indexOf("Mac") != -1;
  }


  /**
   * returns true if running on windows.
   */
  static public boolean isWindows() {
    return System.getProperty("os.name").indexOf("Windows") != -1;
  }


  /**
   * true if running on linux.
   */
  static public boolean isLinux() {
    return System.getProperty("os.name").indexOf("Linux") != -1;
  }


  // .................................................................


  static public File getSettingsFolder() {
    File settingsFolder = null;

    String preferencesPath = Preferences.get("settings.path");
    if (preferencesPath != null) {
      settingsFolder = new File(preferencesPath);

    } else {
      try {
        settingsFolder = platform.getSettingsFolder();
      } catch (Exception e) {
        showError("Problem getting data folder",
                  "Error getting the Wiring data folder.", e);
      }
    }

    // create the folder if it doesn't exist already
    if (!settingsFolder.exists()) {
      if (!settingsFolder.mkdirs()) {
        showError("Settings issues",
                  "Wiring cannot run because it could not\n" +
                  "create a folder to store your settings.", null);
      }
    }
    return settingsFolder;
  }


  /**
   * Convenience method to get a File object for the specified filename inside
   * the settings folder.
   * For now, only used by Preferences to get the preferences.txt file.
   * @param filename A file inside the settings folder.
   * @return filename wrapped as a File object inside the settings folder
   */
  static public File getSettingsFile(String filename) {
    return new File(getSettingsFolder(), filename);
  }


  static public File getBuildFolder() {
    if (buildFolder == null) {
      String buildPath = Preferences.get("build.path");
      if (buildPath != null) {
        buildFolder = new File(buildPath);

      } else {
        buildFolder = createTempFolder("build");
        buildFolder.deleteOnExit();
      }
    }
    return buildFolder;
  }


  /**
   * Get the path to the platform's temporary folder, by creating
   * a temporary temporary file and getting its parent folder.
   * <br/>
   * Modified for revision 0094 to actually make the folder randomized
   * to avoid conflicts in multi-user environments. (Bug 177)
   */
  static public File createTempFolder(String name) {
    try {
      File folder = File.createTempFile(name, null);
      folder.delete();
      folder.mkdirs();
      return folder;

    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }

 
  static public Set<File> getLibraries() {
    return libraries;
  }

 
  static public String getExamplesPath() {
    return examplesFolder.getAbsolutePath();
  }


  static public String getLibrariesPath() {
    return librariesFolder.getAbsolutePath();
  }


  static public File getToolsFolder() {
    return toolsFolder;
  }


  static public String getToolsPath() {
    return toolsFolder.getAbsolutePath();
  }

 
  static public File getHardwareFolder() {
    return getContentFile("hardware");
  }
 
 
  static public String getHardwarePath() {
    return getHardwareFolder().getAbsolutePath();
  }


  static public File getCoresFolder() {
    return getContentFile("cores");
  }
  static public String getCoresPath() {
    return getCoresFolder().getAbsolutePath();
  }
 
 
  static public boolean coreAvailable() {
    Map<String, String> boardPreferences = getBoardPreferences();
    String hardware = boardPreferences.get("build.hardware");
    if (hardware == null) return false;
    String core = boardPreferences.get("build.core");
    if (core == null) return false;
    return true;
  }
 
 
  // there is only one core selected at a time, so only one "libraries" folder per core.
  // return the current core's selected libraries folder
  static public File getCoreLibrariesFolder() {
    Map<String, String> boardPreferences = getBoardPreferences();
    String hardware = boardPreferences.get("build.hardware");
    if (hardware == null) {
      showError("No board selected", "please choose a board from the Tools > Board menu.", null);
      return null;
    }
    String core = boardPreferences.get("build.core");
    String coresPath;
    if (core == null) {
      showError("The board selected has no core specified", "check your board's definition of build.core.", null);
      return null;
    }
    File coresFolder = coresTable.get(core);
    if (coresFolder == null) return null;
    coresPath = coresFolder.getAbsolutePath();
    if (coresPath ==  null) return null;
    return new File(coresFolder, "libraries");
  }
 
 
  static public String getCoreLibrariesPath() {
    return getCoreLibrariesFolder().getAbsolutePath();
  }
 

 
  static public String getAvrBasePath() {   
    return getToolsPath()
    File.separator + "avr" + File.separator + "bin" + File.separator;
  }

 
  static public Target getTarget() {
    return Base.targetsTable.get(Preferences.get("target"));
  }

 
  static public Map<String, String> getBoardPreferences() {
    Target target = getTarget();
    if (target == null) return new LinkedHashMap<String, String>();
    Map<String, Map<String,String>> boardsMap = target.getBoards();
    if (boardsMap == null) return new LinkedHashMap<String, String>();
    Map<String, String> map = boardsMap.get(Preferences.get("board"));
    if (map == null) return new LinkedHashMap<String, String>();
    return map;
  }

 
  static public File getSketchbookFolder() {
    return new File(Preferences.get("sketchbook.path"));
  }


  static public File getSketchbookLibrariesFolder() {
    return new File(getSketchbookFolder(), "libraries");
  }


  static public String getSketchbookLibrariesPath() {
    return getSketchbookLibrariesFolder().getAbsolutePath();
  }

 
  static public File getSketchbookHardwareFolder() {
    return new File(getSketchbookFolder(), "hardware");
  }

 
  static public File getSketchbookCoresFolder() {
    return new File(getSketchbookFolder(), "cores");
  }
 
 
  protected File getDefaultSketchbookFolder() {
    File sketchbookFolder = null;
    try {
      sketchbookFolder = platform.getDefaultSketchbookFolder();
    } catch (Exception e) { }

    if (sketchbookFolder == null) {
      sketchbookFolder = promptSketchbookLocation();
    }

    // create the folder if it doesn't exist already
    boolean result = true;
    if (!sketchbookFolder.exists()) {
      result = sketchbookFolder.mkdirs();
    }

    if (!result) {
      showError("You forgot your sketchbook",
                "Wiring cannot run because it could not\n" +
                "create a folder to store your sketchbook.", null);
    }

    return sketchbookFolder;
  }


  /**
   * Check for a new sketchbook location.
   */
  static protected File promptSketchbookLocation() {
    File folder = null;

    folder = new File(System.getProperty("user.home"), "sketchbook");
    if (!folder.exists()) {
      folder.mkdirs();
      return folder;
    }

    String prompt = "Select (or create new) folder for sketches...";
    folder = Base.selectFolder(prompt, null, null);
    if (folder == null) {
      System.exit(0);
    }
    return folder;
  }


  // .................................................................


  /**
   * Implements the cross-platform headache of opening URLs
   * TODO This code should be replaced by PApplet.link(),
   * however that's not a static method (because it requires
   * an AppletContext when used as an applet), so it's mildly
   * trickier than just removing this method.
   */
  static public void openURL(String url) {
    try {
      platform.openURL(url);

    } catch (Exception e) {
      showWarning("Problem Opening URL",
                  "Could not open the URL\n" + url, e);
    }
  }


  /**
   * Used to determine whether to disable the "Show Sketch Folder" option.
   * @return true If a means of opening a folder is known to be available.
   */
  static protected boolean openFolderAvailable() {
    return platform.openFolderAvailable();
  }


  /**
   * Implements the other cross-platform headache of opening
   * a folder in the machine's native file browser.
   */
  static public void openFolder(File file) {
    try {
      platform.openFolder(file);

    } catch (Exception e) {
      showWarning("Problem Opening Folder",
                  "Could not open the folder\n" + file.getAbsolutePath(), e);
    }
  }


  // .................................................................


  /**
   * Prompt for a fodler and return it as a File object (or null).
   * Implementation for choosing directories that handles both the
   * Mac OS X hack to allow the native AWT file dialog, or uses
   * the JFileChooser on other platforms. Mac AWT trick obtained from
   * <A HREF="http://lists.apple.com/archives/java-dev/2003/Jul/msg00243.html">this post</A>
   * on the OS X Java dev archive which explains the cryptic note in
   * Apple's Java 1.4 release docs about the special System property.
   */
  static public File selectFolder(String prompt, File folder, Frame frame) {
    if (Base.isMacOS()) {
      if (frame == null) frame = new Frame();
      FileDialog fd = new FileDialog(frame, prompt, FileDialog.LOAD);
      if (folder != null) {
        fd.setDirectory(folder.getParent());
      }
      System.setProperty("apple.awt.fileDialogForDirectories", "true");
      fd.setVisible(true);
      System.setProperty("apple.awt.fileDialogForDirectories", "false");
      if (fd.getFile() == null) {
        return null;
      }
      return new File(fd.getDirectory(), fd.getFile());

    } else {
      JFileChooser fc = new JFileChooser();
      fc.setDialogTitle(prompt);
      if (folder != null) {
        fc.setSelectedFile(folder);
      }
      fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

      int returned = fc.showOpenDialog(new JDialog());
      if (returned == JFileChooser.APPROVE_OPTION) {
        return fc.getSelectedFile();
      }
    }
    return null;
  }


  // .................................................................


  /**
   * Give this Frame a Processing icon.
   */
  static public void setIcon(Frame frame) {
    Image image = Toolkit.getDefaultToolkit().createImage(PApplet.ICON_IMAGE);
    frame.setIconImage(image);
  }


  /**
   * Registers key events for a Ctrl-W and ESC with an ActionListener
   * that will take care of disposing the window.
   */
  static public void registerWindowCloseKeys(JRootPane root,
                                             ActionListener disposer) {
    KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
    root.registerKeyboardAction(disposer, stroke,
                                JComponent.WHEN_IN_FOCUSED_WINDOW);

    int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    stroke = KeyStroke.getKeyStroke('W', modifiers);
    root.registerKeyboardAction(disposer, stroke,
                                JComponent.WHEN_IN_FOCUSED_WINDOW);
  }


  // .................................................................


  static public void showReference(String filename) {
    String language = Preferences.get("reference.language");
    File referenceFolder;
    if (language.equals("en")) {
      referenceFolder = Base.getContentFile("reference");
    } else {
      referenceFolder = Base.getContentFile("reference" + File.separator + language);
    }
    File referenceFile = new File(referenceFolder, filename);
    openURL(referenceFile.getAbsolutePath());
  }


  static public void showReference() {
    showReference("index.html");
  }


  static public void showExamples() {
    showReference("learning" + File.separator + "index.html");
  }


  static public void showHardware() {
    showReference("hardware" + File.separator + "index.html");
  }


  static public void showEnvironment() {
    showReference("environment" + File.separator + "index.html");
  }

 
  static public void showFAQ() {
    openURL("http://wiki.wiring.co/index.php?title=FAQ");
  }
 
 
  static public void showGettingStarted() {
    openURL("http://wiring.org.co/learning/tutorials/");
  }
 
 
  static public void showPlatforms() {
    showReference("environment" + File.separator + "platforms.html");
  }


  static public void showTroubleshooting() {
    openURL("http://forum.wiring.co");
  }


  // .................................................................


  /**
   * "No cookie for you" type messages. Nothing fatal or all that
   * much of a bummer, but something to notify the user about.
   */
  static public void showMessage(String title, String message) {
    if (title == null) title = "Message";

    if (commandLine) {
      System.out.println(title + ": " + message);

    } else {
      JOptionPane.showMessageDialog(new Frame(), message, title,
                                    JOptionPane.INFORMATION_MESSAGE);
    }
  }


  /**
   * Non-fatal error message with optional stack trace side dish.
   */
  static public void showWarning(String title, String message, Exception e) {
    if (title == null) title = "Warning";

    if (commandLine) {
      System.out.println(title + ": " + message);

    } else {
      JOptionPane.showMessageDialog(new Frame(), message, title,
                                    JOptionPane.WARNING_MESSAGE);
    }
    if (e != null) e.printStackTrace();
  }


  /**
   * Show an error message that's actually fatal to the program.
   * This is an error that can't be recovered. Use showWarning()
   * for errors that allow P5 to continue running.
   */
  static public void showError(String title, String message, Throwable e) {
    if (title == null) title = "Error";

    if (commandLine) {
      System.err.println(title + ": " + message);

    } else {
      JOptionPane.showMessageDialog(new Frame(), message, title,
                                    JOptionPane.ERROR_MESSAGE);
    }
    if (e != null) e.printStackTrace();
    System.exit(1);
  }


  // ...................................................................



  // incomplete
  static public int showYesNoCancelQuestion(Editor editor, String title,
                                            String primary, String secondary) {
    if (!Base.isMacOS()) {
      int result =
        JOptionPane.showConfirmDialog(null, primary + "\n" + secondary, title,
                                      JOptionPane.YES_NO_CANCEL_OPTION,
                                      JOptionPane.QUESTION_MESSAGE);
      return result;
    } else {
      // Pane formatting adapted from the Quaqua guide
      // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
      JOptionPane pane =
        new JOptionPane("<html> " +
                        "<head> <style type=\"text/css\">"+
                        "b { font: 13pt \"Lucida Grande\" }"+
                        "p { font: 11pt \"Lucida Grande\"; margin-top: 8px }"+
                        "</style> </head>" +
                        "<b>Do you want to save changes to this sketch<BR>" +
                        " before closing?</b>" +
                        "<p>If you don't save, your changes will be lost.",
                        JOptionPane.QUESTION_MESSAGE);

      String[] options = new String[] {
          "Save", "Cancel", "Don't Save"
      };
      pane.setOptions(options);

      // highlight the safest option ala apple hig
      pane.setInitialValue(options[0]);

      // on macosx, setting the destructive property places this option
      // away from the others at the lefthand side
      pane.putClientProperty("Quaqua.OptionPane.destructiveOption",
                             new Integer(2));

      JDialog dialog = pane.createDialog(editor, null);
      dialog.setVisible(true);

      Object result = pane.getValue();
      if (result == options[0]) {
        return JOptionPane.YES_OPTION;
      } else if (result == options[1]) {
        return JOptionPane.CANCEL_OPTION;
      } else if (result == options[2]) {
        return JOptionPane.NO_OPTION;
      } else {
        return JOptionPane.CLOSED_OPTION;
      }
    }
  }

  static public int showYesNoQuestion(Frame editor, String title,
                                            String primary, String secondary) {
    if (!Base.isMacOS()) {
      return JOptionPane.showConfirmDialog(editor,
                                           "<html><body>" +
                                           "<b>" + primary + "</b>" +
                                           "<br>" + secondary, title,
                                           JOptionPane.YES_NO_OPTION,
                                           JOptionPane.QUESTION_MESSAGE);
    } else {
      // Pane formatting adapted from the Quaqua guide
      // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
      JOptionPane pane =
        new JOptionPane("<html> " +
                        "<head> <style type=\"text/css\">"+
                        "b { font: 13pt \"Lucida Grande\" }"+
                        "p { font: 11pt \"Lucida Grande\"; margin-top: 8px }"+
                        "</style> </head>" +
                        "<b>" + primary + "</b>" +
                        "<p>" + secondary + "</p>",
                        JOptionPane.QUESTION_MESSAGE);

      String[] options = new String[] {
          "Yes", "No"
      };
      pane.setOptions(options);

      // highlight the safest option ala apple hig
      pane.setInitialValue(options[0]);

      JDialog dialog = pane.createDialog(editor, null);
      dialog.setVisible(true);

      Object result = pane.getValue();
      if (result == options[0]) {
        return JOptionPane.YES_OPTION;
      } else if (result == options[1]) {
        return JOptionPane.NO_OPTION;
      } else {
        return JOptionPane.CLOSED_OPTION;
      }
    }
  }

  static public String urlDecode(String str) {
    try {
      return URLDecoder.decode(str, "UTF-8");
    } catch (UnsupportedEncodingException e) {  // safe per the JDK source
      return null;
    }
  }

  /**
   * Adjacent the executable on Windows and Linux,
   * or inside Contents/Resources/Java on Mac OS X.
   */
  static protected File wiringRoot;
 
 
  static public File getContentFile(String name) {
    if (wiringRoot == null) {
      // Get the path to the .jar file that contains Base.class
      String path = Base.class.getProtectionDomain().getCodeSource().getLocation().getPath();
      // Path may have URL encoding, so remove it
      String decodedPath = urlDecode(path);
     
      if (decodedPath.contains("/app/bin")) {
        if (Base.isMacOS()) {
          wiringRoot =
          new File(path, "../../build/macosx/work/Processing.app/Contents/Java");
        } else if (Base.isWindows()) {
          wiringRoot =  new File(path, "../../build/windows/work");
        } else if (Base.isLinux()) {
          wiringRoot =  new File(path, "../../build/linux/work");
        }
      } else {
        // The .jar file will be in the lib folder
        File jarFolder = new File(decodedPath).getParentFile();
        if (jarFolder.getName().equals("lib")) {
          // The main Processing installation directory.
          // This works for Windows, Linux, and Apple's Java 6 on OS X.
          wiringRoot = jarFolder.getParentFile();
        } else if (Base.isMacOS()) {
          // This works for Java 7 on OS X. The 'lib' folder is not part of the
          // classpath on OS X, and adding it creates more problems than it's
          // worth.
          wiringRoot = jarFolder;
         
        }
        if (wiringRoot == null || !wiringRoot.exists()) {
          // Try working directory instead (user.dir, different from user.home)
          System.err.println("Could not find lib folder via " +
                             jarFolder.getAbsolutePath() +
                             ", switching to user.dir");
          wiringRoot = new File(System.getProperty("user.dir"));
        }
      }
    }
   
    /*
    String path = System.getProperty("user.dir");

    // Get a path to somewhere inside the .app folder
    if (Base.isMacOS()) {
      String javaroot = System.getProperty("javaroot");
      if (javaroot != null) {
        path = javaroot;
      }
    }
    File working = new File(path);
     */
    return new File(wiringRoot, name);
  }


  /**
   * Get an image associated with the current color theme.
   */
  static public Image getThemeImage(String name, Component who) {
    return getLibImage("theme/" + name, who);
  }


  /**
   * Return an Image object from inside the Processing lib folder.
   */
  static public Image getLibImage(String name, Component who) {
    Image image = null;
    Toolkit tk = Toolkit.getDefaultToolkit();

    File imageLocation = new File(getContentFile("lib"), name);
    image = tk.getImage(imageLocation.getAbsolutePath());
    MediaTracker tracker = new MediaTracker(who);
    tracker.addImage(image, 0);
    try {
      tracker.waitForAll();
    } catch (InterruptedException e) { }
    return image;
  }


  /**
   * Return an InputStream for a file inside the Processing lib folder.
   */
  static public InputStream getLibStream(String filename) throws IOException {
    /*
    Properties p = System.getProperties();
    Enumeration keys = p.keys();
    String res = new String();
    while (keys.hasMoreElements()) {
      String key = (String)keys.nextElement();
      String value = (String)p.get(key);
      res += key + ": " + value+ "\n";
    }
    showMessage("HERE ", getContentFile("lib").getAbsolutePath() +"XXX"+ System.getProperty("os.name")+"XXX"+res);
    */
    return new FileInputStream(new File(getContentFile("lib"), filename));
  }


  // ...................................................................


  /**
   * Get the number of lines in a file by counting the number of newline
   * characters inside a String (and adding 1).
   */
  static public int countLines(String what) {
    int count = 1;
    for (char c : what.toCharArray()) {
      if (c == '\n') count++;
    }
    return count;
  }


  /**
   * Same as PApplet.loadBytes(), however never does gzip decoding.
   */
  static public byte[] loadBytesRaw(File file) throws IOException {
    int size = (int) file.length();
    FileInputStream input = new FileInputStream(file);
    byte buffer[] = new byte[size];
    int offset = 0;
    int bytesRead;
    while ((bytesRead = input.read(buffer, offset, size-offset)) != -1) {
      offset += bytesRead;
      if (bytesRead == 0) break;
    }
    input.close()// weren't properly being closed
    input = null;
    return buffer;
  }


  /**
   * Read from a file with a bunch of attribute/value pairs
   * that are separated by = and ignore comments with #.
   */
  static public HashMap<String,String> readSettings(File inputFile) {
    HashMap<String,String> outgoing = new HashMap<String,String>();
    if (!inputFile.exists()) return outgoing;  // return empty hash

    String lines[] = PApplet.loadStrings(inputFile);
    for (int i = 0; i < lines.length; i++) {
      int hash = lines[i].indexOf('#');
      String line = (hash == -1) ?
        lines[i].trim() : lines[i].substring(0, hash).trim();
      if (line.length() == 0) continue;

      int equals = line.indexOf('=');
      if (equals == -1) {
        System.err.println("ignoring illegal line in " + inputFile);
        System.err.println("  " + line);
        continue;
      }
      String attr = line.substring(0, equals).trim();
      String valu = line.substring(equals + 1).trim();
      outgoing.put(attr, valu);
    }
    return outgoing;
  }


  static public void copyFile(File sourceFile,
                              File targetFile) throws IOException {
    InputStream from =
      new BufferedInputStream(new FileInputStream(sourceFile));
    OutputStream to =
      new BufferedOutputStream(new FileOutputStream(targetFile));
    byte[] buffer = new byte[16 * 1024];
    int bytesRead;
    while ((bytesRead = from.read(buffer)) != -1) {
      to.write(buffer, 0, bytesRead);
    }
    to.flush();
    from.close(); // ??
    from = null;
    to.close(); // ??
    to = null;

    targetFile.setLastModified(sourceFile.lastModified());
  }


  /**
   * Grab the contents of a file as a string.
   */
  static public String loadFile(File file) throws IOException {
    String[] contents = PApplet.loadStrings(file);
    if (contents == null) return null;
    return PApplet.join(contents, "\n");
  }


  /**
   * Spew the contents of a String object out to a file.
   */
  static public void saveFile(String str, File file) throws IOException {
    File temp = File.createTempFile(file.getName(), null, file.getParentFile());
    PApplet.saveStrings(temp, new String[] { str });

    if (file.exists()) {
      boolean result = file.delete();
      if (!result) {
        throw new IOException("Could not remove old version of " +
                              file.getAbsolutePath());
      }
    }
    boolean result = temp.renameTo(file);
    if (!result) {
      throw new IOException("Could not replace " +
                            file.getAbsolutePath());
    }
  }


  /**
   * Copy a folder from one place to another. This ignores all dot files and
   * folders found in the source directory, to avoid copying silly .DS_Store
   * files and potentially troublesome .svn folders.
   */
  static public void copyDir(File sourceDir,
                             File targetDir) throws IOException {
    if (sourceDir.equals(targetDir)) {
      final String urDum = "source and target directories are identical";
      throw new IllegalArgumentException(urDum);
    }
    targetDir.mkdirs();
    String files[] = sourceDir.list();
    for (int i = 0; i < files.length; i++) {
      // Ignore dot files (.DS_Store), dot folders (.svn) while copying
      if (files[i].charAt(0) == '.') continue;
      File source = new File(sourceDir, files[i]);
      File target = new File(targetDir, files[i]);
      if (source.isDirectory()) {
        copyDir(source, target);
        target.setLastModified(source.lastModified());
      } else {
        copyFile(source, target);
      }
    }
  }


  /**
   * Remove all files in a directory and the directory itself.
   */
  static public void removeDir(File dir) {
    if (dir.exists()) {
      removeDescendants(dir);
      if (!dir.delete()) {
        System.err.println("Could not delete " + dir);
      }
    }
  }


  /**
   * Recursively remove all files within a directory,
   * used with removeDir(), or when the contents of a dir
   * should be removed, but not the directory itself.
   * (i.e. when cleaning temp files from lib/build)
   */
  static public void removeDescendants(File dir) {
    if (!dir.exists()) return;

    String files[] = dir.list();
    for (int i = 0; i < files.length; i++) {
      if (files[i].equals(".") || files[i].equals("..")) continue;
      File dead = new File(dir, files[i]);
      if (!dead.isDirectory()) {
        if (!Preferences.getBoolean("compiler.save_build_files")) {
          if (!dead.delete()) {
            // temporarily disabled
            System.err.println("Could not delete " + dead);
          }
        }
      } else {
        removeDir(dead);
      }
    }
  }


  /**
   * Calculate the size of the contents of a folder.
   * Used to determine whether sketches are empty or not.
   * Note that the function calls itself recursively.
   */
  static public int calcFolderSize(File folder) {
    int size = 0;

    String files[] = folder.list();
    // null if folder doesn't exist, happens when deleting sketch
    if (files == null) return -1;

    for (int i = 0; i < files.length; i++) {
      if (files[i].equals(".") || (files[i].equals("..")) ||
          files[i].equals(".DS_Store")) continue;
      File fella = new File(folder, files[i]);
      if (fella.isDirectory()) {
        size += calcFolderSize(fella);
      } else {
        size += (int) fella.length();
      }
    }
    return size;
  }


  /**
   * Recursively creates a list of all files within the specified folder,
   * and returns a list of their relative paths.
   * Ignores any files/folders prefixed with a dot.
   */
  static public String[] listFiles(String path, boolean relative) {
    return listFiles(new File(path), relative);
  }


  static public String[] listFiles(File folder, boolean relative) {
    String path = folder.getAbsolutePath();
    Vector<String> vector = new Vector<String>();
    listFiles(relative ? (path + File.separator) : "", path, vector);
    String outgoing[] = new String[vector.size()];
    vector.copyInto(outgoing);
    return outgoing;
  }


  static protected void listFiles(String basePath,
                                  String path, Vector<String> vector) {
    File folder = new File(path);
    String list[] = folder.list();
    if (list == null) return;

    for (int i = 0; i < list.length; i++) {
      if (list[i].charAt(0) == '.') continue;

      File file = new File(path, list[i]);
      String newPath = file.getAbsolutePath();
      if (newPath.startsWith(basePath)) {
        newPath = newPath.substring(basePath.length());
      }
      vector.add(newPath);
      if (file.isDirectory()) {
        listFiles(basePath, newPath, vector);
      }
    }
  }
}
TOP

Related Classes of processing.app.Base

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.