MicroPython: continued implementing the file manager widget. micropython

Thu, 25 Jul 2019 19:55:40 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 25 Jul 2019 19:55:40 +0200
branch
micropython
changeset 7083
217862c28319
parent 7082
ec199ef0cfc6
child 7084
3eddfc540614

MicroPython: continued implementing the file manager widget.

eric6.e4p file | annotate | diff | comparison | revisions
eric6/E5Gui/E5FileSaveConfirmDialog.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonFileManagerWidget.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonFileSystem.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonProgressInfoDialog.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonProgressInfoDialog.ui file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonSerialPort.py file | annotate | diff | comparison | revisions
--- a/eric6.e4p	Wed Jul 24 20:12:19 2019 +0200
+++ b/eric6.e4p	Thu Jul 25 19:55:40 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>
@@ -461,6 +462,7 @@
     <Source>eric6/MicroPython/MicroPythonFileSystem.py</Source>
     <Source>eric6/MicroPython/MicroPythonFileSystemUtilities.py</Source>
     <Source>eric6/MicroPython/MicroPythonGraphWidget.py</Source>
+    <Source>eric6/MicroPython/MicroPythonProgressInfoDialog.py</Source>
     <Source>eric6/MicroPython/MicroPythonReplWidget.py</Source>
     <Source>eric6/MicroPython/MicroPythonSerialPort.py</Source>
     <Source>eric6/MicroPython/MicrobitDevices.py</Source>
@@ -1845,6 +1847,7 @@
     <Form>eric6/HexEdit/HexEditSearchWidget.ui</Form>
     <Form>eric6/IconEditor/IconSizeDialog.ui</Form>
     <Form>eric6/MicroPython/MicroPythonFileManagerWidget.ui</Form>
+    <Form>eric6/MicroPython/MicroPythonProgressInfoDialog.ui</Form>
     <Form>eric6/MicroPython/MicroPythonReplWidget.ui</Form>
     <Form>eric6/MultiProject/AddProjectDialog.ui</Form>
     <Form>eric6/MultiProject/PropertiesDialog.ui</Form>
@@ -2305,14 +2308,14 @@
     <Other>docs/THANKS</Other>
     <Other>docs/changelog</Other>
     <Other>eric6.e4p</Other>
+    <Other>eric6/APIs/Python/zope-2.10.7.api</Other>
+    <Other>eric6/APIs/Python/zope-2.11.2.api</Other>
+    <Other>eric6/APIs/Python/zope-3.3.1.api</Other>
     <Other>eric6/APIs/Python3/PyQt4.bas</Other>
     <Other>eric6/APIs/Python3/PyQt5.bas</Other>
     <Other>eric6/APIs/Python3/QScintilla2.bas</Other>
     <Other>eric6/APIs/Python3/eric6.api</Other>
     <Other>eric6/APIs/Python3/eric6.bas</Other>
-    <Other>eric6/APIs/Python/zope-2.10.7.api</Other>
-    <Other>eric6/APIs/Python/zope-2.11.2.api</Other>
-    <Other>eric6/APIs/Python/zope-3.3.1.api</Other>
     <Other>eric6/APIs/QSS/qss.api</Other>
     <Other>eric6/APIs/Ruby/Ruby-1.8.7.api</Other>
     <Other>eric6/APIs/Ruby/Ruby-1.8.7.bas</Other>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/E5Gui/E5FileSaveConfirmDialog.py	Thu Jul 25 19:55:40 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 message message to be shown
+        @type str
+        @param title title for the dialog
+        @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 message message to be shown
+    @type str
+    @param title title for the dialog
+    @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()
--- a/eric6/MicroPython/MicroPythonFileManagerWidget.py	Wed Jul 24 20:12:19 2019 +0200
+++ b/eric6/MicroPython/MicroPythonFileManagerWidget.py	Thu Jul 25 19:55:40 2019 +0200
@@ -18,6 +18,7 @@
 
 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
@@ -62,6 +63,7 @@
         self.deviceFileTreeWidget.header().setSortIndicator(
             0, Qt.AscendingOrder)
         
+        self.__progressInfoDialog = None
         self.__fileManager = MicroPythonFileManager(port, self)
         
         self.__fileManager.longListFiles.connect(self.__handleLongListFiles)
@@ -71,6 +73,8 @@
         self.__fileManager.getFileDone.connect(self.__handleGetDone)
         self.__fileManager.rsyncDone.connect(self.__handleRsyncDone)
         self.__fileManager.rsyncMessages.connect(self.__handleRsyncMessages)
+        self.__fileManager.rsyncProgressMessage.connect(
+            self.__handleRsyncProgressMessage)
         self.__fileManager.removeDirectoryDone.connect(self.__newDeviceList)
         self.__fileManager.createDirectoryDone.connect(self.__newDeviceList)
         self.__fileManager.deleteFileDone.connect(self.__newDeviceList)
@@ -89,6 +93,8 @@
         self.__localMenu = QMenu(self)
         self.__localMenu.addAction(self.tr("Change Directory"),
                                    self.__changeLocalDirectory)
+        # TODO: add some more local entries
+        # TODO: add entry to reload
         
         self.__deviceMenu = QMenu(self)
         self.__deviceMenu.addAction(
@@ -103,6 +109,8 @@
         self.__devDelFileAct = self.__deviceMenu.addAction(
             self.tr("Delete File"), self.__deleteDeviceFile)
         self.__deviceMenu.addSeparator()
+        # TODO: add entry to reload
+        self.__deviceMenu.addSeparator()
         self.__deviceMenu.addAction(
             self.tr("Synchronize Time"), self.__synchronizeTime)
         self.__deviceMenu.addAction(
@@ -319,24 +327,18 @@
                 # it is really a file
                 if self.__isFileInList(filename, self.deviceFileTreeWidget):
                     # ask for overwrite permission
-                    ok = E5MessageBox.yesNo(
-                        self,
-                        self.tr("Copy File to Device"),
-                        self.tr("<p>The file <b>{0}</b> exists on the"
-                                " connected device. Overwrite it?</p>")
-                        .format(filename)
-                    )
-                    if ok:
+                    # TODO: test this
+                    action, resultFilename = confirmOverwrite(
+                        filename, 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)
+                    else:
                         deviceFilename = filename
-                    else:
-                        deviceFilename, ok = QInputDialog.getText(
-                            self,
-                            self.tr("Copy File to Device"),
-                            self.tr("Enter a new name:"),
-                            QLineEdit.Normal,
-                            filename)
-                        if not ok or not bool(deviceFilename):
-                            return
                 else:
                     deviceFilename = filename
                 
@@ -357,30 +359,26 @@
                 # it is really a file
                 if self.__isFileInList(filename, self.localFileTreeWidget):
                     # ask for overwrite permission
-                    ok = E5MessageBox.yesNo(
-                        self,
-                        self.tr("Copy File from Device"),
-                        self.tr("<p>The file <b>{0}</b> exists locally."
-                                " Overwrite it?</p>")
-                        .format(filename)
-                    )
-                    if ok:
+                    # TODO: test this
+                    action, resultFilename = confirmOverwrite(
+                        filename, self.tr("Copy File from Device"),
+                        self.tr("The given file exists already."),
+                        True, self)
+                    if action == "cancel":
+                        return
+                    elif action == "rename":
+                        localFilename = resultFilename
+                    else:
                         localFilename = filename
-                    else:
-                        localFilename, ok = QInputDialog.getText(
-                            self,
-                            self.tr("Copy File from Device"),
-                            self.tr("Enter a new name:"),
-                            QLineEdit.Normal,
-                            filename)
-                        if not ok or not bool(localFilename):
-                            return
                 else:
                     localFilename = filename
                 
+                if not os.path.isabs(localFilename):
+                    localFilename = os.path.join(self.localCwd.text(),
+                                                 localFilename)
                 self.__fileManager.get(
                     os.path.join(self.deviceCwd.text(), filename),
-                    os.path.join(self.localCwd.text(), localFilename)
+                    localFilename
                 )
     
     @pyqtSlot(str, str)
@@ -395,6 +393,7 @@
         """
         self.__listLocalFiles(self.localCwd.text())
     
+    # TODO: test this
     @pyqtSlot()
     def on_syncButton_clicked(self):
         """
@@ -436,6 +435,16 @@
             )
         )
     
+    @pyqtSlot(str)
+    def __handleRsyncProgressMessage(self, message):
+        """
+        Private slot handling progress messages sent by the file manager.
+        """
+        # TODO: not implemented yet
+        #   create and open the info dialog, if it is None
+        #   connect to the dialog finished(int) signal to set the memeber variable to None
+        #   add messages to dialog
+    
     @pyqtSlot()
     def __newDeviceList(self):
         """
@@ -462,16 +471,18 @@
         """
         Private slot to change the local directory.
         """
-        path, ok = E5PathPickerDialog.getPath(
+        dirPath, ok = E5PathPickerDialog.getPath(
             self,
             self.tr("Change Directory"),
             self.tr("Select Directory"),
             E5PathPickerModes.DirectoryShowFilesMode,
             defaultDirectory=self.localCwd.text(),
         )
-        if ok and path:
-            self.localCwd.setText(path)
-            self.__listLocalFiles(path)
+        if ok and dirPath:
+            if not os.path.isabs(dirPath):
+                dirPath = os.path.join(self.localCwd.text(), dirPath)
+            self.localCwd.setText(dirPath)
+            self.__listLocalFiles(dirPath)
     
     ##################################################################
     ## Context menu methods for the device files below
@@ -510,12 +521,15 @@
         dirPath, ok = QInputDialog.getText(
             self,
             self.tr("Change Directory"),
-            self.tr("Enter the full directory path on the device:"),
+            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)
     
+    # TODO: test this
     @pyqtSlot()
     def __createDeviceDirectory(self):
         """
@@ -529,6 +543,7 @@
         if ok and dirPath:
             self.__fileManager.mkdir(dirPath)
     
+    # TODO: test this
     @pyqtSlot()
     def __deleteDeviceDirectory(self):
         """
@@ -537,6 +552,7 @@
         if bool(len(self.deviceFileTreeWidget.selectedItems())):
             name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
             dirname = self.deviceCwd.text() + "/" + name[:-1]
+            # TODO: add confirmation
             self.__fileManager.rmdir(dirname)
     
     @pyqtSlot()
@@ -548,6 +564,7 @@
         if bool(len(self.deviceFileTreeWidget.selectedItems())):
             name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
             dirname = self.deviceCwd.text() + "/" + name[:-1]
+            # TODO: add confirmation
             self.__fileManager.rmdir(dirname, recursive=True)
     
     @pyqtSlot()
@@ -558,6 +575,7 @@
         if bool(len(self.deviceFileTreeWidget.selectedItems())):
             name = self.deviceFileTreeWidget.selectedItems()[0].text(0)
             filename = self.deviceCwd.text() + "/" + name
+            # TODO: add confirmation
             self.__fileManager.delete(filename)
     
     @pyqtSlot()
@@ -634,7 +652,7 @@
             msg += "<table>"
             for key, value in versionInfo.items():
                 msg += "<tr><td><b>{0}</b></td><td>{1}</td></tr>".format(
-                    key, value)
+                    key.capitalize(), value)
             msg += "</table>"
         else:
             msg = self.tr("No version information available.")
--- a/eric6/MicroPython/MicroPythonFileSystem.py	Wed Jul 24 20:12:19 2019 +0200
+++ b/eric6/MicroPython/MicroPythonFileSystem.py	Thu Jul 25 19:55:40 2019 +0200
@@ -329,7 +329,7 @@
         assert name
         
         commands = [
-            "import os"
+            "import os",
             "\n".join([
                 "def remove_file(name, recursive=False, force=False):",
                 "    try:",
@@ -413,6 +413,9 @@
         
         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)
@@ -478,6 +481,9 @@
             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
@@ -602,6 +608,8 @@
     @signal rsyncDone(localName, deviceName) emitted after the rsync operation
         has been completed
     @signal rsyncMessages(list) emitted with a list of messages
+    @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 synchTimeDone() emitted after the time was synchronizde to the
@@ -622,6 +630,7 @@
     deleteFileDone = pyqtSignal(str)
     rsyncDone = pyqtSignal(str, str)
     rsyncMessages = pyqtSignal(list)
+    rsyncProgressMessage = pyqtSignal(str)
     removeDirectoryDone = pyqtSignal()
     createDirectoryDone = pyqtSignal()
     synchTimeDone = pyqtSignal()
@@ -770,10 +779,11 @@
         @return tuple containing a list of messages and list of errors
         @rtype tuple of (list of str, list of str)
         """
+        # TODO: get rid of messages and replace by rsyncProgressMessage signal
         messages = []
         errors = []
         
-        if not os.isdir(hostDirectory):
+        if not os.path.isdir(hostDirectory):
             return ([], [self.tr(
                 "The given name '{0}' is not a directory or does not exist.")
                 .format(hostDirectory)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonProgressInfoDialog.py	Thu Jul 25 19:55:40 2019 +0200
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to show progress messages.
+"""
+
+from PyQt5.QtCore import pyqtSlot
+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
+        """
+        # TODO: not implemented yet
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric6/MicroPython/MicroPythonProgressInfoDialog.ui	Thu Jul 25 19:55:40 2019 +0200
@@ -0,0 +1,80 @@
+<?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="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>
--- a/eric6/MicroPython/MicroPythonSerialPort.py	Wed Jul 24 20:12:19 2019 +0200
+++ b/eric6/MicroPython/MicroPythonSerialPort.py	Thu Jul 25 19:55:40 2019 +0200
@@ -10,7 +10,7 @@
 
 from __future__ import unicode_literals
 
-from PyQt5.QtCore import QIODevice, QTime, QCoreApplication
+from PyQt5.QtCore import QIODevice, QTime, QCoreApplication, QEventLoop
 from PyQt5.QtSerialPort import QSerialPort
 
 
@@ -111,7 +111,7 @@
         t = QTime()
         t.start()
         while True:
-            QCoreApplication.processEvents()
+            QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
             c = bytes(self.read(1))
             if c:
                 data += c

eric ide

mercurial