]>

# KDE Application Tutorials

The KDE Application Tutorials Handbook for the KDevelop Integrated Development Environment
Ralf
Nolden
The KDevelop Team

    Ralf.Nolden@post.rwth-aachen.de

21/03/2000
1.02.00
KDE
KDevelop
application tutorials

The KDE Application Tutorials Handbook for the KDevelop Integrated Development Environment, a tutorial collection on how to create KDE applications through examples. This handbook itself is part of the KDevelop Integrated Development Environment and is therefore also licensed under the GNU General Public License; see Copyright for more information.

## Introduction

As KDE is currently becoming a standard for Unix System desktops, more and more developers want to take advantage of the well-written API that the KDE project includes, to create new applications that offer a GUI as well as an easy use of the classes provided with Qt/KDE. The &kdevelop; project therefore intends to provide an IDE which enables developers to create and extend their applications rapidly with the main focus on KDE.

As beginners always have difficulties getting started, this handbook tries to explain certain aspects of application creation for KDE, by describing how to create running applications with the sample code included with the Qt library, using the &kdevelop; IDE as well as creating KDE applications by examples.

The next chapter will explain to you how to create a project with &kdevelop; that is already a complete application. Then we'll walk through the Qt online documentation, containing the sources for examples on how to use the Qt library.

This will give you a first understanding of how the underlying Qt library functions, and how to use the classes provided with it. Qt is our starting point, as KDE is on top of the Qt library classes, and KDE applications make a wide use of the QT classes.

Later, we will cover how to create a KDE application with &kdevelop;'s application wizard. From the minimum framework provided, we will create a running example KDE application that makes use of the KDE libraries, and will explain the different parts of a KDE application in detail.

The &kdevelop; Team wishes you success in learing about KDE and Qt with this handbook, and hope that it will enable you to become another programmer contributing to the KDE project.

### What you should know already

This handbook requires a base knowledge about the C++ programming language as a minimum to understand the code examples and their functionality. Further, it is assumed that you have read The User Manual to &kdevelop; as well as The &kdevelop Programming Handbook and know how the &kdevelop IDE works in general, and what generated projects are.

## Getting Started

Before you start the tutorial, you should have set up &kdevelop; to work correctly. Ensure you have access to the Qt online reference documentation in the "Help" menu or the corresponding book in the documentation tree. When looking at the first page of the Ot documentation, you will see that it contains a link to a section "Tutorials" and "Examples". Under "Tutorials", you will find a 14-step introduction into using the Qt library to build applications. "Examples" will lead you to a list of example programs that Qt includes, and show for example, usage reference in certain classes. Now, if you are an absolute beginner, you should make yourself comfortable with the idea behind Qt - providing a library that offers:

- a base application class (`QApplication`)

- a widget library for graphical user interfaces

- a set of additional helper classes for graphics, file, and data handling

- the signal-slot mechanism for object communication

- event controlling through event loops and virtual methods

If the above list doesn't make sense to you right now, you should read about the Qt library in The &kdevelop; Programming Handbook where the basics for a Qt application are described in detail. There, you will also find an introduction about the difference between a Qt and a KDE application, which is not covered much at the first level of the Qt tutorial step.

To learn about Qt first is a good start for programming KDE applications as well, so we will create a project that will produce the tutorial applications after editing.

To do this, start with opening &kdevelop; and select "Project""New" from the menubar. The application wizard opens and you have to select which type of project you want to build. For Qt applications, you would choose the Qt application, but we will use the mini-KDE project to build our examples. The reason is simple: the Qt tutorials don't contain much code, and by choosing the Qt application in the application wizard, we would have to do a lot of changing in the provided code, to create "true" examples.

But as the mini-KDE application only consists of an empty main window but also checks for the Qt library and header files, it will fit for our purposes and doesn't require much change.

When entering the next wizard page, we have to enter the project-specific information. There, we can leave out any KDE-specific additions such as a mini-icon and a main icon, documentation etc. We just fill in the information that will be useful for building a suitable base for the tutorials. As the structure of the tutorial steps builds the next step on the previously created code, we can start with "MyWidget" as the project name. This will already build us the base class that is needed in chapter 4, so we don't have to take care of that later. As we want to start with the first tutorial step, we will use '1' as the project number, which we can increase later when moving to the next step.

Then, fill in the other required information such as your name, email address, and project directory. That would be all we need, and we can flip to the next page. Insert your header for the project's header file here or use the given example. The same is done with the fourth page, where the same has to be set for the source files. Finally, the last page is entered, where you have to press "Create" to build the project. If the button is not available, start at the first page again and make sure you didn't miss inserting any required information.

If everything went OK, the project has been build and the messages window in the application wizard will have "READY" at it's last line. Select "Close" to finish the wizard.

When looking at the treeview on the left, you will see that the project alreay includes a class `"MyWidget"` - this is because we chose this to be the application name. Further, when opening "Functions" in the classviewer's "Globals" folder, you will see that the application already contains a `main()` function where the execution will start.

Now the application is already set up. You could do an "Execute" or "Make", but as we want to create the tutorial applications, we can go ahead and change the code first to create our tutorial examples, which we'll do in the next step.

## Building the Qt Tutorials

### Step One: Hello World!

As the previous chapter explains, we created a project called "MyWidget". Now, when looking at the first tutorial page of the Qt documentation, you will see that the first application only uses a `main()` function. Therefore, we only have to change the given `main()` function of our generated project to get the first tutorial step running.

To access the `main()` function, select it from the classviewer's "Globals" folder, which contains a subfolder for "Functions" where all non-class functions are listed. This will open the file `main.cpp` in the "C/C++ Files" window and the editing cursor is already set in front of the first line of `main()`.

#### What to change first

We could start changing the given code towards the desired result which is listed in the tutorial, but it can be even easier. Select the contents of the `main()` function with the mouse, or by holding the Shift key pressed while selecting the lines with the down-arrow key. Then select "Cut" from the "Edit" menu or press the DEL key to remove the selected code. This will clear the main function and we can start from scratch to build our first tutorial.

#### Entering the tutorial code

Now, to insert the code from the tutorial, you could first read it carefully and try to remember which line contained which code. Then, after reading the tutorial's line-by-line description, you could use it as an example to write the tutorial by yourself.

Those who want to go the easy way: select the contents of the listed `main()` function in the documentation browser and press the "Copy" icon from the toolbar. Optionally, you could use the context-menu or the according entry in the "Edit" menu. This will copy the selection into the clipboard and, after switching back to the sourcecode window, can be inserted with "Paste" (either from the context menu, the toolbar icon, or the according entry in the "Edit" menu).

Finally, add the according header files outside the `main()` function:

    #include <qapplication.h>
    #include <qpushbutton.h>

That's it! The source code is done for your first tutorial application.

#### Building Hello World!

To build the first tutorial, select "Make" from the "Build" menu or press the corresponding toolbar icon. Optionally, you could choose "Execute" as well, which results in running the application after the build was successful.

If the build stops due to an error, be assured the fault was on the programmer's side, not on the tutorial! It would be sad to have to search for an error, just when you decided to program for KDE and/or Qt, but you can use this to train yourself to use &kdevelop;.

To find an error, you have two options: either select the according error message in the output window, which will bring you to the line the error occured, or press the F4 key. If there is more than one error, you could use the "Next Error" entry in the "View" menu.

#### Exercises

You may ask "Exercises? This one was so simple, I don't need to excercise!", but be assured, things will get more complicated. This one was the easiest example, and the more complex they get, the more thinking is asked on your side, to understand and follow the tutorial steps. Also the excercises will enable you to learn how to actually use &kdevelop; and it's features.

To teach yourself more with the first tutorial, you should try to do the following changes to the given code and see how this will influence the application behaviour:

1. change the pushbutton to a `QLabel`.

2. use `setText()` instead of directly setting the text in the pushbutton's constructor

3. use `QPushButton::resize ( const QSize & s )` instead of setting the size by width and height directly. Create a `QSize` instance first that contains the size

4. play with the sizes to see which is width and which is height

### Step 2 and 3: Extensions

The tutorial steps 2 and 3 are extending the facilities of the first tutorial. Here, you will learn about signals and slots, as well as parent-child relationships between widgets.

Why is it important to learn about that? Well, the signals and slots are building the technology which make the difference when comparing the Qt library to other widget library. Whenever you will hit an implementation problem where you want to communicate between objects, this will make your work incredibly short, and nothing serious will happen if something went wrong, even misconnections do not lead to a segmentation fault. This is the strength of the Qt library and a lot of KDE/Qt developers will not want to miss it's it's flexibility. Understanding the signal/slot mechanism therefore, is essential to create any KDE/Qt application.

Another thing to watch out for is the fact that the button terminates the `QApplication` instance by calling `quit()`. You will meet `quit()` again when working with `QApplication`'s KDE-pendant, KApplication

### Writing your own Widgets

Now, after having successfully modified and extended the `main()` function, you will hit the point where you need to create a new widget. A unique widget, that only can be constructed with the class you're about to write in step 4. Actually, the class has been created by the application wizard for you, so there is not much to add. You have to insert the code that lesson 4 places in the `main.cpp` file into the `MyWidget` classes' header and implementation file. Fortunately, you only have to add the implementation to the constructor code.

To access the constructor, select it from the classviewer. This will automatically bring you to the implementation where you have to add the code.

Here, you only have to watch out for the include of `qfont.h`. `QFont` is only used in the constructor code of `MyWidget`. Therefore, add `#include <qfont.h>` to the top of the `mywidget.cpp` file.

To run the new application, just hit "Execute". This will result in compling the changes after an autosaving.

### Extending the Widget Class

Now, in lesson 5 the target is to extend the widget class, and you will learn about virtual functions as well. Here, the `resizeEvent()` is reimplemented. What is important to learn here (besides extending a class,) is that Qt works with user events by virtual functions that take an event as a parameter. Therefore, you should make yourself comfortable with the following classes of the Qt library:

- `QEvent`

- `QChildEvent`

- `QCloseEvent`

- `QFocusEvent`

- `QKeyEvent`

- `QMouseEvent`

- `QMoveEvent`

- `QPaintEvent`

- `QResizeEvent`

- `QTimerEvent`

Whenever writing custom widgets, especially view areas of applications, you will have to overwrite the default implementation of the widget's event-methods with your own for certain events that have to be processed. Those would be e.g.

`virtual void mousePressEvent ( QMouseEvent * )`

for processing a mouse event to pop up a context menu. In the implementation, you have to insert a formal parameter into the function header, most developers either use `event` or just `e` as the parameter name.

Then, you can process the event's parameters. For a `QMouseEvent`, you have to ask if the right, middle or left mouse button was the one that caused the event.

### Adding a new Class

In lesson 6, you will have to add a class to the tutorial application. Now, normally you would think "I just will create a header file and an implementation file, then I'm done". &kdevelop; will make it easier for you! When adding a class, you should always use the Classgenerator. This will do all the work for you, so you just will have to add the specific code.

To add a class with the Classgenerator, choose "Project""New Class", which will open a dialog to enter all needed values for the class you want to add. The first thing we have to insert is the classname. The tutorial name this `LCDRange`, so this has to be inserted first. Then proceed to the baseclass. The baseclass is the one that the new class inherits. Looking at the tutorial (Qt 1.42), this is `QWidget`. Now, as most GUI classes to add would inherit `QWidget`, the Classgenerator makes it even easier. Leave the baseclass empty, and instead check "QWidget child class" in the "Additional Options" section. This will automatically add the required `Q_OBJECT` macro to the headerfile, to add slots and signals later (which is required in chapter 7).

As the filenames are automatically inserted, you don't have to take care of that. The only thing that we would suggest adding is the documentation. It is always a good style to add a desciptive documentation to the class, especially as the classname `LCDRange` doesn't inform you specifically about the purpose of the widget.

### Step 7-14

For the rest of the tutorial steps, you are prepared and you already know everything you have to know about adding the classes required, and doing the changes.

After each change you should do a new build, and check your code for errors. Run the application, and follow it's execution. Additionally, you should play with &kdevelop;'s options for "Build". Try executing the application with a commandline argument such as `--geometry`, and debugging it with KDbg in the Tools-window.

Then you should be able to proceed with the Qt examples that are provided with Qt.

In the next chapter, you will be introduced into development for KDE 2, by an example application KScribble, which is a small drawing application that will show you the concepts of application design, and how to implement you own program.

## Creating KDE Applications

This handbook will try to teach you KDE/Qt application design by creating an example application step-by-step. The major intention is to gain a certain understanding how two of the most important ideas of graphical user interface programming work: the document-view model, and the multiple document interface (MDI) model.

You will also get a good idea of concepts that are coming up ahead. These include the KDE 2 and the Qt 2.1 libraries, bringing new enhancements and technology to the programmer's hands, that will improve application design and widen the possibilities that are open to use. Keywords such as "widget themes", "Inter-Process Communication" and "embedded applications" are talked about and will allow even more than you can imagine right now.

&kdevelop; 1.1, still based on KDE 1.1.x, provides developers some of the most exciting possibilities, including the ability to begin developing applications for KDE 2, while still running a stable desktop environment. For this reason, this tutorial will be based on a KDE 2 application that already makes use of Qt-2.1, offering a solution for the mentioned MDI model, which we provide as a ready-to-go frame application. During this tutorial you will get a clear insight on how it works and this will also make it much easier to understand the document-view model, which is hard to understand even for advanced programmers. That will help you create other type of applications, which uses the document-view model, even if they only contain one window and you can't see an obvious need for it.

But let's just move on - you will see what's coming up on you and how easy it actually is.

The upcoming chapters of the tutorial cover the following several steps:

- installing KDE 2 (libraries) and setting up &kdevelop; to create KDE 2 applications.

- understanding the basic concepts of MDI and document-view model.

- creating the generic framework for KScribble, and explaining the concept of the framework.

- implementing the document class, which will provide the interface to read and write the document data, as well as maintaining the document.

- implementing the view class, to visualize the data. This class also takes over the interaction with the user.

- adding dialogs to set the pen width, and color of the pen to draw with, including their integration into the toolbar and menubar

- changing the view from the default `QWidget` inheritance to a scrollable view.

The tutorial example itself is based on the code found in the scribble example application that comes with the Qt library. The source of the original example can also be found within the Qt documentation, in the examples section.

## Installing KDE 2

In this chapter we will first go through a couple of steps that will help you set up &kdevelop; for creating KDE 2 / Qt 2.x applications. The requirements are:

- a current Qt-2.1 snapshot, available at http://www.troll.no

- a current KDE 2 snapshot of the packages `kdesupport` and `kdelibs`

Both the Qt-2.1 and KDE 2 libraries are currently under development but are you may consider them close to final. Changes to be expected for the final versions are minimal, and may not even affect your application. This means you are getting to know the newest libraries available - and you have got a valuable time-saving, enabling you to create your application even before the libraries are completed.

The following sections describe what to do in detail within three steps:

1. installing the Qt library

2. installing the KDE libraries

3. setting up &kdevelop;

Then you are ready to go, and we will proceed with creating the first step of the example program KScribble in the next chapter.

### Setting up Qt-2.1

This first step will give you the minimum requirement to follow our example. There is a possibility to avoid installing KDE 2 if you're only interested in creating Qt-only programs. As Qt-2.1 contains all needed classes, and &kdevelop; provides a project template for multiple document interface applications for Qt-2.1, (as well based on the same framecode that is used in the KDE 2 template), you should have no problem at all following the example with a Qt-only application.

To get the newest Qt library, get a recent version from the Troll Tech website at http://www.troll.no, in the download section. Then change user to root, and copy the tarball to the directory where your currently used Qt version is placed as well - on a SuSE Linux system this would be `/usr/lib`, where you should have a directory for either qt-1.44 or qt-2.0.2, depending on the distribution version. Untar the library sources with

    tar zxvf qtxxx.tar.gz

depending on the name of the tarball. Then set the environment variable `QTDIR` to point to the directory where your new Qt-2.1 library is placed with e.g.

    setenv QTDIR=/usr/lib/qt-2.1

Another solution would be to move the qt-1.44 directory, usually just named `qt`, to `qt-1.44` and create a symlink that for now has to point to `qt-2.1`. After the library is built, change it back to point to the `qt-1.44`directory.

Change to the Qt-2.1 directory and type:

    ./configure
    make

You can now change back your `QTDIR` variable to point to your qt-1.44 installation, after successfully compiling the Qt-2.1 library.

Now switch to the directory `/usr/lib/qt-2.1/lib` and copy the created libraries to `/usr/lib` exept the symbolic link `libqt.so`.

Then you are done with Qt, and you can proceed to the next step, setting up KDE 2 libraries. If you want to develop for Qt only, proceed directly to the section Setting up &kdevelop;.

### Setting up KDE 2 Libraries

The second step leads you through installing the needed KDE libraries. Download a recent snapshot of the library packages `kdesupport` and `kdelibs` from http://www.kde.org/. Then things will go a bit easier than for the Qt library. Just untar the sources to your directory, and change to the new kdesupport directory first. There, type:

    ./configure --with-qt-dir=/usr/lib/qt-2.1 --prefix=/opt/kde2
    make

    and as root:

    make install

This will install the kdesupport package to the directory given in the `--prefix` option and compile using the new build Qt-2.1 library, to which you give the path to configure by the option `--with-qt-dir`.

The same applies to the kdelibs package, just change to the kdelibs directory, and type the same as you did above for the kdesupport package.

You are now done with setting up the requirements for the required libraries to build a KDE 2 application.

As a note aside, the KDE library package contains support for automatic HTML documentation generation, which you can easily get by installing KDoc, version 2.x. Then type `makekdedoc` in your kdelibs directory and KDoc will create the HTML documentation in a subdirectory `srcdoc`. In &kdevelop; you can access the KDE library documentation online with setting the KDE library documentation path in the &kdevelop; Setup dialog to point to `kdelibs/srcdoc`. In the same way, set your Qt-Online documentation path in &kdevelop; to point to `/usr/lib/qt-2.1/html`, so you can access the documentation right away from within &kdevelop;.

If you successfully installed the Qt-2.1 and KDE 2 libraries, you're just one step away from creating your first KDE 2 application. You just need to give &kdevelop; the information as to where you installed the libraries - which we'll do in the next section.

### Setting up KDevelop

This part is somewhat the easiest in the whole configuration process for KDE 2 development: Setting up &kdevelop;. Just open &kdevelop;, then select "Options""KDevelop Setup" to access the &kdevelop; configuration. There, change to the last page, titled "Path". This page contains two entry fields, one for the Qt library, and one for the KDE 2 libraries. As you have successfully installed everything, select `/usr/lib/qt-2.1` for the Qt-2.x library path and `/opt/kde2` for the KDE 2 library path. Press the "OK" button and you're done.

Users that only want to develop for Qt-2.x (including the current Qt-2.0.2 version) just have to set the Qt-2.x library path.

Now we can proceed to the next chapter where we will create the first step of the KScribble example.

## Application Concepts

This chapter now introduces you into the ideas of the application models mentioned already: the MDI (Multiple Document Interface) and the Document-View model. A basic introduction into the Document-View model and the generating of a project with &kdevelop; is already given in The &kdevelop; Programming Handbook, but based on a Single Document Interface (SDI). In any case, you should be familiar with the basics of the KDE and Qt classes that are explained in The KDE Library Reference Guide. There, the base classes of the underlying libraries are explained in detail, and how to use them, with an additional description of the Qt signal-slot mechanism and event handling.

As a guideline, you should know already:

- that every KDE application needs one instance of `KApplication` (for a Qt application one instance of `QApplication`)

- generally a main window is inherited from `KTMainWindow` for KDE (for a Qt application inherited from `QMainWindow`)

- that events (e.g., keyboard presses or mouse movements) are sent to the application by the underlying window system

- that QWidget as the base class for all visible Graphical User Interface (GUI) components provides specialized event handlers by virtual functions that filter out the according events by reimplementing `QObject::event()`

- that the Qt signal-slot mechanism means basically that a component can send out signals and a receiver can connect to those signals to process some actions

- that this mechanism avoids inheriting classes for GUI components because the signals inform you about e.g. mouse presses where appropriate for a wiget

### The Document-View Model

The Document-View Model is one of the most basic concepts in the design of applications that rely on graphical user interfaces. Therefore, a certain understanding of the why this is, helps you understand that although the programmer has other possibilities, it makes sense to make use of the Document-View Model.

But first let's follow the usual design of a typical KDE / Qt application:

Your application instance provides the first connection to your application and is the starting point of the event handling for a program. The program itself represents itself to the user by a Graphical User Interface which is most often called a main-window. The main window then provides the appropriate functions for the user such as key accelerators, a menubar, toolbar and a statusbar. In the center, it contains a so-called "view-area" which means that this part is an instance of another class, usually called a "View". The view instance is created when the main window is constructed at program start, and has to be set as the view area component with a method provided by the main window: `setView(your_view)` for KDE applications using `KTMainWindow`, `setCentralWidget(your_view)` for a Qt application using `QMainWindow`. Now, the view obviously is the area that is responsible for interacting with the user to manipulate the data that it represents. As an example, you could use a `QMultiLineEdit` as a view and you will have an editor. Then you can use the provided slots of the view for connections to menubar or toolbar command like this:

While creating the menubar, you want to provide a method for the command "cut" in the "Edit" menu:

    pEditMenu->insertItem(BarIcon("editcut"), i18n("Cu&t"),view, SLOT(cut()),KAccel::Cut, ID_EDIT_CUT);

This creates a menu item in the "Edit" menu, that, when activated, directly calls the slot `cut()` of the instance `view`. We are assuming here, that you have created this instance as a `QMultiLineEdit` and set it as the view area. The Multiline-edit's slot is called, and cuts out the selected text as a result. The functionality is already provided by the class itself, and so there is no need to inherit from `QMultiLineEdit` to create a view area that is capable of such actions. They are ready to use, and make application development very fast- you just need the application instance and the main window, including the connections to your view area, and you're done!

That means, a simple editor can be written by creating one single class that defines the main window behavoir and how to save and read files into the editor - just some basic slots your main view has to implement itself.

But here is the reason why we're now introducing this mysterious Document-View model: You have to provide methods of your own, to read and write the actual files you want to edit with the `QMultiLineEdit` view-area within the main window's interface. Yes, this is obviously the easiest thing to do in this case, and the most logical.

Now, if we have a look at the files and their contents as a so-called "Document", which we can subscribe with the attribute of an "Object" in C++ terminology, the next step is just a little one: If I have a document, a view and a main window, why don't I separate these three objects from each other? We could easily create a small class that is responsible for reading in a file into a text stream, and then call the view to draw the text visible to the user. The same applies to saving the file again - the document class should then provide a saving method that retrieves the text from the view again and saves it as a file. In the example these two methods would be the only actions that are needed to be done by a document class, because the edit-view already provides all methods basically needed for and editor by slots and you can manipulate the content of the view by them directly.

The main idea behind the need for this separation into three objects (document, view, main window) instead of the minimum of two, the view and the main window, is this: what if I want to give the user the possibility to work with a file in two or even more views? Such things can even be done within one main window by splitters or deviders, containing two view instances which shall both display one file. The solution can only be that if the user manipulates the file contents in one view, the other view has to be notified about that and to actualize its contents. Otherwise a bad scenario will occur: if the user closes one view where he added something at the end of the file, which he had cut out at the beginning of the file in the other view, the file will be saved later still containing the cut section, because if the second and last view did not recognize this, it still contains the text without modification from the beginning, plus the inserted one. This means that both views have to be synchronized in the contents they are displaying, and that can be done if all views get notified about each action that the user does, which view he actually uses should be irrelevant. So finally a document class is needed to be the one and only holder of the true contents of the document and is providing the views the possibility to manipulate the contents.

I hope this has given some insight into this model, although in most cases it seems that the programmer can live without it - equally by just using a provided class as the view area, or writing a widget on his own to handle user interaction, as long as you represent one document or file by one view area, the view can be responsible for the data as well and only provide methods to retrieve or set the document contents for actions like reading a file or saving it. The next model to describe, the Multiple Document Interface, will make a difference at this point- there you will see the actual need and the functionality the Document-View model provides.

### The Multiple Document Interface (MDI)

As the last section described the Document-View model, you will probably guess what MDI means. Users that come from other Operating Systems than Unix/Linux are used to it, as are programmers developing for those platforms. X-Window applications traditionally are more targeted towards functionality and stability, Unix users are used to single windows that provide functionality, therefore even the Document-View model is often not needed to create applications. With Qt as a multi-platform toolkit, developers are having even more choice - developing for MS Windows(tm) as well as for Unix systems. While on Windows the difficult of creating applications that are capable of handling so-called child windows has been getting to some kind of standard, this is adressed by the Qt 2.1 library, but on the other hand Unix users can profit from this architecture as well.

What is the meaning of MDI? An MDI application generally has the same concept of a usual application that has, as described above, one application instance and a main window. The view area now make the difference: you don't directly use a view that represents data and provides interaction to manipulate that data but a view that handles other windows looking like top-level windows. These windows are now representing the former view area and the main difference is that the interaction chain changes from

application instance -> main window -> view

to

application instance -> main window -> view -> active child window

The view now is capable of several actions:

- creating as many child windows as the user requests,

- providing methods to retrieve the currently active child window,

- maintaining a list of open views,

- managing the child window behaviour for maximize, minimize, as a window manager does for top-level windows.

Now, you can use "complete" widgets like the `QMultiLineEdit` as child windows, for example for an application that only provides one window and each child window is responsible for it's own data. That could be described as "Multiple Document Interface", whereby each child window is equal to a single document. The application then manages the usual actions such as providing methods for the child window interaction such as cut or copy.

Extending this concept with the document-view model enhances the possibilities by far: imagine that you can open as many windows as you like within the main window, and that a new child window can be a new view of a document that is already shown by another child window. The management for this requires a separation into the already described three objects model, but doesn't limit the actual number of instances of the documents as well as the views.

Fortunately, Qt 2.1 contains the possibility to create such applications, and &kdevelop; provides you with the corresponding application frameworks for both Qt-only programs, as well as KDE 2 applications, both with the same interface methods. So &kdevelop; is appropriate wether you want to develop for either one of these. Using KDE 2 interfaces will offer you still more possibilites through library functions as well as inter-process communication, but those are special aspects that we are to cover by separate introductions to these techniques.

Now you are prepared for following the development for KDE 2 - just follow the next chapter to get a first look at the functionality already provided by &kdevelop; when creating applications. There, we will generate the framework for our tutorial application KScribble and describe the practical aspects of programming MDI applications.

## The MDI Framework

If you're already familiar with previous versions of &kdevelop;, you will know that your IDE uses so-called "frameworks" as a starting point of application development. These frameworks by default contain a support for a generic document-view model, but were limited to SDI (Single Document Interface, one window with one view area) model structures. From &kdevelop; 1.1 on, the application wizard is capable of a lot more than the previous versions. It now offers some new project types especially for those developers that want to stay up-to-date with the upcoming KDE 2, as well as for Qt 2.1.

One major extension are MDI application frameworks for both, KDE 2 and Qt 2.1. These frameworks differ only in the use of some library functions such as file dialogs or message boxes as well as the used toolbar and statusbar classes - the KDE version uses KDE 2 methods, the Qt version the according Qt functions.

To create the fist step of this tutorial application KScribble, be sure that you have followed the instructions in chapter 2 of this tutorial handbook: setting up KDE 2. If that went all right, nothing can go wrong. If you couldn't set up the KDE 2 libraries, be sure that you have Qt-2.1 installed and set up &kdevelop; for that at least. You can follow this tutorial by choosing the according Qt framework as well (which has the differences as mentioned above: other method calls for dialogs and GUI components).

### Creating an MDI Framework

Now we will start jumping into development by creating the frame application for KScribble.

Start &kdevelop; and choose "Project""New" from the menubar, to invoke the application wizard. On the first page, you will be shown a tree of project types. These contain KDE and Qt projects. There, select "KDE 2 MDI" from the KDE section. If you only have Qt 2.1 installed, choose "Qt 2.1 MDI" from the Qt section. Then select "Next", enter "KScribble" as project name and "1.0" as version number. Add your name and email address into the according fields and you're done. Click on the "Create" button and &kdevelop; will generate our first version of KScribble, while you can watch the output in the last page of the wizard. Finally, press "Exit" to return to &kdevelop;. In the output window you will see an additional message showing that the messages file has been set up for your project, which will be important to translate KScribble into other languages.

### Overview

It is important to have an understanding "where to find what" in the generated sources, and where your place as a programmer can be found to make KScribble a unique KDE application.

First of all, in the Classviewer you will see three already created classes, `KScribbleApp`, `KScribbleDoc` and `KScribbleView`. As explained above, each of them has a certain part within the application; the App class builds the main window and coordinates user interaction, the Doc class maintains the documents that an application can manipulate, and finally the View class is responsible for user interaction with the child windows and communicates with the document connected to it. This has several consequences. To make a good use of the provided functionality, the programmer is bound to use the given View class. By default, the View inherits `QWidget` as a minimum requirement, but you are not limited to that at all. Inheriting from another class is not too much work and there is also the possibility to use a `QMainWindow`, or `KTMainWindow` for example, as the class to inherit and use another class that manages the view.

In the next chapter we'll continue with extending KScribble to manage it's documents. After that we'll implement the user interaction and you will see the first functionality that is unique to our application: we can draw pictures, load and save them.

## Defining the Documents

The first step when creating an application based on the Document-View model should always be to think what kind of data the application has to manage. This decides how the view class will look, and especially how the document clas will read and write data to and from files, and offer methods to manipulate the data. As KScribble will be a simple drawing application that operates on graphical data, we will use the Qt class `QPixmap` for storing our paintings while it is edited.

`QPixmap` also offers simple methods to read and write pictures into files, so the serialization of the document data is done in just two lines, one for reading and one for writing. Further, we need to define a pen that draws into a document, set it's width and color and make it available for the view class to retrieve the pen. Actually you want the view offering the drawing methods, but the document as the central element for all views has to hold the pen originally, because two views of the same document would otherwise use different pens!

Therefore to define how our document class should work, we will add one instance of `QPixmap`, one of `QPen` and edit the methods `newDocument()`, `openDocument()` and `saveDocument()`.

### Adding the Instances

Open the file `kscribbledoc.h`, by selecting it in one of the fileviewers, or by a click on the classviewer over the class `KScribbleDoc`.

Then add the lines marked with -> from the following code snippet:

    ->  #include <qpixmap.h>
    ->  #include <qpen.h>

        class KScribbleDoc
        {

    ->    	protected:

    ->        QPen currentPen(){ return pen;};

    ->    		int penWidth()
    ->    			{ return pen.width(); }

          public slots:
            void updateAllViews(KScribbleView *sender);

        	protected:

    ->    		QPixmap buffer;

          private:
    ->    		QPen pen;
            /** the modified flag of the current document */
            bool modified;

As you see, we added pen and buffer, as well as `currentPen()` and `penWidth()`. As pen is declared private, we offer a possibility to retrive the pen as well as the pen width. As these are already implemented within the classdeclaration, we don't have to add them to the implementation file, where we're turning to now.

### Initialization of the Document

Select the method `newDocument()` in the `KScribbleDoc` class to jump to the method declaration. Here, we're only adding one line, marked with the arrow:

      kscribbledoc.cpp

      bool KScribbleDoc::newDocument()
      {
        /////////////////////////////////////////////////
        // TODO: Add your document initialization code here
    ->  pen=QPen( Qt::black, 3 );
        /////////////////////////////////////////////////
        modified=false;
        return true;
      }

This initializes the pen with the color black and width of 3 pixels; the `QPen` class has some more constructors, but this one suits our needs here.

### Implementing the Serialization

What is left to do is to define how to open and save our pictures. This is done in the according methods:

    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, "PNG" ))
    ->     	return false;
         /////////////////////////////////////////////////
    ->     //f.close();

        bool KScribbleDoc::saveDocument(const QString &filename, const char *format /*=0*/)
        {
          QFile f( filename );
    ->     // if ( !f.open( IO_WriteOnly ) )
    ->     // 	return false;

          /////////////////////////////////////////////////
          // TODO: Add your document saving code here
    ->    	if(!buffer.save( filename, "PNG" ))
    ->    		return false;
          /////////////////////////////////////////////////

    ->      //f.close();

Add the lines marked with the arrow again to your code. What we did here is to comment out the passages where the file `filename` is opened, because that is done automatically by the load and save methods of `QPixmap`, which we add instead. Other documents may open a file and read in its contents such as text lines or whatever, so the `QFile` methods are already present in the codeframe. As `save()` and `load()` return a boolean value if the operation was successful, we're returning `false` if not, so the rest of the framework gets a return value and can give out warnings if the operation was not successful.

The `load()` and `save()` methods now are already provided in `QPixmap`. They require the filename and the format as argument. The source framework on the other hand does not call the document methods with the format yet. If only one format is used, it sets the format here. Other methods could detect the format, but we will turn to this later. For now, we're using "PNG" as format. See `QImageIO` for more details about the image formats that can be opened.

Now we're already finished defining our document structure. The `QPixmap` buffer serves us as a buffer, storing the original picture contents while we're working on it. The pen is a valid pen for all views connected to the document. Note that the initialization of the pen is done in `newDocument()`. This method is always called after the constructor within the framework internally, so you should add document instances initializations there as we did with the pen.

In the next chapter we will turn to the view class to define how the view shall cooperate with the user as well as how it accesses the document - and then we'll be ready to paint!

## Defining the View

### 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 already fulfils our needs.

When it comes to defining a new widget's behaviour, 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;

         }

### 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 happening, 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 of it anymore.

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.

### 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 of `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 lose contents which are 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!

## Extending the GUI

As we have seen, we have already provided KScribble the ability to open and save pictures with the document class, and enabled user interaction by overwriting virtual methods in the view class. Next we gained the first functionaliy - we can draw pictures. But when we created the `QPen` instance in the document class, we set some pre-defined values for the pen; the color is black and the pen width set to 3 pixels. As you usually want to change these values in a drawing application, we have to enhance the main GUI by providing ways to set these, according to the currently active window and document connected to it. This chapter will therefore introduce you to:

- adding a new menu to the menubar

- extending the toolbar with icons for the actions defined in the menubar

- creating a new dialog with the &kdevelop; Dialogeditor

- connecting the new commands of the menubar and toolbar to calling the dialogs

- enabling the document class to let us change the pen values

Further, we also add a method to delete the document contents at all with a menubar command.

### Adding the "Pen" Menu

As the title of this section says, we will add a menu for setting the pen values of the documents here. Menus that are inserted into the menubar are instances of `QPopupMenu`, and you can have a look at how the current menubar is created when you switch to the `KScribbleApp` class, method `initMenubar()`.

You will see that the menubar items are created in the order they appear on the menubar - but this isn't necessary. There are two things important to how the menubar will look like:

- in the menus the order of the menu-items

- in the menubar the order of inserting the popup menus.

Last but not least you have to create menus by calling the constructor. The class declaration already contains the pointers to the popup menus, so we will have to add our "Pen" menu here first:

    kscribbleapp.h

    class KScribbleApp
    {
    .
    .
      private:
        QPopupMenu* pPenMenu;

    }

Now we are going to create the menu itself. Change to the implementation of the method `KScribbleApp::initMenuBar()` and add the lines marked with an arrow:

    void KScribbleApp::initMenuBar()
    {
    ..

    ->  ///////////////////////////////////////////////////////////////////
    ->  // menuBar entry pen-Menu
    ->  pPenMenu = new QPopupMenu();
    ->  pPenMenu->insertItem(i18n("&Color"), ID_PEN_COLOR);
    ->  pPenMenu->insertItem(i18n("&Brush"), ID_PEN_BRUSH);

        menuBar()->insertItem(i18n("&Edit"), pEditMenu);
    ->  menuBar()->insertItem(i18n("&Pen"), pPenMenu);
        menuBar()->insertItem(i18n("&View"), pViewMenu);

    ->  connect(pPenMenu, SIGNAL(activated(int)), SLOT(commandCallback(int)));
    ->  connect(pPenMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int)));
    }

You see that we first create the menu with `new QPopupMenu()`. Then we use the `insertItem` methods to add two menu entries, Color and Brush. The visible commands are inserted with the method `i18n()`, which ensures that you can internationalize your appliction. So as a general rule, you would declare all visual text that will appear later by the method `i18n()`. Qt-only programs using Qt > 2.0 would use the corresponding method `tr()` instead of `i18n()`, as Qt has it's own ways of internationalizing applications.

The second argument is a macro, the ID of the menubar item. This ID is a number that we have to set using `#define` in the file `resource.h`, where you will see all other already used ID's declared. There are also other ways to insert menus by directly connecting a slot to the inserted entry, but the application framework uses ID's to select which action has been activated - and highlighted. Therefore each menu entry, independent of the popup menu it appears, has to be a unique number, and as we can hardly remember numbers later, setting a #define for the ID is a nice solution. The popup menu is now inserted into the menubar with `insertItem()` as well, and with the pointer to the menu as second argument.

Note that we inserted the popup menu after the "Edit" menu and before the "View" menu, so it will appear between those menus later in the menubar. What is also important when creating menus is that they should be available to the user with shortcuts; ususally in menus you will see underlined characters that the user can jump to directly by pressing Altand the appropriate underlined letter of the menuitem. As a programmer, you have to set this character by a leading ampersand, so the "Pen" menu will later be accessible via the keyboard by pressing Alt P. Within the menu, the user can press another button to go directly to the command he wants to, so in the menu all items should have this kind of shortcuts as well.

Note that you should write item insertions together in groups that have the same visible access, so you can keep a better overview of the characters you already used so that there are no menu accelerators used twice. (this is also important for your translators: in other languages the used accelerator may not be available in the translated word, so they have to set some accelerators again.)

In the last two lines we're connecting the pen menu with two slots: one for when the menu signals that it is activated and the action should be executed, and one for when it is highlighted. That allows making a statusbar help message available for the user. You can have a look at the methods the menu is connected to, they contain switch statements where the sent menu ID is compared and the following action called.

What is left to do is to add the #define statements to the file `resource.h`:

    resource.h

    ///////////////////////////////////////////////////////////////////
    // Pen-menu entries
    #define ID_PEN_COLOR                14010
    #define ID_PEN_BRUSH                14020

You will see that the numbers are unique for these entries - you have to watch out not to set the same number for two entries. If it happens by accident, there's still the compiler to inform you about redefining.

This is currently all you have to do to add a new menu for your menubar. The actions they will execute are: "Color" will call a color selection dialog, "Brush" will call a dialog (which we still have to create) to select the brush width.

But first we'll extend the toolbar as well by two icons for these actions in the next section.

### Adding Toolbar Buttons

Whenever you think that some new commands should be made available by toolbar buttons as well because they are often used and you want to offer additional functionality, you can easily do that by adding buttons in the framework's `initToolBar()` method of the `App` class. Here, we decide to add a button for both menu entries in the Pen menu, but those need icons - which you can either find in the KDE directory `/toolbar` or, when you don't find an icon that matches your action, have to create yourself.

KIconEdit is very suitable to paint icons, so we will first create them. Choose "New" from the KDevelop"File" menu and select "Icon" as the filetype.

The first icon will be named "`pencolor.xpm`". Now we have to select where we want to have the icon created in our project directory. Press the directory selection button and change to your project directory containing the KScribble sources. Then create a new directory "`toolbar`". Change to that directory and press "OK". The new icon will then be created in the new directory "`toolbar`" and will be opened by KIconEdit within &kdevelop; automatically. Paint something that will signalize the user what the button is intended to do, save the pixmap and then switch to the RFV / LFV in &kdevelop;. Select the icon by a right mouse button press and select "Properties" from the popup menu. You will see that the icon is included in the distribution, but for your program to find the icon again later, you have to set the installation destination as well. Check the "install" option and enter into the line now active below:

`$(kde_datadir)/kscribble/toolbar/pencolor.xpm`

This will install the pixmap in the KDE file system hierarchy's data directory, where each application has its subdirectory containing additional files needed by the application. Icons have to be installed into another subdirectory "`toolbar`", so the application's icon loader can find the pixmaps for your program.

After you're finished, repeat all these above steps with the second icon for selecting the pen width. Name this pixmap "`penwidth.xpm`".

Now we only have to insert the buttons into the toolbar; add the lines marked with the arrow into your code:

    void KScribbleApp::initToolBar()
    {
    ..
        toolBar()->insertButton(BarIcon("editcopy"), ID_EDIT_COPY, true, i18n("Copy"));
        toolBar()->insertButton(BarIcon("editpaste"), ID_EDIT_PASTE, true, i18n("Paste"));
        toolBar()->insertSeparator();
    ->  toolBar()->insertButton(BarIcon("pencolor"), ID_PEN_COLOR, true, i18n("Color") );
    ->  toolBar()->insertButton(BarIcon("penwidth"), ID_PEN_BRUSH, true, i18n("Width") );
    ->  toolBar()->insertSeparator();
        toolBar()->insertButton(BarIcon("help"), ID_HELP_CONTENTS, SIGNAL(clicked()),
    ..
    }

Here, we use the methods of `KToolBar` to insert buttons. The first argument, `BarIcon()`, tells the method to load the icon for the button. What seems unusual is that we don't have to care for the file extension. The preferred format for KDE 2 is `*.PNG`, but it works with xpm's as well. (You could use ImageMagick for that as well which can do that- or use KScribble in a later step to convert your icons to PNG!)

The second argument is again the ID. The commands are then automatically activated, as the `toolBar()` is already connected to the same methods as the menubar is for signal `activated()`. The third argument stands for "available" when true, "deactivated" when false; as we want to have these available, we set this to true. At last, we add a tooltip for the the buttons, which we also embrace with `i18n()` to allow internationalization.

You're done for now, the GUI is extended at least visually. You can compile and run KScribble again and see how it looks. Of course the new items in the menubar and toolbar can't execute any action, that is what we're going to add in the next section. You will also note that the toolbar icons we added are not displayed - which is because we didn't install KScribble and so they can't be found. All other used icons are already shipped with the KDE libraries, so these are already visible.

### Creating the Pen Width Dialog

As we´ve already created the according menubar and toolbar commands, we now have to build the first dialog to set the pen width. For this, select "New" from the KDevelop "File" menu and select "Qt/KDE Dialog". Then enter the dialog file name as `kpenbrushdlg`. The extension will be automatically added. Enter "OK" and the dialogeditor opens an empty widget that will be our dialog background.

When constructing a dialog, we have to think about what is really needed by the user. Here, we need a label to display what will be set, a spinbox with up and down buttons to set the pen width value, and three buttons: one for resetting the pen width to the default value, one to cancel the dialog, and one for taking over the new value - the OK button. In this order we will add the items to the dialog - which is important because the tab-focus follows the order by which the widgets are created. So if you´re starting with the OK button, then the spinbox and then the cancel button, the input focus will change from the ok button to the spinbox, and then to the cancel button - which is not what the user expects.

The tab focus should follow the widget´s items top down, from left to right, so we have to construct the dialog in this order as well. To add items to the dialog, select the "Widgets" tab on the left pane. There you have all available widgets present as icons to construct your dialog. Pressing a widget button will create the new item and place it at the top-left corner of the widget. From there, you can place it with the mouse to the position you would like it to show up. Further, when a widget item is selected, you can set the according values in the "Widget Properties" pane on the right.

The Label

  press the "QLabel" button on the "Widgets" tab and place it at position x:50, y:20. Then select the "General" section in the widget properties pane. Change the text in properity "Text" from "Label" to "Pen Width:". Adjust the width of the label to a width that matches the label contents in x-direction; a width of 120 should be fine. You can do this either by using the mouse or set the value in the "Geometry" section of the properties.

The Spinbox

  Press the "QSpinBox" button on the "Widgets" tab and place it at the right of the label we created in the last step. Now set the variable name in section "C++Code" to "width_spbox". The minimum and maximum values are 1 and 100, which should be fine for setting the brush width.

The Buttons

  Finally, we need the mentioned three buttons. The leftmost button will be the default button. Create a `QPushbutton` and place it somewhere nicely on the bottom of the dialog, set the variable name to `"default_btn"` and the button text to "Default". Proceed with the OK button with variable name "ok_btn" and the cancel button with variable name "cancel_btn" and set the button text to "&OK" and "&Cancel".

If you´re fine with the layout of the dialog, choose "Generate complete sources" from the Build menu and set the classname to "`KPenBrushDlg`", the inheritance to `QDialog`. After pressing "OK", the sources for the dialog are created and added to the project. Now you can return to the editor view in &kdevelop; and we can add the code needed to give the dialog some execution purpose.

### Connections and Setting Up

After we have created the GUI of the dialog, we have to add some functionality to the buttons and provide ways to set and retrieve the selected value of the spinbox - because we want the dialog to display the current value when it gets called, and to access the selected value when the user pressed the OK button to quit the dialog.

In the generated class for the dialog, `KPenBrushDlg`, you can see one method besides the constructor and the destructor, `initDialog()`. This method implements the whole GUI construction, so we don´t have to care for that anymore and we can go directly to add the usual connections for the push buttons first. Add the lines marked by arrows to the constructor of the dialog:

    KPenBrushDlg::KPenBrushDlg(int curr, QWidget *parent, const char *name) : QDialog(parent,name,true){
    	initDialog();

    ->	connect(default_btn, SIGNAL(clicked()), this, SLOT(slotDefault()));
    ->	connect(ok_btn, SIGNAL(clicked()), this, SLOT(accept()));
    ->	connect(cancel_btn, SIGNAL(clicked()), this, SLOT(reject()));
    }

This provides the functionality for the buttons on the bottom of the dialog when the user clicks the button. First, we set the default button to execute a slot called `slotDefault()`. This slot is still to be implemented below, where we will set the default value of the spinbox directly.

The second `connect()` call connects the ok button to call the slot `accept()` provided by `QDialog`, as well as the cancel button gets connected to `QDialog`´s slot `reject()`. This will both close the dialog and will set the result value which we will use later when we implement the method that calls the dialog to determine if we want to use the value set or to ignore any changes.

Now we have to add two methods to set and retrieve the spinbox value:

    void setCurrent(int curr){ width_spbox->setValue(curr); }
    int width() { return width_spbox->value(); };

Add these methods to the class declaration with the modifier "public", as we want to set and retrieve the values when we call the dialog to show up. The `setCurrent()` method will be used to set the current value the pen has, the `width()` method returns us the selected with when the user presses OK and we want to know which value has been chosen.

Last but not least, we need to implement the `slotDefault()` method:

    //kpenbrushdlg.h:
    //method declaration:

    public slots:
      void slotDefault();

    //kpenbrushdlg.cpp:
    //method implementation:

    void KPenBrushDlg::slotDefault()
    {
      width_spbox->setValue(3);
    }

This will set the default value to 3 pixels for the pen.

Now we´re ready with our first dialog and we can turn to over to the other application classes to adapt some things and add the method calls to invoke the dialog.

### Calling the Dialogs

As you may guess, calling the dialogs means that we will not only implement calling our width selection dialog but also add the method for selecting the pen color, but one after another. First, create a method `slotPenBrush()` in the class `KScribbleApp`:

    void KScribbleApp::slotPenBrush()
    {
      slotStatusMsg(i18n("Setting brush width..."));

      // get one window with document for a current pen width
      QWidgetList windows = pWorkspace->windowList();
      KScribbleView* m = (KScribbleView*)windows.at(0);
    	KScribbleDoc* pDoc = m->getDocument();
      int curr_width=pDoc->penWidth();

      // create the dialog, get the new width and set the pen width for all documents
      KPenBrushDlg* dlg= new KPenBrushDlg(this);
      dlg->setCurrent(curr_width);
      if(dlg->exec()){
        int width=dlg->width();
      	for ( int i = 0; i < int(windows.count()); ++i )
      	{
        	m = (KScribbleView*)windows.at(i);
        	if ( m )
        	{
        		pDoc = m->getDocument();
            pDoc->setPenWidth(width);
        	}
      	}
      }
      slotStatusMsg(i18n("Ready."));
    }

Here, we first have to access the window list and retrieve a pointer to a document - which can be a document of any window, because all documents should have the same current pen width. Then we create an integer variable `curr_width` that stores the current pen width.

Now we can call the dialog by creating the dlg instance of `KPenBrushDlg`. Then we set the current pen width by calling `dlg->setCurrent()`, which method we added to the dialog. By calling `dlg->exec()` we invoke the dialog. The `if(`m`)` statement ensures that the following code is only executed when the result code of the dialog has the accept flag set - which means, the code is executed if the user pressed the OK button on the dialog.

Assuming the user changed the value and pressed OK, we have to set all documents to use the new pen width. For that we use the `for()` loop and set every document´s pen width to the width variable we retrieved before with `dlg->width()`.

We don´t have implemented the method `setPenWidth()` in the document class, so we´ll do this right now:

    kscribbledoc.h:

    public:
      void setPenWidth( int w ){ pen.setWidth( w ); }

What is missing to execute any action is to add the methods that shall be called when the menu items are activated or the toolbar buttons pressed. For this, we have to add the ID´s to the slot `commandCallback()`, which selects and executes the according methods we want to call if a menu or toolbar item was chosen:

    void KScribbleApp::commandCallback(int id_)
    {
      switch (id_)
      {
        case ID_PEN_BRUSH:
          slotPenBrush();
          break;

        case ID_PEN_COLOR:
          slotPenColor();
          break;
    ....
      }
    }

This addition also adds the `slotPenColor()` method to the execution list to set the pen color, which we will implement now:

    void KScribbleApp::slotPenColor()
    {
      slotStatusMsg(i18n("Selecting pen color..."));

      QColor myColor;
      int result = KColorDialog::getColor( myColor, this );
      if ( result == KColorDialog::Accepted )
      {
        QWidgetList windows = pWorkspace->windowList();
        KScribbleDoc* pDoc;
        KScribbleView* m;
        for ( int i = 0; i < int(windows.count()); ++i )
        {
          m = (KScribbleView*)windows.at(i);
          if ( m )
          {
            pDoc = m->getDocument();
            pDoc->setPenColor(myColor);
          }
        }
      }
      slotStatusMsg(i18n("Ready."));
    }

When looking at the code, we see that we use another new method of `KScribbleDoc` to set the pen color. This one has to be implemented as well:

    kscribbledoc.h:

        /** sets the pen color */
        void setPenColor( const QColor &c ){ pen.setColor( c ); }

Watch out for adding the declaration of the two new methods `slotPenBrush()` and `slotPenColor()` to the class `KScribbleApp`, so our class knows about these methods.

Now you´re ready! Let´s summarize what we´ve done in this chapter:

1. we first added menubar and toolbar commands/items for two dialogs that we want to call;

2. then we created the first dialog to set the pen width with the KDevelop dialogeditor

3. after that we created the methods we want to call by the menubar/toolbar items which invoke the dialogs and set the poperties we wanted to change

4. finally, we added the needed set functions to the document so we are able to change the values after the dialogs get executed.

By this structure, you are provided the general way how to extend your application with more functionality and manipulating settings that influence the behavoir of the document and view interaction.

## Extended Views

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

### Syncronizing Views

Let´s first explain what this will bring us, and then 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.

### 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:

1. 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.

2. changing the inheritance of `KScribbleView` from `QWidget` to `QScrollView`

3. renaming the virtual methods to the according viewport methods of `QScrollView`

4. 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.

### 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 corresponding 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()` is the position in the 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 "trial 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 and lead to a so-called "flicker" effect. This effect sometimes occurs with widgets, but there are some ways to reduce this behavior. 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, 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 does not 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 to understand, especially when it comes to the geometries that change. On the other hand, we gave our application a whole new functionality through 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, making use of some new methods of the KDE2 libraries, but as usual, this will bring us some interesting functionality. KScribble will soon be able to open and save a whole range of picture formats, as we will remove the restriction of operating only on the png file format.

## Using KImageIO with KScribble

When it comes to images, the Qt and KDE libraries offer a wide variety of operations. Besides actual drawing routines, the libraries support a whole set of image formats which they can read and write - all without any cost on our view. The main class for these operations is `QImageIO`, which has a support library by KDE: `KImageIO`. As a preparation before we can make use of these nice methods, we have to add the according library to KScribble: `libkimgio`.

Open "Project""Options" in &kdevelop; and add the line `-lkimgio` to the additional libraries to link KScribble with.

### Preparing the Document

Now we can go on and apply our changes. First, we have to remove the current restriction in the document class to only read and write PNG files. Just replace "PNG" in the following methods with format, as given by the `openDocument()` and `saveDocument()` methods:

In method `KScribbleDoc::openDocument()`:

`if(!buffer.load( filename, format ))`

In method `KScribbleDoc:: saveDocument()`:

`if(!buffer.save( filename, format ))`

Then we have a default behavior of these methods. For now we don´t have all available file formats. This will be our next task, together with adapting the slots in KScribble that deliver us filenames.

### Registering File Formats

To use `KImageIO`, we have to initialize the library first. For this, we add a call for `registerFormats()` in our `main()` function:

    main.cpp:

    ............
      KApplication app;
      KImageIO::registerFormats();

      if (app.isRestored())
    ............

Note that this call is after the application is instanciated with KApplication app - without the application instance, our program will not run, as `KImageIO` then doen´t know on which application to register the formats. The include file for this call will be added to `kscribble.h`, as we´re going to use some of its methods in `KScribbleApp`:

    kscribble.h:

    #include <kimgio.h>

### Opening Images

Now that we can make use of `KImageIO`, we have to apply the first change to the most important method of `KScribbleApp`: `openDocumentFile()`. This method opens us any document, but until now only with a filename. It just leaves out the extension, as the format is not required by the document class by default. But as we have changed that, we just need a format - and have to adapt the call for `KScribbleDoc::openDocument()` in the method `openDocumentFile()`:

    kscribble.cpp:

      void KScribbleApp::openDocumentFile(const char* file)
      {
      	...........

      	else
        {
    ->    QString format=KImageIO::type(file);
    ->	  if(!doc->openDocument(file,format))
        		KMessageBox::error (this,i18n("Could not open document !"), i18n("Error !"));
      		addRecentFile(file);
      	}
        ............
      }

Of course, this works the same as using `QString format=QImageIO::imageFormat(file);`. Here, `KImageIO` delivers us the format of the image, and we can call the document to open the file by filename and format. Another possiblity would be to detect the format in the document class as well.

### Setting File Filters with KImageIO

Here, we are finishing our tutorial with the last section - we will adapt the file dialogs of KScribble to make use of file filters. For these, `KImageIO` provides nice methods to give us the needed strings for all image file formats that are available for opening and saving. The following implementation replaces the default file filter (which is in fact none - you have to change your own applications´ file filter to your mime type accordingly) with the `pattern()` method of `KImageIO`:

      void KScribbleApp::slotFileOpen()
      {
        slotStatusMsg(i18n("Opening file..."));

    ->    QString fileToOpen=KFileDialog::getOpenFileName(QDir::currentDirPath(),
    ->              KImageIO::pattern(KImageIO::Reading), this, i18n("Open File..."));
        if(!fileToOpen.isEmpty())
        {
      		openDocumentFile(fileToOpen);
        }

        slotStatusMsg(i18n("Ready."));
      }

Here, the mode in `pattern()` is set to Reading - which may differ from the patterns that are retrieved when set to writing. Now we have finished opening files by its filename and format completely. What is missing to complete this structure, is setting the patterns as well for saving a file. For that, the `slotFileSaveAs()` is called, which itself invokes the file dialog that retrieves a file name. There, we will set the pattern mode to Writing:

    void KScribbleApp::slotFileSaveAs()
    {
      slotStatusMsg(i18n("Saving file with a new filename..."));

    ->  QString newName=KFileDialog::getSaveFileName(QDir::currentDirPath(),
    ->                               KImageIO::pattern(KImageIO::Writing), this, i18n("Save as..."));
      if(!newName.isEmpty())
      {
        KScribbleView* m = (KScribbleView*)pWorkspace->activeWindow();
        if( m )
        {
          KScribbleDoc* doc =	m->getDocument();
      	  QString format=QFileInfo(newName).extension();
      	  format=format.upper();
    		  if(!doc->saveDocument(newName,format))
      	  {
      		  KMessageBox::error (this,i18n("Could not save the current document !"), i18n("I/O Error !"));
    				return;
    			}
          doc->changedViewList();
          setWndTitle(m);
        }
      }
      slotStatusMsg(i18n("Ready."));
    }

## Adding Printing Functionality

In this chapter we will show you how easy it is to implement printing functions using Qt. It´s actually just one line of code for us to do here, but we will start understanding who is actually doing the printing job. When the user presses the print button in KScribble or chooses "Print" from the "File" menu, the `slotFilePrint()` method is called in `KScribbleApp`.

This method detects which child window is currently active, and creates a printer instance of the class `QPrinter`. Then it calls the widget´s printing method, `KScribbleView::print()`. Here, the framework already contains the base implementation - which already shows you that for printing you just have to use `QPainter` which then draws on the printer. This method also calls the printing dialog.

What we have to do here is to use `QPainter` methods to draw the pixmap of the document connected to the view. As `QPainter` already offers a whole set of methods `drawPixmap()`, we will of course use one of them:

    void KScribbleView::print(QPrinter *pPrinter)
    {
      if (pPrinter->setup(this))
      {
        QPainter p;
        p.begin(pPrinter);

        ///////////////////////////////
        // TODO: add your printing code here
    ->	p.drawPixmap(0,0,doc->buffer);
        ///////////////////////////////
        p.end();
      }
    }

Here, we paint into the offset of the printer page at 0,0 with our buffer pixmap of the document. That´s all!

You can just go ahead and test it - now you can print any graphics file that KScribble is able to open.

This is now the end of our tour through creating a KDE 2 application. You can find the source package of KScribble complete with an extension that adds cut, copy, paste, and undo functions, as well as drag´n drop here:

`$(KDEDIR)/share/apps/kdevelop/examples/kscribble-1.0.tar.gz`

The example tarball is locally installed and can be downloaded to your home directory, where you can untar it and test it. After untarring the tarball with `tar `zxvf` kscribble-1.0.tar.gz`, load the project and call "Automake and autoconf" from the "Build" menu in &kdevelop;, then call "./configure" from the same menu. The configure options are those of my installtion of the KDE 2 and Qt 2.1, so you have to change them manually to match your installation path for these options.

The appendix also contains the complete sourcecode for this package to read through online.

## Copyright

    KDevelop Copyright 1998,1999,2000 The KDevelop Team.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

## KScribble-1.0 Example Sourcecode

### Project Tarball

The example tarball is locally installed and can be downloaded to your home directory, where you can untar it and test it. After untarring the tarball with `tar `zxvf` kscribble-1.0.tar.gz`, load the project and call "Automake and autoconf" from the "Build" menu in &kdevelop;, then call "./configure" from the same menu. The configure options are those of my installtion of the KDE 2 and Qt 2.1, so you have to change them manually to match your installation path for these options.

`$(KDEDIR)/share/apps/kdevelop/examples/kscribble-1.0.tar.gz`

### main.cpp

    /***************************************************************************
                              main.cpp  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/
    #include <kcmdlineargs.h>
    #include <kaboutdata.h>
    #include <klocale.h>

    #include "kscribble.h"

    static const char *description =
       I18N_NOOP("KDE 2 example application");

    static KCmdLineOptions options[] =
    {
       {  "+[File]", I18N_NOOP("image file to open"), 0  },
       {  0, 0, 0  }
    };

    int main(int argc, char *argv[])
    {
       KAboutData aboutData( "kscribble", I18N_NOOP("KScribble"),
          VERSION, description, KAboutData::License_GPL,
          "(c) 2000, Ralf Nolden");
       aboutData.addAuthor("Ralf Nolden",0, "rnolden@kdevelop.de");
       KCmdLineArgs::init( argc, argv, &aboutData );
       KCmdLineArgs::addCmdLineOptions( options ); // Add our own options.

       KApplication app;
       KImageIO::registerFormats();

       if (app.isRestored())
       {
          RESTORE(KScribbleApp);
       }
       else
       {
          KScribbleApp *kscribble = new KScribbleApp();
          kscribble->show();

          KCmdLineArgs *args = KCmdLineArgs::parsedArgs();

          if (args->count())
            for(int i=0;i<args->count();i++)
              kscribble->openDocumentFile(args->arg(i));
          else
            kscribble->openDocumentFile();

          args->clear();
       }

       return app.exec();
    }

### kscribble.h

    /***************************************************************************
                              kscribble.h  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    #ifndef KSCRIBBLE_H
    #define KSCRIBBLE_H

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif

    // include files for Qt
    #include <qstrlist.h>
    #include <qworkspace.h>

    // include files for KDE
    #include <kapp.h>
    #include <ktmainwindow.h>
    #include <kaccel.h>
    #include <kimgio.h>

    // forward declaration of the KScribble classes
    class KScribbleDoc;
    class KScribbleView;

    /**
      * The base class for KScribble application windows. It sets up the main
      * window and reads the config file as well as providing a menubar, toolbar
      * and statusbar. In initView(), your main view is created as the MDI child window manager.
      * Child windows are created in createClient(), which gets a document instance as it's document to
      * display whereby one document can have several views.The MDI child is an instance of KScribbleView,
      * the document an instance of KScribbleDoc.
      * KScribbleApp reimplements the methods that KTMainWindow provides for main window handling and supports
      * full session management as well as keyboard accelerator configuration by using KAccel.
      * @see KTMainWindow
      * @see KApplication
      * @see KConfig
      * @see KAccel
      *
      * @author Source Framework Automatically Generated by KDevelop, (c) The KDevelop Team.
      * @version KDevelop version 1.1 code generation
      */
    class KScribbleApp : public KTMainWindow
    {
      Q_OBJECT

      public:
        /** construtor of KScribbleApp, calls all init functions to create the application.
         *  @see initMenuBar initToolBar
         */
        KScribbleApp();
        ~KScribbleApp();
        /** enables menuentries/toolbar items
         */
        void enableCommand(int id_);
        /** disables menuentries/toolbar items
         */
        void disableCommand(int id_);
        /** opens a file specified by commandline option
         */
        void openDocumentFile(const char *file=0);

      protected:
        /** queryClose is called by KTMainWindow on each closeEvent of a window. Against the
         *  default implementation (only returns true), this overridden function retrieves all modified documents
         *  from the open document list and asks the user to select which files to save before exiting the application.
         *  @see KTMainWindow#queryClose
         *  @see KTMainWindow#closeEvent
         */
        virtual bool queryClose();
        /** queryExit is called by KTMainWindow when the last window of the application is going to be closed
         *  during the closeEvent().
         *  Against the default implementation that just returns true, this calls saveOptions() to save the
         *  settings of the last window's properties.
         *  @see KTMainWindow#queryExit
         *  @see KTMainWindow#closeEvent
         */
        virtual bool queryExit();
        /** saves the window properties for each open window during session end to the session config file,
         *  including saving the currently opened file by a temporary filename provided by KApplication.
         *  @see KTMainWindow#saveProperties
         */
        virtual void saveProperties(KConfig *_cfg);
        /** reads the session config file and restores the application's state including the last
         *  opened files and documents by reading the temporary files saved by saveProperties()
         *  @see KTMainWindow#readProperties
         */
        virtual void readProperties(KConfig *_cfg);
        /** event filter to catch close events for MDI child windows and is installed in createClient() on every
          * child window. Closing a window calls the eventFilter first which removes the view from the connected
          * documents' view list. If the last view is going to be closed, the eventFilter() tests if the document
          * is modified; if yes, it asks the user to save the document. If the document title contains "Untitled",
          * slotFileSaveAs() gets called to get a save name and path.
          */
        virtual bool eventFilter(QObject* object, QEvent* event);
        /** creates a new child window. The document that will be connected to it
         *  has to be created before and the instances filled, with e.g. openDocument().
         *  Then call createClient() to get a new MDI child window.
         *  @see KScribbleDoc#addView
         *  @see KScribbleDoc#openDocument
         *  @param doc pointer to the document instance that the view will
         *  be connected to.
         */
        void createClient(KScribbleDoc* doc);
        /** accepts drag events for images */
        virtual void dragEnterEvent( QDragEnterEvent* );
        /** accepts drops and opens a new document
         *  for each drop */
        virtual void dropEvent( QDropEvent* );

      private slots:
        /** sets the main application window title each time the active MDI child window changes. */
        void setWndTitle(QWidget*);
        /** switch argument for slot selection by menu or toolbar ID */
        void commandCallback(int id_);
        /** switch argument for Statusbar help entries on slot selection. Add your ID's help
         *  here for toolbars and menubar entries. */
        void statusCallback(int id_);
        /** add a opened file to the recent file list and update recent file menu*/
        void addRecentFile(const QString &file);
        /** clears the document in the actual view to reuse it as the new document */
        void slotFileNew();
        /** open a file and load it into the document*/
        void slotFileOpen();
        /** opens a file from the recent files menu */
        void slotFileOpenRecent(int id_);
        /** save a document */
        void slotFileSave();
        /** save a document by a new filename*/
        void slotFileSaveAs();
        /** asks for saving if the file is modified, then closes the actual file and window*/
        void slotFileClose();
        /** print the actual file */
        void slotFilePrint();
        /** closes all documents and quits the application.*/
        void slotFileQuit();
        /** reverts the last user action for the active window */
        void slotEditUndo();
        /** put the marked text/object into the clipboard and remove
         *  it from the document
         */
        void slotEditCut();
        /** put the marked text/object into the clipboard
         */
        void slotEditCopy();
        /** paste the clipboard into the document
         */
        void slotEditPaste();
        /** clears the current document */
        void slotEditClearAll();
        /** sets the pen width */
        void slotPenBrush();
        /** sets the pen color */
        void slotPenColor();
        /** toggles the toolbar
         */
        void slotViewToolBar();
        /** toggles the statusbar
         */
        void slotViewStatusBar();
        /** creates a new view for the document in the active child window and adds the new view to the
         * list of views the document maintains.
         */
        void slotWindowNewWindow();
        /** changes the statusbar contents for the standard label permanently, used to indicate current actions.
         * @param text the text that is displayed in the statusbar
         */
        void slotStatusMsg(const QString &text);
        /** changes the status message of the whole statusbar for two seconds, then restores the last status.
         * This is used to display statusbar messages that give information about actions for
         * toolbar icons and menuentries.
         * @param text the text that is displayed in the statusbar
         */
        void slotStatusHelpMsg(const QString &text);
        /** gets called when the window menu is activated; recreates the window menu with all opened window titles. */
        void windowMenuAboutToShow();
        /** activates the MDI child widget when it gets selected from the window menu. */
        void windowMenuActivated( int id );

      private:
        /** save general Options like all bar positions and status as well as the geometry and
         * the recent file list to the configuration file
         */
        void saveOptions();
        /** read general Options again and initialize all variables like the recent file list
         */
        void readOptions();
        /** initKeyAccel creates the keyboard accelerator items for the available slots and changes the menu accelerators.
         * @see KAccel
         */
        void initKeyAccel();
        /** initMenuBar creates the menubar and inserts the menupopups as well as creating the helpMenu.
         */
        void initMenuBar();
        /** this creates the toolbars.
         */
        void initToolBar();
        /** sets up the statusbar for the main window by initialzing a statuslabel.
         */
        void initStatusBar();

        /** Creates the main view of the KTMainWindow instance and initializes the MDI view area including any needed
         *  connections.
         */
        void initView();

        /** contains the recently used filenames */
        QStrList recentFiles;

        /** the configuration object of the application */
        KConfig *config;
        /** the key accelerator container */
        KAccel *keyAccel;
        /** the recent file menu containing the last five opened files */
        QPopupMenu *pRecentFileMenu;
        /** the file menu */
        QPopupMenu* pFileMenu;
        /** the edit menu */
        QPopupMenu* pEditMenu;
        /** the pen menu */
        QPopupMenu* pPenMenu;
        /** the view menu */
        QPopupMenu* pViewMenu;
        /** the window menu */
        QPopupMenu *pWindowMenu;
        /** pWorkspace is the MDI frame widget that handles MDI child widgets. Inititalized in
         * initView()
         */
        QWorkspace *pWorkspace;
        /** the printer instance */
        QPrinter *printer;
        /** a counter that gets increased each time the user creates a new document with "File"->"New" */
        int untitledCount;
        /** a list of all open documents. If the last window of a document gets closed, the installed eventFilter
         * removes this document from the list. The document list is checked for modified documents when the user
         * is about to close the application. */
        QList<KScribbleDoc> *pDocList;

    };

    #endif // KSCRIBBLE_H

### kscribble.cpp

    /***************************************************************************
                              kscribble.cpp  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    // include files for QT
    #include <qdir.h>
    #include <qprinter.h>
    #include <qvbox.h>
    #include <qwhatsthis.h>
    #include <qtooltip.h>
    #include <qtoolbutton.h>
    #include <qimage.h>
    #include <qdragobject.h>

    // include files for KDE
    #include <kiconloader.h>
    #include <kmessagebox.h>
    #include <kfiledialog.h>
    #include <kcolordlg.h>
    #include <kmenubar.h>
    #include <klocale.h>
    #include <kconfig.h>

    // application specific includes
    #include "kscribble.h"
    #include "kscribbleview.h"
    #include "kscribbledoc.h"
    #include "resource.h"
    #include "kpenbrushdlg.h"

    KScribbleApp::KScribbleApp()
    {
      config=kapp->config();
      printer = new QPrinter;
      untitledCount=0;
      pDocList = new QList<KScribbleDoc>();
      pDocList->setAutoDelete(true);
      setAcceptDrops(true);

      ///////////////////////////////////////////////////////////////////
      // call inits to invoke all other construction parts
      initMenuBar();
      initToolBar();
      initStatusBar();
      initKeyAccel();
      initView();

      readOptions();

      ///////////////////////////////////////////////////////////////////
      // disable menu and toolbar items at startup
      disableCommand(ID_EDIT_UNDO);
    }

    KScribbleApp::~KScribbleApp()
    {
      delete printer;
    }

    void KScribbleApp::initKeyAccel()
    {
      keyAccel = new KAccel(this);

      // fileMenu accelerators
      keyAccel->connectItem(KStdAccel::New, this, SLOT(slotFileNew()));
      keyAccel->connectItem(KStdAccel::Open, this, SLOT(slotFileOpen()));
      keyAccel->connectItem(KStdAccel::Save, this, SLOT(slotFileSave()));
      keyAccel->connectItem(KStdAccel::Close, this, SLOT(slotFileClose()));
      keyAccel->connectItem(KStdAccel::Print, this, SLOT(slotFilePrint()));
      keyAccel->connectItem(KStdAccel::Quit, this, SLOT(slotFileQuit()));
      // editMenu accelerators
      keyAccel->connectItem(KStdAccel::Cut, this, SLOT(slotEditCut()));
      keyAccel->connectItem(KStdAccel::Copy, this, SLOT(slotEditCopy()));
      keyAccel->connectItem(KStdAccel::Paste, this, SLOT(slotEditPaste()));

      keyAccel->connectItem(KStdAccel::Help, this, SLOT(appHelpActivated()));

      keyAccel->changeMenuAccel(pFileMenu, ID_FILE_NEW, KStdAccel::New);
      keyAccel->changeMenuAccel(pFileMenu, ID_FILE_OPEN, KStdAccel::Open);
      keyAccel->changeMenuAccel(pFileMenu, ID_FILE_SAVE, KStdAccel::Save);
      keyAccel->changeMenuAccel(pFileMenu, ID_FILE_CLOSE, KStdAccel::Close);
      keyAccel->changeMenuAccel(pFileMenu, ID_FILE_PRINT, KStdAccel::Print);
      keyAccel->changeMenuAccel(pFileMenu, ID_FILE_QUIT, KStdAccel::Quit);

      keyAccel->changeMenuAccel(pEditMenu, ID_EDIT_CUT, KStdAccel::Cut);
      keyAccel->changeMenuAccel(pEditMenu, ID_EDIT_COPY, KStdAccel::Copy);
      keyAccel->changeMenuAccel(pEditMenu, ID_EDIT_PASTE, KStdAccel::Paste);

      keyAccel->readSettings();
    }

    void KScribbleApp::initMenuBar()
    {
      ///////////////////////////////////////////////////////////////////
      // MENUBAR
      pRecentFileMenu = new QPopupMenu(this);
      connect(pRecentFileMenu, SIGNAL(activated(int)), SLOT(slotFileOpenRecent(int)));

      ///////////////////////////////////////////////////////////////////
      // menuBar entry file-Menu
      pFileMenu = new QPopupMenu(this);
      pFileMenu->insertItem(BarIcon("filenew"), i18n("&New"), ID_FILE_NEW);
      pFileMenu->insertItem(BarIcon("fileopen"), i18n("&Open..."), ID_FILE_OPEN);
      pFileMenu->insertItem(i18n("Open &recent"), pRecentFileMenu, ID_FILE_OPEN_RECENT);

      pFileMenu->insertItem(i18n("&Close"), ID_FILE_CLOSE);
      pFileMenu->insertSeparator();
      pFileMenu->insertItem(BarIcon("filefloppy") ,i18n("&Save"), ID_FILE_SAVE);
      pFileMenu->insertItem(i18n("Save &As..."), ID_FILE_SAVE_AS);
      pFileMenu->insertSeparator();
      pFileMenu->insertItem(BarIcon("fileprint"), i18n("&Print..."), ID_FILE_PRINT);
      pFileMenu->insertSeparator();
      pFileMenu->insertItem(i18n("E&xit"), ID_FILE_QUIT);

      ///////////////////////////////////////////////////////////////////
      // menuBar entry edit-Menu
      pEditMenu = new QPopupMenu(this);
      pEditMenu->insertItem(BarIcon("undo"), i18n("&Undo"), ID_EDIT_UNDO);
      pEditMenu->insertSeparator();
      pEditMenu->insertItem(BarIcon("editcut"), i18n("Cu&t"), ID_EDIT_CUT);
      pEditMenu->insertItem(BarIcon("editcopy"), i18n("&Copy"), ID_EDIT_COPY);
      pEditMenu->insertItem(BarIcon("editpaste"), i18n("&Paste"), ID_EDIT_PASTE);
      pEditMenu->insertItem(BarIcon("delete"),i18n("&Clear All"), ID_EDIT_CLEAR_ALL);

      ///////////////////////////////////////////////////////////////////
      // menuBar entry pen-Menu
      pPenMenu = new QPopupMenu();
      pPenMenu->insertItem(i18n("&Color"), ID_PEN_COLOR);
      pPenMenu->insertItem(i18n("&Brush"), ID_PEN_BRUSH);

      ///////////////////////////////////////////////////////////////////
      // menuBar entry view-Menu
      pViewMenu = new QPopupMenu(this);
      pViewMenu->setCheckable(true);
      pViewMenu->insertItem(i18n("&Toolbar"), ID_VIEW_TOOLBAR);
      pViewMenu->insertItem(i18n("&Statusbar"), ID_VIEW_STATUSBAR);

      ///////////////////////////////////////////////////////////////////
      // menuBar entry window-Menu
      pWindowMenu = new QPopupMenu(this);
      pWindowMenu->setCheckable(true);

      ///////////////////////////////////////////////////////////////////
      // menuBar entry helpMenu
      QPopupMenu* pHelpMenu = helpMenu(i18n("KScribble" VERSION "\n\n(c) 2000 by\n"
                                      "Ralf Nolden\nRalf.Nolden@post.rwth-aachen.de"));

      ///////////////////////////////////////////////////////////////////
      // MENUBAR CONFIGURATION
      // insert your popup menus with the according menubar entries in the order
      // they will appear later from left to right
      menuBar()->insertItem(i18n("&File"), pFileMenu);
      menuBar()->insertItem(i18n("&Edit"), pEditMenu);
      menuBar()->insertItem(i18n("&Pen"), pPenMenu);
      menuBar()->insertItem(i18n("&View"), pViewMenu);
      menuBar()->insertItem(i18n("&Window"), pWindowMenu );
      menuBar()->insertItem(i18n("&Help"), pHelpMenu);

      ///////////////////////////////////////////////////////////////////
      // CONNECT THE MENU SLOTS WITH SIGNALS
      // for execution slots and statusbar messages
      connect(pFileMenu, SIGNAL(activated(int)), SLOT(commandCallback(int)));
      connect(pFileMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int)));

      connect(pEditMenu, SIGNAL(activated(int)), SLOT(commandCallback(int)));
      connect(pEditMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int)));

      connect(pPenMenu, SIGNAL(activated(int)), SLOT(commandCallback(int)));
      connect(pPenMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int)));

      connect(pViewMenu, SIGNAL(activated(int)), SLOT(commandCallback(int)));
      connect(pViewMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int)));

      connect(pWindowMenu, SIGNAL(aboutToShow() ), SLOT( windowMenuAboutToShow() ) );
      connect(pWindowMenu, SIGNAL(activated(int)), SLOT(commandCallback(int)));
      connect(pWindowMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int)));

    }

    void KScribbleApp::initToolBar()
    {

      ///////////////////////////////////////////////////////////////////
      // TOOLBAR
      toolBar()->insertButton(BarIcon("filenew"), ID_FILE_NEW, true, i18n("New File"));
      toolBar()->insertButton(BarIcon("fileopen"), ID_FILE_OPEN, true, i18n("Open File"));
      toolBar()->insertButton(BarIcon("filefloppy"), ID_FILE_SAVE, true, i18n("Save File"));
      toolBar()->insertButton(BarIcon("fileprint"), ID_FILE_PRINT, true, i18n("Print"));
      toolBar()->insertSeparator();
      toolBar()->insertButton(BarIcon("editcut"), ID_EDIT_CUT, true, i18n("Cut"));
      toolBar()->insertButton(BarIcon("editcopy"), ID_EDIT_COPY, true, i18n("Copy"));
      toolBar()->insertButton(BarIcon("editpaste"), ID_EDIT_PASTE, true, i18n("Paste"));
      toolBar()->insertSeparator();
      toolBar()->insertButton(BarIcon("pencolor"), ID_PEN_COLOR, true, i18n("Color") );
      toolBar()->insertButton(BarIcon("penwidth"), ID_PEN_BRUSH, true, i18n("Width") );
      toolBar()->insertSeparator();
      toolBar()->insertButton(BarIcon("help"), ID_HELP_CONTENTS, SIGNAL(clicked()),
              this, SLOT(appHelpActivated()), true,i18n("Help"));

      QToolButton *btnwhat = QWhatsThis::whatsThisButton(toolBar());
      QToolTip::add(btnwhat, i18n("What's this...?"));
      toolBar()->insertWidget(ID_HELP_WHATS_THIS, btnwhat->sizeHint().width(), btnwhat);

      ///////////////////////////////////////////////////////////////////
      // INSERT YOUR APPLICATION SPECIFIC TOOLBARS HERE WITH toolBar(n)

      ///////////////////////////////////////////////////////////////////
      // CONNECT THE TOOLBAR SLOTS WITH SIGNALS - add new created toolbars by their according number
      // connect for invoking the slot actions
      connect(toolBar(), SIGNAL(clicked(int)), SLOT(commandCallback(int)));
      // connect for the status help on holing icons pressed with the mouse button
      connect(toolBar(), SIGNAL(pressed(int)), SLOT(statusCallback(int)));

    }

    void KScribbleApp::initStatusBar()
    {
      ///////////////////////////////////////////////////////////////////
      // STATUSBAR
      // TODO: add your own items you need for displaying current application status.
      statusBar()->insertItem(i18n("Ready."), ID_STATUS_MSG);
    }

    void KScribbleApp::initView()
    {
      ////////////////////////////////////////////////////////////////////
      // here the main view of the KTMainWindow is created by a background box and
      // the QWorkspace instance for MDI view.
      QVBox* view_back = new QVBox( this );
      view_back->setFrameStyle( QFrame::StyledPanel | QFrame::Sunken );
      pWorkspace = new QWorkspace( view_back );
      connect(pWorkspace, SIGNAL(windowActivated(QWidget*)), this, SLOT(setWndTitle(QWidget*)));
      setView(view_back);
    }

    void KScribbleApp::setWndTitle(QWidget*){
      setCaption(pWorkspace->activeWindow()->caption());
    }

    void KScribbleApp::enableCommand(int id_)
    {
      ///////////////////////////////////////////////////////////////////
      // enable menu and toolbar functions by their ID's
      menuBar()->setItemEnabled(id_, true);
      toolBar()->setItemEnabled(id_, true);
    }

    void KScribbleApp::disableCommand(int id_)
    {
      ///////////////////////////////////////////////////////////////////
      // disable menu and toolbar functions by their ID's
      menuBar()->setItemEnabled(id_, false);
      toolBar()->setItemEnabled(id_, false);
    }

    void KScribbleApp::addRecentFile(const QString &file)
    {
      if(recentFiles.contains(file))
        return; // it's already there

      if( recentFiles.count() < 5)
        recentFiles.prepend(file);
      else{
        recentFiles.remove(recentFiles.last());
        recentFiles.prepend(file);
      }

      pRecentFileMenu->clear();

      for ( int i =0 ; i < (int)recentFiles.count(); i++){
        pRecentFileMenu->insertItem(recentFiles.at(i));
      }

    }

    void KScribbleApp::createClient(KScribbleDoc* doc)
    {
      KScribbleView* w = new KScribbleView(doc, pWorkspace,0,WDestructiveClose);
      w->installEventFilter(this);
      doc->addView(w);
      w->setIcon(kapp->miniIcon());
      if ( pWorkspace->windowList().isEmpty() ) // show the very first window in maximized mode
        w->showMaximized();
      else
        w->show();
    }

    void KScribbleApp::openDocumentFile(const char* file)
    {
      slotStatusMsg(i18n("Opening file..."));
      KScribbleDoc* doc;
      // check, if document already open. If yes, set the focus to the first view
      for(doc=pDocList->first(); doc > 0; doc=pDocList->next())
      {
        if(doc->pathName()==file)
        {
           KScribbleView* view=doc->firstView();
           view->setFocus();
           return;
         }
      }
      doc = new KScribbleDoc();
      pDocList->append(doc);
      doc->newDocument();
      // Creates an untitled window if file is 0
      if(!file)
      {
        untitledCount+=1;
        QString fileName=QString(i18n("Untitled%1")).arg(untitledCount);
        doc->setPathName(fileName);
        doc->setTitle(fileName);
      }
      // Open the file
      else
      {
        QString format=QImageIO::imageFormat(file);
        if(!doc->openDocument(file,format))
          KMessageBox::error (this,i18n("Could not open document !"), i18n("Error !"));
        addRecentFile(file);
      }
      // create the window
      createClient(doc);

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::saveOptions()
    {
      config->setGroup("General Options");
      config->writeEntry("Geometry", size());
      config->writeEntry("Show Toolbar", toolBar()->isVisible());
      config->writeEntry("Show Statusbar",statusBar()->isVisible());
      config->writeEntry("ToolBarPos", (int) toolBar()->barPos());
      config->writeEntry("Recent Files", recentFiles);
    }

    void KScribbleApp::readOptions()
    {

      config->setGroup("General Options");

      // bar status settings
      bool bViewToolbar = config->readBoolEntry("Show Toolbar", true);
      menuBar()->setItemChecked(ID_VIEW_TOOLBAR, bViewToolbar);
      if(!bViewToolbar)
      {
         enableToolBar(KToolBar::Hide);
      }

      bool bViewStatusbar = config->readBoolEntry("Show Statusbar", true);
      menuBar()->setItemChecked(ID_VIEW_STATUSBAR, bViewStatusbar);
      if(!bViewStatusbar)
      {
        enableStatusBar(KStatusBar::Hide);
      }

      // bar position settings
      KToolBar::BarPosition toolBarPos;
      toolBarPos=(KToolBar::BarPosition) config->readNumEntry("ToolBarPos", KToolBar::Top);
      toolBar()->setBarPos(toolBarPos);

      // initialize the recent file list
      config->readListEntry("Recent Files",recentFiles);

      for (int i=0; i < (int) recentFiles.count(); i++)
      {
        pRecentFileMenu->insertItem(recentFiles.at(i));
      }

      QSize size=config->readSizeEntry("Geometry");
      if(!size.isEmpty())
      {
        resize(size);
      }
      else
        resize(400,350);

    }

    void KScribbleApp::saveProperties(KConfig *_cfg)
    {

    }

    void KScribbleApp::readProperties(KConfig* _cfg)
    {
    }

    bool KScribbleApp::queryClose()
    {

      QStringList saveFiles;
      KScribbleDoc* doc;
      if(pDocList->isEmpty())
        return true;

      for(doc=pDocList->first(); doc!=0;doc=pDocList->next()){
        if(doc->isModified())
          saveFiles.append(doc->title());
      }
      if(saveFiles.isEmpty())
        return true;

      switch (KMessageBox::questionYesNoList(this,
          i18n("One or more documents have been modified.\nSave changes before exiting?"),saveFiles))
      {
        case KMessageBox::Yes:
          for(doc=pDocList->first(); doc!=0;doc=pDocList->next()){
            if(doc->title().contains(i18n("Untitled")))
              slotFileSaveAs();
            else
            {
              if(!doc->saveDocument(doc->pathName())){
                KMessageBox::error (this,i18n("Could not save the current document !"), i18n("I/O Error !"));
                return false;
              }
            }
           }
          return true;
        case KMessageBox::No:
        default:
          return true;
      }
    }

    bool KScribbleApp::queryExit()
    {
      saveOptions();
      return true;
    }

    bool KScribbleApp::eventFilter(QObject* object, QEvent* event){
      if(event->type() == QEvent::Close)
      {
        QCloseEvent* e=(QCloseEvent*)event;
        KScribbleView* pView=(KScribbleView*)object;
        KScribbleDoc* pDoc=pView->getDocument();
        if(pDoc->canCloseFrame(pView))
        {
           pDoc->removeView(pView);
           if(!pDoc->firstView())
             pDocList->remove(pDoc);

          e->accept();
          //////////////
          if(pWorkspace->windowList().count()==1)
            setPlainCaption(kapp->caption());
          else
            setCaption(pWorkspace->activeWindow()->caption());
          //////////////
        }
        else
          e->ignore();
      }
      return QWidget::eventFilter( object, event );    // standard event processing
    }

    /////////////////////////////////////////////////////////////////////
    // SLOT IMPLEMENTATION
    /////////////////////////////////////////////////////////////////////

    void KScribbleApp::slotFileNew()
    {
      slotStatusMsg(i18n("Creating new document..."));

      openDocumentFile();

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotFileOpen()
    {
      slotStatusMsg(i18n("Opening file..."));

      QString fileToOpen=KFileDialog::getOpenFileName(QDir::currentDirPath(),
                KImageIO::pattern(KImageIO::Reading), this, i18n("Open File..."));
      if(!fileToOpen.isEmpty())
      {
        openDocumentFile(fileToOpen);
      }

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotFileOpenRecent(int id_)
    {
      slotStatusMsg(i18n("Opening file..."));

      openDocumentFile(pRecentFileMenu->text(id_));

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotFileSave()
    {
      slotStatusMsg(i18n("Saving file..."));
      KScribbleView* m = (KScribbleView*)pWorkspace->activeWindow();
      if( m )
      {
        KScribbleDoc* doc =  m->getDocument();
        if(doc->title().contains(i18n("Untitled")))
         slotFileSaveAs();
        else
          if(!doc->saveDocument(doc->pathName()))
            KMessageBox::error (this,i18n("Could not save the current document !"), i18n("I/O Error !"));
      }

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotFileSaveAs()
    {
      slotStatusMsg(i18n("Saving file with a new filename..."));

      QString newName=KFileDialog::getSaveFileName(QDir::currentDirPath(),
                                   KImageIO::pattern(KImageIO::Writing), this, i18n("Save as..."));
      if(!newName.isEmpty())
      {
        KScribbleView* m = (KScribbleView*)pWorkspace->activeWindow();
        if( m )
        {
          KScribbleDoc* doc =  m->getDocument();
          QString format=QFileInfo(newName).extension();
          format=format.upper();
          if(!doc->saveDocument(newName,format))
          {
            KMessageBox::error (this,i18n("Could not save the current document !"), i18n("I/O Error !"));
            return;
          }
          doc->changedViewList();
          setWndTitle(m);
        }

      }

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotFileClose()
    {
      slotStatusMsg(i18n("Closing file..."));

      KScribbleView* m = (KScribbleView*)pWorkspace->activeWindow();
      if( m )
      {
        KScribbleDoc* doc=m->getDocument();
        doc->closeDocument();
      }

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotFilePrint()
    {
      slotStatusMsg(i18n("Printing..."));

      KScribbleView* m = (KScribbleView*) pWorkspace->activeWindow();
      if ( m )
        m->print( printer );

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotFileQuit()
    {
      slotStatusMsg(i18n("Exiting..."));
      saveOptions();
      // close the first window, the list makes the next one the first again.
      // This ensures that queryClose() is called on each window to ask for closing
      KTMainWindow* w;
      if(memberList)
      {
        for(w=memberList->first(); w!=0; w=memberList->first())
        {
          // only close the window if the closeEvent is accepted. If the user
          // presses Cancel on the saveModified() dialog,
          // the window and the application stay open.
          if(!w->close())
          break;
        }
      }
      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotEditUndo()
    {
      slotStatusMsg(i18n("Reverting last action..."));

      KScribbleView* m = (KScribbleView*) pWorkspace->activeWindow();
      if ( m )
    //    m->undo();

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotEditCut()
    {
      slotStatusMsg(i18n("Cutting selection..."));

      KScribbleView* m = (KScribbleView*) pWorkspace->activeWindow();
      if ( m )
        m->cutSelection();

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotEditCopy()
    {
      slotStatusMsg(i18n("Copying selection to clipboard..."));

      KScribbleView* m = (KScribbleView*) pWorkspace->activeWindow();
      if ( m )
        m->copySelection();

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotEditPaste()
    {
      slotStatusMsg(i18n("Inserting clipboard contents..."));

      KScribbleView* m = (KScribbleView*) pWorkspace->activeWindow();
      if ( m )
        m->pasteSelection();

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotEditClearAll()
    {
      slotStatusMsg(i18n("Clearing the document contents..."));

      KScribbleView* m = (KScribbleView*) pWorkspace->activeWindow();
      if ( m ){
        KScribbleDoc* pDoc = m->getDocument();
        pDoc->editClearAll();
      }
      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotPenBrush()
    {
      slotStatusMsg(i18n("Setting brush width..."));

      // get one window with document for a current pen width
      QWidgetList windows = pWorkspace->windowList();
      KScribbleView* m = (KScribbleView*)windows.at(0);
      KScribbleDoc* pDoc = m->getDocument();
      int curr_width=pDoc->penWidth();

      // create the dialog, get the new width and set the pen width for all documents
      KPenBrushDlg* dlg= new KPenBrushDlg(curr_width,this);
      if(dlg->exec()){
        int width=dlg->width();
        for ( int i = 0; i < int(windows.count()); ++i )
        {
          m = (KScribbleView*)windows.at(i);
          if ( m )
          {
            pDoc = m->getDocument();
            pDoc->setPenWidth(width);
          }
        }
      }
      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotPenColor()
    {
      slotStatusMsg(i18n("Selecting pen color..."));

      QColor myColor;
      int result = KColorDialog::getColor( myColor, this );
      if ( result == KColorDialog::Accepted )
      {
        QWidgetList windows = pWorkspace->windowList();
        KScribbleDoc* pDoc;
        KScribbleView* m;
        for ( int i = 0; i < int(windows.count()); ++i )
        {
          m = (KScribbleView*)windows.at(i);
          if ( m )
          {
            pDoc = m->getDocument();
            pDoc->setPenColor(myColor);
          }
        }
      }
      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotViewToolBar()
    {
      slotStatusMsg(i18n("Toggle the toolbar..."));
      ///////////////////////////////////////////////////////////////////
      // turn Toolbar on or off
      if( menuBar()->isItemChecked(ID_VIEW_TOOLBAR))
      {
        menuBar()->setItemChecked(ID_VIEW_TOOLBAR, false);
        enableToolBar(KToolBar::Hide);
      }
      else
      {
        menuBar()->setItemChecked(ID_VIEW_TOOLBAR, true);
        enableToolBar(KToolBar::Show);
      }

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotViewStatusBar()
    {
      slotStatusMsg(i18n("Toggle the statusbar..."));
      ///////////////////////////////////////////////////////////////////
      //turn Statusbar on or off
      if( menuBar()->isItemChecked(ID_VIEW_STATUSBAR))
      {
        menuBar()->setItemChecked(ID_VIEW_STATUSBAR, false);
        enableStatusBar(KStatusBar::Hide);
      }
      else
      {
        menuBar()->setItemChecked(ID_VIEW_STATUSBAR, true);
        enableStatusBar(KStatusBar::Show);
      }

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotWindowNewWindow()
    {
      slotStatusMsg(i18n("Opening a new application window..."));

      KScribbleView* m = (KScribbleView*) pWorkspace->activeWindow();
      if ( m ){
         KScribbleDoc* doc = m->getDocument();
        createClient(doc);
      }

      slotStatusMsg(i18n("Ready."));
    }

    void KScribbleApp::slotStatusMsg(const QString &text)
    {
      ///////////////////////////////////////////////////////////////////
      // change status message permanently
      statusBar()->clear();
      statusBar()->changeItem(text, ID_STATUS_MSG);
    }

    void KScribbleApp::slotStatusHelpMsg(const QString &text)
    {
      ///////////////////////////////////////////////////////////////////
      // change status message of whole statusbar temporary (text, msec)
      statusBar()->message(text, 2000);
    }

    void KScribbleApp::windowMenuAboutToShow()
    {
      pWindowMenu->clear();

      pWindowMenu->insertItem(i18n("&New Window"), ID_WINDOW_NEW_WINDOW);
      pWindowMenu->insertItem(i18n("&Cascade"),
                              pWorkspace, SLOT(cascade() ),0 , ID_WINDOW_CASCADE );
      pWindowMenu->insertItem(i18n("&Tile"),
                              pWorkspace, SLOT(tile() ),0 , ID_WINDOW_TILE );

      if ( pWorkspace->windowList().isEmpty() ) {
        disableCommand(ID_WINDOW_NEW_WINDOW);
        disableCommand(ID_WINDOW_CASCADE);
        disableCommand(ID_WINDOW_TILE);
      }

      pWindowMenu->insertSeparator();

      QWidgetList windows = pWorkspace->windowList();
      for ( int i = 0; i < int(windows.count()); ++i ) {
        int id = pWindowMenu->insertItem(QString("&%1 ").arg(i+1)+windows.at(i)->caption(),
                                         this, SLOT( windowMenuActivated( int ) ) );
        pWindowMenu->setItemParameter( id, i );
        pWindowMenu->setItemChecked( id, pWorkspace->activeWindow() == windows.at(i) );
      }
    }

    void KScribbleApp::windowMenuActivated( int id )
    {
      QWidget* w = pWorkspace->windowList().at( id );
      if ( w )
        w->setFocus();
    }

    void KScribbleApp::commandCallback(int id_)
    {
      switch (id_)
      {
        case ID_FILE_NEW:
           slotFileNew();
             break;

        case ID_FILE_OPEN:
             slotFileOpen();
             break;

        case ID_FILE_SAVE:
             slotFileSave();
             break;

        case ID_FILE_SAVE_AS:
             slotFileSaveAs();
             break;

        case ID_FILE_CLOSE:
             slotFileClose();
             break;

        case ID_FILE_PRINT:
             slotFilePrint();
             break;

        case ID_FILE_QUIT:
             slotFileQuit();
             break;

        case ID_EDIT_CUT:
             slotEditCut();
             break;

        case ID_EDIT_COPY:
             slotEditCopy();
             break;

        case ID_EDIT_PASTE:
             slotEditPaste();
             break;

        case ID_EDIT_CLEAR_ALL:
             slotEditClearAll();
             break;

        case ID_PEN_BRUSH:
             slotPenBrush();
             break;

        case ID_PEN_COLOR:
             slotPenColor();
             break;

        case ID_VIEW_TOOLBAR:
             slotViewToolBar();
             break;

        case ID_VIEW_STATUSBAR:
             slotViewStatusBar();
             break;

        case ID_WINDOW_NEW_WINDOW:
             slotWindowNewWindow();
           break;

        default:
             break;
      }
    }

    void KScribbleApp::statusCallback(int id_)
    {
      switch (id_)
      {
        case ID_FILE_NEW:
             slotStatusHelpMsg(i18n("Creates a new document"));
             break;

        case ID_FILE_OPEN:
             slotStatusHelpMsg(i18n("Opens an existing document"));
             break;

        case ID_FILE_OPEN_RECENT:
             slotStatusHelpMsg(i18n("Opens a recently used file"));
             break;

        case ID_FILE_SAVE:
             slotStatusHelpMsg(i18n("Saves the currently active document"));
             break;

        case ID_FILE_SAVE_AS:
             slotStatusHelpMsg(i18n("Saves the currently active document as under a new filename"));
             break;

        case ID_FILE_CLOSE:
             slotStatusHelpMsg(i18n("Closes the currently active document"));
             break;

        case ID_FILE_PRINT:
             slotStatusHelpMsg(i18n("Prints out the actual document"));
             break;

        case ID_FILE_QUIT:
             slotStatusHelpMsg(i18n("Quits the application"));
             break;

        case ID_EDIT_UNDO:
             slotStatusHelpMsg(i18n("Reverts the last editing action"));
             break;

        case ID_EDIT_CUT:
             slotStatusHelpMsg(i18n("Cuts the selected section and puts it to the clipboard"));
             break;

        case ID_EDIT_COPY:
             slotStatusHelpMsg(i18n("Copies the selected section to the clipboard"));
             break;

        case ID_EDIT_PASTE:
             slotStatusHelpMsg(i18n("Pastes the clipboard contents to actual position"));
             break;

        case ID_EDIT_CLEAR_ALL:
             slotStatusHelpMsg(i18n("Clears the document contents"));
             break;

        case ID_PEN_BRUSH:
             slotStatusHelpMsg(i18n("Sets the pen width"));
             break;

        case ID_PEN_COLOR:
             slotStatusHelpMsg(i18n("Sets the current pen color"));
             break;

        case ID_VIEW_TOOLBAR:
             slotStatusHelpMsg(i18n("Enables/disables the toolbar"));
             break;

        case ID_VIEW_STATUSBAR:
             slotStatusHelpMsg(i18n("Enables/disables the statusbar"));
             break;

        case ID_WINDOW_NEW_WINDOW:
             slotStatusHelpMsg(i18n("Opens a new view for the current document"));
             break;

        case ID_WINDOW_CASCADE:
             slotStatusHelpMsg(i18n("Cascades all windows"));
             break;

        case ID_WINDOW_TILE:
             slotStatusHelpMsg(i18n("Tiles all windows"));
             break;

        default:
             break;
      }
    }
    /** accepts drops and opens a new document
    for each drop */
    void KScribbleApp::dropEvent( QDropEvent* e){

      QImage img;
      if ( QImageDrag::decode(e, img) )
      {
        KScribbleDoc* doc = new KScribbleDoc();
        untitledCount+=1;
        QString fileName=QString(i18n("Untitled%1")).arg(untitledCount);
        doc->setPathName(fileName);
        doc->setTitle(fileName);
        doc->newDocument();
        pDocList->append(doc);
        KPixmap tmp;
        tmp.resize(img.size());
        tmp.convertFromImage(img);
        doc->setPixmap(tmp);
        doc->resizeDocument(tmp.size());
        doc->setModified();
        createClient(doc);
      }
    }
    /** accepts drag events for images */
    void KScribbleApp::dragEnterEvent( QDragEnterEvent* e){
      e->accept(QImageDrag::canDecode(e));
    }

### kscribbledoc.h

    /***************************************************************************
                              kscribbledoc.h  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    #ifndef KSCRIBBLEDOC_H
    #define KSCRIBBLEDOC_H

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif

    // include files for QT
    #include <qobject.h>
    #include <qstring.h>
    #include <qlist.h>

    #include <qsize.h>
    #include <qpen.h>
    #include <qpoint.h>
    //#include <qpixmap.h>
    #include <qpointarray.h>

    #include <kpixmap.h>

    // forward declaration of the KScribble classes
    class KScribbleView;

    /**  KScribbleDoc provides a document object for a document-view model.
      *
      * The KScribbleDoc class provides a document object that can be used in conjunction with the classes
      * KScribbleApp and KScribbleView to create a document-view model for MDI (Multiple Document Interface)
      * KDE 2 applications based on KApplication and KTMainWindow as main classes and QWorkspace as MDI manager widget.
      * Thereby, the document object is created by the KScribbleApp instance (and kept in a document list) and contains
      * the document structure with the according methods for manipulating the document
      * data by KScribbleView objects. Also, KScribbleDoc contains the methods for serialization of the document data
      * from and to files.
      * @author Source Framework Automatically Generated by KDevelop, (c) The KDevelop Team.
      * @version KDevelop version 1.1 code generation
      */
    class KScribbleDoc : public QObject
    {
      Q_OBJECT

      friend KScribbleView;

      public:
        /** Constructor for the fileclass of the application */
        KScribbleDoc();
        /** Destructor for the fileclass of the application */
        ~KScribbleDoc();

        /** adds a view to the document which represents the document contents. Usually this is your main view. */
        void addView(KScribbleView *view);
        /** removes a view from the list of currently connected views */
        void removeView(KScribbleView *view);
        /** gets called if a view is removed or added */
        void changedViewList();
        /** returns the first view instance */
        KScribbleView* firstView(){ return pViewList->first(); };
        /** returns true, if the requested view is the last view of the document */
        bool isLastView();
        /** This method gets called when the user is about to close a frame window. It checks, if more than one view
         * is connected to the document (then the frame can be closed), if pFrame is the last view and the document is
         * modified, the user gets asked if he wants to save the document.
         */
        bool canCloseFrame(KScribbleView* pFrame);
        /** sets the modified flag for the document after a modifying action on the view connected to the document.*/
        void setModified(bool _m=true){ modified=_m; };
        /** returns if the document is modified or not. Use this to determine if your document needs
         * saving by the user on closing.
         */
        bool isModified(){ return modified; };
        /** deletes the document's contents */
        void deleteContents();
        /** initializes the document generally */
        bool newDocument();
        /** closes the acutal document */
        void closeDocument();
        /** loads the document by filename and format and emits the updateViews() signal */
        bool openDocument(const QString &filename, const char *format=0);
        /** saves the document under filename and format.*/
        bool saveDocument(const QString &filename, const char *format=0);
        /** sets the path to the file connected with the document */
        void setPathName(const QString &name);
        /** returns the pathname of the current document file*/
        const QString& pathName() const;

        /** sets the filename of the document */
        void setTitle(const QString &title);
        /** returns the title of the document */
        const QString& title() const;
        /** get the current Pen */
        const QPen currentPen(){ return pen;};
        /** returns the pen width */
        const int penWidth() { return pen.width(); }
        /** returns the pen color */
        const QColor penColor(){ return pen.color(); }
        /** sets the pen width */
        void setPenWidth( int w ){ pen.setWidth( w ); }
        /** sets the pen color */
        void setPenColor( const QColor &c ){ pen.setColor( c ); }
        /** sets the pen style by a second toolbar */
        void setPenStyle( PenStyle s){ pen.setStyle(s);}
        /** clears the document contents */
        void editClearAll();

        /** get the document size */
        const QSize docSize(){ return size;};
        /** sets the pixmap contents. Used by KScribbleApp
        to create a new document by drop events */
        void setPixmap(KPixmap pix) { buffer=pix;};
        void resizeDocument(QSize m_size) { size=m_size; };
      public slots:
        /** calls repaint() on all views connected to the document object and is called by the view by
         * which the document has been changed.
         * As this view normally repaints itself, it is excluded from the paintEvent.
         */
        void updateAllViews(KScribbleView *sender);

      protected:

        QPen pen;
        QPointArray polyline;
        KPixmap buffer;

      private:
        /** the modified flag of the current document */
        bool modified;
        QString m_title;
        QString m_filename;
        /** the list of the views currently connected to the document */
        QList<KScribbleView> *pViewList;
        QSize size;
    };

    #endif // KSCRIBBLEDOC_H

### kscribbledoc.cpp

    /***************************************************************************
                              kscribbledoc.cpp  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    // include files for Qt
    #include <qdir.h>
    #include <qfileinfo.h>
    #include <qwidget.h>

    // include files for KDE
    #include <klocale.h>
    #include <kmessagebox.h>
    #include <kfiledialog.h>

    // application specific includes
    #include "kscribbledoc.h"
    #include "kscribble.h"
    #include "kscribbleview.h"

    KScribbleDoc::KScribbleDoc()
    {
      pViewList = new QList<KScribbleView>;
      pViewList->setAutoDelete(false);
    }

    KScribbleDoc::~KScribbleDoc()
    {
      delete pViewList;
    }

    void KScribbleDoc::addView(KScribbleView *view)
    {
      pViewList->append(view);
      changedViewList();
    }

    void KScribbleDoc::removeView(KScribbleView *view)
    {
        pViewList->remove(view);
        if(!pViewList->isEmpty())
          changedViewList();
        else
          deleteContents();
    }

    void KScribbleDoc::changedViewList(){

      KScribbleView *w;
      if((int)pViewList->count() == 1){
        w=pViewList->first();
        w->setCaption(m_title);
      }
      else{
        int i;
        for( i=1,w=pViewList->first(); w!=0; i++, w=pViewList->next())
          w->setCaption(QString(m_title+":%1").arg(i));
      }
    }

    bool KScribbleDoc::isLastView() {
      return ((int) pViewList->count() == 1);
    }

    void KScribbleDoc::updateAllViews(KScribbleView *sender)
    {
      KScribbleView *w;
      for(w=pViewList->first(); w!=0; w=pViewList->next())
      {
          w->update(sender);
      }

    }

    void KScribbleDoc::setPathName(const QString &name)
    {
      m_filename=name;
      m_title=QFileInfo(name).fileName();
    }

    const QString& KScribbleDoc::pathName() const
    {
      return m_filename;
    }

    void KScribbleDoc::setTitle(const QString &title)
    {
      m_title=title;
    }

    const QString &KScribbleDoc::title() const
    {
      return m_title;
    }

    void KScribbleDoc::closeDocument()
    {
      KScribbleView *w;
      if(!isLastView())
      {
        for(w=pViewList->first(); w!=0; w=pViewList->next())
        {
            if(!w->close())
             break;
        }
      }
      if(isLastView())
      {
        w=pViewList->first();
        w->close();
      }
    }

    bool KScribbleDoc::newDocument()
    {
      /////////////////////////////////////////////////
      // TODO: Add your document initialization code here
      size=QSize(300,200 );
      pen=QPen( Qt::black, 3 );
      polyline=QPointArray(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;
    }

    bool KScribbleDoc::saveDocument(const QString &filename, const char *format /*=0*/)
    {
      QFile f( filename );
    //  if ( !f.open( IO_WriteOnly ) )
    //    return false;

      /////////////////////////////////////////////////
      // TODO: Add your document saving code here
      if(!buffer.save( filename, format ))
        return false;
      /////////////////////////////////////////////////

    //  f.close();

      modified=false;
      m_filename=filename;
      m_title=QFileInfo(f).fileName();
      return true;
    }

    void KScribbleDoc::deleteContents()
    {
      /////////////////////////////////////////////////
      // TODO: Add implementation to delete the document contents
      buffer.fill( Qt::white );
      /////////////////////////////////////////////////

    }

    bool KScribbleDoc::canCloseFrame(KScribbleView* pFrame)
    {
      if(!isLastView())
        return true;

      bool ret=false;
      if(isModified())
      {
        QString saveName;
        switch(KMessageBox::warningYesNoCancel(pFrame, i18n("The current file has been modified.\n"
        "Do you want to save it?"),title()))
        {
        case KMessageBox::Yes:
          if(title().contains(i18n("Untitled")))
          {
            saveName=KFileDialog::getSaveFileName(QDir::currentDirPath(),
                                 i18n("*|All files"), pFrame, i18n("Save as..."));
            if(saveName.isEmpty())
            return false;
          }
          else
           saveName=pathName();

          if(!saveDocument(saveName))
          {
            switch(KMessageBox::warningYesNo(pFrame,i18n("Could not save the current document !\n"
                              "Close anyway ?"), i18n("I/O Error !")))
            {
              case KMessageBox::Yes:
                ret=true;
              case KMessageBox::No:
                ret=false;
            }
          }
          else
            ret=true;
            break;
        case KMessageBox::No:
          ret=true;
          break;
        case KMessageBox::Cancel:
        default:
          ret=false;
          break;
        }
      }
      else
        ret=true;

      return ret;
    }

    void KScribbleDoc::editClearAll()
    {
      deleteContents();
      setModified();
      updateAllViews(0);
    }

### kscribbleview.h

    /***************************************************************************
                              kscribbleview.h  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    #ifndef KSCRIBBLEVIEW_H
    #define KSCRIBBLEVIEW_H

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif

    // include files for Qt
    #include <qscrollview.h>
    #include <kpixmap.h>

    class KScribbleDoc;

    /** The KScribbleView class provides the view widget for the document instance connected to it and is displayed
     * as a MDI child window in the main view area of the KScribbleApp class instance. The KScribbleApp
     * class also has an eventFilter()  method that gets installed on every KScribbleView instance to
     * control events of the type QEvent::Close.The document connected to the view instance keeps a list
     * of all view that represent the document contents as there can be more than one view. Views get created in
     * KScribbleApp::createClient() and automatically added to the list of views.
     * The KScribbleView class inherits QWidget as a base. Another possible inheritance besides specialized
     + widgets could be QMainWindow so that you can easily set up the main area of your view by setting another view
     * as main widget (QMainWindow::setMainWidget() ).
     * NOTE: The close event always has to be empty (DON`T CALL QWidget::closeEvent(e) in closeEvent())
     * because the installed event filter can only manage a forward implementation. If the QCloseEvent
     * is received by the KScribbleView, the overwritten event handler has to do nothing as the eventFilter
     * has set accept() or ignore() already. If QWidget::closeEvent() is called again, the default event
     * handler will accept the close event and the window gets destroyed even if the installed eventFilter
     * has set the event to be ignored.
     * @author Source Framework Automatically Generated by KDevelop, (c) The KDevelop Team.
     * @version KDevelop version 1.1 code generation
     */
    class KScribbleView : public QScrollView
    {
      Q_OBJECT

      friend KScribbleDoc;

      public:
        /** Constructor for the view
          * @param pDoc  your document instance that the view represents. Create a document
          * before calling the constructor or connect an already existing document to a new MDI child widget.*/
        KScribbleView(KScribbleDoc* pDoc, QWidget* parent, const char *name, int wflags);
        /** Destructor for the main view */
        ~KScribbleView();
        /** returns a pointer to the document connected to the view*/
        KScribbleDoc *getDocument() const;
        /** gets called to redraw the document contents if it has been modified */
        void update(KScribbleView* pSender);
        /** contains the implementation for printing functionality and gets called by KScribbleApp::slotFilePrint() */
        void print(QPrinter *pPrinter);
        /** cuts out a selection */
        void cutSelection();
        /** copies a selection to the clipboard */
        void copySelection();
        /** pastes the clipboard contents to a selection that can be inserted into the picture */
        void pasteSelection();

      protected:

        /** overwritten QWidget::closeEvent() to catch closing views. Does nothing, as the closeEvents for
        * KScribbleView's are processed by KScribbleApp::eventFilter(), so this overwitten closeEvent is necessary
        * and has to be empty. Don't overwrite this method !
        */
        virtual void closeEvent(QCloseEvent* );
        /** overwritten to interpret key events for scrollbars */
        virtual void keyPressEvent( QKeyEvent* );
        /** changed from mousePressEvent() overwriting QScrollView method */
        virtual void viewportMousePressEvent( QMouseEvent* );
        /** changed from mouseReleaseEvent() overwriting QScrollView method */
        virtual void viewportMouseReleaseEvent( QMouseEvent* );
        /** On paste actions inserts the pasted clipboard contents */
        virtual void viewportMouseDoubleClickEvent(QMouseEvent* e);
        /** changed from mouseMoveEvent() overwriting QScrollView method */
        virtual void viewportMouseMoveEvent( QMouseEvent* );
        /** changed from resizeEvent() overwriting QScrollView method */
    //    virtual void viewportResizeEvent( QResizeEvent* );
        /** changed from paintEvent() overwriting QScrollView method */
        virtual void viewportPaintEvent( QPaintEvent* );

        virtual void viewportDragEnterEvent ( QDragEnterEvent * );

        virtual void viewportDragMoveEvent ( QDragMoveEvent * );

        virtual void viewportDragLeaveEvent ( QDragLeaveEvent * );

        virtual void viewportDropEvent ( QDropEvent * );

        /** the document instance */
        KScribbleDoc *doc;

      private:
        KPixmap tmp;
        QRect select;
        QClipboard *cb;
        enum Action{IDLE=0, DRAW, SELECT, PASTE, DRAG} action;
    };

    #endif // KSCRIBBLEVIEW_H

### kscribbleview.cpp

    /***************************************************************************
                              kscribbleview.cpp  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/
    #include <iostream.h>

    // include files for Qt
    #include <qprinter.h>
    #include <qpainter.h>
    #include <qdir.h>
    #include <qsize.h>
    #include <qclipboard.h>
    #include <qimage.h>
    #include <qdragobject.h>

    // include files for KDE
    #include <kiconloader.h>

    // application specific includes
    #include "kscribbleview.h"
    #include "kscribbledoc.h"
    #include "kscribble.h"

    KScribbleView::KScribbleView(KScribbleDoc* pDoc, QWidget *parent, const char* name, int wflags)
     : QScrollView(parent, name, wflags | WPaintClever | WNorthWestGravity | WRepaintNoErase)
    {
      cb = QApplication::clipboard();
      viewport()->setAcceptDrops(true);
      setDragAutoScroll(true);
      doc=pDoc;
      action=IDLE;
      viewport()->setCursor( Qt::crossCursor );
      QSize size=doc->docSize();
      resizeContents(size.width(), size.height());
      resize(size);
    }

    KScribbleView::~KScribbleView()
    {
    }

    KScribbleDoc *KScribbleView::getDocument() const
    {
      return doc;
    }

    void KScribbleView::update(KScribbleView* pSender){
      if(pSender != this)
        viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
    }

    void KScribbleView::print(QPrinter *pPrinter)
    {
      if (pPrinter->setup(this))
      {
        QPainter p;
        p.begin(pPrinter);

        ///////////////////////////////
        // TODO: add your printing code here
        p.drawPixmap(0,0,doc->buffer);
        ///////////////////////////////
        p.end();
      }
    }

    /** cuts out a selection */
    void KScribbleView::cutSelection(){
      select=select.normalize();
      QPixmap cb_pix;
      cb_pix.resize(select.size());
      // copy selection to cb_pix and copy to clipboard
      bitBlt(&cb_pix, 0, 0,
             &doc->buffer, select.x()+contentsX(),  select.y()+contentsY(), cb_pix.width(), cb_pix.height());
      cb->setPixmap(cb_pix);
      // fill cb_pix with white and copy to selection area
      cb_pix.fill(Qt::white);
      bitBlt(&doc->buffer, select.x()+contentsX(), select.y()+contentsY(),
             &cb_pix, 0, 0, cb_pix.width(), cb_pix.height());
      action = IDLE;
      doc->setModified();
      doc->updateAllViews(this);
      viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
    }
    /** copies a selection to the clipboard */
    void KScribbleView::copySelection(){
      select=select.normalize();
      QPixmap cb_pix;
      cb_pix.resize(select.size());
      // copy selection to cb_pix and copy to clipboard
      bitBlt(&cb_pix, 0, 0,
             &doc->buffer, select.x()+contentsX(),  select.y()+contentsY(),cb_pix.width(), cb_pix.height());
      cb->setPixmap(cb_pix);
      action = IDLE;
      viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
    }
    /** pastes the clipboard contents to a selection that can be inserted into the picture */
    void KScribbleView::pasteSelection(){
      select=cb->pixmap().rect();
      action = PASTE;
      viewport()->setCursor( Qt::sizeAllCursor );
    }

    void KScribbleView::closeEvent(QCloseEvent* e){

    // DO NOT CALL QWidget::closeEvent(e) here !!
    // This will accept the closing by QCloseEvent::accept() by default.
    // The installed eventFilter() in KScribbleApp takes care for closing the widget
    // or ignoring the close event

    }

    void KScribbleView::keyPressEvent( QKeyEvent *e )
    {
      switch (e->key())
      {
        case Key_Right:
          scrollBy( 10, 0 );
          break;
        case Key_Left:
          scrollBy( -10,0);
          break;
        case Key_Up:
          scrollBy( 0, -10 );
          break;
        case Key_Down:
          scrollBy( 0, 10 );
          break;
        case Key_Home:
          setContentsPos(0,0);
          break;
        case Key_End:
          setContentsPos(0,viewport()->height()-viewport()->height());
          break;
        case Key_PageUp:
          scrollBy( 0, -viewport()->height() );
          break;
        case Key_PageDown:
          scrollBy( 0, viewport()->height() );
          break;
      }

    }

    void KScribbleView::viewportMousePressEvent( QMouseEvent *e )
    {
      if ( e->button() == LeftButton && action == IDLE)
      {
        action=DRAW;
        doc->polyline[2] = doc->polyline[1] = doc->polyline[0] = viewportToContents(e->pos());
        doc->updateAllViews(this);
      }
      else if ( e->button() == RightButton && action == IDLE)
      {
        action = SELECT;
        QPoint pt=e->pos();
        int x = pt.x() > contentsWidth() ? contentsWidth() : pt.x();
        int y = pt.y() > contentsHeight() ? contentsHeight() : pt.y();
        select.setLeft(x-1);
        select.setTop(y-1);
        select.setRight(x-1);
        select.setBottom(y-1);
      }
      else if( action == SELECT )
      {
        action = IDLE;
        select=select.normalize();
        // drag
        if(select.contains(e->pos(), true)) // point inside the selection
        {
          tmp.resize(select.size());
          bitBlt(&tmp, 0, 0,
                 &doc->buffer, select.x()+contentsX(),  select.y()+contentsY(), tmp.width(), tmp.height());
          QImage img =tmp.convertToImage();
          QDragObject *d = new QImageDrag( img, viewport() );
          d->setPixmap(BarIcon("filenew"));
          d->drag();
        }
        // remove selection
        else
          viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
      }
      else if( action == PASTE )
      {
        if ( e->button() == RightButton )
        {
          action = IDLE;
          viewport()->setCursor( Qt::crossCursor );
        }
        QPoint mv_pt (viewport()->height(), viewport()->width());
        if(QRect(0,0,mv_pt.x(),mv_pt.y()).contains(e->pos()))
          select.moveCenter(e->pos());
        else
        {
          select.moveBottomRight(mv_pt);
        }
        viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
      }
    }

    void KScribbleView::viewportMouseReleaseEvent( QMouseEvent *e )
    {
      if ( action == DRAW )
      {
        action = IDLE;
        doc->updateAllViews(this);
      }
      if ( action == SELECT)
      {
        QPoint pt=e->pos();
        int x = pt.x() > 0 ? pt.x() : 0;
        int y = pt.y() > 0 ? pt.y() : 0;
        select.setRight(x);
        select.setBottom(y);
        QSize size=doc->docSize();
        select = select.intersect(QRect(0,0,size.width(), size.height()));
      }
    }

    /** On paste actions inserts the pasted clipboard contents
     */
    void KScribbleView::viewportMouseDoubleClickEvent(QMouseEvent* e)
    {
      if( action == PASTE )
      {
        action = IDLE;
        select.moveCenter(e->pos());
        viewport()->setCursor( Qt::crossCursor );
        QPixmap cb_pix;
        cb_pix.resize(cb->pixmap().size());
        cb_pix=cb->pixmap();
        bitBlt( &doc->buffer, contentsX()+select.x(), contentsY()+select.y(),
                &cb_pix, 0,0 , select.width(),select.height() );
        viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
        doc->setModified();
        doc->updateAllViews(this);
      }

    }

    void KScribbleView::viewportMouseMoveEvent( QMouseEvent *e )
    {
      if ( action == DRAW )
      {
        QPainter painter;
        painter.begin( &doc->buffer );
        painter.setPen( doc->currentPen() );
        doc->polyline[2] = doc->polyline[1];
        doc->polyline[1] = doc->polyline[0];
        doc->polyline[0] = viewportToContents(e->pos());
        painter.drawPolyline( doc->polyline );
        painter.end();

        QRect r = doc->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() );

        bitBlt(viewport(), r.x()-contentsX(), r.y()-contentsY() ,
               &doc->buffer, r.x(), r.y(), r.width(), r.height() );
        doc->setModified();
        doc->updateAllViews(this);
      }
      if ( action == SELECT )
      {
        QPoint pt=e->pos();
        select.setWidth(select.x()+pt.x());
        select.setHeight(select.y()+pt.y());
        select.setRight(pt.x());
        select.setBottom(pt.y());
        QSize size=doc->docSize();
        select = select.intersect(QRect(0,0,size.width(), size.height()));
        viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
      }
      if( action == PASTE )
      {
        QPoint mv_pt (viewport()->height(), viewport()->width());
        if(QRect(0,0,mv_pt.x(),mv_pt.y()).contains(e->pos()))
          select.moveCenter(e->pos());
        else
        {
          select.moveBottomRight(mv_pt);
        }
        QRect pm_rect=cb->pixmap().rect();
        select.setWidth(pm_rect.width());
        select.setHeight(pm_rect.height());
        QSize size=doc->docSize();
        select = select.intersect(QRect(0,0,size.width(), size.height()));
        viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
        doc->setModified();
        doc->updateAllViews(this);
      }
    }

    //void KScribbleView::viewportResizeEvent( QResizeEvent *e )
    //{
    //}

    void KScribbleView::viewportPaintEvent( QPaintEvent *e )
    {
      bitBlt( viewport(),0,0, &doc->buffer,contentsX() ,contentsY() );

      if( action == PASTE )
      {
        tmp.resize(cb->pixmap().size());
        tmp=cb->pixmap();
      }
      if( action == PASTE || action == DRAG )
      {
        QSize size=doc->docSize();
        select = select.intersect(QRect(0,0,size.width(), size.height()));
        if(select.intersects(e->rect()))
          bitBlt(viewport(), select.x(), select.y(), &tmp, 0, 0, select.width(), select.height());
      }
      if( action == PASTE || action == DRAG || action == SELECT )
      {
    //    if(select.intersects(e->rect()))
    //    {
          QPainter paint_area;
          paint_area.begin(viewport());
          paint_area.setPen(QPen(Qt::black, 0, DashLine));
          paint_area.drawRect( select );
          paint_area.end();
    //    }
      }
      QScrollView::viewportPaintEvent(e);
    }

    void  KScribbleView::viewportDragEnterEvent ( QDragEnterEvent * e)
    {
      e->accept(QImageDrag::canDecode(e));
      action = DRAG;
    }

    void  KScribbleView::viewportDragMoveEvent ( QDragMoveEvent * e)
    {
      QImage img;
      if ( QImageDrag::decode(e, img) ){
        tmp.resize(img.size());
        tmp.convertFromImage(img);
        select.setWidth(tmp.width());
        select.setHeight(tmp.height());
        select.moveCenter(e->pos());
        viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
      }
    }

    void  KScribbleView::viewportDragLeaveEvent ( QDragLeaveEvent * )
    {
      action = IDLE;
      viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
    }

    void  KScribbleView::viewportDropEvent ( QDropEvent * e)
    {
      QImage img;
      if ( QImageDrag::decode(e, img) )
      {
        tmp.resize(img.size());
        tmp.convertFromImage(img);
        select.setWidth(tmp.width());
        select.setHeight(tmp.height());
        select.moveCenter(e->pos());
        bitBlt(&doc->buffer, select.x()+contentsX(), select.y()+contentsY(),
               &tmp, 0, 0, tmp.width(), tmp.height());
        doc->setModified();
        doc->updateAllViews(this);
      }
      action = IDLE;
        viewport()->repaint(0,0,visibleWidth(), visibleHeight(), false);
    }

### kpenbrushdlg.h

    /***************************************************************************
                              kpenbrushdlg.h  -  description
                                 -------------------
        begin                : Fri Jul 23 1999
        copyright            : (C) 1999 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    #ifndef KPENBRUSHDLG_H
    #define KPENBRUSHDLG_H

    //Generated area. DO NOT EDIT!!!(begin)
    #include <qwidget.h>
    #include <qspinbox.h>
    #include <qlabel.h>
    #include <qpushbutton.h>
    //Generated area. DO NOT EDIT!!!(end)

    #include <qdialog.h>
    #include <klocale.h>

    /**
      *@author Ralf Nolden
      */

    class KPenBrushDlg : public QDialog  {
       Q_OBJECT
    public:
      KPenBrushDlg(int curr, QWidget *parent=0, const char *name=0);
      ~KPenBrushDlg();

      int width() { return width_spbox->value(); };

    protected slots:
      void slotDefault();

    protected:
      void initDialog();
      //Generated area. DO NOT EDIT!!!(begin)
      QSpinBox *width_spbox;
      QLabel *width_label;
      QPushButton *default_btn;
      QPushButton *ok_btn;
      QPushButton *cancel_btn;
      //Generated area. DO NOT EDIT!!!(end)

    private:
    };

    #endif

### kpenbrushdlg.cpp

    /***************************************************************************
                              kpenbrushdlg.cpp  -  description
                                 -------------------
        begin                : Fri Jul 23 1999
        copyright            : (C) 1999 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    #include "kpenbrushdlg.h"
    #include <qwhatsthis.h>
    #include <kapp.h>

    KPenBrushDlg::KPenBrushDlg(int curr, QWidget *parent, const char *name)
      : QDialog(parent,name,true,WStyle_ContextHelp)
    {
      initDialog();
      QWhatsThis::add(width_spbox,i18n("Select brush width"));

      width_spbox->setValue(curr);
      connect(default_btn, SIGNAL(clicked()), this, SLOT(slotDefault()));
      connect(ok_btn, SIGNAL(clicked()), this, SLOT(accept()));
      connect(cancel_btn, SIGNAL(clicked()), this, SLOT(reject()));
    }

    KPenBrushDlg::~KPenBrushDlg(){
    }

    void KPenBrushDlg::slotDefault(){
      width_spbox->setValue(3);
    }

### kpenbrushdlgdata.cpp

    /**********************************************************************
                --- KDevelop (KDlgEdit)  generated file ---

                Last generated: Fri Jul 23 10:43:10 1999

                DO NOT EDIT!!!  This file will be automatically
                regenerated by KDevelop.  All changes will be lost.

    **********************************************************************/
    #include <kapp.h>
    #include "kpenbrushdlg.h"

    void  KPenBrushDlg::initDialog(){
      this->resize(370,210);
      this->setMinimumSize(0,0);
      width_spbox= new QSpinBox(this,"width_spbox");
      width_spbox->setGeometry(150,50,100,25);
      width_spbox->setMinimumSize(0,0);
      width_spbox->setValue(1);
      width_spbox->setRange(1,99);

      width_label= new QLabel(this,"width_label");
      width_label->setGeometry(20,50,120,25);
      width_label->setMinimumSize(0,0);
      width_label->setText(i18n("Pen Width:"));

      default_btn= new QPushButton(this,"default");
      default_btn->setGeometry(30,160,100,30);
      default_btn->setMinimumSize(0,0);
      default_btn->setText(i18n("Default"));
      default_btn->setAutoDefault(true);

      ok_btn= new QPushButton(this,"ok");
      ok_btn->setGeometry(140,160,100,30);
      ok_btn->setMinimumSize(0,0);
      ok_btn->setText(i18n("OK"));
      ok_btn->setAutoDefault(true);

      cancel_btn= new QPushButton(this,"cancel");
      cancel_btn->setGeometry(250,160,100,30);
      cancel_btn->setMinimumSize(0,0);
      cancel_btn->setText(i18n("Cancel"));
      cancel_btn->setAutoDefault(true);

    }

### resource.h

    /***************************************************************************
                              resource.h  -  description
                                 -------------------
        begin                : Mon Jan 31 11:05:05 CET 2000
        copyright            : (C) 2000 by Ralf Nolden
        email                : Ralf.Nolden@post.rwth-aachen.de
     ***************************************************************************/

    /***************************************************************************
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General Public License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     ***************************************************************************/

    #ifndef RESOURCE_H
    #define RESOURCE_H

    #ifdef HAVE_CONFIG_H
    #include <config.h>
    #endif

    ///////////////////////////////////////////////////////////////////
    // resource.h  -- contains macros used for commands

    ///////////////////////////////////////////////////////////////////
    // COMMAND VALUES FOR MENUBAR AND TOOLBAR ENTRIES

    ///////////////////////////////////////////////////////////////////
    // File-menu entries
    #define ID_FILE_NEW                 10010
    #define ID_FILE_OPEN                10020
    #define ID_FILE_OPEN_RECENT         10030
    #define ID_FILE_CLOSE               10040

    #define ID_FILE_SAVE                10050
    #define ID_FILE_SAVE_AS             10060

    #define ID_FILE_PRINT               10070

    #define ID_FILE_QUIT                10080

    ///////////////////////////////////////////////////////////////////
    // Edit-menu entries
    #define ID_EDIT_UNDO                11010
    #define ID_EDIT_COPY                11020
    #define ID_EDIT_CUT                 11030
    #define ID_EDIT_PASTE               11040
    #define ID_EDIT_CLEAR_ALL           11050

    ///////////////////////////////////////////////////////////////////
    // Pen-menu entries
    #define ID_PEN_COLOR                14010
    #define ID_PEN_BRUSH                14020

    ///////////////////////////////////////////////////////////////////
    // Draw-menu entries
    #define ID_DRAW_FIND                15010
    #define ID_DRAW_FREEHAND            15020
    #define ID_DRAW_LINE                15030
    #define ID_DRAW_RECT                15040
    #define ID_DRAW_RECT_FILL           15050
    #define ID_DRAW_CIRCLE              15060
    #define ID_DRAW_CIRCLE_FILL         15070
    #define ID_DRAW_ELLIPSE             15080
    #define ID_DRAW_ELLIPSE_FILL        15090
    #define ID_DRAW_SPRAY               15100
    #define ID_DRAW_FILL                15110
    #define ID_DRAW_ERASE               15120

    ///////////////////////////////////////////////////////////////////
    // View-menu entries
    #define ID_VIEW_TOOLBAR             12010
    #define ID_VIEW_STATUSBAR           12020

    ///////////////////////////////////////////////////////////////////
    // Window-menu entries
    #define ID_WINDOW_NEW_WINDOW        13010
    #define ID_WINDOW_CASCADE           13020
    #define ID_WINDOW_TILE              13030

    ///////////////////////////////////////////////////////////////////
    // Help-menu entries
    #define ID_HELP_CONTENTS            1002
    #define ID_HELP_WHATS_THIS          1003
    ///////////////////////////////////////////////////////////////////
    // General application values
    #define ID_STATUS_MSG               1001
    #define TOOLS_TOOLBAR               1
    #endif // RESOURCE_H