pygpibtoolkit/plotter/qgpib_plotter.py

Thu, 24 May 2018 23:22:37 +0200

author
David Douard <david.douard@logilab.fr>
date
Thu, 24 May 2018 23:22:37 +0200
changeset 104
916c255b3079
parent 98
4cbd3d410230
permissions
-rw-r--r--

[plotter] flake8

# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
""" Copyright (c) 2007-2018 David Douard (Paris, FRANCE).
https://bitbucket.org/dddouard/pygpibtoolkit -- mailto:david.douard@sdfa3.org
"""

import os
import sys
import time

from PyQt5 import QtWidgets, QtGui, QtCore, QtPrintSupport, uic
from PyQt5.QtCore import Qt, pyqtSignal

from pygpibtoolkit.plotter.hpgl_qt import QHPGLPlotterWidget
from pygpibtoolkit.plotter.gpib_plotter import GPIBplotter

from pygpibtoolkit.qt5.qpreferences import IntItem, UnicodeItem, ColorItem
from pygpibtoolkit.qt5.qpreferences import BoolItem
from pygpibtoolkit.qt5.qpreferences import PointItem, SizeItem, ByteArrayItem
from pygpibtoolkit.qt5.qpreferences import AbstractPreferences
from pygpibtoolkit.qt5.qpreferenceseditor import PreferencesEditor

from pygpibtoolkit.tools import str_num_key


HERE = os.path.abspath(os.path.dirname(__file__))


try:
    form_class, base_class = uic.loadUiType(
        os.path.join(HERE, "qhpgl_plotter.ui"),
        from_imports=True, import_from='pygpibtoolkit.plotter',)
except Exception as e:
    from pygpibtoolkit.plotter.qhpgl_plotter_ui import (
        Ui_MainWindow as form_class)


class Preferences(AbstractPreferences):
    ORGANISATION = "PyGPIBToolkit"
    APPLICATION = "qgpib_plotter"

    _pos = PointItem()
    _size = SizeItem()
    _appState = ByteArrayItem()

    device = UnicodeItem(default='/dev/ttyUSB0',
                         name=u'device',
                         description=u'GPIB device',
                         group="GPIB settings")
    address = IntItem(default=5, min=0, max=16,
                      name=u'GPIB address',
                      group="GPIB settings")

    background = ColorItem(default=QtGui.QColor("white"),
                           name="Background",
                           group="Colors")
    color0 = ColorItem(default=QtGui.QColor("black"),
                       name="Pen #0",
                       group="Colors")
    color1 = ColorItem(default=QtGui.QColor("green"),
                       name="Pen #1",
                       group="Colors")
    color2 = ColorItem(default=QtGui.QColor("red"),
                       name="Pen #2",
                       group="Colors")
    color3 = ColorItem(default=QtGui.QColor("blue"),
                       name="Pen #3",
                       group="Colors")
    color4 = ColorItem(default=QtGui.QColor("yellow"),
                       name="Pen #4",
                       group="Colors")
    color5 = ColorItem(default=QtGui.QColor("cyan"),
                       name="Pen #5",
                       group="Colors")
    color6 = ColorItem(default=QtGui.QColor("magenta"),
                       name="Pen #6",
                       group="Colors")
    color7 = ColorItem(default=QtGui.QColor("darkred"),
                       name="Pen #7",
                       group="Colors")

    autodisplay = BoolItem(
        default=True,
        name="Auto display",
        description=(
            "Automatically display a new plot as soon as it is received"),
        group="Misc")


class QtHPGLPlotter(QtWidgets.QMainWindow, form_class):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._plots = {}
        self._prefs = Preferences()
        self._receiving = False

        self.setupUi(self)
        self.initializeGPIB()
        if self._prefs._pos:
            self.move(self._prefs._pos)
        if self._prefs._size:
            self.resize(self._prefs._size)
        if self._prefs._appState:
            self.restoreState(self._prefs._appState)
        self.readPreferences()

    def readPreferences(self):
        bg = self._prefs.background
        if bg and bg.isValid():
            self.plotterWidget.qview.setBackgroundBrush(QtGui.QBrush(bg))
        pen_colors = [self._prefs["color%d" % i] for i in range(8)]
        self.plotterWidget.pen_colors = pen_colors

    def replotCurrent(self):
        self.currentPlotChanged(self.plotsView.currentIndex())

    def setupUi(self, widget):
        super().setupUi(widget)
        # actions defined in designer
        self.actionPreferences.triggered.connect(self.preferencesTriggered)
        self.actionQuit.triggered.connect(self.quitTriggered)
        self.actionQuit.setShortcut(QtGui.QKeySequence(u'Ctrl+Q'))
        self.actionOpen.triggered.connect(self.openTriggered)
        self.actionOpen.setShortcut(QtGui.QKeySequence(u'Ctrl+O'))
        self.actionSave.triggered.connect(self.saveTriggered)
        self.actionSave.setShortcut(QtGui.QKeySequence(u'Ctrl+S'))
        self.actionSaveAs.triggered.connect(self.saveAsTriggered)

        self.actionPrint.setShortcut(QtGui.QKeySequence(u'Ctrl+P'))
        self.actionPrint.triggered.connect(self.printTriggered)

        self.plotterWidget = QHPGLPlotterWidget(self)
        self.setCentralWidget(self.plotterWidget)

        self.captureButton.toggled.connect(self.captureToggled)

        self._plots_list = QtCore.QStringListModel()
        self.plotsView.setModel(self._plots_list)
        self.plotsView.activated.connect(self.currentPlotChanged)
        self.plotsView.selectionModel().currentChanged.connect(
            self.currentPlotChanged)
        self.setReceivingLed()

    def currentPlotChanged(self, index, old_index=None):
        if index.isValid():
            value = self.plotsView.model().data(index, Qt.DisplayRole)

            self.plotterWidget.clear()
            self.plotterWidget.parse(self._plots[value])

    def preferencesTriggered(self, checked=False):
        PreferencesEditor(self._prefs, self).exec_()
        self.readPreferences()
        self.replotCurrent()

    def quitTriggered(self, checked=False):
        self.close()

    def closeEvent(self, event):
        # if self.promptForSave():
        if 1:
            self._prefs._pos = self.pos()
            self._prefs._size = self.size()
            self._prefs._appState = self.saveState()
            event.accept()
        else:
            event.ignore()

    def openTriggered(self, checked=False):
        filenames = QtWidgets.QFileDialog.getOpenFileNames(
            self, "Open a HPGL file to display", '.',
            'HPGL files (*.plt)\nAll files (*)')
        self.openFiles(filenames[0])
        # self.displayFirst()

    def displayFirst(self):
        if not self.plotsView.currentIndex().isValid():
            self.plotsView.setCurrentIndex(self.plotsView.model().index(0, 0))

    def openFiles(self, filenames):
        ok = False
        for filename in filenames:
            if os.path.exists(filename):
                data = open(filename).read()
                name = os.path.basename(filename)
                name = os.path.splitext(name)[0]
                lst = self.plotsView.model().stringList()
                lst.append(name)
                self._plots[name] = data
                self.plotsView.model().setStringList(lst)
                ok = True
        if ok:
            self.plotsView.setCurrentIndex(
                self.plotsView.model().index(len(lst)-1, 0))
        return ok

    def plotReceived(self, num):
        self._receiving = False
        self.setReceivingLed()
        plot, timestamp = self.captureThread.getPlot(num)
        name = "plot_%s" % (num)
        self._plots[name] = plot
        model = self.plotsView.model()
        pos = model.rowCount()
        model.insertRows(pos, 1)
        model.setData(model.index(pos), name)
        if self._prefs.autodisplay:
            self.plotsView.setCurrentIndex(model.index(pos))

    def plotStarted(self):
        self._receiving = True
        self.setReceivingLed()

    def printTriggered(self, checked=False):
        cindex = self.plotsView.currentIndex()
        if cindex and cindex.isValid():
            # isn't there a simpler way of getting this?
            plotname = self.plotsView.model().data(cindex, Qt.DisplayRole)
            printer = QtPrintSupport.QPrinter()
            printer.setOutputFormat(printer.PdfFormat)
            printer.setOutputFileName('%s.pdf' % plotname)
            prdialog = QtPrintSupport.QPrintDialog(printer)
            prdialog.setEnabledOptions(prdialog.PrintToFile)
            if prdialog.exec_() == prdialog.Accepted:
                painter = QtGui.QPainter(printer)
                painter.setRenderHint(painter.Antialiasing)
                self.plotterWidget.qview.render(painter)
                painter.end()
        else:
            QtWidgets.QMessageBox.warning(
                self, "Nothing to print",
                "Unable to print: <br><b>No plot is currently selected.</b>")

    def saveTriggered(self, checked=False):
        print("save")

    def saveAsTriggered(self, checked=False):
        index = self.plotsView.selectionModel().currentIndex()
        if index.isValid():
            filename = QtGui.QFileDialog.getSaveFileName(
                self, "Selecte a file name to save HPGL file", '.',
                'HPGL files (*.plt)\nAll files (*)')
            value = self.plotsView.model().data(index, Qt.DisplayRole)
            open(filename, 'w').write(self._plots[value])

    def initializeGPIB(self):
        self._online = False
        try:
            self.gpib_plotter = GPIBplotter(
                device=self._prefs.device,
                address=self._prefs.address,
            )
            self.captureThread = GPIBReceiver(self.gpib_plotter)
            self.captureThread.plotReceived.connect(self.plotReceived)
            self.captureThread.plotStarted.connect(self.plotStarted)
            self.captureThread.start()
        except Exception as e:
            self.captureThread = None
            self.gpib_plotter = None
        self.setCaptureLed()

    def captureToggled(self, state):
        if state:
            if self.gpib_plotter is None:
                self.initializeGPIB()
                if self.gpib_plotter is None:
                    QtWidgets.QMessageBox.critical(
                        self, "GPIB error",
                        "<b>Unable to initialize GPIB connection</b>."
                        "<br>Please check your GPIB dongle and settings.")
                    self._online = False
                    self.setCaptureLed()
                    return
            self._online = True
            self.captureThread.startCapture()
        else:
            if self.captureThread:
                self.captureThread.stopCapture()
            self._online = False
        self.setCaptureLed()

    def setCaptureLed(self):
        if self._online:
            icn = QtGui.QIcon(':/icons/led_green.svg')
        else:
            icn = QtGui.QIcon(':/icons/led_green_off.svg')
        self.captureButton.setIcon(icn)
        self.captureButton.setChecked(self._online)

    def setReceivingLed(self):
        if self._receiving:
            icn = QtGui.QIcon(':/icons/led_red.svg')
        else:
            icn = QtGui.QIcon(':/icons/led_red_off.svg')
        self.receivingButton.setIcon(icn)


class GPIBReceiver(QtCore.QThread):
    plotStarted = pyqtSignal()
    plotReceived = pyqtSignal(int)

    def __init__(self, cnx, parent=None):
        super().__init__(parent)
        self.gpibplotter = cnx
        self.gpibplotter.plot_started_cb = self.plotStarted.emit

        self._cancel = False
        # self._nreceived = 0
        self._plotsmutex = QtCore.QMutex()
        self._plots = []
        self._capturing = False

    def cancel(self):
        self._cancel = True

    def startCapture(self):
        self._capturing = True

    def stopCapture(self):
        self._capturing = False

    def run(self):
        while not self._cancel:
            if not self._capturing:
                self.msleep(100)
                continue

            plot = self.gpibplotter.load_plot(wait_timeout=0.1)
            timestamp = time.time()
            if plot:
                self._plotsmutex.lock()
                self._plots.append((plot, timestamp))
                n = len(self._plots)
                self._plotsmutex.unlock()

                self.plotReceived.emit(n - 1)
            self.msleep(10)

    def getPlot(self, num):
        self._plotsmutex.lock()
        try:
            return self._plots[num]
        finally:
            self._plotsmutex.unlock()


def main():
    global GPIBplotter

    import optparse
    opt = optparse.OptionParser(
        'A simple PyQt4 HP7470A GPIB plotter emulator for USB-GPIB bundle '
        '(Prologix)')
    opt.add_option('-m', '--mockup', default=False,
                   action="store_true",
                   dest='mockup',
                   help='Use a pseudo GPIB connection (for test purpose)',
                   )
    opt.add_option('-v', '--verbose', default=False,
                   action="store_true",
                   dest="verbose",
                   help="Verbose mode",)

    options, argv = opt.parse_args(sys.argv)
    if options.mockup:
        from pygpibtoolkit.plotter.gpib_plotter_mockup import GPIBplotter

    a = QtWidgets.QApplication(argv)
    w = QtHPGLPlotter()
    files = [f for f in argv[1:] if os.path.isfile(f)]
    files.sort(key=str_num_key)
    if w.openFiles(files):
        w.displayFirst()

    w.show()
    a.exec_()


if __name__ == '__main__':
    main()

mercurial