From 4e977273554be45b50d0554a5bc2aaea5acb9d78 Mon Sep 17 00:00:00 2001
From: Matthijs Kooijman <matthijs@stdin.nl>
Date: Thu, 19 Sep 2019 19:31:58 +0200
Subject: [PATCH 1/6] Handle nested submenus of the Tools menu

There is some code that, for each submenu under Tools, shows the
selected item in the label of the submenu itself (e.g. before opening
the submenu). This was done whenever the Tools menu is opened and
iterated over all the items in the submenu to identify the s

Previously, this code only looked at direct children of the submenu.
Now, this code also looks through submenus recursively, to keep the code
working even when items are divided over sub-submenus.

This makes a small behaviour change: previously, the first selected item
with a non-zero label was used. Now, the first selected item is used,
which makes the code a bit cleaner. I cannot quickly see any case where
the first selected item would have an empty text (and even more that
there is *another* selected item), so this check seems unnecessary. If
this case would occur nonetheless, it would only mean the selected item
is not displayed in the tools menu, nothing would otherwise break.
---
 app/src/processing/app/Editor.java | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java
index f0c7c19248c..2ec29c498cb 100644
--- a/app/src/processing/app/Editor.java
+++ b/app/src/processing/app/Editor.java
@@ -761,6 +761,20 @@ private JMenu buildToolsMenu() {
     toolsMenu.add(item);
 
     toolsMenu.addMenuListener(new StubMenuListener() {
+      public JMenuItem getSelectedItemRecursive(JMenu menu) {
+        int count = menu.getItemCount();
+        for (int i=0; i < count; i++) {
+          JMenuItem item = menu.getItem(i);
+
+          if ((item instanceof JMenu))
+            item = getSelectedItemRecursive((JMenu)item);
+
+          if (item != null && item.isSelected())
+            return item;
+        }
+        return null;
+      }
+
       public void menuSelected(MenuEvent e) {
         //System.out.println("Tools menu selected.");
         populatePortMenu();
@@ -772,15 +786,9 @@ public void menuSelected(MenuEvent e) {
             String basename = name;
             int index = name.indexOf(':');
             if (index > 0) basename = name.substring(0, index);
-            String sel = null;
-            int count = menu.getItemCount();
-            for (int i=0; i < count; i++) {
-              JMenuItem item = menu.getItem(i);
-              if (item != null && item.isSelected()) {
-                sel = item.getText();
-                if (sel != null) break;
-              }
-            }
+
+            JMenuItem item = getSelectedItemRecursive(menu);
+            String sel = item != null ? item.getText() : null;
             if (sel == null) {
               if (!name.equals(basename)) menu.setText(basename);
             } else {

From 89cf60074363ba40db4c5ce441950b1ee24153b9 Mon Sep 17 00:00:00 2001
From: Matthijs Kooijman <matthijs@stdin.nl>
Date: Sun, 26 Jan 2020 15:59:43 +0100
Subject: [PATCH 2/6] Separate the boards menu per platform

Previously, the Tools->Boards menu was one long list, divided into
different platforms by (unselectable) headers. When more than one or two
platforms were installed, this quickly results in a very long list of
boards that is hard to navigate.

This commit changes the board menu to have a submenu for each platform,
where each submenu contains just the boards for that platform.

Note that this first keeps a list of board items and then adds those to
the boards menu later. This could have been done directly, but the
intermediate list makes it easier to special-case single platform
installations, as well as sort the list in subsequent commits.

This fixes part of #8858.
---
 app/src/processing/app/Base.java | 46 ++++++++++++++++----------------
 1 file changed, 23 insertions(+), 23 deletions(-)

diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index e9ddc9e4222..3d27f358623 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -1481,24 +1481,21 @@ public void actionPerformed(ActionEvent actionevent) {
     ButtonGroup boardsButtonGroup = new ButtonGroup();
     Map<String, ButtonGroup> buttonGroupsMap = new HashMap<>();
 
+    List<JMenu> platformMenus = new ArrayList<JMenu>();
+
     // 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);
-        }
+        if (platformLabel == null)
+          platformLabel = targetPackage.getId() + "-" + targetPlatform.getId();
+
+        JMenu platformBoardsMenu = new JMenu(tr(platformLabel));
+        MenuScroller.setScrollerFor(platformBoardsMenu);
+        platformMenus.add(platformBoardsMenu);
 
         // Cycle through all boards of this platform
         for (TargetBoard board : targetPlatform.getBoards().values()) {
@@ -1507,14 +1504,27 @@ public void actionPerformed(ActionEvent actionevent) {
           JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup,
                   buttonGroupsMap,
                   board, targetPlatform, targetPackage);
-          boardMenu.add(item);
+          platformBoardsMenu.add(item);
           boardsButtonGroup.add(item);
         }
       }
     }
 
+    JMenuItem firstBoardItem = null;
+    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 there is no current board yet (first startup, or selected
+    // board no longer defined), select first available board.
     if (menuItemsToClickAfterStartup.isEmpty()) {
-      menuItemsToClickAfterStartup.add(selectFirstEnabledMenuItem(boardMenu));
+      menuItemsToClickAfterStartup.add(firstBoardItem);
     }
 
     for (JMenuItem menuItemToClick : menuItemsToClickAfterStartup) {
@@ -1669,16 +1679,6 @@ private static JMenuItem selectVisibleSelectedOrFirstMenuItem(JMenu menu) {
     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();

From 1964b81c3290d80de11736e19640ca52e6cb6345 Mon Sep 17 00:00:00 2001
From: Matthijs Kooijman <matthijs@stdin.nl>
Date: Thu, 19 Sep 2019 17:03:34 +0200
Subject: [PATCH 3/6] Do not use a boards submenu with just one platform

When just one platform is installed, it does not make much sense to use
a submenu, so just add the boards directly under the boards menu as
before.
---
 app/src/processing/app/Base.java | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 3d27f358623..130dc8895a8 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -1511,10 +1511,21 @@ public void actionPerformed(ActionEvent actionevent) {
     }
 
     JMenuItem firstBoardItem = null;
-    for (JMenu platformMenu : platformMenus) {
-      if (firstBoardItem == null && platformMenu.getItemCount() > 0)
-        firstBoardItem = platformMenu.getItem(0);
-      boardMenu.add(platformMenu);
+    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) {

From 55fa3f542fc19e73351efad093a4c1ba9165103f Mon Sep 17 00:00:00 2001
From: Matthijs Kooijman <matthijs@stdin.nl>
Date: Mon, 3 Feb 2020 17:10:55 +0100
Subject: [PATCH 4/6] Sort board submenus alphabetically

This sorts the board submenus themselves, based on the displayed name.
This does not change the ordering of board items within these submenus
(which uses the order from boards.txt).
---
 app/src/processing/app/Base.java | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 130dc8895a8..0ada91f15e1 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -1510,6 +1510,8 @@ public void actionPerformed(ActionEvent actionevent) {
       }
     }
 
+    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,

From e00304935b3a22a2d28ff0ecc49813ad6eadd7b9 Mon Sep 17 00:00:00 2001
From: Cristian Maglie <c.maglie@arduino.cc>
Date: Wed, 25 Mar 2020 12:51:39 +0100
Subject: [PATCH 5/6] Do not translate labels of boards submenus

Otherwise it may happen some weird sorting when untraslated and
translated labels are sorted together:

    Arduino megaAVR Boards
    Arduino nRF52 Board
    ESP32 Arduino
    ESP8266 Modules
    Schede Arduino AVR   <-- the localized string falls to the bottom

Also there is no way for 3rd party boards developers to actually provide
a translation, so let's just remove them.
---
 app/src/processing/app/Base.java          | 2 +-
 arduino-core/src/processing/app/I18n.java | 4 ----
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 0ada91f15e1..1af97af5261 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -1493,7 +1493,7 @@ public void actionPerformed(ActionEvent actionevent) {
         if (platformLabel == null)
           platformLabel = targetPackage.getId() + "-" + targetPlatform.getId();
 
-        JMenu platformBoardsMenu = new JMenu(tr(platformLabel));
+        JMenu platformBoardsMenu = new JMenu(platformLabel);
         MenuScroller.setScrollerFor(platformBoardsMenu);
         platformMenus.add(platformBoardsMenu);
 
diff --git a/arduino-core/src/processing/app/I18n.java b/arduino-core/src/processing/app/I18n.java
index 0ab961aa9ed..1f1a9f93703 100644
--- a/arduino-core/src/processing/app/I18n.java
+++ b/arduino-core/src/processing/app/I18n.java
@@ -106,10 +106,6 @@ public static String format(String fmt, Object... args) {
    * This method is an hack to extract words with gettext tool.
    */
   protected static void unusedStrings() {
-    // These phrases are defined in the "platform.txt".
-    tr("Arduino AVR Boards");
-    tr("Arduino ARM (32-bits) Boards");
-
     // This word is defined in the "boards.txt".
     tr("Processor");
   }

From b821f7561b3cb354caa8415cdd29bdb0c70196d5 Mon Sep 17 00:00:00 2001
From: Cristian Maglie <c.maglie@arduino.cc>
Date: Wed, 25 Mar 2020 12:59:36 +0100
Subject: [PATCH 6/6] Removed some trivial warnings

---
 app/src/processing/app/Base.java | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 1af97af5261..7af728fdd49 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -999,9 +999,9 @@ public boolean handleQuit() {
     // kill uploader (if still alive)
     UploaderUtils uploaderInstance = new UploaderUtils();
     Uploader uploader = uploaderInstance.getUploaderByPreferences(false);
-    if (uploader != null && uploader.programmerPid != null && uploader.programmerPid.isAlive()) {
+    if (uploader != null && Uploader.programmerPid != null && Uploader.programmerPid.isAlive()) {
         // kill the stuck programmer
-        uploader.programmerPid.destroyForcibly();
+        Uploader.programmerPid.destroyForcibly();
     }
 
     if (handleQuitEach()) {
@@ -1444,8 +1444,9 @@ 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();
+          Event e = ((Event) actionevent);
+          filterText = e.getPayload().get("filterText").toString();
+          dropdownItem = e.getPayload().get("dropdownItem").toString();
         }
         try {
           openBoardsManager(filterText, dropdownItem);
@@ -1481,7 +1482,7 @@ public void actionPerformed(ActionEvent actionevent) {
     ButtonGroup boardsButtonGroup = new ButtonGroup();
     Map<String, ButtonGroup> buttonGroupsMap = new HashMap<>();
 
-    List<JMenu> platformMenus = new ArrayList<JMenu>();
+    List<JMenu> platformMenus = new ArrayList<>();
 
     // Cycle through all packages
     for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
@@ -1602,7 +1603,7 @@ public void actionPerformed(ActionEvent e) {
           };
           List<TargetBoard> boards = (List<TargetBoard>) subAction.getValue("board");
           if (boards == null) {
-            boards = new ArrayList<TargetBoard>();
+            boards = new ArrayList<>();
           }
           boards.add(board);
           subAction.putValue("board", boards);
@@ -2003,6 +2004,7 @@ public void keyPressed(KeyEvent e) {
               Base.this.handleFontSizeChange(-1);
             }
             break;
+          default:
           }
         }
       }