Thu, 25 Jul 2019 19:55:40 +0200
MicroPython: continued implementing the file manager widget.
--- 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