MicroPython eric7

Mon, 06 Feb 2023 10:09:18 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 06 Feb 2023 10:09:18 +0100
branch
eric7
changeset 9740
90072e10ae9b
parent 9739
d527cfe919ae
child 9741
901caff48307

MicroPython
- Added functionality to update modules of CircuitPython devices with `circup`
(adapted for use within eric-ide).

docs/changelog.md file | annotate | diff | comparison | revisions
eric7.epj file | annotate | diff | comparison | revisions
src/eric7/APIs/Python3/eric7.api file | annotate | diff | comparison | revisions
src/eric7/APIs/Python3/eric7.bas file | annotate | diff | comparison | revisions
src/eric7/Documentation/Help/source.qch file | annotate | diff | comparison | revisions
src/eric7/Documentation/Help/source.qhp file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.Globals.AppInfo.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonDevices.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/index-eric7.MicroPython.CircuitPythonUpdater.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/index-eric7.MicroPython.html file | annotate | diff | comparison | revisions
src/eric7/DocumentationTools/IndexGenerator.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonDevices.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/CircuitPythonUpdaterInterface.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/CircupFunctions.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/RequirementsDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/RequirementsDialog.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowBundlesDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowBundlesDialog.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowInstalledDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowInstalledDialog.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowModulesDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowModulesDialog.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowOutdatedDialog.py file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/ShowOutdatedDialog.ui file | annotate | diff | comparison | revisions
src/eric7/MicroPython/CircuitPythonUpdater/__init__.py file | annotate | diff | comparison | revisions
src/eric7/PipInterface/PipFreezeDialog.py file | annotate | diff | comparison | revisions
src/eric7/PipInterface/PipFreezeDialog.ui file | annotate | diff | comparison | revisions
src/eric7/eric7_doc.py file | annotate | diff | comparison | revisions
--- a/docs/changelog.md	Mon Feb 06 10:02:35 2023 +0100
+++ b/docs/changelog.md	Mon Feb 06 10:09:18 2023 +0100
@@ -6,6 +6,8 @@
     - Updated the list of known CircuitPython boards.
     - Updated the list of known UF2 capable boards.
     - Some smaller enhancements for CircuitPython devices.
+    - Added functionality to update modules of CircuitPython devices with `circup`
+      (adapted for use within eric-ide).
 - PDF Viewer
     - added a tool based on `QtPdf` and `QtPdfWidgets` to show the contents of PDF files
 
--- a/eric7.epj	Mon Feb 06 10:02:35 2023 +0100
+++ b/eric7.epj	Mon Feb 06 10:09:18 2023 +0100
@@ -333,6 +333,11 @@
       "src/eric7/JediInterface/RefactoringPreviewDialog.ui",
       "src/eric7/MicroPython/AddEditDevicesDialog.ui",
       "src/eric7/MicroPython/BoardDataDialog.ui",
+      "src/eric7/MicroPython/CircuitPythonUpdater/RequirementsDialog.ui",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowBundlesDialog.ui",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowInstalledDialog.ui",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowModulesDialog.ui",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowOutdatedDialog.ui",
       "src/eric7/MicroPython/ConnectionSelectionDialog.ui",
       "src/eric7/MicroPython/EspBackupRestoreFirmwareDialog.ui",
       "src/eric7/MicroPython/EspFirmwareSelectionDialog.ui",
@@ -1276,6 +1281,14 @@
       "src/eric7/MicroPython/AddEditDevicesDialog.py",
       "src/eric7/MicroPython/BoardDataDialog.py",
       "src/eric7/MicroPython/CircuitPythonDevices.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/CircuitPythonUpdaterInterface.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/CircupFunctions.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/RequirementsDialog.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowBundlesDialog.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowInstalledDialog.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowModulesDialog.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/ShowOutdatedDialog.py",
+      "src/eric7/MicroPython/CircuitPythonUpdater/__init__.py",
       "src/eric7/MicroPython/ConnectionSelectionDialog.py",
       "src/eric7/MicroPython/EspBackupRestoreFirmwareDialog.py",
       "src/eric7/MicroPython/EspDevices.py",
--- a/src/eric7/APIs/Python3/eric7.api	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/APIs/Python3/eric7.api	Mon Feb 06 10:09:18 2023 +0100
@@ -2554,8 +2554,35 @@
 eric7.MicroPython.CircuitPythonDevices.CircuitPythonDevice.runScript?4(script)
 eric7.MicroPython.CircuitPythonDevices.CircuitPythonDevice.setButtons?4()
 eric7.MicroPython.CircuitPythonDevices.CircuitPythonDevice.supportsLocalFileAccess?4()
-eric7.MicroPython.CircuitPythonDevices.CircuitPythonDevice?1(microPythonWidget, deviceType, parent=None)
-eric7.MicroPython.CircuitPythonDevices.createDevice?4(microPythonWidget, deviceType, vid, pid)
+eric7.MicroPython.CircuitPythonDevices.CircuitPythonDevice?1(microPythonWidget, deviceType, boardName, parent=None)
+eric7.MicroPython.CircuitPythonDevices.createDevice?4(microPythonWidget, deviceType, vid, pid, boardName)
+eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.CircuitPythonUpdaterInterface.installCircup?4()
+eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.CircuitPythonUpdaterInterface.populateMenu?4(menu)
+eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.CircuitPythonUpdaterInterface?1(device, parent=None)
+eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.isCircupAvailable?4()
+eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.ensure_latest_bundle?4(bundle)
+eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.find_modules?4(device_path, bundles_list)
+eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.get_circuitpython_version?4(device_path)
+eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.install_module?4(device_path, device_modules, name, py, mod_names)
+eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.patch_circup?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_buttonBox_clicked?4(button)
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_copyButton_clicked?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_insertButton_clicked?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_replaceAllButton_clicked?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_replaceSelectionButton_clicked?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_requirementsEdit_textChanged?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_requirementsFilePicker_textChanged?4(txt)
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_saveButton_clicked?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog.on_saveToButton_clicked?4()
+eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.RequirementsDialog?1(devicePath, parent=None)
+eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.ShowBundlesDialog?1(withModules, parent=None)
+eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.ShowInstalledDialog?1(devicePath, parent=None)
+eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.ShowModulesDialog.getSelection?4()
+eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.ShowModulesDialog.on_modulesList_itemChanged?4(item)
+eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.ShowModulesDialog?1(selectionMode=False, parent=None)
+eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.ShowOutdatedDialog.getSelection?4()
+eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.ShowOutdatedDialog.on_modulesList_itemChanged?4(item, column)
+eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.ShowOutdatedDialog?1(devicePath, selectionMode=False, parent=None)
 eric7.MicroPython.ConnectionSelectionDialog.ConnectionSelectionDialog.PortNameRole?7
 eric7.MicroPython.ConnectionSelectionDialog.ConnectionSelectionDialog.VidPidRole?7
 eric7.MicroPython.ConnectionSelectionDialog.ConnectionSelectionDialog.getData?4()
@@ -2583,7 +2610,7 @@
 eric7.MicroPython.EspDevices.EspDevice.runScript?4(script)
 eric7.MicroPython.EspDevices.EspDevice.setButtons?4()
 eric7.MicroPython.EspDevices.EspDevice?1(microPythonWidget, deviceType, parent=None)
-eric7.MicroPython.EspDevices.createDevice?4(microPythonWidget, deviceType, vid, pid)
+eric7.MicroPython.EspDevices.createDevice?4(microPythonWidget, deviceType, vid, pid, boardName)
 eric7.MicroPython.EspFirmwareSelectionDialog.EspFirmwareSelectionDialog.Chips?7
 eric7.MicroPython.EspFirmwareSelectionDialog.EspFirmwareSelectionDialog.FlashAddresses?7
 eric7.MicroPython.EspFirmwareSelectionDialog.EspFirmwareSelectionDialog.FlashModes?7
@@ -2602,7 +2629,7 @@
 eric7.MicroPython.GenericMicroPythonDevices.GenericMicroPythonDevice.setButtons?4()
 eric7.MicroPython.GenericMicroPythonDevices.GenericMicroPythonDevice.supportsLocalFileAccess?4()
 eric7.MicroPython.GenericMicroPythonDevices.GenericMicroPythonDevice?1(microPythonWidget, deviceType, vid, pid, parent=None)
-eric7.MicroPython.GenericMicroPythonDevices.createDevice?4(microPythonWidget, deviceType, vid, pid)
+eric7.MicroPython.GenericMicroPythonDevices.createDevice?4(microPythonWidget, deviceType, vid, pid, boardName)
 eric7.MicroPython.IgnoredDevicesDialog.IgnoredDevicesDialog.getDevices?4()
 eric7.MicroPython.IgnoredDevicesDialog.IgnoredDevicesDialog?1(deviceList, parent=None)
 eric7.MicroPython.MicroPythonCommandsInterface.MicroPythonCommandsInterface.cd?4(dirname)
@@ -2661,7 +2688,7 @@
 eric7.MicroPython.MicroPythonDevices.MicroPythonDevice.supportsLocalFileAccess?4()
 eric7.MicroPython.MicroPythonDevices.MicroPythonDevice?1(microPythonWidget, deviceType, parent=None)
 eric7.MicroPython.MicroPythonDevices.SupportedBoards?7
-eric7.MicroPython.MicroPythonDevices.getDevice?4(deviceType, microPythonWidget, vid, pid)
+eric7.MicroPython.MicroPythonDevices.getDevice?4(deviceType, microPythonWidget, vid, pid, boardName="")
 eric7.MicroPython.MicroPythonDevices.getDeviceIcon?4(boardName, iconFormat=True)
 eric7.MicroPython.MicroPythonDevices.getFoundDevices?4()
 eric7.MicroPython.MicroPythonDevices.getSupportedDevices?4()
@@ -2773,7 +2800,7 @@
 eric7.MicroPython.MicrobitDevices.MicrobitDevice.runScript?4(script)
 eric7.MicroPython.MicrobitDevices.MicrobitDevice.setButtons?4()
 eric7.MicroPython.MicrobitDevices.MicrobitDevice?1(microPythonWidget, deviceType, parent=None)
-eric7.MicroPython.MicrobitDevices.createDevice?4(microPythonWidget, deviceType, vid, pid)
+eric7.MicroPython.MicrobitDevices.createDevice?4(microPythonWidget, deviceType, vid, pid, boardName)
 eric7.MicroPython.PyBoardDevices.PyBoardDevice.DeviceVolumeName?7
 eric7.MicroPython.PyBoardDevices.PyBoardDevice.FlashInstructionsURL?7
 eric7.MicroPython.PyBoardDevices.PyBoardDevice.addDeviceMenuEntries?4(menu)
@@ -2791,7 +2818,7 @@
 eric7.MicroPython.PyBoardDevices.PyBoardDevice.setButtons?4()
 eric7.MicroPython.PyBoardDevices.PyBoardDevice.supportsLocalFileAccess?4()
 eric7.MicroPython.PyBoardDevices.PyBoardDevice?1(microPythonWidget, deviceType, parent=None)
-eric7.MicroPython.PyBoardDevices.createDevice?4(microPythonWidget, deviceType, vid, pid)
+eric7.MicroPython.PyBoardDevices.createDevice?4(microPythonWidget, deviceType, vid, pid, boardName)
 eric7.MicroPython.RP2040Devices.RP2040Device.addDeviceMenuEntries?4(menu)
 eric7.MicroPython.RP2040Devices.RP2040Device.canRunScript?4()
 eric7.MicroPython.RP2040Devices.RP2040Device.canStartFileManager?4()
@@ -2805,7 +2832,7 @@
 eric7.MicroPython.RP2040Devices.RP2040Device.runScript?4(script)
 eric7.MicroPython.RP2040Devices.RP2040Device.setButtons?4()
 eric7.MicroPython.RP2040Devices.RP2040Device?1(microPythonWidget, deviceType, parent=None)
-eric7.MicroPython.RP2040Devices.createDevice?4(microPythonWidget, deviceType, vid, pid)
+eric7.MicroPython.RP2040Devices.createDevice?4(microPythonWidget, deviceType, vid, pid, boardName)
 eric7.MicroPython.UF2FlashDialog.SupportedUF2Boards?7
 eric7.MicroPython.UF2FlashDialog.UF2FlashDialog.DeviceTypeRole?7
 eric7.MicroPython.UF2FlashDialog.UF2FlashDialog.DeviceVidPidRole?7
--- a/src/eric7/APIs/Python3/eric7.bas	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/APIs/Python3/eric7.bas	Mon Feb 06 10:09:18 2023 +0100
@@ -80,6 +80,7 @@
 ChatWidget QWidget Ui_ChatWidget
 ChromeImporter BookmarksImporter
 CircuitPythonDevice MicroPythonDevice
+CircuitPythonUpdaterInterface QObject
 Class ClbrBaseClasses.Class VisibilityMixin
 ClassDecoratorType enum.Enum
 ClassItem UMLItem
@@ -856,6 +857,7 @@
 ReloadStopButton EricToolButton
 RemoveBookmarksCommand QUndoCommand
 ReqPackage Package
+RequirementsDialog QDialog Ui_RequirementsDialog
 RestructuredTextProvider MarkupBase
 ReturnVisitor ast.NodeVisitor
 ReversedPackageDAG PackageDAG
@@ -892,6 +894,10 @@
 ShortcutsDialog QDialog Ui_ShortcutsDialog
 ShortcutsFile QObject
 ShortcutsReader XMLStreamReaderBase
+ShowBundlesDialog QDialog Ui_ShowBundlesDialog
+ShowInstalledDialog QDialog Ui_ShowInstalledDialog
+ShowModulesDialog QDialog Ui_ShowModulesDialog
+ShowOutdatedDialog QDialog Ui_ShowOutdatedDialog
 SimplePatternExtension Extension
 SimplifyNodeVisitor ast.NodeVisitor
 SingleApplicationServer QLocalServer
Binary file src/eric7/Documentation/Help/source.qch has changed
--- a/src/eric7/Documentation/Help/source.qhp	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/Documentation/Help/source.qhp	Mon Feb 06 10:09:18 2023 +0100
@@ -267,6 +267,15 @@
             <section title="eric7.JediInterface.RefactoringPreviewDialog" ref="eric7.JediInterface.RefactoringPreviewDialog.html" />
           </section>
           <section title="eric7.MicroPython" ref="index-eric7.MicroPython.html">
+            <section title="eric7.MicroPython.CircuitPythonUpdater" ref="index-eric7.MicroPython.CircuitPythonUpdater.html">
+              <section title="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html" />
+              <section title="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions" ref="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html" />
+              <section title="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html" />
+              <section title="eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html" />
+              <section title="eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html" />
+              <section title="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html" />
+              <section title="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html" />
+            </section>
             <section title="eric7.MicroPython.AddEditDevicesDialog" ref="eric7.MicroPython.AddEditDevicesDialog.html" />
             <section title="eric7.MicroPython.BoardDataDialog" ref="eric7.MicroPython.BoardDataDialog.html" />
             <section title="eric7.MicroPython.CircuitPythonDevices" ref="eric7.MicroPython.CircuitPythonDevices.html" />
@@ -2684,7 +2693,9 @@
       <keyword name="ChromeImporter.setPath" id="ChromeImporter.setPath" ref="eric7.WebBrowser.Bookmarks.BookmarksImporters.ChromeImporter.html#ChromeImporter.setPath" />
       <keyword name="CircuitPythonDevice" id="CircuitPythonDevice" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice" />
       <keyword name="CircuitPythonDevice (Constructor)" id="CircuitPythonDevice (Constructor)" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.__init__" />
+      <keyword name="CircuitPythonDevice.__aboutToShowLibraryMenu" id="CircuitPythonDevice.__aboutToShowLibraryMenu" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.__aboutToShowLibraryMenu" />
       <keyword name="CircuitPythonDevice.__deviceVolumeMounted" id="CircuitPythonDevice.__deviceVolumeMounted" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.__deviceVolumeMounted" />
+      <keyword name="CircuitPythonDevice.__findDeviceDirectories" id="CircuitPythonDevice.__findDeviceDirectories" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.__findDeviceDirectories" />
       <keyword name="CircuitPythonDevice.__findWorkspace" id="CircuitPythonDevice.__findWorkspace" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.__findWorkspace" />
       <keyword name="CircuitPythonDevice.__flashCircuitPython" id="CircuitPythonDevice.__flashCircuitPython" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.__flashCircuitPython" />
       <keyword name="CircuitPythonDevice.__flashTeensy" id="CircuitPythonDevice.__flashTeensy" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.__flashTeensy" />
@@ -2704,6 +2715,30 @@
       <keyword name="CircuitPythonDevice.setButtons" id="CircuitPythonDevice.setButtons" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.setButtons" />
       <keyword name="CircuitPythonDevice.supportsLocalFileAccess" id="CircuitPythonDevice.supportsLocalFileAccess" ref="eric7.MicroPython.CircuitPythonDevices.html#CircuitPythonDevice.supportsLocalFileAccess" />
       <keyword name="CircuitPythonDevices (Module)" id="CircuitPythonDevices (Module)" ref="eric7.MicroPython.CircuitPythonDevices.html" />
+      <keyword name="CircuitPythonUpdater (Package)" id="CircuitPythonUpdater (Package)" ref="index-eric7.MicroPython.CircuitPythonUpdater.html" />
+      <keyword name="CircuitPythonUpdaterInterface" id="CircuitPythonUpdaterInterface" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface" />
+      <keyword name="CircuitPythonUpdaterInterface (Constructor)" id="CircuitPythonUpdaterInterface (Constructor)" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__init__" />
+      <keyword name="CircuitPythonUpdaterInterface (Module)" id="CircuitPythonUpdaterInterface (Module)" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html" />
+      <keyword name="CircuitPythonUpdaterInterface.__aboutCircup" id="CircuitPythonUpdaterInterface.__aboutCircup" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__aboutCircup" />
+      <keyword name="CircuitPythonUpdaterInterface.__addBundle" id="CircuitPythonUpdaterInterface.__addBundle" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__addBundle" />
+      <keyword name="CircuitPythonUpdaterInterface.__doUpdateModules" id="CircuitPythonUpdaterInterface.__doUpdateModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__doUpdateModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__generateRequirements" id="CircuitPythonUpdaterInterface.__generateRequirements" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__generateRequirements" />
+      <keyword name="CircuitPythonUpdaterInterface.__installFromAvailable" id="CircuitPythonUpdaterInterface.__installFromAvailable" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__installFromAvailable" />
+      <keyword name="CircuitPythonUpdaterInterface.__installFromCode" id="CircuitPythonUpdaterInterface.__installFromCode" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__installFromCode" />
+      <keyword name="CircuitPythonUpdaterInterface.__installModules" id="CircuitPythonUpdaterInterface.__installModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__installModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__installRequirements" id="CircuitPythonUpdaterInterface.__installRequirements" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__installRequirements" />
+      <keyword name="CircuitPythonUpdaterInterface.__listOutdatedModules" id="CircuitPythonUpdaterInterface.__listOutdatedModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__listOutdatedModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__removeBundle" id="CircuitPythonUpdaterInterface.__removeBundle" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__removeBundle" />
+      <keyword name="CircuitPythonUpdaterInterface.__showAvailableModules" id="CircuitPythonUpdaterInterface.__showAvailableModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__showAvailableModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__showBundles" id="CircuitPythonUpdaterInterface.__showBundles" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__showBundles" />
+      <keyword name="CircuitPythonUpdaterInterface.__showBundlesModules" id="CircuitPythonUpdaterInterface.__showBundlesModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__showBundlesModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__showInstalledModules" id="CircuitPythonUpdaterInterface.__showInstalledModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__showInstalledModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__uninstallModules" id="CircuitPythonUpdaterInterface.__uninstallModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__uninstallModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__updateAllModules" id="CircuitPythonUpdaterInterface.__updateAllModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__updateAllModules" />
+      <keyword name="CircuitPythonUpdaterInterface.__updateModules" id="CircuitPythonUpdaterInterface.__updateModules" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.__updateModules" />
+      <keyword name="CircuitPythonUpdaterInterface.installCircup" id="CircuitPythonUpdaterInterface.installCircup" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.installCircup" />
+      <keyword name="CircuitPythonUpdaterInterface.populateMenu" id="CircuitPythonUpdaterInterface.populateMenu" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#CircuitPythonUpdaterInterface.populateMenu" />
+      <keyword name="CircupFunctions (Module)" id="CircupFunctions (Module)" ref="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html" />
       <keyword name="Class" id="Class" ref="eric7.Utilities.ClassBrowsers.ClbrBaseClasses.html#Class" />
       <keyword name="Class" id="Class" ref="eric7.Utilities.ClassBrowsers.pyclbr.html#Class" />
       <keyword name="Class" id="Class" ref="eric7.Utilities.ClassBrowsers.rbclbr.html#Class" />
@@ -13637,6 +13672,21 @@
       <keyword name="ReqPackage.render_as_branch" id="ReqPackage.render_as_branch" ref="eric7.PipInterface.pipdeptree.html#ReqPackage.render_as_branch" />
       <keyword name="ReqPackage.render_as_root" id="ReqPackage.render_as_root" ref="eric7.PipInterface.pipdeptree.html#ReqPackage.render_as_root" />
       <keyword name="ReqPackage.version_spec" id="ReqPackage.version_spec" ref="eric7.PipInterface.pipdeptree.html#ReqPackage.version_spec" />
+      <keyword name="RequirementsDialog" id="RequirementsDialog" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog" />
+      <keyword name="RequirementsDialog (Constructor)" id="RequirementsDialog (Constructor)" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.__init__" />
+      <keyword name="RequirementsDialog (Module)" id="RequirementsDialog (Module)" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html" />
+      <keyword name="RequirementsDialog.__generateRequirements" id="RequirementsDialog.__generateRequirements" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.__generateRequirements" />
+      <keyword name="RequirementsDialog.__updateButtons" id="RequirementsDialog.__updateButtons" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.__updateButtons" />
+      <keyword name="RequirementsDialog.__writeToFile" id="RequirementsDialog.__writeToFile" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.__writeToFile" />
+      <keyword name="RequirementsDialog.on_buttonBox_clicked" id="RequirementsDialog.on_buttonBox_clicked" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_buttonBox_clicked" />
+      <keyword name="RequirementsDialog.on_copyButton_clicked" id="RequirementsDialog.on_copyButton_clicked" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_copyButton_clicked" />
+      <keyword name="RequirementsDialog.on_insertButton_clicked" id="RequirementsDialog.on_insertButton_clicked" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_insertButton_clicked" />
+      <keyword name="RequirementsDialog.on_replaceAllButton_clicked" id="RequirementsDialog.on_replaceAllButton_clicked" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_replaceAllButton_clicked" />
+      <keyword name="RequirementsDialog.on_replaceSelectionButton_clicked" id="RequirementsDialog.on_replaceSelectionButton_clicked" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_replaceSelectionButton_clicked" />
+      <keyword name="RequirementsDialog.on_requirementsEdit_textChanged" id="RequirementsDialog.on_requirementsEdit_textChanged" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_requirementsEdit_textChanged" />
+      <keyword name="RequirementsDialog.on_requirementsFilePicker_textChanged" id="RequirementsDialog.on_requirementsFilePicker_textChanged" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_requirementsFilePicker_textChanged" />
+      <keyword name="RequirementsDialog.on_saveButton_clicked" id="RequirementsDialog.on_saveButton_clicked" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_saveButton_clicked" />
+      <keyword name="RequirementsDialog.on_saveToButton_clicked" id="RequirementsDialog.on_saveToButton_clicked" ref="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html#RequirementsDialog.on_saveToButton_clicked" />
       <keyword name="RestructuredTextProvider" id="RestructuredTextProvider" ref="eric7.QScintilla.MarkupProviders.RestructuredTextProvider.html#RestructuredTextProvider" />
       <keyword name="RestructuredTextProvider (Constructor)" id="RestructuredTextProvider (Constructor)" ref="eric7.QScintilla.MarkupProviders.RestructuredTextProvider.html#RestructuredTextProvider.__init__" />
       <keyword name="RestructuredTextProvider (Module)" id="RestructuredTextProvider (Module)" ref="eric7.QScintilla.MarkupProviders.RestructuredTextProvider.html" />
@@ -14330,6 +14380,25 @@
       <keyword name="ShortcutsReader.__readShortCut" id="ShortcutsReader.__readShortCut" ref="eric7.EricXML.ShortcutsReader.html#ShortcutsReader.__readShortCut" />
       <keyword name="ShortcutsReader.getShortcuts" id="ShortcutsReader.getShortcuts" ref="eric7.EricXML.ShortcutsReader.html#ShortcutsReader.getShortcuts" />
       <keyword name="ShortcutsReader.readXML" id="ShortcutsReader.readXML" ref="eric7.EricXML.ShortcutsReader.html#ShortcutsReader.readXML" />
+      <keyword name="ShowBundlesDialog" id="ShowBundlesDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html#ShowBundlesDialog" />
+      <keyword name="ShowBundlesDialog (Constructor)" id="ShowBundlesDialog (Constructor)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html#ShowBundlesDialog.__init__" />
+      <keyword name="ShowBundlesDialog (Module)" id="ShowBundlesDialog (Module)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html" />
+      <keyword name="ShowInstalledDialog" id="ShowInstalledDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html#ShowInstalledDialog" />
+      <keyword name="ShowInstalledDialog (Constructor)" id="ShowInstalledDialog (Constructor)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html#ShowInstalledDialog.__init__" />
+      <keyword name="ShowInstalledDialog (Module)" id="ShowInstalledDialog (Module)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html" />
+      <keyword name="ShowModulesDialog" id="ShowModulesDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html#ShowModulesDialog" />
+      <keyword name="ShowModulesDialog (Constructor)" id="ShowModulesDialog (Constructor)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html#ShowModulesDialog.__init__" />
+      <keyword name="ShowModulesDialog (Module)" id="ShowModulesDialog (Module)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html" />
+      <keyword name="ShowModulesDialog.__applyFilter" id="ShowModulesDialog.__applyFilter" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html#ShowModulesDialog.__applyFilter" />
+      <keyword name="ShowModulesDialog.__checkCountUpdated" id="ShowModulesDialog.__checkCountUpdated" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html#ShowModulesDialog.__checkCountUpdated" />
+      <keyword name="ShowModulesDialog.getSelection" id="ShowModulesDialog.getSelection" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html#ShowModulesDialog.getSelection" />
+      <keyword name="ShowModulesDialog.on_modulesList_itemChanged" id="ShowModulesDialog.on_modulesList_itemChanged" ref="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html#ShowModulesDialog.on_modulesList_itemChanged" />
+      <keyword name="ShowOutdatedDialog" id="ShowOutdatedDialog" ref="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html#ShowOutdatedDialog" />
+      <keyword name="ShowOutdatedDialog (Constructor)" id="ShowOutdatedDialog (Constructor)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html#ShowOutdatedDialog.__init__" />
+      <keyword name="ShowOutdatedDialog (Module)" id="ShowOutdatedDialog (Module)" ref="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html" />
+      <keyword name="ShowOutdatedDialog.__checkCountUpdated" id="ShowOutdatedDialog.__checkCountUpdated" ref="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html#ShowOutdatedDialog.__checkCountUpdated" />
+      <keyword name="ShowOutdatedDialog.getSelection" id="ShowOutdatedDialog.getSelection" ref="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html#ShowOutdatedDialog.getSelection" />
+      <keyword name="ShowOutdatedDialog.on_modulesList_itemChanged" id="ShowOutdatedDialog.on_modulesList_itemChanged" ref="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html#ShowOutdatedDialog.on_modulesList_itemChanged" />
       <keyword name="SimplePatternExtension" id="SimplePatternExtension" ref="eric7.UI.Previewers.MarkdownExtensions.html#SimplePatternExtension" />
       <keyword name="SimplePatternExtension.extendMarkdown" id="SimplePatternExtension.extendMarkdown" ref="eric7.UI.Previewers.MarkdownExtensions.html#SimplePatternExtension.extendMarkdown" />
       <keyword name="Simplify (Package)" id="Simplify (Package)" ref="index-eric7.Plugins.CheckerPlugins.CodeStyleChecker.Simplify.html" />
@@ -18991,6 +19060,7 @@
       <keyword name="encryptData" id="encryptData" ref="eric7.Utilities.crypto.py3AES.html#encryptData" />
       <keyword name="engineDisplayName" id="engineDisplayName" ref="eric7.Plugins.UiExtensionPlugins.Translator.TranslatorEngines.__init__.html#engineDisplayName" />
       <keyword name="ensureUniqueFilename" id="ensureUniqueFilename" ref="eric7.WebBrowser.Tools.WebBrowserTools.html#ensureUniqueFilename" />
+      <keyword name="ensure_latest_bundle" id="ensure_latest_bundle" ref="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html#ensure_latest_bundle" />
       <keyword name="enum_key_to_value" id="enum_key_to_value" ref="eric7.PipInterface.piplicenses.html#enum_key_to_value" />
       <keyword name="eradicate (Module)" id="eradicate (Module)" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.Miscellaneous.eradicate.html" />
       <keyword name="eric6SettingsName" id="eric6SettingsName" ref="eric7.Preferences.__init__.html#eric6SettingsName" />
@@ -19057,6 +19127,7 @@
       <keyword name="find_license_from_classifier" id="find_license_from_classifier" ref="eric7.PipInterface.piplicenses.html#find_license_from_classifier" />
       <keyword name="find_module" id="find_module" ref="eric7.Utilities.ClassBrowsers.__init__.html#find_module" />
       <keyword name="find_module" id="find_module" ref="eric7.Utilities.ModuleParser.html#find_module" />
+      <keyword name="find_modules" id="find_modules" ref="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html#find_modules" />
       <keyword name="flaskDebug (Module)" id="flaskDebug (Module)" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.Security.Checks.flaskDebug.html" />
       <keyword name="formatTime" id="formatTime" ref="eric7.Plugins.VcsPlugins.vcsPySvn.SvnUtilities.html#formatTime" />
       <keyword name="formatargvalues" id="formatargvalues" ref="eric7.DebugClients.Python.DebugUtilities.html#formatargvalues" />
@@ -19290,6 +19361,7 @@
       <keyword name="getWebEngineVersions" id="getWebEngineVersions" ref="eric7.WebBrowser.Tools.WebBrowserTools.html#getWebEngineVersions" />
       <keyword name="getWindowsExecutablePath" id="getWindowsExecutablePath" ref="eric7.SystemUtilities.FileSystemUtilities.html#getWindowsExecutablePath" />
       <keyword name="getWinregEntry" id="getWinregEntry" ref="eric7.eric7_post_install.html#getWinregEntry" />
+      <keyword name="get_circuitpython_version" id="get_circuitpython_version" ref="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html#get_circuitpython_version" />
       <keyword name="get_class_members" id="get_class_members" ref="eric7.DebugClients.Python.FlexCompleter.html#get_class_members" />
       <keyword name="get_coding" id="get_coding" ref="eric7.Utilities.__init__.html#get_coding" />
       <keyword name="get_codingBytes" id="get_codingBytes" ref="eric7.Utilities.__init__.html#get_codingBytes" />
@@ -19353,6 +19425,7 @@
       <keyword name="insecureHashlibNew (Module)" id="insecureHashlibNew (Module)" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.Security.Checks.insecureHashlibNew.html" />
       <keyword name="insecureSslTls (Module)" id="insecureSslTls (Module)" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.Security.Checks.insecureSslTls.html" />
       <keyword name="installGoogleAPIPackages" id="installGoogleAPIPackages" ref="eric7.EricNetwork.EricGoogleMailHelpers.html#installGoogleAPIPackages" />
+      <keyword name="install_module" id="install_module" ref="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html#install_module" />
       <keyword name="instance" id="instance" ref="eric7.EricNetwork.EricTldExtractor.html#instance" />
       <keyword name="instance" id="instance" ref="eric7.WebBrowser.Tools.WebIconProvider.html#instance" />
       <keyword name="instance" id="instance" ref="eric7.WebBrowser.ZoomManager.ZoomManager.html#instance" />
@@ -19362,6 +19435,7 @@
       <keyword name="isBaseString_1" id="isBaseString_1" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.AstUtilities.html#isBaseString_1" />
       <keyword name="isBytes" id="isBytes" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.AstUtilities.html#isBytes" />
       <keyword name="isBytes_1" id="isBytes_1" ref="eric7.Plugins.CheckerPlugins.CodeStyleChecker.AstUtilities.html#isBytes_1" />
+      <keyword name="isCircupAvailable" id="isCircupAvailable" ref="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html#isCircupAvailable" />
       <keyword name="isClientSecretFileAvailable" id="isClientSecretFileAvailable" ref="eric7.EricNetwork.EricGoogleMailHelpers.html#isClientSecretFileAvailable" />
       <keyword name="isCondaAvailable" id="isCondaAvailable" ref="eric7.CondaInterface.__init__.html#isCondaAvailable" />
       <keyword name="isConfigured" id="isConfigured" ref="eric7.Preferences.__init__.html#isConfigured" />
@@ -19490,6 +19564,7 @@
       <keyword name="patchNewProcessFunctions" id="patchNewProcessFunctions" ref="eric7.DebugClients.Python.MultiProcessDebugExtension.html#patchNewProcessFunctions" />
       <keyword name="patchQProcess" id="patchQProcess" ref="eric7.DebugClients.Python.QProcessExtension.html#patchQProcess" />
       <keyword name="patchSubprocess" id="patchSubprocess" ref="eric7.DebugClients.Python.SubprocessExtension.html#patchSubprocess" />
+      <keyword name="patch_circup" id="patch_circup" ref="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html#patch_circup" />
       <keyword name="pbkdf2" id="pbkdf2" ref="eric7.Utilities.crypto.py3PBKDF2.html#pbkdf2" />
       <keyword name="pipdeptree (Module)" id="pipdeptree (Module)" ref="eric7.PipInterface.pipdeptree.html" />
       <keyword name="piplicenses (Module)" id="piplicenses (Module)" ref="eric7.PipInterface.piplicenses.html" />
@@ -19985,6 +20060,13 @@
       <file>eric7.MicroPython.AddEditDevicesDialog.html</file>
       <file>eric7.MicroPython.BoardDataDialog.html</file>
       <file>eric7.MicroPython.CircuitPythonDevices.html</file>
+      <file>eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html</file>
+      <file>eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html</file>
+      <file>eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html</file>
+      <file>eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html</file>
+      <file>eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html</file>
+      <file>eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html</file>
+      <file>eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html</file>
       <file>eric7.MicroPython.ConnectionSelectionDialog.html</file>
       <file>eric7.MicroPython.EspBackupRestoreFirmwareDialog.html</file>
       <file>eric7.MicroPython.EspDevices.html</file>
@@ -20980,6 +21062,7 @@
       <file>index-eric7.IconEditor.cursors.html</file>
       <file>index-eric7.IconEditor.html</file>
       <file>index-eric7.JediInterface.html</file>
+      <file>index-eric7.MicroPython.CircuitPythonUpdater.html</file>
       <file>index-eric7.MicroPython.html</file>
       <file>index-eric7.MultiProject.html</file>
       <file>index-eric7.Network.IRC.html</file>
Binary file src/eric7/Documentation/Source/eric7.Globals.AppInfo.html has changed
--- a/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonDevices.html	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonDevices.html	Mon Feb 06 10:09:18 2023 +0100
@@ -63,6 +63,10 @@
 <td>Constructor</td>
 </tr>
 <tr>
+<td><a href="#CircuitPythonDevice.__aboutToShowLibraryMenu">__aboutToShowLibraryMenu</a></td>
+<td>Private slot to populate the 'Library Management' menu.</td>
+</tr>
+<tr>
 <td><a href="#CircuitPythonDevice.__deviceVolumeMounted">__deviceVolumeMounted</a></td>
 <td>Private method to check, if the device volume is mounted.</td>
 </tr>
@@ -175,6 +179,13 @@
 reference to the parent object
 </dd>
 </dl>
+<a NAME="CircuitPythonDevice.__aboutToShowLibraryMenu" ID="CircuitPythonDevice.__aboutToShowLibraryMenu"></a>
+<h4>CircuitPythonDevice.__aboutToShowLibraryMenu</h4>
+<b>__aboutToShowLibraryMenu</b>(<i></i>)
+
+<p>
+        Private slot to populate the 'Library Management' menu.
+</p>
 <a NAME="CircuitPythonDevice.__deviceVolumeMounted" ID="CircuitPythonDevice.__deviceVolumeMounted"></a>
 <h4>CircuitPythonDevice.__deviceVolumeMounted</h4>
 <b>__deviceVolumeMounted</b>(<i></i>)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,358 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface</h1>
+
+<p>
+Module implementing an interface to the 'circup' package.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface">CircuitPythonUpdaterInterface</a></td>
+<td>Class implementing an interface to the 'circup' package.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#isCircupAvailable">isCircupAvailable</a></td>
+<td>Function to check for the availability of 'circup'.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="CircuitPythonUpdaterInterface" ID="CircuitPythonUpdaterInterface"></a>
+<h2>CircuitPythonUpdaterInterface</h2>
+
+<p>
+    Class implementing an interface to the 'circup' package.
+</p>
+<h3>Derived from</h3>
+QObject
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__init__">CircuitPythonUpdaterInterface</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__aboutCircup">__aboutCircup</a></td>
+<td>Private slot to show some info about 'circup'.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__addBundle">__addBundle</a></td>
+<td>Private slot to add a bundle to the local bundles list, by "user/repo" github string.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__doUpdateModules">__doUpdateModules</a></td>
+<td>Private method to perform the update of a list of modules.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__generateRequirements">__generateRequirements</a></td>
+<td>Private slot to generate requirements for the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__installFromAvailable">__installFromAvailable</a></td>
+<td>Private slot to install modules onto the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__installFromCode">__installFromCode</a></td>
+<td>Private slot to install modules based on the 'code.py' file of the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__installModules">__installModules</a></td>
+<td>Private method to install the given list of modules.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__installRequirements">__installRequirements</a></td>
+<td>Private slot to install modules determined by a requirements file.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__listOutdatedModules">__listOutdatedModules</a></td>
+<td>Private slot to list the outdated modules of the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__removeBundle">__removeBundle</a></td>
+<td>Private slot to remove one or more bundles from the local bundles list.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__showAvailableModules">__showAvailableModules</a></td>
+<td>Private slot to show the available modules.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__showBundles">__showBundles</a></td>
+<td>Private slot to show the available bundles (default and local).</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__showBundlesModules">__showBundlesModules</a></td>
+<td>Private slot to show the available bundles (default and local) with their modules.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__showInstalledModules">__showInstalledModules</a></td>
+<td>Private slot to show the modules installed on the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__uninstallModules">__uninstallModules</a></td>
+<td>Private slot to uninstall modules from the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__updateAllModules">__updateAllModules</a></td>
+<td>Private slot to update all modules of the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.__updateModules">__updateModules</a></td>
+<td>Private slot to update the modules of the connected device.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.installCircup">installCircup</a></td>
+<td>Public slot to install the 'circup' package via pip.</td>
+</tr>
+<tr>
+<td><a href="#CircuitPythonUpdaterInterface.populateMenu">populateMenu</a></td>
+<td>Public method to populate the 'circup' menu.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="CircuitPythonUpdaterInterface.__init__" ID="CircuitPythonUpdaterInterface.__init__"></a>
+<h4>CircuitPythonUpdaterInterface (Constructor)</h4>
+<b>CircuitPythonUpdaterInterface</b>(<i>device, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>device</i> (CircuitPythonDevice)</dt>
+<dd>
+reference to the CircuitPython device interface
+</dd>
+<dt><i>parent</i> (QObject (optional))</dt>
+<dd>
+reference to the parent object (defaults to None)
+</dd>
+</dl>
+<a NAME="CircuitPythonUpdaterInterface.__aboutCircup" ID="CircuitPythonUpdaterInterface.__aboutCircup"></a>
+<h4>CircuitPythonUpdaterInterface.__aboutCircup</h4>
+<b>__aboutCircup</b>(<i></i>)
+
+<p>
+        Private slot to show some info about 'circup'.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__addBundle" ID="CircuitPythonUpdaterInterface.__addBundle"></a>
+<h4>CircuitPythonUpdaterInterface.__addBundle</h4>
+<b>__addBundle</b>(<i></i>)
+
+<p>
+        Private slot to add a bundle to the local bundles list, by "user/repo" github
+        string.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__doUpdateModules" ID="CircuitPythonUpdaterInterface.__doUpdateModules"></a>
+<h4>CircuitPythonUpdaterInterface.__doUpdateModules</h4>
+<b>__doUpdateModules</b>(<i>modules</i>)
+
+<p>
+        Private method to perform the update of a list of modules.
+</p>
+<dl>
+
+<dt><i>modules</i> (circup.Module)</dt>
+<dd>
+list of modules to be updated
+</dd>
+</dl>
+<a NAME="CircuitPythonUpdaterInterface.__generateRequirements" ID="CircuitPythonUpdaterInterface.__generateRequirements"></a>
+<h4>CircuitPythonUpdaterInterface.__generateRequirements</h4>
+<b>__generateRequirements</b>(<i></i>)
+
+<p>
+        Private slot to generate requirements for the connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__installFromAvailable" ID="CircuitPythonUpdaterInterface.__installFromAvailable"></a>
+<h4>CircuitPythonUpdaterInterface.__installFromAvailable</h4>
+<b>__installFromAvailable</b>(<i></i>)
+
+<p>
+        Private slot to install modules onto the connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__installFromCode" ID="CircuitPythonUpdaterInterface.__installFromCode"></a>
+<h4>CircuitPythonUpdaterInterface.__installFromCode</h4>
+<b>__installFromCode</b>(<i></i>)
+
+<p>
+        Private slot to install modules based on the 'code.py' file of the
+        connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__installModules" ID="CircuitPythonUpdaterInterface.__installModules"></a>
+<h4>CircuitPythonUpdaterInterface.__installModules</h4>
+<b>__installModules</b>(<i>installs</i>)
+
+<p>
+        Private method to install the given list of modules.
+</p>
+<dl>
+
+<dt><i>installs</i> (list of str)</dt>
+<dd>
+list of module names to be installed
+</dd>
+</dl>
+<a NAME="CircuitPythonUpdaterInterface.__installRequirements" ID="CircuitPythonUpdaterInterface.__installRequirements"></a>
+<h4>CircuitPythonUpdaterInterface.__installRequirements</h4>
+<b>__installRequirements</b>(<i></i>)
+
+<p>
+        Private slot to install modules determined by a requirements file.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__listOutdatedModules" ID="CircuitPythonUpdaterInterface.__listOutdatedModules"></a>
+<h4>CircuitPythonUpdaterInterface.__listOutdatedModules</h4>
+<b>__listOutdatedModules</b>(<i></i>)
+
+<p>
+        Private slot to list the outdated modules of the connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__removeBundle" ID="CircuitPythonUpdaterInterface.__removeBundle"></a>
+<h4>CircuitPythonUpdaterInterface.__removeBundle</h4>
+<b>__removeBundle</b>(<i></i>)
+
+<p>
+        Private slot to remove one or more bundles from the local bundles list.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__showAvailableModules" ID="CircuitPythonUpdaterInterface.__showAvailableModules"></a>
+<h4>CircuitPythonUpdaterInterface.__showAvailableModules</h4>
+<b>__showAvailableModules</b>(<i></i>)
+
+<p>
+        Private slot to show the available modules.
+</p>
+<p>
+        These are modules which could be installed on the device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__showBundles" ID="CircuitPythonUpdaterInterface.__showBundles"></a>
+<h4>CircuitPythonUpdaterInterface.__showBundles</h4>
+<b>__showBundles</b>(<i>withModules=False</i>)
+
+<p>
+        Private slot to show the available bundles (default and local).
+</p>
+<dl>
+
+<dt><i>withModules</i> (bool (optional))</dt>
+<dd>
+flag indicating to list the modules and their version
+            (defaults to False)
+</dd>
+</dl>
+<a NAME="CircuitPythonUpdaterInterface.__showBundlesModules" ID="CircuitPythonUpdaterInterface.__showBundlesModules"></a>
+<h4>CircuitPythonUpdaterInterface.__showBundlesModules</h4>
+<b>__showBundlesModules</b>(<i></i>)
+
+<p>
+        Private slot to show the available bundles (default and local) with their
+        modules.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__showInstalledModules" ID="CircuitPythonUpdaterInterface.__showInstalledModules"></a>
+<h4>CircuitPythonUpdaterInterface.__showInstalledModules</h4>
+<b>__showInstalledModules</b>(<i></i>)
+
+<p>
+        Private slot to show the modules installed on the connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__uninstallModules" ID="CircuitPythonUpdaterInterface.__uninstallModules"></a>
+<h4>CircuitPythonUpdaterInterface.__uninstallModules</h4>
+<b>__uninstallModules</b>(<i></i>)
+
+<p>
+        Private slot to uninstall modules from the connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__updateAllModules" ID="CircuitPythonUpdaterInterface.__updateAllModules"></a>
+<h4>CircuitPythonUpdaterInterface.__updateAllModules</h4>
+<b>__updateAllModules</b>(<i></i>)
+
+<p>
+        Private slot to update all modules of the connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.__updateModules" ID="CircuitPythonUpdaterInterface.__updateModules"></a>
+<h4>CircuitPythonUpdaterInterface.__updateModules</h4>
+<b>__updateModules</b>(<i></i>)
+
+<p>
+        Private slot to update the modules of the connected device.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.installCircup" ID="CircuitPythonUpdaterInterface.installCircup"></a>
+<h4>CircuitPythonUpdaterInterface.installCircup</h4>
+<b>installCircup</b>(<i></i>)
+
+<p>
+        Public slot to install the 'circup' package via pip.
+</p>
+<a NAME="CircuitPythonUpdaterInterface.populateMenu" ID="CircuitPythonUpdaterInterface.populateMenu"></a>
+<h4>CircuitPythonUpdaterInterface.populateMenu</h4>
+<b>populateMenu</b>(<i>menu</i>)
+
+<p>
+        Public method to populate the 'circup' menu.
+</p>
+<dl>
+
+<dt><i>menu</i> (QMenu)</dt>
+<dd>
+reference to the menu to be populated
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="isCircupAvailable" ID="isCircupAvailable"></a>
+<h2>isCircupAvailable</h2>
+<b>isCircupAvailable</b>(<i></i>)
+
+<p>
+    Function to check for the availability of 'circup'.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating the availability of 'circup'
+</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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,209 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater.CircupFunctions</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.CircuitPythonUpdater.CircupFunctions</h1>
+
+<p>
+Module implementing variants of some 'circup' functions suitable for 'eric-ide'
+integration.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+
+<tr>
+<td><a href="#ensure_latest_bundle">ensure_latest_bundle</a></td>
+<td>Function to ensure that there's a copy of the latest library bundle available so circup can check the metadata contained therein.</td>
+</tr>
+<tr>
+<td><a href="#find_modules">find_modules</a></td>
+<td>Function to extract metadata from the connected device and available bundles and returns this as a list of Module instances representing the modules on the device.</td>
+</tr>
+<tr>
+<td><a href="#get_circuitpython_version">get_circuitpython_version</a></td>
+<td>Function to return the version number of CircuitPython running on the board connected via ``device_path``, along with the board ID.</td>
+</tr>
+<tr>
+<td><a href="#install_module">install_module</a></td>
+<td>Function to find a connected device and install a given module name.</td>
+</tr>
+<tr>
+<td><a href="#patch_circup">patch_circup</a></td>
+<td>Function to patch 'circup' to use our functions adapted to the use within the eric-ide.</td>
+</tr>
+</table>
+<hr />
+<hr />
+<a NAME="ensure_latest_bundle" ID="ensure_latest_bundle"></a>
+<h2>ensure_latest_bundle</h2>
+<b>ensure_latest_bundle</b>(<i>bundle</i>)
+
+<p>
+    Function to ensure that there's a copy of the latest library bundle available so
+    circup can check the metadata contained therein.
+</p>
+<dl>
+
+<dt><i>bundle</i> (circup.Bundle)</dt>
+<dd>
+reference to the target Bundle object.
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="find_modules" ID="find_modules"></a>
+<h2>find_modules</h2>
+<b>find_modules</b>(<i>device_path, bundles_list</i>)
+
+<p>
+    Function to extract metadata from the connected device and available bundles and
+    returns this as a list of Module instances representing the modules on the device.
+</p>
+<dl>
+
+<dt><i>device_path</i> (str)</dt>
+<dd>
+path to the connected board
+</dd>
+<dt><i>bundles_list</i> (list of circup.Bundle)</dt>
+<dd>
+list of supported bundles
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of Module instances describing the current state of the
+        modules on the connected device
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of circup.Module
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="get_circuitpython_version" ID="get_circuitpython_version"></a>
+<h2>get_circuitpython_version</h2>
+<b>get_circuitpython_version</b>(<i>device_path</i>)
+
+<p>
+    Function to return the version number of CircuitPython running on the board
+    connected via ``device_path``, along with the board ID.
+</p>
+<p>
+    This is obtained from the 'boot_out.txt' file on the device, whose first line
+    will start with something like this:
+</p>
+<p>
+        Adafruit CircuitPython 4.1.0 on 2019-08-02;
+</p>
+<p>
+    While the second line is:
+</p>
+<p>
+        Board ID:raspberry_pi_pico
+</p>
+<dl>
+
+<dt><i>device_path</i> (str)</dt>
+<dd>
+path to the connected board.
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+tuple with the version string for CircuitPython and the board ID string
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+tuple of (str, str)
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="install_module" ID="install_module"></a>
+<h2>install_module</h2>
+<b>install_module</b>(<i>device_path, device_modules, name, py, mod_names</i>)
+
+<p>
+    Function to find a connected device and install a given module name.
+</p>
+<p>
+    Installation is done if it is available in the current module bundle and is not
+    already installed on the device.
+</p>
+<dl>
+
+<dt><i>device_path</i> (str)</dt>
+<dd>
+path to the connected board
+</dd>
+<dt><i>device_modules</i> (list of dict)</dt>
+<dd>
+list of module metadata from the device
+</dd>
+<dt><i>name</i> (str)</dt>
+<dd>
+name of the module to be installed
+</dd>
+<dt><i>py</i> (bool)</dt>
+<dd>
+flag indicating if the module should be installed from source or
+        from a pre-compiled module
+</dd>
+<dt><i>mod_names</i> (dict)</dt>
+<dd>
+dictionary containing metadata from modules that can be generated
+        with circup.get_bundle_versions()
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+flag indicating success
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+bool
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+<hr />
+<a NAME="patch_circup" ID="patch_circup"></a>
+<h2>patch_circup</h2>
+<b>patch_circup</b>(<i></i>)
+
+<p>
+    Function to patch 'circup' to use our functions adapted to the use within the
+    eric-ide.
+</p>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,244 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog</h1>
+
+<p>
+Module implementing a dialog to generate a requirements file.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#RequirementsDialog">RequirementsDialog</a></td>
+<td>Class implementing a dialog to generate a requirements file.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="RequirementsDialog" ID="RequirementsDialog"></a>
+<h2>RequirementsDialog</h2>
+
+<p>
+    Class implementing a dialog to generate a requirements file.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_RequirementsDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#RequirementsDialog.__init__">RequirementsDialog</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.__generateRequirements">__generateRequirements</a></td>
+<td>Private slot to generate the requirements specifiers list.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.__updateButtons">__updateButtons</a></td>
+<td>Private method to set the state of the various buttons.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.__writeToFile">__writeToFile</a></td>
+<td>Private method to write the requirements text to a file.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_buttonBox_clicked">on_buttonBox_clicked</a></td>
+<td>Private slot called by a button of the button box clicked.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_copyButton_clicked">on_copyButton_clicked</a></td>
+<td>Private slot to copy the requirements text to the clipboard.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_insertButton_clicked">on_insertButton_clicked</a></td>
+<td>Private slot to insert the requirements text at the cursor position of the current editor.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_replaceAllButton_clicked">on_replaceAllButton_clicked</a></td>
+<td>Private slot to replace the text of the current editor with the requirements text.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_replaceSelectionButton_clicked">on_replaceSelectionButton_clicked</a></td>
+<td>Private slot to replace the selected text of the current editor with the requirements text.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_requirementsEdit_textChanged">on_requirementsEdit_textChanged</a></td>
+<td>Private slot handling changes of the requirements text.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_requirementsFilePicker_textChanged">on_requirementsFilePicker_textChanged</a></td>
+<td>Private slot handling a change of the requirements file name.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_saveButton_clicked">on_saveButton_clicked</a></td>
+<td>Private slot to save the requirements text to the requirements file.</td>
+</tr>
+<tr>
+<td><a href="#RequirementsDialog.on_saveToButton_clicked">on_saveToButton_clicked</a></td>
+<td>Private slot to write the requirements text to a new file.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="RequirementsDialog.__init__" ID="RequirementsDialog.__init__"></a>
+<h4>RequirementsDialog (Constructor)</h4>
+<b>RequirementsDialog</b>(<i>devicePath, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>devicePath</i> (str)</dt>
+<dd>
+path to the connected board
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="RequirementsDialog.__generateRequirements" ID="RequirementsDialog.__generateRequirements"></a>
+<h4>RequirementsDialog.__generateRequirements</h4>
+<b>__generateRequirements</b>(<i></i>)
+
+<p>
+        Private slot to generate the requirements specifiers list.
+</p>
+<a NAME="RequirementsDialog.__updateButtons" ID="RequirementsDialog.__updateButtons"></a>
+<h4>RequirementsDialog.__updateButtons</h4>
+<b>__updateButtons</b>(<i></i>)
+
+<p>
+        Private method to set the state of the various buttons.
+</p>
+<a NAME="RequirementsDialog.__writeToFile" ID="RequirementsDialog.__writeToFile"></a>
+<h4>RequirementsDialog.__writeToFile</h4>
+<b>__writeToFile</b>(<i>fileName</i>)
+
+<p>
+        Private method to write the requirements text to a file.
+</p>
+<dl>
+
+<dt><i>fileName</i> (str)</dt>
+<dd>
+name of the file to write to
+</dd>
+</dl>
+<a NAME="RequirementsDialog.on_buttonBox_clicked" ID="RequirementsDialog.on_buttonBox_clicked"></a>
+<h4>RequirementsDialog.on_buttonBox_clicked</h4>
+<b>on_buttonBox_clicked</b>(<i>button</i>)
+
+<p>
+        Private slot called by a button of the button box clicked.
+</p>
+<dl>
+
+<dt><i>button</i> (QAbstractButton)</dt>
+<dd>
+button that was clicked
+</dd>
+</dl>
+<a NAME="RequirementsDialog.on_copyButton_clicked" ID="RequirementsDialog.on_copyButton_clicked"></a>
+<h4>RequirementsDialog.on_copyButton_clicked</h4>
+<b>on_copyButton_clicked</b>(<i></i>)
+
+<p>
+        Private slot to copy the requirements text to the clipboard.
+</p>
+<a NAME="RequirementsDialog.on_insertButton_clicked" ID="RequirementsDialog.on_insertButton_clicked"></a>
+<h4>RequirementsDialog.on_insertButton_clicked</h4>
+<b>on_insertButton_clicked</b>(<i></i>)
+
+<p>
+        Private slot to insert the requirements text at the cursor position
+        of the current editor.
+</p>
+<a NAME="RequirementsDialog.on_replaceAllButton_clicked" ID="RequirementsDialog.on_replaceAllButton_clicked"></a>
+<h4>RequirementsDialog.on_replaceAllButton_clicked</h4>
+<b>on_replaceAllButton_clicked</b>(<i></i>)
+
+<p>
+        Private slot to replace the text of the current editor with the
+        requirements text.
+</p>
+<a NAME="RequirementsDialog.on_replaceSelectionButton_clicked" ID="RequirementsDialog.on_replaceSelectionButton_clicked"></a>
+<h4>RequirementsDialog.on_replaceSelectionButton_clicked</h4>
+<b>on_replaceSelectionButton_clicked</b>(<i></i>)
+
+<p>
+        Private slot to replace the selected text of the current editor
+        with the requirements text.
+</p>
+<a NAME="RequirementsDialog.on_requirementsEdit_textChanged" ID="RequirementsDialog.on_requirementsEdit_textChanged"></a>
+<h4>RequirementsDialog.on_requirementsEdit_textChanged</h4>
+<b>on_requirementsEdit_textChanged</b>(<i></i>)
+
+<p>
+        Private slot handling changes of the requirements text.
+</p>
+<a NAME="RequirementsDialog.on_requirementsFilePicker_textChanged" ID="RequirementsDialog.on_requirementsFilePicker_textChanged"></a>
+<h4>RequirementsDialog.on_requirementsFilePicker_textChanged</h4>
+<b>on_requirementsFilePicker_textChanged</b>(<i>txt</i>)
+
+<p>
+        Private slot handling a change of the requirements file name.
+</p>
+<dl>
+
+<dt><i>txt</i> (str)</dt>
+<dd>
+name of the requirements file
+</dd>
+</dl>
+<a NAME="RequirementsDialog.on_saveButton_clicked" ID="RequirementsDialog.on_saveButton_clicked"></a>
+<h4>RequirementsDialog.on_saveButton_clicked</h4>
+<b>on_saveButton_clicked</b>(<i></i>)
+
+<p>
+        Private slot to save the requirements text to the requirements file.
+</p>
+<a NAME="RequirementsDialog.on_saveToButton_clicked" ID="RequirementsDialog.on_saveToButton_clicked"></a>
+<h4>RequirementsDialog.on_saveToButton_clicked</h4>
+<b>on_saveToButton_clicked</b>(<i></i>)
+
+<p>
+        Private slot to write the requirements text to a new file.
+</p>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog</h1>
+
+<p>
+Module implementing a dialog showing the available bundles and their modules.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowBundlesDialog">ShowBundlesDialog</a></td>
+<td>Class implementing a dialog showing the available bundles and their modules.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="ShowBundlesDialog" ID="ShowBundlesDialog"></a>
+<h2>ShowBundlesDialog</h2>
+
+<p>
+    Class implementing a dialog showing the available bundles and their modules.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_ShowBundlesDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowBundlesDialog.__init__">ShowBundlesDialog</a></td>
+<td>Constructor</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="ShowBundlesDialog.__init__" ID="ShowBundlesDialog.__init__"></a>
+<h4>ShowBundlesDialog (Constructor)</h4>
+<b>ShowBundlesDialog</b>(<i>withModules, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>withModules</i> (bool)</dt>
+<dd>
+flag indicating to list the modules and their version
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog</h1>
+
+<p>
+Module implementing a dialog to show the modules installed on the connected device.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowInstalledDialog">ShowInstalledDialog</a></td>
+<td>Class implementing a dialog to show the modules installed on the connected device.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="ShowInstalledDialog" ID="ShowInstalledDialog"></a>
+<h2>ShowInstalledDialog</h2>
+
+<p>
+    Class implementing a dialog to show the modules installed on the connected device.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_ShowInstalledDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowInstalledDialog.__init__">ShowInstalledDialog</a></td>
+<td>Constructor</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="ShowInstalledDialog.__init__" ID="ShowInstalledDialog.__init__"></a>
+<h4>ShowInstalledDialog (Constructor)</h4>
+<b>ShowInstalledDialog</b>(<i>devicePath, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>devicePath</i> (str)</dt>
+<dd>
+path to the connected board
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog</h1>
+
+<p>
+Module implementing a dialog to show the available modules of all bundles.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowModulesDialog">ShowModulesDialog</a></td>
+<td>Class implementing a dialog to show the available modules of all bundles.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="ShowModulesDialog" ID="ShowModulesDialog"></a>
+<h2>ShowModulesDialog</h2>
+
+<p>
+    Class implementing a dialog to show the available modules of all bundles.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_ShowModulesDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowModulesDialog.__init__">ShowModulesDialog</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#ShowModulesDialog.__applyFilter">__applyFilter</a></td>
+<td>Private slot to apply the filter to the list of available modules.</td>
+</tr>
+<tr>
+<td><a href="#ShowModulesDialog.__checkCountUpdated">__checkCountUpdated</a></td>
+<td>Private method to handle an update of the check count.</td>
+</tr>
+<tr>
+<td><a href="#ShowModulesDialog.getSelection">getSelection</a></td>
+<td>Public method to get the list of selected modules.</td>
+</tr>
+<tr>
+<td><a href="#ShowModulesDialog.on_modulesList_itemChanged">on_modulesList_itemChanged</a></td>
+<td>Private slot to handle a change of the check state of an item.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="ShowModulesDialog.__init__" ID="ShowModulesDialog.__init__"></a>
+<h4>ShowModulesDialog (Constructor)</h4>
+<b>ShowModulesDialog</b>(<i>selectionMode=False, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>selectionMode</i> (bool (optional))</dt>
+<dd>
+flag indicating the activation of the selection mode
+            (defaults to False)
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="ShowModulesDialog.__applyFilter" ID="ShowModulesDialog.__applyFilter"></a>
+<h4>ShowModulesDialog.__applyFilter</h4>
+<b>__applyFilter</b>(<i></i>)
+
+<p>
+        Private slot to apply the filter to the list of available modules.
+</p>
+<a NAME="ShowModulesDialog.__checkCountUpdated" ID="ShowModulesDialog.__checkCountUpdated"></a>
+<h4>ShowModulesDialog.__checkCountUpdated</h4>
+<b>__checkCountUpdated</b>(<i></i>)
+
+<p>
+        Private method to handle an update of the check count.
+</p>
+<a NAME="ShowModulesDialog.getSelection" ID="ShowModulesDialog.getSelection"></a>
+<h4>ShowModulesDialog.getSelection</h4>
+<b>getSelection</b>(<i></i>)
+
+<p>
+        Public method to get the list of selected modules.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of selected modules
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+circup.Module
+</dd>
+</dl>
+<a NAME="ShowModulesDialog.on_modulesList_itemChanged" ID="ShowModulesDialog.on_modulesList_itemChanged"></a>
+<h4>ShowModulesDialog.on_modulesList_itemChanged</h4>
+<b>on_modulesList_itemChanged</b>(<i>item</i>)
+
+<p>
+        Private slot to handle a change of the check state of an item.
+</p>
+<dl>
+
+<dt><i>item</i> (QTreeWidgetItem)</dt>
+<dd>
+reference to the changed item
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<a NAME="top" ID="top"></a>
+<h1>eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog</h1>
+
+<p>
+Module implementing a dialog to show outdated modules of a connected device.
+</p>
+<h3>Global Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Classes</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowOutdatedDialog">ShowOutdatedDialog</a></td>
+<td>Class implementing a dialog to show outdated modules of a connected device.</td>
+</tr>
+</table>
+<h3>Functions</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<hr />
+<hr />
+<a NAME="ShowOutdatedDialog" ID="ShowOutdatedDialog"></a>
+<h2>ShowOutdatedDialog</h2>
+
+<p>
+    Class implementing a dialog to show outdated modules of a connected device.
+</p>
+<h3>Derived from</h3>
+QDialog, Ui_ShowOutdatedDialog
+<h3>Class Attributes</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Class Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+<h3>Methods</h3>
+
+<table>
+
+<tr>
+<td><a href="#ShowOutdatedDialog.__init__">ShowOutdatedDialog</a></td>
+<td>Constructor</td>
+</tr>
+<tr>
+<td><a href="#ShowOutdatedDialog.__checkCountUpdated">__checkCountUpdated</a></td>
+<td>Private method to handle an update of the check count.</td>
+</tr>
+<tr>
+<td><a href="#ShowOutdatedDialog.getSelection">getSelection</a></td>
+<td>Public method to get the list of selected modules.</td>
+</tr>
+<tr>
+<td><a href="#ShowOutdatedDialog.on_modulesList_itemChanged">on_modulesList_itemChanged</a></td>
+<td>Private slot to handle a change of the check state of an item.</td>
+</tr>
+</table>
+<h3>Static Methods</h3>
+
+<table>
+<tr><td>None</td></tr>
+</table>
+
+<a NAME="ShowOutdatedDialog.__init__" ID="ShowOutdatedDialog.__init__"></a>
+<h4>ShowOutdatedDialog (Constructor)</h4>
+<b>ShowOutdatedDialog</b>(<i>devicePath, selectionMode=False, parent=None</i>)
+
+<p>
+        Constructor
+</p>
+<dl>
+
+<dt><i>devicePath</i> (str)</dt>
+<dd>
+path to the connected board
+</dd>
+<dt><i>selectionMode</i> (bool (optional))</dt>
+<dd>
+flag indicating the activation of the selection mode
+            (defaults to False)
+</dd>
+<dt><i>parent</i> (QWidget (optional))</dt>
+<dd>
+reference to the parent widget (defaults to None)
+</dd>
+</dl>
+<a NAME="ShowOutdatedDialog.__checkCountUpdated" ID="ShowOutdatedDialog.__checkCountUpdated"></a>
+<h4>ShowOutdatedDialog.__checkCountUpdated</h4>
+<b>__checkCountUpdated</b>(<i></i>)
+
+<p>
+        Private method to handle an update of the check count.
+</p>
+<a NAME="ShowOutdatedDialog.getSelection" ID="ShowOutdatedDialog.getSelection"></a>
+<h4>ShowOutdatedDialog.getSelection</h4>
+<b>getSelection</b>(<i></i>)
+
+<p>
+        Public method to get the list of selected modules.
+</p>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of selected modules
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+circup.Module
+</dd>
+</dl>
+<a NAME="ShowOutdatedDialog.on_modulesList_itemChanged" ID="ShowOutdatedDialog.on_modulesList_itemChanged"></a>
+<h4>ShowOutdatedDialog.on_modulesList_itemChanged</h4>
+<b>on_modulesList_itemChanged</b>(<i>item, column</i>)
+
+<p>
+        Private slot to handle a change of the check state of an item.
+</p>
+<dl>
+
+<dt><i>item</i> (QTreeWidgetItem)</dt>
+<dd>
+reference to the changed item
+</dd>
+<dt><i>column</i> (int)</dt>
+<dd>
+changed column
+</dd>
+</dl>
+<div align="right"><a href="#top">Up</a></div>
+<hr />
+</body></html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/Documentation/Source/index-eric7.MicroPython.CircuitPythonUpdater.html	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html><head>
+<title>eric7.MicroPython.CircuitPythonUpdater</title>
+<meta charset="UTF-8">
+<link rel="stylesheet" href="styles.css">
+</head>
+<body>
+<h1>eric7.MicroPython.CircuitPythonUpdater</h1>
+
+<p>
+Package implementing the updater and associated dialogs.
+</p>
+
+
+<h3>Modules</h3>
+<table>
+
+<tr>
+<td><a href="eric7.MicroPython.CircuitPythonUpdater.CircuitPythonUpdaterInterface.html">CircuitPythonUpdaterInterface</a></td>
+<td>Module implementing an interface to the 'circup' package.</td>
+</tr>
+<tr>
+<td><a href="eric7.MicroPython.CircuitPythonUpdater.CircupFunctions.html">CircupFunctions</a></td>
+<td>Module implementing variants of some 'circup' functions suitable for 'eric-ide' integration.</td>
+</tr>
+<tr>
+<td><a href="eric7.MicroPython.CircuitPythonUpdater.RequirementsDialog.html">RequirementsDialog</a></td>
+<td>Module implementing a dialog to generate a requirements file.</td>
+</tr>
+<tr>
+<td><a href="eric7.MicroPython.CircuitPythonUpdater.ShowBundlesDialog.html">ShowBundlesDialog</a></td>
+<td>Module implementing a dialog showing the available bundles and their modules.</td>
+</tr>
+<tr>
+<td><a href="eric7.MicroPython.CircuitPythonUpdater.ShowInstalledDialog.html">ShowInstalledDialog</a></td>
+<td>Module implementing a dialog to show the modules installed on the connected device.</td>
+</tr>
+<tr>
+<td><a href="eric7.MicroPython.CircuitPythonUpdater.ShowModulesDialog.html">ShowModulesDialog</a></td>
+<td>Module implementing a dialog to show the available modules of all bundles.</td>
+</tr>
+<tr>
+<td><a href="eric7.MicroPython.CircuitPythonUpdater.ShowOutdatedDialog.html">ShowOutdatedDialog</a></td>
+<td>Module implementing a dialog to show outdated modules of a connected device.</td>
+</tr>
+</table>
+</body></html>
\ No newline at end of file
--- a/src/eric7/Documentation/Source/index-eric7.MicroPython.html	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/Documentation/Source/index-eric7.MicroPython.html	Mon Feb 06 10:09:18 2023 +0100
@@ -11,6 +11,14 @@
 Package implementing an interface to MicroPython devices like esp32.
 </p>
 
+<h3>Packages</h3>
+<table>
+
+<tr>
+<td><a href="index-eric7.MicroPython.CircuitPythonUpdater.html">CircuitPythonUpdater</a></td>
+<td>Package implementing the updater and associated dialogs.</td>
+</tr>
+</table>
 
 <h3>Modules</h3>
 <table>
--- a/src/eric7/DocumentationTools/IndexGenerator.py	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/DocumentationTools/IndexGenerator.py	Mon Feb 06 10:09:18 2023 +0100
@@ -190,6 +190,13 @@
                 out = self.__writeIndex(package, element, newline)
             except OSError as v:
                 sys.stderr.write("{0} error: {1}\n".format(package, v[1]))
+            except Exception as ex:
+                sys.stderr.write(
+                    "{0} error writing index: {1}\n".format(
+                        package, str(ex)
+                    )
+                )
+                raise
             else:
                 if out:
                     sys.stdout.write("{0} ok\n".format(out))
--- a/src/eric7/MicroPython/CircuitPythonDevices.py	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/MicroPython/CircuitPythonDevices.py	Mon Feb 06 10:09:18 2023 +0100
@@ -17,6 +17,10 @@
 from eric7.EricWidgets import EricFileDialog, EricMessageBox
 from eric7.SystemUtilities import FileSystemUtilities
 
+from .CircuitPythonUpdater.CircuitPythonUpdaterInterface import (
+    CircuitPythonUpdaterInterface,
+    isCircupAvailable,
+)
 from .MicroPythonDevices import MicroPythonDevice
 from .MicroPythonWidget import HAS_QTCHART
 
@@ -46,6 +50,8 @@
         self.__boardName = boardName
         self.__workspace = self.__findWorkspace()
 
+        self.__updater = CircuitPythonUpdaterInterface(self)
+
         self.__nonUF2devices = {
             "teensy": self.__flashTeensy,
         }
@@ -152,22 +158,6 @@
 
         return self.DeviceVolumeName in self.getWorkspace(silent=True)
 
-    def getWorkspace(self, silent=False):
-        """
-        Public method to get the workspace directory.
-
-        @param silent flag indicating silent operations
-        @type bool
-        @return workspace directory used for saving files
-        @rtype str
-        """
-        if self.__workspace:
-            # return cached entry
-            return self.__workspace
-        else:
-            self.__workspace = self.__findWorkspace(silent=silent)
-            return self.__workspace
-
     def __findDeviceDirectories(self, directories):
         """
         Private method to find the device directories associated with the
@@ -232,6 +222,22 @@
 
             return super().getWorkspace()
 
+    def getWorkspace(self, silent=False):
+        """
+        Public method to get the workspace directory.
+
+        @param silent flag indicating silent operations
+        @type bool
+        @return workspace directory used for saving files
+        @rtype str
+        """
+        if self.__workspace:
+            # return cached entry
+            return self.__workspace
+        else:
+            self.__workspace = self.__findWorkspace(silent=silent)
+            return self.__workspace
+
     def addDeviceMenuEntries(self, menu):
         """
         Public method to add device specific entries to the given menu.
@@ -242,15 +248,8 @@
         connected = self.microPython.isConnected()
 
         self.__libraryMenu = QMenu(self.tr("Library Management"))
-        act = self.__libraryMenu.addAction(
-            self.tr("Install Library Files"), self.__installLibraryFiles
-        )
-        act.setEnabled(self.__deviceVolumeMounted())
-        act = self.__libraryMenu.addAction(
-            self.tr("Install Library Package"),
-            lambda: self.__installLibraryFiles(packageMode=True),
-        )
-        act.setEnabled(self.__deviceVolumeMounted())
+        self.__libraryMenu.aboutToShow.connect(self.__aboutToShowLibraryMenu)
+        self.__libraryMenu.setTearOffEnabled(True)
 
         act = menu.addAction(
             self.tr("Flash CircuitPython Firmware"), self.__flashCircuitPython
@@ -259,6 +258,31 @@
         menu.addSeparator()
         menu.addMenu(self.__libraryMenu)
 
+    @pyqtSlot()
+    def __aboutToShowLibraryMenu(self):
+        """
+        Private slot to populate the 'Library Management' menu.
+        """
+        self.__libraryMenu.clear()
+
+        if isCircupAvailable():
+            self.__updater.populateMenu(self.__libraryMenu)
+        else:
+            act = self.__libraryMenu.addAction(
+                self.tr("Install Library Files"), self.__installLibraryFiles
+            )
+            act.setEnabled(self.__deviceVolumeMounted())
+            act = self.__libraryMenu.addAction(
+                self.tr("Install Library Package"),
+                lambda: self.__installLibraryFiles(packageMode=True),
+            )
+            act.setEnabled(self.__deviceVolumeMounted())
+            self.__libraryMenu.addSeparator()
+            self.__libraryMenu.addAction(
+                self.tr("Install 'circup' Package"),
+                self.__updater.installCircup,
+            )
+
     def hasFlashMenuEntry(self):
         """
         Public method to check, if the device has its own flash menu entry.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/CircuitPythonUpdaterInterface.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,652 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an interface to the 'circup' package.
+"""
+
+import importlib
+import logging
+import os
+import re
+import shutil
+
+import requests
+
+from PyQt6.QtCore import QObject, pyqtSlot
+from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu
+
+from eric7 import Preferences
+from eric7.EricGui.EricOverrideCursor import EricOverrideCursor
+from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog
+from eric7.SystemUtilities import PythonUtilities
+
+try:
+    import circup
+
+    circup.logger.setLevel(logging.WARNING)
+except ImportError:
+    circup = None
+
+
+class CircuitPythonUpdaterInterface(QObject):
+    """
+    Class implementing an interface to the 'circup' package.
+    """
+
+    def __init__(self, device, parent=None):
+        """
+        Constructor
+
+        @param device reference to the CircuitPython device interface
+        @type CircuitPythonDevice
+        @param parent reference to the parent object (defaults to None)
+        @type QObject (optional)
+        """
+        super().__init__(parent)
+
+        self.__device = device
+
+        self.__installMenu = QMenu(self.tr("Install Modules"))
+        self.__installMenu.setTearOffEnabled(True)
+        self.__installMenu.addAction(
+            self.tr("Select from Available Modules"), self.__installFromAvailable
+        )
+        self.__installMenu.addAction(
+            self.tr("Install Requirements"), self.__installRequirements
+        )
+        self.__installMenu.addAction(
+            self.tr("Install based on 'code.py'"), self.__installFromCode
+        )
+        self.__installMenu.addSeparator()
+        self.__installPyAct = self.__installMenu.addAction(
+            self.tr("Install Python Source")
+        )
+        self.__installPyAct.setCheckable(True)
+        self.__installPyAct.setChecked(False)
+        # kind of hack to make this action not hide the menu
+        # Note: parent menus are hidden nevertheless
+        self.__installPyAct.toggled.connect(self.__installMenu.show)
+
+    def populateMenu(self, menu):
+        """
+        Public method to populate the 'circup' menu.
+
+        @param menu reference to the menu to be populated
+        @type QMenu
+        """
+        from .CircupFunctions import patch_circup
+
+        patch_circup()
+
+        act = menu.addAction(self.tr("circup"), self.__aboutCircup)
+        font = act.font()
+        font.setBold(True)
+        act.setFont(font)
+        menu.addSeparator()
+        menu.addAction(self.tr("List Outdated Modules"), self.__listOutdatedModules)
+        menu.addAction(self.tr("Update Modules"), self.__updateModules)
+        menu.addAction(self.tr("Update All Modules"), self.__updateAllModules)
+        menu.addSeparator()
+        menu.addAction(self.tr("Show Available Modules"), self.__showAvailableModules)
+        menu.addAction(self.tr("Show Installed Modules"), self.__showInstalledModules)
+        menu.addMenu(self.__installMenu)
+        menu.addAction(self.tr("Uninstall Modules"), self.__uninstallModules)
+        menu.addSeparator()
+        menu.addAction(
+            self.tr("Generate Requirements ..."), self.__generateRequirements
+        )
+        menu.addSeparator()
+        menu.addAction(self.tr("Show Bundles"), self.__showBundles)
+        menu.addAction(self.tr("Show Bundles with Modules"), self.__showBundlesModules)
+        menu.addSeparator()
+        menu.addAction(self.tr("Add Bundle"), self.__addBundle)
+        menu.addAction(self.tr("Remove Bundles"), self.__removeBundle)
+
+    @pyqtSlot()
+    def __aboutCircup(self):
+        """
+        Private slot to show some info about 'circup'.
+        """
+        version = circup.get_circup_version()
+        if version is None:
+            version = self.tr("unknown")
+
+        EricMessageBox.information(
+            None,
+            self.tr("About circup"),
+            self.tr(
+                """<p><b>circup Version {0}</b></p>"""
+                """<p><i>circup</i> is a tool to manage and update libraries on a"""
+                """ CircuitPython device.</p>""",
+            ).format(version),
+        )
+
+    @pyqtSlot()
+    def installCircup(self):
+        """
+        Public slot to install the 'circup' package via pip.
+        """
+        global circup
+
+        pip = ericApp().getObject("Pip")
+        pip.installPackages(
+            ["circup"], interpreter=PythonUtilities.getPythonExecutable()
+        )
+
+        circup = importlib.import_module("circup")
+        circup.logger.setLevel(logging.WARNING)
+
+    @pyqtSlot()
+    def __showBundles(self, withModules=False):
+        """
+        Private slot to show the available bundles (default and local).
+
+        @param withModules flag indicating to list the modules and their version
+            (defaults to False)
+        @type bool (optional)
+        """
+        from .ShowBundlesDialog import ShowBundlesDialog
+
+        with EricOverrideCursor():
+            dlg = ShowBundlesDialog(withModules=withModules)
+        dlg.exec()
+
+    @pyqtSlot()
+    def __showBundlesModules(self):
+        """
+        Private slot to show the available bundles (default and local) with their
+        modules.
+        """
+        self.__showBundles(withModules=True)
+
+    @pyqtSlot()
+    def __addBundle(self):
+        """
+        Private slot to add a bundle to the local bundles list, by "user/repo" github
+        string.
+        """
+        bundle, ok = QInputDialog.getText(
+            None,
+            self.tr("Add Bundle"),
+            self.tr("Enter Bundle by 'User/Repo' Github String:"),
+            QLineEdit.EchoMode.Normal,
+        )
+        if ok and bundle:
+            bundles = circup.get_bundles_local_dict()
+            modified = False
+
+            # do some cleanup
+            bundle = re.sub(r"https?://github.com/([^/]+/[^/]+)(/.*)?", r"\1", bundle)
+            if bundle in bundles:
+                EricMessageBox.information(
+                    None,
+                    self.tr("Add Bundle"),
+                    self.tr(
+                        """<p>The bundle <b>{0}</b> is already in the list.</p>"""
+                    ).format(bundle),
+                )
+                return
+
+            try:
+                cBundle = circup.Bundle(bundle)
+            except ValueError:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Add Bundle"),
+                    self.tr(
+                        """<p>The bundle string is invalid, expecting github URL"""
+                        """ or 'user/repository' string.</p>"""
+                    ),
+                )
+                return
+
+            result = requests.head("https://github.com/" + bundle)
+            if result.status_code == requests.codes.NOT_FOUND:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Add Bundle"),
+                    self.tr(
+                        """<p>The bundle string is invalid. The repository doesn't"""
+                        """ exist (error code 404).</p>"""
+                    ),
+                )
+                return
+
+            if not cBundle.validate():
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Add Bundle"),
+                    self.tr(
+                        """<p>The bundle string is invalid. Is the repository a valid"""
+                        """circup bundle?</p>"""
+                    ),
+                )
+                return
+
+            # Use the bundle string as the dictionary key for uniqueness
+            bundles[bundle] = bundle
+            modified = True
+            EricMessageBox.information(
+                None,
+                self.tr("Add Bundle"),
+                self.tr("""<p>Added bundle <b>{0}</b> ({1}).</p>""").format(
+                    bundle, cBundle.url
+                ),
+            )
+
+            if modified:
+                # save the bundles list
+                circup.save_local_bundles(bundles)
+                # update and get the new bundle for the first time
+                circup.get_bundle_versions(circup.get_bundles_list())
+
+    @pyqtSlot()
+    def __removeBundle(self):
+        """
+        Private slot to remove one or more bundles from the local bundles list.
+        """
+        localBundles = circup.get_bundles_local_dict()
+        dlg = EricListSelectionDialog(
+            sorted(localBundles.keys()),
+            title=self.tr("Remove Bundles"),
+            message=self.tr("Select the bundles to be removed:"),
+            checkBoxSelection=True,
+        )
+        modified = False
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            bundles = dlg.getSelection()
+            for bundle in bundles:
+                del localBundles[bundle]
+                modified = True
+
+        if modified:
+            circup.save_local_bundles(localBundles)
+            EricMessageBox.information(
+                None,
+                self.tr("Remove Bundles"),
+                self.tr(
+                    """<p>These bundles were removed from the local bundles list.{0}"""
+                    """</p>"""
+                ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(bundles))),
+            )
+
+    @pyqtSlot()
+    def __listOutdatedModules(self):
+        """
+        Private slot to list the outdated modules of the connected device.
+        """
+        from .ShowOutdatedDialog import ShowOutdatedDialog
+
+        devicePath = self.__device.getWorkspace()
+
+        cpyVersion, board_id = circup.get_circuitpython_version(devicePath)
+        circup.CPY_VERSION = cpyVersion
+
+        with EricOverrideCursor():
+            dlg = ShowOutdatedDialog(devicePath=devicePath)
+        dlg.exec()
+
+    @pyqtSlot()
+    def __updateModules(self):
+        """
+        Private slot to update the modules of the connected device.
+        """
+        from .ShowOutdatedDialog import ShowOutdatedDialog
+
+        devicePath = self.__device.getWorkspace()
+
+        cpyVersion, board_id = circup.get_circuitpython_version(devicePath)
+        circup.CPY_VERSION = cpyVersion
+
+        with EricOverrideCursor():
+            dlg = ShowOutdatedDialog(devicePath=devicePath, selectionMode=True)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            modules = dlg.getSelection()
+            self.__doUpdateModules(modules)
+
+    @pyqtSlot()
+    def __updateAllModules(self):
+        """
+        Private slot to update all modules of the connected device.
+        """
+        devicePath = self.__device.getWorkspace()
+
+        cpyVersion, board_id = circup.get_circuitpython_version(devicePath)
+        circup.CPY_VERSION = cpyVersion
+
+        with EricOverrideCursor():
+            modules = [
+                m
+                for m in circup.find_modules(devicePath, circup.get_bundles_list())
+                if m.outofdate
+            ]
+        if modules:
+            self.__doUpdateModules(modules)
+        else:
+            EricMessageBox.information(
+                None,
+                self.tr("Update Modules"),
+                self.tr("All modules are already up-to-date."),
+            )
+
+    def __doUpdateModules(self, modules):
+        """
+        Private method to perform the update of a list of modules.
+
+        @param modules list of modules to be updated
+        @type circup.Module
+        """
+        updatedModules = []
+        for module in modules:
+            try:
+                module.update()
+                updatedModules.append(module.name)
+            except Exception as ex:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Update Modules"),
+                    self.tr(
+                        """<p>There was an error updating <b>{0}</b>.</p>"""
+                        """<p>Error: {1}</p>"""
+                    ).format(module.name, str(ex)),
+                )
+
+        if updatedModules:
+            EricMessageBox.information(
+                None,
+                self.tr("Update Modules"),
+                self.tr(
+                    """<p>These modules were updated on the connected device.{0}</p>"""
+                ).format(
+                    """<ul><li>{0}</li></ul>""".format("</li><li>".join(updatedModules))
+                ),
+            )
+        else:
+            EricMessageBox.information(
+                None,
+                self.tr("Update Modules"),
+                self.tr("No modules could be updated."),
+            )
+
+    @pyqtSlot()
+    def __showAvailableModules(self):
+        """
+        Private slot to show the available modules.
+
+        These are modules which could be installed on the device.
+        """
+        from .ShowModulesDialog import ShowModulesDialog
+
+        with EricOverrideCursor():
+            dlg = ShowModulesDialog()
+        dlg.exec()
+
+    @pyqtSlot()
+    def __showInstalledModules(self):
+        """
+        Private slot to show the modules installed on the connected device.
+        """
+        from .ShowInstalledDialog import ShowInstalledDialog
+
+        devicePath = self.__device.getWorkspace()
+
+        with EricOverrideCursor():
+            dlg = ShowInstalledDialog(devicePath=devicePath)
+        dlg.exec()
+
+    @pyqtSlot()
+    def __installFromAvailable(self):
+        """
+        Private slot to install modules onto the connected device.
+        """
+        from .ShowModulesDialog import ShowModulesDialog
+
+        with EricOverrideCursor():
+            dlg = ShowModulesDialog(selectionMode=True)
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            modules = dlg.getSelection()
+            self.__installModules(modules)
+
+    @pyqtSlot()
+    def __installRequirements(self):
+        """
+        Private slot to install modules determined by a requirements file.
+        """
+        homeDir = (
+            Preferences.getMicroPython("MpyWorkspace")
+            or Preferences.getMultiProject("Workspace")
+            or os.path.expanduser("~")
+        )
+        reqFile = EricFileDialog.getOpenFileName(
+            None,
+            self.tr("Install Modules"),
+            homeDir,
+            self.tr("Text Files (*.txt);;All Files (*)"),
+        )
+        if reqFile:
+            if os.path.exists(reqFile):
+                with open(reqFile, "r") as fp:
+                    requirementsText = fp.read()
+                modules = circup.libraries_from_requirements(requirementsText)
+                if modules:
+                    self.__installModules(modules)
+                else:
+                    EricMessageBox.critical(
+                        None,
+                        self.tr("Install Modules"),
+                        self.tr(
+                            """<p>The given requirements file <b>{0}</b> does not"""
+                            """ contain valid modules.</p>"""
+                        ).format(reqFile),
+                    )
+            else:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Install Modules"),
+                    self.tr(
+                        """<p>The given requirements file <b>{0}</b> does not exist."""
+                        """</p>"""
+                    ).format(reqFile),
+                )
+
+    @pyqtSlot()
+    def __installFromCode(self):
+        """
+        Private slot to install modules based on the 'code.py' file of the
+        connected device.
+        """
+        devicePath = self.__device.getWorkspace()
+
+        codeFile = EricFileDialog.getOpenFileName(
+            None,
+            self.tr("Install Modules"),
+            os.path.join(devicePath, "code.py"),
+            self.tr("Python Files (*.py);;All Files (*)"),
+        )
+        if codeFile:
+            if os.path.exists(codeFile):
+
+                with EricOverrideCursor():
+                    availableModules = circup.get_bundle_versions(
+                        circup.get_bundles_list()
+                    )
+                    moduleNames = {}
+                    for module, metadata in availableModules.items():
+                        moduleNames[module.replace(".py", "")] = metadata
+
+                modules = circup.libraries_from_imports(codeFile, moduleNames)
+                if modules:
+                    self.__installModules(modules)
+                else:
+                    EricMessageBox.critical(
+                        None,
+                        self.tr("Install Modules"),
+                        self.tr(
+                            """<p>The given code file <b>{0}</b> does not"""
+                            """ contain valid import statements or does not import"""
+                            """ external modules.</p>"""
+                        ).format(codeFile),
+                    )
+            else:
+                EricMessageBox.critical(
+                    None,
+                    self.tr("Install Modules"),
+                    self.tr(
+                        """<p>The given code file <b>{0}</b> does not exist.</p>"""
+                    ).format(codeFile),
+                )
+
+    def __installModules(self, installs):
+        """
+        Private method to install the given list of modules.
+
+        @param installs list of module names to be installed
+        @type list of str
+        """
+        devicePath = self.__device.getWorkspace()
+
+        cpyVersion, board_id = circup.get_circuitpython_version(devicePath)
+        circup.CPY_VERSION = cpyVersion
+
+        with EricOverrideCursor():
+            availableModules = circup.get_bundle_versions(circup.get_bundles_list())
+            moduleNames = {}
+            for module, metadata in availableModules.items():
+                moduleNames[module.replace(".py", "")] = metadata
+            toBeInstalled = circup.get_dependencies(installs, mod_names=moduleNames)
+            deviceModules = circup.get_device_versions(devicePath)
+        if toBeInstalled is not None:
+            dependencies = [m for m in toBeInstalled if m not in installs]
+            ok = EricMessageBox.yesNo(
+                None,
+                self.tr("Install Modules"),
+                self.tr("""<p>Ready to install these modules?{0}{1}</p>""").format(
+                    """<ul><li>{0}</li></ul>""".format(
+                        "</li><li>".join(sorted(installs))
+                    ),
+                    self.tr("Dependencies:")
+                    + """<ul><li>{0}</li></ul>""".format(
+                        "</li><li>".join(sorted(dependencies))
+                    )
+                    if dependencies
+                    else "",
+                ),
+                yesDefault=True,
+            )
+            if ok:
+                installedModules = []
+                with EricOverrideCursor():
+                    for library in toBeInstalled:
+                        success = circup.install_module(
+                            devicePath,
+                            deviceModules,
+                            library,
+                            self.__installPyAct.isChecked(),
+                            moduleNames,
+                        )
+                        if success:
+                            installedModules.append(library)
+
+                if installedModules:
+                    EricMessageBox.information(
+                        None,
+                        self.tr("Install Modules"),
+                        self.tr(
+                            "<p>Installation complete. These modules were installed"
+                            " successfully.{0}</p>"
+                        ).format(
+                            """<ul><li>{0}</li></ul>""".format(
+                                "</li><li>".join(sorted(installedModules))
+                            ),
+                        ),
+                    )
+                else:
+                    EricMessageBox.information(
+                        None,
+                        self.tr("Install Modules"),
+                        self.tr(
+                            "<p>Installation complete. No modules were installed.</p>"
+                        ),
+                    )
+        else:
+            EricMessageBox.information(
+                None,
+                self.tr("Install Modules"),
+                self.tr("<p>No modules installation is required.</p>"),
+            )
+
+    @pyqtSlot()
+    def __uninstallModules(self):
+        """
+        Private slot to uninstall modules from the connected device.
+        """
+        devicePath = self.__device.getWorkspace()
+        libraryPath = os.path.join(devicePath, "lib")
+
+        with EricOverrideCursor():
+            deviceModules = circup.get_device_versions(devicePath)
+        modNames = {}
+        for moduleItem, metadata in deviceModules.items():
+            modNames[moduleItem.replace(".py", "").lower()] = metadata
+
+        dlg = EricListSelectionDialog(
+            sorted(modNames.keys()),
+            title=self.tr("Uninstall Modules"),
+            message=self.tr("Select the modules/packages to be uninstalled:"),
+            checkBoxSelection=True,
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            names = dlg.getSelection()
+            for name in names:
+                modulePath = modNames[name]["path"]
+                if os.path.isdir(modulePath):
+                    target = os.path.basename(os.path.dirname(modulePath))
+                    targetPath = os.path.join(libraryPath, target)
+                    # Remove the package directory.
+                    shutil.rmtree(targetPath)
+                else:
+                    target = os.path.basename(modulePath)
+                    targetPath = os.path.join(libraryPath, target)
+                    # Remove the module file
+                    os.remove(targetPath)
+
+            EricMessageBox.information(
+                None,
+                self.tr("Uninstall Modules"),
+                self.tr(
+                    """<p>These modules/packages were uninstalled from the connected"""
+                    """ device.{0}</p>"""
+                ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(names))),
+            )
+
+    @pyqtSlot()
+    def __generateRequirements(self):
+        """
+        Private slot to generate requirements for the connected device.
+        """
+        from .RequirementsDialog import RequirementsDialog
+
+        devicePath = self.__device.getWorkspace()
+
+        cpyVersion, board_id = circup.get_circuitpython_version(devicePath)
+        circup.CPY_VERSION = cpyVersion
+
+        dlg = RequirementsDialog(devicePath=devicePath)
+        dlg.exec()
+
+
+def isCircupAvailable():
+    """
+    Function to check for the availability of 'circup'.
+
+    @return flag indicating the availability of 'circup'
+    @rtype bool
+    """
+    global circup
+
+    return circup is not None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/CircupFunctions.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing variants of some 'circup' functions suitable for 'eric-ide'
+integration.
+"""
+
+#
+# Copyright of the original sources:
+# Copyright (c) 2019 Adafruit Industries
+#
+
+import os
+import shutil
+
+import circup
+import requests
+
+from PyQt6.QtCore import QCoreApplication
+
+from eric7.EricWidgets import EricMessageBox
+
+
+def find_modules(device_path, bundles_list):
+    """
+    Function to extract metadata from the connected device and available bundles and
+    returns this as a list of Module instances representing the modules on the device.
+
+    @param device_path path to the connected board
+    @type str
+    @param bundles_list list of supported bundles
+    @type list of circup.Bundle
+    @return list of Module instances describing the current state of the
+        modules on the connected device
+    @rtype list of circup.Module
+    """
+    result = []
+    try:
+        device_modules = circup.get_device_versions(device_path)
+        bundle_modules = circup.get_bundle_versions(bundles_list)
+        for name, device_metadata in device_modules.items():
+            if name in bundle_modules:
+                path = device_metadata["path"]
+                bundle_metadata = bundle_modules[name]
+                repo = bundle_metadata.get("__repo__")
+                bundle = bundle_metadata.get("bundle")
+                device_version = device_metadata.get("__version__")
+                bundle_version = bundle_metadata.get("__version__")
+                mpy = device_metadata["mpy"]
+                compatibility = device_metadata.get("compatibility", (None, None))
+                result.append(
+                    circup.Module(
+                        path,
+                        repo,
+                        device_version,
+                        bundle_version,
+                        mpy,
+                        bundle,
+                        compatibility,
+                    )
+                )
+    except Exception as ex:
+        # If it's not possible to get the device and bundle metadata, bail out
+        # with a friendly message and indication of what's gone wrong.
+        EricMessageBox.critical(
+            None,
+            QCoreApplication.translate("CircupFunctions", "Find Modules"),
+            QCoreApplication.translate(
+                "CircupFunctions", """<p>There was an error: {0}</p>"""
+            ).format(str(ex)),
+        )
+
+    return result
+
+
+def ensure_latest_bundle(bundle):
+    """
+    Function to ensure that there's a copy of the latest library bundle available so
+    circup can check the metadata contained therein.
+
+    @param bundle reference to the target Bundle object.
+    @type circup.Bundle
+    """
+    tag = bundle.latest_tag
+    do_update = False
+    if tag == bundle.current_tag:
+        for platform in circup.PLATFORMS:
+            # missing directories (new platform added on an existing install
+            # or side effect of pytest or network errors)
+            do_update = do_update or not os.path.isdir(bundle.lib_dir(platform))
+    else:
+        do_update = True
+
+    if do_update:
+        try:
+            circup.get_bundle(bundle, tag)
+            circup.tags_data_save_tag(bundle.key, tag)
+        except requests.exceptions.HTTPError as ex:
+            EricMessageBox.critical(
+                None,
+                QCoreApplication.translate("CircupFunctions", "Download Bundle"),
+                QCoreApplication.translate(
+                    "CircupFunctions",
+                    """<p>There was a problem downloading the bundle. Please try"""
+                    """ again in a moment.</p><p>Error: {0}</p>""",
+                ).format(str(ex)),
+            )
+
+
+def get_circuitpython_version(device_path):
+    """
+    Function to return the version number of CircuitPython running on the board
+    connected via ``device_path``, along with the board ID.
+
+    This is obtained from the 'boot_out.txt' file on the device, whose first line
+    will start with something like this:
+
+        Adafruit CircuitPython 4.1.0 on 2019-08-02;
+
+    While the second line is:
+
+        Board ID:raspberry_pi_pico
+
+    @param device_path path to the connected board.
+    @type str
+    @return tuple with the version string for CircuitPython and the board ID string
+    @rtype tuple of (str, str)
+    """
+    try:
+        with open(os.path.join(device_path, "boot_out.txt")) as boot:
+            version_line = boot.readline()
+            circuit_python = version_line.split(";")[0].split(" ")[-3]
+            board_line = boot.readline()
+            board_id = (
+                board_line[9:].strip()
+                if board_line.startswith("Board ID:")
+                else ""
+            )
+    except FileNotFoundError:
+        EricMessageBox.critical(
+            None,
+            QCoreApplication.translate("CircupFunctions", "Download Bundle"),
+            QCoreApplication.translate(
+                "CircupFunctions",
+                """<p>Missing file <b>boot_out.txt</b> on the device: wrong path or"""
+                """ drive corrupted.</p>""",
+            ),
+        )
+        circuit_python, board_id = "", ""
+
+    return (circuit_python, board_id)
+
+
+def install_module(device_path, device_modules, name, py, mod_names):
+    """
+    Function to find a connected device and install a given module name.
+
+    Installation is done if it is available in the current module bundle and is not
+    already installed on the device.
+
+    @param device_path path to the connected board
+    @type str
+    @param device_modules list of module metadata from the device
+    @type list of dict
+    @param name name of the module to be installed
+    @type str
+    @param py flag indicating if the module should be installed from source or
+        from a pre-compiled module
+    @type bool
+    @param mod_names dictionary containing metadata from modules that can be generated
+        with circup.get_bundle_versions()
+    @type dict
+    @return flag indicating success
+    @rtype bool
+    """
+    if not name:
+        return False
+    elif name in mod_names:
+        library_path = os.path.join(device_path, "lib")
+        if not os.path.exists(library_path):  # pragma: no cover
+            os.makedirs(library_path)
+        metadata = mod_names[name]
+        bundle = metadata["bundle"]
+        # Grab device modules to check if module already installed
+        if name in device_modules:
+            # ignore silently
+            return False
+        if py:
+            # Use Python source for module.
+            source_path = metadata["path"]  # Path to Python source version.
+            if os.path.isdir(source_path):
+                target = os.path.basename(os.path.dirname(source_path))
+                target_path = os.path.join(library_path, target)
+                # Copy the directory.
+                shutil.copytree(source_path, target_path)
+                return True
+            else:
+                target = os.path.basename(source_path)
+                target_path = os.path.join(library_path, target)
+                # Copy file.
+                shutil.copyfile(source_path, target_path)
+                return True
+        else:
+            # Use pre-compiled mpy modules.
+            module_name = os.path.basename(metadata["path"]).replace(".py", ".mpy")
+            if not module_name:
+                # Must be a directory based module.
+                module_name = os.path.basename(os.path.dirname(metadata["path"]))
+            major_version = circup.CPY_VERSION.split(".")[0]
+            bundle_platform = "{0}mpy".format(major_version)
+            bundle_path = os.path.join(bundle.lib_dir(bundle_platform), module_name)
+            if os.path.isdir(bundle_path):
+                target_path = os.path.join(library_path, module_name)
+                # Copy the directory.
+                shutil.copytree(bundle_path, target_path)
+                return True
+            elif os.path.isfile(bundle_path):
+                target = os.path.basename(bundle_path)
+                target_path = os.path.join(library_path, target)
+                # Copy file.
+                shutil.copyfile(bundle_path, target_path)
+                return True
+            else:
+                EricMessageBox.critical(
+                    None,
+                    QCoreApplication.translate("CircupFunctions", "Install Modules"),
+                    QCoreApplication.translate(
+                        "CircupFunctions",
+                        """<p>The compiled version of module <b>{0}</b> cannot be"""
+                        """ found.</p>""",
+                    ).format(name),
+                )
+                return False
+    else:
+        EricMessageBox.critical(
+            None,
+            QCoreApplication.translate("CircupFunctions", "Install Modules"),
+            QCoreApplication.translate(
+                "CircupFunctions", """<p>The module name <b>{0}</b> is not known.</p>"""
+            ).format(name),
+        )
+        return False
+
+
+def patch_circup():
+    """
+    Function to patch 'circup' to use our functions adapted to the use within the
+    eric-ide.
+    """
+    circup.ensure_latest_bundle = ensure_latest_bundle
+    circup.find_modules = find_modules
+    circup.get_circuitpython_version = get_circuitpython_version
+    circup.install_module = install_module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/RequirementsDialog.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,265 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to generate a requirements file.
+"""
+
+import os
+
+import circup
+
+from PyQt6.QtCore import pyqtSlot
+from PyQt6.QtGui import QGuiApplication
+from PyQt6.QtWidgets import QAbstractButton, QDialog, QDialogButtonBox
+
+from eric7.EricWidgets import EricFileDialog, EricMessageBox
+from eric7.EricWidgets.EricApplication import ericApp
+from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
+from eric7.SystemUtilities import FileSystemUtilities
+
+from .Ui_RequirementsDialog import Ui_RequirementsDialog
+
+
+class RequirementsDialog(QDialog, Ui_RequirementsDialog):
+    """
+    Class implementing a dialog to generate a requirements file.
+    """
+
+    def __init__(self, devicePath, parent=None):
+        """
+        Constructor
+
+        @param devicePath path to the connected board
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.__title = self.tr("Generate Requirements")
+
+        self.__refreshButton = self.buttonBox.addButton(
+            self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole
+        )
+
+        self.requirementsFilePicker.setMode(EricPathPickerModes.SAVE_FILE_MODE)
+        self.requirementsFilePicker.setFilters(
+            self.tr("Text Files (*.txt);;All Files (*)")
+        )
+
+        self.__devicePath = devicePath
+
+        self.__requirementsEdited = False
+        self.__requirementsAvailable = False
+
+        self.__generateRequirements()
+
+    def __updateButtons(self):
+        """
+        Private method to set the state of the various buttons.
+        """
+        self.saveButton.setEnabled(
+            self.__requirementsAvailable
+            and bool(self.requirementsFilePicker.text())
+            and os.path.isabs(self.requirementsFilePicker.text())
+        )
+        self.saveToButton.setEnabled(self.__requirementsAvailable)
+        self.copyButton.setEnabled(self.__requirementsAvailable)
+
+        aw = ericApp().getObject("ViewManager").activeWindow()
+        if aw and self.__requirementsAvailable:
+            self.insertButton.setEnabled(True)
+            self.replaceAllButton.setEnabled(True)
+            self.replaceSelectionButton.setEnabled(aw.hasSelectedText())
+        else:
+            self.insertButton.setEnabled(False)
+            self.replaceAllButton.setEnabled(False)
+            self.replaceSelectionButton.setEnabled(False)
+
+    @pyqtSlot(str)
+    def on_requirementsFilePicker_textChanged(self, txt):
+        """
+        Private slot handling a change of the requirements file name.
+
+        @param txt name of the requirements file
+        @type str
+        """
+        self.__updateButtons()
+
+    @pyqtSlot()
+    def on_requirementsEdit_textChanged(self):
+        """
+        Private slot handling changes of the requirements text.
+        """
+        self.__requirementsEdited = True
+
+    @pyqtSlot(QAbstractButton)
+    def on_buttonBox_clicked(self, button):
+        """
+        Private slot called by a button of the button box clicked.
+
+        @param button button that was clicked
+        @type QAbstractButton
+        """
+        if button == self.buttonBox.button(QDialogButtonBox.StandardButton.Close):
+            self.close()
+        elif button == self.__refreshButton:
+            self.__generateRequirements()
+
+    def __generateRequirements(self):
+        """
+        Private slot to generate the requirements specifiers list.
+        """
+        ok = (
+            EricMessageBox.yesNo(
+                self,
+                self.__title,
+                self.tr(
+                    """The requirements were changed. Do you want"""
+                    """ to overwrite these changes?"""
+                ),
+            )
+            if self.__requirementsEdited
+            else True
+        )
+        if ok:
+            self.requirementsEdit.clear()
+            self.__requirementsAvailable = False
+
+            if not bool(self.requirementsFilePicker.text()):
+                self.requirementsFilePicker.setText("requirements.txt")
+
+            fileName = FileSystemUtilities.toNativeSeparators(
+                self.requirementsFilePicker.text()
+            )
+            if fileName and not os.path.isabs(fileName):
+                fileName = ""
+
+            modules = circup.find_modules(self.__devicePath, circup.get_bundles_list())
+            specifiers = []
+            if modules:
+                for module in modules:
+                    specifiers.append(
+                        "{0}=={1}".format(module.name, module.device_version)
+                    )
+
+            if specifiers:
+                self.requirementsEdit.setPlainText("\n".join(specifiers) + "\n")
+                self.__requirementsAvailable = True
+            else:
+                self.requirementsEdit.setPlainText(
+                    self.tr("No package specifiers generated by 'pip freeze'.")
+                )
+
+            self.__updateButtons()
+
+            self.__requirementsEdited = False
+
+    def __writeToFile(self, fileName):
+        """
+        Private method to write the requirements text to a file.
+
+        @param fileName name of the file to write to
+        @type str
+        """
+        if os.path.exists(fileName):
+            ok = EricMessageBox.warning(
+                self,
+                self.__title,
+                self.tr(
+                    """The file <b>{0}</b> already exists. Do you want"""
+                    """ to overwrite it?"""
+                ).format(fileName),
+            )
+            if not ok:
+                return
+
+        txt = self.requirementsEdit.toPlainText()
+        try:
+            with open(fileName, "w") as f:
+                f.write(txt)
+        except OSError as err:
+            EricMessageBox.critical(
+                self,
+                self.__title,
+                self.tr(
+                    """<p>The requirements could not be written"""
+                    """ to <b>{0}</b>.</p><p>Reason: {1}</p>"""
+                ).format(fileName, str(err)),
+            )
+
+    @pyqtSlot()
+    def on_saveButton_clicked(self):
+        """
+        Private slot to save the requirements text to the requirements file.
+        """
+        fileName = self.requirementsFilePicker.text()
+        self.__writeToFile(fileName)
+
+    @pyqtSlot()
+    def on_saveToButton_clicked(self):
+        """
+        Private slot to write the requirements text to a new file.
+        """
+        fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
+            self,
+            self.__title,
+            os.path.expanduser("~"),
+            self.tr("Text Files (*.txt);;All Files (*)"),
+            None,
+            EricFileDialog.DontConfirmOverwrite,
+        )
+        if fileName:
+            ext = os.path.splitext(fileName)[1]
+            if not ext:
+                ex = selectedFilter.split("(*")[1].split(")")[0]
+                if ex:
+                    fileName += ex
+            self.__writeToFile(fileName)
+
+    @pyqtSlot()
+    def on_copyButton_clicked(self):
+        """
+        Private slot to copy the requirements text to the clipboard.
+        """
+        txt = self.requirementsEdit.toPlainText()
+        cb = QGuiApplication.clipboard()
+        cb.setText(txt)
+
+    @pyqtSlot()
+    def on_insertButton_clicked(self):
+        """
+        Private slot to insert the requirements text at the cursor position
+        of the current editor.
+        """
+        aw = ericApp().getObject("ViewManager").activeWindow()
+        if aw:
+            aw.beginUndoAction()
+            aw.insert(self.requirementsEdit.toPlainText())
+            aw.endUndoAction()
+
+    @pyqtSlot()
+    def on_replaceSelectionButton_clicked(self):
+        """
+        Private slot to replace the selected text of the current editor
+        with the requirements text.
+        """
+        aw = ericApp().getObject("ViewManager").activeWindow()
+        if aw:
+            aw.beginUndoAction()
+            aw.replaceSelectedText(self.requirementsEdit.toPlainText())
+            aw.endUndoAction()
+
+    @pyqtSlot()
+    def on_replaceAllButton_clicked(self):
+        """
+        Private slot to replace the text of the current editor with the
+        requirements text.
+        """
+        aw = ericApp().getObject("ViewManager").activeWindow()
+        if aw:
+            aw.setText(self.requirementsEdit.toPlainText())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/RequirementsDialog.ui	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,210 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RequirementsDialog</class>
+ <widget class="QDialog" name="RequirementsDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>550</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Generate Requirements</string>
+  </property>
+  <property name="toolTip">
+   <string>Replace the current selection with the requirements text</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Requirements File:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="EricPathPicker" name="requirementsFilePicker" native="true">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="focusPolicy">
+        <enum>Qt::StrongFocus</enum>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="3" column="1">
+      <widget class="Line" name="line">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QPushButton" name="copyButton">
+       <property name="toolTip">
+        <string>Copy the requirements text to the clipboard</string>
+       </property>
+       <property name="text">
+        <string>Copy</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QPushButton" name="saveButton">
+       <property name="toolTip">
+        <string>Press to save to the requirements file</string>
+       </property>
+       <property name="text">
+        <string>Save</string>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="1">
+      <widget class="QPushButton" name="insertButton">
+       <property name="toolTip">
+        <string>Insert the requirements text at the cursor position</string>
+       </property>
+       <property name="text">
+        <string>Insert</string>
+       </property>
+      </widget>
+     </item>
+     <item row="6" column="1">
+      <widget class="QPushButton" name="replaceSelectionButton">
+       <property name="text">
+        <string>Replace Selection</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="0" rowspan="9">
+      <widget class="QPlainTextEdit" name="requirementsEdit">
+       <property name="tabChangesFocus">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="7" column="1">
+      <widget class="QPushButton" name="replaceAllButton">
+       <property name="toolTip">
+        <string>Replace all text with the requirements text</string>
+       </property>
+       <property name="text">
+        <string>Replace All</string>
+       </property>
+      </widget>
+     </item>
+     <item row="8" column="1">
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item row="1" column="1">
+      <widget class="QPushButton" name="saveToButton">
+       <property name="toolTip">
+        <string>Save to a new file</string>
+       </property>
+       <property name="text">
+        <string>Save To</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="1">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>&lt;b&gt;Editor Actions&lt;/b&gt;</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </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>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>EricPathPicker</class>
+   <extends>QWidget</extends>
+   <header>eric7/EricWidgets/EricPathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>requirementsFilePicker</tabstop>
+  <tabstop>requirementsEdit</tabstop>
+  <tabstop>saveButton</tabstop>
+  <tabstop>saveToButton</tabstop>
+  <tabstop>copyButton</tabstop>
+  <tabstop>insertButton</tabstop>
+  <tabstop>replaceSelectionButton</tabstop>
+  <tabstop>replaceAllButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>RequirementsDialog</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>RequirementsDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowBundlesDialog.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog showing the available bundles and their modules.
+"""
+
+import circup
+
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QDialog, QTreeWidgetItem
+
+from .Ui_ShowBundlesDialog import Ui_ShowBundlesDialog
+
+
+class ShowBundlesDialog(QDialog, Ui_ShowBundlesDialog):
+    """
+    Class implementing a dialog showing the available bundles and their modules.
+    """
+
+    def __init__(self, withModules, parent=None):
+        """
+        Constructor
+
+        @param withModules flag indicating to list the modules and their version
+        @type bool
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.header.setText(
+            self.tr("Available Bundles and Modules")
+            if withModules
+            else self.tr("Available Modules")
+        )
+        self.bundlesWidget.setColumnCount(2)
+
+        localBundles = circup.get_bundles_local_dict().values()
+        bundles = circup.get_bundles_list()
+        availableModules = circup.get_bundle_versions(bundles)
+
+        for bundle in bundles:
+            topItm = QTreeWidgetItem(
+                self.bundlesWidget, [bundle.key, bundle.current_tag]
+            )
+            topItm.setExpanded(True)
+            if bundle.key in localBundles:
+                font = topItm.font(0)
+                font.setUnderline(True)
+                topItm.setFont(0, font)
+            itm = QTreeWidgetItem(topItm, [bundle.url])
+            itm.setFirstColumnSpanned(True)
+
+            if withModules:
+                modulesHeader = QTreeWidgetItem(topItm, [self.tr("Modules")])
+                modulesHeader.setExpanded(True)
+                for name, mod in sorted(availableModules.items()):
+                    if mod["bundle"] == bundle:
+                        QTreeWidgetItem(
+                            modulesHeader,
+                            [name, mod.get("__version__", self.tr("unknown"))],
+                        )
+
+        self.bundlesWidget.resizeColumnToContents(0)
+        self.bundlesWidget.resizeColumnToContents(1)
+        self.bundlesWidget.sortItems(0, Qt.SortOrder.AscendingOrder)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowBundlesDialog.ui	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShowBundlesDialog</class>
+ <widget class="QDialog" name="ShowBundlesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>700</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>CircuitPython Bundles</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="header">
+     <property name="text">
+      <string/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTreeWidget" name="bundlesWidget">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::NoSelection</enum>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="headerHidden">
+      <bool>true</bool>
+     </property>
+     <property name="expandsOnDoubleClick">
+      <bool>false</bool>
+     </property>
+     <column>
+      <property name="text">
+       <string notr="true">1</string>
+      </property>
+     </column>
+    </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>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ShowBundlesDialog</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>ShowBundlesDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowInstalledDialog.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show the modules installed on the connected device.
+"""
+
+import circup
+
+from PyQt6.QtCore import Qt
+from PyQt6.QtWidgets import QDialog, QTreeWidgetItem
+
+from .Ui_ShowInstalledDialog import Ui_ShowInstalledDialog
+
+
+class ShowInstalledDialog(QDialog, Ui_ShowInstalledDialog):
+    """
+    Class implementing a dialog to show the modules installed on the connected device.
+    """
+
+    def __init__(self, devicePath, parent=None):
+        """
+        Constructor
+
+        @param devicePath path to the connected board
+        @type str
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.modulesList.clear()
+        deviceModules = circup.get_device_versions(devicePath)
+        for name, metadata in deviceModules.items():
+            QTreeWidgetItem(
+                self.modulesList,
+                [name, metadata.get("__version__", self.tr("unknown"))],
+            )
+
+        self.modulesList.sortItems(0, Qt.SortOrder.AscendingOrder)
+        self.modulesList.resizeColumnToContents(0)
+        self.modulesList.resizeColumnToContents(1)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowInstalledDialog.ui	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShowInstalledDialog</class>
+ <widget class="QDialog" name="ShowInstalledDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>500</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Installed Modules</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTreeWidget" name="modulesList">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::NoSelection</enum>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <attribute name="headerShowSortIndicator" stdset="0">
+      <bool>false</bool>
+     </attribute>
+     <column>
+      <property name="text">
+       <string>Module</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Version</string>
+      </property>
+     </column>
+    </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>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ShowInstalledDialog</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>ShowInstalledDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowModulesDialog.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show the available modules of all bundles.
+"""
+
+import circup
+
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem
+
+from eric7.EricGui import EricPixmapCache
+
+from .Ui_ShowModulesDialog import Ui_ShowModulesDialog
+
+
+class ShowModulesDialog(QDialog, Ui_ShowModulesDialog):
+    """
+    Class implementing a dialog to show the available modules of all bundles.
+    """
+
+    def __init__(self, selectionMode=False, parent=None):
+        """
+        Constructor
+
+        @param selectionMode flag indicating the activation of the selection mode
+            (defaults to False)
+        @type bool (optional)
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.filterButton.setIcon(EricPixmapCache.getIcon("check"))
+        self.filterButton.clicked.connect(self.__applyFilter)
+
+        self.__checkCount = 0
+        self.__selectionMode = selectionMode
+        if self.__selectionMode:
+            self.buttonBox.setStandardButtons(
+                QDialogButtonBox.StandardButton.Ok
+                | QDialogButtonBox.StandardButton.Cancel
+            )
+        else:
+            self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Close)
+
+        availableModules = circup.get_bundle_versions(circup.get_bundles_list())
+        moduleNames = [m.replace(".py", "") for m in availableModules]
+        if self.__selectionMode:
+            for moduleName in moduleNames:
+                itm = QListWidgetItem(moduleName)
+                itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable)
+                itm.setCheckState(Qt.CheckState.Unchecked)
+                self.modulesList.addItem(itm)
+        else:
+            self.modulesList.addItems(moduleNames)
+        self.modulesList.sortItems(Qt.SortOrder.AscendingOrder)
+
+        self.__applyFilter()
+
+        self.__checkCountUpdated()
+
+    @pyqtSlot()
+    def __applyFilter(self):
+        """
+        Private slot to apply the filter to the list of available modules.
+        """
+        filterStr = self.filterEdit.text()
+        counter = 0
+        for row in range(self.modulesList.count()):
+            itm = self.modulesList.item(row)
+            visible = filterStr in itm.text() if filterStr else True
+            itm.setHidden(not visible)
+            if visible:
+                counter += 1
+
+        self.statusLabel.setText(
+            self.tr("Showing {0} of {1} modules/packages").format(
+                counter, self.modulesList.count()
+            )
+        )
+        self.filterEdit.selectAll()
+        self.filterEdit.setFocus(Qt.FocusReason.OtherFocusReason)
+
+    @pyqtSlot(QListWidgetItem)
+    def on_modulesList_itemChanged(self, item):
+        """
+        Private slot to handle a change of the check state of an item.
+
+        @param item reference to the changed item
+        @type QTreeWidgetItem
+        """
+        if self.__selectionMode:
+            if item.checkState() == Qt.CheckState.Checked:
+                self.__checkCount += 1
+            else:
+                self.__checkCount -= 1
+
+            self.__checkCountUpdated()
+
+    def __checkCountUpdated(self):
+        """
+        Private method to handle an update of the check count.
+        """
+        if self.__selectionMode:
+            self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+                self.__checkCount > 0
+            )
+
+    def getSelection(self):
+        """
+        Public method to get the list of selected modules.
+
+        @return list of selected modules
+        @rtype circup.Module
+        """
+        results = []
+        if self.__selectionMode:
+            for row in range(self.modulesList.count()):
+                itm = self.modulesList.item(row)
+                if itm.checkState() == Qt.CheckState.Checked:
+                    results.append(itm.text())
+
+        return results
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowModulesDialog.ui	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShowModulesDialog</class>
+ <widget class="QDialog" name="ShowModulesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>700</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Available Modules</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Filter:</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="filterEdit">
+       <property name="toolTip">
+        <string>Enter a string used to filter the list below.</string>
+       </property>
+       <property name="placeholderText">
+        <string>Enter Filter String</string>
+       </property>
+       <property name="clearButtonEnabled">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="filterButton">
+       <property name="toolTip">
+        <string>Press to apply the entered filter.</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QListWidget" name="modulesList">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::NoSelection</enum>
+     </property>
+     <property name="sortingEnabled">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="statusLabel"/>
+   </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>
+ </widget>
+ <tabstops>
+  <tabstop>filterEdit</tabstop>
+  <tabstop>filterButton</tabstop>
+  <tabstop>modulesList</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ShowModulesDialog</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>ShowModulesDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowOutdatedDialog.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show outdated modules of a connected device.
+"""
+
+import circup
+
+from PyQt6.QtCore import Qt, pyqtSlot
+from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QTreeWidgetItem
+from semver import VersionInfo
+
+from .Ui_ShowOutdatedDialog import Ui_ShowOutdatedDialog
+
+
+class ShowOutdatedDialog(QDialog, Ui_ShowOutdatedDialog):
+    """
+    Class implementing a dialog to show outdated modules of a connected device.
+    """
+
+    def __init__(self, devicePath, selectionMode=False, parent=None):
+        """
+        Constructor
+
+        @param devicePath path to the connected board
+        @type str
+        @param selectionMode flag indicating the activation of the selection mode
+            (defaults to False)
+        @type bool (optional)
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.header.clear()
+        self.modulesList.clear()
+
+        self.__checkCount = 0
+        self.__selectionMode = selectionMode
+        if self.__selectionMode:
+            self.buttonBox.setStandardButtons(
+                QDialogButtonBox.StandardButton.Ok
+                | QDialogButtonBox.StandardButton.Cancel
+            )
+        else:
+            self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Close)
+
+        self.__modules = {
+            m.name: m
+            for m in circup.find_modules(devicePath, circup.get_bundles_list())
+            if m.outofdate
+        }
+        if self.__modules:
+            self.header.setText(
+                self.tr(
+                    "The following modules are out of date or probably need an update."
+                    "\nMajor Updates may include breaking changes. Review before"
+                    " updating.\nMPY Format changes require an update."
+                )
+            )
+            for module in self.__modules.values():
+                if isinstance(module.bundle_version, str) and not VersionInfo.isvalid(
+                    module.bundle_version
+                ):
+                    reason = self.tr("Incorrect '__version__' Metadata")
+                    needsUpdate = True
+                elif module.bad_format:
+                    reason = self.tr("Corrupted or Unknown MPY Format")
+                    needsUpdate = True
+                elif module.mpy_mismatch:
+                    reason = self.tr("MPY Format")
+                    needsUpdate = True
+                elif module.major_update:
+                    reason = self.tr("Major Version")
+                    needsUpdate = False
+                else:
+                    reason = self.tr("Minor Version")
+                    needsUpdate = False
+                itm = QTreeWidgetItem(
+                    self.modulesList,
+                    [
+                        module.name,
+                        module.device_version
+                        if module.device_version
+                        else self.tr("unknown"),
+                        module.bundle_version
+                        if module.bundle_version
+                        else self.tr("unknown"),
+                        reason,
+                    ],
+                )
+                if self.__selectionMode:
+                    itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable)
+                    itm.setCheckState(
+                        0,
+                        Qt.CheckState.Checked
+                        if needsUpdate
+                        else Qt.CheckState.Unchecked,
+                    )
+                    if needsUpdate:
+                        self.__checkCount += 1
+        else:
+            self.header.setText(self.tr("All modules are up-to-date."))
+
+        self.modulesList.sortItems(0, Qt.SortOrder.AscendingOrder)
+        for column in range(self.modulesList.columnCount()):
+            self.modulesList.resizeColumnToContents(column)
+
+        self.__checkCountUpdated()
+
+    @pyqtSlot(QTreeWidgetItem, int)
+    def on_modulesList_itemChanged(self, item, column):
+        """
+        Private slot to handle a change of the check state of an item.
+
+        @param item reference to the changed item
+        @type QTreeWidgetItem
+        @param column changed column
+        @type int
+        """
+        if self.__selectionMode:
+            if item.checkState(0) == Qt.CheckState.Checked:
+                self.__checkCount += 1
+            else:
+                self.__checkCount -= 1
+
+            self.__checkCountUpdated()
+
+    def __checkCountUpdated(self):
+        """
+        Private method to handle an update of the check count.
+        """
+        if self.__selectionMode:
+            self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setEnabled(
+                self.__checkCount > 0
+            )
+
+    def getSelection(self):
+        """
+        Public method to get the list of selected modules.
+
+        @return list of selected modules
+        @rtype circup.Module
+        """
+        results = []
+        if self.__selectionMode:
+            for row in range(self.modulesList.topLevelItemCount()):
+                itm = self.modulesList.topLevelItem(row)
+                if itm.checkState(0) == Qt.CheckState.Checked:
+                    results.append(self.__modules[itm.text(0)])
+
+        return results
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/ShowOutdatedDialog.ui	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShowOutdatedDialog</class>
+ <widget class="QDialog" name="ShowOutdatedDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>500</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Outdated Modules</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="header">
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTreeWidget" name="modulesList">
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="selectionMode">
+      <enum>QAbstractItemView::NoSelection</enum>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <attribute name="headerShowSortIndicator" stdset="0">
+      <bool>false</bool>
+     </attribute>
+     <column>
+      <property name="text">
+       <string>Module</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Version</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Latest</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Update Reason</string>
+      </property>
+     </column>
+    </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>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ShowOutdatedDialog</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>ShowOutdatedDialog</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>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/eric7/MicroPython/CircuitPythonUpdater/__init__.py	Mon Feb 06 10:09:18 2023 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the updater and associated dialogs.
+"""
--- a/src/eric7/PipInterface/PipFreezeDialog.py	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/PipInterface/PipFreezeDialog.py	Mon Feb 06 10:09:18 2023 +0100
@@ -55,11 +55,12 @@
         if mode is PipFreezeDialogModes.Constraints:
             self.constraintsCheckBox.setChecked(False)
             self.constraintsCheckBox.setEnabled(False)
-
-            self.setWindowTitle(self.tr("Generate Constraints"))
+            self.__title = self.tr("Generate Constraints")
 
         elif mode is PipFreezeDialogModes.Requirements:
-            self.setWindowTitle(self.tr("Generate Requirements"))
+            self.__title = self.tr("Generate Requirements")
+
+        self.setWindowTitle(self.__title)
 
         self.__refreshButton = self.buttonBox.addButton(
             self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole
@@ -139,7 +140,7 @@
         ok = (
             EricMessageBox.yesNo(
                 self,
-                self.tr("Generate Requirements"),
+                self.__title,
                 self.tr(
                     """The requirements were changed. Do you want"""
                     """ to overwrite these changes?"""
@@ -227,7 +228,7 @@
         if os.path.exists(fileName):
             ok = EricMessageBox.warning(
                 self,
-                self.tr("Generate Requirements"),
+                self.__title,
                 self.tr(
                     """The file <b>{0}</b> already exists. Do you want"""
                     """ to overwrite it?"""
@@ -245,7 +246,7 @@
         except OSError as err:
             EricMessageBox.critical(
                 self,
-                self.tr("Generate Requirements"),
+                self.__title,
                 self.tr(
                     """<p>The requirements could not be written"""
                     """ to <b>{0}</b>.</p><p>Reason: {1}</p>"""
@@ -267,7 +268,7 @@
         """
         fileName, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
             self,
-            self.tr("Generate Requirements"),
+            self.__title,
             os.path.expanduser("~"),
             self.tr("Text Files (*.txt);;All Files (*)"),
             None,
--- a/src/eric7/PipInterface/PipFreezeDialog.ui	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/PipInterface/PipFreezeDialog.ui	Mon Feb 06 10:09:18 2023 +0100
@@ -106,13 +106,30 @@
        </property>
       </widget>
      </item>
-     <item row="1" column="1">
-      <widget class="QPushButton" name="saveToButton">
+     <item row="3" column="1">
+      <widget class="Line" name="line">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item row="7" column="1">
+      <widget class="QPushButton" name="replaceAllButton">
        <property name="toolTip">
-        <string>Save to a new file</string>
+        <string>Replace all text with the requirements text</string>
        </property>
        <property name="text">
-        <string>Save To</string>
+        <string>Replace All</string>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="1">
+      <widget class="QPushButton" name="insertButton">
+       <property name="toolTip">
+        <string>Insert the requirements text at the cursor position</string>
+       </property>
+       <property name="text">
+        <string>Insert</string>
        </property>
       </widget>
      </item>
@@ -126,34 +143,14 @@
        </property>
       </widget>
      </item>
-     <item row="3" column="1">
-      <widget class="QPushButton" name="insertButton">
-       <property name="toolTip">
-        <string>Insert the requirements text at the cursor position</string>
-       </property>
-       <property name="text">
-        <string>Insert</string>
+     <item row="0" column="0" rowspan="9">
+      <widget class="QPlainTextEdit" name="requirementsEdit">
+       <property name="tabChangesFocus">
+        <bool>true</bool>
        </property>
       </widget>
      </item>
-     <item row="4" column="1">
-      <widget class="QPushButton" name="replaceSelectionButton">
-       <property name="text">
-        <string>Replace Selection</string>
-       </property>
-      </widget>
-     </item>
-     <item row="5" column="1">
-      <widget class="QPushButton" name="replaceAllButton">
-       <property name="toolTip">
-        <string>Replace all text with the requirements text</string>
-       </property>
-       <property name="text">
-        <string>Replace All</string>
-       </property>
-      </widget>
-     </item>
-     <item row="6" column="1">
+     <item row="8" column="1">
       <spacer name="verticalSpacer">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
@@ -166,10 +163,30 @@
        </property>
       </spacer>
      </item>
-     <item row="0" column="0" rowspan="7">
-      <widget class="QPlainTextEdit" name="requirementsEdit">
-       <property name="tabChangesFocus">
-        <bool>true</bool>
+     <item row="6" column="1">
+      <widget class="QPushButton" name="replaceSelectionButton">
+       <property name="text">
+        <string>Replace Selection</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QPushButton" name="saveToButton">
+       <property name="toolTip">
+        <string>Save to a new file</string>
+       </property>
+       <property name="text">
+        <string>Save To</string>
+       </property>
+      </widget>
+     </item>
+     <item row="4" column="1">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>&lt;b&gt;Editor Actions&lt;/b&gt;</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
        </property>
       </widget>
      </item>
--- a/src/eric7/eric7_doc.py	Mon Feb 06 10:02:35 2023 +0100
+++ b/src/eric7/eric7_doc.py	Mon Feb 06 10:09:18 2023 +0100
@@ -421,6 +421,7 @@
                     continue
 
                 try:
+                    print("Processing", file)
                     module = ModuleParser.readModule(
                         file,
                         basename=basename,
@@ -435,6 +436,13 @@
                 except ImportError as v:
                     sys.stderr.write("{0} error: {1}\n".format(file, v))
                     continue
+                except Exception as ex:
+                    sys.stderr.write(
+                        "{0} error while parsing: {1}\n".format(
+                            file, str(ex)
+                        )
+                    )
+                    raise
 
                 f = FileSystemUtilities.joinext(
                     os.path.join(outputDir, moduleDocument.name()), ".html"
@@ -458,9 +466,21 @@
                         out.write(doc)
                 except OSError as v:
                     sys.stderr.write("{0} error: {1}\n".format(file, v[1]))
+                except Exception as ex:
+                    sys.stderr.write(
+                        "{0} error while writing: {1}\n".format(
+                            file, str(ex)
+                        )
+                    )
+                    raise
                 else:
                     sys.stdout.write("{0} ok\n".format(f))
 
+                sys.stdout.flush()
+                sys.stderr.flush()
+
+    sys.stdout.write("code documentation generated")
+
     sys.stdout.flush()
     sys.stderr.flush()
 

eric ide

mercurial