Next Previous Table of Contents
In this chapter we'll turn to the view class of KScribble to define how the child windows shall work. First of all, we notice that
KScribbleView is derived from QWidget
by default. That is the minimum requirement for a child window, but it lasts already to fullfill
our needs. When it comes to defining a new widget's behavoir, we need to know how the user shall interact with the window. In our
example, this would be obviously the mouse. Therefore, we have to overwrite some virtual methods from QWidget
that process mouse
events the widget receives. What we need is to know when the user presses a mouse button, because the drawing shall only take place
when the mouse is pressed. Then we need to know when the mouse is moved (to know where it moves to) as well as when it is released-to
finish the stroke the user has drawn. Further we want our picture to be painted on the window and resized if the user decides to resize
the window he draws into. As members we will also add a QPointArray
polyline and a boolean value mousePressed. Add the code with the
arrow to your include file for the class KScribbleView:
kscribbleview.h -> #include <qpointarray.h> class KScribbleView { . . protected: virtual void closeEvent(QCloseEvent* ); -> virtual void mousePressEvent( QMouseEvent * ); -> virtual void mouseReleaseEvent( QMouseEvent * ); -> virtual void mouseMoveEvent( QMouseEvent * ); -> virtual void resizeEvent( QResizeEvent * ); -> virtual void paintEvent( QPaintEvent * ); KScribbleDoc *doc; -> private: -> bool mousePressed; -> QPointArray polyline; }
Now we're coming to the actual implementation of the event handlers. As explained in
The KDE Library Reference Guide, Qt has a good way of handling user events, especially when they target on to widgets. QWidget
as a
baseclass preselects the events and provides basic event handlers which, as they are declared as virtual, we can overwrite to define
how our widget shall react on user actions. One is already overwritten: the closeEvent()
method. This is needed, because our
main window, represented in the App class, already preselects closing child windows and handles this; therefore the default event
handler, which just accepts the closing, must be overwritten to prevent that and let the App class do the job.
First of all, we have to declare the widget default behavoir in the constructor by initializing members and setting predefined values:
kscribbleview.cpp KScribbleView::KScribbleView(KScribbleDoc* pDoc, QWidget *parent, const char* name, int wflags) : QWidget(parent, name, wflags) { doc=pDoc; -> setBackgroundMode( QWidget::NoBackground ); -> setCursor( Qt::crossCursor ); -> mousePressed=false; -> polyline=QPointArray(3); }
We're setting the background to NoBackground, a cursor (crossCursor) and initialize mousePressed and polyline. Then we'll start
implementing our first event handler, mousePressEvent()
, to recognize when the user presses the mouse and where:
Note: the following implementations have to be inserted completely, so the lines to add are not marked with an arrow !
void KScribbleView::mousePressEvent( QMouseEvent *e ) { mousePressed = TRUE; polyline[2] = polyline[1] = polyline[0] = e->pos(); }
Here, we're setting mousePressed to true, so we have stored this event somehow. The second line is not so obvious: we're storing the
position where the mouse was pressed into our array's first three elements. As the array is a QPointArray
, it can store values of the
type QPoint
(which contain an x and y value themselves). What we will do with this array is to store positions of the mouse and create
the drawing routine from there in the mouseMoveEvent:
void KScribbleView::mouseMoveEvent( QMouseEvent *e ) { if ( mousePressed ) { QPainter painter; painter.begin( &doc->buffer ); painter.setPen( doc->currentPen() ); polyline[2] = polyline[1]; polyline[1] = polyline[0]; polyline[0] = e->pos(); painter.drawPolyline( polyline ); painter.end(); QRect r = polyline.boundingRect(); r = r.normalize(); r.setLeft( r.left() - doc->penWidth() ); r.setTop( r.top() - doc->penWidth() ); r.setRight( r.right() + doc->penWidth() ); r.setBottom( r.bottom() + doc->penWidth() ); doc->setModified(); bitBlt( this, r.x(), r.y(), &doc->buffer, r.x(), r.y(), r.width(), r.height() ); } }
This event handler is probably the most difficult, so we will do a step-by-step walkthrough to understand what's been done. First of
all, the event handler receives all mouse movements over the widget. But as we're only interested in the move if the mouse is pressed,
because that is the time to draw, we have to ask if mousePressed is true. That has been done by the mousePressEvent()
event
handler before, so we don't have to take care for more. Now we're starting the painting action. First we create a QPainter
and let it
draw into the buffer of the document. This is important, because the document's buffer contains the real contents, the view only acts
as a communicator between the document and the user. We get the pen from the document instance as well by calling
currentPen()
. The next three lines assign the values inside the polyline QPoint
array, setting point 2 to 1, 1 to 0 and 0 to
the point to where the move went (this is the contents of the event we're interested in). Assuming we've just pressed the mouse (so all
three values of the array contain the pressing position) and the first mouse move event appears that contains the first position to
draw a line to; this value is moved into the first position in the array again. You may wonder why we need three points in the array
then, if we're only interested to draw a line from one position to the next. The following lines explain that: after drawing into our
buffer is finished (with drawPolyline()
and painter.end()
), we create a rectangle r and use boundingRect()
from QPointArray
to get a QRect
that contains all three points. Therefore we need three values to have a most-complete rectangle. Then
we use normalize()
to have the leftmost and topmost values the smallest (as coordinates are counted from top->bottom and
left->right). The next thing to do is adapt the size of the rectangle by the size of the pen, because the pen has a thickness we get
with penWidth()
and widen the rectangle by the width of the pen. (Imagine the mouse movement was only two pixels away but the
pen thickness is set to ten- then the rectangle wouldn't contain the whole painted area).
Finally, we set the document modified and use the bitBlt()
function to copy the rectangle out of the buffer into the widget.
bitBlt operates bitwise and is very fast, so that it is a good method to copy the painted area from the buffer on the widget instead of
repainting the whole window. It's arguments are: first the object to draw to (the destination), here it is our widget, so we have to
use the pointer this. The next two arguments give the destination topleft position to start copying to, then follows the
source to draw from with it's coordinates now including the width and height. As the pixmap coordinates are the same as the coordinates
that the widget uses (because our pixmap is drawn into the topleft corner), the coordinates for the source and destination topleft
point are the same. This is something to watch out for in some of the next step, so it may be mentioned here already.
Next comes what happens if we release the mouse button. Then the drawing has to stop when we move the mouse again, so we set mousePressed to false here:
void KScribbleView::mouseReleaseEvent( QMouseEvent * ) { mousePressed = FALSE; }
Now we have finished implementing the user interaction when it comes to the actual drawing operations. The example shows it's not too complicated to use a document-view model. Just create your document instance so that it contains the contents and copy the contents to your view.
What is left to do are two other virtual event handlers that need a reimplementation. First of all, we have to take care that our picture gets painted into the window when something else happens: when you open another window that obscures the painting - then you change to your painting again, but it won't be there, unless your paint event gets processed to redraw the picture:
void KScribbleView::paintEvent( QPaintEvent *e ) { QWidget::paintEvent( e ); QRect r = e->rect(); bitBlt( this, r.x(), r.y(), &doc->buffer, r.x(), r.y(), r.width(), r.height() ); }
This method also uses bitBlt()
to draw the picture from the buffer into the widget. Here, we only need the rectangle that gets
repainted, so we retrieve the geometry from the event ( e->rect()
) and use the coordinates for bitBlt()
, just as we
did in the mouseMoveEvent()
.
The only thing where we didn't do anything about is the size of the pixmap. We didn't set it anywhere - we did not even use the pixmap
in the document class except for loading and saving - but these methods aren't called when creating a new picture. So it seems our
pixmap doesn't have a size nor a predefined background at all (even if we would have set the size, the contents would be random colors
because it is uninitialized).
On the other hand we have the fact that the KScribbleView instances get resized when they show up- at least with the minimum size. This
is the point where we can add the initialization as well, because the user can change the size manually and the widget will receive a
resize event as well. For reasons of simplicity, we want to set the pixmap size the same size the widget has. All this is done in the
event handler resizeEvent()
:
void KScribbleView::resizeEvent( QResizeEvent *e ) { QWidget::resizeEvent( e ); int w = width() > doc->buffer.width() ? width() : doc->buffer.width(); int h = height() > doc->buffer.height() ? height() : doc->buffer.height(); QPixmap tmp( doc->buffer ); doc->buffer.resize( w, h ); doc->buffer.fill( Qt::white ); bitBlt( &doc->buffer, 0, 0, &tmp, 0, 0, tmp.width(), tmp.height() ); }
QWidget
. Then we calculate the size of our picture - because we can resize a window to
make it smaller or bigger, we have to separate these two cases: if we resize to a smaller geometry, the picture shall still contain
it's contents. On the other hand, if we resize to a bigger widget, we have to resize the pixmap as well to that bigger size. The
calculated values are stored in w and h. But before the resize takes place, we create a copy of our pixmap in the document in tmp. Then
we resize the buffer (the document), fill it with white color and then copy back the contents from tmp into buffer. This resizes our
pixmap always syncronous with the widget that displays it but doesn't loose contents which is outside the visible area if the resizing
makes the widget smaller.
Now our first application has gained a step where we can test it's functionality. Just hit "Run" in KDevelop and after KScribble shows up, you're ready to paint your first picture with it !
Next Previous Table of Contents