Következő Előző Tartalom

11. Bővített nézetek

In this chapter we´re going to extend the functionality of our view widget by two enhancements: syncronized views and scrollviews.

11.1 Syncronizing Views

Let´s first explain what this will bring us and how we´re going to do it. While playing with KScribble, you may have noticed, that ifyou open another view of a document by calling "Window"->"New Window", this new view works with the same data as the first view, anddoes like any other view you create with that command. But when it comes to painting into the document, you can only do that in oneview - the other views are not displaying the document contents at the same time. If you obscure one view that doesn´t contain theactual contents with another window and then bring it up to the front again, it will display the acutal contents. That comes becauseafter a widget has been obscured and then activated again, it receives a paint event from the window system, which will callKScribbleView::paintEvent() again and that finally redraws the contents of the area that has been obscured. What we want toachieve is that all views should paint syncronous with the one the user actually paints to. In fact, you will see that this enhancementis a really easy task. The document class already provides us a method updateAllViews(), which calls the update()method on each view in the document´s view list. This makes it very easy to syncronize the document contents - every time the contentsis changed, here by mouse movements (where we copy the changings to the buffer with bitBlt()), we just have to callupdateAllViews(this). The this pointer is needed, because the calling view doesn´t need a repaint and the update() method isonly executed if the sender view is not the same as it´s own.What you´ve got to do here is only to call updateAllViews(this) at the end of the virtual methods mousePressEvent(),mouseMoveEvent() and mouseReleaseEvent() - and you´re done ! Take this as a general rule in your applications: eachtime the contents of the document is changed by a view, call updateAllViews(). How the update has to be executed has to beimplemented in the widget´s update() method; one may be content by setting e.g. the changed text in an editor, in ourapplication we just call repaint(), which generates a paint event and copies the contents of the document into the view again.

11.2 Scrolled Views

In this section we will add a functionality that is most often a thread to developers - if you can´t use an already implemented widgetthat provides the scrolling already. What does scrolling mean ? In our context, the problem begins where we want to open a picture thatis bigger than a view can display. therefore, the result will be that you can only see as much as the view provides, beginning from thetopleft corner; the rest will be cut away from the user´s view. A scrollview on the other hand is a widget that provides a scrollbar onthe right side and on the bottom of the widget by which the user can "move" the contents. In fact, it shows the same size of thedocument contents, but the view area can be moved within the document, so each part can be displayed if the user wants to by moving thescrollbar sliders up and down, left and right. Fortunately, Qt provides a class QScrollView that itself inherits fromQWidget and offers the same base functionality as an ordinary widget but manages the contents by scrollbars automatically -with the additional option that the programmer can either just use an instance of the QScrollView, create the child widgetsto manage with the scrollview as parent and add them to the scrollview with addChild() or create a view by inheritingQScrollView and draw into the viewport, which is a defined area inside the scrollview, instead of directly to the widget. Thedifference here is that QScrollView provides a set of event handlers similar to the QWidget event handlers especially for theviewport. So what was formerly a mousePressEvent() in our view will become a viewportMousePressEvent, a paintEvent()will become a viewportPaintEvent etc. The second possibility will suite our needs to make KScribbleView a scrollable widget and so wewill have to make the following modifications:

Sizing the Document Contents

As already mentioned, we have to set a size to the document contents as well as to initialize this size and provide a method toretrieve the size by the views. For this, we add a variable QSize size to KScribbleDoc as well as the methoddocSize():


kscribbledoc.h:#include <qsize.h>...public:  const QSize docSize(){ return size;};private:  QSize size;

Now we have to modify all methods that deal with initializing and opening the document contents - newDocument() andopenDocument():
  bool KScribbleDoc::newDocument()  {    /////////////////////////////////////////////////    // TODO: Add your document initialization code here->  size=QSize(300,200 );      pen=QPen( Qt::black, 3 );->  buffer.resize(size);->  buffer.fill( Qt::white );    /////////////////////////////////////////////////    modified=false;    return true;  }  bool KScribbleDoc::openDocument(const QString &filename, const char *format /*=0*/)  {   QFile f( filename );  //        if ( !f.open( IO_ReadOnly ) )  //               return false;    /////////////////////////////////////////////////    // TODO: Add your document opening code here      if(!buffer.load( filename, format ))            return false;->  size=buffer.size();    /////////////////////////////////////////////////  //   f.close();          modified=false;    m_filename=filename;     m_title=QFileInfo(f).fileName();    return true;  }

In newDocument(), we initialize the size with a default value of 300 pixels wide and 200 pixels high. This is enough for asmall picture for now and we could add a dialog for resizing as well if we want.When it comes to opening a picture, we have to set the size to the size of the picture. This can be done by callingQPixmap::size(), which we used in openDocument(). Then we´re done with setting the sizes and we can move on toreimplementing KScribbleView and make it a scrollview.

11.3 Adapting the View

As said above, we first have to change some things in the interface of KScribbleView. The following code shows these changings:


#include <qscrollview.h>class KScribbleView : public QScrollView{  Q_OBJECT  protected:    /** changed from mousePressEvent() overwriting QScrollView method */    virtual void viewportMousePressEvent( QMouseEvent* );    /** changed from mouseReleaseEvent() overwriting QScrollView method */    virtual void viewportMouseReleaseEvent( QMouseEvent* );    /** changed from mouseMoveEvent() overwriting QScrollView method */    virtual void viewportMouseMoveEvent( QMouseEvent* );    /** commeted out because we have a document size defined *///    resizeEvent( QResizeEvent* );    /** changed from paintEvent() overwriting QScrollView method */    virtual void viewportPaintEvent( QPaintEvent* );}

Here, we changed the inheritance from QWidget to QScrollView first and added the according include file we need. Also we changed allimplemented event handlers that deal with interaction on the contents of the scrollview to the according methods QScrollView providesfor this purpose and commented out the resizeEvent. Now we can go over to the implementation of these methods and make use of the sizeour picture has. As a view is always created after the document exists, we can resize the widget directly in the constructor to fitthis size and as well resize the contents (which is the viewport size):
#include <qsize.h>KScribbleView::KScribbleView(KScribbleDoc* pDoc, QWidget *parent, const char* name, int wflags) : QScrollView(parent, name, wflags | WPaintClever | WNorthWestGravity | WRepaintNoErase){    doc=pDoc;             mousePressed=false;    polyline=QPointArray(3);->  setResizePolicy ( QScrollView::ResizeOne );->  viewport()->setCursor( Qt::crossCursor );->    QSize size=doc->docSize();      // resize the viewport - this makes the resizeEvent obsolete->    resizeContents(size.width(), size.height());      // resize the widget to show up with the document size->    resize(size);}

Note that formerly, the resizeEvent() took care of resizing the drawing area to the same as the widget size. At the same time,this changed the document size as well, so the document picture had always the same size as the widget. With the already initializedsize of the document (which we set in newDocument() and openDocument()), we just resize the contents by callingresizeContents() provided by QScrollView with the size of the document. You may also notice that we changed thecursor over the widget from the overall widget to the viewport widget, which we can retrieve with viewport(). Now we canreimplement the event handlers. At first, we should take care for the paintEvent, as this is one of the most important ones, because itgets called whenever the widget shows up or is resized.Attention: take care to comment out the resizeEvent() implementation!Now, the paint event will have to copy the pixmap in the buffer to the according position in the view. For this, we have to change thedestination of bitBlt() from this to viewport(), set the topleft position to 0,0 and set the target (the buffer) tocopy from the contentsX and contentsY position on into the viewport:
void KScribbleView::viewportPaintEvent( QPaintEvent *e ){  bitBlt( viewport(),0,0, &doc->buffer,contentsX() ,contentsY() );}

The contentsX() thereby is the position in x-direction of the scrollview´s contents - which goes to position 0 in theviewport´s absolute position, which is the topleft point visible in the scrollview. The same applies to the y-direction. This part issometimes hard to understand and you may have to do a bit "try and error" when implementing your own scrollviews. The other possiblecall of bitBlt() would be to switch the values of the positions and inverting the contents values:bitBlt( viewport(), -contentsX(), -contentsY(), &doc->buffer, 0, 0 );The last changes we need to do are changing the mouse event handlers. First, the mouseMoveEvent(), which changes toviewportMouseMoveEvent(), has a bitBlt() call as well. Here, we have to apply the same chages as in the paint event.Further, in the mousePressEvent() and the mouseMoveEvent(), we have retrieved the position of the mouse events withe->pos(). This position now will deliver us a widget position - not the contents position, so we have to translate this todraw into the correct position of the document with viewportToContents():
  void KScribbleView::viewportMousePressEvent( QMouseEvent *e )  {    mousePressed = TRUE;->  doc->polyline[2] = doc->polyline[1] = doc->polyline[0] = viewportToContents(e->pos());    doc->updateAllViews(this);  }  void KScribbleView::viewportMouseMoveEvent( QMouseEvent *e )  {    if ( mousePressed ) {  ....      doc->polyline[1] = doc->polyline[0];->    doc->polyline[0] = viewportToContents(e->pos());      painter.drawPolyline( doc->polyline );  ....      r.setBottom( r.bottom() + doc->penWidth() );         doc->setModified();->    bitBlt(viewport(), r.x()-contentsX(), r.y()-contentsY() ,->            &doc->buffer, r.x(), r.y(), r.width(), r.height() );        doc->updateAllViews(this);    }  }

In the viewportMouseMoveEvent(), we had to change the destination again from this to viewport() - and with thattranslate the positions. This time, we used the second version of the call we used in viewportPaintEvent(), with subtractingthe contentsX and contentsY values to copy the rectangle containing the current painting into the correct position of the viewport.At last, we will apply a small change in conjunction with the update() method: why should we repaint the whole widget everytime ? This will reduce performance most often and lead to a so-called "flicker" effect. This effect sometimes occurs with widgets, butthere are some ways to reduce this behavoir. Instead of calling repaint(), we could call repaint(false) as well. Thiswill not erase the widget contents before redrawing it. As we copy the document contents directly into the widget, we don´t need toerase it anyway, because all the data will be overwritten anyway. In conjunction with QScrollView, we will reduce the paintingeven more: we limit the update method to call repaint() on the viewport() widget, because that will callviewportPaintEvent(). On the other hand, the painting area we use is the rectangle containing the document contents, which, when thedocument size is smaller than the viewport size. So we can limit the paint event to the rectangle of the viewport where the document is displayed, whose visible width and height we can retrieve and compose to the rectangle. Additionally, we use the erase parameter with false,so the document area doesn´t get erased:
void KScribbleView::update(KScribbleView* pSender){  if(pSender != this)    viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);}

Now you´re ready ! This chapter has been one of the hardest to implement and understand - especially when it comes to the geometriesthat change. But on the other hand, we gave our application a whole new functionality by the new scrollview and the syncronized views.With that, we´re moving on to the last chapter of our tutorial. There, we will apply only a few changes by making use of some newmethods of the KDE2 libraries, but as usual, this will bring us some interesting functionality - KScribble will be able to open andsave a whole range of picture formats and thereby we will remove the restriction of operating only on the png file format.
Következő Előző Tartalom