Következő Előző Tartalom

9. A nézet definiálása

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 thatKScribbleView is derived from QWidget by default. That is the minimum requirement for a child window, but it lasts already to fullfillour needs. When it comes to defining a new widget's behavoir, we need to know how the user shall interact with the window. In ourexample, this would be obviously the mouse. Therefore, we have to overwrite some virtual methods from QWidget that process mouseevents the widget receives. What we need is to know when the user presses a mouse button, because the drawing shall only take placewhen 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-tofinish the stroke the user has drawn. Further we want our picture to be painted on the window and resized if the user decides to resizethe window he draws into. As members we will also add a QPointArray polyline and a boolean value mousePressed. Add the code with thearrow 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 TheKDE Library Reference Guide, Qt has a good way of handling user events, especially when they target on to widgets. QWidget as abaseclass preselects the events and provides basic event handlers which, as they are declared as virtual, we can overwrite to definehow our widget shall react on user actions. One is already overwritten: the closeEvent() method. This is needed, because ourmain window, represented in the App class, already preselects closing child windows and handles this; therefore the default eventhandler, 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 startimplementing 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 theposition where the mouse was pressed into our array's first three elements. As the array is a QPointArray, it can store values of thetype QPoint (which contain an x and y value themselves). What we will do with this array is to store positions of the mouse and createthe 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 ofall, 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() eventhandler 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 itdraw into the buffer of the document. This is important, because the document's buffer contains the real contents, the view only actsas a communicator between the document and the user. We get the pen from the document instance as well by callingcurrentPen(). The next three lines assign the values inside the polyline QPoint array, setting point 2 to 1, 1 to 0 and 0 tothe 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 allthree values of the array contain the pressing position) and the first mouse move event appears that contains the first position todraw 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 arraythen, if we're only interested to draw a line from one position to the next. The following lines explain that: after drawing into ourbuffer 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. Thenwe use normalize() to have the leftmost and topmost values the smallest (as coordinates are counted from top->bottom andleft->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 getwith penWidth() and widen the rectangle by the width of the pen. (Imagine the mouse movement was only two pixels away but thepen 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 ofrepainting the whole window. It's arguments are: first the object to draw to (the destination), here it is our widget, so we have touse the pointer this. The next two arguments give the destination topleft position to start copying to, then follows thesource to draw from with it's coordinates now including the width and height. As the pixmap coordinates are the same as the coordinatesthat the widget uses (because our pixmap is drawn into the topleft corner), the coordinates for the source and destination topleftpoint 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 setmousePressed 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 toocomplicated to use a document-view model. Just create your document instance so that it contains the contents and copy the contents toyour 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 ourpicture gets painted into the window when something else happens: when you open another window that obscures the painting - then youchange 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 getsrepainted, so we retrieve the geometry from the event ( e->rect() ) and use the coordinates for bitBlt(), just as wedid 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 pixmapin the document class except for loading and saving - but these methods aren't called when creating a new picture. So it seems ourpixmap doesn't have a size nor a predefined background at all (even if we would have set the size, the contents would be random colorsbecause 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. Thisis the point where we can add the initialization as well, because the user can change the size manually and the widget will receive aresize 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 theevent 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 tomake it smaller or bigger, we have to separate these two cases: if we resize to a smaller geometry, the picture shall still containit'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. Thecalculated 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. Thenwe resize the buffer (the document), fill it with white color and then copy back the contents from tmp into buffer. This resizes ourpixmap always syncronous with the widget that displays it but doesn't loose contents which is outside the visible area if the resizingmakes 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 showsup, you're ready to paint your first picture with it !
Következő Előző Tartalom