Personal tools
You are here: Home HowTos How To use PyQt and TAU / Taurus
Log in


Forgot your password?
 

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

  1. Using TAU
    1. Official Documentation
    2. TAU Concepts
    3. Tau Core recipes
      1. Creating an object callback for an attribute events / polling
      2. Modify polling period
      3. Setting up a TaurusBaseComponent
      4. Get the list of instantiated attributes
      5. Setting models as a formula with Tango attributes values
    4. Tau Widget Recipes
      1. Before anything else
      2. Showing a an "ATKPanel" like form for a device
      3. Creating a command button
      4. Creating a panel with commands using TauCommandsForm
      5. Filters in a !Tau***Form
      6. Show Device Status in a proper way (or any multi-line string attribute)
      7. Configuring a TauPlot from python
        1. Setting x,y sets
        2. Setting custom labels
        3. Modifying curves colors
        4. Modifying Axis scale
        5. Moving a curve to other axis
        6. Showing Raw Data curves in TaurusPlot or TaurusTrend
      8. Drag and Drop from Tau Widgets
      9. URI models for getting spectrum indexes
      10. Limitation (and work-around) in Tau useParentModel
      11. Moving a widget within a frame
      12. Saving an screenshot image of a Taurus Widget
    5. Icons and Resources in Tau
      1. Using theme icons
      2. Loading resources from a file
    6. Signals and Slots
      1. Connecting signals and slots
      2. Emitting signals
      3. Signatures used by Qt to recognize Signals/Slots
      4. Connecting Signals/Slots by object name
      5. Decorate QWidgets to listen to Double Click events
      6. Managing Key Events
    7. Some Qt Recipes
      1. Standard Widgets
        1. A QTableWidget
        2. A QDialog with QGridLayout and a QDialogButtonBox
        3. A Custom Widget with an image/icon, an edit and a push button; with its …
        4. Standard Dialogs and Widgets
        5. QDialog vs QMainWindow (KeyEvents)
      2. Using a raw QwtPlot
      3. Colors, fonts and decoration
        1. Applying HTML tags and Styles on Tau Widgets
        2. preformatted html styles and colors for States and Qualities
        3. Modifying Qt Palette colors
        4. Modify Qt Style Sheet
      4. Playing sound
    8. QThreads
        1. Random remarks
      1. Updating GUI's from QThread using fake objects
      2. Delaying model subscription using TauEmitterThread

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).
  • 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

  1. open qt designer
  2. create your widget
  3. save the ui_XXX.ui file
  4. > pyuic4 ui_XXX.ui > ui_XXX.py
  5. edit your XXX.py file
  6. Create a new class inheriting from the object that you want to create (QMainWindow or QDialog)
  7. in the __init__ method create an ui_XXX object and call to setupUi and retranslateUi methods
  8. This self.ui object will contain all the widgets and other objects that you added on QtDesigner.
  9. Add to your class the methods (Slots) that you want to execute for each Widget event (Signal)
  10. Connect Signals and Slots
  11. 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 designer
      23c23,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)) 
      

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
      

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

Document Actions