diff -r e3d04faced34 -r e3d034e65afc eric6/MicroPython/MicroPythonGraphWidget.py --- 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)