diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 52331e2e3..701c38142 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -1683,6 +1683,11 @@ public class Base { } + public File getSketchbookToolsFolder() { + return new File(sketchbookFolder, "tools"); + } + + static protected File getDefaultSketchbookFolder() { File sketchbookFolder = null; try { diff --git a/app/src/processing/app/ContributionListPanel.java b/app/src/processing/app/ContributionListPanel.java index 0ecd6fe36..7a08b33ac 100644 --- a/app/src/processing/app/ContributionListPanel.java +++ b/app/src/processing/app/ContributionListPanel.java @@ -100,21 +100,13 @@ public class ContributionListPanel extends JPanel implements Scrollable, Contrib public void contributionAdded(ContributionInfo contributionInfo) { - if (setupProgressBar.isVisible()) { - setupProgressBar.setVisible(false); - } + setupProgressBar.setVisible(false); if (contributionPanelsByInfo.containsKey(contributionInfo)) { return; } - ContributionPanel newPanel = null; - if (contributionInfo.getType() == ContributionType.LIBRARY) { - newPanel = new ContributionPanel(); - - } else if (contributionInfo.getType() == ContributionType.LIBRARY_COMPILATION) { - newPanel = new ContributionPanel(); - } + ContributionPanel newPanel = new ContributionPanel(); synchronized (contributionPanelsByInfo) { contributionPanelsByInfo.put(contributionInfo, newPanel); diff --git a/app/src/processing/app/ContributionListing.java b/app/src/processing/app/ContributionListing.java index 483098320..80702e832 100644 --- a/app/src/processing/app/ContributionListing.java +++ b/app/src/processing/app/ContributionListing.java @@ -37,6 +37,7 @@ import processing.app.Contribution.ContributionInfo.Author; import processing.app.Contribution.ContributionInfo.ContributionType; import processing.app.Library.LibraryInfo; import processing.app.LibraryCompilation.LibraryCompilationInfo; +import processing.app.ToolContribution.ToolInfo; public class ContributionListing { @@ -141,21 +142,21 @@ public class ContributionListing { notifyChange(oldLib, newLib); } - public void addContribution(ContributionInfo libInfo) { + public void addContribution(ContributionInfo info) { - if (librariesByCategory.containsKey(libInfo.category)) { - List list = librariesByCategory.get(libInfo.category); - list.add(libInfo); + if (librariesByCategory.containsKey(info.category)) { + List list = librariesByCategory.get(info.category); + list.add(info); Collections.sort(list); } else { - ArrayList libs = new ArrayList(); - libs.add(libInfo); - librariesByCategory.put(libInfo.category, libs); + ArrayList list = new ArrayList(); + list.add(info); + librariesByCategory.put(info.category, list); } - allLibraries.add(libInfo); + allLibraries.add(info); - notifyAdd(libInfo); + notifyAdd(info); Collections.sort(allLibraries); } @@ -361,7 +362,12 @@ public class ContributionListing { */ private static class ContributionXmlParser extends DefaultHandler { - ArrayList libraries; + final static String LIBRARY_TAG = "library"; + final static String LIBRARY_COMPILATION_TAG = "librarycompilation"; + final static String TOOL_TAG = "tool"; + //final static String MODE_TAG = "mode"; + + ArrayList contributions; String currentCategoryName; @@ -376,7 +382,7 @@ public class ContributionListing { InputSource input = new InputSource(new FileReader(xmlFile)); - libraries = new ArrayList(); + contributions = new ArrayList(); sp.parse(input, this); // throws SAXException } catch (ParserConfigurationException e) { @@ -393,12 +399,12 @@ public class ContributionListing { "The list of libraries downloaded from Processing.org\n" + "appears to be malformed. You can still install libraries\n" + "manually while we work on fixing this.", e); - libraries = null; + contributions = null; } } public ArrayList getLibraries() { - return libraries; + return contributions; } @Override @@ -408,11 +414,11 @@ public class ContributionListing { if ("category".equals(qName)) { currentCategoryName = attributes.getValue("name"); - } else if ("library".equals(qName)) { + } else if (LIBRARY_TAG.equals(qName)) { currentInfo = new LibraryInfo(); setCommonAttributes(attributes); - } else if ("librarycompilation".equals(qName)) { + } else if (LIBRARY_COMPILATION_TAG.equals(qName)) { LibraryCompilationInfo compilationInfo = new LibraryCompilationInfo(); String[] names = attributes.getValue("libraryNames").split(";"); for (int i = 0; i < names.length; i++) { @@ -422,6 +428,10 @@ public class ContributionListing { currentInfo = compilationInfo; setCommonAttributes(attributes); + } else if (TOOL_TAG.equals(qName)) { + currentInfo = new ToolInfo(); + setCommonAttributes(attributes); + } else if ("author".equals(qName)) { Author author = new Author(); author.name = attributes.getValue("name"); @@ -454,8 +464,9 @@ public class ContributionListing { public void endElement(String uri, String localName, String qName) throws SAXException { - if ("library".equals(qName) || "librarycompilation".equals(qName)) { - libraries.add(currentInfo); + if (LIBRARY_TAG.equals(qName) || LIBRARY_COMPILATION_TAG.equals(qName) + || TOOL_TAG.equals(qName)) { + contributions.add(currentInfo); currentInfo = null; } } diff --git a/app/src/processing/app/ContributionManager.java b/app/src/processing/app/ContributionManager.java index 7dd4f9d49..7bf32ac14 100644 --- a/app/src/processing/app/ContributionManager.java +++ b/app/src/processing/app/ContributionManager.java @@ -309,18 +309,21 @@ public class ContributionManager { ArrayList libraries = editor.getMode().contribLibraries; ArrayList compilations = LibraryCompilation.list(libraries); + // Remove libraries from the list that are part of a compilations for (LibraryCompilation compilation : compilations) { for (Library lib : compilation.libraries) { libraries.remove(lib); } } + ArrayList contributions = new ArrayList(); + contributions.addAll(editor.contribTools); + contributions.addAll(libraries); + contributions.addAll(compilations); + ArrayList infoList = new ArrayList(); - for (Library library : libraries) { - infoList.add(library.info); - } - for (LibraryCompilation compilation : compilations) { - infoList.add(compilation.info); + for (Contribution contribution : contributions) { + infoList.add(contribution.getInfo()); } contributionListing.updateInstalledList(infoList); @@ -372,19 +375,22 @@ public class ContributionManager { public void run() { - File libFile = downloader.getFile(); + File contributionFile = downloader.getFile(); - if (libFile != null) { + if (contributionFile != null) { installProgressMonitor.startTask("Installing", ProgressMonitor.UNKNOWN); Contribution contribution = null; switch (info.getType()) { case LIBRARY: - contribution = installLibrary(libFile, false); + contribution = installLibrary(contributionFile, false); break; case LIBRARY_COMPILATION: - contribution = installLibraryCompilation(libFile); + contribution = installLibraryCompilation(contributionFile); + break; + case TOOL: + contribution = installTool(contributionFile); break; } @@ -531,6 +537,71 @@ public class ContributionManager { return fileName; } + protected ToolContribution installTool(File zippedToolFile) { + File tempDir = unzipFileToTemp(zippedToolFile); + + ArrayList discoveredTools = ToolContribution.list(tempDir); + if (discoveredTools.isEmpty()) { + // Sometimes tool authors place all their folders in the base + // directory of a zip file instead of in single folder as the + // guidelines suggest. If this is the case, we might be able to find the + // library by stepping up a directory and searching for libraries again. + discoveredTools = ToolContribution.list(tempDir.getParentFile()); + } + + if (discoveredTools != null && discoveredTools.size() == 1) { + ToolContribution discoveredTool = discoveredTools.get(0); + return installTool(discoveredTool); + } else { + // Diagnose the problem and notify the user + if (discoveredTools == null || discoveredTools.isEmpty()) { + Base.showWarning(DISCOVERY_ERROR_TITLE, + DISCOVERY_INTERNAL_ERROR_MESSAGE, null); + } else { + Base.showWarning("Too many tools", + "We found more than one tool in the file we just\n" + + "downloaded. That shouldn't happen, so we're going\n" + + "to ignore this file.", null); + } + } + + return null; + } + + protected ToolContribution installTool(ToolContribution newTool) { + + ArrayList oldTools = editor.contribTools; + + String toolFolderName = newTool.folder.getName(); + + File toolDestination = editor.getBase().getSketchbookToolsFolder(); + File newToolDest = new File(toolDestination, toolFolderName); + + for (ToolContribution oldTool : oldTools) { + + // XXX: Handle other cases when installing libraries. + // -What if a library by the same name is already installed? + // -What if newLibDest exists, but isn't used by an existing library? + if (oldTool.folder.exists() && oldTool.folder.equals(newToolDest)) { + + if (!backupContribution(oldTool)) { + return null; + } + } + } + + // Move newLib to the sketchbook library folder + if (newTool.folder.renameTo(newToolDest)) { + return ToolContribution.getTool(newToolDest); + } else { + Base.showWarning("Trouble moving new tool to the sketchbook", + "Could not move tool \"" + newTool.info.name + "\" to " + + newToolDest.getAbsolutePath() + ".\n", null); + } + + return null; + } + protected Library installLibrary(File libFile, boolean confirmReplace) { File tempDir = unzipFileToTemp(libFile); @@ -575,7 +646,6 @@ public class ContributionManager { } /** - * * @param confirmReplace * if true and the library is already installed, opens a prompt to * ask the user if it's okay to replace the library. If false, the @@ -639,30 +709,31 @@ public class ContributionManager { } public void refreshInstalled() { - editor.getMode().rebuildLibraryList(); editor.getMode().rebuildImportMenu(); + editor.rebuildToolMenu(); } /** * Moves the given contribution to a backup folder. */ - private boolean backupContribution(Contribution lib) { + private boolean backupContribution(Contribution contribution) { File backupFolder = null; - switch (lib.getInfo().getType()) { + switch (contribution.getInfo().getType()) { case LIBRARY: case LIBRARY_COMPILATION: backupFolder = createLibraryBackupFolder(); break; case MODE: case TOOL: + backupFolder = createToolBackupFolder(); break; } if (backupFolder == null) return false; - String libFolderName = lib.getFolder().getName(); + String libFolderName = contribution.getFolder().getName(); String prefix = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); final String backupName = prefix + "_" + libFolderName; @@ -671,21 +742,17 @@ public class ContributionManager { // try { // FileUtils.moveDirectory(lib.folder, backupFolderForLib); // return true; - if (lib.getFolder().renameTo(backupFolderForLib)) { + if (contribution.getFolder().renameTo(backupFolderForLib)) { return true; } else { // } catch (IOException e) { - Base.showWarning("Trouble creating backup of old \"" + lib.getInfo().name + "\" library", + Base.showWarning("Trouble creating backup of old \"" + contribution.getInfo().name + "\" library", "Could not move library to backup folder:\n" + backupFolderForLib.getAbsolutePath(), null); return false; } } - /** - * @return false if there was an error creating the backup folder, true if it - * already exists or was created successfully - */ private File createLibraryBackupFolder() { File libraryBackupFolder = new File(editor.getBase() @@ -705,6 +772,26 @@ public class ContributionManager { return libraryBackupFolder; } + + private File createToolBackupFolder() { + + File toolsBackupFolder = new File(editor.getBase() + .getSketchbookToolsFolder(), "old"); + + if (!toolsBackupFolder.exists() || !toolsBackupFolder.isDirectory()) { + if (!toolsBackupFolder.mkdirs()) { + Base.showWarning("Trouble creating folder to store old libraries in", + "Could not create folder " + + toolsBackupFolder.getAbsolutePath() + + ".\n" + + "That's gonna prevent us from replacing the library.", + null); + return null; + } + } + + return toolsBackupFolder; + } /** * Returns a file in the parent folder that does not exist yet. If diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 3f9cd1949..5e8180306 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -31,9 +31,7 @@ import java.awt.datatransfer.*; import java.awt.event.*; import java.awt.print.*; import java.io.*; -import java.net.*; import java.util.*; -import java.util.zip.*; import javax.swing.*; import javax.swing.event.*; @@ -98,8 +96,11 @@ public abstract class Editor extends JFrame implements RunnerListener { private final Stack caretRedoStack = new Stack(); private FindReplace find; + JMenu toolsMenu; JMenu modeMenu; + ArrayList coreTools; + ArrayList contribTools; protected Editor(final Base base, String path, int[] location, final Mode mode) { super("Processing"); @@ -451,7 +452,9 @@ public abstract class Editor extends JFrame implements RunnerListener { menubar.add(fileMenu); menubar.add(buildEditMenu()); menubar.add(buildSketchMenu()); - menubar.add(buildToolsMenu()); + rebuildToolList(); + rebuildToolMenu(); + menubar.add(getToolMenu()); JMenu modeMenu = buildModeMenu(); if (modeMenu != null) { @@ -804,109 +807,51 @@ public abstract class Editor extends JFrame implements RunnerListener { abstract public void handleImportLibrary(String jarPath); - protected JMenu buildToolsMenu() { - JMenu menu = new JMenu("Tools"); - - addInternalTools(menu); - addTools(menu, base.getToolsFolder()); - File sketchbookTools = new File(base.getSketchbookFolder(), "tools"); - addTools(menu, sketchbookTools); - - return menu; + public JMenu getToolMenu() { + if (toolsMenu == null) { + rebuildToolMenu(); + } + return toolsMenu; } - protected void addTools(JMenu menu, File sourceFolder) { + protected void rebuildToolList() { + coreTools = ToolContribution.list(base.getToolsFolder()); + contribTools = ToolContribution.list(base.getSketchbookToolsFolder()); + } + + + protected void rebuildToolMenu() { + if (toolsMenu == null) { + toolsMenu = new JMenu("Tools"); + } else { + toolsMenu.removeAll(); + } + + rebuildToolList(); + + addInternalTools(toolsMenu); + addTools(toolsMenu, coreTools); + addTools(toolsMenu, contribTools); + } + + + protected void addTools(JMenu menu, ArrayList tools) { HashMap toolItems = new HashMap(); - File[] folders = sourceFolder.listFiles(new FileFilter() { - public boolean accept(File folder) { - if (folder.isDirectory()) { - //System.out.println("checking " + folder); - File subfolder = new File(folder, "tool"); - return subfolder.exists(); + for (final ToolContribution tool : tools) { + String title = tool.getMenuTitle(); + JMenuItem item = new JMenuItem(title); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + SwingUtilities.invokeLater(tool); + //new Thread(tool).start(); } - return false; - } - }); - - if (folders == null || folders.length == 0) { - return; - } - - for (int i = 0; i < folders.length; i++) { - File toolDirectory = new File(folders[i], "tool"); - - try { - // add dir to classpath for .classes - //urlList.add(toolDirectory.toURL()); - - // add .jar files to classpath - File[] archives = toolDirectory.listFiles(new FilenameFilter() { - public boolean accept(File dir, String name) { - return (name.toLowerCase().endsWith(".jar") || - name.toLowerCase().endsWith(".zip")); - } - }); - - URL[] urlList = new URL[archives.length]; - for (int j = 0; j < urlList.length; j++) { - urlList[j] = archives[j].toURI().toURL(); - } - URLClassLoader loader = new URLClassLoader(urlList); - - String className = null; - for (int j = 0; j < archives.length; j++) { - className = findClassInZipFile(folders[i].getName(), archives[j]); - if (className != null) break; - } - - /* - // Alternatively, could use manifest files with special attributes: - // http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html - // Example code for loading from a manifest file: - // http://forums.sun.com/thread.jspa?messageID=3791501 - File infoFile = new File(toolDirectory, "tool.txt"); - if (!infoFile.exists()) continue; - - String[] info = PApplet.loadStrings(infoFile); - //Main-Class: org.poo.shoe.AwesomerTool - //String className = folders[i].getName(); - String className = null; - for (int k = 0; k < info.length; k++) { - if (info[k].startsWith(";")) continue; - - String[] pieces = PApplet.splitTokens(info[k], ": "); - if (pieces.length == 2) { - if (pieces[0].equals("Main-Class")) { - className = pieces[1]; - } - } - } - */ - // If no class name found, just move on. - if (className == null) continue; - - Class toolClass = Class.forName(className, true, loader); - final Tool tool = (Tool) toolClass.newInstance(); - - tool.init(Editor.this); - - String title = tool.getMenuTitle(); - JMenuItem item = new JMenuItem(title); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - SwingUtilities.invokeLater(tool); - //new Thread(tool).start(); - } - }); - //menu.add(item); - toolItems.put(title, item); - - } catch (Exception e) { - e.printStackTrace(); - } + }); + //menu.add(item); + toolItems.put(title, item); } + ArrayList toolList = new ArrayList(toolItems.keySet()); if (toolList.size() == 0) return; @@ -925,37 +870,6 @@ public abstract class Editor extends JFrame implements RunnerListener { return null; } - - protected String findClassInZipFile(String base, File file) { - // Class file to search for - String classFileName = "/" + base + ".class"; - - try { - ZipFile zipFile = new ZipFile(file); - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = (ZipEntry) entries.nextElement(); - - if (!entry.isDirectory()) { - String name = entry.getName(); - //System.out.println("entry: " + name); - - if (name.endsWith(classFileName)) { - //int slash = name.lastIndexOf('/'); - //String packageName = (slash == -1) ? "" : name.substring(0, slash); - // Remove .class and convert slashes to periods. - return name.substring(0, name.length() - 6).replace('/', '.'); - } - } - } - } catch (IOException e) { - //System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")"); - e.printStackTrace(); - } - return null; - } - - protected JMenuItem createToolMenuItem(String className) { try { Class toolClass = Class.forName(className); diff --git a/app/src/processing/app/ToolContribution.java b/app/src/processing/app/ToolContribution.java new file mode 100644 index 000000000..bb4eb7c27 --- /dev/null +++ b/app/src/processing/app/ToolContribution.java @@ -0,0 +1,201 @@ +package processing.app; + +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.zip.*; + +import processing.app.tools.Tool; + +public class ToolContribution extends Contribution implements Tool { + + Tool tool; + + ToolInfo info; + + File folder; + + static public ToolContribution getTool(File folder) { + try { + ToolContribution tool = new ToolContribution(folder); + if (tool.tool != null) + return tool; + } catch (Exception e) { + } + return null; + } + + private ToolContribution(File folder) throws Exception { + this.folder = folder; + + // XXX: This is repeated in LibraryCompilcation.java + File propertiesFile = new File(folder, "properties.txt"); + + info = new ToolInfo(); + info.tool = this; + + HashMap propertiesTable = Base.readSettings(propertiesFile); + readProperties(propertiesTable, info); + if (info.name == null) { + info.name = folder.getName(); + } + + File toolDirectory = new File(folder, "tool"); + // add dir to classpath for .classes + //urlList.add(toolDirectory.toURL()); + + // add .jar files to classpath + File[] archives = toolDirectory.listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + return (name.toLowerCase().endsWith(".jar") || + name.toLowerCase().endsWith(".zip")); + } + }); + + URL[] urlList = new URL[archives.length]; + for (int j = 0; j < urlList.length; j++) { + urlList[j] = archives[j].toURI().toURL(); + } + URLClassLoader loader = new URLClassLoader(urlList); + + String className = null; + for (int j = 0; j < archives.length; j++) { + className = findClassInZipFile(folder.getName(), archives[j]); + if (className != null) break; + } + + /* + // Alternatively, could use manifest files with special attributes: + // http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html + // Example code for loading from a manifest file: + // http://forums.sun.com/thread.jspa?messageID=3791501 + File infoFile = new File(toolDirectory, "tool.txt"); + if (!infoFile.exists()) continue; + + String[] info = PApplet.loadStrings(infoFile); + //Main-Class: org.poo.shoe.AwesomerTool + //String className = folders[i].getName(); + String className = null; + for (int k = 0; k < info.length; k++) { + if (info[k].startsWith(";")) continue; + + String[] pieces = PApplet.splitTokens(info[k], ": "); + if (pieces.length == 2) { + if (pieces[0].equals("Main-Class")) { + className = pieces[1]; + } + } + } + */ + // If no class name found, just move on. + if (className == null) return; + + Class toolClass = Class.forName(className, true, loader); + tool = (Tool) toolClass.newInstance(); + } + + protected String findClassInZipFile(String base, File file) { + // Class file to search for + String classFileName = "/" + base + ".class"; + + try { + ZipFile zipFile = new ZipFile(file); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) entries.nextElement(); + + if (!entry.isDirectory()) { + String name = entry.getName(); + //System.out.println("entry: " + name); + + if (name.endsWith(classFileName)) { + //int slash = name.lastIndexOf('/'); + //String packageName = (slash == -1) ? "" : name.substring(0, slash); + // Remove .class and convert slashes to periods. + zipFile.close(); + return name.substring(0, name.length() - 6).replace('/', '.'); + } + } + } + zipFile.close(); + } catch (IOException e) { + //System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")"); + e.printStackTrace(); + } + return null; + } + + static protected ArrayList list(File folder) { + ArrayList tools = new ArrayList(); + list(folder, tools); + return tools; + } + + static protected void list(File folder, ArrayList tools) { + + File[] folders = folder.listFiles(new FileFilter() { + public boolean accept(File folder) { + if (folder.isDirectory()) { + //System.out.println("checking " + folder); + File subfolder = new File(folder, "tool"); + return subfolder.exists(); + } + return false; + } + }); + + if (folders == null || folders.length == 0) { + return; + } + + for (int i = 0; i < folders.length; i++) { + try { + final ToolContribution tool = getTool(folders[i]); + if (tool != null) { + tools.add(tool); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + ContributionInfo getInfo() { + return info; + } + + File getFolder() { + return folder; + } + + public void init(Editor editor) { + tool.init(editor); + } + + public void run() { + tool.run(); + } + + public String getMenuTitle() { + return tool.getMenuTitle(); + } + + public static class ToolInfo extends ContributionInfo { + + ToolContribution tool; + + public ContributionType getType() { + return ContributionType.TOOL; + } + + public boolean isInstalled() { + return tool != null; + } + + public Contribution getContribution() { + return tool; + } + + } + +}