[Main Page] [kdraw #1] [kdraw #2] [kdraw #3] [kdraw #4]

KDraw 3


Merely drawing lines is not enough - a modern, full-featured drawing application must offer boxes too. Phil has added the bitBlt function in version 0.7 of the pyKDE bindings - which means that the beautiful bug we had in the previous version has disappeared. If you want to see what it looked like, try to comment all the bitBlt function calls out...


#!/usr/bin/env python

import sys
from kdeui import *
from qt import *

PEN = 1
RECT = 2

class KPictureBox(QWidget):
  
  def __init__(self,*args):
    QWidget.__init__(self,parent,name)
    self.tool=PEN
    
    self.pm=QPixmap(320,300)
    
  def paintEvent(self, QPaintEvent):
    self.p = QPainter()
    self.p.begin(self)
    self.p.fillRect(self.rect(), QBrush(QColor(255,255,255)))
    self.p.drawRect(self.rect())
    self.p.flush()
    self.p.end()
    
  def mouseMoveEvent(self, ev):
    self.p = QPainter()
    self.p.begin(self)
    #
    # I was getting a bit tired of the stream of errors that could
    # result from failure to draw, so I used an exception block here,
    # that isn't present in the c++ version. Of course, now that the
    # code is good, it shouldn't be necessary anymore...
    #
    try:
      if self.tool==PEN:
        self.p.drawLine(self.currentPos, ev.pos())
        self.currentPos=QPoint(ev.pos())
      elif self.tool==RECT:
        bitBlt(self, 0, 0, self.pm)
        self.p.drawRect(QRect(self.currentPos, ev.pos()))
    except:
      pass
      
    self.p.flush()
    self.p.end()
  
  def mousePressEvent(self, ev):
    self.p = QPainter()
    self.p.begin(self)

    try:
      if self.tool==PEN:
        self.p.drawPoint(ev.pos())
        self.currentPos=QPoint(ev.pos())
      elif self.tool==RECT:
        currentPos=QPoint(ev.pos())
        bitBlt(self.pm,0,0,self)
    except:
      pass
      
    self.p.flush()
    self.p.end()
    
  def slotPen(instance):
    instance.tool=PEN
    
  def slotRect(instance):
    instance.tool=RECT
    
class KMainWindow(KTMainWindow):
  def __init__(self):
    KTMainWindow.__init__(self)
    self.setGeometry(100,100,410,315)
    
    self.buttonPen=QPushButton("Pen", self)
    self.buttonPen.setGeometry(5,5,75,25)
    self.buttonPen.show()
    
    self.buttonRect=QPushButton("Rectangle", self)
    self.buttonRect.setGeometry(5,35,75,25)
    self.buttonRect.show()

    self.picture=KPictureBox(self,"picture") 
    self.picture.setGeometry(85,5,320,300)
    
    self.connect(self.buttonPen,SIGNAL("clicked()"), self.picture.slotPen)
    self.connect(self.buttonRect, SIGNAL("clicked()"), self.picture.slotRect)

app=KApplication(sys.argv, "KDraw - Python")
window=KMainWindow()
app.setMainWidget(window)
window.show()
app.exec_loop()

Two buttons are defined, one for the pen, and one for the rectangle. They are connected to slot functions in our Picturebox widgets, in an effort at modular code. Besides, it lets you see that signals and slots are not very class-concious and can cross class distinctions with ease.

This is one of the areas where version 0.7 is not entirely compatible with the previous versions: the convenient function connect has been removed, and now you need to qualify connect with some relevant descendant of QObject. (Or with QObject itself.)

In the mouse event functions, the chosen tool is detected, and if it is a pen, we draw a line, otherwise we draw a rectangle, using the bitBlt function and an off-screen pixmap. The use of pixmap buffers to store graphics has been discussed by Eirik Eng in Linux Journal 31, November 1996, pp. 32-43. Whenever the user presses the mousebutton the current state of the canvas is saved in a pixmap and a rectangle is drawn. With the next move, the copy is put back (without the rectangle) and a new, larger, rectangle is draw.


Changes