+ layout = QtGui.QHBoxLayout(self.centralWidget())
+ layout.addWidget(self.sideBar)
+ layout.addWidget(self.stackedWidget)
+ layout.setSpacing(0)
+ layout.setContentsMargins(0, 0, 0, 0)
+
+ self.resize(settings.mainwindow.size.value())
+ if settings.mainwindow.pos.value():
+ self.move(settings.mainwindow.pos.value())
+
+ def _setup_sidebar(self):
+ self.sideBar = QtGui.QToolBar(self)
+ self.sideBar.setOrientation(QtCore.Qt.Vertical)
+
+ actionGraph = self.sideBar.addAction('Instantaneous Values and Graphs')
+ actionGraph.setCheckable(True)
+ actionGraph.setIcon(icons.graph)
+ actionGraph.triggered.connect(self.showGraphPage)
+
+ #actionAdd = self.sideBar.addAction('Add Device')
+ #actionAdd.setCheckable(True)
+ #actionAdd.setIcon(icons.add)
+ #actionAdd.triggered.connect(self.showAddDevicePage)
+
+ actionLog = self.sideBar.addAction('Logs')
+ actionLog.setCheckable(True)
+ actionLog.setIcon(icons.log)
+ actionLog.triggered.connect(self.showLogPage)
+
+ actionPreferences = self.sideBar.addAction('Preferences')
+ actionPreferences.setCheckable(True)
+ actionPreferences.setIcon(icons.preferences)
+ actionPreferences.triggered.connect(self.showPreferencesPage)
+
+ # Make the buttons at the top exclusive.
+ self.actionGroup = QtGui.QActionGroup(self)
+ self.actionGroup.addAction(actionGraph)
+ #self.actionGroup.addAction(actionAdd)
+ self.actionGroup.addAction(actionLog)
+ self.actionGroup.addAction(actionPreferences)
+
+ # Show graph at startup.
+ actionGraph.setChecked(True)
+
+ # Fill space between buttons on the top and on the bottom.
+ fill = QtGui.QWidget(self)
+ fill.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
+ self.sideBar.addWidget(fill)
+
+ self.actionStartStop = self.sideBar.addAction('Start Acquisition')
+ self.actionStartStop.setIcon(icons.start)
+ self.actionStartStop.triggered.connect(self.start_stop_acquisition)
+
+ actionAbout = self.sideBar.addAction('About')
+ actionAbout.setIcon(icons.about)
+ actionAbout.triggered.connect(self.show_about)
+
+ actionQuit = self.sideBar.addAction('Quit')
+ actionQuit.setIcon(icons.exit)
+ actionQuit.triggered.connect(self.close)
+
+ s = self.style().pixelMetric(QtGui.QStyle.PM_LargeIconSize)
+ self.sideBar.setIconSize(QtCore.QSize(s, s))
+
+ self.sideBar.setStyleSheet('''
+ QToolBar {
+ background-color: white;
+ margin: 0px;
+ border: 0px;
+ border-right: 1px solid black;
+ }
+
+ QToolButton {
+ padding: 10px;
+ border: 0px;
+ border-right: 1px solid black;
+ }
+
+ QToolButton:checked,
+ QToolButton[checkable="false"]:hover {
+ background-color: #c0d0e8;
+ }
+ ''')
+
+ def _setup_graphPage(self):
+ listView = EmptyMessageListView('waiting for data...')
+ listView.setFrameShape(QtGui.QFrame.NoFrame)
+ listView.viewport().setBackgroundRole(QtGui.QPalette.Window)
+ listView.viewport().setAutoFillBackground(True)
+ listView.setMinimumWidth(260)
+ listView.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
+ listView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
+ listView.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
+ listView.setItemDelegate(self.delegate)
+ listView.setModel(self.model)
+ listView.setUniformItemSizes(True)
+ listView.setMinimumSize(self.delegate.sizeHint())
+
+ self.plotwidget = multiplotwidget.MultiPlotWidget(self)
+ self.plotwidget.plotHidden.connect(self._on_plotHidden)
+
+ self.graphPage = QtGui.QSplitter(QtCore.Qt.Horizontal, self)
+ self.graphPage.addWidget(listView)
+ self.graphPage.addWidget(self.plotwidget)
+ self.graphPage.setStretchFactor(0, 0)
+ self.graphPage.setStretchFactor(1, 1)
+
+ def _setup_addDevicePage(self):
+ self.addDevicePage = QtGui.QWidget(self)
+ layout = QtGui.QVBoxLayout(self.addDevicePage)
+ label = QtGui.QLabel('add device page')
+ layout.addWidget(label)
+
+ def _setup_logPage(self):
+ self.logPage = QtGui.QWidget(self)
+ layout = QtGui.QVBoxLayout(self.logPage)
+
+ self.logView = QtGui.QListView(self)
+ self.logView.setModel(self.logModel)
+ self.logView.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
+ self.logView.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
+ layout.addWidget(self.logView)
+
+ btn = QtGui.QPushButton('Save to file...', self)
+ btn.clicked.connect(self.on_save_log_clicked)
+ layout.addWidget(btn)
+
+ def _setup_preferencesPage(self):
+ self.preferencesPage = QtGui.QWidget(self)
+ layout = QtGui.QGridLayout(self.preferencesPage)
+
+ layout.addWidget(QtGui.QLabel('<b>Graph</b>'), 0, 0)
+ layout.addWidget(QtGui.QLabel('Recording time (seconds):'), 1, 0)
+
+ spin = QtGui.QSpinBox(self)
+ spin.setMinimum(10)
+ spin.setMaximum(3600)
+ spin.setSingleStep(10)
+ spin.setValue(settings.graph.backlog.value())
+ spin.valueChanged[int].connect(settings.graph.backlog.setValue)
+ layout.addWidget(spin, 1, 1)
+
+ layout.addWidget(QtGui.QLabel('<b>Logging</b>'), 2, 0)
+ layout.addWidget(QtGui.QLabel('Log level:'), 3, 0)
+
+ cbox = QtGui.QComboBox()
+ descriptions = [
+ 'no messages at all',
+ 'error messages',
+ 'warnings',
+ 'informational messages',
+ 'debug messages',
+ 'very noisy debug messages'
+ ]
+ for i, desc in enumerate(descriptions):
+ level = sr.LogLevel.get(i)
+ text = '{} ({})'.format(level.name, desc)
+ # The numeric log level corresponds to the index of the text in the
+ # combo box. Should this ever change, we could use the 'userData'
+ # that can also be stored in the item.
+ cbox.addItem(text)
+
+ cbox.setCurrentIndex(settings.logging.level.value().id)
+ cbox.currentIndexChanged[int].connect(
+ (lambda i: settings.logging.level.setValue(sr.LogLevel.get(i))))
+ layout.addWidget(cbox, 3, 1)
+
+ layout.addWidget(QtGui.QLabel('Number of lines to log:'), 4, 0)
+
+ spin = QtGui.QSpinBox(self)
+ spin.setMinimum(100)
+ spin.setMaximum(10 * 1000 * 1000)
+ spin.setSingleStep(100)
+ spin.setValue(settings.logging.lines.value())
+ spin.valueChanged[int].connect(settings.logging.lines.setValue)
+ layout.addWidget(spin, 4, 1)
+
+ layout.setRowStretch(layout.rowCount(), 100)
+
+ def showPage(self, page):
+ self.stackedWidget.setCurrentIndex(self._pages.index(page))
+
+ @QtCore.Slot(bool)
+ def showGraphPage(self):
+ self.showPage(self.graphPage)
+
+ @QtCore.Slot(bool)
+ def showAddDevicePage(self):
+ self.showPage(self.addDevicePage)
+
+ @QtCore.Slot(bool)
+ def showLogPage(self):
+ self.showPage(self.logPage)
+
+ @QtCore.Slot(bool)
+ def showPreferencesPage(self):
+ self.showPage(self.preferencesPage)
+
+ @QtCore.Slot(int)
+ def on_setting_graph_backlog_changed(self, bl):
+ for unit in self._plots:
+ plot = self._plots[unit]
+
+ # Remove the limits first, otherwise the range update would
+ # be ignored.
+ plot.view.setLimits(xMin=None, xMax=None)
+
+ # Now change the range, and then use the calculated limits
+ # (also see the comment in '_getPlot()').
+ plot.view.setXRange(-bl, 0, update=True)
+ r = plot.view.viewRange()
+ plot.view.setLimits(xMin=r[0][0], xMax=r[0][1])
+
+ def _getPlot(self, unit):
+ '''Looks up or creates a new plot for 'unit'.'''
+
+ if unit in self._plots:
+ return self._plots[unit]
+
+ # Create a new plot for the unit.
+ plot = self.plotwidget.addPlot()
+ plot.yaxis.setLabel(util.quantity_from_unit(unit), units=util.format_unit(unit))
+ plot.view.setXRange(-settings.graph.backlog.value(), 0, update=False)
+ plot.view.setYRange(-1, 1)
+ plot.view.enableAutoRange(axis=pyqtgraph.ViewBox.YAxis)
+ # Lock to the range calculated by the view using additional padding,
+ # looks nicer this way.
+ r = plot.view.viewRange()
+ plot.view.setLimits(xMin=r[0][0], xMax=r[0][1])
+
+ self._plots[unit] = plot
+ return plot
+
+ def _getCurve(self, plot, deviceID):
+ '''Looks up or creates a new curve for '(plot, deviceID)'.'''
+
+ key = (plot, deviceID)
+ if key in self._curves:
+ return self._curves[key]
+
+ # Create a new curve.
+ curve = pyqtgraph.PlotDataItem(
+ antialias=True,
+ symbolPen=pyqtgraph.mkPen(QtGui.QColor(QtCore.Qt.black)),
+ symbolBrush=pyqtgraph.mkBrush(QtGui.QColor(QtCore.Qt.black)),
+ symbolSize=1
+ )
+ plot.view.addItem(curve)
+
+ self._curves[key] = curve
+ return curve
+
+ def _updatePlots(self):
+ '''Updates all plots.'''
+
+ # Loop over all devices and channels.
+ for row in range(self.model.rowCount()):
+ idx = self.model.index(row, 0)
+ deviceID = self.model.data(idx,
+ datamodel.MeasurementDataModel.idRole)
+ deviceID = tuple(deviceID) # PySide returns a list.
+ traces = self.model.data(idx,
+ datamodel.MeasurementDataModel.tracesRole)
+
+ for unit, trace in traces.items():
+ now = time.time()
+
+ # Remove old samples.
+ l = now - settings.graph.backlog.value()
+ while trace.samples and trace.samples[0][0] < l:
+ trace.samples.pop(0)
+
+ plot = self._getPlot(unit)
+ if not plot.visible:
+ if trace.new:
+ self.plotwidget.showPlot(plot)
+
+ if plot.visible:
+ xdata = [s[0] - now for s in trace.samples]
+ ydata = [s[1] for s in trace.samples]
+
+ color = self.model.data(idx,
+ datamodel.MeasurementDataModel.colorRole)
+
+ curve = self._getCurve(plot, deviceID)
+ curve.setPen(pyqtgraph.mkPen(color=color))
+ curve.setData(xdata, ydata)
+
+ @QtCore.Slot(multiplotwidget.Plot)
+ def _on_plotHidden(self, plot):
+ plotunit = [u for u, p in self._plots.items() if p == plot][0]
+
+ # Mark all traces of all devices/channels with the same unit as the
+ # plot as "old" ('trace.new = False'). As soon as a new sample arrives
+ # on one trace, the plot will be shown again.
+ for row in range(self.model.rowCount()):
+ idx = self.model.index(row, 0)
+ traces = self.model.data(idx, datamodel.MeasurementDataModel.tracesRole)
+
+ for traceunit, trace in traces.items():
+ if traceunit == plotunit:
+ trace.new = False
+
+ @QtCore.Slot()
+ def _stopped(self):
+ if self._closing:
+ # The acquisition was stopped by the 'closeEvent()', close the
+ # window again now that the acquisition has stopped.
+ self.close()
+