toolContribs =
+// ToolContribution.loadAll(Base.getSketchbookToolsFolder());
+// contributions.addAll(toolContribs);
+ contributions.addAll(ToolContribution.loadAll(getSketchbookToolsFolder()));
+
+ contributions.addAll(getExampleContribs());
+ return contributions;
}
- /**
- * 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;
- }
-
- // 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 (Base.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.";
-
- 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();
-
- 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);
- }
-
- } 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 → 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();
-
- 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.resetExamples();
- }
- }
-
-
- 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 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;
- }
+ public byte[] getInstalledContribsInfo() {
+ List contribs = getInstalledContribs();
+ StringList entries = new StringList();
+ for (Contribution c : contribs) {
+ String entry = c.getTypeName() + "=" +
+ PApplet.urlEncode(String.format("name=%s\nurl=%s\nrevision=%d\nversion=%s",
+ c.getName(), c.getUrl(),
+ c.getVersion(), c.getBenignVersion()));
+ entries.append(entry);
}
- return null;
+ String joined =
+ "id=" + UpdateCheck.getUpdateID() + "&" + entries.join("&");
+// StringBuilder sb = new StringBuilder();
+// try {
+// // Truly ridiculous attempt to shove everything into a GET request.
+// // More likely to be seen as part of a grand plot.
+// ByteArrayOutputStream baos = new ByteArrayOutputStream();
+// GZIPOutputStream output = new GZIPOutputStream(baos);
+// PApplet.saveStream(output, new ByteArrayInputStream(joined.getBytes()));
+// output.close();
+// byte[] b = baos.toByteArray();
+// for (int i = 0; i < b.length; i++) {
+// sb.append(PApplet.hex(b[i], 2));
+// }
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// return sb.toString();
+ return joined.getBytes();
}
- // .................................................................
-
-
-// /**
-// * Show the About box.
-// */
-// static public void handleAbout() {
-// new About(activeEditor);
-// }
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
- * Show the Preferences window.
+ * Create or modify a sketch.proprties file to specify the given Mode.
*/
- public void handlePrefs() {
- if (preferencesFrame == null) {
- preferencesFrame = new PreferencesFrame(this);
+ private void saveModeSettings(final File sketchProps, final Mode mode) {
+ try {
+ final Settings settings = new Settings(sketchProps);
+ settings.set("mode", mode.getTitle());
+ settings.set("mode.id", mode.getIdentifier());
+ settings.save();
+ } catch (IOException e) {
+ System.err.println("While creating " + sketchProps + ": " + e.getMessage());
}
- preferencesFrame.showFrame();
}
- /**
- * Show the library installer window.
- */
- public void handleOpenLibraryManager() {
- libraryManagerFrame.showFrame(activeEditor);
+ String getDefaultModeIdentifier() {
+ return "processing.mode.java.JavaMode";
}
- /**
- * Show the tool installer window.
- */
- public void handleOpenToolManager() {
- toolManagerFrame.showFrame(activeEditor);
+ public Mode getDefaultMode() {
+ return coreModes[0];
}
- /**
- * Show the mode installer window.
- */
- public void handleOpenModeManager() {
- modeManagerFrame.showFrame(activeEditor);
+ /** Used by ThinkDifferent so that it can have a Sketchbook menu. */
+ public Mode getNextMode() {
+ return nextMode;
}
/**
- * Show the examples installer window.
+ * The call has already checked to make sure this sketch is not modified,
+ * now change the mode.
+ * @return true if mode is changed.
*/
- public void handleOpenExampleManager() {
- exampleManagerFrame.showFrame(activeEditor);
- }
+ public boolean changeMode(Mode mode) {
+ Mode oldMode = activeEditor.getMode();
+ if (oldMode != mode) {
+ Sketch sketch = activeEditor.getSketch();
+ nextMode = mode;
+ if (sketch.isUntitled()) {
+ // The current sketch is empty, just close and start fresh.
+ // (Otherwise the editor would lose its 'untitled' status.)
+ handleClose(activeEditor, true);
+ handleNew();
- public void handleShowUpdates() {
- updateManagerFrame.showFrame(activeEditor);
+ } else {
+ // If the current editor contains file extensions that the new mode can handle, then
+ // write a sketch.properties file with the new mode specified, and reopen.
+ boolean newModeCanHandleCurrentSource = true;
+ for (final SketchCode code : sketch.getCode()) {
+ if (!mode.validExtension(code.getExtension())) {
+ newModeCanHandleCurrentSource = false;
+ break;
+ }
+ }
+ if (!newModeCanHandleCurrentSource) {
+ return false;
+ } else {
+ final File props = new File(sketch.getCodeFolder(), "sketch.properties");
+ saveModeSettings(props, nextMode);
+ handleClose(activeEditor, true);
+ Editor editor = handleOpen(sketch.getMainFilePath());
+ if (editor == null) {
+ // the Mode change failed (probably code that's out of date)
+ // re-open the sketch using the mode we were in before
+ saveModeSettings(props, oldMode);
+ handleOpen(sketch.getMainFilePath());
+ return false;
+ }
+ }
+ }
+ }
+ return true;
}
- // ...................................................................
-
+ private static class ModeInfo {
+ public final String title;
+ public final String id;
- static public int getRevision() {
- return REVISION;
+ public ModeInfo(String id, String title) {
+ this.id = id;
+ this.title = title;
+ }
}
- /**
- * 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;
+ private static ModeInfo modeInfoFor(final File sketch) {
+ final File sketchFolder = sketch.getParentFile();
+ final File sketchProps = new File(sketchFolder, "sketch.properties");
+ if (!sketchProps.exists()) {
+ return null;
+ }
+ try {
+ final Settings settings = new Settings(sketchProps);
+ final String title = settings.get("mode");
+ final String id = settings.get("mode.id");
+ if (title == null || id == null) {
+ return null;
+ }
+ return new ModeInfo(id, title);
+ } catch (IOException e) {
+ System.err.println("While trying to read " + sketchProps + ": "
+ + e.getMessage());
+ }
+ return null;
}
- //...................................................................
-
-
- static public Platform getPlatform() {
- return platform;
+ private Mode promptForMode(final File sketch, final ModeInfo preferredMode) {
+ final String extension =
+ sketch.getName().substring(sketch.getName().lastIndexOf('.') + 1);
+ final List possibleModes = new ArrayList<>();
+ for (final Mode mode : getModeList()) {
+ if (mode.canEdit(sketch)) {
+ possibleModes.add(mode);
+ }
+ }
+ if (possibleModes.size() == 1 &&
+ possibleModes.get(0).getIdentifier().equals(getDefaultModeIdentifier())) {
+ // If default mode can open it, then do so without prompting.
+ return possibleModes.get(0);
+ }
+ if (possibleModes.size() == 0) {
+ if (preferredMode == null) {
+ final String msg =
+ "I don't know how to open a sketch with the \"" + extension + "\"\n" +
+ "file extension. You'll have to install a different\n" +
+ "Mode for that.";
+ Messages.showWarning("Modeless Dialog", msg);
+ } else {
+ Messages.showWarning("Modeless Dialog",
+ "Install " + preferredMode.title + " Mode " +
+ "to open this sketch.");
+ }
+ return null;
+ }
+ final Mode[] modes = possibleModes.toArray(new Mode[possibleModes.size()]);
+ final String message = preferredMode == null ?
+ (nextMode.getTitle() + " Mode can't open ." + extension + " files, " +
+ "but you have one or more modes\ninstalled that can. " +
+ "Would you like to try one?") :
+ ("That's a " + preferredMode.title + " Mode sketch, " +
+ "but you don't have " + preferredMode.title + " installed.\n" +
+ "Would you like to try a different mode for opening a " +
+ "." + extension + " sketch?");
+ return (Mode) JOptionPane.showInputDialog(null, message, "Choose Wisely",
+ JOptionPane.QUESTION_MESSAGE,
+ null, modes, modes[0]);
}
- static public String getPlatformName() {
- return PConstants.platformNames[PApplet.platform];
+ private Mode selectMode(final File sketch) {
+ final ModeInfo modeInfo = modeInfoFor(sketch);
+ final Mode specifiedMode = modeInfo == null ? null : findMode(modeInfo.id);
+ if (specifiedMode != null) {
+ return specifiedMode;
+ }
+ return promptForMode(sketch, modeInfo);
}
- // 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;
+ protected Mode findMode(String id) {
+ for (Mode mode : getModeList()) {
+ if (mode.getIdentifier().equals(id)) {
+ return mode;
+ }
+ }
+ return null;
}
- /*
- 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";
- }
- }
- */
+ boolean breakTime = false;
+ String[] months = {
+ "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec"
+ };
/**
- * Map a platform constant to its name.
- * @param which PConstants.WINDOWS, PConstants.MACOSX, PConstants.LINUX
- * @return one of "windows", "macosx", or "linux"
+ * Create a new untitled document in a new sketch window.
*/
- static public String getPlatformName(int which) {
- return platformNames.get(which);
- }
-
+ public void handleNew() {
+ try {
+ File newbieDir = null;
+ String newbieName = null;
- static public int getPlatformIndex(String what) {
- Integer entry = platformIndices.get(what);
- return (entry == null) ? -1 : entry.intValue();
- }
+ // 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");
- // 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.
+ // 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());
+ // Make the directory for the new sketch
+ newbieDir.mkdirs();
- /**
- * 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$
- }
+ // Add any template files from the Mode itself
+ File newbieFile = nextMode.addTemplateFiles(newbieDir, newbieName);
+ /*
+ // 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.");
+ }
+ */
- /*
- 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;
- }
+ // Create sketch properties file if it's not the default mode.
+ if (!nextMode.equals(getDefaultMode())) {
+ saveModeSettings(new File(newbieDir, "sketch.properties"), nextMode);
}
- }
- return usableOracleJava;
- }
- */
+ String path = newbieFile.getAbsolutePath();
+ /*Editor editor =*/ handleOpen(path, true);
- /**
- * returns true if running on windows.
- */
- static public boolean isWindows() {
- //return PApplet.platform == PConstants.WINDOWS;
- return System.getProperty("os.name").indexOf("Windows") != -1; //$NON-NLS-1$ //$NON-NLS-2$
+ } 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);
+ }
}
/**
- * true if running on linux.
+ * Prompt for a sketch to open, and open it in a new window.
*/
- static public boolean isLinux() {
- //return PApplet.platform == PConstants.LINUX;
- return System.getProperty("os.name").indexOf("Linux") != -1; //$NON-NLS-1$ //$NON-NLS-2$
- }
+ public void handleOpenPrompt() {
+ final StringList extensions = new StringList();
+ for (Mode mode : getModeList()) {
+ extensions.append(mode.getDefaultExtension());
+ }
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ final String prompt = Language.text("open");
+ // 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);
- /**
- * 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;
+ // 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;
+ }
+ });
-// String preferencesPath = Preferences.get("settings.path"); //$NON-NLS-1$
-// if (preferencesPath != null) {
-// settingsFolder = new File(preferencesPath);
-//
-// } else {
- try {
- settingsFolder = platform.getSettingsFolder();
- } catch (Exception e) {
- showError("Problem getting the settings folder",
- "Error getting the Processing the settings folder.", e);
- }
-// }
+ openDialog.setVisible(true);
- // 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);
+ String directory = openDialog.getDirectory();
+ String filename = openDialog.getFile();
+ if (filename != null) {
+ File inputFile = new File(directory, filename);
+ handleOpen(inputFile.getAbsolutePath());
}
- }
- 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);
- }
+ } else {
+ if (openChooser == null) {
+ openChooser = new JFileChooser();
+ }
+ openChooser.setDialogTitle(prompt);
- /*
- static public File getBuildFolder() {
- if (buildFolder == null) {
- String buildPath = Preferences.get("build.path");
- if (buildPath != null) {
- buildFolder = new File(buildPath);
+ 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;
+ }
- } else {
- //File folder = new File(getTempFolder(), "build");
- //if (!folder.exists()) folder.mkdirs();
- buildFolder = createTempFolder("build");
- buildFolder.deleteOnExit();
+ public String getDescription() {
+ return "Processing Sketch";
+ }
+ });
+ if (openChooser.showOpenDialog(activeEditor) == JFileChooser.APPROVE_OPTION) {
+ handleOpen(openChooser.getSelectedFile().getAbsolutePath());
}
}
- 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
+ * Open a sketch from the path specified. Do not use for untitled sketches.
*/
- 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;
+ public Editor handleOpen(String path) {
+ return handleOpen(path, false);
}
- static public File getToolsFolder() {
- return getContentFile("tools");
+ /**
+ * 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, boolean untitled) {
+ return handleOpen(path, untitled, new EditorState(editors));
}
- 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;
- }
- }
+ protected Editor handleOpen(String path, boolean untitled,
+ EditorState state) {
+ try {
+ // System.err.println("entering handleOpen " + path);
- // 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();
+ final File file = new File(path);
+ if (!file.exists()) {
+ return null;
}
- }
-
- getSketchbookLibrariesFolder().mkdir();
- getSketchbookToolsFolder().mkdir();
- getSketchbookModesFolder().mkdir();
- getSketchbookExamplesFolder().mkdir();
-// System.err.println("sketchbook: " + sketchbookFolder);
- }
+ // 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;
+ }
+ }
+ }
- public void setSketchbookFolder(File folder) {
- sketchbookFolder = folder;
- Preferences.setSketchbookPath(folder.getAbsolutePath());
- rebuildSketchbookMenus();
- }
+ 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 (!nextMode.canEdit(file)) {
+ final Mode mode = selectMode(file);
+ if (mode == null) {
+ return null;
+ }
+ nextMode = mode;
+ }
- static public File getSketchbookFolder() {
- return sketchbookFolder;
- }
+ try {
+ Editor editor = nextMode.createEditor(this, path, state);
+ 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, title,
- JOptionPane.WARNING_MESSAGE);
- } else {
- // Pane formatting adapted from the Quaqua guide
- // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
- JOptionPane pane =
- new JOptionPane(" " +
- " " +
- "" + 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
(even if pre) otherwise Java
- // treats it as a closed tag and reverts to plain formatting.
- message = "" + message + "
" +
- sw.toString().replaceAll("\n", "
");
-
- JOptionPane.showMessageDialog(new Frame(), message, title,
- fatal ?
- JOptionPane.ERROR_MESSAGE :
- JOptionPane.WARNING_MESSAGE);
-
- if (fatal) {
- System.exit(1);
- }
- }
+ 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);
+ }
- // 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;
-// if (result == JOptionPane.YES_OPTION) {
-//
-// } else if (result == JOptionPane.NO_OPTION) {
-// return true; // ok to continue
-//
-// } else if (result == JOptionPane.CANCEL_OPTION) {
-// return false;
-//
-// } else {
-// throw new IllegalStateException();
-// }
- } else {
- // Pane formatting adapted from the Quaqua guide
- // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
- JOptionPane pane =
- new JOptionPane(" " +
- " " +
- "" + Language.text("save.title") + "" +
- "" + 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, 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(" " +
- " " +
- "" + 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 readSettings(File inputFile) {
- if (!inputFile.exists()) {
- if (DEBUG) System.err.println(inputFile + " does not exist.");
- return null;
- }
- String lines[] = PApplet.loadStrings(inputFile);
- if (lines == null) {
- System.err.println("Could not read " + inputFile);
- return null;
+ String[] fileList = folder.list();
+ // If a bad folder or unreadable or whatever, this will come back null
+ if (fileList == null) {
+ return false;
}
- return readSettings(inputFile.toString(), lines);
- }
+ // Alphabetize the list, since it's not always alpha order
+ Arrays.sort(fileList, String.CASE_INSENSITIVE_ORDER);
- /**
- * 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 HahMap 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 Map readSettings(String filename, String[] lines) {
- Map settings = new HashMap<>();
- for (String line : lines) {
- // Remove comments
- int commentMarker = line.indexOf('#');
- if (commentMarker != -1) {
- line = line.substring(0, commentMarker);
+ boolean found = false;
+ for (String name : fileList) {
+ if (name.charAt(0) == '.') { // Skip hidden files
+ continue;
}
- // 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);
- }
+
+ 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 {
- String attr = line.substring(0, equals).trim();
- String valu = line.substring(equals + 1).trim();
- settings.put(attr, valu);
+ // 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, examples);
+ if (anything) {
+ node.add(subnode);
+ found = true;
+ }
}
}
}
- 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.
- */
- static public String loadFile(File file) throws IOException {
- String[] contents = PApplet.loadStrings(file);
- if (contents == null) return null;
- return PApplet.join(contents, "\n");
+ return found;
}
/**
- * Spew the contents of a String object out to a file.
+ * 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.
*/
- static public void saveFile(String str, File file) throws IOException {
- 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());
- }
- // Can't use saveStrings() here b/c Windows will add a ^M to the file
- PrintWriter writer = PApplet.createWriter(temp);
- writer.print(str);
- 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());
+ 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;
}
}
- boolean result = temp.renameTo(file);
- if (!result) {
- throw new IOException("Could not replace " + file.getAbsolutePath() +
- " with " + temp.getAbsolutePath());
- }
+ return null;
}
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
/**
- * 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.
+ * Show the Preferences window.
*/
- 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);
- }
+ public void handlePrefs() {
+ if (preferencesFrame == null) {
+ preferencesFrame = new PreferencesFrame(this);
}
+ preferencesFrame.showFrame();
}
- static public void copyDirNative(File sourceDir,
- File targetDir) throws IOException {
- Process process = null;
- if (Base.isMacOS() || Base.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
+ * Return a File from inside the Processing 'lib' folder.
*/
- static public boolean platformDelete(File file) throws IOException {
- return platform.deleteFile(file);
+ static public File getLibFile(String filename) throws IOException {
+ return new File(Platform.getContentFile("lib"), filename);
}
/**
- * Remove all files in a directory and the directory itself.
+ * Return an InputStream for a file inside the Processing lib folder.
*/
- static public void removeDir(File dir) {
- if (dir.exists()) {
- removeDescendants(dir);
- if (!dir.delete()) {
- System.err.println("Could not delete " + dir);
- }
- }
+ static public InputStream getLibStream(String filename) throws IOException {
+ return new FileInputStream(getLibFile(filename));
}
/**
- * 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)
+ * 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 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);
- }
+ static public File getSettingsFolder() {
+ File settingsFolder = null;
+
+ try {
+ settingsFolder = Platform.getSettingsFolder();
+
+ // create the folder if it doesn't exist already
+ if (!settingsFolder.exists()) {
+ if (!settingsFolder.mkdirs()) {
+ Messages.showError("Settings issues",
+ "Processing cannot run because it could not\n" +
+ "create a folder to store your settings.\n" +
+ settingsFolder.getAbsolutePath(), null);
}
- } else {
- removeDir(dead);
- //dead.delete();
}
+ } catch (Exception e) {
+ Messages.showTrace("An rare and unknowable thing happened",
+ "Could not get the settings folder. Please report:\n" +
+ "http://github.com/processing/processing/issues/new",
+ e, true);
}
+ return settingsFolder;
}
/**
- * 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.
+ * 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 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;
+ static public File getSettingsFile(String filename) {
+ return new File(getSettingsFolder(), filename);
}
- /**
- * 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 vector = new Vector();
- listFiles(relative ? (path + File.separator) : "", path, null, vector);
- String outgoing[] = new String[vector.size()];
- vector.copyInto(outgoing);
- return outgoing;
+ static public File getToolsFolder() {
+ return Platform.getContentFile("tools");
}
- static public String[] listFiles(File folder, boolean relative, String extension) {
- String path = folder.getAbsolutePath();
- Vector vector = new Vector();
- if (extension != null) {
- if (!extension.startsWith(".")) {
- extension = "." + extension;
+ 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()) {
+ Messages.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 itself in the third person.", null);
+ sketchbookFolder = null;
}
}
- listFiles(relative ? (path + File.separator) : "", path, extension, vector);
- String outgoing[] = new String[vector.size()];
- vector.copyInto(outgoing);
- return outgoing;
- }
-
- static protected void listFiles(String basePath,
- String path, String extension,
- Vector vector) {
- File folder = new File(path);
- String[] list = folder.list();
- if (list != null) {
- for (String item : list) {
- if (item.charAt(0) == '.') continue;
- if (extension == null || item.toLowerCase().endsWith(extension)) {
- File file = new File(path, item);
- String newPath = file.getAbsolutePath();
- if (newPath.startsWith(basePath)) {
- newPath = newPath.substring(basePath.length());
- }
- // only add if no ext or match
- if (extension == null || item.toLowerCase().endsWith(extension)) {
- vector.add(newPath);
- }
- if (file.isDirectory()) { // use absolute path
- listFiles(basePath, file.getAbsolutePath(), extension, vector);
- }
- }
+ // 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();
}
}
+ makeSketchbookSubfolders();
}
- /**
- * @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")));
- }
- });
+ public void setSketchbookFolder(File folder) {
+ sketchbookFolder = folder;
+ Preferences.setSketchbookPath(folder.getAbsolutePath());
+ rebuildSketchbookMenus();
+ makeSketchbookSubfolders();
}
- /////////////////////////////////////////////////////////////////////////////
-
-
/**
- * 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.
+ * Create the libraries, modes, tools, examples folders in the sketchbook.
*/
- 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();
+ static protected void makeSketchbookSubfolders() {
+ getSketchbookLibrariesFolder().mkdirs();
+ getSketchbookToolsFolder().mkdirs();
+ getSketchbookModesFolder().mkdirs();
+ getSketchbookExamplesFolder().mkdirs();
+ getSketchbookTemplatesFolder().mkdirs();
}
- /**
- * 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 String[] packageListFromClassPath(String path) {
- Map map = new HashMap();
- String pieces[] =
- PApplet.split(path, File.pathSeparatorChar);
-
- for (int i = 0; i < pieces.length; i++) {
- //System.out.println("checking piece '" + pieces[i] + "'");
- if (pieces[i].length() == 0) continue;
-
- if (pieces[i].toLowerCase().endsWith(".jar") ||
- pieces[i].toLowerCase().endsWith(".zip")) {
- //System.out.println("checking " + pieces[i]);
- packageListFromZip(pieces[i], map);
-
- } else { // it's another type of file or directory
- File dir = new File(pieces[i]);
- if (dir.exists() && dir.isDirectory()) {
- packageListFromFolder(dir, null, map);
- //importCount = magicImportsRecursive(dir, null,
- // map);
- //imports, importCount);
- }
- }
- }
- int mapCount = map.size();
- String output[] = new String[mapCount];
- int index = 0;
- Set set = map.keySet();
- for (String s : set) {
- output[index++] = s.replace('/', '.');
- }
- //System.arraycopy(imports, 0, output, 0, importCount);
- //PApplet.printarr(output);
- return output;
+ static public File getSketchbookFolder() {
+ return sketchbookFolder;
}
- static private void packageListFromZip(String filename, Map map) {
- try {
- ZipFile file = new ZipFile(filename);
- Enumeration entries = file.entries();
- while (entries.hasMoreElements()) {
- ZipEntry entry = (ZipEntry) entries.nextElement();
-
- if (!entry.isDirectory()) {
- String name = entry.getName();
-
- if (name.endsWith(".class")) {
- int slash = name.lastIndexOf('/');
- if (slash == -1) continue;
-
- String pname = name.substring(0, slash);
- if (map.get(pname) == null) {
- map.put(pname, new Object());
- }
- }
- }
- }
- file.close();
- } catch (IOException e) {
- System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")");
- //e.printStackTrace();
- }
+ static public File getSketchbookLibrariesFolder() {
+ return new File(sketchbookFolder, "libraries");
}
- /**
- * Make list of package names by traversing a directory hierarchy.
- * Each time a class is found in a folder, add its containing set
- * of folders to the package list. If another folder is found,
- * walk down into that folder and continue.
- */
- static private void packageListFromFolder(File dir, String sofar,
- Map map) {
- //String imports[],
- //int importCount) {
- //System.err.println("checking dir '" + dir + "'");
- boolean foundClass = false;
- String files[] = dir.list();
-
- for (int i = 0; i < files.length; i++) {
- if (files[i].equals(".") || files[i].equals("..")) continue;
-
- File sub = new File(dir, files[i]);
- if (sub.isDirectory()) {
- String nowfar =
- (sofar == null) ? files[i] : (sofar + "." + files[i]);
- packageListFromFolder(sub, nowfar, map);
- //System.out.println(nowfar);
- //imports[importCount++] = nowfar;
- //importCount = magicImportsRecursive(sub, nowfar,
- // imports, importCount);
- } else if (!foundClass) { // if no classes found in this folder yet
- if (files[i].endsWith(".class")) {
- //System.out.println("unique class: " + files[i] + " for " + sofar);
- map.put(sofar, new Object());
- foundClass = true;
- }
- }
- }
+ static public File getSketchbookToolsFolder() {
+ return new File(sketchbookFolder, "tools");
}
- static public void unzip(File zipFile, File dest) {
- try {
- FileInputStream fis = new FileInputStream(zipFile);
- CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32());
- ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum));
- ZipEntry next = null;
- while ((next = zis.getNextEntry()) != null) {
- File currentFile = new File(dest, next.getName());
- if (next.isDirectory()) {
- currentFile.mkdirs();
- } else {
- File parentDir = currentFile.getParentFile();
- // Sometimes the directory entries aren't already created
- if (!parentDir.exists()) {
- parentDir.mkdirs();
- }
- currentFile.createNewFile();
- unzipEntry(zis, currentFile);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
+ static public File getSketchbookModesFolder() {
+ return new File(sketchbookFolder, "modes");
}
- static protected void unzipEntry(ZipInputStream zin, File f) throws IOException {
- FileOutputStream out = new FileOutputStream(f);
- byte[] b = new byte[512];
- int len = 0;
- while ((len = zin.read(b)) != -1) {
- out.write(b, 0, len);
- }
- out.flush();
- out.close();
+ static public File getSketchbookExamplesFolder() {
+ return new File(sketchbookFolder, "examples");
}
- static public void log(Object from, String message) {
- if (DEBUG) {
- System.out.println(from.getClass().getName() + ": " + message);
- }
+ static public File getSketchbookTemplatesFolder() {
+ return new File(sketchbookFolder, "templates");
}
- static public void log(String message) {
- if (DEBUG) {
- System.out.println(message);
- }
- }
-
+ static protected File getDefaultSketchbookFolder() {
+ File sketchbookFolder = null;
+ try {
+ sketchbookFolder = Platform.getDefaultSketchbookFolder();
+ } catch (Exception e) { }
- static public void logf(String message, Object... args) {
- if (DEBUG) {
- System.out.println(String.format(message, args));
+ if (sketchbookFolder == null) {
+ Messages.showError("No sketchbook",
+ "Problem while trying to get the sketchbook", null);
}
- }
-
- static public void loge(String message, Throwable e) {
- if (DEBUG) {
- System.err.println(message);
- e.printStackTrace();
+ // create the folder if it doesn't exist already
+ boolean result = true;
+ if (!sketchbookFolder.exists()) {
+ result = sketchbookFolder.mkdirs();
}
- }
-
- static public void loge(String message) {
- if (DEBUG) {
- System.out.println(message);
+ if (!result) {
+ Messages.showError("You forgot your sketchbook",
+ "Processing cannot run because it could not\n" +
+ "create a folder to store your sketchbook.", null);
}
+
+ return sketchbookFolder;
}
}
diff --git a/app/src/processing/app/BaseSplash.java b/app/src/processing/app/BaseSplash.java
new file mode 100644
index 0000000000..5dc125f6a1
--- /dev/null
+++ b/app/src/processing/app/BaseSplash.java
@@ -0,0 +1,24 @@
+package processing.app;
+
+import java.io.File;
+
+import processing.app.ui.SplashWindow;
+import processing.app.ui.Toolkit;
+
+
+public class BaseSplash {
+ static public void main(String[] args) {
+ try {
+ final boolean hidpi = Toolkit.highResImages();
+ final String filename = "lib/about-" + (hidpi ? 2 : 1) + "x.png";
+ File splashFile = Platform.getContentFile(filename);
+ SplashWindow.splash(splashFile.toURI().toURL(), hidpi);
+ SplashWindow.invokeMain("processing.app.Base", args);
+ SplashWindow.disposeSplash();
+ } catch (Exception e) {
+ e.printStackTrace();
+ // !@#!@$$! umm
+ //SplashWindow.invokeMain("processing.app.Base", args);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/ChangeDetector.java b/app/src/processing/app/ChangeDetector.java
deleted file mode 100644
index 3895b924fb..0000000000
--- a/app/src/processing/app/ChangeDetector.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package processing.app;
-
-import java.awt.EventQueue;
-import java.awt.Frame;
-import java.awt.event.WindowEvent;
-import java.awt.event.WindowFocusListener;
-import java.io.File;
-import java.io.FilenameFilter;
-import java.lang.reflect.InvocationTargetException;
-
-import javax.swing.JOptionPane;
-
-
-public class ChangeDetector implements WindowFocusListener {
- private Sketch sketch;
- private Editor editor;
-
- // Set true if the user selected 'no'. TODO this can't just skip once,
- // because subsequent returns to the window w/o saving will keep firing.
- private boolean skip = false;
-
-
- public ChangeDetector(Editor editor) {
- this.sketch = editor.sketch;
- this.editor = editor;
- }
-
-
- @Override
- public void windowGainedFocus(WindowEvent e) {
- // Keep the listener instantiated and check this to avoid a maze of
- // adding and removing and re-adding with Preferences changes.
- if (Preferences.getBoolean("editor.watcher")) {
- // if they selected no, skip the next focus event
- if (skip) {
- skip = false;
-
- } else {
- new Thread(new Runnable() {
- @Override
- public void run() {
- checkFileChange();
- }
- }).start();
- }
- }
- }
-
-
- @Override
- public void windowLostFocus(WindowEvent e) {
- // Shouldn't need to do anything here, and not storing anything here b/c we
- // don't want to assume a loss of focus is required before change detection
- }
-
-
- private void checkFileChange() {
- //check that the content of each of the files in sketch matches what is in memory
- if (sketch == null) {
- return;
- }
-
- // make sure the sketch folder exists at all.
- // if it does not, it will be re-saved, and no changes will be detected
- sketch.ensureExistence();
-
- // check file count first
- File sketchFolder = sketch.getFolder();
- File[] sketchFiles = sketchFolder.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- for (String s : editor.getMode().getExtensions()) {
- if (name.toLowerCase().endsWith(s.toLowerCase())) {
- return true;
- }
- }
- return false;
- }
- });
- int fileCount = sketchFiles.length;
-
- if (fileCount != sketch.getCodeCount()) {
- // if they chose to reload and there aren't any files left
- if (reloadSketch(null) && fileCount < 1) {
- try {
- //make a blank file
- sketch.getMainFile().createNewFile();
- } catch (Exception e1) {
- //if that didn't work, tell them it's un-recoverable
- showErrorEDT("Reload failed", "The sketch contains no code files.", e1);
- //don't try to reload again after the double fail
- //this editor is probably trashed by this point, but a save-as might be possible
- skip = true;
- return;
- }
- //it's okay to do this without confirmation, because they already confirmed to deleting the unsaved changes above
- sketch.reload();
- showWarningEDT("Modified Reload",
- "You cannot delete the last code file in a sketch.\n" +
- "A new blank sketch file has been generated for you.");
-
- }
- return;
- }
-
- SketchCode[] codes = sketch.getCode();
- for (SketchCode sc : codes) {
- File sketchFile = sc.getFile();
- if (sketchFile.exists()) {
- long diff = sketchFile.lastModified() - sc.lastModified();
- if (diff != 0) {
- if (Base.isMacOS() && diff == 1000L) {
- // Mac OS X has a one second difference. Not sure if it's a Java bug
- // or something else about how OS X is writing files.
- continue;
- }
- System.out.println(sketchFile.getName() + " " + diff);
- reloadSketch(sc);
- return;
- }
- } else {
- // If a file in the sketch was not found, then it must have been
- // deleted externally, so reload the sketch.
- reloadSketch(sc);
- return;
- }
- }
- }
-
-
- private void setSketchCodeModified(SketchCode sc) {
- sc.setModified(true);
- sketch.setModified(true);
- }
-
-
- /**
- * @param changed The file that was known to be modified
- * @return true if the files in the sketch have been reloaded
- */
- private boolean reloadSketch(SketchCode changed) {
- int response = blockingYesNoPrompt(editor,
- "File Modified",
- "Your sketch has been modified externally.
" +
- "Would you like to reload the sketch?",
- "If you reload the sketch, any unsaved changes will be lost.");
- if (response == JOptionPane.YES_OPTION) {
- sketch.reload();
- rebuildHeaderEDT();
- return true;
- }
-
- // they said no (or canceled), make it possible to stop the msgs by saving
- if (changed != null) {
- //set it to be modified so that it will actually save to disk when the user saves from inside processing
- setSketchCodeModified(changed);
-
- } else {
- // Because the number of files changed, they may be working with a file
- // that doesn't exist any more. So find the files that are missing,
- // and mark them as modified so that the next "Save" will write them.
- for (SketchCode sc : sketch.getCode()) {
- if (!sc.getFile().exists()) {
- setSketchCodeModified(sc);
- }
- }
- // If files were simply added, then nothing needs done
- }
- rebuildHeaderEDT();
- skip = true;
- return false;
- }
-
-
- private void showErrorEDT(final String title, final String message,
- final Exception e) {
- EventQueue.invokeLater(new Runnable() {
- @Override
- public void run() {
- Base.showError(title, message, e);
- }
- });
- }
-
-
- private void showWarningEDT(final String title, final String message) {
- EventQueue.invokeLater(new Runnable() {
- @Override
- public void run() {
- Base.showWarning(title, message);
- }
- });
- }
-
-
- private int blockingYesNoPrompt(final Frame editor, final String title,
- final String message1,
- final String message2) {
- final int[] result = { -1 }; // yuck
- try {
- //have to wait for a response on this one
- EventQueue.invokeAndWait(new Runnable() {
- @Override
- public void run() {
- result[0] = Base.showYesNoQuestion(editor, title, message1, message2);
- }
- });
- } catch (InvocationTargetException e) {
- //occurs if Base.showYesNoQuestion throws an error, so, shouldn't happen
- e.getTargetException().printStackTrace();
- } catch (InterruptedException e) {
- //occurs if the EDT is interrupted, so, shouldn't happen
- e.printStackTrace();
- }
- return result[0];
- }
-
-
- private void rebuildHeaderEDT() {
- EventQueue.invokeLater(new Runnable() {
- @Override
- public void run() {
- editor.header.rebuild();
- }
- });
- }
-}
diff --git a/app/src/processing/app/Console.java b/app/src/processing/app/Console.java
new file mode 100644
index 0000000000..73b652f337
--- /dev/null
+++ b/app/src/processing/app/Console.java
@@ -0,0 +1,261 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2012-16 The Processing Foundation
+ Copyright (c) 2004-12 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 as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ 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.text.SimpleDateFormat;
+import java.util.Date;
+
+
+/**
+ * Non-GUI handling of System.out and System.err redirection.
+ *
+ * Be careful when debugging this class, because if it's throwing exceptions,
+ * don't take over System.err, and debug while watching just System.out
+ * or just call println() or whatever directly to systemOut or systemErr.
+ *
+ * Also note that encodings will not work properly when run from Eclipse. This
+ * means that if you use non-ASCII characters in a println() or some such,
+ * the characters won't print properly in the Processing and/or Eclipse console.
+ * It seems that Eclipse's console-grabbing and that of Processing don't
+ * get along with one another. Use 'ant run' to work on encoding-related issues.
+ */
+public class Console {
+ // Single static instance shared because there's only one real System.out.
+ // Within the input handlers, the currentConsole variable will be used to
+ // echo things to the correct location.
+
+ /** The original System.out */
+ static PrintStream systemOut;
+ /** The original System.err */
+ static PrintStream systemErr;
+
+ /** Our replacement System.out */
+ static PrintStream consoleOut;
+ /** Our replacement System.err */
+ static PrintStream consoleErr;
+
+ /** All stdout also written to a file */
+ static OutputStream stdoutFile;
+ /** All stderr also written to a file */
+ static OutputStream stderrFile;
+
+ /** stdout listener for the currently active Editor */
+ static OutputStream editorOut;
+ /** stderr listener for the currently active Editor */
+ static OutputStream editorErr;
+
+
+ static public void startup() {
+ if (systemOut != null) {
+ // TODO fix this dreadful style choice in how the Console is initialized
+ // (This is not good code.. startup() should gracefully deal with this.
+ // It's just a low priority relative to the likelihood of trouble.)
+ new Exception("startup() called more than once").printStackTrace(systemErr);
+ return;
+ }
+ systemOut = System.out;
+ systemErr = System.err;
+
+ // placing everything inside a try block because this can be a dangerous
+ // time for the lights to blink out and crash for and obscure reason.
+ try {
+ SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd_HHmmss");
+ // Moving away from a random string in 0256 (and adding hms) because
+ // the random digits looked like times anyway, causing confusion.
+ //String randy = String.format("%04d", (int) (1000 * Math.random()));
+ //final String stamp = formatter.format(new Date()) + "_" + randy;
+ final String stamp = formatter.format(new Date());
+
+ File consoleDir = Base.getSettingsFile("console");
+ if (consoleDir.exists()) {
+ // clear old debug files
+ File[] stdFiles = consoleDir.listFiles(new FileFilter() {
+ final String todayPrefix = stamp.substring(0, 4);
+
+ public boolean accept(File file) {
+ if (!file.isDirectory()) {
+ String name = file.getName();
+ if (name.endsWith(".err") || name.endsWith(".out")) {
+ // don't delete any of today's debug messages
+ return !name.startsWith(todayPrefix);
+ }
+ }
+ return false;
+ }
+ });
+ // Remove any files that aren't from today
+ for (File file : stdFiles) {
+ file.delete();
+ }
+ } else {
+ consoleDir.mkdirs();
+ consoleDir.setWritable(true, false);
+ }
+
+ File outFile = new File(consoleDir, stamp + ".out");
+ outFile.setWritable(true, false);
+ stdoutFile = new FileOutputStream(outFile);
+ File errFile = new File(consoleDir, stamp + ".err");
+ errFile.setWritable(true, false);
+ stderrFile = new FileOutputStream(errFile);
+
+ consoleOut = new PrintStream(new ConsoleStream(false));
+ consoleErr = new PrintStream(new ConsoleStream(true));
+
+ System.setOut(consoleOut);
+ System.setErr(consoleErr);
+
+ } catch (Exception e) {
+ stdoutFile = null;
+ stderrFile = null;
+
+ consoleOut = null;
+ consoleErr = null;
+
+ System.setOut(systemOut);
+ System.setErr(systemErr);
+
+ e.printStackTrace();
+ }
+ }
+
+
+ static public void setEditor(OutputStream out, OutputStream err) {
+ editorOut = out;
+ editorErr = err;
+ }
+
+
+ static public void systemOut(String what) {
+ systemOut.println(what);
+ }
+
+
+ static public void systemErr(String what) {
+ systemErr.println(what);
+ }
+
+
+ /**
+ * Close the streams so that the temporary files can be deleted.
+ *
+ * File.deleteOnExit() cannot be used because the stdout and stderr
+ * files are inside a folder, and have to be deleted before the
+ * folder itself is deleted, which can't be guaranteed when using
+ * the deleteOnExit() method.
+ */
+ static public void shutdown() {
+ // replace original streams to remove references to console's streams
+ System.setOut(systemOut);
+ System.setErr(systemErr);
+
+ cleanup(consoleOut);
+ cleanup(consoleErr);
+
+ // also have to close the original FileOutputStream
+ // otherwise it won't be shut down completely
+ cleanup(stdoutFile);
+ cleanup(stderrFile);
+ }
+
+
+ static private void cleanup(OutputStream output) {
+ try {
+ if (output != null) {
+ output.flush();
+ output.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ static class ConsoleStream extends OutputStream {
+ boolean err; // whether stderr or stdout
+ byte single[] = new byte[1];
+
+ public ConsoleStream(boolean err) {
+ this.err = err;
+ }
+
+ public void close() { }
+
+ public void flush() { }
+
+ public void write(byte b[]) { // appears never to be used
+ write(b, 0, b.length);
+ }
+
+ public void write(byte b[], int offset, int length) {
+ // First write to the original stdout/stderr
+ if (err) {
+ systemErr.write(b, offset, length);
+ } else {
+ systemOut.write(b, offset, length);
+ }
+
+ // Write to the files that are storing this information
+ writeFile(b, offset, length);
+
+ // Write to the console of the current Editor, if any
+ try {
+ if (err) {
+ if (editorErr != null) {
+ editorErr.write(b, offset, length);
+ }
+ } else {
+ if (editorOut != null) {
+ editorOut.write(b, offset, length);
+ }
+ }
+ } catch (IOException e) {
+ // Avoid this function being called in a recursive, infinite loop
+ e.printStackTrace(systemErr);
+ }
+ }
+
+ public void writeFile(byte b[], int offset, int length) {
+ final OutputStream echo = err ? stderrFile : stdoutFile;
+ if (echo != null) {
+ try {
+ echo.write(b, offset, length);
+ echo.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void write(int b) {
+ single[0] = (byte) b;
+ write(single, 0, 1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/EditorLineStatus.java b/app/src/processing/app/EditorLineStatus.java
deleted file mode 100644
index ff7a1bacd8..0000000000
--- a/app/src/processing/app/EditorLineStatus.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2005-07 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 as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- 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 javax.swing.*;
-
-
-/**
- * Li'l status bar fella that shows the line number.
- */
-public class EditorLineStatus extends JComponent {
- Editor editor;
-// JEditTextArea textarea;
- int start = -1, stop;
-
- Color foreground;
- Color background;
- Font font;
- int high;
-
- String text = "";
-
-
- public EditorLineStatus(Editor editor) {
- this.editor = editor;
-
-// textarea = editor.getTextArea();
- // not pretty, but it just does one thing...
-// textarea.editorLineStatus = this;
- editor.getTextArea().editorLineStatus = this;
-
- updateMode();
- }
-
-
- public void updateMode() {
- Mode mode = editor.getMode();
- background = mode.getColor("linestatus.bgcolor");
- font = mode.getFont("linestatus.font");
- foreground = mode.getColor("linestatus.color");
- high = mode.getInteger("linestatus.height");
- }
-
-
- public void set(int newStart, int newStop) {
- if ((newStart == start) && (newStop == stop)) return;
-
- start = newStart;
- stop = newStop;
-
- /*
- if (start == stop) {
- text = "Line " + (start + 1);
- } else {
- text = "Lines " + (start + 1) + " to " + (stop + 1);
- }
- */
- if (start == stop) {
- text = String.valueOf(start+1);
- } else {
- text = (start+1) + " - " + (stop+1);
- }
-
- repaint();
- }
-
-
- public void paintComponent(Graphics g) {
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
- RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
- g.setColor(background);
- Dimension size = getSize();
- g.fillRect(0, 0, size.width, size.height);
-
- g.setFont(font);
- g.setColor(foreground);
- int baseline = (high + g.getFontMetrics().getAscent()) / 2;
- // With 7u40 (or Source Code Sans?) things seem to be edged up a bit
- g.drawString(text, 6, baseline - 1);
- }
-
-
- public Dimension getPreferredSize() {
- return new Dimension(300, high);
- }
-
- public Dimension getMinimumSize() {
- return getPreferredSize();
- }
-
- public Dimension getMaximumSize() {
- return new Dimension(3000, high);
- }
-}
diff --git a/app/src/processing/app/EditorStatus.java b/app/src/processing/app/EditorStatus.java
deleted file mode 100644
index af50ad716b..0000000000
--- a/app/src/processing/app/EditorStatus.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/* -*- 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 as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- 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 javax.swing.*;
-
-
-/**
- * Panel just below the editing area that contains status messages.
- */
-public class EditorStatus extends JPanel {
- static final int HIGH = 28;
-
- Color[] bgcolor;
- Color[] fgcolor;
-
- static public final int NOTICE = 0;
- static public final int ERR = 1;
- static public final int EDIT = 2;
-
- static final int YES = 1;
- static final int NO = 2;
- static final int CANCEL = 3;
- static final int OK = 4;
-
- static final String NO_MESSAGE = "";
-
- Editor editor;
-
- int mode;
- String message;
-
- Font font;
- FontMetrics metrics;
- int ascent;
-
- Image offscreen;
- int sizeW, sizeH;
-
-// JButton cancelButton;
-// JButton okButton;
-// JTextField editField;
-
- int response;
-
- boolean indeterminate;
- Thread thread;
-
-
-
- public EditorStatus(Editor editor) {
- this.editor = editor;
- empty();
- updateMode();
- }
-
-
- public void updateMode() {
- Mode mode = editor.getMode();
- bgcolor = new Color[] {
- mode.getColor("status.notice.bgcolor"),
- mode.getColor("status.error.bgcolor"),
- mode.getColor("status.edit.bgcolor")
- };
-
- fgcolor = new Color[] {
- mode.getColor("status.notice.fgcolor"),
- mode.getColor("status.error.fgcolor"),
- mode.getColor("status.edit.fgcolor")
- };
-
- font = mode.getFont("status.font");
- metrics = null;
- }
-
-
- public void empty() {
- mode = NOTICE;
- message = NO_MESSAGE;
- repaint();
- }
-
-
- public void notice(String message) {
- mode = NOTICE;
- this.message = message;
- repaint();
- }
-
-
- public void unnotice(String unmessage) {
- if (message.equals(unmessage)) empty();
- }
-
-
- public void error(String message) {
- mode = ERR;
- this.message = message;
- repaint();
- }
-
-
-// public void edit(String message, String dflt) {
-// mode = EDIT;
-// this.message = message;
-//
-// response = 0;
-// okButton.setVisible(true);
-// cancelButton.setVisible(true);
-// editField.setVisible(true);
-// editField.setText(dflt);
-// editField.selectAll();
-// editField.requestFocusInWindow();
-//
-// repaint();
-// }
-
-
-// public void unedit() {
-// okButton.setVisible(false);
-// cancelButton.setVisible(false);
-// editField.setVisible(false);
-// editor.textarea.requestFocusInWindow();
-// empty();
-// }
-
-
- public void startIndeterminate() {
- indeterminate = true;
- thread = new Thread() {
- public void run() {
- while (Thread.currentThread() == thread) {
- repaint();
- try {
- Thread.sleep(1000 / 10);
- } catch (InterruptedException e) { }
- }
- }
- };
- thread.setName("Editor Status");
- thread.start();
- }
-
-
- public void stopIndeterminate() {
- indeterminate = false;
- thread = null;
- repaint();
- }
-
-
- public void paintComponent(Graphics screen) {
-// if (okButton == null) setup();
-
- Dimension size = getSize();
- if ((size.width != sizeW) || (size.height != sizeH)) {
- // component has been resized
- offscreen = null;
- }
-
- if (offscreen == null) {
- sizeW = size.width;
- sizeH = size.height;
-// setButtonBounds();
- if (Toolkit.highResDisplay()) {
- offscreen = createImage(sizeW*2, sizeH*2);
- } else {
- offscreen = createImage(sizeW, sizeH);
- }
- }
-
- Graphics g = offscreen.getGraphics();
- /*Graphics2D g2 =*/ Toolkit.prepareGraphics(g);
-
- g.setFont(font);
- if (metrics == null) {
- metrics = g.getFontMetrics();
- ascent = metrics.getAscent();
- }
-
- g.setColor(bgcolor[mode]);
- g.fillRect(0, 0, sizeW, sizeH);
-
- g.setColor(fgcolor[mode]);
- g.setFont(font); // needs to be set each time on osx
- g.drawString(message, Preferences.GUI_SMALL, (sizeH + ascent) / 2);
-
- if (indeterminate) {
- //int x = cancelButton.getX();
- //int w = cancelButton.getWidth();
- int w = Preferences.BUTTON_WIDTH;
- int x = getWidth() - Preferences.GUI_SMALL - w;
- int y = getHeight() / 3;
- int h = getHeight() / 3;
- g.setColor(new Color(0x80000000, true));
- g.drawRect(x, y, w, h);
- for (int i = 0; i < 10; i++) {
- int r = (int) (x + Math.random() * w);
- g.drawLine(r, y, r, y+h);
- }
- }
-
- screen.drawImage(offscreen, 0, 0, sizeW, sizeH, null);
- }
-
-
- /*
- protected void setup() {
- if (okButton == null) {
- cancelButton = new JButton(Preferences.PROMPT_CANCEL);
- okButton = new JButton(Preferences.PROMPT_OK);
-
- cancelButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (mode == EDIT) {
- unedit();
- //editor.toolbar.clear();
- }
- }
- });
-
- okButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- // answering to rename/new code question
- if (mode == EDIT) { // this if() isn't (shouldn't be?) necessary
- String answer = editField.getText();
- editor.getSketch().nameCode(answer);
- unedit();
- }
- }
- });
-
- // !@#(* aqua ui #($*(( that turtle-neck wearing #(** (#$@)(
- // os9 seems to work if bg of component is set, but x still a bastard
- if (Base.isMacOS()) {
- //yesButton.setBackground(bgcolor[EDIT]);
- //noButton.setBackground(bgcolor[EDIT]);
- cancelButton.setBackground(bgcolor[EDIT]);
- okButton.setBackground(bgcolor[EDIT]);
- }
- setLayout(null);
-
- add(cancelButton);
- add(okButton);
-
- cancelButton.setVisible(false);
- okButton.setVisible(false);
-
- editField = new JTextField();
- // disabling, was not in use
- //editField.addActionListener(this);
-
- //if (Base.platform != Base.MACOSX) {
- editField.addKeyListener(new KeyAdapter() {
-
- // Grab ESC with keyPressed, because it's not making it to keyTyped
- public void keyPressed(KeyEvent event) {
- if (event.getKeyChar() == KeyEvent.VK_ESCAPE) {
- unedit();
- //editor.toolbar.clear();
- event.consume();
- }
- }
-
- // use keyTyped to catch when the feller is actually
- // added to the text field. with keyTyped, as opposed to
- // keyPressed, the keyCode will be zero, even if it's
- // enter or backspace or whatever, so the keychar should
- // be used instead. grr.
- public void keyTyped(KeyEvent event) {
- //System.out.println("got event " + event);
- int c = event.getKeyChar();
-
- if (c == KeyEvent.VK_ENTER) { // accept the input
- String answer = editField.getText();
- editor.getSketch().nameCode(answer);
- unedit();
- event.consume();
-
- // easier to test the affirmative case than the negative
- } else if ((c == KeyEvent.VK_BACK_SPACE) ||
- (c == KeyEvent.VK_DELETE) ||
- (c == KeyEvent.VK_RIGHT) ||
- (c == KeyEvent.VK_LEFT) ||
- (c == KeyEvent.VK_UP) ||
- (c == KeyEvent.VK_DOWN) ||
- (c == KeyEvent.VK_HOME) ||
- (c == KeyEvent.VK_END) ||
- (c == KeyEvent.VK_SHIFT)) {
- // these events are ignored
-
-// } else if (c == KeyEvent.VK_ESCAPE) {
-// unedit();
-// editor.toolbar.clear();
-// event.consume();
-
- } else if (c == KeyEvent.VK_SPACE) {
- String t = editField.getText();
- int start = editField.getSelectionStart();
- int end = editField.getSelectionEnd();
- editField.setText(t.substring(0, start) + "_" +
- t.substring(end));
- editField.setCaretPosition(start+1);
- event.consume();
-
- } else if ((c == '_') || (c == '.') || // allow .pde and .java
- ((c >= 'A') && (c <= 'Z')) ||
- ((c >= 'a') && (c <= 'z'))) {
- // these are ok, allow them through
-
- } else if ((c >= '0') && (c <= '9')) {
- // getCaretPosition == 0 means that it's the first char
- // and the field is empty.
- // getSelectionStart means that it *will be* the first
- // char, because the selection is about to be replaced
- // with whatever is typed.
- if ((editField.getCaretPosition() == 0) ||
- (editField.getSelectionStart() == 0)) {
- // number not allowed as first digit
- //System.out.println("bad number bad");
- event.consume();
- }
- } else {
- event.consume();
- //System.out.println("code is " + code + " char = " + c);
- }
- //System.out.println("code is " + code + " char = " + c);
- }
- });
- add(editField);
- editField.setVisible(false);
- }
- }
-
-
- private void setButtonBounds() {
- int top = (sizeH - BUTTON_HEIGHT) / 2;
- int eachButton = Preferences.GUI_SMALL + Preferences.BUTTON_WIDTH;
-
- int cancelLeft = sizeW - eachButton;
- int noLeft = cancelLeft - eachButton;
- int yesLeft = noLeft - eachButton;
-
- //yesButton.setLocation(yesLeft, top);
- //noButton.setLocation(noLeft, top);
- cancelButton.setLocation(cancelLeft, top);
- okButton.setLocation(noLeft, top);
-
- //yesButton.setSize(Preferences.BUTTON_WIDTH, Preferences.BUTTON_HEIGHT);
- //noButton.setSize(Preferences.BUTTON_WIDTH, Preferences.BUTTON_HEIGHT);
- cancelButton.setSize(Preferences.BUTTON_WIDTH, BUTTON_HEIGHT);
- okButton.setSize(Preferences.BUTTON_WIDTH, BUTTON_HEIGHT);
-
- // edit field height is awkward, and very different between mac and pc,
- // so use at least the preferred height for now.
- int editWidth = 2*Preferences.BUTTON_WIDTH;
- int editHeight = editField.getPreferredSize().height;
- int editTop = (1 + sizeH - editHeight) / 2; // add 1 for ceil
- editField.setBounds(yesLeft - Preferences.BUTTON_WIDTH, editTop,
- editWidth, editHeight);
- }
- */
-
-
- public Dimension getPreferredSize() {
- return getMinimumSize();
- }
-
-
- public Dimension getMinimumSize() {
- return new Dimension(300, HIGH);
- }
-
-
- public Dimension getMaximumSize() {
- return new Dimension(super.getMaximumSize().width, HIGH);
- }
-
-
- /*
- public void actionPerformed(ActionEvent e) {
- if (e.getSource() == cancelButton) {
- if (mode == EDIT) unedit();
- //editor.toolbar.clear();
-
- } else if (e.getSource() == okButton) {
- // answering to rename/new code question
- if (mode == EDIT) { // this if() isn't (shouldn't be?) necessary
- String answer = editField.getText();
- editor.getSketch().nameCode(answer);
- unedit();
- }
- }
- }
- */
-}
diff --git a/app/src/processing/app/Language.java b/app/src/processing/app/Language.java
index f6c6a93912..06be82fc5a 100644
--- a/app/src/processing/app/Language.java
+++ b/app/src/processing/app/Language.java
@@ -31,9 +31,6 @@
* Internationalization (i18n)
*/
public class Language {
-// static private final String FILE = "processing.app.languages.PDE";
- //static private final String LISTING = "processing/app/languages/languages.txt";
-
// Store the language information in a file separate from the preferences,
// because preferences need the language on load time.
static protected final String PREF_FILE = "language.txt";
@@ -48,8 +45,6 @@ public class Language {
/** Available languages */
private HashMap languages;
- //private ResourceBundle bundle;
- //private Settings bundle;
private LanguageBundle bundle;
@@ -66,7 +61,8 @@ private Language() {
// Set available languages
languages = new HashMap();
for (String code : listSupported()) {
- languages.put(code, Locale.forLanguageTag(code).getDisplayLanguage(Locale.forLanguageTag(code)));
+ Locale locale = Locale.forLanguageTag(code);
+ languages.put(code, locale.getDisplayLanguage(locale));
}
// Set default language
@@ -91,18 +87,22 @@ private Language() {
static private String[] listSupported() {
// List of languages in alphabetical order. (Add yours here.)
- // Also remember to add it to the corresponding build/build.xml rule.
+ // Also remember to add it to build/shared/lib/languages/languages.txt.
final String[] SUPPORTED = {
+ "ar", // Arabic
"de", // German, Deutsch
"en", // English
"el", // Greek
"es", // Spanish
"fr", // French, Français
+ "it", // Italiano, Italian
"ja", // Japanese
"ko", // Korean
"nl", // Dutch, Nederlands
"pt", // Portuguese
+ "ru", // Russian
"tr", // Turkish
+ "uk", // Ukrainian
"zh" // Chinese
};
return SUPPORTED;
@@ -148,18 +148,19 @@ static private String loadLanguage() {
*/
static public void saveLanguage(String language) {
try {
- Base.saveFile(language, prefFile);
+ Util.saveFile(language, prefFile);
+ prefFile.setWritable(true, false);
} catch (Exception e) {
e.printStackTrace();
}
- Base.getPlatform().saveLanguage(language);
+ Platform.saveLanguage(language);
}
/** Singleton constructor */
static public Language init() {
if (instance == null) {
- synchronized(Language.class) {
+ synchronized (Language.class) {
if (instance == null) {
instance = new Language();
}
@@ -169,35 +170,58 @@ static public Language init() {
}
- /** Get translation from bundles. */
- static public String text(String text) {
-// ResourceBundle bundle = init().bundle;
+ static private String get(String key) {
LanguageBundle bundle = init().bundle;
try {
- return bundle.getString(text);
- } catch (MissingResourceException e) {
- return text;
- }
+ String value = bundle.getString(key);
+ if (value != null) {
+ return value;
+ }
+ } catch (MissingResourceException e) { }
+
+ return null;
}
- static public String interpolate(String text, Object... arguments) {
-// return String.format(init().bundle.getString(text), arguments);
- return String.format(init().bundle.getString(text), arguments);
+ /** Get translation from bundles. */
+ static public String text(String key) {
+ String value = get(key);
+ if (value == null) {
+ // MissingResourceException and null values
+ return key;
+ }
+ return value;
}
- static public String pluralize(String text, int count) {
-// ResourceBundle bundle = init().bundle;
- LanguageBundle bundle = init().bundle;
+ static public String interpolate(String key, Object... arguments) {
+ String value = get(key);
+ if (value == null) {
+ return key;
+ }
+// System.out.println(" interp for " + key + " is " + String.format(value, arguments));
+ return String.format(value, arguments);
+ }
+
- String fmt = text + ".%s";
- String key = String.format(fmt, count);
- if (bundle.containsKey(key)) {
- return interpolate(key, count);
+ static public String pluralize(String key, int count) {
+ // First check if the bundle contains an entry for this specific count
+ String customKey = key + "." + count;
+ String value = get(customKey);
+ if (value != null) {
+ return String.format(value, count);
}
- return interpolate(String.format(fmt, "n"), count);
+ // Use the general 'n' version for n items
+ return interpolate(key + ".n", count);
+ }
+
+
+ /**
+ * @param which either yes, no, cancel, ok, or browse
+ */
+ static public String getPrompt(String which) {
+ return Language.text("prompt." + which);
}
@@ -216,6 +240,18 @@ static public String getLanguage() {
}
+ /**
+ * Is this a CJK language where Input Method support is suggested/required?
+ * @return true if the user is running in Japanese, Korean, or Chinese
+ */
+ static public boolean useInputMethod() {
+ final String language = getLanguage();
+ return (language.equals("ja") ||
+ language.equals("ko") ||
+ language.equals("zh"));
+ }
+
+
// /** Set new language (called by Preferences) */
// static public void setLanguage(String language) {
// this.language = language;
@@ -273,6 +309,8 @@ static class LanguageBundle {
LanguageBundle(String language) throws IOException {
table = new HashMap();
+ // Check to see if the user is working on localization,
+ // and has their own .properties files in their sketchbook.
String baseFilename = "languages/PDE.properties";
String langFilename = "languages/PDE_" + language + ".properties";
@@ -294,6 +332,9 @@ static class LanguageBundle {
void read(File additions) {
String[] lines = PApplet.loadStrings(additions);
+ if (lines == null) {
+ throw new NullPointerException("File not found:\n" + additions.getAbsolutePath());
+ }
//for (String line : lines) {
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
diff --git a/app/src/processing/app/Library.java b/app/src/processing/app/Library.java
index ccfb312784..34bc6360e1 100644
--- a/app/src/processing/app/Library.java
+++ b/app/src/processing/app/Library.java
@@ -5,6 +5,8 @@
import processing.app.contrib.*;
import processing.core.*;
+import processing.data.StringDict;
+import processing.data.StringList;
public class Library extends LocalContribution {
@@ -23,10 +25,10 @@ public class Library extends LocalContribution {
protected String group;
/** Packages provided by this library. */
- String[] packageList;
+ StringList packageList;
/** Per-platform exports for this library. */
- HashMap exportList;
+ HashMap exportList;
/** Applet exports (cross-platform by definition). */
String[] appletExportList;
@@ -68,6 +70,8 @@ public boolean accept(File dir, String name) {
if (name.equals("linux")) return false;
if (name.equals("linux32")) return false;
if (name.equals("linux64")) return false;
+ if (name.equals("linux-armv6hf")) return false;
+ if (name.equals("linux-arm64")) return false;
if (name.equals("android")) return false;
}
return true;
@@ -112,11 +116,19 @@ private Library(File folder, String groupName) {
examplesFolder = new File(folder, "examples");
referenceFile = new File(folder, "reference/index.html");
+ handle();
+ }
+
+
+ /**
+ * Handles all the Java-specific parsing for library handling.
+ */
+ protected void handle() {
File exportSettings = new File(libraryFolder, "export.txt");
- Map exportTable = exportSettings.exists() ?
- Base.readSettings(exportSettings) : new HashMap();
+ StringDict exportTable = exportSettings.exists() ?
+ Util.readSettings(exportSettings) : new StringDict();
- exportList = new HashMap();
+ exportList = new HashMap<>();
// get the list of files just in the library root
String[] baseList = libraryFolder.list(standardFilter);
@@ -139,7 +151,7 @@ private Library(File folder, String groupName) {
// for the host platform, need to figure out what's available
File nativeLibraryFolder = libraryFolder;
- String hostPlatform = Base.getPlatformName();
+ String hostPlatform = Platform.getName();
// System.out.println("1 native lib folder now " + nativeLibraryFolder);
// see if there's a 'windows', 'macosx', or 'linux' folder
File hostLibrary = new File(libraryFolder, hostPlatform);
@@ -149,11 +161,26 @@ private Library(File folder, String groupName) {
// System.out.println("2 native lib folder now " + nativeLibraryFolder);
// check for bit-specific version, e.g. on windows, check if there
// is a window32 or windows64 folder (on windows)
- hostLibrary = new File(libraryFolder, hostPlatform + Base.getNativeBits());
+ hostLibrary =
+ new File(libraryFolder, hostPlatform + Platform.getNativeBits());
if (hostLibrary.exists()) {
nativeLibraryFolder = hostLibrary;
}
// System.out.println("3 native lib folder now " + nativeLibraryFolder);
+
+ if (hostPlatform.equals("linux") && System.getProperty("os.arch").equals("arm")) {
+ hostLibrary = new File(libraryFolder, "linux-armv6hf");
+ if (hostLibrary.exists()) {
+ nativeLibraryFolder = hostLibrary;
+ }
+ }
+ if (hostPlatform.equals("linux") && System.getProperty("os.arch").equals("aarch64")) {
+ hostLibrary = new File(libraryFolder, "linux-arm64");
+ if (hostLibrary.exists()) {
+ nativeLibraryFolder = hostLibrary;
+ }
+ }
+
// save that folder for later use
nativeLibraryPath = nativeLibraryFolder.getAbsolutePath();
@@ -162,6 +189,8 @@ private Library(File folder, String groupName) {
String platformName = platformNames[i];
String platformName32 = platformName + "32";
String platformName64 = platformName + "64";
+ String platformNameArmv6hf = platformName + "-armv6hf";
+ String platformNameArm64 = platformName + "-arm64";
// First check for things like 'application.macosx=' or 'application.windows32' in the export.txt file.
// These will override anything in the platform-specific subfolders.
@@ -171,6 +200,10 @@ private Library(File folder, String groupName) {
String[] platformList32 = platform32 == null ? null : PApplet.splitTokens(platform32, ", ");
String platform64 = exportTable.get("application." + platformName + "64");
String[] platformList64 = platform64 == null ? null : PApplet.splitTokens(platform64, ", ");
+ String platformArmv6hf = exportTable.get("application." + platformName + "-armv6hf");
+ String[] platformListArmv6hf = platformArmv6hf == null ? null : PApplet.splitTokens(platformArmv6hf, ", ");
+ String platformArm64 = exportTable.get("application." + platformName + "-arm64");
+ String[] platformListArm64 = platformArm64 == null ? null : PApplet.splitTokens(platformArm64, ", ");
// If nothing specified in the export.txt entries, look for the platform-specific folders.
if (platformAll == null) {
@@ -182,14 +215,20 @@ private Library(File folder, String groupName) {
if (platform64 == null) {
platformList64 = listPlatformEntries(libraryFolder, platformName64, baseList);
}
+ if (platformListArmv6hf == null) {
+ platformListArmv6hf = listPlatformEntries(libraryFolder, platformNameArmv6hf, baseList);
+ }
+ if (platformListArm64 == null) {
+ platformListArm64 = listPlatformEntries(libraryFolder, platformNameArm64, baseList);
+ }
- if (platformList32 != null || platformList64 != null) {
+ if (platformList32 != null || platformList64 != null || platformListArmv6hf != null || platformListArm64 != null) {
multipleArch[i] = true;
}
// if there aren't any relevant imports specified or in their own folders,
// then use the baseList (root of the library folder) as the default.
- if (platformList == null && platformList32 == null && platformList64 == null) {
+ if (platformList == null && platformList32 == null && platformList64 == null && platformListArmv6hf == null && platformListArm64 == null) {
exportList.put(platformName, baseList);
} else {
@@ -204,6 +243,12 @@ private Library(File folder, String groupName) {
if (platformList64 != null) {
exportList.put(platformName64, platformList64);
}
+ if (platformListArmv6hf != null) {
+ exportList.put(platformNameArmv6hf, platformListArmv6hf);
+ }
+ if (platformListArm64 != null) {
+ exportList.put(platformNameArm64, platformListArm64);
+ }
}
}
// for (String p : exportList.keySet()) {
@@ -212,7 +257,7 @@ private Library(File folder, String groupName) {
// }
// get the path for all .jar files in this code folder
- packageList = Base.packageListFromClassPath(getClassPath());
+ packageList = Util.packageListFromClassPath(getClassPath());
}
@@ -237,7 +282,7 @@ static String[] listPlatformEntries(File libraryFolder, String folderName, Strin
}
- static protected HashMap packageWarningMap = new HashMap();
+ static protected HashMap packageWarningMap = new HashMap<>();
/**
* Add the packages provided by this library to the master list that maps
@@ -245,15 +290,15 @@ static String[] listPlatformEntries(File libraryFolder, String folderName, Strin
* @param importToLibraryTable mapping from package names to Library objects
*/
// public void addPackageList(HashMap importToLibraryTable) {
- public void addPackageList(HashMap> importToLibraryTable) {
+ public void addPackageList(Map> importToLibraryTable) {
// PApplet.println(packages);
for (String pkg : packageList) {
// pw.println(pkg + "\t" + libraryFolder.getAbsolutePath());
// PApplet.println(pkg + "\t" + getName());
// Library library = importToLibraryTable.get(pkg);
- ArrayList libraries = importToLibraryTable.get(pkg);
+ List libraries = importToLibraryTable.get(pkg);
if (libraries == null) {
- libraries = new ArrayList();
+ libraries = new ArrayList<>();
importToLibraryTable.put(pkg, libraries);
} else {
if (Base.DEBUG) {
@@ -317,10 +362,13 @@ public String getClassPath() {
cp.append(File.pathSeparatorChar);
cp.append(new File(libraryFolder, jar).getAbsolutePath());
}
- jarHeads = new File(nativeLibraryPath).list(jarFilter);
- for (String jar : jarHeads) {
- cp.append(File.pathSeparatorChar);
- cp.append(new File(nativeLibraryPath, jar).getAbsolutePath());
+ File nativeLibraryFolder = new File(nativeLibraryPath);
+ if (!libraryFolder.equals(nativeLibraryFolder)) {
+ jarHeads = new File(nativeLibraryPath).list(jarFilter);
+ for (String jar : jarHeads) {
+ cp.append(File.pathSeparatorChar);
+ cp.append(new File(nativeLibraryPath, jar).getAbsolutePath());
+ }
}
//cp.setLength(cp.length() - 1); // remove the last separator
return cp.toString();
@@ -358,8 +406,8 @@ public File[] getAppletExports() {
}
- public File[] getApplicationExports(int platform, int bits) {
- String[] list = getApplicationExportList(platform, bits);
+ public File[] getApplicationExports(int platform, String variant) {
+ String[] list = getApplicationExportList(platform, variant);
return wrapFiles(list);
}
@@ -369,14 +417,20 @@ public File[] getApplicationExports(int platform, int bits) {
* If no 32 or 64-bit version of the exports exists, it returns the version
* that doesn't specify bit depth.
*/
- public String[] getApplicationExportList(int platform, int bits) {
+ public String[] getApplicationExportList(int platform, String variant) {
String platformName = PConstants.platformNames[platform];
- if (bits == 32) {
+ if (variant.equals("32")) {
String[] pieces = exportList.get(platformName + "32");
if (pieces != null) return pieces;
- } else if (bits == 64) {
+ } else if (variant.equals("64")) {
String[] pieces = exportList.get(platformName + "64");
if (pieces != null) return pieces;
+ } else if (variant.equals("armv6hf")) {
+ String[] pieces = exportList.get(platformName + "-armv6hf");
+ if (pieces != null) return pieces;
+ } else if (variant.equals("arm64")) {
+ String[] pieces = exportList.get(platformName + "-arm64");
+ if (pieces != null) return pieces;
}
return exportList.get(platformName);
}
@@ -397,63 +451,46 @@ public boolean hasMultipleArch(int platform) {
}
- public boolean supportsArch(int platform, int bits) {
+ public boolean supportsArch(int platform, String variant) {
// If this is a universal library, or has no natives, then we're good.
if (multipleArch[platform] == false) {
return true;
}
- return getApplicationExportList(platform, bits) != null;
+ return getApplicationExportList(platform, variant) != null;
}
-// static boolean hasMultipleArch(String platformName, ArrayList libraries) {
-// int platform = Base.getPlatformIndex(platformName);
- static public boolean hasMultipleArch(int platform, ArrayList libraries) {
- for (Library library : libraries) {
- if (library.hasMultipleArch(platform)) {
- return true;
- }
- }
- return false;
+ static public boolean hasMultipleArch(int platform, List libraries) {
+ return libraries.stream().anyMatch(library -> library.hasMultipleArch(platform));
}
- // for sorting
-// public int compareTo(Object o) {
-// return prettyName.compareTo(((LibraryFolder) o).prettyName);
-// }}
-
-
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static protected FilenameFilter junkFolderFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
- // skip .DS_Store files, .svn folders, etc
+ // skip .DS_Store files, .svn and .git folders, etc
if (name.charAt(0) == '.') return false;
- if (name.equals("CVS")) return false;
- return (new File(dir, name).isDirectory());
+ if (name.equals("CVS")) return false; // old skool
+ return new File(dir, name).isDirectory();
}
};
- static public ArrayList discover(File folder) {
- ArrayList libraries = new ArrayList();
- discover(folder, libraries);
- return libraries;
- }
-
+ static public List discover(File folder) {
+ List libraries = new ArrayList<>();
+ String[] folderNames = folder.list(junkFolderFilter);
- static public void discover(File folder, ArrayList libraries) {
- String[] list = folder.list(junkFolderFilter);
-
- // if a bad folder or something like that, this might come back null
- if (list != null) {
+ // if a bad folder or unreadable, folderNames might be null
+ if (folderNames != null) {
// 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);
+ Arrays.sort(folderNames, String.CASE_INSENSITIVE_ORDER);
- for (String potentialName : list) {
+ // TODO some weirdness because ContributionType.LIBRARY.isCandidate()
+ // handles some, but not all, of this [fry 200116]
+ for (String potentialName : folderNames) {
File baseFolder = new File(folder, potentialName);
File libraryFolder = new File(baseFolder, "library");
File libraryJar = new File(libraryFolder, potentialName + ".jar");
@@ -465,50 +502,48 @@ static public void discover(File folder, ArrayList libraries) {
libraries.add(baseFolder);
} else {
- 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);
+ final 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)";
+ Messages.showMessage("Ignoring bad library name", mess);
continue;
}
}
}
}
- }
-
-
- static protected ArrayList list(File folder) {
- ArrayList libraries = new ArrayList();
- list(folder, libraries);
return libraries;
}
- static protected void list(File folder, ArrayList libraries) {
- ArrayList librariesFolders = new ArrayList();
- discover(folder, librariesFolders);
+ static public List list(File folder) {
+ List libraries = new ArrayList<>();
+ List librariesFolders = new ArrayList<>();
+ librariesFolders.addAll(discover(folder));
for (File baseFolder : librariesFolders) {
libraries.add(new Library(baseFolder));
}
- String[] list = folder.list(junkFolderFilter);
- if (list != null) {
- for (String subfolderName : list) {
+ /*
+ // Support libraries inside of one level of subfolders? I believe this was
+ // the compromise for supporting library groups, but probably a bad idea
+ // because it's not compatible with the Manager.
+ String[] folderNames = folder.list(junkFolderFilter);
+ if (folderNames != null) {
+ for (String subfolderName : folderNames) {
File subfolder = new File(folder, subfolderName);
if (!librariesFolders.contains(subfolder)) {
- ArrayList discoveredLibFolders = new ArrayList();
- discover(subfolder, discoveredLibFolders);
-
+ List discoveredLibFolders = discover(subfolder);
for (File discoveredFolder : discoveredLibFolders) {
libraries.add(new Library(discoveredFolder, subfolderName));
}
}
}
}
+ */
+ return libraries;
}
diff --git a/app/src/processing/app/Messages.java b/app/src/processing/app/Messages.java
new file mode 100644
index 0000000000..d8c78d43f1
--- /dev/null
+++ b/app/src/processing/app/Messages.java
@@ -0,0 +1,365 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2015 The Processing Foundation
+
+ 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.Frame;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+
+import processing.app.ui.Editor;
+
+public class Messages {
+ /**
+ * "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 (Base.isCommandLine()) {
+ System.out.println(title + ": " + message);
+
+ } else {
+ JOptionPane.showMessageDialog(new Frame(), message, title,
+ JOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+
+
+ /**
+ * Non-fatal error message.
+ */
+ static public void showWarning(String title, String message) {
+ showWarning(title, message, null);
+ }
+
+ /**
+ * Non-fatal error message with optional stack trace side dish.
+ */
+ static public void showWarning(String title, String message, Throwable e) {
+ if (title == null) title = "Warning";
+
+ if (Base.isCommandLine()) {
+ System.out.println(title + ": " + message);
+
+ } else {
+ JOptionPane.showMessageDialog(new Frame(), message, title,
+ JOptionPane.WARNING_MESSAGE);
+ }
+ 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";
+
+ final String message = primary + "\n" + secondary;
+ if (Base.isCommandLine()) {
+ System.out.println(title + ": " + message);
+
+ } else {
+// JOptionPane.showMessageDialog(new Frame(), message,
+// title, JOptionPane.WARNING_MESSAGE);
+ if (!Platform.isMacOS()) {
+ JOptionPane.showMessageDialog(new JFrame(),
+ "" +
+ "" + primary + "" +
+ "
" + secondary, title,
+ JOptionPane.WARNING_MESSAGE);
+ } else {
+ // Pane formatting adapted from the Quaqua guide
+ // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
+ JOptionPane pane =
+ new JOptionPane(" " +
+ " " +
+ "" + 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;
+// }
+ }
+ }
+ 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
(even if pre) otherwise Java
+ // treats it as a closed tag and reverts to plain formatting.
+ message = ("" + message +
+ "
" +
+ sw + "").replaceAll("\n", "
");
+
+ JOptionPane.showMessageDialog(new Frame(), message, title,
+ fatal ?
+ JOptionPane.ERROR_MESSAGE :
+ JOptionPane.WARNING_MESSAGE);
+
+ if (fatal) {
+ System.exit(1);
+ }
+ }
+ }
+
+
+ // ...................................................................
+
+
+
+ // incomplete
+ static public int showYesNoCancelQuestion(Editor editor, String title,
+ String primary, String secondary) {
+ if (!Platform.isMacOS()) {
+ int result =
+ JOptionPane.showConfirmDialog(null, primary + "\n" + secondary, title,
+ JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ return result;
+// if (result == JOptionPane.YES_OPTION) {
+//
+// } else if (result == JOptionPane.NO_OPTION) {
+// return true; // ok to continue
+//
+// } else if (result == JOptionPane.CANCEL_OPTION) {
+// return false;
+//
+// } else {
+// throw new IllegalStateException();
+// }
+
+ } else {
+ // Pane formatting adapted from the Quaqua guide
+ // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
+ JOptionPane pane =
+ new JOptionPane(" " +
+ " " +
+ "" + Language.text("save.title") + "" +
+ "" + 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, title,
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ } else {
+ int result = showCustomQuestion(editor, title, primary, secondary,
+ 0, "Yes", "No");
+ if (result == 0) {
+ return JOptionPane.YES_OPTION;
+ } else if (result == 1) {
+ return JOptionPane.NO_OPTION;
+ } else {
+ return JOptionPane.CLOSED_OPTION;
+ }
+ }
+ }
+
+
+ /**
+ * @param highlight A valid array index for options[] that specifies the
+ * default (i.e. safe) choice.
+ * @return The (zero-based) index of the selected value, -1 otherwise.
+ */
+ static public int showCustomQuestion(Frame editor, String title,
+ String primary, String secondary,
+ int highlight, String... options) {
+ Object result;
+ if (!Platform.isMacOS()) {
+ return JOptionPane.showOptionDialog(editor,
+ "" + primary + "
" + secondary, title,
+ JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null,
+ options, options[highlight]);
+ } else {
+ // Pane formatting adapted from the Quaqua guide
+ // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
+ JOptionPane pane =
+ new JOptionPane(" " +
+ " " +
+ "" + 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 d3c3f2036f..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,16 +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.plaf.basic.BasicTreeUI;
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;
@@ -49,24 +53,22 @@ public abstract class Mode {
protected File folder;
protected TokenMarker tokenMarker;
- protected HashMap keywordToReference =
- new HashMap();
+ protected Map keywordToReference = new HashMap<>();
protected Settings theme;
// protected Formatter formatter;
// protected Tool formatter;
// maps imported packages to their library folder
-// protected HashMap importToLibraryTable;
- protected HashMap> importToLibraryTable;
+ protected Map> importToLibraryTable;
// these menus are shared so that they needn't be rebuilt for all windows
// each time a sketch is created, renamed, or moved.
protected JMenu examplesMenu; // this is for the menubar, not the toolbar
protected JMenu importMenu;
-// protected JTree examplesTree;
- protected JFrame examplesFrame;
+ protected ExamplesFrame examplesFrame;
+ protected SketchbookFrame sketchbookFrame;
// popup menu used for the toolbar
protected JMenu toolbarMenu;
@@ -75,10 +77,10 @@ public abstract class Mode {
protected File librariesFolder;
protected File referenceFolder;
- protected File examplesContribFolder;
+// protected File examplesContribFolder;
- public ArrayList coreLibraries;
- public ArrayList contribLibraries;
+ public List coreLibraries;
+ public List contribLibraries;
/** Library folder for core. (Used for OpenGL in particular.) */
protected Library coreLibrary;
@@ -108,9 +110,6 @@ public Mode(Base base, File folder) {
librariesFolder = new File(folder, "libraries");
referenceFolder = new File(folder, "reference");
- // Get path to the contributed examples compatible with this mode
- examplesContribFolder = Base.getSketchbookExamplesFolder();
-
// rebuildToolbarMenu();
rebuildLibraryList();
// rebuildExamplesMenu();
@@ -120,8 +119,8 @@ public Mode(Base base, File folder) {
loadKeywords(file);
}
} catch (IOException e) {
- Base.showWarning("Problem loading keywords",
- "Could not load keywords file for " + getTitle() + " mode.", e);
+ Messages.showWarning("Problem loading keywords",
+ "Could not load keywords file for " + getTitle() + " mode.", e);
}
}
@@ -166,7 +165,13 @@ protected void loadKeywords(File keywordFile,
if (htmlFilename.endsWith("_")) {
keyword += "_";
}
- keywordToReference.put(keyword, htmlFilename);
+ // Allow the bare size() command to override the lookup
+ // for StringList.size() and others, but not vice-versa.
+ // https://github.com/processing/processing/issues/4224
+ boolean seen = keywordToReference.containsKey(keyword);
+ if (!seen || (seen && keyword.equals(htmlFilename))) {
+ keywordToReference.put(keyword, htmlFilename);
+ }
}
}
}
@@ -194,7 +199,7 @@ public ClassLoader getClassLoader() {
public void setupGUI() {
try {
// First load the default theme data for the whole PDE.
- theme = new Settings(Base.getContentFile("lib/theme.txt"));
+ theme = new Settings(Platform.getContentFile("lib/theme.txt"));
// The mode-specific theme.txt file should only contain additions,
// and in extremely rare cases, it might override entries from the
@@ -206,57 +211,96 @@ public void setupGUI() {
theme.load(modeTheme);
}
+ // Against my better judgment, adding the ability to override themes
+ // https://github.com/processing/processing/issues/5445
+ File sketchbookTheme =
+ new File(Base.getSketchbookFolder(), "theme.txt");
+ if (sketchbookTheme.exists()) {
+ theme.load(sketchbookTheme);
+ }
+
// other things that have to be set explicitly for the defaults
theme.setColor("run.window.bgcolor", SystemColor.control);
-// loadBackground();
-
} catch (IOException e) {
- Base.showError("Problem loading theme.txt",
- "Could not load theme.txt, please re-install Processing", e);
+ Messages.showError("Problem loading theme.txt",
+ "Could not load theme.txt, please re-install Processing", e);
}
}
- /*
- protected void loadBackground() {
- String suffix = Toolkit.highResDisplay() ? "-2x.png" : ".png";
- backgroundImage = loadImage("theme/mode" + suffix);
- if (backgroundImage == null) {
- // If the image wasn't available, try the other resolution.
- // i.e. we don't (currently) have low-res versions of mode.png,
- // so this will grab the 2x version and scale it when drawn.
- suffix = !Toolkit.highResDisplay() ? "-2x.png" : ".png";
- backgroundImage = loadImage("theme/mode" + suffix);
- }
+ public File getContentFile(String path) {
+ return new File(folder, path);
}
- public void drawBackground(Graphics g, int offset) {
- if (backgroundImage != null) {
- if (!Toolkit.highResDisplay()) {
- // Image might be downsampled from a 2x version. If so, we need nice
- // anti-aliasing for the very geometric images we're using.
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
- RenderingHints.VALUE_ANTIALIAS_ON);
- g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ public InputStream getContentStream(String path) throws FileNotFoundException {
+ return new FileInputStream(getContentFile(path));
+ }
+
+
+ /**
+ * Add files to a folder to create an empty sketch. This can be overridden
+ * to add template files to a sketch for Modes that need them.
+ *
+ * @param sketchFolder the directory where the new sketch should live
+ * @param sketchName the name of the new sketch
+ * @return the main file for the sketch to be opened via handleOpen()
+ * @throws IOException if the file somehow already exists
+ */
+ public File addTemplateFiles(File sketchFolder,
+ String sketchName) throws IOException {
+ // Make an empty .pde file
+ File newbieFile =
+ new File(sketchFolder, sketchName + "." + getDefaultExtension());
+
+ try {
+ // First see if the user has overridden the default template
+ File templateFolder = checkSketchbookTemplate();
+
+ // Next see if the Mode has its own template
+ if (templateFolder == null) {
+ templateFolder = getTemplateFolder();
}
- g.drawImage(backgroundImage, 0, -offset,
- BACKGROUND_WIDTH, BACKGROUND_HEIGHT, null);
+ if (templateFolder.exists()) {
+ Util.copyDir(templateFolder, sketchFolder);
+ File templateFile =
+ new File(sketchFolder, "sketch." + getDefaultExtension());
+ if (!templateFile.renameTo(newbieFile)) {
+ System.err.println("Error while assigning the sketch template.");
+ }
+ } else {
+ if (!newbieFile.createNewFile()) {
+ System.err.println(newbieFile + " already exists.");
+ }
+ }
+ } catch (Exception e) {
+ // just spew out this error and try to recover below
+ e.printStackTrace();
}
+ return newbieFile;
}
- */
- public File getContentFile(String path) {
- return new File(folder, path);
+ /**
+ * See if the user has their own template for this Mode. If the default
+ * extension is "pde", this will look for a file called sketch.pde to use
+ * as the template for all sketches.
+ */
+ protected File checkSketchbookTemplate() {
+ File user = new File(Base.getSketchbookTemplatesFolder(), getTitle());
+ if (user.exists()) {
+ File template = new File(user, "sketch." + getDefaultExtension());
+ if (template.exists() && template.canRead()) {
+ return user;
+ }
+ }
+ return null;
}
- public InputStream getContentStream(String path) throws FileNotFoundException {
- return new FileInputStream(getContentFile(path));
+ public File getTemplateFolder() {
+ return getContentFile("template");
}
@@ -283,8 +327,8 @@ public String getIdentifier() {
/**
* Create a new editor associated with this mode.
*/
- abstract public Editor createEditor(Base base, String path, EditorState state);
- //abstract public Editor createEditor(Base base, String path, int[] location);
+ abstract public Editor createEditor(Base base, String path,
+ EditorState state) throws EditorException;
/**
@@ -314,20 +358,42 @@ public File getReferenceFolder() {
public void rebuildLibraryList() {
//new Exception("Rebuilding library list").printStackTrace(System.out);
// reset the table mapping imports to libraries
- importToLibraryTable = new HashMap>();
+ Map> newTable = new HashMap<>();
- coreLibraries = Library.list(librariesFolder);
- for (Library lib : coreLibraries) {
- lib.addPackageList(importToLibraryTable);
+ Library core = getCoreLibrary();
+ if (core != null) {
+ core.addPackageList(newTable);
}
+ coreLibraries = Library.list(librariesFolder);
File contribLibrariesFolder = Base.getSketchbookLibrariesFolder();
- if (contribLibrariesFolder != null) {
- contribLibraries = Library.list(contribLibrariesFolder);
- for (Library lib : contribLibraries) {
- lib.addPackageList(importToLibraryTable);
+ contribLibraries = Library.list(contribLibrariesFolder);
+
+ // Check to see if video and sound are installed and move them
+ // from the contributed list to the core list.
+ List foundationLibraries = new ArrayList<>();
+ for (Library lib : contribLibraries) {
+ if (lib.isFoundation()) {
+ foundationLibraries.add(lib);
}
}
+ coreLibraries.addAll(foundationLibraries);
+ contribLibraries.removeAll(foundationLibraries);
+
+ for (Library lib : coreLibraries) {
+ lib.addPackageList(newTable);
+ }
+
+ for (Library lib : contribLibraries) {
+ lib.addPackageList(newTable);
+ }
+
+ // Make this Map thread-safe
+ importToLibraryTable = Collections.unmodifiableMap(newTable);
+
+ if (base != null) {
+ base.getEditors().forEach(Editor::librariesChanged);
+ }
}
@@ -337,7 +403,7 @@ public Library getCoreLibrary() {
public Library getLibrary(String pkgName) throws SketchException {
- ArrayList libraries = importToLibraryTable.get(pkgName);
+ List libraries = importToLibraryTable.get(pkgName);
if (libraries == null) {
return null;
@@ -352,7 +418,7 @@ public Library getLibrary(String pkgName) throws SketchException {
secondary += "" + library.getName() + " (" + location + ")
";
}
secondary += "Extra libraries need to be removed before this sketch can be used.";
- Base.showWarningTiered("Duplicate Library Problem", primary, secondary, null);
+ Messages.showWarningTiered("Duplicate Library Problem", primary, secondary, null);
throw new SketchException("Duplicate libraries found for " + pkgName + ".");
} else {
@@ -380,13 +446,13 @@ public void insertToolbarRecentMenu() {
if (toolbarMenu == null) {
rebuildToolbarMenu();
} else {
- toolbarMenu.insert(base.getToolbarRecentMenu(), 1);
+ toolbarMenu.insert(Recent.getToolbarMenu(), 1);
}
}
public void removeToolbarRecentMenu() {
- toolbarMenu.remove(base.getToolbarRecentMenu());
+ toolbarMenu.remove(Recent.getToolbarMenu());
}
@@ -421,7 +487,7 @@ public void actionPerformed(ActionEvent e) {
item = new JMenuItem(Language.text("examples.add_examples"));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
- base.handleOpenExampleManager();
+ ContributionManager.openExamples();
}
});
toolbarMenu.add(item);
@@ -497,7 +563,7 @@ public void rebuildImportMenu() { //JMenu importMenu) {
JMenuItem addLib = new JMenuItem(Language.text("menu.library.add_library"));
addLib.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
- base.handleOpenLibraryManager();
+ ContributionManager.openLibraries();
}
});
importMenu.add(addLib);
@@ -539,7 +605,7 @@ public void actionPerformed(ActionEvent e) {
contrib.setEnabled(false);
importMenu.add(contrib);
- HashMap subfolders = new HashMap();
+ HashMap subfolders = new HashMap<>();
for (Library library : contribLibraries) {
JMenuItem item = new JMenuItem(library.getName());
@@ -565,74 +631,16 @@ public void actionPerformed(ActionEvent e) {
}
- /*
- public JMenu getExamplesMenu() {
- if (examplesMenu == null) {
- rebuildExamplesMenu();
- }
- return examplesMenu;
- }
-
-
- public void rebuildExamplesMenu() {
- if (examplesMenu == null) {
- examplesMenu = new JMenu("Examples");
- }
- rebuildExamplesMenu(examplesMenu, false);
- }
-
-
- public void rebuildExamplesMenu(JMenu menu, boolean replace) {
- try {
- // break down the examples folder for examples
- File[] subfolders = getExampleCategoryFolders();
-
- for (File sub : subfolders) {
- Base.addDisabledItem(menu, sub.getName());
-// JMenuItem categoryItem = new JMenuItem(sub.getName());
-// categoryItem.setEnabled(false);
-// menu.add(categoryItem);
- base.addSketches(menu, sub, replace);
- menu.addSeparator();
- }
-
-// if (coreLibraries == null) {
-// rebuildLibraryList();
-// }
-
- // get library examples
- Base.addDisabledItem(menu, "Libraries");
- for (Library lib : coreLibraries) {
- if (lib.hasExamples()) {
- JMenu libMenu = new JMenu(lib.getName());
- base.addSketches(libMenu, lib.getExamplesFolder(), replace);
- menu.add(libMenu);
- }
- }
-
- // get contrib library examples
- boolean any = false;
- for (Library lib : contribLibraries) {
- if (lib.hasExamples()) {
- any = true;
- }
- }
- if (any) {
- menu.addSeparator();
- Base.addDisabledItem(menu, "Contributed");
- for (Library lib : contribLibraries) {
- if (lib.hasExamples()) {
- JMenu libMenu = new JMenu(lib.getName());
- base.addSketches(libMenu, lib.getExamplesFolder(), replace);
- menu.add(libMenu);
- }
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
+ /**
+ * Require examples to explicitly state that they're compatible with this
+ * Mode before they're included. Helpful for Modes like p5js or Python
+ * where the .java examples cannot be used.
+ * @since 3.2
+ * @return true if an examples package must list this Mode's identifier
+ */
+ public boolean requireExampleCompatibility() {
+ return false;
}
- */
/**
@@ -648,62 +656,14 @@ public boolean accept(File dir, String name) {
}
- public DefaultMutableTreeNode buildExamplesTree() {
- DefaultMutableTreeNode root = new DefaultMutableTreeNode("Examples");
-
- try {
-
- File[] examples = getExampleCategoryFolders();
-
- for (File subFolder : examples) {
- DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(subFolder.getName());
- if (base.addSketches(subNode, subFolder)) {
- root.add(subNode);
- }
- }
-
- DefaultMutableTreeNode foundationLibraries =
- new DefaultMutableTreeNode(Language.text("examples.core_libraries"));
-
- // Get examples for core libraries
- for (Library lib : coreLibraries) {
- if (lib.hasExamples()) {
- DefaultMutableTreeNode libNode = new DefaultMutableTreeNode(lib.getName());
- if (base.addSketches(libNode, lib.getExamplesFolder()))
- foundationLibraries.add(libNode);
- }
- }
- if(foundationLibraries.getChildCount() > 0) {
- root.add(foundationLibraries);
- }
-
- // Get examples for third party libraries
- DefaultMutableTreeNode contributed = new
- DefaultMutableTreeNode(Language.text("examples.libraries"));
- for (Library lib : contribLibraries) {
- if (lib.hasExamples()) {
- DefaultMutableTreeNode libNode = new DefaultMutableTreeNode(lib.getName());
- base.addSketches(libNode, lib.getExamplesFolder());
- contributed.add(libNode);
- }
- }
- if(contributed.getChildCount() > 0){
- root.add(contributed);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- return root;
- }
-
- public void resetExamples() {
+ public void rebuildExamplesFrame() {
if (examplesFrame != null) {
boolean visible = examplesFrame.isVisible();
Rectangle bounds = null;
if (visible) {
bounds = examplesFrame.getBounds();
examplesFrame.setVisible(false);
+ examplesFrame.dispose();
}
examplesFrame = null;
if (visible) {
@@ -714,436 +674,59 @@ public void resetExamples() {
}
- /**
- * Function to give a JTree a pretty alternating gray-white colouring for
- * its rows.
- *
- * @param tree
- */
- private void colourizeTreeRows(JTree tree) {
- // Code in this function adapted from:
- // http://mateuszstankiewicz.eu/?p=263
- tree.setCellRenderer(new DefaultTreeCellRenderer() {
-
- @Override
- public Component getTreeCellRendererComponent(JTree tree, Object value,
- boolean sel,
- boolean expanded,
- boolean leaf, int row,
- boolean hasFocus) {
- JComponent c = (JComponent) super
- .getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row,
- hasFocus);
-
- if (!tree.isRowSelected(row)) {
- if (row % 2 == 0) {
-
- // Need to set this, else the gray from the odd
- // rows colours this gray as well.
- c.setBackground(new Color(255, 255, 255));
-
- setBackgroundSelectionColor(new Color(0, 0, 255));
- setTextSelectionColor(Color.WHITE);
- setBorderSelectionColor(new Color(0, 0, 255));
- } else {
-
- // Set background for entire component (including the image).
- // Using transparency messes things up, probably since the
- // transparent colour is not good friends with the images background colour.
- c.setBackground(new Color(240, 240, 240));
-
- // Can't use setBackgroundSelectionColor() directly, since then, the
- // image's background isn't affected.
- // The setUI() doesn't fix the image's background because the
- // transparency likely interferes with its normal background,
- // making its background lighter than the rest.
-// setBackgroundNonSelectionColor(new Color(190, 190, 190));
-
- setBackgroundSelectionColor(new Color(0, 0, 255));
- setTextSelectionColor(Color.WHITE);
- setBorderSelectionColor(new Color(0, 0, 255));
- }
- } else {// Transparent blue if selected
- c.setBackground(new Color(127, 127, 255));
- }
-
- c.setOpaque(true);
- return c;
- }
-
- });
-
- tree.setUI(new BasicTreeUI() {
-
- @Override
- protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets,
- Rectangle bounds, TreePath path, int row,
- boolean isExpanded, boolean hasBeenExpanded,
- boolean isLeaf) {
- Graphics g2 = g.create();
-
- if (!tree.isRowSelected(row)) {
- if (row % 2 == 0) {
- // Need to set this, else the gray from the odd rows
- // affects the even rows too.
- g2.setColor(new Color(255, 255, 255, 128));
- } else {
- // Transparent light-gray
- g2.setColor(new Color(226, 226, 226, 128));
- }
- } else
- // Transparent blue if selected
- g2.setColor(new Color(0, 0, 255, 128));
-
- g2.fillRect(0, bounds.y, tree.getWidth(), bounds.height);
-
- g2.dispose();
-
- super.paintRow(g, clipBounds, insets, bounds, path, row, isExpanded,
- hasBeenExpanded, isLeaf);
- }
- });
- }
-
-
public void showExamplesFrame() {
if (examplesFrame == null) {
- examplesFrame = new JFrame(getTitle() + " " + Language.text("examples"));
- Toolkit.setIcon(examplesFrame);
- Toolkit.registerWindowCloseKeys(examplesFrame.getRootPane(), new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- examplesFrame.setVisible(false);
- }
- });
-
- JPanel examplesPanel = new JPanel();
- examplesPanel.setLayout(new BorderLayout());
- examplesPanel.setBackground(Color.WHITE);
-
- final JPanel openExamplesManagerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
- JButton addExamplesButton = new JButton(Language.text("examples.add_examples"));
- openExamplesManagerPanel.add(addExamplesButton);
- openExamplesManagerPanel.setOpaque(false);
- Border lineBorder = BorderFactory.createMatteBorder(0, 0, 1, 0, Color.BLACK);
- Border paddingBorder = BorderFactory.createEmptyBorder(3, 5, 1, 4);
- openExamplesManagerPanel.setBorder(BorderFactory.createCompoundBorder(lineBorder, paddingBorder));
- openExamplesManagerPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
- openExamplesManagerPanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
- addExamplesButton.addActionListener(new ActionListener() {
- @Override
- public void actionPerformed(ActionEvent e) {
- base.handleOpenExampleManager();
- }
- });
-
- final JTree tree = new JTree(buildExamplesTree());
-
- colourizeTreeRows(tree);
-
- tree.setOpaque(true);
- tree.setAlignmentX(Component.LEFT_ALIGNMENT);
-
- tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
- tree.setShowsRootHandles(true);
- // expand the root
- tree.expandRow(0);
- // now hide the root
- tree.setRootVisible(false);
-
- // After 2.0a7, no longer expanding each of the categories at Casey's
- // request. He felt that the window was too complicated too quickly.
-// for (int row = tree.getRowCount()-1; row >= 0; --row) {
-// tree.expandRow(row);
-// }
-
- tree.addMouseListener(new MouseAdapter() {
- public void mouseClicked(MouseEvent e) {
- if (e.getClickCount() == 2) {
- DefaultMutableTreeNode node =
- (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
-
- int selRow = tree.getRowForLocation(e.getX(), e.getY());
- //TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
- //if (node != null && node.isLeaf() && node.getPath().equals(selPath)) {
- if (node != null && node.isLeaf() && selRow != -1) {
- SketchReference sketch = (SketchReference) node.getUserObject();
- base.handleOpen(sketch.getPath());
- }
- }
- }
- });
- tree.addKeyListener(new KeyAdapter() {
- public void keyPressed(KeyEvent e) {
- if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { // doesn't fire keyTyped()
- examplesFrame.setVisible(false);
- }
- }
- public void keyTyped(KeyEvent e) {
- if (e.getKeyChar() == KeyEvent.VK_ENTER) {
- DefaultMutableTreeNode node =
- (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
- if (node != null && node.isLeaf()) {
- SketchReference sketch = (SketchReference) node.getUserObject();
- base.handleOpen(sketch.getPath());
- }
- }
- }
- });
-
- tree.addTreeExpansionListener(new TreeExpansionListener() {
- @Override
- public void treeExpanded(TreeExpansionEvent event) {
- updateExpanded(tree);
- }
-
- @Override
- public void treeCollapsed(TreeExpansionEvent event) {
- updateExpanded(tree);
- }
- });
-
- tree.setBorder(new EmptyBorder(0, 5, 5, 5));
- if (Base.isMacOS()) {
- tree.setToggleClickCount(2);
- } else {
- tree.setToggleClickCount(1);
- }
-
- JScrollPane treePane = new JScrollPane(tree);
- treePane.setPreferredSize(new Dimension(250, 300));
- treePane.setBorder(new EmptyBorder(2, 0, 0, 0));
- treePane.setOpaque(true);
- treePane.setBackground(Color.WHITE);
- treePane.setAlignmentX(Component.LEFT_ALIGNMENT);
-
- examplesPanel.add(openExamplesManagerPanel,BorderLayout.PAGE_START);
- examplesPanel.add(treePane, BorderLayout.CENTER);
- examplesFrame.getContentPane().add(examplesPanel);
- examplesFrame.pack();
-
- restoreExpanded(tree);
- }
-
- // Space for the editor plus a li'l gap
- int roughWidth = examplesFrame.getWidth() + 20;
- Point p = null;
- // If no window open, or the editor is at the edge of the screen
- if (base.activeEditor == null ||
- (p = base.activeEditor.getLocation()).x < roughWidth) {
- // Center the window on the screen
- examplesFrame.setLocationRelativeTo(null);
- } else {
- // Open the window relative to the editor
- examplesFrame.setLocation(p.x - roughWidth, p.y);
+ examplesFrame = new ExamplesFrame(base, this);
}
- examplesFrame.setVisible(true);
+ examplesFrame.setVisible();
}
- protected void updateExpanded(JTree tree) {
- Enumeration en = tree.getExpandedDescendants(new TreePath(tree.getModel().getRoot()));
- //en.nextElement(); // skip the root "Examples" node
-
- StringBuilder s = new StringBuilder();
- while (en.hasMoreElements()) {
- //System.out.println(en.nextElement());
- TreePath tp = (TreePath) en.nextElement();
- Object[] path = tp.getPath();
- for (Object o : path) {
- DefaultMutableTreeNode p = (DefaultMutableTreeNode) o;
- String name = (String) p.getUserObject();
- //System.out.print(p.getUserObject().getClass().getName() + ":" + p.getUserObject() + " -> ");
- //System.out.print(name + " -> ");
- s.append(name);
- s.append(File.separatorChar);
- }
- //System.out.println();
- s.setCharAt(s.length() - 1, File.pathSeparatorChar);
- }
- s.setLength(s.length() - 1); // nix that last separator
- String pref = "examples." + getClass().getName() + ".visible";
- Preferences.set(pref, s.toString());
- Preferences.save();
-// System.out.println(s);
-// System.out.println();
- }
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- protected void restoreExpanded(JTree tree) {
- String pref = "examples." + getClass().getName() + ".visible";
- String value = Preferences.get(pref);
- if (value != null) {
- String[] paths = PApplet.split(value, File.pathSeparator);
- for (String path : paths) {
-// System.out.println("trying to expand " + path);
- String[] items = PApplet.split(path, File.separator);
- DefaultMutableTreeNode[] nodes = new DefaultMutableTreeNode[items.length];
- expandTree(tree, null, items, nodes, 0);
- }
+ public DefaultMutableTreeNode buildSketchbookTree() {
+ DefaultMutableTreeNode sbNode =
+ new DefaultMutableTreeNode(Language.text("sketchbook.tree"));
+ try {
+ base.addSketches(sbNode, Base.getSketchbookFolder(), false);
+ } catch (IOException e) {
+ e.printStackTrace();
}
+ return sbNode;
}
- void expandTree(JTree tree, Object object, String[] items, DefaultMutableTreeNode[] nodes, int index) {
-// if (object == null) {
-// object = model.getRoot();
-// }
- TreeModel model = tree.getModel();
-
- if (index == 0) {
- nodes[0] = (DefaultMutableTreeNode) model.getRoot();
- expandTree(tree, nodes[0], items, nodes, 1);
-
- } else if (index < items.length) {
-// String item = items[0];
-// TreeModel model = object.getModel();
-// System.out.println(object.getClass().getName());
- DefaultMutableTreeNode node = (DefaultMutableTreeNode) object;
- int count = model.getChildCount(node);
-// System.out.println("child count is " + count);
- for (int i = 0; i < count; i++) {
- DefaultMutableTreeNode child = (DefaultMutableTreeNode) model.getChild(node, i);
- if (items[index].equals(child.getUserObject())) {
- nodes[index] = child;
- expandTree(tree, child, items, nodes, index+1);
- }
+ /** Sketchbook has changed, update it on next viewing. */
+ public void rebuildSketchbookFrame() {
+ if (sketchbookFrame != null) {
+ boolean visible = sketchbookFrame.isVisible();
+ Rectangle bounds = null;
+ if (visible) {
+ bounds = sketchbookFrame.getBounds();
+ sketchbookFrame.setVisible(false);
+ sketchbookFrame.dispose();
+ }
+ sketchbookFrame = null;
+ if (visible) {
+ showSketchbookFrame();
+ sketchbookFrame.setBounds(bounds);
}
- } else { // last one
-// PApplet.println(nodes);
- tree.expandPath(new TreePath(nodes));
- }
- }
-
-
-// void
-
-// protected TreePath findPath(FileItem item) {
-// ArrayList items = new ArrayList();
-//// FileItem which = item.isDirectory() ? item : (FileItem) item.getParent();
-//// FileItem which = item;
-// FileItem which = (FileItem) item.getParent();
-// while (which != null) {
-// items.add(0, which);
-// which = (FileItem) which.getParent();
-// }
-// return new TreePath(items.toArray());
-//// FileItem[] array = items.toArray();
-//// return new TreePath(array);
-// }
-
-
-// public static void loadExpansionState(JTree tree, Enumeration enumeration) {
-// if (enumeration != null) {
-// while (enumeration.hasMoreElements()) {
-// TreePath treePath = (TreePath) enumeration.nextElement();
-// tree.expandPath(treePath);
-// }
-// }
-// }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
- public DefaultMutableTreeNode buildSketchbookTree(){
- DefaultMutableTreeNode sbNode = new DefaultMutableTreeNode(Language.text("sketchbook.tree"));
- try {
- base.addSketches(sbNode, Base.getSketchbookFolder());
- } catch (IOException e) {
- e.printStackTrace();
}
- return sbNode;
}
- protected JFrame sketchbookFrame;
public void showSketchbookFrame() {
if (sketchbookFrame == null) {
- sketchbookFrame = new JFrame(Language.text("sketchbook"));
- Toolkit.setIcon(sketchbookFrame);
- Toolkit.registerWindowCloseKeys(sketchbookFrame.getRootPane(),
- new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- sketchbookFrame.setVisible(false);
- }
- });
-
- final JTree tree = new JTree(buildSketchbookTree());
- tree.getSelectionModel()
- .setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
- tree.setShowsRootHandles(true);
- tree.expandRow(0);
- tree.setRootVisible(false);
-
- tree.addMouseListener(new MouseAdapter() {
- public void mouseClicked(MouseEvent e) {
- if (e.getClickCount() == 2) {
- DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree
- .getLastSelectedPathComponent();
-
- int selRow = tree.getRowForLocation(e.getX(), e.getY());
- //TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
- //if (node != null && node.isLeaf() && node.getPath().equals(selPath)) {
- if (node != null && node.isLeaf() && selRow != -1) {
- SketchReference sketch = (SketchReference) node.getUserObject();
- base.handleOpen(sketch.getPath());
- }
- }
- }
- });
-
- tree.addKeyListener(new KeyAdapter() {
- public void keyPressed(KeyEvent e) {
- if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { // doesn't fire keyTyped()
- sketchbookFrame.setVisible(false);
- }
- }
-
- public void keyTyped(KeyEvent e) {
- if (e.getKeyChar() == KeyEvent.VK_ENTER) {
- DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree
- .getLastSelectedPathComponent();
- if (node != null && node.isLeaf()) {
- SketchReference sketch = (SketchReference) node.getUserObject();
- base.handleOpen(sketch.getPath());
- }
- }
- }
- });
-
- tree.setBorder(new EmptyBorder(5, 5, 5, 5));
- if (Base.isMacOS()) {
- tree.setToggleClickCount(2);
- } else {
- tree.setToggleClickCount(1);
- }
- JScrollPane treePane = new JScrollPane(tree);
- treePane.setPreferredSize(new Dimension(250, 450));
- treePane.setBorder(new EmptyBorder(0, 0, 0, 0));
- sketchbookFrame.getContentPane().add(treePane);
- sketchbookFrame.pack();
+ sketchbookFrame = new SketchbookFrame(base, this);
}
-
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- // Space for the editor plus a li'l gap
- int roughWidth = sketchbookFrame.getWidth() + 20;
- Point p = null;
- // If no window open, or the editor is at the edge of the screen
- if (base.activeEditor == null
- || (p = base.activeEditor.getLocation()).x < roughWidth) {
- // Center the window on the screen
- sketchbookFrame.setLocationRelativeTo(null);
- } else {
- // Open the window relative to the editor
- sketchbookFrame.setLocation(p.x - roughWidth, p.y);
- }
- sketchbookFrame.setVisible(true);
- }
- });
+ sketchbookFrame.setVisible();
}
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
/**
* Get an ImageIcon object from the Mode folder.
* Or when prefixed with /lib, load it from the main /lib folder.
@@ -1176,6 +759,12 @@ public Image loadImage(String filename) {
}
+ public Image loadImageX(String filename) {
+ final int res = Toolkit.highResImages() ? 2 : 1;
+ return loadImage(filename + "-" + res + "x.png");
+ }
+
+
// public EditorButton loadButton(String name) {
// return new EditorButton(this, name);
// }
@@ -1195,16 +784,24 @@ public String lookupReference(String keyword) {
}
- //public TokenMarker getTokenMarker() throws IOException {
- // File keywordsFile = new File(folder, "keywords.txt");
- // return new PdeKeywords(keywordsFile);
- //}
+ /**
+ * Specialized version of getTokenMarker() that can be overridden to
+ * provide different TokenMarker objects for different file types.
+ * @since 3.2
+ * @param code the code for which we need a TokenMarker
+ */
+ public TokenMarker getTokenMarker(SketchCode code) {
+ return getTokenMarker();
+ }
+
+
public TokenMarker getTokenMarker() {
return tokenMarker;
}
+
protected TokenMarker createTokenMarker() {
- return new PdeKeywords();
+ return new PdeTokenMarker();
}
@@ -1275,7 +872,7 @@ public SyntaxStyle getStyle(String attribute) {
}
- public Image getGradient(String attribute, int wide, int high) {
+ public Image makeGradient(String attribute, int wide, int high) {
int top = getColor(attribute + ".gradient.top").getRGB();
int bot = getColor(attribute + ".gradient.bottom").getRGB();
@@ -1427,9 +1024,14 @@ public void prepareExportFolder(File targetFolder) {
if (targetFolder != null) {
// Nuke the old applet/application folder because it can cause trouble
if (Preferences.getBoolean("export.delete_target_folder")) {
-// System.out.println("temporarily skipping deletion of " + targetFolder);
- Base.removeDir(targetFolder);
- // targetFolder.renameTo(dest);
+ if (targetFolder.exists()) {
+ try {
+ Platform.deleteFile(targetFolder);
+ } catch (IOException e) {
+ // ignore errors/continue; likely to be ok
+ e.printStackTrace();
+ }
+ }
}
// Create a fresh output folder (needed before preproc is run next)
targetFolder.mkdirs();
@@ -1445,6 +1047,13 @@ public void prepareExportFolder(File targetFolder) {
// base.handleNewReplace();
// }
+
+ // this is Java-specific, so keeping it in JavaMode
+// public String getSearchPath() {
+// return null;
+// }
+
+
@Override
public String toString() {
return getTitle();
diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java
index 208c08b9bd..16a31bf80e 100644
--- a/app/src/processing/app/Platform.java
+++ b/app/src/processing/app/Platform.java
@@ -3,7 +3,8 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2008 Ben Fry and Casey Reas
+ Copyright (c) 2012-15 The Processing Foundation
+ Copyright (c) 2008-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 as published by
@@ -22,142 +23,347 @@
package processing.app;
-import java.awt.Desktop;
import java.io.File;
+import java.io.FilenameFilter;
import java.io.IOException;
-import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
-import javax.swing.UIManager;
-
-import com.sun.jna.Library;
-import com.sun.jna.Native;
import com.sun.jna.platform.FileUtils;
+import processing.app.platform.DefaultPlatform;
+import processing.core.PApplet;
+import processing.core.PConstants;
+
-/**
- * Used by Base for platform-specific tweaking, for instance finding the
- * sketchbook location using the Windows registry, or OS X event handling.
- *
- * The methods in this implementation are used by default, and can be
- * overridden by a subclass, if loaded by Base.main().
- *
- * These methods throw vanilla-flavored Exceptions, so that error handling
- * occurs inside Base.
- *
- * There is currently no mechanism for adding new platforms, as the setup is
- * not automated. We could use getProperty("os.arch") perhaps, but that's
- * debatable (could be upper/lowercase, have spaces, etc.. basically we don't
- * know if name is proper Java package syntax.)
- */
public class Platform {
- Base base;
+ static DefaultPlatform inst;
+
+ static Map platformNames = new HashMap<>();
+ static {
+ platformNames.put(PConstants.WINDOWS, "windows"); //$NON-NLS-1$
+ platformNames.put(PConstants.MACOSX, "macosx"); //$NON-NLS-1$
+ platformNames.put(PConstants.LINUX, "linux"); //$NON-NLS-1$
+ }
+
+ static Map platformIndices = new HashMap<>();
+ static {
+ platformIndices.put("windows", PConstants.WINDOWS); //$NON-NLS-1$
+ platformIndices.put("macosx", PConstants.MACOSX); //$NON-NLS-1$
+ platformIndices.put("linux", PConstants.LINUX); //$NON-NLS-1$
+ }
+
+ /** How many bits this machine is */
+ static int nativeBits;
+ static {
+ nativeBits = 32; // perhaps start with 32
+ String bits = System.getProperty("sun.arch.data.model"); //$NON-NLS-1$
+ if (bits != null) {
+ if (bits.equals("64")) { //$NON-NLS-1$
+ nativeBits = 64;
+ }
+ } else {
+ // if some other strange vm, maybe try this instead
+ if (System.getProperty("java.vm.name").contains("64")) { //$NON-NLS-1$ //$NON-NLS-2$
+ nativeBits = 64;
+ }
+ }
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ static public void init() {
+ try {
+ Class> platformClass = Class.forName("processing.app.Platform"); //$NON-NLS-1$
+ if (Platform.isMacOS()) {
+ platformClass = Class.forName("processing.app.platform.MacPlatform"); //$NON-NLS-1$
+ } else if (Platform.isWindows()) {
+ platformClass = Class.forName("processing.app.platform.WindowsPlatform"); //$NON-NLS-1$
+ } else if (Platform.isLinux()) {
+ platformClass = Class.forName("processing.app.platform.LinuxPlatform"); //$NON-NLS-1$
+ }
+ inst = (DefaultPlatform) platformClass.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ Messages.showError("Problem Setting the Platform",
+ "An unknown error occurred while trying to load\n" +
+ "platform-specific code for your machine.", e);
+ }
+ }
+
+
+ static public void initBase(Base base) throws Exception {
+ inst.initBase(base);
+ }
+
+
+ static public void setLookAndFeel() throws Exception {
+ inst.setLookAndFeel();
+ }
+
+
+ static public File getSettingsFolder() throws Exception {
+ return inst.getSettingsFolder();
+ }
- public void init(Base base) {
- this.base = base;
+ static public File getDefaultSketchbookFolder() throws Exception {
+ return inst.getDefaultSketchbookFolder();
}
+ static public void saveLanguage(String languageCode) {
+ inst.saveLanguage(languageCode);
+ }
+
+
+// static public void openURL(String url) throws Exception {
+// inst.openURL(url);
+// }
+//
+//
+// public boolean openFolderAvailable() {
+// return inst.openFolderAvailable();
+// }
+//
+//
+// public void openFolder(File file) throws Exception {
+// inst.openFolder(file);
+// }
+
+
/**
- * Set the default L & F. While I enjoy the bounty of the sixteen possible
- * exception types that this UIManager method might throw, I feel that in
- * just this one particular case, I'm being spoiled by those engineers
- * at Sun, those Masters of the Abstractionverse. So instead, I'll pretend
- * that I'm not offered eleven dozen ways to report to the user exactly what
- * went wrong, and I'll bundle them all into a single catch-all "Exception".
- * Because in the end, all I really care about is whether things worked or
- * not. And even then, I don't care.
- * @throws Exception Just like I said.
+ * 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.
*/
- public void setLookAndFeel() throws Exception {
- String laf = Preferences.get("editor.laf");
- if (laf == null || laf.length() == 0) { // normal situation
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
- } else {
- UIManager.setLookAndFeel(laf);
+ static public void openURL(String url) {
+ try {
+ inst.openURL(url);
+
+ } catch (Exception e) {
+ Messages.showWarning("Problem Opening URL",
+ "Could not open the URL\n" + url, e);
}
}
/**
- * Handle any platform-specific languages saving. This is necessary on OS X
- * because of how bundles are handled, but perhaps your platform would like
- * to Think Different too?
- * @param languageCode 2-digit lowercase ISO language code
+ * 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.
*/
- public void saveLanguage(String languageCode) { }
+ static public boolean openFolderAvailable() {
+ return inst.openFolderAvailable();
+ }
/**
- * This function should throw an exception or return a value.
- * Do not return null.
+ * Implements the other cross-platform headache of opening
+ * a folder in the machine's native file browser.
*/
- public File getSettingsFolder() throws Exception {
- // otherwise make a .processing directory int the user's home dir
- File home = new File(System.getProperty("user.home"));
- return new File(home, ".processing");
-
- /*
+ static public void openFolder(File file) {
try {
- Class clazz = Class.forName("processing.app.macosx.ThinkDifferent");
- Method m = clazz.getMethod("getLibraryFolder", new Class[] { });
- String libraryPath = (String) m.invoke(null, new Object[] { });
- //String libraryPath = BaseMacOS.getLibraryFolder();
- File libraryFolder = new File(libraryPath);
- dataFolder = new File(libraryFolder, "Processing");
+ inst.openFolder(file);
} catch (Exception e) {
- showError("Problem getting data folder",
- "Error getting the Processing data folder.", e);
+ Messages.showWarning("Problem Opening Folder",
+ "Could not open the folder\n" + file.getAbsolutePath(), e);
}
- */
}
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
/**
- * @return if not overridden, a folder named "sketchbook" in user.home.
- * @throws Exception so that subclasses can throw a fit
+ * Return whether sketches will run as 32- or 64-bits based
+ * on the JVM that's in use.
*/
- public File getDefaultSketchbookFolder() throws Exception {
- return new File(System.getProperty("user.home"), "sketchbook");
+ static public int getNativeBits() {
+ return nativeBits;
}
- public void openURL(String url) throws Exception {
- Desktop.getDesktop().browse(new URI(url));
- /*
- String launcher = Preferences.get("launcher");
- if (launcher != null) {
- Runtime.getRuntime().exec(new String[] { launcher, url });
- } else {
- showLauncherWarning();
+ /**
+ * Return the value of the os.arch property
+ */
+ static public String getNativeArch() {
+ // This will return "arm" for 32-bit ARM, "aarch64" for 64-bit ARM (both on Linux)
+ return System.getProperty("os.arch");
+ }
+
+
+ /*
+ * Return a string that identifies the variant of a platform
+ * e.g. "32" or "64" on Intel
+ */
+ static public String getVariant() {
+ return getVariant(PApplet.platform, getNativeArch(), getNativeBits());
+ }
+
+
+ static public String getVariant(int platform, String arch, int bits) {
+ if (platform == PConstants.LINUX &&
+ bits == 32 && "arm".equals(Platform.getNativeArch())) {
+ return "armv6hf"; // assume armv6hf
+ } else if (platform == PConstants.LINUX &&
+ bits == 64 && "aarch64".equals(Platform.getNativeArch())) {
+ return "arm64";
}
- */
+
+ return Integer.toString(bits); // 32 or 64
}
- public boolean openFolderAvailable() {
- return Desktop.isDesktopSupported();
- /*
- return Preferences.get("launcher") != null;
- */
+ static public String getName() {
+ return PConstants.platformNames[PApplet.platform];
}
- public void openFolder(File file) throws Exception {
- Desktop.getDesktop().open(file);
- /*
- String launcher = Preferences.get("launcher");
- if (launcher != null) {
- String folder = file.getAbsolutePath();
- Runtime.getRuntime().exec(new String[] { launcher, folder });
- } else {
- showLauncherWarning();
+ /**
+ * 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 getName(int which) {
+ return platformNames.get(which);
+ }
+
+
+ static public int getIndex(String what) {
+ Integer entry = platformIndices.get(what);
+ return (entry == null) ? -1 : entry.intValue();
+ }
+
+
+ // 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.
+
+
+ /**
+ * 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$
+ }
+
+
+ /**
+ * returns true if running on windows.
+ */
+ static public boolean isWindows() {
+ //return PApplet.platform == PConstants.WINDOWS;
+ return System.getProperty("os.name").indexOf("Windows") != -1; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+
+ /**
+ * true if running on linux.
+ */
+ static public boolean isLinux() {
+ //return PApplet.platform == PConstants.LINUX;
+ return System.getProperty("os.name").indexOf("Linux") != -1; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ static protected File processingRoot;
+
+ /**
+ * Get reference to a file adjacent to the executable on Windows and Linux,
+ * or inside Contents/Resources/Java on Mac OS X. This will return the local
+ * JRE location, *whether or not it is the active JRE*.
+ */
+ static public File getContentFile(String name) {
+ if (processingRoot == null) {
+ // Get the path to the .jar file that contains Base.class
+ URL pathURL =
+ Base.class.getProtectionDomain().getCodeSource().getLocation();
+ // Decode URL
+ String decodedPath;
+ try {
+ decodedPath = pathURL.toURI().getSchemeSpecificPart();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ if (decodedPath.contains("/app/bin")) { // This means we're in Eclipse
+ final File build = new File(decodedPath, "../../build").getAbsoluteFile();
+ if (Platform.isMacOS()) {
+ processingRoot = new File(build, "macosx/work/Processing.app/Contents/Java");
+ } else if (Platform.isWindows()) {
+ processingRoot = new File(build, "windows/work");
+ } else if (Platform.isLinux()) {
+ processingRoot = new File(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 (Platform.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(""); // resolves to "user.dir"
+ }
+ }
+ }
+ return new File(processingRoot, name);
+ }
+
+
+ static public File getJavaHome() {
+ if (Platform.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(".");
+ }
+ });
+ return new File(plugins[0], "Contents/Home/jre");
}
- */
+ // On all other platforms, it's the 'java' folder adjacent to Processing
+ return getContentFile("java");
}
+ /** Get the path to the embedded Java executable. */
+ static public String getJavaPath() {
+ String javaPath = "bin/java" + (Platform.isWindows() ? ".exe" : "");
+ File javaFile = new File(getJavaHome(), javaPath);
+ try {
+ return javaFile.getCanonicalPath();
+ } catch (IOException e) {
+ return javaFile.getAbsolutePath();
+ }
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
/**
* Attempts to move to the Trash on OS X, or the Recycle Bin on Windows.
* Also tries to find a suitable Trash location on Linux.
@@ -166,14 +372,14 @@ public void openFolder(File file) throws Exception {
* @return true if the folder was successfully removed
* @throws IOException
*/
- final public boolean deleteFile(File file) throws IOException {
+ static public boolean deleteFile(File file) throws IOException {
FileUtils fu = FileUtils.getInstance();
if (fu.hasTrash()) {
fu.moveToTrash(new File[] { file });
return true;
} else if (file.isDirectory()) {
- Base.removeDir(file);
+ Util.removeDir(file);
return true;
} else {
@@ -185,43 +391,25 @@ final public boolean deleteFile(File file) throws IOException {
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- public interface CLibrary extends Library {
- CLibrary INSTANCE = (CLibrary)Native.loadLibrary("c", CLibrary.class);
- int setenv(String name, String value, int overwrite);
- String getenv(String name);
- int unsetenv(String name);
- int putenv(String string);
- }
-
-
- public void setenv(String variable, String value) {
- CLibrary clib = CLibrary.INSTANCE;
- clib.setenv(variable, value, 1);
+ static public void setenv(String variable, String value) {
+ inst.setenv(variable, value);
}
- public String getenv(String variable) {
- CLibrary clib = CLibrary.INSTANCE;
- return clib.getenv(variable);
+ static public String getenv(String variable) {
+ return inst.getenv(variable);
}
- public int unsetenv(String variable) {
- CLibrary clib = CLibrary.INSTANCE;
- return clib.unsetenv(variable);
+ static public int unsetenv(String variable) {
+ return inst.unsetenv(variable);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- /*
- protected void showLauncherWarning() {
- Base.showWarning("No launcher available",
- "Unspecified platform, no launcher available.\n" +
- "To enable opening URLs or folders, add a \n" +
- "\"launcher=/path/to/app\" line to preferences.txt",
- null);
- }
- */
-}
+ static public int getSystemDPI() {
+ return inst.getSystemDPI();
+ }
+}
\ No newline at end of file
diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java
index f24c67251d..1b05083e19 100644
--- a/app/src/processing/app/Preferences.java
+++ b/app/src/processing/app/Preferences.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2014 The Processing Foundation
+ Copyright (c) 2014-19 The Processing Foundation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
@@ -21,10 +21,13 @@
package processing.app;
-import java.awt.*;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.SystemColor;
import java.io.*;
import java.util.*;
+import processing.app.ui.Toolkit;
import processing.core.*;
@@ -49,32 +52,13 @@ public class Preferences {
static final String DEFAULTS_FILE = "defaults.txt"; //$NON-NLS-1$
static final String PREFS_FILE = "preferences.txt"; //$NON-NLS-1$
- static HashMap defaults;
- static HashMap table = new HashMap();
+ static Map defaults;
+ static Map table = new HashMap<>();
static File preferencesFile;
- static final String PROMPT_YES = Language.text("prompt.yes");
- static final String PROMPT_NO = Language.text("prompt.no");
- static final String PROMPT_CANCEL = Language.text("prompt.cancel");
- static final String PROMPT_OK = Language.text("prompt.ok");
- static final String PROMPT_BROWSE = Language.text("prompt.browse");
-
- /**
- * Standardized width for buttons. Mac OS X 10.3 wants 70 as its default,
- * Windows XP needs 66, and my Ubuntu machine needs 80+, so 80 seems proper.
- */
- static public int BUTTON_WIDTH =
- Integer.parseInt(Language.text("preferences.button.width"));
-
- // Indents and spacing standards. These probably need to be modified
- // per platform as well, because Mac OS X is so huge, Windows is smaller,
- // and Linux is all over the map. Consider these deprecated.
-
- static final int GUI_BIG = 13;
- static final int GUI_BETWEEN = 8;
- static final int GUI_SMALL = 6;
-
+// /** @return true if the sketchbook file did not exist */
+// static public boolean init() {
static public void init() {
// start by loading the defaults, in case something
// important was deleted from the user prefs
@@ -83,56 +67,40 @@ static public void init() {
// replacing the file after doing a search for "preferences.txt".
load(Base.getLibStream(DEFAULTS_FILE));
} catch (Exception e) {
- Base.showError(null, "Could not read default settings.\n" +
- "You'll need to reinstall Processing.", e);
+ Messages.showError(null, "Could not read default settings.\n" +
+ "You'll need to reinstall Processing.", e);
}
- /* provisionally removed in 3.0a6, see changes in load()
-
- // check for platform-specific properties in the defaults
- String platformExt = "." + PConstants.platformNames[PApplet.platform]; //$NON-NLS-1$
- int platformExtLength = platformExt.length();
-
- // Get a list of keys that are specific to this platform
- ArrayList platformKeys = new ArrayList();
- for (String key : table.keySet()) {
- if (key.endsWith(platformExt)) {
- platformKeys.add(key);
- }
- }
-
- // Use those platform-specific keys to override
- for (String key : platformKeys) {
- // this is a key specific to a particular platform
- String actualKey = key.substring(0, key.length() - platformExtLength);
- String value = get(key);
- set(actualKey, value);
- }
- */
-
// Clone the defaults, then override any them with the user's preferences.
// This ensures that any new/added preference will be present.
- defaults = new HashMap(table);
+ defaults = new HashMap<>(table);
// other things that have to be set explicitly for the defaults
setColor("run.window.bgcolor", SystemColor.control); //$NON-NLS-1$
+ // For CJK users, enable IM support by default
+ if (Language.useInputMethod()) {
+ setBoolean("editor.input_method_support", true);
+ }
+
// next load user preferences file
preferencesFile = Base.getSettingsFile(PREFS_FILE);
- if (preferencesFile.exists()) {
+ boolean firstRun = !preferencesFile.exists();
+ if (!firstRun) {
try {
load(new FileInputStream(preferencesFile));
} catch (Exception ex) {
- Base.showError("Error reading preferences",
- "Error reading the preferences file. " +
- "Please delete (or move)\n" +
- preferencesFile.getAbsolutePath() +
- " and restart Processing.", ex);
+ Messages.showError("Error reading preferences",
+ "Error reading the preferences file. " +
+ "Please delete (or move)\n" +
+ preferencesFile.getAbsolutePath() +
+ " and restart Processing.", ex);
}
}
- if (checkSketchbookPref() || !preferencesFile.exists()) {
+ if (checkSketchbookPref() || firstRun) {
+// if (firstRun) {
// create a new preferences file if none exists
// saves the defaults out to the file
save();
@@ -141,19 +109,37 @@ static public void init() {
PApplet.useNativeSelect =
Preferences.getBoolean("chooser.files.native"); //$NON-NLS-1$
- // Set http proxy for folks that require it.
+ // Adding option to disable this in case it's getting in the way
+ if (get("proxy.system").equals("true")) {
+ // Use the system proxy settings by default
+ // https://github.com/processing/processing/issues/2643
+ System.setProperty("java.net.useSystemProxies", "true");
+ }
+
+ // Set HTTP, HTTPS, and SOCKS proxies for individuals
+ // who want/need to override the system setting
// http://docs.oracle.com/javase/6/docs/technotes/guides/net/proxies.html
- String proxyHost = get("proxy.host");
- String proxyPort = get("proxy.port");
+ // Less readable version with the Oracle style sheet:
+ // http://docs.oracle.com/javase/8/docs/technotes/guides/net/proxies.html
+ handleProxy("http", "http.proxyHost", "http.proxyPort");
+ handleProxy("https", "https.proxyHost", "https.proxyPort");
+ handleProxy("socks", "socksProxyHost", "socksProxyPort");
+ }
+
+
+ static void handleProxy(String protocol, String hostProp, String portProp) {
+ String proxyHost = get("proxy." + protocol + ".host");
+ String proxyPort = get("proxy." + protocol + ".port");
if (proxyHost != null && proxyHost.length() != 0 &&
proxyPort != null && proxyPort.length() != 0) {
- System.setProperty("http.proxyHost", proxyHost);
- System.setProperty("http.proxyPort", proxyPort);
+ System.setProperty(hostProp, proxyHost);
+ System.setProperty(portProp, proxyPort);
}
+
}
- static protected String getPreferencesPath() {
+ static public String getPreferencesPath() {
return preferencesFile.getAbsolutePath();
}
@@ -223,24 +209,48 @@ static protected boolean isPlatformSpecific(String key, String value,
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- static protected void save() {
- // on startup, don't worry about it
- // this is trying to update the prefs for who is open
- // before Preferences.init() has been called.
- if (preferencesFile == null) return;
-
- // Fix for 0163 to properly use Unicode when writing preferences.txt
- PrintWriter writer = PApplet.createWriter(preferencesFile);
+ static public void save() {
+ // On startup it'll be null, don't worry about it. It's trying to update
+ // the prefs for the open sketch before Preferences.init() has been called.
+ if (preferencesFile != null) {
+ try {
+ File dir = preferencesFile.getParentFile();
+ File preferencesTemp = File.createTempFile("preferences", ".txt", dir);
+ preferencesTemp.setWritable(true, false);
+
+ // Fix for 0163 to properly use Unicode when writing preferences.txt
+ PrintWriter writer = PApplet.createWriter(preferencesTemp);
+
+ String[] keyList = table.keySet().toArray(new String[table.size()]);
+ // Sorting is really helpful for debugging, diffing, and finding keys
+ keyList = PApplet.sort(keyList);
+ for (String key : keyList) {
+ writer.println(key + "=" + table.get(key)); //$NON-NLS-1$
+ }
+ writer.flush();
+ writer.close();
+
+ // Rename preferences.txt to preferences.old
+ File oldPreferences = new File(dir, "preferences.old");
+ if (oldPreferences.exists()) {
+ if (!oldPreferences.delete()) {
+ throw new IOException("Could not delete preferences.old");
+ }
+ }
+ if (preferencesFile.exists() &&
+ !preferencesFile.renameTo(oldPreferences)) {
+ throw new IOException("Could not replace preferences.old");
+ }
+ // Make the temporary file into the real preferences
+ if (!preferencesTemp.renameTo(preferencesFile)) {
+ throw new IOException("Could not move preferences file into place");
+ }
- String[] keyList = table.keySet().toArray(new String[table.size()]);
- // Sorting is really helpful for debugging, diffing, and finding keys
- keyList = PApplet.sort(keyList);
- for (String key : keyList) {
- writer.println(key + "=" + table.get(key)); //$NON-NLS-1$
+ } catch (IOException e) {
+ Messages.showWarning("Preferences",
+ "Could not save the Preferences file.", e);
+ }
}
-
- writer.flush();
- writer.close();
}
@@ -384,7 +394,7 @@ static public Font getFont(String attr) {
} catch (Exception e) {
// Adding try/catch block because this may be where
// a lot of startup crashes are happening.
- Base.log("Error with font " + get(attr) + " for attribute " + attr);
+ Messages.log("Error with font " + get(attr) + " for attribute " + attr);
}
return new Font("Dialog", Font.PLAIN, 12);
}
@@ -413,7 +423,12 @@ static protected boolean checkSketchbookPref() {
}
- static protected String getSketchbookPath() {
+ static public String getOldSketchbookPath() {
+ return get("sketchbook.path");
+ }
+
+
+ static public String getSketchbookPath() {
return get("sketchbook.path.three"); //$NON-NLS-1$
}
diff --git a/app/src/processing/app/contrib/ContributionChangeListener.java b/app/src/processing/app/Problem.java
similarity index 55%
rename from app/src/processing/app/contrib/ContributionChangeListener.java
rename to app/src/processing/app/Problem.java
index 66283e50bc..cb12ad5e3e 100644
--- a/app/src/processing/app/contrib/ContributionChangeListener.java
+++ b/app/src/processing/app/Problem.java
@@ -2,9 +2,7 @@
/*
Part of the Processing project - http://processing.org
-
- Copyright (c) 2013 The Processing Foundation
- Copyright (c) 2011-12 Ben Fry and Casey Reas
+ Copyright (c) 2012-16 The Processing Foundation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
@@ -15,15 +13,23 @@
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
+ 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.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package processing.app.contrib;
+package processing.app;
+
+
+public interface Problem {
+ public boolean isError();
+ public boolean isWarning();
-public interface ContributionChangeListener {
- public void contributionAdded(Contribution Contribution);
- public void contributionRemoved(Contribution Contribution);
- public void contributionChanged(Contribution oldLib, Contribution newLib);
+ public int getTabIndex();
+ public int getLineNumber(); // 0-indexed
+ public String getMessage();
+
+ public int getStartOffset();
+ public int getStopOffset();
}
+
diff --git a/app/src/processing/app/ProgressFrame.java b/app/src/processing/app/ProgressFrame.java
deleted file mode 100644
index 355f225315..0000000000
--- a/app/src/processing/app/ProgressFrame.java
+++ /dev/null
@@ -1,379 +0,0 @@
-package processing.app;
-
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JProgressBar;
-import javax.swing.SwingWorker;
-
-//Class used to handle progress bar, and run Save As or Add File in
-//background so that
-//progress bar can update without freezing
-public class ProgressFrame extends JFrame implements PropertyChangeListener {
-
- private static final long serialVersionUID = 1L;
-
- private JProgressBar progressBar;
-
- private JLabel saveAsLabel;
-
- private TaskSaveAs t;
-
- private TaskAddFile t2;
-
- private File[] copyItems;
-
- private File newFolder;
-
- private File addFile, sourceFile;
-
- private Editor editor;
-
- // create a new background thread to save as
- public class TaskSaveAs extends SwingWorker {
-
- @Override
- protected Void doInBackground() throws Exception {
- // a large part of the file copying happens in this background
- // thread
-
- long totalSize = 0;
- for (File copyable : copyItems) {
- totalSize += getFileLength(copyable);
- }
-
- long progress = 0;
- setProgress(0);
- for (File copyable : ProgressFrame.this.copyItems) {
- // loop to copy over the items that make sense, and to set the
- // current progress
-
- if (copyable.isDirectory()) {
- copyDir(copyable,
- new File(ProgressFrame.this.newFolder, copyable.getName()),
- this, progress, totalSize);
- progress += getFileLength(copyable);
- } else {
- copyFile(copyable,
- new File(ProgressFrame.this.newFolder, copyable.getName()),
- this, progress, totalSize);
- if (getFileLength(copyable) < 524288) {
- // If the file length > 0.5MB, the copyFile() function has
- // been redesigned to change progress every 0.5MB so that
- // the progress bar doesn't stagnate during that time
- progress += getFileLength(copyable);
- setProgress((int) Math.min(Math.ceil(progress * 100.0 / totalSize),
- 100));
- }
- }
- }
-
- return null;
- }
-
- public void setProgressBarStatus(int status) {
-
- setProgress(status);
- }
-
- @Override
- public void done() {
- // to close the progress bar automatically when done, and to
- // print that Saving is done in Message Area
-
- editor.statusNotice(Language.text("editor.status.saving.done"));
- ProgressFrame.this.closeProgressBar();
- }
-
- }
-
- // create a new background thread to add a file
- public class TaskAddFile extends SwingWorker {
-
- @Override
- protected Void doInBackground() throws Exception {
- // a large part of the file copying happens in this background
- // thread
-
- setProgress(0);
-
- copyFile(sourceFile, addFile, this);
-
- if (addFile.length() < 1024) {
- // If the file length > 1kB, the copyFile() function has
- // been redesigned to change progress every 1kB so that
- // the progress bar doesn't stagnate during that time
-
- // If file <1 kB, just fill up Progress Bar to 100%
- // directly, since time to copy is now negligable (when
- // perceived by a human, anyway)
- setProgress(100);
- }
-
- return null;
- }
-
- public void setProgressBarStatus(int status) {
- setProgress(status);
- }
-
- @Override
- public void done() {
- // to close the progress bar automatically when done, and to
- // print that adding file is done in Message Area
-
- editor.statusNotice(Language.text("editor.status.drag_and_drop.files_added.1"));
- ProgressFrame.this.closeProgressBar();
- }
-
- }
-
- //Use for Save As
- public ProgressFrame(File[] c, File nf, String oldName, String newName,
- Editor editor) {
- // initialize a copyItems and newFolder, which are used for file
- // copying in the background thread
- copyItems = c;
- newFolder = nf;
- this.editor = editor;
-
- // the UI of the progress bar follows
- setDefaultCloseOperation(HIDE_ON_CLOSE);
- setBounds(200, 200, 400, 140);
- setResizable(false);
- setTitle("Saving As...");
- JPanel panel = new JPanel(null);
- add(panel);
- setContentPane(panel);
- saveAsLabel = new JLabel("Saving " + oldName + " as " + newName + "...");
- saveAsLabel.setBounds(40, 20, 300, 20);
-
- progressBar = new JProgressBar(0, 100);
- progressBar.setValue(0);
- progressBar.setBounds(40, 50, 300, 30);
- progressBar.setStringPainted(true);
-
- panel.add(progressBar);
- panel.add(saveAsLabel);
- Toolkit.setIcon(this);
- this.setVisible(true);
-
- // create an instance of TaskSaveAs and run execute() on this
- // instance to
- // start background thread
- t = new TaskSaveAs();
- t.addPropertyChangeListener(this);
- t.execute();
- }
-
- //Use for Add File
- public ProgressFrame(File sf, File add, Editor editor) {
-
- addFile = add;
- sourceFile = sf;
- this.editor = editor;
-
- // the UI of the progress bar follows
- setDefaultCloseOperation(HIDE_ON_CLOSE);
- setBounds(200, 200, 400, 140);
- setResizable(false);
- setTitle("Adding File...");
- JPanel panel = new JPanel(null);
- add(panel);
- setContentPane(panel);
- saveAsLabel = new JLabel("Adding " + addFile.getName());
- saveAsLabel.setBounds(40, 20, 300, 20);
-
- progressBar = new JProgressBar(0, 100);
- progressBar.setValue(0);
- progressBar.setBounds(40, 50, 300, 30);
- progressBar.setStringPainted(true);
-
- panel.add(progressBar);
- panel.add(saveAsLabel);
- Toolkit.setIcon(this);
- this.setVisible(true);
-
- // create an instance of TaskAddFile and run execute() on this
- // instance to
- // start background thread
- t2 = new TaskAddFile();
- t2.addPropertyChangeListener(this);
- t2.execute();
- }
-
- public long getFileLength(File f)// function to return the length of
- // the file, or
- // ENTIRE directory, including the
- // component files
- // and sub-folders if passed
- {
- long fol_len = 0;
- if (f.isDirectory()) {
- String files[] = f.list();
- for (int i = 0; i < files.length; i++) {
- File temp = new File(f, files[i]);
- if (temp.isDirectory()) {
- fol_len += getFileLength(temp);
- } else {
- fol_len += (temp.length());
- }
- }
- } else {
- return (f.length());
- }
- return fol_len;
- }
-
- public void propertyChange(PropertyChangeEvent evt)
- // detects a change in the property of the background task, i.e., is
- // called when the size of files already copied changes
- {
- if ("progress" == evt.getPropertyName()) {
- int progress = (Integer) evt.getNewValue();
- progressBar.setValue(progress);
- }
- }
-
- private void closeProgressBar()
- // closes progress bar
- {
- this.dispose();
- }
-
- static public void copyFile(File sourceFile, File targetFile,
- ProgressFrame.TaskSaveAs progBar,
- double progress, double totalSize)
- throws IOException {
- // Overloaded copyFile that is called whenever a Save As is being done, so that the
- // ProgressBar is updated for very large files as well
- BufferedInputStream from = new BufferedInputStream(
- new FileInputStream(
- sourceFile));
- BufferedOutputStream to = new BufferedOutputStream(
- new FileOutputStream(
- targetFile));
- byte[] buffer = new byte[16 * 1024];
- int bytesRead;
- int totalRead = 0;
- while ((bytesRead = from.read(buffer)) != -1) {
- to.write(buffer, 0, bytesRead);
- totalRead += bytesRead;
- if (totalRead >= 524288) //to update progress bar every 0.5MB
- {
- progress += totalRead;
- progBar.setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0
- / totalSize), 100));
- totalRead = 0;
- }
- }
- if (sourceFile.length() > 524288) {
- // Update the progress bar one final time if file size is more than 0.5MB,
- // otherwise, the update is handled either by the copyDir function,
- // or directly by ProgressFrame.TaskSaveAs.doInBackground()
- progress += totalRead;
- progBar.setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0
- / totalSize), 100));
- }
- from.close();
- from = null;
- to.flush();
- to.close();
- to = null;
-
- targetFile.setLastModified(sourceFile.lastModified());
- targetFile.setExecutable(sourceFile.canExecute());
- }
-
- static public void copyFile(File sourceFile, File targetFile,
- ProgressFrame.TaskAddFile progBar)
- throws IOException {
- // Overloaded copyFile that is called whenever a addFile is being done,
- // so that the
- // ProgressBar is updated
- double totalSize = sourceFile.length();
- int progress = 0;
- BufferedInputStream from = new BufferedInputStream(
- new FileInputStream(
- sourceFile));
- BufferedOutputStream to = new BufferedOutputStream(
- new FileOutputStream(
- targetFile));
- byte[] buffer = new byte[16 * 1024];
- int bytesRead;
- int totalRead = 0;
- while ((bytesRead = from.read(buffer)) != -1) {
- to.write(buffer, 0, bytesRead);
- totalRead += bytesRead;
- if (totalRead >= 1024) // to update progress bar every 1kB
- {
- progress += totalRead;
- progBar.setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0
- / totalSize), 100));
- totalRead = 0;
- }
- }
- if (sourceFile.length() > 1024) {
- // Update the progress bar one final time if file size is more than
- // 1kB,
- // otherwise, the update is handled directly by
- // ProgressFrame.TaskAddFile.doInBackground()
- progress += totalRead;
- progBar.setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0
- / totalSize), 100));
- }
- from.close();
- from = null;
- to.flush();
- to.close();
- to = null;
- targetFile.setLastModified(sourceFile.lastModified());
- targetFile.setExecutable(sourceFile.canExecute());
- }
-
- static public double copyDir(File sourceDir, File targetDir,
- ProgressFrame.TaskSaveAs progBar,
- double progress, double totalSize)
- throws IOException {
- // Overloaded copyDir so that the Save As progress bar gets updated when the
- // files are in folders as well (like in the data folder)
- 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();
- progress = copyDir(source, target, progBar, progress, totalSize);
- progBar.setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0
- / totalSize), 100));
- target.setLastModified(source.lastModified());
- } else {
- copyFile(source, target, progBar, progress, totalSize);
- // Update SaveAs progress bar
- progress += source.length();
- progBar.setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0
- / totalSize), 100));
- }
- }
- return progress;
- }
-
-}
diff --git a/app/src/processing/app/RunnerListenerEdtAdapter.java b/app/src/processing/app/RunnerListenerEdtAdapter.java
new file mode 100644
index 0000000000..a436eefbca
--- /dev/null
+++ b/app/src/processing/app/RunnerListenerEdtAdapter.java
@@ -0,0 +1,48 @@
+package processing.app;
+
+import java.awt.EventQueue;
+
+public class RunnerListenerEdtAdapter implements RunnerListener {
+
+ private RunnerListener wrapped;
+
+ public RunnerListenerEdtAdapter(RunnerListener wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public void statusError(String message) {
+ EventQueue.invokeLater(() -> wrapped.statusError(message));
+ }
+
+ @Override
+ public void statusError(Exception exception) {
+ EventQueue.invokeLater(() -> wrapped.statusError(exception));
+ }
+
+ @Override
+ public void statusNotice(String message) {
+ EventQueue.invokeLater(() -> wrapped.statusNotice(message));
+ }
+
+ @Override
+ public void startIndeterminate() {
+ EventQueue.invokeLater(() -> wrapped.startIndeterminate());
+ }
+
+ @Override
+ public void stopIndeterminate() {
+ EventQueue.invokeLater(() -> wrapped.stopIndeterminate());
+ }
+
+ @Override
+ public void statusHalt() {
+ EventQueue.invokeLater(() -> wrapped.statusHalt());
+ }
+
+ @Override
+ public boolean isHalted() {
+ return wrapped.isHalted();
+ }
+}
+
diff --git a/app/src/processing/app/Settings.java b/app/src/processing/app/Settings.java
index 58d66ac5b2..efd016f231 100644
--- a/app/src/processing/app/Settings.java
+++ b/app/src/processing/app/Settings.java
@@ -27,6 +27,7 @@
import java.io.*;
import java.util.*;
+import processing.app.ui.Toolkit;
import processing.core.*;
@@ -36,31 +37,31 @@
* and to make way for future ability to customize.
*/
public class Settings {
- /**
- * Copy of the defaults in case the user mangles a preference.
+ /**
+ * Copy of the defaults in case the user mangles a preference.
* It's necessary to keep a copy of the defaults around, because the user may
- * have replaced a setting on their own. In the past, we used to load the
+ * have replaced a setting on their own. In the past, we used to load the
* defaults, then replace those with what was in the user's preferences file.
- * Problem is, if something like a font entry in the user's file no longer
+ * Problem is, if something like a font entry in the user's file no longer
* parses properly, we need to be able to get back to a clean version of that
* setting so we can recover.
*/
HashMap defaults;
-
+
/** Table of attributes/values. */
HashMap table = new HashMap();;
-
+
/** Associated file for this settings data. */
File file;
public Settings(File file) throws IOException {
this.file = file;
-
+
if (file.exists()) {
load();
}
-
+
// clone the hash table
defaults = (HashMap) table.clone();
}
@@ -70,7 +71,7 @@ public void load() {
load(file);
}
-
+
public void load(File additions) {
String[] lines = PApplet.loadStrings(additions);
for (String line : lines) {
@@ -87,7 +88,7 @@ public void load(File additions) {
}
// check for platform-specific properties in the defaults
- String platformExt = "." + Base.getPlatformName();
+ String platformExt = "." + Platform.getName();
int platformExtLength = platformExt.length();
for (String key : table.keySet()) {
if (key.endsWith(platformExt)) {
@@ -176,7 +177,7 @@ public void setColor(String attr, Color what) {
}
- // identical version found in Preferences.java
+ // identical version found in Preferences.java
public Font getFont(String attr) {
try {
boolean replace = false;
@@ -204,7 +205,8 @@ public Font getFont(String attr) {
style |= Font.ITALIC;
}
int size = PApplet.parseInt(pieces[2], 12);
-
+ size = Toolkit.zoom(size);
+
// replace bad font with the default from lib/preferences.txt
if (replace) {
set(attr, value);
@@ -222,9 +224,9 @@ public Font getFont(String attr) {
}
} catch (Exception e) {
- // Adding try/catch block because this may be where
- // a lot of startup crashes are happening.
- Base.log("Error with font " + get(attr) + " for attribute " + attr);
+ // Adding try/catch block because this may be where
+ // a lot of startup crashes are happening.
+ Messages.log("Error with font " + get(attr) + " for attribute " + attr);
}
return new Font("Dialog", Font.PLAIN, 12);
}
diff --git a/app/src/processing/app/SingleInstance.java b/app/src/processing/app/SingleInstance.java
index 2ab4d442dc..d9409d1526 100644
--- a/app/src/processing/app/SingleInstance.java
+++ b/app/src/processing/app/SingleInstance.java
@@ -21,6 +21,7 @@
*/
package processing.app;
+import java.awt.EventQueue;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
@@ -28,8 +29,6 @@
import java.net.ServerSocket;
import java.net.Socket;
-import javax.swing.SwingUtilities;
-
import processing.core.PApplet;
@@ -38,8 +37,6 @@
* Processing from running simultaneously. If there's already an instance
* running, it'll handle opening a new empty sketch, or any files that had
* been passed in on the command line.
- *
- * @author Peter Kalauskas, Ben Fry
*/
public class SingleInstance {
static final String SERVER_PORT = "instance_server.port";
@@ -58,15 +55,17 @@ static boolean alreadyRunning(String[] args) {
}
-// static void startServer(final Platform platform) {
static void startServer(final Base base) {
try {
- final ServerSocket ss = new ServerSocket(0, 0, InetAddress.getByName(null));
+ Messages.log("Opening SingleInstance socket");
+ final ServerSocket ss =
+ new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
Preferences.set(SERVER_PORT, "" + ss.getLocalPort());
final String key = "" + Math.random();
Preferences.set(SERVER_KEY, key);
Preferences.save();
+ Messages.log("Starting SingleInstance thread");
new Thread(new Runnable() {
public void run() {
while (true) {
@@ -74,28 +73,26 @@ public void run() {
Socket s = ss.accept(); // blocks (sleeps) until connection
final BufferedReader reader = PApplet.createReader(s.getInputStream());
String receivedKey = reader.readLine();
- Base.log(this, "key is " + key + ", received is " + receivedKey);
-// Base.log(this, "platform base is " + platform.base);
+ Messages.log(this, "key is " + key + ", received is " + receivedKey);
-// if (platform.base != null) {
if (key.equals(receivedKey)) {
- SwingUtilities.invokeLater(new Runnable() {
+ EventQueue.invokeLater(new Runnable() {
public void run() {
try {
- Base.log(this, "about to read line");
+ Messages.log(this, "about to read line");
String path = reader.readLine();
if (path == null) {
// Because an attempt was made to launch the PDE again,
// throw the user a bone by at least opening a new
// Untitled window for them.
- Base.log(this, "opening new empty sketch");
+ Messages.log(this, "opening new empty sketch");
// platform.base.handleNew();
base.handleNew();
} else {
// loop through the sketches that were passed in
do {
- Base.log(this, "calling open with " + path);
+ Messages.log(this, "calling open with " + path);
// platform.base.handleOpen(filename);
base.handleOpen(path);
path = reader.readLine();
@@ -107,33 +104,35 @@ public void run() {
}
});
} else {
- Base.log(this, "keys do not match");
+ Messages.log(this, "keys do not match");
}
// }
} catch (IOException e) {
- Base.loge("SingleInstance error while listening", e);
+ Messages.loge("SingleInstance error while listening", e);
}
}
}
}, "SingleInstance Server").start();
} catch (IOException e) {
- Base.loge("Could not create single instance server.", e);
+ Messages.loge("Could not create single instance server.", e);
}
}
static boolean sendArguments(String[] args) { //, long timeout) {
try {
+ Messages.log("Checking to see if Processing is already running");
int port = Preferences.getInteger(SERVER_PORT);
String key = Preferences.get(SERVER_KEY);
Socket socket = null;
try {
- socket = new Socket(InetAddress.getByName(null), port);
+ socket = new Socket(InetAddress.getLoopbackAddress(), port);
} catch (Exception ignored) { }
if (socket != null) {
+ Messages.log("Processing is already running, sending command line");
PrintWriter writer = PApplet.createWriter(socket.getOutputStream());
writer.println(key);
for (String arg : args) {
@@ -144,9 +143,9 @@ static boolean sendArguments(String[] args) { //, long timeout) {
return true;
}
} catch (IOException e) {
- System.err.println("Error sending commands to other instance.");
- e.printStackTrace();
+ Messages.loge("Error sending commands to other instance", e);
}
+ Messages.log("Processing is not already running (or could not connect)");
return false;
}
}
diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java
index 09fc78305c..e686dc1823 100644
--- a/app/src/processing/app/Sketch.java
+++ b/app/src/processing/app/Sketch.java
@@ -23,15 +23,28 @@
package processing.app;
+import processing.app.ui.Editor;
+import processing.app.ui.Recent;
+import processing.app.ui.Toolkit;
import processing.core.*;
-import java.awt.*;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.EventQueue;
+import java.awt.FileDialog;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.*;
+import javax.swing.border.EmptyBorder;
/**
@@ -64,6 +77,7 @@ public class Sketch {
private SketchCode current;
private int currentIndex;
+
/**
* Number of sketchCode objects (tabs) in the current sketch. Note that this
* will be the same as code.length, because the getCode() method returns
@@ -77,28 +91,8 @@ public class Sketch {
/** Moved out of Editor and into here for cleaner access. */
private boolean untitled;
-// /** Class path determined during build. */
-// private String classPath;
-//
-// /**
-// * This is *not* the "Processing" libraries path, this is the Java libraries
-// * path, as in java.library.path=BlahBlah, which identifies search paths for
-// * DLLs or JNILIBs. (It's Java's LD_LIBRARY_PATH, for you UNIX fans.)
-// */
-// private String javaLibraryPath;
-//
-// /**
-// * List of library folders, set up in the preprocess() method.
-// */
-// private ArrayList importedLibraries;
-// //private ArrayList importedLibraries;
-
- /**
- * Most recent, default build path. This will contain the .java files that
- * have been preprocessed, as well as any .class files that were compiled.
- */
-// private File buildFolder;
-
+ /** true if we've posted a "sketch disappeared" warning */
+ private boolean disappearedWarning;
/**
* Used by the command-line version to create a sketch object.
@@ -131,6 +125,7 @@ protected void load(String path) {
int suffixLength = mode.getDefaultExtension().length() + 1;
name = mainFilename.substring(0, mainFilename.length() - suffixLength);
folder = new File(new File(path).getParent());
+ disappearedWarning = false;
load();
}
@@ -153,16 +148,46 @@ protected void load() {
codeFolder = new File(folder, "code");
dataFolder = new File(folder, "data");
- // get list of files in the sketch folder
- String list[] = folder.list();
+ List filenames = new ArrayList<>();
+ List extensions = new ArrayList<>();
- // reset these because load() may be called after an
- // external editor event. (fix for 0099)
- codeCount = 0;
+ getSketchCodeFiles(filenames, extensions);
- code = new SketchCode[list.length];
+ codeCount = filenames.size();
+ code = new SketchCode[codeCount];
- String[] extensions = mode.getExtensions();
+ for (int i = 0; i < codeCount; i++) {
+ String filename = filenames.get(i);
+ String extension = extensions.get(i);
+ code[i] = new SketchCode(new File(folder, filename), extension);
+ }
+
+ // move the main class to the first tab
+ // start at 1, if it's at zero, don't bother
+ for (int i = 1; i < codeCount; i++) {
+ //if (code[i].file.getName().equals(mainFilename)) {
+ if (code[i].getFile().equals(primaryFile)) {
+ SketchCode temp = code[0];
+ code[0] = code[i];
+ code[i] = temp;
+ break;
+ }
+ }
+
+ // sort the entries at the top
+ sortCode();
+
+ // set the main file to be the current tab
+ if (editor != null) {
+ setCurrentCode(0);
+ }
+ }
+
+
+ public void getSketchCodeFiles(List outFilenames,
+ List outExtensions) {
+ // get list of files in the sketch folder
+ String list[] = folder.list();
for (String filename : list) {
// Ignoring the dot prefix files is especially important to avoid files
@@ -176,41 +201,19 @@ protected void load() {
// figure out the name without any extension
String base = filename;
// now strip off the .pde and .java extensions
- for (String extension : extensions) {
+ for (String extension : mode.getExtensions()) {
if (base.toLowerCase().endsWith("." + extension)) {
base = base.substring(0, base.length() - (extension.length() + 1));
// Don't allow people to use files with invalid names, since on load,
// it would be otherwise possible to sneak in nasty filenames. [0116]
if (isSanitaryName(base)) {
- code[codeCount++] =
- new SketchCode(new File(folder, filename), extension);
+ if (outFilenames != null) outFilenames.add(filename);
+ if (outExtensions != null) outExtensions.add(extension);
}
}
}
}
- // Remove any code that wasn't proper
- code = (SketchCode[]) PApplet.subset(code, 0, codeCount);
-
- // move the main class to the first tab
- // start at 1, if it's at zero, don't bother
- for (int i = 1; i < codeCount; i++) {
- //if (code[i].file.getName().equals(mainFilename)) {
- if (code[i].getFile().equals(primaryFile)) {
- SketchCode temp = code[0];
- code[0] = code[i];
- code[i] = temp;
- break;
- }
- }
-
- // sort the entries at the top
- sortCode();
-
- // set the main file to be the current tab
- if (editor != null) {
- setCurrentCode(0);
- }
}
@@ -227,6 +230,20 @@ public void reload() {
}
+ /**
+ * Load a tab that the user added to the sketch or modified with an external
+ * editor.
+ */
+ public void loadNewTab(String filename, String ext, boolean newAddition) {
+ if (newAddition) {
+ insertCode(new SketchCode(new File(folder, filename), ext));
+ } else {
+ replaceCode(new SketchCode(new File(folder, filename), ext));
+ }
+ sortCode();
+ }
+
+
protected void replaceCode(SketchCode newCode) {
for (int i = 0; i < codeCount; i++) {
if (code[i].getFileName().equals(newCode.getFileName())) {
@@ -264,6 +281,13 @@ protected void sortCode() {
SketchCode temp = code[who];
code[who] = code[i];
code[i] = temp;
+
+ // We also need to update the current tab
+ if (currentIndex == i) {
+ currentIndex = who;
+ } else if (currentIndex == who) {
+ currentIndex = i;
+ }
}
}
}
@@ -281,8 +305,8 @@ public void handleNewCode() {
// if read-only, give an error
if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
- Base.showMessage(Language.text("new.messages.is_read_only"),
- Language.text("new.messages.is_read_only.description"));
+ Messages.showMessage(Language.text("new.messages.is_read_only"),
+ Language.text("new.messages.is_read_only.description"));
return;
}
@@ -300,22 +324,22 @@ public void handleRenameCode() {
ensureExistence();
if (currentIndex == 0 && isUntitled()) {
- Base.showMessage(Language.text("rename.messages.is_untitled"),
- Language.text("rename.messages.is_untitled.description"));
+ Messages.showMessage(Language.text("rename.messages.is_untitled"),
+ Language.text("rename.messages.is_untitled.description"));
return;
}
if (isModified()) {
- Base.showMessage(Language.text("menu.file.save"),
- Language.text("rename.messages.is_modified"));
+ Messages.showMessage(Language.text("menu.file.save"),
+ Language.text("rename.messages.is_modified"));
return;
}
// if read-only, give an error
if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
- Base.showMessage(Language.text("rename.messages.is_read_only"),
- Language.text("rename.messages.is_read_only.description"));
+ Messages.showMessage(Language.text("rename.messages.is_read_only"),
+ Language.text("rename.messages.is_read_only.description"));
return;
}
@@ -323,31 +347,30 @@ public void handleRenameCode() {
// TODO maybe just popup a text area?
renamingCode = true;
String prompt = (currentIndex == 0) ?
- Language.text("editor.sketch.rename.description") : Language.text("editor.tab.rename.description");
+ Language.text("editor.sketch.rename.description") :
+ Language.text("editor.tab.rename.description");
String oldName = (current.isExtension(mode.getDefaultExtension())) ?
current.getPrettyName() : current.getFileName();
- // editor.status.edit(prompt, oldName);
- promptForTabName(prompt+":", oldName);
+ promptForTabName(prompt + ":", oldName);
}
-
+
+
/**
* Displays a dialog for renaming or creating a new tab
- * @param prompt - msg to display
- * @param oldName
*/
protected void promptForTabName(String prompt, String oldName) {
final JTextField field = new JTextField(oldName);
-
+
field.addKeyListener(new KeyAdapter() {
// Forget ESC, the JDialog should handle it.
- // Use keyTyped to catch when the feller is actually added to the text
- // field. With keyTyped, as opposed to keyPressed, the keyCode will be
- // zero, even if it's enter or backspace or whatever, so the keychar
+ // Use keyTyped to catch when the feller is actually added to the text
+ // field. With keyTyped, as opposed to keyPressed, the keyCode will be
+ // zero, even if it's enter or backspace or whatever, so the keychar
// should be used instead. Grr.
public void keyTyped(KeyEvent event) {
//System.out.println("got event " + event);
char ch = event.getKeyChar();
- if ((ch == '_') || (ch == '.') || // allow.pde and .java
+ if ((ch == '_') || (ch == '.') || // allow.pde and .java
(('A' <= ch) && (ch <= 'Z')) || (('a' <= ch) && (ch <= 'z'))) {
// These events are allowed straight through.
} else if (ch == ' ') {
@@ -363,13 +386,13 @@ public void keyTyped(KeyEvent event) {
// getSelectionStart means that it *will be* the first
// char, because the selection is about to be replaced
// with whatever is typed.
- if (field.getCaretPosition() == 0 ||
+ if (field.getCaretPosition() == 0 ||
field.getSelectionStart() == 0) {
// number not allowed as first digit
event.consume();
}
} else if (ch == KeyEvent.VK_ENTER) {
- // Slightly ugly hack that ensures OK button of the dialog consumes
+ // Slightly ugly hack that ensures OK button of the dialog consumes
// the Enter key event. Since the text field is the default component
// in the dialog, OK doesn't consume Enter key event, by default.
Container parent = field.getParent();
@@ -377,14 +400,14 @@ public void keyTyped(KeyEvent event) {
parent = parent.getParent();
}
JOptionPane pane = (JOptionPane) parent;
- final JPanel pnlBottom = (JPanel)
+ final JPanel pnlBottom = (JPanel)
pane.getComponent(pane.getComponentCount() - 1);
for (int i = 0; i < pnlBottom.getComponents().length; i++) {
Component component = pnlBottom.getComponents()[i];
if (component instanceof JButton) {
final JButton okButton = (JButton) component;
if (okButton.getText().equalsIgnoreCase("OK")) {
- ActionListener[] actionListeners =
+ ActionListener[] actionListeners =
okButton.getActionListeners();
if (actionListeners.length > 0) {
actionListeners[0].actionPerformed(null);
@@ -405,8 +428,8 @@ public void keyTyped(KeyEvent event) {
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE,
null, new Object[] {
- Preferences.PROMPT_OK,
- Preferences.PROMPT_CANCEL },
+ Language.getPrompt("ok"),
+ Language.getPrompt("cancel") },
field);
if (userReply == JOptionPane.OK_OPTION) {
@@ -448,17 +471,17 @@ protected void nameCode(String newName) {
}
if (newName.startsWith(".")) {
- Base.showWarning("Problem with rename",
- "The name cannot start with a period.");
+ Messages.showWarning(Language.text("name.messages.problem_renaming"),
+ Language.text("name.messages.starts_with_dot.description"));
return;
}
int dot = newName.lastIndexOf('.');
String newExtension = newName.substring(dot+1).toLowerCase();
if (!mode.validExtension(newExtension)) {
- Base.showWarning("Problem with rename",
- "\"." + newExtension + "\"" +
- "is not a valid extension.");
+ Messages.showWarning(Language.text("name.messages.problem_renaming"),
+ Language.interpolate("name.messages.invalid_extension.description",
+ newExtension));
return;
}
@@ -466,10 +489,9 @@ protected void nameCode(String newName) {
if (!mode.isDefaultExtension(newExtension)) {
if (renamingCode) { // If creating a new tab, don't show this error
if (current == code[0]) { // If this is the main tab, disallow
- Base.showWarning("Problem with rename",
- "The first tab cannot be a ." + newExtension + " file.\n" +
- "(It may be time for you to graduate to a\n" +
- "\"real\" programming environment, hotshot.)");
+ Messages.showWarning(Language.text("name.messages.problem_renaming"),
+ Language.interpolate("name.messages.main_java_extension.description",
+ newExtension));
return;
}
}
@@ -493,9 +515,9 @@ protected void nameCode(String newName) {
// http://processing.org/bugs/bugzilla/543.html
for (SketchCode c : code) {
if (c != current && sanitaryName.equalsIgnoreCase(c.getPrettyName())) {
- Base.showMessage("Nope",
- "A file named \"" + c.getFileName() + "\" already exists at\n" +
- "\"" + folder.getAbsolutePath() + "\"");
+ Messages.showMessage(Language.text("name.messages.new_sketch_exists"),
+ Language.interpolate("name.messages.new_sketch_exists.description",
+ c.getFileName(), folder.getAbsolutePath()));
return;
}
}
@@ -509,16 +531,17 @@ protected void nameCode(String newName) {
String folderName = newName.substring(0, newName.indexOf('.'));
File newFolder = new File(folder.getParentFile(), folderName);
if (newFolder.exists()) {
- Base.showWarning("Cannot Rename",
- "Sorry, a sketch (or folder) named " +
- "\"" + newName + "\" already exists.");
+ Messages.showWarning(Language.text("name.messages.new_folder_exists"),
+ Language.interpolate("name.messages.new_folder_exists.description",
+ newName));
return;
}
// renaming the containing sketch folder
boolean success = folder.renameTo(newFolder);
if (!success) {
- Base.showWarning("Error", "Could not rename the sketch folder.");
+ Messages.showWarning(Language.text("name.messages.error"),
+ Language.text("name.messages.no_rename_folder.description"));
return;
}
// let this guy know where he's living (at least for a split second)
@@ -538,9 +561,9 @@ protected void nameCode(String newName) {
// This isn't changing folders, just changes the name
newFile = new File(newFolder, newName);
if (!current.renameTo(newFile, newExtension)) {
- Base.showWarning("Error",
- "Could not rename \"" + current.getFileName() +
- "\" to \"" + newFile.getName() + "\"");
+ Messages.showWarning(Language.text("name.messages.error"),
+ Language.interpolate("name.messages.no_rename_file.description",
+ current.getFileName(), newFile.getName()));
return;
}
@@ -550,7 +573,7 @@ protected void nameCode(String newName) {
code[i].setFolder(newFolder);
}
// Update internal state to reflect the new location
- updateInternal(sanitaryName, newFolder);
+ updateInternal(sanitaryName, newFolder, renamingCode);
// File newMainFile = new File(newFolder, newName + ".pde");
// String newMainFilePath = newMainFile.getAbsolutePath();
@@ -570,9 +593,9 @@ protected void nameCode(String newName) {
} else { // else if something besides code[0]
if (!current.renameTo(newFile, newExtension)) {
- Base.showWarning("Error",
- "Could not rename \"" + current.getFileName() +
- "\" to \"" + newFile.getName() + "\"");
+ Messages.showWarning(Language.text("name.messages.error"),
+ Language.interpolate("name.messages.no_rename_file.description",
+ current.getFileName(), newFile.getName()));
return;
}
}
@@ -584,9 +607,9 @@ protected void nameCode(String newName) {
throw new IOException("createNewFile() returned false");
}
} catch (IOException e) {
- Base.showWarning("Error",
- "Could not create the file \"" + newFile + "\"\n" +
- "in \"" + folder.getAbsolutePath() + "\"", e);
+ Messages.showWarning(Language.text("name.messages.error"),
+ Language.interpolate("name.messages.no_create_file.description",
+ newFile, folder.getAbsolutePath()), e);
return;
}
SketchCode newCode = new SketchCode(newFile, newExtension);
@@ -601,7 +624,7 @@ protected void nameCode(String newName) {
setCurrentCode(newName);
// update the tabs
- editor.header.rebuild();
+ editor.rebuildHeader();
}
@@ -615,18 +638,18 @@ public void handleDeleteCode() {
// if read-only, give an error
if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
- Base.showMessage(Language.text("delete.messages.is_read_only"),
- Language.text("delete.messages.is_read_only.description"));
+ Messages.showMessage(Language.text("delete.messages.is_read_only"),
+ Language.text("delete.messages.is_read_only.description"));
return;
}
// don't allow if untitled
- if (currentIndex == 0 && isUntitled()) {
- Base.showMessage(Language.text("delete.messages.cannot_delete"),
- Language.text("delete.messages.cannot_delete.description"));
+ if (currentIndex == 0 && isUntitled()) {
+ Messages.showMessage(Language.text("delete.messages.cannot_delete"),
+ Language.text("delete.messages.cannot_delete.description"));
return;
}
-
+
// confirm deletion with user, yes/no
Object[] options = { Language.text("prompt.ok"), Language.text("prompt.cancel") };
String prompt = (currentIndex == 0) ?
@@ -646,39 +669,45 @@ public void handleDeleteCode() {
// to do a save on the handleNew()
// delete the entire sketch
- Base.removeDir(folder);
+ Util.removeDir(folder);
// get the changes into the sketchbook menu
//sketchbook.rebuildMenus();
- // make a new sketch, and i think this will rebuild the sketch menu
+ // make a new sketch and rebuild the sketch menu
//editor.handleNewUnchecked();
//editor.handleClose2();
- editor.base.handleClose(editor, false);
+ editor.getBase().rebuildSketchbookMenus();
+ editor.getBase().handleClose(editor, false);
} else {
// delete the file
if (!current.deleteFile()) {
- Base.showMessage(Language.text("delete.messages.cannot_delete.file"),
- Language.text("delete.messages.cannot_delete.file.description")+" \"" +
- current.getFileName() + "\".");
+ Messages.showMessage(Language.text("delete.messages.cannot_delete.file"),
+ Language.text("delete.messages.cannot_delete.file.description")+" \"" +
+ current.getFileName() + "\".");
return;
}
// remove code from the list
removeCode(current);
+ // update the tabs
+ editor.rebuildHeader();
+
// just set current tab to the main tab
setCurrentCode(0);
- // update the tabs
- editor.header.repaint();
}
}
}
- protected void removeCode(SketchCode which) {
+ /**
+ * Remove a SketchCode from the list of files without deleting its file.
+ * @see #handleDeleteCode()
+ */
+ public void removeCode(SketchCode which) {
// remove it from the internal list of files
// resort internal list of files
for (int i = 0; i < codeCount; i++) {
@@ -734,9 +763,9 @@ protected void calcModified() {
break;
}
}
- editor.header.repaint();
+ editor.repaintHeader();
- if (Base.isMacOS()) {
+ if (Platform.isMacOS()) {
// http://developer.apple.com/qa/qa2001/qa1146.html
Object modifiedParam = modified ? Boolean.TRUE : Boolean.FALSE;
// https://developer.apple.com/library/mac/technotes/tn2007/tn2196.html#WINDOW_DOCUMENTMODIFIED
@@ -750,6 +779,16 @@ public boolean isModified() {
}
+ /**
+ * Ensure that all SketchCodes are up-to-date, so that sc.save() works.
+ */
+ public void updateSketchCodes() {
+// if (current.isModified()) {
+ current.setProgram(editor.getText());
+// }
+ }
+
+
/**
* Save all code in the current sketch. This just forces the files to save
* in place, so if it's an untitled (un-saved) sketch, saveAs() should be
@@ -760,24 +799,23 @@ public boolean save() throws IOException {
ensureExistence();
// first get the contents of the editor text area
-// if (current.isModified()) {
- current.setProgram(editor.getText());
-// }
+ updateSketchCodes();
// don't do anything if not actually modified
//if (!modified) return false;
if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
- Base.showMessage("Sketch is read-only",
- "Some files are marked \"read-only\", so you'll\n" +
- "need to re-save this sketch to another location.");
+ Messages.showMessage(Language.text("save_file.messages.is_read_only"),
+ Language.text("save_file.messages.is_read_only.description"));
// if the user cancels, give up on the save()
if (!saveAs()) return false;
}
- for (int i = 0; i < codeCount; i++) {
- if (code[i].isModified()) code[i].save();
+ for (SketchCode sc : code) {
+ if (sc.isModified()) {
+ sc.save();
+ }
}
calcModified();
return true;
@@ -795,12 +833,13 @@ public boolean save() throws IOException {
* Also removes the previously-generated .class and .jar files,
* because they can cause trouble.
*/
- protected boolean saveAs() throws IOException {
+ public boolean saveAs() throws IOException {
String newParentDir = null;
String newName = null;
-
- final String oldName2 = folder.getName();
- // TODO rewrite this to use shared version from PApplet
+ String oldName = folder.getName();
+
+ // TODO rewrite this to use shared version from PApplet (But because that
+ // specifies a callback function, this needs to wait until the refactoring)
final String PROMPT = Language.text("save");
if (Preferences.getBoolean("chooser.files.native")) {
// get new name for folder
@@ -812,8 +851,8 @@ protected boolean saveAs() throws IOException {
// default to the parent folder of where this was
fd.setDirectory(folder.getParent());
}
- String oldName = folder.getName();
- fd.setFile(oldName);
+ String oldFolderName = folder.getName();
+ fd.setFile(oldFolderName);
fd.setVisible(true);
newParentDir = fd.getDirectory();
newName = fd.getFile();
@@ -844,9 +883,9 @@ protected boolean saveAs() throws IOException {
String sanitaryName = Sketch.checkName(newName);
File newFolder = new File(newParentDir, sanitaryName);
if (!sanitaryName.equals(newName) && newFolder.exists()) {
- Base.showMessage("Cannot Save",
- "A sketch with the cleaned name\n" +
- "“" + sanitaryName + "” already exists.");
+ Messages.showMessage(Language.text("save_file.messages.sketch_exists"),
+ Language.interpolate("save_file.messages.sketch_exists.description",
+ sanitaryName));
return false;
}
newName = sanitaryName;
@@ -863,9 +902,9 @@ protected boolean saveAs() throws IOException {
// resaved (with the same name) to another location/folder.
for (int i = 1; i < codeCount; i++) {
if (newName.equalsIgnoreCase(code[i].getPrettyName())) {
- Base.showMessage("Nope",
- "You can't save the sketch as \"" + newName + "\"\n" +
- "because the sketch already has a tab with that name.");
+ Messages.showMessage(Language.text("save_file.messages.tab_exists"),
+ Language.interpolate("save_file.messages.tab_exists.description",
+ newName));
return false;
}
}
@@ -875,7 +914,7 @@ protected boolean saveAs() throws IOException {
// just use "save" here instead, because the user will have received a
// message (from the operating system) about "do you want to replace?"
return save();
- }
+ }
// check to see if the user is trying to save this sketch inside itself
try {
@@ -883,9 +922,8 @@ protected boolean saveAs() throws IOException {
String oldPath = folder.getCanonicalPath() + File.separator;
if (newPath.indexOf(oldPath) == 0) {
- Base.showWarning("How very Borges of you",
- "You cannot save the sketch into a folder\n" +
- "inside itself. This would go on forever.", null);
+ Messages.showWarning(Language.text("save_file.messages.recursive_save"),
+ Language.text("save_file.messages.recursive_save.description"));
return false;
}
} catch (IOException e) { }
@@ -893,7 +931,7 @@ protected boolean saveAs() throws IOException {
// if the new folder already exists, then first remove its contents before
// copying everything over (user will have already been warned).
if (newFolder.exists()) {
- Base.removeDir(newFolder);
+ Util.removeDir(newFolder);
}
// in fact, you can't do this on Windows because the file dialog
// will instead put you inside the folder, but it happens on OS X a lot.
@@ -903,9 +941,7 @@ protected boolean saveAs() throws IOException {
// grab the contents of the current tab before saving
// first get the contents of the editor text area
- if (current.isModified()) {
- current.setProgram(editor.getText());
- }
+ updateSketchCodes();
File[] copyItems = folder.listFiles(new FileFilter() {
public boolean accept(File file) {
@@ -915,9 +951,12 @@ public boolean accept(File file) {
return false;
}
// list of files/folders to be ignored during "save as"
- for (String ignorable : mode.getIgnorable()) {
- if (name.equals(ignorable)) {
- return false;
+ String[] ignorable = mode.getIgnorable();
+ if (ignorable != null) {
+ for (String ignore : ignorable) {
+ if (name.equals(ignore)) {
+ return false;
+ }
}
}
// ignore the extensions for code, since that'll be copied below
@@ -934,22 +973,10 @@ public boolean accept(File file) {
return true;
}
});
-
-
- final File newFolder2 = newFolder;
- final File[] copyItems2 = copyItems;
- final String newName2 = newName;
-
- // Create a new event dispatch thread- to display ProgressBar
- // while Saving As
- javax.swing.SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- new ProgressFrame(copyItems2, newFolder2, oldName2, newName2, editor);
- }
- });
-
-
- // save the other tabs to their new location
+
+ startSaveAsThread(oldName, newName, newFolder, copyItems);
+
+ // save the other tabs to their new location (main tab saved below)
for (int i = 1; i < codeCount; i++) {
File newFile = new File(newFolder, code[i].getFileName());
code[i].saveAs(newFile);
@@ -958,40 +985,225 @@ public void run() {
// While the old path to the main .pde is still set, remove the entry from
// the Recent menu so that it's not sticking around after the rename.
// If untitled, it won't be in the menu, so there's no point.
- if (!isUntitled()) {
- editor.removeRecent();
- }
+// if (!isUntitled()) {
+// Recent.remove(editor);
+// }
+ // Folks didn't like this behavior, so shutting it off
+ // https://github.com/processing/processing/issues/5902
// save the main tab with its new name
File newFile = new File(newFolder, newName + "." + mode.getDefaultExtension());
code[0].saveAs(newFile);
- updateInternal(newName, newFolder);
+ updateInternal(newName, newFolder, false);
// Make sure that it's not an untitled sketch
setUntitled(false);
// Add this sketch back using the new name
- editor.addRecent();
+ Recent.append(editor);
// let Editor know that the save was successful
return true;
}
+ AtomicBoolean saving = new AtomicBoolean();
+
+ public boolean isSaving() {
+ return saving.get();
+ }
+
+
+ /**
+ * Kick off a background thread to copy everything *but* the .pde files.
+ * Due to the poor way (dating back to the late 90s with DBN) that our
+ * save() and saveAs() methods have been implemented to return booleans,
+ * there isn't a good way to return a value to the calling thread without
+ * a good bit of refactoring (that should be done at some point).
+ * As a result, this method will return 'true' before the full "Save As"
+ * has completed, which will cause problems in weird cases.
+ *
+ * For instance, the threading will cause problems while saving an untitled
+ * sketch that has an enormous data folder while quitting. The save thread to
+ * move those data folder files won't have finished before this returns true,
+ * and the PDE may quit before the SwingWorker completes its job.
+ *
+ * 3843
+ */
+ void startSaveAsThread(final String oldName, final String newName,
+ final File newFolder, final File[] copyItems) {
+ saving.set(true);
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ final JFrame frame =
+ new JFrame("Saving \u201C" + newName + "\u201C...");
+ frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
+
+ Box box = Box.createVerticalBox();
+ box.setBorder(new EmptyBorder(16, 16, 16, 16));
+
+ if (Platform.isMacOS()) {
+ frame.setBackground(Color.WHITE);
+ }
+
+ JLabel label =
+ new JLabel("Saving additional files from the sketch folder...");
+ box.add(label);
+ box.add(Box.createVerticalStrut(8));
+
+ final JProgressBar progressBar = new JProgressBar(0, 100);
+ // no luck, stuck with ugly on OS X
+ //progressBar.putClientProperty("JComponent.sizeVariant", "regular");
+ progressBar.setValue(0);
+ progressBar.setStringPainted(true);
+ box.add(progressBar);
+
+ frame.getContentPane().add(box);
+ frame.pack();
+ frame.setLocationRelativeTo(editor);
+ Toolkit.setIcon(frame);
+ frame.setVisible(true);
+
+ new SwingWorker() {
+
+ @Override
+ protected Void doInBackground() throws Exception {
+ addPropertyChangeListener(new PropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent evt) {
+ if ("progress".equals(evt.getPropertyName())) {
+ progressBar.setValue((Integer) evt.getNewValue());
+ }
+ }
+ });
+
+ long totalSize = 0;
+ for (File copyable : copyItems) {
+ totalSize += Util.calcSize(copyable);
+ }
+
+ long progress = 0;
+ setProgress(0);
+ for (File copyable : copyItems) {
+ if (copyable.isDirectory()) {
+ copyDir(copyable,
+ new File(newFolder, copyable.getName()),
+ progress, totalSize);
+ progress += Util.calcSize(copyable);
+ } else {
+ copyFile(copyable,
+ new File(newFolder, copyable.getName()),
+ progress, totalSize);
+ if (Util.calcSize(copyable) < 512 * 1024) {
+ // If the file length > 0.5MB, the copyFile() function has
+ // been redesigned to change progress every 0.5MB so that
+ // the progress bar doesn't stagnate during that time
+ progress += Util.calcSize(copyable);
+ setProgress((int) (progress * 100L / totalSize));
+ }
+ }
+ }
+ saving.set(false);
+ return null;
+ }
+
+
+ /**
+ * Overloaded copyFile that is called whenever a Save As is being done,
+ * so that the ProgressBar is updated for very large files as well.
+ */
+ void copyFile(File sourceFile, File targetFile,
+ long progress, long totalSize) throws IOException {
+ BufferedInputStream from =
+ new BufferedInputStream(new FileInputStream(sourceFile));
+ BufferedOutputStream to =
+ new BufferedOutputStream(new FileOutputStream(targetFile));
+ byte[] buffer = new byte[16 * 1024];
+ int bytesRead;
+ int progRead = 0;
+ while ((bytesRead = from.read(buffer)) != -1) {
+ to.write(buffer, 0, bytesRead);
+ progRead += bytesRead;
+ if (progRead >= 512 * 1024) { // to update progress bar every 0.5MB
+ progress += progRead;
+ //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
+ setProgress((int) (100L * progress / totalSize));
+ progRead = 0;
+ }
+ }
+ // Final update to progress bar
+ setProgress((int) (100L * progress / totalSize));
+
+ from.close();
+ from = null;
+ to.flush();
+ to.close();
+ to = null;
+
+ targetFile.setLastModified(sourceFile.lastModified());
+ targetFile.setExecutable(sourceFile.canExecute());
+ }
+
+
+ long copyDir(File sourceDir, File targetDir,
+ long progress, long totalSize) throws IOException {
+ // Overloaded copyDir so that the Save As progress bar gets updated when the
+ // files are in folders as well (like in the data folder)
+ if (sourceDir.equals(targetDir)) {
+ final String urDum = "source and target directories are identical";
+ throw new IllegalArgumentException(urDum);
+ }
+ targetDir.mkdirs();
+ String files[] = sourceDir.list();
+ for (String filename : files) {
+ // Ignore dot files (.DS_Store), dot folders (.svn) while copying
+ if (filename.charAt(0) == '.') {
+ continue;
+ }
+
+ File source = new File(sourceDir, filename);
+ File target = new File(targetDir, filename);
+ if (source.isDirectory()) {
+ progress = copyDir(source, target, progress, totalSize);
+ //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
+ setProgress((int) (100L * progress / totalSize));
+ target.setLastModified(source.lastModified());
+ } else {
+ copyFile(source, target, progress, totalSize);
+ progress += source.length();
+ //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
+ setProgress((int) (100L * progress / totalSize));
+ }
+ }
+ return progress;
+ }
+
+
+ @Override
+ public void done() {
+ frame.dispose();
+ editor.statusNotice(Language.text("editor.status.saving.done"));
+ }
+ }.execute();
+ }
+ });
+ }
+
/**
* Update internal state for new sketch name or folder location.
*/
- protected void updateInternal(String sketchName, File sketchFolder) {
+ protected void updateInternal(String sketchName, File sketchFolder,
+ boolean renaming) {
// reset all the state information for the sketch object
- String oldPath = getMainFilePath();
+ String oldPath = getMainFilePath();
primaryFile = code[0].getFile();
// String newPath = getMainFilePath();
// editor.base.renameRecent(oldPath, newPath);
name = sketchName;
folder = sketchFolder;
+ disappearedWarning = false;
codeFolder = new File(folder, "code");
dataFolder = new File(folder, "data");
@@ -1003,8 +1215,12 @@ protected void updateInternal(String sketchName, File sketchFolder) {
calcModified();
// System.out.println("modified is now " + modified);
editor.updateTitle();
- editor.base.rebuildSketchbookMenus();
- editor.base.handleRecentRename(editor,oldPath);
+ editor.getBase().rebuildSketchbookMenus();
+ if (renaming) {
+ // only update the Recent menu if it's a rename, not a Save As
+ // https://github.com/processing/processing/issues/5902
+ Recent.rename(editor, oldPath);
+ }
// editor.header.rebuild();
}
@@ -1020,10 +1236,8 @@ public void handleAddFile() {
// if read-only, give an error
if (isReadOnly()) {
// if the files are read-only, need to first do a "save as".
- Base.showMessage("Sketch is Read-Only",
- "Some files are marked \"read-only\", so you'll\n" +
- "need to re-save the sketch in another location,\n" +
- "and try again.");
+ Messages.showMessage(Language.text("add_file.messages.is_read_only"),
+ Language.text("add_file.messages.is_read_only.description"));
return;
}
@@ -1070,18 +1284,21 @@ public boolean addFile(File sourceFile) {
String codeExtension = null;
boolean replacement = false;
+ boolean isCode = false;
+
// if the file appears to be code related, drop it
// into the code folder, instead of the data folder
if (filename.toLowerCase().endsWith(".class") ||
filename.toLowerCase().endsWith(".jar") ||
filename.toLowerCase().endsWith(".dll") ||
+ filename.toLowerCase().endsWith(".dylib") ||
filename.toLowerCase().endsWith(".jnilib") ||
filename.toLowerCase().endsWith(".so")) {
//if (!codeFolder.exists()) codeFolder.mkdirs();
prepareCodeFolder();
destFile = new File(codeFolder, filename);
-
+ isCode = true;
} else {
for (String extension : mode.getExtensions()) {
String lower = filename.toLowerCase();
@@ -1099,7 +1316,8 @@ public boolean addFile(File sourceFile) {
// check whether this file already exists
if (destFile.exists()) {
Object[] options = { Language.text("prompt.ok"), Language.text("prompt.cancel") };
- String prompt = "Replace the existing version of " + filename + "?";
+ String prompt = Language.interpolate("add_file.messages.confirm_replace",
+ filename);
int result = JOptionPane.showOptionDialog(editor,
prompt,
"Replace",
@@ -1121,35 +1339,38 @@ public boolean addFile(File sourceFile) {
if (replacement) {
boolean muchSuccess = destFile.delete();
if (!muchSuccess) {
- Base.showWarning("Error adding file",
- "Could not delete the existing '" +
- filename + "' file.", null);
+ Messages.showWarning(Language.text("add_file.messages.error_adding"),
+ Language.interpolate("add_file.messages.cannot_delete.description", filename));
return false;
}
}
// make sure they aren't the same file
if ((codeExtension == null) && sourceFile.equals(destFile)) {
- Base.showWarning("You can't fool me",
- "This file has already been copied to the\n" +
- "location from which where you're trying to add it.\n" +
- "I ain't not doin nuthin'.", null);
+ Messages.showWarning(Language.text("add_file.messages.same_file"),
+ Language.text("add_file.messages.same_file.description"));
return false;
}
- // in case the user is "adding" the code in an attempt
- // to update the sketch's tabs
- if (!sourceFile.equals(destFile)) {
- final File sourceFile2 = sourceFile;
- final File destFile2 = destFile;
- // Create a new event dispatch thread- to display ProgressBar
- // while Saving As
- javax.swing.SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- new ProgressFrame(sourceFile2, destFile2, editor);
+ // Handles "Add File" when a .pde is used. For beta 1, this no longer runs
+ // on a separate thread because it's totally unnecessary (a .pde file is
+ // not going to be so large that it's ever required) and otherwise we have
+ // to introduce a threading block here.
+ // https://github.com/processing/processing/issues/3383
+ if (!sourceFile.equals(destFile)) {
+ try {
+ Util.copyFile(sourceFile, destFile);
+
+ } catch (IOException e) {
+ Messages.showWarning(Language.text("add_file.messages.error_adding"),
+ Language.interpolate("add_file.messages.cannot_add.description", filename), e);
+ return false;
}
- });
- }
+ }
+
+ if (isCode) {
+ editor.codeFolderChanged();
+ }
if (codeExtension != null) {
SketchCode newCode = new SketchCode(destFile, codeExtension);
@@ -1162,7 +1383,7 @@ public void run() {
sortCode();
}
setCurrentCode(filename);
- editor.header.repaint();
+ editor.repaintHeader();
if (isUntitled()) { // TODO probably not necessary? problematic?
// Mark the new code as modified so that the sketch is saved
current.setModified(true);
@@ -1194,8 +1415,8 @@ public void setCurrentCode(int which) {
// System.out.println(current.visited);
// }
// if current is null, then this is the first setCurrent(0)
- if (((currentIndex == which) && (current != null))
- || which >= codeCount || which < 0) {
+ if (which < 0 || which >= codeCount ||
+ ((currentIndex == which) && (current == code[currentIndex]))) {
return;
}
@@ -1212,8 +1433,7 @@ public void setCurrentCode(int which) {
current.visited = System.currentTimeMillis();
editor.setCode(current);
-// editor.header.rebuild();
- editor.header.repaint();
+ editor.repaintHeader();
}
@@ -1221,7 +1441,7 @@ public void setCurrentCode(int which) {
* Internal helper function to set the current tab based on a name.
* @param findName the file name (not pretty name) to be shown
*/
- protected void setCurrentCode(String findName) {
+ public void setCurrentCode(String findName) {
for (int i = 0; i < codeCount; i++) {
if (findName.equals(code[i].getFileName()) ||
findName.equals(code[i].getPrettyName())) {
@@ -1237,17 +1457,11 @@ protected void setCurrentCode(String findName) {
*/
public File makeTempFolder() {
try {
- File buildFolder = Base.createTempFolder(name, "temp", null);
-// if (buildFolder.mkdirs()) {
- return buildFolder;
-
-// } else {
-// Base.showWarning("Build folder bad",
-// "Could not create a place to build the sketch.", null);
-// }
+ return Util.createTempFolder(name, "temp", null);
+
} catch (IOException e) {
- Base.showWarning("Build folder bad",
- "Could not find a place to build the sketch.", e);
+ Messages.showWarning(Language.text("temp_dir.messages.bad_build_folder"),
+ Language.text("temp_dir.messages.bad_build_folder.description"), e);
}
return null;
}
@@ -1295,33 +1509,34 @@ public void prepareBuild(File targetFolder) throws SketchException {
/**
- * Make sure the sketch hasn't been moved or deleted by some
- * nefarious user. If they did, try to re-create it and save.
- * Only checks to see if the main folder is still around,
- * but not its contents.
+ * Make sure the sketch hasn't been moved or deleted by a nefarious user.
+ * If they did, try to re-create it and save. Only checks whether the
+ * main folder is still around, but not its contents.
*/
public void ensureExistence() {
if (!folder.exists()) {
- // Disaster recovery, try to salvage what's there already.
- Base.showWarning("Sketch Disappeared",
- "The sketch folder has disappeared.\n " +
- "Will attempt to re-save in the same location,\n" +
- "but anything besides the code will be lost.", null);
- try {
- folder.mkdirs();
- modified = true;
+ // Avoid an infinite loop if we've already warned about this
+ // https://github.com/processing/processing/issues/4805
+ if (!disappearedWarning) {
+ disappearedWarning = true;
+
+ // Disaster recovery, try to salvage what's there already.
+ Messages.showWarning(Language.text("ensure_exist.messages.missing_sketch"),
+ Language.text("ensure_exist.messages.missing_sketch.description"));
+ try {
+ folder.mkdirs();
+ modified = true;
+
+ for (int i = 0; i < codeCount; i++) {
+ code[i].save(); // this will force a save
+ }
+ calcModified();
- for (int i = 0; i < codeCount; i++) {
- code[i].save(); // this will force a save
+ } catch (Exception e) {
+ // disappearedWarning prevents infinite loop in this scenario
+ Messages.showWarning(Language.text("ensure_exist.messages.unrecoverable"),
+ Language.text("ensure_exist.messages.unrecoverable.description"), e);
}
- calcModified();
-
- } catch (Exception e) {
- Base.showWarning("Could not re-save sketch",
- "Could not properly re-save the sketch. " +
- "You may be in trouble at this point,\n" +
- "and it might be time to copy and paste " +
- "your code to another text editor.", e);
}
}
}
@@ -1334,25 +1549,27 @@ public void ensureExistence() {
*/
public boolean isReadOnly() {
String apath = folder.getAbsolutePath();
- Mode mode = editor.getMode();
- if (apath.startsWith(mode.getExamplesFolder().getAbsolutePath()) ||
- apath.startsWith(mode.getLibrariesFolder().getAbsolutePath())) {
- return true;
+ List modes = editor.getBase().getModeList();
+ // Make sure it's not read-only for another Mode besides this one
+ // https://github.com/processing/processing/issues/773
+ for (Mode mode : modes) {
+ if (apath.startsWith(mode.getExamplesFolder().getAbsolutePath()) ||
+ apath.startsWith(mode.getLibrariesFolder().getAbsolutePath())) {
+ return true;
+ }
+ }
- // canWrite() doesn't work on directories
- //} else if (!folder.canWrite()) {
- } else {
- // check to see if each modified code file can be written to
- for (int i = 0; i < codeCount; i++) {
- if (code[i].isModified() &&
- code[i].fileReadOnly() &&
- code[i].fileExists()) {
- //System.err.println("found a read-only file " + code[i].file);
- return true;
- }
+ // check to see if each modified code file can be written to
+ // canWrite() doesn't work on directories
+ for (int i = 0; i < codeCount; i++) {
+ if (code[i].isModified() &&
+ code[i].fileReadOnly() &&
+ code[i].fileExists()) {
+ //System.err.println("found a read-only file " + code[i].file);
+ return true;
}
- //return true;
}
+
return false;
}
@@ -1520,9 +1737,7 @@ static public String checkName(String origName) {
if (!newName.equals(origName)) {
String msg =
- "The sketch name had to be modified. Sketch names can only consist\n" +
- "of ASCII characters and numbers (but cannot start with a number).\n" +
- "They should also be less than 64 characters long.";
+ Language.text("check_name.messages.is_name_modified");
System.out.println(msg);
}
return newName;
diff --git a/app/src/processing/app/SketchCode.java b/app/src/processing/app/SketchCode.java
index 38399cd045..59ed06652e 100644
--- a/app/src/processing/app/SketchCode.java
+++ b/app/src/processing/app/SketchCode.java
@@ -26,6 +26,7 @@
import java.io.*;
+import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.undo.*;
@@ -54,7 +55,7 @@ public class SketchCode {
/** Last time this tab was visited */
long visited;
-
+
/** The last time this tab was saved to disk */
private long lastModified;
@@ -136,7 +137,7 @@ protected boolean renameTo(File what, String ext) {
public void copyTo(File dest) throws IOException {
- Base.saveFile(program, dest);
+ Util.saveFile(program, dest);
}
@@ -179,7 +180,7 @@ public String getSavedProgram() {
public int getLineCount() {
- return Base.countLines(program);
+ return Util.countLines(program);
}
@@ -223,6 +224,11 @@ public Document getDocument() {
}
+ public String getDocumentText() throws BadLocationException {
+ return document.getText(0, document.getLength());
+ }
+
+
public void setDocument(Document d) {
document = d;
}
@@ -275,7 +281,13 @@ public long lastVisited() {
* Load this piece of code from a file.
*/
public void load() throws IOException {
- program = Base.loadFile(file);
+ program = Util.loadFile(file);
+
+ if (program == null) {
+ System.err.println("There was a problem loading " + file);
+ System.err.println("This may happen because you don't have permissions to read the file, or the file has gone missing.");
+ throw new IOException("Cannot read or access " + file);
+ }
// Remove NUL characters because they'll cause problems,
// and their presence is very difficult to debug.
@@ -286,7 +298,7 @@ public void load() throws IOException {
savedProgram = program;
// This used to be the "Fix Encoding and Reload" warning, but since that
- // tool has been removed, it just rambles about text editors and encodings.
+ // tool has been removed, let's ramble about text editors and encodings.
if (program.indexOf('\uFFFD') != -1) {
System.err.println(file.getName() + " contains unrecognized characters.");
System.err.println("You should re-open " + file.getName() +
@@ -296,7 +308,7 @@ public void load() throws IOException {
System.err.println();
}
- lastModified = file.lastModified();
+ setLastModified();
setModified(false);
}
@@ -309,7 +321,7 @@ public void save() throws IOException {
// TODO re-enable history
//history.record(s, SketchHistory.SAVE);
- Base.saveFile(program, file);
+ Util.saveFile(program, file);
savedProgram = program;
lastModified = file.lastModified();
setModified(false);
@@ -320,11 +332,11 @@ public void save() throws IOException {
* Save this file to another location, used by Sketch.saveAs()
*/
public void saveAs(File newFile) throws IOException {
- Base.saveFile(program, newFile);
+ Util.saveFile(program, newFile);
savedProgram = program;
file = newFile;
makePrettyName();
- lastModified = file.lastModified();
+ setLastModified();
setModified(false);
}
@@ -336,12 +348,22 @@ public void saveAs(File newFile) throws IOException {
public void setFolder(File sketchFolder) {
file = new File(sketchFolder, file.getName());
}
-
+
+
+ /**
+ * Set the last known modification time, so that we're not re-firing
+ * "hey, this is modified!" events incessantly.
+ */
+ public void setLastModified() {
+ lastModified = file.lastModified();
+ }
+
+
/**
* Used to determine whether this file was modified externally
* @return The time the file was last modified
*/
- public long lastModified(){
+ public long getLastModified() {
return lastModified;
}
}
diff --git a/app/src/processing/app/SketchException.java b/app/src/processing/app/SketchException.java
index e2d6eeef35..4a32d2e79d 100644
--- a/app/src/processing/app/SketchException.java
+++ b/app/src/processing/app/SketchException.java
@@ -28,33 +28,35 @@
* An exception with a line number attached that occurs
* during either pre-processing, compile, or run time.
*/
-public class SketchException extends Exception /*RuntimeException*/ {
+public class SketchException extends Exception {
protected String message;
protected int codeIndex;
protected int codeLine;
protected int codeColumn;
protected boolean showStackTrace;
-
+
public SketchException(String message) {
this(message, true);
}
+
public SketchException(String message, boolean showStackTrace) {
this(message, -1, -1, -1, showStackTrace);
}
+
public SketchException(String message, int file, int line) {
this(message, file, line, -1, true);
}
-
+
public SketchException(String message, int file, int line, int column) {
this(message, file, line, column, true);
}
-
-
- public SketchException(String message, int file, int line, int column,
+
+
+ public SketchException(String message, int file, int line, int column,
boolean showStackTrace) {
this.message = message;
this.codeIndex = file;
@@ -62,71 +64,76 @@ public SketchException(String message, int file, int line, int column,
this.codeColumn = column;
this.showStackTrace = showStackTrace;
}
-
-
- /**
- * Override getMessage() in Throwable, so that I can set
+
+
+ /**
+ * Override getMessage() in Throwable, so that I can set
* the message text outside the constructor.
*/
public String getMessage() {
return message;
}
-
-
+
+
public void setMessage(String message) {
this.message = message;
}
-
-
+
+
public int getCodeIndex() {
return codeIndex;
}
-
-
+
+
public void setCodeIndex(int index) {
codeIndex = index;
}
-
-
+
+
public boolean hasCodeIndex() {
return codeIndex != -1;
}
-
-
+
+
public int getCodeLine() {
return codeLine;
}
-
-
+
+
public void setCodeLine(int line) {
this.codeLine = line;
}
-
-
+
+
public boolean hasCodeLine() {
return codeLine != -1;
}
-
-
+
+
public void setCodeColumn(int column) {
this.codeColumn = column;
}
-
-
+
+
public int getCodeColumn() {
return codeColumn;
}
-
+
public void showStackTrace() {
showStackTrace = true;
}
-
-
+
+
public void hideStackTrace() {
showStackTrace = false;
}
-
+
+
+ public boolean isStackTraceEnabled() {
+ return showStackTrace;
+ }
+
/**
* Nix the java.lang crap out of an exception message
diff --git a/app/src/processing/app/SketchReference.java b/app/src/processing/app/SketchReference.java
index 7e55f23219..f87fc23f71 100644
--- a/app/src/processing/app/SketchReference.java
+++ b/app/src/processing/app/SketchReference.java
@@ -6,19 +6,19 @@
public class SketchReference {
String name;
File pde;
-
-
+
+
public SketchReference(String name, File pde) {
this.name = name;
this.pde = pde;
}
-
-
+
+
public String getPath() {
return pde.getAbsolutePath();
}
-
-
+
+
public String toString() {
return name;
}
diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java
index 098eda1c2a..5103f5d856 100644
--- a/app/src/processing/app/UpdateCheck.java
+++ b/app/src/processing/app/UpdateCheck.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2005-12 Ben Fry and Casey Reas
+ Copyright (c) 2005-15 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 as published by
@@ -31,6 +31,7 @@
import javax.swing.JOptionPane;
+import processing.app.contrib.ContributionManager;
import processing.core.PApplet;
@@ -43,9 +44,12 @@
* to check for updates. Also included is the operating system and
* its version and the version of Java being used to run Processing.
*
- * 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 map = new HashMap();
+ StringList list = new StringList();
+ String pieces[] =
+ PApplet.split(path, File.pathSeparatorChar);
+
+ for (int i = 0; i < pieces.length; i++) {
+ //System.out.println("checking piece '" + pieces[i] + "'");
+ if (pieces[i].length() == 0) continue;
+
+ if (pieces[i].toLowerCase().endsWith(".jar") ||
+ pieces[i].toLowerCase().endsWith(".zip")) {
+ //System.out.println("checking " + pieces[i]);
+ packageListFromZip(pieces[i], list);
+
+ } else { // it's another type of file or directory
+ File dir = new File(pieces[i]);
+ if (dir.exists() && dir.isDirectory()) {
+ packageListFromFolder(dir, null, list);
+ //importCount = magicImportsRecursive(dir, null,
+ // map);
+ //imports, importCount);
+ }
+ }
+ }
+// int mapCount = map.size();
+// String output[] = new String[mapCount];
+// int index = 0;
+// Set set = map.keySet();
+// for (String s : set) {
+// output[index++] = s.replace('/', '.');
+// }
+// return output;
+ StringList outgoing = new StringList(list.size());
+ for (String item : list) {
+ outgoing.append(item.replace('/', '.'));
+ }
+ return outgoing;
+ }
+
+
+ static private void packageListFromZip(String filename, StringList list) {
+ try {
+ ZipFile file = new ZipFile(filename);
+ Enumeration> entries = file.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = (ZipEntry) entries.nextElement();
+
+ if (!entry.isDirectory()) {
+ String name = entry.getName();
+
+ // Avoid META-INF because some jokers but .class files in there
+ // https://github.com/processing/processing/issues/5778
+ if (name.endsWith(".class") && !name.startsWith("META-INF/")) {
+ int slash = name.lastIndexOf('/');
+ if (slash != -1) {
+ String packageName = name.substring(0, slash);
+ list.appendUnique(packageName);
+ }
+ }
+ }
+ }
+ file.close();
+ } catch (IOException e) {
+ System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")");
+ //e.printStackTrace();
+ }
+ }
+
+
+ /**
+ * Make list of package names by traversing a directory hierarchy.
+ * Each time a class is found in a folder, add its containing set
+ * of folders to the package list. If another folder is found,
+ * walk down into that folder and continue.
+ */
+ static private void packageListFromFolder(File dir, String sofar,
+ StringList list) {
+// Map map) {
+ boolean foundClass = false;
+ String files[] = dir.list();
+
+ for (int i = 0; i < files.length; i++) {
+ if (files[i].equals(".") || files[i].equals("..")) continue;
+
+ File sub = new File(dir, files[i]);
+ if (sub.isDirectory()) {
+ String nowfar =
+ (sofar == null) ? files[i] : (sofar + "." + files[i]);
+ packageListFromFolder(sub, nowfar, list);
+ //System.out.println(nowfar);
+ //imports[importCount++] = nowfar;
+ //importCount = magicImportsRecursive(sub, nowfar,
+ // imports, importCount);
+ } else if (!foundClass) { // if no classes found in this folder yet
+ if (files[i].endsWith(".class")) {
+ //System.out.println("unique class: " + files[i] + " for " + sofar);
+// map.put(sofar, new Object());
+ list.appendUnique(sofar);
+ foundClass = true;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Extract the contents of a .zip archive into a folder.
+ * Ignores (does not extract) any __MACOSX files from macOS archives.
+ */
+ static public void unzip(File zipFile, File dest) {
+ try {
+ FileInputStream fis = new FileInputStream(zipFile);
+ CheckedInputStream checksum = new CheckedInputStream(fis, new Adler32());
+ ZipInputStream zis = new ZipInputStream(new BufferedInputStream(checksum));
+ ZipEntry entry = null;
+ while ((entry = zis.getNextEntry()) != null) {
+ final String name = entry.getName();
+ if (!name.startsWith(("__MACOSX"))) {
+ File currentFile = new File(dest, name);
+ if (entry.isDirectory()) {
+ currentFile.mkdirs();
+ } else {
+ File parentDir = currentFile.getParentFile();
+ // Sometimes the directory entries aren't already created
+ if (!parentDir.exists()) {
+ parentDir.mkdirs();
+ }
+ currentFile.createNewFile();
+ unzipEntry(zis, currentFile);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ static protected void unzipEntry(ZipInputStream zin, File f) throws IOException {
+ FileOutputStream out = new FileOutputStream(f);
+ byte[] b = new byte[512];
+ int len = 0;
+ while ((len = zin.read(b)) != -1) {
+ out.write(b, 0, len);
+ }
+ out.flush();
+ out.close();
+ }
+
+
+ static public byte[] gzipEncode(byte[] what) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ GZIPOutputStream output = new GZIPOutputStream(baos);
+ PApplet.saveStream(output, new ByteArrayInputStream(what));
+ output.close();
+ return baos.toByteArray();
+ }
+
+
+ static public final boolean containsNonASCII(String what) {
+ for (char c : what.toCharArray()) {
+ if (c < 32 || c > 127) return true;
+ }
+ return false;
+ }
+}
diff --git a/app/src/processing/app/WebServer.java b/app/src/processing/app/WebServer.java
index abed4e0912..710fd7b2e6 100644
--- a/app/src/processing/app/WebServer.java
+++ b/app/src/processing/app/WebServer.java
@@ -5,21 +5,20 @@
import java.util.*;
import java.util.zip.*;
-//import javax.swing.SwingUtilities;
/**
* This code is placed here in anticipation of running the reference from an
* internal web server that reads the docs from a zip file, instead of using
* thousands of .html files on the disk, which is really inefficient.
*
- * This is a very simple, multi-threaded HTTP server, originally based on
+ * This is a very simple, multi-threaded HTTP server, originally based on
* this article on java.sun.com.
*/
public class WebServer implements HttpConstants {
/* Where worker threads stand idle */
static Vector threads = new Vector();
-
+
/* the web server's virtual root */
//static File root;
@@ -29,7 +28,7 @@ public class WebServer implements HttpConstants {
/* max # worker threads */
static int workers = 5;
-// static PrintStream log = System.out;
+// static PrintStream log = System.out;
/*
@@ -88,7 +87,7 @@ static void printProps() {
}
*/
-
+
/* print to stdout */
// protected static void p(String s) {
// System.out.println(s);
@@ -105,7 +104,7 @@ protected static void log(String s) {
// }
}
-
+
//public static void main(String[] a) throws Exception {
static public int launch(String zipPath) throws IOException {
final ZipFile zip = new ZipFile(zipPath);
@@ -226,7 +225,7 @@ public synchronized void run() {
}
}
-
+
void handleClient() throws IOException {
InputStream is = new BufferedInputStream(s.getInputStream());
PrintStream ps = new PrintStream(s.getOutputStream());
@@ -240,7 +239,7 @@ void handleClient() throws IOException {
buf[i] = 0;
}
try {
- // We only support HTTP GET/HEAD, and don't support any fancy HTTP
+ // We only support HTTP GET/HEAD, and don't support any fancy HTTP
// options, so we're only interested really in the first line.
int nread = 0, r = 0;
@@ -254,7 +253,7 @@ void handleClient() throws IOException {
nread += r;
for (; i < nread; i++) {
if (buf[i] == (byte)'\n' || buf[i] == (byte)'\r') {
- break outerloop; // read one line
+ break outerloop; // read one line
}
}
}
@@ -312,7 +311,7 @@ void handleClient() throws IOException {
send404(ps);
}
/*
- String fname =
+ String fname =
(new String(buf, 0, index, i-index)).replace('/', File.separatorChar);
if (fname.startsWith(File.separator)) {
fname = fname.substring(1);
@@ -338,7 +337,7 @@ void handleClient() throws IOException {
}
}
-
+
boolean printHeaders(ZipEntry targ, PrintStream ps) throws IOException {
boolean ret = false;
int rCode = 0;
@@ -386,8 +385,8 @@ boolean printHeaders(ZipEntry targ, PrintStream ps) throws IOException {
ps.write(EOL); // adding another newline here [fry]
return ret;
}
-
-
+
+
boolean printHeaders(File targ, PrintStream ps) throws IOException {
boolean ret = false;
int rCode = 0;
@@ -432,7 +431,7 @@ boolean printHeaders(File targ, PrintStream ps) throws IOException {
return ret;
}
-
+
void send404(PrintStream ps) throws IOException {
ps.write(EOL);
ps.write(EOL);
@@ -442,7 +441,7 @@ void send404(PrintStream ps) throws IOException {
ps.write(EOL);
}
-
+
void sendFile(File targ, PrintStream ps) throws IOException {
InputStream is = null;
ps.write(EOL);
@@ -454,8 +453,8 @@ void sendFile(File targ, PrintStream ps) throws IOException {
}
sendFile(is, ps);
}
-
-
+
+
void sendFile(InputStream is, PrintStream ps) throws IOException {
try {
int n;
@@ -489,19 +488,19 @@ static void fillMap() {
setSuffix(".snd", "audio/basic");
setSuffix(".au", "audio/basic");
setSuffix(".wav", "audio/x-wav");
-
+
setSuffix(".gif", "image/gif");
setSuffix(".jpg", "image/jpeg");
setSuffix(".jpeg", "image/jpeg");
-
+
setSuffix(".htm", "text/html");
setSuffix(".html", "text/html");
- setSuffix(".css", "text/css");
+ setSuffix(".css", "text/css");
setSuffix(".java", "text/javascript");
-
+
setSuffix(".txt", "text/plain");
setSuffix(".java", "text/plain");
-
+
setSuffix(".c", "text/plain");
setSuffix(".cc", "text/plain");
setSuffix(".c++", "text/plain");
diff --git a/app/src/processing/app/contrib/AvailableContribution.java b/app/src/processing/app/contrib/AvailableContribution.java
index 2cef2c9e0d..4d5240c451 100644
--- a/app/src/processing/app/contrib/AvailableContribution.java
+++ b/app/src/processing/app/contrib/AvailableContribution.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013 The Processing Foundation
+ Copyright (c) 2013-20 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -15,49 +15,54 @@
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
+ 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.contrib;
import java.io.*;
-import java.util.List;
-import java.util.Map;
import processing.app.Base;
import processing.app.Language;
+import processing.app.Platform;
+import processing.app.Util;
import processing.core.PApplet;
+import processing.data.StringDict;
+import processing.data.StringList;
/**
- * A class to hold information about a Contribution that can be downloaded.
+ * A class to hold information about a Contribution that can be downloaded.
*/
public class AvailableContribution extends Contribution {
protected final ContributionType type; // Library, tool, etc.
protected final String link; // Direct link to download the file
-
- public AvailableContribution(ContributionType type, Map params) {
+
+ public AvailableContribution(ContributionType type, StringDict params) {
this.type = type;
this.link = params.get("download");
-
- //category = ContributionListing.getCategory(params.get("category"));
- categories = parseCategories(params.get("category"));
- specifiedImports = parseImports(params.get("imports"));
+
+ categories = parseCategories(params);
+ imports = parseImports(params);
name = params.get("name");
- authorList = params.get("authorList");
+ authors = params.get("authors");
+// if (authors == null) {
+// authors = params.get("authorList");
+// }
url = params.get("url");
sentence = params.get("sentence");
paragraph = params.get("paragraph");
-
+
String versionStr = params.get("version");
if (versionStr != null) {
version = PApplet.parseInt(versionStr, 0);
}
-
- prettyVersion = params.get("prettyVersion");
-
+
+ setPrettyVersion(params.get("prettyVersion"));
+
String lastUpdatedStr = params.get("lastUpdated");
if (lastUpdatedStr != null) {
try {
@@ -70,14 +75,14 @@ public AvailableContribution(ContributionType type, Map params)
if (minRev != null) {
minRevision = PApplet.parseInt(minRev, 0);
}
-
+
String maxRev = params.get("maxRevision");
if (maxRev != null) {
maxRevision = PApplet.parseInt(maxRev, 0);
}
}
-
-
+
+
/**
* @param contribArchive
* a zip file containing the library to install
@@ -91,12 +96,11 @@ public AvailableContribution(ContributionType type, Map params)
*/
public LocalContribution install(Base base, File contribArchive,
boolean confirmReplace, StatusPanel status) {
- // Unzip the file into the modes, tools, or libraries folder inside the
- // sketchbook. Unzipping to /tmp is problematic because it may be on
+ // Unzip the file into the modes, tools, or libraries folder inside the
+ // sketchbook. Unzipping to /tmp is problematic because it may be on
// another file system, so move/rename operations will break.
-// File sketchbookContribFolder = type.getSketchbookFolder();
- File tempFolder = null;
-
+ File tempFolder = null;
+
try {
tempFolder = type.createTempFolder();
} catch (IOException e) {
@@ -104,85 +108,62 @@ public LocalContribution install(Base base, File contribArchive,
status.setErrorMessage(Language.text("contrib.errors.temporary_directory"));
return null;
}
- Base.unzip(contribArchive, tempFolder);
-// System.out.println("temp folder is " + tempFolder);
-// Base.openFolder(tempFolder);
+ Util.unzip(contribArchive, tempFolder);
// Now go looking for a legit contrib inside what's been unpacked.
File contribFolder = null;
-
- // Sometimes contrib authors place all their folders in the base directory
- // of the .zip file instead of in single folder as the guidelines suggest.
- if (type.isCandidate(tempFolder)) {
- /*
- // Can't just rename the temp folder, because a contrib with this name
- // may already exist. Instead, create a new temp folder, and rename the
- // old one to be the correct folder.
- File enclosingFolder = null;
- try {
- enclosingFolder = Base.createTempFolder(type.toString(), "tmp", sketchbookContribFolder);
- } catch (IOException e) {
- status.setErrorMessage("Could not create a secondary folder to install.");
- return null;
- }
- contribFolder = new File(enclosingFolder, getName());
- tempFolder.renameTo(contribFolder);
- tempFolder = enclosingFolder;
- */
+
+ /*
+ if (!type.isCandidate(tempFolder)) {
if (status != null) {
status.setErrorMessage(Language.interpolate("contrib.errors.needs_repackage", getName(), type.getTitle()));
}
return null;
}
+ */
-// if (contribFolder == null) {
- // Find the first legitimate looking folder in what we just unzipped
- contribFolder = type.findCandidate(tempFolder);
-// }
LocalContribution installedContrib = null;
-
+ // Find the first legitimate folder in what we just unzipped
+ contribFolder = type.findCandidate(tempFolder);
if (contribFolder == null) {
if (status != null) {
status.setErrorMessage(Language.interpolate("contrib.errors.no_contribution_found", type));
}
-
} else {
File propFile = new File(contribFolder, type + ".properties");
- if (writePropertiesFile(propFile)) {
- // 1. contribFolder now has a legit contribution, load it to get info.
+ if (!propFile.exists()) {
+ status.setErrorMessage("This contribution is missing " +
+ propFile.getName() +
+ ", please contact the author for a fix.");
+
+ } else if (writePropertiesFile(propFile)) {
+ // contribFolder now has a legit contribution, load it to get info.
LocalContribution newContrib = type.load(base, contribFolder);
-
- // 1.1. get info we need to delete the newContrib folder later
+
+ // get info we need to delete the newContrib folder later
File newContribFolder = newContrib.getFolder();
-
- // 2. Check to make sure nothing has the same name already,
+
+ // Check to make sure nothing has the same name already,
// backup old if needed, then move things into place and reload.
- installedContrib =
+ installedContrib =
newContrib.copyAndLoad(base, confirmReplace, status);
-
- // Restart no longer needed. Yay!
-// if (newContrib != null && type.requiresRestart()) {
-// installedContrib.setRestartFlag();
-// //status.setMessage("Restart Processing to finish the installation.");
-// }
-
- // 3.1 Unlock all the jars if it is a mode or tool
+
+ // Unlock all the jars if it is a mode or tool
if (newContrib.getType() == ContributionType.MODE) {
- ((ModeContribution)newContrib).clearClassLoader(base);
- }
- else if (newContrib.getType() == ContributionType.TOOL) {
- ((ToolContribution)newContrib).clearClassLoader(base);
+ ((ModeContribution) newContrib).clearClassLoader(base);
+
+ } else if (newContrib.getType() == ContributionType.TOOL) {
+ ((ToolContribution) newContrib).clearClassLoader();
}
-
- // 3.2 Delete the newContrib, do a garbage collection, hope and pray
+
+ // Delete the newContrib, do a garbage collection, hope and pray
// that Java will unlock the temp folder on Windows now
newContrib = null;
System.gc();
-
-
- if (Base.isWindows()) {
- // we'll even give it a second to finish up ... because file ops are
- // just that flaky on Windows.
+
+ if (Platform.isWindows()) {
+ // we'll even give it a second to finish up,
+ // because file ops are just that flaky on Windows.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
@@ -190,9 +171,9 @@ else if (newContrib.getType() == ContributionType.TOOL) {
}
}
- // 4. Okay, now actually delete that temp folder
- Base.removeDir(newContribFolder);
-
+ // delete the contrib folder inside the libraryXXXXXXtmp folder
+ Util.removeDir(newContribFolder, false);
+
} else {
if (status != null) {
status.setErrorMessage(Language.text("contrib.errors.overwriting_properties"));
@@ -202,17 +183,17 @@ else if (newContrib.getType() == ContributionType.TOOL) {
// Remove any remaining boogers
if (tempFolder.exists()) {
- Base.removeDir(tempFolder);
+ Util.removeDir(tempFolder, false);
}
return installedContrib;
}
-
-
+
+
public boolean isInstalled() {
return false;
}
-
+
public ContributionType getType() {
return type;
}
@@ -226,49 +207,33 @@ public ContributionType getType() {
* manager. However, it also ensures that valid fields in the properties file
* aren't overwritten, since the properties file may be more recent than the
* contributions.txt file.
- *
- * @param propFile
- * @return
*/
public boolean writePropertiesFile(File propFile) {
try {
- Map properties = Base.readSettings(propFile);
+ StringDict properties = Util.readSettings(propFile);
String name = properties.get("name");
- if (name == null || name.isEmpty())
+ if (name == null || name.isEmpty()) {
name = getName();
+ }
String category;
- List categoryList = parseCategories(properties.get("category"));
- if (categoryList.size() == 1 && categoryList.get(0).equals("Unknown")) {
+ StringList categoryList = parseCategories(properties);
+ if (categoryList.size() == 1 &&
+ categoryList.get(0).equals(UNKNOWN_CATEGORY)) {
category = getCategoryStr();
} else {
- StringBuilder sb = new StringBuilder();
- for (String cat : categories) {
- sb.append(cat);
- sb.append(',');
- }
- sb.deleteCharAt(sb.length() - 1);
- category = sb.toString();
+ category = categoryList.join(",");
}
- String specifiedImport = "";
- List importsList = parseImports(properties.get("imports"));
- if (importsList == null || importsList.isEmpty()) {
- specifiedImport = getImportStr();
- } else {
- StringBuilder sbImport = new StringBuilder();
- for (String it : specifiedImports) {
- sbImport.append(it);
- sbImport.append(',');
- }
- sbImport.deleteCharAt(sbImport.length() - 1);
- specifiedImport = sbImport.toString();
- }
+ StringList importsList = parseImports(properties);
- String authorList = properties.get("authorList");
- if (authorList == null || authorList.isEmpty()) {
- authorList = getAuthorList();
+ String authors = properties.get(AUTHORS_PROPERTY);
+// if (authors == null) {
+// authors = properties.get("authorList"); // before 3.0a11
+// }
+ if (authors == null || authors.isEmpty()) {
+ authors = getAuthorList();
}
String url = properties.get("url");
@@ -291,20 +256,18 @@ public boolean writePropertiesFile(File propFile) {
version = Integer.parseInt(properties.get("version"));
} catch (NumberFormatException e) {
version = getVersion();
- System.err.println("The version number for the “" + name
- + "” contribution is not set properly.");
- System.err
- .println("Please contact the author to fix it according to the guidelines.");
+ System.err.println("The version number for “" + name + "” is not a number.");
+ System.err.println("Please contact the author to fix it according to the guidelines.");
}
String prettyVersion = properties.get("prettyVersion");
- if (prettyVersion == null || prettyVersion.isEmpty())
- prettyVersion = getPrettyVersion();
-
+ if (prettyVersion != null && prettyVersion.isEmpty()) {
+ prettyVersion = null;
+ }
+
String compatibleContribsList = null;
-
if (getType() == ContributionType.EXAMPLES) {
- compatibleContribsList = properties.get("compatibleModesList");
+ compatibleContribsList = properties.get(MODES_PROPERTY);
}
long lastUpdated;
@@ -312,7 +275,7 @@ public boolean writePropertiesFile(File propFile) {
lastUpdated = Long.parseLong(properties.get("lastUpdated"));
} catch (NumberFormatException nfe) {
lastUpdated = getLastUpdated();
- // Better comment these out till all contribs have a lastUpdated
+ // Better comment these out till all contribs have a lastUpdated
// System.err.println("The last updated date for the “" + name
// + "” contribution is not set properly.");
// System.err
@@ -335,27 +298,31 @@ public boolean writePropertiesFile(File propFile) {
maxRev = getMaxRevision();
// System.err.println("The maximum compatible revision for the “" + name
// + "” contribution is not set properly. Assuming maximum revision INF.");
- }
+ }
if (propFile.delete() && propFile.createNewFile() && propFile.setWritable(true)) {
PrintWriter writer = PApplet.createWriter(propFile);
writer.println("name=" + name);
writer.println("category=" + category);
- writer.println("authorList=" + authorList);
+ writer.println(AUTHORS_PROPERTY + "=" + authors);
writer.println("url=" + url);
writer.println("sentence=" + sentence);
writer.println("paragraph=" + paragraph);
writer.println("version=" + version);
- writer.println("prettyVersion=" + prettyVersion);
+ if (prettyVersion != null) {
+ writer.println("prettyVersion=" + prettyVersion);
+ }
writer.println("lastUpdated=" + lastUpdated);
writer.println("minRevision=" + minRev);
writer.println("maxRevision=" + maxRev);
- if (getType() == ContributionType.LIBRARY) {
- writer.println("imports=" + specifiedImport);
+ if ((getType() == ContributionType.LIBRARY || getType() == ContributionType.MODE) && importsList != null) {
+ writer.println("imports=" + importsList.join(","));
}
if (getType() == ContributionType.EXAMPLES) {
- writer.println("compatibleModesList=" + compatibleContribsList);
+ if (compatibleContribsList != null) {
+ writer.println(MODES_PROPERTY + "=" + compatibleContribsList);
+ }
}
writer.flush();
diff --git a/app/src/processing/app/contrib/ContribProgressBar.java b/app/src/processing/app/contrib/ContribProgressBar.java
index 47371c84bc..8a0d41c3b6 100644
--- a/app/src/processing/app/contrib/ContribProgressBar.java
+++ b/app/src/processing/app/contrib/ContribProgressBar.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013-15 The Processing Foundation
+ Copyright (c) 2013-20 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -21,6 +21,9 @@
*/
package processing.app.contrib;
+import java.awt.EventQueue;
+import java.lang.reflect.InvocationTargetException;
+
import javax.swing.JProgressBar;
@@ -48,10 +51,50 @@ public void setProgress(int value) {
}
@Override
- public void finished() {
+ public final void finished() {
super.finished();
- finishedAction();
+ try {
+ EventQueue.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ finishedAction();
+ }
+ });
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else {
+ cause.printStackTrace();
+ }
+ }
}
public abstract void finishedAction();
+
+ @Override
+ public final void cancel() {
+ super.cancel();
+ try {
+ EventQueue.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ cancelAction();
+ }
+ });
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else {
+ cause.printStackTrace();
+ }
+ }
+ }
+
+ public void cancelAction() { }
}
diff --git a/app/src/processing/app/contrib/ContribProgressMonitor.java b/app/src/processing/app/contrib/ContribProgressMonitor.java
index 8e627d957d..4c897e22ae 100644
--- a/app/src/processing/app/contrib/ContribProgressMonitor.java
+++ b/app/src/processing/app/contrib/ContribProgressMonitor.java
@@ -26,7 +26,7 @@
// This code seems like it's adapted from old example code found on the web.
// https://github.com/processing/processing/issues/3176
-abstract class ContribProgressMonitor {
+public abstract class ContribProgressMonitor {
static final int UNKNOWN = -1;
boolean canceled = false;
boolean error = false;
diff --git a/app/src/processing/app/contrib/Contribution.java b/app/src/processing/app/contrib/Contribution.java
index 8103570acb..aa401375e6 100644
--- a/app/src/processing/app/contrib/Contribution.java
+++ b/app/src/processing/app/contrib/Contribution.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013 The Processing Foundation
+ Copyright (c) 2013-16 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -21,43 +21,48 @@
*/
package processing.app.contrib;
-import java.util.ArrayList;
+import java.io.File;
import java.util.Arrays;
import java.util.List;
import processing.core.PApplet;
+import processing.data.StringDict;
+import processing.data.StringList;
import processing.app.Language;
+import processing.app.Util;
+
abstract public class Contribution {
- static final String SPECIAL_CATEGORY_NAME = "Starred";
+ static final String IMPORTS_PROPERTY = "imports";
+ static final String CATEGORIES_PROPERTY = "categories";
+ static final String MODES_PROPERTY = "modes";
+ static final String AUTHORS_PROPERTY = "authors";
+
+ static final String SPECIAL_CATEGORY = "Starred";
+ static final String UNKNOWN_CATEGORY = "Unknown";
static final List validCategories =
Arrays.asList("3D", "Animation", "Data", "Geometry", "GUI", "Hardware",
- "I/O", "Math", "Simulation", "Sound", SPECIAL_CATEGORY_NAME, "Typography",
- "Utilities", "Video & Vision", "Other");
-
- //protected String category; // "Sound"
- protected List categories; // "Sound", "Typography"
- protected String name; // "pdf" or "PDF Export"
- protected String authorList; // Ben Fry
- protected String url; // http://processing.org
- protected String sentence; // Write graphics to PDF files.
- protected String paragraph; //
- protected int version; // 102
- protected String prettyVersion; // "1.0.2"
- protected long lastUpdated; // 1402805757
- protected int minRevision; // 0
- protected int maxRevision; // 227
- protected List specifiedImports; // pdf.export.*,pdf.convert.common.*
-
-
- // "Sound"
-// public String getCategory() {
-// return category;
-// }
+ "I/O", "Math", "Simulation", "Sound", SPECIAL_CATEGORY,
+ "Typography", "Utilities", "Video & Vision", "Other");
+
+ static final String FOUNDATION_AUTHOR = "The Processing Foundation";
+
+ protected StringList categories; // "Sound", "Typography"
+ protected String name; // "pdf" or "PDF Export"
+ protected String authors; // [Ben Fry](http://benfry.com)
+ protected String url; // http://processing.org
+ protected String sentence; // Write graphics to PDF files.
+ protected String paragraph; //
+ protected int version; // 102
+ protected String prettyVersion; // "1.0.2"
+ protected long lastUpdated; // 1402805757
+ protected int minRevision; // 0
+ protected int maxRevision; // 227
+ protected StringList imports; // pdf.export,pdf.convert.common (list of packages, not imports)
// "Sound", "Utilities"... see valid list in ContributionListing
- protected List getCategories() {
+ protected StringList getCategories() {
return categories;
}
@@ -86,28 +91,29 @@ protected boolean hasCategory(String category) {
// pdf.export.*,pdf.convert.common.*
- protected List getImports() {
- return specifiedImports;
+ protected StringList getImports() {
+ return imports;
}
-
+/*
protected String getImportStr() {
- if (specifiedImports == null || specifiedImports.isEmpty()) {
+ if (imports == null || imports.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
- for (String importName : specifiedImports) {
+ for (String importName : imports) {
sb.append(importName);
sb.append(',');
}
sb.deleteCharAt(sb.length() - 1); // delete last comma
return sb.toString();
}
+*/
protected boolean hasImport(String importName) {
- if (specifiedImports != null && importName != null) {
- for (String c : specifiedImports) {
- if (importName.equalsIgnoreCase(c)) {
+ if (imports != null && importName != null) {
+ for (String c : imports) {
+ if (importName.equals(c)) {
return true;
}
}
@@ -124,7 +130,7 @@ public String getName() {
// "[Ben Fry](http://benfry.com/)"
public String getAuthorList() {
- return authorList;
+ return authors;
}
@@ -152,21 +158,41 @@ public int getVersion() {
}
- // "1.0.2"
+ public void setPrettyVersion(String pretty) {
+ if (pretty != null) {
+ // some entries were written as "null", causing that to show in the ui
+ if (pretty.equals("null") || pretty.length() == 0) {
+ pretty = null;
+ }
+ }
+ prettyVersion = pretty;
+ }
+
+
+ // "1.0.2" or null if not present
public String getPrettyVersion() {
return prettyVersion;
}
+
+ // returns prettyVersion, or "" if null
+ public String getBenignVersion() {
+ return (prettyVersion != null) ? prettyVersion : "";
+ }
+
+
// 1402805757
public long getLastUpdated() {
return lastUpdated;
}
+
// 0
public int getMinRevision() {
return minRevision;
}
+
// 227
public int getMaxRevision() {
return maxRevision;
@@ -174,7 +200,7 @@ public int getMaxRevision() {
public boolean isCompatible(int versionNum) {
- return ((maxRevision == 0 || versionNum < maxRevision) && versionNum > minRevision);
+ return ((maxRevision == 0 || versionNum <= maxRevision) && versionNum >= minRevision);
}
@@ -215,28 +241,48 @@ boolean isUpdateFlagged() {
/**
- * Returns true if the contribution is a starred/recommended contribution, or
- * is by the Processing Foundation.
- *
- * @return
+ * Returns true if the contribution is a starred/recommended contribution,
+ * or is by the Processing Foundation.
*/
boolean isSpecial() {
- try {
- return (authorList.indexOf("The Processing Foundation") != -1 ||
- categories.contains(SPECIAL_CATEGORY_NAME));
- } catch (NullPointerException npe) {
- return false;
+ if (authors != null &&
+ authors.contains(FOUNDATION_AUTHOR)) {
+ return true;
+ }
+
+ if (categories != null &&
+ categories.hasValue(SPECIAL_CATEGORY)) {
+ return true;
}
+
+ return false;
}
+ public boolean isFoundation() {
+ return FOUNDATION_AUTHOR.equals(authors);
+ }
+
+
+ public StringDict loadProperties(File contribFolder) {
+ return loadProperties(contribFolder, getType());
+ }
+
+
+ static public StringDict loadProperties(File contribFolder,
+ ContributionType type) {
+ File propertiesFile = new File(contribFolder, type.getPropertiesName());
+ if (propertiesFile.exists()) {
+ return Util.readSettings(propertiesFile);
+ }
+ return null;
+ }
+
/**
* @return a single element list with "Unknown" as the category.
*/
- static List defaultCategory() {
- List outgoing = new ArrayList();
- outgoing.add("Unknown");
- return outgoing;
+ static StringList unknownCategoryList() {
+ return new StringList(UNKNOWN_CATEGORY);
}
@@ -244,44 +290,105 @@ static List defaultCategory() {
* @return the list of categories that this contribution is part of
* (e.g. "Typography / Geometry"). "Unknown" if the category null.
*/
- static List parseCategories(String categoryStr) {
- List outgoing = new ArrayList();
+ static StringList parseCategories(StringDict properties) {
+ StringList outgoing = new StringList();
+ String categoryStr = properties.get(CATEGORIES_PROPERTY);
+ if (categoryStr == null) {
+ categoryStr = properties.get("category"); // try the old way
+ }
if (categoryStr != null) {
+ // Can't use splitTokens() because the names sometimes have spaces
String[] listing = PApplet.trim(PApplet.split(categoryStr, ','));
for (String category : listing) {
if (validCategories.contains(category)) {
category = translateCategory(category);
- outgoing.add(category);
+ outgoing.append(category);
}
}
}
if (outgoing.size() == 0) {
- return defaultCategory();
+ return unknownCategoryList();
}
return outgoing;
}
/**
- * @return the list of imports that this contribution (library) contains.
+ * Returns the list of imports specified by this library author. Only
+ * necessary for library authors that want to override the default behavior
+ * of importing all packages in their library.
+ * @return null if no entries found
*/
- static List parseImports(String importStr) {
- List outgoing = new ArrayList();
+ static StringList parseImports(StringDict properties) {
+ StringList outgoing = new StringList();
+ String importStr = properties.get(IMPORTS_PROPERTY);
if (importStr != null) {
String[] importList = PApplet.trim(PApplet.split(importStr, ','));
for (String importName : importList) {
- outgoing.add(importName);
+ if (!importName.isEmpty()) {
+ outgoing.append(importName);
+ }
}
}
return (outgoing.size() > 0) ? outgoing : null;
}
+ /**
+ * Helper function that creates a StringList of the compatible Modes
+ * for this Contribution.
+ */
+ static StringList parseModeList(StringDict properties) {
+ String unparsedModes = properties.get(MODES_PROPERTY);
+
+ // Workaround for 3.0 alpha/beta bug for 3.0b2
+ if ("null".equals(unparsedModes)) {
+ properties.remove(MODES_PROPERTY);
+ unparsedModes = null;
+ }
+
+ StringList outgoing = new StringList();
+ if (unparsedModes != null) {
+ outgoing.append(PApplet.trim(PApplet.split(unparsedModes, ',')));
+ }
+ return outgoing;
+ }
+
+
static private String translateCategory(String cat) {
// Converts Other to other, I/O to i_o, Video & Vision to video_vision
String cleaned = cat.replaceAll("[\\W]+", "_").toLowerCase();
return Language.text("contrib.category." + cleaned);
}
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o instanceof Contribution) {
+ Contribution that = (Contribution) o;
+ return name.toLowerCase().equals(that.name.toLowerCase());
+ }
+ return false;
+ }
+
+
+ @Override
+ public int hashCode() {
+ return name.toLowerCase().hashCode();
+ }
+
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ public interface Filter {
+ boolean matches(Contribution contrib);
+ }
}
diff --git a/app/src/processing/app/contrib/ContributionFilter.java b/app/src/processing/app/contrib/ContributionFilter.java
deleted file mode 100644
index 0419b7a69a..0000000000
--- a/app/src/processing/app/contrib/ContributionFilter.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package processing.app.contrib;
-
-interface ContributionFilter {
- boolean matches(Contribution contrib);
-}
\ No newline at end of file
diff --git a/app/src/processing/app/contrib/ContributionListPanel.java b/app/src/processing/app/contrib/ContributionListPanel.java
deleted file mode 100644
index e9c0ed4dbc..0000000000
--- a/app/src/processing/app/contrib/ContributionListPanel.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2013 The Processing Foundation
- Copyright (c) 2011-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.contrib;
-
-import java.util.*;
-import java.util.List;
-import java.util.Map.Entry;
-
-import javax.swing.*;
-import javax.swing.border.*;
-import javax.swing.event.*;
-
-import java.awt.*;
-
-import processing.app.Base;
-
-
-// The "Scrollable" implementation and its methods here take care of preventing
-// the scrolling area from running exceptionally slowly. Not sure why they're
-// necessary in the first place, however; seems like odd behavior.
-// It also allows the description text in the panels to wrap properly.
-
-public class ContributionListPanel extends JPanel implements Scrollable, ContributionChangeListener {
-
- ContributionManagerDialog contribManager;
- TreeMap panelByContribution;
-
- static HyperlinkListener nullHyperlinkListener = new HyperlinkListener() {
- public void hyperlinkUpdate(HyperlinkEvent e) { }
- };
-
- private ContributionPanel selectedPanel;
-// protected JPanel statusPlaceholder;
- private StatusPanel status;
- private ContributionFilter filter;
-// private ContributionListing contribListing;
- private ContributionListing contribListing = ContributionListing.getInstance();
-
-
- public ContributionListPanel(ContributionManagerDialog libraryManager,
- ContributionFilter filter) {
- super();
- this.contribManager = libraryManager;
- this.filter = filter;
-
-// contribListing = ContributionListing.getInstance();
-
- setLayout(new GridBagLayout());
- setOpaque(true);
-
- if (Base.isLinux()) {
- // Because of a bug with GNOME, getColor returns the wrong value for
- // List.background. We'll just assume its white. The number of people
- // using Linux and an inverted color theme should be small enough.
- setBackground(Color.white);
- } else {
- setBackground(UIManager.getColor("List.background"));
- }
-
- panelByContribution = new TreeMap(
- contribListing.getComparator());
-
-// statusPlaceholder = new JPanel();
-// statusPlaceholder.setVisible(false);
- status = new StatusPanel();
- }
-
-
- private void updatePanelOrdering() {
- int row = 0;
- for (Entry entry : panelByContribution.entrySet()) {
- GridBagConstraints c = new GridBagConstraints();
- c.fill = GridBagConstraints.HORIZONTAL;
- c.weightx = 1;
- c.gridx = 0;
- c.gridy = row++;
- c.anchor = GridBagConstraints.NORTH;
-
- add(entry.getValue(), c);
- }
-
- GridBagConstraints c = new GridBagConstraints();
- c.fill = GridBagConstraints.BOTH;
- c.weightx = 1;
- c.weighty = 1;
- c.gridx = 0;
- c.gridy = row++;
- c.anchor = GridBagConstraints.NORTH;
- add(status, c);
- }
-
-
- public void contributionAdded(final Contribution contribution) {
- if (filter.matches(contribution)) {
- EventQueue.invokeLater(new Runnable() {
- public void run() {
- if (!panelByContribution.containsKey(contribution)) {
- ContributionPanel newPanel = new ContributionPanel(ContributionListPanel.this);
- synchronized (panelByContribution) {
- panelByContribution.put(contribution, newPanel);
- }
- if (newPanel != null) {
- newPanel.setContribution(contribution);
- add(newPanel);
- updatePanelOrdering();
- updateColors(); // XXX this is the place
- }
- }
- }
- });
- }
- }
-
-
- public void contributionRemoved(final Contribution contribution) {
- EventQueue.invokeLater(new Runnable() {
- public void run() {
- synchronized (panelByContribution) {
- ContributionPanel panel = panelByContribution.get(contribution);
- if (panel != null) {
- remove(panel);
- panelByContribution.remove(contribution);
- }
- }
- updatePanelOrdering();
- updateColors();
- updateUI();
- }
- });
- }
-
-
- public void contributionChanged(final Contribution oldContrib,
- final Contribution newContrib) {
- EventQueue.invokeLater(new Runnable() {
- public void run() {
- synchronized (panelByContribution) {
- ContributionPanel panel = panelByContribution.get(oldContrib);
- if (panel == null) {
- contributionAdded(newContrib);
- } else {
- panelByContribution.remove(oldContrib);
- panel.setContribution(newContrib);
- panelByContribution.put(newContrib, panel);
- updatePanelOrdering();
- }
- }
- }
- });
- }
-
-
- public void filterLibraries(List filteredContributions) {
- synchronized (panelByContribution) {
- Set hiddenPanels =
- new TreeSet(contribListing.getComparator());
- hiddenPanels.addAll(panelByContribution.keySet());
-
- for (Contribution info : filteredContributions) {
- ContributionPanel panel = panelByContribution.get(info);
- if (panel != null) {
- panel.setVisible(true);
- hiddenPanels.remove(info);
- }
- }
-
- for (Contribution info : hiddenPanels) {
- ContributionPanel panel = panelByContribution.get(info);
- if (panel != null) {
- panel.setVisible(false);
- }
- }
- }
- }
-
-
- protected void setSelectedPanel(ContributionPanel panel) {
- if (selectedPanel == panel) {
- selectedPanel.setSelected(true);
-
- } else {
- ContributionPanel lastSelected = selectedPanel;
- selectedPanel = panel;
-
- if (lastSelected != null) {
- lastSelected.setSelected(false);
- }
- panel.setSelected(true);
-
- updateColors();
- requestFocusInWindow();
- }
- }
-
-
- protected ContributionPanel getSelectedPanel() {
- return selectedPanel;
- }
-
-
- /**
- * Updates the colors of all library panels that are visible.
- */
- protected void updateColors() {
- int count = 0;
- synchronized (panelByContribution) {
- for (Entry entry : panelByContribution.entrySet()) {
- ContributionPanel panel = entry.getValue();
-
- if (panel.isVisible() && panel.isSelected()) {
- panel.setBackground(UIManager.getColor("List.selectionBackground"));
- panel.setForeground(UIManager.getColor("List.selectionForeground"));
- panel.setBorder(UIManager.getBorder("List.focusCellHighlightBorder"));
- count++;
-
- } else {
- Border border = null;
- if (panel.isVisible()) {
- if (Base.isMacOS()) {
- if (count % 2 == 1) {
- border = UIManager.getBorder("List.oddRowBackgroundPainter");
- } else {
- border = UIManager.getBorder("List.evenRowBackgroundPainter");
- }
- } else {
- if (count % 2 == 1) {
- panel.setBackground(new Color(219, 224, 229));
- } else {
- panel.setBackground(new Color(241, 241, 241));
- }
- }
- count++;
- }
-
- if (border == null) {
- border = BorderFactory.createEmptyBorder(1, 1, 1, 1);
- }
- panel.setBorder(border);
- panel.setForeground(UIManager.getColor("List.foreground"));
- }
- }
- }
- }
-
-
- public Dimension getPreferredScrollableViewportSize() {
- return getPreferredSize();
- }
-
-
- /**
- * Amount to scroll to reveal a new page of items
- */
- public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
- if (orientation == SwingConstants.VERTICAL) {
- int blockAmount = visibleRect.height;
- if (direction > 0) {
- visibleRect.y += blockAmount;
- } else {
- visibleRect.y -= blockAmount;
- }
-
- blockAmount += getScrollableUnitIncrement(visibleRect, orientation, direction);
- return blockAmount;
- }
- return 0;
- }
-
-
- /**
- * Amount to scroll to reveal the rest of something we are on or a new item
- */
- public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
- if (orientation == SwingConstants.VERTICAL) {
- int lastHeight = 0, height = 0;
- int bottomOfScrollArea = visibleRect.y + visibleRect.height;
-
- for (Component c : getComponents()) {
- if (c.isVisible()) {
- if (c instanceof ContributionPanel) {
- Dimension d = c.getPreferredSize();
-
- int nextHeight = height + d.height;
-
- if (direction > 0) {
- // scrolling down
- if (nextHeight > bottomOfScrollArea) {
- return nextHeight - bottomOfScrollArea;
- }
- } else {
- // scrolling up
- if (nextHeight > visibleRect.y) {
- if (visibleRect.y != height) {
- return visibleRect.y - height;
- } else {
- return visibleRect.y - lastHeight;
- }
- }
- }
-
- lastHeight = height;
- height = nextHeight;
- }
- }
- }
- }
- return 0;
- }
-
-
- public boolean getScrollableTracksViewportHeight() {
- return false;
- }
-
-
- public boolean getScrollableTracksViewportWidth() {
- return true;
- }
-}
diff --git a/app/src/processing/app/contrib/ContributionListing.java b/app/src/processing/app/contrib/ContributionListing.java
index c8ed71ed5b..6f35ca3fd7 100644
--- a/app/src/processing/app/contrib/ContributionListing.java
+++ b/app/src/processing/app/contrib/ContributionListing.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013 The Processing Foundation
+ Copyright (c) 2013-16 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -21,52 +21,62 @@
*/
package processing.app.contrib;
+import java.awt.EventQueue;
import java.io.*;
+import java.lang.reflect.InvocationTargetException;
import java.net.*;
-import java.nio.file.Files;
+import java.text.Normalizer;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Pattern;
import processing.app.Base;
import processing.app.Library;
+import processing.app.Util;
import processing.core.PApplet;
+import processing.data.StringDict;
public class ContributionListing {
- // Stable URL that will redirect to wherever we're hosting the file
- static final String LISTING_URL =
- "http://download.processing.org/contribs.txt";
-
static volatile ContributionListing singleInstance;
+ /** Stable URL that will redirect to wherever the file is hosted */
+ static final String LISTING_URL = "http://download.processing.org/contribs";
+ static final String LOCAL_FILENAME = "contribs.txt";
+
+ /** Location of the listing file on disk, will be read and written. */
File listingFile;
- ArrayList listeners;
- ArrayList advertisedContributions;
+
+ List listeners;
+ List advertisedContributions;
Map> librariesByCategory;
- public Map librariesByImportHeader;
- ArrayList allContributions;
- boolean hasDownloadedLatestList;
- boolean hasListDownloadFailed;
+ Map librariesByImportHeader;
+ // TODO: Every contribution is getting added twice
+ // and nothing is replaced ever.
+ Set allContributions;
+ boolean listDownloaded;
+ boolean listDownloadFailed;
ReentrantLock downloadingListingLock;
private ContributionListing() {
- listeners = new ArrayList();
- advertisedContributions = new ArrayList();
- librariesByCategory = new HashMap>();
- librariesByImportHeader = new HashMap();
- allContributions = new ArrayList();
+ listeners = new ArrayList<>();
+ advertisedContributions = new ArrayList<>();
+ librariesByCategory = new HashMap<>();
+ librariesByImportHeader = new HashMap<>();
+ allContributions = new LinkedHashSet<>();
downloadingListingLock = new ReentrantLock();
- listingFile = Base.getSettingsFile("contributions.txt");
- listingFile.setWritable(true);
+ //listingFile = Base.getSettingsFile("contributions.txt");
+ listingFile = Base.getSettingsFile(LOCAL_FILENAME);
+ listingFile.setWritable(true, false);
if (listingFile.exists()) {
setAdvertisedList(listingFile);
}
}
- public static ContributionListing getInstance() {
+ static public ContributionListing getInstance() {
if (singleInstance == null) {
synchronized (ContributionListing.class) {
if (singleInstance == null) {
@@ -78,7 +88,7 @@ public static ContributionListing getInstance() {
}
- void setAdvertisedList(File file) {
+ private void setAdvertisedList(File file) {
listingFile = file;
advertisedContributions.clear();
@@ -86,7 +96,6 @@ void setAdvertisedList(File file) {
for (Contribution contribution : advertisedContributions) {
addContribution(contribution);
}
- Collections.sort(allContributions, nameComparator);
}
@@ -94,8 +103,8 @@ void setAdvertisedList(File file) {
* Adds the installed libraries to the listing of libraries, replacing any
* pre-existing libraries by the same name as one in the list.
*/
- protected void updateInstalledList(List installedContributions) {
- for (Contribution contribution : installedContributions) {
+ protected void updateInstalledList(List installed) {
+ for (Contribution contribution : installed) {
Contribution existingContribution = getContribution(contribution);
if (existingContribution != null) {
replaceContribution(existingContribution, contribution);
@@ -123,17 +132,14 @@ protected void replaceContribution(Contribution oldLib, Contribution newLib) {
if (oldLib.getImports() != null) {
for (String importName : oldLib.getImports()) {
- if (librariesByImportHeader.containsKey(importName)) {
- librariesByImportHeader.put(importName, newLib);
+ if (getLibrariesByImportHeader().containsKey(importName)) {
+ getLibrariesByImportHeader().put(importName, newLib);
}
}
}
- for (int i = 0; i < allContributions.size(); i++) {
- if (allContributions.get(i) == oldLib) {
- allContributions.set(i, newLib);
- }
- }
+ allContributions.remove(oldLib);
+ allContributions.add(newLib);
notifyChange(oldLib, newLib);
}
@@ -143,23 +149,22 @@ protected void replaceContribution(Contribution oldLib, Contribution newLib) {
private void addContribution(Contribution contribution) {
if (contribution.getImports() != null) {
for (String importName : contribution.getImports()) {
- librariesByImportHeader.put(importName, contribution);
+ getLibrariesByImportHeader().put(importName, contribution);
}
}
for (String category : contribution.getCategories()) {
if (librariesByCategory.containsKey(category)) {
List list = librariesByCategory.get(category);
list.add(contribution);
- Collections.sort(list, nameComparator);
+ Collections.sort(list, COMPARATOR);
} else {
- ArrayList list = new ArrayList();
+ ArrayList list = new ArrayList<>();
list.add(contribution);
librariesByCategory.put(category, list);
}
allContributions.add(contribution);
notifyAdd(contribution);
- Collections.sort(allContributions, nameComparator);
}
}
@@ -172,7 +177,7 @@ protected void removeContribution(Contribution contribution) {
}
if (contribution.getImports() != null) {
for (String importName : contribution.getImports()) {
- librariesByImportHeader.remove(importName);
+ getLibrariesByImportHeader().remove(importName);
}
}
allContributions.remove(contribution);
@@ -192,20 +197,20 @@ private Contribution getContribution(Contribution contribution) {
protected AvailableContribution getAvailableContribution(Contribution info) {
- Iterator iter = advertisedContributions.iterator();
- while(iter.hasNext()) {
- AvailableContribution advertised = iter.next();
- if (advertised.getType() == info.getType() &&
- advertised.getName().equals(info.getName())) {
- return advertised;
+ synchronized (advertisedContributions) {
+ for (AvailableContribution advertised : advertisedContributions) {
+ if (advertised.getType() == info.getType() &&
+ advertised.getName().equals(info.getName())) {
+ return advertised;
+ }
}
}
return null;
}
- protected Set getCategories(ContributionFilter filter) {
- Set outgoing = new HashSet();
+ protected Set getCategories(Contribution.Filter filter) {
+ Set outgoing = new HashSet<>();
Set categorySet = librariesByCategory.keySet();
for (String categoryName : categorySet) {
@@ -213,7 +218,7 @@ protected Set getCategories(ContributionFilter filter) {
if (filter.matches(contrib)) {
// TODO still not sure why category would be coming back null [fry]
// http://code.google.com/p/processing/issues/detail?id=1387
- if (categoryName != null && categoryName.trim().length() != 0) {
+ if (categoryName != null && !categoryName.trim().isEmpty()) {
outgoing.add(categoryName);
}
break;
@@ -224,47 +229,11 @@ protected Set getCategories(ContributionFilter filter) {
}
-// public List getAllContributions() {
-// return new ArrayList(allContributions);
-// }
-
-
-// public List getLibararies(String category) {
-// ArrayList libinfos =
-// new ArrayList(librariesByCategory.get(category));
-// Collections.sort(libinfos, nameComparator);
-// return libinfos;
-// }
-
-
- protected List getFilteredLibraryList(String category, List filters) {
- ArrayList filteredList =
- new ArrayList(allContributions);
-
- Iterator it = filteredList.iterator();
- while (it.hasNext()) {
- Contribution libInfo = it.next();
- //if (category != null && !category.equals(libInfo.getCategory())) {
- if (category != null && !libInfo.hasCategory(category)) {
- it.remove();
- } else {
- for (String filter : filters) {
- if (!matches(libInfo, filter)) {
- it.remove();
- break;
- }
- }
- }
- }
- return filteredList;
- }
-
-
- private boolean matches(Contribution contrib, String filter) {
- int colon = filter.indexOf(":");
+ public boolean matches(Contribution contrib, String typed) {
+ int colon = typed.indexOf(":");
if (colon != -1) {
- String isText = filter.substring(0, colon);
- String property = filter.substring(colon + 1);
+ String isText = typed.substring(0, colon);
+ String property = typed.substring(colon + 1);
// Chances are the person is still typing the property, so rather than
// make the list flash empty (because nothing contains "is:" or "has:",
@@ -274,23 +243,37 @@ private boolean matches(Contribution contrib, String filter) {
}
if ("is".equals(isText) || "has".equals(isText)) {
- return hasProperty(contrib, filter.substring(colon + 1));
+ return hasProperty(contrib, typed.substring(colon + 1));
} else if ("not".equals(isText)) {
- return !hasProperty(contrib, filter.substring(colon + 1));
+ return !hasProperty(contrib, typed.substring(colon + 1));
}
}
- filter = ".*" + filter.toLowerCase() + ".*";
+ typed = ".*" + typed.toLowerCase() + ".*";
+
+ return (matchField(contrib.getName(), typed) ||
+ matchField(contrib.getAuthorList(), typed) ||
+ matchField(contrib.getSentence(), typed) ||
+ matchField(contrib.getParagraph(), typed) ||
+ contrib.hasCategory(typed));
+ }
+
+
+ static private boolean matchField(String field, String typed) {
+ return (field != null) &&
+ removeAccents(field.toLowerCase()).matches(typed);
+ }
+
- return contrib.getAuthorList() != null && contrib.getAuthorList().toLowerCase().matches(filter)
- || contrib.getSentence() != null && contrib.getSentence().toLowerCase().matches(filter)
- || contrib.getParagraph() != null && contrib.getParagraph().toLowerCase().matches(filter)
- || contrib.hasCategory(filter)
- || contrib.getName() != null && contrib.getName().toLowerCase().matches(filter);
+ // TODO is this removing characters with accents, not ascii normalizing them? [fry]
+ static private String removeAccents(String str) {
+ String nfdNormalizedString = Normalizer.normalize(str, Normalizer.Form.NFD);
+ Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
+ return pattern.matcher(nfdNormalizedString).replaceAll("");
}
- private boolean isProperty(String property) {
+ static private boolean isProperty(String property) {
return property.startsWith("updat") || property.startsWith("upgrad")
|| property.startsWith("instal") && !property.startsWith("installabl")
|| property.equals("tool") || property.startsWith("lib")
@@ -315,60 +298,55 @@ private boolean hasProperty(Contribution contrib, String property) {
}
if (property.startsWith("lib")) {
return contrib.getType() == ContributionType.LIBRARY;
-// return contrib.getType() == Contribution.Type.LIBRARY
-// || contrib.getType() == Contribution.Type.LIBRARY_COMPILATION;
}
if (property.equals("mode")) {
return contrib.getType() == ContributionType.MODE;
}
-// if (property.equals("compilation")) {
-// return contrib.getType() == Contribution.Type.LIBRARY_COMPILATION;
-// }
-
return false;
}
- protected List getCompatibleContributionList(List filteredLibraries, boolean filter) {
- ArrayList filteredList =
- new ArrayList(filteredLibraries);
-
- if (!filter)
- return filteredList;
-
- Iterator it = filteredList.iterator();
- while (it.hasNext()) {
- Contribution libInfo = it.next();
- if (!libInfo.isCompatible(Base.getRevision())) {
- it.remove();
+ /*
+ protected List listCompatible(List contribs, boolean filter) {
+ List filteredList =
+ new ArrayList(contribs);
+
+ if (filter) {
+ Iterator it = filteredList.iterator();
+ while (it.hasNext()) {
+ Contribution libInfo = it.next();
+ if (!libInfo.isCompatible(Base.getRevision())) {
+ it.remove();
+ }
}
}
return filteredList;
}
+ */
private void notifyRemove(Contribution contribution) {
- for (ContributionChangeListener listener : listeners) {
+ for (ChangeListener listener : listeners) {
listener.contributionRemoved(contribution);
}
}
private void notifyAdd(Contribution contribution) {
- for (ContributionChangeListener listener : listeners) {
+ for (ChangeListener listener : listeners) {
listener.contributionAdded(contribution);
}
}
private void notifyChange(Contribution oldLib, Contribution newLib) {
- for (ContributionChangeListener listener : listeners) {
+ for (ChangeListener listener : listeners) {
listener.contributionChanged(oldLib, newLib);
}
}
- protected void addContributionListener(ContributionChangeListener listener) {
+ protected void addListener(ChangeListener listener) {
for (Contribution contrib : allContributions) {
listener.contributionAdded(contrib);
}
@@ -376,152 +354,113 @@ protected void addContributionListener(ContributionChangeListener listener) {
}
- /*
- private void removeContributionListener(ContributionChangeListener listener) {
- listeners.remove(listener);
- }
-
-
- private ArrayList getContributionListeners() {
- return new ArrayList(listeners);
- }
- */
-
-
/**
* Starts a new thread to download the advertised list of contributions.
* Only one instance will run at a time.
*/
- protected void downloadAvailableList(final ContribProgressMonitor progress) {
+ public void downloadAvailableList(final Base base,
+ final ContribProgressMonitor progress) {
+
+ // TODO: replace with SwingWorker [jv]
new Thread(new Runnable() {
public void run() {
downloadingListingLock.lock();
- URL url = null;
try {
- url = new URL(LISTING_URL);
- } catch (MalformedURLException e) {
- progress.error(e);
- progress.finished();
- }
-
- if (!progress.isFinished()) {
- File tempContribFile = Base.getSettingsFile("contributions_temp.txt");
- tempContribFile.setWritable(true);
- ContributionManager.download(url, tempContribFile, progress);
+ URL url = new URL(LISTING_URL);
+ // testing port
+// url = new URL("http", "download.processing.org", 8989, "/contribs");
+
+// "http://download.processing.org/contribs";
+// System.out.println(url);
+// final String contribInfo =
+// base.getInstalledContribsInfo();
+// "?id=" + Preferences.get("update.id") +
+// "&" + base.getInstalledContribsInfo();
+// url = new URL(LISTING_URL + "?" + contribInfo);
+// System.out.println(contribInfo.length() + " " + contribInfo);
+
+ File tempContribFile = Base.getSettingsFile("contribs.tmp");
+ tempContribFile.setWritable(true, false);
+ ContributionManager.download(url, base.getInstalledContribsInfo(),
+ tempContribFile, progress);
if (!progress.isCanceled() && !progress.isError()) {
- try {
- Files.deleteIfExists(listingFile.toPath());
- listingFile = new File(Files.move(tempContribFile.toPath(), tempContribFile.toPath().resolveSibling(listingFile.toPath())).toString());
- } catch (IOException e) {
- e.printStackTrace();
+ if (listingFile.exists()) {
+ listingFile.delete(); // may silently fail, but below may still work
+ }
+ if (tempContribFile.renameTo(listingFile)) {
+ listDownloaded = true;
+ listDownloadFailed = false;
+ try {
+ // TODO: run this in SwingWorker done() [jv]
+ EventQueue.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ setAdvertisedList(listingFile);
+ base.setUpdatesAvailable(countUpdates(base));
+ }
+ });
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else {
+ cause.printStackTrace();
+ }
+ }
+ } else {
+ listDownloadFailed = true;
}
- hasDownloadedLatestList = true;
- hasListDownloadFailed = false;
- setAdvertisedList(listingFile);
}
- else
- hasListDownloadFailed = true;
+
+ } catch (MalformedURLException e) {
+ progress.error(e);
+ progress.finished();
+ } finally {
+ downloadingListingLock.unlock();
}
- downloadingListingLock.unlock();
}
}, "Contribution List Downloader").start();
}
- boolean hasUpdates() {
- for (Contribution info : allContributions) {
- if (hasUpdates(info)) {
- return true;
- }
- }
- return false;
- }
-
- boolean hasUpdates(Base base) {
- for (ModeContribution m : base.getModeContribs())
- if (hasUpdates(m))
- return true;
- for (Library l : base.getActiveEditor().getMode().contribLibraries)
- if (hasUpdates(l))
- return true;
- for (ToolContribution t : base.getActiveEditor().contribTools)
- if (hasUpdates(t))
- return true;
- return false;
- }
-
-
- boolean hasUpdates(Contribution contribution) {
+ protected boolean hasUpdates(Contribution contribution) {
if (contribution.isInstalled()) {
Contribution advertised = getAvailableContribution(contribution);
if (advertised == null) {
return false;
}
- return advertised.getVersion() > contribution.getVersion();
+ return advertised.getVersion() > contribution.getVersion()
+ && advertised.isCompatible(Base.getRevision());
}
return false;
}
- String getLatestVersion(Contribution contribution) {
+ protected String getLatestPrettyVersion(Contribution contribution) {
Contribution newestContrib = getAvailableContribution(contribution);
- String latestVersion = newestContrib.getPrettyVersion();
- if (latestVersion != null && !latestVersion.isEmpty()) {
- if (latestVersion.toLowerCase().startsWith("build")) // For Python mode
- return ("v" + latestVersion.substring(5, latestVersion.indexOf(','))
- .trim());
- else if (latestVersion.toLowerCase().startsWith("v")) // For ketai library
- return latestVersion;
- else
- return ("v" + latestVersion);
- }
- else
+ if (newestContrib == null) {
return null;
+ }
+ return newestContrib.getPrettyVersion();
}
-
- boolean hasDownloadedLatestList() {
- return hasDownloadedLatestList;
+ protected boolean hasDownloadedLatestList() {
+ return listDownloaded;
}
- boolean hasListDownloadFailed() {
- return hasListDownloadFailed;
+ protected boolean hasListDownloadFailed() {
+ return listDownloadFailed;
}
-// /**
-// * @return a lowercase string with all non-alphabetic characters removed
-// */
-// static protected String normalize(String s) {
-// return s.toLowerCase().replaceAll("^\\p{Lower}", "");
-// }
-
-
-// /**
-// * @return the proper, valid name of this category to be displayed in the UI
-// * (e.g. "Typography / Geometry"). "Unknown" if the category null.
-// */
-// static public String getCategory(String category) {
-// if (category == null) {
-// return "Unknown";
-// }
-// String normCatName = normalize(category);
-//
-// for (String validCatName : validCategories) {
-// String normValidCatName = normalize(validCatName);
-// if (normValidCatName.equals(normCatName)) {
-// return validCatName;
-// }
-// }
-// return category;
-// }
-
-
- ArrayList parseContribList(File file) {
- ArrayList outgoing = new ArrayList();
+ private List parseContribList(File file) {
+ List outgoing =
+ new ArrayList<>();
if (file != null && file.exists()) {
String[] lines = PApplet.loadStrings(file);
@@ -534,7 +473,7 @@ ArrayList parseContribList(File file) {
System.err.println("Error in contribution listing file on line " + (start+1));
// Scan forward for the next blank line
int end = ++start;
- while (end < lines.length && lines[end].trim().length() != 0) {
+ while (end < lines.length && !lines[end].trim().isEmpty()) {
end++;
}
start = end + 1;
@@ -542,14 +481,12 @@ ArrayList parseContribList(File file) {
} else {
// Scan forward for the next blank line
int end = ++start;
- while (end < lines.length && lines[end].trim().length() != 0) {
+ while (end < lines.length && !lines[end].trim().isEmpty()) {
end++;
}
String[] contribLines = PApplet.subset(lines, start, end-start);
-
- Map contribParams = Base.readSettings(file.getName(), contribLines);
-
+ StringDict contribParams = Util.readSettings(file.getName(), contribLines);
outgoing.add(new AvailableContribution(contribType, contribParams));
start = end + 1;
}
@@ -559,19 +496,59 @@ ArrayList parseContribList(File file) {
}
-// boolean isDownloadingListing() {
-// return downloadingListingLock.isLocked();
-// }
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+ /**
+ * TODO This needs to be called when the listing loads, and also whenever
+ * the contribs list has been updated (for whatever reason). In addition,
+ * the caller (presumably Base) should update all Editor windows with the
+ * correct information on the number of items available.
+ * @return The number of contributions that have available updates.
+ */
+ public int countUpdates(Base base) {
+ int count = 0;
+ for (ModeContribution mc : base.getModeContribs()) {
+ if (hasUpdates(mc)) {
+ count++;
+ }
+ }
+ for (Library lib : base.getActiveEditor().getMode().contribLibraries) {
+ if (hasUpdates(lib)) {
+ count++;
+ }
+ }
+ for (Library lib : base.getActiveEditor().getMode().coreLibraries) {
+ if (hasUpdates(lib)) {
+ count++;
+ }
+ }
+ for (ToolContribution tc : base.getToolContribs()) {
+ if (hasUpdates(tc)) {
+ count++;
+ }
+ }
+ for (ExamplesContribution ec : base.getExampleContribs()) {
+ if (hasUpdates(ec)) {
+ count++;
+ }
+ }
+ return count;
+ }
- public Comparator super Contribution> getComparator() {
- return nameComparator;
+ /** Used by JavaEditor to auto-import */
+ public Map getLibrariesByImportHeader() {
+ return librariesByImportHeader;
}
- static Comparator nameComparator = new Comparator() {
- public int compare(Contribution o1, Contribution o2) {
- return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase());
- }
- };
+ static public Comparator COMPARATOR = Comparator.comparing(o -> o.getName().toLowerCase());
+
+
+ public interface ChangeListener {
+ public void contributionAdded(Contribution Contribution);
+ public void contributionRemoved(Contribution Contribution);
+ public void contributionChanged(Contribution oldLib, Contribution newLib);
+ }
}
diff --git a/app/src/processing/app/contrib/ContributionManager.java b/app/src/processing/app/contrib/ContributionManager.java
index b6b467b766..af9c4d8995 100644
--- a/app/src/processing/app/contrib/ContributionManager.java
+++ b/app/src/processing/app/contrib/ContributionManager.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2013 The Processing Foundation
+ Copyright (c) 2013-20 The Processing Foundation
Copyright (c) 2011-12 Ben Fry and Casey Reas
This program is free software; you can redistribute it and/or modify
@@ -21,52 +21,66 @@
*/
package processing.app.contrib;
+import java.awt.EventQueue;
import java.io.*;
+import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.util.*;
import javax.swing.SwingWorker;
import processing.app.Base;
-import processing.app.Editor;
import processing.app.Language;
+import processing.app.Messages;
+import processing.app.Util;
+import processing.app.ui.Editor;
+import processing.core.PApplet;
+import processing.data.StringDict;
public class ContributionManager {
- static public final ContributionListing contribListing;
-
- static {
- contribListing = ContributionListing.getInstance();
- }
+ static ContributionListing listing;
/**
- * Blocks until the file is downloaded or an error occurs. Returns true if the
- * file was successfully downloaded, false otherwise.
+ * Blocks until the file is downloaded or an error occurs.
*
- * @param source
- * the URL of the file to download
- * @param dest
- * the file on the local system where the file will be written. This
- * must be a file (not a directory), and must already exist.
- * @param progress
- * null if progress is irrelevant, such as when downloading for an
- * install during startup, when the ProgressMonitor is useless since
- * UI isn't setup yet.
- * @throws FileNotFoundException
- * if an error occurred downloading the file
+ * @param source the URL of the file to download
+ * @param post Binary blob of POST data if a payload should be sent.
+ * Must already be URL-encoded and will be Gzipped for upload.
+ * @param dest The file on the local system where the file will be written.
+ * This must be a file (not a directory), and must already exist.
+ * @param progress null if progress is irrelevant, such as when downloading
+ * for an install during startup, when the ProgressMonitor
+ * is useless since UI isn't setup yet.
+ *
+ * @return true if the file was successfully downloaded, false otherwise.
*/
- static boolean download(URL source, File dest, ContribProgressMonitor progress) {
+ static boolean download(URL source, byte[] post,
+ File dest, ContribProgressMonitor progress) {
boolean success = false;
try {
-// System.out.println("downloading file " + source);
-// URLConnection conn = source.openConnection();
HttpURLConnection conn = (HttpURLConnection) source.openConnection();
+ // Will not handle a protocol change (see below)
HttpURLConnection.setFollowRedirects(true);
conn.setConnectTimeout(15 * 1000);
conn.setReadTimeout(60 * 1000);
- conn.setRequestMethod("GET");
- conn.connect();
+
+ if (post == null) {
+ conn.setRequestMethod("GET");
+ conn.connect();
+
+ } else {
+ post = Util.gzipEncode(post);
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
+ conn.setRequestProperty("Content-Encoding", "gzip");
+ conn.setRequestProperty("Content-Length", String.valueOf(post.length));
+ conn.setUseCaches(false);
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.getOutputStream().write(post);
+ }
if (progress != null) {
// TODO this is often -1, may need to set progress to indeterminate
@@ -76,27 +90,37 @@ static boolean download(URL source, File dest, ContribProgressMonitor progress)
progress.startTask(Language.text("contrib.progress.downloading"), fileSize);
}
- InputStream in = conn.getInputStream();
- FileOutputStream out = new FileOutputStream(dest);
+ int response = conn.getResponseCode();
+ // Default won't follow HTTP -> HTTPS redirects for security reasons
+ // http://stackoverflow.com/a/1884427
+ if (response >= 300 && response < 400) {
+ // Handle SSL redirects from HTTP sources
+ // https://github.com/processing/processing/issues/5554
+ String newLocation = conn.getHeaderField("Location");
+ return download(new URL(newLocation), post, dest, progress);
- byte[] b = new byte[8192];
- int amount;
- if (progress != null) {
- int total = 0;
- while (!progress.isCanceled() && (amount = in.read(b)) != -1) {
- out.write(b, 0, amount);
- total += amount;
- progress.setProgress(total);
- }
} else {
- while ((amount = in.read(b)) != -1) {
- out.write(b, 0, amount);
+ InputStream in = conn.getInputStream();
+ FileOutputStream out = new FileOutputStream(dest);
+
+ byte[] b = new byte[8192];
+ int amount;
+ if (progress != null) {
+ int total = 0;
+ while (!progress.isCanceled() && (amount = in.read(b)) != -1) {
+ out.write(b, 0, amount);
+ total += amount;
+ progress.setProgress(total);
+ }
+ } else {
+ while ((amount = in.read(b)) != -1) {
+ out.write(b, 0, amount);
+ }
}
+ out.flush();
+ out.close();
+ success = true;
}
- out.flush();
- out.close();
- success = true;
-
} catch (SocketTimeoutException ste) {
if (progress != null) {
progress.error(ste);
@@ -107,8 +131,6 @@ static boolean download(URL source, File dest, ContribProgressMonitor progress)
progress.error(ioe);
progress.cancel();
}
- // Hiding stack trace. An error has been shown where needed.
-// ioe.printStackTrace();
}
if (progress != null) {
progress.finished();
@@ -128,13 +150,13 @@ static boolean download(URL source, File dest, ContribProgressMonitor progress)
* old version of a contribution that is being updated). Must not be
* null.
*/
- static void downloadAndInstall(final Editor editor,
+ static void downloadAndInstall(final Base base,
final URL url,
final AvailableContribution ad,
final ContribProgressBar downloadProgress,
final ContribProgressBar installProgress,
final StatusPanel status) {
-
+ // TODO: replace with SwingWorker [jv]
new Thread(new Runnable() {
public void run() {
String filename = url.getFile();
@@ -144,26 +166,41 @@ public void run() {
contribZip.setWritable(true); // necessary?
try {
- download(url, contribZip, downloadProgress);
+ download(url, null, contribZip, downloadProgress);
if (!downloadProgress.isCanceled() && !downloadProgress.isError()) {
installProgress.startTask(Language.text("contrib.progress.installing"), ContribProgressMonitor.UNKNOWN);
- LocalContribution contribution =
- ad.install(editor.getBase(), contribZip, false, status);
+ final LocalContribution contribution =
+ ad.install(base, contribZip, false, status);
if (contribution != null) {
- contribListing.replaceContribution(ad, contribution);
- if (contribution.getType() == ContributionType.MODE) {
- ArrayList contribModes = editor.getBase().getModeContribs();
- if (!contribModes.contains(contribution)) {
- contribModes.add((ModeContribution) contribution);
- }
+ try {
+ // TODO: run this in SwingWorker done() [jv]
+ EventQueue.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ listing.replaceContribution(ad, contribution);
+ /*
+ if (contribution.getType() == ContributionType.MODE) {
+ List contribModes = editor.getBase().getModeContribs();
+ if (!contribModes.contains(contribution)) {
+ contribModes.add((ModeContribution) contribution);
+ }
+ }
+ */
+ base.refreshContribs(contribution.getType());
+ base.setUpdatesAvailable(listing.countUpdates(base));
+ }
+ });
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ throw (Exception) e.getCause();
}
- refreshInstalled(editor);
}
installProgress.finished();
- }
- else {
+
+ } else {
if (downloadProgress.exception instanceof SocketTimeoutException) {
status.setErrorMessage(Language
.interpolate("contrib.errors.contrib_download.timeout",
@@ -177,12 +214,24 @@ public void run() {
contribZip.delete();
} catch (Exception e) {
- // Hiding stack trace. The error message ought to suffice.
-// e.printStackTrace();
- status
- .setErrorMessage(Language
- .interpolate("contrib.errors.download_and_install",
- ad.getName()));
+ String msg = null;
+ if (e instanceof RuntimeException) {
+ Throwable cause = ((RuntimeException) e).getCause();
+ if (cause instanceof NoClassDefFoundError ||
+ cause instanceof NoSuchMethodError) {
+ msg = "This item is not compatible with this version of Processing";
+ } else if (cause instanceof UnsupportedClassVersionError) {
+ msg = "This item needs to be recompiled for Java " +
+ PApplet.javaPlatform;
+ }
+ }
+
+ if (msg == null) {
+ msg = Language.interpolate("contrib.errors.download_and_install", ad.getName());
+ // Something unexpected, so print the trace
+ e.printStackTrace();
+ }
+ status.setErrorMessage(msg);
downloadProgress.cancel();
installProgress.cancel();
}
@@ -200,17 +249,15 @@ public void run() {
/**
* Non-blocking call to download and install a contribution in a new thread.
* Used when information about the progress of the download and install
- * procedure is not of importance, such as if a contribution has to be
+ * procedure is not of importance, such as if a contribution has to be
* installed at startup time.
- *
- * @param url
- * Direct link to the contribution.
- * @param ad
- * The AvailableContribution to be downloaded and installed.
+ *
+ * @param url Direct link to the contribution.
+ * @param ad The AvailableContribution to be downloaded and installed.
*/
static void downloadAndInstallOnStartup(final Base base, final URL url,
final AvailableContribution ad) {
-
+ // TODO: replace with SwingWorker [jv]
new Thread(new Runnable() {
public void run() {
String filename = url.getFile();
@@ -220,32 +267,38 @@ public void run() {
contribZip.setWritable(true); // necessary?
try {
- download(url, contribZip, null);
+ download(url, null, contribZip, null);
- LocalContribution contribution = ad.install(base, contribZip,
+ final LocalContribution contribution = ad.install(base, contribZip,
false, null);
if (contribution != null) {
- contribListing.replaceContribution(ad, contribution);
- if (contribution.getType() == ContributionType.MODE) {
- ArrayList contribModes = base
- .getModeContribs();
- if (contribModes != null && !contribModes.contains(contribution)) {
- contribModes.add((ModeContribution) contribution);
+ try {
+ // TODO: run this in SwingWorker done() [jv]
+ EventQueue.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ listing.replaceContribution(ad, contribution);
+ base.refreshContribs(contribution.getType());
+ base.setUpdatesAvailable(listing.countUpdates(base));
+ }
+ });
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else {
+ cause.printStackTrace();
}
}
- if (base.getActiveEditor() != null) {
- refreshInstalled(base.getActiveEditor());
- }
}
contribZip.delete();
-
handleUpdateFailedMarkers(ad, filename.substring(0, filename.lastIndexOf('.')));
} catch (Exception e) {
-// Chuck the stack trace. The user might have no idea why it is appearing, or what (s)he did wrong...
-// e.printStackTrace();
String arg = "contrib.startup.errors.download_install";
System.err.println(Language.interpolate(arg, ad.getName()));
}
@@ -258,42 +311,47 @@ public void run() {
}
-/**
- * After install, this function checks whether everything went properly or not.
- * If not, it adds a marker file so that the next time Processing is started, installPreviouslyFailed()
- * can install the contribution.
- * @param ac
- * The contribution just installed.
- * @param filename
- * The name of the folder in which the contribution is supposed to be stored.
- */
- static private void handleUpdateFailedMarkers(final AvailableContribution ac, String filename) {
-
- File contribLocn = ac.getType().getSketchbookFolder();
+ /**
+ * After install, this function checks whether everything went properly.
+ * If not, it adds a marker file so that the next time Processing is started,
+ * installPreviouslyFailed() can install the contribution.
+ * @param c the contribution just installed
+ * @param filename name of the folder for the contribution
+ */
+ static private void handleUpdateFailedMarkers(final AvailableContribution c,
+ String filename) {
+ File typeFolder = c.getType().getSketchbookFolder();
- for (File contribDir : contribLocn.listFiles())
+ for (File contribDir : typeFolder.listFiles()) {
if (contribDir.isDirectory()) {
+ /*
File[] contents = contribDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String file) {
- return file.equals(ac.getType() + ".properties");
+ return file.equals(c.getType() + ".properties");
}
});
- if (contents.length > 0 && Base.readSettings(contents[0]).get("name").equals(ac.getName())) {
+ if (contents.length > 0 && Util.readSettings(contents[0]).get("name").equals(c.getName())) {
return;
}
+ */
+ File propsFile = new File(contribDir, c.getType() + ".properties");
+ if (propsFile.exists()) {
+ StringDict props = Util.readSettings(propsFile);
+ if (c.getName().equals(props.get("name"))) {
+ return;
+ }
+ }
}
+ }
try {
- new File(contribLocn, ac.getName()).createNewFile();
+ new File(typeFolder, c.getName()).createNewFile();
} catch (IOException e) {
-// Again, forget about the stack trace. The user ain't done wrong
-// e.printStackTrace();
String arg = "contrib.startup.errors.new_marker";
- System.err.println(Language.interpolate(arg, ac.getName()));
+ System.err.println(Language.interpolate(arg, c.getName()));
}
-
}
@@ -303,29 +361,28 @@ public boolean accept(File dir, String file) {
* anything and providing feedback via the console status area, such as when
* the user tries to run a sketch that imports uninstaled libraries.
*
- * @param aList
- * The list of AvailableContributions to be downloaded and installed.
+ * @param list The list of AvailableContributions to be downloaded and installed.
*/
- public static void downloadAndInstallOnImport(final Base base,
- final ArrayList aList) {
+ static public void downloadAndInstallOnImport(final Base base,
+ final List