Merged with the 'micropython' branch to add widgets to support development for embedded controllers with MicroPython (ESP8266/ESP32, CircuitPython and BBC micro:bit).

Tue, 20 Aug 2019 17:07:06 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 20 Aug 2019 17:07:06 +0200
changeset 7143
9eb66bad154d
parent 7142
b0321ba66119 (current diff)
parent 7140
22f5fd76c10f (diff)
child 7145
ceb3e8b242c1

Merged with the 'micropython' branch to add widgets to support development for embedded controllers with MicroPython (ESP8266/ESP32, CircuitPython and BBC micro:bit).

docs/changelog file | annotate | diff | comparison | revisions
--- a/docs/changelog	Tue Aug 20 00:37:14 2019 +0300
+++ b/docs/changelog	Tue Aug 20 17:07:06 2019 +0200
@@ -1,5 +1,10 @@
 Change Log
 ----------
+Version 19.9:
+- bug fixes
+- added widgets to support development for embedded controllers with
+  MicroPython (ESP8266/ESP32, CircuitPython and BBC micro:bit)
+
 Version 19.8:
 - bug fixes
 - Third Party packages
--- a/eric6.e4p	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6.e4p	Tue Aug 20 17:07:06 2019 +0200
@@ -129,6 +129,7 @@
     <Source>eric6/E5Gui/E5ErrorMessage.py</Source>
     <Source>eric6/E5Gui/E5ErrorMessageFilterDialog.py</Source>
     <Source>eric6/E5Gui/E5FileDialog.py</Source>
+    <Source>eric6/E5Gui/E5FileSaveConfirmDialog.py</Source>
     <Source>eric6/E5Gui/E5GenericDiffHighlighter.py</Source>
     <Source>eric6/E5Gui/E5Led.py</Source>
     <Source>eric6/E5Gui/E5LineEdit.py</Source>
@@ -144,6 +145,7 @@
     <Source>eric6/E5Gui/E5PasswordMeter.py</Source>
     <Source>eric6/E5Gui/E5PathPicker.py</Source>
     <Source>eric6/E5Gui/E5PathPickerDialog.py</Source>
+    <Source>eric6/E5Gui/E5ProcessDialog.py</Source>
     <Source>eric6/E5Gui/E5ProgressDialog.py</Source>
     <Source>eric6/E5Gui/E5SideBar.py</Source>
     <Source>eric6/E5Gui/E5SimpleHelpDialog.py</Source>
@@ -454,6 +456,21 @@
     <Source>eric6/IconEditor/__init__.py</Source>
     <Source>eric6/IconEditor/cursors/__init__.py</Source>
     <Source>eric6/IconEditor/cursors/cursors_rc.py</Source>
+    <Source>eric6/MicroPython/CircuitPythonDevices.py</Source>
+    <Source>eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py</Source>
+    <Source>eric6/MicroPython/EspDevices.py</Source>
+    <Source>eric6/MicroPython/EspFirmwareSelectionDialog.py</Source>
+    <Source>eric6/MicroPython/MicroPythonCommandsInterface.py</Source>
+    <Source>eric6/MicroPython/MicroPythonDevices.py</Source>
+    <Source>eric6/MicroPython/MicroPythonFileManager.py</Source>
+    <Source>eric6/MicroPython/MicroPythonFileManagerWidget.py</Source>
+    <Source>eric6/MicroPython/MicroPythonFileSystemUtilities.py</Source>
+    <Source>eric6/MicroPython/MicroPythonGraphWidget.py</Source>
+    <Source>eric6/MicroPython/MicroPythonProgressInfoDialog.py</Source>
+    <Source>eric6/MicroPython/MicroPythonSerialPort.py</Source>
+    <Source>eric6/MicroPython/MicroPythonWidget.py</Source>
+    <Source>eric6/MicroPython/MicrobitDevices.py</Source>
+    <Source>eric6/MicroPython/__init__.py</Source>
     <Source>eric6/MultiProject/AddProjectDialog.py</Source>
     <Source>eric6/MultiProject/MultiProject.py</Source>
     <Source>eric6/MultiProject/MultiProjectBrowser.py</Source>
@@ -890,6 +907,7 @@
     <Source>eric6/Preferences/ConfigurationPages/IrcPage.py</Source>
     <Source>eric6/Preferences/ConfigurationPages/LogViewerPage.py</Source>
     <Source>eric6/Preferences/ConfigurationPages/MasterPasswordEntryDialog.py</Source>
+    <Source>eric6/Preferences/ConfigurationPages/MicroPythonPage.py</Source>
     <Source>eric6/Preferences/ConfigurationPages/MimeTypesPage.py</Source>
     <Source>eric6/Preferences/ConfigurationPages/MultiProjectPage.py</Source>
     <Source>eric6/Preferences/ConfigurationPages/NetworkPage.py</Source>
@@ -1762,6 +1780,7 @@
     <Form>eric6/Debugger/VariablesFilterDialog.ui</Form>
     <Form>eric6/E5Gui/E5ErrorMessageFilterDialog.ui</Form>
     <Form>eric6/E5Gui/E5ListSelectionDialog.ui</Form>
+    <Form>eric6/E5Gui/E5ProcessDialog.ui</Form>
     <Form>eric6/E5Gui/E5SimpleHelpDialog.ui</Form>
     <Form>eric6/E5Gui/E5StringListEditWidget.ui</Form>
     <Form>eric6/E5Gui/E5ToolBarDialog.ui</Form>
@@ -1832,6 +1851,11 @@
     <Form>eric6/HexEdit/HexEditReplaceWidget.ui</Form>
     <Form>eric6/HexEdit/HexEditSearchWidget.ui</Form>
     <Form>eric6/IconEditor/IconSizeDialog.ui</Form>
+    <Form>eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui</Form>
+    <Form>eric6/MicroPython/EspFirmwareSelectionDialog.ui</Form>
+    <Form>eric6/MicroPython/MicroPythonFileManagerWidget.ui</Form>
+    <Form>eric6/MicroPython/MicroPythonProgressInfoDialog.ui</Form>
+    <Form>eric6/MicroPython/MicroPythonWidget.ui</Form>
     <Form>eric6/MultiProject/AddProjectDialog.ui</Form>
     <Form>eric6/MultiProject/PropertiesDialog.ui</Form>
     <Form>eric6/Network/IRC/IrcChannelEditDialog.ui</Form>
@@ -2084,6 +2108,7 @@
     <Form>eric6/Preferences/ConfigurationPages/IrcPage.ui</Form>
     <Form>eric6/Preferences/ConfigurationPages/LogViewerPage.ui</Form>
     <Form>eric6/Preferences/ConfigurationPages/MasterPasswordEntryDialog.ui</Form>
+    <Form>eric6/Preferences/ConfigurationPages/MicroPythonPage.ui</Form>
     <Form>eric6/Preferences/ConfigurationPages/MimeTypesPage.ui</Form>
     <Form>eric6/Preferences/ConfigurationPages/MultiProjectPage.ui</Form>
     <Form>eric6/Preferences/ConfigurationPages/NetworkPage.ui</Form>
--- a/eric6/Debugger/DebugUI.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/Debugger/DebugUI.py	Tue Aug 20 17:07:06 2019 +0200
@@ -563,8 +563,7 @@
         act = E5Action(
             self.tr('Clear Breakpoints'),
             self.tr('Clear Breakpoints'),
-            QKeySequence(
-                self.tr("Ctrl+Shift+C", "Debug|Clear Breakpoints")), 0,
+            0, 0,
             self.dbgSetBpActGrp, 'dbg_clear_breakpoint')
         act.setStatusTip(self.tr('Clear Breakpoints'))
         act.setWhatsThis(self.tr(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/E5Gui/E5FileSaveConfirmDialog.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter a file system path using a file picker.
+"""
+
+from __future__ import unicode_literals
+
+import os
+
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel
+
+from .E5PathPicker import E5PathPicker, E5PathPickerModes
+from .E5LineEdit import E5ClearableLineEdit
+
+
+class E5FileSaveConfirmDialog(QDialog):
+    """
+    Class implementing a dialog to enter a file system path using a file
+    picker.
+    """
+    def __init__(self, filename, title, message="", picker=True, parent=None):
+        """
+        Constructor
+        
+        @param filename file name to be shown
+        @type str
+        @param title title for the dialog
+        @type str
+        @param message message to be shown
+        @type str
+        @param picker flag indicating to use a path picker
+        @type bool
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(E5FileSaveConfirmDialog, self).__init__(parent)
+        
+        self.setMinimumWidth(400)
+        
+        self.__selectedAction = "cancel"
+        self.__filename = filename
+        
+        self.__layout = QVBoxLayout(self)
+        
+        self.__label = QLabel(self)
+        self.__label.setWordWrap(True)
+        if message:
+            self.__label.setText(message)
+        else:
+            self.__label.setText(self.tr("The given file exists already."))
+        
+        if picker:
+            self.__pathPicker = E5PathPicker(self)
+            self.__pathPicker.setMode(E5PathPickerModes.SaveFileMode)
+        else:
+            self.__pathPicker = E5ClearableLineEdit(self)
+        
+        self.__buttonBox = QDialogButtonBox(self)
+        self.__cancelButton = self.__buttonBox.addButton(
+            QDialogButtonBox.Cancel)
+        self.__overwriteButton = self.__buttonBox.addButton(
+            self.tr("Overwrite"), QDialogButtonBox.AcceptRole)
+        self.__renameButton = self.__buttonBox.addButton(
+            self.tr("Rename"), QDialogButtonBox.AcceptRole)
+        
+        self.__layout.addWidget(self.__label)
+        self.__layout.addWidget(self.__pathPicker)
+        self.__layout.addWidget(self.__buttonBox)
+        
+        # set values and states
+        self.__pathPicker.setText(filename)
+        if picker:
+            self.__pathPicker.setDefaultDirectory(os.path.dirname(filename))
+        self.__renameButton.setEnabled(False)
+        self.__cancelButton.setDefault(True)
+        
+        self.__buttonBox.clicked.connect(self.__buttonBoxClicked)
+        self.__pathPicker.textChanged.connect(self.__filenameChanged)
+    
+    def __buttonBoxClicked(self, button):
+        """
+        Private slot to handle the user clicking a button.
+        
+        @param button reference to the clicked button
+        @type QAbstractButton
+        """
+        if button == self.__cancelButton:
+            self.__selectedAction = "cancel"
+            self.reject()
+        elif button == self.__renameButton:
+            self.__selectedAction = "rename"
+            self.accept()
+        elif button == self.__overwriteButton:
+            self.__selectedAction = "overwrite"
+            self.accept()
+    
+    def __filenameChanged(self, text):
+        """
+        Private slot to handle a change of the file name.
+        
+        @param text new file name
+        @type str
+        """
+        self.__renameButton.setEnabled(text != self.__filename)
+    
+    def selectedAction(self):
+        """
+        Public method to get the selected action and associated data.
+        
+        @return tuple containing the selected action (cancel, rename,
+            overwrite) and the filename (in case of 'rename' or 'overwrite')
+        @rtype tuple of (str, str)
+        """
+        if self.__selectedAction == "rename":
+            filename = self.__pathPicker.text()
+        elif self.__selectedAction == "overwrite":
+            filename = self.__filename
+        else:
+            filename = ""
+        return self.__selectedAction, filename
+
+
+def confirmOverwrite(filename, title, message="", picker=True, parent=None):
+    """
+    Function to confirm that a file shall be overwritten.
+    
+    @param filename file name to be shown
+    @type str
+    @param title title for the dialog
+    @type str
+    @param message message to be shown
+    @type str
+    @param picker flag indicating to use a path picker
+    @type bool
+    @param parent reference to the parent widget
+    @type QWidget
+    @return tuple containing the selected action (cancel, rename,
+        overwrite) and the filename (in case of 'rename' or 'overwrite')
+    @rtype tuple of (str, str)
+    """
+    dlg = E5FileSaveConfirmDialog(filename, title, message=message,
+                                  picker=picker, parent=parent)
+    dlg.exec_()
+    return dlg.selectedAction()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/E5Gui/E5ProcessDialog.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog starting a process and showing its output.
+"""
+
+from __future__ import unicode_literals
+try:
+    str = unicode
+except NameError:
+    pass
+
+import os
+
+from PyQt5.QtCore import QProcess, QTimer, pyqtSlot, Qt, QCoreApplication, \
+    QProcessEnvironment
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QLineEdit
+
+from E5Gui import E5MessageBox
+
+from .Ui_E5ProcessDialog import Ui_E5ProcessDialog
+
+from Globals import strToQByteArray
+import Preferences
+
+
+class E5ProcessDialog(QDialog, Ui_E5ProcessDialog):
+    """
+    Class implementing a dialog starting a process and showing its output.
+    
+    It starts a QProcess and displays a dialog that
+    shows the output of the process. The dialog is modal,
+    which causes a synchronized execution of the process.
+    """
+    def __init__(self, outputTitle="", windowTitle="", parent=None):
+        """
+        Constructor
+        
+        @param outputTitle title for the output group
+        @type str
+        @param windowTitle title of the dialog
+        @type str
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(E5ProcessDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
+        
+        if windowTitle:
+            self.setWindowTitle(windowTitle)
+        if outputTitle:
+            self.outputGroup.setTitle(outputTitle)
+        
+        self.__process = None
+        
+        self.show()
+        QCoreApplication.processEvents()
+    
+    def __finish(self):
+        """
+        Private slot called when the process finished or the user pressed
+        the button.
+        """
+        if self.__process is not None and \
+           self.__process.state() != QProcess.NotRunning:
+            self.__process.terminate()
+            QTimer.singleShot(2000, self.__process.kill)
+            self.__process.waitForFinished(3000)
+        
+        self.inputGroup.setEnabled(False)
+        self.inputGroup.hide()
+        
+        self.__process = None
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
+        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
+            Qt.OtherFocusReason)
+    
+    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.Close):
+            self.close()
+        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
+            self.statusLabel.setText(self.tr("Process canceled."))
+            self.__finish()
+    
+    def __procFinished(self, exitCode, exitStatus):
+        """
+        Private slot connected to the finished signal.
+        
+        @param exitCode exit code of the process
+        @type int
+        @param exitStatus exit status of the process
+        @type QProcess.ExitStatus
+        """
+        self.__normal = (exitStatus == QProcess.NormalExit) and (exitCode == 0)
+        if self.__normal:
+            self.statusLabel.setText(self.tr("Process finished successfully."))
+        elif exitStatus == QProcess.CrashExit:
+            self.statusLabel.setText(self.tr("Process crashed."))
+        else:
+            self.statusLabel.setText(
+                self.tr("Process finished with exit code {0}")
+                .format(exitCode))
+        self.__finish()
+    
+    def startProcess(self, program, args, workingDir=None, showArgs=True,
+                     environment=None):
+        """
+        Public slot used to start the process.
+        
+        @param program path of the program to be executed
+        @type str
+        @param args list of arguments for the process
+        @type list of str
+        @param workingDir working directory for the process
+        @type str
+        @param showArgs flag indicating to show the arguments
+        @type bool
+        @param environment dictionary of environment settings to add
+            or change for the process
+        @type dict
+        @return flag indicating a successful start of the process
+        @rtype bool
+        """
+        self.errorGroup.hide()
+        self.__normal = False
+        self.__intercept = False
+        
+        if environment is None:
+            environment = {}
+        
+        if showArgs:
+            self.resultbox.append(program + ' ' + ' '.join(args))
+            self.resultbox.append('')
+        
+        self.__process = QProcess()
+        if environment:
+            env = QProcessEnvironment.systemEnvironment()
+            for key, value in environment.items():
+                env.insert(key, value)
+            self.__process.setProcessEnvironment(env)
+        
+        self.__process.finished.connect(self.__procFinished)
+        self.__process.readyReadStandardOutput.connect(self.__readStdout)
+        self.__process.readyReadStandardError.connect(self.__readStderr)
+        
+        if workingDir:
+            self.__process.setWorkingDirectory(workingDir)
+        
+        self.__process.start(program, args)
+        procStarted = self.__process.waitForStarted(10000)
+        if not procStarted:
+            self.buttonBox.setFocus()
+            self.inputGroup.setEnabled(False)
+            E5MessageBox.critical(
+                self,
+                self.tr('Process Generation Error'),
+                self.tr(
+                    '<p>The process <b>{0}</b> could not be started.</p>'
+                ).format(program))
+        else:
+            self.inputGroup.setEnabled(True)
+            self.inputGroup.show()
+        
+        return procStarted
+    
+    def normalExit(self):
+        """
+        Public method to check for a normal process termination.
+        
+        @return flag indicating normal process termination
+        @rtype bool
+        """
+        return self.__normal
+    
+    def normalExitWithoutErrors(self):
+        """
+        Public method to check for a normal process termination without
+        error messages.
+        
+        @return flag indicating normal process termination
+        @rtype bool
+        """
+        return self.__normal and self.errors.toPlainText() == ""
+    
+    def __readStdout(self):
+        """
+        Private slot to handle the readyReadStandardOutput signal.
+        
+        It reads the output of the process and inserts it into the
+        output pane.
+        """
+        if self.__process is not None:
+            s = str(self.__process.readAllStandardOutput(),
+                    Preferences.getSystem("IOEncoding"),
+                    'replace')
+            self.resultbox.insertPlainText(s)
+            self.resultbox.ensureCursorVisible()
+            
+            QCoreApplication.processEvents()
+    
+    def __readStderr(self):
+        """
+        Private slot to handle the readyReadStandardError signal.
+        
+        It reads the error output of the process and inserts it into the
+        error pane.
+        """
+        if self.__process is not None:
+            s = str(self.__process.readAllStandardError(),
+                    Preferences.getSystem("IOEncoding"),
+                    'replace')
+            
+            self.errorGroup.show()
+            self.errors.insertPlainText(s)
+            self.errors.ensureCursorVisible()
+            
+            QCoreApplication.processEvents()
+    
+    def on_passwordCheckBox_toggled(self, isOn):
+        """
+        Private slot to handle the password checkbox toggled.
+        
+        @param isOn flag indicating the status of the check box
+        @type bool
+        """
+        if isOn:
+            self.input.setEchoMode(QLineEdit.Password)
+        else:
+            self.input.setEchoMode(QLineEdit.Normal)
+    
+    @pyqtSlot()
+    def on_sendButton_clicked(self):
+        """
+        Private slot to send the input to the git process.
+        """
+        inputTxt = self.input.text()
+        inputTxt += os.linesep
+        
+        if self.passwordCheckBox.isChecked():
+            self.errors.insertPlainText(os.linesep)
+            self.errors.ensureCursorVisible()
+        else:
+            self.errors.insertPlainText(inputTxt)
+            self.errors.ensureCursorVisible()
+        
+        self.__process.write(strToQByteArray(inputTxt))
+        
+        self.passwordCheckBox.setChecked(False)
+        self.input.clear()
+    
+    def on_input_returnPressed(self):
+        """
+        Private slot to handle the press of the return key in the input field.
+        """
+        self.__intercept = True
+        self.on_sendButton_clicked()
+    
+    def keyPressEvent(self, evt):
+        """
+        Protected slot to handle a key press event.
+        
+        @param evt the key press event (QKeyEvent)
+        """
+        if self.__intercept:
+            self.__intercept = False
+            evt.accept()
+            return
+        
+        super(E5ProcessDialog, self).keyPressEvent(evt)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/E5Gui/E5ProcessDialog.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>E5ProcessDialog</class>
+ <widget class="QDialog" name="E5ProcessDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>593</width>
+    <height>499</height>
+   </rect>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout">
+   <item>
+    <widget class="QGroupBox" name="outputGroup">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>2</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="title">
+      <string>Output</string>
+     </property>
+     <layout class="QVBoxLayout">
+      <item>
+       <widget class="QTextEdit" name="resultbox">
+        <property name="readOnly">
+         <bool>true</bool>
+        </property>
+        <property name="acceptRichText">
+         <bool>false</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="statusLabel"/>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="errorGroup">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>1</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="title">
+      <string>Errors</string>
+     </property>
+     <layout class="QVBoxLayout">
+      <item>
+       <widget class="QTextEdit" name="errors">
+        <property name="readOnly">
+         <bool>true</bool>
+        </property>
+        <property name="acceptRichText">
+         <bool>false</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="inputGroup">
+     <property name="title">
+      <string>Input</string>
+     </property>
+     <layout class="QGridLayout">
+      <item row="1" column="1">
+       <spacer>
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Expanding</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>327</width>
+          <height>29</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item row="1" column="2">
+       <widget class="QPushButton" name="sendButton">
+        <property name="toolTip">
+         <string>Press to send the input to the running process</string>
+        </property>
+        <property name="text">
+         <string>&amp;Send</string>
+        </property>
+        <property name="shortcut">
+         <string>Alt+S</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0" colspan="3">
+       <widget class="E5ClearableLineEdit" name="input">
+        <property name="toolTip">
+         <string>Enter data to be sent to the running process</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QCheckBox" name="passwordCheckBox">
+        <property name="toolTip">
+         <string>Select to switch the input field to password mode</string>
+        </property>
+        <property name="text">
+         <string>&amp;Password Mode</string>
+        </property>
+        <property name="shortcut">
+         <string>Alt+P</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <pixmapfunction>qPixmapFromMimeSource</pixmapfunction>
+ <customwidgets>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>resultbox</tabstop>
+  <tabstop>errors</tabstop>
+  <tabstop>input</tabstop>
+  <tabstop>passwordCheckBox</tabstop>
+  <tabstop>sendButton</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- a/eric6/Globals/__init__.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/Globals/__init__.py	Tue Aug 20 17:07:06 2019 +0200
@@ -446,19 +446,23 @@
     """
     if size < 1024:
         return QCoreApplication.translate(
-            "Globals", "{0:.1f} Bytes").format(size)
+            "Globals", "{0:4.2f} Bytes").format(size)
     elif size < 1024 * 1024:
         size /= 1024
         return QCoreApplication.translate(
-            "Globals", "{0:.1f} KiB").format(size)
+            "Globals", "{0:4.2f} KiB").format(size)
     elif size < 1024 * 1024 * 1024:
         size /= 1024 * 1024
         return QCoreApplication.translate(
-            "Globals", "{0:.2f} MiB").format(size)
-    else:
+            "Globals", "{0:4.2f} MiB").format(size)
+    elif size < 1024 * 1024 * 1024 * 1024:
         size /= 1024 * 1024 * 1024
         return QCoreApplication.translate(
-            "Globals", "{0:.2f} GiB").format(size)
+            "Globals", "{0:4.2f} GiB").format(size)
+    else:
+        size /= 1024 * 1024 * 1024 * 1024
+        return QCoreApplication.translate(
+            "Globals", "{0:4.2f} TiB").format(size)
 
 
 ###############################################################################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/CircuitPythonDevices.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,232 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the device interface class for CircuitPython boards.
+"""
+
+from __future__ import unicode_literals
+
+import shutil
+import os
+
+from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtWidgets import QDialog
+
+from E5Gui import E5MessageBox, E5FileDialog
+
+from .MicroPythonDevices import MicroPythonDevice
+from .MicroPythonWidget import HAS_QTCHART
+
+import Utilities
+
+
+class CircuitPythonDevice(MicroPythonDevice):
+    """
+    Class implementing the device for CircuitPython boards.
+    """
+    DeviceVolumeName = "CIRCUITPY"
+    
+    def __init__(self, microPythonWidget, parent=None):
+        """
+        Constructor
+        
+        @param microPythonWidget reference to the main MicroPython widget
+        @type MicroPythonWidget
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(CircuitPythonDevice, self).__init__(microPythonWidget, parent)
+    
+    def setButtons(self):
+        """
+        Public method to enable the supported action buttons.
+        """
+        super(CircuitPythonDevice, self).setButtons()
+        self.microPython.setActionButtons(
+            run=True, repl=True, files=True, chart=HAS_QTCHART)
+        
+        if self.__deviceVolumeMounted():
+            self.microPython.setActionButtons(open=True, save=True)
+    
+    def forceInterrupt(self):
+        """
+        Public method to determine the need for an interrupt when opening the
+        serial connection.
+        
+        @return flag indicating an interrupt is needed
+        @rtype bool
+        """
+        return False
+    
+    def deviceName(self):
+        """
+        Public method to get the name of the device.
+        
+        @return name of the device
+        @rtype str
+        """
+        return self.tr("CircuitPython")
+    
+    def canStartRepl(self):
+        """
+        Public method to determine, if a REPL can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a REPL
+            and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def canStartPlotter(self):
+        """
+        Public method to determine, if a Plotter can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def canRunScript(self):
+        """
+        Public method to determine, if a script can be executed.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def runScript(self, script):
+        """
+        Public method to run the given Python script.
+        
+        @param script script to be executed
+        @type str
+        """
+        pythonScript = script.split("\n")
+        self.sendCommands(pythonScript)
+    
+    def canStartFileManager(self):
+        """
+        Public method to determine, if a File Manager can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            File Manager and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def supportsLocalFileAccess(self):
+        """
+        Public method to indicate file access via a local directory.
+        
+        @return flag indicating file access via local directory
+        @type bool
+        """
+        return self.__deviceVolumeMounted()
+    
+    def __deviceVolumeMounted(self):
+        """
+        Private method to check, if the device volume is mounted.
+        
+        @return flag indicated a mounted device
+        @rtype bool
+        """
+        return self.getWorkspace().endswith(self.DeviceVolumeName)
+    
+    def getWorkspace(self):
+        """
+        Public method to get the workspace directory.
+        
+        @return workspace directory used for saving files
+        @rtype str
+        """
+        # Attempts to find the path on the filesystem that represents the
+        # plugged in CIRCUITPY board.
+        deviceDirectory = Utilities.findVolume(self.DeviceVolumeName)
+        
+        if deviceDirectory:
+            return deviceDirectory
+        else:
+            # return the default workspace and give the user a warning
+            E5MessageBox.warning(
+                self.microPython,
+                self.tr("Workspace Directory"),
+                self.tr("Python files for CircuitPython devices are stored on"
+                        " the device. Therefore, to edit these files you need"
+                        " to have the device plugged in. Until you plug in a"
+                        " device, the standard directory will be used."))
+            
+            return super(CircuitPythonDevice, self).getWorkspace()
+    
+    def addDeviceMenuEntries(self, menu):
+        """
+        Public method to add device specific entries to the given menu.
+        
+        @param menu reference to the context menu
+        @type QMenu
+        """
+        connected = self.microPython.isConnected()
+        
+        act = menu.addAction(self.tr("Flash CircuitPython Firmware"),
+                             self.__flashCircuitPython)
+        act.setEnabled(not connected)
+        menu.addSeparator()
+        act = menu.addAction(self.tr("Install Library Files"),
+                             self.__installLibraryFiles)
+        act.setEnabled(self.__deviceVolumeMounted())
+    
+    @pyqtSlot()
+    def __flashCircuitPython(self):
+        """
+        Private slot to flash a CircuitPython firmware to the device.
+        """
+        ok = E5MessageBox.information(
+            self.microPython,
+            self.tr("Flash CircuitPython Firmware"),
+            self.tr("Please reset the device to bootloader mode and confirm"
+                    " when ready."),
+            E5MessageBox.StandardButtons(
+                E5MessageBox.Abort |
+                E5MessageBox.Ok))
+        if ok:
+            from .CircuitPythonFirmwareSelectionDialog import (
+                CircuitPythonFirmwareSelectionDialog)
+            dlg = CircuitPythonFirmwareSelectionDialog()
+            if dlg.exec_() == QDialog.Accepted:
+                cpyPath, devicePath = dlg.getData()
+                shutil.copy2(cpyPath, devicePath)
+    
+    @pyqtSlot()
+    def __installLibraryFiles(self):
+        """
+        Private slot to install Python files into the onboard library.
+        """
+        if not self.__deviceVolumeMounted():
+            E5MessageBox.critical(
+                self.microPython,
+                self.tr("Install Library Files"),
+                self.tr("""The device volume "<b>{0}</b>" is not available."""
+                        """ Ensure it is mounted properly and try again."""))
+            return
+        
+        target = os.path.join(self.getWorkspace(), "lib")
+        # ensure that the library directory exists on the device
+        if not os.path.isdir(target):
+            os.makedirs(target)
+        
+        libraryFiles = E5FileDialog.getOpenFileNames(
+            self.microPython,
+            self.tr("Install Library Files"),
+            os.path.expanduser("~"),
+            self.tr("Compiled Python Files (*.mpy);;"
+                    "Python Files (*.py);;"
+                    "All Files (*)"))
+        
+        for libraryFile in libraryFiles:
+            if os.path.exists(libraryFile):
+                shutil.copy2(libraryFile, target)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter the firmware flashing data.
+"""
+
+from __future__ import unicode_literals
+
+import os
+
+from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox
+
+from E5Gui.E5PathPicker import E5PathPickerModes
+from E5Gui import E5MessageBox
+
+from .Ui_CircuitPythonFirmwareSelectionDialog import (
+    Ui_CircuitPythonFirmwareSelectionDialog
+)
+
+import Utilities
+import UI.PixmapCache
+
+
+class CircuitPythonFirmwareSelectionDialog(
+        QDialog, Ui_CircuitPythonFirmwareSelectionDialog):
+    """
+    Class implementing a dialog to enter the firmware flashing data.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(CircuitPythonFirmwareSelectionDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.retestButton.setIcon(UI.PixmapCache.getIcon("rescan"))
+        
+        self.firmwarePicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.firmwarePicker.setFilters(
+            self.tr("CircuitPython Firmware Files (*.uf2);;"
+                    "All Files (*)"))
+        
+        self.bootPicker.setMode(E5PathPickerModes.DirectoryShowFilesMode)
+        
+        boards = (
+            ("", ""),           # indicator for no selection
+            
+            ("Circuit Playground Express", "CPLAYBOOT"),
+            ("Feather M0 Express", "FEATHERBOOT"),
+            ("Feather M4 Express", "FEATHERBOOT"),
+            ("Gemma M0", "GEMMABOOT"),
+            ("Grand Central M4 Express", "GCM4BOOT"),
+            ("ItsyBitsy M0 Express", "ITSYBOOT"),
+            ("ItsyBitsy M4 Express", "ITSYM4BOOT"),
+            ("Metro M0 Express", "METROBOOT"),
+            ("Metro M4 Express", "METROM4BOOT"),
+            ("NeoTrelis M4 Express", "TRELM4BOOT"),
+            ("Trinket M0", "TRINKETBOOT"),
+            
+            ("Manual Select", "<manual>"),
+        )
+        for boardName, bootVolume in boards:
+            self.boardComboBox.addItem(boardName, bootVolume)
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    def __updateOkButton(self):
+        """
+        Private method to update the state of the OK button and the retest
+        button.
+        """
+        firmwareFile = self.firmwarePicker.text()
+        self.retestButton.setEnabled(bool(firmwareFile) and
+                                     os.path.exists(firmwareFile))
+        
+        if not bool(firmwareFile) or not os.path.exists(firmwareFile):
+            enable = False
+        else:
+            volumeName = self.boardComboBox.currentData()
+            if volumeName and volumeName != "<manual>":
+                # check if the user selected a board and the board is in
+                # bootloader mode
+                deviceDirectory = Utilities.findVolume(volumeName)
+                if deviceDirectory:
+                    self.bootPicker.setText(deviceDirectory)
+                    enable = True
+                else:
+                    enable = False
+                    E5MessageBox.warning(
+                        self,
+                        self.tr("Select Path to Device"),
+                        self.tr("""<p>The device volume <b>{0}</b> could not"""
+                                """ be found. Is the device in 'bootloader'"""
+                                """ mode and mounted?</p> <p>Alternatively"""
+                                """ select the Manual Select" entry and"""
+                                """ enter the path to the device below.</p>""")
+                        .format(volumeName)
+                    )
+            
+            elif volumeName == "<manual>":
+                # select the device path manually
+                deviceDirectory = self.bootPicker.text()
+                enable = (bool(deviceDirectory) and
+                          os.path.exists(deviceDirectory))
+            
+            else:
+                # illegal entry
+                enable = False
+        
+        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
+    
+    @pyqtSlot(str)
+    def on_firmwarePicker_textChanged(self, firmware):
+        """
+        Private slot handling a change of the firmware path.
+        
+        @param firmware path to the firmware
+        @type str
+        """
+        self.__updateOkButton()
+    
+    @pyqtSlot(int)
+    def on_boardComboBox_currentIndexChanged(self, index):
+        """
+        Private slot to handle the selection of a board type.
+        
+        @param index index of the selected board type
+        @type int
+        """
+        if self.boardComboBox.itemData(index) == "<manual>":
+            self.bootPicker.clear()
+            self.bootPicker.setEnabled(True)
+        else:
+            self.bootPicker.setEnabled(False)
+        
+        self.__updateOkButton()
+    
+    @pyqtSlot()
+    def on_retestButton_clicked(self):
+        """
+        Private slot to research for the selected volume.
+        """
+        self.__updateOkButton()
+    
+    @pyqtSlot(str)
+    def on_bootPicker_textChanged(self, devicePath):
+        """
+        Private slot handling a change of the device path.
+        
+        @param devicePath path to the device
+        @type str
+        """
+        self.__updateOkButton()
+    
+    def getData(self):
+        """
+        Public method to obtain the entered data.
+        
+        @return tuple containing the path to the CircuitPython firmware file
+            and the path to the device
+        @rtype tuple of (str, str)
+        """
+        return self.firmwarePicker.text(), self.bootPicker.text()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/CircuitPythonFirmwareSelectionDialog.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CircuitPythonFirmwareSelectionDialog</class>
+ <widget class="QDialog" name="CircuitPythonFirmwareSelectionDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>124</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Flash CircuitPython Firmware</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="1" column="1">
+    <widget class="QComboBox" name="boardComboBox">
+     <property name="toolTip">
+      <string>Select the board type or 'Manual'</string>
+     </property>
+     <property name="sizeAdjustPolicy">
+      <enum>QComboBox::AdjustToContents</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="1" colspan="3">
+    <widget class="E5PathPicker" name="firmwarePicker" native="true">
+     <property name="focusPolicy">
+      <enum>Qt::WheelFocus</enum>
+     </property>
+     <property name="toolTip">
+      <string>Enter the path of the CircuitPython firmware file</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="3">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>339</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="0" column="0">
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Firmware:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Boot Path:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>Board Type:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="2" column="1" colspan="3">
+    <widget class="E5PathPicker" name="bootPicker" native="true">
+     <property name="focusPolicy">
+      <enum>Qt::WheelFocus</enum>
+     </property>
+     <property name="toolTip">
+      <string>Enter the path to the device in bootloader mode</string>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0" colspan="4">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="2">
+    <widget class="QToolButton" name="retestButton">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
+     <property name="toolTip">
+      <string>Press to search the selected volume</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>firmwarePicker</tabstop>
+  <tabstop>boardComboBox</tabstop>
+  <tabstop>retestButton</tabstop>
+  <tabstop>bootPicker</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>CircuitPythonFirmwareSelectionDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>CircuitPythonFirmwareSelectionDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/EspDevices.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,226 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing some utility functions and the MicroPythonDevice base
+class.
+"""
+
+from __future__ import unicode_literals
+
+import sys
+
+from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtWidgets import QDialog
+
+from E5Gui import E5MessageBox
+from E5Gui.E5ProcessDialog import E5ProcessDialog
+from E5Gui.E5Application import e5App
+
+from .MicroPythonDevices import MicroPythonDevice
+from .MicroPythonWidget import HAS_QTCHART
+
+
+class EspDevice(MicroPythonDevice):
+    """
+    Class implementing the device for ESP32 and ESP8266 based boards.
+    """
+    def __init__(self, microPythonWidget, parent=None):
+        """
+        Constructor
+        
+        @param microPythonWidget reference to the main MicroPython widget
+        @type MicroPythonWidget
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(EspDevice, self).__init__(microPythonWidget, parent)
+    
+    def setButtons(self):
+        """
+        Public method to enable the supported action buttons.
+        """
+        super(EspDevice, self).setButtons()
+        self.microPython.setActionButtons(
+            run=True, repl=True, files=True, chart=HAS_QTCHART)
+    
+    def forceInterrupt(self):
+        """
+        Public method to determine the need for an interrupt when opening the
+        serial connection.
+        
+        @return flag indicating an interrupt is needed
+        @rtype bool
+        """
+        return True
+    
+    def deviceName(self):
+        """
+        Public method to get the name of the device.
+        
+        @return name of the device
+        @rtype str
+        """
+        return self.tr("ESP8266, ESP32")
+    
+    def canStartRepl(self):
+        """
+        Public method to determine, if a REPL can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a REPL
+            and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def canStartPlotter(self):
+        """
+        Public method to determine, if a Plotter can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def canRunScript(self):
+        """
+        Public method to determine, if a script can be executed.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return self.canStartRepl()
+    
+    def runScript(self, script):
+        """
+        Public method to run the given Python script.
+        
+        @param script script to be executed
+        @type str
+        """
+        pythonScript = script.split("\n")
+        self.sendCommands(pythonScript)
+    
+    def canStartFileManager(self):
+        """
+        Public method to determine, if a File Manager can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            File Manager and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def addDeviceMenuEntries(self, menu):
+        """
+        Public method to add device specific entries to the given menu.
+        
+        @param menu reference to the context menu
+        @type QMenu
+        """
+        connected = self.microPython.isConnected()
+        
+        act = menu.addAction(self.tr("Erase Flash"),
+                             self.__eraseFlash)
+        act.setEnabled(not connected)
+        act = menu.addAction(self.tr("Flash MicroPython Firmware"),
+                             self.__flashMicroPython)
+        act.setEnabled(not connected)
+        menu.addSeparator()
+        act = menu.addAction(self.tr("Flash Additional Firmware"),
+                             self.__flashAddons)
+        act.setEnabled(not connected)
+        menu.addSeparator()
+        menu.addAction(self.tr("Install 'esptool.py'"), self.__installEspTool)
+    
+    @pyqtSlot()
+    def __eraseFlash(self):
+        """
+        Private slot to erase the device flash memory.
+        """
+        ok = E5MessageBox.yesNo(
+            self,
+            self.tr("Erase Flash"),
+            self.tr("""Shall the flash of the selected device really be"""
+                    """ erased?"""))
+        if ok:
+            flashArgs = [
+                "-u",
+                "-m", "esptool",
+                "--port", self.microPython.getCurrentPort(),
+                "erase_flash",
+            ]
+            dlg = E5ProcessDialog(self.tr("'esptool erase_flash' Output"),
+                                  self.tr("Erase Flash"))
+            res = dlg.startProcess(sys.executable, flashArgs)
+            if res:
+                dlg.exec_()
+    
+    @pyqtSlot()
+    def __flashMicroPython(self):
+        """
+        Private slot to flash a MicroPython firmware to the device.
+        
+        @exception ValueError raised to indicate an unsupported chip type
+        """
+        from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog
+        dlg = EspFirmwareSelectionDialog()
+        if dlg.exec_() == QDialog.Accepted:
+            chip, firmware, _ = dlg.getData()
+            if chip == "esp8266":
+                flashAddress = "0x0000"
+            elif chip == "esp32":
+                flashAddress = "0x1000"
+            else:
+                raise ValueError(self.tr("Unsupported chip type '{0}'.")
+                                 .format(chip))
+            flashArgs = [
+                "-u",
+                "-m", "esptool",
+                "--chip", chip,
+                "--port", self.microPython.getCurrentPort(),
+                "write_flash",
+                flashAddress,
+                firmware,
+            ]
+            dlg = E5ProcessDialog(self.tr("'esptool write_flash' Output"),
+                                  self.tr("Flash MicroPython Firmware"))
+            res = dlg.startProcess(sys.executable, flashArgs)
+            if res:
+                dlg.exec_()
+    
+    @pyqtSlot()
+    def __flashAddons(self):
+        """
+        Private slot to flash some additional firmware images.
+        """
+        from .EspFirmwareSelectionDialog import EspFirmwareSelectionDialog
+        dlg = EspFirmwareSelectionDialog(addon=True)
+        if dlg.exec_() == QDialog.Accepted:
+            chip, firmware, flashAddress = dlg.getData()
+            flashArgs = [
+                "-u",
+                "-m", "esptool",
+                "--chip", chip,
+                "--port", self.microPython.getCurrentPort(),
+                "write_flash",
+                flashAddress.lower(),
+                firmware,
+            ]
+            dlg = E5ProcessDialog(self.tr("'esptool write_flash' Output"),
+                                  self.tr("Flash Additional Firmware"))
+            res = dlg.startProcess(sys.executable, flashArgs)
+            if res:
+                dlg.exec_()
+    
+    @pyqtSlot()
+    def __installEspTool(self):
+        """
+        Private slot to install the esptool package via pip.
+        """
+        pip = e5App().getObject("Pip")
+        pip.installPackages(["esptool"], interpreter=sys.executable)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/EspFirmwareSelectionDialog.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to select the ESP chip type and the firmware to
+be flashed.
+"""
+
+from __future__ import unicode_literals
+
+import os
+
+from PyQt5.QtCore import pyqtSlot, QRegularExpression
+from PyQt5.QtGui import QRegularExpressionValidator
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox
+
+from E5Gui.E5PathPicker import E5PathPickerModes
+
+from .Ui_EspFirmwareSelectionDialog import Ui_EspFirmwareSelectionDialog
+
+
+class EspFirmwareSelectionDialog(QDialog, Ui_EspFirmwareSelectionDialog):
+    """
+    Class implementing a dialog to select the ESP chip type and the firmware to
+    be flashed.
+    """
+    def __init__(self, addon=False, parent=None):
+        """
+        Constructor
+        
+        @param addon flag indicating an addon firmware
+        @type bool
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(EspFirmwareSelectionDialog, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.__addon = addon
+        
+        self.firmwarePicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.firmwarePicker.setFilters(
+            self.tr("Firmware Files (*.bin);;All Files (*)"))
+        
+        self.espComboBox.addItems(["", "ESP32", "ESP8266"])
+        
+        if addon:
+            self.__validator = QRegularExpressionValidator(
+                QRegularExpression(r"[0-9a-fA-F]{0,4}")
+            )
+            self.addressEdit.setValidator(self.__validator)
+        else:
+            self.addressLabel.hide()
+            self.addressEdit.hide()
+        
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+    
+    def __updateOkButton(self):
+        """
+        Private method to update the state of the OK button.
+        """
+        firmwareFile = self.firmwarePicker.text()
+        enable = (bool(self.espComboBox.currentText()) and
+                  bool(firmwareFile) and os.path.exists(firmwareFile))
+        if self.__addon:
+            enable &= bool(self.addressEdit.text())
+        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
+    
+    @pyqtSlot(str)
+    def on_espComboBox_currentTextChanged(self, chip):
+        """
+        Private slot to handle the selection of a chip type.
+        
+        @param chip selected chip type
+        @type str
+        """
+        self.__updateOkButton()
+    
+    @pyqtSlot(str)
+    def on_firmwarePicker_textChanged(self, firmware):
+        """
+        Private slot handling a change of the firmware path.
+        
+        @param firmware path to the firmware
+        @type str
+        """
+        self.__updateOkButton()
+    
+    def getData(self):
+        """
+        Public method to get the entered data.
+        
+        @return tuple containing the selected chip type, the path of the
+            firmware file and the flash address
+        @rtype tuple of (str, str, str)
+        """
+        if self.__addon:
+            address = self.addressEdit.text()
+        else:
+            address = ""
+        
+        return (
+            self.espComboBox.currentText().lower(),
+            self.firmwarePicker.text(),
+            address,
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/EspFirmwareSelectionDialog.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EspFirmwareSelectionDialog</class>
+ <widget class="QDialog" name="EspFirmwareSelectionDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>114</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Flash MicroPython Firmware</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="0" column="0">
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>ESP Chip Type:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QComboBox" name="espComboBox">
+       <property name="toolTip">
+        <string>Select the ESP chip type</string>
+       </property>
+       <property name="sizeAdjustPolicy">
+        <enum>QComboBox::AdjustToContents</enum>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="2">
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>Firmware:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1" colspan="2">
+      <widget class="E5PathPicker" name="firmwarePicker" native="true">
+       <property name="focusPolicy">
+        <enum>Qt::WheelFocus</enum>
+       </property>
+       <property name="toolTip">
+        <string>Enter the path of the firmware file</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="addressLabel">
+       <property name="text">
+        <string>Address:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1" colspan="2">
+      <widget class="E5ClearableLineEdit" name="addressEdit">
+       <property name="toolTip">
+        <string>Enter the flash addres in the hexadecimal form</string>
+       </property>
+       <property name="maxLength">
+        <number>4</number>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>espComboBox</tabstop>
+  <tabstop>firmwarePicker</tabstop>
+  <tabstop>addressEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>EspFirmwareSelectionDialog</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>EspFirmwareSelectionDialog</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/eric6/MicroPython/MicroPythonCommandsInterface.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,825 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing some file system commands for MicroPython.
+"""
+
+from __future__ import unicode_literals
+
+import ast
+import time
+import os
+
+from PyQt5.QtCore import (
+    pyqtSlot, pyqtSignal, QObject, QThread, QTimer, QCoreApplication,
+    QEventLoop
+)
+
+from .MicroPythonSerialPort import MicroPythonSerialPort
+
+import Preferences
+
+
+class MicroPythonCommandsInterface(QObject):
+    """
+    Class implementing some file system commands for MicroPython.
+    
+    Commands are provided to perform operations on the file system of a
+    connected MicroPython device. Supported commands are:
+    <ul>
+    <li>ls: directory listing</li>
+    <li>lls: directory listing with meta data</li>
+    <li>cd: change directory</li>
+    <li>pwd: get the current directory</li>
+    <li>put: copy a file to the connected device</li>
+    <li>get: get a file from the connected device</li>
+    <li>rm: remove a file from the connected device</li>
+    <li>rmrf: remove a file/directory recursively (like 'rm -rf' in bash)
+    <li>mkdir: create a new directory</li>
+    <li>rmdir: remove an empty directory</li>
+    </ul>
+    
+    There are additional commands related to time and version.
+    <ul>
+    <li>version: get version info about MicroPython</li>
+    <li>getImplementation: get some implementation information</li>
+    <li>syncTime: synchronize the time of the connected device</li>
+    <li>showTime: show the current time of the connected device</li>
+    </ul>
+    
+    @signal executeAsyncFinished() emitted to indicate the end of an
+        asynchronously executed list of commands (e.g. a script)
+    @signal dataReceived(data) emitted to send data received via the serial
+        connection for further processing
+    """
+    executeAsyncFinished = pyqtSignal()
+    dataReceived = pyqtSignal(bytes)
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(MicroPythonCommandsInterface, self).__init__(parent)
+        
+        self.__repl = parent
+        
+        self.__blockReadyRead = False
+        
+        self.__serial = MicroPythonSerialPort(
+            timeout=Preferences.getMicroPython("SerialTimeout"),
+            parent=self)
+        self.__serial.readyRead.connect(self.__readSerial)
+    
+    @pyqtSlot()
+    def __readSerial(self):
+        """
+        Private slot to read all available serial data and emit it with the
+        "dataReceived" signal for further processing.
+        """
+        if not self.__blockReadyRead:
+            data = bytes(self.__serial.readAll())
+            self.dataReceived.emit(data)
+    
+    @pyqtSlot()
+    def connectToDevice(self, port):
+        """
+        Public slot to start the manager.
+        
+        @param port name of the port to be used
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        return self.__serial.openSerialLink(port)
+    
+    @pyqtSlot()
+    def disconnectFromDevice(self):
+        """
+        Public slot to stop the thread.
+        """
+        self.__serial.closeSerialLink()
+    
+    def isConnected(self):
+        """
+        Public method to get the connection status.
+        
+        @return flag indicating the connection status
+        @rtype bool
+        """
+        return self.__serial.isConnected()
+    
+    @pyqtSlot()
+    def handlePreferencesChanged(self):
+        """
+        Public slot to handle a change of the preferences.
+        """
+        self.__serial.setTimeout(Preferences.getMicroPython("SerialTimeout"))
+    
+    def write(self, data):
+        """
+        Public method to write data to the connected device.
+        
+        @param data data to be written
+        @type bytes or bytearray
+        """
+        self.__serial.isConnected() and self.__serial.write(data)
+    
+    def __rawOn(self):
+        """
+        Private method to switch the connected device to 'raw' mode.
+        
+        Note: switching to raw mode is done with synchronous writes.
+        
+        @return flag indicating success
+        @@rtype bool
+        """
+        if not self.__serial:
+            return False
+        
+        rawReplMessage = b"raw REPL; CTRL-B to exit\r\n>"
+        
+        self.__serial.write(b"\x02")        # end raw mode if required
+        self.__serial.waitForBytesWritten()
+        for _i in range(3):
+            # CTRL-C three times to break out of loops
+            self.__serial.write(b"\r\x03")
+            self.__serial.waitForBytesWritten()
+            QThread.msleep(10)
+        self.__serial.readAll()             # read all data and discard it
+        self.__serial.write(b"\r\x01")      # send CTRL-A to enter raw mode
+        self.__serial.readUntil(rawReplMessage)
+        if self.__serial.hasTimedOut():
+            # it timed out; try it again and than fail
+            self.__serial.write(b"\r\x01")  # send CTRL-A again
+            self.__serial.readUntil(rawReplMessage)
+            if self.__serial.hasTimedOut():
+                return False
+        
+        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
+        self.__serial.readAll()             # read all data and discard it
+        return True
+    
+    def __rawOff(self):
+        """
+        Private method to switch 'raw' mode off.
+        """
+        if self.__serial:
+            self.__serial.write(b"\x02")      # send CTRL-B to cancel raw mode
+            self.__serial.readUntil(b">>> ")  # read until Python prompt
+            self.__serial.readAll()           # read all data and discard it
+    
+    def execute(self, commands):
+        """
+        Public method to send commands to the connected device and return the
+        result.
+        
+        If no serial connection is available, empty results will be returned.
+        
+        @param commands list of commands to be executed
+        @type str
+        @return tuple containing stdout and stderr output of the device
+        @rtype tuple of (bytes, bytes)
+        """
+        if not self.__serial:
+            return b"", b""
+        
+        if not self.__serial.isConnected():
+            return b"", b"Device not connected or not switched on."
+        
+        result = bytearray()
+        err = b""
+        
+        # switch on raw mode
+        self.__blockReadyRead = True
+        ok = self.__rawOn()
+        if not ok:
+            self.__blockReadyRead = False
+            return (
+                b"",
+                b"Could not switch to raw mode. Is the device switched on?"
+            )
+        
+        # send commands
+        QThread.msleep(10)
+        for command in commands:
+            if command:
+                commandBytes = command.encode("utf-8")
+                self.__serial.write(commandBytes + b"\x04")
+                QCoreApplication.processEvents(
+                    QEventLoop.ExcludeUserInputEvents)
+                ok = self.__serial.readUntil(b"OK")
+                if ok != b"OK":
+                    return (
+                        b"",
+                        "Expected 'OK', got '{0}', followed by '{1}'".format(
+                            ok, self.__serial.readAll()).encode("utf-8")
+                    )
+                
+                # read until prompt
+                response = self.__serial.readUntil(b"\x04>")
+                if self.__serial.hasTimedOut():
+                    self.__blockReadyRead = False
+                    return b"", b"Timeout while processing commands."
+                if b"\x04" in response[:-2]:
+                    # split stdout, stderr
+                    out, err = response[:-2].split(b"\x04")
+                    result += out
+                else:
+                    err = b"invalid response received: " + response
+                if err:
+                    self.__blockReadyRead = False
+                    return b"", err
+        
+        # switch off raw mode
+        QThread.msleep(10)
+        self.__rawOff()
+        self.__blockReadyRead = False
+        
+        return bytes(result), err
+    
+    def executeAsync(self, commandsList):
+        """
+        Public method to execute a series of commands over a period of time
+        without returning any result (asynchronous execution).
+        
+        @param commandsList list of commands to be execute on the device
+        @type list of bytes
+        """
+        def remainingTask(commands):
+            self.executeAsync(commands)
+        
+        if commandsList:
+            command = commandsList[0]
+            self.__serial.write(command)
+            remainder = commandsList[1:]
+            QTimer.singleShot(2, lambda: remainingTask(remainder))
+        else:
+            self.executeAsyncFinished.emit()
+    
+    def __shortError(self, error):
+        """
+        Private method to create a shortened error message.
+        
+        @param error verbose error message
+        @type bytes
+        @return shortened error message
+        @rtype str
+        """
+        if error:
+            decodedError = error.decode("utf-8")
+            try:
+                return decodedError.split["\r\n"][-2]
+            except Exception:
+                return decodedError
+        return self.tr("Detected an error without indications.")
+    
+    ##################################################################
+    ## Methods below implement the file system commands
+    ##################################################################
+    
+    def ls(self, dirname=""):
+        """
+        Public method to get a directory listing of the connected device.
+        
+        @param dirname name of the directory to be listed
+        @type str
+        @return tuple containg the directory listing
+        @rtype tuple of str
+        @exception IOError raised to indicate an issue with the device
+        """
+        if self.__repl.isMicrobit():
+            # BBC micro:bit does not support directories
+            commands = [
+                "import os as __os_",
+                "print(__os_.listdir())",
+                "del __os_",
+            ]
+        else:
+            commands = [
+                "import os as __os_",
+                "print(__os_.listdir('{0}'))".format(dirname),
+                "del __os_",
+            ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        return ast.literal_eval(out.decode("utf-8"))
+    
+    def lls(self, dirname="", fullstat=False, showHidden=False):
+        """
+        Public method to get a long directory listing of the connected device
+        including meta data.
+        
+        @param dirname name of the directory to be listed
+        @type str
+        @param fullstat flag indicating to return the full stat() tuple
+        @type bool
+        @param showHidden flag indicating to show hidden files as well
+        @type bool
+        @return list containing the directory listing with tuple entries of
+            the name and and a tuple of mode, size and time (if fullstat is
+            false) or the complete stat() tuple. 'None' is returned in case the
+            directory doesn't exist.
+        @rtype tuple of (str, tuple)
+        @exception IOError raised to indicate an issue with the device
+        """
+        if self.__repl.isMicrobit():
+            # BBC micro:bit does not support directories
+            commands = [
+                "import os as __os_",
+                "\n".join([
+                    "def is_visible(filename, showHidden):",
+                    "    return showHidden or "
+                    "(filename[0] != '.' and filename[-1] != '~')",
+                ]),
+                "\n".join([
+                    "def stat(filename):",
+                    "    size = __os_.size(filename)",
+                    "    return (0, 0, 0, 0, 0, 0, size, 0, 0, 0)"
+                ]),
+                "\n".join([
+                    "def listdir_stat(showHidden):",
+                    "    files = __os_.listdir()",
+                    "    return list((f, stat(f)) for f in files if"
+                    " is_visible(f,showHidden))",
+                ]),
+                "print(listdir_stat({0}))".format(showHidden),
+                "del __os_, stat, listdir_stat, is_visible",
+            ]
+        else:
+            commands = [
+                "import os as __os_",
+                "\n".join([
+                    "def is_visible(filename, showHidden):",
+                    "    return showHidden or "
+                    "(filename[0] != '.' and filename[-1] != '~')",
+                ]),
+                "\n".join([
+                    "def stat(filename):",
+                    "    try:",
+                    "        rstat = __os_.lstat(filename)",
+                    "    except:",
+                    "        rstat = __os_.stat(filename)",
+                    "    return tuple(rstat)",
+                ]),
+                "\n".join([
+                    "def listdir_stat(dirname, showHidden):",
+                    "    try:",
+                    "        files = __os_.listdir(dirname)",
+                    "    except OSError:",
+                    "        return None",
+                    "    if dirname in ('', '/'):",
+                    "        return list((f, stat(f)) for f in files if"
+                    " is_visible(f, showHidden))",
+                    "    return list((f, stat(dirname + '/' + f))"
+                    " for f in files if is_visible(f, showHidden))",
+                ]),
+                "print(listdir_stat('{0}', {1}))".format(dirname, showHidden),
+                "del __os_, stat, listdir_stat, is_visible",
+            ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        fileslist = ast.literal_eval(out.decode("utf-8"))
+        if fileslist is None:
+            return None
+        else:
+            if fullstat:
+                return fileslist
+            else:
+                return [(f, (s[0], s[6], s[8])) for f, s in fileslist]
+    
+    def cd(self, dirname):
+        """
+        Public method to change the current directory on the connected device.
+        
+        @param dirname directory to change to
+        @type str
+        @exception IOError raised to indicate an issue with the device
+        """
+        assert dirname
+        
+        commands = [
+            "import os as __os_",
+            "__os_.chdir('{0}')".format(dirname),
+            "del __os_",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+    
+    def pwd(self):
+        """
+        Public method to get the current directory of the connected device.
+        
+        @return current directory
+        @rtype str
+        @exception IOError raised to indicate an issue with the device
+        """
+        if self.__repl.isMicrobit():
+            # BBC micro:bit does not support directories
+            return ""
+        
+        commands = [
+            "import os as __os_",
+            "print(__os_.getcwd())",
+            "del __os_",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        return out.decode("utf-8").strip()
+    
+    def rm(self, filename):
+        """
+        Public method to remove a file from the connected device.
+        
+        @param filename name of the file to be removed
+        @type str
+        @exception IOError raised to indicate an issue with the device
+        """
+        assert filename
+        
+        commands = [
+            "import os as __os_",
+            "__os_.remove('{0}')".format(filename),
+            "del __os_",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+    
+    def rmrf(self, name, recursive=False, force=False):
+        """
+        Public method to remove a file or directory recursively.
+        
+        @param name of the file or directory to remove
+        @type str
+        @param recursive flag indicating a recursive deletion
+        @type bool
+        @param force flag indicating to ignore errors
+        @type bool
+        @return flag indicating success
+        @rtype bool
+        @exception IOError raised to indicate an issue with the device
+        """
+        assert name
+        
+        commands = [
+            "import os as __os_",
+            "\n".join([
+                "def remove_file(name, recursive=False, force=False):",
+                "    try:",
+                "        mode = __os_.stat(name)[0]",
+                "        if mode & 0x4000 != 0:",
+                "            if recursive:",
+                "                for file in __os_.listdir(name):",
+                "                    success = remove_file(name + '/' + file,"
+                " recursive, force)",
+                "                    if not success and not force:",
+                "                        return False",
+                "                __os_.rmdir(name)",
+                "            else:",
+                "                if not force:",
+                "                    return False",
+                "        else:",
+                "            __os_.remove(name)",
+                "    except:",
+                "        if not force:",
+                "            return False",
+                "    return True",
+            ]),
+            "print(remove_file('{0}', {1}, {2}))".format(name, recursive,
+                                                         force),
+            "del __os_, remove_file",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        return ast.literal_eval(out.decode("utf-8"))
+    
+    def mkdir(self, dirname):
+        """
+        Public method to create a new directory.
+        
+        @param dirname name of the directory to create
+        @type str
+        @exception IOError raised to indicate an issue with the device
+        """
+        assert dirname
+   
+        commands = [
+            "import os as __os_",
+            "__os_.mkdir('{0}')".format(dirname),
+            "del __os_",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+    
+    def rmdir(self, dirname):
+        """
+        Public method to remove a directory.
+        
+        @param dirname name of the directory to be removed
+        @type str
+        @exception IOError raised to indicate an issue with the device
+        """
+        assert dirname
+   
+        commands = [
+            "import os as __os_",
+            "__os_.rmdir('{0}')".format(dirname),
+            "del __os_",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+    
+    def put(self, hostFileName, deviceFileName=None):
+        """
+        Public method to copy a local file to the connected device.
+        
+        @param hostFileName name of the file to be copied
+        @type str
+        @param deviceFileName name of the file to copy to
+        @type str
+        @return flag indicating success
+        @rtype bool
+        @exception IOError raised to indicate an issue with the device
+        """
+        if not os.path.isfile(hostFileName):
+            raise IOError("No such file: {0}".format(hostFileName))
+        
+        with open(hostFileName, "rb") as hostFile:
+            content = hostFile.read()
+            # convert eol '\r'
+            content = content.replace(b"\r\n", b"\r")
+            content = content.replace(b"\n", b"\r")
+        
+        if not deviceFileName:
+            deviceFileName = os.path.basename(hostFileName)
+        
+        commands = [
+            "fd = open('{0}', 'wb')".format(deviceFileName),
+            "f = fd.write",
+        ]
+        while content:
+            chunk = content[:64]
+            commands.append("f(" + repr(chunk) + ")")
+            content = content[64:]
+        commands.extend([
+            "fd.close()",
+            "del f, fd",
+        ])
+        
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        return True
+    
+    def get(self, deviceFileName, hostFileName=None):
+        """
+        Public method to copy a file from the connected device.
+        
+        @param deviceFileName name of the file to copy
+        @type str
+        @param hostFileName name of the file to copy to
+        @type str
+        @return flag indicating success
+        @rtype bool
+        @exception IOError raised to indicate an issue with the device
+        """
+        if not hostFileName:
+            hostFileName = deviceFileName
+        
+        commands = [
+            "\n".join([
+                "def send_data():",
+                "    try:",
+                "        from microbit import uart as u",
+                "    except ImportError:",
+                "        try:",
+                "            from machine import UART",
+                "            u = UART(0, {0})".format(115200),
+                "        except Exception:",
+                "            try:",
+                "                from sys import stdout as u",
+                "            except Exception:",
+                "                raise Exception('Could not find UART module"
+                " in device.')",
+                "    f = open('{0}', 'rb')".format(deviceFileName),
+                "    r = f.read",
+                "    result = True",
+                "    while result:",
+                "        result = r(32)",
+                "        if result:",
+                "            u.write(result)",
+                "    f.close()",
+            ]),
+            "send_data()",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        
+        # write the received bytes to the local file
+        # convert eol to "\n"
+        out = out.replace(b"\r\n", b"\n")
+        out = out.replace(b"\r", b"\n")
+        with open(hostFileName, "wb") as hostFile:
+            hostFile.write(out)
+        return True
+    
+    def fileSystemInfo(self):
+        """
+        Public method to obtain information about the currently mounted file
+        systems.
+        
+        @return tuple of tuples containing the file system name, the total
+            size, the used size and the free size
+        @rtype tuple of tuples of (str, int, int, int)
+        @exception IOError raised to indicate an issue with the device
+        """
+        commands = [
+            "import os as __os_",
+            "\n".join([
+                "def fsinfo():",
+                "    infolist = []",
+                "    info = __os_.statvfs('/')",
+                "    if info[0] == 0:",
+                # assume it is just mount points
+                "        fsnames = __os_.listdir('/')",
+                "        for fs in fsnames:",
+                "            fs = '/' + fs",
+                "            infolist.append((fs, __os_.statvfs(fs)))",
+                "    else:",
+                "        infolist.append(('/', info))",
+                "    return infolist",
+            ]),
+            "print(fsinfo())",
+            "del __os_, fsinfo",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        infolist = ast.literal_eval(out.decode("utf-8"))
+        if infolist is None:
+            return None
+        else:
+            filesystemInfos = []
+            for fs, info in infolist:
+                totalSize = info[2] * info[1]
+                freeSize = info[4] * info[1]
+                usedSize = totalSize - freeSize
+                filesystemInfos.append((fs, totalSize, usedSize, freeSize))
+        
+        return tuple(filesystemInfos)
+    
+    ##################################################################
+    ## non-filesystem related methods below
+    ##################################################################
+    
+    def version(self):
+        """
+        Public method to get the MicroPython version information of the
+        connected device.
+        
+        @return dictionary containing the version information
+        @rtype dict
+        @exception IOError raised to indicate an issue with the device
+        """
+        commands = [
+            "import os as __os_",
+            "print(__os_.uname())",
+            "del __os_",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        
+        rawOutput = out.decode("utf-8").strip()
+        rawOutput = rawOutput[1:-1]
+        items = rawOutput.split(",")
+        result = {}
+        for item in items:
+            key, value = item.strip().split("=")
+            result[key.strip()] = value.strip()[1:-1]
+        return result
+    
+    def getImplementation(self):
+        """
+        Public method to get some implementation information of the connected
+        device.
+        
+        @return dictionary containing the implementation information
+        @rtype dict
+        @exception IOError raised to indicate an issue with the device
+        """
+        commands = [
+            "import sys as __sys_",
+            "res = {}",                             # __IGNORE_WARNING_M613__
+            "\n".join([
+                "try:",
+                "    res['name'] = __sys_.implementation.name",
+                "except AttributeError:",
+                "    res['name'] = 'unknown'",
+            ]),
+            "\n".join([
+                "try:",
+                "    res['version'] = '.'.join((str(i) for i in"
+                " __sys_.implementation.version))",
+                "except AttributeError:",
+                "    res['version'] = 'unknown'",
+            ]),
+            "print(res)",
+            "del res, __sys_",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        return ast.literal_eval(out.decode("utf-8"))
+    
+    def syncTime(self):
+        """
+        Public method to set the time of the connected device to the local
+        computer's time.
+        
+        @exception IOError raised to indicate an issue with the device
+        """
+        now = time.localtime(time.time())
+        commands = [
+            "\n".join([
+                "def set_time(rtc_time):",
+                "    rtc = None",
+                "    try:",           # Pyboard (it doesn't have machine.RTC())
+                "        import pyb as __pyb_",
+                "        rtc = __pyb_.RTC()",
+                "        clock_time = rtc_time[:6] + (rtc_time[6] + 1, 0)",
+                "        rtc.datetime(clock_time)",
+                "        del __pyb_",
+                "    except Exception:",
+                "        try:",
+                "            import machine as __machine_",
+                "            rtc = __machine_.RTC()",
+                "            try:",     # ESP8266 may use rtc.datetime()
+                "                clock_time = rtc_time[:6] +"
+                " (rtc_time[6] + 1, 0)",
+                "                rtc.datetime(clock_time)",
+                "            except Exception:",  # ESP32 uses rtc.init()
+                "                rtc.init(rtc_time[:6])",
+                "            del __machine_",
+                "        except:",
+                "            try:",
+                "                import rtc as __rtc_",
+                "                import time as __time_",
+                "                clock=__rtc_.RTC()",
+                "                clock.datetime = __time_.struct_time("
+                "rtc_time + (-1, -1))",
+                "                del __rtc_, __time_",
+                "            except:",
+                "                pass",
+            ]),
+            "set_time({0})".format((now.tm_year, now.tm_mon, now.tm_mday,
+                                    now.tm_hour, now.tm_min, now.tm_sec,
+                                    now.tm_wday)),
+            "del set_time",
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+    
+    def getTime(self):
+        """
+        Public method to get the current time of the device.
+        
+        @return time of the device
+        @rtype str
+        @exception IOError raised to indicate an issue with the device
+        """
+        commands = [
+            "import time as __time_",
+            "\n".join([
+                "try:",
+                "    print(__time_.strftime('%Y-%m-%d %H:%M:%S',"
+                # __IGNORE_WARNING_M601__
+                " __time_.localtime()))",
+                "except AttributeError:",
+                "    tm = __time_.localtime()",
+                "    print('{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'"
+                ".format(tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour,"
+                " tm.tm_min, tm.tm_sec))",
+                "    del tm",
+            ]),
+            "del __time_"
+        ]
+        out, err = self.execute(commands)
+        if err:
+            raise IOError(self.__shortError(err))
+        return out.decode("utf-8").strip()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonDevices.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,339 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing some utility functions and the MicroPythonDevice base
+class.
+"""
+
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from PyQt5.QtCore import pyqtSlot, QObject
+
+import UI.PixmapCache
+import Preferences
+
+
+SupportedBoards = {
+    "esp": {
+        "ids": [
+            (0x1A86, 0x7523),       # HL-340
+            (0x10C4, 0xEA60),       # CP210x
+            (0x0403, 0x6015),       # Sparkfun ESP32
+        ],
+        "description": "ESP8266, ESP32",
+        "icon": "esp32Device",
+    },
+    
+    "circuitpython": {
+        "ids": [
+            (0x2B04, 0xC00C),       # Particle Argon
+            (0x2B04, 0xC00D),       # Particle Boron
+            (0x2B04, 0xC00E),       # Particle Xenon
+            (0x239A, None),         # Any Adafruit Boards
+            (0x1209, 0xBAB1),       # Electronic Cats Meow Meow
+            (0x1209, 0xBAB2),       # Electronic Cats CatWAN USBStick
+            (0x1209, 0xBAB3),       # Electronic Cats Bast Pro Mini M0
+            (0x1B4F, 0x8D22),       # SparkFun SAMD21 Mini Breakout
+            (0x1B4F, 0x8D23),       # SparkFun SAMD21 Dev Breakout
+            (0x1209, 0x2017),       # Mini SAM M4
+            (0x1209, 0x7102),       # Mini SAM M0
+        ],
+        "description": "CircuitPython Boards",
+        "icon": "circuitPythonDevice",
+    },
+    
+    "bbc_microbit": {
+        "ids": [
+            (0x0D28, 0x0204),       # micro:bit
+        ],
+        "description": "BBC micro:bit",
+        "icon": "microbitDevice",
+    },
+}
+
+
+def getSupportedDevices():
+    """
+    Function to get a list of supported MicroPython devices.
+    
+    @return set of tuples with the board type and description
+    @rtype set of tuples of (str, str)
+    """
+    boards = []
+    for board in SupportedBoards:
+        boards.append(
+            (board, SupportedBoards[board]["description"]))
+    return boards
+
+
+def getFoundDevices():
+    """
+    Function to check the serial ports for supported MicroPython devices.
+    
+    @return set of tuples with the board type, a description and the serial
+        port it is connected at
+    @rtype set of tuples of (str, str, str)
+    """
+    from PyQt5.QtSerialPort import QSerialPortInfo
+    
+    foundDevices = []
+    
+    availablePorts = QSerialPortInfo.availablePorts()
+    for port in availablePorts:
+        vid = port.vendorIdentifier()
+        pid = port.productIdentifier()
+        for board in SupportedBoards:
+            if ((vid, pid) in SupportedBoards[board]["ids"] or
+                    (vid, None) in SupportedBoards[board]["ids"]):
+                foundDevices.append(
+                    (board, SupportedBoards[board]["description"],
+                     port.portName()))
+                break
+        else:
+            logging.debug("Unknown device: (0x%04x:0x%04x)", vid, pid)
+    
+    return foundDevices
+
+
+def getDeviceIcon(boardName, iconFormat=True):
+    """
+    Function to get the icon for the given board.
+    
+    @param boardName name of the board
+    @type str
+    @param iconFormat flag indicating to get an icon or a pixmap
+    @type bool
+    @return icon for the board (iconFormat == True) or
+        a pixmap (iconFormat == False)
+    @rtype QIcon or QPixmap
+    """
+    if boardName in SupportedBoards:
+        iconName = SupportedBoards[boardName]["icon"]
+    else:
+        # return a generic MicroPython icon
+        iconName = "micropython48"
+    
+    if iconFormat:
+        return UI.PixmapCache.getIcon(iconName)
+    else:
+        return UI.PixmapCache.getPixmap(iconName)
+
+
+def getDevice(deviceType, microPythonWidget):
+    """
+    Public method to instantiate a specific MicroPython device interface.
+    
+    @param deviceType type of the device interface
+    @type str
+    @param microPythonWidget reference to the main MicroPython widget
+    @type MicroPythonWidget
+    @return instantiated device interface
+    @rtype MicroPythonDevice
+    """
+    if deviceType == "esp":
+        from .EspDevices import EspDevice
+        return EspDevice(microPythonWidget)
+    elif deviceType == "circuitpython":
+        from .CircuitPythonDevices import CircuitPythonDevice
+        return CircuitPythonDevice(microPythonWidget)
+    elif deviceType == "bbc_microbit":
+        from .MicrobitDevices import MicrobitDevice
+        return MicrobitDevice(microPythonWidget)
+    else:
+        # nothing specific requested
+        return MicroPythonDevice(microPythonWidget)
+
+
+class MicroPythonDevice(QObject):
+    """
+    Base class for the more specific MicroPython devices.
+    """
+    def __init__(self, microPythonWidget, parent=None):
+        """
+        Constructor
+        
+        @param microPythonWidget reference to the main MicroPython widget
+        @type MicroPythonWidget
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(MicroPythonDevice, self).__init__(parent)
+        
+        self.microPython = microPythonWidget
+    
+    def setButtons(self):
+        """
+        Public method to enable the supported action buttons.
+        """
+        self.microPython.setActionButtons(
+            open=False, save=False,
+            run=False, repl=False, files=False, chart=False)
+    
+    def forceInterrupt(self):
+        """
+        Public method to determine the need for an interrupt when opening the
+        serial connection.
+        
+        @return flag indicating an interrupt is needed
+        @rtype bool
+        """
+        return True
+    
+    def deviceName(self):
+        """
+        Public method to get the name of the device.
+        
+        @return name of the device
+        @rtype str
+        """
+        return self.tr("Unsupported Device")
+    
+    def canStartRepl(self):
+        """
+        Public method to determine, if a REPL can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a REPL
+            and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return False, self.tr("REPL is not supported by this device.")
+    
+    def setRepl(self, on):
+        """
+        Public method to set the REPL status and dependent status.
+        
+        @param on flag indicating the active status
+        @type bool
+        """
+        pass
+    
+    def canStartPlotter(self):
+        """
+        Public method to determine, if a Plotter can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return False, self.tr("Plotter is not supported by this device.")
+    
+    def setPlotter(self, on):
+        """
+        Public method to set the Plotter status and dependent status.
+        
+        @param on flag indicating the active status
+        @type bool
+        """
+        pass
+    
+    def canRunScript(self):
+        """
+        Public method to determine, if a script can be executed.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return False, self.tr("Running scripts is not supported by this"
+                              " device.")
+    
+    def runScript(self, script):
+        """
+        Public method to run the given Python script.
+        
+        @param script script to be executed
+        @type str
+        """
+        pass
+    
+    def canStartFileManager(self):
+        """
+        Public method to determine, if a File Manager can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            File Manager and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return False, self.tr("File Manager is not supported by this device.")
+    
+    def setFileManager(self, on):
+        """
+        Public method to set the File Manager status and dependent status.
+        
+        @param on flag indicating the active status
+        @type bool
+        """
+        pass
+    
+    def supportsLocalFileAccess(self):
+        """
+        Public method to indicate file access via a local directory.
+        
+        @return flag indicating file access via local directory
+        @type bool
+        """
+        return False        # default
+    
+    def getWorkspace(self):
+        """
+        Public method to get the workspace directory.
+        
+        @return workspace directory used for saving files
+        @rtype str
+        """
+        return (Preferences.getMultiProject("Workspace") or
+                os.path.expanduser("~"))
+    
+    def sendCommands(self, commandsList):
+        """
+        Public method to send a list of commands to the device.
+        
+        @param commandsList list of commands to be sent to the device
+        @type list of str
+        """
+        rawOn = [       # sequence of commands to enter raw mode
+            b'\x02',            # Ctrl-B: exit raw repl (just in case)
+            b'\r\x03\x03\x03',  # Ctrl-C three times: interrupt any running
+                                # program
+            b'\r\x01',          # Ctrl-A: enter raw REPL
+        ]
+        newLine = [b'print("\\n")\r', ]
+        commands = [c.encode("utf-8)") + b'\r' for c in commandsList]
+        commands.append(b'\r')
+        commands.append(b'\x04')
+        rawOff = [b'\x02']
+        commandSequence = rawOn + newLine + commands + rawOff
+        self.microPython.commandsInterface().executeAsync(commandSequence)
+    
+    @pyqtSlot()
+    def handleDataFlood(self):
+        """
+        Public slot handling a data floof from the device.
+        """
+        pass
+    
+    def addDeviceMenuEntries(self, menu):
+        """
+        Public method to add device specific entries to the given menu.
+        
+        @param menu reference to the context menu
+        @type QMenu
+        """
+        pass
+    
+    def hasTimeCommands(self):
+        """
+        Public method to check, if the device supports time commands.
+        
+        The default returns True.
+        
+        @return flag indicating support for time commands
+        @rtype bool
+        """
+        return True
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonFileManager.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,459 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing some file system commands for MicroPython.
+"""
+
+from __future__ import unicode_literals
+
+import os
+import stat
+import shutil
+
+from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
+
+from .MicroPythonFileSystemUtilities import (
+    mtime2string, mode2string, decoratedName, listdirStat
+)
+
+
+class MicroPythonFileManager(QObject):
+    """
+    Class implementing an interface to the device file system commands with
+    some additional sugar.
+    
+    @signal longListFiles(result) emitted with a tuple of tuples containing the
+        name, mode, size and time for each directory entry
+    @signal currentDir(dirname) emitted to report the current directory of the
+        device
+    @signal currentDirChanged(dirname) emitted to report back a change of the
+        current directory
+    @signal getFileDone(deviceFile, localFile) emitted after the file was
+        fetched from the connected device and written to the local file system
+    @signal putFileDone(localFile, deviceFile) emitted after the file was
+        copied to the connected device
+    @signal deleteFileDone(deviceFile) emitted after the file has been deleted
+        on the connected device
+    @signal rsyncDone(localName, deviceName) emitted after the rsync operation
+        has been completed
+    @signal rsyncProgressMessage(msg) emitted to send a message about what
+        rsync is doing
+    @signal removeDirectoryDone() emitted after a directory has been deleted
+    @signal createDirectoryDone() emitted after a directory was created
+    @signal fsinfoDone(fsinfo) emitted after the file system information was
+        obtained
+    
+    @signal error(exc) emitted with a failure message to indicate a failure
+        during the most recent operation
+    """
+    longListFiles = pyqtSignal(tuple)
+    currentDir = pyqtSignal(str)
+    currentDirChanged = pyqtSignal(str)
+    getFileDone = pyqtSignal(str, str)
+    putFileDone = pyqtSignal(str, str)
+    deleteFileDone = pyqtSignal(str)
+    rsyncDone = pyqtSignal(str, str)
+    rsyncProgressMessage = pyqtSignal(str)
+    removeDirectoryDone = pyqtSignal()
+    createDirectoryDone = pyqtSignal()
+    fsinfoDone = pyqtSignal(tuple)
+    
+    error = pyqtSignal(str, str)
+    
+    def __init__(self, commandsInterface, parent=None):
+        """
+        Constructor
+        
+        @param commandsInterface reference to the commands interface object
+        @type MicroPythonCommandsInterface
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(MicroPythonFileManager, self).__init__(parent)
+        
+        self.__commandsInterface = commandsInterface
+    
+    @pyqtSlot(str)
+    def lls(self, dirname, showHidden=False):
+        """
+        Public slot to get a long listing of the given directory.
+        
+        @param dirname name of the directory to list
+        @type str
+        @param showHidden flag indicating to show hidden files as well
+        @type bool
+        """
+        try:
+            filesList = self.__commandsInterface.lls(
+                dirname, showHidden=showHidden)
+            result = [(decoratedName(name, mode),
+                       mode2string(mode),
+                       str(size),
+                       mtime2string(mtime)) for
+                      name, (mode, size, mtime) in filesList]
+            self.longListFiles.emit(tuple(result))
+        except Exception as exc:
+            self.error.emit("lls", str(exc))
+    
+    @pyqtSlot()
+    def pwd(self):
+        """
+        Public slot to get the current directory of the device.
+        """
+        try:
+            pwd = self.__commandsInterface.pwd()
+            self.currentDir.emit(pwd)
+        except Exception as exc:
+            self.error.emit("pwd", str(exc))
+    
+    @pyqtSlot(str)
+    def cd(self, dirname):
+        """
+        Public slot to change the current directory of the device.
+        
+        @param dirname name of the desired current directory
+        @type str
+        """
+        try:
+            self.__commandsInterface.cd(dirname)
+            self.currentDirChanged.emit(dirname)
+        except Exception as exc:
+            self.error.emit("cd", str(exc))
+    
+    @pyqtSlot(str)
+    @pyqtSlot(str, str)
+    def get(self, deviceFileName, hostFileName=""):
+        """
+        Public slot to get a file from the connected device.
+        
+        @param deviceFileName name of the file on the device
+        @type str
+        @param hostFileName name of the local file
+        @type str
+        """
+        if hostFileName and os.path.isdir(hostFileName):
+            # only a local directory was given
+            hostFileName = os.path.join(hostFileName,
+                                        os.path.basename(deviceFileName))
+        try:
+            self.__commandsInterface.get(deviceFileName, hostFileName)
+            self.getFileDone.emit(deviceFileName, hostFileName)
+        except Exception as exc:
+            self.error.emit("get", str(exc))
+    
+    @pyqtSlot(str)
+    @pyqtSlot(str, str)
+    def put(self, hostFileName, deviceFileName=""):
+        """
+        Public slot to put a file onto the device.
+        
+        @param hostFileName name of the local file
+        @type str
+        @param deviceFileName name of the file on the connected device
+        @type str
+        """
+        try:
+            self.__commandsInterface.put(hostFileName, deviceFileName)
+            self.putFileDone.emit(hostFileName, deviceFileName)
+        except Exception as exc:
+            self.error.emit("put", str(exc))
+    
+    @pyqtSlot(str)
+    def delete(self, deviceFileName):
+        """
+        Public slot to delete a file on the device.
+        
+        @param deviceFileName name of the file on the connected device
+        @type str
+        """
+        try:
+            self.__commandsInterface.rm(deviceFileName)
+            self.deleteFileDone.emit(deviceFileName)
+        except Exception as exc:
+            self.error.emit("delete", str(exc))
+    
+    def __rsync(self, hostDirectory, deviceDirectory, mirror=True,
+                localDevice=False):
+        """
+        Private method to synchronize a local directory to the device.
+        
+        @param hostDirectory name of the local directory
+        @type str
+        @param deviceDirectory name of the directory on the device
+        @type str
+        @param mirror flag indicating to mirror the local directory to
+            the device directory
+        @type bool
+        @param localDevice flag indicating device access via local file system
+        @type bool
+        @return list of errors
+        @rtype list of str
+        """
+        errors = []
+        
+        if not os.path.isdir(hostDirectory):
+            return [self.tr(
+                "The given name '{0}' is not a directory or does not exist.")
+                .format(hostDirectory)
+            ]
+        
+        self.rsyncProgressMessage.emit(
+            self.tr("Synchronizing <b>{0}</b>.").format(deviceDirectory)
+        )
+        
+        doneMessage = self.tr("Done synchronizing <b>{0}</b>.").format(
+            deviceDirectory)
+        
+        sourceDict = {}
+        sourceFiles = listdirStat(hostDirectory)
+        for name, nstat in sourceFiles:
+            sourceDict[name] = nstat
+        
+        destinationDict = {}
+        if localDevice:
+            if not os.path.isdir(deviceDirectory):
+                # simply copy destination to source
+                shutil.copytree(hostDirectory, deviceDirectory)
+                self.rsyncProgressMessage.emit(doneMessage)
+                return errors
+            else:
+                destinationFiles = listdirStat(deviceDirectory)
+                for name, nstat in destinationFiles:
+                    destinationDict[name] = nstat
+        else:
+            try:
+                destinationFiles = self.__commandsInterface.lls(
+                    deviceDirectory, fullstat=True)
+            except Exception as exc:
+                return [str(exc)]
+            if destinationFiles is None:
+                # the destination directory does not exist
+                try:
+                    self.__commandsInterface.mkdir(deviceDirectory)
+                except Exception as exc:
+                    return [str(exc)]
+            else:
+                for name, nstat in destinationFiles:
+                    destinationDict[name] = nstat
+        
+        destinationSet = set(destinationDict.keys())
+        sourceSet = set(sourceDict.keys())
+        toAdd = sourceSet - destinationSet                  # add to dev
+        toDelete = destinationSet - sourceSet               # delete from dev
+        toUpdate = destinationSet.intersection(sourceSet)   # update files
+        
+        if localDevice:
+            for sourceBasename in toAdd:
+                # name exists in source but not in device
+                sourceFilename = os.path.join(hostDirectory, sourceBasename)
+                destFilename = os.path.join(deviceDirectory, sourceBasename)
+                self.rsyncProgressMessage.emit(
+                    self.tr("Adding <b>{0}</b>...").format(destFilename))
+                if os.path.isfile(sourceFilename):
+                    shutil.copy2(sourceFilename, destFilename)
+                elif os.path.isdir(sourceFilename):
+                    # recurse
+                    errs = self.__rsync(sourceFilename, destFilename,
+                                        mirror=mirror, localDevice=localDevice)
+                    # just note issues but ignore them otherwise
+                    errors.extend(errs)
+            
+            if mirror:
+                for destBasename in toDelete:
+                    # name exists in device but not local, delete
+                    destFilename = os.path.join(deviceDirectory, destBasename)
+                    if os.path.isdir(destFilename):
+                        shutil.rmtree(destFilename, ignore_errors=True)
+                    elif os.path.isfile(destFilename):
+                        os.remove(destFilename)
+            
+            for sourceBasename in toUpdate:
+                # names exist in both; do an update
+                sourceStat = sourceDict[sourceBasename]
+                destStat = destinationDict[sourceBasename]
+                sourceFilename = os.path.join(hostDirectory, sourceBasename)
+                destFilename = os.path.join(deviceDirectory, sourceBasename)
+                destMode = destStat[0]
+                if os.path.isdir(sourceFilename):
+                    if os.path.isdir(destFilename):
+                        # both are directories => recurs
+                        errs = self.__rsync(sourceFilename, destFilename,
+                                            mirror=mirror,
+                                            localDevice=localDevice)
+                        # just note issues but ignore them otherwise
+                        errors.extend(errs)
+                    else:
+                        self.rsyncProgressMessage.emit(
+                            self.tr("Source <b>{0}</b> is a directory and"
+                                    " destination <b>{1}</b> is a file."
+                                    " Ignoring it.")
+                            .format(sourceFilename, destFilename)
+                        )
+                else:
+                    if os.path.isdir(destFilename):
+                        self.rsyncProgressMessage.emit(
+                            self.tr("Source <b>{0}</b> is a file and"
+                                    " destination <b>{1}</b> is a directory."
+                                    " Ignoring it.")
+                            .format(sourceFilename, destFilename)
+                        )
+                    else:
+                        if sourceStat[8] > destStat[8]:     # mtime
+                            self.rsyncProgressMessage.emit(
+                                self.tr("Updating <b>{0}</b>...")
+                                .format(destFilename)
+                            )
+                        shutil.copy2(sourceFilename, destFilename)
+        else:
+            for sourceBasename in toAdd:
+                # name exists in source but not in device
+                sourceFilename = os.path.join(hostDirectory, sourceBasename)
+                destFilename = deviceDirectory + "/" + sourceBasename
+                self.rsyncProgressMessage.emit(
+                    self.tr("Adding <b>{0}</b>...").format(destFilename))
+                if os.path.isfile(sourceFilename):
+                    try:
+                        self.__commandsInterface.put(sourceFilename,
+                                                     destFilename)
+                    except Exception as exc:
+                        # just note issues but ignore them otherwise
+                        errors.append(str(exc))
+                elif os.path.isdir(sourceFilename):
+                    # recurse
+                    errs = self.__rsync(sourceFilename, destFilename,
+                                        mirror=mirror)
+                    # just note issues but ignore them otherwise
+                    errors.extend(errs)
+        
+            if mirror:
+                for destBasename in toDelete:
+                    # name exists in device but not local, delete
+                    destFilename = deviceDirectory + "/" + destBasename
+                    self.rsyncProgressMessage.emit(
+                        self.tr("Removing <b>{0}</b>...").format(destFilename))
+                    try:
+                        self.__commandsInterface.rmrf(destFilename,
+                                                      recursive=True,
+                                                      force=True)
+                    except Exception as exc:
+                        # just note issues but ignore them otherwise
+                        errors.append(str(exc))
+            
+            for sourceBasename in toUpdate:
+                # names exist in both; do an update
+                sourceStat = sourceDict[sourceBasename]
+                destStat = destinationDict[sourceBasename]
+                sourceFilename = os.path.join(hostDirectory, sourceBasename)
+                destFilename = deviceDirectory + "/" + sourceBasename
+                destMode = destStat[0]
+                if os.path.isdir(sourceFilename):
+                    if stat.S_ISDIR(destMode):
+                        # both are directories => recurs
+                        errs = self.__rsync(sourceFilename, destFilename,
+                                            mirror=mirror)
+                        # just note issues but ignore them otherwise
+                        errors.extend(errs)
+                    else:
+                        self.rsyncProgressMessage.emit(
+                            self.tr("Source <b>{0}</b> is a directory and"
+                                    " destination <b>{1}</b> is a file."
+                                    " Ignoring it.")
+                            .format(sourceFilename, destFilename)
+                        )
+                else:
+                    if stat.S_ISDIR(destMode):
+                        self.rsyncProgressMessage.emit(
+                            self.tr("Source <b>{0}</b> is a file and"
+                                    " destination <b>{1}</b> is a directory."
+                                    " Ignoring it.")
+                            .format(sourceFilename, destFilename)
+                        )
+                    else:
+                        if sourceStat[8] > destStat[8]:     # mtime
+                            self.rsyncProgressMessage.emit(
+                                self.tr("Updating <b>{0}</b>...")
+                                .format(destFilename)
+                            )
+                            try:
+                                self.__commandsInterface.put(sourceFilename,
+                                                             destFilename)
+                            except Exception as exc:
+                                errors.append(str(exc))
+        
+        self.rsyncProgressMessage.emit(doneMessage)
+        
+        return errors
+    
+    @pyqtSlot(str, str)
+    @pyqtSlot(str, str, bool)
+    @pyqtSlot(str, str, bool, bool)
+    def rsync(self, hostDirectory, deviceDirectory, mirror=True,
+              localDevice=False):
+        """
+        Public slot to synchronize a local directory to the device.
+        
+        @param hostDirectory name of the local directory
+        @type str
+        @param deviceDirectory name of the directory on the device
+        @type str
+        @param mirror flag indicating to mirror the local directory to
+            the device directory
+        @type bool
+        @param localDevice flag indicating device access via local file system
+        @type bool
+        """
+        errors = self.__rsync(hostDirectory, deviceDirectory, mirror=mirror,
+                              localDevice=localDevice)
+        if errors:
+            self.error.emit("rsync", "\n".join(errors))
+        
+        self.rsyncDone.emit(hostDirectory, deviceDirectory)
+    
+    @pyqtSlot(str)
+    def mkdir(self, dirname):
+        """
+        Public slot to create a new directory.
+        
+        @param dirname name of the directory to create
+        @type str
+        """
+        try:
+            self.__commandsInterface.mkdir(dirname)
+            self.createDirectoryDone.emit()
+        except Exception as exc:
+            self.error.emit("mkdir", str(exc))
+    
+    @pyqtSlot(str)
+    @pyqtSlot(str, bool)
+    def rmdir(self, dirname, recursive=False):
+        """
+        Public slot to (recursively) remove a directory.
+        
+        @param dirname name of the directory to be removed
+        @type str
+        @param recursive flag indicating a recursive removal
+        @type bool
+        """
+        try:
+            if recursive:
+                self.__commandsInterface.rmrf(dirname, recursive=True,
+                                              force=True)
+            else:
+                self.__commandsInterface.rmdir(dirname)
+            self.removeDirectoryDone.emit()
+        except Exception as exc:
+            self.error.emit("rmdir", str(exc))
+    
+    def fileSystemInfo(self):
+        """
+        Public method to obtain information about the currently mounted file
+        systems.
+        """
+        try:
+            fsinfo = self.__commandsInterface.fileSystemInfo()
+            self.fsinfoDone.emit(fsinfo)
+        except Exception as exc:
+            self.error.emit("fileSystemInfo", str(exc))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonFileManagerWidget.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,954 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a file manager for MicroPython devices.
+"""
+
+from __future__ import unicode_literals
+
+import os
+import shutil
+
+from PyQt5.QtCore import pyqtSlot, Qt, QPoint
+from PyQt5.QtWidgets import (
+    QWidget, QTreeWidgetItem, QHeaderView, QMenu, QInputDialog, QLineEdit,
+    QDialog
+)
+
+from E5Gui import E5MessageBox, E5PathPickerDialog
+from E5Gui.E5PathPicker import E5PathPickerModes
+from E5Gui.E5FileSaveConfirmDialog import confirmOverwrite
+from E5Gui.E5Application import e5App
+
+from .Ui_MicroPythonFileManagerWidget import Ui_MicroPythonFileManagerWidget
+
+from .MicroPythonFileManager import MicroPythonFileManager
+from .MicroPythonFileSystemUtilities import (
+    mtime2string, mode2string, decoratedName, listdirStat
+)
+
+from UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
+
+import UI.PixmapCache
+import Preferences
+import Utilities
+import Globals
+
+
+class MicroPythonFileManagerWidget(QWidget, Ui_MicroPythonFileManagerWidget):
+    """
+    Class implementing a file manager for MicroPython devices.
+    """
+    def __init__(self, commandsInterface, deviceWithLocalAccess, parent=None):
+        """
+        Constructor
+        
+        @param commandsInterface reference to the commands interface object
+        @type MicroPythonCommandsInterface
+        @param deviceWithLocalAccess flag indicating the device supports file
+            access via a local directory
+        @type bool
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(MicroPythonFileManagerWidget, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.__repl = parent
+        self.__deviceWithLocalAccess = deviceWithLocalAccess
+        
+        self.syncButton.setIcon(UI.PixmapCache.getIcon("2rightarrow"))
+        self.putButton.setIcon(UI.PixmapCache.getIcon("1rightarrow"))
+        self.putAsButton.setIcon(UI.PixmapCache.getIcon("putAs"))
+        self.getButton.setIcon(UI.PixmapCache.getIcon("1leftarrow"))
+        self.getAsButton.setIcon(UI.PixmapCache.getIcon("getAs"))
+        self.localUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
+        self.localReloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
+        self.deviceUpButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
+        self.deviceReloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
+        
+        self.deviceUpButton.setEnabled(not self.__repl.isMicrobit())
+        
+        self.putButton.setEnabled(False)
+        self.putAsButton.setEnabled(False)
+        self.getButton.setEnabled(False)
+        self.getAsButton.setEnabled(False)
+        
+        self.localFileTreeWidget.header().setSortIndicator(
+            0, Qt.AscendingOrder)
+        self.deviceFileTreeWidget.header().setSortIndicator(
+            0, Qt.AscendingOrder)
+        
+        self.__progressInfoDialog = None
+        self.__fileManager = MicroPythonFileManager(commandsInterface, self)
+        
+        self.__fileManager.longListFiles.connect(self.__handleLongListFiles)
+        self.__fileManager.currentDir.connect(self.__handleCurrentDir)
+        self.__fileManager.currentDirChanged.connect(self.__handleCurrentDir)
+        self.__fileManager.putFileDone.connect(self.__newDeviceList)
+        self.__fileManager.getFileDone.connect(self.__handleGetDone)
+        self.__fileManager.rsyncDone.connect(self.__handleRsyncDone)
+        self.__fileManager.rsyncProgressMessage.connect(
+            self.__handleRsyncProgressMessage)
+        self.__fileManager.removeDirectoryDone.connect(self.__newDeviceList)
+        self.__fileManager.createDirectoryDone.connect(self.__newDeviceList)
+        self.__fileManager.deleteFileDone.connect(self.__newDeviceList)
+        self.__fileManager.fsinfoDone.connect(self.__fsInfoResultReceived)
+        
+        self.__fileManager.error.connect(self.__handleError)
+        
+        self.localFileTreeWidget.customContextMenuRequested.connect(
+            self.__showLocalContextMenu)
+        self.deviceFileTreeWidget.customContextMenuRequested.connect(
+            self.__showDeviceContextMenu)
+        
+        self.__localMenu = QMenu(self)
+        self.__localMenu.addAction(self.tr("Change Directory"),
+                                   self.__changeLocalDirectory)
+        self.__localMenu.addAction(
+            self.tr("Create Directory"), self.__createLocalDirectory)
+        self.__localDelDirTreeAct = self.__localMenu.addAction(
+            self.tr("Delete Directory Tree"), self.__deleteLocalDirectoryTree)
+        self.__localMenu.addSeparator()
+        self.__localDelFileAct = self.__localMenu.addAction(
+            self.tr("Delete File"), self.__deleteLocalFile)
+        self.__localMenu.addSeparator()
+        act = self.__localMenu.addAction(self.tr("Show Hidden Files"))
+        act.setCheckable(True)
+        act.setChecked(Preferences.getMicroPython("ShowHiddenLocal"))
+        act.triggered[bool].connect(self.__localHiddenChanged)
+        
+        self.__deviceMenu = QMenu(self)
+        if not self.__repl.isMicrobit():
+            self.__deviceMenu.addAction(
+                self.tr("Change Directory"), self.__changeDeviceDirectory)
+            self.__deviceMenu.addAction(
+                self.tr("Create Directory"), self.__createDeviceDirectory)
+            if not self.__deviceWithLocalAccess:
+                self.__devDelDirAct = self.__deviceMenu.addAction(
+                    self.tr("Delete Directory"), self.__deleteDeviceDirectory)
+            self.__devDelDirTreeAct = self.__deviceMenu.addAction(
+                self.tr("Delete Directory Tree"),
+                self.__deleteDeviceDirectoryTree)
+            self.__deviceMenu.addSeparator()
+        self.__devDelFileAct = self.__deviceMenu.addAction(
+            self.tr("Delete File"), self.__deleteDeviceFile)
+        self.__deviceMenu.addSeparator()
+        act = self.__deviceMenu.addAction(self.tr("Show Hidden Files"))
+        act.setCheckable(True)
+        act.setChecked(Preferences.getMicroPython("ShowHiddenDevice"))
+        act.triggered[bool].connect(self.__deviceHiddenChanged)
+        if not parent.isMicrobit():
+            self.__deviceMenu.addSeparator()
+            self.__deviceMenu.addAction(
+                self.tr("Show Filesystem Info"), self.__showFileSystemInfo)
+    
+    def start(self):
+        """
+        Public method to start the widget.
+        """
+        dirname = ""
+        vm = e5App().getObject("ViewManager")
+        aw = vm.activeWindow()
+        if aw:
+            dirname = os.path.dirname(aw.getFileName())
+        if not dirname:
+            dirname = (Preferences.getMultiProject("Workspace") or
+                       os.path.expanduser("~"))
+        self.__listLocalFiles(dirname)
+        
+        if self.__deviceWithLocalAccess:
+            dirname = self.__repl.getDeviceWorkspace()
+            if dirname:
+                self.__listLocalFiles(dirname, True)
+        else:
+            self.__fileManager.pwd()
+    
+    def stop(self):
+        """
+        Public method to stop the widget.
+        """
+        pass
+    
+    @pyqtSlot(str, str)
+    def __handleError(self, method, error):
+        """
+        Private slot to handle errors.
+        
+        @param method name of the method the error occured in
+        @type str
+        @param error error message
+        @type str
+        """
+        E5MessageBox.warning(
+            self,
+            self.tr("Error handling device"),
+            self.tr("<p>There was an error communicating with the connected"
+                    " device.</p><p>Method: {0}</p><p>Message: {1}</p>")
+            .format(method, error))
+    
+    @pyqtSlot(str)
+    def __handleCurrentDir(self, dirname):
+        """
+        Private slot to handle a change of the current directory of the device.
+        
+        @param dirname name of the current directory
+        @type str
+        """
+        self.deviceCwd.setText(dirname)
+        self.__newDeviceList()
+    
+    @pyqtSlot(tuple)
+    def __handleLongListFiles(self, filesList):
+        """
+        Private slot to receive a long directory listing.
+        
+        @param filesList tuple containing tuples with name, mode, size and time
+            for each directory entry
+        @type tuple of (str, str, str, str)
+        """
+        self.deviceFileTreeWidget.clear()
+        for name, mode, size, dateTime in filesList:
+            itm = QTreeWidgetItem(self.deviceFileTreeWidget,
+                                  [name, mode, size, dateTime])
+            itm.setTextAlignment(1, Qt.AlignHCenter)
+            itm.setTextAlignment(2, Qt.AlignRight)
+        self.deviceFileTreeWidget.header().resizeSections(
+            QHeaderView.ResizeToContents)
+    
+    def __listLocalFiles(self, dirname="", localDevice=False):
+        """
+        Private method to populate the local files list.
+        
+        @param dirname name of the local directory to be listed
+        @type str
+        @param localDevice flag indicating device access via local file system
+        @type bool
+        """
+        if not dirname:
+            dirname = os.getcwd()
+        if dirname.endswith(os.sep):
+            dirname = dirname[:-1]
+        if localDevice:
+            self.deviceCwd.setText(dirname)
+            showHidden = Preferences.getMicroPython("ShowHiddenDevice")
+        else:
+            self.localCwd.setText(dirname)
+            showHidden = Preferences.getMicroPython("ShowHiddenLocal")
+        
+        filesStatList = listdirStat(dirname, showHidden=showHidden)
+        filesList = [(
+            decoratedName(f, s[0], os.path.isdir(os.path.join(dirname, f))),
+            mode2string(s[0]),
+            str(s[6]),
+            mtime2string(s[8])) for f, s in filesStatList]
+        if localDevice:
+            fileTreeWidget = self.deviceFileTreeWidget
+        else:
+            fileTreeWidget = self.localFileTreeWidget
+        fileTreeWidget.clear()
+        for item in filesList:
+            itm = QTreeWidgetItem(fileTreeWidget, item)
+            itm.setTextAlignment(1, Qt.AlignHCenter)
+            itm.setTextAlignment(2, Qt.AlignRight)
+        fileTreeWidget.header().resizeSections(
+            QHeaderView.ResizeToContents)
+    
+    @pyqtSlot(QTreeWidgetItem, int)
+    def on_localFileTreeWidget_itemActivated(self, item, column):
+        """
+        Private slot to handle the activation of a local item.
+        
+        If the item is a directory, the list will be re-populated for this
+        directory.
+        
+        @param item reference to the activated item
+        @type QTreeWidgetItem
+        @param column column of the activation
+        @type int
+        """
+        name = os.path.join(self.localCwd.text(), item.text(0))
+        if name.endswith("/"):
+            # directory names end with a '/'
+            self.__listLocalFiles(name[:-1])
+        elif Utilities.MimeTypes.isTextFile(name):
+            e5App().getObject("ViewManager").getEditor(name)
+    
+    @pyqtSlot()
+    def on_localFileTreeWidget_itemSelectionChanged(self):
+        """
+        Private slot handling a change of selection in the local pane.
+        """
+        enable = bool(len(self.localFileTreeWidget.selectedItems()))
+        if enable:
+            enable &= not (
+                self.localFileTreeWidget.selectedItems()[0].text(0)
+                .endswith("/"))
+        self.putButton.setEnabled(enable)
+        self.putAsButton.setEnabled(enable)
+    
+    @pyqtSlot()
+    def on_localUpButton_clicked(self):
+        """
+        Private slot to go up one directory level.
+        """
+        cwd = self.localCwd.text()
+        dirname = os.path.dirname(cwd)
+        self.__listLocalFiles(dirname)
+    
+    @pyqtSlot()
+    def on_localReloadButton_clicked(self):
+        """
+        Private slot to reload the local list.
+        """
+        dirname = self.localCwd.text()
+        self.__listLocalFiles(dirname)
+    
+    @pyqtSlot(QTreeWidgetItem, int)
+    def on_deviceFileTreeWidget_itemActivated(self, item, column):
+        """
+        Private slot to handle the activation of a device item.
+        
+        If the item is a directory, the current working directory is changed
+        and the list will be re-populated for this directory.
+        
+        @param item reference to the activated item
+        @type QTreeWidgetItem
+        @param column column of the activation
+        @type int
+        """
+        name = os.path.join(self.deviceCwd.text(), item.text(0))
+        if self.__deviceWithLocalAccess:
+            if name.endswith("/"):
+                # directory names end with a '/'
+                self.__listLocalFiles(name[:-1], True)
+            elif Utilities.MimeTypes.isTextFile(name):
+                e5App().getObject("ViewManager").getEditor(name)
+        else:
+            if name.endswith("/"):
+                # directory names end with a '/'
+                self.__fileManager.cd(name[:-1])
+    
+    @pyqtSlot()
+    def on_deviceFileTreeWidget_itemSelectionChanged(self):
+        """
+        Private slot handling a change of selection in the local pane.
+        """
+        enable = bool(len(self.deviceFileTreeWidget.selectedItems()))
+        if enable:
+            enable &= not (
+                self.deviceFileTreeWidget.selectedItems()[0].text(0)
+                .endswith("/"))
+        self.getButton.setEnabled(enable)
+        self.getAsButton.setEnabled(enable)
+    
+    @pyqtSlot()
+    def on_deviceUpButton_clicked(self):
+        """
+        Private slot to go up one directory level on the device.
+        """
+        cwd = self.deviceCwd.text()
+        dirname = os.path.dirname(cwd)
+        if self.__deviceWithLocalAccess:
+            self.__listLocalFiles(dirname, True)
+        else:
+            self.__fileManager.cd(dirname)
+    
+    @pyqtSlot()
+    def on_deviceReloadButton_clicked(self):
+        """
+        Private slot to reload the device list.
+        """
+        dirname = self.deviceCwd.text()
+        if self.__deviceWithLocalAccess:
+            self.__listLocalFiles(dirname, True)
+        else:
+            if dirname:
+                self.__newDeviceList()
+            else:
+                self.__fileManager.pwd()
+    
+    def __isFileInList(self, filename, treeWidget):
+        """
+        Private method to check, if a file name is contained in a tree widget.
+        
+        @param filename name of the file to check
+        @type str
+        @param treeWidget reference to the tree widget to be checked against
+        @return flag indicating that the file name is present
+        @rtype bool
+        """
+        itemCount = treeWidget.topLevelItemCount()
+        if itemCount:
+            for row in range(itemCount):
+                if treeWidget.topLevelItem(row).text(0) == filename:
+                    return True
+        
+        return False
+    
+    @pyqtSlot()
+    def on_putButton_clicked(self, putAs=False):
+        """
+        Private slot to copy the selected file to the connected device.
+        
+        @param putAs flag indicating to give it a new name
+        @type bool
+        """
+        selectedItems = self.localFileTreeWidget.selectedItems()
+        if selectedItems:
+            filename = selectedItems[0].text(0).strip()
+            if not filename.endswith("/"):
+                # it is really a file
+                if putAs:
+                    deviceFilename, ok = QInputDialog.getText(
+                        self,
+                        self.tr("Put File As"),
+                        self.tr("Enter a new name for the file"),
+                        QLineEdit.Normal,
+                        filename)
+                    if not ok or not filename:
+                        return
+                else:
+                    deviceFilename = filename
+                
+                if self.__isFileInList(deviceFilename,
+                                       self.deviceFileTreeWidget):
+                    # ask for overwrite permission
+                    action, resultFilename = confirmOverwrite(
+                        deviceFilename, self.tr("Copy File to Device"),
+                        self.tr("The given file exists already"
+                                " (Enter file name only)."),
+                        False, self)
+                    if action == "cancel":
+                        return
+                    elif action == "rename":
+                        deviceFilename = os.path.basename(resultFilename)
+                
+                if self.__deviceWithLocalAccess:
+                    shutil.copy2(
+                        os.path.join(self.localCwd.text(), filename),
+                        os.path.join(self.deviceCwd.text(), deviceFilename)
+                    )
+                    self.__listLocalFiles(self.deviceCwd.text(),
+                                          localDevice=True)
+                else:
+                    deviceCwd = self.deviceCwd.text()
+                    if deviceCwd:
+                        if deviceCwd != "/":
+                            deviceFilename = deviceCwd + "/" + deviceFilename
+                        else:
+                            deviceFilename = "/" + deviceFilename
+                    self.__fileManager.put(
+                        os.path.join(self.localCwd.text(), filename),
+                        deviceFilename
+                    )
+    
+    @pyqtSlot()
+    def on_putAsButton_clicked(self):
+        """
+        Private slot to copy the selected file to the connected device
+        with a different name.
+        """
+        self.on_putButton_clicked(putAs=True)
+    
+    @pyqtSlot()
+    def on_getButton_clicked(self, getAs=False):
+        """
+        Private slot to copy the selected file from the connected device.
+        
+        @param getAs flag indicating to give it a new name
+        @type bool
+        """
+        selectedItems = self.deviceFileTreeWidget.selectedItems()
+        if selectedItems:
+            filename = selectedItems[0].text(0).strip()
+            if not filename.endswith("/"):
+                # it is really a file
+                if getAs:
+                    localFilename, ok = QInputDialog.getText(
+                        self,
+                        self.tr("Get File As"),
+                        self.tr("Enter a new name for the file"),
+                        QLineEdit.Normal,
+                        filename)
+                    if not ok or not filename:
+                        return
+                else:
+                    localFilename = filename
+                
+                if self.__isFileInList(localFilename,
+                                       self.localFileTreeWidget):
+                    # ask for overwrite permission
+                    action, resultFilename = confirmOverwrite(
+                        localFilename, self.tr("Copy File from Device"),
+                        self.tr("The given file exists already."),
+                        True, self)
+                    if action == "cancel":
+                        return
+                    elif action == "rename":
+                        localFilename = resultFilename
+                
+                if self.__deviceWithLocalAccess:
+                    shutil.copy2(
+                        os.path.join(self.deviceCwd.text(), filename),
+                        os.path.join(self.localCwd.text(), localFilename)
+                    )
+                    self.__listLocalFiles(self.localCwd.text())
+                else:
+                    deviceCwd = self.deviceCwd.text()
+                    if deviceCwd:
+                        filename = deviceCwd + "/" + filename
+                    self.__fileManager.get(
+                        filename,
+                        os.path.join(self.localCwd.text(), localFilename)
+                    )
+    
+    @pyqtSlot()
+    def on_getAsButton_clicked(self):
+        """
+        Private slot to copy the selected file from the connected device
+        with a different name.
+        """
+        self.on_getButton_clicked(getAs=True)
+    
+    @pyqtSlot(str, str)
+    def __handleGetDone(self, deviceFile, localFile):
+        """
+        Private slot handling a successful copy of a file from the device.
+        
+        @param deviceFile name of the file on the device
+        @type str
+        @param localFile name of the local file
+        @type str
+        """
+        self.__listLocalFiles(self.localCwd.text())
+    
+    @pyqtSlot()
+    def on_syncButton_clicked(self):
+        """
+        Private slot to synchronize the local directory to the device.
+        """
+        self.__fileManager.rsync(
+            self.localCwd.text(),
+            self.deviceCwd.text(),
+            mirror=True,
+            localDevice=self.__deviceWithLocalAccess,
+        )
+    
+    @pyqtSlot(str, str)
+    def __handleRsyncDone(self, localDir, deviceDir):
+        """
+        Private method to handle the completion of the rsync operation.
+        
+        @param localDir name of the local directory
+        @type str
+        @param deviceDir name of the device directory
+        @type str
+        """
+        self.__listLocalFiles(self.localCwd.text())
+        self.__newDeviceList()
+    
+    @pyqtSlot(str)
+    def __handleRsyncProgressMessage(self, message):
+        """
+        Private slot handling progress messages sent by the file manager.
+        
+        @param message message to be shown
+        @type str
+        """
+        if self.__progressInfoDialog is None:
+            from .MicroPythonProgressInfoDialog import (
+                MicroPythonProgressInfoDialog
+            )
+            self.__progressInfoDialog = MicroPythonProgressInfoDialog(self)
+            self.__progressInfoDialog.finished.connect(
+                self.__progressInfoDialogFinished)
+        self.__progressInfoDialog.show()
+        self.__progressInfoDialog.addMessage(message)
+    
+    @pyqtSlot()
+    def __progressInfoDialogFinished(self):
+        """
+        Private slot handling the closing of the progress info dialog.
+        """
+        self.__progressInfoDialog.deleteLater()
+        self.__progressInfoDialog = None
+    
+    @pyqtSlot()
+    def __newDeviceList(self):
+        """
+        Private slot to initiate a new long list of the device directory.
+        """
+        self.__fileManager.lls(
+            self.deviceCwd.text(),
+            showHidden=Preferences.getMicroPython("ShowHiddenDevice")
+        )
+    
+    ##################################################################
+    ## Context menu methods for the local files below
+    ##################################################################
+    
+    @pyqtSlot(QPoint)
+    def __showLocalContextMenu(self, pos):
+        """
+        Private slot to show the REPL context menu.
+        
+        @param pos position to show the menu at
+        @type QPoint
+        """
+        hasSelection = bool(len(self.localFileTreeWidget.selectedItems()))
+        if hasSelection:
+            name = self.localFileTreeWidget.selectedItems()[0].text(0)
+            isDir = name.endswith("/")
+            isFile = not isDir
+        else:
+            isDir = False
+            isFile = False
+        self.__localDelDirTreeAct.setEnabled(isDir)
+        self.__localDelFileAct.setEnabled(isFile)
+        
+        self.__localMenu.exec_(self.localFileTreeWidget.mapToGlobal(pos))
+    
+    @pyqtSlot()
+    def __changeLocalDirectory(self, localDevice=False):
+        """
+        Private slot to change the local directory.
+        
+        @param localDevice flag indicating device access via local file system
+        @type bool
+        """
+        if localDevice:
+            cwdWidget = self.deviceCwd
+        else:
+            cwdWidget = self.localCwd
+        
+        dirPath, ok = E5PathPickerDialog.getPath(
+            self,
+            self.tr("Change Directory"),
+            self.tr("Select Directory"),
+            E5PathPickerModes.DirectoryShowFilesMode,
+            path=cwdWidget.text(),
+            defaultDirectory=cwdWidget.text(),
+        )
+        if ok and dirPath:
+            if not os.path.isabs(dirPath):
+                dirPath = os.path.join(cwdWidget.text(), dirPath)
+            cwdWidget.setText(dirPath)
+            self.__listLocalFiles(dirPath, localDevice=localDevice)
+    
+    @pyqtSlot()
+    def __createLocalDirectory(self, localDevice=False):
+        """
+        Private slot to create a local directory.
+        
+        @param localDevice flag indicating device access via local file system
+        @type bool
+        """
+        if localDevice:
+            cwdWidget = self.deviceCwd
+        else:
+            cwdWidget = self.localCwd
+        
+        dirPath, ok = QInputDialog.getText(
+            self,
+            self.tr("Create Directory"),
+            self.tr("Enter directory name:"),
+            QLineEdit.Normal)
+        if ok and dirPath:
+            dirPath = os.path.join(cwdWidget.text(), dirPath)
+            try:
+                os.mkdir(dirPath)
+                self.__listLocalFiles(cwdWidget.text(),
+                                      localDevice=localDevice)
+            except (OSError, IOError) as exc:
+                E5MessageBox.critical(
+                    self,
+                    self.tr("Create Directory"),
+                    self.tr("""<p>The directory <b>{0}</b> could not be"""
+                            """ created.</p><p>Reason: {1}</p>""").format(
+                        dirPath, str(exc))
+                )
+    
+    @pyqtSlot()
+    def __deleteLocalDirectoryTree(self, localDevice=False):
+        """
+        Private slot to delete a local directory tree.
+        
+        @param localDevice flag indicating device access via local file system
+        @type bool
+        """
+        if localDevice:
+            cwdWidget = self.deviceCwd
+            fileTreeWidget = self.deviceFileTreeWidget
+        else:
+            cwdWidget = self.localCwd
+            fileTreeWidget = self.localFileTreeWidget
+        
+        if bool(len(fileTreeWidget.selectedItems())):
+            name = fileTreeWidget.selectedItems()[0].text(0)
+            dirname = os.path.join(cwdWidget.text(), name[:-1])
+            dlg = DeleteFilesConfirmationDialog(
+                self,
+                self.tr("Delete Directory Tree"),
+                self.tr(
+                    "Do you really want to delete this directory tree?"),
+                [dirname])
+            if dlg.exec_() == QDialog.Accepted:
+                try:
+                    shutil.rmtree(dirname)
+                    self.__listLocalFiles(cwdWidget.text(),
+                                          localDevice=localDevice)
+                except Exception as exc:
+                    E5MessageBox.critical(
+                        self,
+                        self.tr("Delete Directory Tree"),
+                        self.tr("""<p>The directory <b>{0}</b> could not be"""
+                                """ deleted.</p><p>Reason: {1}</p>""").format(
+                            dirname, str(exc))
+                    )
+    
+    @pyqtSlot()
+    def __deleteLocalFile(self, localDevice=False):
+        """
+        Private slot to delete a local file.
+        
+        @param localDevice flag indicating device access via local file system
+        @type bool
+        """
+        if localDevice:
+            cwdWidget = self.deviceCwd
+            fileTreeWidget = self.deviceFileTreeWidget
+        else:
+            cwdWidget = self.localCwd
+            fileTreeWidget = self.localFileTreeWidget
+        
+        if bool(len(fileTreeWidget.selectedItems())):
+            name = fileTreeWidget.selectedItems()[0].text(0)
+            filename = os.path.join(cwdWidget.text(), name)
+            dlg = DeleteFilesConfirmationDialog(
+                self,
+                self.tr("Delete File"),
+                self.tr(
+                    "Do you really want to delete this file?"),
+                [filename])
+            if dlg.exec_() == QDialog.Accepted:
+                try:
+                    os.remove(filename)
+                    self.__listLocalFiles(cwdWidget.text(),
+                                          localDevice=localDevice)
+                except (OSError, IOError) as exc:
+                    E5MessageBox.critical(
+                        self,
+                        self.tr("Delete File"),
+                        self.tr("""<p>The file <b>{0}</b> could not be"""
+                                """ deleted.</p><p>Reason: {1}</p>""").format(
+                            filename, str(exc))
+                    )
+    
+    @pyqtSlot(bool)
+    def __localHiddenChanged(self, checked):
+        """
+        Private slot handling a change of the local show hidden menu entry.
+        
+        @param checked new check state of the action
+        @type bool
+        """
+        Preferences.setMicroPython("ShowHiddenLocal", checked)
+        self.on_localReloadButton_clicked()
+    
+    ##################################################################
+    ## Context menu methods for the device files below
+    ##################################################################
+    
+    @pyqtSlot(QPoint)
+    def __showDeviceContextMenu(self, pos):
+        """
+        Private slot to show the REPL context menu.
+        
+        @param pos position to show the menu at
+        @type QPoint
+        """
+        hasSelection = bool(len(self.deviceFileTreeWidget.selectedItems()))
+        if hasSelection:
+            name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
+            isDir = name.endswith("/")
+            isFile = not isDir
+        else:
+            isDir = False
+            isFile = False
+        if not self.__repl.isMicrobit():
+            if not self.__deviceWithLocalAccess:
+                self.__devDelDirAct.setEnabled(isDir)
+            self.__devDelDirTreeAct.setEnabled(isDir)
+        self.__devDelFileAct.setEnabled(isFile)
+        
+        self.__deviceMenu.exec_(self.deviceFileTreeWidget.mapToGlobal(pos))
+    
+    @pyqtSlot()
+    def __changeDeviceDirectory(self):
+        """
+        Private slot to change the current directory of the device.
+        
+        Note: This triggers a re-population of the device list for the new
+        current directory.
+        """
+        if self.__deviceWithLocalAccess:
+            self.__changeLocalDirectory(True)
+        else:
+            dirPath, ok = QInputDialog.getText(
+                self,
+                self.tr("Change Directory"),
+                self.tr("Enter the directory path on the device:"),
+                QLineEdit.Normal,
+                self.deviceCwd.text())
+            if ok and dirPath:
+                if not dirPath.startswith("/"):
+                    dirPath = self.deviceCwd.text() + "/" + dirPath
+                self.__fileManager.cd(dirPath)
+    
+    @pyqtSlot()
+    def __createDeviceDirectory(self):
+        """
+        Private slot to create a directory on the device.
+        """
+        if self.__deviceWithLocalAccess:
+            self.__createLocalDirectory(True)
+        else:
+            dirPath, ok = QInputDialog.getText(
+                self,
+                self.tr("Create Directory"),
+                self.tr("Enter directory name:"),
+                QLineEdit.Normal)
+            if ok and dirPath:
+                self.__fileManager.mkdir(dirPath)
+    
+    @pyqtSlot()
+    def __deleteDeviceDirectory(self):
+        """
+        Private slot to delete an empty directory on the device.
+        """
+        if self.__deviceWithLocalAccess:
+            self.__deleteLocalDirectoryTree(True)
+        else:
+            if bool(len(self.deviceFileTreeWidget.selectedItems())):
+                name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
+                cwd = self.deviceCwd.text()
+                if cwd:
+                    if cwd != "/":
+                        dirname = cwd + "/" + name[:-1]
+                    else:
+                        dirname = "/" + name[:-1]
+                else:
+                    dirname = name[:-1]
+                dlg = DeleteFilesConfirmationDialog(
+                    self,
+                    self.tr("Delete Directory"),
+                    self.tr(
+                        "Do you really want to delete this directory?"),
+                    [dirname])
+                if dlg.exec_() == QDialog.Accepted:
+                    self.__fileManager.rmdir(dirname)
+    
+    @pyqtSlot()
+    def __deleteDeviceDirectoryTree(self):
+        """
+        Private slot to delete a directory and all its subdirectories
+        recursively.
+        """
+        if self.__deviceWithLocalAccess:
+            self.__deleteLocalDirectoryTree(True)
+        else:
+            if bool(len(self.deviceFileTreeWidget.selectedItems())):
+                name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
+                cwd = self.deviceCwd.text()
+                if cwd:
+                    if cwd != "/":
+                        dirname = cwd + "/" + name[:-1]
+                    else:
+                        dirname = "/" + name[:-1]
+                else:
+                    dirname = name[:-1]
+                dlg = DeleteFilesConfirmationDialog(
+                    self,
+                    self.tr("Delete Directory Tree"),
+                    self.tr(
+                        "Do you really want to delete this directory tree?"),
+                    [dirname])
+                if dlg.exec_() == QDialog.Accepted:
+                    self.__fileManager.rmdir(dirname, recursive=True)
+    
+    @pyqtSlot()
+    def __deleteDeviceFile(self):
+        """
+        Private slot to delete a file.
+        """
+        if self.__deviceWithLocalAccess:
+            self.__deleteLocalFile(True)
+        else:
+            if bool(len(self.deviceFileTreeWidget.selectedItems())):
+                name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
+                dirname = self.deviceCwd.text()
+                if dirname:
+                    if dirname != "/":
+                        filename = dirname + "/" + name
+                    else:
+                        filename = "/" + name
+                else:
+                    filename = name
+                dlg = DeleteFilesConfirmationDialog(
+                    self,
+                    self.tr("Delete File"),
+                    self.tr(
+                        "Do you really want to delete this file?"),
+                    [filename])
+                if dlg.exec_() == QDialog.Accepted:
+                    self.__fileManager.delete(filename)
+    
+    @pyqtSlot(bool)
+    def __deviceHiddenChanged(self, checked):
+        """
+        Private slot handling a change of the device show hidden menu entry.
+        
+        @param checked new check state of the action
+        @type bool
+        """
+        Preferences.setMicroPython("ShowHiddenDevice", checked)
+        self.on_deviceReloadButton_clicked()
+    
+    @pyqtSlot()
+    def __showFileSystemInfo(self):
+        """
+        Private slot to show some file system information.
+        """
+        self.__fileManager.fileSystemInfo()
+    
+    @pyqtSlot(tuple)
+    def __fsInfoResultReceived(self, fsinfo):
+        """
+        Private slot to show the file system information of the device.
+        
+        @param fsinfo tuple of tuples containing the file system name, the
+            total size, the used size and the free size
+        @type tuple of tuples of (str, int, int, int)
+        """
+        msg = self.tr("<h3>Filesystem Information</h3>")
+        for name, totalSize, usedSize, freeSize in fsinfo:
+            msg += self.tr(
+                "<h4>{0}</h4"
+                "<table>"
+                "<tr><td>Total Size: </td><td align='right'>{1}</td></tr>"
+                "<tr><td>Used Size: </td><td align='right'>{2}</td></tr>"
+                "<tr><td>Free Size: </td><td align='right'>{3}</td></tr>"
+                "</table>"
+            ).format(name,
+                     Globals.dataString(totalSize),
+                     Globals.dataString(usedSize),
+                     Globals.dataString(freeSize),
+                     )
+        E5MessageBox.information(
+            self,
+            self.tr("Filesystem Information"),
+            msg)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonFileManagerWidget.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,279 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MicroPythonFileManagerWidget</class>
+ <widget class="QWidget" name="MicroPythonFileManagerWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>675</width>
+    <height>338</height>
+   </rect>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="text">
+      <string>Local Files</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="2">
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QLabel" name="label_2">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Device Files</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="E5Led" name="deviceConnectedLed" native="true"/>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="0">
+    <widget class="QTreeWidget" name="localFileTreeWidget">
+     <property name="contextMenuPolicy">
+      <enum>Qt::CustomContextMenu</enum>
+     </property>
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <attribute name="headerShowSortIndicator" stdset="0">
+      <bool>true</bool>
+     </attribute>
+     <column>
+      <property name="text">
+       <string>Name</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Mode</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Size</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Time</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <spacer name="verticalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>26</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QToolButton" name="syncButton">
+       <property name="toolTip">
+        <string>Press to sync the local directory to the device directory</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="putButton">
+       <property name="toolTip">
+        <string>Press to copy the selected file to the device</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="putAsButton">
+       <property name="toolTip">
+        <string>Press to copy the selected file to the device with a new name</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="getButton">
+       <property name="toolTip">
+        <string>Press to copy the selected file from the device</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="getAsButton">
+       <property name="toolTip">
+        <string>Press to copy the selected file from the device with a new name</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>26</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="2">
+    <widget class="QTreeWidget" name="deviceFileTreeWidget">
+     <property name="contextMenuPolicy">
+      <enum>Qt::CustomContextMenu</enum>
+     </property>
+     <property name="alternatingRowColors">
+      <bool>true</bool>
+     </property>
+     <property name="rootIsDecorated">
+      <bool>false</bool>
+     </property>
+     <property name="itemsExpandable">
+      <bool>false</bool>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <attribute name="headerShowSortIndicator" stdset="0">
+      <bool>true</bool>
+     </attribute>
+     <column>
+      <property name="text">
+       <string>Name</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Mode</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Size</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Time</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="spacing">
+      <number>2</number>
+     </property>
+     <item>
+      <widget class="QLineEdit" name="localCwd">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="localUpButton">
+       <property name="toolTip">
+        <string>Press to move one directory level up</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="localReloadButton">
+       <property name="toolTip">
+        <string>Press to reload the list</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="2" column="2">
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <property name="spacing">
+      <number>2</number>
+     </property>
+     <item>
+      <widget class="QLineEdit" name="deviceCwd">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="deviceUpButton">
+       <property name="toolTip">
+        <string>Press to move one directory level up</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="deviceReloadButton">
+       <property name="toolTip">
+        <string>Press to reload the list</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5Led</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5Led.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>localFileTreeWidget</tabstop>
+  <tabstop>deviceFileTreeWidget</tabstop>
+  <tabstop>syncButton</tabstop>
+  <tabstop>putButton</tabstop>
+  <tabstop>putAsButton</tabstop>
+  <tabstop>getButton</tabstop>
+  <tabstop>getAsButton</tabstop>
+  <tabstop>localCwd</tabstop>
+  <tabstop>localUpButton</tabstop>
+  <tabstop>localReloadButton</tabstop>
+  <tabstop>deviceCwd</tabstop>
+  <tabstop>deviceUpButton</tabstop>
+  <tabstop>deviceReloadButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonFileSystemUtilities.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing some file system utility functions.
+"""
+
+from __future__ import unicode_literals
+
+import time
+import stat
+import os
+
+
+def mtime2string(mtime):
+    """
+    Function to convert a time value to a string representation.
+    
+    @param mtime time value
+    @type int
+    @return string representation of the given time
+    @rtype str
+    """
+    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mtime))
+
+
+def mode2string(mode):
+    """
+    Function to convert a mode value to a string representation.
+    
+    @param mode mode value
+    @type int
+    @return string representation of the given mode value
+    @rtype str
+    """
+    return stat.filemode(mode)
+
+
+def decoratedName(name, mode, isDir=False):
+    """
+    Function to decorate the given name according to the given mode.
+    
+    @param name file or directory name
+    @type str
+    @param mode mode value
+    @type int
+    @param isDir flag indicating that name is a directory
+    @type bool
+    @return decorated file or directory name
+    @rtype str
+    """
+    if stat.S_ISDIR(mode) or isDir:
+        # append a '/' for directories
+        return name + "/"
+    elif stat.S_ISLNK(mode):
+        # append a '@' for links
+        return name + "@"
+    else:
+        # no change
+        return name
+
+
+def isVisible(name, showHidden):
+    """
+    Function to check, if a filesystem entry is a hidden file or directory.
+    
+    @param name name to be checked
+    @type str
+    @param showHidden flag indicating to show hidden files as well
+    @type bool
+    @return flag indicating a visible filesystem entry
+    @rtype bool
+    """
+    return (
+        showHidden or
+        (not name.startswith(".") and not name.endswith("~"))
+    )
+
+
+def fstat(filename):
+    """
+    Function to get the stat() of file.
+    
+    @param filename name of the file
+    @type str
+    @return tuple containing the stat() result
+    @rtype tuple
+    """
+    try:
+        rstat = os.lstat(filename)
+    except Exception:
+        rstat = os.stat(filename)
+    return tuple(rstat)
+
+
+def listdirStat(dirname, showHidden=False):
+    """
+    Function to get a list of directory entries and associated stat() tuples.
+    
+    @param dirname name of the directory to list
+    @type str
+    @param showHidden flag indicating to show hidden files as well
+    @type bool
+    @return list of tuples containing the entry name and the associated
+        stat() tuple
+    @rtype list of tuple of (str, tuple)
+    """
+    try:
+        if dirname:
+            files = os.listdir(dirname)
+        else:
+            files = os.listdir()
+    except OSError:
+        return []
+    
+    if dirname in ('', '/'):
+        return [(f, fstat(f)) for f in files if isVisible(f, showHidden)]
+    
+    return [(f, fstat(os.path.join(dirname, f))) for f in files
+            if isVisible(f, showHidden)]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonGraphWidget.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,345 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the MicroPython graph widget.
+"""
+
+from __future__ import unicode_literals
+
+from collections import deque
+import bisect
+import os
+import time
+import csv
+
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
+from PyQt5.QtGui import QPainter
+from PyQt5.QtWidgets import (
+    QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QSizePolicy, QSpacerItem,
+    QLabel, QSpinBox
+)
+from PyQt5.QtChart import QChartView, QChart, QLineSeries, QValueAxis
+
+from E5Gui import E5MessageBox
+
+import UI.PixmapCache
+import Preferences
+
+
+class MicroPythonGraphWidget(QWidget):
+    """
+    Class implementing the MicroPython graph widget.
+    
+    @signal dataFlood emitted to indicate, that too much data is received
+    """
+    dataFlood = pyqtSignal()
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(MicroPythonGraphWidget, self).__init__(parent)
+        
+        self.__layout = QHBoxLayout()
+        self.__layout.setContentsMargins(2, 2, 2, 2)
+        self.setLayout(self.__layout)
+        
+        self.__chartView = QChartView(self)
+        self.__chartView.setSizePolicy(
+            QSizePolicy.Expanding, QSizePolicy.Expanding)
+        self.__layout.addWidget(self.__chartView)
+        
+        self.__verticalLayout = QVBoxLayout()
+        self.__verticalLayout.setContentsMargins(0, 0, 0, 0)
+        self.__layout.addLayout(self.__verticalLayout)
+        
+        self.__saveButton = QToolButton(self)
+        self.__saveButton.setIcon(UI.PixmapCache.getIcon("fileSave"))
+        self.__saveButton.setToolTip(self.tr("Press to save the raw data"))
+        self.__saveButton.clicked.connect(self.on_saveButton_clicked)
+        self.__verticalLayout.addWidget(self.__saveButton)
+        self.__verticalLayout.setAlignment(self.__saveButton, Qt.AlignHCenter)
+        
+        spacerItem = QSpacerItem(20, 20, QSizePolicy.Minimum,
+                                 QSizePolicy.Expanding)
+        self.__verticalLayout.addItem(spacerItem)
+        
+        label = QLabel(self.tr("max. X:"))
+        self.__verticalLayout.addWidget(label)
+        self.__verticalLayout.setAlignment(label, Qt.AlignHCenter)
+        
+        self.__maxX = 100
+        self.__maxXSpinBox = QSpinBox()
+        self.__maxXSpinBox.setMinimum(100)
+        self.__maxXSpinBox.setMaximum(1000)
+        self.__maxXSpinBox.setSingleStep(100)
+        self.__maxXSpinBox.setToolTip(self.tr(
+            "Enter the maximum number of data points to be plotted."))
+        self.__maxXSpinBox.setValue(self.__maxX)
+        self.__maxXSpinBox.setAlignment(Qt.AlignRight)
+        self.__verticalLayout.addWidget(self.__maxXSpinBox)
+        
+        # holds the data to be checked for plotable data
+        self.__inputBuffer = []
+        # holds the raw data
+        self.__rawData = []
+        self.__dirty = False
+        
+        self.__maxY = 1000
+        self.__flooded = False  # flag indicating a data flood
+        
+        self.__data = [deque([0] * self.__maxX)]
+        self.__series = [QLineSeries()]
+        
+        # Y-axis ranges
+        self.__yRanges = [1, 5, 10, 25, 50, 100, 250, 500, 1000]
+        
+        # setup the chart
+        self.__chart = QChart()
+        self.__chart.legend().hide()
+        self.__chart.addSeries(self.__series[0])
+        self.__axisX = QValueAxis()
+        self.__axisX.setRange(0, self.__maxX)
+        self.__axisX.setLabelFormat("time")
+        self.__axisY = QValueAxis()
+        self.__axisY.setRange(-self.__maxY, self.__maxY)
+        self.__axisY.setLabelFormat("%d")
+        self.__chart.setAxisX(self.__axisX, self.__series[0])
+        self.__chart.setAxisY(self.__axisY, self.__series[0])
+        self.__chartView.setChart(self.__chart)
+        self.__chartView.setRenderHint(QPainter.Antialiasing)
+        
+        self.__maxXSpinBox.valueChanged.connect(self.__handleMaxXChanged)
+    
+    @pyqtSlot(bytes)
+    def processData(self, data):
+        """
+        Public slot to process the raw data.
+        
+        It takes raw bytes, checks the data for a valid tuple of ints or
+        floats and adds the data to the graph. If the the length of the bytes
+        data is greater than 1024 then a dataFlood signal is emitted to ensure
+        eric can take action to remain responsive.
+        
+        @param data raw data received from the connected device via the main
+            device widget
+        @type bytes
+        """
+        # flooding guard
+        if self.__flooded:
+            return
+        
+        if len(data) > 1024:
+            self.__flooded = True
+            self.dataFlood.emit()
+            return
+        
+        # disable the inputs while processing data
+        self.__saveButton.setEnabled(False)
+        self.__maxXSpinBox.setEnabled(False)
+        
+        data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
+        self.__inputBuffer.append(data)
+        
+        # check if the data contains a Python tuple containing numbers (int
+        # or float) on a single line
+        inputBytes = b"".join(self.__inputBuffer)
+        lines = inputBytes.splitlines(True)
+        for line in lines:
+            if not line.endswith(b"\n"):
+                # incomplete line (last line); skip it
+                break
+            
+            line = line.strip()
+            if line.startswith(b"(") and line.endswith(b")"):
+                # it may be a tuple we are interested in
+                rawValues = [val.strip() for val in line[1:-1].split(b",")]
+                values = []
+                for raw in rawValues:
+                    try:
+                        values.append(int(raw))
+                        # ok, it is an integer
+                        continue
+                    except ValueError:
+                        # test for a float
+                        pass
+                    try:
+                        values.append(float(raw))
+                    except ValueError:
+                        # it is not an int or float, ignore it
+                        continue
+                if values:
+                    self.__addData(tuple(values))
+        
+        self.__inputBuffer = []
+        if lines[-1] and not lines[-1].endswith(b"\n"):
+            # Append any left over bytes for processing next time data is
+            # received.
+            self.__inputBuffer.append(lines[-1])
+        
+        # re-enable the inputs
+        self.__saveButton.setEnabled(True)
+        self.__maxXSpinBox.setEnabled(True)
+    
+    def __addData(self, values):
+        """
+        Private method to add a tuple of values to the graph.
+        
+        It ensures there are the required number of line series, adds the data
+        to the line series and updates the range of the chart so the chart
+        displays nicely.
+        
+        @param values tuple containing the data to be added
+        @type tuple of int or float
+        """
+        # store incoming data to be able to dump it as CSV upon request
+        self.__rawData.append(values)
+        self.__dirty = True
+        
+        # check number of incoming values and adjust line series accordingly
+        if len(values) != len(self.__series):
+            valuesLen = len(values)
+            seriesLen = len(self.__series)
+            if valuesLen > seriesLen:
+                # add a nwe line series
+                for _index in range(valuesLen - seriesLen):
+                    newSeries = QLineSeries()
+                    self.__chart.addSeries(newSeries)
+                    self.__chart.setAxisX(self.__axisX, newSeries)
+                    self.__chart.setAxisY(self.__axisY, newSeries)
+                    self.__series.append(newSeries)
+                    self.__data.append(deque([0] * self.__maxX))
+            else:
+                # remove obsolete line series
+                for oldSeries in self.__series[valuesLen:]:
+                    self.__chart.removeSeries(oldSeries)
+                self.__series = self.__series[:valuesLen]
+                self.__data = self.__data[:valuesLen]
+        
+        # add the new values to the display and compute the maximum range
+        maxRanges = []
+        for index, value in enumerate(values):
+            self.__data[index].appendleft(value)
+            maxRanges.append(max([max(self.__data[index]),
+                                  abs(min(self.__data[index]))]))
+            if len(self.__data[index]) > self.__maxX:
+                self.__data[index].pop()
+        
+        # re-scale the y-axis
+        maxYRange = max(maxRanges)
+        yRange = bisect.bisect_left(self.__yRanges, maxYRange)
+        if yRange < len(self.__yRanges):
+            self.__maxY = self.__yRanges[yRange]
+        elif maxYRange > self.__maxY:
+            self.__maxY += self.__maxY
+        elif maxYRange < self.__maxY / 2:
+            self.__maxY /= 2
+        self.__axisY.setRange(-self.__maxY, self.__maxY)
+        
+        # ensure that floats are used to label the y-axis if the range is small
+        if self.__maxY <= 5:
+            self.__axisY.setLabelFormat("%2.2f")
+        else:
+            self.__axisY.setLabelFormat("%d")
+        
+        # update the line series
+        for index, series in enumerate(self.__series):
+            series.clear()
+            xyValues = []
+            for x in range(self.__maxX):
+                value = self.__data[index][self.__maxX - 1 - x]
+                xyValues.append((x, value))
+            for xy in xyValues:
+                series.append(*xy)
+    
+    @pyqtSlot()
+    def on_saveButton_clicked(self):
+        """
+        Private slot to save the raw data to a CSV file.
+        """
+        self.saveData()
+    
+    def hasData(self):
+        """
+        Public method to check, if the chart contains some valid data.
+        
+        @return flag indicating valid data
+        @rtype bool
+        """
+        return len(self.__rawData) > 0
+    
+    def isDirty(self):
+        """
+        Public method to check, if the chart contains unsaved data.
+        
+        @return flag indicating unsaved data
+        @rtype bool
+        """
+        return self.hasData() and self.__dirty
+    
+    def saveData(self):
+        """
+        Public method to save the dialog's raw data.
+        
+        @return flag indicating success
+        @rtype bool
+        """
+        baseDir = (Preferences.getMultiProject("Workspace") or
+                   os.path.expanduser("~"))
+        dataDir = os.path.join(baseDir, "data_capture")
+        
+        if not os.path.exists(dataDir):
+            os.makedirs(dataDir)
+        
+        # save the raw data as a CSV file
+        fileName = "{0}.csv".format(time.strftime("%Y%m%d-%H%M%S"))
+        fullPath = os.path.join(dataDir, fileName)
+        try:
+            csvFile = open(fullPath, "w")
+            csvWriter = csv.writer(csvFile)
+            csvWriter.writerows(self.__rawData)
+            csvFile.close()
+            
+            self.__dirty = False
+            return True
+        except (IOError, OSError) as err:
+            E5MessageBox.critical(
+                self,
+                self.tr("Save Chart Data"),
+                self.tr(
+                    """<p>The chart data could not be saved into file"""
+                    """ <b>{0}</b>.</p><p>Reason: {1}</p>""").format(
+                    fullPath, str(err)))
+            return False
+    
+    @pyqtSlot(int)
+    def __handleMaxXChanged(self, value):
+        """
+        Private slot handling a change of the max. X spin box.
+        
+        @param value value of the spin box
+        @type int
+        """
+        delta = value - self.__maxX
+        if delta == 0:
+            # nothing to change
+            return
+        elif delta > 0:
+            # range must be increased
+            for deq in self.__data:
+                deq.extend([0] * delta)
+        else:
+            # range must be decreased
+            data = []
+            for deq in self.__data:
+                data.append(deque(list(deq)[:value]))
+            self.__data = data
+        
+        self.__maxX = value
+        self.__axisX.setRange(0, self.__maxX)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonProgressInfoDialog.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show progress messages.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import pyqtSlot
+from PyQt5.QtGui import QTextCursor
+from PyQt5.QtWidgets import QDialog
+
+from .Ui_MicroPythonProgressInfoDialog import Ui_MicroPythonProgressInfoDialog
+
+
+class MicroPythonProgressInfoDialog(QDialog, Ui_MicroPythonProgressInfoDialog):
+    """
+    Class implementing a dialog to show progress messages.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(MicroPythonProgressInfoDialog, self).__init__(parent)
+        self.setupUi(self)
+    
+    @pyqtSlot(str)
+    def addMessage(self, message):
+        """
+        Public slot to add a message to the progress display.
+        
+        @param message progress information to be shown
+        @type str
+        """
+        tc = self.progressEdit.textCursor()
+        tc.movePosition(QTextCursor.End)
+        self.progressEdit.setTextCursor(tc)
+        self.progressEdit.appendHtml(message)
+        self.progressEdit.ensureCursorVisible()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonProgressInfoDialog.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MicroPythonProgressInfoDialog</class>
+ <widget class="QDialog" name="MicroPythonProgressInfoDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>500</width>
+    <height>400</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Progress Information</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QPlainTextEdit" name="progressEdit">
+     <property name="tabChangesFocus">
+      <bool>true</bool>
+     </property>
+     <property name="lineWrapMode">
+      <enum>QPlainTextEdit::NoWrap</enum>
+     </property>
+     <property name="readOnly">
+      <bool>true</bool>
+     </property>
+     <property name="textInteractionFlags">
+      <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>MicroPythonProgressInfoDialog</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>MicroPythonProgressInfoDialog</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/eric6/MicroPython/MicroPythonSerialPort.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a QSerialPort with additional functionality for
+MicroPython devices.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import QIODevice, QTime, QCoreApplication, QEventLoop
+from PyQt5.QtSerialPort import QSerialPort
+
+
+class MicroPythonSerialPort(QSerialPort):
+    """
+    Class implementing a QSerialPort with additional functionality for
+    MicroPython devices.
+    """
+    def __init__(self, timeout=10000, parent=None):
+        """
+        Constructor
+        
+        @param timeout timout in milliseconds to be set
+        @type int
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(MicroPythonSerialPort, self).__init__(parent)
+        
+        self.__connected = False
+        self.__timeout = timeout      # 10s default timeout
+        self.__timedOut = False
+    
+    def setTimeout(self, timeout):
+        """
+        Public method to set the timeout for device operations.
+        
+        @param timeout timout in milliseconds to be set
+        @type int
+        """
+        self.__timeout = timeout
+    
+    def openSerialLink(self, port):
+        """
+        Public method to open a serial link to a given serial port.
+        
+        @param port port name to connect to
+        @type str
+        @return flag indicating success
+        @rtype bool
+        """
+        self.setPortName(port)
+        if self.open(QIODevice.ReadWrite):
+            self.setDataTerminalReady(True)
+            # 115.200 baud, 8N1
+            self.setBaudRate(115200)
+            self.setDataBits(QSerialPort.Data8)
+            self.setParity(QSerialPort.NoParity)
+            self.setStopBits(QSerialPort.OneStop)
+            
+            self.__connected = True
+            return True
+        else:
+            return False
+    
+    def closeSerialLink(self):
+        """
+        Public method to close the open serial connection.
+        """
+        if self.__connected:
+            self.close()
+            
+            self.__connected = False
+    
+    def isConnected(self):
+        """
+        Public method to get the connection state.
+        
+        @return flag indicating the connection state
+        @rtype bool
+        """
+        return self.__connected
+    
+    def hasTimedOut(self):
+        """
+        Public method to check, if the last 'readUntil' has timed out.
+        
+        @return flag indicating a timeout
+        @@rtype bool
+        """
+        return self.__timedOut
+    
+    def readUntil(self, expected=b"\n", size=None):
+        r"""
+        Public method to read data until an expected sequence is found
+        (default: \n) or a specific size is exceeded.
+        
+        @param expected expected bytes sequence
+        @type bytes
+        @param size maximum data to be read
+        @type int
+        @return bytes read from the device including the expected sequence
+        @rtype bytes
+        """
+        data = bytearray()
+        self.__timedOut = False
+        
+        t = QTime()
+        t.start()
+        while True:
+            QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
+            c = bytes(self.read(1))
+            if c:
+                data += c
+                if data.endswith(expected):
+                    break
+                if size is not None and len(data) >= size:
+                    break
+            if t.elapsed() > self.__timeout:
+                self.__timedOut = True
+                break
+        
+        return bytes(data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonWidget.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,1359 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the MicroPython REPL widget.
+"""
+
+from __future__ import unicode_literals
+
+import re
+import time
+import os
+
+from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent
+from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush
+from PyQt5.QtWidgets import (
+    QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy,
+    QTextEdit, QToolButton
+)
+
+from E5Gui.E5ZoomWidget import E5ZoomWidget
+from E5Gui import E5MessageBox, E5FileDialog
+from E5Gui.E5Application import e5App
+from E5Gui.E5ProcessDialog import E5ProcessDialog
+
+from .Ui_MicroPythonWidget import Ui_MicroPythonWidget
+
+from . import MicroPythonDevices
+try:
+    from .MicroPythonGraphWidget import MicroPythonGraphWidget
+    HAS_QTCHART = True
+except ImportError:
+    HAS_QTCHART = False
+from .MicroPythonFileManagerWidget import MicroPythonFileManagerWidget
+try:
+    from .MicroPythonCommandsInterface import MicroPythonCommandsInterface
+    HAS_QTSERIALPORT = True
+except ImportError:
+    HAS_QTSERIALPORT = False
+
+import Globals
+import UI.PixmapCache
+import Preferences
+import Utilities
+
+# ANSI Colors (see https://en.wikipedia.org/wiki/ANSI_escape_code)
+AnsiColorSchemes = {
+    "Windows 7": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(128, 0, 0)),
+        2: QBrush(QColor(0, 128, 0)),
+        3: QBrush(QColor(128, 128, 0)),
+        4: QBrush(QColor(0, 0, 128)),
+        5: QBrush(QColor(128, 0, 128)),
+        6: QBrush(QColor(0, 128, 128)),
+        7: QBrush(QColor(192, 192, 192)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Windows 10": {
+        0: QBrush(QColor(12, 12, 12)),
+        1: QBrush(QColor(197, 15, 31)),
+        2: QBrush(QColor(19, 161, 14)),
+        3: QBrush(QColor(193, 156, 0)),
+        4: QBrush(QColor(0, 55, 218)),
+        5: QBrush(QColor(136, 23, 152)),
+        6: QBrush(QColor(58, 150, 221)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(118, 118, 118)),
+        11: QBrush(QColor(231, 72, 86)),
+        12: QBrush(QColor(22, 198, 12)),
+        13: QBrush(QColor(249, 241, 165)),
+        14: QBrush(QColor(59, 12, 255)),
+        15: QBrush(QColor(180, 0, 158)),
+        16: QBrush(QColor(97, 214, 214)),
+        17: QBrush(QColor(242, 242, 242)),
+    },
+    "PuTTY": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(187, 0, 0)),
+        2: QBrush(QColor(0, 187, 0)),
+        3: QBrush(QColor(187, 187, 0)),
+        4: QBrush(QColor(0, 0, 187)),
+        5: QBrush(QColor(187, 0, 187)),
+        6: QBrush(QColor(0, 187, 187)),
+        7: QBrush(QColor(187, 187, 187)),
+        10: QBrush(QColor(85, 85, 85)),
+        11: QBrush(QColor(255, 85, 85)),
+        12: QBrush(QColor(85, 255, 85)),
+        13: QBrush(QColor(255, 255, 85)),
+        14: QBrush(QColor(85, 85, 255)),
+        15: QBrush(QColor(255, 85, 255)),
+        16: QBrush(QColor(85, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "xterm": {
+        0: QBrush(QColor(0, 0, 0)),
+        1: QBrush(QColor(205, 0, 0)),
+        2: QBrush(QColor(0, 205, 0)),
+        3: QBrush(QColor(205, 205, 0)),
+        4: QBrush(QColor(0, 0, 238)),
+        5: QBrush(QColor(205, 0, 205)),
+        6: QBrush(QColor(0, 205, 205)),
+        7: QBrush(QColor(229, 229, 229)),
+        10: QBrush(QColor(127, 127, 127)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+    "Ubuntu": {
+        0: QBrush(QColor(1, 1, 1)),
+        1: QBrush(QColor(222, 56, 43)),
+        2: QBrush(QColor(57, 181, 74)),
+        3: QBrush(QColor(255, 199, 6)),
+        4: QBrush(QColor(0, 11, 184)),
+        5: QBrush(QColor(118, 38, 113)),
+        6: QBrush(QColor(44, 181, 233)),
+        7: QBrush(QColor(204, 204, 204)),
+        10: QBrush(QColor(128, 128, 128)),
+        11: QBrush(QColor(255, 0, 0)),
+        12: QBrush(QColor(0, 255, 0)),
+        13: QBrush(QColor(255, 255, 0)),
+        14: QBrush(QColor(0, 0, 255)),
+        15: QBrush(QColor(255, 0, 255)),
+        16: QBrush(QColor(0, 255, 255)),
+        17: QBrush(QColor(255, 255, 255)),
+    },
+}
+
+
+class MicroPythonWidget(QWidget, Ui_MicroPythonWidget):
+    """
+    Class implementing the MicroPython REPL widget.
+    
+    @signal dataReceived(data) emitted to send data received via the serial
+        connection for further processing
+    """
+    ZoomMin = -10
+    ZoomMax = 20
+    
+    DeviceTypeRole = Qt.UserRole
+    DevicePortRole = Qt.UserRole + 1
+    
+    dataReceived = pyqtSignal(bytes)
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(MicroPythonWidget, self).__init__(parent)
+        self.setupUi(self)
+        
+        self.__ui = parent
+        
+        self.__superMenu = QMenu(self)
+        self.__superMenu.aboutToShow.connect(self.__aboutToShowSuperMenu)
+        
+        self.menuButton.setObjectName(
+            "micropython_supermenu_button")
+        self.menuButton.setIcon(UI.PixmapCache.getIcon("superMenu"))
+        self.menuButton.setToolTip(self.tr("pip Menu"))
+        self.menuButton.setPopupMode(QToolButton.InstantPopup)
+        self.menuButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+        self.menuButton.setFocusPolicy(Qt.NoFocus)
+        self.menuButton.setAutoRaise(True)
+        self.menuButton.setShowMenuInside(True)
+        self.menuButton.setMenu(self.__superMenu)
+        
+        self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon(
+            "", False))
+        
+        self.openButton.setIcon(UI.PixmapCache.getIcon("open"))
+        self.saveButton.setIcon(UI.PixmapCache.getIcon("fileSaveAs"))
+        
+        self.checkButton.setIcon(UI.PixmapCache.getIcon("question"))
+        self.runButton.setIcon(UI.PixmapCache.getIcon("start"))
+        self.replButton.setIcon(UI.PixmapCache.getIcon("terminal"))
+        self.filesButton.setIcon(UI.PixmapCache.getIcon("filemanager"))
+        self.chartButton.setIcon(UI.PixmapCache.getIcon("chart"))
+        self.connectButton.setIcon(UI.PixmapCache.getIcon("linkConnect"))
+        
+        self.__zoomLayout = QHBoxLayout()
+        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
+                                 QSizePolicy.Minimum)
+        self.__zoomLayout.addSpacerItem(spacerItem)
+        
+        self.__zoom0 = self.replEdit.fontPointSize()
+        self.__zoomWidget = E5ZoomWidget(
+            UI.PixmapCache.getPixmap("zoomOut"),
+            UI.PixmapCache.getPixmap("zoomIn"),
+            UI.PixmapCache.getPixmap("zoomReset"), self)
+        self.__zoomLayout.addWidget(self.__zoomWidget)
+        self.layout().insertLayout(
+            self.layout().count() - 1,
+            self.__zoomLayout)
+        self.__zoomWidget.setMinimum(self.ZoomMin)
+        self.__zoomWidget.setMaximum(self.ZoomMax)
+        self.__zoomWidget.valueChanged.connect(self.__doZoom)
+        self.__currentZoom = 0
+        
+        self.__fileManagerWidget = None
+        
+        self.__interface = MicroPythonCommandsInterface(self)
+        self.__device = None
+        self.__connected = False
+        self.__setConnected(False)
+        
+        if not HAS_QTSERIALPORT:
+            self.replEdit.setHtml(self.tr(
+                "<h3>The QtSerialPort package is not available.<br/>"
+                "MicroPython support is deactivated.</h3>"))
+            self.setEnabled(False)
+            return
+        
+        self.__vt100Re = re.compile(
+            r'(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])')
+        
+        self.__populateDeviceTypeComboBox()
+        
+        self.replEdit.installEventFilter(self)
+        
+        self.replEdit.customContextMenuRequested.connect(
+            self.__showContextMenu)
+        self.__ui.preferencesChanged.connect(self.__handlePreferencesChanged)
+        self.__ui.preferencesChanged.connect(
+            self.__interface.handlePreferencesChanged)
+        
+        self.__handlePreferencesChanged()
+        
+        charFormat = self.replEdit.currentCharFormat()
+        self.DefaultForeground = charFormat.foreground()
+        self.DefaultBackground = charFormat.background()
+    
+    def __populateDeviceTypeComboBox(self):
+        """
+        Private method to populate the device type selector.
+        """
+        currentDevice = self.deviceTypeComboBox.currentText()
+        
+        self.deviceTypeComboBox.clear()
+        self.deviceInfoLabel.clear()
+        
+        self.deviceTypeComboBox.addItem("", "")
+        devices = MicroPythonDevices.getFoundDevices()
+        if devices:
+            self.deviceInfoLabel.setText(
+                self.tr("%n supported device(s) detected.", n=len(devices)))
+            
+            index = 0
+            for device in sorted(devices):
+                index += 1
+                self.deviceTypeComboBox.addItem(
+                    self.tr("{0} at {1}".format(device[1], device[2])))
+                self.deviceTypeComboBox.setItemData(
+                    index, device[0], self.DeviceTypeRole)
+                self.deviceTypeComboBox.setItemData(
+                    index, device[2], self.DevicePortRole)
+                
+        else:
+            self.deviceInfoLabel.setText(
+                self.tr("No supported devices detected."))
+        
+        index = self.deviceTypeComboBox.findText(currentDevice,
+                                                 Qt.MatchExactly)
+        if index == -1:
+            # entry is no longer present
+            index = 0
+            if self.__connected:
+                # we are still connected, so disconnect
+                self.on_connectButton_clicked()
+        
+        self.on_deviceTypeComboBox_activated(index)
+        self.deviceTypeComboBox.setCurrentIndex(index)
+    
+    def __handlePreferencesChanged(self):
+        """
+        Private slot to handle a change in preferences.
+        """
+        self.__colorScheme = Preferences.getMicroPython("ColorScheme")
+        
+        self.__font = Preferences.getEditorOtherFonts("MonospacedFont")
+        self.replEdit.setFontFamily(self.__font.family())
+        self.replEdit.setFontPointSize(self.__font.pointSize())
+        
+        if Preferences.getMicroPython("ReplLineWrap"):
+            self.replEdit.setLineWrapMode(QTextEdit.WidgetWidth)
+        else:
+            self.replEdit.setLineWrapMode(QTextEdit.NoWrap)
+    
+    def commandsInterface(self):
+        """
+        Public method to get a reference to the commands interface object.
+        
+        @return reference to the commands interface object
+        @rtype MicroPythonCommandsInterface
+        """
+        return self.__interface
+    
+    def isMicrobit(self):
+        """
+        Public method to check, if the connected/selected device is a
+        BBC micro:bit.
+        
+        @return flag indicating a micro:bit device
+        rtype bool
+        """
+        if self.__device and "micro:bit" in self.__device.deviceName():
+            return True
+        
+        return False
+    
+    @pyqtSlot(int)
+    def on_deviceTypeComboBox_activated(self, index):
+        """
+        Private slot handling the selection of a device type.
+        
+        @param index index of the selected device
+        @type int
+        """
+        deviceType = self.deviceTypeComboBox.itemData(
+            index, self.DeviceTypeRole)
+        self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon(
+            deviceType, False))
+        
+        self.__device = MicroPythonDevices.getDevice(deviceType, self)
+        self.__device.setButtons()
+        
+        self.connectButton.setEnabled(bool(deviceType))
+    
+    @pyqtSlot()
+    def on_checkButton_clicked(self):
+        """
+        Private slot to check for connected devices.
+        """
+        self.__populateDeviceTypeComboBox()
+    
+    def setActionButtons(self, **kwargs):
+        """
+        Public method to set the enabled state of the various action buttons.
+        
+        @keyparam kwargs keyword arguments containg the enabled states (keys
+            are 'run', 'repl', 'files', 'chart', 'open', 'save'
+        @type dict
+        """
+        if "open" in kwargs:
+            self.openButton.setEnabled(kwargs["open"])
+        if "save" in kwargs:
+            self.saveButton.setEnabled(kwargs["save"])
+        if "run" in kwargs:
+            self.runButton.setEnabled(kwargs["run"])
+        if "repl" in kwargs:
+            self.replButton.setEnabled(kwargs["repl"])
+        if "files" in kwargs:
+            self.filesButton.setEnabled(kwargs["files"])
+        if "chart" in kwargs:
+            self.chartButton.setEnabled(kwargs["chart"])
+    
+    @pyqtSlot(QPoint)
+    def __showContextMenu(self, pos):
+        """
+        Private slot to show the REPL context menu.
+        
+        @param pos position to show the menu at
+        @type QPoint
+        """
+        if Globals.isMacPlatform():
+            copyKeys = QKeySequence(Qt.CTRL + Qt.Key_C)
+            pasteKeys = QKeySequence(Qt.CTRL + Qt.Key_V)
+        else:
+            copyKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C)
+            pasteKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_V)
+        menu = QMenu(self)
+        menu.addAction(self.tr("Clear"), self.__clear)
+        menu.addSeparator()
+        menu.addAction(self.tr("Copy"), self.replEdit.copy, copyKeys)
+        menu.addAction(self.tr("Paste"), self.__paste, pasteKeys)
+        menu.addSeparator()
+        menu.exec_(self.replEdit.mapToGlobal(pos))
+    
+    def __setConnected(self, connected):
+        """
+        Private method to set the connection status LED.
+        
+        @param connected connection state
+        @type bool
+        """
+        self.__connected = connected
+        
+        self.deviceConnectedLed.setOn(connected)
+        if self.__fileManagerWidget:
+            self.__fileManagerWidget.deviceConnectedLed.setOn(connected)
+        
+        self.deviceTypeComboBox.setEnabled(not connected)
+        
+        if connected:
+            self.connectButton.setIcon(
+                UI.PixmapCache.getIcon("linkDisconnect"))
+            self.connectButton.setToolTip(self.tr(
+                "Press to disconnect the current device"))
+        else:
+            self.connectButton.setIcon(
+                UI.PixmapCache.getIcon("linkConnect"))
+            self.connectButton.setToolTip(self.tr(
+                "Press to connect the selected device"))
+    
+    def isConnected(self):
+        """
+        Public method to get the connection state.
+        
+        @return connection state
+        @rtype bool
+        """
+        return self.__connected
+    
+    def __showNoDeviceMessage(self):
+        """
+        Private method to show a message dialog indicating a missing device.
+        """
+        E5MessageBox.critical(
+            self,
+            self.tr("No device attached"),
+            self.tr("""Please ensure the device is plugged into your"""
+                    """ computer and selected.\n\nIt must have a version"""
+                    """ of MicroPython (or CircuitPython) flashed onto"""
+                    """ it before anything will work.\n\nFinally press"""
+                    """ the device's reset button and wait a few seconds"""
+                    """ before trying again."""))
+    
+    @pyqtSlot(bool)
+    def on_replButton_clicked(self, checked):
+        """
+        Private slot to connect to enable or disable the REPL widget.
+       
+        If the selected device is not connected yet, this will be done now.
+        
+        @param checked state of the button
+        @type bool
+        """
+        if not self.__device:
+            self.__showNoDeviceMessage()
+            return
+        
+        if checked:
+            ok, reason = self.__device.canStartRepl()
+            if not ok:
+                E5MessageBox.warning(
+                    self,
+                    self.tr("Start REPL"),
+                    self.tr("""<p>The REPL cannot be started.</p><p>Reason:"""
+                            """ {0}</p>""").format(reason))
+                return
+            
+            self.replEdit.clear()
+            self.__interface.dataReceived.connect(self.__processData)
+            
+            if not self.__interface.isConnected():
+                self.__connectToDevice()
+                if self.__device.forceInterrupt():
+                    # send a Ctrl-B (exit raw mode)
+                    self.__interface.write(b'\x02')
+                    # send Ctrl-C (keyboard interrupt)
+                    self.__interface.write(b'\x03')
+            
+            self.__device.setRepl(True)
+            self.replEdit.setFocus(Qt.OtherFocusReason)
+        else:
+            self.__interface.dataReceived.disconnect(self.__processData)
+            if (not self.chartButton.isChecked() and
+                    not self.filesButton.isChecked()):
+                self.__disconnectFromDevice()
+            self.__device.setRepl(False)
+        self.replButton.setChecked(checked)
+    
+    @pyqtSlot()
+    def on_connectButton_clicked(self):
+        """
+        Private slot to connect to the selected device or disconnect from the
+        currently connected device.
+        """
+        if self.__connected:
+            self.__disconnectFromDevice()
+            
+            if self.replButton.isChecked():
+                self.on_replButton_clicked(False)
+            if self.filesButton.isChecked():
+                self.on_filesButton_clicked(False)
+            if self.chartButton.isChecked():
+                self.on_chartButton_clicked(False)
+        else:
+            self.__connectToDevice()
+    
+    @pyqtSlot()
+    def __clear(self):
+        """
+        Private slot to clear the REPL pane.
+        """
+        self.replEdit.clear()
+        self.__interface.isConnected() and self.__interface.write(b"\r")
+    
+    @pyqtSlot()
+    def __paste(self):
+        """
+        Private slot to perform a paste operation.
+        """
+        clipboard = QApplication.clipboard()
+        if clipboard:
+            pasteText = clipboard.text()
+            if pasteText:
+                pasteText = pasteText.replace('\n\r', '\r')
+                pasteText = pasteText.replace('\n', '\r')
+                self.__interface.isConnected() and self.__interface.write(
+                    pasteText.encode("utf-8"))
+    
+    def eventFilter(self, obj, evt):
+        """
+        Public method to process events for the REPL pane.
+        
+        @param obj reference to the object the event was meant for
+        @type QObject
+        @param evt reference to the event object
+        @type QEvent
+        @return flag to indicate that the event was handled
+        @rtype bool
+        """
+        if obj is self.replEdit and evt.type() == QEvent.KeyPress:
+            # handle the key press event on behalve of the REPL pane
+            key = evt.key()
+            msg = bytes(evt.text(), 'utf8')
+            if key == Qt.Key_Backspace:
+                msg = b'\b'
+            elif key == Qt.Key_Delete:
+                msg = b'\x1B[\x33\x7E'
+            elif key == Qt.Key_Up:
+                msg = b'\x1B[A'
+            elif key == Qt.Key_Down:
+                msg = b'\x1B[B'
+            elif key == Qt.Key_Right:
+                msg = b'\x1B[C'
+            elif key == Qt.Key_Left:
+                msg = b'\x1B[D'
+            elif key == Qt.Key_Home:
+                msg = b'\x1B[H'
+            elif key == Qt.Key_End:
+                msg = b'\x1B[F'
+            elif ((Globals.isMacPlatform() and
+                   evt.modifiers() == Qt.MetaModifier) or
+                  (not Globals.isMacPlatform() and
+                   evt.modifiers() == Qt.ControlModifier)):
+                if Qt.Key_A <= key <= Qt.Key_Z:
+                    # devices treat an input of \x01 as Ctrl+A, etc.
+                    msg = bytes([1 + key - Qt.Key_A])
+            elif (evt.modifiers() == Qt.ControlModifier | Qt.ShiftModifier or
+                  (Globals.isMacPlatform() and
+                   evt.modifiers() == Qt.ControlModifier)):
+                if key == Qt.Key_C:
+                    self.replEdit.copy()
+                    msg = b''
+                elif key == Qt.Key_V:
+                    self.__paste()
+                    msg = b''
+            elif key in (Qt.Key_Return, Qt.Key_Enter):
+                tc = self.replEdit.textCursor()
+                tc.movePosition(QTextCursor.EndOfLine)
+                self.replEdit.setTextCursor(tc)
+            self.__interface.isConnected() and self.__interface.write(msg)
+            return True
+        
+        else:
+            # standard event processing
+            return super(MicroPythonWidget, self).eventFilter(obj, evt)
+    
+    def __processData(self, data):
+        """
+        Private slot to process bytes received from the device.
+        
+        @param data bytes received from the device
+        @type bytes
+        """
+        tc = self.replEdit.textCursor()
+        # the text cursor must be on the last line
+        while tc.movePosition(QTextCursor.Down):
+            pass
+        
+        # set the font
+        charFormat = tc.charFormat()
+        charFormat.setFontFamily(self.__font.family())
+        charFormat.setFontPointSize(self.__font.pointSize())
+        tc.setCharFormat(charFormat)
+        
+        index = 0
+        while index < len(data):
+            if data[index] == 8:        # \b
+                tc.movePosition(QTextCursor.Left)
+                self.replEdit.setTextCursor(tc)
+            elif data[index] == 13:     # \r
+                pass
+            elif (len(data) > index + 1 and
+                  data[index] == 27 and
+                  data[index + 1] == 91):
+                # VT100 cursor command detected: <Esc>[
+                index += 2      # move index to after the [
+                match = self.__vt100Re.search(data[index:].decode("utf-8"))
+                if match:
+                    # move to last position in control sequence
+                    # ++ will be done at end of loop
+                    index += match.end() - 1
+                    
+                    action = match.group("action")
+                    if action in "ABCD":
+                        if match.group("count") == "":
+                            count = 1
+                        else:
+                            count = int(match.group("count"))
+                        
+                        if action == "A":       # up
+                            tc.movePosition(QTextCursor.Up, n=count)
+                            self.replEdit.setTextCursor(tc)
+                        elif action == "B":     # down
+                            tc.movePosition(QTextCursor.Down, n=count)
+                            self.replEdit.setTextCursor(tc)
+                        elif action == "C":     # right
+                            tc.movePosition(QTextCursor.Right, n=count)
+                            self.replEdit.setTextCursor(tc)
+                        elif action == "D":     # left
+                            tc.movePosition(QTextCursor.Left, n=count)
+                            self.replEdit.setTextCursor(tc)
+                    elif action == "K":     # delete things
+                        if match.group("count") in ("", "0"):
+                            # delete to end of line
+                            tc.movePosition(QTextCursor.EndOfLine,
+                                            mode=QTextCursor.KeepAnchor)
+                            tc.removeSelectedText()
+                            self.replEdit.setTextCursor(tc)
+                        elif match.group("count") == "1":
+                            # delete to beinning of line
+                            tc.movePosition(QTextCursor.StartOfLine,
+                                            mode=QTextCursor.KeepAnchor)
+                            tc.removeSelectedText()
+                            self.replEdit.setTextCursor(tc)
+                        elif match.group("count") == "2":
+                            # delete whole line
+                            tc.movePosition(QTextCursor.EndOfLine)
+                            tc.movePosition(QTextCursor.StartOfLine,
+                                            mode=QTextCursor.KeepAnchor)
+                            tc.removeSelectedText()
+                            self.replEdit.setTextCursor(tc)
+                    elif action == "m":
+                        self.__setCharFormat(match.group(0)[:-1].split(";"),
+                                             tc)
+            else:
+                tc.deleteChar()
+                self.replEdit.setTextCursor(tc)
+                self.replEdit.insertPlainText(chr(data[index]))
+            
+            index += 1
+        
+        self.replEdit.ensureCursorVisible()
+    
+    def __setCharFormat(self, formatCodes, textCursor):
+        """
+        Private method setting the current text format of the REPL pane based
+        on the passed ANSI codes.
+        
+        Following codes are used:
+        <ul>
+        <li>0: Reset</li>
+        <li>1: Bold font (weight 75)</li>
+        <li>2: Light font (weight 25)</li>
+        <li>3: Italic font</li>
+        <li>4: Underlined font</li>
+        <li>9: Strikeout font</li>
+        <li>21: Bold off (weight 50)</li>
+        <li>22: Light off (weight 50)</li>
+        <li>23: Italic off</li>
+        <li>24: Underline off</li>
+        <li>29: Strikeout off</li>
+        <li>30: foreground Black</li>
+        <li>31: foreground Dark Red</li>
+        <li>32: foreground Dark Green</li>
+        <li>33: foreground Dark Yellow</li>
+        <li>34: foreground Dark Blue</li>
+        <li>35: foreground Dark Magenta</li>
+        <li>36: foreground Dark Cyan</li>
+        <li>37: foreground Light Gray</li>
+        <li>39: reset foreground to default</li>
+        <li>40: background Black</li>
+        <li>41: background Dark Red</li>
+        <li>42: background Dark Green</li>
+        <li>43: background Dark Yellow</li>
+        <li>44: background Dark Blue</li>
+        <li>45: background Dark Magenta</li>
+        <li>46: background Dark Cyan</li>
+        <li>47: background Light Gray</li>
+        <li>49: reset background to default</li>
+        <li>53: Overlined font</li>
+        <li>55: Overline off</li>
+        <li>90: bright foreground Dark Gray</li>
+        <li>91: bright foreground Red</li>
+        <li>92: bright foreground Green</li>
+        <li>93: bright foreground Yellow</li>
+        <li>94: bright foreground Blue</li>
+        <li>95: bright foreground Magenta</li>
+        <li>96: bright foreground Cyan</li>
+        <li>97: bright foreground White</li>
+        <li>100: bright background Dark Gray</li>
+        <li>101: bright background Red</li>
+        <li>102: bright background Green</li>
+        <li>103: bright background Yellow</li>
+        <li>104: bright background Blue</li>
+        <li>105: bright background Magenta</li>
+        <li>106: bright background Cyan</li>
+        <li>107: bright background White</li>
+        </ul>
+        
+        @param formatCodes list of format codes
+        @type list of str
+        @param textCursor reference to the text cursor
+        @type QTextCursor
+        """
+        if not formatCodes:
+            # empty format codes list is treated as a reset
+            formatCodes = ["0"]
+        
+        charFormat = textCursor.charFormat()
+        for formatCode in formatCodes:
+            try:
+                formatCode = int(formatCode)
+            except ValueError:
+                # ignore non digit values
+                continue
+            
+            if formatCode == 0:
+                charFormat.setFontWeight(50)
+                charFormat.setFontItalic(False)
+                charFormat.setFontUnderline(False)
+                charFormat.setFontStrikeOut(False)
+                charFormat.setFontOverline(False)
+                charFormat.setForeground(self.DefaultForeground)
+                charFormat.setBackground(self.DefaultBackground)
+            elif formatCode == 1:
+                charFormat.setFontWeight(75)
+            elif formatCode == 2:
+                charFormat.setFontWeight(25)
+            elif formatCode == 3:
+                charFormat.setFontItalic(True)
+            elif formatCode == 4:
+                charFormat.setFontUnderline(True)
+            elif formatCode == 9:
+                charFormat.setFontStrikeOut(True)
+            elif formatCode in (21, 22):
+                charFormat.setFontWeight(50)
+            elif formatCode == 23:
+                charFormat.setFontItalic(False)
+            elif formatCode == 24:
+                charFormat.setFontUnderline(False)
+            elif formatCode == 29:
+                charFormat.setFontStrikeOut(False)
+            elif formatCode == 53:
+                charFormat.setFontOverline(True)
+            elif formatCode == 55:
+                charFormat.setFontOverline(False)
+            elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37):
+                charFormat.setForeground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 30])
+            elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47):
+                charFormat.setBackground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 40])
+            elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97):
+                charFormat.setForeground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 80])
+            elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107):
+                charFormat.setBackground(
+                    AnsiColorSchemes[self.__colorScheme][formatCode - 90])
+            elif formatCode == 39:
+                charFormat.setForeground(self.DefaultForeground)
+            elif formatCode == 49:
+                charFormat.setBackground(self.DefaultBackground)
+        
+        textCursor.setCharFormat(charFormat)
+    
+    def __doZoom(self, value):
+        """
+        Private slot to zoom the REPL pane.
+        
+        @param value zoom value
+        @type int
+        """
+        if value < self.__currentZoom:
+            self.replEdit.zoomOut(self.__currentZoom - value)
+        elif value > self.__currentZoom:
+            self.replEdit.zoomIn(value - self.__currentZoom)
+        self.__currentZoom = value
+    
+    def getCurrentPort(self):
+        """
+        Public method to determine the port path of the selected device.
+        
+        @return path of the port of the selected device
+        @rtype str
+        """
+        portName = self.deviceTypeComboBox.itemData(
+            self.deviceTypeComboBox.currentIndex(),
+            self.DevicePortRole)
+        
+        if Globals.isWindowsPlatform():
+            # return it unchanged
+            return portName
+        else:
+            # return with device path prepended
+            return "/dev/{0}".format(portName)
+    
+    def getDeviceWorkspace(self):
+        """
+        Public method to get the workspace directory of the device.
+        
+        @return workspace directory of the device
+        @rtype str
+        """
+        if self.__device:
+            return self.__device.getWorkspace()
+        else:
+            return ""
+    
+    def __connectToDevice(self):
+        """
+        Private method to connect to the selected device.
+        """
+        port = self.getCurrentPort()
+        if self.__interface.connectToDevice(port):
+            self.__setConnected(True)
+            
+            if Preferences.getMicroPython("SyncTimeAfterConnect"):
+                self.__synchronizeTime(quiet=True)
+        else:
+            E5MessageBox.warning(
+                self,
+                self.tr("Serial Device Connect"),
+                self.tr("""<p>Cannot connect to device at serial port"""
+                        """ <b>{0}</b>.</p>""").format(port))
+    
+    def __disconnectFromDevice(self):
+        """
+        Private method to disconnect from the device.
+        """
+        self.__interface.disconnectFromDevice()
+        self.__setConnected(False)
+    
+    @pyqtSlot()
+    def on_runButton_clicked(self):
+        """
+        Private slot to execute the script of the active editor on the
+        selected device.
+        
+        If the REPL is not active yet, it will be activated, which might cause
+        an unconnected device to be connected.
+        """
+        if not self.__device:
+            self.__showNoDeviceMessage()
+            return
+        
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if aw is None:
+            E5MessageBox.critical(
+                self,
+                self.tr("Run Script"),
+                self.tr("""There is no editor open. Abort..."""))
+            return
+        
+        script = aw.text()
+        if not script:
+            E5MessageBox.critical(
+                self,
+                self.tr("Run Script"),
+                self.tr("""The current editor does not contain a script."""
+                        """ Abort..."""))
+            return
+        
+        ok, reason = self.__device.canRunScript()
+        if not ok:
+            E5MessageBox.warning(
+                self,
+                self.tr("Run Script"),
+                self.tr("""<p>Cannot run script.</p><p>Reason:"""
+                        """ {0}</p>""").format(reason))
+            return
+        
+        if not self.replButton.isChecked():
+            # activate on the REPL
+            self.on_replButton_clicked(True)
+        if self.replButton.isChecked():
+            self.__device.runScript(script)
+    
+    @pyqtSlot()
+    def on_openButton_clicked(self):
+        """
+        Private slot to open a file of the connected device.
+        """
+        if not self.__device:
+            self.__showNoDeviceMessage()
+            return
+        
+        workspace = self.__device.getWorkspace()
+        fileName = E5FileDialog.getOpenFileName(
+            self,
+            self.tr("Open Python File"),
+            workspace,
+            self.tr("Python3 Files (*.py);;All Files (*)"))
+        if fileName:
+            e5App().getObject("ViewManager").openSourceFile(fileName)
+    
+    @pyqtSlot()
+    def on_saveButton_clicked(self):
+        """
+        Private slot to save the current editor to the connected device.
+        """
+        if not self.__device:
+            self.__showNoDeviceMessage()
+            return
+        
+        workspace = self.__device.getWorkspace()
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if aw:
+            aw.saveFileAs(workspace)
+    
+    @pyqtSlot(bool)
+    def on_chartButton_clicked(self, checked):
+        """
+        Private slot to open a chart view to plot data received from the
+        connected device.
+       
+        If the selected device is not connected yet, this will be done now.
+        
+        @param checked state of the button
+        @type bool
+        """
+        if not HAS_QTCHART:
+            # QtChart not available => fail silently
+            return
+        
+        if not self.__device:
+            self.__showNoDeviceMessage()
+            return
+        
+        if checked:
+            ok, reason = self.__device.canStartPlotter()
+            if not ok:
+                E5MessageBox.warning(
+                    self,
+                    self.tr("Start Chart"),
+                    self.tr("""<p>The Chart cannot be started.</p><p>Reason:"""
+                            """ {0}</p>""").format(reason))
+                return
+            
+            self.__chartWidget = MicroPythonGraphWidget(self)
+            self.__interface.dataReceived.connect(
+                self.__chartWidget.processData)
+            self.__chartWidget.dataFlood.connect(
+                self.handleDataFlood)
+            
+            self.__ui.addSideWidget(self.__ui.BottomSide, self.__chartWidget,
+                                    UI.PixmapCache.getIcon("chart"),
+                                    self.tr("μPy Chart"))
+            self.__ui.showSideWidget(self.__chartWidget)
+            
+            if not self.__interface.isConnected():
+                self.__connectToDevice()
+                if self.__device.forceInterrupt():
+                    # send a Ctrl-B (exit raw mode)
+                    self.__interface.write(b'\x02')
+                    # send Ctrl-C (keyboard interrupt)
+                    self.__interface.write(b'\x03')
+            
+            self.__device.setPlotter(True)
+        else:
+            if self.__chartWidget.isDirty():
+                res = E5MessageBox.okToClearData(
+                    self,
+                    self.tr("Unsaved Chart Data"),
+                    self.tr("""The chart contains unsaved data."""),
+                    self.__chartWidget.saveData)
+                if not res:
+                    # abort
+                    return
+            
+            self.__interface.dataReceived.disconnect(
+                self.__chartWidget.processData)
+            self.__chartWidget.dataFlood.disconnect(
+                self.handleDataFlood)
+            
+            if (not self.replButton.isChecked() and
+                    not self.filesButton.isChecked()):
+                self.__disconnectFromDevice()
+            
+            self.__device.setPlotter(False)
+            self.__ui.removeSideWidget(self.__chartWidget)
+            
+            self.__chartWidget.deleteLater()
+            self.__chartWidget = None
+        
+        self.chartButton.setChecked(checked)
+    
+    @pyqtSlot()
+    def handleDataFlood(self):
+        """
+        Public slot handling a data flood from the device.
+        """
+        self.on_connectButton_clicked()
+        self.__device.handleDataFlood()
+    
+    @pyqtSlot(bool)
+    def on_filesButton_clicked(self, checked):
+        """
+        Private slot to open a file manager window to the connected device.
+       
+        If the selected device is not connected yet, this will be done now.
+        
+        @param checked state of the button
+        @type bool
+        """
+        if not self.__device:
+            self.__showNoDeviceMessage()
+            return
+        
+        if checked:
+            ok, reason = self.__device.canStartFileManager()
+            if not ok:
+                E5MessageBox.warning(
+                    self,
+                    self.tr("Start File Manager"),
+                    self.tr("""<p>The File Manager cannot be started.</p>"""
+                            """<p>Reason: {0}</p>""").format(reason))
+                return
+            
+            if not self.__interface.isConnected():
+                self.__connectToDevice()
+            self.__fileManagerWidget = MicroPythonFileManagerWidget(
+                self.__interface, self.__device.supportsLocalFileAccess(),
+                self)
+            
+            self.__ui.addSideWidget(self.__ui.BottomSide,
+                                    self.__fileManagerWidget,
+                                    UI.PixmapCache.getIcon("filemanager"),
+                                    self.tr("μPy Files"))
+            self.__ui.showSideWidget(self.__fileManagerWidget)
+
+            self.__device.setFileManager(True)
+            
+            self.__fileManagerWidget.start()
+        else:
+            self.__fileManagerWidget.stop()
+            
+            if (not self.replButton.isChecked() and
+                    not self.chartButton.isChecked()):
+                self.__disconnectFromDevice()
+            
+            self.__device.setFileManager(False)
+            self.__ui.removeSideWidget(self.__fileManagerWidget)
+            
+            self.__fileManagerWidget.deleteLater()
+            self.__fileManagerWidget = None
+        
+        self.filesButton.setChecked(checked)
+    
+    ##################################################################
+    ## Super Menu related methods below
+    ##################################################################
+    
+    def __aboutToShowSuperMenu(self):
+        """
+        Private slot to populate the Super Menu before showing it.
+        """
+        self.__superMenu.clear()
+        if self.__device:
+            hasTime = self.__device.hasTimeCommands()
+        else:
+            hasTime = False
+        
+        act = self.__superMenu.addAction(
+            self.tr("Show Version"), self.__showDeviceVersion)
+        act.setEnabled(self.__connected)
+        act = self.__superMenu.addAction(
+            self.tr("Show Implementation"), self.__showImplementation)
+        act.setEnabled(self.__connected)
+        self.__superMenu.addSeparator()
+        if hasTime:
+            act = self.__superMenu.addAction(
+                self.tr("Synchronize Time"), self.__synchronizeTime)
+            act.setEnabled(self.__connected)
+            act = self.__superMenu.addAction(
+                self.tr("Show Device Time"), self.__showDeviceTime)
+            act.setEnabled(self.__connected)
+            self.__superMenu.addAction(
+                self.tr("Show Local Time"), self.__showLocalTime)
+            self.__superMenu.addSeparator()
+        if not Globals.isWindowsPlatform():
+            self.__superMenu.addAction(
+                self.tr("Compile Python File"), self.__compileFile2Mpy)
+            act = self.__superMenu.addAction(
+                self.tr("Compile Current Editor"), self.__compileEditor2Mpy)
+            aw = e5App().getObject("ViewManager").activeWindow()
+            act.setEnabled(bool(aw))
+            self.__superMenu.addSeparator()
+        if self.__device:
+            self.__device.addDeviceMenuEntries(self.__superMenu)
+    
+    @pyqtSlot()
+    def __showDeviceVersion(self):
+        """
+        Private slot to show some version info about MicroPython of the device.
+        """
+        try:
+            versionInfo = self.__interface.version()
+            if versionInfo:
+                msg = self.tr(
+                    "<h3>Device Version Information</h3>"
+                )
+                msg += "<table>"
+                for key, value in versionInfo.items():
+                    msg += "<tr><td><b>{0}</b></td><td>{1}</td></tr>".format(
+                        key.capitalize(), value)
+                msg += "</table>"
+            else:
+                msg = self.tr("No version information available.")
+            
+            E5MessageBox.information(
+                self,
+                self.tr("Device Version Information"),
+                msg)
+        except Exception as exc:
+            self.__showError("version()", str(exc))
+    
+    @pyqtSlot()
+    def __showImplementation(self):
+        """
+        Private slot to show some implementation related information.
+        """
+        try:
+            impInfo = self.__interface.getImplementation()
+            if impInfo["name"] == "micropython":
+                name = "MicroPython"
+            elif impInfo["name"] == "circuitpython":
+                name = "CircuitPython"
+            elif impInfo["name"] == "unknown":
+                name = self.tr("unknown")
+            else:
+                name = impInfo["name"]
+            if impInfo["version"] == "unknown":
+                version = self.tr("unknown")
+            else:
+                version = impInfo["version"]
+            
+            E5MessageBox.information(
+                self,
+                self.tr("Device Implementation Information"),
+                self.tr(
+                    "<h3>Device Implementation Information</h3>"
+                    "<p>This device contains <b>{0} {1}</b>.</p>"
+                ).format(name, version)
+            )
+        except Exception as exc:
+            self.__showError("getImplementation()", str(exc))
+    
+    @pyqtSlot()
+    def __synchronizeTime(self, quiet=False):
+        """
+        Private slot to set the time of the connected device to the local
+        computer's time.
+        
+        @param quiet flag indicating to not show a message
+        @type bool
+        """
+        try:
+            self.__interface.syncTime()
+            
+            if not quiet:
+                E5MessageBox.information(
+                    self,
+                    self.tr("Synchronize Time"),
+                    self.tr("<p>The time of the connected device was"
+                            " synchronized with the local time.</p>") +
+                    self.__getDeviceTime()
+                )
+        except Exception as exc:
+            self.__showError("syncTime()", str(exc))
+    
+    def __getDeviceTime(self):
+        """
+        Private method to get a string containing the date and time of the
+        connected device.
+        
+        @return date and time of the connected device
+        @rtype str
+        """
+        try:
+            dateTimeString = self.__interface.getTime()
+            try:
+                date, time = dateTimeString.strip().split(None, 1)
+                return self.tr(
+                    "<h3>Device Date and Time</h3>"
+                    "<table>"
+                    "<tr><td><b>Date</b></td><td>{0}</td></tr>"
+                    "<tr><td><b>Time</b></td><td>{1}</td></tr>"
+                    "</table>"
+                ).format(date, time)
+            except ValueError:
+                return self.tr(
+                    "<h3>Device Date and Time</h3>"
+                    "<p>{0}</p>"
+                ).format(dateTimeString.strip())
+        except Exception as exc:
+            self.__showError("getTime()", str(exc))
+            return ""
+    
+    @pyqtSlot()
+    def __showDeviceTime(self):
+        """
+        Private slot to show the date and time of the connected device.
+        """
+        msg = self.__getDeviceTime()
+        E5MessageBox.information(
+            self,
+            self.tr("Device Date and Time"),
+            msg)
+    
+    @pyqtSlot()
+    def __showLocalTime(self):
+        """
+        Private slot to show the local date and time.
+        """
+        localdatetime = time.localtime()
+        loacldate = time.strftime('%Y-%m-%d', localdatetime)
+        localtime = time.strftime('%H:%M:%S', localdatetime)
+        E5MessageBox.information(
+            self,
+            self.tr("Local Date and Time"),
+            self.tr("<h3>Local Date and Time</h3>"
+                    "<table>"
+                    "<tr><td><b>Date</b></td><td>{0}</td></tr>"
+                    "<tr><td><b>Time</b></td><td>{1}</td></tr>"
+                    "</table>"
+                    ).format(loacldate, localtime)
+        )
+    
+    def __showError(self, method, error):
+        """
+        Private method to show some error message.
+        
+        @param method name of the method the error occured in
+        @type str
+        @param error error message
+        @type str
+        """
+        E5MessageBox.warning(
+            self,
+            self.tr("Error handling device"),
+            self.tr("<p>There was an error communicating with the connected"
+                    " device.</p><p>Method: {0}</p><p>Message: {1}</p>")
+            .format(method, error))
+    
+    def __crossCompile(self, pythonFile="", title=""):
+        """
+        Private method to cross compile a Python file to a .mpy file.
+        
+        @param pythonFile name of the Python file to be compiled
+        @type str
+        @param title title for the various dialogs
+        @type str
+        """
+        program = Preferences.getMicroPython("MpyCrossCompiler")
+        if not program:
+            program = "mpy-cross"
+            if not Utilities.isinpath(program):
+                E5MessageBox.critical(
+                    self,
+                    title,
+                    self.tr("""The MicroPython cross compiler"""
+                            """ <b>mpy-cross</b> cannot be found. Ensure it"""
+                            """ is in the search path or configure it on"""
+                            """ the MicroPython configuration page."""))
+                return
+        
+        if not pythonFile:
+            defaultDirectory = ""
+            aw = e5App().getObject("ViewManager").activeWindow()
+            if aw:
+                fn = aw.getFileName()
+                if fn:
+                    defaultDirectory = os.path.dirname(fn)
+            if not defaultDirectory:
+                defaultDirectory = Preferences.getMultiProject("Workspace")
+            pythonFile = E5FileDialog.getOpenFileName(
+                self,
+                title,
+                defaultDirectory,
+                self.tr("Python Files (*.py);;All Files (*)"))
+            if not pythonFile:
+                # user cancelled
+                return
+        
+        if not os.path.exists(pythonFile):
+            E5MessageBox.critical(
+                self,
+                title,
+                self.tr("""The Python file <b>{0}</b> does not exist."""
+                        """ Aborting...""").format(pythonFile))
+            return
+        
+        compileArgs = [
+            pythonFile,
+        ]
+        dlg = E5ProcessDialog(self.tr("'mpy-cross' Output"), title)
+        res = dlg.startProcess(program, compileArgs)
+        if res:
+            dlg.exec_()
+    
+    @pyqtSlot()
+    def __compileFile2Mpy(self):
+        """
+        Private slot to cross compile a Python file (*.py) to a .mpy file.
+        """
+        self.__crossCompile(title=self.tr("Compile Python File"))
+    
+    @pyqtSlot()
+    def __compileEditor2Mpy(self):
+        """
+        Private slot to cross compile the current editor to a .mpy file.
+        """
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if not aw.checkDirty():
+            # editor still has unsaved changes, abort...
+            return
+        if not aw.isPyFile():
+            # no Python file
+            E5MessageBox.critical(
+                self,
+                self.tr("Compile Current Editor"),
+                self.tr("""The current editor does not contain a Python"""
+                        """ file. Aborting..."""))
+            return
+        
+        self.__crossCompile(
+            pythonFile=aw.getFileName(),
+            title=self.tr("Compile Current Editor")
+        )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonWidget.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MicroPythonWidget</class>
+ <widget class="QWidget" name="MicroPythonWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>456</width>
+    <height>548</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="deviceIconLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>48</width>
+         <height>48</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QGridLayout" name="gridLayout">
+       <item row="1" column="0" colspan="4">
+        <widget class="QLabel" name="deviceInfoLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="3">
+        <widget class="E5Led" name="deviceConnectedLed" native="true"/>
+       </item>
+       <item row="0" column="0">
+        <widget class="QComboBox" name="deviceTypeComboBox">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QToolButton" name="checkButton">
+         <property name="toolTip">
+          <string>Press to check for connected devices</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="2">
+        <widget class="E5ToolButton" name="menuButton"/>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QToolButton" name="openButton">
+       <property name="toolTip">
+        <string>Press to open a file of the connected device</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="saveButton">
+       <property name="toolTip">
+        <string>Press to save the current editor to the connected device</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="Line" name="line">
+       <property name="lineWidth">
+        <number>2</number>
+       </property>
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="runButton">
+       <property name="toolTip">
+        <string>Press to run the current script on the selected device</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="replButton">
+       <property name="toolTip">
+        <string>Press to open a terminal (REPL) on the selected device</string>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="filesButton">
+       <property name="toolTip">
+        <string>Press to open a file manager on the selected device (REPL must be disconnected first)</string>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="chartButton">
+       <property name="toolTip">
+        <string>Press to open a chart window to display data receive from the selected device</string>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QToolButton" name="connectButton">
+       <property name="toolTip">
+        <string>Press to connect the selected device</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QTextEdit" name="replEdit">
+     <property name="contextMenuPolicy">
+      <enum>Qt::CustomContextMenu</enum>
+     </property>
+     <property name="undoRedoEnabled">
+      <bool>false</bool>
+     </property>
+     <property name="lineWrapMode">
+      <enum>QTextEdit::NoWrap</enum>
+     </property>
+     <property name="acceptRichText">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5ToolButton</class>
+   <extends>QToolButton</extends>
+   <header>E5Gui/E5ToolButton.h</header>
+  </customwidget>
+  <customwidget>
+   <class>E5Led</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5Led.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>deviceTypeComboBox</tabstop>
+  <tabstop>checkButton</tabstop>
+  <tabstop>menuButton</tabstop>
+  <tabstop>openButton</tabstop>
+  <tabstop>saveButton</tabstop>
+  <tabstop>runButton</tabstop>
+  <tabstop>replButton</tabstop>
+  <tabstop>filesButton</tabstop>
+  <tabstop>chartButton</tabstop>
+  <tabstop>connectButton</tabstop>
+  <tabstop>replEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicrobitDevices.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,329 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the device interface class for BBC micro:bit boards.
+"""
+
+from __future__ import unicode_literals
+
+import sys
+import os
+
+from PyQt5.QtCore import pyqtSlot, QStandardPaths
+
+from .MicroPythonDevices import MicroPythonDevice
+from .MicroPythonWidget import HAS_QTCHART
+
+from E5Gui import E5MessageBox, E5FileDialog
+from E5Gui.E5Application import e5App
+from E5Gui.E5ProcessDialog import E5ProcessDialog
+
+import Utilities
+
+
+class MicrobitDevice(MicroPythonDevice):
+    """
+    Class implementing the device for BBC micro:bit boards.
+    """
+    def __init__(self, microPythonWidget, parent=None):
+        """
+        Constructor
+        
+        @param microPythonWidget reference to the main MicroPython widget
+        @type MicroPythonWidget
+        @param parent reference to the parent object
+        @type QObject
+        """
+        super(MicrobitDevice, self).__init__(microPythonWidget, parent)
+    
+    def setButtons(self):
+        """
+        Public method to enable the supported action buttons.
+        """
+        super(MicrobitDevice, self).setButtons()
+        self.microPython.setActionButtons(
+            run=True, repl=True, files=True, chart=HAS_QTCHART)
+    
+    def forceInterrupt(self):
+        """
+        Public method to determine the need for an interrupt when opening the
+        serial connection.
+        
+        @return flag indicating an interrupt is needed
+        @rtype bool
+        """
+        return True
+    
+    def deviceName(self):
+        """
+        Public method to get the name of the device.
+        
+        @return name of the device
+        @rtype str
+        """
+        return self.tr("BBC micro:bit")
+    
+    def canStartRepl(self):
+        """
+        Public method to determine, if a REPL can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a REPL
+            and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def canStartPlotter(self):
+        """
+        Public method to determine, if a Plotter can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def canRunScript(self):
+        """
+        Public method to determine, if a script can be executed.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            Plotter and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def runScript(self, script):
+        """
+        Public method to run the given Python script.
+        
+        @param script script to be executed
+        @type str
+        """
+        pythonScript = script.split("\n")
+        self.sendCommands(pythonScript)
+    
+    def canStartFileManager(self):
+        """
+        Public method to determine, if a File Manager can be started.
+        
+        @return tuple containing a flag indicating it is safe to start a
+            File Manager and a reason why it cannot.
+        @rtype tuple of (bool, str)
+        """
+        return True, ""
+    
+    def getWorkspace(self):
+        """
+        Public method to get the workspace directory.
+        
+        @return workspace directory used for saving files
+        @rtype str
+        """
+        # Attempts to find the path on the filesystem that represents the
+        # plugged in MICROBIT board.
+        deviceDirectory = Utilities.findVolume("MICROBIT")
+        
+        if deviceDirectory:
+            return deviceDirectory
+        else:
+            # return the default workspace and give the user a warning
+            E5MessageBox.warning(
+                self.microPython,
+                self.tr("Workspace Directory"),
+                self.tr("Could not find an attached BBC micro:bit.\n\n"
+                        "Please make sure the device is plugged "
+                        "into this computer."))
+            
+            return super(MicrobitDevice, self).getWorkspace()
+    
+    def hasTimeCommands(self):
+        """
+        Public method to check, if the device supports time commands.
+        
+        The default returns True.
+        
+        @return flag indicating support for time commands
+        @rtype bool
+        """
+        return False
+    
+    def addDeviceMenuEntries(self, menu):
+        """
+        Public method to add device specific entries to the given menu.
+        
+        @param menu reference to the context menu
+        @type QMenu
+        """
+        connected = self.microPython.isConnected()
+        
+        act = menu.addAction(self.tr("Flash Default MicroPython Firmware"),
+                             self.__flashMicroPython)
+        act.setEnabled(not connected)
+        act = menu.addAction(self.tr("Flash Custom MicroPython Firmware"),
+                             self.__flashCustomMicroPython)
+        act.setEnabled(not connected)
+        menu.addSeparator()
+        act = menu.addAction(self.tr("Flash Script"), self.__flashScript)
+        act.setToolTip(self.tr(
+            "Flash the current script to the selected device."))
+        act.setEnabled(not connected)
+        act = menu.addAction(self.tr("Save Script as 'main.py'"),
+                             self.__saveMain)
+        act.setToolTip(self.tr(
+            "Save the current script as 'main.py' on the connected device"))
+        act.setEnabled(connected)
+        menu.addSeparator()
+        act = menu.addAction(self.tr("Reset micro:bit"), self.__resetDevice)
+        act.setEnabled(connected)
+        menu.addSeparator()
+        menu.addAction(self.tr("Install 'uflash'"), self.__installUflashTool)
+    
+    @pyqtSlot()
+    def __flashMicroPython(self):
+        """
+        Private slot to flash the default MicroPython firmware to the device.
+        """
+        flashArgs = [
+            "-u",
+            "-m", "uflash",
+        ]
+        dlg = E5ProcessDialog(self.tr("'uflash' Output"),
+                              self.tr("Flash Default MicroPython Firmware"))
+        res = dlg.startProcess(sys.executable, flashArgs)
+        if res:
+            dlg.exec_()
+    
+    @pyqtSlot()
+    def __flashCustomMicroPython(self):
+        """
+        Private slot to flash a custom MicroPython firmware to the device.
+        """
+        downloadsPath = QStandardPaths.standardLocations(
+            QStandardPaths.DownloadLocation)[0]
+        firmware = E5FileDialog.getOpenFileName(
+            None,
+            self.tr("Flash Custom MicroPython Firmware"),
+            downloadsPath,
+            self.tr("MicroPython Firmware Files (*.hex);;All Files (*)"))
+        if firmware and os.path.exists(firmware):
+            flashArgs = [
+                "-u",
+                "-m", "uflash",
+                "--runtime", firmware,
+            ]
+            dlg = E5ProcessDialog(
+                self.tr("'uflash' Output"),
+                self.tr("Flash Default MicroPython Firmware"))
+            res = dlg.startProcess(sys.executable, flashArgs)
+            if res:
+                dlg.exec_()
+    
+    @pyqtSlot()
+    def __flashScript(self):
+        """
+        Private slot to flash the current script onto the selected device.
+        """
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if not aw:
+            return
+        
+        if not (aw.isPy3File() or aw.isPy2File()):
+            yes = E5MessageBox.yesNo(
+                None,
+                self.tr("Flash Script"),
+                self.tr("""The current editor does not contain a Python"""
+                        """ script. Flash it anyway?"""))
+            if not yes:
+                return
+        
+        script = aw.text().strip()
+        if not script:
+            E5MessageBox.warning(
+                self,
+                self.tr("Flash Script"),
+                self.tr("""The script is empty. Aborting."""))
+            return
+        
+        if aw.checkDirty():
+            filename = aw.getFileName()
+            flashArgs = [
+                "-u",
+                "-m", "uflash",
+                filename,
+            ]
+            dlg = E5ProcessDialog(self.tr("'uflash' Output"),
+                                  self.tr("Flash Script"))
+            res = dlg.startProcess(sys.executable, flashArgs)
+            if res:
+                dlg.exec_()
+    
+    @pyqtSlot()
+    def __saveMain(self):
+        """
+        Private slot to copy the current script as 'main.py' onto the
+        connected device.
+        """
+        aw = e5App().getObject("ViewManager").activeWindow()
+        if not aw:
+            return
+        
+        if not (aw.isPy3File() or aw.isPy2File()):
+            yes = E5MessageBox.yesNo(
+                None,
+                self.tr("Save Script as 'main.py'"),
+                self.tr("""The current editor does not contain a Python"""
+                        """ script. Write it anyway?"""))
+            if not yes:
+                return
+        
+        script = aw.text().strip()
+        if not script:
+            E5MessageBox.warning(
+                self,
+                self.tr("Save Script as 'main.py'"),
+                self.tr("""The script is empty. Aborting."""))
+            return
+        
+        commands = [
+            "fd = open('main.py', 'wb')",
+            "f = fd.write",
+        ]
+        for line in script.splitlines():
+            commands.append("f(" + repr(line + "\n") + ")")
+        commands.append("fd.close()")
+        out, err = self.microPython.commandsInterface().execute(commands)
+        if err:
+            E5MessageBox.critical(
+                self,
+                self.tr("Save Script as 'main.py'"),
+                self.tr("""<p>The script could not be saved to the"""
+                        """ device.</p><p>Reason: {0}</p>""")
+                .format(err.decode("utf-8")))
+        
+        # reset the device
+        self.microPython.commandsInterface().execute([
+            "import microbit",
+            "microbit.reset()",
+        ])
+    
+    @pyqtSlot()
+    def __resetDevice(self):
+        """
+        Private slot to reset the connected device.
+        """
+        self.microPython.commandsInterface().execute([
+            "import microbit",
+            "microbit.reset()",
+        ])
+    
+    @pyqtSlot()
+    def __installUflashTool(self):
+        """
+        Private slot to install the uflash package via pip.
+        """
+        pip = e5App().getObject("Pip")
+        pip.installPackages(["uflash"], interpreter=sys.executable)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/__init__.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing an interface to MicroPython devices like esp32.
+"""
+
+from __future__ import unicode_literals
--- a/eric6/Preferences/ConfigurationDialog.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/Preferences/ConfigurationDialog.py	Tue Aug 20 17:07:06 2019 +0200
@@ -172,6 +172,9 @@
                 "logViewerPage":
                 [self.tr("Log-Viewer"), "preferences-logviewer.png",
                  "LogViewerPage", None, None],
+                "microPythonPage":
+                [self.tr("MicroPython"), "micropython",
+                 "MicroPythonPage", None, None],
                 "mimeTypesPage":
                 [self.tr("Mimetypes"), "preferences-mimetypes.png",
                  "MimeTypesPage", None, None],
--- a/eric6/Preferences/ConfigurationPages/InterfacePage.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/Preferences/ConfigurationPages/InterfacePage.py	Tue Aug 20 17:07:06 2019 +0200
@@ -86,6 +86,8 @@
         # right side
         self.codeDocumentationViewerCheckBox.setChecked(
             Preferences.getUI("ShowCodeDocumentationViewer"))
+        self.microPythonCheckBox.setChecked(
+            Preferences.getUI("ShowMicroPython"))
         self.pypiCheckBox.setChecked(
             Preferences.getUI("ShowPyPIPackageManager"))
         self.condaCheckBox.setChecked(
@@ -175,6 +177,9 @@
             "ShowCodeDocumentationViewer",
             self.codeDocumentationViewerCheckBox.isChecked())
         Preferences.setUI(
+            "ShowMicroPython",
+            self.microPythonCheckBox.isChecked())
+        Preferences.setUI(
             "ShowPyPIPackageManager",
             self.pypiCheckBox.isChecked())
         Preferences.setUI(
--- a/eric6/Preferences/ConfigurationPages/InterfacePage.ui	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/Preferences/ConfigurationPages/InterfacePage.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -445,6 +445,16 @@
            </property>
           </widget>
          </item>
+         <item row="0" column="1">
+          <widget class="QCheckBox" name="microPythonCheckBox">
+           <property name="toolTip">
+            <string>Select to activate the MicroPython widget</string>
+           </property>
+           <property name="text">
+            <string>MicroPython</string>
+           </property>
+          </widget>
+         </item>
         </layout>
        </widget>
       </item>
@@ -518,6 +528,7 @@
   <tabstop>fileBrowserCheckBox</tabstop>
   <tabstop>symbolsCheckBox</tabstop>
   <tabstop>codeDocumentationViewerCheckBox</tabstop>
+  <tabstop>microPythonCheckBox</tabstop>
   <tabstop>pypiCheckBox</tabstop>
   <tabstop>condaCheckBox</tabstop>
   <tabstop>cooperationCheckBox</tabstop>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Preferences/ConfigurationPages/MicroPythonPage.py	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the MicroPython configuration page.
+"""
+
+from __future__ import unicode_literals
+
+from E5Gui.E5PathPicker import E5PathPickerModes
+
+from .ConfigurationPageBase import ConfigurationPageBase
+from .Ui_MicroPythonPage import Ui_MicroPythonPage
+
+import Preferences
+
+from MicroPython.MicroPythonWidget import AnsiColorSchemes
+
+
+class MicroPythonPage(ConfigurationPageBase, Ui_MicroPythonPage):
+    """
+    Class implementing the MicroPython configuration page.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(MicroPythonPage, self).__init__()
+        self.setupUi(self)
+        self.setObjectName("MicroPythonPage")
+        
+        self.colorSchemeComboBox.addItems(sorted(AnsiColorSchemes.keys()))
+        
+        self.mpyCrossPicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.mpyCrossPicker.setFilters(self.tr("All Files (*)"))
+        
+        # set initial values
+        self.timeoutSpinBox.setValue(
+            Preferences.getMicroPython("SerialTimeout") / 1000)
+        # converted to seconds
+        self.syncTimeCheckBox.setChecked(
+            Preferences.getMicroPython("SyncTimeAfterConnect"))
+        self.colorSchemeComboBox.setCurrentIndex(
+            self.colorSchemeComboBox.findText(
+                Preferences.getMicroPython("ColorScheme")))
+        self.replWrapCheckBox.setChecked(
+            Preferences.getMicroPython("ReplLineWrap"))
+        self.mpyCrossPicker.setText(
+            Preferences.getMicroPython("MpyCrossCompiler"))
+    
+    def save(self):
+        """
+        Public slot to save the MicroPython configuration.
+        """
+        Preferences.setMicroPython(
+            "SerialTimeout", self.timeoutSpinBox.value() * 1000)
+        # converted to milliseconds
+        Preferences.setMicroPython(
+            "SyncTimeAfterConnect", self.syncTimeCheckBox.isChecked())
+        Preferences.setMicroPython(
+            "ColorScheme", self.colorSchemeComboBox.currentText())
+        Preferences.setMicroPython(
+            "ReplLineWrap", self.replWrapCheckBox.isChecked())
+        Preferences.setMicroPython(
+            "MpyCrossCompiler", self.mpyCrossPicker.text())
+    
+
+def create(dlg):
+    """
+    Module function to create the configuration page.
+    
+    @param dlg reference to the configuration dialog
+    @return reference to the instantiated page (ConfigurationPageBase)
+    """
+    return MicroPythonPage()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/Preferences/ConfigurationPages/MicroPythonPage.ui	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MicroPythonPage</class>
+ <widget class="QWidget" name="MicroPythonPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>476</width>
+    <height>356</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="headerLabel">
+     <property name="text">
+      <string>&lt;b&gt;Configure MicroPython&lt;/b&gt;</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="Line" name="line9_3">
+     <property name="frameShape">
+      <enum>QFrame::HLine</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Sunken</enum>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="title">
+      <string>Serial Link</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Timeout for Serial Link Communication:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QSpinBox" name="timeoutSpinBox">
+        <property name="toolTip">
+         <string>Enter the timout value</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+        </property>
+        <property name="suffix">
+         <string> s</string>
+        </property>
+        <property name="minimum">
+         <number>1</number>
+        </property>
+        <property name="maximum">
+         <number>30</number>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="2">
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>195</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item row="1" column="0" colspan="3">
+       <widget class="QCheckBox" name="syncTimeCheckBox">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="toolTip">
+         <string>Select to synchronize the time after connection is established</string>
+        </property>
+        <property name="text">
+         <string>Synchronize Time at Connect</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>REPL Pane</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Color Scheme for ANSI Escape Codes:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QComboBox" name="colorSchemeComboBox">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="toolTip">
+         <string>Select the color scheme to be applied for ANSI color escape codes</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0" colspan="2">
+       <widget class="QCheckBox" name="replWrapCheckBox">
+        <property name="toolTip">
+         <string>Select to wrap long line in the REPL pane</string>
+        </property>
+        <property name="text">
+         <string>Wrap long lines</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_3">
+     <property name="title">
+      <string>MPY Cross Compiler</string>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Program:</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="E5PathPicker" name="mpyCrossPicker" native="true">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="focusPolicy">
+         <enum>Qt::WheelFocus</enum>
+        </property>
+        <property name="toolTip">
+         <string>Enter the path of the cross compiler executable</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>252</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>timeoutSpinBox</tabstop>
+  <tabstop>syncTimeCheckBox</tabstop>
+  <tabstop>colorSchemeComboBox</tabstop>
+  <tabstop>replWrapCheckBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- a/eric6/Preferences/__init__.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/Preferences/__init__.py	Tue Aug 20 17:07:06 2019 +0200
@@ -47,9 +47,7 @@
 
 from E5Network.E5Ftp import E5FtpProxyType
 
-from Globals import settingsNameOrganization, settingsNameGlobal, \
-    settingsNameRecent, isWindowsPlatform, getPyQt5ModulesDirectory, \
-    qVersionTuple
+import Globals
 
 from Project.ProjectBrowserFlags import SourcesBrowserFlag, FormsBrowserFlag, \
     ResourcesBrowserFlag, TranslationsBrowserFlag, InterfacesBrowserFlag, \
@@ -168,6 +166,7 @@
         "ShowCondaPackageManager": True,        # right side
         "ShowCooperation": True,                # right side
         "ShowIrc": True,                        # right side
+        "ShowMicroPython": True,                # right side
         "ShowNumbersViewer": True,              # bottom side
         "ViewProfiles2": {
             "edit": [
@@ -626,7 +625,7 @@
         "YAMLFoldComment": False,
     }
     
-    if isWindowsPlatform():
+    if Globals.isWindowsPlatform():
         editorDefaults["EOLMode"] = QsciScintilla.EolWindows
     else:
         editorDefaults["EOLMode"] = QsciScintilla.EolUnix
@@ -1558,7 +1557,7 @@
         "AddressAreaForeGround": QColor(Qt.black),
         "RecentNumber": 9,
     }
-    if isWindowsPlatform():
+    if Globals.isWindowsPlatform():
         hexEditorDefaults["Font"] = "Courier,10,-1,5,50,0,0,0,0,0"
     else:
         hexEditorDefaults["Font"] = "Monospace,10,-1,5,50,0,0,0,0,0"
@@ -1590,6 +1589,22 @@
     pipDefaults = {
         "PipSearchIndex": "",           # used by the search command
     }
+    
+    # defaults for MicroPython
+    microPythonDefaults = {
+        "SerialTimeout": 2000,          # timeout in milliseconds
+        "ReplLineWrap": True,           # wrap the REPL lines
+        "SyncTimeAfterConnect": True,
+        "ShowHiddenLocal": True,
+        "ShowHiddenDevice": True,
+        "MpyCrossCompiler": "",    # path of the mpy cross compiler
+    }
+    if Globals.isWindowsPlatform():
+        microPythonDefaults["ColorScheme"] = "Windows 10"
+    elif Globals.isMacPlatform():
+        microPythonDefaults["ColorScheme"] = "xterm"
+    else:
+        microPythonDefaults["ColorScheme"] = "Ubuntu"
 
 
 def readToolGroups(prefClass=Prefs):
@@ -1693,13 +1708,13 @@
     """
     Prefs.settings = QSettings(
         QSettings.IniFormat, QSettings.UserScope,
-        settingsNameOrganization, settingsNameGlobal)
-    if not isWindowsPlatform():
+        Globals.settingsNameOrganization, Globals.settingsNameGlobal)
+    if not Globals.isWindowsPlatform():
         hp = QDir.homePath()
         dn = QDir(hp)
         dn.mkdir(".eric6")
-    QCoreApplication.setOrganizationName(settingsNameOrganization)
-    QCoreApplication.setApplicationName(settingsNameGlobal)
+    QCoreApplication.setOrganizationName(Globals.settingsNameOrganization)
+    QCoreApplication.setApplicationName(Globals.settingsNameGlobal)
     try:
         Prefs.settings.setAtomicSyncRequired(False)
     except AttributeError:
@@ -1789,7 +1804,7 @@
     """
     Prefs.rsettings = QSettings(
         QSettings.IniFormat, QSettings.UserScope,
-        settingsNameOrganization, settingsNameRecent)
+        Globals.settingsNameOrganization, Globals.settingsNameRecent)
     
 
 def getVarFilters(prefClass=Prefs):
@@ -1995,7 +2010,7 @@
                "ShowCodeDocumentationViewer", "ShowPyPIPackageManager",
                "ShowCondaPackageManager", "ShowCooperation", "ShowIrc",
                "ShowTemplateViewer", "ShowFileBrowser", "ShowSymbolsViewer",
-               "ShowNumbersViewer", "UseNativeMenuBar"]:
+               "ShowNumbersViewer", "ShowMicroPython", "UseNativeMenuBar"]:
         return toBool(prefClass.settings.value(
             "UI/" + key, prefClass.uiDefaults[key]))
     elif key in ["TabViewManagerFilenameLength", "CaptionFilenameLength",
@@ -3201,7 +3216,7 @@
     @param prefClass preferences class used as the storage area
     @return the requested setting (string)
     """
-    if qVersionTuple() < (5, 0, 0):
+    if Globals.qVersionTuple() < (5, 0, 0):
         s = prefClass.settings.value(
             "Qt/Qt4TranslationsDir",
             prefClass.qtDefaults["Qt4TranslationsDir"])
@@ -3212,14 +3227,15 @@
     if s == "":
         s = os.getenv("QTTRANSLATIONSDIR", "")
     if s == "":
-        if qVersionTuple() < (5, 0, 0):
+        if Globals.qVersionTuple() < (5, 0, 0):
             s = os.getenv("QT4TRANSLATIONSDIR", "")
         else:
             s = os.getenv("QT5TRANSLATIONSDIR", "")
     if s == "":
         s = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
-    if s == "" and isWindowsPlatform():
-        transPath = os.path.join(getPyQt5ModulesDirectory(), "translations")
+    if s == "" and Globals.isWindowsPlatform():
+        transPath = os.path.join(Globals.getPyQt5ModulesDirectory(),
+                                 "translations")
         if os.path.exists(transPath):
             s = transPath
     return s
@@ -3803,6 +3819,40 @@
     prefClass.settings.setValue("Pip/" + key, value)
 
 
+def getMicroPython(key, prefClass=Prefs):
+    """
+    Module function to retrieve the MicroPython related settings.
+    
+    @param key the key of the value to get
+    @param prefClass preferences class used as the storage area
+    @return the requested MicroPython value
+    """
+    if key in ("SerialTimeout"):
+        return int(prefClass.settings.value(
+            "MicroPython/" + key,
+            prefClass.microPythonDefaults[key]))
+    elif key in ["ReplLineWrap", "SyncTimeAfterConnect", "ShowHiddenLocal",
+                 "ShowHiddenDevice"]:
+        return toBool(prefClass.settings.value(
+            "MicroPython/" + key,
+            prefClass.microPythonDefaults[key]))
+    else:
+        return prefClass.settings.value(
+            "MicroPython/" + key,
+            prefClass.microPythonDefaults[key])
+
+
+def setMicroPython(key, value, prefClass=Prefs):
+    """
+    Module function to store the pip MicroPython settings.
+    
+    @param key the key of the setting to be set
+    @param value the value to be set
+    @param prefClass preferences class used as the storage area
+    """
+    prefClass.settings.setValue("MicroPython/" + key, value)
+
+
 def getGeometry(key, prefClass=Prefs):
     """
     Module function to retrieve the display geometry.
--- a/eric6/UI/FindFileDialog.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/UI/FindFileDialog.py	Tue Aug 20 17:07:06 2019 +0200
@@ -242,10 +242,10 @@
 
     @pyqtSlot()
     def on_openFilesButton_clicked(self):
-       """
-       Private slot to handle the selection of the 'Open Files' radio button.
-       """
-       self.__enableFindButton()
+        """
+        Private slot to handle the selection of the 'Open Files' radio button.
+        """
+        self.__enableFindButton()
     
     @pyqtSlot()
     def on_filterCheckBox_clicked(self):
--- a/eric6/UI/UserInterface.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/UI/UserInterface.py	Tue Aug 20 17:07:06 2019 +0200
@@ -907,6 +907,15 @@
                                   UI.PixmapCache.getIcon("irc.png"),
                                   self.tr("IRC"))
         
+        if Preferences.getUI("ShowMicroPython"):
+            # Create the MicroPython part of the user interface
+            logging.debug("Creating MicroPython Widget...")
+            from MicroPython.MicroPythonWidget import MicroPythonWidget
+            self.microPythonWidget = MicroPythonWidget(self)
+            self.rToolbox.addItem(self.microPythonWidget,
+                                  UI.PixmapCache.getIcon("micropython"),
+                                  self.tr("MicroPython"))
+        
         ####################################################
         ## Populate the bottom toolbox
         ####################################################
@@ -1095,6 +1104,15 @@
                 self.irc, UI.PixmapCache.getIcon("irc.png"),
                 self.tr("IRC"))
         
+        if Preferences.getUI("ShowMicroPython"):
+            # Create the MicroPython part of the user interface
+            logging.debug("Creating MicroPython Widget...")
+            from MicroPython.MicroPythonWidget import MicroPythonWidget
+            self.microPythonWidget = MicroPythonWidget(self)
+            self.rightSidebar.addTab(
+                self.microPythonWidget, UI.PixmapCache.getIcon("micropython"),
+                self.tr("MicroPython"))
+        
         ####################################################
         ## Populate the bottom side bar
         ####################################################
@@ -1193,11 +1211,15 @@
         """
         Public method to add a widget to the sides.
         
-        @param side side to add the widget to (UserInterface.LeftSide,
-            UserInterface.BottomSide)
-        @param widget reference to the widget to add (QWidget)
-        @param icon icon to be used (QIcon)
-        @param label label text to be shown (string)
+        @param side side to add the widget to
+        @type int (one of UserInterface.LeftSide, UserInterface.BottomSide,
+            UserInterface.RightSide)
+        @param widget reference to the widget to add
+        @type QWidget
+        @param icon icon to be used
+        @type QIcon
+        @param label label text to be shown
+        @type str
         """
         assert side in [UserInterface.LeftSide, UserInterface.BottomSide,
                         UserInterface.RightSide]
@@ -1216,12 +1238,13 @@
                 self.bottomSidebar.addTab(widget, icon, label)
             elif side == UserInterface.RightSide:
                 self.rightSidebar.addTab(widget, icon, label)
-        
+    
     def removeSideWidget(self, widget):
         """
         Public method to remove a widget added using addSideWidget().
         
-        @param widget reference to the widget to remove (QWidget)
+        @param widget reference to the widget to remove
+        @type QWidget
         """
         if self.__layoutType == "Toolboxes":
             for container in [self.lToolbox, self.hToolbox, self.rToolbox]:
@@ -1234,7 +1257,34 @@
                 index = container.indexOf(widget)
                 if index != -1:
                     container.removeTab(index)
-        
+    
+    def showSideWidget(self, widget):
+        """
+        Public method to show a specific widget placed in the side widgets.
+        
+        @param widget reference to the widget to be shown
+        @type QWidget
+        """
+        if self.__layoutType == "Toolboxes":
+            for dock in [self.lToolboxDock, self.hToolboxDock,
+                         self.rToolboxDock]:
+                container = dock.widget()
+                index = container.indexOf(widget)
+                if index != -1:
+                    dock.show()
+                    container.setCurrentIndex(index)
+                    dock.raise_()
+        elif self.__layoutType == "Sidebars":
+            for container in [self.leftSidebar, self.bottomSidebar,
+                              self.rightSidebar]:
+                index = container.indexOf(widget)
+                if index != -1:
+                    container.show()
+                    container.setCurrentIndex(index)
+                    container.raise_()
+                    if container.isAutoHiding():
+                        container.setFocus()
+    
     def showLogViewer(self):
         """
         Public method to show the Log-Viewer.
--- a/eric6/Utilities/__init__.py	Tue Aug 20 00:37:14 2019 +0300
+++ b/eric6/Utilities/__init__.py	Tue Aug 20 17:07:06 2019 +0200
@@ -32,6 +32,8 @@
 import fnmatch
 import glob
 import getpass
+import ctypes
+import subprocess
 
 
 def __showwarning(message, category, filename, lineno, file=None, line=""):
@@ -1277,6 +1279,70 @@
     return dirs
 
 
+def findVolume(volumeName):
+    """
+    Function to find the directory belonging to a given volume name.
+    
+    @param volumeName name of the volume to search for
+    @type str
+    @return directory path of the given volume name
+    @rtype str
+    """
+    volumeDirectory = None
+    
+    if isWindowsPlatform():
+        # we are on a Windows platform
+        def getVolumeName(diskName):
+            """
+            Local function to determine the volume of a disk or device.
+            
+            Each disk or external device connected to windows has an
+            attribute called "volume name". This function returns the
+            volume name for the given disk/device.
+
+            Code from http://stackoverflow.com/a/12056414
+            """
+            volumeNameBuffer = ctypes.create_unicode_buffer(1024)
+            ctypes.windll.kernel32.GetVolumeInformationW(
+                ctypes.c_wchar_p(diskName), volumeNameBuffer,
+                ctypes.sizeof(volumeNameBuffer), None, None, None, None, 0)
+            return volumeNameBuffer.value
+        
+        #
+        # In certain circumstances, volumes are allocated to USB
+        # storage devices which cause a Windows popup to raise if their
+        # volume contains no media. Wrapping the check in SetErrorMode
+        # with SEM_FAILCRITICALERRORS (1) prevents this popup.
+        #
+        oldMode = ctypes.windll.kernel32.SetErrorMode(1)
+        try:
+            for disk in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
+                dirpath = "{0}:\\".format(disk)
+                if (os.path.exists(dirpath) and
+                        getVolumeName(dirpath) == volumeName):
+                    volumeDirectory = dirpath
+                    break
+        finally:
+            ctypes.windll.kernel32.SetErrorMode(oldMode)
+    else:
+        # we are on a Linux or macOS platform
+        for mountCommand in ["mount", "/sbin/mount", "/usr/sbin/mount"]:
+            try:
+                mountOutput = (subprocess.check_output(mountCommand)
+                               .splitlines())
+                mountedVolumes = [x.split()[2] for x in mountOutput]
+                for volume in mountedVolumes:
+                    if volume.decode("utf-8").endswith(volumeName):
+                        volumeDirectory = volume.decode("utf-8")
+                        break
+                if volumeDirectory:
+                    break
+            except FileNotFoundError:
+                pass
+    
+    return volumeDirectory
+
+
 def getTestFileName(fn):
     """
     Function to build the filename of a unittest file.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/default/chart.svg	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      .ColorScheme-Highlight {
+        color:#3daee9;
+      }
+      </style>
+  </defs>
+ <path 
+     style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 4 3 L 4 9 L 4 10 L 4 15 L 4 16 L 4 17 L 3 17 L 3 18 L 4 18 L 4 19 L 5 19 L 5 18 L 18.292969 18 L 19 18 L 19 17.292969 L 19 17 L 18.707031 17 L 17.958984 16.251953 L 17.130859 15.423828 L 15.130859 13.423828 L 15.126953 13.427734 L 15.121094 13.423828 L 12.998047 15.546875 L 11.703125 14.251953 L 10.875 13.423828 L 8.875 11.423828 L 8.8730469 11.423828 L 7 11.423828 L 6 11.423828 L 5 11.423828 L 5 11 L 5 10 L 6 10 L 7 10 L 8.8730469 10 L 8.875 10 L 10.875 8 L 11.703125 7.171875 L 12.998047 5.8769531 L 15.121094 8 L 15.126953 7.9960938 L 15.130859 8 L 17.130859 6 L 17.958984 5.171875 L 19 4.1308594 L 18.292969 3.4238281 L 17.251953 4.4648438 L 16.423828 5.2929688 L 15.126953 6.5898438 L 13 4.4648438 L 12.998047 4.4667969 L 12.996094 4.4648438 L 11.46875 5.9921875 L 5 5.9921875 L 5 5 L 5 3 L 4 3 z M 5 6 L 11.460938 6 L 10.996094 6.4648438 L 10.167969 7.2929688 L 8.4609375 9 L 7 9 L 6 9 L 5 9 L 5 6 z M 5 12.423828 L 6 12.423828 L 7 12.423828 L 8.4609375 12.423828 L 10.167969 14.130859 L 10.996094 14.958984 L 11.460938 15.423828 L 5 15.423828 L 5 15 L 5 12.423828 z M 15.126953 14.833984 L 16.423828 16.130859 L 17.251953 16.958984 L 17.292969 17 L 5 17 L 5 16 L 5 15.431641 L 11.46875 15.431641 L 12.996094 16.958984 L 12.998047 16.957031 L 13 16.958984 L 15.126953 14.833984 z "
+     class="ColorScheme-Text"
+     />
+</svg>
Binary file eric6/icons/default/circuitPythonDevice.png has changed
Binary file eric6/icons/default/esp32Device.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/default/filemanager.svg	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,241 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="22"
+   viewBox="0 0 22 22"
+   id="svg7925"
+   version="1.1"
+   inkscape:version="0.91 r13725"
+   sodipodi:docname="system-file-manager.svg">
+  <defs
+     id="defs7927">
+    <linearGradient
+       id="linearGradient4274"
+       inkscape:collect="always">
+      <stop
+         id="stop4276"
+         offset="0"
+         style="stop-color:#ffffff;stop-opacity:1;" />
+      <stop
+         id="stop4278"
+         offset="1"
+         style="stop-color:#ffffff;stop-opacity:0.48760331" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4274"
+       id="linearGradient8485"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.69224711,0,0,0.69224711,-264.27155,1668.4354)"
+       x1="390.57144"
+       y1="498.298"
+       x2="442.57144"
+       y2="498.298" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4393"
+       id="linearGradient4399"
+       x1="419.4624"
+       y1="499.23697"
+       x2="432.57144"
+       y2="523.79797"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.34616832,0,0,0.34616832,-141.20459,864.54218)" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4393">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop4395" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0;"
+         offset="1"
+         id="stop4397" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4172-5"
+       id="linearGradient4178"
+       y1="548.88599"
+       y2="495.30789"
+       x2="397.2283"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.34616832,0,0,0.34605565,-133.24271,862.87242)"
+       x1="434.16153" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4172-5">
+      <stop
+         style="stop-color:#127bdc;stop-opacity:1"
+         id="stop4174-6" />
+      <stop
+         offset="1"
+         style="stop-color:#64b4f4;stop-opacity:1"
+         id="stop4176-6" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4227"
+       id="linearGradient4225"
+       gradientUnits="userSpaceOnUse"
+       x1="396.57144"
+       y1="498.798"
+       x2="426.57144"
+       y2="511.798"
+       gradientTransform="matrix(0.34616832,0,0,0.34616832,-133.24271,864.50368)" />
+    <linearGradient
+       id="linearGradient4227"
+       inkscape:collect="always">
+      <stop
+         id="stop4229"
+         offset="0"
+         style="stop-color:#f5f5f5;stop-opacity:1" />
+      <stop
+         id="stop4231"
+         offset="1"
+         style="stop-color:#f9f9f9;stop-opacity:1" />
+    </linearGradient>
+    <linearGradient
+       gradientTransform="matrix(0.34616832,0,0,0.34616832,-133.24271,862.81136)"
+       inkscape:collect="always"
+       xlink:href="#linearGradient4291"
+       id="linearGradient4297"
+       x1="388.57144"
+       y1="487.798"
+       x2="416.57144"
+       y2="507.798"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4291">
+      <stop
+         style="stop-color:#1d5e8c;stop-opacity:1"
+         offset="0"
+         id="stop4293" />
+      <stop
+         style="stop-color:#2675a7;stop-opacity:1"
+         offset="1"
+         id="stop4295" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4274"
+       id="linearGradient8603"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.34616832,0,0,0.34616832,-133.24271,2893.0497)"
+       x1="390.57144"
+       y1="498.298"
+       x2="442.57144"
+       y2="498.298" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4274"
+       id="linearGradient8603-0"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.34616832,0,0,0.34616832,-135.15325,827.3248)"
+       x1="390.57144"
+       y1="498.298"
+       x2="442.57144"
+       y2="498.298" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="14.052007"
+     inkscape:cy="13.482364"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     units="px"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-nodes="true"
+     inkscape:object-nodes="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid8605" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7930">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Livello 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1030.3622)">
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:url(#linearGradient4297);fill-opacity:1"
+       id="rect4180"
+       d="m 1,1042.3622 20,0.501 0,-8.501 -10,0 -1.588488,-2.0013 -8.411512,0 z"
+       sodipodi:nodetypes="ccccccc" />
+    <rect
+       ry="1.0000174"
+       rx="0.9128685"
+       y="1036.3622"
+       x="2"
+       height="5.0000172"
+       width="18"
+       id="rect4223"
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient4225);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:url(#linearGradient4178)"
+       d="m 9,1038.3622 -2,1 -6,0 -0.0895868,1.0011 -5e-7,9.5003 c 0,0.2699 0.244354,0.4954 0.4919395,0.4954 l 19.0105828,0.01 c 0.255387,0.01 0.500065,-0.1978 0.500065,-0.5 L 21,1038.3622 Z"
+       id="rect4113"
+       sodipodi:nodetypes="cccccscccc" />
+    <path
+       inkscape:connector-curvature="0"
+       style="opacity:0.3;fill:#ffffff;fill-opacity:1;fill-rule:evenodd"
+       id="path4224"
+       d="m 1,1039.3622 -0.0895868,1.0011 7.0895868,0 1,-2 -2,0.9989 z"
+       sodipodi:nodetypes="cccccc" />
+    <path
+       inkscape:connector-curvature="0"
+       style="opacity:0.3;fill:#ffffff;fill-opacity:1;fill-rule:evenodd"
+       id="path4196"
+       d="m 9.411512,1032.3622 0.588488,3 11,0 0,-1 -10,0 z"
+       sodipodi:nodetypes="cccccc" />
+    <path
+       style="opacity:0.09899998;fill:url(#linearGradient4399);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 0.9104132,1040.3633 7.0895868,-0 1,-2 12,0 0,11.6563 -10.385049,0 z"
+       id="path4383"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccccc" />
+    <path
+       style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.85;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:url(#linearGradient8603);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="m 2.9121094,1036.3622 c -0.5057292,0 -0.9121094,0.446 -0.9121094,1 l 0,1 c 0,-0.554 0.4063802,-1 0.9121094,-1 l 16.0878906,0 c 0.505729,0 0.912109,0.446 0.912109,1 l 0,-1 c 0,-0.554 -0.40638,-1 -0.912109,-1 z"
+       id="rect8607"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="sscsscsss" />
+    <path
+       style="fill:#000000;color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.3;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;filter-blend-mode:normal;filter-gaussianBlur-deviation:0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+       d="M 20.992188 8 L 20.912109 18.505859 C 20.912109 18.808059 20.667496 19.015859 20.412109 19.005859 L 1.4023438 18.996094 C 1.1547582 18.996094 0.91015625 18.771853 0.91015625 18.501953 L 0.91015625 19.501953 C 0.91015625 19.771853 1.1547582 19.996094 1.4023438 19.996094 L 20.412109 20.005859 C 20.667496 20.015859 20.912109 19.808059 20.912109 19.505859 L 21 8 L 20.992188 8 z "
+       transform="translate(0,1030.3622)"
+       id="path4164" />
+  </g>
+</svg>
Binary file eric6/icons/default/getAs.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/default/linkConnect.svg	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+     style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 18.292969 3 L 14.792969 6.5 L 13.292969 5 L 11.292969 7 L 8 10.292969 L 6 12.292969 L 7.5 13.792969 L 3 18.292969 L 3.7070312 19 L 8.2070312 14.5 L 9.7070312 16 L 11.707031 14 L 15 10.707031 L 17 8.7070312 L 15.5 7.2070312 L 19 3.7070312 L 18.292969 3 z "
+     class="ColorScheme-Text"
+     />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/default/linkDisconnect.svg	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,22 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      .ColorScheme-NegativeText {
+        color:#da4453;
+      }
+      </style>
+  </defs>
+ <path 
+     style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 18.292969 3 L 14.792969 6.5 L 13.292969 5 L 11.292969 7 L 9.5 8.7929688 L 11 10.292969 L 11.707031 11 L 13.207031 12.5 L 15 10.707031 L 17 8.7070312 L 15.5 7.2070312 L 19 3.7070312 L 18.292969 3 z M 8.7929688 9.5 L 8 10.292969 L 6 12.292969 L 7.5 13.792969 L 3 18.292969 L 3.7070312 19 L 8.2070312 14.5 L 9.7070312 16 L 11.707031 14 L 12.5 13.207031 L 11 11.707031 L 10.292969 11 L 8.7929688 9.5 z "
+     class="ColorScheme-Text"
+     />
+    <path
+     style="fill:currentColor;fill-opacity:1;stroke:none" 
+     d="M 14.833984 14 L 14 14.833984 L 15.666016 16.5 L 14 18.166016 L 14.833984 19 L 16.5 17.333984 L 18.166016 19 L 19 18.166016 L 17.333984 16.5 L 19 14.833984 L 18.166016 14 L 16.5 15.666016 L 14.833984 14 z "
+     class="ColorScheme-NegativeText"
+     />
+</svg>
Binary file eric6/icons/default/microbitDevice.png has changed
Binary file eric6/icons/default/micropython.png has changed
Binary file eric6/icons/default/micropython48.png has changed
Binary file eric6/icons/default/putAs.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/default/question.svg	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path 
+    style="fill:currentColor;fill-opacity:1;stroke:none" 
+    d="M 10.96875 3 C 9.21782 3 7.56187 3.4216019 6 4.2636719 L 6.7773438 6.0507812 C 7.5582637 5.6672613 8.2600456 5.3933025 8.8847656 5.2265625 C 9.5177356 5.0598325 10.162692 4.9765625 10.820312 4.9765625 C 11.806753 4.9765625 12.563734 5.1968119 13.089844 5.6386719 C 13.615944 6.0805419 13.878906 6.7136725 13.878906 7.5390625 C 13.878906 7.9809325 13.822111 8.369625 13.707031 8.703125 C 13.591951 9.036605 13.394724 9.369635 13.115234 9.703125 C 12.835754 10.036615 12.247563 10.586476 11.351562 11.353516 C 10.251483 12.356846 9.10046 14.139 9 16 L 11 16 L 10.994141 15.96875 C 10.994141 15.21007 11.125612 14.601138 11.388672 14.142578 C 11.659952 13.675688 12.201805 13.087576 13.015625 12.378906 C 14.010295 11.536836 14.673153 10.903726 15.001953 10.478516 C 15.338993 10.053316 15.589506 9.6041363 15.753906 9.1289062 C 15.918406 8.6536762 16 8.1071944 16 7.4902344 C 16 6.0728944 15.55227 4.97119 14.65625 4.1875 C 13.76024 3.39546 12.53061 3 10.96875 3 z M 9 17 L 9 19 L 11 19 L 11 17 L 9 17 z "
+    class="ColorScheme-Text"
+    />  
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/default/start.svg	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+  <defs id="defs3051">
+    <style type="text/css" id="current-color-scheme">
+      .ColorScheme-Text {
+        color:#232629;
+      }
+      </style>
+  </defs>
+ <path
+    style="fill:currentColor;fill-opacity:1;stroke:none"
+    d="m 4 4 0 14 L 18 11 Z"
+    class="ColorScheme-Text"
+    />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/icons/default/terminal.svg	Tue Aug 20 17:07:06 2019 +0200
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2987" width="16px" height="16px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs2989">
+  <linearGradient id="linearGradient2880" x1="16.143" x2="16.143" y1="4" y2="44" gradientTransform="matrix(.23078 0 0 .15386 2.4612 4.8074)" gradientUnits="userSpaceOnUse">
+   <stop id="stop2225-6-4-7-2" style="stop-color:#fff" offset="0"/>
+   <stop id="stop2229-2-5-5-8" style="stop-color:#fff;stop-opacity:0" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient2883" x1="16.626" x2="20.055" y1="15.298" y2="24.628" gradientTransform="matrix(.30476 0 0 .32156 .68572 1.0807)" gradientUnits="userSpaceOnUse">
+   <stop id="stop2687-1-9-0-2" style="stop-color:#fff" offset="0"/>
+   <stop id="stop2689-5-4-3-1" style="stop-color:#fff;stop-opacity:0" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient2886" x1="16" x2="16" y1="27.045" y2="16" gradientTransform="matrix(.34286 0 0 .36364 -.42808 -.81818)" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient3680-6"/>
+  <linearGradient id="linearGradient3680-6">
+   <stop id="stop3682-4" style="stop-color:#dcdcdc" offset="0"/>
+   <stop id="stop3684-8" style="stop-color:#fff" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient2890" x1="20" x2="20" y1="28" y2="26" gradientTransform="matrix(.375 0 0 .375 -.74978 -.5)" gradientUnits="userSpaceOnUse" xlink:href="#linearGradient3680-6"/>
+  <linearGradient id="linearGradient2894" x1="12.579" x2="12.213" y1="2.9165" y2="47.279" gradientTransform="matrix(.28855 0 0 .25608 1.0743 2.6116)" gradientUnits="userSpaceOnUse">
+   <stop id="stop2240-1-6-7-0" style="stop-color:#fff" offset="0"/>
+   <stop id="stop2242-7-3-7-2" style="stop-color:#fff;stop-opacity:0" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient2897" x1="23.071" x2="23.071" y1="36.047" y2="33.296" gradientTransform="matrix(.33032 0 0 .32624 .074248 1.9649)" gradientUnits="userSpaceOnUse">
+   <stop id="stop2225-6-4-7" style="stop-color:#fff" offset="0"/>
+   <stop id="stop2229-2-5-5" style="stop-color:#fff;stop-opacity:0" offset="1"/>
+  </linearGradient>
+  <radialGradient id="radialGradient2900" cx="7.4957" cy="8.4498" r="20" gradientTransform="matrix(0 .47178 -.86826 -1.9907e-8 15.337 1.0829)" gradientUnits="userSpaceOnUse">
+   <stop id="stop3790-0-0" style="stop-color:#505050" offset="0"/>
+   <stop id="stop3792-0-2" style="stop-color:#141414" offset="1"/>
+  </radialGradient>
+  <linearGradient id="linearGradient2902" x1="16.143" x2="16.143" y1="4" y2="44" gradientTransform="matrix(.28207 0 0 .20514 1.2304 3.5766)" gradientUnits="userSpaceOnUse">
+   <stop id="stop3796-3-0" style="stop-color:#323232" offset="0"/>
+   <stop id="stop3798-8-6" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient2983" x1="24" x2="24" y1="7.96" y2="43.865" gradientTransform="matrix(.3333 0 0 .32429 .00075427 .06858)" gradientUnits="userSpaceOnUse">
+   <stop id="stop4324-9-7" style="stop-color:#f0f0f0" offset="0"/>
+   <stop id="stop2860-4-4" style="stop-color:#d7d7d8" offset=".085525"/>
+   <stop id="stop2862-5-9" style="stop-color:#b2b2b3" offset=".92166"/>
+   <stop id="stop4326-1-1" style="stop-color:#979798" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient2985" x1="10.014" x2="10.014" y1="44.96" y2="2.8765" gradientTransform="matrix(.31912 0 0 .29298 .34112 1.4648)" gradientUnits="userSpaceOnUse">
+   <stop id="stop4334-7-6" style="stop-color:#595959" offset="0"/>
+   <stop id="stop4336-8-0" style="stop-color:#b3b3b3" offset="1"/>
+  </linearGradient>
+ </defs>
+ <metadata id="metadata2992">
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title/>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+  <rect id="rect2551-5-8" x=".50107" y="2.5011" width="14.998" height="11.998" rx="1" ry="1" style="fill-rule:evenodd;fill:url(#linearGradient2983);stroke-linecap:round;stroke-linejoin:round;stroke-width:1.0021;stroke:url(#linearGradient2985)"/>
+  <rect id="rect1314-3-3" x="2.5" y="4.5" width="11" height="8" rx="0" ry="0" style="color:#000000;fill:url(#radialGradient2900);stroke-linecap:round;stroke-linejoin:round;stroke:url(#linearGradient2902)"/>
+  <rect id="rect2221-3-8" x="1.501" y="3.5011" width="12.998" height="9.9979" rx="0" ry="0" style="fill:none;opacity:.4;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.0021;stroke:url(#linearGradient2897)"/>
+  <rect id="rect2556-8-5" x="1.5019" y="3.5019" width="12.996" height="9.9963" style="fill:none;opacity:.8;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.0037;stroke:url(#linearGradient2894)"/>
+  <path id="path3651-2" d="m9.0002 10v-0.75h-3v0.75h3z" style="fill:url(#linearGradient2890)"/>
+  <path id="path3653-7" d="m3.6862 9-0.68571-0.72727 1.5429-1.2727-1.5429-1.2727 0.68571-0.72727 2.3143 2-2.3143 2z" style="fill:url(#linearGradient2886)"/>
+  <path id="path3333-3-0" d="m1.6667 3c-0.36824 0-0.66667 0.31488-0.66667 0.70342v5.145c9.545e-4 0.043283 0.018837 0.084214 0.049602 0.11286 0.030769 0.028643 0.071495 0.04238 0.1123 0.037874l13.714-2.2911c0.070547-0.011738 0.12282-0.075385 0.12381-0.15073v-2.8539c0-0.38854-0.29843-0.70342-0.66667-0.70342h-12.667z" style="fill-rule:evenodd;fill:url(#linearGradient2883);opacity:.2"/>
+  <rect id="rect1314-3-3-9" x="3.5" y="5.5" width="9" height="6" rx="0" ry="0" style="color:#000000;fill:none;opacity:.1;stroke-linecap:round;stroke-linejoin:round;stroke:url(#linearGradient2880)"/>
+ </g>
+</svg>

eric ide

mercurial