--- a/src/eric7/MicroPython/MicroPythonWidget.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/MicroPython/MicroPythonWidget.py Wed Jul 13 14:55:47 2022 +0200 @@ -15,8 +15,15 @@ from PyQt6.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent from PyQt6.QtGui import QColor, QKeySequence, QTextCursor, QBrush, QClipboard from PyQt6.QtWidgets import ( - QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy, - QTextEdit, QToolButton, QDialog + QWidget, + QMenu, + QApplication, + QHBoxLayout, + QSpacerItem, + QSizePolicy, + QTextEdit, + QToolButton, + QDialog, ) from EricWidgets.EricZoomWidget import EricZoomWidget @@ -29,14 +36,18 @@ from . import MicroPythonDevices from . import UF2FlashDialog + 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 @@ -182,92 +193,90 @@ 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.ItemDataRole.UserRole DeviceBoardRole = Qt.ItemDataRole.UserRole + 1 DevicePortRole = Qt.ItemDataRole.UserRole + 2 DeviceVidRole = Qt.ItemDataRole.UserRole + 3 DevicePidRole = Qt.ItemDataRole.UserRole + 4 - + dataReceived = pyqtSignal(bytes) - + ManualMarker = "<manual>" - + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent widget @type QWidget """ super().__init__(parent) self.setupUi(self) - + self.layout().setContentsMargins(0, 3, 0, 0) - + self.__ui = parent - + self.__superMenu = QMenu(self) self.__superMenu.aboutToShow.connect(self.__aboutToShowSuperMenu) - - self.menuButton.setObjectName( - "micropython_supermenu_button") + + self.menuButton.setObjectName("micropython_supermenu_button") self.menuButton.setIcon(UI.PixmapCache.getIcon("superMenu")) self.menuButton.setToolTip(self.tr("MicroPython Menu")) - self.menuButton.setPopupMode( - QToolButton.ToolButtonPopupMode.InstantPopup) - self.menuButton.setToolButtonStyle( - Qt.ToolButtonStyle.ToolButtonIconOnly) + self.menuButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) + self.menuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.menuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.menuButton.setAutoRaise(True) self.menuButton.setShowMenuInside(True) self.menuButton.setMenu(self.__superMenu) - - self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon( - "", False)) - + + 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.Policy.Expanding, - QSizePolicy.Policy.Minimum) + spacerItem = QSpacerItem( + 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum + ) self.__zoomLayout.addSpacerItem(spacerItem) - + self.__zoom0 = self.replEdit.fontPointSize() self.__zoomWidget = EricZoomWidget( UI.PixmapCache.getPixmap("zoomOut"), UI.PixmapCache.getPixmap("zoomIn"), - UI.PixmapCache.getPixmap("zoomReset"), self) + UI.PixmapCache.getPixmap("zoomReset"), + self, + ) self.__zoomLayout.addWidget(self.__zoomWidget) - self.layout().insertLayout( - self.layout().count() - 1, - self.__zoomLayout) + 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.__chartWidget = None - + self.__unknownPorts = [] self.__lastPort = None self.__lastDeviceType = None - + if HAS_QTSERIALPORT: self.__interface = MicroPythonCommandsInterface(self) else: @@ -275,229 +284,241 @@ 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.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])') - + r"(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])" + ) + self.__populateDeviceTypeComboBox() - + self.replEdit.installEventFilter(self) # Hack to intercept middle button paste self.__origReplEditMouseReleaseEvent = self.replEdit.mouseReleaseEvent self.replEdit.mouseReleaseEvent = self.__replEditMouseReleaseEvent - - self.replEdit.customContextMenuRequested.connect( - self.__showContextMenu) + + self.replEdit.customContextMenuRequested.connect(self.__showContextMenu) self.__ui.preferencesChanged.connect(self.__handlePreferencesChanged) - self.__ui.preferencesChanged.connect( - self.__interface.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, unknownDevices, unknownPorts = ( - MicroPythonDevices.getFoundDevices() - ) + devices, unknownDevices, unknownPorts = MicroPythonDevices.getFoundDevices() if devices: supportedMessage = self.tr( - "%n supported device(s) detected.", "", len(devices)) - - for index, (boardType, boardName, description, portName, - vid, pid) in enumerate(sorted(devices), 1): + "%n supported device(s) detected.", "", len(devices) + ) + + for index, ( + boardType, + boardName, + description, + portName, + vid, + pid, + ) in enumerate(sorted(devices), 1): self.deviceTypeComboBox.addItem( - self.tr("{0} - {1} ({2})", - "board name, description, port name") - .format(boardName, description, portName) + self.tr( + "{0} - {1} ({2})", "board name, description, port name" + ).format(boardName, description, portName) ) self.deviceTypeComboBox.setItemData( - index, boardType, self.DeviceTypeRole) + index, boardType, self.DeviceTypeRole + ) self.deviceTypeComboBox.setItemData( - index, boardName, self.DeviceBoardRole) + index, boardName, self.DeviceBoardRole + ) self.deviceTypeComboBox.setItemData( - index, portName, self.DevicePortRole) - self.deviceTypeComboBox.setItemData( - index, vid, self.DeviceVidRole) - self.deviceTypeComboBox.setItemData( - index, pid, self.DevicePidRole) - + index, portName, self.DevicePortRole + ) + self.deviceTypeComboBox.setItemData(index, vid, self.DeviceVidRole) + self.deviceTypeComboBox.setItemData(index, pid, self.DevicePidRole) + else: supportedMessage = self.tr("No supported devices detected.") - + self.__unknownPorts = unknownPorts if self.__unknownPorts: unknownMessage = self.tr( - "\n%n unknown device(s) for manual selection.", "", - len(self.__unknownPorts)) + "\n%n unknown device(s) for manual selection.", + "", + len(self.__unknownPorts), + ) if self.deviceTypeComboBox.count(): - self.deviceTypeComboBox.insertSeparator( - self.deviceTypeComboBox.count()) + self.deviceTypeComboBox.insertSeparator(self.deviceTypeComboBox.count()) self.deviceTypeComboBox.addItem(self.tr("Manual Selection")) self.deviceTypeComboBox.setItemData( self.deviceTypeComboBox.count() - 1, - self.ManualMarker, self.DeviceTypeRole) + self.ManualMarker, + self.DeviceTypeRole, + ) else: unknownMessage = "" - + self.deviceInfoLabel.setText(supportedMessage + unknownMessage) - - index = self.deviceTypeComboBox.findText(currentDevice, - Qt.MatchFlag.MatchExactly) + + index = self.deviceTypeComboBox.findText( + currentDevice, Qt.MatchFlag.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) - + if unknownDevices: ignoredUnknown = { - tuple(d) - for d in Preferences.getMicroPython("IgnoredUnknownDevices") + tuple(d) for d in Preferences.getMicroPython("IgnoredUnknownDevices") } - uf2Devices = {(*x[2], x[1]) - for x in UF2FlashDialog.getFoundDevices()} - newUnknownDevices = ( - set(unknownDevices) - ignoredUnknown - uf2Devices - ) + uf2Devices = {(*x[2], x[1]) for x in UF2FlashDialog.getFoundDevices()} + newUnknownDevices = set(unknownDevices) - ignoredUnknown - uf2Devices if newUnknownDevices: button = EricMessageBox.information( self, self.tr("Unknown MicroPython Device"), self.tr( - '<p>Detected these unknown serial devices</p>' - '<ul>' - '<li>{0}</li>' - '</ul>' - '<p>Please report them together with the board name' + "<p>Detected these unknown serial devices</p>" + "<ul>" + "<li>{0}</li>" + "</ul>" + "<p>Please report them together with the board name" ' and a short description to <a href="mailto:{1}">' - ' the eric bug reporting address</a> if it is a' - ' MicroPython board.</p>' - ).format("</li><li>".join([ - self.tr("{0} (0x{1:04x}/0x{2:04x})", - "description, VId, PId").format( - desc, vid, pid) - for vid, pid, desc in newUnknownDevices]), - BugAddress), - EricMessageBox.Ignore | EricMessageBox.Ok + " the eric bug reporting address</a> if it is a" + " MicroPython board.</p>" + ).format( + "</li><li>".join( + [ + self.tr( + "{0} (0x{1:04x}/0x{2:04x})", "description, VId, PId" + ).format(desc, vid, pid) + for vid, pid, desc in newUnknownDevices + ] + ), + BugAddress, + ), + EricMessageBox.Ignore | EricMessageBox.Ok, ) if button == EricMessageBox.Ignore: ignoredUnknown = list(ignoredUnknown | newUnknownDevices) - Preferences.setMicroPython("IgnoredUnknownDevices", - ignoredUnknown) + Preferences.setMicroPython("IgnoredUnknownDevices", ignoredUnknown) else: yes = EricMessageBox.yesNo( self, self.tr("Unknown MicroPython Device"), - self.tr("""Would you like to add them to the list of""" - """ manually configured devices?"""), - yesDefault=True) + self.tr( + """Would you like to add them to the list of""" + """ manually configured devices?""" + ), + yesDefault=True, + ) if yes: self.__addUnknownDevices(list(newUnknownDevices)) - + 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.LineWrapMode.WidgetWidth) else: self.replEdit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) - + if self.__chartWidget is not None: self.__chartWidget.preferencesChanged() - + 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 or Calliope mini. - + @return flag indicating a micro:bit device rtype bool """ if self.__device and ( - "micro:bit" in self.__device.deviceName() or - "Calliope" in self.__device.deviceName() + "micro:bit" in self.__device.deviceName() + or "Calliope" 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) + deviceType = self.deviceTypeComboBox.itemData(index, self.DeviceTypeRole) if deviceType == self.ManualMarker: self.connectButton.setEnabled(bool(self.__unknownPorts)) else: - self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon( - deviceType, False)) - - vid = self.deviceTypeComboBox.itemData( - index, self.DeviceVidRole) - pid = self.deviceTypeComboBox.itemData( - index, self.DevicePidRole) - - self.__device = MicroPythonDevices.getDevice(deviceType, self, - vid, pid) + self.deviceIconLabel.setPixmap( + MicroPythonDevices.getDeviceIcon(deviceType, False) + ) + + vid = self.deviceTypeComboBox.itemData(index, self.DeviceVidRole) + pid = self.deviceTypeComboBox.itemData(index, self.DevicePidRole) + + self.__device = MicroPythonDevices.getDevice(deviceType, self, vid, pid) 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 @@ -514,12 +535,12 @@ self.filesButton.setEnabled(kwargs["files"]) if "chart" in kwargs: self.chartButton.setEnabled(kwargs["chart"] and HAS_QTCHART) - + @pyqtSlot(QPoint) def __showContextMenu(self, pos): """ Private slot to show the REPL context menu. - + @param pos position to show the menu at @type QPoint """ @@ -531,65 +552,73 @@ copyKeys = QKeySequence("Ctrl+Shift+C") pasteKeys = QKeySequence("Ctrl+Shift+V") selectAllKeys = QKeySequence("Ctrl+Shift+A") - + menu = QMenu(self) act = menu.addAction( - UI.PixmapCache.getIcon("editDelete"), self.tr("Clear"), - self.__clear) + UI.PixmapCache.getIcon("editDelete"), self.tr("Clear"), self.__clear + ) act.setEnabled(bool(self.replEdit.toPlainText())) menu.addSeparator() act = menu.addAction( - UI.PixmapCache.getIcon("editCopy"), self.tr("Copy"), - copyKeys, self.replEdit.copy) + UI.PixmapCache.getIcon("editCopy"), + self.tr("Copy"), + copyKeys, + self.replEdit.copy, + ) act.setEnabled(self.replEdit.textCursor().hasSelection()) act = menu.addAction( - UI.PixmapCache.getIcon("editPaste"), self.tr("Paste"), - pasteKeys, self.__paste) - act.setEnabled(self.replEdit.canPaste() and - self.__interface.isConnected()) + UI.PixmapCache.getIcon("editPaste"), + self.tr("Paste"), + pasteKeys, + self.__paste, + ) + act.setEnabled(self.replEdit.canPaste() and self.__interface.isConnected()) menu.addSeparator() act = menu.addAction( - UI.PixmapCache.getIcon("editSelectAll"), self.tr("Select All"), - selectAllKeys, self.replEdit.selectAll) + UI.PixmapCache.getIcon("editSelectAll"), + self.tr("Select All"), + selectAllKeys, + self.replEdit.selectAll, + ) act.setEnabled(bool(self.replEdit.toPlainText())) - + 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")) + 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")) - + 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. @@ -597,58 +626,63 @@ EricMessageBox.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.""")) - + 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: EricMessageBox.warning( self, self.tr("Start REPL"), - self.tr("""<p>The REPL cannot be started.</p><p>Reason:""" - """ {0}</p>""").format(reason)) + 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') + self.__interface.write(b"\x02") # send Ctrl-C (keyboard interrupt) - self.__interface.write(b'\x03') - + self.__interface.write(b"\x03") + self.__device.setRepl(True) self.replEdit.setFocus(Qt.FocusReason.OtherFocusReason) else: self.__interface.dataReceived.disconnect(self.__processData) - if (not self.chartButton.isChecked() and - not self.filesButton.isChecked()): + 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): """ @@ -658,7 +692,7 @@ if self.__connected: with EricOverrideCursor(): self.__disconnectFromDevice() - + if self.replButton.isChecked(): self.on_replButton_clicked(False) if self.filesButton.isChecked(): @@ -668,7 +702,7 @@ else: with EricOverrideCursor(): self.__connectToDevice() - + @pyqtSlot() def __clear(self): """ @@ -676,12 +710,12 @@ """ self.replEdit.clear() self.__interface.isConnected() and self.__interface.write(b"\r") - + @pyqtSlot() def __paste(self, mode=QClipboard.Mode.Clipboard): """ Private slot to perform a paste operation. - + @param mode paste mode (defaults to QClipboard.Mode.Clipboard) @type QClipboard.Mode (optional) """ @@ -690,15 +724,16 @@ if clipboard: pasteText = clipboard.text(mode=mode) if pasteText: - pasteText = pasteText.replace('\n\r', '\r') - pasteText = pasteText.replace('\n', '\r') + pasteText = pasteText.replace("\n\r", "\r") + pasteText = pasteText.replace("\n", "\r") self.__interface.isConnected() and self.__interface.write( - pasteText.encode("utf-8")) - + 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 @@ -709,43 +744,45 @@ if obj is self.replEdit and evt.type() == QEvent.Type.KeyPress: # handle the key press event on behalve of the REPL pane key = evt.key() - msg = bytes(evt.text(), 'utf8') + msg = bytes(evt.text(), "utf8") if key == Qt.Key.Key_Backspace: - msg = b'\b' + msg = b"\b" elif key == Qt.Key.Key_Delete: - msg = b'\x1B[\x33\x7E' + msg = b"\x1B[\x33\x7E" elif key == Qt.Key.Key_Up: - msg = b'\x1B[A' + msg = b"\x1B[A" elif key == Qt.Key.Key_Down: - msg = b'\x1B[B' + msg = b"\x1B[B" elif key == Qt.Key.Key_Right: - msg = b'\x1B[C' + msg = b"\x1B[C" elif key == Qt.Key.Key_Left: - msg = b'\x1B[D' + msg = b"\x1B[D" elif key == Qt.Key.Key_Home: - msg = b'\x1B[H' + msg = b"\x1B[H" elif key == Qt.Key.Key_End: - msg = b'\x1B[F' - elif ((Globals.isMacPlatform() and - evt.modifiers() == Qt.KeyboardModifier.MetaModifier) or - (not Globals.isMacPlatform() and - evt.modifiers() == Qt.KeyboardModifier.ControlModifier)): + msg = b"\x1B[F" + elif ( + Globals.isMacPlatform() + and evt.modifiers() == Qt.KeyboardModifier.MetaModifier + ) or ( + not Globals.isMacPlatform() + and evt.modifiers() == Qt.KeyboardModifier.ControlModifier + ): if Qt.Key.Key_A <= key <= Qt.Key.Key_Z: # devices treat an input of \x01 as Ctrl+A, etc. msg = bytes([1 + key - Qt.Key.Key_A]) - elif ( - evt.modifiers() == ( - Qt.KeyboardModifier.ControlModifier | - Qt.KeyboardModifier.ShiftModifier) or - (Globals.isMacPlatform() and - evt.modifiers() == Qt.KeyboardModifier.ControlModifier) + elif evt.modifiers() == ( + Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier + ) or ( + Globals.isMacPlatform() + and evt.modifiers() == Qt.KeyboardModifier.ControlModifier ): if key == Qt.Key.Key_C: self.replEdit.copy() - msg = b'' + msg = b"" elif key == Qt.Key.Key_V: self.__paste() - msg = b'' + msg = b"" elif key == Qt.Key.Key_A: self.replEdit.selectAll() msg = b"" @@ -758,32 +795,32 @@ else: # standard event processing return super().eventFilter(obj, evt) - + def __replEditMouseReleaseEvent(self, evt): """ Private method handling mouse release events for the replEdit widget. - + Note: this is a hack because QTextEdit does not allow filtering of QEvent.Type.MouseButtonRelease. To make middle button paste work, we had to intercept the protected event method (some kind of reimplementing it). - + @param evt reference to the event object @type QMouseEvent """ if evt.button() == Qt.MouseButton.MiddleButton: self.__paste(mode=QClipboard.Mode.Selection) - msg = b'' + msg = b"" if self.__interface.isConnected(): self.__interface.write(msg) evt.accept() else: self.__origReplEditMouseReleaseEvent(evt) - + def __processData(self, data): """ Private slot to process bytes received from the device. - + @param data bytes received from the device @type bytes """ @@ -791,96 +828,92 @@ # the text cursor must be on the last line while tc.movePosition(QTextCursor.MoveOperation.Down): pass - + # set the font charFormat = tc.charFormat() charFormat.setFontFamilies([self.__font.family()]) charFormat.setFontPointSize(self.__font.pointSize()) tc.setCharFormat(charFormat) - + index = 0 while index < len(data): - if data[index] == 8: # \b + if data[index] == 8: # \b tc.movePosition(QTextCursor.MoveOperation.Left) self.replEdit.setTextCursor(tc) - elif data[index] in (4, 13): # EOT, \r + elif data[index] in (4, 13): # EOT, \r pass - elif (len(data) > index + 1 and - data[index] == 27 and - data[index + 1] == 91): + 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 [ + index += 2 # move index to after the [ match = self.__vt100Re.search( - data[index:].decode("utf-8", errors="replace")) + data[index:].decode("utf-8", errors="replace") + ) 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.MoveOperation.Up, - n=count) + + if action == "A": # up + tc.movePosition(QTextCursor.MoveOperation.Up, n=count) self.replEdit.setTextCursor(tc) - elif action == "B": # down - tc.movePosition(QTextCursor.MoveOperation.Down, - n=count) + elif action == "B": # down + tc.movePosition(QTextCursor.MoveOperation.Down, n=count) self.replEdit.setTextCursor(tc) - elif action == "C": # right - tc.movePosition(QTextCursor.MoveOperation.Right, - n=count) + elif action == "C": # right + tc.movePosition(QTextCursor.MoveOperation.Right, n=count) self.replEdit.setTextCursor(tc) - elif action == "D": # left - tc.movePosition(QTextCursor.MoveOperation.Left, - n=count) + elif action == "D": # left + tc.movePosition(QTextCursor.MoveOperation.Left, n=count) self.replEdit.setTextCursor(tc) - elif action == "K": # delete things + elif action == "K": # delete things if match.group("count") in ("", "0"): # delete to end of line tc.movePosition( QTextCursor.MoveOperation.EndOfLine, - mode=QTextCursor.MoveMode.KeepAnchor) + mode=QTextCursor.MoveMode.KeepAnchor, + ) tc.removeSelectedText() self.replEdit.setTextCursor(tc) elif match.group("count") == "1": # delete to beinning of line tc.movePosition( QTextCursor.MoveOperation.StartOfLine, - mode=QTextCursor.MoveMode.KeepAnchor) + mode=QTextCursor.MoveMode.KeepAnchor, + ) tc.removeSelectedText() self.replEdit.setTextCursor(tc) elif match.group("count") == "2": # delete whole line - tc.movePosition( - QTextCursor.MoveOperation.EndOfLine) + tc.movePosition(QTextCursor.MoveOperation.EndOfLine) tc.movePosition( QTextCursor.MoveOperation.StartOfLine, - mode=QTextCursor.MoveMode.KeepAnchor) + mode=QTextCursor.MoveMode.KeepAnchor, + ) tc.removeSelectedText() self.replEdit.setTextCursor(tc) elif action == "m": - self.__setCharFormat(match.group(0)[:-1].split(";"), - tc) + 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> @@ -931,7 +964,7 @@ <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 @@ -940,7 +973,7 @@ if not formatCodes: # empty format codes list is treated as a reset formatCodes = ["0"] - + charFormat = textCursor.charFormat() for formatCode in formatCodes: try: @@ -948,7 +981,7 @@ except ValueError: # ignore non digit values continue - + if formatCode == 0: charFormat.setFontWeight(50) charFormat.setFontItalic(False) @@ -981,27 +1014,31 @@ charFormat.setFontOverline(False) elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37): charFormat.setForeground( - AnsiColorSchemes[self.__colorScheme][formatCode - 30]) + AnsiColorSchemes[self.__colorScheme][formatCode - 30] + ) elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47): charFormat.setBackground( - AnsiColorSchemes[self.__colorScheme][formatCode - 40]) + AnsiColorSchemes[self.__colorScheme][formatCode - 40] + ) elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97): charFormat.setForeground( - AnsiColorSchemes[self.__colorScheme][formatCode - 80]) + AnsiColorSchemes[self.__colorScheme][formatCode - 80] + ) elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107): charFormat.setBackground( - AnsiColorSchemes[self.__colorScheme][formatCode - 90]) + 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 """ @@ -1010,11 +1047,11 @@ 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 """ @@ -1028,21 +1065,21 @@ return "/dev/{0}".format(portName) else: return "" - + def getCurrentBoard(self): """ Public method to get the board name of the selected device. - + @return board name of the selected device @rtype str """ boardName = self.deviceTypeComboBox.currentData(self.DeviceBoardRole) return boardName - + def getDeviceWorkspace(self): """ Public method to get the workspace directory of the device. - + @return workspace directory of the device @rtype str """ @@ -1050,7 +1087,7 @@ return self.__device.getWorkspace() else: return "" - + def __connectToDevice(self): """ Private method to connect to the selected device. @@ -1058,90 +1095,103 @@ port = self.getCurrentPort() if not port: from .ConnectionSelectionDialog import ConnectionSelectionDialog + with EricOverridenCursor(): dlg = ConnectionSelectionDialog( self.__unknownPorts, self.__lastPort, self.__lastDeviceType ) if dlg.exec() == QDialog.DialogCode.Accepted: vid, pid, port, deviceType = dlg.getData() - + self.deviceIconLabel.setPixmap( - MicroPythonDevices.getDeviceIcon(deviceType, False)) + MicroPythonDevices.getDeviceIcon(deviceType, False) + ) self.__device = MicroPythonDevices.getDevice( - deviceType, self, vid, pid) + deviceType, self, vid, pid + ) self.__device.setButtons() - + self.__lastPort = port self.__lastDeviceType = deviceType else: return - + if self.__interface.connectToDevice(port): self.__setConnected(True) - - if (Preferences.getMicroPython("SyncTimeAfterConnect") and - self.__device.hasTimeCommands()): + + if ( + Preferences.getMicroPython("SyncTimeAfterConnect") + and self.__device.hasTimeCommands() + ): self.__synchronizeTime(quiet=True) else: with EricOverridenCursor(): EricMessageBox.warning( self, self.tr("Serial Device Connect"), - self.tr("""<p>Cannot connect to device at serial""" - """ port <b>{0}</b>.</p>""").format(port)) - + 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 = ericApp().getObject("ViewManager").activeWindow() if aw is None: EricMessageBox.critical( self, self.tr("Run Script"), - self.tr("""There is no editor open. Abort...""")) + self.tr("""There is no editor open. Abort..."""), + ) return - + script = aw.text() if not script: EricMessageBox.critical( self, self.tr("Run Script"), - self.tr("""The current editor does not contain a script.""" - """ Abort...""")) + self.tr( + """The current editor does not contain a script.""" """ Abort...""" + ), + ) return - + ok, reason = self.__device.canRunScript() if not ok: EricMessageBox.warning( self, self.tr("Run Script"), - self.tr("""<p>Cannot run script.</p><p>Reason:""" - """ {0}</p>""").format(reason)) + 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): """ @@ -1150,17 +1200,18 @@ if not self.__device: self.__showNoDeviceMessage() return - + workspace = self.getDeviceWorkspace() if workspace: fileName = EricFileDialog.getOpenFileName( self, self.tr("Open Python File"), workspace, - self.tr("Python3 Files (*.py);;All Files (*)")) + self.tr("Python3 Files (*.py);;All Files (*)"), + ) if fileName: ericApp().getObject("ViewManager").openSourceFile(fileName) - + @pyqtSlot() def on_saveButton_clicked(self): """ @@ -1169,61 +1220,65 @@ if not self.__device: self.__showNoDeviceMessage() return - + aw = ericApp().getObject("ViewManager").activeWindow() if aw: workspace = self.getDeviceWorkspace() if workspace: 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: # QtCharts not available => fail silently return - + if not self.__device: self.__showNoDeviceMessage() return - + if checked: ok, reason = self.__device.canStartPlotter() if not ok: EricMessageBox.warning( self, self.tr("Start Chart"), - self.tr("""<p>The Chart cannot be started.</p><p>Reason:""" - """ {0}</p>""").format(reason)) + 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.__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') + self.__interface.write(b"\x02") # send Ctrl-C (keyboard interrupt) - self.__interface.write(b'\x03') - + self.__interface.write(b"\x03") + self.__device.setPlotter(True) else: if self.__chartWidget.isDirty(): @@ -1231,28 +1286,26 @@ self, self.tr("Unsaved Chart Data"), self.tr("""The chart contains unsaved data."""), - self.__chartWidget.saveData) + 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.__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): """ @@ -1260,76 +1313,77 @@ """ 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: EricMessageBox.warning( self, self.tr("Start File Manager"), - self.tr("""<p>The File Manager cannot be started.</p>""" - """<p>Reason: {0}</p>""").format(reason)) + self.tr( + """<p>The File Manager cannot be started.</p>""" + """<p>Reason: {0}</p>""" + ).format(reason), + ) return - + with EricOverrideCursor(): if not self.__interface.isConnected(): self.__connectToDevice() if self.__connected: self.__fileManagerWidget = MicroPythonFileManagerWidget( - self.__interface, - self.__device.supportsLocalFileAccess(), - self) - + self.__interface, self.__device.supportsLocalFileAccess(), self + ) + self.__ui.addSideWidget( self.__ui.BottomSide, self.__fileManagerWidget, UI.PixmapCache.getIcon("filemanager"), - self.tr("µPy Files") + 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()): + + 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() - + # prepare the download menu if self.__device: menuEntries = self.__device.getDownloadMenuEntries() @@ -1340,46 +1394,52 @@ downloadMenu.addSeparator() else: downloadMenu.addAction( - text, - functools.partial(self.__downloadFromUrl, url) + text, functools.partial(self.__downloadFromUrl, url) ) else: downloadMenu = None - + # populate the super menu hasTime = self.__device.hasTimeCommands() if self.__device else False - + act = self.__superMenu.addAction( - self.tr("Show Version"), self.__showDeviceVersion) + self.tr("Show Version"), self.__showDeviceVersion + ) act.setEnabled(self.__connected) act = self.__superMenu.addAction( - self.tr("Show Implementation"), self.__showImplementation) + self.tr("Show Implementation"), self.__showImplementation + ) act.setEnabled(self.__connected) act = self.__superMenu.addAction( - self.tr("Show Board Data"), self.__showBoardInformation) + self.tr("Show Board Data"), self.__showBoardInformation + ) act.setEnabled(self.__connected) self.__superMenu.addSeparator() if hasTime: act = self.__superMenu.addAction( - self.tr("Synchronize Time"), self.__synchronizeTime) + self.tr("Synchronize Time"), self.__synchronizeTime + ) act.setEnabled(self.__connected) act = self.__superMenu.addAction( - self.tr("Show Device Time"), self.__showDeviceTime) + self.tr("Show Device Time"), self.__showDeviceTime + ) act.setEnabled(self.__connected) - self.__superMenu.addAction( - self.tr("Show Local Time"), self.__showLocalTime) + self.__superMenu.addAction(self.tr("Show Local Time"), self.__showLocalTime) if hasTime: act = self.__superMenu.addAction( - self.tr("Show Time"), self.__showLocalAndDeviceTime) + self.tr("Show Time"), self.__showLocalAndDeviceTime + ) act.setEnabled(self.__connected) self.__superMenu.addSeparator() if not Globals.isWindowsPlatform(): available = self.__mpyCrossAvailable() act = self.__superMenu.addAction( - self.tr("Compile Python File"), self.__compileFile2Mpy) + self.tr("Compile Python File"), self.__compileFile2Mpy + ) act.setEnabled(available) act = self.__superMenu.addAction( - self.tr("Compile Current Editor"), self.__compileEditor2Mpy) + self.tr("Compile Current Editor"), self.__compileEditor2Mpy + ) aw = ericApp().getObject("ViewManager").activeWindow() act.setEnabled(available and bool(aw)) self.__superMenu.addSeparator() @@ -1389,27 +1449,30 @@ if downloadMenu is None: # generic download action act = self.__superMenu.addAction( - self.tr("Download Firmware"), self.__downloadFirmware) + self.tr("Download Firmware"), self.__downloadFirmware + ) act.setEnabled(self.__device.hasFirmwareUrl()) else: # download sub-menu self.__superMenu.addMenu(downloadMenu) self.__superMenu.addSeparator() act = self.__superMenu.addAction( - self.tr("Show Documentation"), self.__showDocumentation) + self.tr("Show Documentation"), self.__showDocumentation + ) act.setEnabled(self.__device.hasDocumentationUrl()) self.__superMenu.addSeparator() if not self.__device.hasFlashMenuEntry(): - self.__superMenu.addAction(self.tr("Flash UF2 Device"), - self.__flashUF2) + self.__superMenu.addAction(self.tr("Flash UF2 Device"), self.__flashUF2) self.__superMenu.addSeparator() - self.__superMenu.addAction(self.tr("Manage Unknown Devices"), - self.__manageUnknownDevices) - self.__superMenu.addAction(self.tr("Ignored Serial Devices"), - self.__manageIgnored) + self.__superMenu.addAction( + self.tr("Manage Unknown Devices"), self.__manageUnknownDevices + ) + self.__superMenu.addAction( + self.tr("Ignored Serial Devices"), self.__manageIgnored + ) self.__superMenu.addSeparator() self.__superMenu.addAction(self.tr("Configure"), self.__configure) - + @pyqtSlot() def __showDeviceVersion(self): """ @@ -1418,24 +1481,20 @@ try: versionInfo = self.__interface.version() if versionInfo: - msg = self.tr( - "<h3>Device Version Information</h3>" - ) + 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) + key.capitalize(), value + ) msg += "</table>" else: msg = self.tr("No version information available.") - - EricMessageBox.information( - self, - self.tr("Device Version Information"), - msg) + + EricMessageBox.information(self, self.tr("Device Version Information"), msg) except Exception as exc: self.__showError("version()", str(exc)) - + @pyqtSlot() def __showImplementation(self): """ @@ -1453,21 +1512,21 @@ name = impInfo["name"] version = ( self.tr("unknown") - if impInfo["version"] == "unknown" else - impInfo["version"] + if impInfo["version"] == "unknown" + else impInfo["version"] ) - + EricMessageBox.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) + ).format(name, version), ) except Exception as exc: self.__showError("getImplementation()", str(exc)) - + @pyqtSlot() def __showBoardInformation(self): """ @@ -1475,43 +1534,46 @@ """ try: boardInfo = self.__interface.getBoardInformation() - + from .BoardDataDialog import BoardDataDialog + dlg = BoardDataDialog(boardInfo) dlg.exec() except Exception as exc: self.__showError("getBoardInformation()", 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 """ if self.__device and self.__device.hasTimeCommands(): try: self.__interface.syncTime(self.__device.getDeviceType()) - + if not quiet: with EricOverridenCursor(): EricMessageBox.information( self, self.tr("Synchronize Time"), - self.tr("<p>The time of the connected device was" - " synchronized with the local time.</p>") + - self.__getDeviceTime() + 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 """ @@ -1528,16 +1590,15 @@ "</table>" ).format(date, time) except ValueError: - return self.tr( - "<h3>Device Date and Time</h3>" - "<p>{0}</p>" - ).format(dateTimeString.strip()) + 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 "" else: return "" - + @pyqtSlot() def __showDeviceTime(self): """ @@ -1545,81 +1606,77 @@ """ msg = self.__getDeviceTime() if msg: - EricMessageBox.information( - self, - self.tr("Device Date and Time"), - msg) - + EricMessageBox.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() - localdate = time.strftime('%Y-%m-%d', localdatetime) - localtime = time.strftime('%H:%M:%S', localdatetime) + localdate = time.strftime("%Y-%m-%d", localdatetime) + localtime = time.strftime("%H:%M:%S", localdatetime) EricMessageBox.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(localdate, localtime) + 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(localdate, localtime), ) - + @pyqtSlot() def __showLocalAndDeviceTime(self): """ Private slot to show the local and device time side-by-side. """ localdatetime = time.localtime() - localdate = time.strftime('%Y-%m-%d', localdatetime) - localtime = time.strftime('%H:%M:%S', localdatetime) - + localdate = time.strftime("%Y-%m-%d", localdatetime) + localtime = time.strftime("%H:%M:%S", localdatetime) + try: deviceDateTimeString = self.__interface.getTime() try: - devicedate, devicetime = ( - deviceDateTimeString.strip().split(None, 1) - ) + devicedate, devicetime = deviceDateTimeString.strip().split(None, 1) EricMessageBox.information( self, self.tr("Date and Time"), - self.tr("<table>" - "<tr><th></th><th>Local Date and Time</th>" - "<th>Device Date and Time</th></tr>" - "<tr><td><b>Date</b></td>" - "<td align='center'>{0}</td>" - "<td align='center'>{2}</td></tr>" - "<tr><td><b>Time</b></td>" - "<td align='center'>{1}</td>" - "<td align='center'>{3}</td></tr>" - "</table>" - ).format(localdate, localtime, - devicedate, devicetime) + self.tr( + "<table>" + "<tr><th></th><th>Local Date and Time</th>" + "<th>Device Date and Time</th></tr>" + "<tr><td><b>Date</b></td>" + "<td align='center'>{0}</td>" + "<td align='center'>{2}</td></tr>" + "<tr><td><b>Time</b></td>" + "<td align='center'>{1}</td>" + "<td align='center'>{3}</td></tr>" + "</table>" + ).format(localdate, localtime, devicedate, devicetime), ) except ValueError: EricMessageBox.information( self, self.tr("Date and Time"), - self.tr("<table>" - "<tr><th>Local Date and Time</th>" - "<th>Device Date and Time</th></tr>" - "<tr><td align='center'>{0} {1}</td>" - "<td align='center'>{2}</td></tr>" - "</table>" - ).format(localdate, localtime, - deviceDateTimeString.strip()) + self.tr( + "<table>" + "<tr><th>Local Date and Time</th>" + "<th>Device Date and Time</th></tr>" + "<tr><td align='center'>{0} {1}</td>" + "<td align='center'>{2}</td></tr>" + "</table>" + ).format(localdate, localtime, deviceDateTimeString.strip()), ) except Exception as exc: self.__showError("getTime()", str(exc)) - + 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 @@ -1629,15 +1686,17 @@ EricMessageBox.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)) - + 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 __mpyCrossAvailable(self): """ Private method to check the availability of mpy-cross. - + @return flag indicating the availability of mpy-cross @rtype bool """ @@ -1650,13 +1709,13 @@ else: if Utilities.isExecutable(program): available = True - + return available - + 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 @@ -1669,12 +1728,15 @@ EricMessageBox.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.""")) + 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 = ericApp().getObject("ViewManager").activeWindow() @@ -1684,27 +1746,30 @@ defaultDirectory = os.path.dirname(fn) if not defaultDirectory: defaultDirectory = ( - Preferences.getMicroPython("MpyWorkspace") or - Preferences.getMultiProject("Workspace") or - os.path.expanduser("~") + Preferences.getMicroPython("MpyWorkspace") + or Preferences.getMultiProject("Workspace") + or os.path.expanduser("~") ) pythonFile = EricFileDialog.getOpenFileName( self, title, defaultDirectory, - self.tr("Python Files (*.py);;All Files (*)")) + self.tr("Python Files (*.py);;All Files (*)"), + ) if not pythonFile: # user cancelled return - + if not os.path.exists(pythonFile): EricMessageBox.critical( self, title, - self.tr("""The Python file <b>{0}</b> does not exist.""" - """ Aborting...""").format(pythonFile)) + self.tr( + """The Python file <b>{0}</b> does not exist.""" """ Aborting...""" + ).format(pythonFile), + ) return - + compileArgs = [ pythonFile, ] @@ -1712,14 +1777,14 @@ 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): """ @@ -1734,15 +1799,17 @@ EricMessageBox.critical( self, self.tr("Compile Current Editor"), - self.tr("""The current editor does not contain a Python""" - """ file. Aborting...""")) + self.tr( + """The current editor does not contain a Python""" + """ file. Aborting...""" + ), + ) return - + self.__crossCompile( - pythonFile=aw.getFileName(), - title=self.tr("Compile Current Editor") + pythonFile=aw.getFileName(), title=self.tr("Compile Current Editor") ) - + @pyqtSlot() def __showDocumentation(self): """ @@ -1751,10 +1818,10 @@ if self.__device is None or not self.__device.hasDocumentationUrl(): # abort silently return - + url = self.__device.getDocumentationUrl() ericApp().getObject("UserInterface").launchHelpViewer(url) - + @pyqtSlot() def __downloadFirmware(self): """ @@ -1763,45 +1830,44 @@ if self.__device is None or not self.__device.hasFirmwareUrl(): # abort silently return - + self.__device.downloadFirmware() - + def __downloadFromUrl(self, url): """ Private method to open a web browser for the given URL. - + @param url URL to be opened @type str """ if self.__device is None: # abort silently return - + if url: ericApp().getObject("UserInterface").launchHelpViewer(url) - + @pyqtSlot() def __manageIgnored(self): """ Private slot to manage the list of ignored serial devices. """ from .IgnoredDevicesDialog import IgnoredDevicesDialog - + dlg = IgnoredDevicesDialog( - Preferences.getMicroPython("IgnoredUnknownDevices"), - self) + Preferences.getMicroPython("IgnoredUnknownDevices"), self + ) if dlg.exec() == QDialog.DialogCode.Accepted: ignoredDevices = dlg.getDevices() - Preferences.setMicroPython("IgnoredUnknownDevices", - ignoredDevices) - + Preferences.setMicroPython("IgnoredUnknownDevices", ignoredDevices) + @pyqtSlot() def __configure(self): """ Private slot to open the MicroPython configuration page. """ ericApp().getObject("UserInterface").showPreferences("microPythonPage") - + @pyqtSlot() def __manageUnknownDevices(self): """ @@ -1809,33 +1875,33 @@ list of supported boards). """ from .UnknownDevicesDialog import UnknownDevicesDialog + dlg = UnknownDevicesDialog() dlg.exec() - + def __addUnknownDevices(self, devices): """ Private method to add devices to the list of manually added boards. - + @param devices list of not ignored but unknown devices @type list of tuple of (int, int, str) """ from .AddEditDevicesDialog import AddEditDevicesDialog - + if len(devices) > 1: - from EricWidgets.EricListSelectionDialog import ( - EricListSelectionDialog - ) + from EricWidgets.EricListSelectionDialog import EricListSelectionDialog + sdlg = EricListSelectionDialog( [d[2] for d in devices], title=self.tr("Add Unknown Devices"), message=self.tr("Select the devices to be added:"), - checkBoxSelection=True + checkBoxSelection=True, ) if sdlg.exec() == QDialog.DialogCode.Accepted: selectedDevices = sdlg.getSelection() else: selectedDevices = devices[0][2] - + if selectedDevices: manualDevices = Preferences.getMicroPython("ManualDevices") for vid, pid, description in devices: @@ -1844,10 +1910,10 @@ if dlg.exec() == QDialog.DialogCode.Accepted: manualDevices.append(dlg.getDeviceDict()) Preferences.setMicroPython("ManualDevices", manualDevices) - + # rescan the ports self.__populateDeviceTypeComboBox() - + @pyqtSlot() def __flashUF2(self): """