KDE Alkalmazás Tankönyv: Bővített nézetek

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 call`KScribbleView::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 from`QWidget` 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 inheriting`QScrollView` 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:

- first we have to make the document use a size of its contents. This can be done by getting the size for an opened picture, for anew picture we have to define a default start size. In other painting applications, this size can be changed most often by a commandprovided by the user interface, e.g. a dialog that asks for the new width and height as well as a method to shrink the picturecontents to fit into that new size.
- changing the inheritance of KScribbleView from `QWidget` to `QScrollView`
- renaming the virtual methods to the according viewport methods of `QScrollView`
- adapt the virtual event handlers to act on the geometry of the viewport. This means that the old implementation relies on thegeometries of `QWidget` starting at the topleft corner of a widget. If the view is scrolled and the topleft corner is not visible, wehave to ensure the positions retrieved from the `QWidget` coordinates are translated to viewport coordinates

### 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 method`docSize()`:

    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()` and`openDocument()`:

      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 calling`QPixmap::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 calling`resizeContents()` 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 to`viewportMouseMoveEvent()`, 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 with`e->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 call`viewportPaintEvent()`. 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