--- a/eric6/MicroPython/MicroPythonFileManagerWidget.py Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManagerWidget.py Wed Jul 24 20:12:19 2019 +0200 @@ -11,10 +11,13 @@ import os -from PyQt5.QtCore import pyqtSlot, Qt -from PyQt5.QtWidgets import QWidget, QTreeWidgetItem, QHeaderView +from PyQt5.QtCore import pyqtSlot, Qt, QPoint +from PyQt5.QtWidgets import ( + QWidget, QTreeWidgetItem, QHeaderView, QMenu, QInputDialog, QLineEdit +) -from E5Gui import E5MessageBox +from E5Gui import E5MessageBox, E5PathPickerDialog +from E5Gui.E5PathPicker import E5PathPickerModes from E5Gui.E5Application import e5App from .Ui_MicroPythonFileManagerWidget import Ui_MicroPythonFileManagerWidget @@ -64,34 +67,49 @@ self.__fileManager.longListFiles.connect(self.__handleLongListFiles) self.__fileManager.currentDir.connect(self.__handleCurrentDir) self.__fileManager.currentDirChanged.connect(self.__handleCurrentDir) - self.__fileManager.putFileDone.connect(self.__handlePutDone) + self.__fileManager.putFileDone.connect(self.__newDeviceList) self.__fileManager.getFileDone.connect(self.__handleGetDone) self.__fileManager.rsyncDone.connect(self.__handleRsyncDone) self.__fileManager.rsyncMessages.connect(self.__handleRsyncMessages) + self.__fileManager.removeDirectoryDone.connect(self.__newDeviceList) + self.__fileManager.createDirectoryDone.connect(self.__newDeviceList) + self.__fileManager.deleteFileDone.connect(self.__newDeviceList) + self.__fileManager.synchTimeDone.connect(self.__timeSynchronized) + self.__fileManager.showTimeDone.connect(self.__deviceTimeReceived) + self.__fileManager.showVersionDone.connect( + self.__deviceVersionReceived) - self.__fileManager.longListFilesFailed.connect(self.__handleError) - self.__fileManager.currentDirFailed.connect(self.__handleError) - self.__fileManager.currentDirChangeFailed.connect(self.__handleError) - self.__fileManager.putFileFailed.connect(self.__handleError) - self.__fileManager.getFileFailed.connect(self.__handleError) - self.__fileManager.rsyncFailed.connect(self.__handleError) + 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) - # TODO: add context menus for panes (separate menus) - # local pane: - # Change Directory - # - # device pane: - # Change Directory - # Create Directory - # Delete Directory - # Delete Directory Tree (= recursive delete) - # ---------------------------- - # Delete File - # ---------------------------- - # Synchronize Time - # Show Time - # ---------------------------- - # Show Version + self.__deviceMenu = QMenu(self) + self.__deviceMenu.addAction( + self.tr("Change Directory"), self.__changeDeviceDirectory) + self.__deviceMenu.addAction( + self.tr("Create Directory"), self.__createDeviceDirectory) + 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() + self.__deviceMenu.addAction( + self.tr("Synchronize Time"), self.__synchronizeTime) + self.__deviceMenu.addAction( + self.tr("Show Time"), self.__showDeviceTime) + self.__deviceMenu.addSeparator() + self.__deviceMenu.addAction( + self.tr("Show Version"), self.__showDeviceVersion) def start(self): """ @@ -117,11 +135,13 @@ """ self.__fileManager.disconnect() - @pyqtSlot(str) - def __handleError(self, error): + @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 """ @@ -129,7 +149,8 @@ self, self.tr("Error handling device"), self.tr("<p>There was an error communicating with the connected" - " device.</p><p>Message: {0}</p>").format(error)) + " device.</p><p>Method: {0}</p><p>Message: {1}</p>") + .format(method, error)) @pyqtSlot(str) def __handleCurrentDir(self, dirname): @@ -305,13 +326,23 @@ " connected device. Overwrite it?</p>") .format(filename) ) - if not ok: - return - # TODO: allow to rename the new file + if ok: + 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 self.__fileManager.put( os.path.join(self.localCwd.text(), filename), - os.path.join(self.deviceCwd.text(), filename) + os.path.join(self.deviceCwd.text(), deviceFilename) ) @pyqtSlot() @@ -333,28 +364,26 @@ " Overwrite it?</p>") .format(filename) ) - if not ok: - return - # TODO: allow to rename the new file + if ok: + 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 self.__fileManager.get( os.path.join(self.deviceCwd.text(), filename), - os.path.join(self.localCwd.text(), filename) + os.path.join(self.localCwd.text(), localFilename) ) @pyqtSlot(str, str) - def __handlePutDone(self, localFile, deviceFile): - """ - Private slot handling a successful copy of a file to the device. - - @param localFile name of the local file - @type str - @param deviceFile name of the file on the device - @type str - """ - self.__fileManager.lls(self.deviceCwd.text()) - - @pyqtSlot(str, str) def __handleGetDone(self, deviceFile, localFile): """ Private slot handling a successful copy of a file from the device. @@ -406,3 +435,210 @@ "</li><li>".join(messages) ) ) + + @pyqtSlot() + def __newDeviceList(self): + """ + Private slot to initiate a new long list of the device directory. + """ + self.__fileManager.lls(self.deviceCwd.text()) + + ################################################################## + ## 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 + """ + self.__localMenu.exec_(self.localFileTreeWidget.mapToGlobal(pos)) + + @pyqtSlot() + def __changeLocalDirectory(self): + """ + Private slot to change the local directory. + """ + path, 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) + + ################################################################## + ## 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 + 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. + """ + dirPath, ok = QInputDialog.getText( + self, + self.tr("Change Directory"), + self.tr("Enter the full directory path on the device:"), + QLineEdit.Normal, + self.deviceCwd.text()) + if ok and dirPath: + self.__fileManager.cd(dirPath) + + @pyqtSlot() + def __createDeviceDirectory(self): + """ + Private slot to create a directory on the device. + """ + 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 bool(len(self.deviceFileTreeWidget.selectedItems())): + name = self.deviceFileTreeWidget.selectedItems()[0].text(0) + dirname = self.deviceCwd.text() + "/" + name[:-1] + self.__fileManager.rmdir(dirname) + + @pyqtSlot() + def __deleteDeviceDirectoryTree(self): + """ + Private slot to delete a directory and all its subdirectories + recursively. + """ + if bool(len(self.deviceFileTreeWidget.selectedItems())): + name = self.deviceFileTreeWidget.selectedItems()[0].text(0) + dirname = self.deviceCwd.text() + "/" + name[:-1] + self.__fileManager.rmdir(dirname, recursive=True) + + @pyqtSlot() + def __deleteDeviceFile(self): + """ + Private slot to delete a file + """ + if bool(len(self.deviceFileTreeWidget.selectedItems())): + name = self.deviceFileTreeWidget.selectedItems()[0].text(0) + filename = self.deviceCwd.text() + "/" + name + self.__fileManager.delete(filename) + + @pyqtSlot() + def __synchronizeTime(self): + """ + Private slot to synchronize the local time to the device. + """ + self.__fileManager.synchronizeTime() + + @pyqtSlot() + def __timeSynchronized(self): + """ + Private slot handling the successful syncronization of the time. + """ + E5MessageBox.information( + self, + self.tr("Synchronize Time"), + self.tr("The time of the connected device was synchronized with" + " the local time.")) + + @pyqtSlot() + def __showDeviceTime(self): + """ + Private slot to show the date and time of the connected device. + """ + self.__fileManager.showTime() + + @pyqtSlot(str) + def __deviceTimeReceived(self, dateTimeString): + """ + Private slot handling the receipt of the device date and time. + + @param dateTimeString string containg the date and time of the device + @type str + """ + try: + date, time = dateTimeString.strip().split(None, 1) + msg = 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: + msg = self.tr( + "<h3>Device Date and Time</h3>" + "<p>{0}</p>" + ).format(dateTimeString.strip()) + E5MessageBox.information( + self, + self.tr("Device Date and Time"), + msg) + + @pyqtSlot() + def __showDeviceVersion(self): + """ + Private slot to show some version info about MicroPython of the device. + """ + self.__fileManager.showVersion() + + @pyqtSlot(dict) + def __deviceVersionReceived(self, versionInfo): + """ + Private slot handling the receipt of the version info. + + @param versionInfo dictionary containing the version information + @type dict + """ + 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, value) + msg += "</table>" + else: + msg = self.tr("No version information available.") + E5MessageBox.information( + self, + self.tr("Device Version Information"), + msg)