Thursday, 8 January 2009

Implementing a Custom Widget using pyqt4.

Pat from the linux link tech show (and others) have told me to stop whinging, and solve my own IT worries. I won't go over these again here, but scroll down for the full story.
So I dusted down all the programming manuals on the shelf and got to it, and I have to say, so far so good, I have spend many happy hours coding, and am REALLY enjoying the experience.

The tools - python and the qt4 toolkit, mysql, mysqldb, qt-designer and SPE (a wonderful python IDE)

Tomorrow, I have a meeting with a colleague, and I need to show him where I'm at. Thought I'd share here also. Here's a screenshot.



A thing of beauty IMHO!

Anyway, I was delighted how easy PyQt4 makes it to embed a custom widget. My little dental chart slotted into the tabPageWidget above is such a thing. The code is below for all to laugh at.
Note - if you have python and pyqt4 installed, this script will run as a top-level window, something ALL qt widgets are capable of doing. Very cool.

The chartWidget inherits from the QWidgetClass, but overwrites several methods, including the all-important paint method. So today I learned how to draw rectangles, rounded rectangles, lines and points again. QCanvas is like a big etch-a-sketch for nerds. Much Fun.

NOTE - this code borrows heavily from the "counters.py" example in chapter 11 of the pyqt4 book by Mark Summerfield.


#!/usr/bin/env python
from __future__ import division
from PyQt4 import QtGui,QtCore

class chartWidget(QtGui.QWidget):
'''a custom widget to show a standard UK dental chart - allows for user navigation with mouse and/or keyboard'''
def __init__(self, parent=None):
super(chartWidget,self).__init__(parent)
self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding))
self.grid = [[18,17,16,15,14,13,12,11,21,22,23,24,25,26,27,28],[48,47,46,45,44,43,42,41,31,32,33,34,35,36,37,38]]
self.selected = [0, 0]
self.setMinimumSize(self.minimumSizeHint())
def sizeHint(self):
return QtCore.QSize(600, 150)
def minimumSizeHint(self):
return QtCore.QSize(240, 80)
def mousePressEvent(self, event):
xOffset = self.width() / 16
yOffset = self.height() / 2
x= event.x()//xOffset
if event.y() < yOffset:
y = 0
else:
y = 1
self.selected = [x, y]
self.update()

def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Left:
self.selected[0] = 15 if self.selected[0] == 0 else self.selected[0] - 1
elif event.key() == QtCore.Qt.Key_Right:
self.selected[0] = 0 if self.selected[0] == 15 else self.selected[0] + 1
elif event.key() == QtCore.Qt.Key_Up:
self.selected[1] = 1 if self.selected[1] == 0 else self.selected[1] - 1
elif event.key() == QtCore.Qt.Key_Down:
self.selected[1] = 0 if self.selected[1] == 1 else self.selected[1] + 1
self.update()

def paintEvent(self,event=None):
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
midline=self.width()/100
midlineV=self.height()/20
xOffset = (self.width() - midline) / 16 #cell width
yOffset = (self.height() - midlineV) / 2 #cell height
painter.setPen(QtCore.Qt.red)
painter.drawLine(0,self.height()/2,self.width(),self.height()/2)
painter.drawLine(self.width()/2,0,self.width()/2,self.height())

for x in range(16):
if x>7:
midx=midline
else:
midx=0
for y in range(2):
cell = self.grid[y][x]
rect = QtCore.QRectF(x * xOffset + midx, y * yOffset+y*midlineV,xOffset, yOffset).adjusted(0.5, 0.5, -0.5, -0.5)
backTooth=False
color = QtGui.QColor("#ddddff")
if str(cell)[1] in ("8","7","6","5","4"): #molars
backTooth=True
painter.setPen(QtGui.QColor("black"))
painter.setBrush(color)
if backTooth:
outerRect=rect.adjusted(0,2,0,-2)
irw=outerRect.width()/4 #inner rectangle width
irh=outerRect.height()/4 #inner rectangle height
innerRect=rect.adjusted(irw,irh,-irw,-irh)
painter.drawRect(outerRect)
painter.drawRect(innerRect)
painter.drawLine(outerRect.topLeft(),innerRect.topLeft())
painter.drawLine(outerRect.topRight(),innerRect.topRight())
painter.drawLine(outerRect.bottomLeft(),innerRect.bottomLeft())
painter.drawLine(outerRect.bottomRight(),innerRect.bottomRight())
else:
outerRect=rect.adjusted(0,2,0,-2)
rw=outerRect.width()/3
rh=outerRect.height()/2.1
innerRect=rect.adjusted(rw,rh,-rw,-rh)
painter.drawRect(outerRect)
painter.drawRect(innerRect)
painter.drawLine(outerRect.topLeft(),innerRect.topLeft())
painter.drawLine(outerRect.topRight(),innerRect.topRight())
painter.drawLine(outerRect.bottomLeft(),innerRect.bottomLeft())
painter.drawLine(outerRect.bottomRight(),innerRect.bottomRight())

#draw a rectangle around the selected tooth, but don't overwrite the centre
if [x, y] == self.selected:
painter.setPen(QtGui.QPen(QtCore.Qt.blue, 3))
painter.setBrush(QtCore.Qt.transparent)
painter.drawRect(rect.adjusted(1,1,-1,-1))

if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
form = chartWidget()
form.show()
sys.exit(app.exec_())

2 comments:

Iain said...

Looks good to me. I work with a retail insurance system. The original system was an Access database written by one of the insurance managers. It fitted the business requirements perfectly - far better than a system written by an outsider.
It was really only abandoned because of the underpowered architecture.

Husen Daudi said...

Thanks for helpful blog,
I got lots of ideas to implement custom widget with pyqt4.