[HP3562A] port HP3562A app to Py3k/Qt5

Tue, 15 May 2018 16:09:16 +0200

author
David Douard <david.douard@logilab.fr>
date
Tue, 15 May 2018 16:09:16 +0200
changeset 89
61074a32808a
parent 88
2612a4d53bf8
child 90
869de27dedc7

[HP3562A] port HP3562A app to Py3k/Qt5

pygpibtoolkit/HP3562A/datablockwidget.py file | annotate | diff | comparison | revisions
pygpibtoolkit/HP3562A/q3562A.py file | annotate | diff | comparison | revisions
pygpibtoolkit/tools.py file | annotate | diff | comparison | revisions
setup.py file | annotate | diff | comparison | revisions
--- a/pygpibtoolkit/HP3562A/datablockwidget.py	Tue May 15 16:08:33 2018 +0200
+++ b/pygpibtoolkit/HP3562A/datablockwidget.py	Tue May 15 16:09:16 2018 +0200
@@ -10,23 +10,27 @@
 # 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-2008 David Douard (Paris, FRANCE).
-http://www.logilab.org/project/pygpibtoolkit -- mailto:david.douard@logilab.fr
+""" Copyright (c) 2007-2018 David Douard (Paris, FRANCE).
+http://www.logilab.org/project/pygpibtoolkit -- mailto:david.douard@sdfa3.org
 """
 
 import re
 import numpy
-from itertools import izip, count as icount
+
+from PyQt5 import QtCore, QtWidgets
+from PyQt5.QtCore import Qt
+
 
-from PyQt4 import QtCore, QtGui
-from PyQt4.QtCore import Qt, SIGNAL
-import  state_decoder
-import  trace_decoder
-import  coord_decoder
 from pygpibtoolkit.qt4.mpl import QMplCanvas, FigureCanvas
+
+from pygpibtoolkit.HP3562A import state_decoder
+from pygpibtoolkit.HP3562A import trace_decoder
+from pygpibtoolkit.HP3562A import coord_decoder
 from pygpibtoolkit.HP3562A import mathtools
 
-children = [ ]
+
+children = []
+
 
 def getChild(datablock):
     """
@@ -37,20 +41,22 @@
             return child
     return None
 
-class DatablockMDIChild(QtGui.QMainWindow):
+
+class DatablockMDIChild(QtWidgets.QMainWindow):
     seqnumber = 1
     _username = "Window"
-    
+
     @classmethod
     def isValidDatablock(cls, datablock):
         return False
-    
+
     def __init__(self, datablock, name=None):
-        QtGui.QMainWindow.__init__(self)
+        super().__init__()
         if name is not None:
             self.username = name
         else:
-            self.username = self.__class__._username + " " + str(self.seqnumber)
+            self.username = "{} {}".format(
+                self.__class__._username, self.seqnumber)
         self.setAttribute(Qt.WA_DeleteOnClose)
         self.isUntitled = True
         self.dataIsModified = False
@@ -61,47 +67,50 @@
 
     def setDatablock(self, datablock):
         self.datablock = datablock
-        
+
     def setupUI(self):
         # setup headers views as a docked window
         assert isinstance(self._header_struct, tuple)
         self.headerDocks = []
         self.tables = []
         for i, header_struct in enumerate(self._header_struct):
-            dock = QtGui.QDockWidget("Header" + (i>0 and (' %s'%(i+1)) or ''),
-                                     self)
-            #dock.setFeatures(dock.NoDockWidgetFeatures) 
-            sarea = QtGui.QScrollArea(dock)
+            dock = QtWidgets.QDockWidget(
+                "Header" + (i > 0 and (' %s' % (i+1)) or ''),
+                self)
+            # dock.setFeatures(dock.NoDockWidgetFeatures)
+            sarea = QtWidgets.QScrollArea(dock)
             dock.setWidget(sarea)
             self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
             self.headerDocks.append(dock)
 
-            l = QtGui.QVBoxLayout(sarea)
-            l.setContentsMargins(0,0,0,0)
-            table = QtGui.QTableWidget(sarea)
-            table.setStyleSheet('font-size: 10px;')
-            self.setupRowsHeight(table)
+            l = QtWidgets.QVBoxLayout(sarea)
+            l.setContentsMargins(0, 0, 0, 0)
+            table = QtWidgets.QTableWidget(sarea)
+            # table.setStyleSheet('font-size: 10px;')
+            # self.setupRowsHeight(table)
             table.setShowGrid(False)
             table.setAlternatingRowColors(True)
             table.verticalHeader().hide()
             l.addWidget(table, 1)
             self.tables.append(table)
-            
+
     def setupRowsHeight(self, table):
-        if table.verticalHeader().minimumSectionSize()>0:
+        if table.verticalHeader().minimumSectionSize() > 0:
             cellsize = table.verticalHeader().minimumSectionSize()
         else:
             cellsize = 15
         table.verticalHeader().setDefaultSectionSize(cellsize)
 
     def updateHeaderData(self):
-        for header, table, header_struct in izip(self.header, self.tables, self._header_struct):
+        for header, table, header_struct in zip(
+                self.header, self.tables, self._header_struct):
             table.clear()
             table.setRowCount(len(header_struct))
             table.setColumnCount(2)
             table.setHorizontalHeaderLabels(['Parameter', 'Value'])
-            bool_re = re.compile(r'((?P<before>.*) )?(?P<flag>\w+/\w+)( (?P<after>.*))?')
-            item = QtGui.QTableWidgetItem()
+            bool_re = re.compile(
+                r'((?P<before>.*) )?(?P<flag>\w+/\w+)( (?P<after>.*))?')
+            item = QtWidgets.QTableWidgetItem()
             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
             for i, row in enumerate(header_struct):
                 pname = row[0]
@@ -110,9 +119,7 @@
                 if typ is None:
                     continue
                 val = header.get(key, "N/A")
-                if isinstance(val, basestring):
-                    val = unicode(val)
-                elif typ is bool and isinstance(val, typ):
+                if typ is bool and isinstance(val, typ):
                     m = bool_re.match(key)
                     if m:
                         d = m.groupdict()
@@ -125,29 +132,29 @@
                         val = d['flag'].split('/')[not val]
                     else:
                         val = str(val)
-                else:            
+                else:
                     val = str(val)
                 val = val.strip()
                 if val:
-                    if val[0]+val[-1] in ['""',"''"]:
+                    if val[0]+val[-1] in ['""', "''"]:
                         val = val[1:-1]
-                    if val[0:2]+val[-1] in ['u""',"u''"]:
+                    if val[0:2]+val[-1] in ['u""', "u''"]:
                         val = val[2:-1]
                     while val and val.endswith(chr(0)):
                         val = val[:-1]
-                item_ = QtGui.QTableWidgetItem(item)
+                item_ = QtWidgets.QTableWidgetItem(item)
                 item_.setText(key)
                 table.setItem(i, 0, item_)
-                item_ = QtGui.QTableWidgetItem(item)
+                item_ = QtWidgets.QTableWidgetItem(item)
                 item_.setText(val)
                 table.setItem(i, 1, item_)
             table.resizeColumnsToContents()
-            #table.resizeRowsToContents()
-            #self.setupRowsHeight(self.table)    
-        
+            # table.resizeRowsToContents()
+            # self.setupRowsHeight(self.table)
+
     def userFriendlyName(self):
         return self.username
-    
+
     def closeEvent(self, event):
         if self.maybeSave():
             event.accept()
@@ -156,90 +163,90 @@
 
     def maybeSave(self):
         if self.dataIsModified:
-            ret = QtGui.QMessageBox.warning(self, self.tr("MDI"),
-                    self.tr("'%1' has been modified.\n"\
-                            "Do you want to save your changes?")
-                    .arg(self.userFriendlyCurrentFile()),
-                    QtGui.QMessageBox.Yes | QtGui.QMessageBox.Default,
-                    QtGui.QMessageBox.No,
-                    QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Escape)
-            if ret == QtGui.QMessageBox.Yes:
+            ret = QtWidgets.QMessageBox.warning(
+                self, self.tr("MDI"),
+                self.tr("'%1' has been modified.\n"
+                        "Do you want to save your changes?")
+                .arg(self.userFriendlyCurrentFile()),
+                QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Default,
+                QtWidgets.QMessageBox.No,
+                QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Escape)
+            if ret == QtWidgets.QMessageBox.Yes:
                 return self.save()
-            elif ret == QtGui.QMessageBox.Cancel:
+            elif ret == QtWidgets.QMessageBox.Cancel:
                 return False
         return True
 
+
 class StateBinaryDatablockMDIChild(DatablockMDIChild):
     _username = "State"
-    _header_struct = state_decoder.HEADER,
+    _header_struct = (state_decoder.HEADER, )
+
     @classmethod
     def isValidDatablock(cls, datablock):
         try:
             h = state_decoder.decode_state(datablock)
-            assert len(h)>0
+            assert len(h) > 0
             return True
-        except Exception, e:
+        except Exception as e:
             return False
 
     def setDatablock(self, datablock):
-        DatablockMDIChild.setDatablock(self, datablock)
+        super().setDatablock(datablock)
         self.header = [state_decoder.decode_state(self.datablock)]
 
+
 class TraceBinaryDatablockMDIChild(DatablockMDIChild):
     _username = "Trace"
-    _header_struct = trace_decoder.HEADER,
+    _header_struct = (trace_decoder.HEADER, )
+
     @classmethod
     def isValidDatablock(cls, datablock):
         try:
             h, t = trace_decoder.decode_trace(datablock)
-            assert len(h)>0
+            assert len(h) > 0
             assert len(t)
             return True
-        except Exception, e:
+        except Exception as e:
             return False
 
     def __init__(self, datablock, name=None):
-        DatablockMDIChild.__init__(self, datablock, name)
+        super().__init__(datablock, name)
         self.updateTraceData()
-        
+
     def setDatablock(self, datablock):
-        DatablockMDIChild.setDatablock(self, datablock)
+        super().setDatablock(datablock)
         self.header, self.trace = trace_decoder.decode_trace(self.datablock)
         self.header = [self.header]
-        
+
     def setupToolBar(self):
-        toolbar = QtGui.QToolBar(self)
+        toolbar = QtWidgets.QToolBar(self)
         self.addToolBar(toolbar)
-        self.ylogaction = QtGui.QAction(self.tr("Y Log"), self)
+        self.ylogaction = QtWidgets.QAction(self.tr("Y Log"), self)
         self.ylogaction.setCheckable(True)
-        self.connect(self.ylogaction, SIGNAL('toggled(bool)'),
-                     self.updateTraceData)
+        self.ylogaction.toggled.connect(self.updateTraceData)
         toolbar.addAction(self.ylogaction)
-        
-        
+
     def setupUI(self):
         self.setupToolBar()
-        DatablockMDIChild.setupUI(self)
-        mainw = QtGui.QWidget(self)
-        l = QtGui.QVBoxLayout(mainw)
-        l.setMargin(0)
         self.canvas = QMplCanvas(self)
+        super().setupUI()
+        mainw = QtWidgets.QWidget(self)
+        l = QtWidgets.QVBoxLayout(mainw)
+        l.setContentsMargins(0, 0, 0, 0)
         l.addWidget(self.canvas, 1)
-        
+
         self.setCentralWidget(mainw)
-        
+
     def updateTraceData(self):
-        #self.canvas.deleteLater()
-        #self.canvas = QMplCanvas()
-        #self.centralWidget().layout().addWidget(self.canvas, 1)
         f0 = self.header[0]['Start freq value']
         dx = self.header[0]['Delta X-axis']
         n = self.header[0]['Number of elements']
-        x = numpy.linspace(f0, f0+dx*n, len(self.trace)) 
+        x = numpy.linspace(f0, f0+dx*n, len(self.trace))
         y = self.trace.copy()
         if self.ylogaction.isChecked():
-            minv = min(y[y>0])
-            y[y==0] = minv
+            minv = min(y[y > 0])
+            y[y == 0] = minv
             y = numpy.log10(y)
             y = y*10
         self.canvas.axes.clear()
@@ -255,49 +262,52 @@
         if self.header[0]['Display function']:
             self.canvas.axes.set_title(self.header[0]['Display function'])
 
+        # compute THD, if any
         y = self.trace.copy()
         if f0 > 0:
             # must add some initial zeros
-            yy = numpy.zeros(f0/dx+len(y))
+            yy = numpy.zeros(int(f0/dx) + len(y))
             yy[-len(y):] = y
             y = yy
         msg = ""
         try:
             f0, thd = mathtools.thd(y, db=True)
-            f0 = f0*dx
+            f0 = f0 * dx
             assert thd
         except:
             pass
         else:
-            msg += 'THD:%.2g db  Freq:%.2f Hz  '%(thd, f0)
+            msg += 'THD:%.2g db  Freq:%.2f Hz  ' % (thd, f0)
         try:
             thdn = mathtools.thd_n(y, db=True)
         except:
             pass
         else:
-            msg += 'THD+N:%.2g db  '%thdn
+            msg += 'THD+N:%.2g db  ' % thdn
         self.statusBar().showMessage(msg)
         self.canvas.draw() 
         
         
 class CoordBinaryDatablockMDIChild(TraceBinaryDatablockMDIChild):
     _username = "Coord"
-    _header_struct = coord_decoder.TRACE_HEADER, coord_decoder.HEADER,  
+    _header_struct = (coord_decoder.TRACE_HEADER, coord_decoder.HEADER, )
+
     @classmethod
     def isValidDatablock(cls, datablock):
         try:
             h1, h2, t = coord_decoder.decode_coord(datablock)
-            assert len(h1)>0
-            assert len(h2)>0
-            assert len(t)>0
+            assert len(h1) > 0
+            assert len(h2) > 0
+            assert len(t) > 0
             return True
-        except Exception, e:
+        except Exception as e:
             return False
+
     def setupToolBar(self):
         pass
 
     def setDatablock(self, datablock):
-        DatablockMDIChild.setDatablock(self, datablock)
+        super().setDatablock(datablock)
         h1, h2, self.trace = coord_decoder.decode_coord(self.datablock)
         self.header = [h2, h1]
 
@@ -305,7 +315,7 @@
         f0 = self.header[0]['Start freq value']
         dx = self.header[0]['Delta X-axis']
         n = self.header[0]['Number of elements']
-        x = numpy.linspace(f0, f0+dx*n, len(self.trace)) 
+        x = numpy.linspace(f0, f0+dx*n, len(self.trace))
         y = self.trace.copy()
 
         y = y.clip(min=self.header[1]['Min value of data'],
@@ -330,6 +340,3 @@
 children.append(CoordBinaryDatablockMDIChild)
 children.append(TraceBinaryDatablockMDIChild)
 children.append(StateBinaryDatablockMDIChild)
-
-
-    
--- a/pygpibtoolkit/HP3562A/q3562A.py	Tue May 15 16:08:33 2018 +0200
+++ b/pygpibtoolkit/HP3562A/q3562A.py	Tue May 15 16:09:16 2018 +0200
@@ -11,35 +11,34 @@
 # 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-2008 David Douard (Paris, FRANCE).
-http://www.logilab.org/project/pygpibtoolkit -- mailto:david.douard@logilab.fr
+""" Copyright (c) 2007-2018 David Douard (Paris, FRANCE).
+http://www.logilab.org/project/pygpibtoolkit -- mailto:david.douard@sdfa3.org
 """
 
-import os, sys
-import time
-import glob
+import os
+import sys
 
-from PyQt4 import QtGui, QtCore, uic
-from PyQt4.QtCore import SIGNAL, Qt
+from PyQt5 import QtWidgets, QtGui, QtCore
+from PyQt5.QtCore import pyqtSignal
 
-# WARNING, may be "replaced" by mockup
-from dump_datablock import HP3562dumper
+from pygpibtoolkit.tools import str_num_key
 
-from pygpibtoolkit.qt4.qpreferences import BaseItem, IntItem, UnicodeItem, ColorItem
-from pygpibtoolkit.qt4.qpreferences import PointItem, SizeItem, ByteArrayItem
-from pygpibtoolkit.qt4.qpreferences import AbstractPreferences
-from pygpibtoolkit.qt4.qpreferenceseditor import PreferencesEditor
+from pygpibtoolkit.qt5.qpreferences import (
+    BaseItem, IntItem, UnicodeItem, ColorItem,
+    PointItem, SizeItem, ByteArrayItem, AbstractPreferences)
+from pygpibtoolkit.qt5.qpreferenceseditor import PreferencesEditor
+import pygpibtoolkit.qt5.resources_rc
+import pygpibtoolkit.qt5.icons_ressource_rc
 
-from pygpibtoolkit.tools import str_num_cmp
+# WARNING, may be "replaced" by mock
+from pygpibtoolkit.HP3562A.dump_datablock import HP3562dumper
 
-import pygpibtoolkit.qt4.resources_rc
-import pygpibtoolkit.qt4.icons_ressource_rc
+from pygpibtoolkit.HP3562A.datablockwidget import getChild
 
-from datablockwidget import getChild
 
 class Preferences(AbstractPreferences):
-    ORGANISATION="Logilab"
-    APPLICATION="q3562"
+    ORGANISATION = "Logilab"
+    APPLICATION = "q3562"
 
     _pos = PointItem()
     _size = SizeItem()
@@ -52,24 +51,23 @@
     address = IntItem(default=0, min=0, max=16,
                       name=u'GPIB address',
                       group="GPIB settings")
-    
+
     background = ColorItem(default=QtGui.QColor("white"),
                            name="Plots background",
                            group="Colors")
-    
-class Qt3562(QtGui.QMainWindow):
+
+
+class Qt3562(QtWidgets.QMainWindow):
     def __init__(self, parent=None):
-        QtGui.QMainWindow.__init__(self, parent)
-        self.workspace = QtGui.QWorkspace()
+        super().__init__(parent)
+        self.workspace = QtWidgets.QMdiArea(self)
         self.setCentralWidget(self.workspace)
-        self.connect(self.workspace, SIGNAL("windowActivated(QWidget *)"),
-                     self.updateMenus)
+        self.workspace.subWindowActivated.connect(self.updateMenus)
         self.windowMapper = QtCore.QSignalMapper(self)
-        self.connect(self.windowMapper, QtCore.SIGNAL("mapped(QWidget *)"),
-                     self.workspace, QtCore.SLOT("setActiveWindow(QWidget *)"))
-        
+        self.windowMapper.mapped.connect(self.workspace.setActiveSubWindow)
+
         self._receiving = False
-        
+
         self.createActions()
         self.createMenus()
         self.createToolBars()
@@ -88,131 +86,130 @@
 
     def readPreferences(self):
         bg = self._prefs.background
-        #pen_colors = [self._prefs["color%d"%i] for i in range(8)]
-        #self.plotterWidget.pen_colors = pen_colors
-                
+        # pen_colors = [self._prefs["color%d"%i] for i in range(8)]
+        # self.plotterWidget.pen_colors = pen_colors
+
     def createActions(self):
-        self.openAct = QtGui.QAction(QtGui.QIcon(":/icons/open.png"),
-                        self.tr("&Open..."), self)
-        self.openAct.setShortcut(self.tr("Ctrl+O"))
-        self.openAct.setStatusTip(self.tr("Open an existing data block file"))
-        self.connect(self.openAct, QtCore.SIGNAL("triggered()"), self.open)
-
-        self.saveAsAct = QtGui.QAction(self.tr("Save &As..."), self)
-        self.saveAsAct.setStatusTip(self.tr("Save the document under a new name"))
-        self.connect(self.saveAsAct, QtCore.SIGNAL("triggered()"), self.saveAs)
+        self.openAct = QtWidgets.QAction(
+            QtGui.QIcon(":/icons/open.png"),
+            self.tr("&Open..."), self,
+            shortcut=self.tr("Ctrl+O"),
+            statusTip=self.tr("Open an existing data block file"),
+            triggered=self.open)
 
-        self.exitAct = QtGui.QAction(self.tr("E&xit"), self)
-        self.exitAct.setShortcut(self.tr("Ctrl+Q"))
-        self.exitAct.setStatusTip(self.tr("Exit the application"))
-        self.connect(self.exitAct, QtCore.SIGNAL("triggered()"), self.close)
-                
-        self.closeAct = QtGui.QAction(self.tr("Cl&ose"), self)
-        self.closeAct.setShortcut(self.tr("Ctrl+W"))
-        self.closeAct.setStatusTip(self.tr("Close the active window"))
-        self.connect(self.closeAct, QtCore.SIGNAL("triggered()"),
-                     self.workspace.closeActiveWindow)
+        self.saveAsAct = QtWidgets.QAction(
+            self.tr("Save &As..."),
+            self, statusTip=self.tr("Save the document under a new name"),
+            triggered=self.saveAs)
 
-        self.closeAllAct = QtGui.QAction(self.tr("Close &All"), self)
-        self.closeAllAct.setStatusTip(self.tr("Close all the windows"))
-        self.connect(self.closeAllAct, QtCore.SIGNAL("triggered()"),
-                     self.workspace.closeAllWindows)
+        self.exitAct = QtWidgets.QAction(
+            self.tr("E&xit"), self,
+            shortcut=self.tr("Ctrl+Q"),
+            statusTip=self.tr("Exit the application"),
+            triggered=self.close)
+
+        self.closeAct = QtWidgets.QAction(
+            self.tr("Cl&ose"), self,
+            shortcut=self.tr("Ctrl+W"),
+            statusTip=self.tr("Close the active window"),
+            triggered=self.workspace.closeActiveSubWindow)
 
-        self.tileAct = QtGui.QAction(self.tr("&Tile"), self)
-        self.tileAct.setStatusTip(self.tr("Tile the windows"))
-        self.connect(self.tileAct, QtCore.SIGNAL("triggered()"),
-                     self.workspace.tile)
+        self.closeAllAct = QtWidgets.QAction(
+            self.tr("Close &All"), self,
+            statusTip=self.tr("Close all the windows"),
+            triggered=self.workspace.closeAllSubWindows)
 
-        self.cascadeAct = QtGui.QAction(self.tr("&Cascade"), self)
-        self.cascadeAct.setStatusTip(self.tr("Cascade the windows"))
-        self.connect(self.cascadeAct, QtCore.SIGNAL("triggered()"),
-                     self.workspace.cascade)
+        self.tileAct = QtWidgets.QAction(
+            self.tr("&Tile"), self,
+            statusTip=self.tr("Tile the windows"),
+            triggered=self.workspace.tileSubWindows)
 
-        self.arrangeAct = QtGui.QAction(self.tr("Arrange &icons"), self)
-        self.arrangeAct.setStatusTip(self.tr("Arrange the icons"))
-        self.connect(self.arrangeAct, QtCore.SIGNAL("triggered()"),
-                     self.workspace.arrangeIcons)
+        self.cascadeAct = QtWidgets.QAction(
+            self.tr("&Cascade"), self,
+            statusTip=self.tr("Cascade the windows"),
+            triggered=self.workspace.cascadeSubWindows)
 
-        self.nextAct = QtGui.QAction(self.tr("Ne&xt"), self)
-        self.nextAct.setShortcut(self.tr("Ctrl+F6"))
-        self.nextAct.setStatusTip(self.tr("Move the focus to the next window"))
-        self.connect(self.nextAct, QtCore.SIGNAL("triggered()"),
-                     self.workspace.activateNextWindow)
+        #self.arrangeAct = QtWidgets.QAction(
+        #    self.tr("Arrange &icons"), self,
+        #    statusTip=self.tr("Arrange the icons"),
+        #    triggered=self.workspace.arrangeIcons)
 
-        self.previousAct = QtGui.QAction(self.tr("Pre&vious"), self)
-        self.previousAct.setShortcut(self.tr("Ctrl+Shift+F6"))
-        self.previousAct.setStatusTip(self.tr("Move the focus to the previous "
-                                              "window"))
-        self.connect(self.previousAct, QtCore.SIGNAL("triggered()"),
-                     self.workspace.activatePreviousWindow)
+        self.nextAct = QtWidgets.QAction(
+            self.tr("Ne&xt"), self,
+            shortcut=self.tr("Ctrl+F6"),
+            statusTip=self.tr("Move the focus to the next window"),
+            triggered=self.workspace.activateNextSubWindow)
 
-        self.separatorAct = QtGui.QAction(self)
+        self.previousAct = QtWidgets.QAction(
+            self.tr("Pre&vious"), self,
+            shortcut=self.tr("Ctrl+Shift+F6"),
+            statusTip=self.tr("Move the focus to the previous window"),
+            triggered=self.workspace.activatePreviousSubWindow)
+
+        self.separatorAct = QtWidgets.QAction(self)
         self.separatorAct.setSeparator(True)
 
-        self.aboutAct = QtGui.QAction(self.tr("&About"), self)
-        self.aboutAct.setStatusTip(self.tr("Show the application's About box"))
-        self.connect(self.aboutAct, QtCore.SIGNAL("triggered()"), self.about)
+        self.aboutAct = QtWidgets.QAction(
+            self.tr("&About"), self,
+            statusTip=self.tr("Show the application's About box"),
+            triggered=self.about)
 
-        self.aboutQtAct = QtGui.QAction(self.tr("About &Qt"), self)
-        self.aboutQtAct.setStatusTip(self.tr("Show the Qt library's About box"))
-        self.connect(self.aboutQtAct, QtCore.SIGNAL("triggered()"),
-                     QtGui.qApp, QtCore.SLOT("aboutQt()"))
-
+        self.aboutQtAct = QtWidgets.QAction(
+            self.tr("About &Qt"), self,
+            statusTip=self.tr("Show the Qt library's About box"),
+            triggered=QtWidgets.qApp.aboutQt)
 
-        self.getStateAct = QtGui.QAction(QtGui.QIcon(":/icons/state.svg"),
-                                         self.tr("Get state"), self)
-        self.getStateAct.setCheckable(True)
-        self.getStateAct.setStatusTip(self.tr("Retrieve State from GPIB device"))
-        self.connect(self.getStateAct, QtCore.SIGNAL("triggered()"),
-                     self.getState)
-        self.getTraceAct = QtGui.QAction(QtGui.QIcon(":/icons/trace.svg"),
-                                         self.tr("Get trace"), self)
-        self.getTraceAct.setCheckable(True)
-        self.getTraceAct.setStatusTip(self.tr("Retrieve Trace from GPIB device"))
-        self.connect(self.getTraceAct, QtCore.SIGNAL("triggered()"),
-                     self.getTrace)
-        self.getCoordAct = QtGui.QAction(QtGui.QIcon(":/icons/coord.svg"),
-                                         self.tr("Get coord"), self)
-        self.getCoordAct.setCheckable(True)
-        self.getCoordAct.setStatusTip(self.tr("Retrieve Coord from GPIB device"))
-        self.connect(self.getCoordAct, QtCore.SIGNAL("triggered()"),
-                     self.getCoord)
+        self.getStateAct = QtWidgets.QAction(
+            QtGui.QIcon(":/icons/state.svg"),
+            self.tr("Get state"), self,
+            checkable=True,
+            statusTip=self.tr("Retrieve State from GPIB device"),
+            triggered=self.getState)
+
+        self.getTraceAct = QtWidgets.QAction(
+            QtGui.QIcon(":/icons/trace.svg"),
+            self.tr("Get trace"), self,
+            checkable=True,
+            statusTip=self.tr("Retrieve Trace from GPIB device"),
+            triggered=self.getTrace)
+
+        self.getCoordAct = QtWidgets.QAction(
+            QtGui.QIcon(":/icons/coord.svg"),
+            self.tr("Get coord"), self,
+            checkable=True,
+            statusTip=self.tr("Retrieve Coord from GPIB device"),
+            triggered=self.getCoord)
 
-        self.channelActGroups = QtGui.QActionGroup(self)
-        self.channelActGroups.setExclusive(True)
-        self.channelAAct = QtGui.QAction(QtGui.QIcon(":/icons/displayA.svg"),
-                                         self.tr("Channel A"), self)
-        self.channelAAct.setCheckable(True)
-        self.channelAAct.setChecked(True)
-        self.channelAAct.setStatusTip(self.tr("Retrieve from channel A"))
+        self.channelActGroups = QtWidgets.QActionGroup(self, exclusive=True)
+        self.channelAAct = QtWidgets.QAction(
+            QtGui.QIcon(":/icons/displayA.svg"),
+            self.tr("Channel A"), self,
+            checkable=True, checked=True,
+            statusTip=self.tr("Retrieve from channel A"))
         self.channelActGroups.addAction(self.channelAAct)
-        self.channelBAct = QtGui.QAction(QtGui.QIcon(":/icons/displayB.svg"),
-                                         self.tr("Channel B"), self)
-        self.channelBAct.setCheckable(True)
-        self.channelBAct.setStatusTip(self.tr("Retrieve from channel B"))
+
+        self.channelBAct = QtWidgets.QAction(
+            QtGui.QIcon(":/icons/displayB.svg"),
+            self.tr("Channel B"), self,
+            checkable=True, checked=False,
+            statusTip=self.tr("Retrieve from channel B"))
         self.channelActGroups.addAction(self.channelBAct)
-        
-        
+
     def setupUi(self):
-        self.connect(self.actionPreferences, SIGNAL('triggered(bool)'),
-                     self.preferencesTriggered)
-        self.connect(self.actionQuit, SIGNAL('triggered(bool)'),
-                     self.quitTriggered)
+        self.actionPreferences.triggered.connect(self.preferencesTriggered)
+        self.actionQuit.triggered.connect(self.quitTriggered)
         self.actionQuit.setShortcut(QtGui.QKeySequence(u'Ctrl+Q'))
-        self.connect(self.actionOpen, SIGNAL('triggered(bool)'),
-                     self.openTriggered)
+        self.actionOpen.triggered.connect(self.openTriggered)
         self.actionOpen.setShortcut(QtGui.QKeySequence(u'Ctrl+O'))
-        self.connect(self.actionSave, SIGNAL('triggered(bool)'),
-                     self.saveTriggered)
+        self.actionSave.triggered.connect(self.saveTriggered)
         self.actionSave.setShortcut(QtGui.QKeySequence(u'Ctrl+S'))
-        self.connect(self.actionSaveAs, SIGNAL('triggered(bool)'),
-                     self.saveAsTriggered)
+        self.actionSaveAs.triggered.connect(self.saveAsTriggered)
 
-        
     def about(self):
-        QtGui.QMessageBox.about(self, self.tr("About Q3562"),
+        QtWidgets.QMessageBox.about(
+            self, self.tr("About Q3562"),
             self.tr("A simple application to talk to the HP3562A DSA."))
-          
+
     def updateMenus(self):
         hasMdiChild = (self.activeMdiChild() is not None)
         self.saveAsAct.setEnabled(hasMdiChild)
@@ -220,11 +217,11 @@
         self.closeAllAct.setEnabled(hasMdiChild)
         self.tileAct.setEnabled(hasMdiChild)
         self.cascadeAct.setEnabled(hasMdiChild)
-        self.arrangeAct.setEnabled(hasMdiChild)
+        # self.arrangeAct.setEnabled(hasMdiChild)
         self.nextAct.setEnabled(hasMdiChild)
         self.previousAct.setEnabled(hasMdiChild)
         self.separatorAct.setVisible(hasMdiChild)
-                
+
     def updateWindowMenu(self):
         self.windowMenu.clear()
         self.windowMenu.addAction(self.closeAct)
@@ -232,29 +229,30 @@
         self.windowMenu.addSeparator()
         self.windowMenu.addAction(self.tileAct)
         self.windowMenu.addAction(self.cascadeAct)
-        self.windowMenu.addAction(self.arrangeAct)
+        # self.windowMenu.addAction(self.arrangeAct)
         self.windowMenu.addSeparator()
         self.windowMenu.addAction(self.nextAct)
         self.windowMenu.addAction(self.previousAct)
         self.windowMenu.addAction(self.separatorAct)
-        
-        windows = self.workspace.windowList()
+
+        windows = self.workspace.subWindowList()
         self.separatorAct.setVisible(len(windows) != 0)
-        
+
         i = 0
         for child in windows:
             if i < 9:
-                text = self.tr("&%1 %2").arg(i + 1).arg(child.userFriendlyName())
+                text = self.tr("&{0} {1}").format(
+                    i + 1, child.userFriendlyName())
             else:
-                text = self.tr("%1 %2").arg(i + 1).arg(child.userFriendlyName())
+                text = self.tr("%{0} {1}").format(
+                    i + 1, child.userFriendlyName())
             i += 1
             action = self.windowMenu.addAction(text)
             action.setCheckable(True)
             action.setChecked(child == self.activeMdiChild())
-            self.connect(action, QtCore.SIGNAL("triggered()"),
-                         self.windowMapper, QtCore.SLOT("map()"))
+            action.triggered.connect(self.windowMapper.map)
             self.windowMapper.setMapping(action, child)
-                
+
     def createMenus(self):
         self.fileMenu = self.menuBar().addMenu(self.tr("&File"))
         self.fileMenu.addAction(self.openAct)
@@ -271,14 +269,13 @@
         self.deviceMenu.addAction(self.getCoordAct)
 
         self.windowMenu = self.menuBar().addMenu(self.tr("&Window"))
-        self.connect(self.windowMenu, QtCore.SIGNAL("aboutToShow()"), 
-                     self.updateWindowMenu)
-        
+        self.windowMenu.aboutToShow.connect(self.updateWindowMenu)
+
         self.menuBar().addSeparator()
         self.helpMenu = self.menuBar().addMenu(self.tr("&Help"))
         self.helpMenu.addAction(self.aboutAct)
         self.helpMenu.addAction(self.aboutQtAct)
-        
+
     def createToolBars(self):
         self.fileToolBar = self.addToolBar(self.tr("File"))
         self.fileToolBar.setObjectName('filetoolbar')
@@ -294,10 +291,10 @@
         self.deviceToolBar.addAction(self.getStateAct)
         self.deviceToolBar.addAction(self.getTraceAct)
         self.deviceToolBar.addAction(self.getCoordAct)
-                
+
     def createStatusBar(self):
         self.statusBar().showMessage(self.tr("Ready"))
-                    
+
     def preferencesTriggered(self, checked=False):
         PreferencesEditor(self._prefs, self).exec_()
         self.readPreferences()
@@ -312,28 +309,31 @@
             event.accept()
         else:
             event.ignore()
-        
+
     def open(self, checked=False):
-        filenames = QtGui.QFileDialog.getOpenFileNames(self, "Open a dumped data block file", '.', 'bin files (*.bin)\nAll files (*)')
-        self.openFiles(filenames)
-                
+        filenames = QtWidgets.QFileDialog.getOpenFileNames(
+            self, "Open a dumped data block file", '.',
+            'bin files (*.bin)\nAll files (*)')
+        self.openFiles(filenames[0])
+
     def openFiles(self, filenames):
         ok = False
         for filename in filenames:
             filename = str(filename)
             if os.path.exists(filename):
-                data = open(filename).read()
+                with open(filename, 'rb') as f:
+                    data = f.read()
                 name = os.path.basename(filename)
-                #name = os.path.splitext(name)[0]
+                # name = os.path.splitext(name)[0]
                 child_cls = getChild(data)
                 assert child_cls
                 child = child_cls(data, name=name)
-                self.workspace.addWindow(child)
+                self.workspace.addSubWindow(child)
                 child.show()
                 ok = True
         self.statusBar().showMessage(self.tr("File(s) loaded"), 2000)
         return ok
-                        
+
     def saveAs(self, checked=False):
         return
 #         index = self.plotsView.selectionModel().currentIndex()
@@ -342,30 +342,29 @@
 #             n = index.row()
 #             value = unicode(self.plotsView.model().data(index, Qt.DisplayRole).toString())
 #             open(filename, 'w').write(self._plots[value])
-            
+
     def activeMdiChild(self):
-        return self.workspace.activeWindow()
-        
+        return self.workspace.activeSubWindow()
+
     def initializeGPIB(self):
         try:
             self.gpib_dumper = HP3562dumper(device=self._prefs.device,
                                             address=self._prefs.address,
                                             )
             self.captureThread = GPIBConnection(self.gpib_dumper)
-            self.connect(self.captureThread, SIGNAL('datablockDumped(const QByteArray)'),
-                         self.datablockReceived)
+            self.captureThread.datablockDumped.connect(self.datablockReceived)
             self.captureThread.start()
-        except Exception, e:
-            print "ERROR: cannot initialize GPIB"
-            print e
+        except Exception as e:
+            print("ERROR: cannot initialize GPIB")
+            print(e)
             self.gpib_plotter = None
 
     def datablockReceived(self, datablock):
-        datablock = datablock.data()
+        # datablock = datablock.data()
         child_cls = getChild(datablock)
         assert child_cls
         child = child_cls(datablock)
-        self.workspace.addWindow(child)
+        self.workspace.addSubWindow(child)
         child.show()
         self.getStateAct.setChecked(False)
         self.getTraceAct.setChecked(False)
@@ -374,53 +373,62 @@
         self._receiving = False
         # give control back to fron panel
         self.captureThread.sendCommand('LCL')
-        
+
     def selectChannel(self):
         if self.channelAAct.isEnabled():
             self.captureThread.sendCommand("A")
         else:
             self.captureThread.sendCommand("B")
-        
+
     def getState(self):
         if self._receiving:
             return
         self._receiving = True
-        self.statusBar().showMessage(self.tr("Waiting for a %1 data block").arg(self.tr('state')))
+        self.statusBar().showMessage(
+            self.tr("Waiting for a %s data block") % (self.tr('state')))
         self.getStateAct.setChecked(True)
-        self.captureThread.startCapture(mode="state")
-        
+        if self.captureThread:
+            self.captureThread.startCapture(mode="state")
+
     def getTrace(self):
         if self._receiving:
             return
-        self._receiving = True        
-        self.statusBar().showMessage(self.tr("Waiting for a %1 data block").arg(self.tr('trace')))
+        self._receiving = True
+        self.statusBar().showMessage(
+            self.tr("Waiting for a %s data block") % (self.tr('trace')))
         self.getTraceAct.setChecked(True)
         self.selectChannel()
-        self.captureThread.startCapture(mode="trace")
+        if self.captureThread:
+            self.captureThread.startCapture(mode="trace")
 
     def getCoord(self):
         if self._receiving:
             return
-        self._receiving = True        
-        self.statusBar().showMessage(self.tr("Waiting for a %1 data block").arg(self.tr('coord')))
+        self._receiving = True
+        self.statusBar().showMessage(
+            self.tr("Waiting for a %s data block") % (self.tr('coord')))
         self.getCoordAct.setChecked(True)
         self.selectChannel()
-        self.captureThread.startCapture(mode="coord")
+        if self.captureThread:
+            self.captureThread.startCapture(mode="coord")
+
 
 class GPIBConnection(QtCore.QThread):
+    datablockDumped = pyqtSignal(object)
+
     def __init__(self, cnx):
         QtCore.QThread.__init__(self)
         self.gpibdumper = cnx
-        
+
         self._cancelmutex = QtCore.QMutex()
         self._cancel = False
         self._modemutex = QtCore.QMutex()
         self._mode = None
-        #self._nreceived = 0
+        # self._nreceived = 0
         self._startstopmutex = QtCore.QMutex()
         self._startstop = QtCore.QWaitCondition()
         self._capturing = False
-        
+
     def cancel(self):
         self._cancelmutex.lock()
         self._cancel = True
@@ -430,11 +438,11 @@
         if self._capturing:
             return
         return self.gpibdumper.send_command(cmd)
-        
+
     def startCapture(self, mode):
         self._modemutex.lock()
         self._mode = mode
-        self._modemutex.unlock()        
+        self._modemutex.unlock()
         self._startstop.wakeOne()
 
     def stopCapture(self):
@@ -458,15 +466,15 @@
             self._modemutex.unlock()
             datablock = self.gpibdumper.dump(mode=mode)
             self._capturing = False
-            timestamp = time.time()
             if datablock:
-                self.emit(SIGNAL('datablockDumped(const QByteArray)'),
-                          QtCore.QByteArray(datablock))
+                self.datablockDumped.emit(datablock)
             self.msleep(10)
-    
+
+
 def main():
     import optparse
-    opt = optparse.OptionParser('A simple PyQt4 HP7470A GPIB plotter emulator for USB-GPIB bundle (ProLogix)')
+    opt = optparse.OptionParser(
+        'A simple GPIB-based nterface for the HP3562A DSA')
     opt.add_option('-m', '--mockup', default=False,
                    action="store_true",
                    dest='mockup',
@@ -476,22 +484,22 @@
                    action="store_true",
                    dest="verbose",
                    help="Verbose mode",)
-    
+
     options, argv = opt.parse_args(sys.argv)
 
     if options.mockup:
         # overwrite "normal" module
         global HP3562dumper
-        from dump_datablock_mockup import HP3562dumper
+        from pygpibtoolkit.HP3562A.dump_datablock_mockup import HP3562dumper
 
-    a = QtGui.QApplication(argv)
+    a = QtWidgets.QApplication(argv)
     w = Qt3562()
     files = [f for f in argv[1:] if os.path.isfile(f)]
-    files.sort(cmp=str_num_cmp)
-    w.openFiles(files)    
+    files.sort(key=str_num_key)
+    w.openFiles(files)
 
     w.show()
     a.exec_()
-        
+
 if __name__ == '__main__':
     main()
--- a/pygpibtoolkit/tools.py	Tue May 15 16:08:33 2018 +0200
+++ b/pygpibtoolkit/tools.py	Tue May 15 16:09:16 2018 +0200
@@ -22,17 +22,12 @@
     return (a > b) - (a < b)
 
 
-def str_num_cmp(s1, s2):
-    """
-    string comparator function that will put 'toto_10' after 'toto_2'
+def str_num_key(s1):
+    """string comparator function that will allow to put 'toto_10' after 'toto_2'
     Also works for strings like 'toto_1_et_23er25'.
     """
     r = re.compile(r'((?<=\d)\D+|(?<=\D)\d+)')
-    r1 = r.split(s1)
-    r2 = r.split(s2)
-    r1 = [not x.isdigit() and x or int(x) for x in r1 if x]
-    r2 = [not x.isdigit() and x or int(x) for x in r2 if x]
-    return cmp(r1, r2)
+    return [not x.isdigit() and x or int(x) for x in r.split(s1) if x]
 
 
 class AbstractRegister:
--- a/setup.py	Tue May 15 16:08:33 2018 +0200
+++ b/setup.py	Tue May 15 16:09:16 2018 +0200
@@ -13,6 +13,7 @@
           'hp3562-state=pygpibtoolkit.HP3562A.state_decoder:main',
           'hp3562-trace=pygpibtoolkit.HP3562A.trace_decoder:main',
           'hp3562-dump=pygpibtoolkit.HP3562A.dump_datablock:main',
+          'hp3562=pygpibtoolkit.HP3562A.q3562A:main',
           'pygpib-plotter=pygpibtoolkit.plotter.qgpib_plotter:main',
           ]}
       )

mercurial