MicroPython

Fri, 12 Feb 2021 16:15:18 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 12 Feb 2021 16:15:18 +0100
changeset 8096
5425a9072300
parent 8095
d8caff84ffcf
child 8097
5af9c426c46b

MicroPython
- added a generic dialog for flashing UF2 capable devices (with device detection)

docs/changelog file | annotate | diff | comparison | revisions
eric6.epj file | annotate | diff | comparison | revisions
eric6/Documentation/Source/eric6.MicroPython.CircuitPythonDevices.html file | annotate | diff | comparison | revisions
eric6/MicroPython/CircuitPythonDevices.py file | annotate | diff | comparison | revisions
eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py file | annotate | diff | comparison | revisions
eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui file | annotate | diff | comparison | revisions
eric6/MicroPython/EspDevices.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonDevices.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonWidget.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicrobitDevices.py file | annotate | diff | comparison | revisions
eric6/MicroPython/PyBoardDevices.py file | annotate | diff | comparison | revisions
eric6/MicroPython/UF2FlashDialog.py file | annotate | diff | comparison | revisions
eric6/MicroPython/UF2FlashDialog.ui file | annotate | diff | comparison | revisions
--- a/docs/changelog	Thu Feb 11 11:59:32 2021 +0100
+++ b/docs/changelog	Fri Feb 12 16:15:18 2021 +0100
@@ -13,6 +13,8 @@
 - MicroPython:
   -- extended the list of supported (recognized) CircuitPython boards
   -- added capability to manually configure devices not yet known by eric6
+  -- added a generic dialog for flashing UF2 capable devices (with device
+     detection)
 - pip Interface
   -- changed code for the search function to work with the PyPI search page
      because the XML-RPC search interface of PyPI is still disabled
--- a/eric6.epj	Thu Feb 11 11:59:32 2021 +0100
+++ b/eric6.epj	Fri Feb 12 16:15:18 2021 +0100
@@ -251,7 +251,6 @@
       "eric6/IconEditor/__init__.py",
       "eric6/IconEditor/cursors/__init__.py",
       "eric6/MicroPython/CircuitPythonDevices.py",
-      "eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py",
       "eric6/MicroPython/EspBackupRestoreFirmwareDialog.py",
       "eric6/MicroPython/EspDevices.py",
       "eric6/MicroPython/EspFirmwareSelectionDialog.py",
@@ -1626,7 +1625,8 @@
       "setup.py",
       "eric6/MicroPython/AddEditDevicesDialog.py",
       "eric6/MicroPython/UnknownDevicesDialog.py",
-      "eric6/MicroPython/GenericMicroPythonDevices.py"
+      "eric6/MicroPython/GenericMicroPythonDevices.py",
+      "eric6/MicroPython/UF2FlashDialog.py"
     ],
     "FORMS": [
       "eric6/CondaInterface/CondaExecDialog.ui",
@@ -1669,7 +1669,6 @@
       "eric6/HexEdit/HexEditReplaceWidget.ui",
       "eric6/HexEdit/HexEditSearchWidget.ui",
       "eric6/IconEditor/IconSizeDialog.ui",
-      "eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui",
       "eric6/MicroPython/EspBackupRestoreFirmwareDialog.ui",
       "eric6/MicroPython/EspFirmwareSelectionDialog.ui",
       "eric6/MicroPython/IgnoredDevicesDialog.ui",
@@ -2074,7 +2073,8 @@
       "eric6/WebBrowser/WebBrowserLanguagesDialog.ui",
       "eric6/WebBrowser/ZoomManager/ZoomValuesDialog.ui",
       "eric6/MicroPython/AddEditDevicesDialog.ui",
-      "eric6/MicroPython/UnknownDevicesDialog.ui"
+      "eric6/MicroPython/UnknownDevicesDialog.ui",
+      "eric6/MicroPython/UF2FlashDialog.ui"
     ],
     "RESOURCES": [],
     "INTERFACES": [],
--- a/eric6/Documentation/Source/eric6.MicroPython.CircuitPythonDevices.html	Thu Feb 11 11:59:32 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,479 +0,0 @@
-<!DOCTYPE html>
-<html><head>
-<title>eric6.MicroPython.CircuitPythonDevices</title>
-<meta charset="UTF-8">
-<style>
-body {
-    background: #EDECE6;
-    margin: 0em 1em 10em 1em;
-    color: black;
-}
-
-h1 { color: white; background: #85774A; }
-h2 { color: white; background: #85774A; }
-h3 { color: white; background: #9D936E; }
-h4 { color: white; background: #9D936E; }
-    
-a { color: #BA6D36; }
-
-</style>
-</head>
-<body>
-<a NAME="top" ID="top"></a>
-<h1>eric6.MicroPython.CircuitPythonDevices</h1>
-
-<p>
-Module implementing the device interface class for CircuitPython boards.
-</p>
-<h3>Global Attributes</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Classes</h3>
-
-<table>
-
-<tr>
-<td><a href="#CircuitPythonDevice">CircuitPythonDevice</a></td>
-<td>Class implementing the device for CircuitPython boards.</td>
-</tr>
-</table>
-<h3>Functions</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<hr />
-<hr />
-<a NAME="CircuitPythonDevice" ID="CircuitPythonDevice"></a>
-<h2>CircuitPythonDevice</h2>
-
-<p>
-    Class implementing the device for CircuitPython boards.
-</p>
-<h3>Derived from</h3>
-MicroPythonDevice
-<h3>Class Attributes</h3>
-
-<table>
-<tr><td>DeviceVolumeName</td></tr>
-</table>
-<h3>Class Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-<h3>Methods</h3>
-
-<table>
-
-<tr>
-<td><a href="#CircuitPythonDevice.__init__">CircuitPythonDevice</a></td>
-<td>Constructor</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.__deviceVolumeMounted">__deviceVolumeMounted</a></td>
-<td>Private method to check, if the device volume is mounted.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.__findWorkspace">__findWorkspace</a></td>
-<td>Private method to find the workspace directory.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.__flashCircuitPython">__flashCircuitPython</a></td>
-<td>Private slot to flash a CircuitPython firmware to the device.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.__flashTeensy">__flashTeensy</a></td>
-<td>Private method to show a message box because Teens does not support the UF2 bootloader yet.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.__installLibraryFiles">__installLibraryFiles</a></td>
-<td>Private slot to install Python files into the onboard library.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.addDeviceMenuEntries">addDeviceMenuEntries</a></td>
-<td>Public method to add device specific entries to the given menu.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.canRunScript">canRunScript</a></td>
-<td>Public method to determine, if a script can be executed.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.canStartFileManager">canStartFileManager</a></td>
-<td>Public method to determine, if a File Manager can be started.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.canStartPlotter">canStartPlotter</a></td>
-<td>Public method to determine, if a Plotter can be started.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.canStartRepl">canStartRepl</a></td>
-<td>Public method to determine, if a REPL can be started.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.deviceName">deviceName</a></td>
-<td>Public method to get the name of the device.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.forceInterrupt">forceInterrupt</a></td>
-<td>Public method to determine the need for an interrupt when opening the serial connection.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.getDocumentationUrl">getDocumentationUrl</a></td>
-<td>Public method to get the device documentation URL.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.getDownloadMenuEntries">getDownloadMenuEntries</a></td>
-<td>Public method to retrieve the entries for the downloads menu.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.getWorkspace">getWorkspace</a></td>
-<td>Public method to get the workspace directory.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.runScript">runScript</a></td>
-<td>Public method to run the given Python script.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.setButtons">setButtons</a></td>
-<td>Public method to enable the supported action buttons.</td>
-</tr>
-<tr>
-<td><a href="#CircuitPythonDevice.supportsLocalFileAccess">supportsLocalFileAccess</a></td>
-<td>Public method to indicate file access via a local directory.</td>
-</tr>
-</table>
-<h3>Static Methods</h3>
-
-<table>
-<tr><td>None</td></tr>
-</table>
-
-<a NAME="CircuitPythonDevice.__init__" ID="CircuitPythonDevice.__init__"></a>
-<h4>CircuitPythonDevice (Constructor)</h4>
-<b>CircuitPythonDevice</b>(<i>microPythonWidget, parent=None</i>)
-
-<p>
-        Constructor
-</p>
-<dl>
-
-<dt><i>microPythonWidget</i> (MicroPythonWidget)</dt>
-<dd>
-reference to the main MicroPython widget
-</dd>
-<dt><i>parent</i> (QObject)</dt>
-<dd>
-reference to the parent object
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.__deviceVolumeMounted" ID="CircuitPythonDevice.__deviceVolumeMounted"></a>
-<h4>CircuitPythonDevice.__deviceVolumeMounted</h4>
-<b>__deviceVolumeMounted</b>(<i></i>)
-
-<p>
-        Private method to check, if the device volume is mounted.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-flag indicated a mounted device
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-bool
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.__findWorkspace" ID="CircuitPythonDevice.__findWorkspace"></a>
-<h4>CircuitPythonDevice.__findWorkspace</h4>
-<b>__findWorkspace</b>(<i>silent=False</i>)
-
-<p>
-        Private method to find the workspace directory.
-</p>
-<dl>
-
-<dt><i>silent</i> (bool)</dt>
-<dd>
-flag indicating silent operations
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-workspace directory used for saving files
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-str
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.__flashCircuitPython" ID="CircuitPythonDevice.__flashCircuitPython"></a>
-<h4>CircuitPythonDevice.__flashCircuitPython</h4>
-<b>__flashCircuitPython</b>(<i></i>)
-
-<p>
-        Private slot to flash a CircuitPython firmware to the device.
-</p>
-<a NAME="CircuitPythonDevice.__flashTeensy" ID="CircuitPythonDevice.__flashTeensy"></a>
-<h4>CircuitPythonDevice.__flashTeensy</h4>
-<b>__flashTeensy</b>(<i></i>)
-
-<p>
-        Private method to show a message box because Teens does not support
-        the UF2 bootloader yet.
-</p>
-<a NAME="CircuitPythonDevice.__installLibraryFiles" ID="CircuitPythonDevice.__installLibraryFiles"></a>
-<h4>CircuitPythonDevice.__installLibraryFiles</h4>
-<b>__installLibraryFiles</b>(<i></i>)
-
-<p>
-        Private slot to install Python files into the onboard library.
-</p>
-<a NAME="CircuitPythonDevice.addDeviceMenuEntries" ID="CircuitPythonDevice.addDeviceMenuEntries"></a>
-<h4>CircuitPythonDevice.addDeviceMenuEntries</h4>
-<b>addDeviceMenuEntries</b>(<i>menu</i>)
-
-<p>
-        Public method to add device specific entries to the given menu.
-</p>
-<dl>
-
-<dt><i>menu</i> (QMenu)</dt>
-<dd>
-reference to the context menu
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.canRunScript" ID="CircuitPythonDevice.canRunScript"></a>
-<h4>CircuitPythonDevice.canRunScript</h4>
-<b>canRunScript</b>(<i></i>)
-
-<p>
-        Public method to determine, if a script can be executed.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-tuple containing a flag indicating it is safe to start a
-            Plotter and a reason why it cannot.
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-tuple of (bool, str)
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.canStartFileManager" ID="CircuitPythonDevice.canStartFileManager"></a>
-<h4>CircuitPythonDevice.canStartFileManager</h4>
-<b>canStartFileManager</b>(<i></i>)
-
-<p>
-        Public method to determine, if a File Manager can be started.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-tuple containing a flag indicating it is safe to start a
-            File Manager and a reason why it cannot.
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-tuple of (bool, str)
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.canStartPlotter" ID="CircuitPythonDevice.canStartPlotter"></a>
-<h4>CircuitPythonDevice.canStartPlotter</h4>
-<b>canStartPlotter</b>(<i></i>)
-
-<p>
-        Public method to determine, if a Plotter can be started.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-tuple containing a flag indicating it is safe to start a
-            Plotter and a reason why it cannot.
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-tuple of (bool, str)
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.canStartRepl" ID="CircuitPythonDevice.canStartRepl"></a>
-<h4>CircuitPythonDevice.canStartRepl</h4>
-<b>canStartRepl</b>(<i></i>)
-
-<p>
-        Public method to determine, if a REPL can be started.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-tuple containing a flag indicating it is safe to start a REPL
-            and a reason why it cannot.
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-tuple of (bool, str)
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.deviceName" ID="CircuitPythonDevice.deviceName"></a>
-<h4>CircuitPythonDevice.deviceName</h4>
-<b>deviceName</b>(<i></i>)
-
-<p>
-        Public method to get the name of the device.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-name of the device
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-str
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.forceInterrupt" ID="CircuitPythonDevice.forceInterrupt"></a>
-<h4>CircuitPythonDevice.forceInterrupt</h4>
-<b>forceInterrupt</b>(<i></i>)
-
-<p>
-        Public method to determine the need for an interrupt when opening the
-        serial connection.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-flag indicating an interrupt is needed
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-bool
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.getDocumentationUrl" ID="CircuitPythonDevice.getDocumentationUrl"></a>
-<h4>CircuitPythonDevice.getDocumentationUrl</h4>
-<b>getDocumentationUrl</b>(<i></i>)
-
-<p>
-        Public method to get the device documentation URL.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-documentation URL of the device
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-str
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.getDownloadMenuEntries" ID="CircuitPythonDevice.getDownloadMenuEntries"></a>
-<h4>CircuitPythonDevice.getDownloadMenuEntries</h4>
-<b>getDownloadMenuEntries</b>(<i></i>)
-
-<p>
-        Public method to retrieve the entries for the downloads menu.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-list of tuples with menu text and URL to be opened for each
-            entry
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-list of tuple of (str, str)
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.getWorkspace" ID="CircuitPythonDevice.getWorkspace"></a>
-<h4>CircuitPythonDevice.getWorkspace</h4>
-<b>getWorkspace</b>(<i>silent=False</i>)
-
-<p>
-        Public method to get the workspace directory.
-</p>
-<dl>
-
-<dt><i>silent</i> (bool)</dt>
-<dd>
-flag indicating silent operations
-</dd>
-</dl>
-<dl>
-<dt>Return:</dt>
-<dd>
-workspace directory used for saving files
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-str
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.runScript" ID="CircuitPythonDevice.runScript"></a>
-<h4>CircuitPythonDevice.runScript</h4>
-<b>runScript</b>(<i>script</i>)
-
-<p>
-        Public method to run the given Python script.
-</p>
-<dl>
-
-<dt><i>script</i> (str)</dt>
-<dd>
-script to be executed
-</dd>
-</dl>
-<a NAME="CircuitPythonDevice.setButtons" ID="CircuitPythonDevice.setButtons"></a>
-<h4>CircuitPythonDevice.setButtons</h4>
-<b>setButtons</b>(<i></i>)
-
-<p>
-        Public method to enable the supported action buttons.
-</p>
-<a NAME="CircuitPythonDevice.supportsLocalFileAccess" ID="CircuitPythonDevice.supportsLocalFileAccess"></a>
-<h4>CircuitPythonDevice.supportsLocalFileAccess</h4>
-<b>supportsLocalFileAccess</b>(<i></i>)
-
-<p>
-        Public method to indicate file access via a local directory.
-</p>
-<dl>
-<dt>Return:</dt>
-<dd>
-flag indicating file access via local directory
-</dd>
-</dl>
-<dl>
-<dt>Return Type:</dt>
-<dd>
-bool
-</dd>
-</dl>
-<div align="right"><a href="#top">Up</a></div>
-<hr />
-</body></html>
\ No newline at end of file
--- a/eric6/MicroPython/CircuitPythonDevices.py	Thu Feb 11 11:59:32 2021 +0100
+++ b/eric6/MicroPython/CircuitPythonDevices.py	Fri Feb 12 16:15:18 2021 +0100
@@ -214,6 +214,15 @@
                              self.__installLibraryFiles)
         act.setEnabled(self.__deviceVolumeMounted())
     
+    def hasFlashMenuEntry(self):
+        """
+        Public method to check, if the device has its own flash menu entry.
+        
+        @return flag indicating a specific flash menu entry
+        @rtype bool
+        """
+        return True
+    
     @pyqtSlot()
     def __flashCircuitPython(self):
         """
@@ -226,21 +235,9 @@
                     self.__nonUF2devices[name]()
                     break
             else:
-                button = E5MessageBox.information(
-                    self.microPython,
-                    self.tr("Flash CircuitPython Firmware"),
-                    self.tr("Please reset the device to bootloader mode and"
-                            " confirm when ready."),
-                    E5MessageBox.StandardButtons(
-                        E5MessageBox.Abort |
-                        E5MessageBox.Ok))
-                if button == E5MessageBox.Ok:
-                    from .CircuitPythonFirmwareSelectionDialog import (
-                        CircuitPythonFirmwareSelectionDialog)
-                    dlg = CircuitPythonFirmwareSelectionDialog()
-                    if dlg.exec() == QDialog.Accepted:
-                        cpyPath, devicePath = dlg.getData()
-                        shutil.copy2(cpyPath, devicePath)
+                from .UF2FlashDialog import UF2FlashDialog
+                dlg = UF2FlashDialog(boardType="circuitpython")
+                dlg.exec()
     
     def __flashTeensy(self):
         """
--- a/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py	Thu Feb 11 11:59:32 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,296 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2019 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing a dialog to enter the firmware flashing data.
-"""
-
-import os
-
-from PyQt5.QtCore import pyqtSlot, QCoreApplication
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox
-
-from E5Gui.E5PathPicker import E5PathPickerModes
-from E5Gui import E5MessageBox
-
-from .Ui_CircuitPythonFirmwareSelectionDialog import (
-    Ui_CircuitPythonFirmwareSelectionDialog
-)
-
-import Utilities
-import UI.PixmapCache
-import Preferences
-
-
-class CircuitPythonFirmwareSelectionDialog(
-        QDialog, Ui_CircuitPythonFirmwareSelectionDialog):
-    """
-    Class implementing a dialog to enter the firmware flashing data.
-    """
-    #
-    # The Boards list is built with data extracted from:
-    # - https://github.com/adafruit/uf2-samdx1
-    # - https://github.com/adafruit/Adafruit_nRF52_Bootloader
-    #
-    Boards = (
-        # Adafruit boards
-        ("--- Adafruit ---", ""),
-        ("BLM Badge", "BADGEBOOT"),
-        ("BadgeLC", "BADGELCBOOT"),
-        ("CLUE nRF52840", "CLUEBOOT"),
-        ("Circuit Playground Express", "CPLAYBOOT"),
-        ("Circuit Playground nRF52840", "CPLAYBTBOOT"),
-        ("Feather Arcade D51", "ARCADE-D5"),
-        ("Feather M0", "FEATHERBOOT"),
-        ("Feather M0 Express", "FEATHERBOOT"),
-        ("Feather M4 CAN Express", "FTHRCANBOOT"),
-        ("Feather M4 Express", "FEATHERBOOT"),
-        ("Feather nRF52840 Express", "FTHR840BOOT"),
-        ("Feather nRF52840 Sense", "FTHR840BOOT"),
-        ("Gemma M0", "GEMMABOOT"),
-        ("Grand Central M4 Express", "GCM4BOOT"),
-        ("Hallowing M0", "HALLOWBOOT"),
-        ("Hallowing M4", "HALLOM4BOOT"),
-        ("Hallowing Mask M4", "MASKM4BOOT"),
-        ("Itsy Arcade D51", "ARCADE-D5"),
-        ("ItsyBitsy M0 Express", "ITSYBOOT"),
-        ("ItsyBitsy M4 Express", "ITSYM4BOOT"),
-        ("ItsyBitsy nRF52840 Express", " ITSY840BOOT"),
-        ("Metro M0", "METROBOOT"),
-        ("Metro M4 AirLift", "METROM4BOOT"),
-        ("Metro M4 Express", "METROM4BOOT"),
-        ("Metro nRF52840 Express", "METR840BOOT"),
-        ("NeoPixel Trinkey M0", "TRINKEYBOOT"),
-        ("NeoTrelis M4 Express", "TRELM4BOOT"),
-        ("PyBadge", "BADGEBOOT"),
-        ("PyGamer", "PYGAMERBOOT"),
-        ("PyPortal", "PORTALBOOT"),
-        ("PyPortal M4 Express", "PORTALBOOT"),
-        ("PyPortal Pynt", "PORTALBOOT"),
-        ("PyPortal Titano", "PORTALBOOT"),
-        ("PyRuler", "TRINKETBOOT"),
-        ("QT Py M0", "QTPY_BOOT"),
-        ("Radiofruit M0", "RADIOBOOT"),
-        ("Trellis M4 Express", "TRELM4BOOT"),
-        ("Trinket M0", "TRINKETBOOT"),
-        ("crickit", "CRICKITBOOT"),
-        ("pIRKey M0", "PIRKEYBOOT"),
-        
-        # Arduino boards
-        ("--- Arduino ---", ""),
-        ("MKR1000", "MKR1000"),
-        ("MKR1300", "MKR1300"),
-        ("MKRZero", "MKRZEROBOOT"),
-        ("Nano 33 BLE", "NANO33BOOT"),
-        ("Zero", "ZEROBOOT"),
-        
-        # Particle boards
-        ("--- Particle ---", ""),
-        ("Argon", "ARGONBOOT  "),
-        ("Boron", "BORONBOOT  "),
-        ("Xenon", "XENONBOOT  "),
-        
-        # Seed boards
-        ("--- Seeed Studio ---", ""),
-        ("Grove Zero", "Grove Zero"),
-        ("Seeduino XIAO", "Arduino"),
-        
-        # SparkFun boards
-        ("--- SparkFun ---", ""),
-        ("Qwiic Micro", "QwiicMicro"),
-        ("SAMD21 Dev Breakout", "SPARKFUN"),
-        ("SAMD21 Mini Breakout", "SPARKFUN"),
-        ("SAMD51 Thing Plus", "51THINGBOOT"),
-        ("RedBoard Turbo", "TURBOBOOT"),
-        ("Pro nRF52840 Mini", "NRF52BOOT"),
-        
-        # other boards we know about
-        (QCoreApplication.translate("CircuitPythonFirmwareSelectionDialog",
-                                    "--- Others ---"), ""),
-        ("ARAMCON Badge 2019", "ARAMBOOT"),
-        ("AtelierDuMaker NRF52840 Breakout", "ADM840BOOT"),
-        ("BlueMicro", "BLUEMICRO"),
-        ("Capable Robot Programmable USB Hub", "USBHUBBOOT"),
-        ("CircuitBrains Basic", "BOOT"),
-        ("CircuitBrains Deluxe", "BOOT"),
-        ("Eitech Robotics", "ROBOTICS"),
-        ("ElectronicCats Bast BLE", "BASTBLE"),
-        ("Fluff M0", "FLUFFBOOT"),
-        ("Generic Corp. SAMD21 Board", "SAMD21"),
-        ("Generic Corp. SAME54 Board", "SAME54"),
-        ("IkigaiSense Vita nRF52840", "ISVITABoot"),
-        ("Itaca uChip CircuitPython", "UCHIPYBOOT"),
-        ("MakerDiary MDK nRF52840 USB Dongle", "MDK840DONGL"),
-        ("MakerDiary nRF52840 M.2 Module", "nRF52840M2"),
-        ("MakerDiary Pitaya Go", "PITAYAGO"),
-        ("Microchip SAME54 Xplained", "E54XBOOT"),
-        ("Mini SAM M0", "MINISAMBOOT"),
-        ("Mini SAM M4", "MINISAMBOOT"),
-        ("Nice Keyboards nice!nano", "NICENANO"),
-        ("OHS2020 Badge", "BADGEBOOT"),
-        ("PewPew", "PEWBOOT"),
-        ("Raytac MDBT50Q-RX", "MDBT50QBOOT"),
-        ("Robotics Masters Robo HAT MM1", "ROBOM0BOOT"),
-        ("Robotics Masters Robo HAT MM1 M4", "ROBOM4BOOT"),
-        ("Serpente", "SERPENTBOOT"),
-        ("The Open Book Feather", "BOOKBOOT"),
-        ("Watterott Wattuino RC", "RCBOOT"),
-        ("Waveshare nRF52840 Eval", "WS52840EVK"),
-        ("Winterbloom Big Honking Button", "HONKBOOT"),
-        ("Winterbloom Binary Star", "STARBOOT"),
-        ("Winterbloom Gemini", "GEMINIBOOT"),
-        ("Winterbloom Sol", "SOLBOOT"),
-        ("XinaBox CC03", "CC03"),
-        ("XinaBox CS11", "CS11"),
-        ("dadamachines automat", "AUTOMAT"),
-        ("eduSense senseBox MCU", "SENSEBOX"),
-        ("maholli PyCubedv04", "PYCUBEDBOOT"),
-        ("maholli SAM32", "SAM32BOOT"),
-        ("ndGarage ndBit6", "ND6BOOT"),
-        ("ndGarage ndBit7", "ND7BOOT"),
-    )
-    
-    def __init__(self, parent=None):
-        """
-        Constructor
-        
-        @param parent reference to the parent widget
-        @type QWidget
-        """
-        super(CircuitPythonFirmwareSelectionDialog, self).__init__(parent)
-        self.setupUi(self)
-        
-        self.retestButton.setIcon(UI.PixmapCache.getIcon("rescan"))
-        
-        self.firmwarePicker.setMode(E5PathPickerModes.OpenFileMode)
-        self.firmwarePicker.setFilters(
-            self.tr("CircuitPython Firmware Files (*.uf2);;"
-                    "All Files (*)"))
-        
-        self.bootPicker.setMode(E5PathPickerModes.DirectoryShowFilesMode)
-        
-        self.__manualMarker = "<manual>"
-        self.boardComboBox.addItem("", ""),     # indicator for no selection
-        for boardName, bootVolume in self.Boards:
-            self.boardComboBox.addItem(boardName, bootVolume)
-        manualDevices = Preferences.getMicroPython("ManualDevices")
-        if manualDevices:
-            self.boardComboBox.addItem(self.tr("--- Local Devices ---"), "")
-            for device in manualDevices:
-                self.boardComboBox.addItem(device["description"],
-                                           device["volume"])
-        self.boardComboBox.addItem(self.tr("Manual Select"),
-                                   self.__manualMarker),
-        
-        msh = self.minimumSizeHint()
-        self.resize(max(self.width(), msh.width()), msh.height())
-    
-    def __updateOkButton(self):
-        """
-        Private method to update the state of the OK button and the retest
-        button.
-        """
-        firmwareFile = self.firmwarePicker.text()
-        self.retestButton.setEnabled(bool(firmwareFile) and
-                                     os.path.exists(firmwareFile))
-        
-        if not bool(firmwareFile) or not os.path.exists(firmwareFile):
-            enable = False
-        else:
-            volumeName = self.boardComboBox.currentData()
-            if volumeName and volumeName != self.__manualMarker:
-                # check if the user selected a board and the board is in
-                # bootloader mode
-                deviceDirectories = Utilities.findVolume(volumeName,
-                                                         findAll=True)
-                if len(deviceDirectories) > 1:
-                    enable = False
-                    E5MessageBox.warning(
-                        self,
-                        self.tr("Select Path to Device"),
-                        self.tr("There are multiple devices in 'bootloader'"
-                                " mode and mounted. Please make sure, that"
-                                " only one device is prepared for flashing.")
-                    )
-                elif len(deviceDirectories) == 1:
-                    self.bootPicker.setText(deviceDirectories[0])
-                    enable = True
-                else:
-                    enable = False
-                    E5MessageBox.warning(
-                        self,
-                        self.tr("Select Path to Device"),
-                        self.tr("""<p>The device volume <b>{0}</b> could not"""
-                                """ be found. Is the device in 'bootloader'"""
-                                """ mode and mounted?</p> <p>Alternatively"""
-                                """ select the "Manual Select" entry and"""
-                                """ enter the path to the device below.</p>""")
-                        .format(volumeName)
-                    )
-            
-            elif volumeName == self.__manualMarker:
-                # select the device path manually
-                deviceDirectory = self.bootPicker.text()
-                enable = (bool(deviceDirectory) and
-                          os.path.exists(deviceDirectory))
-            
-            else:
-                # illegal entry
-                enable = False
-        
-        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
-    
-    @pyqtSlot(str)
-    def on_firmwarePicker_textChanged(self, firmware):
-        """
-        Private slot handling a change of the firmware path.
-        
-        @param firmware path to the firmware
-        @type str
-        """
-        self.__updateOkButton()
-    
-    @pyqtSlot(int)
-    def on_boardComboBox_currentIndexChanged(self, index):
-        """
-        Private slot to handle the selection of a board type.
-        
-        @param index index of the selected board type
-        @type int
-        """
-        if self.boardComboBox.itemData(index) == self.__manualMarker:
-            self.bootPicker.clear()
-            self.bootPicker.setEnabled(True)
-        else:
-            self.bootPicker.setEnabled(False)
-        
-        self.__updateOkButton()
-    
-    @pyqtSlot()
-    def on_retestButton_clicked(self):
-        """
-        Private slot to research for the selected volume.
-        """
-        self.__updateOkButton()
-    
-    @pyqtSlot(str)
-    def on_bootPicker_textChanged(self, devicePath):
-        """
-        Private slot handling a change of the device path.
-        
-        @param devicePath path to the device
-        @type str
-        """
-        self.__updateOkButton()
-    
-    def getData(self):
-        """
-        Public method to obtain the entered data.
-        
-        @return tuple containing the path to the CircuitPython firmware file
-            and the path to the device
-        @rtype tuple of (str, str)
-        """
-        return self.firmwarePicker.text(), self.bootPicker.text()
--- a/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui	Thu Feb 11 11:59:32 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>CircuitPythonFirmwareSelectionDialog</class>
- <widget class="QDialog" name="CircuitPythonFirmwareSelectionDialog">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>500</width>
-    <height>124</height>
-   </rect>
-  </property>
-  <property name="windowTitle">
-   <string>Flash CircuitPython Firmware</string>
-  </property>
-  <property name="sizeGripEnabled">
-   <bool>true</bool>
-  </property>
-  <layout class="QGridLayout" name="gridLayout">
-   <item row="1" column="1">
-    <widget class="QComboBox" name="boardComboBox">
-     <property name="toolTip">
-      <string>Select the board type or 'Manual'</string>
-     </property>
-     <property name="sizeAdjustPolicy">
-      <enum>QComboBox::AdjustToContents</enum>
-     </property>
-    </widget>
-   </item>
-   <item row="0" column="1" colspan="3">
-    <widget class="E5PathPicker" name="firmwarePicker" native="true">
-     <property name="focusPolicy">
-      <enum>Qt::WheelFocus</enum>
-     </property>
-     <property name="toolTip">
-      <string>Enter the path of the CircuitPython firmware file</string>
-     </property>
-    </widget>
-   </item>
-   <item row="1" column="3">
-    <spacer name="horizontalSpacer">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="sizeHint" stdset="0">
-      <size>
-       <width>339</width>
-       <height>20</height>
-      </size>
-     </property>
-    </spacer>
-   </item>
-   <item row="0" column="0">
-    <widget class="QLabel" name="label_3">
-     <property name="text">
-      <string>Firmware:</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="0">
-    <widget class="QLabel" name="label_2">
-     <property name="text">
-      <string>Boot Path:</string>
-     </property>
-    </widget>
-   </item>
-   <item row="1" column="0">
-    <widget class="QLabel" name="label">
-     <property name="text">
-      <string>Board Type:</string>
-     </property>
-    </widget>
-   </item>
-   <item row="2" column="1" colspan="3">
-    <widget class="E5PathPicker" name="bootPicker" native="true">
-     <property name="focusPolicy">
-      <enum>Qt::WheelFocus</enum>
-     </property>
-     <property name="toolTip">
-      <string>Enter the path to the device in bootloader mode</string>
-     </property>
-    </widget>
-   </item>
-   <item row="3" column="0" colspan="4">
-    <widget class="QDialogButtonBox" name="buttonBox">
-     <property name="orientation">
-      <enum>Qt::Horizontal</enum>
-     </property>
-     <property name="standardButtons">
-      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
-     </property>
-    </widget>
-   </item>
-   <item row="1" column="2">
-    <widget class="QToolButton" name="retestButton">
-     <property name="enabled">
-      <bool>false</bool>
-     </property>
-     <property name="toolTip">
-      <string>Press to search the selected volume</string>
-     </property>
-    </widget>
-   </item>
-  </layout>
- </widget>
- <customwidgets>
-  <customwidget>
-   <class>E5PathPicker</class>
-   <extends>QWidget</extends>
-   <header>E5Gui/E5PathPicker.h</header>
-   <container>1</container>
-  </customwidget>
- </customwidgets>
- <tabstops>
-  <tabstop>firmwarePicker</tabstop>
-  <tabstop>boardComboBox</tabstop>
-  <tabstop>retestButton</tabstop>
-  <tabstop>bootPicker</tabstop>
- </tabstops>
- <resources/>
- <connections>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>accepted()</signal>
-   <receiver>CircuitPythonFirmwareSelectionDialog</receiver>
-   <slot>accept()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>248</x>
-     <y>254</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>157</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>buttonBox</sender>
-   <signal>rejected()</signal>
-   <receiver>CircuitPythonFirmwareSelectionDialog</receiver>
-   <slot>reject()</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>316</x>
-     <y>260</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>286</x>
-     <y>274</y>
-    </hint>
-   </hints>
-  </connection>
- </connections>
-</ui>
--- a/eric6/MicroPython/EspDevices.py	Thu Feb 11 11:59:32 2021 +0100
+++ b/eric6/MicroPython/EspDevices.py	Fri Feb 12 16:15:18 2021 +0100
@@ -156,6 +156,15 @@
         menu.addSeparator()
         menu.addAction(self.tr("Install 'esptool.py'"), self.__installEspTool)
     
+    def hasFlashMenuEntry(self):
+        """
+        Public method to check, if the device has its own flash menu entry.
+        
+        @return flag indicating a specific flash menu entry
+        @rtype bool
+        """
+        return True
+    
     @pyqtSlot()
     def __eraseFlash(self):
         """
--- a/eric6/MicroPython/MicroPythonDevices.py	Thu Feb 11 11:59:32 2021 +0100
+++ b/eric6/MicroPython/MicroPythonDevices.py	Fri Feb 12 16:15:18 2021 +0100
@@ -170,12 +170,12 @@
     """
     Function to check the serial ports for supported MicroPython devices.
     
-    @return tuple containing a list of tuples with the board type, a
-        description and the serial port it is connected at for known device
-        types and a list of tuples with VID, PID and description for unknown
-        devices
-    @rtype tuple of (list of tuples of (str, str, str), list of tuples of
-        (int, int, str)
+    @return tuple containing a list of tuples with the board type, the port
+        description, a description, the serial port it is connected at, the
+        VID and PID for known device types and a list of tuples with VID, PID
+        and description for unknown devices
+    @rtype tuple of (list of tuples of (str, str, str, str, int, int),
+        list of tuples of (int, int, str)
     """
     from PyQt5.QtSerialPort import QSerialPortInfo
     
@@ -499,6 +499,15 @@
         """
         pass
     
+    def hasFlashMenuEntry(self):
+        """
+        Public method to check, if the device has its own flash menu entry.
+        
+        @return flag indicating a specific flash menu entry
+        @rtype bool
+        """
+        return False
+    
     def hasTimeCommands(self):
         """
         Public method to check, if the device supports time commands.
--- a/eric6/MicroPython/MicroPythonWidget.py	Thu Feb 11 11:59:32 2021 +0100
+++ b/eric6/MicroPython/MicroPythonWidget.py	Fri Feb 12 16:15:18 2021 +0100
@@ -28,6 +28,7 @@
 from .Ui_MicroPythonWidget import Ui_MicroPythonWidget
 
 from . import MicroPythonDevices
+from . import UF2FlashDialog
 try:
     from .MicroPythonGraphWidget import MicroPythonGraphWidget
     HAS_QTCHART = True
@@ -178,8 +179,6 @@
 }
 
 
-# TODO: extract UF2 flashing support into a general MPy functionality with own
-#       vid/pid list
 class MicroPythonWidget(QWidget, Ui_MicroPythonWidget):
     """
     Class implementing the MicroPython REPL widget.
@@ -347,7 +346,10 @@
                 tuple(d)
                 for d in Preferences.getMicroPython("IgnoredUnknownDevices")
             }
-            newUnknownDevices = set(unknownDevices) - ignoredUnknown
+            uf2Devices = {(*x[2], x[1]) for x in UF2FlashDialog.getFoundDevices()}
+            newUnknownDevices = (
+                set(unknownDevices) - ignoredUnknown - uf2Devices
+            )
             if newUnknownDevices:
                 button = E5MessageBox.information(
                     self,
@@ -1282,7 +1284,11 @@
             act = self.__superMenu.addAction(
                 self.tr("Show Documentation"), self.__showDocumentation)
             act.setEnabled(self.__device.hasDocumentationUrl())
-        self.__superMenu.addSeparator()
+            self.__superMenu.addSeparator()
+        if not self.__device.hasFlashMenuEntry():
+            self.__superMenu.addAction(self.tr("Flash UF2 Device"),
+                                       self.__flashUF2)
+            self.__superMenu.addSeparator()
         self.__superMenu.addAction(self.tr("Manage Unknown Devices"),
                                    self.__manageUnknownDevices)
         self.__superMenu.addAction(self.tr("Ignored Serial Devices"),
@@ -1706,3 +1712,12 @@
             
             # rescan the ports
             self.__populateDeviceTypeComboBox()
+    
+    @pyqtSlot()
+    def __flashUF2(self):
+        """
+        Private slot to flash MicroPython/CircuitPython to a device
+        support the UF2 bootloader.
+        """
+        dlg = UF2FlashDialog.UF2FlashDialog()
+        dlg.exec()
--- a/eric6/MicroPython/MicrobitDevices.py	Thu Feb 11 11:59:32 2021 +0100
+++ b/eric6/MicroPython/MicrobitDevices.py	Fri Feb 12 16:15:18 2021 +0100
@@ -166,6 +166,15 @@
                              self.__resetDevice)
         act.setEnabled(connected)
     
+    def hasFlashMenuEntry(self):
+        """
+        Public method to check, if the device has its own flash menu entry.
+        
+        @return flag indicating a specific flash menu entry
+        @rtype bool
+        """
+        return True
+    
     @pyqtSlot()
     def __flashMicroPython(self, firmware=False):
         """
--- a/eric6/MicroPython/PyBoardDevices.py	Thu Feb 11 11:59:32 2021 +0100
+++ b/eric6/MicroPython/PyBoardDevices.py	Fri Feb 12 16:15:18 2021 +0100
@@ -235,6 +235,15 @@
         menu.addAction(self.tr("MicroPython Flash Instructions"),
                        self.__showFlashInstructions)
     
+    def hasFlashMenuEntry(self):
+        """
+        Public method to check, if the device has its own flash menu entry.
+        
+        @return flag indicating a specific flash menu entry
+        @rtype bool
+        """
+        return True
+    
     @pyqtSlot()
     def __showFlashInstructions(self):
         """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/UF2FlashDialog.py	Fri Feb 12 16:15:18 2021 +0100
@@ -0,0 +1,716 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to flash any UF2 capable device.
+"""
+
+import os
+import shutil
+
+from PyQt5.QtCore import pyqtSlot, Qt, QCoreApplication, QThread, QEventLoop
+from PyQt5.QtWidgets import QDialog
+
+from E5Gui.E5PathPicker import E5PathPickerModes
+
+from .Ui_UF2FlashDialog import Ui_UF2FlashDialog
+
+import UI.PixmapCache
+import Utilities
+
+from . import MicroPythonDevices
+
+SupportedUF2Boards = {
+    "circuitpython": {
+        "volumes": {
+            (0x03EB, 0x2402): [
+                "SAMD21",         # SAMD21 Board
+                "SAME54",         # SAME54 Board
+            ],
+            (0x04D8, 0xEC44): [
+                "PYCUBEDBOOT",    # PyCubedv04
+            ],
+            (0x04D8, 0xEC63): [
+                "BOOT",           # CircuitBrains Basic
+            ],
+            (0x04D8, 0xEC64): [
+                "BOOT",           # CircuitBrains Deluxe
+            ],
+            (0x04D8, 0xED5F): [
+                "UCHIPYBOOT",     # uChip CircuitPython
+            ],
+            (0x04D8, 0xEDB3): [
+                "USBHUBBOOT",     # Programmable USB Hub
+            ],
+            (0x04D8, 0xEDBE): [
+                "SAM32BOOT",      # SAM32
+            ],
+            (0x04D8, 0xEF66): [
+                "SENSEBOX",       # senseBox MCU
+            ],
+            (0x1209, 0x2017): [
+                "MINISAMBOOT",    # Mini SAM M4
+            ],
+            (0x1209, 0x4D44): [
+                "ROBOM0BOOT",     # Robo HAT MM1
+                "ROBOM4BOOT",     # Robo HAT MM1 M4
+            ],
+            (0x1209, 0x4DDD): [
+                "SapBOOT",        # CP Sapling
+            ],
+            (0x1209, 0x7102): [
+                "MINISAMBOOT",    # Mini SAM M0
+            ],
+            (0x1209, 0x805A): [
+                "BASTBLE",        # Bast BLE
+            ],
+            (0x1209, 0xE3E2): [
+                "StackRduino",    # StackRduino M0 PRO
+            ],
+            (0x1209, 0xF501): [
+                "M4SHIMBOOT",     # M4-Shim
+            ],
+            (0x16D0, 0x0CDA): [
+                "AUTOMAT",        # automat
+            ],
+            (0x1B4F, 0x0019): [
+                "QwiicMicro",     # Sparkfun Qwiic Micro
+            ],
+            (0x1B4F, 0x0D22): [
+                "SPARKFUN",       # Sparkfun SAMD21 Mini Breakout
+            ],
+            (0x1B4F, 0x0D23): [
+                "SPARKFUN",       # Sparkfun SAMD21 Dev Breakout
+            ],
+            (0x1D50, 0x6110): [
+                "ROBOTICS",       # Robotics
+            ],
+            (0x1D50, 0x6112): [
+                "RCBOOT",         # Wattuino RC
+            ],
+            (0x1D50, 0x6160): [
+                "BLUEMICRO",      # BlueMicro
+            ],
+            (0x230A, 0x00E9): [
+                "TAU_BOOT",       # Tau
+            ],
+            (0x2341, 0x0057): [
+                "NANOBOOT",       # NANO 33 IoT
+            ],
+            (0x2341, 0x8053): [
+                "MKR1300",        # MKR1300
+            ],
+            (0x239A, 0x000F): [
+                "ITSYBOOT",       # ItsyBitsy M0 Express
+            ],
+            (0x239A, 0x0013): [
+                "METROBOOT",      # Metro M0
+            ],
+            (0x239A, 0x0015): [
+                "FEATHERBOOT",    # Feather M0
+            ],
+            (0x239A, 0x0018): [
+                "CPLAYBOOT",      # CPlay Express
+            ],
+            (0x239A, 0x001B): [
+                "FEATHERBOOT",    # Feather M0 Express
+            ],
+            (0x239A, 0x001C): [
+                "GEMMABOOT",      # Gemma M0
+            ],
+            (0x239A, 0x001E): [
+                "TRINKETBOOT",    # Trinket M0
+            ],
+            (0x239A, 0x0021): [
+                "METROM4BOOT",    # Metro M4 Express
+            ],
+            (0x239A, 0x0022): [
+                "ARCADE-D5",      # Feather Arcade D51
+                "FEATHERBOOT",    # Feather M4 Express
+            ],
+            (0x239A, 0x0024): [
+                "RADIOBOOT",      # Radiofruit M0
+            ],
+            (0x239A, 0x0027): [
+                "PIRKEYBOOT",     # pIRKey M0
+            ],
+            (0x239A, 0x0029): [
+                "ARGONBOOT  ",    # Argon
+                "BORONBOOT  ",    # Boron
+                "FTHR840BOOT",    # Feather nRF52840 Express
+                "MDBT50QBOOT",    # Raytac MDBT50Q-RX
+                "MDK840DONGL",    # MDK nRF52840 USB Dongle
+                "WS52840EVK",     # Waveshare nRF52840 Eval
+                "XENONBOOT  ",    # Xenon
+            ],
+            (0x239A, 0x002B): [
+                "ARCADE-D5",      # Itsy Arcade D51
+                "ITSYM4BOOT",     # ItsyBitsy M4 Express
+            ],
+            (0x239A, 0x002D): [
+                "CRICKITBOOT",    # crickit
+            ],
+            (0x239A, 0x002F): [
+                "TRELM4BOOT",     # Trellis M4 Express
+            ],
+            (0x239A, 0x0031): [
+                "GCM4BOOT",       # Grand Central M4 Express
+            ],
+            (0x239A, 0x0033): [
+                "PYBADGEBOOT",    # PyBadge
+            ],
+            (0x239A, 0x0034): [
+                "BADGELCBOOT",    # BadgeLC
+                "PEWBOOT",        # PewPew
+            ],
+            (0x239A, 0x0035): [
+                "MKRZEROBOOT",    # MKRZero
+                "PORTALBOOT",     # PyPortal M4 Express
+            ],
+            (0x239A, 0x0037): [
+                "METROM4BOOT",    # Metro M4 AirLift
+            ],
+            (0x239A, 0x003D): [
+                "PYGAMERBOOT",    # PyGamer
+            ],
+            (0x239A, 0x003F): [
+                "METR840BOOT",    # Metro nRF52840 Express
+            ],
+            (0x239A, 0x0045): [
+                "CPLAYBTBOOT",    # Circuit Playground nRF52840
+            ],
+            (0x239A, 0x0047): [
+                "MASKM4BOOT",     # Hallowing Mask M4
+            ],
+            (0x239A, 0x0049): [
+                "HALLOM4BOOT",    # HalloWing M4
+            ],
+            (0x239A, 0x004D): [
+                "SNEKBOOT",       # snekboard
+            ],
+            (0x239A, 0x0051): [
+                "ITSY840BOOT",    # ItsyBitsy nRF52840 Express
+            ],
+            (0x239A, 0x0057): [
+                "SERPENTBOOT",    # Serpente
+            ],
+            (0x239A, 0x0061): [
+                "SOLBOOT",        # Sol
+            ],
+            (0x239A, 0x0063): [
+                "NANO33BOOT",     # Nano 33 BLE
+            ],
+            (0x239A, 0x0065): [
+                "ND6BOOT",        # ndBit6
+            ],
+            (0x239A, 0x006B): [
+                "shIRtty",        # shIRtty
+            ],
+            (0x239A, 0x0071): [
+                "CLUEBOOT",       # CLUE nRF52840
+            ],
+            (0x239A, 0x0079): [
+                "ARAMBOOT",       # ARAMCON Badge 2019
+            ],
+            (0x239A, 0x007D): [
+                "BOOKBOOT",       # The Open Book Feather
+            ],
+            (0x239A, 0x007F): [
+                "BADGEBOOT",      # OHS2020 Badge
+            ],
+            (0x239A, 0x0087): [
+                "FTHRSNSBOOT",    # Feather nRF52840 Sense
+            ],
+            (0x239A, 0x0093): [
+                "ISVITABoot",     # IkigaiSense Vita nRF52840
+            ],
+            (0x239A, 0x0095): [
+                "UARTLOGBOOT",    # UARTLogger II
+            ],
+            (0x239A, 0x009F): [
+                "ADM840BOOT",     # AtelierDuMaker NRF52840 Breakout
+            ],
+            (0x239A, 0x00AF): [
+                "FLUFFBOOT",      # Fluff M0
+            ],
+            (0x239A, 0x00B3): [
+                "NICENANO",       # nice!nano
+            ],
+            (0x239A, 0x00B5): [
+                "E54XBOOT",       # SAME54 Xplained
+            ],
+            (0x239A, 0x00B9): [
+                "ND7BOOT",        # ndBit7
+            ],
+            (0x239A, 0x00BF): [
+                "BADGEBOOT",      # BLM Badge
+            ],
+            (0x239A, 0x00C3): [
+                "GEMINIBOOT",     # Gemini
+            ],
+            (0x239A, 0x00CB): [
+                "QTPY_BOOT",      # QT Py M0
+            ],
+            (0x239A, 0x00CD): [
+                "FTHRCANBOOT",    # Feather M4 CAN Express
+            ],
+            (0x239A, 0x00EF): [
+                "TRINKEYBOOT",    # NeoPixel Trinkey M0
+            ],
+            (0x239A, 0x00F5): [
+                "STARBOOT",       # Binary Star
+            ],
+            (0x239A, 0xB000): [
+                "HALLOWBOOT",     # Hallowing M0
+            ],
+            (0x239A, 0xE005): [
+                "HONKBOOT",       # Big Honking Button
+            ],
+            (0x2886, 0x000D): [
+                "Grove Zero",     # Grove Zero
+            ],
+            (0x2886, 0x002F): [
+                "Seeed XIAO",     # Seeeduino XIAO
+                "Arduino",        # Seeeduino XIAO
+            ],
+            (0x2886, 0xF00E): [
+                "PITAYAGO",       # Pitaya Go
+            ],
+            (0x2886, 0xF00F): [
+                "nRF52840M2",     # MakerDiary nRF52840 M.2 Module
+            ],
+            (0x3171, 0x0100): [
+                "CMDBOOT",        # COMMANDER
+            ],
+        },
+        "instructions": QCoreApplication.translate(
+            "UF2FlashDialog",
+            "<h3>CircuitPython Board</h3>"
+            "<p>In order to prepare the board for flashing follow these"
+            " steps:</p><ol>"
+            "<li>Switch your device to 'bootloader' mode by double-pressing"
+            " the reset button.</li>"
+            "<li>Wait until the device has entered 'bootloader' mode.</li>"
+            "<li>(If this does not happen, then try shorter or longer"
+            " pauses between presses.)</li>"
+            "<li>Ensure the boot volume is available (this may require"
+            " mounting it).</li>"
+            "<li>Select the firmware file to be flashed and click the"
+            " flash button.</li>"
+            "</ol>"
+        ),
+        "firmware": "CircuitPython",
+    },
+    
+    "rp2040": {
+        "volumes": [
+            
+        ],
+        "instructions": QCoreApplication.translate(
+            "UF2FlashDialog",
+            "<h3>Pi Pico (RP2040) Board</h3>"
+            "<p>In order to prepare the board for flashing follow these"
+            " steps:</p><ol>"
+            "<li>Plug in your board while holding the BOOTSEL button.</li>"
+            "<li>Wait until the device has entered 'bootloader' mode.</li>"
+            "<li>Ensure the boot volume is available (this may require"
+            " mounting it).</li>"
+            "<li>Select the firmware file to be flashed and click the"
+            " flash button.</li>"
+            "</ol>"
+        ),
+        "firmware": "MicroPython",
+    },
+}
+
+
+def getFoundDevices(boardType=""):
+    """
+    Function to get the list of known serial devices supporting UF2.
+    
+    @param boardType specific board type to search for
+    @type str
+    @return list of tuples with the board type, the port description, the
+        VID and PID
+    @rtype list of tuple of (str, str, int, int)
+    """
+    from PyQt5.QtSerialPort import QSerialPortInfo
+    
+    foundDevices = []
+    
+    availablePorts = QSerialPortInfo.availablePorts()
+    for port in availablePorts:
+        vid = port.vendorIdentifier()
+        pid = port.productIdentifier()
+        
+        if vid == 0 and pid == 0:
+            # no device detected at port
+            continue
+        
+        for board in SupportedUF2Boards:
+            if not boardType or (board == boardType):
+                if (vid, pid) in SupportedUF2Boards[board]["volumes"]:
+                    foundDevices.append((
+                        board,
+                        port.description(),
+                        (vid, pid),
+                    ))
+    
+    return foundDevices
+
+
+class UF2FlashDialog(QDialog, Ui_UF2FlashDialog):
+    """
+    Class implementing a dialog to flash any UF2 capable device.
+    """
+    DeviceTypeRole = Qt.UserRole
+    DeviceVidPidRole = Qt.UserRole + 1
+    
+    def __init__(self, boardType="", parent=None):
+        """
+        Constructor
+        
+        @param boardType specific board type to show the dialog for
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super(UF2FlashDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.refreshButton.setIcon(UI.PixmapCache.getIcon("rescan"))
+        
+        self.firmwarePicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.firmwarePicker.setFilters(
+            self.tr("MicroPython/CircuitPython Files (*.uf2);;"
+                    "All Files (*)"))
+        
+        self.bootPicker.setMode(E5PathPickerModes.DirectoryShowFilesMode)
+        self.bootPicker.setEnabled(False)
+        
+        self.__mandatoryStyleSheet = (
+            "QLineEdit {border: 2px solid;border-color: #800000}"
+        )
+        self.__manualType = "<manual>"
+        
+        self.__boardType = boardType
+        
+        self.__populate()
+        
+        self.__updateFlashButton()
+    
+    def __populate(self):
+        """
+        Private method to (re-)populate the dialog.
+        """
+        # save the currently selected device
+        currentDevice = self.devicesComboBox.currentText()
+        firmwareFile = self.firmwarePicker.text()
+        
+        # clear the entries first
+        self.devicesComboBox.clear()
+        self.firmwarePicker.clear()
+        self.bootPicker.clear()
+        self.infoLabel.clear()
+        self.infoEdit.clear()
+        
+        # now populate the entries with data
+        devices = getFoundDevices(boardType=self.__boardType)
+        if len(devices) == 0:
+            # no device detected
+            devices = filter(
+                lambda x: x[0] in SupportedUF2Boards,
+                MicroPythonDevices.getFoundDevices()[0]
+            )
+            if devices:
+                self.__showSpecificInstructions(list(devices))
+            else:
+                self.__showAllInstructions()
+            self.devicesComboBox.addItem("")
+            self.devicesComboBox.addItem(self.tr("Manual Select"))
+            self.devicesComboBox.setItemData(1, self.__manualType,
+                                             self.DeviceTypeRole)
+        elif len(devices) == 1:
+            self.devicesComboBox.addItem(devices[0][1])
+            self.devicesComboBox.setItemData(
+                0, devices[0][0], self.DeviceTypeRole)
+            self.devicesComboBox.setItemData(
+                0, devices[0][2], self.DeviceVidPidRole)
+            self.devicesComboBox.addItem(self.tr("Manual Select"))
+            self.devicesComboBox.setItemData(1, self.__manualType,
+                                             self.DeviceTypeRole)
+            self.on_devicesComboBox_currentIndexChanged(0)
+        else:
+            self.devicesComboBox.addItem("")
+            for index, (boardType, description,
+                        vidpid) in enumerate(sorted(devices), 1):
+                self.devicesComboBox.addItem(description)
+                self.devicesComboBox.setItemData(
+                    index, boardType, self.DeviceTypeRole)
+                self.devicesComboBox.setItemData(
+                    index, vidpid, self.DeviceVidPidRole)
+            self.devicesComboBox.addItem(self.tr("Manual Select"))
+            self.devicesComboBox.setItemData(index + 1, self.__manualType,
+                                             self.DeviceTypeRole)
+        
+        # reselect the remembered device, if it is still there
+        if currentDevice:
+            self.devicesComboBox.setCurrentText(currentDevice)
+            self.firmwarePicker.setText(firmwareFile)
+        else:
+            self.devicesComboBox.setCurrentIndex(0)
+    
+    def __updateFlashButton(self):
+        """
+        Private method to update the state of the Flash button and the retest
+        button.
+        """
+        firmwareFile = self.firmwarePicker.text()
+        if self.devicesComboBox.currentData(self.DeviceTypeRole) is not None:
+            if bool(firmwareFile) and os.path.exists(firmwareFile):
+                self.firmwarePicker.setStyleSheet("")
+            else:
+                self.firmwarePicker.setStyleSheet(self.__mandatoryStyleSheet)
+            
+            if bool(self.bootPicker.text()):
+                self.bootPicker.setStyleSheet("")
+            else:
+                self.bootPicker.setStyleSheet(self.__mandatoryStyleSheet)
+        else:
+            self.firmwarePicker.setStyleSheet("")
+            self.bootPicker.setStyleSheet("")
+        
+        enable = (
+            bool(self.bootPicker.text()) and
+            bool(firmwareFile) and
+            os.path.exists(firmwareFile)
+        )
+        self.flashButton.setEnabled(enable)
+    
+    def __showAllInstructions(self):
+        """
+        Private method to show instructions for resetting devices to bootloader
+        mode.
+        """
+        self.infoLabel.setText(self.tr("Reset Instructions:"))
+        
+        htmlText = self.tr(
+            "<h4>No known devices detected.</h4>"
+            "<p>Follow the appropriate instructions below to set <b>one</b>"
+            " board into 'bootloader' mode. Press <b>Refresh</b> when ready."
+            "</p>"
+        )
+        for boardType in SupportedUF2Boards:
+            htmlText += "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
+        self.infoEdit.setHtml(htmlText)
+    
+    def __showSpecificInstructions(self, devices):
+        """
+        Private method to show instructions for resetting devices to bootloader
+        mode for a list of detected devices.
+        
+        @param devices list of detected devices
+        @type list of str
+        """
+        boardTypes = set(x[0] for x in devices)
+        
+        self.infoLabel.setText(self.tr("Reset Instructions:"))
+        
+        if self.__boardType:
+            htmlText = self.tr(
+                "<h4>Flash {0} Firmware</h4>"
+                "<p>Follow the instructions below to set <b>one</b> board into"
+                " 'bootloader' mode. Press <b>Refresh</b> when ready.</p>"
+                "<hr/>{1}"
+            ).format(
+                SupportedUF2Boards[self.__boardType]["firmware"],
+                SupportedUF2Boards[self.__boardType]["instructions"],
+            )
+        else:
+            htmlText = self.tr(
+                "<h4>Potentially UF2 capable devices found</h4>"
+                "<p>Found these potentially UF2 capable devices:</p>"
+                "<ul><li>{0}</li></ul>"
+                "<p>Follow the instructions below to set <b>one</b> board into"
+                " 'bootloader' mode. Press <b>Refresh</b> when ready.</p>"
+            ).format(
+                "</li><li>".join(sorted(x[1] for x in devices))
+            )
+            for boardType in sorted(boardTypes):
+                htmlText += (
+                    "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
+                )
+        self.infoEdit.setHtml(htmlText)
+    
+    def __showTypedInstructions(self, boardType):
+        """
+        Private method to show instructions for resetting devices to bootloader
+        mode for a specific board type.
+        
+        @param boardType type of the board to show instructions for
+        @type str
+        """
+        self.infoLabel.setText(self.tr("Reset Instructions:"))
+        
+        htmlText = self.tr(
+            "<h4>No known devices detected.</h4>"
+            "<p>Follow the instructions below to set <b>one</b> board into"
+            " 'bootloader' mode. Press <b>Refresh</b> when ready.</p>"
+        )
+        htmlText += "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
+        self.infoEdit.setHtml(htmlText)
+    
+    def __showManualInstructions(self):
+        """
+        Private method to show instructions for flashing devices manually.
+        """
+        self.infoLabel.setText(self.tr("Flash Instructions:"))
+        
+        htmlText = self.tr(
+            "<h4>Flash method 'manual' selected.</h4>"
+            "<p>Follow the instructions below to flash a device by entering"
+            "the data manually.</p><ol>"
+            "<li>Change the device to 'bootloader' mode.</li>"
+            "<li>Wait until the device has entered 'bootloader' mode.</li>"
+            "<li>Ensure the boot volume is available (this may require"
+            " mounting it) and select its path.</li>"
+            "<li>Select the firmware file to be flashed and click the"
+            " flash button.</li>"
+            "</ol>"
+        )
+        for boardType in SupportedUF2Boards:
+            htmlText += "<hr/>" + SupportedUF2Boards[boardType]["instructions"]
+        self.infoEdit.setHtml(htmlText)
+    
+    def __showNoVolumeInformation(self, volumes):
+        """
+        Private method to show information about the expected boot volume(s).
+        
+        @param volumes list of expected volume names
+        @type list of str
+        """
+        self.infoLabel.setText(self.tr("Boot Volume not found:"))
+        
+        htmlText = self.tr(
+            "<h4>No Boot Volume detected.</h4>"
+            "<p>Please ensure that the boot volume of the device to be flashed"
+            " is available. "
+        )
+        if len(volumes) == 1:
+            htmlText += self.tr(
+                "This volume should be named <b>{0}</b>."
+                " Press <b>Refresh</b> when ready.</p>"
+            ).format(volumes[0])
+        else:
+            htmlText += self.tr(
+                "This volume should have one of these names.</p>"
+                "<ul><li>{0}</li></ul>"
+                "<p>Press <b>Refresh</b> when ready.</p>"
+            ).format("</li><li>".join(sorted(volumes)))
+        self.infoEdit.setHtml(htmlText)
+    
+    def __showMultipleVolumesInformation(self, volumePaths):
+        """
+        Private method to show information because multiple devices of the
+        same type are ready for flashing.
+        
+        Note: This is a dangerous situation!
+        
+        @param volumePaths list of volume paths
+        @type list of str
+        """
+        self.infoLabel.setText(self.tr("Multiple Boot Volumes found:"))
+        
+        htmlText = self.tr(
+            "<h4>Multiple Boot Volumes were found</h4>"
+            "<p>These volume paths were found.</p><ul><li>{0}</li></ul>"
+            "<p>Please ensure that only one device of a type is ready for"
+            " flashing. Press <b>Refresh</b> when ready.</p>"
+        ).format("</li><li>".join(sorted(volumePaths)))
+        self.infoEdit.setHtml(htmlText)
+    
+    @pyqtSlot()
+    def on_flashButton_clicked(self):
+        """
+        Private slot to flash the selected MicroPython or CircuitPython
+        firmware onto the device.
+        """
+        boardType = self.devicesComboBox.currentData(self.DeviceTypeRole)
+        firmwarePath = self.firmwarePicker.text()
+        volumePath = self.bootPicker.text()
+        if os.path.exists(firmwarePath) and os.path.exists(volumePath):
+            firmwareType = SupportedUF2Boards[boardType]["firmware"]
+            self.infoLabel.setText(
+                self.tr("Flashing {0}").format(firmwareType))
+            self.infoEdit.setHtml(self.tr(
+                "<p>Flashing the {0} firmware to the device. Please wait"
+                " until the device resets automatically</p>."
+            ).format(firmwareType))
+            QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
+            shutil.copy2(firmwarePath, volumePath)
+            QThread.sleep(1)
+            self.on_refreshButton_clicked()
+    
+    @pyqtSlot()
+    def on_refreshButton_clicked(self):
+        """
+        Private slot to refresh the dialog.
+        """
+        self.__populate()
+    
+    @pyqtSlot(int)
+    def on_devicesComboBox_currentIndexChanged(self, index):
+        """
+        Private slot to handle the selection of a board.
+        
+        @param index selected index
+        @type int
+        """
+        vidpid = self.devicesComboBox.itemData(index, self.DeviceVidPidRole)
+        boardType = self.devicesComboBox.itemData(index, self.DeviceTypeRole)
+        
+        self.bootPicker.setEnabled(boardType == self.__manualType)
+        if boardType == self.__manualType:
+            self.__showManualInstructions()
+        
+        if vidpid is None:
+            if boardType is None:
+                self.bootPicker.clear()
+        else:
+            volumes = SupportedUF2Boards[boardType]["volumes"][vidpid]
+            foundVolumes = []
+            for volume in volumes:
+                foundVolumes += Utilities.findVolume(volume, findAll=True)
+            
+            if len(foundVolumes) == 0:
+                self.__showNoVolumeInformation(volumes)
+                self.bootPicker.clear()
+            elif len(foundVolumes) == 1:
+                self.bootPicker.setText(foundVolumes[0])
+            else:
+                self.__showMultipleVolumesInformation()
+                self.bootPicker.clear()
+        
+        self.__updateFlashButton()
+    
+    @pyqtSlot(str)
+    def on_firmwarePicker_textChanged(self, text):
+        """
+        Private slot handling a change of the firmware file.
+        
+        @param text current text of the firmware edit
+        @type str
+        """
+        self.__updateFlashButton()
+    
+    @pyqtSlot(str)
+    def on_bootPicker_textChanged(self, text):
+        """
+        Private slot handling a change of the boot volume.
+        
+        @param text current text of the boot volume edit
+        @type str
+        """
+        self.__updateFlashButton()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/UF2FlashDialog.ui	Fri Feb 12 16:15:18 2021 +0100
@@ -0,0 +1,209 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>UF2FlashDialog</class>
+ <widget class="QDialog" name="UF2FlashDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Flash UF2 Device</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="0" column="0">
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Detected Devices:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QComboBox" name="devicesComboBox">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="toolTip">
+        <string>Select the device to be flashed</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_3">
+       <property name="text">
+        <string>MicroPython:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="E5PathPicker" name="firmwarePicker" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="focusPolicy">
+        <enum>Qt::WheelFocus</enum>
+       </property>
+       <property name="toolTip">
+        <string>Enter the path of the MicroPython / CircuitPython firmware file</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>'Boot' Path:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="E5PathPicker" name="bootPicker" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="focusPolicy">
+        <enum>Qt::WheelFocus</enum>
+       </property>
+       <property name="toolTip">
+        <string>Enter the path of the bootloader volume</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QPushButton" name="flashButton">
+     <property name="text">
+      <string>Flash MicroPython / CircuitPython</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QFrame" name="infoFrame">
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="infoLabel">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QTextEdit" name="infoEdit">
+        <property name="readOnly">
+         <bool>false</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="refreshButton">
+       <property name="text">
+        <string>Refresh</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Close</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>devicesComboBox</tabstop>
+  <tabstop>firmwarePicker</tabstop>
+  <tabstop>bootPicker</tabstop>
+  <tabstop>flashButton</tabstop>
+  <tabstop>infoEdit</tabstop>
+  <tabstop>refreshButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>UF2FlashDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>480</x>
+     <y>576</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>596</x>
+     <y>523</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>UF2FlashDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>448</x>
+     <y>578</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>598</x>
+     <y>454</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

eric ide

mercurial