# The &kdevelop; &kde; application Tutorial

Ralf
Nolden

    Ralf.Nolden@post.rwth-aachen.de

2000
2001
Ralf Nolden
&FDLNotice;
01/03/2001
1.40.00

&kdevelop;is an integrated and easy to use environment for C/C++ programmers.

The &kde; Application Tutorials Handbook for the &kdevelop; Integrated Development Environment, a tutorial collection on how to create &kde; applications by examples.

KDE
kdevelop
programming
IDE
development

## Introduction

As &kde; is becoming a standard for &UNIX; system desktops, more and more developers want to take advantage of the mature and well-written API that the &kde; project provides. This API allows developers to easily create &GUI; applications, and make using the classes provided by &Qt; and &kde; easy.

The &kdevelop; project intends to provide an &IDE; that enables developers to create and extend their applications rapidly, with the main focus on building &kde; applications, although you are not tied to this.

To help beginners get off the ground, this handbook tries to explain certain aspects of application creation for &kde; by describing how to create running applications. This is achieved by combining the sample code included with the &Qt; library, and the functionality of the &kdevelop; &IDE;, so you are creating &kde; applications in a minimum of time by following the examples.

The goal of this book is that you should by the time you have completed it have a broad understanding of how the underlying &Qt; library functions, the classes it provides, how &kde; works with this.

The &kdevelop; team wishes you success in learning about &kde; and &Qt;, and we hope &kdevelop; and this handbook will enable you to join the ranks of the programmers who contribute to the &kde; project.

## Getting Started

### Things you should know before you begin

This handbook requires a certain base knowledge about the C++ programming language. You will need this to understand the code examples and their functionality. It is further assumed you have read the &kdevelop; user manual, as well as the &kdevelop; programming handbook. If this is the case, you will already have an understanding how the &kdevelop; &IDE; works in general, and what generated projects are. These things will not be covered again in this handbook.

If you've already gone through the User Manual , you should have &kdevelop; set up to your satisfaction. To follow this tutorial, you should check that you have access to the &Qt; online reference documentation, through either the Help menu, or that you can see this book in the documentation tree.

When looking a the first page of the &Qt; 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 which show you example usage reference for certain classes.

If you are an absolute beginner, you should be at least comfortable with the idea behind &Qt;. That is, 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 that list is meaningless to you, you should start with the &Qt; library in the Programming handbook, where the basics of a &Qt; application are described in detail. There you will also find an introduction to the difference between a pure &Qt; application and a &kde; application.

### The first steps

To learn about &Qt; will give you a good start in programming &kde; applications as well, so we will be creating a project that will produce the tutorial applications (after some editing, of course).

To do this, start by opening &kdevelop; and select Project New from the menu. The application wizard will open and you can select the type of project you wish to build. For &Qt; applications you would choose the Qt application option, but we will be using the Mini-KDE project to build our examples. The reason is simple, the &Qt; examples don't contain much code, and by choosing the &Qt; application in the application wizard, we would have to heavily edit the provided code to create true examples. As the Mini-KDE consists of only an empty main window and some checks for the &Qt; library and header files, it will fit our purposes with the least amount of changes.

When entering the next wizard page, you should enter the project specific information. You 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 each stage on the previously created code, we can start with `MyWidget` as the project name. This will then build us the base class that is needed in . As we want to start with the first tutorial step, use `1` as the project number. We can increase this later, when moving to the next step.

Next fill in any other required information, such as your name, email address, and project directory. These are all that are required for our purposes, so we can flip to the next page, where you can insert any header information you want to include in the project header files (or you can use the example provided). The same is done for the next page, which sets header information to be included in the source files. Finally the last page, where you press Create to build the project. If the Create is not available, start at the first page again, and make sure you didn't miss any required information.

If everything went Ok, the project has been built, and the message window in the application wizard will have `READY` as it's final line. Select Close to exit the wizard.

When looking at the tree view on the left, you will see that the project already includes a class `MyWidget`. This is because we chose this to be the application name. Further when opening Functions in the class viewer Globals folder, you will see the application already contains a `main()` function where the execution will start.

Now the application is set up. You could do an Execute or Make already, but as we want to create the tutorial applications, we will instead go ahead and change the code to create the tutorial examples.

## Building the &Qt; Tutorials

### Hello World!

As the previous chapter explains, we have created a project called MyWidget. When looking at the first tutorial page of the &Qt; documentation, you will see that the first application uses only the `main()`. Therefore, we have only to change the already created `main()` 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 with the editing cursor already set in front of the first line of `main()`.

### What to change first

We could start by just changing the already given code to the desired code as listed in the tutorial. There is however an even easier way. Select the contents of the `main()` with the mouse, or by holding the Shift key while selecting the lines with the Down Arrow. Then select Cut from the Edit menu, or press the Delete key. This removes the selected code, clearing the `main()` and leaving the way clear to start from scratch.

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 example by yourself.

Or, you could do it the easy way. Select the contents of the listed `main()` in the documentation browser, and press the Copy from the toolbar (or use the context menu, or the appropriate entry in the Edit menu). This will copy the selection to the clipboard, and, after switching back to the sourcecode window, can be inserted with Paste, either from the context menu, the toolbar, or the Paste entry in the Edit menu.

Whichever method you choose, you need to add the appropriate header files, outside the `main()`:

#### Headers for the first Tutorial example

    #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, you can simply choose Make from the Build menu, or press the appropriate toolbar button. You could choose instead Execute, which results in running the application after the build was successful.

If the build stops due to an error, you can rest assured the fault was on the programmer's side, not the tutorial. It would be sad to be stopped in your tracks just when you're taking your first steps towards programming for &kde; (and/or &Qt;), but you can use it as an opportunity to learn more about &kdevelop;!

To find an error, you have two options. Either select the appropriate error message in the output window, which will take you to the line of source code where the error occurred, or press the F4 key. If you are unfortunate enough to have more than one error, you can use the Next Error in the View menu, to work your way through the list.

### Excercises

You may be asking Excercises? This was so simple, I don't need to do any exercises!, but be assured, things will be getting more complicated. This was the easiest example, and the more complex they become, the more thinking is required on your side to understand and follow the tutorial steps. The exercises will additionally show you how to actually use &kdevelop; and it's features.

To teach yourself more with the first tutorial, you should try the following changes to the source code, and see how this influences 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 the width, and which the height.

### 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 the parent-child relationships between widgets.

Why are these things important? Well, signals and slots are the building blocks of the technology which differentiates &Qt; from other widget libraries. Whenever you hit an implementation problem where you want to communicate between objects, this mechanism will make your work incredibly short, and nothing serious can happen even if something goes wrong - misconnections don't lead to a segmentation fault. This is one of the major strengths of the &Qt; library, and a lot of &kde;/&Qt; developers wouldn't want to do without it's flexibility. Understanding the signal/slot mechanism is therefor essential to creating a &kde;/&Qt; application.

Watch out for the fact that the button terminates the `QApplication` instance by calling `quit()`. You will be meeting `quit()` again when working with `QApplication`'s KDE equivalent, `KApplication`

### Writing your own Widgets

After having successfully followed the tutorial steps, modifying and extending the `main()`, you will hit the point where you want to create a new widget. A unique widget, that can only 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. Just insert the code that lesson 4 places in the `main.cpp` into the `MyWidget` header and implementation file. Fortunately, you have only to add the implementation to the constructor code.

To access the constructor, select it from the class viewer. This will automatically take you to the implementation where you can add the code.

Here you have only 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 your new application, just hit the Execute icon. &kdevelop; will first save and then compile your changes, and finally your applicaiton will be run.

### Extending the Widget Class

In tutorial lesson 5, the target is to extend the widget class. You'll also be learning here about virtual functions. Here the `resizeEvent()` function is reimplemented. What is important to learn here, besides extending a class, is that &Qt; works with user events through virtual functions that take an event as a parameter.

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 the view areas of applications, you will may to overwrite the default implementation of the widget's event-methods with your own.

An example of this could be `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 use either `event` or `e` as the parameter name.

After this you can process the event's parameters. For a `QMouseEvent`, you have to ask if the &RMB;, &MMB; or &LMB; 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 might think I will just create a header file, and an implementation file, and then I'm done!. &kdevelop; once again makes it easier for you. When adding a class, you should always use the class generator. This will do all the hard work for you, so you will just have to add the specific code.

To add a class with the class generator, choose Project New Class.... A dialog will open for you to enter the values for the class you wish to add. The first thing you need to add is the classname, which is `LCDRange` in the tutorial.

Next add the baseclass. The baseclass is the one that the new class inherits. Looking at the tutorial, this is `QWidget`. As most GUI classes to add will be inheriting `QWidget`, &kdevelop; has a shortcut for you. Simply leave the Baseclass field empty, and instead check generate a QWidget-Childclass in the Additional Options section. This will automatically add the required `Q_OBJECT` macro to the headerfile, to later add slots and signals. Slots and signals will be required in step 7.

As the filenames are automatically inserted, you don't have to worry about that. The only thing we would suggest (strongly!) adding here is documentation. It is always good style to add descriptive documentation to the class, especially as a classname such as `LCDRange` may not inform you specifically about the purpose of the widget.

### Steps 7 through 14

For the rest of the tutorial steps, you are now prepared, and know everything you need to know, so far as using the &kdevelop; IDE to aid your way with such things as adding classes and editing sources.

After each change you should do a new build and check your code for errors. Run the application and follow it's execution. You should additionally play with &kdevelop;'s options for Build. Execute the application with a commandline argument such as `--geometry`, for example, or run the debugger.

When you are comfortable, you should be able to proceed with the rest of the provided &Qt; examples.

In the following chapter, you will be introduced to development for &kde; 2.x, through an example application, KScribble. This will be a small drawing application, which will show you the concepts of application design, and how to implement your own program.

## Creating &kde; applications

This part of the tutorial will be teaching you about &kde;/&Qt; aplication design by creating an example application step by step. The major intention here is to help you gain an understanding of how the two most important ideas of a graphical user interface programming work: the Document View model and the Multiple Document Interface (MDI) model.

You will also gain a good idea of concepts that are new in &kde; 2.x and the &Qt; 2.x library. These bring new enhancements and technology to the programmers hands, improving application design and widening the possibilities for your use. Key concepts include widget themes, inter-process communication, and embedded applications.

During this tutorial you will gain a clear insight into how the MDI model works, and also into the Document View model, which is difficult to understand for even seasoned programmers. This will help you create other types of application, which use the document view model even if they only contain one window, and you can't see an obvious need for it.

The following chapters of the tutorial cover these steps:

- Understanding the basic concepts of the MDI and document view models.

- Creating the generic framework for KScribble, and explaining the concepts of that 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 user interaction.

- Adding dialogs to set such things as pen width, and the 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 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.

## Application Concepts

This chapter now introduces you the the ideas of the application models mentioned already: The MDI (Multiple Document Interface) and the document view models. A basic introduction into the document view model and the generating of such a project with &kdevelop; is also covered in the &kdevelop; Programming Manual, but based on a Single Document Interface (SDI). In any case, you should be familiar already 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 additional description of the &Qt; signal/slot mechanism and event handling.

As a general guideline you should already know:

- That every &kde; application needs one instance of `KApplication` (for a &Qt; application, one instance of `QApplication`).

- That generally a main window is inherited from `KTMainWindow` for &kde; and `QMainWindow` for &Qt;.

- That events (&eg; 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, provices specialized event handlers through virtual functions. These filter out the appropriate events by reimplementing the `QObject::event()`.

- That the &Qt; signal/slot mechanism means basically that a component can send out signals, and a reciever can connect to those signals to process some actions.

- That this mechanism avoids inheriting classes for GUI components, because the signals inform you about things like mouse presses, as appropriate for a widget.

### The document view model

The document view model is one of the most basic concept sin application design for those applications that rely on graphical user interfaces. Therefore, a certain understanding of it's purpose is needed before the programmer can see it's possibilities, and the many ways to make use of it.

First, lets take a look at 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 represents itself to the user through 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, a 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 then has to be set as the view area component with a method provide by the main window: `setView(your_view)` for &kde; applications using the `KTMainWindow` class, `setCentralWidget(your_view)` for a &Qt; application using the `QMainWindow` class.

Now the view is obviously 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 the menubar or toolbar command, like this:

      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 suppose you have already created this instance as a `QMultiLineEdit` and set this as the view area.

The multi line edit's slot gets called, and cuts out the selected text as a reaction. 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 mean a simple editor can be written by creating one single class that defines the main window behaviour, and how to save and read files into the editor, just a few basic slots that your main view has to implement itself.

Here is the reason we're now introducing this mysterious document view model: You have to provide the methods on your own to read and write the actual files you want to edit, with this method of using `QMultiLineEdit` view area within the main window's interface.

Yes this is obviously the easiest thing to do in this case, and perhaps the most logical. Yet, take a look at the files and their contents as a so-called Document, to which we can apply the attribute of an Object in C++ terminology. Then 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 can easily create a small class that is responsible for reading 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 could 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 necessary in the document class, because the edit view already provides all the methods basically needed for an editor, through slots, and you can manipulate the content of the view through these slots directly.

The main idea for this separation into three objects (document, view and main window) instead of the usual two (view and main window) is the answer to this question: What if I want to give the user the possibility to work with the file in two, or even more windows?

Such things could even be done within one main window, through splitters or dividers, containing two (or more) view instances which both display one file.

There it is then, the solution can only be that if the user manipulates the file contents in one view, the other view must be notified about this and refresh it's contents. Otherwise a bad scenario will occur if the user closes one view where he added something to the end of the file which he had cut out at the beginning, but from the other view, the file would be saved later still containing the cut section. This is because if the second and final view did not recognize the cutting, it still contains the text without modification from the beginning, plus the newly inserted text.

This means that both views have to be synchronized, and this can be done if all views are notified about each action that the user does. In that case, which view he actually uses is immaterial.

So finally, a document class is needed to be the one and only holder of the true contents of the document, and it then is providing the views the possibility to manipulate those contents.

I hope this has given some insight into this model, although in most cases the programmer can live without it, whether by just using a provided class as the view area, or by writing a widget on his own to handle user interaction. Just so long as you represent one document or file by one view area, then the view can be responsible for the data as well as the display, and only provide the methods to retrive or set the document contents for actions such as reading a file or saving it.

The next model to describe, the MDI, will make a difference at this point. There you will see the actual necessity for the document view model, and the extra functionality it provides.

### The multiple document interface (MDI)

As the last section described the document view model, you will probably have long since guessed what the MDI means. Users coming from other operating systems than &UNIX;/&Linux; are used to it, as well as programmers developing for those platforms.

X-Window applications traditionally are more targeted towards functionality and stability, and &UNIX; users are used to single windows that provide functionality, so the document view model is often not required to create applications.

With &Qt; as a multi-platform toolkit, developers have even more choice, developing for &Microsoft; &Windows; as well as for &UNIX; systems. On windows, the lack of &IDE;'s that are capable of handling so called child windows has been getting to some kind of standard, so this is well addressed by the &Qt; 2.x library. So, &UNIX; users can profit from this architecture as well.

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

    aplication 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 behavior for maximize, minimize, much as a window manager does for top level windows.

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

Extending this concept with the document view model inhances 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.x contains the possibility to create such applications, and &kdevelop; provides you with the appropriate application framework for both &Qt; only programs and for &kde; applications. Using &kde; interfaces will offer you still more possiblilities, through library functions, as well as inter-process communication, but these are specail aspects that are covered in more specific introductions to these techniques

Now you are prepared for following the development of such an application for &kde; 2.x. 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;, then you will know that your &IDE; uses so-called frameworks as a starting point for application development. These frameworks by default contain support for a generic document-view model, but were previously limited to SDI (Single Document Interface, one window with one view area) model structures.

&kdevelop;'s application wizard is now capable of a lot more than previous versions. It offers new project types, including the major extensions we're interested in, MDI application frameworks for both &kde; 2.x and &Qt; 2.x. These frameworks differ only in the use of some library functions, such as file dialogs or message boxes, as well as the choice of toolbar and statusbar classes. The &kde; version uses &kde; 2.x methods, the &Qt; version the corresponding &Qt; functions.

### Creating an MDI framework

We're going to jump right 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, and from this list, select KDE 2 MDI from the KDE section. If you want to make a &Qt; application instead, choose the corresponding option, Qt 2.x MDI. Then choose Next to continue.

On this page, enter `KScribble` as the project name, and `1.0` as the version number. Add your name and email address in the appropriate fields, and you're done.

Click on the Create button, and &kdevelop; will generate our first version of KScribble. You can watch the output in the last page of the wizard. Notice the messages telling you that the messages file has been set up for your project, this will later allow you to have KScribble translated into other languages. When everything is done, you can finally press Exit to return to the &kdevelop; &IDE;.

### Overview

It's important at this point to have some understanding of where in the newly generated sources things are located, and what you as a programmer can now do to make KScribble into a unique &kde; application.

First of all, in the Classes viewer, you will see three already created classes, `KScribbleApp`, `KScribbleDoc`, and `KScribbleView`. As previously explained, each of them has a certain part to play 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. Finally the View class is responsible for user interaction with the child windows, and communicates with the document connected to it.

These facts have several consequences. To make best use of the provided functionality, the programmer is bound to use the given View class. By default, the View inherits `QWidget` as a minimum, but you're far from limited to this. Inheriting from another class is not too much work, and there is also the possibility to use &eg; a `QMainWindow` or `KTMainWindow` as the class to inherit from, and use another class to manage the view.

In the next chapter we'll begin 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 pictures and save them too!

## Defining the documents

The first step when creating an application based on the document view model should always be to think about what kind of data the application has to manage. This decides for you what the view class will look like, but more especially it decides how the document class will read and write data to and from files, and how it will offer methods to manipulate that 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 they are being 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.

We need also to define a pen that draws into a document, set it's width, and it's color, and make it available for the view class to retrieve. Actually, you want the view offering the drawing methods, but the document as the central element for all the views has to hold the pen originally, because you could otherwise end up with two views of the same document using different pens!

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

### Adding the Instances

Open the file `kscribbledoc.h` by selecting it in one of the file viewers, or by a click on the Classes viewer 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 can see, we have added pen and buffer, as well as `currentPen()` and `penWidth()`. As pen is declared private, we offer a possibility to retrieve the pen as well as the pen width. As these are already implemented within the class declaration, we don't have to add them to the implementation file, which is where we now turn our attention.

### 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 a width of 3 pixels. The `QPen` class has some more constructors, but this one is sufficient for our needs here.

### Implementing the serialization

What is now left to do is to define how to open and save oru pictures. This is done through the following 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 arrows to your code. What we have done 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 have added instead.

Other documents may open a file and read in it's contents, such as text lines, so the `QFile` methods are already present in the codeframe.

As `save()` and `load()` return a boolean value if the operation is successful, we're returning false if not, so that 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 are now already provided in `QPixmap`. They require the filename and format as arguments. The source framework, on the other hand, does not call the document methods with the format just yet.

If only one format is used, it is sufficient to set the format here. For now we are using PNG as the format. See `QImageIO` for more details about the image formats that can be opened.

Now we're almost finished defining our document structure. The `QPixmap` buffer serves us as a buffer, storing the original picture contents while we're wroking on it, and the pen is a valid pen for all views connected to the document. Note that the intialization of the pen is done in `newDocument()`. This method is aways called after the constructor within the framework internally, so you should add document instances initializations there, as we have done 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. Then we'll be ready to paint!

## Defining the view

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. This is the minimum requirement for a child window, but it's is sufficient for our needs.

When it comes to defining a new widget's behavior, we need to know how the user shall interact with the window. In our example, this will obviously be with the moust. Therefore, we have to overwrite some virtual methods from `QWidget` that process mouse events the widget recieves.

What we need is to know when the user presses a mouse button, because the drawing shall only take place when the mouse button is pressed. then we need to know when the mouse is moved (and to know where it moves to), as well as when it has been released, in order 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`.

From the example below, 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 come to the actual implementation of the event handlers. As explained in the &kde; library reference, &Qt; has a good way of handling user events, especially when they target widgets. `QWidget` as a base class preselects the events and provides basic code handlers. As these are declared as virtual, we can overwrite to define how our widget shall react to user actions. One is already overwritten, the `closeEvent()` method. This is necessary 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 to let the App class do the job.

First of all we have to declare the widget default behavior 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 initializing `mousePressed` and `polyline`. Then we'll start implementing our first event handler, `mousePressEvent()`, to recognize when the user presses the mouse and where.

The following implementations have to be inserted as a whole, 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 are setting `mousePressed` to true, so this event is stored somehow. the second line is not so obvious. There we are 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 a 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.

As we're only interested in the move if the mouse is pressed, because that is the time to draw, we have to ask then if `mousePressed` is true. This has been already taken care of through the `mousePressEvent()` event handler, so we don't have to worry any more about it.

Now we're ready to start 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 is only acting 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 values inside the polyline `QPoint` array, setting point 2 to 1, 1 to 0 and 0 to the point the move went to. This is the contents of the event we're interested in. Suppose 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 be wondering why the array needs three points then, if we're only interested in drawing a line from one position to the next. These 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 the most complete rectangle. Then we use `normalize()` to have the leftmost and topmost values the smallest (as coordinates are counted from top to bottom, and from left to right).

The next thing to do is to adapt the size of the rectangle to the size of the pen. The pen has a thickness we get with `penWidth()`, and we widen the rectangle by the width of the pen. Imagine the mouse movement was only two pixels, but the pen thickness is set to ten pixels wide - the rectangle wouldn't contain the entire painted area if it isn't enlarged by the pen width first.

Finally, we set the document modified, and use `bitBlt()` to copy the rectangle out of the buffer and into the widget. `bitBlt` operates bitwise, and is very fast, so that is a good method to copy the painted area from the buffer to the widget, instead of repainting the whole window. It's first argument is the object to draw onto (the destination), in this case our widget, so we use the pointer `this`.

The next two arguments give the destination top left position to start copying to, then follows the source to copy the picture from, including it's coordinates and the width and height.

As the pixmap coordinates are the same as the coordinates the widget uses, because our pixmap is drawn into the top left corner, the coordinates for the source and destination top left point are the same. This is however, something you will need to watch out for in some of the next steps, so we bring it to your attention now that this may not always be the case.

Next comes what happens if we release the mouse button. The drawing has to stop when we move the mouse again, so we set `mousePressed` to false here:

    void KScribbleView::mouseReleaseEvent( QMouseEvent * ) {
    	mousePressed = FALSE;
    }

So, now we have finished implementing the user interaction when it comes to actual drawing operations. As the example shows, it's not too complicated to use a document view model. Just create your document interface so that it contains the contents, and copy the contents to your view.

### Painting and resizing the document

What is now left to do are two more virtual event handlers that need reimplementation. First of all, we have to take care that our picture gets painted into the window when something else happens. For example, when you open another window that obscures the painting, then change focus 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 need only the rectangle that gets repainted, so we we retrieve the gemoetry from the event (`e->rect()` and use the coordinates for `bitBlt()`, just as we did in the `mouseMoveEvent()`

We still haven't done anything about 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. Even if we would have set the size, it's 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 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 for now to the same size as the widget. 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 or `QWidget`. Then we calculate the size of our picture. Because we can resize a window to be either bigger or smaller, we have to separate these two cases. If we resize to a smaller geometry, the picture should still contain it's contents. If, on the other hand, 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`. Before the resize takes place though, we create a copy of our pixmap in the document in tmp. Then we resize the buffer (the document), fill it with white, and then copy back the contents from tmp to the buffer. This resizes our pixmap always synchronous with the widget that displays it, but doesn't loose contents which are outside the visible area, if the resizing made the widget smaller.

Now, finally, 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 teh document class, and we have enabled user interaction through overwriting virtual methods in the view class. All this has gained us our first functionality, and now we can draw pictures as well.

When we created the `QPen` instance in the document class however, we set some predefined values for the pen. The color is black, and the pen is 3 pixels wide. As you would 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 which window is currently active, and the document connected to it.

This chapter then, will introduce you to the following concepts:

- Adding a new menu to the menubar.

- Extending the toolbar with icons for the actions defined in the menubar.

- Creating a new dialog with &Qt; Designer.

- Connecting the new commands of the menubar and toolbar to calling the dialogs.

- Enabling the document class to let us change the pen values.

Finally, we also add a method to delete the document contents complete with a menubar command.

### Adding the Pen menu

As the title of this section says, we will be adding a menu for setting pen values 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 that isn't necessary. There are two important things that affect the appearance of the menubar:

- The order of the menu items in the menus.

- The order of insertion of the popup menu into the menubar.

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

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

    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 now 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 interanationalize your application. As a rule you should alwyas declare all visible-to-the-user text with the method `i18n()`. for &Qt; only programs, us it's corresponding method `tr()` instead of `i18n()`, as &Qt; has it's own ways of internationalizing applications.

The second argument above 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 the other already used IDs declared. There are also other ways to insert menus, by directly connecting a slot to the inserted entry, but the applicaiton framework uses IDs to select which action has been activated, and highlighted. Therefore, each menu entry, independent of which popup menu it appears in, has it's own unique number. Since the numbers aren't too easy to remember 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 insert the popup menu after the Edit men and before the View menu, so that it will appear between those menus in the menubar.

What is also importnat when creating menus is that they should be available to the user with shortcuts. In menus, you will often see underlined characters that the user can jump directly to, by pressing &Alt; and the underlined letter of the menu entry. As a programmer, you set this character with an ampersand, so in our example the Pen will later be accessible from the keyboard by pressing &Alt;P . Within the menu, the user can press another button to go directly to the command he wants, so in the menu itself all items should have this kind of accelerator key as well. Note that you should write item insertions together in group that have the same visible access, so you can kee a better overview of the characters you hvae already used. No accelerators should be visible twice at the same time.

This is also important for translators. In other languages, the character you chose as accelerator may not be in the menu entry anymore, so translators will have to reset them sometimes.

Finally, in the last two lines above, we're connecting the pen menu with two slots. One is 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 to the user. You can have a look at the methods the menu is connected to, as they contain switch statements where the menu ID sent is compared and the corresponding action is called.

What's left now to do is 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 must be careful ont to set the same number for two entries, but if this should happen by accident there's still the compiler which will inform you about redefining.

This is currently all you have to do to add a new menu to your menubar. The actions they will execute simple, Color will call a color setting dialog box, and Brush will call a dialog to select the brush width. Of course, we haven't created this dialog yet!

First, lets extend the toolbar with two icons for these actions.

### Adding toolbar buttons

You might think that some new commands should be made available as toolbar buttons, because they are often used and offer increased functionality. You can easily do this by adding buttons in the framework's `initToolBar()` method of the App class.

Here we've decided to add a button for both of our new menu entries in the Pen menu, but they first need icons.

You will very likely find icons to use in the &kde; directory `/toolbar`, or if you are of an artistic mind you can create some for yourself. KIconEdit is very suitable for painting icons, so we'll take a small detour and make our own icons.

Choose New from the &kdevelop; File menu, and select from the Linux/KDE tab the entry for Icon (*.xpm). Enter the name you want to give your new icon in the field on the top right, which for our example could be `pencolor.xpm`. You can choose where in your project directory you want the icon to be saved, by browsing to a different location in the bottom part of the dialog. It is good practise to keep toolbar icons in a separate directory, so create a new directory under your KScribble project directory, and name it `toolbar`. Change to this directory in the New File... dialog and press the OK button.

The new icon should now be created in the new `toolbar` directory, and will be opened by &kiconedit; automatically. Paint something that will symbolize to the user what the button is intended to do, save the pixmap within &kiconedit; and then switch back to &kdevelop; In the Files or Groups panel, select the icon by a &RMB; click 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 newly active field the following:

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

This will install the pixmap in the &kde, file system hierarchy's data directory, where each application has it's own subdirectory containing additional files needed by the application. Icons have to be installed into a subdirectory here called `toolbar`, so the application's icon loader can find the pixmaps for your program.

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

Now we have only to insert these buttons into the toolbar. Add the arrow marked lines from the following example into your sources:

    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 lad the icon for the button. What may seem unusual is there is no mention of the format. The preferred format for &kde; 2 is PNG, but it works just fine with xpm files as well. You can use &pixie; to convert them in any case, or perhaps a later version of KScribble!

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 means `available` when true and `deactivated` when false. As we want these new icons to be available, we set this to true.

Finally, we add a tooltip for these buttons, which we also embrace with `i18n()` to allow internationalization.

Now the &gui; is extended, at least visually. You can compile and run KScribble again, and see how it looks now. Of course the new items in the menu and toolbars can't execute any action, that is what we're going to be doing in the next section. You will also note that the toolbar icons we just added are not displayed - which is because we didn't install KScribble, and so they can't be found. The other icons you can see were already shipped with &kde;, so they are visible without needing any new installation.

### Creating the pen width dialog

As we've already created the appropriate 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 Designer File (*.ui). Enter the dialog file name as `kpenbrushdlg`, and the extension will be automatically added. Press the OK button.

When constructing a dialog, we have to think about what is really needed by the user. Here we will need a label, to display what will be set. A spinbox, with up and down buttons to set the pen width value is needed, and three other buttons, one for resetting the pen width to the default value, one to cancel the dialog, and an OK button for applying the new value.

The &Qt; Designer will open, first asking you to choose which kind of interface item you wish to create (in this case, we are making a Dialog, and then when you have chosen, showing you an empty canvas that will be the dialog background.

We will add the buttons in this order to the dialog. Order is important, because the tab focus follows the order by which the widgets are created. If you start with the OK button, then the spinbox, then the Cancel button, the input focus will also start with the OK button, then the spinbox, and then the Cancel button, which is probably not what the user is expecting. The tab focus should follow the widget's items top to bottom, from left to right, so we should construct the dialog in this order also.

The Label

  Choose the icon that looks like an A, or choose Tools Display TextLabel to select the label tool. You can now use the cursor to draw out a label on the canvas. You don't need to be too precise in your dimensions, as they are very simple to change afterwards, but place the top left corner at position x: 50, y: 20. &Qt; Designer shows you this position with a yellow tooltip, as you drag the label around the canvas.

  In the Property Editor on the left side of the screen, choose the text property. Change the default text (TextLabel1) to say `Pen Width`. Adjust the width of the label to a width that matches the label contents in the x direction. About 120 pixels should be fine, and you can either use your mouse to drag the label to a new size, or by setting the values in the geometry section of the Property Editor.

The Spinbox

  Press the Spinbox icon on the toolbar, or choose Tools Input SpinBox from the menu. Place your spinbox on the widget background, to the right of the label we created in the last step.

  In the Property Editor, set the name of the spin box to `width_spbox`. In the maxValue and minValue fields, set them to 100 and 1 respectively. This should be sufficient for brush widths.

The Buttons

  Finally, we need to create the three buttons. The leftmost button is the default one. Create a button, with either the icon that looks like an Ok button, or the Tools Buttons Pushbutton menu entry. Place it somewhere nicely on the bottom of the dailog, and set the name to `default_btn`, and the button text to `Default`.

  Repeat this for the OK button, with the name `ok_btn` and the Cancel button, with the name `cancel_btn`. Set the button text to `&OK` and `&Cancel` respectively.

When you're happy with the layout of the dialog choose File Save and then close &Qt; Designer

### 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. 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 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 worry about that anymore, and we can go directly to adding the usual connections for the pushbuttons first.

Add the lines marked with 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 three buttons on the bottom of the dialog, when the user clicks on them. 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 to call the slot `accept()`, provided by `QDialog`. The next line connects the Cancel to `QDialog`'s `reject()` slot. This will both close the dialog and reset the resulting value, which we will use later, when we implement the method that calls the dialog to determine if we want to see 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 width 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 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 the method added for selecting the pen color, one after the other.

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. This can be the document of any window, because all documents should have the same current pen width. Then we create an integer variable `cur_width`, which 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()`, the method we added to the dialog.

The `if()` ensures that the following code is only executed when the result code of the dialog box has the accept flag set, which means of course, the code is only 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 this we use the `for()` loop, and set every document's pen width to the `width` variable we retrieved before with `dlg->width()`.

We haven't yet implemented the method `setPenWidth()` in the document class, so we'll do that 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 yet another new method of the `KScribbleDoc` class to set the pen color. This one has to be implemented as follows:

    kscribbledoc.h:

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

Be sure to add the declaration of the two new methods, `slotPenBrush()` and `slotPenColor()` to the class `KScribbleApp`, so that our class knows about these methods.

All done! Lets do quick summary on what we've done in this chapter:

- First we added menubar and toolbar commands for two dialogs that we want to call.

- Then we created the first dialog, to set the pen width, with &Qt; Designer.

- After that, we created all the necessary methods for our menubar/toolbar items to invoke the dialogs, and to set the properties we want to change.

- Finally, we added the necessary set of functions to the document, so that we are able to change the values after the dialogs are executed.

Through this structure, you should now have an overview of how to extend your application, adding more functionality and manipulating settings that influence the behavior of the document and view interaction.

## Extended views

In this chapter we're going to extend the funtionality of our view widget with two enhancements: synchronized views and scrolled views.

### Synchronizing views

Let's first explain what advantage this will bring us, and how we're going to do it. While playing with KScribble, you may have noticed that if you open another view of a document, by calling Window New Window, this new view works with the same data as the first view, and so do any further views you create.

When it comes to painting into the document however, you can only do this in one view, because the other views are not displaying the document contents at the same time. Only if you obscure one view that doesn't display the current (updated) contents, and then bring it to the front again, it will then display the current contents.

This is because after a widget has been obscured and then activated again, it receives a paint event from the window system. This calls `KScribbleView::paintEvent()` again, and that is the one that finally redraws the contents of the area that was obscured.

What we want to achieve though, is that all views should paint in synchrony with each the one the user is actualy painting into. You will see in fact, this enhancement is a very simple 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 synchronize the document contents. Every time the document contents are changed, in our case by mouse movoements (where we copy the changes to the buffer with `bitBlt()`, we just have to call `updateAllViews(this)`. The `this` pointer is necessary 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()`, then you're finished.

Take this as a general rule in your applications, that each time the contents of the document is changed by a view, call `updateAllViews()`. How the update is to be executed is implemented in the widget's `update()` method. You may be content to simply setting &eg;, the changed text in an editor. For 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 piece of functionality that can be quite threatening to developers, if you can't use an already implemented widget that provides the scrolling for you.

What precisely does scrolling mean? In our context, the problem begins when we want to see a picture that is bigger than the view can display. The result so far is that you can only see as much as the size of the view allows, starting at the top left corner. The rest will be hidden from the user's sight.

A scrollview on the other hand, is a widget that provides a scrollbar on the right and bottom sides of the widget, by which the user can move the contents.

In fact, the contents are not moving, but the view area is moving instead, to a different position within the document. Fortunately &Qt; provides a class (`QScrollView`) that itself inhereits from `QWidget` and offers the same base functionality as an ordinary widget, while managing the contents through scrollbars. You have the additional option of using either an instance of `QScrollView`, creating the child widgets to manage the scroll view as parent and then add them to the scrollview with `addChild()`, or to create a view by inheriting `QScrollView` and draw into the viewport.

The viewport is a defined area inside the scrollview which you can draw into, instead of directly to the widget. The difference here is that `QScrollView` provides a set of event handlers similar to `QWidget`, but especially for the viewport. So, what was formerly a `mousePressEvent()` in our view, will become instead a `viewportMousePressEvent()`, and a `paintEvent()` will become a `viewportPaintEvent()`.

This second possibility then will suit our needs to make `KScribbleView` a scrollable widget, and so we have to make the following modifications:

- First we have to make the document use a size of it's contents. This can be done by getting the size of the opened picture, and for a new picture, defining a default start size. In other painting applications, this size can normally be changed by a command provided by the &gui;, &eg; a dialog that asks for a new width and height as well as a method to shrink the picture contents to fit that new size.

- Changing the inheritance of `KScribbleView` from `QWidget` to `QScrollView`.

- Renaming the virtual methods to the corresponding viewport methods of `QScrollView`.

- Adapting the virtual event handlers to act on the geometry of the viewport. The old implementation relies on the geometries of `QWidget` starting at the top left corner of the widget. If the view is scrolled, and the top left corner is not visible, we have to ensure that the positions retrieved from `QWidget` coordinates are then 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 for all views to retrieve this size.

To do 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 want to modify all methods that deal with initializing, and opening the document contents. These are `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 new picture, we have to set the size to the size of that 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 changes:

    #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 then added the required include file. We also changed all implemented event handlers that deal with the interaction between the contents and the scroll view to the corresponding `QScrollView provided methods, and commented out the `resizeEvent()`.``

Now we can go over to the implementation of these methods, and make use of the size our picture now 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 to resize the contents (that 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, 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` along with the size of the document.

Now we can reimplement the event handlers. First, we should take care of the `paintEvent`, as this is one of the most important ones, because it is called whenever the widget first shows up and also when it is resized.

Be sure to comment out the `resizeEvent()` implementation!

The paint event will have to copy the pixmap in the buffer to the appropriate position in the view. For this we have to change the destination of `bitBlt()` from `this` to `viewport()`, set the top left position to 0,0, and then set the target (the buffer) to cpoy from the `contentsX` and `contentsY` position onto 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 becomes position 0 in the viewport's absolute position, and is now the top left point visible in the scroll view. The same applies to the y direction. This part is sometimes hard to understand, and ou may have to do a bit of trial and error when implementing your own scroll views.

The other possible call of `bitBlt()` would be to switch the values of the positions, and invert the contents values:

    bitBlt( viewport(), -contentsX(), -contentsY(), &doc->buffer, 0, 0 );

The final changes we must make are changing the mouse event handlers.

First the `mouseMoveEvent()`, which changes to `viewportMouseMoveEvent()`, and has a `bitBlt()` call as well. Here we have to apply the same changes as in the paint event.

Further, in the case of `mousePressEvent()` and the `mouseMoveEvent()`, we have retrieved the position of the mouse events with `e->pos()`. This position will now be delivering us a widget position, and not the contents position, so we have to translate this to draw into the correct position in 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()`, subtracting the `contentsX()` and `contentsY` values to copy the rectangle containing the current painting into the correct position in the viewport.

Finally, 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 often leads to the so-called flicker effect.

This flicker effect sometimes occurs with widgets, but there are 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 are copying the document contents directly into the widget, we don't need to erase it first, as all the data will be overwritten anyway.

In conjunction with `QScrollView`, we will reduce the painting even more: we can limit the update method to call `repaint()` on the `viewport()` widget, because that will call `viewportPaintEvent()`.

On the other hand, the painting area we use is the rectangle containing the document contents, which can result in the document size being smaller than the viewport size. So we can limit the paint event in this case the rectangle of the viewport where the document is displayed, whose visible height and width we can retrieve and compose to the rectangle.

Additionally, we use the erase parameter with false, so the document area doesn't get erased:

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

Now you're ready! This chapter has been one of the hardest to implement and understand, especially when it comes to the geometries that change. On the other hand, we gave our application a whole new level of functionality with the new scrollview and synchronized views.

With that, we move on to the last chapter of our tutorial. There we will apply only a few changes, making use of some methods of the &kde; 2.x libraries. As usual, this will bring us new and interesting functionality, allowing KScribble to open and save a whole range of picture formats, and thereby 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. Apart from actual drawing routines, the libraries support a whole set of image formats which they can read and write, all without any cost on our part.

The main class for thse operations is `KImageIO`, which has a support library by &kde;, also called KImageIO.

As a preparation before we can make use of these nice methods, we have to add the appropriate library to KScribble: libkimgio. Open the Project Options menu choice in &kdevelop;, and add the line `-lkimgio` to the additional libraries to link KScribble with.

### Preparing the document

Now we can go and apply our changes. First we have to remove the curent restrictions in the document class, which until now have allowed us 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 for these methods. For now, we don't have all the available file formats yet, so this will be our next task, together with adapting the slots in KSribble that deliver us the filenames.

### Registering file formats

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

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

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

Note that the call is after the application is instanciated with `KApplication>` app. Without the application instance, our program will not run, as `KImageIO` then doesn'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 it's methods in `KScribbleApp`:

    #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 any document, but until now only with the filename. It just leaves out the extension, as the format is not required by the document class by default. As we have just changed that, we need now a format, and we 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 possibility would be to detect the format in the document class.

### Setting file filters with `KImageIO`

Here we're finally at the end of our tutoral. We will adapt the file dialogs of KScribble to make use of file filters.

For these, `KImageIO` provides nice methods to give us all the required strings for all image file formats that are available for reading or saving.

The following implementation replaces the default file filter, which is, in fact, no filter at all. You have to change your own application file filter to your mime type according to the `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 that we have finished opening files by filename and format combined, we're not quite done. Something is missing to complete this structure. Setting the patters for saving a file also.

For this job, `slotFileSaveAs` is called, which invokes for itself the file dialog that retrieves a filename. 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 by giving you an overview of who is actually doing the printing job.

When the user presses the print button in KScribble, or chooses File Print, 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`.

This then calls the widget's printing method, `ScribbleView::print()`. Here, the framework already contains a base implementation. A quick look will tell 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 such as `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 position 0,0, with our buffer pixmap of the document. That's All!

Go right ahead and test it. Now you can print any graphics 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 of KScribble, complete with a further extension that adds cut, copy, paste, and undo functions, as well as drag'n drop in the Appendix.

## Credits and Licenses

&kdevelop;

&underFDL; &underGPL;

## KScribble-1.0 Example sourcecode

The example tarball is locally installed and can be downloaded into 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 the authors installation of &kde; 2.x and &Qt; 2.x, so you may have to change them manually to match your installation path for these options.

The tarball can be found in `$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