Wed, 24 Jul 2019 20:12:19 +0200
MicroPython: continued implementing the file manager widget.
--- a/eric6/MicroPython/CircuitPythonDevices.py Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/CircuitPythonDevices.py Wed Jul 24 20:12:19 2019 +0200 @@ -135,20 +135,24 @@ path = '{0}:\\'.format(disk) if (os.path.exists(path) and getVolumeName(path) == 'CIRCUITPY'): - return path + deviceDirectory = path + break finally: ctypes.windll.kernel32.SetErrorMode(oldMode) else: # we are on a Linux or macOS platform - for mountCommand in ['mount', '/sbin/mount']: + for mountCommand in ['mount', '/sbin/mount', '/usr/sbin/mount']: try: mountOutput = check_output(mountCommand).splitlines() mountedVolumes = [x.split()[2] for x in mountOutput] for volume in mountedVolumes: if volume.endswith(b'CIRCUITPY'): deviceDirectory = volume.decode('utf-8') + break + if deviceDirectory: + break except FileNotFoundError: - next + pass if deviceDirectory: return deviceDirectory
--- a/eric6/MicroPython/MicroPythonDevices.py Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicroPythonDevices.py Wed Jul 24 20:12:19 2019 +0200 @@ -299,3 +299,12 @@ Public slot handling a data floof from the device. """ pass + + def addActions(self, menu): + """ + Public method to add device specific entries to the given menu. + + @param menu reference to the context menu + @type QMenu + """ + pass
--- 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)
--- a/eric6/MicroPython/MicroPythonFileManagerWidget.ui Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileManagerWidget.ui Wed Jul 24 20:12:19 2019 +0200 @@ -39,6 +39,9 @@ </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> @@ -129,6 +132,9 @@ </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>
--- a/eric6/MicroPython/MicroPythonFileSystem.py Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicroPythonFileSystem.py Wed Jul 24 20:12:19 2019 +0200 @@ -74,10 +74,13 @@ """ Private method to switch the connected device to 'raw' mode. - Note: switching to raw mode is done with synchroneous writes. + Note: switching to raw mode is done with synchronous writes. + + @return flag indicating success + @@rtype bool """ if not self.__serial: - return + return False rawReplMessage = b"raw REPL; CTRL-B to exit\r\n" softRebootMessage = b"soft reboot\r\n" @@ -92,16 +95,25 @@ 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(): + return False self.__serial.write(b"\x04") # send CTRL-D to soft reset self.__serial.readUntil(softRebootMessage) + if self.__serial.hasTimedOut(): + return False # some MicroPython devices seem to need to be convinced in some # special way data = self.__serial.readUntil(rawReplMessage) + if self.__serial.hasTimedOut(): + return False if not data.endswith(rawReplMessage): self.__serial.write(b"\r\x01") # send CTRL-A again self.__serial.readUntil(rawReplMessage) + if self.__serial.hasTimedOut(): + return False self.__serial.readAll() # read all data and discard it + return True def __rawOff(self): """ @@ -128,7 +140,13 @@ result = bytearray() err = b"" - self.__rawOn() + ok = self.__rawOn() + if not ok: + return ( + b"", + b"Could not switch to raw mode. Is the device switched on?" + ) + QThread.msleep(10) for command in commands: if command: @@ -136,9 +154,14 @@ self.__serial.write(commandBytes + b"\x04") # read until prompt response = self.__serial.readUntil(b"\x04>") - # split stdout, stderr - out, err = response[2:-2].split(b"\x04") - result += out + if self.__serial.hasTimedOut(): + return b"", b"Timeout while processing commands." + if b"\x04" in response[2:-2]: + # split stdout, stderr + out, err = response[2:-2].split(b"\x04") + result += out + else: + err = b"invalid response received: " + response if err: return b"", err QThread.msleep(10) @@ -511,21 +534,30 @@ " try:", # Pyboard (it doesn't have machine.RTC()) " import pyb", " rtc = pyb.RTC()", - " rtc.datetime(rtc_time)", + " clock_time = rtc_time[:6] + (rtc_time[6] + 1, 0)", + " rtc.datetime(clock_time)", " except:", " try:", " import machine", " rtc = machine.RTC()", - " try:", # ESP8266 uses rtc.datetime() - " rtc.datetime(rtc_time)", + " try:", # ESP8266 may use rtc.datetime() + " clock_time = rtc_time[:6] +" + " (rtc_time[6] + 1, 0)", + " rtc.datetime(clock_time)", " except:", # ESP32 uses rtc.init() - " rtc.init(rtc_time)", + " rtc.init(rtc_time[:6])", " except:", - " pass", + " try:", + " import rtc, time", + " clock=rtc.RTC()", + " clock.datetime = time.struct_time(rtc_time +" + " (-1, -1))", + " except:", + " pass", ]), "set_time({0})".format((now.tm_year, now.tm_mon, now.tm_mday, - now.tm_wday + 1, now.tm_hour, now.tm_min, - now.tm_sec, 0)) + now.tm_hour, now.tm_min, now.tm_sec, + now.tm_wday)) ] out, err = self.__execute(commands) if err: @@ -570,21 +602,17 @@ @signal rsyncDone(localName, deviceName) emitted after the rsync operation has been completed @signal rsyncMessages(list) emitted with a list of messages + @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 + device + @signal showTimeDone(dateTime) emitted after the date and time was fetched + from the connected device + @signal showVersionDone(versionInfo) emitted after the version information + was fetched from the connected device - @signal longListFilesFailed(exc) emitted with a failure message to indicate - a failed long listing operation - @signal currentDirFailed(exc) emitted with a failure message to indicate - that the current directory is not available - @signal currentDirChangeFailed(exc) emitted with a failure message to - indicate that the current directory could not be changed - @signal getFileFailed(exc) emitted with a failure message to indicate that - the file could not be fetched - @signal putFileFailed(exc) emitted with a failure message to indicate that - the file could not be copied - @signal deleteFileFailed(exc) emitted with a failure message to indicate - that the file could not be deleted on the device - @signal rsyncFailed(exc) emitted with a failure message to indicate that - the rsync operation could not be completed + @signal error(exc) emitted with a failure message to indicate a failure + during the most recent operation """ longListFiles = pyqtSignal(tuple) currentDir = pyqtSignal(str) @@ -594,14 +622,13 @@ deleteFileDone = pyqtSignal(str) rsyncDone = pyqtSignal(str, str) rsyncMessages = pyqtSignal(list) + removeDirectoryDone = pyqtSignal() + createDirectoryDone = pyqtSignal() + synchTimeDone = pyqtSignal() + showTimeDone = pyqtSignal(str) + showVersionDone = pyqtSignal(dict) - longListFilesFailed = pyqtSignal(str) - currentDirFailed = pyqtSignal(str) - currentDirChangeFailed = pyqtSignal(str) - getFileFailed = pyqtSignal(str) - putFileFailed = pyqtSignal(str) - deleteFileFailed = pyqtSignal(str) - rsyncFailed = pyqtSignal(str) + error = pyqtSignal(str, str) def __init__(self, port, parent=None): """ @@ -650,7 +677,7 @@ name, (mode, size, time) in filesList] self.longListFiles.emit(tuple(result)) except Exception as exc: - self.longListFilesFailed.emit(str(exc)) + self.error.emit("lls", str(exc)) @pyqtSlot() def pwd(self): @@ -661,7 +688,7 @@ pwd = self.__fs.pwd() self.currentDir.emit(pwd) except Exception as exc: - self.currentDirFailed.emit(str(exc)) + self.error.emit("pwd", str(exc)) @pyqtSlot(str) def cd(self, dirname): @@ -675,7 +702,7 @@ self.__fs.cd(dirname) self.currentDirChanged.emit(dirname) except Exception as exc: - self.currentDirChangeFailed.emit(str(exc)) + self.error.emit("cd", str(exc)) @pyqtSlot(str) @pyqtSlot(str, str) @@ -696,7 +723,7 @@ self.__fs.get(deviceFileName, hostFileName) self.getFileDone.emit(deviceFileName, hostFileName) except Exception as exc: - self.getFileFailed.emit(str(exc)) + self.error.emit("get", str(exc)) @pyqtSlot(str) @pyqtSlot(str, str) @@ -713,7 +740,7 @@ self.__fs.put(hostFileName, deviceFileName) self.putFileDone.emit(hostFileName, deviceFileName) except Exception as exc: - self.putFileFailed.emit(str(exc)) + self.error.emit("put", str(exc)) @pyqtSlot(str) def delete(self, deviceFileName): @@ -727,7 +754,7 @@ self.__fs.rm(deviceFileName) self.deleteFileDone.emit(deviceFileName) except Exception as exc: - self.deleteFileFailed.emit(str(exc)) + self.error.emit("delete", str(exc)) def __rsync(self, hostDirectory, deviceDirectory, mirror=True): """ @@ -841,9 +868,11 @@ return messages, errors + @pyqtSlot(str, str) + @pyqtSlot(str, str, bool) def rsync(self, hostDirectory, deviceDirectory, mirror=True): """ - Public method to synchronize a local directory to the device. + Public slot to synchronize a local directory to the device. @param hostDirectory name of the local directory @type str @@ -856,9 +885,82 @@ messages, errors = self.__rsync(hostDirectory, deviceDirectory, mirror=mirror) if errors: - self.rsyncFailed.emit("\n".join(errors)) + self.error.emit("rsync", "\n".join(errors)) if messages: self.rsyncMessages.emit(messages) 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.__fs.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.__fs.rmrf(dirname, recursive=True, force=True) + else: + self.__fs.rmdir(dirname) + self.removeDirectoryDone.emit() + except Exception as exc: + self.error.emit("rmdir", str(exc)) + + ################################################################## + ## some non-filesystem related methods below + ################################################################## + + @pyqtSlot() + def synchronizeTime(self): + """ + Public slot to set the time of the connected device to the local + computer's time. + """ + try: + self.__fs.syncTime() + self.synchTimeDone.emit() + except Exception as exc: + self.error.emit("rmdir", str(exc)) + + @pyqtSlot() + def showTime(self): + """ + Public slot to get the current date and time of the device. + """ + try: + dt = self.__fs.showTime() + self.showTimeDone.emit(dt) + except Exception as exc: + self.error.emit("showTime", str(exc)) + + @pyqtSlot() + def showVersion(self): + """ + Public slot to get the version info for the MicroPython run by the + connected device. + """ + try: + versionInfo = self.__fs.version() + self.showVersionDone.emit(versionInfo) + except Exception as exc: + self.error.emit("showVersion", str(exc))
--- a/eric6/MicroPython/MicroPythonReplWidget.py Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.py Wed Jul 24 20:12:19 2019 +0200 @@ -218,8 +218,6 @@ self.__populateDeviceTypeComboBox() - self.replEdit.setContextMenuPolicy(Qt.CustomContextMenu) - self.replEdit.installEventFilter(self) self.replEdit.customContextMenuRequested.connect( @@ -334,7 +332,9 @@ menu.addAction(self.tr("Copy"), self.replEdit.copy, copyKeys) menu.addAction(self.tr("Paste"), self.__paste, pasteKeys) menu.addSeparator() - # TODO: add device specific context menu entries + if self.__device is not None: + # allow device interface to add specific context menu entries + self.__device.addActions(menu) menu.exec_(self.replEdit.mapToGlobal(pos)) def setConnected(self, connected):
--- a/eric6/MicroPython/MicroPythonReplWidget.ui Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicroPythonReplWidget.ui Wed Jul 24 20:12:19 2019 +0200 @@ -146,6 +146,9 @@ </item> <item> <widget class="QTextEdit" name="replEdit"> + <property name="contextMenuPolicy"> + <enum>Qt::CustomContextMenu</enum> + </property> <property name="undoRedoEnabled"> <bool>false</bool> </property>
--- a/eric6/MicroPython/MicroPythonSerialPort.py Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicroPythonSerialPort.py Wed Jul 24 20:12:19 2019 +0200 @@ -32,6 +32,7 @@ self.__connected = False self.__timeout = timeout # 10s default timeout + self.__timedOut = False def setTimeout(self, timeout): """ @@ -83,6 +84,15 @@ """ 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 @@ -96,6 +106,7 @@ @rtype bytes """ data = bytearray() + self.__timedOut = False t = QTime() t.start() @@ -111,6 +122,7 @@ # else: # break if t.elapsed() > self.__timeout: + self.__timedOut = True break return bytes(data)
--- a/eric6/MicroPython/MicrobitDevices.py Tue Jul 23 19:43:14 2019 +0200 +++ b/eric6/MicroPython/MicrobitDevices.py Wed Jul 24 20:12:19 2019 +0200 @@ -104,7 +104,6 @@ self.__plotterActive = on self.microPython.setActionButtons(files=not on) - # TODO: not yet implemented def canStartFileManager(self): """ Public method to determine, if a File Manager can be started. @@ -113,9 +112,14 @@ 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.") + if self.__replActive or self.__plotterActive: + return False, self.tr("The file manager and the REPL/plotter use" + " the same USB serial connection. Only one" + " can be active at any time. Disconnect the" + " REPL/plotter and try again.") + else: + return True, "" - # TODO: not yet implemented def setFileManager(self, on): """ Public method to set the File Manager status and dependent status. @@ -123,7 +127,9 @@ @param on flag indicating the active status @type bool """ - pass + self.__fileManagerActive = on + self.microPython.setActionButtons( + run=not on, repl=not on, chart=HAS_QTCHART and not on) @pyqtSlot() def handleDataFlood(self):