How To use PyQt and TAU / Taurus
This file is NOT the official Tau Documentation, it's just my personal collection of recipes to make first steps easier ...
Index
- Using TAU
- Official Documentation
- TAU Concepts
- Tau Core recipes
-
Tau Widget Recipes
- Before anything else
- Showing a an "ATKPanel" like form for a device
- Creating a command button
- Creating a panel with commands using TauCommandsForm
- Filters in a !Tau***Form
- Show Device Status in a proper way (or any multi-line string attribute)
- Configuring a TauPlot from python
- Drag and Drop from Tau Widgets
- URI models for getting spectrum indexes
- Limitation (and work-around) in Tau useParentModel
- Moving a widget within a frame
- Saving an screenshot image of a Taurus Widget
- Icons and Resources in Tau
- Signals and Slots
- Some Qt Recipes
- QThreads
Using TAU
This file is NOT the official Tau Documentation, it's just my personal usage notes.
To use the official documentation please follow these steps.
Official Documentation
The documentation of the last stable release is available at:
http://www.tango-controls.org/static/tau/latest/doc/html/index.html
You can also rebuild sphinx documentation from tau sources:
sudo easy_install sphinx svn co .../tau ##root path containing setup.py cd tau python setup.py build_resources --logo=./widget/resources/extra-icons/institutes/logo_alba.png ##building TAU resources python setup.py build_doc ##building documentation konqueror build/sphinx/html/index.html
Qt information is available at:
New taurus can be downloaded using:
svn co https://tango-cs.svn.sourceforge.net/svnroot/tango-cs/tau/branches/tau_restructure_01 taurus
TAU Concepts
- Database
(tango://)?<host>:<port>
- Device
(<database>)?(<devicename>|<devicealias>)
- Attribute
<device>/<attrname>
- Configuration
<attribute>?configuration=<confname>
- Property
<device>?property=<propname> <attribute>?property=<propname>
- Command
<device>?command=<cmdname>
Tau Core recipes
Creating an object callback for an attribute events / polling
A = tau.Attribute('some/place/vgct-01/p2') class Hi(): def event_received(self,source,type_,value): print time.ctime() print source print type print value mande = Hi() A.addListener(mande.event_received) 2010-06-15 13:11:07,027 tangoHost:10000/some/place/vgct-01/p2 2 DeviceAttribute[... A.removeListener(mande.event_received)
Modify polling period
A = tau.Attribute('some/place/ccg-01') A.changePollingPeriod(3000) A.enablePolling() A.disablePolling()
Setting up a TaurusBaseComponent
import taurus from taurus.qt.qtgui.base import TaurusBaseComponent from PyQt4 import Qt app = Qt.QApplication([]) class receiver(TaurusBaseComponent,Qt.QObject): def __init__(self, parent = None, designMode = False, splash=True): name = self.__class__.__name__ Qt.QObject.__init__(self, parent) TaurusBaseComponent.__init__(self, name) def handleEvent(self,evt_source,evt_type,evt_value): print('event received') def getModelClass(self): return taurus.core.TaurusAttribute r = receiver() r.setModel('sys/database/2/status') app.exec_()
Get the list of instantiated attributes
import taurus factory = taurus.Factory() factory.tango_attrs.keys() Out[81]: ['controls02:10000/test/sim/tonto/temperature'] factory.tango_devs.keys() Out[82]: ['controls02:10000/test/sim/tonto'] #In the future: attrs = factory.getExistingAttributes() devs = factory.getExistingDevices()
Setting models as a formula with Tango attributes values
This is accomplished using the "eval://" scheme for taurus attributes. See
documentation in
<machine_with_taurus_installed>
/homelocal/sicilia/doc/taurus/devel/api/taurus/core/evaluation.html
So, lets consider that you want to see a trend of the a/b/c/d attribute
multiplied by 10...
-From the command line:
taurustrend eval://10*{a/b/c/d}
-From the GUI:
1.- open the "input data selection" dialog
2.- select the attribute you want to multiply (tango://a/b/c/d) from the list
and edit it so that it reads eval://{tango://a/b/c/d}*10
Note that you can refer to any attribute and you can have more than one
attribute in your formulas. Also you can use many mathematical expressions
(sin, exp, ...)
Tau Widget Recipes
Before anything else
You have to create a QApplication instance before creating any tau object:
import sys from tau.widget import Qt qapp = Qt.QApplication(sys.argv)
Showing a an "ATKPanel" like form for a device
#!/bin/python
import tau,tau.widget
tau.widget.Qt.QApplication([])
tf = tau.widget.TauForm()
dev = 'some/place/vgct-01'
attrs = tau.Device(dev).get_attribute_list()
##Configuring the TauForm:
tf.setWithButtons(False)
tf.setWindowTitle(dev)
tf.setModel('%s/%s' % (dev,a) for a in ['state','status','p1','p2','p3','p4'])
##Adapting the status widget to show properly an status
sw = status.readWidget()
sw.setAlignment(QtCore.Qt.AlignLeft)
sw.setMinimumHeight(300)
#sw.setShowQuality(False) #It didn't work as expected
tf.show()
Creating a command button
The main methods/slots/signals to configure it are:
- setModel(QString) <= DEVICE_NAME
- setCommand(QString) <= COMMAND_NAME
- setParameters(QStringList) <= [PARAMS]
- commandExecuted() => result
- setDangerMessage(MESSAGE or None)
#!/bin/python
tcb = tau.widget.buttons.TauCommandButton(command='sendCommand',text='Push!',parameters=['VER\r\n'])
tcb.setWindowTitle('TauCommand')
tcb.setModel('domain/family/vgct-01')
tcb.setDangerMessage('Are you sure?')
tcb.show()
CLICK():
MainThread INFO 2010-04-14 18:04:57,728 TauCommandButton.domain/family/vgct-01: Execution of command sendCommand returned: 1.00,1.10
Creating a panel with commands using TauCommandsForm
#!/bin/python
import tau,tau.widget,tau.widget.forms
tau.widget.Qt.QApplication([])
DEV_COMMS = ['cc_on','cc_off','sendcommand']
l = lambda c: any(str(c.cmd_name).lower()==s.lower() for s in DEV_COMMS)
sortkey = lambda x: DEV_COMMS.index(x.cmd_name.lower()) if str(x.cmd_name).lower() in DEV_COMMS else 10
tcf = tau.widget.forms.TauCommandsForm()
tcf.setModel('domain/family/vgct-01')
tcf.setDefaultParameters({'cc_on':['P1','P2'],'cc_off':['P1','P2']})
tcf.setSortKey(sortkey)
tcf.setViewFilters([l])
tcf.show()
Filters in a !Tau***Form
form = TauAttrForm() form.setViewFilters([lambda x: x.name.lower() in ['state', 'status', 'position', 'velocity', 'acceleration', ... form.setMinimumSize(Qt.QSize(500,850)) form.setModel(tango_device_name)
use of form.setSortKey()
@staticmethod def get_comms_form(device,form=None,parent=None): params = DEV_COMMS.get(get_eqtype(device),[]) if not form: form = tau.widget.forms.TauCommandsForm(parent) elif hasattr(form,'setModel'): form.setModel('') try: form.setModel(device) if params: form.setSortKey(lambda x,vals=[s[0] for s in params]: vals.index(x.cmd_name.lower()) if str(x.cmd_name).lower() in vals else 10) form.setViewFilters([lambda c: any(str(c.cmd_name).lower()==s[0] for s in params)]) form.setDefaultParameters(dict((k,v) for k,v in params if v)) #form.setPalette(palettes.get_halfWhite_palette()) except Exception,e: self.error('Unable to setModel for VaccaPanel.comms_from!!: %s'%e) return form
Show Device Status in a proper way (or any multi-line string attribute)
import sys,tau.widget from PyQt4 import Qt qapp = Qt.QApplication(sys.argv) statusframe = tau.widget.Qt.QScrollArea() status = tau.widget.TauValueLabel(self._statusframe) status.setShowQuality(False) status.setAlignment(Qt.Qt.AlignLeft) status.setFixedHeight(1000) status.setFixedWidth(5000) statusframe.setHorizontalScrollBarPolicy(Qt.Qt.ScrollBarAlwaysOn) statusframe.setVerticalScrollBarPolicy(Qt.Qt.ScrollBarAlwaysOn) statusframe.setWidget(status) #Here I set my preffered widget height policy statusframe.setFixedHeight(200) status.setModel('bl24-circe/ct/alarms/status') statusframe.show()
Configuring a TauPlot from python
from tau.widget.qwt import TauPlot from PyQt4 import Qwt5,Qt qapp = Qt.QApplication([]) tp = TauPlot()
Setting x,y sets
tp.setModel('domain/family/Composer/IPAxxis|domain/family/Composer/IPPressures') #Two spectrums as two x,y sets tp.setModel('domain/family/Composer/CCGAxxis|domain/family/Composer/CCGPressures,domain/family/Composer/IPPressures') #Two spectrums as two x,y sets
Setting custom labels
positions = [0,17,31,33,49,63,80,94,111,114,116,122] labels=['Q1A','Q1B','Q1LTB','Q2A','Q2B','Q3A','Q3B','Q4A','Q4B','Q4BT','Q4C','Q4D+RF'] tp.setAxisCustomLabels(Qwt5.QwtPlot.xBottom,zip(positions,labels),60)
Modifying curves colors
curve = tp.curves['domain/family/composer/ippressures'] cap = curve.getAppearanceProperties() cap.lColor = Qt.Qt.green tp.setCurveAppearanceProperties({curve.getCurveName():cap})
Modifying Axis scale
tp.setAxisScaleType(Qwt5.QwtPlot.yLeft,Qwt5.QwtScaleTransformation.Linear) tp.setAxisScaleType(Qwt5.QwtPlot.yLeft,Qwt5.QwtScaleTransformation.Log10)
Moving a curve to other axis
from the plot object
tp.setCurvesYAxis(['sr/vc/all/thermocouples'],tp.yRight)
from the curve object
curve = tp.getCurve('sr/vc/all/thermocouples')
curve.setAxis(tp.xBottom,tp.yRight)
tp.enableAxis(tp.yRight)
Showing Raw Data curves in TaurusPlot or TaurusTrend
import fandango import PyTangoArchiving as pta rd = pta.Reader('hdb') ats = fandango.tango.get_matching_device_attributes('sr04/vc/eps-plc-01/*TC*' vals = rd.get_attributes_values(ats,'2011-10-18','2011-10-22',correlate=False,text=False,asHistoryBuffer=False) from PyQt4 import Qt,Qwt5 from PyQt4.Qwt5 import qpl qapp = Qt.QApplication([]) def get_color_for_i(i): top = 256**3 tot = top/i c = tot return Qt.QColor(c%256,(c/256)%256,((c/256)/256)%256 p = qplt.Plot(*[qplt.Curve([x[0] for x in c],[y[1] for y in c],'curve %d'%i,qplt.Pen(get_color_for_i(i))) for i,c in enumerate(vals.values())])
Drag and Drop from Tau Widgets
Code added in TauValue.DefaultLabelWidget(TauConfigLabel):
def mousePressEvent(self, event): '''reimplemented to provide drag events''' print 'In DefaultLabelWidget.mousePressEvent' Qt.QLabel.mousePressEvent(self, event) if event.button() == Qt.Qt.LeftButton: self.dragStartPosition = event.pos() def mouseMoveEvent(self, event): '''reimplemented to provide drag events''' if not event.buttons() & Qt.Qt.LeftButton: return mimeData = Qt.QMimeData() mimeData.setText(self.tauValueBuddy.getModelName()) drag = Qt.QDrag(self) drag.setMimeData(mimeData) drag.setHotSpot(event.pos() - self.rect().topLeft()) dropAction = drag.start(Qt.Qt.CopyAction)
URI models for getting spectrum indexes
a/b/c/d#i
Limitation (and work-around) in Tau useParentModel
Due to a limitation imposed by the Qt support for notifying widget reparenting, it is hard to take care of qt/tau parenting for the 100% of the situations. Hence a manual work-around has been implemented: the TauBaseWidget?.recheckTauParent() method.
- recheckTauParent()
Forces the widget to recheck its Tau parent. Tau Widgets will in most situations keep track of changes in their tau parenting, but in some special cases (which unfortunately tend to occur when using Qt Designer) they may not update it correctly.
If this happens, you can manually call this method.
For more information, check the following issue demo example </devel/examples/parent_model_issue_demo.py>
Moving a widget within a frame
tf = tau.widget.Qt.QFrame() ql = tau.widget.Qt.QLabel() ql.setPixmap(tau.widget.Qt.QPixmap('image.gif').scaledToHeight(75)) def moveWidget(widget,steps,tstep,start,end): for i in range(steps): widget.move(tau.widget.Qt.QPoint(start[0]+(i+1)*(end[0]-start[0])/steps,start[1]+(i+1)*(end[1]-start[1])/steps)) (widget.parentWidget() or widget).repaint(),Event().wait(tstep) moveWidget(l,10,.1,(180,180),(0,0))
Saving an screenshot image of a Taurus Widget
from taurus.qt.qtgui.util import grabWidget grabWidget(main_window, "/var/www/machine_status.png", period=1.5)
Icons and Resources in Tau
Using theme icons
Theme icons are the icons used by the operating system applications (and other) to display common information. Example, the folder icon or the open document icon. Up to Qt 4.6, Qt was not able to recognize the OS theme icons. Therefore, tau comes with a library of theme icons that conforms to the standard icon naming specification ( http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html) called "Tango Icon Library" (nothing to do with our tango control system!). On your application you may want to use theme icons in widgets that do usual actions (open a file, find operation, etc).
You can see a list of available images at: taurus/qt/qtgui/resource/catalog.html
BEFORE in tau you had to do:
from tau.widget.resources.qrc_tango_icons_status import * fo_icon = Qt.QIcon(":/status/folder-open.svg") fo_pixmap = Qt.QPixmap(":/status/folder-open.svg") OR from taurus.qt.qtgui.resource.qrc_extra_icons_leds_images24 import * ql.setPixmap(Qt.QPixmap(":/leds/images24/ledblueoff.png"))
this meant:
- having to import a specific python module (which could be quite large) that contains all icon binary data
- having to know which is the path to the icon and the file format
- creating a new object each time you want to use it ( imagine you have a tree with 1000 nodes. This meant creating an icon for each node if you are not careful in your code)
NOW you can (and should) use:
import tau.widget.resources fo_icon = tau.widget.resources.getThemeIcon("folder-open") fo_pixmap = tau.widget.resources.getThemePixmap("folder-open") OR px = taurus.qt.qtgui.resource.getPixmap(':/leds/images24/ledgreenoff.png') If you have Qt < 4.6 this will give you the default tau internal "Tango Icon Library". If you have Qt >= 4.6 this will give you the OS theme icon.
Other icons
for all other tau icons (or if you want to force using the default tau internal "Tango Icon Library") you should use:
e_icon = tau.widget.resources.getIcon(":/extra-icons/expand-all.svg") e_pixmap = tau.widget.resources.getPixmap(":extra-icons/expand-all.svg")
Advantages
- you DON'T have to write in your code: from tau.widget.resources.qrc_tango_icons_status import *
- Your application will have the same theme as the machine it is in so it will be have a much nicer look and feel (starting from Qt 4.6 :-P )
- Internally tau uses an optimization that shares the icons/pixmaps saving CPU (load and draw) and memory
Loading resources from a file
en file.py: main_pressure = "a/b/c/d" en la aplicacion: resource_factory = tau.Factory('res') resource_factory.loadResource('file.py') resource_factory.loadResource({"main_pressure" : "a/b/c/d"})
Signals and Slots
Connecting signals and slots
Connections between signals and slots (and other signals) are made using the QtCore.QObject.connect() method. For example:
QtCore.QObject.connect(a, QtCore.SIGNAL("QtSig()"), b, QtCore.SLOT("QtSlot()"))
QtCore.QObject.connect(a, QtCore.SIGNAL("PySig()"), b, QtCore.SLOT("QtSlot()"))
QtCore.QObject.connect(a, QtCore.SIGNAL("QtSig()"), pyFunction) #A python function with NO arguments
QtCore.QObject.connect(a, QtCore.SIGNAL("QtSig()"), pyClass.pyMethod) #A python function with NO arguments
QtCore.QObject.connect(a, QtCore.SIGNAL("PySig"), pyFunction) #A python signal WITH arguments
"modelChanged(const QString &)": it means that an string is passes as argument to the slot
Emitting signals
Any instance of a class that is derived from the QtCore.QObject class can emit a signal using its emit() method. This takes a minimum of one argument which is the signal. Any other arguments are passed to the connected slots as the signal arguments. For example:
a.emit(QtCore.SIGNAL("clicked()")) #Connect it to a python function with NO arguments
a.emit(QtCore.SIGNAL("pySig"), "Hello", "World") #To be connected to a python function WITH arguments
Signatures used by Qt to recognize Signals/Slots
Signals emitted by the class must be added to __pyqtSignals__ member
__pyqtSignals__ = ("modelChanged(const QString &)",)
Slots available must be decorated providing a header for Qt
@QtCore.pyqtSignature("setModel(QString)") def setModel(self,model): self.setModelCheck(model)
Connecting Signals/Slots by object name
From BakeOutProgrammer code. If an object is created with a given name and later self.metaObject().connectSlotsByName(self) is called:
class UiMainWindow(TauMainWindow): def __init__(self, parent=None, flags=0): ... self.controllerCombo = QtGui.QComboBox() self.controllerCombo.setObjectName("controllerCombo") ... self.metaObject().connectSlotsByName(self)
Then you don't need to connect the signals of the object, it will be automatically linked if a method with on_OBJECTNAME_SIGNALNAME exists in the self (e.g. QMainWindow) object:
def on_controllerCombo_currentIndexChanged(self, devModel): self.info("In on_controllerCombo_currentIndexChanged)\tdevModel: %s" % devModel) ...
Another example would be:
class myqw(Qt.QWidget): def __init__(self,parent): Qt.QWidget.__init__(self,parent) self.setLayout(Qt.QHBoxLayout()) self.button = Qt.QPushButton() self.button.setObjectName('button') self.layout().addWidget(self.button) self.metaObject().connectSlotsByName(self) def on_button_pressed(self): print('pressed') qw = myqw() qw = show()
Decorate QWidgets to listen to Double Click events
def DoubleClickable(QtKlass): """ This class decorator overrides the mouseDoubleClickEvent virtual method """ class DoubleClickableQtKlass(QtKlass): def __init__(self,*args): self.my_hook = None QtKlass.__init__(self,*args) def setClickHook(self,hook): """ the hook must be a function or callable """ self.my_hook = hook #self.onEdit def mouseDoubleClickEvent(self,event): if self.my_hook is not None: self.my_hook() else: try: QtKlass.mouseDoubleClickEvent(self) except: pass return DoubleClickableQtKlass def clicked(): print 'double click!' ql = DoubleClickable(Qt.QLineEdit)() ql.setClickHook(clicked) ql.show()
Managing Key Events
def keyPressEvent(self,event): if event.key() == QtCore.Qt.Key_Escape: self.reject() elif event.key() in [QtCore.Qt.Key_Return,QtCore.Qt.Key_Enter]: buttons = [b for b in \ [self.ui.ScanButton,self.ui.buttonBox.buttons()[1],self.ui.AddressEdit,self.ui.RangeEdit] \ if b.hasFocus()] if buttons: for b in buttons: if hasattr(b,'click'): b.click() else: self.accept() return
Some Qt Recipes
Standard Widgets
A QTableWidget
tab = QtGui.QTableWidget() tab.setWindowTitle('%s: %s to %s' % (attribute,start,stop)) tab.setRowCount(len(values)) tab.setColumnCount(2) tab.setHorizontalHeaderLabels(['TIME','VALUE']) for i,tup in enumerate(values): date,value = tup tab.setItem(i,0,Qt.QTableWidgetItem(epoch2str(date))) tab.setItem(i,1,Qt.QTableWidgetItem(str(value))) tab.show() tab.resizeColumnsToContents() tab.horizontalHeader().setStretchLastSection(True) TABS.append(tab) tab.connect(tab,Qt.SIGNAL('close()'),lambda o=tab: TABS.remove(o)) print 'show_history done ...' return tab
A QDialog with QGridLayout and a QDialogButtonBox
In !QGridLayout.addWidget method the arguments are row, column, rowSpan and columnSpan
wi = QtGui.QDialog() wi.setLayout(QtGui.QGridLayout()) begin = QtGui.QLineEdit() begin.setText(epoch2str(time.time()-3600)) end = QtGui.QLineEdit() end.setText(epoch2str(time.time())) wi.setWindowTitle('Show Archiving') wi.layout().addWidget(QtGui.QLabel('Enter Begin and End dates in %s format'%tformat),0,0,1,2) wi.layout().addWidget(QtGui.QLabel('Begin:'),1,0,1,1) wi.layout().addWidget(QtGui.QLabel('End:'),2,0,1,1) wi.layout().addWidget(begin,1,1,1,1) wi.layout().addWidget(end,2,1,1,1) buttons = Qt.QDialogButtonBox(Qt.QDialogButtonBox.Ok|Qt.QDialogButtonBox.Cancel) wi.connect(buttons,Qt.SIGNAL('accepted()'),wi.accept) wi.connect(buttons,Qt.SIGNAL('rejected()'),wi.reject) wi.layout().addWidget(buttons,3,0,1,2)
A Custom Widget with an image/icon, an edit and a push button; with its own signals
from PyQt4 import Qt app = Qt.QApplication([]) class SearchEdit(Qt.QWidget): __pyqtSignals__ = ("search(QString)",) def __init__(self,parent=None): Qt.QWidget.__init__(self,parent) self._pixmap = Qt.QPixmap('search.png') self._label = Qt.QLabel() self._label.setPixmap(self._pixmap) self._edit = Qt.QLineEdit() self._button = Qt.QPushButton() self._button.setText('Search') self.connect(self._edit,Qt.SIGNAL('returnPressed()'),self._button.animateClick) self.connect(self._button,Qt.SIGNAL('clicked()'),self._emitSearch) self.setLayout(Qt.QHBoxLayout()) self.layout().addWidget(self._label) self.layout().addWidget(self._edit) self.layout().addWidget(self._button) return def _emitSearch(self): text = self._edit.text() if text: self.emit(Qt.SIGNAL("search(QString)"), text) return wi = SearchEdit() def trace(s=''): print(str(s)) wi.connect(wi,Qt.SIGNAL("search(QString)"),trace) wi.show()
Standard Dialogs and Widgets
QMessageBox(icon,Title,Question,FlagsForButtons,parent)
44: qmsg = Qt.QMessageBox(None) 45: qmsg.setWindowTitle('Error') 46: qmsg.setText('Unable to load device') 47: qmsg.setIcon(qmsg.Critical) 48: qmsg.setDetailedText('patatin, patatan') 49: qmsg.setDefaultButton(qmsg.Ok) 50: qmsg.show() or qmsg = Qt.QMessageBox(Qt.QMessageBox.Critical,'%s Error'%model,'%s not available'%model,Qt.QMessageBox.Ok,self) qmsg.show() or v = QtGui.QMessageBox.warning(None,'SplitterTester', \ 'You must introduce or select an IP from IPAddress Combo Box', \ QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel); if v == QtGui.QMessageBox.Cancel: return
QInputDialog(parent,title,question,QLineEdit,defaultText)
returns (text,OkPressed)
In [29]:QtGui.QInputDialog.getText(None,'hola','que tal?',QtGui.QLineEdit.Normal,'bien') Out[29]:(<PyQt4.QtCore.QString object at 0x83ad2ac>, True)
QFont: Setting a monospaced font in a QTextEdit panel
font = QtGui.QFont() font.setStyleHint(QtGui.QFont.TypeWriter) font.setFamily("unexistentfont") self.ui.TextPanel.setFont(font)
Focus: .setFocus(), hasFocus() available on most of widgets
QDialog vs QMainWindow (KeyEvents)
- QDialog automatically connects Return and Escape keys to Ok and Cancel buttons.
- In QMainWindow it must be hacked using keyPressEvent methods
Using a raw QwtPlot
import fandango import PyTangoArchiving as pta rd = pta.Reader('hdb') ats = fandango.tango.get_matching_device_attributes('sr04/vc/eps-plc-01/*TC*' vals = rd.get_attributes_values(ats,'2011-10-18','2011-10-22',correlate=False,text=False,asHistoryBuffer=False) from PyQt4 import Qt,Qwt5 from PyQt4.Qwt5 import qpl qapp = Qt.QApplication([]) def get_color_for_i(i): top = 256**3 tot = top/i c = tot return Qt.QColor(c%256,(c/256)%256,((c/256)/256)%256 p = qplt.Plot(*[qplt.Curve([x[0] for x in c],[y[1] for y in c],'curve %d'%i,qplt.Pen(get_color_for_i(i))) for i,c in enumerate(vals.values())])
Colors, fonts and decoration
Applying HTML tags and Styles on Tau Widgets
On QWidget.setTooltip or QWidget.setText:
#This example is from ui_curvesAppearanceChooser.py qw = QComboBox() qw.setToolTip(QtGui.QApplication.translate("curvesAppearanceChooserDlg", "<html><head> <meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n" "p, li { white-space: pre-wrap; }\n" "</style></head><body style=\" font-family:\'Sans Serif\'; font-size:10pt; font-weight:400; font-style:normal;\">\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"> Style of the pen used to connect the points. </p></body></html>" , None, QtGui.QApplication.UnicodeUTF8))
On QGraphicsTextItem:
text = 'bla bla bla' text = "<body bgcolor='lime' style ='font-size:14px;margin-top:60px;'>"+text+"</body>" item = Qt.QGraphicsTextItem() item.setHtml(txt)
preformatted html styles and colors for States and Qualities
Using QBrush colors:
def myQGraphicTextItem.updateStyle(self): ... if self.getModelObj().getType() == taubase.PyTango.ArgType.DevState: bg_brush, fg_brush = colors.QT_DEVICE_STATE_PALETTE.qbrush(v.value) elif self.getModelObj().getType() == taubase.PyTango.ArgType.DevBoolean: bg_brush, fg_brush = colors.QT_DEVICE_STATE_PALETTE.qbrush((taubase.PyTango.DevState.FAULT,taubase.PyTango.DevState.ON)[v.value]) else: bg_brush, fg_brush = colors.QT_ATTRIBUTE_QUALITY_PALETTE.qbrush(v.quality) if self._currText: self._currHtmlText = '<p style="color:%s">%s</p>' % (bg_brush.color().name(),self._currText) def myQGraphicTextItem.paint(self,...): self.setHtml(self._currHtmlText)
Using styles for html tags:
app = tau.widget.Qt.QApplication([]) ta = tau.Attribute('SR14/VC/PIR-01/Pressure') a,unit = ta.read(),ta.getUnit() #Obtain the style related to the HTML tag you are going to use and your quality style = tau.widget.colors.QT_ATTRIBUTE_QUALITY_PALETTE.htmlStyle('P' or '',a.quality) Out[36]:"<style type='text/css'>\nP.ATTR_VALID { background-color : rgb(0, 255, 0); color : rgb(0, 0, 0); }\n</style>" txt = "<p class='%s'>%s%s</p>" % (a.quality,str(a.value),unit and ' %s'%unit or '') tl = tau.widget.Qt.QLabel() tl.setText(style+txt) tl.show()
Modifying Qt Palette colors
# This example replaces the blue background of selected cells in tables palette = Qt.QPalette() brush = Qt.QBrush(Qt.Qt.white) palette.setBrush(palette.Active,palette.Highlight,brush) MyTable.setPalette(palette)
Modify Qt Style Sheet
plot.setStyleSheet("QToolTip { opacity: 255; }")
or at the application level:
app = Qt.QApplication(sys.argv) app.setStyleSheet("QToolTip { opacity: 255; }")
Playing sound
[ http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qsound.html ]
Qt.QSound.play('mysound.wav')
QThreads
QThreads object cannot access directly to GUI objects ... All access must be done using Signals and Slots; so any change to GUI (including displaying messages) must be done through emit and declared functions with arguments.
class QPinger(QtCore.QThread): def __init__(self,parent,ipstart,iprange): QtCore.QThread.__init__(self) ... if hasattr(parent,'scan_finished'): QtCore.QObject.connect(self, QtCore.SIGNAL("scan_finished"), parent.scan_finished) def run(self): ... self.emit(QtCore.SIGNAL("scan_finished"), founds)
Random remarks
Note: it works with milliseconds instead of seconds'':
- QThread.start() to launch QThread.run() method
- QThread.wait() equals to threading.Thread.join()
- QThread.wait(millis): equals to threading.Event.wait(seconds)
- QMutex.lock() ; QMutex.unlock ... typical mutex
- QWaitCondition: Like threading.Event but a mutex is unlocked during wait and locked again when the wait finishes.
- QWaitCondition.wait(QMutex): Forces the Thread to wait until QWaitCondition.wakeOne() or .wakeAll() is called in another thread.
- QWaitCondition.wait(QMutex, time=int(millis)): This works like threading.Event.wait(seconds).
- QWaitCondition.wait(QMutex): Forces the Thread to wait until QWaitCondition.wakeOne() or .wakeAll() is called in another thread.
- Signals: QThread.started,.finished,.terminated (launched by terminate(); it is thread unsafe!)
Updating GUI's from QThread using fake objects
import Queue,traceback class TauEmitterThread(QtCore.QThread): """ This object get items from a python Queue and performs a thread safe operation on them. It is useful to delay signals in a background thread. :param parent: a Qt/Tau object :param queue: if None parent.getQueue() is used, if not then the queue passed as argument is used :param target: the method to be executed using each queue item as argument """ def __init__(self, parent=None,queue=None,method=None): """Parent most not be None and must be a TauGraphicsScene!""" #if not isinstance(parent, TauGraphicsScene): #raise RuntimeError("Illegal parent for TauGraphicsUpdateThread") QtCore.QThread.__init__(self, parent) self.log = Logger('TauEmitterThread') self.queue = queue self.todo = Queue.Queue() self.method = method self.emitter = QtCore.QObject(QtGui.QApplication.instance()) self._done = 0 QtCore.QObject.connect(self.emitter, QtCore.SIGNAL("doSomething"), self._doSomething) QtCore.QObject.connect(self.emitter, QtCore.SIGNAL("somethingDone"), self._next) self.emitter.moveToThread(QtGui.QApplication.instance().thread()) def getQueue(self): if self.queue: return self.queue elif hasattr(self.parent(),'getQueue'): self.parent().getQueue() else: return None def getDone(self): """ Returns % of done tasks in 0-1 range """ return self._done/(self._done+self.getQueue().qsize()) if self._done else 0. def _doSomething(self,args): print '#'*80 self.log.debug('At TauEmitterThread._doSomething(%s)'%str(args)) if self.method: try: self.method(args) except: self.log.error('At TauEmitterThread._doSomething(): %s' % traceback.format_exc()) self.emitter.emit(QtCore.SIGNAL("somethingDone")) self._done += 1 return def _next(self): queue = self.getQueue() self.log.debug('At TauEmitterThread._next(), %d items remaining.' % queue.qsize()) if not queue.empty(): try: item = queue.get(False) #A blocking get here would hang the GUIs!!! self.todo.put(item) except Queue.Empty,e: pass return def run(self): print '#'*80 self.log.debug('At TauEmitterThread.run()') self._next() while True: item = self.todo.get(True) if type(item) in tau.core.utils.types.StringTypes: if item == "exit": break else: continue self.emitter.emit(QtCore.SIGNAL("doSomething"), item) #End of while self.log.info('Out of TauEmitterThread.run()') #End of Thread
Delaying model subscription using TauEmitterThread
#Applying TauEmitterThread to an existing class: import Queue from functools import partial def modelSetter(args): obj,model = args[0],args[1] obj.setModel(model) class TauGrid(QtGui.QFrame, TauBaseWidget): ... def __init__(self, parent = None, designMode = False): ... self.modelsQueue = Queue.Queue() self.modelsThread = TauEmitterThread(parent=self,queue=self.modelsQueue,method=modelSetter ) ... def build_widgets(...): ... previous,synoptic_value = synoptic_value,TauValue(cell_frame) #synoptic_value.setModel(model) self.modelsQueue.put((synoptic_value,model)) ... def setModel(self,model): ... if hasattr(self,'modelsThread') and not self.modelsThread.isRunning(): self.modelsThread.start() ...
How to create a TAU and PyQt GUI or Widgets
Create a GUI
Copied from http://www.cells.es/Members/srubio/howto/pyqt
- open qt designer
- create your widget
- save the ui_XXX.ui file
- > pyuic4 ui_XXX.ui > ui_XXX.py
- edit your XXX.py file
- Create a new class inheriting from the object that you want to create (QMainWindow or QDialog)
- in the __init__ method create an ui_XXX object and call to setupUi and retranslateUi methods
- This self.ui object will contain all the widgets and other objects that you added on QtDesigner.
- Add to your class the methods (Slots) that you want to execute for each Widget event (Signal)
- Connect Signals and Slots
- Browse to http://www.riverbankcomputing.com/static/Docs/PyQt4/html/classes.html to see what each PyQt class offers to you.
# This is the contents of the MyGui.py file
# It assumes that you have created ui_MyGui.ui with QtDesigner
# You have to execute >/pyuic4 ui_MyGui.ui > ui_MyGui.py to generate the file to import
from PyQt4 import QtGui,QtCore
from ui_MyGui import ui_MyGui
class MyGUI(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = ui_MyGui()
self.ui.setupUi(self)
self.connect(self.ui.push_button,QtCore.SIGNAL('clicked(bool)'),self.DoAction)
def DoAction(self,arg = None):
print 'What a %s action.' % ('wonderful' if arg else 'sad')
app = QtGui.QApplication(sys.argv[0:1])
gui = MyGui()
gui.show()
sys.exit(app.exec_())
How to create a TAU Widget
Writing your own widgets from a template
- goto widget/utils
- type:
python widgetgen.py <classname> <superclass> <outputfile> [<qtfile>]
But it needed some patches (DbWidget example using svn tau.widget.utils package)!!! :
- First the template is generated:
python ../widget/utils/widgetgen.py DbWidget QtGui.QTextEdit dbwidget.py
- Editing dbwidget.py :
- Added taubase to imports
7c7 < from tau.widget import TauBaseWidget --- > from tau.widget import TauBaseWidget,taubase
- Modified init to avoit non-TAU parents to be passed to TauBaseWidget?.init
it caused exceptions in designer23c23,28 < self.call__init__(TauBaseWidget, name, parent, designMode=designMode) --- > if isinstance(parent,TauBaseWidget): #It was needed to avoid exceptions in TauDesigner! > print 'in DbWidget->TauBaseWidget __init__(parent)' > self.call__init__(TauBaseWidget, name, parent, designMode=designMode) > else: > print 'in DbWidget->TauBaseWidget __init__()' > self.call__init__(TauBaseWidget, name, designMode=designMode) 50c55,56 < raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self)) --- > return tau.core.TauDatabase > #raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self))
- Instead of eventReceived I overwrote eventHandle;
because in that method appeared the execution exceptions related to setToolTip() and setText(value)112a119,141 > def eventHandle(self, value): > """ eventHandle, called from setChangeEvent and setConfigEvent > param @value set previously to self.getDisplayValue() if nothing specific received > """ > #update text/title #Commented because it was setting text = "controls01:10000" > #if not self.getShowText(): value = '' > #if self._setText: self._setText(value) > > #update tooltip #Commented because there's no tooltip for Db; it caused exception! > #self.setToolTip(self.getFormatedToolTip()) > > print 'in DbWidget eventHandle' > obj = self.getModelObj() > if obj: > print 'obj is not null' > # sergi: this is the command I added to update the info displayed > text = obj.get_info() > self.setText(text) > print 'text set as "%s"' % text > ##update appearance > self.updateStyle() > print 'out of DbWidget eventHandle'
- getModelClass modified to return TauDatabase
50c55,56 < raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self)) --- > return tau.core.TauDatabase > #raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self))
- Added taubase to imports
Now all execution/designer problems are solved
Customization of the Widget
- Setup the signals to be subscribable from Qt:
__pyqtSignals__ = ("modelChanged(const QString &)",)
- Modify methods needed for your widget:
- Model initialization:
@QtCore.pyqtSignature("setModel(QString)") def setModel(self,model): self.setModelCheck(model) ##It must be included
- Rewrite event Handling (launched by changeEvent(QString) and configEvent()):
@QtCore.pyqtSignature("changeEvent(QString)") def setChangeEvent(self, value): self.eventHandle(value) @QtCore.pyqtSignature("configEvent()") def setConfigEvent(self): self.eventHandle(self.getDisplayValue() or self.getNoneValue()) def eventHandle(self, value): # update text/title if not self.getShowText(): value = '' if self._setText: self._setText(value) ##It must be included #update tooltip self.setToolTip(self.getFormatedToolTip()) ##It must be included #TODO: update whatsThis #update appearance self.updateStyle() ##It must be included
- Model initialization:
Any of these methods could be rewritten; those decorated will appear in QtDesigner connections view.
- Rewrite updateStyle : use it to customize look&feel, call it from eventHandle!
NOTE: Not event-driven objects can be customized using this method.
- Signals to connect with:
- getChangeSignal : returns QtCore.SIGNAL('changeEvent(QString)')
- getConfigSignal : QtCore.SIGNAL('configEvent()')
QtCore.QObject.connect(self, self.getChangeSignal(), self.setChangeEvent)
About declaring functions available to Qt Signals/Slots connection see #SignaturesusedbyQttorecognizeSignalsSlots
More things …
from unfinished Thiago's tutorial
- Must inherit from TauBaseWidget
- Must inherit from QWidget or any of its subclasses
- Must provide the following constructor:
def __init__(self, parent = None, designMode = False)
- must call the TauBaseWidget Constructor:
self.call__init__(TauBaseWidget, name, designMode=designMode)
- Must define two properties:
- model
- parentModel
- Should implement getModelClass
- Should implement updateStyle
Event Sequence
e.g.: two ways of launching fireEvent(...):
- setModel(QString) -> setModelCheck(QString)
- eventReceived : handle for events received (from Tango or also from Qt?)
Once fireEvent is called it notifies all listeners (included the widget itself!):
- fireEvent(type)
- emit(self.getConfigSignal() or (self.getChangeSignal(),getDisplayValue())
Note: This signals are connected to setConfigEvent and setChangeEvent Note: To control what is send with ChangeSignal the DisplayValue must be set first.
Once event is received the sequence is:
- setChangeEvent(value) / setConfigEvent() #available slots for events:
@QtCore.pyqtSignature("changeEvent(QString)") / @QtCore.pyqtSignature("configEvent()") - eventHandle(value or getDisplayValue) #it performs real event management.
It must be reloaded and include a call for updateStyle() or TauBaseWidget.eventHandle(self,value). - updateStyle() #It should always contain a self.update() call
Notes:
- getDisplayValue(): by default returns getModelObj().getDisplayValue()
- ...
Accessing Models from ipython
Minimal example (take care of how getFactory is used!)
#! python
import tau.core
tfact = tau.core.TauManager().getFactory()()
mks2 = tfact.getDevice('tango:10000/LAB/VC/MKS-02')
<tau.core.TauDevice Object>
attr = mks2.getAttribute('Status')
attr.read()
<PyTango AttributeValue Object>
print attr.read().value
...
mks2.getHWObj()
<PyTango DeviceProxy Object>
print mks2.getHWObj().get_property(['SerialLine'])
...
Reading/Writing properties of a TauDevice
import tau.core #From inside any widget using a TauDevice as Model: model = self.getModel() #A TauDatabase method is used to read properties list all_properties = tau.core.TauManager().getFactory()().getDatabase().get_device_property_list(model.name(),'*') #Any PyTango.DeviceProxy method can be used all_props_values = model.get_property(all_properties) #it returns a dict object #For updating the value of a property: model.put_property({'prop_name':prop_value}) #OR model.put_property(all_props_values)
Changing internal Tau Polling
tau.Attribute(c).changePollingPeriod(period_ms)
Adding a widget to TAU packages and/or Qt Designer
- Widget plugin files must be placed at $PYTHONPATH/tau/widget/designer or any other folder in PYQTDESIGNERPATH
- Must inherit from TauWidgetPlugin and implement a few methods:
import tauplugin from tau.widget.dbwidget import DbWidget class DbWidgetPlugin(tauplugin.TauWidgetPlugin): """TauPlotPlugin(tauplugin.TauWidgetPlugin) Provides a Python custom plugin for Qt Designer by implementing the QDesignerCustomWidgetPlugin via a PyQt-specific custom plugin class. """ def getWidgetClass(self): return DbWidget def getIconName(self): return 'columnview.png' def includeFile(self): return "tau.widget.dbwidget"
tau.widget Remarks:
- widget module is for generic tango widgets only
- there will be modules for gl, qwt, etc
- if you implement a widget that has reference to any system or interprets tango data in a very particular way it is not a widget. Just part of your application

