The K Desktop Environment

Next Previous Table of Contents

9. Defining the View

9.1 Interactivity with the User

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;

     }

9.2 Reimplementing Event Handlers

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.

9.3 Painting and Resizing the Document

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() );
}

Here, we first call the resizeEvent handler or 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