diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index dee3103de6d..6545b6ed222 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -1277,20 +1277,6 @@ protected void rebuildSketchbookMenu(JMenu menu) { } } - public LibraryList getIDELibs() { - if (libraries == null) - return new LibraryList(); - LibraryList res = new LibraryList(libraries); - res.removeAll(getUserLibs()); - return res; - } - - public LibraryList getUserLibs() { - if (libraries == null) - return new LibraryList(); - return libraries.filterLibrariesInSubfolder(getSketchbookFolder()); - } - public void rebuildImportMenu(JMenu importMenu) { importMenu.removeAll(); @@ -1304,34 +1290,10 @@ public void actionPerformed(ActionEvent e) { } }); importMenu.add(addLibraryMenuItem); - importMenu.addSeparator(); - // Split between user supplied libraries and IDE libraries - TargetPlatform targetPlatform = getTargetPlatform(); - - if (targetPlatform != null) { - LibraryList ideLibs = getIDELibs(); - LibraryList userLibs = getUserLibs(); + if (libraries != null) { try { - // Find the current target. Get the platform, and then select the - // correct name and core path. - PreferencesMap prefs = targetPlatform.getPreferences(); - if (prefs != null) { - String platformName = prefs.get("name"); - if (platformName != null) { - JMenuItem platformItem = new JMenuItem(_(platformName)); - platformItem.setEnabled(false); - importMenu.add(platformItem); - } - } - if (ideLibs.size() > 0) { - importMenu.addSeparator(); - addLibraries(importMenu, ideLibs); - } - if (userLibs.size() > 0) { - importMenu.addSeparator(); - addLibraries(importMenu, userLibs); - } + addLibraries(importMenu, libraries); } catch (IOException e) { e.printStackTrace(); } @@ -1344,101 +1306,137 @@ public void rebuildExamplesMenu(JMenu menu) { // Add examples from distribution "example" folder boolean found = addSketches(menu, examplesFolder, false); - if (found) menu.addSeparator(); + if (found) { + // Prepend a label + JMenuItem label = new JMenuItem(_("Core examples")); + label.setEnabled(false); + menu.add(label, 0); + } // Add examples from libraries - LibraryList ideLibs = getIDELibs(); - ideLibs.sort(); - for (Library lib : ideLibs) - addSketchesSubmenu(menu, lib, false); - - LibraryList userLibs = getUserLibs(); - if (userLibs.size()>0) { - menu.addSeparator(); - userLibs.sort(); - for (Library lib : userLibs) - addSketchesSubmenu(menu, lib, false); + if (libraries != null && !libraries.isEmpty()) { + for (LibraryList sub : libraries.getSubs()) { + if (found) + menu.addSeparator(); + + // Prepend a label + JMenuItem label = new JMenuItem(sub.getName()); + label.setEnabled(false); + menu.add(label); + + addLibraryExamples(menu, sub); + + found = true; + } } } catch (IOException e) { e.printStackTrace(); } } - public LibraryList scanLibraries(List folders) throws IOException { - LibraryList res = new LibraryList(); - for (File folder : folders) - res.addOrReplaceAll(scanLibraries(folder)); - return res; + protected void addLibraryExamples(JMenu menu, LibraryList libs) throws IOException { + // Add any subdirectories first + for (LibraryList sub : libs.getSubs()) { + if (sub.isEmpty()) + continue; + + JMenu submenu = new JMenu(sub.getName()); + addLibraryExamples(submenu, sub); + menu.add(submenu); + MenuScroller.setScrollerFor(submenu); + } + + // Then add the libraries directly in this folder + for (Library lib : libs.getLibs()) { + addSketchesSubmenu(menu, lib, false); + } } - public LibraryList scanLibraries(File folder) throws IOException { - LibraryList res = new LibraryList(); + public void updateLibraries() throws IOException { + // Calculate paths for libraries and examples + examplesFolder = getContentFile("examples"); + toolsFolder = getContentFile("tools"); - String list[] = folder.list(new OnlyDirs()); - // if a bad folder or something like that, this might come back null - if (list == null) - return res; + libraries = new LibraryList(null); + librariesFolders = new ArrayList(); - for (String libName : list) { - File subfolder = new File(folder, libName); - if (!Sketch.isSanitaryName(libName)) { - String mess = I18n.format(_("The library \"{0}\" 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)"), - libName); - Base.showMessage(_("Ignoring bad library name"), mess); - continue; + File ideLibs = getContentFile("libraries"); + libraries.addSub(scanLibraries(ideLibs, _("Libraries for all boards"), true)); + librariesFolders.add(ideLibs); + + TargetPlatform targetPlatform = getTargetPlatform(); + if (targetPlatform != null) { + File platformFolder = targetPlatform.getFolder(); + File platformLibs = new File(platformFolder, "libraries"); + librariesFolders.add(platformLibs); + libraries.addSub(scanLibraries(platformLibs, I18n.format(_("Libraries for {0}"), targetPlatform.getName()), true)); + + String core = getBoardPreferences().get("build.core"); + TargetPlatform referencedPlatform = null; + if (core.contains(":")) { + String referencedCore = core.split(":")[0]; + referencedPlatform = Base.getTargetPlatform(referencedCore, targetPlatform.getId()); + if (referencedPlatform != null) { + File referencedPlatformFolder = referencedPlatform.getFolder(); + File referencedLibs = new File(referencedPlatformFolder, "libraries"); + librariesFolders.add(referencedLibs); + libraries.addSub(scanLibraries(referencedLibs, I18n.format(_("Libraries for {0}"), referencedPlatform.getName()), true)); + } } + } + + File sketchbookLibs = getSketchbookLibrariesFolder(); + librariesFolders.add(sketchbookLibs); + libraries.addSub(scanLibraries(sketchbookLibs, _("Libraries from your sketchbook"), true)); + } + + public LibraryList scanLibraries(File folder, String name, boolean allow_legacy) throws IOException { + LibraryList res = new LibraryList(name != null ? name : folder.getName()); + File[] subfolders = folder.listFiles(new OnlyDirs()); + if (subfolders != null) { + for (File subfolder : subfolders) + scanLibraryFolder(res, subfolder, allow_legacy); + } + + res.sort(); + + return res; + } + + public void scanLibraryFolder(LibraryList list, File folder, boolean allow_legacy) throws IOException { + if (Library.isLibrary(folder)) { + // This looks like a library, add it try { - Library lib = Library.create(subfolder); + Library lib = Library.create(folder); // (also replace previously found libs with the same name) if (lib != null) - res.addOrReplace(lib); + list.addOrReplace(lib); } catch (IOException e) { System.out.println(I18n.format(_("Invalid library found in {0}: {1}"), - subfolder, e.getMessage())); + folder, e.getMessage())); } + } else { + // This didn't look like a library, see if we can find libraries + // in the subdirectories + LibraryList sub = scanLibraries(folder, null, false); + + if (!sub.isEmpty()) + list.addSub(sub); } - return res; } public void onBoardOrPortChange() { - TargetPlatform targetPlatform = getTargetPlatform(); - if (targetPlatform == null) - return; - - // Calculate paths for libraries and examples - examplesFolder = getContentFile("examples"); - toolsFolder = getContentFile("tools"); - - File platformFolder = targetPlatform.getFolder(); - librariesFolders = new ArrayList(); - librariesFolders.add(getContentFile("libraries")); - String core = getBoardPreferences().get("build.core"); - if (core.contains(":")) { - String referencedCore = core.split(":")[0]; - TargetPlatform referencedPlatform = Base.getTargetPlatform(referencedCore, targetPlatform.getId()); - if (referencedPlatform != null) { - File referencedPlatformFolder = referencedPlatform.getFolder(); - librariesFolders.add(new File(referencedPlatformFolder, "libraries")); - } - } - librariesFolders.add(new File(platformFolder, "libraries")); - librariesFolders.add(getSketchbookLibrariesFolder()); - - // Scan for libraries in each library folder. - // Libraries located in the latest folders on the list can override - // other libraries with the same name. + // Scan for libraries try { - libraries = scanLibraries(librariesFolders); + updateLibraries(); } catch (IOException e) { showWarning(_("Error"), _("Error loading libraries"), e); } // Populate importToLibraryTable importToLibraryTable = new HashMap(); - for (Library lib : libraries) { + for (Library lib : getLibraries()) { try { String headers[] = headerListFromIncludePath(lib.getSrcFolder()); for (String header : headers) { @@ -1493,9 +1491,8 @@ public void rebuildBoardsMenu(JMenu toolsMenu, Editor editor) throws Exception { first = false; // Add a title for each platform - String platformLabel = targetPlatform.getPreferences().get("name"); - if (platformLabel != null && !targetPlatform.getBoards().isEmpty()) { - JMenuItem menuLabel = new JMenuItem(_(platformLabel)); + if (!targetPlatform.getBoards().isEmpty()) { + JMenuItem menuLabel = new JMenuItem(_(targetPlatform.getName())); menuLabel.setEnabled(false); boardsMenu.add(menuLabel); } @@ -1838,11 +1835,31 @@ public void actionPerformed(ActionEvent e) { } protected void addLibraries(JMenu menu, LibraryList libs) throws IOException { + for (LibraryList sub : libs.getSubs()) { + menu.addSeparator(); - LibraryList list = new LibraryList(libs); - list.sort(); + // Prepend a label + JMenuItem label = new JMenuItem(sub.getName()); + label.setEnabled(false); + menu.add(label); - for (Library lib : list) { + addLibrariesRecursive(menu, sub); + } + } + + protected void addLibrariesRecursive(JMenu menu, LibraryList libs) throws IOException { + // Add any subdirectories first + for (LibraryList sub : libs.getSubs()) { + if (sub.isEmpty()) + continue; + + JMenu submenu = new JMenu(sub.getName()); + addLibrariesRecursive(submenu, sub); + menu.add(submenu); + MenuScroller.setScrollerFor(submenu); + } + // Then add the libraries directly in this folder + for (Library lib : libs.getLibs()) { @SuppressWarnings("serial") AbstractAction action = new AbstractAction(lib.getName()) { public void actionPerformed(ActionEvent event) { @@ -1860,8 +1877,6 @@ public void actionPerformed(ActionEvent event) { JMenuItem item = new JMenuItem(action); item.putClientProperty("library", lib); menu.add(item); - - // XXX: DAM: should recurse here so that library folders can be nested } } @@ -1899,7 +1914,7 @@ protected void loadHardware(File folder) { try { packages.put(target, new TargetPackage(target, subfolder)); } catch (TargetPlatformException e) { - System.out.println("WARNING: Error loading hardware folder " + target); + System.out.println("WARNING: Error loading hardware folder " + target + " from " + subfolder); System.out.println(" " + e.getMessage()); } } @@ -2137,8 +2152,8 @@ static public File createTempFolder(String name) { } - static public LibraryList getLibraries() { - return libraries; + static public List getLibraries() { + return libraries.getAll(); } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 4b2c6b44b4c..56fff7f8f16 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -33,7 +33,6 @@ import processing.app.helpers.PreferencesMap; import processing.app.helpers.FileUtils; import processing.app.packages.Library; -import processing.app.packages.LibraryList; import processing.app.preproc.*; import processing.core.*; import static processing.app.I18n._; @@ -94,7 +93,7 @@ public class Sketch { /** * List of library folders. */ - private LibraryList importedLibraries; + private ArrayList importedLibraries; /** * File inside the build directory that contains the build options @@ -1386,7 +1385,7 @@ public void preprocess(String buildPath, PdePreprocessor preprocessor) throws Ru // grab the imports from the code just preproc'd - importedLibraries = new LibraryList(); + importedLibraries = new ArrayList(); for (String item : preprocessor.getExtraImports()) { Library lib = Base.importToLibraryTable.get(item); if (lib != null && !importedLibraries.contains(lib)) { @@ -1419,7 +1418,7 @@ public void preprocess(String buildPath, PdePreprocessor preprocessor) throws Ru } - public LibraryList getImportedLibraries() { + public List getImportedLibraries() { return importedLibraries; } diff --git a/app/src/processing/app/debug/TargetPlatform.java b/app/src/processing/app/debug/TargetPlatform.java index 2666347fac4..9a715e4cfbd 100644 --- a/app/src/processing/app/debug/TargetPlatform.java +++ b/app/src/processing/app/debug/TargetPlatform.java @@ -141,6 +141,13 @@ public String getId() { return id; } + public String getName() { + String name = preferences.get("name"); + if (name == null) + return containerPackage.getId() + ":" + id; + return name; + } + public File getFolder() { return folder; } diff --git a/app/src/processing/app/packages/Library.java b/app/src/processing/app/packages/Library.java index 9c505fe4e41..f1fb9bcd83c 100644 --- a/app/src/processing/app/packages/Library.java +++ b/app/src/processing/app/packages/Library.java @@ -9,6 +9,11 @@ import processing.app.helpers.FileUtils; import processing.app.helpers.PreferencesMap; +import processing.app.helpers.filefilters.OnlyFilesWithExtension; +import processing.app.Sketch; + +import static processing.app.I18n._; +import processing.app.I18n; public class Library { @@ -36,6 +41,36 @@ public class Library { "Device Control", "Timing", "Data Storage", "Data Processing", "Other", "Uncategorized" }); + /** + * Check a folder to see if it contains a proper (non-legacy) library. + */ + public static boolean isProperLibrary(File libFolder) throws IOException { + // A library is considered "new" if it contains a file called + // "library.properties" + File check = new File(libFolder, "library.properties"); + return check.exists() && check.isFile(); + } + + /** + * Check a folder to see if it is a proper library, or looks like a + * legacy library (we can never tell for sure, though). + */ + public static boolean isLibrary(File libFolder) throws IOException { + if (isProperLibrary(libFolder)) + return true; + + // If it has a utility folder, then it's probably a legacy library + File utilFolder = new File(libFolder, "utility"); + if (utilFolder.exists() && utilFolder.isDirectory()) + return true; + + // If it his some .h files, it's probably a legacy library + if (libFolder.list(new OnlyFilesWithExtension(".h")).length > 0) + return true; + + return false; + } + /** * Scans inside a folder and create a Library object out of it. Automatically * detects legacy libraries. Automatically fills metadata from @@ -45,10 +80,15 @@ public class Library { * @return */ static public Library create(File libFolder) throws IOException { - // A library is considered "new" if it contains a file called - // "library.properties" - File check = new File(libFolder, "library.properties"); - if (!check.exists() || !check.isFile()) + if (!Sketch.isSanitaryName(libFolder.getName())) { + String mess = I18n.format(_("The library \"{0}\" 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)"), + libFolder.getName()); + throw new IOException(mess); + } + + if (!isProperLibrary(libFolder)) return createLegacyLibrary(libFolder); else return createLibrary(libFolder); diff --git a/app/src/processing/app/packages/LibraryList.java b/app/src/processing/app/packages/LibraryList.java index 343ff4bde6a..ad6ab9f14d0 100644 --- a/app/src/processing/app/packages/LibraryList.java +++ b/app/src/processing/app/packages/LibraryList.java @@ -2,24 +2,28 @@ import java.io.File; import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Collection; import java.util.Collections; import processing.app.helpers.FileUtils; @SuppressWarnings("serial") -public class LibraryList extends ArrayList { +public class LibraryList { + protected ArrayList subs; + protected ArrayList libs; + protected String name; - public LibraryList(LibraryList libs) { - super(libs); - } - - public LibraryList() { + public LibraryList(String name) { super(); + this.name = name; + this.subs = new ArrayList(); + this.libs = new ArrayList(); } public Library getByName(String name) { - for (Library l : this) + for (Library l : libs) if (l.getName().equals(name)) return l; return null; @@ -28,8 +32,8 @@ public Library getByName(String name) { public void addOrReplace(Library lib) { Library l = getByName(lib.getName()); if (l != null) - remove(l); - add(lib); + libs.remove(l); + libs.add(lib); } public void addOrReplaceAll(Collection c) { @@ -37,34 +41,62 @@ public void addOrReplaceAll(Collection c) { addOrReplace(l); } + public void addSub(LibraryList sub) { + subs.add(sub); + } + + public List getSubs() { + return subs; + } + + public List getLibs() { + return libs; + } + + public List getAll() { + ArrayList list = new ArrayList(); + + for (LibraryList sub : subs) + list.addAll(sub.getAll()); + + list.addAll(libs); + + return list; + } + public void sort() { - Collections.sort(this, Library.CASE_INSENSITIVE_ORDER); + Collections.sort(libs, Library.CASE_INSENSITIVE_ORDER); + Collections.sort(subs, LibraryList.CASE_INSENSITIVE_ORDER); } public Library search(String name, String arch) { - for (Library lib : this) { + for (Library lib : libs) { if (!lib.getName().equals(name)) continue; if (!lib.supportsArchitecture(arch)) continue; return lib; } + for (LibraryList sub : subs) { + Library lib = sub.search(name, arch); + if (lib != null) + return lib; + } return null; } - public LibraryList filterByArchitecture(String reqArch) { - LibraryList res = new LibraryList(); - for (Library lib : this) - if (lib.supportsArchitecture(reqArch)) - res.add(lib); - return res; + public static final Comparator CASE_INSENSITIVE_ORDER = new Comparator() { + @Override + public int compare(LibraryList o1, LibraryList o2) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + }; + + public String getName() { + return name; } - public LibraryList filterLibrariesInSubfolder(File subFolder) { - LibraryList res = new LibraryList(); - for (Library lib : this) - if (FileUtils.isSubDirectory(subFolder, lib.getFolder())) - res.add(lib); - return res; + public boolean isEmpty() { + return libs.isEmpty() && subs.isEmpty(); } }