diff --git a/app/.classpath b/app/.classpath
index 27164be5b04..956c4a5c8f2 100644
--- a/app/.classpath
+++ b/app/.classpath
@@ -54,5 +54,6 @@
+
diff --git a/app/lib/eventbus4j-0.0.1.jar b/app/lib/eventbus4j-0.0.1.jar
new file mode 100755
index 00000000000..ba882e56e48
Binary files /dev/null and b/app/lib/eventbus4j-0.0.1.jar differ
diff --git a/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java b/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
index d28d735e35f..b894cca767b 100644
--- a/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
+++ b/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
@@ -85,9 +85,7 @@ private void builtInPackageIsNewerCheck() throws InterruptedException {
return;
}
- while (!base.hasActiveEditor()) {
- Thread.sleep(100);
- }
+ base.getWaitActiveEditor();
if (VersionComparator.greaterThan(installedBuiltIn.getParsedVersion(), installedNotBuiltIn.getParsedVersion())) {
SwingUtilities.invokeLater(() -> {
diff --git a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
index 96fd987b099..614fe1610e6 100644
--- a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
+++ b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
@@ -125,7 +125,7 @@ public void run() {
}
SwingUtilities.invokeLater(() -> {
- Editor ed = base.getActiveEditor();
+ Editor ed = base.getWaitActiveEditor();
boolean accessibleIde = PreferencesData.getBoolean("ide.accessible");
if (accessibleIde) {
notificationPopup = new NotificationPopup(ed, hyperlinkListener, text, false, this, button1Name, button2Name);
diff --git a/app/src/cc/arduino/view/JMenuLazy.java b/app/src/cc/arduino/view/JMenuLazy.java
new file mode 100755
index 00000000000..e1398ac9ffa
--- /dev/null
+++ b/app/src/cc/arduino/view/JMenuLazy.java
@@ -0,0 +1,105 @@
+package cc.arduino.view;
+
+import java.awt.Component;
+import java.util.LinkedList;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+
+/**
+ * Avoid slow rendering of menu while load full menu tree in background
+ *
+ * @author Ricardo JL Rufino - (ricardo.jl.rufino@gmail.com)
+ * @date 13 de mai de 2020
+ */
+public class JMenuLazy extends JMenu {
+
+ private LinkedList components = new LinkedList<>();
+
+ private boolean loading = false;
+
+ public JMenuLazy(String s) {
+ super(s);
+ addMenuListener(menuListener);
+ }
+
+ public void setLoading(boolean loading) {
+ this.loading = loading;
+ this.setEnabled(!loading);
+ }
+
+ public boolean isLoading() {
+ return loading;
+ }
+
+ @Override
+ public Component add(Component c) {
+ if (isLoading()) {
+ components.add(c);
+ return c;
+ }
+ return super.add(c);
+ }
+
+ @Override
+ public JMenuItem add(JMenuItem c) {
+ if (isLoading()) {
+ components.add(c);
+ return c;
+ }
+ return super.add(c);
+ }
+
+ @Override
+ public void addSeparator() {
+ if (isLoading()) {
+ components.add(new JPopupMenu.Separator());
+ return;
+ }
+ super.addSeparator();
+ }
+
+ public void removeAll() {
+ super.removeAll();
+ synchronized (components) {
+ components.clear();
+ }
+ };
+
+ private MenuListener menuListener = new MenuListener() {
+
+ @Override
+ public void menuSelected(MenuEvent e) {
+ if (!isLoading()) {
+ if (loading == false) {
+ if (!components.isEmpty()) {
+ for (Component component : components) {
+ if (component instanceof JSeparator) {
+ JMenuLazy.super.addSeparator();
+ } else {
+ JMenuLazy.super.add(component);
+
+ }
+ }
+ components.clear();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void menuDeselected(MenuEvent e) {
+
+ }
+
+ @Override
+ public void menuCanceled(MenuEvent e) {
+
+ }
+ };
+
+}
diff --git a/app/src/cc/arduino/view/SplashScreenHelper.java b/app/src/cc/arduino/view/SplashScreenHelper.java
index 108c1c8b2f6..b6ebf3672f7 100644
--- a/app/src/cc/arduino/view/SplashScreenHelper.java
+++ b/app/src/cc/arduino/view/SplashScreenHelper.java
@@ -48,6 +48,8 @@ public class SplashScreenHelper {
private final SplashScreen splash;
private Rectangle2D.Double splashTextArea;
private Graphics2D splashGraphics;
+ private int progress = 0;
+ private String text;
public SplashScreenHelper(SplashScreen splash) {
this.splash = splash;
@@ -58,8 +60,19 @@ public SplashScreenHelper(SplashScreen splash) {
desktopHints = null;
}
}
+
+ public void setProgress(int progress) {
+ this.progress = progress;
+ splashText(text);
+ }
+
+ public void splashText(String text, int progress) {
+ this.progress = progress;
+ splashText(text);
+ }
public void splashText(String text) {
+ this.text = text;
if (splash == null) {
printText(text);
return;
@@ -76,10 +89,13 @@ public void splashText(String text) {
eraseLastStatusText();
drawText(text);
+
+ if(progress > 0) drawProgress();
ensureTextIsDiplayed();
}
+
private void ensureTextIsDiplayed() {
synchronized (SplashScreen.class) {
if (splash.isVisible()) {
@@ -88,6 +104,14 @@ private void ensureTextIsDiplayed() {
}
}
+
+ private void drawProgress() {
+ splashGraphics.setPaint(new Color(0, 100, 104));
+ splashGraphics.setStroke(new BasicStroke(2));
+ int w = (int)(splashTextArea.getWidth()*(progress/100.0f));
+ splashGraphics.drawLine( (int) splashTextArea.getX(), (int) splashTextArea.getY(), (int) splashTextArea.getX() + w, (int) splashTextArea.getY());
+ }
+
private void drawText(String str) {
splashGraphics.setPaint(Color.BLACK);
FontMetrics metrics = splashGraphics.getFontMetrics();
diff --git a/app/src/cc/arduino/view/preferences/Preferences.java b/app/src/cc/arduino/view/preferences/Preferences.java
index 005d2f83e54..d1f139616ef 100644
--- a/app/src/cc/arduino/view/preferences/Preferences.java
+++ b/app/src/cc/arduino/view/preferences/Preferences.java
@@ -135,6 +135,7 @@ private void initComponents() {
checkUpdatesBox = new javax.swing.JCheckBox();
saveVerifyUploadBox = new javax.swing.JCheckBox();
accessibleIDEBox = new javax.swing.JCheckBox();
+ checkRestoreSketch = new javax.swing.JCheckBox();
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
scaleSpinner = new javax.swing.JSpinner();
@@ -284,6 +285,9 @@ public void mouseEntered(java.awt.event.MouseEvent evt) {
accessibleIDEBox.setText(tr("Use accessibility features"));
checkboxesContainer.add(accessibleIDEBox);
+
+ checkRestoreSketch.setText(tr("Ask to restore sketches"));
+ checkboxesContainer.add(checkRestoreSketch);
jLabel1.setText(tr("Interface scale:"));
@@ -718,6 +722,7 @@ private void autoScaleCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//
private javax.swing.JButton browseButton;
private javax.swing.JCheckBox checkUpdatesBox;
private javax.swing.JCheckBox accessibleIDEBox;
+ private javax.swing.JCheckBox checkRestoreSketch;
private javax.swing.JPanel checkboxesContainer;
private javax.swing.JComboBox comboLanguage;
private javax.swing.JLabel comboLanguageLabel;
@@ -832,6 +837,10 @@ private void savePreferencesData() {
PreferencesData.setBoolean("update.check", checkUpdatesBox.isSelected());
PreferencesData.setBoolean("ide.accessible", accessibleIDEBox.isSelected());
+
+ if(checkRestoreSketch.isSelected()) {
+ PreferencesData.set("last.sketch.restore", "ask");
+ }
PreferencesData.set("boardsmanager.additional.urls", additionalBoardsManagerField.getText().replace("\r\n", "\n").replace("\r", "\n").replace("\n", ","));
@@ -912,6 +921,8 @@ private void showPreferencesData() {
}
accessibleIDEBox.setSelected(PreferencesData.getBoolean("ide.accessible"));
+
+ checkRestoreSketch.setSelected(PreferencesData.get("last.sketch.restore", "ask").equals("ask"));
saveVerifyUploadBox.setSelected(PreferencesData.getBoolean("editor.save_on_verify"));
diff --git a/app/src/log4j2.xml b/app/src/log4j2.xml
index 64f6b8f063f..9213b320841 100644
--- a/app/src/log4j2.xml
+++ b/app/src/log4j2.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 34193e07dbf..90e77739d4e 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -40,15 +40,29 @@
import cc.arduino.packages.DiscoveryManager;
import cc.arduino.packages.Uploader;
import cc.arduino.view.Event;
+import cc.arduino.view.JMenuLazy;
import cc.arduino.view.JMenuUtils;
import cc.arduino.view.SplashScreenHelper;
+
import com.github.zafarkhaja.semver.Version;
+import com.ricardojlrufino.eventbus.EventBus;
+import com.ricardojlrufino.eventbus.EventBusListener;
+import com.ricardojlrufino.eventbus.EventDispatcher;
+import com.ricardojlrufino.eventbus.EventHandler;
+import com.ricardojlrufino.eventbus.EventMessage;
+import com.ricardojlrufino.eventbus.dispatcher.DebounceEventDispatcher;
+import com.ricardojlrufino.eventbus.dispatcher.SingleThreadEventDispatcher;
+
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
import processing.app.debug.TargetBoard;
import processing.app.debug.TargetPackage;
import processing.app.debug.TargetPlatform;
import processing.app.helpers.*;
+import processing.app.helpers.filefilters.ExamplesFilter;
import processing.app.helpers.filefilters.OnlyDirs;
import processing.app.helpers.filefilters.OnlyFilesWithExtension;
import processing.app.javax.swing.filechooser.FileNameExtensionFilter;
@@ -68,10 +82,11 @@
import java.io.*;
import java.util.List;
import java.util.Timer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.*;
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;
@@ -86,11 +101,13 @@
* 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 Logger log;
public static Map FIND_DIALOG_STATE = new HashMap<>();
private final ContributionInstaller contributionInstaller;
@@ -124,6 +141,9 @@ public class Base {
private PdeKeywords pdeKeywords;
private final List recentSketchesMenuItems = new LinkedList<>();
+
+ // Executor to load / reload menus in backgroud.
+ private ExecutorService menuExecutor = Executors.newCachedThreadPool();
static public void main(String args[]) throws Exception {
if (!OSUtils.isWindows()) {
@@ -160,11 +180,17 @@ public static boolean isMacOsAboutMenuItemPresent() {
}
static public void initLogger() {
+
+ System.setProperty("log4j.dir", BaseNoGui.getSettingsFolder().getAbsolutePath());
+
+ log = LogManager.getLogger(Base.class); // init log4j
+
+ // JAVA UTIL LOGS ....
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);
+ java.util.logging.Logger globalLogger = java.util.logging.Logger.getLogger(java.util.logging.Logger.GLOBAL_LOGGER_NAME);
globalLogger.setLevel(consoleHandler.getLevel());
// Remove default
@@ -172,7 +198,7 @@ static public void initLogger() {
for(Handler handler : handlers) {
globalLogger.removeHandler(handler);
}
- Logger root = Logger.getLogger("");
+ java.util.logging.Logger root = java.util.logging.Logger.getLogger("");
handlers = root.getHandlers();
for(Handler handler : handlers) {
root.removeHandler(handler);
@@ -180,11 +206,104 @@ static public void initLogger() {
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);
+ java.util.logging.Logger.getLogger("cc.arduino.packages.autocomplete").setParent(globalLogger);
+ java.util.logging.Logger.getLogger("br.com.criativasoft.cpluslibparser").setParent(globalLogger);
+ java.util.logging.Logger.getLogger(Base.class.getPackage().getName()).setParent(globalLogger);
}
+
+ /**
+ * Configuration of internal events launched by the application.
+ * Only certain types of events will need a specific dispatcher, such as those that need a debounce.
+ * The other types of events do not need a dispatcher, they will use the standard dispatcher {@link SingleThreadEventDispatcher}
+ */
+ public void initEvents() {
+
+ // Avoids call same method multiple time...
+ EventBus.configDispatcher(UIEvents.MenuBoardSettingsChanged.class, new DebounceEventDispatcher(1000));
+ EventBus.configDispatcher(UIEvents.TriggerFixBoardOrPortChange.class, new DebounceEventDispatcher(200));
+
+ EventBus.addBusListener(new EventBusListener() {
+
+ @Override
+ public void onError(Exception e, E event, EventHandler handler) {
+
+ BaseNoGui.showWarning("Event Handler Exception", e.getMessage(), e);
+
+ }
+
+ /**
+ * Called before the event is "scheduled" to run. It is the responsibility of the dispatcher to accept or not the event.
+ * This method is executed on the same thread as the method that triggered the event, thus allowing tracking.
+ * NOTE: It may be that events here are not actually executed, as they can be ignored by the dispatcher
+ */
+ @Override
+ public void beforeDispatch(E event, EventHandler handler, EventDispatcher eventDispatcher) {
+
+// // Here you can enable to see which methods are launching the events.
+// if (event instanceof UIEvents.TriggerFixBoardOrPortChange) {
+// EventBusUtils.printStack(event, handler);
+// }
+ }
+
+ @Override
+ public void eventIgnored(E event, EventDispatcher eventDispatcher,String reason) {
+
+ log.debug("[IGNORED] Event: " + event + ", Reason: '"+reason );
+
+ if (UIEvents.BoardOrPortChange.class == event.getClass()) {
+ // EventBusUtils.printStack();
+ }
+
+ }
+
+ });
+
+ initEventsHandlers();
+ }
+
+ public void initEventsHandlers() {
+
+ // ==== MenuBoardSettingsChanged ====
+ // Custom menus from Board clicked.
+ // This method is also called at the first startup of the menu
+ EventBus.register(UIEvents.MenuBoardSettingsChanged.class, event -> {
+
+ onBoardOrPortChange();
+
+ });
+
+ // There are several places in the IDE that call onBoardOrPortChange indiscriminately, causing poor performance.
+ // This wind will serve to debounce the method.
+ EventBus.register(UIEvents.TriggerFixBoardOrPortChange.class, event -> {
+
+ _onBoardOrPortChange();
+
+ });
+
+
+ // Rebuild Menus.
+ EventBus.register(UIEvents.LibraryIndexUpdated.class, event -> {
+
+ Base.this.rebuildImportMenu(Editor.importMenu);
+ Base.this.rebuildExamplesMenu(Editor.examplesMenu);
+
+ });
+
+ // Update PdeKeywords
+ EventBus.register(UIEvents.LibraryIndexUpdated.class, event -> {
+
+ boolean reload = getPdeKeywords().reloadIfNeed();
+
+ if (reload) {
+ for (Editor editor : editors) {
+ editor.updateKeywords(getPdeKeywords());
+ }
+ }
+
+ });
+
+ }
static protected boolean isCommandLine() {
return commandLine;
@@ -205,8 +324,6 @@ public Base(String[] args) throws Exception {
BaseNoGui.initLogger();
- initLogger();
-
BaseNoGui.initPlatform();
BaseNoGui.getPlatform().init();
@@ -224,7 +341,8 @@ public Base(String[] args) throws Exception {
if (parser.isGuiMode()) {
System.out.println("Set log4j store directory " + BaseNoGui.getSettingsFolder().getAbsolutePath());
}
- System.setProperty("log4j.dir", BaseNoGui.getSettingsFolder().getAbsolutePath());
+
+ initLogger();
BaseNoGui.checkInstallationFolder();
@@ -249,6 +367,9 @@ public Base(String[] args) throws Exception {
// Setup the theme coloring fun
Theme.init();
System.setProperty("swing.aatext", PreferencesData.get("editor.antialias", "true"));
+
+ // Setup event system
+ initEvents();
// Set the look and feel before opening the window
try {
@@ -263,7 +384,7 @@ public Base(String[] args) throws Exception {
splash = new SplashScreenHelper(null);
}
- splash.splashText(tr("Loading configuration..."));
+ splash.splashText(tr("Loading configuration..."), 10);
BaseNoGui.initVersion();
@@ -274,12 +395,12 @@ public Base(String[] args) throws Exception {
untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp");
DeleteFilesOnShutdown.add(untitledFolder);
- splash.splashText(tr("Initializing packages..."));
+ splash.splashText(tr("Initializing packages..."), 20);
BaseNoGui.initPackages();
parser.getUploadPort().ifPresent(BaseNoGui::selectSerialPort);
- splash.splashText(tr("Preparing boards..."));
+ splash.splashText(tr("Preparing boards..."), 30);
if (!isCommandLine()) {
rebuildBoardsMenu();
@@ -293,9 +414,6 @@ public Base(String[] args) throws Exception {
// Setup board-dependent variables.
onBoardOrPortChange();
- pdeKeywords = new PdeKeywords();
- pdeKeywords.reload();
-
final GPGDetachedSignatureVerifier gpgDetachedSignatureVerifier = new GPGDetachedSignatureVerifier();
contributionInstaller = new ContributionInstaller(BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
libraryInstaller = new LibraryInstaller(BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
@@ -458,7 +576,7 @@ public Base(String[] args) throws Exception {
// No errors exit gracefully
System.exit(0);
} else if (parser.isGuiMode()) {
- splash.splashText(tr("Starting..."));
+ splash.splashText(tr("Starting..."), 80);
for (String path : parser.getFilenames()) {
// Correctly resolve relative paths
@@ -492,11 +610,8 @@ public Base(String[] args) throws Exception {
// 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();
- }
+
+ splash.setProgress(90); // last 10% is not predictable, but ide should open fast from here
new Thread(new BuiltInCoreIsNewerCheck(this)).start();
@@ -534,13 +649,27 @@ private void installKeyboardInputMap() {
*
* @throws Exception
*/
- protected boolean restoreSketches() throws Exception {
+ protected void restoreSketches() throws Exception {
// Iterate through all sketches that were open last time p5 was running.
// If !windowPositionValid, then ignore the coordinates found for each.
+
+ String restoreOption = PreferencesData.get("last.sketch.restore", "ask");
+ // Always New
+ if (restoreOption.equals("blank")) {
+ handleNew();
+ return;
+ }
+
// Save the sketch path and window placement for each open sketch
int count = PreferencesData.getInteger("last.sketch.count");
- int opened = 0;
+
+ if (count <= 0) {
+ handleNew();
+ return;
+ }
+
+ ArrayList options = new ArrayList<>();
for (int i = count - 1; i >= 0; i--) {
String path = PreferencesData.get("last.sketch" + i + ".path");
if (path == null) {
@@ -554,13 +683,84 @@ protected boolean restoreSketches() throws Exception {
// 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++;
- }
+
+ options.add(new File(path));
+ }
+
+ // Show dialog
+
+ JPanel restore = new JPanel();
+ restore.setLayout(new BorderLayout());
+ JPanel checkBoxPanel = new JPanel();
+ checkBoxPanel.setBorder(BorderFactory.createTitledBorder(tr("Select to restore")));
+ List checkboxList = new LinkedList<>();
+ for (File opt : options) {
+ JCheckBox box = new JCheckBox(opt.getName());
+ box.setActionCommand(opt.getAbsolutePath());
+ box.setSelected(true);
+ checkboxList.add(box);
+ checkBoxPanel.add(box);
+ }
+ restore.add(checkBoxPanel);
+ JCheckBox alwaysAskCB = new JCheckBox(tr("Always ask"));
+ restore.add(alwaysAskCB, BorderLayout.SOUTH);
+
+ int chosenOption;
+
+ if (restoreOption.equals("ask")) {
+ alwaysAskCB.setSelected(true);
+
+ // Allow set icon on JOptionPane
+ JFrame frame = new JFrame();
+ setIcon(frame);
+
+ chosenOption = JOptionPane.showConfirmDialog(frame, restore, tr("Restore Sketchs ?"), JOptionPane.YES_NO_OPTION);
+
+ }else { // restoreOption = restore
+ chosenOption = JOptionPane.YES_OPTION;
+ }
+
+ // Save preferences
+ if (alwaysAskCB.isSelected()) {
+ PreferencesData.set("last.sketch.restore", "ask");
+ }else {
+ if (chosenOption == JOptionPane.YES_OPTION)
+ PreferencesData.set("last.sketch.restore", "restore");
+ else {
+ PreferencesData.set("last.sketch.restore", "blank");
+ }
+ }
+
+ if (chosenOption == JOptionPane.CLOSED_OPTION) {
+ System.err.println("Exiting...");
+ System.exit(0);
+ }
+
+ if (chosenOption == JOptionPane.YES_OPTION) {
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+ try {
+ for (int j = 0; j < checkboxList.size(); j++) {
+ JCheckBox checkbox = checkboxList.get(j);
+ if (checkbox.isSelected()) {
+ int[] location = retrieveSketchLocation("" + j);
+ // If file did not exist, null will be returned for the Editor
+ handleOpen(new File(checkbox.getActionCommand()), location, nextEditorLocation(), false, false);
+ }
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ new Thread(runnable).start();
+
+ }else {
+ handleNew();
}
- return (opened > 0);
}
/**
@@ -761,7 +961,7 @@ protected File createNewUntitled() throws IOException {
int multiples = index / 26;
- if(multiples > 0){
+ if (multiples > 0){
newbieName = ((char) ('a' + (multiples-1))) + "" + ((char) ('a' + (index % 26))) + "";
}else{
newbieName = ((char) ('a' + index)) + "";
@@ -1044,14 +1244,10 @@ protected boolean handleQuitEach() {
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("starting rebuild");
+ rebuildSketchbookMenu(Editor.sketchbookMenu);
+ rebuildToolbarMenu(Editor.toolbarMenu);
+ //System.out.println("done with rebuild");
//System.out.println("async exit");
}
@@ -1073,29 +1269,46 @@ public void actionPerformed(ActionEvent e) {
});
menu.add(item);
menu.addSeparator();
+
+ // Execute in backgroud thread, no need UI thread becouse no rendering needed
+ menuExecutor.execute(() -> {
+ // 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();
+ });
- // 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) {
+ protected void rebuildSketchbookMenu(JMenuLazy menu) {
+
+ // Avoid call twice from "Editor.buildMenuBar"
+ if (menu.isLoading()) return;
+
+ menu.setLoading(true); // mark as not enabled
+
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);
- }
+
+ // Execute in backgroud thread, no need UI thread becouse no rendering needed
+ menuExecutor.submit(() -> {
+
+ 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);
+ }
+
+ SwingUtilities.invokeLater(() -> menu.setLoading(false));
+ });
+
}
private LibraryList getSortedLibraries() {
@@ -1122,10 +1335,7 @@ public void rebuildImportMenu(JMenu importMenu) {
addLibraryMenuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Base.this.handleAddLibrary();
- BaseNoGui.librariesIndexer.rescanLibraries();
- Base.this.onBoardOrPortChange();
- Base.this.rebuildImportMenu(Editor.importMenu);
- Base.this.rebuildExamplesMenu(Editor.examplesMenu);
+ Base.this.rescanLibraries();
}
});
importMenu.add(addLibraryMenuItem);
@@ -1169,201 +1379,241 @@ public void actionPerformed(ActionEvent event) {
}
}
- public void rebuildExamplesMenu(JMenu menu) {
+ /**
+ * Rescan libraries in backgroud.
+ * Fire: {@link LibraryIndexUpdated}
+ */
+ protected void rescanLibraries() {
+
+ menuExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ BaseNoGui.librariesIndexer.rescanLibraries();
+ EventBus.notify(new UIEvents.LibraryIndexUpdated());
+ }
+ });
+
+ }
+
+ public void rebuildExamplesMenu(JMenuLazy menu) {
if (menu == null) {
return;
}
-
+
+ // Avoid call twice from "Editor.buildMenuBar"
+ if (menu.isLoading()) return;
+
+ menu.setLoading(true);
+
menu.removeAll();
+
+ // Execute in backgroud thread, no need UI thread becouse no rendering needed
+ menuExecutor.execute(() -> {
+
+ // 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();
+ }
- // 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
- String boardId = null;
- String referencedPlatformName = null;
- String myArch = null;
- TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
- if (targetPlatform != null) {
- myArch = targetPlatform.getId();
- boardId = BaseNoGui.getTargetBoard().getName();
- 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");
+ // Libraries can come from 4 locations: collect info about all four
+ String boardId = null;
+ String referencedPlatformName = null;
+ String myArch = null;
+ TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
+ if (targetPlatform != null) {
+ myArch = targetPlatform.getId();
+ boardId = BaseNoGui.getTargetBoard().getName();
+ 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");
+ }
+ }
}
- }
- }
- // 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
- Location location = lib.getLocation();
- // Is this library compatible?
- List arch = lib.getArchitectures();
- boolean compatible;
- if (myArch == null || arch == null || arch.contains("*")) {
- compatible = true;
- } else {
- compatible = arch.contains(myArch);
- }
- // IDE Libaries (including retired)
- if (location == Location.IDE_BUILTIN) {
- if (compatible) {
- // only compatible IDE libs are shown
- if (lib.getTypes().contains("Retired")) {
- retiredIdeLibs.add(lib);
+ // 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
+ Location location = lib.getLocation();
+ // Is this library compatible?
+ List arch = lib.getArchitectures();
+ boolean compatible;
+ if (myArch == null || arch == null || arch.contains("*")) {
+ compatible = true;
} else {
- ideLibs.add(lib);
+ compatible = arch.contains(myArch);
}
- }
- // Platform Libraries
- } else if (location == Location.CORE) {
- // all platform libs are assumed to be compatible
- platformLibs.add(lib);
- // Referenced Platform Libraries
- } else if (location == Location.REFERENCED_CORE) {
- // all referenced platform libs are assumed to be compatible
- referencedPlatformLibs.add(lib);
- // Sketchbook Libraries (including incompatible)
- } else if (location == Location.SKETCHBOOK) {
- if (compatible) {
- // libraries promoted from sketchbook (behave as builtin)
- if (!lib.getTypes().isEmpty() && lib.getTypes().contains("Arduino")
- && lib.getArchitectures().contains("*")) {
- ideLibs.add(lib);
+ // IDE Libaries (including retired)
+ if (location == Location.IDE_BUILTIN) {
+ if (compatible) {
+ // only compatible IDE libs are shown
+ if (lib.getTypes().contains("Retired")) {
+ retiredIdeLibs.add(lib);
+ } else {
+ ideLibs.add(lib);
+ }
+ }
+ // Platform Libraries
+ } else if (location == Location.CORE) {
+ // all platform libs are assumed to be compatible
+ platformLibs.add(lib);
+ // Referenced Platform Libraries
+ } else if (location == Location.REFERENCED_CORE) {
+ // all referenced platform libs are assumed to be compatible
+ referencedPlatformLibs.add(lib);
+ // Sketchbook Libraries (including incompatible)
+ } else if (location == Location.SKETCHBOOK) {
+ if (compatible) {
+ // libraries promoted from sketchbook (behave as builtin)
+ if (!lib.getTypes().isEmpty() && 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 {
- sketchbookLibs.add(lib);
+ otherLibs.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);
- }
+ // 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 (!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(format(tr("Examples for {0}"), boardId));
- label.setEnabled(false);
- menu.add(label);
- for (UserLibrary lib : platformLibs) {
- addSketchesSubmenu(menu, lib);
- }
- }
+ if (!platformLibs.isEmpty()) {
+ menu.addSeparator();
+ platformLibs.sort();
+ label = new JMenuItem(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(format(tr("Examples for {0}"), referencedPlatformName));
- label.setEnabled(false);
- menu.add(label);
- for (UserLibrary lib : referencedPlatformLibs) {
- addSketchesSubmenu(menu, lib);
- }
- }
+ if (!referencedPlatformLibs.isEmpty()) {
+ menu.addSeparator();
+ referencedPlatformLibs.sort();
+ label = new JMenuItem(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 (!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"));
- MenuScroller.setScrollerFor(incompatible);
- menu.add(incompatible);
- for (UserLibrary lib : sketchbookIncompatibleLibs) {
- addSketchesSubmenu(incompatible, lib);
- }
- }
+ if (!sketchbookIncompatibleLibs.isEmpty()) {
+ sketchbookIncompatibleLibs.sort();
+ JMenu incompatible = new JMenu(tr("INCOMPATIBLE"));
+ MenuScroller.setScrollerFor(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);
- }
- }
+ 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);
+ }
+ }
+
+ SwingUtilities.invokeLater(() -> {
+ menu.setLoading(false);
+ });
+ });
+
}
private static String priorPlatformFolder;
- private static boolean newLibraryImported;
-
+
+ /**
+ * Trigger: {@link UIEvents.LibraryIndexUpdated} and {@link UIEvents.BoardOrPortChange}
+ */
public void onBoardOrPortChange() {
- BaseNoGui.onBoardOrPortChange();
+ EventBus.notify(new UIEvents.TriggerFixBoardOrPortChange());
+ }
+ // this will be called from: UIEvents.TriggerFixBoardOrPortChange
+ private void _onBoardOrPortChange() {
+
// reload keywords when package/platform changes
TargetPlatform tp = BaseNoGui.getTargetPlatform();
+
+ log.debug("BoardOrPortChange : board = {}" , tp.getId());
+
if (tp != null) {
String platformFolder = tp.getFolder().getAbsolutePath();
- if (priorPlatformFolder == null || !priorPlatformFolder.equals(platformFolder) || newLibraryImported) {
- pdeKeywords = new PdeKeywords();
- pdeKeywords.reload();
+ if (priorPlatformFolder == null || !priorPlatformFolder.equals(platformFolder)) {
priorPlatformFolder = platformFolder;
- newLibraryImported = false;
- for (Editor editor : editors) {
- editor.updateKeywords(pdeKeywords);
- }
+ getPdeKeywords().setNeedReload(true);
}
}
-
- // Update editors status bar
- for (Editor editor : editors) {
- editor.onBoardOrPortChange();
- }
+
+ // TODO: Here it would be better to load it in the background.
+ // For some listeners are only interested in changing the board, not in libraries.
+ BaseNoGui.onBoardOrPortChange();
+
+ // Notify interested parties such as: rebuildExamplesMenu | PdeKeywords.reload()
+ EventBus.notify(new UIEvents.LibraryIndexUpdated());
+
+ // NOTE Before events: 'for' in editors[i].onBoardOrPortChange();
+ EventBus.notify(new UIEvents.BoardOrPortChange());
+
}
public void openLibraryManager(final String filterText, String dropdownItem) {
@@ -1393,10 +1643,8 @@ protected void onIndexesUpdated() throws Exception {
// Manager dialog is modal, waits here until closed
//handleAddLibrary();
- newLibraryImported = true;
+ getPdeKeywords().setNeedReload(true);
onBoardOrPortChange();
- rebuildImportMenu(Editor.importMenu);
- rebuildExamplesMenu(Editor.examplesMenu);
}
public void openBoardsManager(final String filterText, String dropdownItem) throws Exception {
@@ -1434,121 +1682,132 @@ protected void onIndexesUpdated() throws Exception {
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) {
- Event e = ((Event) actionevent);
- filterText = e.getPayload().get("filterText").toString();
- dropdownItem = e.getPayload().get("dropdownItem").toString();
- }
- try {
- openBoardsManager(filterText, dropdownItem);
- } catch (Exception e) {
- //TODO show error
- e.printStackTrace();
- }
- }
- }));
- boardsCustomMenus.add(boardMenu);
+ // Execute in backgroud thread, no need UI thread because no rendering needed
+ menuExecutor.execute(() -> {
+ // 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) {
+ Event e = ((Event) actionevent);
+ filterText = e.getPayload().get("filterText").toString();
+ dropdownItem = e.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;
+ // 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
- for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
- for (TargetPlatform targetPlatform : targetPackage.platforms()) {
- for (String customMenuTitle : targetPlatform.getCustomMenus().values()) {
- JMenu customMenu = new JMenu(tr(customMenuTitle));
- customMenu.putClientProperty("platform", getPlatformUniqueId(targetPlatform));
- customMenu.putClientProperty("removeOnWindowDeactivation", true);
- boardsCustomMenus.add(customMenu);
+ // Separate "Install boards..." command from installed boards
+ boardMenu.add(new JSeparator());
+
+ // Generate custom menus for all platforms
+ for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
+ for (TargetPlatform targetPlatform : targetPackage.platforms()) {
+ for (String customMenuTitle : targetPlatform.getCustomMenus().values()) {
+ JMenu customMenu = new JMenu(tr(customMenuTitle));
+ customMenu.putClientProperty("platform", getPlatformUniqueId(targetPlatform));
+ customMenu.putClientProperty("removeOnWindowDeactivation", true);
+ boardsCustomMenus.add(customMenu);
+ }
+ }
}
- }
- }
-
- List menuItemsToClickAfterStartup = new LinkedList<>();
-
- ButtonGroup boardsButtonGroup = new ButtonGroup();
- Map buttonGroupsMap = new HashMap<>();
- List platformMenus = new ArrayList<>();
-
- // Cycle through all packages
- for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
- // For every package cycle through all platform
- for (TargetPlatform targetPlatform : targetPackage.platforms()) {
-
- // Add a title for each platform
- String platformLabel = targetPlatform.getPreferences().get("name");
- if (platformLabel == null)
- platformLabel = targetPackage.getId() + "-" + targetPlatform.getId();
-
- // add an hint that this core lives in sketchbook
- if (targetPlatform.isInSketchbook())
- platformLabel += " (in sketchbook)";
-
- JMenu platformBoardsMenu = new JMenu(platformLabel);
- MenuScroller.setScrollerFor(platformBoardsMenu);
- platformMenus.add(platformBoardsMenu);
-
- // 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);
- platformBoardsMenu.add(item);
- boardsButtonGroup.add(item);
+ List menuItemsToClickAfterStartup = new LinkedList<>();
+
+ ButtonGroup boardsButtonGroup = new ButtonGroup();
+ Map buttonGroupsMap = new HashMap<>();
+
+ List platformMenus = new ArrayList<>();
+
+ // Cycle through all packages
+ for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
+ // For every package cycle through all platform
+ for (TargetPlatform targetPlatform : targetPackage.platforms()) {
+
+ // Add a title for each platform
+ String platformLabel = targetPlatform.getPreferences().get("name");
+ if (platformLabel == null)
+ platformLabel = targetPackage.getId() + "-" + targetPlatform.getId();
+
+ // add an hint that this core lives in sketchbook
+ if (targetPlatform.isInSketchbook())
+ platformLabel += " (in sketchbook)";
+
+ JMenu platformBoardsMenu = new JMenu(platformLabel);
+ MenuScroller.setScrollerFor(platformBoardsMenu);
+ platformMenus.add(platformBoardsMenu);
+
+ // Cycle through all boards of this platform
+ for (TargetBoard board : targetPlatform.getBoards().values()) {
+ if (board.getPreferences().get("hide") != null)
+ continue;
+
+ try {
+ JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup,
+ buttonGroupsMap,
+ board, targetPlatform, targetPackage);
+ platformBoardsMenu.add(item);
+ boardsButtonGroup.add(item);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
}
- }
- }
- platformMenus.sort((x,y) -> x.getText().compareToIgnoreCase(y.getText()));
+ platformMenus.sort((x,y) -> x.getText().compareToIgnoreCase(y.getText()));
- JMenuItem firstBoardItem = null;
- if (platformMenus.size() == 1) {
- // When just one platform exists, add the board items directly,
- // rather than using a submenu
- for (Component boardItem : platformMenus.get(0).getMenuComponents()) {
- boardMenu.add(boardItem);
- if (firstBoardItem == null)
- firstBoardItem = (JMenuItem)boardItem;
- }
- } else {
- // For multiple platforms, use submenus
- for (JMenu platformMenu : platformMenus) {
- if (firstBoardItem == null && platformMenu.getItemCount() > 0)
- firstBoardItem = platformMenu.getItem(0);
- boardMenu.add(platformMenu);
- }
- }
+ JMenuItem firstBoardItem = null;
+ if (platformMenus.size() == 1) {
+ // When just one platform exists, add the board items directly,
+ // rather than using a submenu
+ for (Component boardItem : platformMenus.get(0).getMenuComponents()) {
+ boardMenu.add(boardItem);
+ if (firstBoardItem == null)
+ firstBoardItem = (JMenuItem)boardItem;
+ }
+ } else {
+ // For multiple platforms, use submenus
+ for (JMenu platformMenu : platformMenus) {
+ if (firstBoardItem == null && platformMenu.getItemCount() > 0)
+ firstBoardItem = platformMenu.getItem(0);
+ boardMenu.add(platformMenu);
+ }
+ }
- if (firstBoardItem == null) {
- throw new IllegalStateException("No available boards");
- }
+ if (firstBoardItem == null) {
+ throw new IllegalStateException("No available boards");
+ }
- // If there is no current board yet (first startup, or selected
- // board no longer defined), select first available board.
- if (menuItemsToClickAfterStartup.isEmpty()) {
- menuItemsToClickAfterStartup.add(firstBoardItem);
- }
+ // If there is no current board yet (first startup, or selected
+ // board no longer defined), select first available board.
+ if (menuItemsToClickAfterStartup.isEmpty()) {
+ menuItemsToClickAfterStartup.add(firstBoardItem);
+ }
- for (JMenuItem menuItemToClick : menuItemsToClickAfterStartup) {
- menuItemToClick.setSelected(true);
- menuItemToClick.getAction().actionPerformed(new ActionEvent(this, -1, ""));
- }
+ for (JMenuItem menuItemToClick : menuItemsToClickAfterStartup) {
+ menuItemToClick.setSelected(true);
+ menuItemToClick.getAction().actionPerformed(new ActionEvent(this, -1, ""));
+ }
+
+ });
+
}
private String getPlatformUniqueId(TargetPlatform platform) {
@@ -1572,12 +1831,12 @@ private JRadioButtonMenuItem createBoardMenusAndCustomMenus(
@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);
rebuildProgrammerMenu();
}
};
@@ -1601,8 +1860,11 @@ public void actionPerformed(ActionEvent actionevent) {
@SuppressWarnings("serial")
Action subAction = new AbstractAction(tr(boardCustomMenu.get(customMenuOption))) {
public void actionPerformed(ActionEvent e) {
+
PreferencesData.set("custom_" + menuId, ((List) getValue("board")).get(0).getId() + "_" + getValue("custom_menu_option"));
- onBoardOrPortChange();
+
+ EventBus.notify(new UIEvents.MenuBoardSettingsChanged(((List) getValue("board")).get(0), (String) getValue("custom_menu_option")));
+
}
};
List boards = (List) subAction.getValue("board");
@@ -1631,7 +1893,7 @@ public void actionPerformed(ActionEvent e) {
return item;
}
-
+
private void filterVisibilityOfSubsequentBoardMenus(List boardsCustomMenus, TargetBoard board,
int fromIndex) {
for (int i = fromIndex; i < boardsCustomMenus.size(); i++) {
@@ -1768,7 +2030,7 @@ public int compare(File file, File file2) {
boolean ifound = false;
for (File subfolder : files) {
- if (!FileUtils.isSCCSOrHiddenFile(subfolder) && subfolder.isDirectory()
+ if (subfolder.isDirectory() && !FileUtils.isSCCSOrHiddenFile(subfolder)
&& addSketchesSubmenu(menu, subfolder.getName(), subfolder)) {
ifound = true;
}
@@ -1777,7 +2039,22 @@ && addSketchesSubmenu(menu, subfolder.getName(), subfolder)) {
}
private boolean addSketchesSubmenu(JMenu menu, UserLibrary lib) {
- return addSketchesSubmenu(menu, lib.getName(), lib.getInstalledFolder());
+
+ JMenu submenu = new JMenu(lib.getName());
+
+ // Compatibility mode: not all community libraries are following the specification, look for common names found.
+ File[] list = lib.getInstalledFolder().listFiles(new ExamplesFilter());
+
+ // By spec only exist 1 Examples folder, on top level.
+ if (list.length > 0) {
+ if (addSketches(submenu, list[0])) {
+ menu.add(submenu);
+ MenuScroller.setScrollerFor(submenu);
+ return true;
+ }
+ }
+
+ return false;
}
private boolean addSketchesSubmenu(JMenu menu, String name, File folder) {
@@ -1844,34 +2121,6 @@ public void actionPerformed(ActionEvent e) {
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"), 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
@@ -2132,7 +2381,7 @@ static public File selectFolder(String prompt, File folder, Component parent) {
/**
* Give this Frame an icon.
*/
- static public void setIcon(Frame frame) {
+ static public void setIcon(Window frame) {
if (OSUtils.isMacOS()) {
return;
}
@@ -2177,7 +2426,7 @@ static public void showReference(String prefix, String filename) {
if (!referenceFile.exists())
referenceFile = new File(referenceFolder, filename + ".html");
- if(referenceFile.exists()){
+ if (referenceFile.exists()){
openURL(referenceFile.getAbsolutePath());
}else{
showWarning(tr("Problem Opening URL"), format(tr("Could not open the URL\n{0}"), referenceFile), null);
@@ -2474,7 +2723,7 @@ public void handleAddLibrary() {
// FIXME error when importing. ignoring :(
} finally {
// delete zip created temp folder, if exists
- newLibraryImported = true;
+ getPdeKeywords().setNeedReload(true);
FileUtils.recursiveDelete(tmpFolder);
}
}
@@ -2486,6 +2735,19 @@ public static DiscoveryManager getDiscoveryManager() {
public Editor getActiveEditor() {
return activeEditor;
}
+
+ public Editor getWaitActiveEditor() {
+ while (getActiveEditor() == null) {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return getActiveEditor();
+ }
+
+
public boolean hasActiveEditor() {
return activeEditor != null;
@@ -2496,6 +2758,7 @@ public List getEditors() {
}
public PdeKeywords getPdeKeywords() {
+ if (pdeKeywords == null) pdeKeywords = new PdeKeywords() ; // make method safe, while load in backgroud..
return pdeKeywords;
}
diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java
index 2ec29c498cb..2f84b4fd240 100644
--- a/app/src/processing/app/Editor.java
+++ b/app/src/processing/app/Editor.java
@@ -80,12 +80,11 @@
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.text.BadLocationException;
-import javax.swing.text.Document;
-import javax.swing.text.Element;
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
import com.jcraft.jsch.JSchException;
+import com.ricardojlrufino.eventbus.EventBus;
import cc.arduino.CompilerProgressListener;
import cc.arduino.packages.BoardPort;
@@ -93,6 +92,7 @@
import cc.arduino.packages.Uploader;
import cc.arduino.packages.uploaders.SerialUploader;
import cc.arduino.view.GoToLineNumber;
+import cc.arduino.view.JMenuLazy;
import cc.arduino.view.StubMenuListener;
import cc.arduino.view.findreplace.FindReplace;
import jssc.SerialPortException;
@@ -104,6 +104,7 @@
import processing.app.helpers.OSUtils;
import processing.app.helpers.PreferencesMapException;
import processing.app.helpers.StringReplacer;
+import processing.app.helpers.UIEvents;
import processing.app.legacy.PApplet;
import processing.app.syntax.PdeKeywords;
import processing.app.syntax.SketchTextArea;
@@ -189,8 +190,8 @@ public boolean test(SketchController controller) {
// these menus are shared so that they needn't be rebuilt for all windows
// each time a sketch is created, renamed, or moved.
static JMenu toolbarMenu;
- static JMenu sketchbookMenu;
- static JMenu examplesMenu;
+ static JMenuLazy sketchbookMenu;
+ static JMenuLazy examplesMenu;
static JMenu importMenu;
private static JMenu portMenu;
@@ -381,6 +382,25 @@ public void windowDeactivated(WindowEvent e) {
// default the console output to the last opened editor
EditorConsole.setCurrentEditorConsole(console);
+
+ // Init EventBus handlers
+ registerEventBus();
+ }
+
+
+ private void registerEventBus() {
+
+ EventBus.register(this, UIEvents.BoardOrPortChange.class, event -> {
+ onBoardOrPortChange();
+ });
+
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ EventBus.unregisterHandlers(this);
+ EventBus.notify(new UIEvents.EditorClosed());
}
@@ -600,16 +620,15 @@ private JMenu buildFileMenu() {
fileMenu.add(recentSketchesMenu);
if (sketchbookMenu == null) {
- sketchbookMenu = new JMenu(tr("Sketchbook"));
+ sketchbookMenu = new JMenuLazy(tr("Sketchbook"));
MenuScroller.setScrollerFor(sketchbookMenu);
base.rebuildSketchbookMenu(sketchbookMenu);
}
fileMenu.add(sketchbookMenu);
if (examplesMenu == null) {
- examplesMenu = new JMenu(tr("Examples"));
+ examplesMenu = new JMenuLazy(tr("Examples"));
MenuScroller.setScrollerFor(examplesMenu);
- base.rebuildExamplesMenu(examplesMenu);
}
fileMenu.add(examplesMenu);
@@ -1003,7 +1022,7 @@ private void addInternalTools(JMenu menu) {
private void selectSerialPort(String name) {
- if(portMenu == null) {
+ if (portMenu == null) {
System.out.println(tr("serialMenu is null"));
return;
}
@@ -2166,8 +2185,8 @@ public void run() {
}
public void handleSerial() {
- if(serialPlotter != null) {
- if(serialPlotter.isClosed()) {
+ if (serialPlotter != null) {
+ if (serialPlotter.isClosed()) {
serialPlotter = null;
} else {
statusError(tr("Serial monitor not available while plotter is open"));
@@ -2276,8 +2295,8 @@ public void handleSerial() {
}
public void handlePlotter() {
- if(serialMonitor != null) {
- if(serialMonitor.isClosed()) {
+ if (serialMonitor != null) {
+ if (serialMonitor.isClosed()) {
serialMonitor = null;
} else {
statusError(tr("Plotter not available while serial monitor is open"));
diff --git a/app/src/processing/app/NewBoardListener.java b/app/src/processing/app/NewBoardListener.java
index 7e0fe61d708..79938b5b92f 100644
--- a/app/src/processing/app/NewBoardListener.java
+++ b/app/src/processing/app/NewBoardListener.java
@@ -55,13 +55,7 @@ public void propertyChange(PropertyChangeEvent event) {
@Override
public void run() {
- while (base.getActiveEditor() == null) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
+ base.getWaitActiveEditor();
BaseNoGui.addPropertyChangeListener(this);
checkForNewBoardAttached();
}
diff --git a/app/src/processing/app/helpers/UIEvents.java b/app/src/processing/app/helpers/UIEvents.java
new file mode 100755
index 00000000000..6f6e234cfec
--- /dev/null
+++ b/app/src/processing/app/helpers/UIEvents.java
@@ -0,0 +1,73 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ PdeKeywords - handles text coloring and links to html reference
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2004-06 Ben Fry and Casey Reas
+ Copyright (c) 2001-04 Massachusetts Institute of Technology
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.app.helpers;
+
+import com.ricardojlrufino.eventbus.EventMessage;
+
+import processing.app.debug.TargetBoard;
+
+/**
+ * Events for UI Interface.
+ *
+ * @author Ricardo JL Rufino - (ricardo.jl.rufino@gmail.com)
+ * @date 21 de mai de 2020
+ */
+public interface UIEvents {
+
+ public static class BoardOrPortChange implements EventMessage { }
+
+ public static class LibraryIndexUpdated implements EventMessage {}
+
+ public static class PlatformChage implements EventMessage {}
+
+ public static class EditorClosed implements EventMessage {}
+
+ /**
+ * Please do not use this event. It should only be used internally by the
+ * Base.onBoardOrPortChange method. Use: {@link BoardOrPortChange}
+ **/
+ public static class TriggerFixBoardOrPortChange implements EventMessage {
+ }
+
+ public static class MenuBoardSettingsChanged implements EventMessage {
+ private String custosOption;
+ private TargetBoard board;
+
+ public MenuBoardSettingsChanged(TargetBoard board, String custosOption) {
+ this.board = board;
+ this.custosOption = custosOption;
+ }
+
+ public TargetBoard getBoard() {
+ return board;
+ }
+
+ public String getCustosOption() {
+ return custosOption;
+ }
+
+ }
+
+}
diff --git a/app/src/processing/app/syntax/PdeKeywords.java b/app/src/processing/app/syntax/PdeKeywords.java
index 838800b3d5d..4fa394af067 100644
--- a/app/src/processing/app/syntax/PdeKeywords.java
+++ b/app/src/processing/app/syntax/PdeKeywords.java
@@ -45,6 +45,8 @@ public class PdeKeywords {
private static final Map KNOWN_TOKEN_TYPES = new HashMap<>();
private static final Pattern ALPHA = Pattern.compile("\\w");
+
+ private boolean needReload = true;
static {
KNOWN_TOKEN_TYPES.put("RESERVED_WORD", TokenTypes.RESERVED_WORD);
@@ -99,6 +101,23 @@ public void reload() {
Base.showError("Problem loading keywords", "Could not load keywords.txt,\nplease re-install Arduino.", e);
System.exit(1);
}
+
+ needReload = false;
+ }
+
+ public boolean reloadIfNeed() {
+
+ if (needReload) {
+ reload();
+ return true;
+ }else {
+ return false;
+ }
+
+ }
+
+ public void setNeedReload(boolean needReload) {
+ this.needReload = needReload;
}
private void parseKeywordsTxt(File input) throws Exception {
@@ -129,6 +148,7 @@ private void parseKeywordsTxt(File input) throws Exception {
if (pieces.length >= 3) {
parseHTMLReferenceFileName(pieces[2], keyword);
}
+
if (pieces.length >= 4) {
parseRSyntaxTextAreaTokenType(pieces[3], keyword);
}
@@ -173,9 +193,11 @@ private void parseRSyntaxTextAreaTokenType(String tokenTypeAsString, String keyw
}
private void parseHTMLReferenceFileName(String piece, String keyword) {
- String htmlFilename = piece.trim();
- if (htmlFilename.length() > 0) {
- keywordToReference.put(keyword, htmlFilename);
+ if (piece != null && keyword != null && !piece.isEmpty()) {
+ String htmlFilename = piece.trim();
+ if (htmlFilename.length() > 0) {
+ keywordToReference.put(keyword, htmlFilename);
+ }
}
}
diff --git a/app/src/processing/app/tools/MenuScroller.java b/app/src/processing/app/tools/MenuScroller.java
index 9e9aacbcafd..b403f12a356 100644
--- a/app/src/processing/app/tools/MenuScroller.java
+++ b/app/src/processing/app/tools/MenuScroller.java
@@ -289,7 +289,7 @@ public MenuScroller(JPopupMenu menu, int scrollCount, int interval,
upItem = new MenuScrollItem(MenuIcon.UP, -1);
downItem = new MenuScrollItem(MenuIcon.DOWN, +1);
- setScrollCount(scrollCount);
+ this.scrollCount = scrollCount;
setInterval(interval);
setTopFixedCount(topFixedCount);
setBottomFixedCount(bottomFixedCount);
diff --git a/app/test/cc/arduino/contributions/UpdatableLibraryTest.java b/app/test/cc/arduino/contributions/UpdatableLibraryTest.java
index e06c12710bf..0dab3531cd1 100644
--- a/app/test/cc/arduino/contributions/UpdatableLibraryTest.java
+++ b/app/test/cc/arduino/contributions/UpdatableLibraryTest.java
@@ -36,8 +36,7 @@ public void testUpdatableLibrary() throws Exception {
LibrariesIndexer indexer = new LibrariesIndexer(index_SD_only);
BaseNoGui.librariesIndexer = indexer;
indexer.parseIndex();
- indexer.setLibrariesFolders(folders);
- indexer.rescanLibraries();
+ indexer.setLibrariesFoldersAndRescan(folders);
ContributedLibrary sdLib = indexer.getIndex().getInstalled("SD").get();
assertTrue("SD lib is installed", sdLib.isLibraryInstalled());
@@ -46,7 +45,7 @@ public void testUpdatableLibrary() throws Exception {
assertTrue(ContributionsSelfCheck.checkForUpdatableLibraries());
folders.add(new UserLibraryFolder(SD121, Location.SKETCHBOOK));
- indexer.setLibrariesFolders(folders);
+ indexer.setLibrariesFoldersAndRescan(folders);
sdLib = indexer.getIndex().getInstalled("SD").get();
assertTrue("SD lib is installed", sdLib.isLibraryInstalled());
@@ -63,8 +62,7 @@ public void testUpdatableLibraryWithBundled() throws Exception {
LibrariesIndexer indexer = new LibrariesIndexer(index_Bridge_only);
BaseNoGui.librariesIndexer = indexer;
indexer.parseIndex();
- indexer.setLibrariesFolders(folders);
- indexer.rescanLibraries();
+ indexer.setLibrariesFoldersAndRescan(folders);
ContributedLibrary l = indexer.getIndex().getInstalled("Bridge").get();
assertTrue("Bridge lib is installed", l.isLibraryInstalled());
@@ -73,7 +71,7 @@ public void testUpdatableLibraryWithBundled() throws Exception {
assertTrue(ContributionsSelfCheck.checkForUpdatableLibraries());
folders.add(new UserLibraryFolder(Bridge170, Location.SKETCHBOOK));
- indexer.setLibrariesFolders(folders);
+ indexer.setLibrariesFoldersAndRescan(folders);
l = indexer.getIndex().getInstalled("Bridge").get();
assertTrue("Bridge lib is installed", l.isLibraryInstalled());
diff --git a/arduino-core/src/cc/arduino/contributions/libraries/LibrariesIndexer.java b/arduino-core/src/cc/arduino/contributions/libraries/LibrariesIndexer.java
index fb0b0c76abe..20169af056d 100644
--- a/arduino-core/src/cc/arduino/contributions/libraries/LibrariesIndexer.java
+++ b/arduino-core/src/cc/arduino/contributions/libraries/LibrariesIndexer.java
@@ -113,9 +113,13 @@ private void parseIndex(File file) throws IOException {
}
}
- public void setLibrariesFolders(List folders) {
- librariesFolders = folders;
- rescanLibraries();
+ public void setLibrariesFolders( List folders ) {
+ this.librariesFolders = folders;
+ }
+
+ public void setLibrariesFoldersAndRescan( List folders ) {
+ setLibrariesFolders(folders);
+ rescanLibraries();
}
public List getLibrariesFolders() {
@@ -142,7 +146,7 @@ public void setArchitecturePriority(String arch) {
priorityComparator = new UserLibraryPriorityComparator(arch);
}
- public void rescanLibraries() {
+ public synchronized void rescanLibraries() {
// Clear all installed flags
installedLibraries.clear();
diff --git a/arduino-core/src/processing/app/BaseNoGui.java b/arduino-core/src/processing/app/BaseNoGui.java
index c47a82d69b8..4cf07691bb1 100644
--- a/arduino-core/src/processing/app/BaseNoGui.java
+++ b/arduino-core/src/processing/app/BaseNoGui.java
@@ -678,6 +678,7 @@ static public void onBoardOrPortChange() {
if (getTargetPlatform() != null) {
librariesIndexer.setArchitecturePriority(getTargetPlatform().getId());
}
+
librariesIndexer.rescanLibraries();
populateImportToLibraryTable();
diff --git a/arduino-core/src/processing/app/helpers/filefilters/ExamplesFilter.java b/arduino-core/src/processing/app/helpers/filefilters/ExamplesFilter.java
new file mode 100755
index 00000000000..8bf90e699c6
--- /dev/null
+++ b/arduino-core/src/processing/app/helpers/filefilters/ExamplesFilter.java
@@ -0,0 +1,16 @@
+package processing.app.helpers.filefilters;
+
+import java.io.File;
+
+public class ExamplesFilter extends OnlyDirs {
+
+
+ @Override
+ public boolean accept( File dir , String name ) {
+
+ if (!super.accept(dir, name)) return false;
+
+ return name.equalsIgnoreCase("example") || name.equalsIgnoreCase("examples") ;
+ }
+
+}