Closing the last open sketch will quit Processing."; + String prefix = Preferences.get("editor.untitled.prefix"); - 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; + // 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("yyMMdd"); + //SimpleDateFormat formatter = new SimpleDateFormat("MMMdd"); + //String purty = formatter.format(new Date()).toLowerCase(); + 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) { + Messages.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 { + Messages.showWarning("Sunshine", + "No really, time for some fresh air for you.", null); } + return; } - } + 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(sketchbookFolder, newbieName).exists()); - Preferences.unset("server.port"); //$NON-NLS-1$ - Preferences.unset("server.key"); //$NON-NLS-1$ + // Make the directory for the new sketch + newbieDir.mkdirs(); - // This will store the sketch count as zero - editors.remove(editor); -// System.out.println("editors size now " + editors.size()); -// storeSketches(); + // Add any template files from the Mode itself + File newbieFile = nextMode.addTemplateFiles(newbieDir, newbieName); - // Save out the current prefs state - Preferences.save(); + /* + // Make an empty pde file + File newbieFile = + new File(newbieDir, newbieName + "." + nextMode.getDefaultExtension()); //$NON-NLS-1$ + if (!newbieFile.createNewFile()) { + throw new IOException(newbieFile + " already exists."); + } + */ - if (defaultFileMenu == null) { - if (modeSwitch) { - // need to close this editor, ever so temporarily - editor.setVisible(false); - editor.dispose(); - activeEditor = null; - editors.remove(editor); - } else { - // Since this wasn't an actual Quit event, call System.exit() - System.exit(0); - } - } else { // on OS X, update the default file menu - editor.setVisible(false); - editor.dispose(); - defaultFileMenu.insert(getRecentMenu(), 2); - activeEditor = null; - editors.remove(editor); + // Create sketch properties file if it's not the default mode. + if (!nextMode.equals(getDefaultMode())) { + saveModeSettings(new File(newbieDir, "sketch.properties"), nextMode); } - } else { - // More than one editor window open, - // proceed with closing the current window. - editor.setVisible(false); - editor.dispose(); - editors.remove(editor); + String path = newbieFile.getAbsolutePath(); + /*Editor editor =*/ handleOpen(path, true); + + } catch (IOException e) { + Messages.showWarning("That's new to me", + "A strange and unexplainable error occurred\n" + + "while trying to create a new sketch.", e); } - return true; } /** - * Handler for File → Quit. - * @return false if canceled, true otherwise. + * Prompt for a sketch to open, and open it in a new window. */ - public boolean handleQuit() { - // If quit is canceled, this will be replaced anyway - // by a later handleQuit() that is not canceled. -// storeSketches(); - - 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; -// } - if (!editor.checkModified()) { - 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 rebuildSketchbookMenusAsync() { - //System.out.println("async enter"); - //new Exception().printStackTrace(); - EventQueue.invokeLater(new Runnable() { - public void run() { - rebuildSketchbookMenus(); - } - }); - } - - - public void thinkDifferentExamples() { - nextMode.showExamplesFrame(); - } - - - /** - * Synchronous version of rebuild, used when the sketchbook folder has - * changed, so that the libraries are properly re-scanned before those menus - * (and the examples window) are rebuilt. - */ - protected void rebuildSketchbookMenus() { - // rebuildSketchbookMenu(); // no need to rebuild sketchbook post 3.0 - for (Mode mode : getModeList()) { - //mode.rebuildLibraryList(); - mode.rebuildImportMenu(); // calls rebuildLibraryList - mode.rebuildToolbarMenu(); - mode.rebuildExamplesFrame(); - mode.rebuildSketchbookFrame(); - } - } - - - protected void rebuildSketchbookMenu() { -// System.err.println("sketchbook: " + sketchbookFolder); - sketchbookMenu.removeAll(); - populateSketchbookMenu(sketchbookMenu); -// boolean found = false; -// try { -// found = addSketches(sketchbookMenu, sketchbookFolder, false); -// } catch (IOException e) { -// Base.showWarning("Sketchbook Menu Error", -// "An error occurred while trying to list the sketchbook.", e); -// } -// if (!found) { -// JMenuItem empty = new JMenuItem("(empty)"); -// empty.setEnabled(false); -// sketchbookMenu.add(empty); -// } - } - - - public void populateSketchbookMenu(JMenu menu) { - boolean found = false; - try { - found = addSketches(menu, sketchbookFolder, false); - } catch (IOException e) { - Base.showWarning("Sketchbook Menu Error", - "An error occurred while trying to list the sketchbook.", e); - } - if (!found) { - JMenuItem empty = new JMenuItem(Language.text("menu.file.sketchbook.empty")); - empty.setEnabled(false); - menu.add(empty); - } - } + public void handleOpenPrompt() { + final StringList extensions = new StringList(); + for (Mode mode : getModeList()) { + extensions.append(mode.getDefaultExtension()); + } -// public JMenu getSketchbookMenu() { -// if (sketchbookMenu == null) { -// sketchbookMenu = new JMenu(Language.text("menu.file.sketchbook")); -// rebuildSketchbookMenu(); -// } -// return sketchbookMenu; -// } - - -// public JMenu getRecentMenu() { -// if (recentMenu == null) { -// recentMenu = recent.createMenu(); -// } else { -// recent.updateMenu(recentMenu); -// } -// return recentMenu; -// } - - - public JMenu getRecentMenu() { - return recent.getMenu(); - } - - - public JMenu getToolbarRecentMenu() { - return recent.getToolbarMenu(); - } - - - public void handleRecent(Editor editor) { - recent.handle(editor); - } - public void handleRecentRename(Editor editor,String oldPath){ - recent.handleRename(editor,oldPath); - } - - /** - * Called before a sketch is renamed so that its old name is - * no longer in the menu. - */ - public void removeRecent(Editor editor) { - recent.remove(editor); - } - - - /** - * 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; - } - - if (folder.getName().equals("libraries")) { - return false; // let's not go there - } - - String[] list = folder.list(); - // If a bad folder or unreadable or whatever, this will come back null - if (list == null) { - return false; - } - - // Alphabetize the 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 Disappeared", - "The selected sketch no longer exists.\n" + - "You may need to restart Processing to update\n" + - "the sketchbook menu.", null); - } - } - }; - // offers no speed improvement - //menu.addActionListener(listener); - - boolean found = false; - -// for (int i = 0; i < list.length; i++) { -// if ((list[i].charAt(0) == '.') || -// list[i].equals("CVS")) continue; - for (String name : list) { - if (name.charAt(0) == '.') { - continue; - } - - File subfolder = new File(folder, name); - if (subfolder.isDirectory()) { - File entry = checkSketchFolder(subfolder, name); - if (entry != null) { - - JMenuItem item = new JMenuItem(name); - item.addActionListener(listener); - item.setActionCommand(entry.getAbsolutePath()); - menu.add(item); - found = true; - - } else { - // not a sketch folder, but maybe a subfolder containing sketches - JMenu submenu = new JMenu(name); - // needs to be separate var otherwise would set ifound to false - boolean anything = addSketches(submenu, subfolder, replaceExisting); - if (anything && !name.equals("old")) { //Don't add old contributions - menu.add(submenu); - found = true; - } - } - } - } - return found; - } - - - protected boolean addSketches(DefaultMutableTreeNode node, File folder) throws IOException { - // skip .DS_Store files, etc (this shouldn't actually be necessary) - if (!folder.isDirectory()) { - return false; - } - - if (folder.getName().equals("libraries")) { - return false; // let's not go there - } - - String[] fileList = folder.list(); - // If a bad folder or unreadable or whatever, this will come back null - if (fileList == null) { - return false; - } - - // Alphabetize the list, since it's not always alpha order - Arrays.sort(fileList, String.CASE_INSENSITIVE_ORDER); - -// ActionListener listener = new ActionListener() { -// public void actionPerformed(ActionEvent e) { -// String path = e.getActionCommand(); -// if (new File(path).exists()) { -// handleOpen(path); -// } else { -// showWarning("Sketch Disappeared", -// "The selected sketch no longer exists.\n" + -// "You may need to restart Processing to update\n" + -// "the sketchbook menu.", null); -// } -// } -// }; - // offers no speed improvement - //menu.addActionListener(listener); - - boolean found = false; - for (String name : fileList) { - //Skip hidden files - if (name.charAt(0) == '.') { - continue; - } - -// JTree tree = null; -// TreePath[] a = tree.getSelectionPaths(); -// for (TreePath path : a) { -// Object[] o = path.getPath(); -// } - - File subfolder = new File(folder, name); - if (subfolder.isDirectory()) { - File entry = checkSketchFolder(subfolder, name); - if (entry != null) { - DefaultMutableTreeNode item = - new DefaultMutableTreeNode(new SketchReference(name, entry)); - - node.add(item); - found = true; - - } else { - // not a sketch folder, but maybe a subfolder containing sketches - DefaultMutableTreeNode subnode = new DefaultMutableTreeNode(name); - // needs to be separate var otherwise would set ifound to false - boolean anything = addSketches(subnode, subfolder); - if (anything) { - node.add(subnode); - found = true; - } - } - } - } - return found; - } - - - /** - * Check through the various modes and see if this is a legit sketch. - * Because the default mode will be the first in the list, this will always - * prefer that one over the others. - */ - File checkSketchFolder(File subfolder, String item) { - for (Mode mode : getModeList()) { - File entry = new File(subfolder, item + "." + mode.getDefaultExtension()); //$NON-NLS-1$ - // if a .pde file of the same prefix as the folder exists.. - if (entry.exists()) { - return entry; - } - } - return null; - } - - - // ................................................................. - - -// /** -// * Show the About box. -// */ -// static public void handleAbout() { -// new About(activeEditor); -// } - - - /** - * Show the Preferences window. - */ - public void handlePrefs() { - if (preferencesFrame == null) { - preferencesFrame = new PreferencesFrame(this); - } - preferencesFrame.showFrame(); - } - - - /** - * Show the library installer window. - */ - public void handleOpenLibraryManager() { - libraryManagerFrame.showFrame(activeEditor); - } - - - /** - * Show the tool installer window. - */ - public void handleOpenToolManager() { - toolManagerFrame.showFrame(activeEditor); - } - - - /** - * Show the mode installer window. - */ - public void handleOpenModeManager() { - modeManagerFrame.showFrame(activeEditor); - } - - - /** - * Show the examples installer window. - */ - public void handleOpenExampleManager() { - exampleManagerFrame.showFrame(activeEditor); - } - - - public void handleShowUpdates() { - updateManagerFrame.showFrame(activeEditor); - } - - - // ................................................................... - - - static public int getRevision() { - return REVISION; - } - - - /** - * Return the version name, something like 1.5 or 2.0b8 or 0213 if it's not - * a release version. - */ - static public String getVersionName() { - return VERSION_NAME; - } - - - //................................................................... - - - static public Platform getPlatform() { - return platform; - } - - - static public String getPlatformName() { - return PConstants.platformNames[PApplet.platform]; - } - - - // Because the Oracle JDK is 64-bit only, we lose this ability, feature, - // edge case, headache. -// /** -// * Return whether sketches will run as 32- or 64-bits. On Linux and Windows, -// * this is the bit depth of the machine, while on OS X it's determined by the -// * setting from preferences, since both 32- and 64-bit are supported. -// */ -// static public int getNativeBits() { -// if (Base.isMacOS()) { -// return Preferences.getInteger("run.options.bits"); //$NON-NLS-1$ -// } -// return nativeBits; -// } - - /** - * Return whether sketches will run as 32- or 64-bits based - * on the JVM that's in use. - */ - static public int getNativeBits() { - return nativeBits; - } - - - /* - 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); - } - + final String prompt = Language.text("open"); - static public int getPlatformIndex(String what) { - Integer entry = platformIndices.get(what); - return (entry == null) ? -1 : entry.intValue(); - } + // don't use native dialogs on Linux (or anyone else w/ override) + if (Preferences.getBoolean("chooser.files.native")) { //$NON-NLS-1$ + // use the front-most window frame for placing file dialog + FileDialog openDialog = + new FileDialog(activeEditor, prompt, FileDialog.LOAD); + // Only show .pde files as eligible bachelors + openDialog.setFilenameFilter(new FilenameFilter() { + public boolean accept(File dir, String name) { + // confirmed to be working properly [fry 110128] + for (String ext : extensions) { + if (name.toLowerCase().endsWith("." + ext)) { //$NON-NLS-1$ + return true; + } + } + return false; + } + }); - // These were changed to no longer rely on PApplet and PConstants because - // of conflicts that could happen with older versions of core.jar, where - // the MACOSX constant would instead read as the LINUX constant. + openDialog.setVisible(true); + String directory = openDialog.getDirectory(); + String filename = openDialog.getFile(); + if (filename != null) { + File inputFile = new File(directory, filename); + handleOpen(inputFile.getAbsolutePath()); + } - /** - * returns true if Processing is running on a Mac OS X machine. - */ - static public boolean isMacOS() { - //return PApplet.platform == PConstants.MACOSX; - return System.getProperty("os.name").indexOf("Mac") != -1; //$NON-NLS-1$ //$NON-NLS-2$ - } + } else { + if (openChooser == null) { + openChooser = new JFileChooser(); + } + openChooser.setDialogTitle(prompt); + openChooser.setFileFilter(new javax.swing.filechooser.FileFilter() { + public boolean accept(File file) { + // JFileChooser requires you to explicitly say yes to directories + // as well (unlike the AWT chooser). Useful, but... different. + // http://code.google.com/p/processing/issues/detail?id=1151 + if (file.isDirectory()) { + return true; + } + for (String ext : extensions) { + if (file.getName().toLowerCase().endsWith("." + ext)) { //$NON-NLS-1$ + return true; + } + } + return false; + } - /* - static private Boolean usableOracleJava; - - // Make sure this is Oracle Java 7u40 or later. This is temporary. - static public boolean isUsableOracleJava() { - if (usableOracleJava == null) { - usableOracleJava = false; - - if (Base.isMacOS() && - System.getProperty("java.vendor").contains("Oracle")) { - String version = System.getProperty("java.version"); // 1.7.0_40 - String[] m = PApplet.match(version, "1.(\\d).*_(\\d+)"); - - if (m != null && - PApplet.parseInt(m[1]) >= 7 && - PApplet.parseInt(m[2]) >= 40) { - usableOracleJava = true; + public String getDescription() { + return "Processing Sketch"; } + }); + if (openChooser.showOpenDialog(activeEditor) == JFileChooser.APPROVE_OPTION) { + handleOpen(openChooser.getSelectedFile().getAbsolutePath()); } } - return usableOracleJava; } - */ /** - * returns true if running on windows. + * Open a sketch from the path specified. Do not use for untitled sketches. */ - static public boolean isWindows() { - //return PApplet.platform == PConstants.WINDOWS; - return System.getProperty("os.name").indexOf("Windows") != -1; //$NON-NLS-1$ //$NON-NLS-2$ + public Editor handleOpen(String path) { + return handleOpen(path, false); } /** - * true if running on linux. + * 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 */ - static public boolean isLinux() { - //return PApplet.platform == PConstants.LINUX; - return System.getProperty("os.name").indexOf("Linux") != -1; //$NON-NLS-1$ //$NON-NLS-2$ + public Editor handleOpen(String path, boolean untitled) { + return handleOpen(path, untitled, new EditorState(editors)); } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - /** - * Get the directory that can store settings. (Library on OS X, App Data or - * something similar on Windows, a dot folder on Linux.) Removed this as a - * preference for 3.0a3 because we need this to be stable. - */ - static public File getSettingsFolder() { - File settingsFolder = null; - -// String preferencesPath = Preferences.get("settings.path"); //$NON-NLS-1$ -// if (preferencesPath != null) { -// settingsFolder = new File(preferencesPath); -// -// } else { + protected Editor handleOpen(String path, boolean untitled, + EditorState state) { try { - settingsFolder = platform.getSettingsFolder(); - } catch (Exception e) { - showError("Problem getting the settings folder", - "Error getting the Processing the settings folder.", e); - } -// } + // System.err.println("entering handleOpen " + path); - // create the folder if it doesn't exist already - if (!settingsFolder.exists()) { - if (!settingsFolder.mkdirs()) { - showError("Settings issues", - "Processing cannot run because it could not\n" + - "create a folder to store your settings.", null); + final File file = new File(path); + if (!file.exists()) { + return null; } - } - return settingsFolder; - } - - - /** - * Convenience method to get a File object for the specified filename inside - * the settings folder. Used to get preferences and recent sketch files. - * @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 { - //File folder = new File(getTempFolder(), "build"); - //if (!folder.exists()) folder.mkdirs(); - buildFolder = createTempFolder("build"); - buildFolder.deleteOnExit(); + // Cycle through open windows to make sure that it's not already open. + for (Editor editor : editors) { + // User may have double-clicked any PDE in the sketch folder, + // so we have to check each open tab (not just the main one). + // https://github.com/processing/processing/issues/2506 + for (SketchCode tab : editor.getSketch().getCode()) { + if (tab.getFile().equals(file)) { + editor.toFront(); + // move back to the top of the recent list + Recent.append(editor); + return editor; + } + } } - } - return buildFolder; - } - */ - - - /** - * Create a temporary folder by using the createTempFile() mechanism, - * deleting the file it creates, and making a folder using the location - * that was provided. - * - * Unlike createTempFile(), there is no minimum size for prefix. If - * prefix is less than 3 characters, the remaining characters will be - * filled with underscores - */ - static public File createTempFolder(String prefix, String suffix, File directory) throws IOException { - int fillChars = 3 - prefix.length(); - for (int i = 0; i < fillChars; i++) { - prefix += '_'; - } - File folder = File.createTempFile(prefix, suffix, directory); - // Now delete that file and create a folder in its place - folder.delete(); - folder.mkdirs(); - // And send the folder back to your friends - return folder; - } - - - static public File getToolsFolder() { - return getContentFile("tools"); - } - - static public void locateSketchbookFolder() { - // 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. - String sketchbookPath = Preferences.getSketchbookPath(); - if (sketchbookPath != null) { - sketchbookFolder = new File(sketchbookPath); - if (!sketchbookFolder.exists()) { - Base.showWarning("Sketchbook folder disappeared", - "The sketchbook folder no longer exists.\n" + - "Processing will switch to the default sketchbook\n" + - "location, and create a new sketchbook folder if\n" + - "necessary. Processing will then stop talking\n" + - "about himself in the third person.", null); - sketchbookFolder = null; + if (!Sketch.isSanitaryName(file.getName())) { + Messages.showWarning("You're tricky, but not tricky enough", + file.getName() + " is not a valid name for a sketch.\n" + + "Better to stick to ASCII, no spaces, and make sure\n" + + "it doesn't start with a number.", null); + return null; } - } - // If no path is set, get the default sketchbook folder for this platform - if (sketchbookFolder == null) { - sketchbookFolder = getDefaultSketchbookFolder(); - Preferences.setSketchbookPath(sketchbookFolder.getAbsolutePath()); - if (!sketchbookFolder.exists()) { - sketchbookFolder.mkdirs(); + if (!nextMode.canEdit(file)) { + final Mode mode = selectMode(file); + if (mode == null) { + return null; + } + nextMode = mode; } - } - - getSketchbookLibrariesFolder().mkdir(); - getSketchbookToolsFolder().mkdir(); - getSketchbookModesFolder().mkdir(); - getSketchbookExamplesFolder().mkdir(); -// System.err.println("sketchbook: " + sketchbookFolder); - } - - - public void setSketchbookFolder(File folder) { - sketchbookFolder = folder; - Preferences.setSketchbookPath(folder.getAbsolutePath()); - rebuildSketchbookMenus(); - } + try { + Editor editor = nextMode.createEditor(this, path, state); - static public File getSketchbookFolder() { - return sketchbookFolder; - } - + editor.setUpdatesAvailable(updatesAvailable); - static public File getSketchbookLibrariesFolder() { - return new File(sketchbookFolder, "libraries"); - } + // opened successfully, let's go to work + editor.getSketch().setUntitled(untitled); + editors.add(editor); + Recent.append(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); - static public File getSketchbookToolsFolder() { - return new File(sketchbookFolder, "tools"); - } + return editor; + } catch (EditorException ee) { + if (ee.getMessage() != null) { // null if the user canceled + Messages.showWarning("Error opening sketch", ee.getMessage(), ee); + } + } catch (NoSuchMethodError nsme) { + Messages.showWarning("Mode out of date", + nextMode.getTitle() + " is not compatible with this version of Processing.\n" + + "Try updating the Mode or contact its author for a new version.", nsme); + } catch (Throwable t) { + if (nextMode.equals(getDefaultMode())) { + Messages.showTrace("Serious Problem", + "An unexpected, unknown, and unrecoverable error occurred\n" + + "while opening a new editor window. Please report this.", t, true); + } else { + Messages.showTrace("Mode Problems", + "A nasty error occurred while trying to use " + nextMode.getTitle() + ".\n" + + "It may not be compatible with this version of Processing.\n" + + "Try updating the Mode or contact its author for a new version.", t, false); + } + } + if (editors.isEmpty()) { + Mode defaultMode = getDefaultMode(); + if (nextMode == defaultMode) { + // unreachable? hopefully? + Messages.showError("Editor Problems", + "An error occurred while trying to change modes.\n" + + "We'll have to quit for now because it's an\n" + + "unfortunate bit of indigestion with the default Mode.", + null); + } else { + // Don't leave the user hanging or the PDE locked up + // https://github.com/processing/processing/issues/4467 + if (untitled) { + nextMode = defaultMode; + handleNew(); + return null; // ignored by any caller - static public File getSketchbookModesFolder() { - return new File(sketchbookFolder, "modes"); - } + } else { + // This null response will be kicked back to changeMode(), + // signaling it to re-open the sketch in the default Mode. + return null; + } + } + } + /* + if (editors.isEmpty()) { + // if the bad mode is the default mode, don't go into an infinite loop + // trying to recreate a window with the default mode. + Mode defaultMode = getDefaultMode(); + if (nextMode == defaultMode) { + Base.showError("Editor Problems", + "An error occurred while trying to change modes.\n" + + "We'll have to quit for now because it's an\n" + + "unfortunate bit of indigestion with the default Mode.", + null); + } else { + editor = defaultMode.createEditor(this, path, state); + } + } + */ - static public File getSketchbookExamplesFolder() { - return new File(sketchbookFolder, "examples"); + } catch (Throwable t) { + Messages.showTrace("Terrible News", + "A serious error occurred while " + + "trying to create a new editor window.", t, + nextMode == getDefaultMode()); // quit if default + nextMode = getDefaultMode(); + } + return null; } - static protected File getDefaultSketchbookFolder() { - File sketchbookFolder = null; - try { - sketchbookFolder = platform.getDefaultSketchbookFolder(); - } catch (Exception e) { } - - if (sketchbookFolder == null) { - showError("No sketchbook", - "Problem while trying to get the sketchbook", null); - } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - // 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", - "Processing cannot run because it could not\n" + - "create a folder to store your sketchbook.", null); + /** + * Close a sketch as specified by its editor window. + * @param editor Editor object of the sketch to be closed. + * @param modeSwitch Whether this close is being done in the context of a + * mode switch. + * @return true if succeeded in closing, false if canceled. + */ + public boolean handleClose(Editor editor, boolean modeSwitch) { + // Check if modified +// boolean immediate = editors.size() == 1; + if (!editor.checkModified()) { + return false; } - return sketchbookFolder; - } + // Close the running window, avoid window boogers with multiple sketches + editor.internalCloseRunner(); +// System.out.println("editors size is " + editors.size()); + if (editors.size() == 1) { + // For 0158, when closing the last window /and/ it was already an + // untitled sketch, just give up and let the user quit. +// if (Preferences.getBoolean("sketchbook.closing_last_window_quits") || +// (editor.untitled && !editor.getSketch().isModified())) { + if (Platform.isMacOS()) { + // If the central menubar isn't supported on this OS X JVM, + // we have to do the old behavior. Yuck! + if (defaultFileMenu == null) { + Object[] options = { Language.text("prompt.ok"), Language.text("prompt.cancel") }; + String prompt = + " " + + "
" + + "Are you sure you want to Quit?" + + "Closing the last open sketch will quit Processing."; -// /** -// * Check for a new sketchbook location. -// */ -// static protected File promptSketchbookLocation() { -// // Most often this will happen on Linux, so default to their home dir. -// File folder = new File(System.getProperty("user.home"), "sketchbook"); -// String prompt = "Select a folder to place sketches..."; -// -//// FolderSelector fs = new FolderSelector(prompt, folder, new Frame()); -//// folder = fs.getFolder(); -// folder = Base.selectFolder(prompt, folder, new Frame()); -// -//// folder = Base.selectFolder(prompt, folder, null); -//// PApplet.selectFolder(prompt, -//// "promptSketchbookCallback", dflt, -//// Preferences.this, dialog); -// -// if (folder == null) { -// System.exit(0); -// } -// // Create the folder if it doesn't exist already -// if (!folder.exists()) { -// folder.mkdirs(); -// return folder; -// } -// return folder; -// } + 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; + } + } + } + Preferences.unset("server.port"); //$NON-NLS-1$ + Preferences.unset("server.key"); //$NON-NLS-1$ - // ................................................................. +// // This will store the sketch count as zero +// editors.remove(editor); +// System.out.println("editors size now " + editors.size()); +// storeSketches(); + // Save out the current prefs state + Preferences.save(); - /** - * Implements the cross-platform headache of opening URLs. - * - * For 2.0a8 and later, this requires the parameter to be an actual URL, - * meaning that you can't send it a file:// path without a prefix. It also - * just calls into Platform, which now uses java.awt.Desktop (where - * possible, meaning not on Linux) now that we're requiring Java 6. - * As it happens the URL must also be properly URL-encoded. - */ - static public void openURL(String url) { - try { - platform.openURL(url); + if (defaultFileMenu == null) { + if (modeSwitch) { + // need to close this editor, ever so temporarily + editor.setVisible(false); + editor.dispose(); + activeEditor = null; + editors.remove(editor); + } else { + // Since this wasn't an actual Quit event, call System.exit() + System.exit(0); + } + } else { // on OS X, update the default file menu + editor.setVisible(false); + editor.dispose(); + defaultFileMenu.insert(Recent.getMenu(), 2); + activeEditor = null; + editors.remove(editor); + } - } catch (Exception e) { - showWarning("Problem Opening URL", - "Could not open the URL\n" + url, e); + } else { + // More than one editor window open, + // proceed with closing the current window. + editor.setVisible(false); + editor.dispose(); + editors.remove(editor); } + return true; } /** - * 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. + * Handler for File → Quit. + * @return false if canceled, true otherwise. */ - static protected boolean openFolderAvailable() { - return platform.openFolderAvailable(); - } + public boolean handleQuit() { + // If quit is canceled, this will be replaced anyway + // by a later handleQuit() that is not canceled. +// storeSketches(); + if (handleQuitEach()) { + // make sure running sketches close before quitting + for (Editor editor : editors) { + editor.internalCloseRunner(); + } + // Save out the current prefs state + Preferences.save(); - /** - * 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); + // Finished with this guy + Console.shutdown(); - } catch (Exception e) { - showWarning("Problem Opening Folder", - "Could not open the folder\n" + file.getAbsolutePath(), e); + if (!Platform.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; } - // ................................................................. - - -// /** -// * Prompt for a folder 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 -// * this post -// * 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(); //.pack(); -// FileDialog fd = new FileDialog(frame, prompt, FileDialog.LOAD); -// if (folder != null) { -// fd.setDirectory(folder.getParent()); -// //fd.setFile(folder.getName()); -// } -// 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; -// } - - -// static class FolderSelector { -// File folder; -// boolean ready; -// -// FolderSelector(String prompt, File defaultFile, Frame parentFrame) { -// PApplet.selectFolder(prompt, "callback", defaultFile, this, parentFrame); -// } -// -// public void callback(File folder) { -// this.folder = folder; -// ready = true; -// } -// -// boolean isReady() { -// return ready; -// } + /** + * 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++; // -// /** block until the folder is available */ -// File getFolder() { -// while (!ready) { -// try { -// Thread.sleep(100); -// } catch (InterruptedException e) { } +// } else { +// return false; // } -// return folder; -// } -// } -// -// -// /** -// * Blocking version of folder selection. Runs and sleeps until an answer -// * comes back. Avoid using: try to make things work with the async -// * selectFolder inside PApplet instead. -// */ -// static public File selectFolder(String prompt, File folder, Frame frame) { -// return new FolderSelector(prompt, folder, frame).getFolder(); -// } + if (!editor.checkModified()) { + return false; + } + } + return true; + } - // ................................................................. + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** - * "No cookie for you" type messages. Nothing fatal or all that - * much of a bummer, but something to notify the user about. + * Asynchronous version of menu rebuild to be used on save and rename + * to prevent the interface from locking up until the menus are done. */ - 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); - } + protected void rebuildSketchbookMenusAsync() { + EventQueue.invokeLater(new Runnable() { + public void run() { + rebuildSketchbookMenus(); + } + }); } - /** - * Non-fatal error message. - */ - static public void showWarning(String title, String message) { - showWarning(title, message, null); + public void thinkDifferentExamples() { + nextMode.showExamplesFrame(); } + /** - * Non-fatal error message with optional stack trace side dish. + * Synchronous version of rebuild, used when the sketchbook folder has + * changed, so that the libraries are properly re-scanned before those menus + * (and the examples window) are rebuilt. */ - static public void showWarning(String title, String message, Throwable e) { - if (title == null) title = "Warning"; - - if (commandLine) { - System.out.println(title + ": " + message); - - } else { - JOptionPane.showMessageDialog(new Frame(), message, title, - JOptionPane.WARNING_MESSAGE); + protected void rebuildSketchbookMenus() { + for (Mode mode : getModeList()) { + mode.rebuildImportMenu(); // calls rebuildLibraryList + mode.rebuildToolbarMenu(); + mode.rebuildExamplesFrame(); + mode.rebuildSketchbookFrame(); } - if (e != null) e.printStackTrace(); } - /** - * Non-fatal error message with optional stack trace side dish. - */ - static public void showWarningTiered(String title, - String primary, String secondary, - Throwable e) { - if (title == null) title = "Warning"; + protected void rebuildSketchbookMenu() { + sketchbookMenu.removeAll(); + populateSketchbookMenu(sketchbookMenu); + } - final String message = primary + "\n" + secondary; - if (commandLine) { - System.out.println(title + ": " + message); - } else { -// JOptionPane.showMessageDialog(new Frame(), message, -// title, JOptionPane.WARNING_MESSAGE); - if (!Base.isMacOS()) { - JOptionPane.showMessageDialog(new JFrame(), - "
" + - "" + primary + "" + - "" + secondary + "
", - JOptionPane.WARNING_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(new JFrame(), 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; -// } - } + public void populateSketchbookMenu(JMenu menu) { + boolean found = false; + try { + found = addSketches(menu, sketchbookFolder, false); + } catch (IOException e) { + Messages.showWarning("Sketchbook Menu Error", + "An error occurred while trying to list the sketchbook.", e); + } + if (!found) { + JMenuItem empty = new JMenuItem(Language.text("menu.file.sketchbook.empty")); + empty.setEnabled(false); + menu.add(empty); } - 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); + /* + public JMenu getRecentMenu() { + return recent.getMenu(); } - /** - * Testing a new warning window that includes the stack trace. - */ - static private void showBadnessTrace(String title, String message, - Throwable t, boolean fatal) { - if (title == null) title = fatal ? "Error" : "Warning"; - - if (commandLine) { - System.err.println(title + ": " + message); - if (t != null) { - t.printStackTrace(); - } - - } else { - StringWriter sw = new StringWriter(); - t.printStackTrace(new PrintWriter(sw)); - // Necessary to replace \n with" + Language.text("save.hint") + "
", - JOptionPane.QUESTION_MESSAGE); - - String[] options = new String[] { - Language.text("save.btn.save"), Language.text("prompt.cancel"), Language.text("save.btn.dont_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", - Integer.valueOf(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; - } - } + // Called before a sketch is renamed so that its old name is + // no longer in the menu. + public void removeRecent(Editor editor) { + recent.remove(editor); } + */ - static public int showYesNoQuestion(Frame editor, String title, - String primary, String secondary) { - if (!Base.isMacOS()) { - return JOptionPane.showConfirmDialog(editor, - "" + - "" + primary + "" + - "" + secondary + "
", - JOptionPane.QUESTION_MESSAGE); - - String[] options = new String[] { - "Yes", "No" - }; - pane.setOptions(options); - - // highlight the safest option ala apple hig - pane.setInitialValue(options[0]); + /** + * 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; + } - JDialog dialog = pane.createDialog(editor, null); - dialog.setVisible(true); + if (folder.getName().equals("libraries")) { + return false; // let's not go there + } - 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; + if (folder.getName().equals("sdk")) { + // This could be Android's SDK folder. Let's double check: + File suspectSDKPath = new File(folder.getParent(), folder.getName()); + File expectedSDKPath = new File(sketchbookFolder, "android" + File.separator + "sdk"); + if (expectedSDKPath.getAbsolutePath().equals(suspectSDKPath.getAbsolutePath())) { + return false; // Most likely the SDK folder, skip it } } - } + String[] list = folder.list(); + // If a bad folder or unreadable or whatever, this will come back null + if (list == null) { + return false; + } - static protected File processingRoot; + // Alphabetize the list, since it's not always alpha order + Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); - /** - * Get reference to a file adjacent to the executable on Windows and Linux, - * or inside Contents/Resources/Java on Mac OS X. - */ - static public File getContentFile(String name) { - if (processingRoot == 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 = PApplet.urlDecode(path); - - if (decodedPath.contains("/app/bin")) { // This means we're in Eclipse - if (Base.isMacOS()) { - processingRoot = - new File(path, "../../build/macosx/work/Processing.app/Contents/Java"); - } else if (Base.isWindows()) { - processingRoot = new File(path, "../../build/windows/work"); - } else if (Base.isLinux()) { - processingRoot = 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. - processingRoot = jarFolder.getParentFile(); - } else if (Base.isMacOS()) { - // This works for Java 8 on OS X. We don't have things inside a 'lib' - // folder on OS X. Adding it caused more problems than it was worth. - processingRoot = jarFolder; - } - if (processingRoot == null || !processingRoot.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"); - processingRoot = new File(System.getProperty("user.dir")); + 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 { + Messages.showWarning("Sketch Disappeared", + "The selected sketch no longer exists.\n" + + "You may need to restart Processing to update\n" + + "the sketchbook menu.", null); + } } + }; + // offers no speed improvement + //menu.addActionListener(listener); + + boolean found = false; + +// for (int i = 0; i < list.length; i++) { +// if ((list[i].charAt(0) == '.') || +// list[i].equals("CVS")) continue; + for (String name : list) { + if (name.charAt(0) == '.') { + continue; } - } - return new File(processingRoot, name); - } + File subfolder = new File(folder, name); + if (subfolder.isDirectory()) { + File entry = checkSketchFolder(subfolder, name); + if (entry != null) { + + JMenuItem item = new JMenuItem(name); + item.addActionListener(listener); + item.setActionCommand(entry.getAbsolutePath()); + menu.add(item); + found = true; - static public File getJavaHome() { - if (isMacOS()) { - //return "Contents/PlugIns/jdk1.7.0_40.jdk/Contents/Home/jre/bin/java"; - File[] plugins = getContentFile("../PlugIns").listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) { - return dir.isDirectory() && - name.endsWith(".jdk") && !name.startsWith("."); + } else { + // not a sketch folder, but maybe a subfolder containing sketches + JMenu submenu = new JMenu(name); + // needs to be separate var otherwise would set ifound to false + boolean anything = addSketches(submenu, subfolder, replaceExisting); + if (anything && !name.equals("old")) { //Don't add old contributions + menu.add(submenu); + found = true; + } } - }); - return new File(plugins[0], "Contents/Home/jre"); + } } - // On all other platforms, it's the 'java' folder adjacent to Processing - return getContentFile("java"); + return found; } - /** Get the path to the embedded Java executable. */ - static public String getJavaPath() { - String javaPath = "bin/java" + (isWindows() ? ".exe" : ""); - File javaFile = new File(getJavaHome(), javaPath); - try { - return javaFile.getCanonicalPath(); - } catch (IOException e) { - return javaFile.getAbsolutePath(); + public boolean addSketches(DefaultMutableTreeNode node, File folder, + boolean examples) throws IOException { + // skip .DS_Store files, etc (this shouldn't actually be necessary) + if (!folder.isDirectory()) { + return false; } - } - - - /** - * Return a File from inside the Processing 'lib' folder. - */ - static public File getLibFile(String filename) throws IOException { - return new File(getContentFile("lib"), filename); - } - - - /** - * Return an InputStream for a file inside the Processing lib folder. - */ - static public InputStream getLibStream(String filename) throws IOException { - return new FileInputStream(getLibFile(filename)); - } - - - // Note: getLibImage() has moved to Toolkit - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + final String folderName = folder.getName(); - /** - * 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++; + // Don't look inside the 'libraries' folders in the sketchbook + if (folderName.equals("libraries")) { + return false; } - 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; + // When building the sketchbook, don't show the contributed 'examples' + // like it's a subfolder. But when loading examples, allow the folder + // to be named 'examples'. + if (!examples && folderName.equals("examples")) { + return false; } - input.close(); // weren't properly being closed - input = null; - return buffer; - } +// // Conversely, when looking for examples, ignore the other folders +// // (to avoid going through hoops with the tree node setup). +// if (examples && !folderName.equals("examples")) { +// return false; +// } +// // Doesn't quite work because the parent will be 'examples', and we want +// // to walk inside that, but the folder itself will have a different name - /** - * Read from a file with a bunch of attribute/value pairs - * that are separated by = and ignore comments with #. - * Changed in 3.0a6 to return null (rather than empty hash) if no file, - * and changed return type to Map instead of HashMap. - */ - static public Map" + secondary + "
", + JOptionPane.WARNING_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(new JFrame(), 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; +// } + } + } + 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 (Base.isCommandLine()) { + System.err.println(title + ": " + message); + + } else { + JOptionPane.showMessageDialog(new Frame(), message, title, + JOptionPane.ERROR_MESSAGE); + } + if (e != null) e.printStackTrace(); + System.exit(1); + } + + + /** + * Testing a new warning window that includes the stack trace. + */ + static public void showTrace(String title, String message, + Throwable t, boolean fatal) { + if (title == null) title = fatal ? "Error" : "Warning"; + + if (Base.isCommandLine()) { + System.err.println(title + ": " + message); + if (t != null) { + t.printStackTrace(); + } + + } else { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + // Necessary to replace \n with" + Language.text("save.hint") + "
", + JOptionPane.QUESTION_MESSAGE); + + String[] options = new String[] { + Language.text("save.btn.save"), + Language.text("prompt.cancel"), + Language.text("save.btn.dont_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", + Integer.valueOf(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 (!Platform.isMacOS()) { + return JOptionPane.showConfirmDialog(editor, + "" + + "" + primary + "" + + "" + secondary, // + "
", + JOptionPane.QUESTION_MESSAGE); + + pane.setOptions(options); + + // highlight the safest option ala apple hig + pane.setInitialValue(options[highlight]); + + JDialog dialog = pane.createDialog(editor, null); + dialog.setVisible(true); + + result = pane.getValue(); + } + for (int i = 0; i < options.length; i++) { + if (result != null && result.equals(options[i])) return i; + } + return -1; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static public void log(Object from, String message) { + if (Base.DEBUG) { + System.out.println(from.getClass().getName() + ": " + message); + } + } + + + static public void log(String message) { + if (Base.DEBUG) { + System.out.println(message); + } + } + + + static public void logf(String message, Object... args) { + if (Base.DEBUG) { + System.out.println(String.format(message, args)); + } + } + + + static public void loge(String message, Throwable e) { + if (Base.DEBUG) { + if (message != null) { + System.err.println(message); + } + e.printStackTrace(); + } + } + + + static public void loge(String message) { + if (Base.DEBUG) { + System.err.println(message); + } + } +} diff --git a/app/src/processing/app/Mode.java b/app/src/processing/app/Mode.java index 5251d594b8..9c87f7232b 100644 --- a/app/src/processing/app/Mode.java +++ b/app/src/processing/app/Mode.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2013 The Processing Foundation + Copyright (c) 2013-15 The Processing Foundation Copyright (c) 2010-13 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify @@ -29,15 +29,20 @@ import java.awt.image.WritableRaster; import java.io.*; import java.util.*; +import java.util.List; import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.border.EmptyBorder; -import javax.swing.event.TreeExpansionEvent; -import javax.swing.event.TreeExpansionListener; import javax.swing.tree.*; +import processing.app.contrib.ContributionManager; import processing.app.syntax.*; +import processing.app.ui.Editor; +import processing.app.ui.EditorException; +import processing.app.ui.EditorState; +import processing.app.ui.ExamplesFrame; +import processing.app.ui.Recent; +import processing.app.ui.SketchbookFrame; +import processing.app.ui.Toolkit; import processing.core.PApplet; import processing.core.PConstants; @@ -48,24 +53,22 @@ public abstract class Mode { protected File folder; protected TokenMarker tokenMarker; - protected HashMap
- * The ID number also helps provide us a general idea of how many
- * people are using Processing, which helps us when writing grant
- * proposals and that kind of thing so that we can keep Processing free.
+ * Aside from the privacy invasion of knowing that an anonymous Processing
+ * user opened the software at one time during a 24-hour period somewhere
+ * in the world, we use the ID number to give us a general idea of how many
+ * people are using Processing, which helps us when writing grant proposals
+ * and that kind of thing so that we can keep Processing free. The numbers
+ * are also sometimes used in ugly charts when Ben and Casey present.
*/
public class UpdateCheck {
private final Base base;
@@ -55,26 +59,36 @@ public class UpdateCheck {
static private final long ONE_DAY = 24 * 60 * 60 * 1000;
+ static boolean allowed;
+
public UpdateCheck(Base base) {
this.base = base;
- new Thread(new Runnable() {
- public void run() {
- try {
- Thread.sleep(20 * 1000); // give the PDE time to get rolling
- updateCheck();
-
- } catch (Exception e) {
- // This can safely be ignored, too many situations where no net
- // connection is available that behave in strange ways.
- // Covers likely IOException, InterruptedException, and any others.
- }
- }
- }, "Update Checker").start();
+
+ if (isAllowed()) {
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(5 * 1000); // give the PDE time to get rolling
+ updateCheck();
+
+ } catch (Exception e) {
+ // This can safely be ignored, too many situations where no net
+ // connection is available that behave in strange ways.
+ // Covers likely IOException, InterruptedException, and any others.
+ }
+ }
+ }, "Update Checker").start();
+ }
}
- public void updateCheck() throws IOException, InterruptedException {
+ /**
+ * Turned into a separate method so that anyone needed update.id will get
+ * a legit answer. Had a problem with the contribs script where the id
+ * wouldn't be set so a null id would be sent to the contribs server.
+ */
+ static public long getUpdateID() {
// generate a random id in case none exists yet
Random r = new Random();
long id = r.nextLong();
@@ -85,8 +99,12 @@ public void updateCheck() throws IOException, InterruptedException {
} else {
Preferences.set("update.id", String.valueOf(id));
}
+ return id;
+ }
- String info = PApplet.urlEncode(id + "\t" +
+
+ public void updateCheck() throws IOException, InterruptedException {
+ String info = PApplet.urlEncode(getUpdateID() + "\t" +
PApplet.nf(Base.getRevision(), 4) + "\t" +
System.getProperty("java.version") + "\t" +
System.getProperty("java.vendor") + "\t" +
@@ -108,31 +126,28 @@ public void updateCheck() throws IOException, InterruptedException {
Preferences.set("update.last", String.valueOf(now));
if (base.activeEditor != null) {
- boolean offerToUpdateContributions = true;
+// boolean offerToUpdateContributions = true;
if (latest > Base.getRevision()) {
System.out.println("You are running Processing revision 0" +
Base.getRevision() + ", the latest build is 0" +
latest + ".");
// Assume the person is busy downloading the latest version
- offerToUpdateContributions = !promptToVisitDownloadPage();
+// offerToUpdateContributions = !promptToVisitDownloadPage();
+ promptToVisitDownloadPage();
}
+ /*
if (offerToUpdateContributions) {
// Wait for xml file to be downloaded and updates to come in.
// (this should really be handled better).
Thread.sleep(5 * 1000);
- if ((!base.libraryManagerFrame.hasAlreadyBeenOpened()
- && !base.toolManagerFrame.hasAlreadyBeenOpened()
- && !base.modeManagerFrame.hasAlreadyBeenOpened()
- && !base.exampleManagerFrame.hasAlreadyBeenOpened())
- && (base.libraryManagerFrame.hasUpdates(base)
- || base.toolManagerFrame.hasUpdates(base)
- || base.modeManagerFrame.hasUpdates(base)
- || base.exampleManagerFrame.hasUpdates(base))) {
+ if ((!base.contributionManagerFrame.hasAlreadyBeenOpened()
+ && (base.contributionManagerFrame.hasUpdates(base)))){
promptToOpenContributionManager();
}
}
+ */
}
}
@@ -150,7 +165,7 @@ protected boolean promptToVisitDownloadPage() {
options,
options[0]);
if (result == JOptionPane.YES_OPTION) {
- Base.openURL(DOWNLOAD_URL);
+ Platform.openURL(DOWNLOAD_URL);
return true;
}
@@ -159,9 +174,12 @@ protected boolean promptToVisitDownloadPage() {
protected boolean promptToOpenContributionManager() {
- String contributionPrompt = Language.text("update_check.updates_available.contributions");
+ String contributionPrompt =
+ Language.text("update_check.updates_available.contributions");
- Object[] options = { Language.text("prompt.yes"), Language.text("prompt.no") };
+ Object[] options = {
+ Language.text("prompt.yes"), Language.text("prompt.no")
+ };
int result = JOptionPane.showOptionDialog(base.activeEditor,
contributionPrompt,
Language.text("update_check"),
@@ -171,7 +189,7 @@ protected boolean promptToOpenContributionManager() {
options,
options[0]);
if (result == JOptionPane.YES_OPTION) {
- base.handleShowUpdates();
+ ContributionManager.openUpdates();
return true;
}
@@ -186,4 +204,10 @@ protected int readInt(String filename) throws IOException {
BufferedReader reader = new BufferedReader(isr);
return Integer.parseInt(reader.readLine());
}
+
+
+ static public boolean isAllowed() {
+ // Disable update checks for the paranoid
+ return Preferences.getBoolean("update.check");
+ }
}
diff --git a/app/src/processing/app/Util.java b/app/src/processing/app/Util.java
new file mode 100644
index 0000000000..fefc3b8b71
--- /dev/null
+++ b/app/src/processing/app/Util.java
@@ -0,0 +1,677 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2012-15 The Processing Foundation
+ Copyright (c) 2004-12 Ben Fry and Casey Reas
+
+ 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.io.*;
+import java.nio.file.Files;
+import java.util.Enumeration;
+import java.util.zip.*;
+
+import processing.core.PApplet;
+import processing.data.StringDict;
+import processing.data.StringList;
+
+
+public class Util {
+
+ /**
+ * 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 #.
+ * Changed in 3.x to return null (rather than empty hash) if no file,
+ * and changed return type to StringDict instead of Map or HashMap.
+ */
+ static public StringDict readSettings(File inputFile) {
+ if (!inputFile.exists()) {
+ Messages.loge(inputFile + " does not exist inside readSettings()");
+ return null;
+ }
+ String lines[] = PApplet.loadStrings(inputFile);
+ if (lines == null) {
+ System.err.println("Could not read " + inputFile);
+ return null;
+ }
+ return readSettings(inputFile.toString(), lines);
+ }
+
+
+ /**
+ * Parse a String array that contains attribute/value pairs separated
+ * by = (the equals sign). The # (hash) symbol is used to denote comments.
+ * Comments can be anywhere on a line. Blank lines are ignored.
+ * In 3.0a6, no longer taking a blank HashMap as param; no cases in the main
+ * PDE code of adding to a (Hash)Map. Also returning the Map instead of void.
+ * Both changes modify the method signature, but this was only used by the
+ * contrib classes.
+ */
+ static public StringDict readSettings(String filename, String[] lines) {
+ StringDict settings = new StringDict();
+ for (String line : lines) {
+ // Remove comments
+ int commentMarker = line.indexOf('#');
+ if (commentMarker != -1) {
+ line = line.substring(0, commentMarker);
+ }
+ // Remove extra whitespace
+ line = line.trim();
+
+ if (line.length() != 0) {
+ int equals = line.indexOf('=');
+ if (equals == -1) {
+ if (filename != null) {
+ System.err.println("Ignoring illegal line in " + filename);
+ System.err.println(" " + line);
+ }
+ } else {
+ String attr = line.substring(0, equals).trim();
+ String valu = line.substring(equals + 1).trim();
+ settings.set(attr, valu);
+ }
+ }
+ }
+ return settings;
+ }
+
+
+ static public void copyFile(File sourceFile,
+ File targetFile) throws IOException {
+ BufferedInputStream from =
+ new BufferedInputStream(new FileInputStream(sourceFile));
+ BufferedOutputStream 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);
+ }
+ from.close();
+ from = null;
+
+ to.flush();
+ to.close();
+ to = null;
+
+ targetFile.setLastModified(sourceFile.lastModified());
+ targetFile.setExecutable(sourceFile.canExecute());
+ }
+
+
+ /**
+ * Grab the contents of a file as a string. Connects lines with \n,
+ * even if the input file used \r\n.
+ */
+ 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. As of 3.0 beta 2,
+ * this will replace and write \r\n for newlines on Windows.
+ * https://github.com/processing/processing/issues/3455
+ * As of 3.3.7, this puts a newline at the end of the file,
+ * per good practice/POSIX: https://stackoverflow.com/a/729795
+ */
+ static public void saveFile(String text, File file) throws IOException {
+ String[] lines = text.split("\\r?\\n");
+ File temp = File.createTempFile(file.getName(), null, file.getParentFile());
+ try {
+ // fix from cjwant to prevent symlinks from being destroyed.
+ File canon = file.getCanonicalFile();
+ // assign the var as second step since previous line may throw exception
+ file = canon;
+ } catch (IOException e) {
+ throw new IOException("Could not resolve canonical representation of " +
+ file.getAbsolutePath());
+ }
+ // Could use saveStrings(), but the we wouldn't be able to checkError()
+ PrintWriter writer = PApplet.createWriter(temp);
+ for (String line : lines) {
+ writer.println(line);
+ }
+ boolean error = writer.checkError(); // calls flush()
+ writer.close(); // attempt to close regardless
+ if (error) {
+ throw new IOException("Error while trying to save " + file);
+ }
+
+ // remove the old file before renaming the temp file
+ 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() +
+ " with " + temp.getAbsolutePath());
+ }
+ }
+
+
+ /**
+ * Create a temporary folder by using the createTempFile() mechanism,
+ * deleting the file it creates, and making a folder using the location
+ * that was provided.
+ *
+ * Unlike createTempFile(), there is no minimum size for prefix. If
+ * prefix is less than 3 characters, the remaining characters will be
+ * filled with underscores
+ */
+ static public File createTempFolder(String prefix, String suffix,
+ File directory) throws IOException {
+ int fillChars = 3 - prefix.length();
+ for (int i = 0; i < fillChars; i++) {
+ prefix += '_';
+ }
+ File folder = File.createTempFile(prefix, suffix, directory);
+ // Now delete that file and create a folder in its place
+ folder.delete();
+ folder.mkdirs();
+ // And send the folder back to your friends
+ return folder;
+ }
+
+
+ /**
+ * 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;
+ //if (files[i].equals(".") || files[i].equals("..")) continue;
+ File source = new File(sourceDir, files[i]);
+ File target = new File(targetDir, files[i]);
+ if (source.isDirectory()) {
+ //target.mkdirs();
+ copyDir(source, target);
+ target.setLastModified(source.lastModified());
+ } else {
+ copyFile(source, target);
+ }
+ }
+ }
+
+
+ static public void copyDirNative(File sourceDir,
+ File targetDir) throws IOException {
+ Process process = null;
+ if (Platform.isMacOS() || Platform.isLinux()) {
+ process = Runtime.getRuntime().exec(new String[] {
+ "cp", "-a", sourceDir.getAbsolutePath(), targetDir.getAbsolutePath()
+ });
+ } else {
+ // TODO implement version that uses XCOPY here on Windows
+ throw new RuntimeException("Not yet implemented on Windows");
+ }
+ try {
+ int result = process.waitFor();
+ if (result != 0) {
+ throw new IOException("Error while copying (result " + result + ")");
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+// /**
+// * Delete a file or directory in a platform-specific manner. Removes a File
+// * object (a file or directory) from the system by placing it in the Trash
+// * or Recycle Bin (if available) or simply deleting it (if not).
+// *
+// * When the file/folder is on another file system, it may simply be removed
+// * immediately, without additional warning. So only use this if you want to,
+// * you know, "delete" the subject in question.
+// *
+// * NOTE: Not yet tested nor ready for prime-time.
+// *
+// * @param file the victim (a directory or individual file)
+// * @return true if all ends well
+// * @throws IOException what went wrong
+// */
+// static public boolean platformDelete(File file) throws IOException {
+// return Base.getPlatform().deleteFile(file);
+// }
+
+
+ /**
+ * Remove all files in a directory and the directory itself.
+ * Prints error messages with failed filenames. Does not follow symlinks.
+ */
+ static public boolean removeDir(File dir) {
+ return removeDir(dir, true);
+ }
+
+ /**
+ * Remove all files in a directory and the directory itself.
+ * Optinally prints error messages with failed filenames.
+ * Does not follow symlinks.
+ */
+ static public boolean removeDir(File dir, boolean printErrorMessages) {
+ if (!dir.exists()) return true;
+
+ boolean result = true;
+ if (!Files.isSymbolicLink(dir.toPath())) {
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ if (child.isFile()) {
+ boolean deleted = child.delete();
+ if (!deleted && printErrorMessages) {
+ System.err.println("Could not delete " + child.getAbsolutePath());
+ }
+ result &= deleted;
+ } else if (child.isDirectory()) {
+ result &= removeDir(child, printErrorMessages);
+ }
+ }
+ }
+ }
+ boolean deleted = dir.delete();
+ if (!deleted && printErrorMessages) {
+ System.err.println("Could not delete " + dir.getAbsolutePath());
+ }
+ result &= deleted;
+ return result;
+ }
+
+
+ /**
+ * Function to return the length of the file, or entire directory, including
+ * the component files and sub-folders if passed.
+ * @param file The file or folder to calculate
+ */
+ static public long calcSize(File file) {
+ return file.isFile() ? file.length() : Util.calcFolderSize(file);
+ }
+
+
+ /**
+ * 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 long 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.
+ * @param relative true return relative paths instead of absolute paths
+ */
+ static public String[] listFiles(File folder, boolean relative) {
+ return listFiles(folder, relative, null);
+ }
+
+
+ static public String[] listFiles(File folder, boolean relative,
+ String extension) {
+ if (extension != null) {
+ if (!extension.startsWith(".")) {
+ extension = "." + extension;
+ }
+ }
+
+ StringList list = new StringList();
+ listFilesImpl(folder, relative, extension, list);
+
+ if (relative) {
+ String[] outgoing = new String[list.size()];
+ // remove the slash (or backslash) as well
+ int prefixLength = folder.getAbsolutePath().length() + 1;
+ for (int i = 0; i < outgoing.length; i++) {
+ outgoing[i] = list.get(i).substring(prefixLength);
+ }
+ return outgoing;
+ }
+ return list.array();
+ }
+
+
+ static void listFilesImpl(File folder, boolean relative,
+ String extension, StringList list) {
+ File[] items = folder.listFiles();
+ if (items != null) {
+ for (File item : items) {
+ String name = item.getName();
+ if (name.charAt(0) != '.') {
+ if (item.isDirectory()) {
+ listFilesImpl(item, relative, extension, list);
+
+ } else { // a file
+ if (extension == null || name.endsWith(extension)) {
+ list.append(item.getAbsolutePath());
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * @param folder source folder to search
+ * @return a list of .jar and .zip files in that folder
+ */
+ static public File[] listJarFiles(File folder) {
+ return folder.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return (!name.startsWith(".") &&
+ (name.toLowerCase().endsWith(".jar") ||
+ name.toLowerCase().endsWith(".zip")));
+ }
+ });
+ }
+
+
+ /////////////////////////////////////////////////////////////////////////////
+
+
+ /**
+ * Given a folder, return a list of absolute paths to all jar or zip files
+ * inside that folder, separated by pathSeparatorChar.
+ *
+ * This will prepend a colon (or whatever the path separator is)
+ * so that it can be directly appended to another path string.
+ *
+ * As of 0136, this will no longer add the root folder as well.
+ *
+ * This function doesn't bother checking to see if there are any .class
+ * files in the folder or within a subfolder.
+ */
+ static public String contentsToClassPath(File folder) {
+ if (folder == null) return "";
+
+ StringBuilder sb = new StringBuilder();
+ String sep = System.getProperty("path.separator");
+
+ try {
+ String path = folder.getCanonicalPath();
+
+ // When getting the name of this folder, make sure it has a slash
+ // after it, so that the names of sub-items can be added.
+ if (!path.endsWith(File.separator)) {
+ path += File.separator;
+ }
+
+ String list[] = folder.list();
+ for (int i = 0; i < list.length; i++) {
+ // Skip . and ._ files. Prior to 0125p3, .jar files that had
+ // OS X AppleDouble files associated would cause trouble.
+ if (list[i].startsWith(".")) continue;
+
+ if (list[i].toLowerCase().endsWith(".jar") ||
+ list[i].toLowerCase().endsWith(".zip")) {
+ sb.append(sep);
+ sb.append(path);
+ sb.append(list[i]);
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace(); // this would be odd
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * A classpath, separated by the path separator, will contain
+ * a series of .jar/.zip files or directories containing .class
+ * files, or containing subdirectories that have .class files.
+ *
+ * @param path the input classpath
+ * @return array of possible package names
+ */
+ static public StringList packageListFromClassPath(String path) {
+// Map