Continued implementing the MicroPython support. micropython

Wed, 17 Jul 2019 20:31:47 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 17 Jul 2019 20:31:47 +0200
branch
micropython
changeset 7066
e3d034e65afc
parent 7065
e3d04faced34
child 7067
3fc4082fc6ba

Continued implementing the MicroPython support.

eric6/MicroPython/MicroPythonGraphWidget.py file | annotate | diff | comparison | revisions
eric6/MicroPython/MicroPythonReplWidget.py file | annotate | diff | comparison | revisions
--- a/eric6/MicroPython/MicroPythonGraphWidget.py	Tue Jul 16 20:12:53 2019 +0200
+++ b/eric6/MicroPython/MicroPythonGraphWidget.py	Wed Jul 17 20:31:47 2019 +0200
@@ -11,15 +11,22 @@
 
 from collections import deque
 import bisect
+import os
+import time
+import csv
 
-from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
 from PyQt5.QtGui import QPainter
 from PyQt5.QtWidgets import (
-    QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QSizePolicy, QSpacerItem
+    QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QSizePolicy, QSpacerItem,
+    QLabel, QSpinBox
 )
 from PyQt5.QtChart import QChartView, QChart, QLineSeries, QValueAxis
 
+from E5Gui import E5MessageBox
+
 import UI.PixmapCache
+import Preferences
 
 
 class MicroPythonGraphWidget(QWidget):
@@ -40,7 +47,7 @@
         super(MicroPythonGraphWidget, self).__init__(parent)
         
         self.__layout = QHBoxLayout()
-        self.__layout.setContentsMargins(0, 0, 0, 0)
+        self.__layout.setContentsMargins(2, 2, 2, 2)
         self.setLayout(self.__layout)
         
         self.__chartView = QChartView(self)
@@ -48,26 +55,42 @@
             QSizePolicy.Expanding, QSizePolicy.Expanding)
         self.__layout.addWidget(self.__chartView)
         
-        self.__buttonsLayout = QVBoxLayout()
-        self.__buttonsLayout.setContentsMargins(0, 0, 0, 0)
-        self.__layout.addLayout(self.__buttonsLayout)
+        self.__verticalLayout = QVBoxLayout()
+        self.__verticalLayout.setContentsMargins(0, 0, 0, 0)
+        self.__layout.addLayout(self.__verticalLayout)
         
         self.__saveButton = QToolButton(self)
         self.__saveButton.setIcon(UI.PixmapCache.getIcon("fileSave"))
         self.__saveButton.setToolTip(self.tr("Press to save the raw data"))
         self.__saveButton.clicked.connect(self.on_saveButton_clicked)
-        self.__buttonsLayout.addWidget(self.__saveButton)
+        self.__verticalLayout.addWidget(self.__saveButton)
+        self.__verticalLayout.setAlignment(self.__saveButton, Qt.AlignHCenter)
         
         spacerItem = QSpacerItem(20, 20, QSizePolicy.Minimum,
                                  QSizePolicy.Expanding)
-        self.__buttonsLayout.addItem(spacerItem)
+        self.__verticalLayout.addItem(spacerItem)
+        
+        label = QLabel(self.tr("max. X:"))
+        self.__verticalLayout.addWidget(label)
+        self.__verticalLayout.setAlignment(label, Qt.AlignHCenter)
+        
+        self.__maxX = 100
+        self.__maxXSpinBox = QSpinBox()
+        self.__maxXSpinBox.setMinimum(100)
+        self.__maxXSpinBox.setMaximum(1000)
+        self.__maxXSpinBox.setSingleStep(100)
+        self.__maxXSpinBox.setToolTip(self.tr(
+            "Enter the maximum number of data points to be plotted."))
+        self.__maxXSpinBox.setValue(self.__maxX)
+        self.__maxXSpinBox.setAlignment(Qt.AlignRight)
+        self.__verticalLayout.addWidget(self.__maxXSpinBox)
         
         # holds the data to be checked for plotable data
         self.__inputBuffer = []
         # holds the raw data
         self.__rawData = []
+        self.__dirty = False
         
-        self.__maxX = 100
         self.__maxY = 1000
         self.__flooded = False  # flag indicating a data flood
         
@@ -91,6 +114,8 @@
         self.__chart.setAxisY(self.__axisY, self.__series[0])
         self.__chartView.setChart(self.__chart)
         self.__chartView.setRenderHint(QPainter.Antialiasing)
+        
+        self.__maxXSpinBox.valueChanged.connect(self.__handleMaxXChanged)
     
     @pyqtSlot(bytes)
     def processData(self, data):
@@ -115,6 +140,10 @@
             self.dataFlood.emit()
             return
         
+        # disable the inputs while processing data
+        self.__saveButton.setEnabled(False)
+        self.__maxXSpinBox.setEnabled(False)
+        
         data = data.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
         self.__inputBuffer.append(data)
         
@@ -153,6 +182,10 @@
             # Append any left over bytes for processing next time data is
             # received.
             self.__inputBuffer.append(lines[-1])
+        
+        # re-enable the inputs
+        self.__saveButton.setEnabled(True)
+        self.__maxXSpinBox.setEnabled(True)
     
     def __addData(self, values):
         """
@@ -167,6 +200,7 @@
         """
         # store incoming data to be able to dump it as CSV upon request
         self.__rawData.append(values)
+        self.__dirty = True
         
         # check number of incoming values and adjust line series accordingly
         if len(values) != len(self.__series):
@@ -238,7 +272,16 @@
         @return flag indicating valid data
         @rtype bool
         """
-        # TODO: not implemented yet
+        return len(self.__rawData) > 0
+    
+    def isDirty(self):
+        """
+        Public method to check, if the chart contains unsaved data.
+        
+        @return flag indicating unsaved data
+        @rtype bool
+        """
+        return self.hasData() and self.__dirty
     
     def saveData(self):
         """
@@ -247,4 +290,56 @@
         @return flag indicating success
         @rtype bool
         """
-        # TODO: not implemented yet
+        baseDir = (Preferences.getMultiProject("Workspace") or
+                   os.path.expanduser("~"))
+        dataDir = os.path.join(baseDir, "data_capture")
+        
+        if not os.path.exists(dataDir):
+            os.makedirs(dataDir)
+        
+        # save the raw data as a CSV file
+        fileName = "{0}.csv".format(time.strftime("%Y%m%d-%H%M%S"))
+        fullPath = os.path.join(dataDir, fileName)
+        try:
+            csvFile = open(fullPath, "w")
+            csvWriter = csv.writer(csvFile)
+            csvWriter.writerows(self.__rawData)
+            csvFile.close()
+            
+            self.__dirty = False
+            return True
+        except (IOError, OSError) as err:
+            E5MessageBox.critical(
+                self,
+                self.tr("Save Chart Data"),
+                self.tr(
+                    """<p>The chart data could not be saved into file"""
+                    """ <b>{0}</b>.</p><p>Reason: {1}</p>""").format(
+                    fullPath, str(err)))
+            return False
+    
+    @pyqtSlot(int)
+    def __handleMaxXChanged(self, value):
+        """
+        Private slot handling a change of the max. X spin box.
+        
+        @param value value of the spin box
+        @type int
+        """
+        delta = value - self.__maxX
+        if delta == 0:
+            # nothing to change
+            return
+        elif delta > 0:
+            # range must be increased
+            for deq in self.__data:
+                deq.extend([0] * delta)
+        else:
+            # range must be decreased
+            data = []
+            for deq in self.__data:
+                data.append(deque(list(deq)[:value]))
+            self.__data = data
+        
+        self.__maxX = value
+        self.__axisX.setRange(0, self.__maxX)
--- a/eric6/MicroPython/MicroPythonReplWidget.py	Tue Jul 16 20:12:53 2019 +0200
+++ b/eric6/MicroPython/MicroPythonReplWidget.py	Wed Jul 17 20:31:47 2019 +0200
@@ -14,7 +14,7 @@
 from PyQt5.QtCore import (
     pyqtSlot, pyqtSignal, Qt, QPoint, QEvent, QIODevice, QTimer
 )
-from PyQt5.QtGui import QColor, QKeySequence, QTextCursor
+from PyQt5.QtGui import QColor, QKeySequence, QTextCursor, QBrush
 from PyQt5.QtWidgets import (
     QWidget, QMenu, QApplication, QHBoxLayout, QSpacerItem, QSizePolicy)
 try:
@@ -55,6 +55,26 @@
     
     dataReceived = pyqtSignal(bytes)
     
+    # ANSI Colors
+    AnsiColors = {
+        (1, 30): QBrush(Qt.darkGray),
+        (1, 31): QBrush(Qt.red),
+        (1, 32): QBrush(Qt.green),
+        (1, 33): QBrush(Qt.yellow),
+        (1, 34): QBrush(Qt.blue),
+        (1, 35): QBrush(Qt.magenta),
+        (1, 36): QBrush(Qt.cyan),
+        (1, 37): QBrush(Qt.white),
+        (2, 30): QBrush(Qt.black),
+        (2, 31): QBrush(Qt.darkRed),
+        (2, 32): QBrush(Qt.darkGreen),
+        (2, 33): QBrush(Qt.darkYellow),
+        (2, 34): QBrush(Qt.darkBlue),
+        (2, 35): QBrush(Qt.darkMagenta),
+        (2, 36): QBrush(Qt.darkCyan),
+        (2, 37): QBrush(Qt.lightGray),
+    }
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -115,7 +135,7 @@
             return
         
         self.__vt100Re = re.compile(
-            r'(?P<count>[\d]*)(;?[\d]*)*(?P<action>[ABCDKm])')
+            r'(?P<count>\d*);?(?P<color>\d*)(?P<action>[ABCDKm])')
         
         self.__populateDeviceTypeComboBox()
         
@@ -127,6 +147,10 @@
         
         self.replEdit.customContextMenuRequested.connect(
             self.__showContextMenu)
+        
+        defaultCharFormat = self.replEdit.textCursor().charFormat()
+        self.DefaultForeground = defaultCharFormat.foreground()
+        self.DefaultBackground = defaultCharFormat.background()
     
     def __populateDeviceTypeComboBox(self):
         """
@@ -218,6 +242,7 @@
         menu = QMenu(self)
         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):
@@ -310,18 +335,6 @@
         self.__closeSerialLink()
         self.setConnected(False)
     
-    def __activatePlotter(self):
-        """
-        Private method to activate a data plotter widget.
-        """
-        # TODO: not implemented yet
-    
-    def __deactivatePlotter(self):
-        """
-        Private method to deactivate the plotter widget.
-        """
-        # TODO: not implemented yet
-    
     @pyqtSlot()
     def __paste(self):
         """
@@ -441,7 +454,26 @@
                                             mode=QTextCursor.KeepAnchor)
                             tc.removeSelectedText()
                             self.replEdit.setTextCursor(tc)
-                    # TODO: add handling of 'm' (colors)
+                    elif action == "m":
+                        print(match.group("count"), match.group("color"))
+                        charFormat = tc.charFormat()
+                        if count == 0 and match.group("color") == "":
+                            # reset color
+                            charFormat.setForeground(self.DefaultForeground)
+                            charFormat.setBackground(self.DefaultBackground)
+                        elif count in (0, 1, 2):
+                            if match.group("color") != "":
+                                color = int(match.group("color"))
+                                if count == 0:
+                                    count = 1
+                                if 30 <= color <= 37:
+                                    charFormat.setForeground(
+                                        self.AnsiColors[(count, color)])
+                                elif 40 <= color <= 47:
+                                    charFormat.setBackground(
+                                        self.AnsiColors[(count, color - 10)])
+                        tc.setCharFormat(charFormat)
+                        self.replEdit.setTextCursor(tc)
             elif data[index] == 10:     # \n
                 tc.movePosition(QTextCursor.End)
                 self.replEdit.setTextCursor(tc)
@@ -633,7 +665,7 @@
             self.__ui = e5App().getObject("UserInterface")
         
         if self.__plotterRunning:
-            if self.__chartWidget.hasData():
+            if self.__chartWidget.isDirty():
                 res = E5MessageBox.okToClearData(
                     self,
                     self.tr("Unsaved Chart Data"),
@@ -668,7 +700,7 @@
             
             self.__ui.addSideWidget(self.__ui.BottomSide, self.__chartWidget,
                                     UI.PixmapCache.getIcon("chart"),
-                                    self.tr("Chart"))
+                                    self.tr("μPy Chart"))
             self.__ui.showSideWidget(self.__chartWidget)
             
             if not self.__serial:

eric ide

mercurial