mirror of https://github.com/noisymime/Arduino.git
2369 lines
78 KiB
Java
2369 lines
78 KiB
Java
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
Part of the Processing project - http://processing.org
|
|
|
|
Copyright (c) 2004-10 Ben Fry and Casey Reas
|
|
Copyright (c) 2001-04 Massachusetts Institute of Technology
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License version 2
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software Foundation,
|
|
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
package processing.app;
|
|
|
|
import cc.arduino.Compiler;
|
|
import cc.arduino.Constants;
|
|
import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
|
|
import cc.arduino.UploaderUtils;
|
|
import cc.arduino.contributions.*;
|
|
import cc.arduino.contributions.libraries.*;
|
|
import cc.arduino.contributions.libraries.ui.LibraryManagerUI;
|
|
import cc.arduino.contributions.packages.ContributedPlatform;
|
|
import cc.arduino.contributions.packages.ContributionInstaller;
|
|
import cc.arduino.contributions.packages.ContributionsIndexer;
|
|
import cc.arduino.contributions.packages.ui.ContributionManagerUI;
|
|
import cc.arduino.files.DeleteFilesOnShutdown;
|
|
import cc.arduino.packages.DiscoveryManager;
|
|
import cc.arduino.view.Event;
|
|
import cc.arduino.view.JMenuUtils;
|
|
import cc.arduino.view.SplashScreenHelper;
|
|
|
|
import org.apache.commons.compress.utils.IOUtils;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import processing.app.debug.TargetBoard;
|
|
import processing.app.debug.TargetPackage;
|
|
import processing.app.debug.TargetPlatform;
|
|
import processing.app.helpers.*;
|
|
import processing.app.helpers.filefilters.OnlyDirs;
|
|
import processing.app.helpers.filefilters.OnlyFilesWithExtension;
|
|
import processing.app.javax.swing.filechooser.FileNameExtensionFilter;
|
|
import processing.app.legacy.PApplet;
|
|
import processing.app.macosx.ThinkDifferent;
|
|
import processing.app.packages.LibraryList;
|
|
import processing.app.packages.UserLibrary;
|
|
import processing.app.syntax.PdeKeywords;
|
|
import processing.app.syntax.SketchTextAreaDefaultInputMap;
|
|
import processing.app.tools.MenuScroller;
|
|
import processing.app.tools.ZipDeflater;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import java.util.List;
|
|
import java.util.Timer;
|
|
import java.util.logging.Handler;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
import static processing.app.I18n.tr;
|
|
|
|
|
|
/**
|
|
* The base class for the main processing application.
|
|
* Primary role of this class is for platform identification and
|
|
* general interaction with the system (launching URLs, loading
|
|
* files and images, etc) that comes from that.
|
|
*/
|
|
public class Base {
|
|
|
|
private static final int RECENT_SKETCHES_MAX_SIZE = 10;
|
|
|
|
private static boolean commandLine;
|
|
public static volatile Base INSTANCE;
|
|
|
|
public static Map<String, Object> FIND_DIALOG_STATE = new HashMap<>();
|
|
private final ContributionInstaller contributionInstaller;
|
|
private final LibraryInstaller libraryInstaller;
|
|
private ContributionsSelfCheck contributionsSelfCheck;
|
|
|
|
// set to true after the first time the menu is built.
|
|
// so that the errors while building don't show up again.
|
|
boolean builtOnce;
|
|
|
|
// classpath for all known libraries for p5
|
|
// (both those in the p5/libs folder and those with lib subfolders
|
|
// found in the sketchbook)
|
|
static public String librariesClassPath;
|
|
|
|
// Location for untitled items
|
|
static File untitledFolder;
|
|
|
|
// p5 icon for the window
|
|
// static Image icon;
|
|
|
|
// int editorCount;
|
|
List<Editor> editors = Collections.synchronizedList(new ArrayList<Editor>());
|
|
Editor activeEditor;
|
|
|
|
// these menus are shared so that the board and serial port selections
|
|
// are the same for all windows (since the board and serial port that are
|
|
// actually used are determined by the preferences, which are shared)
|
|
private List<JMenu> boardsCustomMenus;
|
|
private List<JMenuItem> programmerMenus;
|
|
|
|
private PdeKeywords pdeKeywords;
|
|
private final List<JMenuItem> recentSketchesMenuItems = new LinkedList<>();
|
|
|
|
static public void main(String args[]) throws Exception {
|
|
if (!OSUtils.isWindows()) {
|
|
// Those properties helps enabling anti-aliasing on Linux
|
|
// (but not on Windows where they made things worse actually
|
|
// and the font rendering becomes ugly).
|
|
|
|
// Those properties must be set before initializing any
|
|
// graphic object, otherwise they don't have any effect.
|
|
System.setProperty("awt.useSystemAAFontSettings", "on");
|
|
System.setProperty("swing.aatext", "true");
|
|
}
|
|
System.setProperty("java.net.useSystemProxies", "true");
|
|
|
|
if (OSUtils.isMacOS()) {
|
|
System.setProperty("apple.laf.useScreenMenuBar",
|
|
String.valueOf(!System.getProperty("os.version").startsWith("10.13")
|
|
|| com.apple.eawt.Application.getApplication().isAboutMenuItemPresent()));
|
|
|
|
ThinkDifferent.init();
|
|
}
|
|
|
|
try {
|
|
INSTANCE = new Base(args);
|
|
} catch (Throwable e) {
|
|
e.printStackTrace(System.err);
|
|
System.exit(255);
|
|
}
|
|
}
|
|
|
|
static public void initLogger() {
|
|
Handler consoleHandler = new ConsoleLogger();
|
|
consoleHandler.setLevel(Level.ALL);
|
|
consoleHandler.setFormatter(new LogFormatter("%1$tl:%1$tM:%1$tS [%4$7s] %2$s: %5$s%n"));
|
|
|
|
Logger globalLogger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
|
|
globalLogger.setLevel(consoleHandler.getLevel());
|
|
|
|
// Remove default
|
|
Handler[] handlers = globalLogger.getHandlers();
|
|
for(Handler handler : handlers) {
|
|
globalLogger.removeHandler(handler);
|
|
}
|
|
Logger root = Logger.getLogger("");
|
|
handlers = root.getHandlers();
|
|
for(Handler handler : handlers) {
|
|
root.removeHandler(handler);
|
|
}
|
|
|
|
globalLogger.addHandler(consoleHandler);
|
|
|
|
Logger.getLogger("cc.arduino.packages.autocomplete").setParent(globalLogger);
|
|
Logger.getLogger("br.com.criativasoft.cpluslibparser").setParent(globalLogger);
|
|
Logger.getLogger(Base.class.getPackage().getName()).setParent(globalLogger);
|
|
|
|
}
|
|
|
|
static protected boolean isCommandLine() {
|
|
return commandLine;
|
|
}
|
|
|
|
// Returns a File object for the given pathname. If the pathname
|
|
// is not absolute, it is interpreted relative to the current
|
|
// directory when starting the IDE (which is not the same as the
|
|
// current working directory!).
|
|
static public File absoluteFile(String path) {
|
|
return BaseNoGui.absoluteFile(path);
|
|
}
|
|
|
|
public Base(String[] args) throws Exception {
|
|
Thread deleteFilesOnShutdownThread = new Thread(DeleteFilesOnShutdown.INSTANCE);
|
|
deleteFilesOnShutdownThread.setName("DeleteFilesOnShutdown");
|
|
Runtime.getRuntime().addShutdownHook(deleteFilesOnShutdownThread);
|
|
|
|
BaseNoGui.initLogger();
|
|
|
|
initLogger();
|
|
|
|
BaseNoGui.initPlatform();
|
|
|
|
BaseNoGui.getPlatform().init();
|
|
|
|
BaseNoGui.initPortableFolder();
|
|
|
|
// Look for a possible "--preferences-file" parameter and load preferences
|
|
BaseNoGui.initParameters(args);
|
|
|
|
CommandlineParser parser = new CommandlineParser(args);
|
|
parser.parseArgumentsPhase1();
|
|
commandLine = !parser.isGuiMode();
|
|
|
|
SplashScreenHelper splash;
|
|
if (parser.isGuiMode()) {
|
|
// Setup all notification widgets
|
|
splash = new SplashScreenHelper(SplashScreen.getSplashScreen());
|
|
BaseNoGui.notifier = new GUIUserNotifier(this);
|
|
|
|
// Setup the theme coloring fun
|
|
Theme.init();
|
|
System.setProperty("swing.aatext", PreferencesData.get("editor.antialias", "true"));
|
|
|
|
// Set the look and feel before opening the window
|
|
try {
|
|
BaseNoGui.getPlatform().setLookAndFeel();
|
|
} catch (Exception e) {
|
|
// ignore
|
|
}
|
|
|
|
// Use native popups so they don't look so crappy on osx
|
|
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
|
|
} else {
|
|
splash = new SplashScreenHelper(null);
|
|
}
|
|
|
|
splash.splashText(tr("Loading configuration..."));
|
|
|
|
BaseNoGui.initVersion();
|
|
|
|
// Don't put anything above this line that might make GUI,
|
|
// because the platform has to be inited properly first.
|
|
|
|
// Create a location for untitled sketches
|
|
untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp");
|
|
DeleteFilesOnShutdown.add(untitledFolder);
|
|
|
|
BaseNoGui.checkInstallationFolder();
|
|
|
|
// If no path is set, get the default sketchbook folder for this platform
|
|
if (BaseNoGui.getSketchbookPath() == null) {
|
|
File defaultFolder = getDefaultSketchbookFolderOrPromptForIt();
|
|
if (BaseNoGui.getPortableFolder() != null)
|
|
PreferencesData.set("sketchbook.path", BaseNoGui.getPortableSketchbookFolder());
|
|
else
|
|
PreferencesData.set("sketchbook.path", defaultFolder.getAbsolutePath());
|
|
if (!defaultFolder.exists()) {
|
|
defaultFolder.mkdirs();
|
|
}
|
|
}
|
|
|
|
splash.splashText(tr("Initializing packages..."));
|
|
BaseNoGui.initPackages();
|
|
|
|
splash.splashText(tr("Preparing boards..."));
|
|
|
|
if (!isCommandLine()) {
|
|
rebuildBoardsMenu();
|
|
rebuildProgrammerMenu();
|
|
} else {
|
|
TargetBoard lastSelectedBoard = BaseNoGui.getTargetBoard();
|
|
if (lastSelectedBoard != null)
|
|
BaseNoGui.selectBoard(lastSelectedBoard);
|
|
}
|
|
|
|
// Setup board-dependent variables.
|
|
onBoardOrPortChange();
|
|
|
|
pdeKeywords = new PdeKeywords();
|
|
pdeKeywords.reload();
|
|
|
|
contributionInstaller = new ContributionInstaller(BaseNoGui.getPlatform(), new GPGDetachedSignatureVerifier());
|
|
libraryInstaller = new LibraryInstaller(BaseNoGui.getPlatform());
|
|
|
|
parser.parseArgumentsPhase2();
|
|
|
|
// Save the preferences. For GUI mode, this happens in the quit
|
|
// handler, but for other modes we should also make sure to save
|
|
// them.
|
|
if (parser.isForceSavePrefs()) {
|
|
PreferencesData.save();
|
|
}
|
|
|
|
if (parser.isInstallBoard()) {
|
|
ContributionsIndexer indexer = new ContributionsIndexer(
|
|
BaseNoGui.getSettingsFolder(), BaseNoGui.getHardwareFolder(),
|
|
BaseNoGui.getPlatform(), new GPGDetachedSignatureVerifier());
|
|
ProgressListener progressListener = new ConsoleProgressListener();
|
|
|
|
List<String> downloadedPackageIndexFiles = contributionInstaller.updateIndex(progressListener);
|
|
contributionInstaller.deleteUnknownFiles(downloadedPackageIndexFiles);
|
|
indexer.parseIndex();
|
|
indexer.syncWithFilesystem();
|
|
|
|
String[] boardToInstallParts = parser.getBoardToInstall().split(":");
|
|
|
|
ContributedPlatform selected = null;
|
|
if (boardToInstallParts.length == 3) {
|
|
selected = indexer.getIndex().findPlatform(boardToInstallParts[0], boardToInstallParts[1], VersionHelper.valueOf(boardToInstallParts[2]).toString());
|
|
} else if (boardToInstallParts.length == 2) {
|
|
List<ContributedPlatform> platformsByName = indexer.getIndex().findPlatforms(boardToInstallParts[0], boardToInstallParts[1]);
|
|
Collections.sort(platformsByName, new DownloadableContributionVersionComparator());
|
|
if (!platformsByName.isEmpty()) {
|
|
selected = platformsByName.get(platformsByName.size() - 1);
|
|
}
|
|
}
|
|
if (selected == null) {
|
|
System.out.println(tr("Selected board is not available"));
|
|
System.exit(1);
|
|
}
|
|
|
|
ContributedPlatform installed = indexer.getInstalled(boardToInstallParts[0], boardToInstallParts[1]);
|
|
|
|
if (!selected.isReadOnly()) {
|
|
contributionInstaller.install(selected, progressListener);
|
|
}
|
|
|
|
if (installed != null && !installed.isReadOnly()) {
|
|
contributionInstaller.remove(installed);
|
|
}
|
|
|
|
System.exit(0);
|
|
|
|
} else if (parser.isInstallLibrary()) {
|
|
BaseNoGui.onBoardOrPortChange();
|
|
|
|
ProgressListener progressListener = new ConsoleProgressListener();
|
|
libraryInstaller.updateIndex(progressListener);
|
|
|
|
LibrariesIndexer indexer = new LibrariesIndexer(BaseNoGui.getSettingsFolder());
|
|
indexer.parseIndex();
|
|
indexer.setSketchbookLibrariesFolder(BaseNoGui.getSketchbookLibrariesFolder());
|
|
indexer.setLibrariesFolders(BaseNoGui.getLibrariesPath());
|
|
|
|
for (String library : parser.getLibraryToInstall().split(",")) {
|
|
String[] libraryToInstallParts = library.split(":");
|
|
|
|
ContributedLibrary selected = null;
|
|
if (libraryToInstallParts.length == 2) {
|
|
selected = indexer.getIndex().find(libraryToInstallParts[0], VersionHelper.valueOf(libraryToInstallParts[1]).toString());
|
|
} else if (libraryToInstallParts.length == 1) {
|
|
List<ContributedLibrary> librariesByName = indexer.getIndex().find(libraryToInstallParts[0]);
|
|
Collections.sort(librariesByName, new DownloadableContributionVersionComparator());
|
|
if (!librariesByName.isEmpty()) {
|
|
selected = librariesByName.get(librariesByName.size() - 1);
|
|
}
|
|
}
|
|
if (selected == null) {
|
|
System.out.println(tr("Selected library is not available"));
|
|
System.exit(1);
|
|
}
|
|
|
|
ContributedLibrary installed = indexer.getIndex().getInstalled(libraryToInstallParts[0]);
|
|
if (selected.isReadOnly()) {
|
|
libraryInstaller.remove(installed, progressListener);
|
|
} else {
|
|
libraryInstaller.install(selected, installed, progressListener);
|
|
}
|
|
}
|
|
|
|
System.exit(0);
|
|
|
|
} else if (parser.isVerifyOrUploadMode()) {
|
|
// Set verbosity for command line build
|
|
PreferencesData.setBoolean("build.verbose", parser.isDoVerboseBuild());
|
|
PreferencesData.setBoolean("upload.verbose", parser.isDoVerboseUpload());
|
|
|
|
// Set preserve-temp flag
|
|
PreferencesData.setBoolean("runtime.preserve.temp.files", parser.isPreserveTempFiles());
|
|
|
|
// Make sure these verbosity preferences are only for the current session
|
|
PreferencesData.setDoSave(false);
|
|
|
|
Sketch sketch = null;
|
|
String outputFile = null;
|
|
|
|
try {
|
|
// Build
|
|
splash.splashText(tr("Verifying..."));
|
|
|
|
File sketchFile = BaseNoGui.absoluteFile(parser.getFilenames().get(0));
|
|
sketch = new Sketch(sketchFile);
|
|
|
|
outputFile = new Compiler(sketch).build(progress -> {}, false);
|
|
} catch (Exception e) {
|
|
// Error during build
|
|
e.printStackTrace();
|
|
System.exit(1);
|
|
}
|
|
|
|
if (parser.isUploadMode()) {
|
|
// Upload
|
|
splash.splashText(tr("Uploading..."));
|
|
|
|
try {
|
|
List<String> warnings = new ArrayList<>();
|
|
UploaderUtils uploader = new UploaderUtils();
|
|
boolean res = uploader.upload(sketch, null, outputFile,
|
|
parser.isDoUseProgrammer(),
|
|
parser.isNoUploadPort(), warnings);
|
|
for (String warning : warnings) {
|
|
System.out.println(tr("Warning") + ": " + warning);
|
|
}
|
|
if (!res) {
|
|
throw new Exception();
|
|
}
|
|
} catch (Exception e) {
|
|
// Error during upload
|
|
System.out.flush();
|
|
System.err.flush();
|
|
System.err
|
|
.println(tr("An error occurred while uploading the sketch"));
|
|
System.exit(1);
|
|
}
|
|
}
|
|
|
|
// No errors exit gracefully
|
|
System.exit(0);
|
|
} else if (parser.isGuiMode()) {
|
|
splash.splashText(tr("Starting..."));
|
|
|
|
for (String path : parser.getFilenames()) {
|
|
// Correctly resolve relative paths
|
|
File file = absoluteFile(path);
|
|
|
|
// Fix a problem with systems that use a non-ASCII languages. Paths are
|
|
// being passed in with 8.3 syntax, which makes the sketch loader code
|
|
// unhappy, since the sketch folder naming doesn't match up correctly.
|
|
// http://dev.processing.org/bugs/show_bug.cgi?id=1089
|
|
if (OSUtils.isWindows()) {
|
|
try {
|
|
file = file.getCanonicalFile();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
if (!parser.isForceSavePrefs())
|
|
PreferencesData.setDoSave(true);
|
|
if (handleOpen(file, retrieveSketchLocation(".default"), false) == null) {
|
|
String mess = I18n.format(tr("Failed to open sketch: \"{0}\""), path);
|
|
// Open failure is fatal in upload/verify mode
|
|
if (parser.isVerifyOrUploadMode())
|
|
showError(null, mess, 2);
|
|
else
|
|
showWarning(null, mess, null);
|
|
}
|
|
}
|
|
|
|
installKeyboardInputMap();
|
|
|
|
// Check if there were previously opened sketches to be restored
|
|
restoreSketches();
|
|
|
|
// Create a new empty window (will be replaced with any files to be opened)
|
|
if (editors.isEmpty()) {
|
|
handleNew();
|
|
}
|
|
|
|
new Thread(new BuiltInCoreIsNewerCheck(this)).start();
|
|
|
|
// Check for boards which need an additional core
|
|
new Thread(new NewBoardListener(this)).start();
|
|
|
|
// Check for updates
|
|
if (PreferencesData.getBoolean("update.check")) {
|
|
new UpdateCheck(this);
|
|
|
|
contributionsSelfCheck = new ContributionsSelfCheck(this, new UpdatableBoardsLibsFakeURLsHandler(this), contributionInstaller, libraryInstaller);
|
|
new Timer(false).schedule(contributionsSelfCheck, Constants.BOARDS_LIBS_UPDATABLE_CHECK_START_PERIOD);
|
|
}
|
|
|
|
} else if (parser.isNoOpMode()) {
|
|
// Do nothing (intended for only changing preferences)
|
|
System.exit(0);
|
|
} else if (parser.isGetPrefMode()) {
|
|
BaseNoGui.dumpPrefs(parser);
|
|
}
|
|
}
|
|
|
|
private void installKeyboardInputMap() {
|
|
UIManager.put("RSyntaxTextAreaUI.inputMap", new SketchTextAreaDefaultInputMap());
|
|
}
|
|
|
|
/**
|
|
* Post-constructor setup for the editor area. Loads the last
|
|
* sketch that was used (if any), and restores other Editor settings.
|
|
* The complement to "storePreferences", this is called when the
|
|
* application is first launched.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
protected boolean restoreSketches() throws Exception {
|
|
// Iterate through all sketches that were open last time p5 was running.
|
|
// If !windowPositionValid, then ignore the coordinates found for each.
|
|
|
|
// Save the sketch path and window placement for each open sketch
|
|
int count = PreferencesData.getInteger("last.sketch.count");
|
|
int opened = 0;
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
String path = PreferencesData.get("last.sketch" + i + ".path");
|
|
if (path == null) {
|
|
continue;
|
|
}
|
|
if (BaseNoGui.getPortableFolder() != null && !new File(path).isAbsolute()) {
|
|
File absolute = new File(BaseNoGui.getPortableFolder(), path);
|
|
try {
|
|
path = absolute.getCanonicalPath();
|
|
} catch (IOException e) {
|
|
// path unchanged.
|
|
}
|
|
}
|
|
int[] location = retrieveSketchLocation("" + i);
|
|
// If file did not exist, null will be returned for the Editor
|
|
if (handleOpen(new File(path), location, nextEditorLocation(), false, false) != null) {
|
|
opened++;
|
|
}
|
|
}
|
|
return (opened > 0);
|
|
}
|
|
|
|
/**
|
|
* Store screen dimensions on last close
|
|
*/
|
|
protected void storeScreenDimensions() {
|
|
// Save the width and height of the screen
|
|
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
|
|
PreferencesData.setInteger("last.screen.width", screen.width);
|
|
PreferencesData.setInteger("last.screen.height", screen.height);
|
|
}
|
|
|
|
/**
|
|
* Store list of sketches that are currently open.
|
|
* Called when the application is quitting and documents are still open.
|
|
*/
|
|
protected void storeSketches() {
|
|
|
|
// If there is only one sketch opened save his position as default
|
|
if (editors.size() == 1) {
|
|
storeSketchLocation(editors.get(0), ".default");
|
|
}
|
|
|
|
// Save the sketch path and window placement for each open sketch
|
|
String untitledPath = untitledFolder.getAbsolutePath();
|
|
List<Editor> reversedEditors = new LinkedList<>(editors);
|
|
Collections.reverse(reversedEditors);
|
|
int index = 0;
|
|
for (Editor editor : reversedEditors) {
|
|
Sketch sketch = editor.getSketch();
|
|
String path = sketch.getMainFilePath();
|
|
// Skip untitled sketches if they do not contains changes.
|
|
if (path.startsWith(untitledPath) && !sketch.isModified()) {
|
|
continue;
|
|
}
|
|
storeSketchLocation(editor, "" + index);
|
|
index++;
|
|
}
|
|
PreferencesData.setInteger("last.sketch.count", index);
|
|
}
|
|
|
|
private void storeSketchLocation(Editor editor, String index) {
|
|
String path = editor.getSketch().getMainFilePath();
|
|
String loc = StringUtils.join(editor.getPlacement(), ',');
|
|
PreferencesData.set("last.sketch" + index + ".path", path);
|
|
PreferencesData.set("last.sketch" + index + ".location", loc);
|
|
}
|
|
|
|
private int[] retrieveSketchLocation(String index) {
|
|
if (PreferencesData.get("last.screen.height") == null)
|
|
return defaultEditorLocation();
|
|
|
|
// if screen size has changed, the window coordinates no longer
|
|
// make sense, so don't use them unless they're identical
|
|
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
|
|
int screenW = PreferencesData.getInteger("last.screen.width");
|
|
int screenH = PreferencesData.getInteger("last.screen.height");
|
|
|
|
if ((screen.width != screenW) || (screen.height != screenH))
|
|
return defaultEditorLocation();
|
|
|
|
String locationStr = PreferencesData
|
|
.get("last.sketch" + index + ".location");
|
|
if (locationStr == null)
|
|
return defaultEditorLocation();
|
|
return PApplet.parseInt(PApplet.split(locationStr, ','));
|
|
}
|
|
|
|
protected void storeRecentSketches(SketchController sketch) {
|
|
if (sketch.isUntitled()) {
|
|
return;
|
|
}
|
|
|
|
Set<String> sketches = new LinkedHashSet<>();
|
|
sketches.add(sketch.getSketch().getMainFilePath());
|
|
sketches.addAll(PreferencesData.getCollection("recent.sketches"));
|
|
|
|
PreferencesData.setCollection("recent.sketches", sketches);
|
|
}
|
|
|
|
protected void removeRecentSketchPath(String path) {
|
|
Collection<String> sketches = new LinkedList<>(PreferencesData.getCollection("recent.sketches"));
|
|
sketches.remove(path);
|
|
PreferencesData.setCollection("recent.sketches", sketches);
|
|
}
|
|
|
|
// Because of variations in native windowing systems, no guarantees about
|
|
// changes to the focused and active Windows can be made. Developers must
|
|
// never assume that this Window is the focused or active Window until this
|
|
// Window receives a WINDOW_GAINED_FOCUS or WINDOW_ACTIVATED event.
|
|
protected void handleActivated(Editor whichEditor) {
|
|
activeEditor = whichEditor;
|
|
activeEditor.rebuildRecentSketchesMenu();
|
|
if (PreferencesData.getBoolean("editor.external")) {
|
|
try {
|
|
// If the list of files on disk changed, recreate the tabs for them
|
|
if (activeEditor.getSketch().reload())
|
|
activeEditor.createTabs();
|
|
else // Let the current tab know it was activated, so it can reload
|
|
activeEditor.getCurrentTab().activated();
|
|
} catch (IOException e) {
|
|
System.err.println(e);
|
|
}
|
|
}
|
|
|
|
// set the current window to be the console that's getting output
|
|
EditorConsole.setCurrentEditorConsole(activeEditor.console);
|
|
}
|
|
|
|
protected int[] defaultEditorLocation() {
|
|
int defaultWidth = PreferencesData.getInteger("editor.window.width.default");
|
|
int defaultHeight = PreferencesData.getInteger("editor.window.height.default");
|
|
Rectangle screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds();
|
|
return new int[]{
|
|
(screen.width - defaultWidth) / 2,
|
|
(screen.height - defaultHeight) / 2,
|
|
defaultWidth, defaultHeight, 0
|
|
};
|
|
}
|
|
|
|
protected int[] nextEditorLocation() {
|
|
if (activeEditor == null) {
|
|
// If no current active editor, use default placement
|
|
return defaultEditorLocation();
|
|
}
|
|
|
|
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
|
|
|
|
// With a currently active editor, open the new window
|
|
// using the same dimensions, but offset slightly.
|
|
synchronized (editors) {
|
|
int[] location = activeEditor.getPlacement();
|
|
|
|
// Just in case the bounds for that window are bad
|
|
final int OVER = 50;
|
|
location[0] += OVER;
|
|
location[1] += OVER;
|
|
|
|
if (location[0] == OVER || location[2] == OVER
|
|
|| location[0] + location[2] > screen.width
|
|
|| location[1] + location[3] > screen.height) {
|
|
// Warp the next window to a randomish location on screen.
|
|
int[] l = defaultEditorLocation();
|
|
l[0] *= Math.random() * 2;
|
|
l[1] *= Math.random() * 2;
|
|
return l;
|
|
}
|
|
|
|
return location;
|
|
}
|
|
}
|
|
|
|
|
|
// .................................................................
|
|
|
|
|
|
boolean breakTime = false;
|
|
String[] months = {
|
|
"jan", "feb", "mar", "apr", "may", "jun",
|
|
"jul", "aug", "sep", "oct", "nov", "dec"
|
|
};
|
|
|
|
protected File createNewUntitled() throws IOException {
|
|
File newbieDir = null;
|
|
String newbieName = null;
|
|
|
|
// In 0126, untitled sketches will begin in the temp folder,
|
|
// and then moved to a new location because Save will default to Save As.
|
|
File sketchbookDir = BaseNoGui.getSketchbookFolder();
|
|
File newbieParentDir = untitledFolder;
|
|
|
|
// Use a generic name like sketch_031008a, the date plus a char
|
|
int index = 0;
|
|
//SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd");
|
|
//SimpleDateFormat formatter = new SimpleDateFormat("MMMdd");
|
|
//String purty = formatter.format(new Date()).toLowerCase();
|
|
Calendar cal = Calendar.getInstance();
|
|
int day = cal.get(Calendar.DAY_OF_MONTH); // 1..31
|
|
int month = cal.get(Calendar.MONTH); // 0..11
|
|
String purty = months[month] + PApplet.nf(day, 2);
|
|
|
|
do {
|
|
if (index == 26*26) {
|
|
// In 0166, avoid running past zz by sending people outdoors.
|
|
if (!breakTime) {
|
|
showWarning(tr("Time for a Break"),
|
|
tr("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 {
|
|
showWarning(tr("Sunshine"),
|
|
tr("No really, time for some fresh air for you."), null);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
int multiples = index / 26;
|
|
|
|
if(multiples > 0){
|
|
newbieName = ((char) ('a' + (multiples-1))) + "" + ((char) ('a' + (index % 26))) + "";
|
|
}else{
|
|
newbieName = ((char) ('a' + index)) + "";
|
|
}
|
|
newbieName = "sketch_" + purty + newbieName;
|
|
newbieDir = new File(newbieParentDir, newbieName);
|
|
index++;
|
|
// Make sure it's not in the temp folder *and* it's not in the sketchbook
|
|
} while (newbieDir.exists() || new File(sketchbookDir, newbieName).exists());
|
|
|
|
// Make the directory for the new sketch
|
|
newbieDir.mkdirs();
|
|
|
|
// Make an empty pde file
|
|
File newbieFile = new File(newbieDir, newbieName + ".ino");
|
|
if (!newbieFile.createNewFile()) {
|
|
throw new IOException();
|
|
}
|
|
FileUtils.copyFile(new File(getContentFile("examples"), "01.Basics" + File.separator + "BareMinimum" + File.separator + "BareMinimum.ino"), newbieFile);
|
|
return newbieFile;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create a new untitled document in a new sketch window.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public void handleNew() throws Exception {
|
|
try {
|
|
File file = createNewUntitled();
|
|
if (file != null) {
|
|
handleOpen(file, true);
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
if (activeEditor != null) {
|
|
activeEditor.statusError(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Prompt for a sketch to open, and open it in a new window.
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public void handleOpenPrompt() throws Exception {
|
|
// get the frontmost window frame for placing file dialog
|
|
FileDialog fd = new FileDialog(activeEditor, tr("Open an Arduino sketch..."), FileDialog.LOAD);
|
|
File lastFolder = new File(PreferencesData.get("last.folder", BaseNoGui.getSketchbookFolder().getAbsolutePath()));
|
|
if (lastFolder.exists() && lastFolder.isFile()) {
|
|
lastFolder = lastFolder.getParentFile();
|
|
}
|
|
fd.setDirectory(lastFolder.getAbsolutePath());
|
|
|
|
// Only show .pde files as eligible bachelors
|
|
fd.setFilenameFilter(new FilenameFilter() {
|
|
public boolean accept(File dir, String name) {
|
|
return name.toLowerCase().endsWith(".ino")
|
|
|| name.toLowerCase().endsWith(".pde");
|
|
}
|
|
});
|
|
|
|
fd.setVisible(true);
|
|
|
|
String directory = fd.getDirectory();
|
|
String filename = fd.getFile();
|
|
|
|
// User canceled selection
|
|
if (filename == null) return;
|
|
|
|
File inputFile = new File(directory, filename);
|
|
|
|
PreferencesData.set("last.folder", inputFile.getAbsolutePath());
|
|
handleOpen(inputFile);
|
|
}
|
|
|
|
|
|
/**
|
|
* Open a sketch in a new window.
|
|
*
|
|
* @param file File to open
|
|
* @return the Editor object, so that properties (like 'untitled')
|
|
* can be set by the caller
|
|
* @throws Exception
|
|
*/
|
|
public Editor handleOpen(File file) throws Exception {
|
|
return handleOpen(file, false);
|
|
}
|
|
|
|
public Editor handleOpen(File file, boolean untitled) throws Exception {
|
|
return handleOpen(file, nextEditorLocation(), untitled);
|
|
}
|
|
|
|
protected Editor handleOpen(File file, int[] location, boolean untitled) throws Exception {
|
|
return handleOpen(file, location, location, true, untitled);
|
|
}
|
|
|
|
protected Editor handleOpen(File file, int[] storedLocation, int[] defaultLocation, boolean storeOpenedSketches, boolean untitled) throws Exception {
|
|
if (!file.exists()) return null;
|
|
|
|
// Cycle through open windows to make sure that it's not already open.
|
|
for (Editor editor : editors) {
|
|
if (editor.getSketch().getPrimaryFile().getFile().equals(file)) {
|
|
editor.toFront();
|
|
return editor;
|
|
}
|
|
}
|
|
|
|
Editor editor = new Editor(this, file, storedLocation, defaultLocation, BaseNoGui.getPlatform());
|
|
|
|
// Make sure that the sketch actually loaded
|
|
if (editor.getSketchController() == null) {
|
|
return null; // Just walk away quietly
|
|
}
|
|
|
|
editor.untitled = untitled;
|
|
|
|
editors.add(editor);
|
|
|
|
if (storeOpenedSketches) {
|
|
// Store information on who's open and running
|
|
// (in case there's a crash or something that can't be recovered)
|
|
storeSketches();
|
|
storeRecentSketches(editor.getSketchController());
|
|
rebuildRecentSketchesMenuItems();
|
|
PreferencesData.save();
|
|
}
|
|
|
|
// now that we're ready, show the window
|
|
// (don't do earlier, cuz we might move it based on a window being closed)
|
|
SwingUtilities.invokeLater(() -> editor.setVisible(true));
|
|
|
|
return editor;
|
|
}
|
|
|
|
protected void rebuildRecentSketchesMenuItems() {
|
|
Set<File> recentSketches = new LinkedHashSet<File>() {
|
|
|
|
@Override
|
|
public boolean add(File file) {
|
|
if (size() >= RECENT_SKETCHES_MAX_SIZE) {
|
|
return false;
|
|
}
|
|
return super.add(file);
|
|
}
|
|
};
|
|
|
|
for (String path : PreferencesData.getCollection("recent.sketches")) {
|
|
File file = new File(path);
|
|
if (file.exists()) {
|
|
recentSketches.add(file);
|
|
}
|
|
}
|
|
|
|
recentSketchesMenuItems.clear();
|
|
for (final File recentSketch : recentSketches) {
|
|
JMenuItem recentSketchMenuItem = new JMenuItem(recentSketch.getParentFile().getName());
|
|
recentSketchMenuItem.addActionListener(new ActionListener() {
|
|
@Override
|
|
public void actionPerformed(ActionEvent actionEvent) {
|
|
try {
|
|
handleOpen(recentSketch);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
});
|
|
recentSketchesMenuItems.add(recentSketchMenuItem);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Close a sketch as specified by its editor window.
|
|
*
|
|
* @param editor Editor object of the sketch to be closed.
|
|
* @return true if succeeded in closing, false if canceled.
|
|
*/
|
|
public boolean handleClose(Editor editor) {
|
|
// Check if modified
|
|
// boolean immediate = editors.size() == 1;
|
|
if (!editor.checkModified()) {
|
|
return false;
|
|
}
|
|
|
|
if (editors.size() == 1) {
|
|
storeScreenDimensions();
|
|
storeSketches();
|
|
|
|
// This will store the sketch count as zero
|
|
editors.remove(editor);
|
|
try {
|
|
Editor.serialMonitor.close();
|
|
} catch (Exception e) {
|
|
//ignore
|
|
}
|
|
rebuildRecentSketchesMenuItems();
|
|
|
|
// Save out the current prefs state
|
|
PreferencesData.save();
|
|
|
|
// Since this wasn't an actual Quit event, call System.exit()
|
|
System.exit(0);
|
|
|
|
} else {
|
|
// More than one editor window open,
|
|
// proceed with closing the current window.
|
|
editor.setVisible(false);
|
|
editor.dispose();
|
|
// for (int i = 0; i < editorCount; i++) {
|
|
// if (editor == editors[i]) {
|
|
// for (int j = i; j < editorCount-1; j++) {
|
|
// editors[j] = editors[j+1];
|
|
// }
|
|
// editorCount--;
|
|
// // Set to null so that garbage collection occurs
|
|
// editors[editorCount] = null;
|
|
// }
|
|
// }
|
|
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.
|
|
storeScreenDimensions();
|
|
storeSketches();
|
|
try {
|
|
Editor.serialMonitor.close();
|
|
} catch (Exception e) {
|
|
// ignore
|
|
}
|
|
|
|
if (handleQuitEach()) {
|
|
// Save out the current prefs state
|
|
PreferencesData.save();
|
|
|
|
if (!OSUtils.hasMacOSStyleMenus()) {
|
|
// 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() {
|
|
for (Editor editor : editors) {
|
|
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.
|
|
*/
|
|
public void rebuildSketchbookMenus() {
|
|
//System.out.println("async enter");
|
|
//new Exception().printStackTrace();
|
|
SwingUtilities.invokeLater(new Runnable() {
|
|
public void run() {
|
|
//System.out.println("starting rebuild");
|
|
rebuildSketchbookMenu(Editor.sketchbookMenu);
|
|
rebuildToolbarMenu(Editor.toolbarMenu);
|
|
//System.out.println("done with rebuild");
|
|
}
|
|
});
|
|
//System.out.println("async exit");
|
|
}
|
|
|
|
|
|
protected void rebuildToolbarMenu(JMenu menu) {
|
|
JMenuItem item;
|
|
menu.removeAll();
|
|
|
|
// Add the single "Open" item
|
|
item = Editor.newJMenuItem(tr("Open..."), 'O');
|
|
item.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
try {
|
|
handleOpenPrompt();
|
|
} catch (Exception e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
}
|
|
});
|
|
menu.add(item);
|
|
menu.addSeparator();
|
|
|
|
// Add a list of all sketches and subfolders
|
|
boolean sketches = addSketches(menu, BaseNoGui.getSketchbookFolder());
|
|
if (sketches) menu.addSeparator();
|
|
|
|
// Add each of the subfolders of examples directly to the menu
|
|
boolean found = addSketches(menu, BaseNoGui.getExamplesFolder());
|
|
if (found) menu.addSeparator();
|
|
}
|
|
|
|
|
|
protected void rebuildSketchbookMenu(JMenu menu) {
|
|
menu.removeAll();
|
|
addSketches(menu, BaseNoGui.getSketchbookFolder());
|
|
|
|
JMenu librariesMenu = JMenuUtils.findSubMenuWithLabel(menu, "libraries");
|
|
if (librariesMenu != null) {
|
|
menu.remove(librariesMenu);
|
|
}
|
|
JMenu hardwareMenu = JMenuUtils.findSubMenuWithLabel(menu, "hardware");
|
|
if (hardwareMenu != null) {
|
|
menu.remove(hardwareMenu);
|
|
}
|
|
}
|
|
|
|
private List<ContributedLibrary> getSortedLibraries() {
|
|
List<ContributedLibrary> installedLibraries = new LinkedList<>(BaseNoGui.librariesIndexer.getInstalledLibraries());
|
|
Collections.sort(installedLibraries, new LibraryByTypeComparator());
|
|
Collections.sort(installedLibraries, new LibraryOfSameTypeComparator());
|
|
return installedLibraries;
|
|
}
|
|
|
|
public void rebuildImportMenu(JMenu importMenu) {
|
|
if (importMenu == null)
|
|
return;
|
|
importMenu.removeAll();
|
|
|
|
JMenuItem menu = new JMenuItem(tr("Manage Libraries..."));
|
|
// Ctrl+Shift+I on Windows and Linux, Command+Shift+I on macOS
|
|
menu.setAccelerator(KeyStroke.getKeyStroke('I',
|
|
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() |
|
|
ActionEvent.SHIFT_MASK));
|
|
menu.addActionListener(e -> openLibraryManager("", ""));
|
|
importMenu.add(menu);
|
|
importMenu.addSeparator();
|
|
|
|
JMenuItem addLibraryMenuItem = new JMenuItem(tr("Add .ZIP Library..."));
|
|
addLibraryMenuItem.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
Base.this.handleAddLibrary();
|
|
Base.this.onBoardOrPortChange();
|
|
Base.this.rebuildImportMenu(Editor.importMenu);
|
|
Base.this.rebuildExamplesMenu(Editor.examplesMenu);
|
|
}
|
|
});
|
|
importMenu.add(addLibraryMenuItem);
|
|
importMenu.addSeparator();
|
|
|
|
// Split between user supplied libraries and IDE libraries
|
|
TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
|
|
|
|
if (targetPlatform != null) {
|
|
List<ContributedLibrary> libs = getSortedLibraries();
|
|
String lastLibType = null;
|
|
for (ContributedLibrary lib : libs) {
|
|
if (lastLibType == null || !lastLibType.equals(lib.getTypes().get(0))) {
|
|
if (lastLibType != null) {
|
|
importMenu.addSeparator();
|
|
}
|
|
lastLibType = lib.getTypes().get(0);
|
|
JMenuItem platformItem = new JMenuItem(I18n.format(tr("{0} libraries"), tr(lastLibType)));
|
|
platformItem.setEnabled(false);
|
|
importMenu.add(platformItem);
|
|
}
|
|
|
|
AbstractAction action = new AbstractAction(lib.getName()) {
|
|
public void actionPerformed(ActionEvent event) {
|
|
UserLibrary l = (UserLibrary) getValue("library");
|
|
try {
|
|
activeEditor.getSketchController().importLibrary(l);
|
|
} catch (IOException e) {
|
|
showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e);
|
|
}
|
|
}
|
|
};
|
|
action.putValue("library", lib);
|
|
|
|
// Add new element at the bottom
|
|
JMenuItem item = new JMenuItem(action);
|
|
item.putClientProperty("library", lib);
|
|
importMenu.add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void rebuildExamplesMenu(JMenu menu) {
|
|
if (menu == null) {
|
|
return;
|
|
}
|
|
|
|
menu.removeAll();
|
|
|
|
// Add examples from distribution "example" folder
|
|
JMenuItem label = new JMenuItem(tr("Built-in Examples"));
|
|
label.setEnabled(false);
|
|
menu.add(label);
|
|
boolean found = addSketches(menu, BaseNoGui.getExamplesFolder());
|
|
if (found) {
|
|
menu.addSeparator();
|
|
}
|
|
|
|
// Libraries can come from 4 locations: collect info about all four
|
|
File ideLibraryPath = BaseNoGui.getContentFile("libraries");
|
|
File sketchbookLibraryPath = BaseNoGui.getSketchbookLibrariesFolder();
|
|
File platformLibraryPath = null;
|
|
File referencedPlatformLibraryPath = null;
|
|
String boardId = null;
|
|
String referencedPlatformName = null;
|
|
String myArch = null;
|
|
TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
|
|
if (targetPlatform != null) {
|
|
myArch = targetPlatform.getId();
|
|
boardId = BaseNoGui.getTargetBoard().getName();
|
|
platformLibraryPath = new File(targetPlatform.getFolder(), "libraries");
|
|
String core = BaseNoGui.getBoardPreferences().get("build.core", "arduino");
|
|
if (core.contains(":")) {
|
|
String refcore = core.split(":")[0];
|
|
TargetPlatform referencedPlatform = BaseNoGui.getTargetPlatform(refcore, myArch);
|
|
if (referencedPlatform != null) {
|
|
referencedPlatformName = referencedPlatform.getPreferences().get("name");
|
|
referencedPlatformLibraryPath = new File(referencedPlatform.getFolder(), "libraries");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Divide the libraries into 7 lists, corresponding to the 4 locations
|
|
// with the retired IDE libs further divided into their own list, and
|
|
// any incompatible sketchbook libs further divided into their own list.
|
|
// The 7th list of "other" libraries should always be empty, but serves
|
|
// as a safety feature to prevent any library from vanishing.
|
|
LibraryList allLibraries = BaseNoGui.librariesIndexer.getInstalledLibraries();
|
|
LibraryList ideLibs = new LibraryList();
|
|
LibraryList retiredIdeLibs = new LibraryList();
|
|
LibraryList platformLibs = new LibraryList();
|
|
LibraryList referencedPlatformLibs = new LibraryList();
|
|
LibraryList sketchbookLibs = new LibraryList();
|
|
LibraryList sketchbookIncompatibleLibs = new LibraryList();
|
|
LibraryList otherLibs = new LibraryList();
|
|
for (UserLibrary lib : allLibraries) {
|
|
// Get the library's location - used for sorting into categories
|
|
File libraryLocation = lib.getInstalledFolder().getParentFile();
|
|
// Is this library compatible?
|
|
List<String> arch = lib.getArchitectures();
|
|
boolean compatible;
|
|
if (myArch == null || arch == null || arch.contains("*")) {
|
|
compatible = true;
|
|
} else {
|
|
compatible = arch.contains(myArch);
|
|
}
|
|
// IDE Libaries (including retired)
|
|
if (libraryLocation.equals(ideLibraryPath)) {
|
|
if (compatible) {
|
|
// only compatible IDE libs are shown
|
|
boolean retired = false;
|
|
List<String> types = lib.getTypes();
|
|
if (types != null) retired = types.contains("Retired");
|
|
if (retired) {
|
|
retiredIdeLibs.add(lib);
|
|
} else {
|
|
ideLibs.add(lib);
|
|
}
|
|
}
|
|
// Platform Libraries
|
|
} else if (libraryLocation.equals(platformLibraryPath)) {
|
|
// all platform libs are assumed to be compatible
|
|
platformLibs.add(lib);
|
|
// Referenced Platform Libraries
|
|
} else if (libraryLocation.equals(referencedPlatformLibraryPath)) {
|
|
// all referenced platform libs are assumed to be compatible
|
|
referencedPlatformLibs.add(lib);
|
|
// Sketchbook Libraries (including incompatible)
|
|
} else if (libraryLocation.equals(sketchbookLibraryPath)) {
|
|
if (compatible) {
|
|
// libraries promoted from sketchbook (behave as builtin)
|
|
if (lib.getTypes() != null && lib.getTypes().contains("Arduino")
|
|
&& lib.getArchitectures().contains("*")) {
|
|
ideLibs.add(lib);
|
|
} else {
|
|
sketchbookLibs.add(lib);
|
|
}
|
|
} else {
|
|
sketchbookIncompatibleLibs.add(lib);
|
|
}
|
|
// Other libraries of unknown type (should never occur)
|
|
} else {
|
|
otherLibs.add(lib);
|
|
}
|
|
}
|
|
|
|
// Add examples from libraries
|
|
if (!ideLibs.isEmpty()) {
|
|
ideLibs.sort();
|
|
label = new JMenuItem(tr("Examples for any board"));
|
|
label.setEnabled(false);
|
|
menu.add(label);
|
|
}
|
|
for (UserLibrary lib : ideLibs) {
|
|
addSketchesSubmenu(menu, lib);
|
|
}
|
|
|
|
if (!retiredIdeLibs.isEmpty()) {
|
|
retiredIdeLibs.sort();
|
|
JMenu retired = new JMenu(tr("RETIRED"));
|
|
menu.add(retired);
|
|
for (UserLibrary lib : retiredIdeLibs) {
|
|
addSketchesSubmenu(retired, lib);
|
|
}
|
|
}
|
|
|
|
if (!platformLibs.isEmpty()) {
|
|
menu.addSeparator();
|
|
platformLibs.sort();
|
|
label = new JMenuItem(I18n.format(tr("Examples for {0}"), boardId));
|
|
label.setEnabled(false);
|
|
menu.add(label);
|
|
for (UserLibrary lib : platformLibs) {
|
|
addSketchesSubmenu(menu, lib);
|
|
}
|
|
}
|
|
|
|
if (!referencedPlatformLibs.isEmpty()) {
|
|
menu.addSeparator();
|
|
referencedPlatformLibs.sort();
|
|
label = new JMenuItem(I18n.format(tr("Examples for {0}"), referencedPlatformName));
|
|
label.setEnabled(false);
|
|
menu.add(label);
|
|
for (UserLibrary lib : referencedPlatformLibs) {
|
|
addSketchesSubmenu(menu, lib);
|
|
}
|
|
}
|
|
|
|
if (!sketchbookLibs.isEmpty()) {
|
|
menu.addSeparator();
|
|
sketchbookLibs.sort();
|
|
label = new JMenuItem(tr("Examples from Custom Libraries"));
|
|
label.setEnabled(false);
|
|
menu.add(label);
|
|
for (UserLibrary lib : sketchbookLibs) {
|
|
addSketchesSubmenu(menu, lib);
|
|
}
|
|
}
|
|
|
|
if (!sketchbookIncompatibleLibs.isEmpty()) {
|
|
sketchbookIncompatibleLibs.sort();
|
|
JMenu incompatible = new JMenu(tr("INCOMPATIBLE"));
|
|
menu.add(incompatible);
|
|
for (UserLibrary lib : sketchbookIncompatibleLibs) {
|
|
addSketchesSubmenu(incompatible, lib);
|
|
}
|
|
}
|
|
|
|
if (!otherLibs.isEmpty()) {
|
|
menu.addSeparator();
|
|
otherLibs.sort();
|
|
label = new JMenuItem(tr("Examples from Other Libraries"));
|
|
label.setEnabled(false);
|
|
menu.add(label);
|
|
for (UserLibrary lib : otherLibs) {
|
|
addSketchesSubmenu(menu, lib);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static String priorPlatformFolder;
|
|
private static boolean newLibraryImported;
|
|
|
|
public void onBoardOrPortChange() {
|
|
BaseNoGui.onBoardOrPortChange();
|
|
|
|
// reload keywords when package/platform changes
|
|
TargetPlatform tp = BaseNoGui.getTargetPlatform();
|
|
if (tp != null) {
|
|
String platformFolder = tp.getFolder().getAbsolutePath();
|
|
if (priorPlatformFolder == null || !priorPlatformFolder.equals(platformFolder) || newLibraryImported) {
|
|
pdeKeywords = new PdeKeywords();
|
|
pdeKeywords.reload();
|
|
priorPlatformFolder = platformFolder;
|
|
newLibraryImported = false;
|
|
for (Editor editor : editors) {
|
|
editor.updateKeywords(pdeKeywords);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update editors status bar
|
|
for (Editor editor : editors) {
|
|
editor.onBoardOrPortChange();
|
|
}
|
|
}
|
|
|
|
public void openLibraryManager(final String filterText, String dropdownItem) {
|
|
if (contributionsSelfCheck != null) {
|
|
contributionsSelfCheck.cancel();
|
|
}
|
|
@SuppressWarnings("serial")
|
|
LibraryManagerUI managerUI = new LibraryManagerUI(activeEditor, libraryInstaller) {
|
|
@Override
|
|
protected void onIndexesUpdated() throws Exception {
|
|
BaseNoGui.initPackages();
|
|
rebuildBoardsMenu();
|
|
rebuildProgrammerMenu();
|
|
onBoardOrPortChange();
|
|
updateUI();
|
|
if (StringUtils.isNotEmpty(dropdownItem)) {
|
|
selectDropdownItemByClassName(dropdownItem);
|
|
}
|
|
if (StringUtils.isNotEmpty(filterText)) {
|
|
setFilterText(filterText);
|
|
}
|
|
}
|
|
};
|
|
managerUI.setLocationRelativeTo(activeEditor);
|
|
managerUI.updateUI();
|
|
managerUI.setVisible(true);
|
|
// Manager dialog is modal, waits here until closed
|
|
|
|
//handleAddLibrary();
|
|
newLibraryImported = true;
|
|
onBoardOrPortChange();
|
|
rebuildImportMenu(Editor.importMenu);
|
|
rebuildExamplesMenu(Editor.examplesMenu);
|
|
}
|
|
|
|
public void openBoardsManager(final String filterText, String dropdownItem) throws Exception {
|
|
if (contributionsSelfCheck != null) {
|
|
contributionsSelfCheck.cancel();
|
|
}
|
|
@SuppressWarnings("serial")
|
|
ContributionManagerUI managerUI = new ContributionManagerUI(activeEditor, contributionInstaller) {
|
|
@Override
|
|
protected void onIndexesUpdated() throws Exception {
|
|
BaseNoGui.initPackages();
|
|
rebuildBoardsMenu();
|
|
rebuildProgrammerMenu();
|
|
updateUI();
|
|
if (StringUtils.isNotEmpty(dropdownItem)) {
|
|
selectDropdownItemByClassName(dropdownItem);
|
|
}
|
|
if (StringUtils.isNotEmpty(filterText)) {
|
|
setFilterText(filterText);
|
|
}
|
|
}
|
|
};
|
|
managerUI.setLocationRelativeTo(activeEditor);
|
|
managerUI.updateUI();
|
|
managerUI.setVisible(true);
|
|
// Installer dialog is modal, waits here until closed
|
|
|
|
// Reload all boards (that may have been installed/updated/removed)
|
|
BaseNoGui.initPackages();
|
|
rebuildBoardsMenu();
|
|
rebuildProgrammerMenu();
|
|
onBoardOrPortChange();
|
|
}
|
|
|
|
public void rebuildBoardsMenu() throws Exception {
|
|
boardsCustomMenus = new LinkedList<>();
|
|
|
|
// The first custom menu is the "Board" selection submenu
|
|
JMenu boardMenu = new JMenu(tr("Board"));
|
|
boardMenu.putClientProperty("removeOnWindowDeactivation", true);
|
|
MenuScroller.setScrollerFor(boardMenu).setTopFixedCount(1);
|
|
|
|
boardMenu.add(new JMenuItem(new AbstractAction(tr("Boards Manager...")) {
|
|
public void actionPerformed(ActionEvent actionevent) {
|
|
String filterText = "";
|
|
String dropdownItem = "";
|
|
if (actionevent instanceof Event) {
|
|
filterText = ((Event) actionevent).getPayload().get("filterText").toString();
|
|
dropdownItem = ((Event) actionevent).getPayload().get("dropdownItem").toString();
|
|
}
|
|
try {
|
|
openBoardsManager(filterText, dropdownItem);
|
|
} catch (Exception e) {
|
|
//TODO show error
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}));
|
|
boardsCustomMenus.add(boardMenu);
|
|
|
|
// If there are no platforms installed we are done
|
|
if (BaseNoGui.packages.size() == 0)
|
|
return;
|
|
|
|
// Separate "Install boards..." command from installed boards
|
|
boardMenu.add(new JSeparator());
|
|
|
|
// Generate custom menus for all platforms
|
|
Set<String> customMenusTitles = new HashSet<>();
|
|
for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
|
|
for (TargetPlatform targetPlatform : targetPackage.platforms()) {
|
|
customMenusTitles.addAll(targetPlatform.getCustomMenus().values());
|
|
}
|
|
}
|
|
for (String customMenuTitle : customMenusTitles) {
|
|
JMenu customMenu = new JMenu(tr(customMenuTitle));
|
|
customMenu.putClientProperty("removeOnWindowDeactivation", true);
|
|
boardsCustomMenus.add(customMenu);
|
|
}
|
|
|
|
List<JMenuItem> menuItemsToClickAfterStartup = new LinkedList<>();
|
|
|
|
ButtonGroup boardsButtonGroup = new ButtonGroup();
|
|
Map<String, ButtonGroup> buttonGroupsMap = new HashMap<>();
|
|
|
|
// Cycle through all packages
|
|
boolean first = true;
|
|
for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
|
|
// For every package cycle through all platform
|
|
for (TargetPlatform targetPlatform : targetPackage.platforms()) {
|
|
|
|
// Add a separator from the previous platform
|
|
if (!first)
|
|
boardMenu.add(new JSeparator());
|
|
first = false;
|
|
|
|
// Add a title for each platform
|
|
String platformLabel = targetPlatform.getPreferences().get("name");
|
|
if (platformLabel != null && !targetPlatform.getBoards().isEmpty()) {
|
|
JMenuItem menuLabel = new JMenuItem(tr(platformLabel));
|
|
menuLabel.setEnabled(false);
|
|
boardMenu.add(menuLabel);
|
|
}
|
|
|
|
// Cycle through all boards of this platform
|
|
for (TargetBoard board : targetPlatform.getBoards().values()) {
|
|
if (board.getPreferences().get("hide") != null)
|
|
continue;
|
|
JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup,
|
|
buttonGroupsMap,
|
|
board, targetPlatform, targetPackage);
|
|
boardMenu.add(item);
|
|
boardsButtonGroup.add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (menuItemsToClickAfterStartup.isEmpty()) {
|
|
menuItemsToClickAfterStartup.add(selectFirstEnabledMenuItem(boardMenu));
|
|
}
|
|
|
|
for (JMenuItem menuItemToClick : menuItemsToClickAfterStartup) {
|
|
menuItemToClick.setSelected(true);
|
|
menuItemToClick.getAction().actionPerformed(new ActionEvent(this, -1, ""));
|
|
}
|
|
}
|
|
|
|
private JRadioButtonMenuItem createBoardMenusAndCustomMenus(
|
|
final List<JMenu> boardsCustomMenus, List<JMenuItem> menuItemsToClickAfterStartup,
|
|
Map<String, ButtonGroup> buttonGroupsMap,
|
|
TargetBoard board, TargetPlatform targetPlatform, TargetPackage targetPackage)
|
|
throws Exception {
|
|
String selPackage = PreferencesData.get("target_package");
|
|
String selPlatform = PreferencesData.get("target_platform");
|
|
String selBoard = PreferencesData.get("board");
|
|
|
|
String boardId = board.getId();
|
|
String packageName = targetPackage.getId();
|
|
String platformName = targetPlatform.getId();
|
|
|
|
// Setup a menu item for the current board
|
|
@SuppressWarnings("serial")
|
|
Action action = new AbstractAction(board.getName()) {
|
|
public void actionPerformed(ActionEvent actionevent) {
|
|
BaseNoGui.selectBoard((TargetBoard) getValue("b"));
|
|
filterVisibilityOfSubsequentBoardMenus(boardsCustomMenus, (TargetBoard) getValue("b"), 1);
|
|
|
|
onBoardOrPortChange();
|
|
rebuildImportMenu(Editor.importMenu);
|
|
rebuildExamplesMenu(Editor.examplesMenu);
|
|
}
|
|
};
|
|
action.putValue("b", board);
|
|
|
|
JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
|
|
|
|
if (selBoard.equals(boardId) && selPackage.equals(packageName)
|
|
&& selPlatform.equals(platformName)) {
|
|
menuItemsToClickAfterStartup.add(item);
|
|
}
|
|
|
|
PreferencesMap customMenus = targetPlatform.getCustomMenus();
|
|
for (final String menuId : customMenus.keySet()) {
|
|
String title = customMenus.get(menuId);
|
|
JMenu menu = getBoardCustomMenu(tr(title));
|
|
|
|
if (board.hasMenu(menuId)) {
|
|
PreferencesMap boardCustomMenu = board.getMenuLabels(menuId);
|
|
for (String customMenuOption : boardCustomMenu.keySet()) {
|
|
@SuppressWarnings("serial")
|
|
Action subAction = new AbstractAction(tr(boardCustomMenu.get(customMenuOption))) {
|
|
public void actionPerformed(ActionEvent e) {
|
|
PreferencesData.set("custom_" + menuId, ((TargetBoard) getValue("board")).getId() + "_" + getValue("custom_menu_option"));
|
|
onBoardOrPortChange();
|
|
}
|
|
};
|
|
subAction.putValue("board", board);
|
|
subAction.putValue("custom_menu_option", customMenuOption);
|
|
|
|
if (!buttonGroupsMap.containsKey(menuId)) {
|
|
buttonGroupsMap.put(menuId, new ButtonGroup());
|
|
}
|
|
|
|
JRadioButtonMenuItem subItem = new JRadioButtonMenuItem(subAction);
|
|
menu.add(subItem);
|
|
buttonGroupsMap.get(menuId).add(subItem);
|
|
|
|
String selectedCustomMenuEntry = PreferencesData.get("custom_" + menuId);
|
|
if (selBoard.equals(boardId) && (boardId + "_" + customMenuOption).equals(selectedCustomMenuEntry)) {
|
|
menuItemsToClickAfterStartup.add(subItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
private void filterVisibilityOfSubsequentBoardMenus(List<JMenu> boardsCustomMenus, TargetBoard board,
|
|
int fromIndex) {
|
|
for (int i = fromIndex; i < boardsCustomMenus.size(); i++) {
|
|
JMenu menu = boardsCustomMenus.get(i);
|
|
for (int m = 0; m < menu.getItemCount(); m++) {
|
|
JMenuItem menuItem = menu.getItem(m);
|
|
menuItem.setVisible(menuItem.getAction().getValue("board").equals(board));
|
|
}
|
|
menu.setVisible(ifThereAreVisibleItemsOn(menu));
|
|
|
|
if (menu.isVisible()) {
|
|
JMenuItem visibleSelectedOrFirstMenuItem = selectVisibleSelectedOrFirstMenuItem(menu);
|
|
if (!visibleSelectedOrFirstMenuItem.isSelected()) {
|
|
visibleSelectedOrFirstMenuItem.setSelected(true);
|
|
visibleSelectedOrFirstMenuItem.getAction().actionPerformed(null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean ifThereAreVisibleItemsOn(JMenu menu) {
|
|
for (int i = 0; i < menu.getItemCount(); i++) {
|
|
if (menu.getItem(i).isVisible()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private JMenu getBoardCustomMenu(String label) throws Exception {
|
|
for (JMenu menu : boardsCustomMenus) {
|
|
if (label.equals(menu.getText())) {
|
|
return menu;
|
|
}
|
|
}
|
|
throw new Exception("Custom menu not found!");
|
|
}
|
|
|
|
public List<JMenuItem> getProgrammerMenus() {
|
|
return programmerMenus;
|
|
}
|
|
|
|
private static JMenuItem selectVisibleSelectedOrFirstMenuItem(JMenu menu) {
|
|
JMenuItem firstVisible = null;
|
|
for (int i = 0; i < menu.getItemCount(); i++) {
|
|
JMenuItem item = menu.getItem(i);
|
|
if (item != null && item.isVisible()) {
|
|
if (item.isSelected()) {
|
|
return item;
|
|
}
|
|
if (firstVisible == null) {
|
|
firstVisible = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (firstVisible != null) {
|
|
return firstVisible;
|
|
}
|
|
|
|
throw new IllegalStateException("Menu has no enabled items");
|
|
}
|
|
|
|
private static JMenuItem selectFirstEnabledMenuItem(JMenu menu) {
|
|
for (int i = 1; i < menu.getItemCount(); i++) {
|
|
JMenuItem item = menu.getItem(i);
|
|
if (item != null && item.isEnabled()) {
|
|
return item;
|
|
}
|
|
}
|
|
throw new IllegalStateException("Menu has no enabled items");
|
|
}
|
|
|
|
public void rebuildProgrammerMenu() {
|
|
programmerMenus = new LinkedList<>();
|
|
|
|
ButtonGroup group = new ButtonGroup();
|
|
for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
|
|
for (TargetPlatform targetPlatform : targetPackage.platforms()) {
|
|
for (String programmer : targetPlatform.getProgrammers().keySet()) {
|
|
String id = targetPackage.getId() + ":" + programmer;
|
|
|
|
@SuppressWarnings("serial")
|
|
AbstractAction action = new AbstractAction(targetPlatform.getProgrammer(programmer).get("name")) {
|
|
public void actionPerformed(ActionEvent actionevent) {
|
|
PreferencesData.set("programmer", "" + getValue("id"));
|
|
}
|
|
};
|
|
action.putValue("id", id);
|
|
JMenuItem item = new JRadioButtonMenuItem(action);
|
|
if (PreferencesData.get("programmer").equals(id)) {
|
|
item.setSelected(true);
|
|
}
|
|
group.add(item);
|
|
programmerMenus.add(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan a folder recursively, and add any sketches found to the menu
|
|
* specified. Set the openReplaces parameter to true when opening the sketch
|
|
* should replace the sketch in the current window, or false when the
|
|
* sketch should open in a new window.
|
|
*/
|
|
protected boolean addSketches(JMenu menu, File folder) {
|
|
if (folder == null)
|
|
return false;
|
|
|
|
if (!folder.isDirectory()) return false;
|
|
|
|
File[] files = folder.listFiles();
|
|
// If a bad folder or unreadable or whatever, this will come back null
|
|
if (files == null) return false;
|
|
|
|
// Alphabetize files, since it's not always alpha order
|
|
Arrays.sort(files, new Comparator<File>() {
|
|
@Override
|
|
public int compare(File file, File file2) {
|
|
return file.getName().compareToIgnoreCase(file2.getName());
|
|
}
|
|
});
|
|
|
|
boolean ifound = false;
|
|
|
|
for (File subfolder : files) {
|
|
if (FileUtils.isSCCSOrHiddenFile(subfolder)) {
|
|
continue;
|
|
}
|
|
|
|
if (!subfolder.isDirectory()) continue;
|
|
|
|
if (addSketchesSubmenu(menu, subfolder.getName(), subfolder)) {
|
|
ifound = true;
|
|
}
|
|
}
|
|
|
|
return ifound;
|
|
}
|
|
|
|
private boolean addSketchesSubmenu(JMenu menu, UserLibrary lib) {
|
|
return addSketchesSubmenu(menu, lib.getName(), lib.getInstalledFolder());
|
|
}
|
|
|
|
private boolean addSketchesSubmenu(JMenu menu, String name, File folder) {
|
|
|
|
ActionListener listener = new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
String path = e.getActionCommand();
|
|
File file = new File(path);
|
|
if (file.exists()) {
|
|
try {
|
|
handleOpen(file);
|
|
} catch (Exception e1) {
|
|
e1.printStackTrace();
|
|
}
|
|
} else {
|
|
showWarning(tr("Sketch Does Not Exist"),
|
|
tr("The selected sketch no longer exists.\n"
|
|
+ "You may need to restart Arduino to update\n"
|
|
+ "the sketchbook menu."), null);
|
|
}
|
|
}
|
|
};
|
|
|
|
File entry = new File(folder, name + ".ino");
|
|
if (!entry.exists() && (new File(folder, name + ".pde")).exists())
|
|
entry = new File(folder, name + ".pde");
|
|
|
|
// if a .pde file of the same prefix as the folder exists..
|
|
if (entry.exists()) {
|
|
|
|
if (!BaseNoGui.isSanitaryName(name)) {
|
|
if (!builtOnce) {
|
|
String complaining = I18n
|
|
.format(
|
|
tr("The sketch \"{0}\" cannot be used.\n"
|
|
+ "Sketch names must contain only basic letters and numbers\n"
|
|
+ "(ASCII-only with no spaces, "
|
|
+ "and it cannot start with a number).\n"
|
|
+ "To get rid of this message, remove the sketch from\n"
|
|
+ "{1}"), name, entry.getAbsolutePath());
|
|
showMessage(tr("Ignoring sketch with bad name"), complaining);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
JMenuItem item = new JMenuItem(name);
|
|
item.addActionListener(listener);
|
|
item.setActionCommand(entry.getAbsolutePath());
|
|
menu.add(item);
|
|
return true;
|
|
}
|
|
|
|
// don't create an extra menu level for a folder named "examples"
|
|
if (folder.getName().equals("examples"))
|
|
return addSketches(menu, folder);
|
|
|
|
// not a sketch folder, but maybe a subfolder containing sketches
|
|
JMenu submenu = new JMenu(name);
|
|
boolean found = addSketches(submenu, folder);
|
|
if (found) {
|
|
menu.add(submenu);
|
|
MenuScroller.setScrollerFor(submenu);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
protected void addLibraries(JMenu menu, LibraryList libs) throws IOException {
|
|
|
|
LibraryList list = new LibraryList(libs);
|
|
list.sort();
|
|
|
|
for (UserLibrary lib : list) {
|
|
@SuppressWarnings("serial")
|
|
AbstractAction action = new AbstractAction(lib.getName()) {
|
|
public void actionPerformed(ActionEvent event) {
|
|
UserLibrary l = (UserLibrary) getValue("library");
|
|
try {
|
|
activeEditor.getSketchController().importLibrary(l);
|
|
} catch (IOException e) {
|
|
showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e);
|
|
}
|
|
}
|
|
};
|
|
action.putValue("library", lib);
|
|
|
|
// Add new element at the bottom
|
|
JMenuItem item = new JMenuItem(action);
|
|
item.putClientProperty("library", lib);
|
|
menu.add(item);
|
|
|
|
// XXX: DAM: should recurse here so that library folders can be nested
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a folder, return a list of the header files in that folder (but not
|
|
* the header files in its sub-folders, as those should be included from
|
|
* within the header files at the top-level).
|
|
*/
|
|
static public String[] headerListFromIncludePath(File path) throws IOException {
|
|
String[] list = path.list(new OnlyFilesWithExtension(".h"));
|
|
if (list == null) {
|
|
throw new IOException();
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Show the About box.
|
|
*/
|
|
@SuppressWarnings("serial")
|
|
public void handleAbout() {
|
|
final Image image = Theme.getLibImage("about", activeEditor,
|
|
Theme.scale(475), Theme.scale(300));
|
|
final Window window = new Window(activeEditor) {
|
|
public void paint(Graphics graphics) {
|
|
Graphics2D g = Theme.setupGraphics2D(graphics);
|
|
g.drawImage(image, 0, 0, null);
|
|
|
|
Font f = new Font("SansSerif", Font.PLAIN, Theme.scale(11));
|
|
g.setFont(f);
|
|
g.setColor(new Color(0,151,156));
|
|
g.drawString(BaseNoGui.VERSION_NAME_LONG, Theme.scale(33), Theme.scale(20));
|
|
}
|
|
};
|
|
window.addMouseListener(new MouseAdapter() {
|
|
public void mousePressed(MouseEvent e) {
|
|
window.dispose();
|
|
}
|
|
});
|
|
int w = image.getWidth(activeEditor);
|
|
int h = image.getHeight(activeEditor);
|
|
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
|
|
window.setBounds((screen.width - w) / 2, (screen.height - h) / 2, w, h);
|
|
window.setLocationRelativeTo(activeEditor);
|
|
window.setVisible(true);
|
|
}
|
|
|
|
|
|
/**
|
|
* Show the Preferences window.
|
|
*/
|
|
public void handlePrefs() {
|
|
cc.arduino.view.preferences.Preferences dialog = new cc.arduino.view.preferences.Preferences(activeEditor, this);
|
|
if (activeEditor != null) {
|
|
dialog.setLocationRelativeTo(activeEditor);
|
|
}
|
|
dialog.setVisible(true);
|
|
}
|
|
|
|
/**
|
|
* Adjust font size
|
|
*/
|
|
public void handleFontSizeChange(int change) {
|
|
String pieces[] = PreferencesData.get("editor.font").split(",");
|
|
try {
|
|
int newSize = Integer.parseInt(pieces[2]) + change;
|
|
if (newSize < 4)
|
|
newSize = 4;
|
|
pieces[2] = String.valueOf(newSize);
|
|
} catch (NumberFormatException e) {
|
|
// ignore
|
|
return;
|
|
}
|
|
PreferencesData.set("editor.font", StringUtils.join(pieces, ','));
|
|
getEditors().forEach(Editor::applyPreferences);
|
|
}
|
|
|
|
public List<JMenu> getBoardsCustomMenus() {
|
|
return boardsCustomMenus;
|
|
}
|
|
|
|
public File getDefaultSketchbookFolderOrPromptForIt() {
|
|
File sketchbookFolder = BaseNoGui.getDefaultSketchbookFolder();
|
|
|
|
if (sketchbookFolder == null && !isCommandLine()) {
|
|
sketchbookFolder = promptSketchbookLocation();
|
|
}
|
|
|
|
// create the folder if it doesn't exist already
|
|
boolean result = true;
|
|
if (!sketchbookFolder.exists()) {
|
|
result = sketchbookFolder.mkdirs();
|
|
}
|
|
|
|
if (!result) {
|
|
showError(tr("You forgot your sketchbook"),
|
|
tr("Arduino cannot run because it could not\n" +
|
|
"create a folder to store your sketchbook."), null);
|
|
}
|
|
|
|
return sketchbookFolder;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check for a new sketchbook location.
|
|
*/
|
|
static protected File promptSketchbookLocation() {
|
|
File folder = null;
|
|
|
|
folder = new File(System.getProperty("user.home"), "sketchbook");
|
|
if (!folder.exists()) {
|
|
folder.mkdirs();
|
|
return folder;
|
|
}
|
|
|
|
String prompt = tr("Select (or create new) folder for sketches...");
|
|
folder = selectFolder(prompt, null, null);
|
|
if (folder == null) {
|
|
System.exit(0);
|
|
}
|
|
return folder;
|
|
}
|
|
|
|
|
|
// .................................................................
|
|
|
|
|
|
/**
|
|
* Implements the cross-platform headache of opening URLs
|
|
* TODO This code should be replaced by PApplet.link(),
|
|
* however that's not a static method (because it requires
|
|
* an AppletContext when used as an applet), so it's mildly
|
|
* trickier than just removing this method.
|
|
*/
|
|
static public void openURL(String url) {
|
|
try {
|
|
BaseNoGui.getPlatform().openURL(url);
|
|
|
|
} catch (Exception e) {
|
|
showWarning(tr("Problem Opening URL"),
|
|
I18n.format(tr("Could not open the URL\n{0}"), url), e);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Used to determine whether to disable the "Show Sketch Folder" option.
|
|
*
|
|
* @return true If a means of opening a folder is known to be available.
|
|
*/
|
|
static protected boolean openFolderAvailable() {
|
|
return BaseNoGui.getPlatform().openFolderAvailable();
|
|
}
|
|
|
|
|
|
/**
|
|
* Implements the other cross-platform headache of opening
|
|
* a folder in the machine's native file browser.
|
|
*/
|
|
static public void openFolder(File file) {
|
|
try {
|
|
BaseNoGui.getPlatform().openFolder(file);
|
|
|
|
} catch (Exception e) {
|
|
showWarning(tr("Problem Opening Folder"),
|
|
I18n.format(tr("Could not open the folder\n{0}"), file.getAbsolutePath()), e);
|
|
}
|
|
}
|
|
|
|
|
|
// .................................................................
|
|
|
|
|
|
static public File selectFolder(String prompt, File folder, Component parent) {
|
|
JFileChooser fc = new JFileChooser();
|
|
fc.setDialogTitle(prompt);
|
|
if (folder != null) {
|
|
fc.setSelectedFile(folder);
|
|
}
|
|
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
|
|
|
int returned = fc.showOpenDialog(parent);
|
|
if (returned == JFileChooser.APPROVE_OPTION) {
|
|
return fc.getSelectedFile();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
// .................................................................
|
|
|
|
|
|
/**
|
|
* Give this Frame an icon.
|
|
*/
|
|
static public void setIcon(Frame frame) {
|
|
if (OSUtils.isMacOS()) {
|
|
return;
|
|
}
|
|
|
|
List<Image> icons = Stream
|
|
.of("16", "24", "32", "48", "64", "72", "96", "128", "256")
|
|
.map(res -> "/lib/icons/" + res + "x" + res + "/apps/arduino.png")
|
|
.map(path -> BaseNoGui.getContentFile(path).getAbsolutePath())
|
|
.map(absPath -> Toolkit.getDefaultToolkit().createImage(absPath))
|
|
.collect(Collectors.toList());
|
|
frame.setIconImages(icons);
|
|
}
|
|
|
|
|
|
/**
|
|
* Registers key events for a Ctrl-W and ESC with an ActionListener
|
|
* that will take care of disposing the window.
|
|
*/
|
|
static public void registerWindowCloseKeys(JRootPane root,
|
|
ActionListener disposer) {
|
|
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
|
root.registerKeyboardAction(disposer, stroke,
|
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
|
|
|
int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
|
stroke = KeyStroke.getKeyStroke('W', modifiers);
|
|
root.registerKeyboardAction(disposer, stroke,
|
|
JComponent.WHEN_IN_FOCUSED_WINDOW);
|
|
}
|
|
|
|
|
|
// .................................................................
|
|
|
|
|
|
static public void showReference(String filename) {
|
|
showReference("reference/www.arduino.cc/en", filename);
|
|
}
|
|
|
|
static public void showReference(String prefix, String filename) {
|
|
File referenceFolder = getContentFile(prefix);
|
|
File referenceFile = new File(referenceFolder, filename);
|
|
if (!referenceFile.exists())
|
|
referenceFile = new File(referenceFolder, filename + ".html");
|
|
|
|
if(referenceFile.exists()){
|
|
openURL(referenceFile.getAbsolutePath());
|
|
}else{
|
|
showWarning(tr("Problem Opening URL"), I18n.format(tr("Could not open the URL\n{0}"), referenceFile), null);
|
|
}
|
|
}
|
|
|
|
public static void showEdisonGettingStarted() {
|
|
showReference("reference/Edison_help_files", "ArduinoIDE_guide_edison");
|
|
}
|
|
|
|
static public void showArduinoGettingStarted() {
|
|
if (OSUtils.isMacOS()) {
|
|
showReference("Guide/MacOSX");
|
|
} else if (OSUtils.isWindows()) {
|
|
showReference("Guide/Windows");
|
|
} else {
|
|
openURL("http://www.arduino.cc/playground/Learning/Linux");
|
|
}
|
|
}
|
|
|
|
static public void showReference() {
|
|
showReference("Reference/HomePage");
|
|
}
|
|
|
|
|
|
static public void showEnvironment() {
|
|
showReference("Guide/Environment");
|
|
}
|
|
|
|
|
|
static public void showTroubleshooting() {
|
|
showReference("Guide/Troubleshooting");
|
|
}
|
|
|
|
|
|
static public void showFAQ() {
|
|
showReference("Main/FAQ");
|
|
}
|
|
|
|
|
|
// .................................................................
|
|
|
|
|
|
/**
|
|
* "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) {
|
|
BaseNoGui.showMessage(title, message);
|
|
}
|
|
|
|
|
|
/**
|
|
* Non-fatal error message with optional stack trace side dish.
|
|
*/
|
|
static public void showWarning(String title, String message, Exception e) {
|
|
BaseNoGui.showWarning(title, message, e);
|
|
}
|
|
|
|
|
|
static public void showError(String title, String message, Throwable e) {
|
|
showError(title, message, e, 1);
|
|
}
|
|
|
|
static public void showError(String title, String message, int exit_code) {
|
|
showError(title, message, null, exit_code);
|
|
}
|
|
|
|
/**
|
|
* 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, int exit_code) {
|
|
BaseNoGui.showError(title, message, e, exit_code);
|
|
}
|
|
|
|
|
|
static public File getContentFile(String name) {
|
|
return BaseNoGui.getContentFile(name);
|
|
}
|
|
|
|
|
|
// ...................................................................
|
|
|
|
|
|
/**
|
|
* 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) {
|
|
return BaseNoGui.countLines(what);
|
|
}
|
|
|
|
|
|
/**
|
|
* Same as PApplet.loadBytes(), however never does gzip decoding.
|
|
*/
|
|
static public byte[] loadBytesRaw(File file) throws IOException {
|
|
int size = (int) file.length();
|
|
FileInputStream input = null;
|
|
try {
|
|
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;
|
|
}
|
|
return buffer;
|
|
} finally {
|
|
IOUtils.closeQuietly(input);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Read from a file with a bunch of attribute/value pairs
|
|
* that are separated by = and ignore comments with #.
|
|
*/
|
|
static public HashMap<String, String> readSettings(File inputFile) {
|
|
HashMap<String, String> outgoing = new HashMap<>();
|
|
if (!inputFile.exists()) return outgoing; // return empty hash
|
|
|
|
String lines[] = PApplet.loadStrings(inputFile);
|
|
for (int i = 0; i < lines.length; i++) {
|
|
int hash = lines[i].indexOf('#');
|
|
String line = (hash == -1) ?
|
|
lines[i].trim() : lines[i].substring(0, hash).trim();
|
|
if (line.length() == 0) continue;
|
|
|
|
int equals = line.indexOf('=');
|
|
if (equals == -1) {
|
|
System.err.println("ignoring illegal line in " + inputFile);
|
|
System.err.println(" " + line);
|
|
continue;
|
|
}
|
|
String attr = line.substring(0, equals).trim();
|
|
String valu = line.substring(equals + 1).trim();
|
|
outgoing.put(attr, valu);
|
|
}
|
|
return outgoing;
|
|
}
|
|
|
|
|
|
static public void copyFile(File sourceFile,
|
|
File targetFile) throws IOException {
|
|
InputStream from = null;
|
|
OutputStream to = null;
|
|
try {
|
|
from = new BufferedInputStream(new FileInputStream(sourceFile));
|
|
to = new BufferedOutputStream(new FileOutputStream(targetFile));
|
|
byte[] buffer = new byte[16 * 1024];
|
|
int bytesRead;
|
|
while ((bytesRead = from.read(buffer)) != -1) {
|
|
to.write(buffer, 0, bytesRead);
|
|
}
|
|
to.flush();
|
|
} finally {
|
|
IOUtils.closeQuietly(from);
|
|
IOUtils.closeQuietly(to);
|
|
}
|
|
|
|
targetFile.setLastModified(sourceFile.lastModified());
|
|
}
|
|
|
|
|
|
/**
|
|
* Grab the contents of a file as a string.
|
|
*/
|
|
static public String loadFile(File file) throws IOException {
|
|
return BaseNoGui.loadFile(file);
|
|
}
|
|
|
|
|
|
/**
|
|
* Spew the contents of a String object out to a file.
|
|
*/
|
|
static public void saveFile(String str, File file) throws IOException {
|
|
BaseNoGui.saveFile(str, 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 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;
|
|
}
|
|
|
|
public void handleAddLibrary() {
|
|
JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home"));
|
|
fileChooser.setDialogTitle(tr("Select a zip file or a folder containing the library you'd like to add"));
|
|
fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
|
|
fileChooser.setFileFilter(new FileNameExtensionFilter(tr("ZIP files or folders"), "zip"));
|
|
|
|
Dimension preferredSize = fileChooser.getPreferredSize();
|
|
fileChooser.setPreferredSize(new Dimension(preferredSize.width + 200, preferredSize.height + 200));
|
|
|
|
int returnVal = fileChooser.showOpenDialog(activeEditor);
|
|
|
|
if (returnVal != JFileChooser.APPROVE_OPTION) {
|
|
return;
|
|
}
|
|
|
|
File sourceFile = fileChooser.getSelectedFile();
|
|
File tmpFolder = null;
|
|
|
|
try {
|
|
// unpack ZIP
|
|
if (!sourceFile.isDirectory()) {
|
|
try {
|
|
tmpFolder = FileUtils.createTempFolder();
|
|
ZipDeflater zipDeflater = new ZipDeflater(sourceFile, tmpFolder);
|
|
zipDeflater.deflate();
|
|
File[] foldersInTmpFolder = tmpFolder.listFiles(new OnlyDirs());
|
|
if (foldersInTmpFolder.length != 1) {
|
|
throw new IOException(tr("Zip doesn't contain a library"));
|
|
}
|
|
sourceFile = foldersInTmpFolder[0];
|
|
} catch (IOException e) {
|
|
activeEditor.statusError(e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
File libFolder = sourceFile;
|
|
if (FileUtils.isSubDirectory(new File(PreferencesData.get("sketchbook.path")), libFolder)) {
|
|
activeEditor.statusError(tr("A subfolder of your sketchbook is not a valid library"));
|
|
return;
|
|
}
|
|
|
|
if (FileUtils.isSubDirectory(libFolder, new File(PreferencesData.get("sketchbook.path")))) {
|
|
activeEditor.statusError(tr("You can't import a folder that contains your sketchbook"));
|
|
return;
|
|
}
|
|
|
|
String libName = libFolder.getName();
|
|
if (!BaseNoGui.isSanitaryName(libName)) {
|
|
String mess = I18n.format(tr("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);
|
|
activeEditor.statusError(mess);
|
|
return;
|
|
}
|
|
|
|
String[] headers;
|
|
if (new File(libFolder, "library.properties").exists()) {
|
|
headers = BaseNoGui.headerListFromIncludePath(UserLibrary.create(libFolder).getSrcFolder());
|
|
} else {
|
|
headers = BaseNoGui.headerListFromIncludePath(libFolder);
|
|
}
|
|
if (headers.length == 0) {
|
|
activeEditor.statusError(tr("Specified folder/zip file does not contain a valid library"));
|
|
return;
|
|
}
|
|
|
|
// copy folder
|
|
File destinationFolder = new File(BaseNoGui.getSketchbookLibrariesFolder(), sourceFile.getName());
|
|
if (!destinationFolder.mkdir()) {
|
|
activeEditor.statusError(I18n.format(tr("A library named {0} already exists"), sourceFile.getName()));
|
|
return;
|
|
}
|
|
try {
|
|
FileUtils.copy(sourceFile, destinationFolder);
|
|
} catch (IOException e) {
|
|
activeEditor.statusError(e);
|
|
return;
|
|
}
|
|
activeEditor.statusNotice(tr("Library added to your libraries. Check \"Include library\" menu"));
|
|
} catch (IOException e) {
|
|
// FIXME error when importing. ignoring :(
|
|
} finally {
|
|
// delete zip created temp folder, if exists
|
|
newLibraryImported = true;
|
|
FileUtils.recursiveDelete(tmpFolder);
|
|
}
|
|
}
|
|
|
|
public static DiscoveryManager getDiscoveryManager() {
|
|
return BaseNoGui.getDiscoveryManager();
|
|
}
|
|
|
|
public Editor getActiveEditor() {
|
|
return activeEditor;
|
|
}
|
|
|
|
public boolean hasActiveEditor() {
|
|
return activeEditor != null;
|
|
}
|
|
|
|
public List<Editor> getEditors() {
|
|
return new LinkedList<>(editors);
|
|
}
|
|
|
|
public PdeKeywords getPdeKeywords() {
|
|
return pdeKeywords;
|
|
}
|
|
|
|
public List<JMenuItem> getRecentSketchesMenuItems() {
|
|
return recentSketchesMenuItems;
|
|
}
|
|
|
|
}
|