make qplotter a working app: can new load several hpgl files à display them; have a preference system

Wed, 16 Jan 2008 01:20:02 +0100

author
David Douard <david.douard@logilab.fr>
date
Wed, 16 Jan 2008 01:20:02 +0100
changeset 23
cb97962a1ae9
parent 22
b2f4646161be
child 24
44866ca03611

make qplotter a working app: can new load several hpgl files à display them; have a preference system

hpgl_plotter.qrc file | annotate | diff | comparison | revisions
hpgl_qt.py file | annotate | diff | comparison | revisions
icons/led_green.png file | annotate | diff | comparison | revisions
icons/led_green.svg file | annotate | diff | comparison | revisions
icons/led_green_off.png file | annotate | diff | comparison | revisions
icons/led_green_off.svg file | annotate | diff | comparison | revisions
qgpib_plotter.py file | annotate | diff | comparison | revisions
qhpgl_plotter.ui file | annotate | diff | comparison | revisions
qpreferences.py file | annotate | diff | comparison | revisions
qpreferences_dialog.ui file | annotate | diff | comparison | revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hpgl_plotter.qrc	Wed Jan 16 01:20:02 2008 +0100
@@ -0,0 +1,8 @@
+<RCC>
+    <qresource prefix="/" >
+        <file>icons/led_green.png</file>
+        <file>icons/led_green.svg</file>
+        <file>icons/led_green_off.png</file>
+        <file>icons/led_green_off.svg</file>
+    </qresource>
+</RCC>
--- a/hpgl_qt.py	Fri Jan 11 18:50:46 2008 +0100
+++ b/hpgl_qt.py	Wed Jan 16 01:20:02 2008 +0100
@@ -21,22 +21,31 @@
     
     def __init__(self, parent=None):
         QtGui.QWidget.__init__(self, parent)
+        l = QtGui.QVBoxLayout(self)
+        l.setMargin(1)
+        self.qview = QtGui.QGraphicsView(self)
+        self.qview.scale(0.5,-0.5)
+        l = self.layout()
+        l.addWidget(self.qview)
+        self.setBackgroundRole(QtGui.QPalette.Base)
+        self.setSizePolicy(QtGui.QSizePolicy.Expanding,
+                           QtGui.QSizePolicy.Expanding)
+        self.clear()
+        HPGLParser.__init__(self)
 
+    def parse(self, data):
+        HPGLParser.parse(self, data)
+        self.resize(self.size())
+        
+    def clear(self):
         self.qpen = QtGui.QPen(QtCore.Qt.blue)
         self.qbrush = QtGui.QBrush(QtCore.Qt.blue)
         self.qfont = QtGui.QFont('Courier') 
         self.qantialiased = False
         self.qtransformed = False
         self.qscene = QtGui.QGraphicsScene()
-        self.qview = QtGui.QGraphicsView(self.qscene, self)
-        self.qview.scale(0.5,-0.5)
-        l = QtGui.QVBoxLayout(self)
-        l.addWidget(self.qview)
-        self.setBackgroundRole(QtGui.QPalette.Base)
-        self.setSizePolicy(QtGui.QSizePolicy.Expanding,
-                           QtGui.QSizePolicy.Expanding)
-        HPGLParser.__init__(self)
-
+        self.qview.setScene(self.qscene)
+        
     def _get_PW(self):
         return self._pen_width
     def _set_PW(self, value):
Binary file icons/led_green.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icons/led_green.svg	Wed Jan 16 01:20:02 2008 +0100
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="210mm"
+   height="297mm"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.45.1"
+   sodipodi:docbase="/home/david/Electronic/HP3562/icons"
+   sodipodi:docname="led_green.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs4">
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4117">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop4119" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4121" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3136">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3138" />
+      <stop
+         style="stop-color:#00ff00;stop-opacity:0;"
+         offset="1"
+         id="stop3140" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3136"
+       id="radialGradient3142"
+       cx="327.41925"
+       cy="34.101982"
+       fx="327.41925"
+       fy="34.101982"
+       r="130.1324"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.5695707e-7,-3.2469858,2.296245,6.8708969e-6,366.91049,1504.183)" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4117"
+       id="linearGradient4123"
+       x1="260.95242"
+       y1="121.18842"
+       x2="488.65955"
+       y2="351.48065"
+       gradientUnits="userSpaceOnUse" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4117"
+       id="linearGradient4129"
+       gradientUnits="userSpaceOnUse"
+       x1="260.95242"
+       y1="121.18842"
+       x2="488.65955"
+       y2="351.48065"
+       gradientTransform="matrix(1.2457226,0,0,1.2615854,-176.7768,-90.496508)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.48082306"
+     inkscape:cx="372.04724"
+     inkscape:cy="586.3824"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     inkscape:window-width="996"
+     inkscape:window-height="714"
+     inkscape:window-x="0"
+     inkscape:window-y="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="opacity:1;fill:#00ff00;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.29390666;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29390666, 0.29390666;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect2160"
+       width="259.97089"
+       height="259.97089"
+       x="185.09927"
+       y="180.93974" />
+    <rect
+       y="176.38744"
+       x="180.54697"
+       height="269.0755"
+       width="269.0755"
+       id="rect2162"
+       style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#00c40b;stroke-width:9.17689896;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <rect
+       y="180.93974"
+       x="185.09927"
+       height="259.97089"
+       width="259.97089"
+       id="rect3134"
+       style="opacity:1;fill:url(#radialGradient3142);fill-opacity:1.0;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.29390666;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29390666, 0.29390666;stroke-dashoffset:0;stroke-opacity:1" />
+    <path
+       style="fill:url(#linearGradient4129);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 185.09375 180.9375 L 185.09375 352.5625 C 190.93752 352.79982 196.83856 352.9375 202.78125 352.9375 C 312.80014 352.9375 406.95049 311.61044 445.0625 253.25 L 445.0625 180.9375 L 185.09375 180.9375 z "
+       id="path3144" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path3146"
+       sodipodi:cx="303.646"
+       sodipodi:cy="129.98544"
+       sodipodi:rx="0"
+       sodipodi:ry="7.2791853"
+       d="M 303.646 129.98544 A 0 7.2791853 0 1 1  303.646,129.98544 A 0 7.2791853 0 1 1  303.646 129.98544 z" />
+  </g>
+</svg>
Binary file icons/led_green_off.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/icons/led_green_off.svg	Wed Jan 16 01:20:02 2008 +0100
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://web.resource.org/cc/"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="210mm"
+   height="297mm"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.45.1"
+   sodipodi:docbase="/home/david/Electronic/HP3562/icons"
+   sodipodi:docname="led_green_off.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient4117">
+      <stop
+         style="stop-color:#acacac;stop-opacity:0.68783069;"
+         offset="0"
+         id="stop4119" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop4121" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3136">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3138" />
+      <stop
+         style="stop-color:#00ff00;stop-opacity:0;"
+         offset="1"
+         id="stop3140" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4117"
+       id="linearGradient4129"
+       gradientUnits="userSpaceOnUse"
+       x1="260.95242"
+       y1="121.18842"
+       x2="488.65955"
+       y2="351.48065"
+       gradientTransform="matrix(1.2457226,0,0,1.2615854,-176.7768,-90.496508)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.48082306"
+     inkscape:cx="372.04724"
+     inkscape:cy="586.3824"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     inkscape:window-width="996"
+     inkscape:window-height="714"
+     inkscape:window-x="0"
+     inkscape:window-y="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Calque 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="opacity:1;fill:#009900;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:0.29390666;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:0.29390666, 0.29390666;stroke-dashoffset:0;stroke-opacity:1"
+       id="rect2160"
+       width="259.97089"
+       height="259.97089"
+       x="185.09927"
+       y="180.93974" />
+    <rect
+       y="176.38744"
+       x="180.54697"
+       height="269.0755"
+       width="269.0755"
+       id="rect2162"
+       style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#008304;stroke-width:9.17689896;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+    <path
+       style="fill:url(#linearGradient4129);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:5;stroke-linecap:butt;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 185.09375 180.9375 L 185.09375 352.5625 C 190.93752 352.79982 196.83856 352.9375 202.78125 352.9375 C 312.80014 352.9375 406.95049 311.61044 445.0625 253.25 L 445.0625 180.9375 L 185.09375 180.9375 z "
+       id="path3144" />
+    <path
+       sodipodi:type="arc"
+       style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path3146"
+       sodipodi:cx="303.646"
+       sodipodi:cy="129.98544"
+       sodipodi:rx="0"
+       sodipodi:ry="7.2791853"
+       d="M 303.646 129.98544 A 0 7.2791853 0 1 1  303.646,129.98544 A 0 7.2791853 0 1 1  303.646 129.98544 z" />
+  </g>
+</svg>
--- a/qgpib_plotter.py	Fri Jan 11 18:50:46 2008 +0100
+++ b/qgpib_plotter.py	Wed Jan 16 01:20:02 2008 +0100
@@ -1,34 +1,152 @@
 #
 
-from PyQt4 import QtGui, QtCore
+import os, sys
+
+from PyQt4 import QtGui, QtCore, uic
 from PyQt4.QtCore import SIGNAL, Qt
 
-from gppib_plotter import GPIBplotter
+from gpib_plotter import GPIBplotter
 from hpgl_qt import QHPGLPlotterWidget
 
-ORGANISATION="Logilab"
-APPLICATION="qgpib_plotter"
-def variant(v):
-    _cvrts = {1: lambda x:x.toInt(),
-              10: lambda x:unicode(x.toString()),
-              }
-    t = v.userType()
+form_class, base_class = uic.loadUiType(os.path.join(os.path.dirname(__file__), "qhpgl_plotter.ui"))
+
+from qpreferences import PreferenceItem, AbstractPreferences, PreferencesEditor
+
+class Preferences(AbstractPreferences):
+    ORGANISATION="Logilab"
+    APPLICATION="qgpib_plotter"
 
-class Preferences(object):
-    _defaults = {'device': '/dev/ttyUSB0',
-                 'address': 0,
-                 }
-    def __init__(self):
-        self._settings = qc.QSettings(qc.QSettings.UserScope,
-                                      ORGANISATION, APPLICATION)
-
-    def getPref(self, key):
-        val = self._settings.value(key, QtCore.QVariant(self._defaults[key])) 
-
-            
-class QtHPGLPlotter(QtGui.QMainWindow):
+    device = PreferenceItem('/dev/ttyUSB0', name=u'device', description=u'GPIB device')
+    address = PreferenceItem(5, name=u'GPIB address')
+    _pos = PreferenceItem(basetype=QtCore.QPoint)
+    _size = PreferenceItem(basetype=QtCore.QSize)
+    _appState = PreferenceItem(basetype=QtCore.QByteArray)
+    
+class QtHPGLPlotter(QtGui.QMainWindow, form_class):
     def __init__(self, parent=None):
         QtGui.QMainWindow.__init__(self, parent)
-        self.gpib_plotter = GPIBplotter(
+        self._plots = {}        
+        self._prefs = Preferences()
+        self.setupUi()
+        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)
+
+    def setupUi(self):
+        form_class.setupUi(self, self) # call qtdesigner generated form creation        
+        # actions defined in designer
+        self.connect(self.actionPreferences, SIGNAL('triggered(bool)'),
+                     self.preferencesTriggered)
+        self.connect(self.actionQuit, SIGNAL('triggered(bool)'),
+                     self.quitTriggered)
+        self.connect(self.actionOpen, SIGNAL('triggered(bool)'),
+                     self.openTriggered)
+        self.connect(self.actionSave, SIGNAL('triggered(bool)'),
+                     self.saveTriggered)
+        self.connect(self.actionSaveAs, SIGNAL('triggered(bool)'),
+                     self.saveAsTriggered)
+
+        self.plotterWidget = QHPGLPlotterWidget(self)
+        self.setCentralWidget(self.plotterWidget)
+
+        self.connect(self.captureButton, SIGNAL("toggled(bool)"),
+                     self.captureToggled)
+
+        self._plots_list = QtGui.QStringListModel()
+        self.plotsView.setModel(self._plots_list)
+        self.connect(self.plotsView, SIGNAL('activated(const QModelIndex&)'),
+                     self.currentPlotChanged)
+        self.connect(self.plotsView.selectionModel(),
+                     SIGNAL('currentChanged(const QModelIndex&, const QModelIndex&)'),
+                     self.currentPlotChanged)
+
+    def currentPlotChanged(self, index, old_index=None):
+        if index.isValid():
+            value = unicode(self.plotsView.model().data(index, Qt.DisplayRole).toString())
+            
+            #self.plotterWidget = QHPGLPlotterWidget(self)
+            #self.setCentralWidget(self.plotterWidget)
+            self.plotterWidget.clear()
+            self.plotterWidget.parse(self._plots[value])
+            
+    def preferencesTriggered(self, checked=False):
+        PreferencesEditor(self._prefs, self).exec_()
+
+    def quitTriggered(self, checked=False):
+        self.close()
 
+    def closeEvent(self, event):
+        if 1:
+        #if self.promptForSave():
+            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 = QtGui.QFileDialog.getOpenFileNames(self, "Open a HPGL file to display", '.', 'HPGL files (*.plt)\nAll files (*)')
+        for filename in filenames:
+            filename = str(filename)
+            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)
+                #self.plotsView.setCurrentIndex(self.plotsView.model().index(self.plotsView.model().stringList().count(), 0))
+            
+            
+        
+    def saveTriggered(self, checked=False):
+        print "save"
+    def saveAsTriggered(self, checked=False):
+        print "saveAs"
+        
+    def initializeGPIB(self):
+        try:
+            self.gpib_plotter = GPIBplotter(device=self._prefs.device,
+                                            address=self._prefs.address,
+                                            )
+        except:
+            self.gpib_plotter = None
+        self._online = False
+        self.setCaptureLed()
+        
+    def captureToggled(self, state):
+        if state:
+            if self.gpib_plotter is None:
+                self.initializeGPIB()
+                if self.gpib_plotter is None:
+                    QtGui.QMessageBox.critical(self, self.tr("GPIB error"),
+                                               self.tr("<b>Unable to initialize GPIB connection</b>.<br>Please check your GPIB dongle and settings."))
+                    self._online = False
+                else:
+                    # todo
+                    self._online = True
+        else:
+            self._online = False
+        self.setCaptureLed()
+            
+            
+    def setCaptureLed(self):
+        if self._online:
+            icn = QtGui.QIcon(':/icons/led_green.png')
+        else:
+            icn = QtGui.QIcon(':/icons/led_green_off.png')
+        self.captureButton.setIcon(icn)
+        self.captureButton.setChecked(self._online)
+        
+if __name__ == '__main__':
+    a = QtGui.QApplication([])
+    w = QtHPGLPlotter()
+    w.show()
+    a.exec_()
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qhpgl_plotter.ui	Wed Jan 16 01:20:02 2008 +0100
@@ -0,0 +1,130 @@
+<ui version="4.0" >
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>507</width>
+    <height>492</height>
+   </rect>
+  </property>
+  <property name="windowTitle" >
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget" />
+  <widget class="QMenuBar" name="menubar" >
+   <property name="geometry" >
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>507</width>
+     <height>25</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menu_File" >
+    <property name="title" >
+     <string>&amp;File</string>
+    </property>
+    <addaction name="actionOpen" />
+    <addaction name="actionSave" />
+    <addaction name="actionSaveAs" />
+    <addaction name="separator" />
+    <addaction name="actionQuit" />
+   </widget>
+   <widget class="QMenu" name="menuEdit" >
+    <property name="title" >
+     <string>Edit</string>
+    </property>
+    <addaction name="actionPreferences" />
+   </widget>
+   <addaction name="menu_File" />
+   <addaction name="menuEdit" />
+  </widget>
+  <widget class="QStatusBar" name="statusbar" />
+  <widget class="QDockWidget" name="plotsDock" >
+   <attribute name="dockWidgetArea" >
+    <number>1</number>
+   </attribute>
+   <widget class="QWidget" name="dockWidgetContents" >
+    <layout class="QVBoxLayout" >
+     <property name="spacing" >
+      <number>4</number>
+     </property>
+     <property name="leftMargin" >
+      <number>2</number>
+     </property>
+     <property name="topMargin" >
+      <number>2</number>
+     </property>
+     <property name="rightMargin" >
+      <number>2</number>
+     </property>
+     <property name="bottomMargin" >
+      <number>2</number>
+     </property>
+     <item>
+      <widget class="QToolButton" name="captureButton" >
+       <property name="minimumSize" >
+        <size>
+         <width>0</width>
+         <height>29</height>
+        </size>
+       </property>
+       <property name="text" >
+        <string>on line</string>
+       </property>
+       <property name="icon" >
+        <iconset resource="hpgl_plotter.qrc" >:/icons/led_green.png</iconset>
+       </property>
+       <property name="iconSize" >
+        <size>
+         <width>16</width>
+         <height>16</height>
+        </size>
+       </property>
+       <property name="checkable" >
+        <bool>true</bool>
+       </property>
+       <property name="toolButtonStyle" >
+        <enum>Qt::ToolButtonTextBesideIcon</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QListView" name="plotsView" />
+     </item>
+    </layout>
+   </widget>
+  </widget>
+  <action name="actionOpen" >
+   <property name="text" >
+    <string>&amp;Open</string>
+   </property>
+  </action>
+  <action name="actionSave" >
+   <property name="text" >
+    <string>&amp;Save</string>
+   </property>
+  </action>
+  <action name="actionSaveAs" >
+   <property name="text" >
+    <string>Save as...</string>
+   </property>
+  </action>
+  <action name="actionQuit" >
+   <property name="text" >
+    <string>&amp;Quit</string>
+   </property>
+  </action>
+  <action name="actionPreferences" >
+   <property name="text" >
+    <string>Preferences</string>
+   </property>
+  </action>
+ </widget>
+ <resources>
+  <include location="hpgl_plotter.qrc" />
+ </resources>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qpreferences.py	Wed Jan 16 01:20:02 2008 +0100
@@ -0,0 +1,141 @@
+import os
+from PyQt4 import QtCore, QtGui, uic
+
+def fromVariant(v):
+    _cvrts = {0: lambda x:None,
+              1: lambda x:x.toBool(),
+              2: lambda x:x.toInt()[0],
+              6: lambda x:x.toDouble()[0],
+              10: lambda x:unicode(x.toString()),
+              12: lambda x:x.toByteArray(),
+              21: lambda x:x.toSize(),
+              22: lambda x:x.toSizeF(),
+              25: lambda x:x.toPoint(),
+              26: lambda x:x.toPointF(),
+              
+              }
+    t = v.userType()
+    return _cvrts[t](v)
+    
+class PreferenceItem(object):
+    _id = 0
+    def __init__(self, default=None, basetype=None, name=None, description=None):
+        self._default = default
+        self._basetype = basetype
+        if self._basetype is None:
+            self._basetype = self._default.__class__
+        self._id = "_pref%X"%self.__class__._id
+        self._name = name
+        self._description = description
+        self.__class__._id += 1
+
+    def __get__(self, obj, cls):
+        if obj is None:
+            return self
+        try:
+            return obj.getPref(self._id)
+        except Exception, e:
+            #print "humm", e
+            return None
+
+    def __set__(self, obj, value):
+        obj.setPref(self._id, value)
+        
+class AbstractPreferences(QtCore.QObject):
+    def __init__(self):
+        QtCore.QObject.__init__(self)
+        self._settings = QtCore.QSettings(QtCore.QSettings.UserScope,
+                                          self.ORGANISATION, self.APPLICATION)
+        self._prefs = {}
+        for k in dir(self.__class__):
+            item = getattr(self.__class__, k)
+            if isinstance(item, PreferenceItem):
+                self._prefs[item._id] = k            
+        
+    def getPref(self, key):
+        key = self._prefs.get(key, key)
+        default = getattr(self.__class__, key)._default
+        if default is not None:
+            default = QtCore.QVariant(default)
+        else:
+            default = QtCore.QVariant()
+        val = self._settings.value(key, default)
+        return fromVariant(val)
+    
+    def setPref(self, key, value):
+        key = self._prefs.get(key, key)
+        self._settings.setValue(key, QtCore.QVariant(value))
+
+    def keys(self):
+        return [k for k in self._prefs.values() if not k.startswith('_')]
+
+    def getName(self, key):
+        item = getattr(self.__class__, key)
+        return item._name
+
+    def getDescription(self, key):
+        item = getattr(self.__class__, key)
+        return item._description
+    
+form_class, base_class = uic.loadUiType(os.path.join(os.path.dirname(__file__), "qpreferences_dialog.ui"))
+
+class PreferencesEditor(QtGui.QDialog, form_class):
+    def __init__(self, preferences, parent=None):
+        QtGui.QDialog.__init__(self, parent)
+        self.setupUi(self)
+        self._prefs = preferences
+        self.buildUI()
+
+    def buildUI(self):
+        w = self.centralFrame
+        g = QtGui.QGridLayout(w)
+        p = self._prefs
+        eds = {}
+        self._editors = eds
+        for i, k in enumerate(p.keys()):
+            name = p.getName(k)
+            if not name:
+                name = k
+            l = QtGui.QLabel(name, w)
+            g.addWidget(l, i, 0)
+            if p.getDescription(k):
+                l.setToolTip(p.getDescription(k))
+            
+            e = QtGui.QLineEdit(w)
+            eds[k] = e
+            g.addWidget(e, i, 1)
+            val = p.getPref(k)
+            if val is None:
+                val = ''
+            if not isinstance(val, basestring):
+                val = unicode(val)
+            e.setText(val)
+            
+        g.addWidget(QtGui.QWidget(w), i+1, 0)
+        g.setRowStretch(i+1,1)
+        g.setColumnStretch(1,1)
+
+    def accept(self):
+        p=self._prefs
+        for k in p.keys():
+            newval = unicode(self._editors[k].text())
+            p.setPref(k, newval)            
+        return QtGui.QDialog.accept(self)
+            
+if __name__ == '__main__':
+    class TestPreferences(AbstractPreferences):
+        ORGANISATION="Logilab"
+        APPLICATION="test_qpref_editor"
+
+        device = PreferenceItem('/dev/ttyUSB0', name="the device")
+        address = PreferenceItem(5, description="GPIB address of the plotter")
+        _pos = PreferenceItem(None)
+        _size = PreferenceItem(None)
+        _appState = PreferenceItem(None)
+        
+    a = QtGui.QApplication([])
+
+    prefs = TestPreferences()
+    w = PreferencesEditor(prefs)
+    w.show()
+    a.exec_()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qpreferences_dialog.ui	Wed Jan 16 01:20:02 2008 +0100
@@ -0,0 +1,73 @@
+<ui version="4.0" >
+ <class>PreferencesDialog</class>
+ <widget class="QDialog" name="PreferencesDialog" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle" >
+   <string>Dialog</string>
+  </property>
+  <layout class="QVBoxLayout" >
+   <item>
+    <widget class="QFrame" name="centralFrame" >
+     <property name="frameShape" >
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow" >
+      <enum>QFrame::Raised</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox" >
+     <property name="orientation" >
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons" >
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PreferencesDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PreferencesDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel" >
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel" >
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

mercurial