Next Previous Table of Contents

### 11. Extended Views

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 if you open another view of a document by calling "Window"->"New Window", this new view works with the same data as the first view, and does like any other view you create with that command. But when it comes to painting into the document, you can only do that in one view - the other views are not displaying the document contents at the same time. If you obscure one view that doesn´t contain the actual contents with another window and then bring it up to the front again, it will display the acutal contents. That comes because after 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 to achieve is that all views should paint syncronous with the one the user actually paints to. In fact, you will see that this enhancement is 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 contents is changed, here by mouse movements (where we copy the changings to the buffer with `bitBlt()`), we just have to call updateAllViews(this). The this pointer is needed, because the calling view doesn´t need a repaint and the `update()` method is only 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: each time the contents of the document is changed by a view, call `updateAllViews()`. How the update has to be executed has to be implemented in the widget´s `update()` method; one may be content by setting e.g. the changed text in an editor, in our application 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 widget that provides the scrolling already. What does scrolling mean ? In our context, the problem begins where we want to open a picture that is bigger than a view can display. therefore, the result will be that you can only see as much as the view provides, beginning from the topleft 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 on the 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 the document contents, but the view area can be moved within the document, so each part can be displayed if the user wants to by moving the scrollbar 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 widgets to 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. The difference here is that `QScrollView` provides a set of event handlers similar to the `QWidget` event handlers especially for the viewport. 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 we will 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 a new picture we have to define a default start size. In other painting applications, this size can be changed most often by a command provided by the user interface, e.g. a dialog that asks for the new width and height as well as a method to shrink the picture contents 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 the geometries of `QWidget` starting at the topleft corner of a widget. If the view is scrolled and the topleft corner is not visible, we have 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 to retrieve 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 a small 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 to reimplementing 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 all implemented event handlers that deal with interaction on the contents of the scrollview to the according methods `QScrollView` provides for this purpose and commented out the resizeEvent. Now we can go over to the implementation of these methods and make use of the size our picture has. As a view is always created after the document exists, we can resize the widget directly in the constructor to fit this 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 initialized size 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 the cursor over the widget from the overall widget to the viewport widget, which we can retrieve with `viewport()`. Now we can reimplement the event handlers. At first, we should take care for the paintEvent, as this is one of the most important ones, because it gets 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 the destination of `bitBlt()` from this to `viewport()`, set the topleft position to 0,0 and set the target (the buffer) to copy 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 the viewport´s absolute position, which is the topleft point visible in the scrollview. The same applies to the y-direction. This part is sometimes hard to understand and you may have to do a bit "try and error" when implementing your own scrollviews. The other possible call 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 to draw 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 that translate the positions. This time, we used the second version of the call we used in `viewportPaintEvent()`, with subtracting the 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 every time ? This will reduce performance most often and lead to a so-called "flicker" effect. This effect sometimes occurs with widgets, but there are some ways to reduce this behavoir. Instead of calling `repaint()`, we could call `repaint(false)` as well. This will not erase the widget contents before redrawing it. As we copy the document contents directly into the widget, we don´t need to erase it anyway, because all the data will be overwritten anyway. In conjunction with `QScrollView`, we will reduce the painting even 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 the document 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 geometries that 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 new methods of the KDE2 libraries, but as usual, this will bring us some interesting functionality - KScribble will be able to open and save a whole range of picture formats and thereby we will remove the restriction of operating only on the png file format.

Next Previous Table of Contents