eric6/MicroPython/MicroPythonReplWidget.py

branch
micropython
changeset 7134
21d23ca51680
parent 7133
7aa4832b3730
child 7135
44fcfc99b864
diff -r 7aa4832b3730 -r 21d23ca51680 eric6/MicroPython/MicroPythonReplWidget.py
--- a/eric6/MicroPython/MicroPythonReplWidget.py	Mon Aug 12 14:55:08 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1246 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing the MicroPython REPL widget.
-"""
-
-from __future__ import unicode_literals
-
-import re
-import time
-
-from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QPoint, QEvent
-from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush
-from PyQt5.QtWidgets import (
-    QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy,
-    QTextEdit, QToolButton
-)
-
-from E5Gui.E5ZoomWidget import E5ZoomWidget
-from E5Gui import E5MessageBox, E5FileDialog
-from E5Gui.E5Application import e5App
-
-from .Ui_MicroPythonReplWidget import Ui_MicroPythonReplWidget
-
-from . import MicroPythonDevices
-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
-
-import Globals
-import UI.PixmapCache
-import Preferences
-
-# ANSI Colors (see https://en.wikipedia.org/wiki/ANSI_escape_code)
-AnsiColorSchemes = {
-    "Windows 7": {
-        0: QBrush(QColor(0, 0, 0)),
-        1: QBrush(QColor(128, 0, 0)),
-        2: QBrush(QColor(0, 128, 0)),
-        3: QBrush(QColor(128, 128, 0)),
-        4: QBrush(QColor(0, 0, 128)),
-        5: QBrush(QColor(128, 0, 128)),
-        6: QBrush(QColor(0, 128, 128)),
-        7: QBrush(QColor(192, 192, 192)),
-        10: QBrush(QColor(128, 128, 128)),
-        11: QBrush(QColor(255, 0, 0)),
-        12: QBrush(QColor(0, 255, 0)),
-        13: QBrush(QColor(255, 255, 0)),
-        14: QBrush(QColor(0, 0, 255)),
-        15: QBrush(QColor(255, 0, 255)),
-        16: QBrush(QColor(0, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "Windows 10": {
-        0: QBrush(QColor(12, 12, 12)),
-        1: QBrush(QColor(197, 15, 31)),
-        2: QBrush(QColor(19, 161, 14)),
-        3: QBrush(QColor(193, 156, 0)),
-        4: QBrush(QColor(0, 55, 218)),
-        5: QBrush(QColor(136, 23, 152)),
-        6: QBrush(QColor(58, 150, 221)),
-        7: QBrush(QColor(204, 204, 204)),
-        10: QBrush(QColor(118, 118, 118)),
-        11: QBrush(QColor(231, 72, 86)),
-        12: QBrush(QColor(22, 198, 12)),
-        13: QBrush(QColor(249, 241, 165)),
-        14: QBrush(QColor(59, 12, 255)),
-        15: QBrush(QColor(180, 0, 158)),
-        16: QBrush(QColor(97, 214, 214)),
-        17: QBrush(QColor(242, 242, 242)),
-    },
-    "PuTTY": {
-        0: QBrush(QColor(0, 0, 0)),
-        1: QBrush(QColor(187, 0, 0)),
-        2: QBrush(QColor(0, 187, 0)),
-        3: QBrush(QColor(187, 187, 0)),
-        4: QBrush(QColor(0, 0, 187)),
-        5: QBrush(QColor(187, 0, 187)),
-        6: QBrush(QColor(0, 187, 187)),
-        7: QBrush(QColor(187, 187, 187)),
-        10: QBrush(QColor(85, 85, 85)),
-        11: QBrush(QColor(255, 85, 85)),
-        12: QBrush(QColor(85, 255, 85)),
-        13: QBrush(QColor(255, 255, 85)),
-        14: QBrush(QColor(85, 85, 255)),
-        15: QBrush(QColor(255, 85, 255)),
-        16: QBrush(QColor(85, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "xterm": {
-        0: QBrush(QColor(0, 0, 0)),
-        1: QBrush(QColor(205, 0, 0)),
-        2: QBrush(QColor(0, 205, 0)),
-        3: QBrush(QColor(205, 205, 0)),
-        4: QBrush(QColor(0, 0, 238)),
-        5: QBrush(QColor(205, 0, 205)),
-        6: QBrush(QColor(0, 205, 205)),
-        7: QBrush(QColor(229, 229, 229)),
-        10: QBrush(QColor(127, 127, 127)),
-        11: QBrush(QColor(255, 0, 0)),
-        12: QBrush(QColor(0, 255, 0)),
-        13: QBrush(QColor(255, 255, 0)),
-        14: QBrush(QColor(0, 0, 255)),
-        15: QBrush(QColor(255, 0, 255)),
-        16: QBrush(QColor(0, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-    "Ubuntu": {
-        0: QBrush(QColor(1, 1, 1)),
-        1: QBrush(QColor(222, 56, 43)),
-        2: QBrush(QColor(57, 181, 74)),
-        3: QBrush(QColor(255, 199, 6)),
-        4: QBrush(QColor(0, 11, 184)),
-        5: QBrush(QColor(118, 38, 113)),
-        6: QBrush(QColor(44, 181, 233)),
-        7: QBrush(QColor(204, 204, 204)),
-        10: QBrush(QColor(128, 128, 128)),
-        11: QBrush(QColor(255, 0, 0)),
-        12: QBrush(QColor(0, 255, 0)),
-        13: QBrush(QColor(255, 255, 0)),
-        14: QBrush(QColor(0, 0, 255)),
-        15: QBrush(QColor(255, 0, 255)),
-        16: QBrush(QColor(0, 255, 255)),
-        17: QBrush(QColor(255, 255, 255)),
-    },
-}
-
-
-# TODO: add config option to synchronize the device time upon connect
-class MicroPythonReplWidget(QWidget, Ui_MicroPythonReplWidget):
-    """
-    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.UserRole
-    DevicePortRole = Qt.UserRole + 1
-    
-    dataReceived = pyqtSignal(bytes)
-    
-    def __init__(self, parent=None):
-        """
-        Constructor
-        
-        @param parent reference to the parent widget
-        @type QWidget
-        """
-        super(MicroPythonReplWidget, self).__init__(parent)
-        self.setupUi(self)
-        
-        self.__ui = parent
-        
-        self.__superMenu = QMenu(self)
-        self.__superMenu.aboutToShow.connect(self.__aboutToShowSuperMenu)
-        
-        self.menuButton.setObjectName(
-            "micropython_supermenu_button")
-        self.menuButton.setIcon(UI.PixmapCache.getIcon("superMenu"))
-        self.menuButton.setToolTip(self.tr("pip Menu"))
-        self.menuButton.setPopupMode(QToolButton.InstantPopup)
-        self.menuButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
-        self.menuButton.setFocusPolicy(Qt.NoFocus)
-        self.menuButton.setAutoRaise(True)
-        self.menuButton.setShowMenuInside(True)
-        self.menuButton.setMenu(self.__superMenu)
-        
-        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.Expanding,
-                                 QSizePolicy.Minimum)
-        self.__zoomLayout.addSpacerItem(spacerItem)
-        
-        self.__zoom0 = self.replEdit.fontPointSize()
-        self.__zoomWidget = E5ZoomWidget(
-            UI.PixmapCache.getPixmap("zoomOut"),
-            UI.PixmapCache.getPixmap("zoomIn"),
-            UI.PixmapCache.getPixmap("zoomReset"), self)
-        self.__zoomLayout.addWidget(self.__zoomWidget)
-        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.__interface = MicroPythonCommandsInterface(self)
-        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.setEnabled(False)
-            return
-        
-        self.__vt100Re = re.compile(
-            r'(?P<count>\d*)(?P<color>(?:;?\d*)*)(?P<action>[ABCDKm])')
-        
-        self.__populateDeviceTypeComboBox()
-        
-        self.replEdit.installEventFilter(self)
-        
-        self.replEdit.customContextMenuRequested.connect(
-            self.__showContextMenu)
-        self.__ui.preferencesChanged.connect(self.__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 = MicroPythonDevices.getFoundDevices()
-        if devices:
-            self.deviceInfoLabel.setText(
-                self.tr("%n supported device(s) detected.", n=len(devices)))
-            
-            index = 0
-            for device in sorted(devices):
-                index += 1
-                self.deviceTypeComboBox.addItem(
-                    self.tr("{0} at {1}".format(device[1], device[2])))
-                self.deviceTypeComboBox.setItemData(
-                    index, device[0], self.DeviceTypeRole)
-                self.deviceTypeComboBox.setItemData(
-                    index, device[2], self.DevicePortRole)
-                
-        else:
-            self.deviceInfoLabel.setText(
-                self.tr("No supported devices detected."))
-        
-        index = self.deviceTypeComboBox.findText(currentDevice,
-                                                 Qt.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)
-    
-    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.WidgetWidth)
-        else:
-            self.replEdit.setLineWrapMode(QTextEdit.NoWrap)
-    
-    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.
-        
-        @return flag indicating a micro:bit device
-        rtype bool
-        """
-        if self.__device and "micro:bit" 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)
-        self.deviceIconLabel.setPixmap(MicroPythonDevices.getDeviceIcon(
-            deviceType, False))
-        
-        self.__device = MicroPythonDevices.getDevice(deviceType, self)
-        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
-        """
-        if "open" in kwargs:
-            self.openButton.setEnabled(kwargs["open"])
-        if "save" in kwargs:
-            self.saveButton.setEnabled(kwargs["save"])
-        if "run" in kwargs:
-            self.runButton.setEnabled(kwargs["run"])
-        if "repl" in kwargs:
-            self.replButton.setEnabled(kwargs["repl"])
-        if "files" in kwargs:
-            self.filesButton.setEnabled(kwargs["files"])
-        if "chart" in kwargs:
-            self.chartButton.setEnabled(kwargs["chart"])
-    
-    @pyqtSlot(QPoint)
-    def __showContextMenu(self, pos):
-        """
-        Private slot to show the REPL context menu.
-        
-        @param pos position to show the menu at
-        @type QPoint
-        """
-        if Globals.isMacPlatform():
-            copyKeys = QKeySequence(Qt.CTRL + Qt.Key_C)
-            pasteKeys = QKeySequence(Qt.CTRL + Qt.Key_V)
-        else:
-            copyKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_C)
-            pasteKeys = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_V)
-        menu = QMenu(self)
-        menu.addAction(self.tr("Clear"), self.__clear)
-        menu.addSeparator()
-        menu.addAction(self.tr("Copy"), self.replEdit.copy, copyKeys)
-        menu.addAction(self.tr("Paste"), self.__paste, pasteKeys)
-        menu.addSeparator()
-        menu.exec_(self.replEdit.mapToGlobal(pos))
-    
-    def setConnected(self, connected):
-        """
-        Public 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"))
-        else:
-            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.
-        """
-        E5MessageBox.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."""))
-    
-    @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:
-                E5MessageBox.warning(
-                    self,
-                    self.tr("Start REPL"),
-                    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')
-                    # send Ctrl-C (keyboard interrupt)
-                    self.__interface.write(b'\x03')
-            
-            self.__device.setRepl(True)
-            self.replEdit.setFocus(Qt.OtherFocusReason)
-        else:
-            self.__interface.dataReceived.disconnect(self.__processData)
-            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):
-        """
-        Private slot to connect to the selected device or disconnect from the
-        currently connected device.
-        """
-        if self.__connected:
-            self.__disconnectFromDevice()
-            
-            if self.replButton.isChecked():
-                self.on_replButton_clicked(False)
-            if self.filesButton.isChecked():
-                self.on_filesButton_clicked(False)
-            if self.chartButton.isChecked():
-                self.on_chartButton_clicked(False)
-        else:
-            self.__connectToDevice()
-    
-    @pyqtSlot()
-    def __clear(self):
-        """
-        Private slot to clear the REPL pane.
-        """
-        self.replEdit.clear()
-        self.__interface.isConnected() and self.__interface.write(b"\r")
-    
-    @pyqtSlot()
-    def __paste(self):
-        """
-        Private slot to perform a paste operation.
-        """
-        clipboard = QApplication.clipboard()
-        if clipboard:
-            pasteText = clipboard.text()
-            if pasteText:
-                pasteText = pasteText.replace('\n\r', '\r')
-                pasteText = pasteText.replace('\n', '\r')
-                self.__interface.isConnected() and self.__interface.write(
-                    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
-        @type QEvent
-        @return flag to indicate that the event was handled
-        @rtype bool
-        """
-        if obj is self.replEdit and evt.type() == QEvent.KeyPress:
-            # handle the key press event on behalve of the REPL pane
-            key = evt.key()
-            msg = bytes(evt.text(), 'utf8')
-            if key == Qt.Key_Backspace:
-                msg = b'\b'
-            elif key == Qt.Key_Delete:
-                msg = b'\x1B[\x33\x7E'
-            elif key == Qt.Key_Up:
-                msg = b'\x1B[A'
-            elif key == Qt.Key_Down:
-                msg = b'\x1B[B'
-            elif key == Qt.Key_Right:
-                msg = b'\x1B[C'
-            elif key == Qt.Key_Left:
-                msg = b'\x1B[D'
-            elif key == Qt.Key_Home:
-                msg = b'\x1B[H'
-            elif key == Qt.Key_End:
-                msg = b'\x1B[F'
-            elif ((Globals.isMacPlatform() and
-                   evt.modifiers() == Qt.MetaModifier) or
-                  (not Globals.isMacPlatform() and
-                   evt.modifiers() == Qt.ControlModifier)):
-                if Qt.Key_A <= key <= Qt.Key_Z:
-                    # devices treat an input of \x01 as Ctrl+A, etc.
-                    msg = bytes([1 + key - Qt.Key_A])
-            elif (evt.modifiers() == Qt.ControlModifier | Qt.ShiftModifier or
-                  (Globals.isMacPlatform() and
-                   evt.modifiers() == Qt.ControlModifier)):
-                if key == Qt.Key_C:
-                    self.replEdit.copy()
-                    msg = b''
-                elif key == Qt.Key_V:
-                    self.__paste()
-                    msg = b''
-            elif key in (Qt.Key_Return, Qt.Key_Enter):
-                tc = self.replEdit.textCursor()
-                tc.movePosition(QTextCursor.EndOfLine)
-                self.replEdit.setTextCursor(tc)
-            self.__interface.isConnected() and self.__interface.write(msg)
-            return True
-        
-        else:
-            # standard event processing
-            return super(MicroPythonReplWidget, self).eventFilter(obj, evt)
-    
-    def __processData(self, data):
-        """
-        Private slot to process bytes received from the device.
-        
-        @param data bytes received from the device
-        @type bytes
-        """
-        tc = self.replEdit.textCursor()
-        # the text cursor must be on the last line
-        while tc.movePosition(QTextCursor.Down):
-            pass
-        
-        # set the font
-        charFormat = tc.charFormat()
-        charFormat.setFontFamily(self.__font.family())
-        charFormat.setFontPointSize(self.__font.pointSize())
-        tc.setCharFormat(charFormat)
-        
-        index = 0
-        while index < len(data):
-            if data[index] == 8:        # \b
-                tc.movePosition(QTextCursor.Left)
-                self.replEdit.setTextCursor(tc)
-            elif data[index] == 13:     # \r
-                pass
-            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 [
-                match = self.__vt100Re.search(data[index:].decode("utf-8"))
-                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.Up, n=count)
-                            self.replEdit.setTextCursor(tc)
-                        elif action == "B":     # down
-                            tc.movePosition(QTextCursor.Down, n=count)
-                            self.replEdit.setTextCursor(tc)
-                        elif action == "C":     # right
-                            tc.movePosition(QTextCursor.Right, n=count)
-                            self.replEdit.setTextCursor(tc)
-                        elif action == "D":     # left
-                            tc.movePosition(QTextCursor.Left, n=count)
-                            self.replEdit.setTextCursor(tc)
-                    elif action == "K":     # delete things
-                        if match.group("count") in ("", "0"):
-                            # delete to end of line
-                            tc.movePosition(QTextCursor.EndOfLine,
-                                            mode=QTextCursor.KeepAnchor)
-                            tc.removeSelectedText()
-                            self.replEdit.setTextCursor(tc)
-                        elif match.group("count") == "1":
-                            # delete to beinning of line
-                            tc.movePosition(QTextCursor.StartOfLine,
-                                            mode=QTextCursor.KeepAnchor)
-                            tc.removeSelectedText()
-                            self.replEdit.setTextCursor(tc)
-                        elif match.group("count") == "2":
-                            # delete whole line
-                            tc.movePosition(QTextCursor.EndOfLine)
-                            tc.movePosition(QTextCursor.StartOfLine,
-                                            mode=QTextCursor.KeepAnchor)
-                            tc.removeSelectedText()
-                            self.replEdit.setTextCursor(tc)
-                    elif action == "m":
-                        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>
-        <li>1: Bold font (weight 75)</li>
-        <li>2: Light font (weight 25)</li>
-        <li>3: Italic font</li>
-        <li>4: Underlined font</li>
-        <li>9: Strikeout font</li>
-        <li>21: Bold off (weight 50)</li>
-        <li>22: Light off (weight 50)</li>
-        <li>23: Italic off</li>
-        <li>24: Underline off</li>
-        <li>29: Strikeout off</li>
-        <li>30: foreground Black</li>
-        <li>31: foreground Dark Red</li>
-        <li>32: foreground Dark Green</li>
-        <li>33: foreground Dark Yellow</li>
-        <li>34: foreground Dark Blue</li>
-        <li>35: foreground Dark Magenta</li>
-        <li>36: foreground Dark Cyan</li>
-        <li>37: foreground Light Gray</li>
-        <li>39: reset foreground to default</li>
-        <li>40: background Black</li>
-        <li>41: background Dark Red</li>
-        <li>42: background Dark Green</li>
-        <li>43: background Dark Yellow</li>
-        <li>44: background Dark Blue</li>
-        <li>45: background Dark Magenta</li>
-        <li>46: background Dark Cyan</li>
-        <li>47: background Light Gray</li>
-        <li>49: reset background to default</li>
-        <li>53: Overlined font</li>
-        <li>55: Overline off</li>
-        <li>90: bright foreground Dark Gray</li>
-        <li>91: bright foreground Red</li>
-        <li>92: bright foreground Green</li>
-        <li>93: bright foreground Yellow</li>
-        <li>94: bright foreground Blue</li>
-        <li>95: bright foreground Magenta</li>
-        <li>96: bright foreground Cyan</li>
-        <li>97: bright foreground White</li>
-        <li>100: bright background Dark Gray</li>
-        <li>101: bright background Red</li>
-        <li>102: bright background Green</li>
-        <li>103: bright background Yellow</li>
-        <li>104: bright background Blue</li>
-        <li>105: bright background Magenta</li>
-        <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
-        @type QTextCursor
-        """
-        if not formatCodes:
-            # empty format codes list is treated as a reset
-            formatCodes = ["0"]
-        
-        charFormat = textCursor.charFormat()
-        for formatCode in formatCodes:
-            try:
-                formatCode = int(formatCode)
-            except ValueError:
-                # ignore non digit values
-                continue
-            
-            if formatCode == 0:
-                charFormat.setFontWeight(50)
-                charFormat.setFontItalic(False)
-                charFormat.setFontUnderline(False)
-                charFormat.setFontStrikeOut(False)
-                charFormat.setFontOverline(False)
-                charFormat.setForeground(self.DefaultForeground)
-                charFormat.setBackground(self.DefaultBackground)
-            elif formatCode == 1:
-                charFormat.setFontWeight(75)
-            elif formatCode == 2:
-                charFormat.setFontWeight(25)
-            elif formatCode == 3:
-                charFormat.setFontItalic(True)
-            elif formatCode == 4:
-                charFormat.setFontUnderline(True)
-            elif formatCode == 9:
-                charFormat.setFontStrikeOut(True)
-            elif formatCode in (21, 22):
-                charFormat.setFontWeight(50)
-            elif formatCode == 23:
-                charFormat.setFontItalic(False)
-            elif formatCode == 24:
-                charFormat.setFontUnderline(False)
-            elif formatCode == 29:
-                charFormat.setFontStrikeOut(False)
-            elif formatCode == 53:
-                charFormat.setFontOverline(True)
-            elif formatCode == 55:
-                charFormat.setFontOverline(False)
-            elif formatCode in (30, 31, 32, 33, 34, 35, 36, 37):
-                charFormat.setForeground(
-                    AnsiColorSchemes[self.__colorScheme][formatCode - 30])
-            elif formatCode in (40, 41, 42, 43, 44, 45, 46, 47):
-                charFormat.setBackground(
-                    AnsiColorSchemes[self.__colorScheme][formatCode - 40])
-            elif formatCode in (90, 91, 92, 93, 94, 95, 96, 97):
-                charFormat.setForeground(
-                    AnsiColorSchemes[self.__colorScheme][formatCode - 80])
-            elif formatCode in (100, 101, 102, 103, 104, 105, 106, 107):
-                charFormat.setBackground(
-                    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
-        """
-        if value < self.__currentZoom:
-            self.replEdit.zoomOut(self.__currentZoom - value)
-        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
-        """
-        portName = self.deviceTypeComboBox.itemData(
-            self.deviceTypeComboBox.currentIndex(),
-            self.DevicePortRole)
-        
-        if Globals.isWindowsPlatform():
-            # return it unchanged
-            return portName
-        else:
-            # return with device path prepended
-            return "/dev/{0}".format(portName)
-    
-    def getDeviceWorkspace(self):
-        """
-        Public method to get the workspace directory of the device.
-        
-        @return workspace directory of the device
-        @rtype str
-        """
-        if self.__device:
-            return self.__device.getWorkspace()
-        else:
-            return ""
-    
-    def __connectToDevice(self):
-        """
-        Private method to connect to the selected device.
-        """
-        port = self.getCurrentPort()
-        if self.__interface.connectToDevice(port):
-            self.setConnected(True)
-        else:
-            E5MessageBox.warning(
-                self,
-                self.tr("Serial Device Connect"),
-                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 = e5App().getObject("ViewManager").activeWindow()
-        if aw is None:
-            E5MessageBox.critical(
-                self,
-                self.tr("Run Script"),
-                self.tr("""There is no editor open. Abort..."""))
-            return
-        
-        script = aw.text()
-        if not script:
-            E5MessageBox.critical(
-                self,
-                self.tr("Run Script"),
-                self.tr("""The current editor does not contain a script."""
-                        """ Abort..."""))
-            return
-        
-        ok, reason = self.__device.canRunScript()
-        if not ok:
-            E5MessageBox.warning(
-                self,
-                self.tr("Run Script"),
-                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):
-        """
-        Private slot to open a file of the connected device.
-        """
-        if not self.__device:
-            self.__showNoDeviceMessage()
-            return
-        
-        workspace = self.__device.getWorkspace()
-        fileName = E5FileDialog.getOpenFileName(
-            self,
-            self.tr("Open Python File"),
-            workspace,
-            self.tr("Python3 Files (*.py);;All Files (*)"))
-        if fileName:
-            e5App().getObject("ViewManager").openSourceFile(fileName)
-    
-    @pyqtSlot()
-    def on_saveButton_clicked(self):
-        """
-        Private slot to save the current editor to the connected device.
-        """
-        if not self.__device:
-            self.__showNoDeviceMessage()
-            return
-        
-        workspace = self.__device.getWorkspace()
-        aw = e5App().getObject("ViewManager").activeWindow()
-        if aw:
-            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:
-            # QtChart not available => fail silently
-            return
-        
-        if not self.__device:
-            self.__showNoDeviceMessage()
-            return
-        
-        if checked:
-            ok, reason = self.__device.canStartPlotter()
-            if not ok:
-                E5MessageBox.warning(
-                    self,
-                    self.tr("Start Chart"),
-                    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.__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')
-                    # send Ctrl-C (keyboard interrupt)
-                    self.__interface.write(b'\x03')
-            
-            self.__device.setPlotter(True)
-        else:
-            if self.__chartWidget.isDirty():
-                res = E5MessageBox.okToClearData(
-                    self,
-                    self.tr("Unsaved Chart Data"),
-                    self.tr("""The chart contains unsaved data."""),
-                    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.__disconnectFromDevice()
-            
-            self.__device.setPlotter(False)
-            self.__ui.removeSideWidget(self.__chartWidget)
-            
-            self.__chartWidget.deleteLater()
-            self.__chartWidget = None
-        
-        self.chartButton.setChecked(checked)
-    
-    @pyqtSlot()
-    def handleDataFlood(self):
-        """
-        Public slot handling a data flood from the device.
-        """
-        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:
-                E5MessageBox.warning(
-                    self,
-                    self.tr("Start File Manager"),
-                    self.tr("""<p>The File Manager cannot be started.</p>"""
-                            """<p>Reason: {0}</p>""").format(reason))
-                return
-            
-            if not self.__interface.isConnected():
-                self.__connectToDevice()
-            self.__fileManagerWidget = MicroPythonFileManagerWidget(
-                self.__interface, self.__device.supportsLocalFileAccess(),
-                self)
-            
-            self.__ui.addSideWidget(self.__ui.BottomSide,
-                                    self.__fileManagerWidget,
-                                    UI.PixmapCache.getIcon("filemanager"),
-                                    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()):
-                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()
-        if self.__device:
-            hasTime = self.__device.hasTimeCommands()
-        else:
-            hasTime = False
-        
-        # TODO: add menu entry to cross-compile a .py file (using mpy-cross)
-        act = self.__superMenu.addAction(
-            self.tr("Show Version"), self.__showDeviceVersion)
-        act.setEnabled(self.__connected)
-        act = self.__superMenu.addAction(
-            self.tr("Show Implementation"), self.__showImplementation)
-        act.setEnabled(self.__connected)
-        self.__superMenu.addSeparator()
-        if hasTime:
-            act = self.__superMenu.addAction(
-                self.tr("Synchronize Time"), self.__synchronizeTime)
-            act.setEnabled(self.__connected)
-            act = self.__superMenu.addAction(
-                self.tr("Show Device Time"), self.__showDeviceTime)
-            act.setEnabled(self.__connected)
-            self.__superMenu.addAction(
-                self.tr("Show Local Time"), self.__showLocalTime)
-            self.__superMenu.addSeparator()
-        if self.__device:
-            self.__device.addDeviceMenuEntries(self.__superMenu)
-    
-    @pyqtSlot()
-    def __showDeviceVersion(self):
-        """
-        Private slot to show some version info about MicroPython of the device.
-        """
-        try:
-            versionInfo = self.__interface.version()
-            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.capitalize(), value)
-                msg += "</table>"
-            else:
-                msg = self.tr("No version information available.")
-            
-            E5MessageBox.information(
-                self,
-                self.tr("Device Version Information"),
-                msg)
-        except Exception as exc:
-            self.__showError("version()", str(exc))
-    
-    @pyqtSlot()
-    def __showImplementation(self):
-        """
-        Private slot to show some implementation related information.
-        """
-        try:
-            impInfo = self.__interface.getImplementation()
-            if impInfo["name"] == "micropython":
-                name = "MicroPython"
-            elif impInfo["name"] == "circuitpython":
-                name = "CircuitPython"
-            elif impInfo["name"] == "unknown":
-                name = self.tr("unknown")
-            else:
-                name = impInfo["name"]
-            if impInfo["version"] == "unknown":
-                version = self.tr("unknown")
-            else:
-                version = impInfo["version"]
-            
-            E5MessageBox.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)
-            )
-        except Exception as exc:
-            self.__showError("getImplementation()", str(exc))
-    
-    # TODO: show device time after sync
-    @pyqtSlot()
-    def __synchronizeTime(self):
-        """
-        Private slot to set the time of the connected device to the local
-        computer's time.
-        """
-        try:
-            self.__interface.syncTime()
-            
-            E5MessageBox.information(
-                self,
-                self.tr("Synchronize Time"),
-                self.tr("The time of the connected device was synchronized"
-                        " with the local time."))
-        except Exception as exc:
-            self.__showError("syncTime()", str(exc))
-    
-    @pyqtSlot()
-    def __showDeviceTime(self):
-        """
-        Private slot to show the date and time of the connected device.
-        """
-        try:
-            dateTimeString = self.__interface.getTime()
-            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)
-        except Exception as exc:
-            self.__showError("getTime()", str(exc))
-    
-    @pyqtSlot()
-    def __showLocalTime(self):
-        """
-        Private slot to show the local date and time.
-        """
-        localdatetime = time.localtime()
-        loacldate = time.strftime('%Y-%m-%d', localdatetime)
-        localtime = time.strftime('%H:%M:%S', localdatetime)
-        E5MessageBox.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(loacldate, localtime)
-        )
-    
-    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
-        @type str
-        """
-        E5MessageBox.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))

eric ide

mercurial