]>

# KDE Anwendungs Tutorials

Der KDE Anwendungs-Tutorial Leitfaden für die Integrierte Entwicklungsumgebung KDevelop
Ralf Nolden
Das KDevelop Team

    <Ralf.Nolden@post.rwth-aachen.de>

Ralf
Funken

    <RFunken@kdevelop.de>

Stephan
Johach

    <lucardus@onlinehome.de>

Version 1.0 , Sat Mar 4 2000

Das KDE Anwendungs-Tutorial Leitfaden für die Integrierte Entwicklungsumgebung KDevelop; eine Sammlung von Anleitungen, die anhand von Beispielen zeigen, wie man eine KDE Applikation und die mit der QT Bibliothek ausgelieferten Tutorial Beispiele erstellt. Dieser Leitfaden ist Teil der Integrierten Entwicklungsumgebung KDevelop und fällt damit unter die GNU General Public Licence. Weitere Informationen hierzu unter Copyright.

KDE

## Einleitung

Da KDE derzeit zu einem Standard bei den Oberflächen von UNIX Systemen heranwächst, wollen immer mehr Entwickler die Vorzüge des sorgfältig geschriebenen API genießen, das das KDE Projekt zum Erstellen neuer Applikationen bereitstellt, die über ein GUI verfügen, und das die einfache Verwendung der von QT/KDE zur Verfügung gestellten Klassen ermöglicht.

Das KDevelop Projekt hat es sich zur Aufgabe gemacht, eine IDE verfügbar zu machen, die es Entwicklern ermöglicht, ihre Anwendungen rasch zu erstellen und weiterzuentwickeln, wobei der Schwerpunkt auf KDE liegt.

Da Anfänger immer mit Startschwierigkeiten zu kämpfen haben, versucht dieser Leitfaden, bestimmte Aspekte der Erstellung von KDE Anwendungen zu erläutern. Dieses Ziel soll anhand von Anleitungen zur Vorgehensweise, wie man mit Hilfe der KDevelop IDE aus den mit der QT Bibliothek ausgelieferten Beispielen, lauffähige Anwendungen erstellt, erreicht werden.

Aus diesem Grund erklärt das folgende Kapitel zunächst, wie Sie mit KDevelop ein Projekt erstellen, das bereits eine komplette Anwendung darstellt. Danach wenden wir uns der Online Dokumentation von QT zu, die Quelltexte mit Beispielen zur Anwendung der QT Bibliothek enthält. Das wird Ihnen einen ersten Eindruck vermitteln, wie die darunterliegende Bibliothek funktioniert und wie man die von ihr bereitgestellten Klassen verwendet.

Die QT Bibliothek ist unser Ausgangspunkt; denn KDE basiert auf den QT Klassen, und die KDE Applikationen nutzen diese ausgiebig.

Außerdem werden wir uns ansehen, wie man mit dem Application Wizard eine Mini-KDE Anwendung erstellt. Ausgehend von dem bereitgestellten minimalen Gerüst, erstellen wir eine lauffähige KDE Beispielanwendung, die von den KDE Bibliotheken Gebrauch macht und erläutern die verschiedenen Bestandteile einer KDE Anwendung bis ins Detail.

Das KDevelop Team wünscht Ihnen viel Erfolg beim Erlernen von KDE und QT mit Hilfe dieses Leitfadens und hofft, daß sie danach in der Lage sind, als Programmierer beim KDE Projekt mitzuhelfen.

### Welche Kenntnisse sollten Sie mitbringen?

Dieses Handbuch setzt im mindesten grundlegende Kenntnisse der C++ Programmiersprache voraus, die zum Verständnis der Quelltext Beispiele und deren Funktionalität unerläßlich sind.

Desweiteren setzen wir voraus, daß Sie Das Benutzerhandbuch zu KDevelop und The KDevelop Programming Handbook gelesen haben und wissen, wie die KDevelop IDE grundsätzlich funktioniert und was erstellte Projekte sind.

## Erste Schritte

Erste Schritte - das bedeutet, sie sollten KDevelop bereits soweit eingerichtet haben, daß es korrekt arbeitet. Stellen Sie sicher, daß sie über das Menü "Hilfe" oder das entsprechende Buch im Dokumentationsbaum Zugriff auf die Online Referenz-Dokumentation von QT haben. Wenn Sie sich die erste Seite der QT Dokumentation anschauen, werden Sie dort Verweise auf die Abschnitte "Tutorial" und "Example Programs" sehen. Unter "Tutorial" finden Sie eine Einführung, die in 14 Schritten die Verwendung der QT Bibliothek zum Erstellen von Anwendungen aufzeigt.

"Example Programs" führt Sie hingegen zu einer Reihe von Beispielprogrammen, die QT beifügt sind und auf die in den Beschreibungen mancher Klassen als beispielhafte Anwendung verwiesen wird. Wenn Sie ein absoluter Neuling sind, sollte Ihnen die Idee, die hinter QT steckt, angenehm erscheinen; die Bereitstellung einer Bibliothek, die folgendes bietet:

- Eine Basisklasse für Anwendungen (`QApplication`)

- Eine Widget Bibliothek für grafische Benutzerschnittstellen.

- Ein Sortiment weiterer Hilfsklassen für Grafiken, Datei- und Datenverwaltung.

- Den signal-slot Mechanismus für die Kommunikation zwischen Klassen.

- Ereignis Überwachung durch event loops und virtuelle Methoden.

Wenn Sie damit jetzt noch gar nichts anfangen können, sollten Sie in The KDevelop Programming Handbook mehr über die QT Bibliothek nachlesen. Dort erfahren Sie detaillierte Einzelheiten zu den Grundlagen für eine QT Anwendung. Desweiteren finden Sie dort eine Einführung in die Unterschiede zwischen QT- und KDE-Anwendungen, im ersten Schritt des QT Tutorials sind das aber nicht sonderlich viele.

Wenn wir uns zunächst mit der QT vertraut machen, ist das nebenbei ein guter Einstieg in die Programmierung von KDE Anwendungen. Darum legen wir ein Projekt an, das nach dem Bearbeiten die Tutorial Anwendungen erstellt.

Dazu starten wir zunächst KDevelop und wählen im Menü "Projekt" den Menüpunkt "Neu". Der Application Wizard erscheint, und Sie müssen sich entscheiden, welche Art von Projekt Sie erstellen möchten. Für QT Anwendungen würden Sie QT wählen, aber wir benutzen zum Erstellen unserer Beispiele das Mini-KDE Projekt. Das hat einen ganz einfachen Grund: Die QT Tutorials enthalten nicht besonders viel Code, und würden wir im Application Wizard die QT Anwendung auswählen, müssten wir uns die Arbeit machen, den vorliegenden Code so zu ändern, daß wir ein "echtes" Beispiel erstellen können.

Auch wenn die Mini-KDE Anwendung nur aus einem leeren Fenster besteht und ebenfalls nach der QT Bibliothek und deren Headerdateien sucht, ist sie für unsere Zwecke genau richtig und verlangt keine großen Änderungen.

Auf der nächsten Seite des Wizards müssen wir die projektspezifischen Informationen eingeben. Hier können wir sämtliche KDE-spezfischen Zusätze weglassen, beispielsweise das Mini-Icon und das Programm Icon, die Dokumentation, usw. Wir geben nur die Informationen ein, die zum Erstellen einer passenden Basis für die einzelnen Schritte des Tutorials von Nutzen sind.

Da die einzelnen Tutorial Schritte jeweils auf dem in den vorherigen Schritten erstellten Code aufbauen, beginnen wir mit dem Projektnamen `MyWidget` . Dadurch wird bereits die Basisklasse erstellt, die in Kapitel 4 benötigt wird, und wir müssen uns später nicht mehr darum kümmern. Da wir mit dem ersten Schritt des Tutorials beginnen wollen, geben wir als Versionsnummer eine '1' an. Diese wird hochgezählt, wenn wir die nächsten Schritte durchlaufen.

Nun tragen Sie die restlichen benötigten Informationen wie Ihren Namen, Ihre E-Mail Adresse und das Projekt-Verzeichnis ein. Mehr brauchen wir nicht, und wir können zur nächsten Seite springen.

Hier können Sie Informationen bezüglich der Versionsverwaltung CVS eingeben. Aber diese Seite interessiert uns nicht. Wir lassen die Voreinstellung für "VCS Unterstützung" auf "NONE" und gehen direkt zur nächsten Seite.

Tragen Sie hier den Dateikopf für die Headerdateien Ihres Projektes ein, oder belassen Sie es bei den Vorgaben. Das gleiche gilt für die 5. Seite, wo selbiges für den Dateikopf der Quelltext-Dateien eingetragen werden kann.

Schließlich sind wir auf der letzten Seite angelangt. Hier müssen wir auf "Erzeugen" klicken, um das Projekt zu erstellen. Ist der Button nicht verfügbar, gehen wir zur ersten Seite zurück und schauen nach, ob wir nicht irgendwo eine benötigte Information vergessen haben.

Ist alles in Ordnung, wurde das Projekt erstellt, und in der letzten Zeile des Ausgabefensters des Application Wizards steht ***Erfolg***.

Wählen Sie "Abbrechen", um den Wizard zu beenden. Wenn Sie nun links auf die Baumansicht schauen, erkennen Sie, daß das Projekt bereits eine Klasse "MyWidget" enthält. Das ist so, weil wir uns für diese Bezeichnung als Anwendungsnamen entschieden haben. Öffnen wir im Ordner "Globals" der Klassenansicht den Unterordner "Funktionen", erkennen wir, daß die Anwendung bereits eine Funktion `main()` besitzt, in der die Ausführung des Programmes beginnt.

Die Anwendung ist damit bereits fertiggestellt. Sie könnten nun auf "Erstellen" oder "Ausführen" gehen. Aber wir wollen ja die Tutorial Anwendungen erzeugen, darum fahren wir fort und änderen zuerst den Code, um unsere Tutorial Beispiele zu erstellen. Das ist der nächste Schritt.

## Das Erstellen der QT Tutorials

### Schritt 1: Hallo Welt!

Wie im vorangehenden Kapitel erläutert, haben wir bereits ein Projekt mit dem Namen `MyWidget` erstellt. Wenn Sie nun die erste Seite des QT Tutorials betrachten, werden Sie feststellen, daß die erste Anwendung nur die Funktion `main()` verwendet. Darum müssen wir jetzt nur die vorgegebene `main()` Funktion unseres Projektes ändern, um das erste Beispiel des Tutorials zum Laufen zu bringen.

Um in den Quelltext der Funktion `main()` zu springen, gehen Sie mit der Maus in den Ordner "Globale", und von dort in den Unterordner "Funktionen", der alle globalen Funktionen enthält, die keiner Klasse angehören. Durch Anklicken des Eintrages von `main()` öffnet sich die Datei `main.cpp` im Fenster "C/C++ Dateien", und der Cursor steht am Anfang der ersten Zeile von `main()`.

#### Die erste Änderung

Wir könnten nun damit beginnen, den vorhandenen Code so zu ändern, daß er dem im Tutorial aufgeführten entspricht. Aber das geht auch einfacher. Markieren Sie den Inhalt von `main()` mit der Maus oder durch Festhalten der "Shift"-Taste, während Sie die Zeilen mit der Pfeil-nach-unten Taste markieren. Wählen Sie nun "Ausschneiden" aus dem Menü "Bearbeiten" oder betätigen Sie die "Entf"-Taste, um den markierten Code zu entfernen. Dadurch erhalten wir eine leere main Funktion, und wir können ganz von vorn beginnen, um unser erstes Tutorial zu erstellen.

Fügen Sie nun den Code aus dem Beispiel ein. Dazu könnten Sie zunächst das Beispiel sorgfältig studieren und sich danach merken, welche Zeile welchen Code enthält. Nachdem Sie dann die zeilenweise Beschreibung des Tutorials gelesen haben, könnten Sie diese als Anleitung verwenden, um den Code selbst zu schreiben.

Nun, wer den einfachen Weg gehen möchte, markiert den Inhalt der `main.()` Funktion im Dokumentationsbrowser und klickt auf das Icon zum Kopieren in der Werkzeugleiste. Alternativ können Sie hierzu auch das Kontextmenü oder den entsprechenden Eintrag aus dem Menü "Bearbeiten" benutzen. Hierdurch wird die Markierung in die Zwischenablage kopiert und kann, nachdem wir ins Quelltext Fenster zurückgekehrt sind, über "Einfügen" (entweder aus dem Kontextmenü, der Werkzeugleiste oder dem entsprechenden Eintrag im Menü "Bearbeiten") in unsere `main.()` Funktion eingefügt werden.

Abschließend fügen Sie die Einträge zum Einbinden der entsprechenden Headerdateien außerhalb (und vor!)der `main()` Funktion hinzu.

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

Das war's. Da haben Sie den Quellcode für Ihre erste Tutorial Anwendung!

#### Wir übersetzen Hello World!

Um das erste Tutorial zu übersetzen, wählen Sie im Menü "Erstellen" den Punkt "Erstellen", oder klicken Sie auf das entsprechende Icon in der Werkzeugleiste. Genausogut können Sie "Ausführen" nehmen, was dazu führt, daß die Anwendung nach dem Erstellen gestartet wird. Tritt während des Erstellens ein Fehler auf, können Sie davon ausgehen, daß der Fehler beim Programmierer liegt, nicht beim Tutorial! Es wäre zwar traurig, wenn Sie jetzt schon nach einem Fehler suchen müssen, wo Sie doch gerade erst anfangen KDE und/oder QT zu programmieren, aber nutzen Sie diese Gelegenheit einfach dazu, sich im Umgang mit KDevelop zu üben.

Jetzt gibt es für Sie zwei Möglichkeiten, einen Fehler zu finden: Entweder Sie gehen auf die entsprechende Zeile in der Ausgabeansicht, oder Sie drücken F4. Beides führt dazu, daß im Quelltext die Zeile angesprungen wird, in der der Fehler auftrat. Das selbe Ziel erreichen Sie im Menü "Ansicht", indem Sie dort den Menüpunkt "Nächster Fehler" auswählen.

#### Übungen

Übungen? Was soll das? werden Sie sich fragen. Das war so einfach, ich brauche keine Übungen. Aber seien Sie gewiß, es wird noch komplizierter. Das vorangegangene Beispiel war das leichteste, aber mit wachsender Komplexität wird von Ihnen mehr Mitdenken gefordert werden, damit Sie den weiteren Schritten des Tutorials folgen können und die Beispiele verstehen.

Darüberhinaus lernen Sie durch diese Übungen, wie man KDevelop und seine Möglichkeiten einsetzt. Um noch ein wenig mehr aus dem ersten Tutorial zu lernen, sollten Sie einmal versuchen, am bestehenden Code die nachstehenden Änderungen vorzunehmen. Finden Sie heraus, welchen Einfluß diese Änderungen auf das Verhalten der Anwendung haben.

1. Ändern Sie den Pushbutton in ein QLabel.

2. Verzichten Sie auf das direkte Setzen des Textes für den Pushbutton im Konstruktor, und verwenden Sie stattdessen `setText()`.

3. Verwenden Sie `QPushButton::resize ( const QSize & s )` anstelle des direkten Setzens von Höhe und Breite. Legen Sie dazu zunächst eine Instanz von `QSize` mit der gewünschten Größe an.

4. Spielen Sie mit den Größen herum, um zu sehen, welche Höhe oder Breite beeinflußt.

### Schritt 2 und 3: Erweiterungen

Die Schritte 2 und 3 des Tutorials erweitern das erste Tutorial. Sie lernen darin etwas über signals und slots und über die Eltern-Kind Beziehungen zwischen Widgets. Aber warum ist es eigentlich so wichtig, etwas darüber zu lernen? Nun ja, signals und slots verkörpern die Technologie, die den Unterschied zwischen der QT und anderen Widget Bibliotheken ausmacht. Jedes Mal, wenn sie auf ein Implementierungsproblem stoßen, bei dem zwei Objekte miteinander kommunizieren sollen, vereinfacht dieser Mechanismus die Sache ungemein, und es passiert nichts schlimmes, wenn einmal etwas schiefgeht. Selbst Fehler beim Verbinden der Objekte führen nicht zu einem Absturz. Darin liegt die Stärke der QT Bibliothek, und eine Menge KDE/QT Entwickler würden sie aufgrund ihrer Flexibilität nicht mehr missen wollen.

Das Verständnis des signal/slot Mechanismus ist wesentlich für die Erstellung jeder KDE/QT Anwendung.

Beachten Sie auch, daß der Button die Instanz von `QApplication` durch Aufruf von `quit()` beendet. Dieses `quit()` begegnet Ihnen wieder, wenn wir uns mit dem KDE Pendant von `QApplication`, der Klasse `KApplication` beschäftigen.

### Schreiben Sie Ihre eigenen Widgets

Nachdem Sie nun die `main()` Funktion erfolgreich verändert und erweitert haben, sind wir an dem Punkt angelangt, an dem Sie ein neues Widget erstellen. Ein einzigartiges Widget, das nur mit der Klasse erzeugt werden kann, die Sie in diesem Schritt erstellen werden.

Eigentlich hat der Application Wizard den Code hierfür bereits für Sie erzeugt, so daß es nicht mehr viel zu tun gibt. Sie müssen nur noch den Code, den Übung 4 der Datei `main.cpp` hinzufügt, in die Headerdatei und die Implementierungsdatei der Klasse `MyWidget` eintragen.

Glücklicherweise müssen Sie die Implementierung nur noch dem Konstruktor von `MyWidget` hinzufügen. Zum Konstruktor gelangen wir durch Anwählen des Eintrages im Klassenbrowser. Damit springen wir automatisch direkt zur Implementierung im Quelltext, zu der Stelle also, wo Sie den Code einfügen müssen. Sie müssen sich nur noch um das Einbinden von `qfont.h` kümmern. `QFont` wird ausschließlich im Konstruktor von `MyWidget` benutzt. Darum fügen wir die Zeile `&&num;;include &<;qfont.h&>;` am Anfang von `mywidget.cpp` ein.

Zum Starten der Anwendung, gehen Sie einfach auf "Ausführen". Das führt zunächst nach einem automatischen Speichern zum Übersetzen Ihrer Änderungen.

### Wir Erweitern die Widget Klasse

Übung 5 hat das Ziel, die Widget Klasse zu erweitern. Darum lernen Sie hier auch etwas über virtuelle Funktionen. In unserem Fall werden wir die Funktion `resizeEvent()` neu schreiben. Abgesehen vom Erweitern unserer Klassen, ist es wichtig zu wissen, daß QT benutzerdefinierte Ereignisse über virtuelle Funktionen abarbeitet, die ein Ereignisobjekt als Parameter übergeben bekommen.

Darum sollten Sie sich mit den folgenden Klassen der QT Bibliothek vertraut machen:

- QEvent

- QChildEvent

- QCloseEvent

- QFocusEvent

- QKeyEvent

- QMouseEvent

- QMoveEvent

- QPaintEvent

- QResizeEvent

- QTimerEvent

Jedesmal, wenn Sie ein eigenes Widget schreiben, insbesondere dann, wenn damit sichtbare Teile der Anwendung dargestellt werden, müssen Sie die Standard Implementierung der Event-Methoden des Widgets durch eigene Methoden überschreiben, die bestimmte Ereignisse abarbeiten sollen.

Dazu gehört beispielsweise `virtual void mousePressEvent ( QMouseEvent * )`, die für die Verarbeitung der Maus-Ereignisse zuständig ist, die ein Kontextmenü aufklappen lassen.

Bei der Implementierung müssen Sie im Funktionskopf einen formalen Parameter angeben. Die meisten Entwickler vergeben für den Parameter den Namen `event` oder einfach `e`. Nun können Sie die Parameter des Ereignisses abfragen. Bei einem `QMouseEvent` müssen Sie feststellen, ob das Ereignis durch die rechte, die mittlere oder die linke Maustaste ausgelöst wurde.

### Wir fügen eine neue Klasse hinzu

In Übung 6 werden Sie der Tutorial Anwendung eine neue Klasse hinzufügen müssen. Normalerweise würden Sie jetzt denken: "Ich muß bloß eine Headerdatei und die Implementierungsdatei anlegen, dann bin ich fertig", aber KDevelop macht Ihnen die Sache noch einfacher.

Wenn Sie eine neue Klasse anlegen, sollten Sie dazu immer den Klassengenerator benutzen. Das nimmt Ihnen die ganze Arbeit ab, und Sie müssen der so angelegten Klasse nur noch den spezifischen Code hinzufügen. Um mit dem Klassengenerator eine neue Klasse anzulegen, wählen Sie im Menü "Projekt" den Menüpunkt "Neue Klasse...". Daraufhin öffnet sich ein Dialog, in dem Sie alle notwendigen Informationen zum Erstellen der Klasse eingeben.

Zunächst müssen wir den Namen der Klasse angeben. Das Tutorial verwendet den Namen `LCDRange`, also geben wir diesen ein.

Nun fahren wir mit der Basisklasse fort. Die Basisklasse ist diejenige Klasse, von der unsere Klasse abgeleitet wird. Wenn wir im Tutorial (QT 1.42)nachsehen, erfahren wir, es handelt sich um `QWidget`. Weil fast alle benötigten GUI Klassen von `QWidget` abgeleitet werden, macht uns der Klassengenerator die Angelegenheit noch leichter.

Wir lassen das Feld für die Basisklasse leer und markieren unter "Zusätzliche Optionen" die Checkbox "QWidget-Childklasse erzeugen". Dadurch wird in der Headerdatei der Klasse automatisch das `Q&&lowbar;;OBJECT` Makro eingetragen, das für die später (in Kapitel 7) benötigten slots und signals unbedingt notwendig ist. Da die Dateinamen bereits automatisch eingetragen werden, müssen Sie sich darum nicht mehr kümmern.

Allerdings empfehlen wir Ihnen, das Feld Dokumentation auszufüllen. Es zählt zum guten Stil, der Klasse eine beschreibende Dokumentation mit auf den Weg zu geben, insbesondere wenn der Name der Klasse, hier `LCDRange`, keine genauere Auskunft über den Verwendungszweck des Widgets gibt.

### Übung 7-14

Sie sind nun für die restlichen Schritte des Tutorials gerüstet und wissen alles, was sie wissen müssen, um die benötigten Klasse zu erstellen und Änderungen daran vorzunehmen. Nach jeder Änderung sollten Sie die Anwendung mit "Erstellen" übersetzen und Ihren Code auf Fehler untersuchen. Starten Sie die Anwendung, und verfolgen Sie deren Ausführung. Außerdem sollten Sie mit den "Erstellen" Optionen von KDevelop herumspielen; führen Sie die Anwendung mit einer Kommandozeilenoption aus, beispielsweise --geometry, und debuggen Sie das Programm mit KDbg aus dem "Werkzeuge"-Menü oder dem internen Debugger von KDevelop.

Danach sollten Sie in der Lage sein, mit den QT Beispielen fortzufahren, die mit QT geliefert werden. Im nächsten Kapitel werden Sie mit Hilfe der Beispielanwendung KScribble an die Entwicklung für KDE 2 herangeführt. KScribble ist eine kleine Zeichenanwendung, an der Ihnen die Konzepte des Anwendungsdesigns gezeigt werden, und an der Sie sehen, wie Sie Ihr Programm implementieren.

## KDE Applikationen erstellen

Dieses Handbuch versucht Ihnen das KDE/Qt Anwendungsdesign zu zeigen, indem Schritt für Schritt eine Beispielanwendung erstellt wird. Das Hauptziel ist, ein gewisses Verständnis dafür zu entwickeln, wie zwei der wichtigsten Ideen der graphischen Benutzeroberfläche verwirklicht sind: das Document View Modell und das Multiple Document Interface (MDI) Modell.

Dadurch erhalten Sie eine gute Vorstellung von den Konzepten, die demnächst kommen werden: KDE 2 und die Qt 2.1 Bibliothek, die dem Programmierer neue Erweiterungen und Technologien an die Hand geben, die das Anwendungsdesign verbessern und die zur Verfügung stehenden Möglichkeiten erweitern werden. Schlüsselworte wie Widget-Themes, Interprozesskommunikation und eingebettete Anwendungen sind populäre Gesprächsthemen und werden sogar mehr ermöglichen, als Sie sich im Moment vorstellen können. Deshalb bietet KDevelop 1.1, das noch auf KDE 1.1.x basiert, Programmierern einige der meistgeforderten Möglichkeiten: Entwicklung von KDE 2 Anwendungen, während Sie noch auf einer stabilen Oberfläche sind. Sie ahnen schon, daß diese Einführung auf einer KDE 2 Anwendung basiert, die schon Gebrauch von Qt-2.1 macht und eine Lösung für das erwähnte MDI Modell bietet - angeboten als "ready-to-go" Anwendung. In dieser Einführung werden Sie eine klare Einsicht in die Funktionweise erhalten, und das wird es auch viel einfacher machen, das Document View Modell zu verstehen, was sogar für erfahrene Programmierer schwierig ist. Dies wird Ihnen helfen andere Typen von Anwendungen zu schreiben, die ebenfalls das Document View Modell verwenden, auch wenn sie nur ein einziges Fenster enthalten und die Notwendigkeit nicht offensichtlich ist. Aber lassen Sie uns einfach beginnen - Sie werden sehen was auf Sie zukommt und wie einfach es tatsächlich ist. Die weiteren Kapitel dieser Einführung behandeln die folgenden Schritte:

- Installation von KDE 2 (Bibliotheken) und Einstellungen in KDevelop zur Erzeugung von KDE 2 Anwendungen.

- Verstehen der grundlegenden Konzepte der MDI und Document View Modelle.

- Erzeugen des generischen Layouts für KScribble und Erklärung des Layoutkonzeptes.

- Implementierung der Document Klasse, die die Schnittstelle zum Lesen und Schreiben der Dokumentdaten zur Verfügung stellt, sowie das Dokument verwaltet.

- Implementierung der view Klasse zur Visualisierung der Daten. Diese Klasse übernimmt auch die Interaktion mit dem Benutzer.

- Hinzufügen von Dialogen zur Einstellung der Größe und Farbe des Stiftes, mit dem gezeichnet wird, und deren Integration in die Werkzeug- und Menüleiste.

- Ändern der Ansicht der Standard `QWidget` Vererbung in eine rollbare Ansicht.

Das Beispiel selbst basiert auf dem Code, den Sie in der scribble Beispielanwendung finden können, die mit der Qt-Bibliothek geliefert wird. Die Quellen des Originals können Sie auch in der Qt Dokumentation im Abschnitt Beispiele finden.

## Installation von KDE 2

In diesem Kapitel werden wir zunächst einige Schritte unternehmen, die Ihnen helfen KDevelop für die Erstellung von KDE 2 / Qt 2.x Anwendungen vorzubereiten. Erforderlich sind:

- ein aktueller Qt-2.1 Snapshot erhältlich auf http://www.troll.no

- ein aktueller KDE 2 Snapshot der Pakete `kdesupport` and `kdelibs`

Sowohl die Qt-2.1 als auch die KDE 2 Bibliotheken befinden sich zur Zeit in der Entwicklung werden aber als "final" betrachtet und die Änderungen, die für die tatsächlichen Release Versionen erwartet werden sind äußerst gering und werden möglicherweise Ihre Anwendungen in keiner Weise berühren. Das bedeutet, daß Sie die neuesten Bibliotheken kennenlernen werden und es Ihnen möglich ist wertvolle Zeit zu sparen, indem Sie Ihre Anwendung entwickeln können bevor die Bibliotheken überhaupt fertig sind. Die folgenden Abschnitte beschreiben im Detail, in drei Schritten was zu tun ist:

- Installieren der Qt Bibliothek

- Installieren der KDE Bibliothek

- KDevelop einstellen

Dann sind Sie bereit und wir werden im nächsten Kapitel mit dem ersten Schritt zur Erstellung des Beispielprogramm KScribble fortfahren.

### Vorbereitung von Qt-2.1

Dieser erste Schritt wird Sie zur Minimalanforderung für unser Beispielprogramm führen- woraus hervorgeht, daß es nicht nötig ist KDE 2 zu installieren, wenn Sie nur daran interessiert sind "nur-Qt" Programme zu entwickeln. Da Qt-2.1 alle benötigten Klassen enthält und KDevelop auch eine Projektschablone für Qt-2.1 MDI Anwendungen zur Verfügung stellt, die auf dem gleichen Rahmencode basiert, der in der KDE 2 Schablone verwendet wird, sollten Sie kein Problem haben dem Beispiel mit einer "nur-Qt Anwendung" zu folgen.

Nun, um die neueste Qt Bibliothek zu bekommen, holen Sie sich eine neue Version von der Troll Tech Website unter http://www.troll.no, Sektion Download. Wechseln Sie dann nach root und kopieren Sie das Tarball in das Verzeichnis, in dem sich auch Ihre bisherige Qt Version befindet - auf einem SuSE Linux System wäre das `/usr/lib`. Dort sollte sich entweder ein Verzeichnis für qt-1.44 oder qt-2.0.2 befinden, je nach Distributionsversion. Entpacken Sie die Bibliotheksquellen mit

`tar zxvf qtxxx.tar.gz`

abhängig vom Namen des Tarballs. Setzen Sie dann die Umgebungsvariable `QTDIR` so, daß sie auf das Verzeichnis zeigt, in dem sich Ihre neue Qt-2.1 Bibliothek befindet, z.B. mit `setenv QTDIR=/usr/lib/qt-2.1`.Eine andere Lösung wäre es, das qt-1.44 Verzeichnis, daß normalerweise nur qt heißt, in qt-1.44 umzubenennen und einen symlink qt zu erzeugen, der auf qt-2.1 zeigt. Nachdem die Bibliothek erzeugt ist, lassen Sie ihn wieder auf qt-1.44 zeigen.

Wechseln Sie in das Qt-2.1 Verzeichnis und geben Sie ein:

    ./configure
    make

Nachdem Sie die Qt-2.1 Bibliotheken erfolgreich erstellt haben, können Sie die QTDIR Variable wieder auf Ihre Qt-1.44 zeigen lassen.

Wechseln Sie nun in das Verzeichnis `/usr/lib/qt-2.1/lib` und kopieren Sie alle erzeugten Bibliotheken außer dem symbolischen Link `libqt.so` nach `/usr/lib`.

Sie sind nun fertig mit Qt und können zum nächsten Schritt weitergehen, dem Einrichten der KDE 2 Bibliotheken. Wenn Sie nur für Qt entwickeln wollen, gehen Sie weiter zum Abschnitt Einrichten von KDevelop.

### Einrichten der KDE 2 Bibliotheken

Der zweite Schritt ist das Installieren der benötigten KDE Bibliotheken. Laden Sie einen neuen Snapshot der Bibliothekspakete `kdesupport` und `kdelibs` von http://www.kde.org herunter. Danach wird es etwas einfacher als bei den Qt Bibliotheken. Packen Sie einfach die Quellen in Ihrem Verzeichnis aus und wechseln Sie zuerst in das kdesupport Verzeichnis. Geben Sie dort ein:

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

    und als root:

    make install

Dies wird das kdesupport Paket in dem Verzeichnis installieren, das Sie mit der --prefix Option angegeben haben und kompiliert mit der neu erstellten Qt-2.1 Bibliothek, deren Pfad Sie mit der Option --with-qt-dir angegeben haben.

Dasselbe gilt für das kdelibs Paket, wechseln Sie einfach in das kdelibs Verzeichnis, und geben Sie das gleiche wie beim kdesupport Paket ein.

Sie haben nun alle Bibliotheken, die zum Erstellen einer KDE 2 Applikation erforderlich sind.

Nebenbei bemerkt, das KDE Bibliothekspaket enthält die Unterstützung zur automatischen Generierung der HTML Dokumentation, die Sie auf einfache Weise erhalten können, indem Sie Kdoc installieren. Geben Sie dann im kdelibs Verzeichnis `makekdedoc` ein, und Kdoc erstellt die Bibliotheksdokumentation im Unterverzeichnis `srcdoc`. In KDevelop können Sie online auf die Dokumentation zugreifen, wenn Sie im Setup Dialog den Pfad zur KDE Bibliotheksdokumentation auf `kdelibs/srcdoc` einstellen. Auf die gleiche Art und Weise geben Sie auch den Pfad zu Ihrer Qt Dokumentation an, indem Sie `/usr/lib/qt-2.1/html` einstellen. Sie können so die Dokumentation direkt aus KDevelop heraus erreichen.

Wenn Sie erfolgreich die Qt-2.1 und KDE 2 Bibliotheken installiert haben, fehlt nur noch ein Schritt bis zur Erstellung Ihrer ersten KDE 2 Applikation, Sie müssen nur KDevelop die nötigen Informationen geben, wo Sie die Bibliotheken installiert haben. Dies werden wir im nächsten Abschnitt machen.

### Einrichten von KDevelop

Dieser Teil ist der einfachste des ganzen Konfigurationsprozesses für die KDE 2 Entwicklung: das Einrichten von KDevelop. Öffnen Sie einfach KDevelop und wählen Sie "Einstellungen"->"KDevelop Setup" um die KDevelop Konfiguration zu erreichen. Wechseln Sie dort zur letzten Seite namens "Pfad". Diese Seite enthält zwei Eingabefelder, eins für die Qt und eins für die KDE 2 Bibliotheken. Da Sie alles erfolgreich installiert haben, wählen Sie `/usr/lib/qt-2.1` für den Qt-2.x Bibliothekspfad und `/opt/kde2` für KDE 2. Drücken Sie dann den "OK" Knopf, und Sie sind fertig.

Benutzer, die nur für Qt-2.x (inklusive der aktuellen Qt-2.0.2 Version) entwickeln wollen, müssen nur den Qt-2.x Pfad setzen.

Wir können nun zum nächsten Kapitel weitergehen, in dem wir den ersten Schritt des KScribble Beispielprogrammes erstellen werden.

## Anwendungskonzepte

Dieses Kapitel stellt Ihnen die Ideen der bereits erwähnten Applikationsmodelle vor: des MDI (Multiple Document Interface) und des Document View Modells. Eine Basiseinführung in das Doc-View Modell und die Generierung eines Projektes mit KDevelop ist schon im KDevelop Programming Handbook enthalten, aber auf Basis des Single Document Interfaces (SDI). In jedem Falle sollten Sie mit den Grundlagen der KDE und Qt Klassen vertraut sein, die in The KDE Library Reference Guide erklärt sind. Die Basisklassen der darunterliegenden Bibliotheken und ihre Verwendung werden dort im Detail beschrieben, außerdem finden Sie zusätzlich eine Beschreibung des Qt Signal-Slot Verfahrens und der Ereignisverarbeitung.

Sie sollten bereits wissen:

- Daß jede KDE Applikation eine Instanz von `KApplication` (bei Qt eine Instanz von `QApplication`) benötigt

- Generell ein Main Fenster von `KTMainWindow` bei KDE (for Qt von `QMainWindow`) abgeleitet ist

- Daß Ereignisse (z.B. Tastendrücke oder Mausbewegungen) von darunterliegenden Window System an die Anwendung gesendet werden

- Daß `QWidget` als Basisklasse für alle Graphical User Interface (GUI) Komponenten spezialisierte Event Handler durch virtuelle Funktionen bereitstellt, die die entsprechenden Events durch Reimplementation von `QObject::event()` ausfiltern

- Daß das Qt Signal-Slot Verfahren bedeutet, daß eine Komponente Signale aussendet und ein Empfänger sich mit diesen Signalen verbinden kann, um bestimmte Aktionen auszuführen.

- Daß dieser Mechanismus es unnötig macht, Klassen für GUI Komponenten zu beerben, weil die Signale Sie über z.B. Mausereignisse informieren, wo es passend für ein Widget ist.

### Das Document-View Modell

Das Document-View Modell ist eines der grundlegendsten Konzepte für GUI basiertes Anwendungsdesign. Daher ist ein gewisses Verständnis für das "Warum" nötig, um zu erkennen, daß es Sinn macht es zu benutzen, obwohl der Programmierer andere Möglichkeiten hat. Lassen Sie uns aber zuerst einen Blick auf das normale Design einer typischen KDE / Qt Applikation werfen:

Die Instanz Ihrer Anwendung stellt die erste Verbindung zu Ihrer Applikation zu Verfügung und ist der Startpunkt der Ereignisbehandlung eines Programms. Das Programm selber präsentiert sich dem Benutzer über eine graphische Benutzerschnittstelle, die meistens Hauptfenster genannt wird. Das Hauptfenster stellt dann dem Benutzer die erforderlichen Funktionen zur Verfügung, wie Tastenkürzel, eine Menüleiste, eine Werkzeugleiste und eine Statuszeile. Im Zentrum befindet sich ein sogenannter "Ansichtsbereich", was bedeutet, daß dieser Bereich die Instanz einer anderen Klasse ist, normalerweise "Ansicht" genannt. Diese Instanz wird erzeugt, wenn das Hauptfenster beim Programmstart konstruiert wird und muß mit Hilfe einer Methode des Hauptfensters zum Ansichtsbereich gemacht werden: `setView(your&&lowbar;;view)` bei KDE Applikationen die `KTMainWindow` verwenden, `setCentralWidget(your&&lowbar;;view)` bei Qt Applikationen die `QMainWindow` verwenden. Nun, offensichtlich ist die Ansicht der Bereich, der verantwortlich für die Interaktion mit dem Benutzer ist, wenn die Daten, die er repräsentiert manipuliert werden sollen. Zum Beispiel könnten Sie `QMultiLineEdit` als Ansicht verwenden, und Sie hätten einen Editor. Dann könnten Sie die gegebenen Slots der Ansicht zur Kommunikation mit der Menüleiste oder der Werkzeugleiste folgendermaßen verwenden:

Während Sie die Menüleiste erstellen, möchten Sie eine Methode für das Kommando "Cut" im "Edit" Menü bereitstellen:

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

Dies erzeugt einen Menüeintrag im "Edit" Menü, der, wenn aktiviert, direkt den Slot `cut()` der Instanz `view` aufruft, wobei wir davon ausgehen, daß Sie diese Instanz als `QMultiLineEdit` erzeugt und sie zum Ansichtsbereich gemacht haben. Der Slot des MultiLineEdit wird aufgerufen und schneidet als Reaktion den ausgewählten Text aus, die Funktionalität dazu wird bereits von der Klasse selbst zur Verfügung gestellt, es gibt also keine Notwendigkeit von `QMultiLineEdit` zu erben, um einen Ansichtsbereich zu erzeugen, der fähig zu solchen Aktionen ist. Diese Funktionen können sofort aufgerufen werden und machen das Erstellen von Anwendungen sehr einfach; Sie brauchen nur die Anwendungsinstanz und das Hauptfenster mit den Verbindungen zu Ihrem Ansichtsbereich, und das ist alles ! Das bedeutet, ein einfacher Editor kann geschrieben werden, indem man eine einzige Klasse erzeugt, die das Hauptfenster definiert und das Ein- und Auslesen von Daten implementiert- nur einige Basisslots, die Ihre Hauptansicht selbst implementieren muß.

Aber hier ist nun der Grund für dieses mysteriöse Document-View Modell: Sie müssen selbst Methoden zur Verfügung stellen, um die Dateien mit dem `QMultiLineEdit` Ansichtsbereich lesen und schreiben zu können, und zwar innerhalb des Hauptfensterinterfaces. Das ist offensichtlich der einfachste und logischste Weg. Nun, wenn wir die Dateien und ihren Inhalt als ein sogenanntes "Dokument" betrachten, das wir mit dem Attribut eines "Objektes" in der C++ Terminologie beschreiben, ist der nächste Schritt nur ein kleiner: Ich habe ein Dokument, eine Ansicht und ein Hauptfenster- warum trenne ich diese drei Objekte nicht voneinander? Wir könnten auf einfache Weise eine kleine Klasse erzeugen, die eine Datei in einen Textstream liest und dann die Ansicht aufrufen, um diesen Text für den Benutzer sichtbar zu machen. Dasselbe gilt auch wieder für das Speichern der Datei- Die Dokumentklasse sollte dann eine Methode anbieten, den Text aus der Ansicht zu lesen und als Datei zu speichern. Im Beispiel wären diese beiden Methoden die einzigen Aktionen die von der Dokumentklasse ausgeführt werden müssten, weil die Edit-Ansicht bereits alle Methoden über Slots bereitstellt, die für einen Editor benötigt werden, und Sie können den Inhalt der Ansicht direkt über diese Slots manipulieren.

Nun, die Hauptidee hinter dieser Aufteilung in drei Objekte (Dokument, Ansicht, Hauptfenster), anstatt der minimal zwei (Ansicht und Hauptfenster), ist folgende Frage: Was, wenn ich dem Benutzer die Möglichkeit geben will mit zwei oder sogar mehr Ansichten zu arbeiten? Solche Dinge kann man sogar mit nur einem Hauptfenster durch Trenner erreichen, wobei zwei Instanzen einer Datei angezeigt werden. Die Lösung kann dann nur sein, daß, wenn der Benutzer den Dateiinhalt in einem Fenster verändert, die andere Ansicht darüber informiert wird und ihren Inhalt aktualisieren muß. Sonst würde folgendes passieren: Wenn der Benutzer in einer Ansicht am Anfang etwas ausschneidet, dann am Ende der anderen Ansicht einfügt und dann die erste Ansicht schließt, würde die Datei mit beiden Teilen gespeichert, weil die zweite Ansicht nichts von dem Ausschneiden weiß. Das bedeutet, daß die beiden Asichten synchronisiert werden müssen, und das kann erreicht werden, indem alle Ansichten ständig über alle Benutzeraktionen informiert werden. Welche Ansicht der User tatsächlich verwendet sollte egal sein. Das bedeutet, daß schließlich eine Dokumentklasse benötigt wird, die alleine den wahren Inhalt des Dokumentes enthält und den Ansichten die Möglichkeit bietet, diesen zu verändern.

Ich hoffe, dies konnte einen Einblick in dieses Modell geben, obwohl es in den meisten Fällen so scheint, als ob der Programmierer ohne es leben könne- genauso könnte er auch ein Widget selber schreiben, das die Benutzeraktionen behandelt, anstatt einfach eine vorgebene Klasse als Ansichtsbereich zu verwenden, solange Sie nur ein Dokument in einem Ansichtsbereich präsentieren, kann die Ansicht auch für die Daten verantwortlich sein und nur Methoden zum Lesen oder Speichern einer Datei zur Verfügung stellen. Das nächste beschriebene Modell wird sich in diesem Punkt unterscheiden- dort werden Sie die tatsächliche Notwendigkeit und die Funktionalität, die die Dokumentklasse bereitstellt, erkennen.

### Das Multiple Document Interface (MDI)

Da der letzte Abschnitt das Document View Modell beschrieben hat, können Sie sicher erraten, was MDI bedeutet. Benutzer die von anderen Plattformen als Unix/Linux kommen, sowie die Programmierer, die für diese Plattformen entwickeln, sind daran gewöhnt. Da X-Window Applikationen traditionell mehr auf Funktionalität und Stabilität abzielen, sind Unix Anwender an einzelne Fenster gewöhnt, die Funktionalität bieten, weshalb oft nicht einmal das Document View Modell benötigt wird, um Anwendungen zu erstellen. Mit Qt als Multi-Plattform Werkzeug, haben Entwickler sogar noch mehr Auswahl- die Entwicklung für sowohl Unix, als auch MS Windows(tm). Auf Windows sind Applikationen, die sogenannte Kind Fenster verwalten können zu einer Art Standard geworden und die Qt 2.1 Bibliothek zielt darauf ab, daß auch Unix Benutzer von dieser Architektur profitieren können.

Was ist nun die Bedeutung von MDI? Eine MDI Applikation hat das gleiche Konzept, wie eine gewöhnliche Anwendung, die, wie oben beschrieben, eine Anwendungsinstanz und ein Hauptfenster hat. Der Ansichtsbereich macht nun den Unterschied: Sie verwenden nicht direkt eine Ansicht, die Daten repräsentiert und Interaktion zur Manipulation der Daten bereitstellt, sondern eine Ansicht, die andere Fenster verwaltet, die wie top-level Fenster aussehen. Diese Fenster repräsentieren nun den ehemaligen Ansichtsbereich und der Hauptunterschied ist, daß die Interaktionskette sich von

    Applikations Instanz -> Hauptfenster -> Ansicht

    nach

    Applikations Instanz -> Hauptfenster -> Ansicht -> Aktives Kind Fenster

ändert.

Die Ansicht beherrscht nun verschiedene Aktionen:

- Erzeugen von so vielen Kindfenstern, wie der Benutzer anfordert

- Zur Verfügung stellen von Methoden, zur Ermittlung des aktiven Kindfensters

- Verwaltung einer Liste offener Ansichten

- Steuern des Verhaltens des Kindfensters bei Maximieren, Minimieren, so wie es der Fenstermanager für top-level Fenster macht

Nun, Sie können komplette Widgets wie `QMultiLineEdit` als Kindfenster für Anwendungen verwenden, die nur ein Fenster bereitstellen, und jedes Fenster ist für seine eigenen Daten verantwortlich. Dies könnte als Multiple Document Interface beschrieben werden, wobei jedes Fenster wie ein einzelnes Dokument ist. Die Anwendung verwaltet dann die üblichen Aktionen, wie Bereitstellen von Methoden zur Interaktion, wie Ausschneiden und Kopieren. Die Erweiterung dieses Konzepts um das Document View Modell, verbessert die Möglichkeiten bei weitem: stellen Sie sich vor, daß Sie innerhalb des Haupfensters so viele Fenster öffnen können wie Sie wollen und das ein Kindfenster, die neue Ansicht eines Dokuments sein kann, das bereits in einem anderen Kindfenster gezeigt wird. Die Verwaltung hierfür macht die schon beschriebene Aufteilung in das drei Objekte Modell nötig, aber limitiert weder die Anzahl der tatsächlichen Instanzen, noch die der Ansichten.

Glücklicherweise enthält Qt 2.1 die Möglichkeit solche Anwendungen zu schreiben und KDevelop bietet Ihnen die entsprechenden Anwendungsschablonen für beides, sowohl nur-Qt Programme als auch KDE 2 Programme mit denselben Interface Methoden; Sie können also beide Typen adequat erstellen. Die Verwendung des KDE 2 Interfaces bietet Ihnen sogar noch mehr Möglichkeiten durch Bibliotheksfunktionen sowie Interprozesskommunikation, dies sind jedoch spezielle Aspekte die durch separate Einführungen abgedeckt werden müssen.

Sie sind nun vorbereitet der Entwicklung für KDE 2 zu folgen.Lesen Sie einfach das nächste Kapitel, um einen ersten Einblick in die Funktionalität zu erhalten, die bereits von KDevelop zur Verfügung gestellt wird, wenn sie Anwendungen erstellen. Dort werden wir das Framework für unsere Beispielanwendung KScribble generieren und die praktischen Aspekte der Programmierung von MDI Applikationen beschreiben.

## Das MDI Framework

Wenn Sie bereits frühere Versionen von KDevelop kennen, werden Sie wissen, daß Ihre IDE sogenannte "Frameworks" als Startpunkt der Anwendungsentwicklung verwendet. Diese Frameworks enthalten standardmäßig die Unterstützung für ein generisches Document View Modell, waren aber bisher auf SDI Strukturen (Single Document Interface, ein Fenster mit einem Ansichtsbereich) limitiert. Seit KDevelop 1.1, kann der Application Wizard eine Menge mehr als die vorhergehenden Versionen: er bietet einige neue Projekttypen insbesondere für jene Programmierer an, die up-to-date mit der kommenden KDE 2, sowie mit Qt 2.1 bleiben wollen. Eine der Haupterweiterungen sind MDI Frameworks für sowohl KDE 2 als auch Qt 2.1. Sie unterscheiden sich nur in der Verwendung einiger Bibliotheksfunktionen, wie Dateidialoge oder Message Boxen, sowie den verwendeten Werkzeugleisten- und Statuszeilenklassen- die KDE Version verwendet KDE 2 Methoden, die Qt Version die entsprechenden Qt Funktionen.

Zur Erstellung des ersten Schrittes der Beispielanwendung KScribble, müssen Sie sicherstellen, daß Sie die Anweisungen in Kapitel 5 dieser Anleitung befolgt haben: Installation von KDE 2. Wenn das alles funktioniert hat, kann nichts schiefgehen. Wenn Sie die KDE 2 Bibliotheken nicht installieren konnten, stellen Sie sicher, daß Qt 2.1 installiert wurde und konfigurieren Sie KDevelop wenigstens für Qt 2.1. Sie können dieser Anleitung auch folgen, wenn Sie das entsprechende Qt Framework wählen (mit den oben beschriebenen Unterschieden: andere Methodenaufrufe für Dialoge und GUI Komponenten).

### Erzeugen eines MDI Frameworks

Nun werden wir uns in die Entwicklung stürzen, indem wir den Rahmen für KScribble erzeugen. Starten Sie KDevelop und wählen Sie "Projekt"-"Neu" aus dem Menü, um den Applikation Wizard zu starten. Auf der ersten Seite wird Ihnen ein Baum mit Projekttypen angezeigt. Dieser enthält KDE und Qt Projekte. Wählen Sie "KDE 2 MDI" aus dem KDE Abschnitt. Wenn Sie nur Qt 2.1 installiert haben, wählen Sie "Qt 2.1 MDI" aus dem Qt Abschnitt. Dann wählen Sie "Weiter", geben "KScribble" als Projeknamen und 1.0 als Versionsnummer ein. Fügen Sie Ihren Namen und Ihre E-Mail Adresse in die entsprechenden Felder ein, fertig. Klicken Sie auf den "Erzeugen" Knopf und KDevelop wird unsere erste Version von KScribble erzeugen, während Sie die Ausgabe auf der letzten Seite des AppWiz verfolgen können. Schließlich drücken Sie noch "Beenden" um zu KDevelop zurückzukehren. Im Ausgabefenster sehen Sie eine weitere Meldung, daß die Message Datei für Ihr Projekt erstellt wurde, was wichtig sein wird, wenn Sie KScribble in andere Sprachen übersetzen.

### Übersicht

Nun ist es wichtig, daß Sie wissen wo Sie was in den generierten Quellen finden und wo Ihr Platz als Programmierer ist, damit KScribble eine einzigartige KDE Applikation wird.

Zunächst einmal sehen Sie in der Klassenansicht drei, bereits erzeugte Klassen, `KScribbleApp`, `KScribbleDoc` und `KScribbleView`. Wie oben erklärt, spielt jede von ihnen eine bestimmte Rolle in Ihrer Anwendung; die App Klasse bildet das Hauptfenster und koordiniert die Benutzerinteraktion, die Doc Klasse verwaltet die Dokumente, die eine Anwendung manipulieren kann; schließlich ist die View Klasse verantwortlich für die Benutzerinteraktion mit den Kindfenstern und kommuniziert mit dem ihr angeschlossenen Dokument. Dies hat verschiedene Konsequenzen: um guten Nutzen aus der gegebenen Funktionalität zu ziehen, muß sich der Programmierer an die gegebene View Klasse halten. Standardmäßig erbt die Ansicht `QWidget` als minimale Anforderung, aber Sie sind bei weitem nicht darauf beschränkt. Von einer anderen Klasse zu erben, ist nicht allzuviel Arbeit und schließlich gibt es noch die Möglichkeit, z.B. `QMainWindow` oder `KTMainWindow` als zu beerbende Klasse zu verwenden und eine andere Klasse für der Verwaltung der Ansicht zu benutzen.

Im nächsten Kapitel werden wir fortfahren KScribble um die Verwaltung seiner Dokumente zu erweitern. Danach werden wir die Benutzerinteraktion implementieren und Sie werden die erste Funktionalität unserer Anwendung sehen: wir können Bilder zeichnen, sie laden und sie speichern.

## Definition der Dokumente

Der erste Schritt beim Erstellen einer Applikation, die auf dem Document-View Modell basiert, sollte immer sein, sich zu überlegen, welche Arten von Daten die Anwendung verwalten muß. Dies entscheidet, wie die View Klasse aussehen wird, aber insbesondere, wie die Dokumentklasse Daten in und aus Dateien lesen und schreiben wird und welche Methoden angeboten werden, um die Daten zu manipulieren. Da KScribble eine einfache Zeichenanwendung sein wird, die mit graphischen Daten umgeht, werden wir die Qt Klasse `QPixmap` dazu verwenden, unsere Daten zu speichen während wir editieren. `QPixmap` bietet auch einfache Methoden, Bilder in und aus Dateien zu lesen und zu schreiben, so daß die Serialisation mit zwei Zeilen erreicht werden kann, eine zum Lesen und eine zum Schreiben. Weiterhin müssen wir einen Stift definieren, der in ein Dokument schreibt, seine Breite und Farbe einstellen und der View Klasse ermöglichen, den Stift anzusprechen - tatsächlich wollen Sie, daß die Ansicht die Zeichenmethoden anbietet, aber das Dokument als zentrales Element für alle Ansichten, muß das Original des Stiftes besitzen, da sonst verschiedene Ansichten desselben Dokuments verschiedene Stifte verwenden würden!

Daher fügen wir zur Definition der Arbeitsweise unserer Dokumentklasse eine Instanz von `QPixmap` und eine von `QPen` hinzu und editieren die Methoden `newDocument()`, `openDocument()` und `saveDocument()`.

### Hinzufügen der Instanzen

Öffnen Sie die Datei `kscribbledoc.h` durch Auswahl in einer der Baumansichten oder durch einen Klick in der Klassenansicht über `KScribbleDoc`. Dann fügen Sie die mit -> markierten Zeilen aus dem folgenden Codeausschnit hinzu:

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

Wie Sie sehen, haben wir pen und buffer, sowie `currentPen()` und `penWidth()` hinzugefügt. Da pen als private deklariert ist, bieten wir ein Möglichkeiten sowohl auf den Stift selber, als auch auf die Stiftbreite zuzugreifen. Da diese bereits in der Klassendeklaration implementiert sind, müssen wir sie nicht der Implementationsdatei hinzufügen, der wir uns nun zuwenden.

### Initialisierung des Dokuments

Wählen Sie die Methode `newDocument()` in der `KScribbleDoc`-Klasse um zur Methodendeklaration zu springen. Hier fügen wir nur eine, mit dem Pfeil markierte Zeile ein:

      kscribbledoc.cpp

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

Dies initialisiert den Stift mit der Farbe Schwarz und einer Weite von drei Pixeln; die `QPen` Klasse hat noch einige weitere Konstruktoren, aber dieser hier reicht für unsere Zwecke.

### Implementierung der Serialisation

Was noch zu tun ist, ist zu definieren, wie wir unsere Bilder öffnen und speichern wollen. Dies wird in den entsprechenden Methoden erledigt:

        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();

Fügen Sie wieder die Zeilen mit dem Pfeil Ihrem Code hinzu. Was wir getan haben, ist die Zeilen, in denen `Dateiname` geöffnet wird, auszukommentieren, weil dies automatisch von den Laden- und Speichermethoden von `QPixmap` erledigt wird, die wir stattdessen hinzufügen. Andere Dokumente mögen Dateien öffnen und deren Inhalt ( Textzeilen oder was auch immer) lesen, weshalb die `QFile` Methoden schon im Frame enthalten sind.Da `save()` und `load()` bei Erfolg einen Boolschen Wert zurückliefern, Geben wir false zurück, wenn nicht, so daß der Rest der Anwendung einen Returnwert erhält und Warnungen ausgibt, wenn die Operation nicht erfolgreich war. Die `load()` und `save()` Methoden sind nun schon in `QPixmap` enthalten. Sie benötigen den Dateinamen und das Format als Argument; die Quellen rufen die Dokument Methoden jedoch noch nicht mit dem Format auf. Wenn nur ein Format verwendet wird, reicht es, dieses hier zu setzen, andere Methoden könnten das Format zum Beispiel ermitteln - aber wir werden später noch einmal darauf zurückkommen. Für jetzt verwenden wir "PNG" als Format; sehen Sie unter `QImageIO` nach, um mehr über Bildformate zu erfahren.

Nun sind wir fast mit der Definition unserer Dokumentstruktur fertig. Der `QPixmap` Puffer dient uns als Speicher für unser Originalbild, während wir daran arbeiten. Der Stift ist gültig für alle Ansichten, die mit dem Dokument verbunden sind. Beachten Sie, daß die Initialisierung des Stiftes in `newDocument` erfolgt. Diese Methode wird immer intern nach dem Konstruktor aufgerufen, Sie sollten also dort die Instanzinitialisierungen vornehmen, wie wir es mit dem Stift gemacht haben.

Im nächsten Kapitel werden wir uns der View Klasse widmen, um zu definieren, wie die Ansicht mit dem Benutzer kooperieren soll und wie sie auf das Dokument zugreift, und dann sind wir bereit zu zeichnen !

## Definition der Ansicht

### Interaktion mit dem Benutzer

In diesem Kapitel werden wir uns der View Klasse von KScribble zuwenden, um zu definieren, wie die Kindfenster arbeiten sollen. Als erstes sehen wir, daß KScribbleView standarmäßig von `QWidget` abgeleitet ist. Das ist die Mindestanforderung für ein Kindfenster, aber es reicht bereits für unsere Bedürfnisse aus. Wenn es darum geht, das Verhalten eines neuen Widgets zu definieren, müssen wir wissen, wie der Benutzer mit dem Fenster interagieren soll. In unserem Beispiel, soll dies offensichtlich mit Hilfe der Maus geschehen. Daher müssen wir einige virtuelle Methoden von `QWidget` überschreiben, die die Mausereignisse verarbeiten die unser Widget empfängt. Was wir wissen müssen ist, wann der Benutzer eine Maustaste drückt, weil nur gezeichnet werden soll, wenn eine Maustaste gedrückt ist. Außerdem müssen wir wissen, wann die Maus bewegt wird (und wohin), sowie den Zeitpunkt, zu dem die Maustaste losgelassen wird, da dann der Zeichenzug beendet ist. Weiterhin wollen wir, daß unser Bild im Fenster gezeichnet wird und seine Größe angepasst wird, wenn der Benutzer sich entschließt, das Fenster in der Größe zu verändern. Wir werden auch ein Member `QPointArray` und einen boolschen Wert mousePressed hinzufügen. Fügen Sie den, mit dem Pfeil gekennzeichneten Code, Ihrer Klasse KScribbleView hinzu:

       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;

         }

### Reimplementierung von Event Handlern

Wir kommen jetzt zu der tatsächlichen Implementierung der Ereignis Handler. Wie in The KDE Library Reference Guide erkärt, hat Qt gute Methoden, Ereignisse zu behandeln, besonders wenn die Ziele Widgets sind. `QWidget` als Basisklasse, preselektiert die Events und stellt Basis Event Handler zur Verfügung, die, da sie als virtuell deklariert sind, überladen werden können, so daß wir definieren können, wie unser Widget auf Ereignisse reagieren soll. Eine Methode ist bereits überladen: die `closeEvent()` Methode. Dies ist notwendig, weil unser Hauptfenster, repräsentiert in der App Klasse, bereits das Schließen von Kind Fenstern preselektiert und dies behandelt; daher muß der Standard Event Handler, der nur das Schließen akzeptiert, überschrieben werden und diese Arbeit die App Klasse machen lassen.

Als erstes müssen wir im Konstruktor das Standardverhalten des Widgets deklarieren, indem wir Member initialisieren und vordefinierte Werte setzen:

        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);
        }

Wir setzen den Hintergrund auf NoBackground, setzen einen Cursor (crossCursor) und initialisieren mousePressed und polyline. Nun beginnen wir mit der Implementation unseres ersten Ereignis Handlers, `mousePressEvent()`, um zu erkennen wann der Benutzer die Maus drückt und wo.

Beachten Sie: Die folgenden Implementationen müssen komplett eingefügt werden, es gibt also keinen Pfeil !

    void KScribbleView::mousePressEvent( QMouseEvent *e )
    {
      mousePressed = TRUE;
      polyline[2] = polyline[1] = polyline[0] = e->pos();
    }

Hier setzen wir mousePressed auf true, wir haben das Event also irgendwie behandelt. Die zweite Zeile ist nicht so offensichtlich: wir speichern die Position, an der die Maustaste gedrückt wurde, in den ersten drei Elementen unseres Feldes. Da das Feld ein `QPointArray` ist, kann es Werte vom Typ `QPoint` speichern (die selber wieder einen x und y Wert enthalten). Wir werden in diesem Feld Mauspositionen speichern und daraus die Zeichenroutine im mouseMoveEvent entwickeln:

    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() );
      }
    }

Dieser Event Handler ist wahrscheinlich der schwierigste, wir werden ihn also Schritt für Schritt durchgehen, um zu verstehen was gemacht wird. Als erstes empfängt der Handler alle Mausbewegungen über dem Widget. Da wir aber nur an Bewegungen interessiert sind, wenn gleichzeitig eine Maustaste gedrückt ist, weil dann gezeichnet werden muß, haben wir gefragt ob mousePressed true ist. Dies wurde vom `mousePressEvent()` Handler bereits gemacht, daher brauchen wir uns darum nicht weiter zu kümmern. Nun beginnen wir zu zeichnen. Als erstes erzeugen wir einen `QPainter` und lassen ihn in den Puffer des Dokumentes zeichnen. Das ist wichtig, da der Puffer die wirklichen Daten enthält, die Ansicht dient nur als Kommunikator zwischen Dokument und Benutzer. Den Stift holen wir ebenfalls aus der Dokumentinstanz, indem wir `currentPen()` aufrufen. Die nächsten drei Zeilen weisen die Werte des polyline `QPoint` zu und setzen Punkt 2 auf 1, 1 auf 0 und 0 auf den Punkt, zu dem die Mausbewegung ging (das ist der Wert an dem wir interessiert sind). Angenommen wir haben gerade die Maus gedrückt (also enthalten alle Werte diese Position) und das erste Mausereignis, das die Position enthält, zu der eine Linie gezeichnet werden soll, findet statt; dann wird dieser Wert wieder in das erste Element des Feldes eingetragen. Sie mögen sich fragen, warum wir dann drei Elemente im Feld brauchen, wenn wir nur eine Linie von einer zu nächsten Position zeichnen wollen. Die folgenden Zeilen erklären das: nachdem die Übertragung in unseren Puffer erfolgt ist (mit `drawPolyline()` und `painter.end()`), erstellen wir ein Rechteck r und verwenden `boundingRect()` von `QPointArray` um ein `QRect` zu erhalten, das alle drei Punkte enthält. Daher brauchen wir die drei Punkte, um ein fast fertiges Rechteck zu erhalten. Dann verwenden wir `normalize()`, damit der linke, obere Wert am kleinsten ist (da Koordinaten von links, oben nach rechts, unten zunehmen). Das nächste ist, die Größe des Rechtecks der Breite des Stiftes anzupassen, weil der Stift eine bestimmte Breite hat, die wir mit `penWidth()` ermitteln, und um das Rechteck um die Breite des Stiftes zu erweitern (Stellen Sie sich vor, die Maus wäre nur um zwei Pixel bewegt worden, der Stift hätte aber eine Breite von 10 Pixeln, dann würde das Rechteck nicht den ganzen gezeichneten Bereich enthalten). Schließlich markieren wir das Dokument noch als modifiziert und verwenden die `bitBlt()` Funktion, um das Rechteck aus dem Puffer in das Widget zu kopieren. bitBlt arbeitet bitweise und ist sehr schnell, dies ist also eine bessere Methode den gezeichneten Bereich in das Widget zu kopieren, als das ganze Widget neuzuzeichnen. Seine Argumente sind: Erst das Objekt in das gezeichnet werden soll (das Ziel), in diesem Fall ist es unser Widget, wir müssen also den Zeiger this verwenden. Die nächsten beiden Argumente sind die linke, obere Ecke des Zieles, gefolgt von der Quelle, mit deren Höhe und Breite. Da die pixmap Koordinaten die gleichen sind, die auch unser Widget verwendet (weil unser Pixmap in der linken oberen Ecke gezeichnet wurde), sind die Koordinaten für den linken, oberen Punkt bei Quelle und Ziel identisch. Darauf muß in einem der nächsten Schritte geachtet werden, daher wird es hier schon erwähnt. Als nächstes kommt, was beim Loslassen der Maustaste geschieht. Es muß dann aufgehört werden bei Mausbewegung zu zeichnen, also setzen wir mousePressed auf false:

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

Wir sind jetzt mit der Implementierung der Benutzerinteraktion fertig, was die Zeichenfunktionen angeht. Das Beispiel zeigt, daß es nicht allzu kompliziert ist, ein Document View Modell zu benutzen. Erzeugen Sie nur die Dokumentinstanz, so daß sie die Inhalte enthält, und kopieren Sie die Inhalte in Ihre Ansicht.

### Zeichnen und Größe des Dokuments ändern.

Übrig bleiben zwei virtuelle Handler, die reimplementiert werden müssen. Als erstes müssen wir darauf achten, daß unser Bild im Fenster neu gezeichnet wird, wenn etwas anderes passiert: wenn Sie ein anderes Fenster öffnen, das die Zeichnung verdeckt, wird sie nicht mehr da sein, wenn sie wieder zu Ihrer Zeichnung wechseln, es sei denn, Ihr Zeichenereignis wird ausgeführt, so daß das Bild neu gezeichnet wird:

    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() );
    }

Diese Methode verwendet ebenfalls `bitBlt()` zum Zeichnen des Puffers in das Widget. Hier brauchen wir nur den Ausschnitt der, neugezeichnet wird, also holen wir uns die Geometrie des Events ( `e->rect()` ) und verwenden die Koordinaten für `bitBlt()` genau wie wir es bei `mouseMoveEvent()` gemacht haben.

Das einzige, um das wir uns nicht gekümmert haben, ist die Größe des Pixmaps. Wir haben sie nirgendwo gesetzt - wir haben noch nicht einmal das Pixmap aus der Dokumentklasse benutzt, außer zum Laden und Speichern- aber diese Methoden werden nicht aufgerufen, wenn ein neues Bild erzeugt wird. Es scheint also, unser Pixmap habe weder eine Größe noch einen vordefinierten Hintergrund (selbst wenn wir die Größe gesetzt hätten, wären die Inhalte zufällige Farben, weil es nicht initialisiert wurde). Andererseits haben wir die Tatsache, daß die KScribbleView Instanzen in der Größe angepasst werden wenn sie angezeigt werden- wenigstens auf die Minimalgröße. Das ist der Punkt, an dem wir auch die Initialisierung vornehmen können, weil der Benutzer die Größe manuell ändern kann und das Widget ebenfalls ein resize Event erhält. Aus Gründen der Einfachheit, setzen wir die Pixmap Größe gleich der Widgetgröße. All dies geschieht im 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() );
    }

Hier wird zunächst der resizeEvent Handler von `QWidget` aufgerufen. Dann berechnen wir die Größe unseres Bildes- da wir ein Fenster sowohl kleiner als auch größer machen können, müssen wir diese beiden Fälle unterscheiden: wenn wir verkleinern, soll das Bild immer noch seinen gesamten Inhalt behalten. Wenn wir jedoch das Widget vergrößern, müssen wir das Pixmap auch auf diese neue Größe bringen. Die errechneten Werte werden in w und h gespeichert. Bevor jedoch die Größe verändert wird, erzeugen wir eine Kopie des Pixmaps in tmp. Dann verändern wir den Puffer (das Dokument), füllen es mit weißer Farbe und kopieren dann den Inhalt von tmp in den Puffer zurück. Die ändert unser Pixmap immer synchron mit dem Widget, von dem es angezeigt wird, aber es verliert nicht die Daten, die außerhalb des sichtbaren Bereiches liegen, wenn das Widget verkleinert wird.

Wir sind an einem Punkt angekommen, an dem wir die Funktionalität unserer Anwendung testen können. Drücken Sie "Ausführen", und nachdem KScribble angezeigt wird, sind Sie bereit, Ihr erstes Bild damit zu zeichnen !

## Erweiterung der GUI

Wie wir gesehen haben, haben wir KScribble bereits die Fähigkeit gegeben, Bilder mit der Dokumentklasse zu öffnen und zu speichern, haben durch Überladen virtueller Methoden Interaktion ermöglicht, und wir haben die erste Funktionalität erreicht- wir können auch Bilder zeichnen. Aber als wir die `QPen` Instanz erzeugt haben, haben wir vordefinierte Werte für den Stift verwendet; die Farbe ist Schwarz und die Breite ist auf 3 Pixel eingestellt. Da Sie für gewöhnlich diese Werte ändern können wollen, müssen wir die Haupt-GUI um Funktionen, diese Werte zu setzen, erweitern, je nachdem welches Fenster gerade aktiv ist und welches Dokument damit verbunden ist. Dieses Kapitel wird Ihnen daher zeigen:

- Das Hinzufügen eines neuen Menüs zur Menüleiste

- Erweiterung der Werkzeugleiste um Icons, für die in der Menüleiste definierten Methoden

- Die Erstellung eines neuen Dialoges mit dem KDevelop Dialogeditor

- Verbinden der neuen Kommandos der Menü- und Werkzeugleiste mit Aufrufen der Dialoge

- Ermöglichen der Änderung der Stiftgröße in der Dokumentklasse

Desweiteren fügen wir eine Methode hinzu, den Inhalt des gesamten Dokuments über das Menü zu löschen.

### Hinzufügen des "Stift" Menüs

Wie der Name dieses Abschnittes schon sagt, werden wir hier ein Menü zum Setzen der Stiftwerte des Dokuments hinzufügen. Menüs, die in die Menüleiste eingefügt werden, sind Instanzen von `QPopupMenu`, und Sie können einen Blick darauf werfen, wie die aktuelle Menüleiste aufgebaut ist, wenn Sie zur `KScribbleApp` Klasse, Methode `initMenubar()` wechseln. Sie werden sehen, daß die Menüleistenelemente in der Reihenfolge ihres Erscheinens auf der Menüleiste erzeugt wurden, aber das ist nicht notwendigerweise so. Es gibt zwei Dinge, die für das Aussehen der Menüleiste wichtig sind:

- In den Menüs, die Reihenfolge der Einträge

- In der Menüleiste, die Reihenfolge der Popupmenüs

Schließlich und endlich werden wir zuerst die Menüs erzeugen müssen, indem wir den Konstruktor aufrufen. Die Klassendeklaration enthält bereits die Zeiger auf die Popupmenüs, also werden wir zuerst das "Stift" Menü hier einfügen:

    kscribbleapp.h

    class KScribbleApp
    {
    .
    .
      private:
        QPopupMenu* pPenMenu;

    }

Jetzt werden wir das Menü selbst erzeugen. Ändern Sie die Implementation der Methode `KScribbleApp::initMenuBar()` und editieren Sie die, mit dem Pfeil markierten Zeilen:

    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)));
    }

Sie sehen, daß wir zunächst das Menü mit `new QPopupMenu()` erzeugen. Dann verwenden wir die insertItem Methode, zum Hinzufügen der Menüeinträge Color und Brush. Die sichtbaren Kommandos werden mit der Methode `i18n()` eingefügt, womit sichergestellt ist, daß Sie Ihre Applikation internationalisieren können. Als generelle Regel kann gesagt werden, daß Sie alle später sichtbaren Texte mit der Methode `i18n()` deklarieren sollten. Nur-Qt Programme, die Qt > 2.0 verwenden, benutzen dazu die Methode `tr()`, da Qt eigene Verfahren hat, Anwendungen zu internationalisieren. Das zweite Argument ist ein Makro, die ID des Menüeintrages. Diese ID ist eine Nummer, die wir mit &&num;;define in der resource.h, in der Sie auch die anderen bisher definierten ID's finden, setzen müssen. Es gibt auch den Weg, Menüs direkt einzufügen, indem Sie einen Slot mit dem gewählten Eintrag verbinden, aber das Framework dieser Anwendung verwendet ID's, um festzustellen, welche Aktion ausgewählt und markiert wurde. Deshalb muß jeder Menüeintrag, unbhängig davon, in welchem Popupmenü er erscheint, eine einzigartige Nummer besitzen, und da wir uns Nummern schlecht merken können, ist die Verwendung eines &&num;;define für die ID eine gute Lösung. Das Popupmenü wird nun ebenfalls mit `insertItem()` der Menüleiste hinzugefügt, und zwar mit einem Zeiger auf das Menü als zweitem Argument. Beachten Sie, daß wir das Popupmenü nach dem Edit- und vor dem Viewmenü eingefügt haben, es wird also später zwischen diesen beiden Menüs erscheinen. Was auch wichtig bei der Erstellung von Menüs ist, ist, daß sie dem Benutzer auch über Shortcuts zur Verfügung gestellt werden sollten; normalerweise sehen Sie in Menüs einen unterstrichenen Buchstaben, der zusammen mit ALT die entsprechende Funktion aufruft. Als Programmierer, müssen Sie diesen Buchstaben durch ein vorangestelltes "&&;" setzen, das "Pen" Menü wird also später durch drücken von ALT+P erreichbar sein. Innerhalb des Menüs kann der Benutzer auch nur den Buchstaben eingeben, um den entsprechen Menüpunkt auszuführen, Sie sollten also für die anderen Menüpunkte ebenfalls Tastaturkürzel zur Verfügung stellen. Achten Sie darauf, den Code zum Einfügen von Einträgen in Gruppen zusammenzuschreiben, die Sie überblicken können, damit Sie keine Kürzel doppelt verwenden (Dies ist auch für Ihre Übersetzer wichtig: In anderen Sprachen kommt das gewählte Kürzel vielleicht nicht im Menütext vor, der Übersetzer muß also auch wieder ein Kürzel auswählen).

In den letzten beiden Zeilen verbinden wir die Stiftmenüs mit zwei Slots: einen für das Menüsignal "aktiviert, Aktion ausführen" und einen für die Markierung des Menüeintrages. Dies ermöglicht es, eine Hilfemeldung in der Stauszeile anzuzeigen. Sie können sich die Methoden ansehen, die mit dem Menü verbunden sind. Sie enthalten Anweisungen, in denen die gesendete ID geprüft und die folgende Aktion aufgerufen wird. Was noch zu tun bleibt, ist die &&num;;define Anweisungen in die resource.h einzutragen:

    resource.h ///////////////////////////////////////////////////////////////////
    // Pen-menu entries
    #define ID_PEN_COLOR                14010
    #define ID_PEN_BRUSH                14020

Sie sehen, daß die Nummern für jeden Eintrag einzigartig sind. Sie müssen darauf achten, nicht zweimal dieselbe Nummer zu vergeben. Sollte es aber dennoch einmal versehentlich passieren, wird Sie der Compiler über die Redefinition informieren.

Das ist zum jetzigen Zeitpunkt alles, das Sie zum Hinzufügen eines neuen Menüs tun müssen. Die ausgeführten Aktionen sind: "Color" (Aufruf des Farbauswahldialogs) und "Brush" (Aufruf des Dialogs zum Setzen der Stiftbreite). Letzeren Dialog werden wir noch erstellen, aber zuerst werden wir im nächsten Kapitel auch die Werkzeugleiste um zwei Icons für diese Aktionen erweitern.

### Hinzufügen von Knöpfen zur Werkzeugleiste

Wenn Sie neue Kommandos über Knöpfe in der Werkzeugleiste zur Verfügung stellen möchten, können Sie dies leicht mit der `initToolbar()` Methode der `App` Klasse tun. Hier entscheiden wir uns, je einen Knopf für die beiden Menüeinträge im Stiftmenü hinzuzufügen, aber diese Knöpfe brauchen Icons. Sie können sie entweder im KDE Verzeichnis "toolbar" finden, oder, wenn keins der Icons zu der Aktion paßt, sie selbst erzeugen. KIconEdit ist gut dafür geeignet Icons zu erstellen, also werden wir sie zuerst erzeugen. Wählen Sie "Neu" aus dem KDevelop Dateimenü, gehen Sie auf den Linux/KDE Tabulator und wählen Sie dann "Icon" als Dateityp. Das erste Icon nennen wir "pencolor.xpm". Nun müssen wir sagen, wo das Icon erzeugt werden soll. Drücken Sie den Verzeichnisauswahlknopf und wechseln Sie in das Verzeichnis mit Ihren KScribble Quelldateien. Dort erzeugen Sie ein neues Verzeichnis "toolbar". Wechseln Sie in dieses Verzeichnis und drücken Sie "OK". Das neue Icon wird erzeugt und automatisch mit KIconEdit innerhalb von KDevelop geöffnet. Zeichnen Sie etwas, daß dem Benutzer den Sinn des Knopfes zeigt, speichern Sie das Pixmap und wählen Sie dann den LFV / RFV in KDevelop. Wählen Sie das Icon mit der rechten Maustaste aus und gehen Sie im Popupmenü auf "Eigenschaften". Sie sehen, daß das Icon der Distribution hinzugefügt wurde, damit es aber später von Ihrem Programm gefunden werden kann, müssen Sie das Installationsziel ebenfalls angeben. Markieren Sie die "installieren" Option und geben Sie dann darunter ein:

`$(kde&&lowbar;;datadir)/kscribble/toolbar/pencolor.xpm`

Damit wird das Pixmap im data Verzeichnis der KDE Dateisystemhierarchie installiert, in der jede Anwendung ihr Unterverzeichnis mit zusätzlich benötigten Dateien hat. Icons müssen in einem Unterverzeichnis namens toolbar abgelegt werden, damit der icon loader die Pixmaps für Ihr Programm finden kann.

Wenn Sie damit fertig sind, wiederholen Sie die gleichen Schritte für das Icon zum Setzen der Stiftweite. Nennen Sie dieses Icon "penwidth.xpm".

Nun müssen wir nur noch Knöpfe auf der Werkzeugleiste erzeugen; fügen Sie dazu die markierten Zeilen Ihrem Code hinzu:

    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()),
    ..
    }

Wir verwenden an dieser Stelle die Methoden von KToolBar um die Knöpfe einzufügen. Das erste Argument, `BarIcon()`, veranlaßt die Methode, das Icon für den Knopf zu laden. Was etwas ungewöhnlich erscheint ist, daß wir uns nicht um die Dateierweiterung zu kümmern brauchen. Das bevorzugte Format unter KDE 2 ist *.PNG, aber es funktioniert auch mit xpm's ( Sie könnten ImageMagick für die Konvertierung der Icons nach PNG verwenden, oder, zu einem späteren Zeitpunkt,KScribble !).

Das zweite Argument ist wiederum die ID. Die Kommandos werden dann automatisch aktiviert, da `toolBar()` bereits mit denselben Methoden verbunden ist wie die Menübar, über das Signal `activated()`. Das dritte Argument steht für "erreichbar" wenn wahr, "deaktiviert" wenn falsch; da die Knöpfe verfügbar sein sollen, setzen wir es auf wahr. Schließlich setzen wir noch eine Kurzinfo für die Knöpfe, die wir wieder in `i18n()` einschließen, um die Internationalisierung zu ermöglichen.

Nun sind Sie für's erste fertig. Die GUI ist, zumindest visuell, erweitert. Sie können KScribble wieder kompilieren und ausführen und sich anschauen, wie es aussieht- natürlich haben die neuen Elemente in der Menü- und in der Werkzeugleiste noch keine Funktion- dies wird sich aber im nächsten Abschnitt ändern. Sie werden vielleicht auch bemerken, daß die Icons, die wir hinzugefügt haben, nicht angezeigt werden. Dies liegt daran, daß wir KScribble nicht installiert haben, und so werden sie nicht gefunden. Alle anderen Icons werden bereits mit den KDE Bibliotheken geliefert, deshalb sind sie schon sichtbar.

### Erzeugen des Dialogs für die Stiftweite

Da wir schon die Menüleisten- und Werkzeugleistenkommandos erstellt haben, müssen wir jetzt unseren ersten Dialog für das Setzen der Stiftweite erzeugen. Dazu wählen Sie "Neu" aus dem Dateimenü und dann "Qt/KDE Dialog". Geben Sie als Dialognamen `kpenbrushdlg` ein, die Erweiterung wird automatisch angehängt. Wählen Sie "OK" und der Dialogeditor öffnet ein leeres Widget, das den Hintergrund für unseren Dialog bilden wird. Wenn wir einen Dialog entwerfen, müssen wir uns überlegen, was der Benutzer wirklich braucht; in unserem Fall brauchen wir ein Label, das beschreibt, was geändert wird, eine Spinbox mit auf und ab Knöpfen zum Setzen der Stiftweite und drei weitere Knöpfe: einen zum Zurücksetzen der Weite, einen zum Abbrechen des Dialogs und einen zum Übernehmen des neuen Wertes - den "OK" Knopf. In dieser Reihenfolge werden wir die Elemente auch hinzufügen, was wichtig ist, weil der Fokus der Tabulatortaste der Reihenfolge folgt, in der die Widgets erstellt werden. D.h., wenn Sie mit dem "OK" Knopf anfangen, bringt uns das Drücken der Tabtaste zuerst zur Spinbox und dann zum "Cancel" Knopf - was so nicht erwartet wird. Der Tabfokus sollte den Elementen von oben nach unten und von links nach rechts folgen, also müssen wir den Dialog auch in dieser Reihenfolge erstellen. Um Elemente zum Dialog hinzuzufügen, wählen Sie den Widgets Tabulator im linken Panel. Dort werden alle verfügbaren Widgets durch Icons repräsentiert, damit Sie Ihren Dialog erstellen können. Ein Druck auf ein Widget Icon erzeugt das neue Element in der linken, oberen Ecke des Hauptwidgets. Von dort können Sie es mit der Maus zu der Position ziehen, an der Sie das Element haben wollen. Desweiteren können Sie die Einstellungen für das gewählte Widget im "Widgeteigenschaften" Panel auf der rechten Bilschirmseite vornehmen.

Das Label: Wählen Sie `QLabel` aus dem Widgettabulator und plazieren Sie es an Position x:50, y:20. Dann gehen Sie zum "General" Abschnitt in den Widgeteigenschaften. Ändern Sie den Text der Eigenschaft "Text" von "Label:" nach "Pen Width:". Passen Sie die Breite in x-Richtung dem Text des Labels an, ein Wert von 120 sollte reichen. Sie können dies entweder mit der Maus machen, oder den Wert im "Geometry" Abschnitt eingeben.

Die Spinbox: Wählen Sie `QSpinbox` und plazieren Sie sie rechts neben dem eben erstellten Label. Setzen Sie den Variablennamen im Abschnitt "C++ Code" auf "width&&lowbar;;spbox". Die Minimal- und Maximalwerte sind 1 und 100, was für unsere Stiftweite ausreichen sollte.

Die Knöpfe: Schließlich brauchen wir noch unsere drei Knöpfe. Der linke Knopf soll der Standardknopf sein. Erzeugen Sie einen `QPushbutton` und plazieren Sie ihn an einer günstigen Stelle unten im Dialog, setzen Sie den Variablennamen auf "default&&lowbar;;btn" und den Text auf "Default". Machen Sie das gleiche mit dem "OK" Knopf, Variablenname "ok&&lowbar;;btn" und dem "Cancel" Knopf, Variablenname "cancel&&lowbar;;btn". Setzen Sie die Knopftexte auf "&&;OK" und "&&;Cancel".

Wenn Sie mit Ihrem Layout zufrieden sind, wählen Sie "Alle Quellen erzeugen" aus dem "Erstellen" Menü, geben Sie als Klassennamen "KPenBrushDlg" ein und wählen Sie `QDialog`. Nachdem Sie "OK" gedrückt haben, werden die Quellen für Ihren Dialog erzeugt und dem Projekt hinzugefügt. Sie können nun wieder zur Editoransicht in KDevelop zurückkehren und wir können den Code hinzufügen, der benötigt wird, damit der Dialog auch etwas macht.

### Verbinden und Einstellen

Nachdem wie nun die GUI des Dialogs erstellt haben, müssen wir den Köpfen noch die Funktionalität geben, und Mittel zur Verfügung stellen, die Werte der Spinbox zu holen und zu setzen, weil wir wollen, daß der Dialog den aktuellen Wert anzeigt, wenn er aufgerufen wird, und wir möchten den eingestellten Wert auslesen, wenn der "OK" Knopf gedrückt wird.

In der erzeugten Klasse `KPenBrushDlg` können Sie neben dem Konstruktor und dem Destruktor noch die Methode `initDialog()` finden. Diese Methode implementiert die gesamte GUI Konstruktion, deshalb müssen wir uns darum nicht mehr kümmern, sondern können direkt die üblichen Verbindungen für unsere Knöpfe erstellen. Fügen Sie die markierten Zeilen in den Konstruktor des Dialogs ein:

    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()));
    }

Dies implementiert die Funktionalität für die Knöpfe, für den Fall, daß sie gedrückt werden. Als erstes stellen wir den Standardknopf so ein, daß er den Slot `slotDefault()` ausführt. Dieser Slot, in dem wir den Standardwert für die Spinbox direkt setzen, muß noch implementiert werden.

Der zweite `connect()` Aufruf verbindet den "OK" Knopf mit dem Slot `accept()`, der von `QDialog` bereitgestellt wird, genauso wie der `reject()` Slot, den wir mit dem "Cancel" Knopf verbinden. "Cancel" wird sowohl den Dialog schließen, als auch den resultierenden Wert setzen, den wir später, wenn wir die Methode implementieren, die den Dialog aufruft, dazu verwenden werden, herauszufinden ob der neu eingestellte Wert verwendet oder die ganze Aktion abgebrochen werden soll.

Jetzt werden wir die beiden Methoden zum Setzen und holen der Spinbox-Werte hinzufügen:

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

Fügen Sie diese Methoden in der Klassendeklaration mit dem Modifizierer "public" ein, da wir die Werte setzen und holen wollen, wenn der Dialog angezeigt wird. Die Methode `setCurrent()` wird zum Setzen des aktuellen Stiftwertes verwendet, die `width()` Methode liefert den Wert zurück, den die Spinbox hat, wenn der Benutzer "OK" drückt.

Schließlich müssen wir noch die Methode `slotDefault()` implementieren:

    //kpenbrushdlg.h:
    //method declaration:

    public slots:
      void slotDefault();

    //kpenbrushdlg.cpp:
    //method implementation:

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

Dies setzt den Standardwert für die Stiftbreite auf 3 Pixel.

Wir sind nun mit unserem ersten Dialog fertig und können uns den anderen Klassen der Anwendung zuwenden, um noch einiges zu adaptieren und Methoden zu implementieren, die den Dialog aufrufen.

### Aufrufen der Dialoge

Wie Sie sich vielleicht schon denken können, bedeutet das Aufrufen der Dialoge nicht nur, daß wir den Dialog zur Auswahl der Stiftgröße implementieren werden, sondern auch, daß wir eine Methode zur Auswahl der Stiftfarbe hinzufügen. Doch eins nach dem anderen. Als erstes erzeugen Sie eine Methode `slotPenBrush()` in der Klasse `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."));
    }

Hier müssen wir zuerst auf die Fensterliste zugreifen und den Zeiger auf ein Dokument holen - das ein Dokument eines beliebigen Fensters sein kann, da alle Dokumente die gleiche aktuelle Stiftweite haben sollten. Dann legen wir eine Integervariable curr&&lowbar;;width an, welche die aktuelle Stiftweite speichert.

Jetzt können wir den Dialog aufrufen, indem wir eine dlg Instanz von KPenBrushDlg erzeugen. Dann setzen wir die aktuelle Stiftweite durch einen Aufruf der Methode `dlg->setCurrent()`, die wir ja dem Dialog hinzugefügt haben. Durch Aufruf von `dlg->exec()` zeigen wir den Dialog an. Die `if()` Anweisung stellt sicher, daß der folgende Code nur ausgeführt wird, wenn das Accept Flag gesetzt ist - das bedeutet, der Code wird ausgeführt, wenn der Benutzer den "OK" Knopf gedrückt hat.

Davon ausgehend, daß der Benutzer den Wert geändert und "OK" gedrückt hat, müssen wir in allen Dokumenten die neue Stiftweite einstellen. Dazu verwenden wir die `for()` Schleife und setzen in jedem Dokument die entsprechende Variable, die wir zuvor mit `dlg->width()` geholt haben.

Wir haben die Methode `setPenWidth()` noch nicht in der Dokumentklasse implementiert, also werden wir das jetzt nachholen:

    kscribbledoc.h:

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

Was noch fehlt, damit überhaupt eine Aktion ausgeführt wird, ist die Methoden hinzuzufügen, die aufgerufen werden sollen, wenn die Menüpunkte aktiviert oder die Knöpfe in der Werkzeugleiste gedrückt werden. Dazu müssen wir die ID's im Slot `commandCallback()` eintragen, der die entsprechenden Methoden auswählt und ausführt:

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

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

Diese Erweiterung fügt auch die Methode `slotPenColor()` zum Setzen der Stiftfarbe in die Liste der auszuführenden Methoden ein. Diese Methode werden wir nun implementieren:

    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."));
    }

Wenn wir uns den Code ansehen, erkennen wir, daß wir eine weitere neue Methode von `KScribbleDoc` verwenden, um die Farbe des Stifts zu setzen. Diese Methode müssen wir ebenfalls implementieren:

    kscribbledoc.h:

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

Denken Sie daran, die Deklarationen für `slotPenBrush()` und `slotPenColor()` auch einzufügen, damit Ihre Klasse `KScribbleApp` sie überhaupt kennt.

Nun sind Sie fertig. Lassen Sie uns zusammenfassen, was wir in diesem Kapitel getan haben:

- Zunächst haben wir Menü- und Werkzeugleistenkommandos und -elemente für zwei Dialoge, die wir aufrufen wollen, eingefügt.

- Dann haben wir mit dem Dialogeditor den Dialog zum Einstellen der Stiftweite erstellt.

- Als nächstes haben wir die Methoden geschrieben, die die Dialoge aufrufen und die Eigenschaften setzen die wir ändern wollen.

- Schließlich haben wir die benötigten Set-Methoden hinzugefügt, sodaß wir in der Lage sind, die Werte zu ändern, nachdem die Dialoge ausgeführt worden sind.

Mit dieser Struktur steht Ihnen ein allgemeiner Weg zur Verfügung, Ihre Anwendung um weitere Funktionalität zu erweitern und Einstellungen zu manipulieren, die das Verhalten des Dokumentes und die Interaktion mit den Ansichten beeinflussen.

## Erweiterte Ansichten

In diesem Kapitel werden wir die Funktionalität unseres Widgets durch zwei Verbesserungen erweitern: synchronisierte Ansichten und rollbare Ansichten.

### Synchronisierung der Ansichten

Lassen Sie uns zuerst erklären, was uns das Ganze bringen wird und wie wir es erreichen können. Während Sie mit KScribble experimentiert haben, werden Sie vielleicht bemerkt haben, daß, wenn Sie eine neue Ansicht des Dokuments durch Aufruf von "Fenster"->"neues Fenster" öffnen, diese neue Ansicht mit denselben Daten arbeitet, wie die erste Ansicht und sich genauso verhält, wie jede Ansicht, die Sie mit diesem Kommando erzeugen. Aber wenn es darum geht, in das Dokument zu zeichnen, können Sie das nur in einer Ansicht tun- die anderen Ansichten zeigen nicht den jeweils aktuellen Inhalt. Wenn Sie eine Ansicht, die nicht die aktuellen Daten enthält, verdecken und sie dann wieder in den Vordergrund holen, werden die richtigen Daten angezeigt. Das liegt daran, das jede Ansicht, wenn Sie verdeckt war, ein paint-Ereignis vom Fenstersystem erhält, wenn Sie wieder in den Vordergrund kommt, dann `KScribbleView::paintEvent()` aufruft und dadurch den Bereich, der verdeckt war schließlich neuzeichnet. Was wir erreichen wollen, ist daß alle Ansichten synchron mit der Ansicht gezeichnet werden, in der der Benutzer gerade arbeitet. Tatsächlich werden Sie sehen, daß diese Erweiterung leicht zu bewerkstelligen ist. Die Dokumentklasse bietet uns bereits eine Methode `updateAllViews()`, welche die Methode `update()` für jede Ansicht in der Ansichtenliste des Dokuments aufruft. Das macht es sehr einfach die Inhalte der Ansichten zu synchronisieren - jedes Mal, wenn der Inhalt verändert wird, hier durch Mausbewegungen ( durch die wir die Änderungen mittels `bitBlt()` in den Puffer kopieren), müssen wir nur updateAllViews(this) aufrufen. Der this Zeiger ist nötig, weil die aufrufende Ansicht nicht neugezeichnet werden muß und `update()` nur ausgeführt werden soll, wenn Sender und Empfänger verschieden sind.

Was Sie hier tun müssen, ist nur updateAllViews(this) am Ende der virtuellen Methoden `mousePressEvent()`, `mouseMoveEvent()` und `mouseReleaseEvent()` aufzurufen- und Sie sind fertig ! Nehmen Sie dies als allgemeine Regel in Ihren Anwendungen: jedesmal wenn sich der Inhalt eines Dokuments in einer Ansicht ändert, rufen Sie `updateAllViews()` auf. Wie die Aktualisierung durchgeführt wird, muß in der `update()` Methode des Widgets implementiert werden; man mag zufrieden sein, indem man z.B. den geänderten Text in einem Editor setzt, in unserer Anwendung rufen wir nur die Funktion `repaint()` auf, die ein paint-Event auslöst und den Inhalt des Dokuments wieder in die Ansicht kopiert.

### Rollbare Ansichten

In diesem Abschnitt werden wir eine Funktionalität implementieren, die meistens zum Kreuz für die Entwickler wird - wenn Sie nicht auf bereits implementierte Widgets zurückgreifen können, die das Rollen schon mitbringen. Was bedeutet Rollen? In unserem Kontext beginnt das Problem damit, das wir ein Bild öffnen wollen, das viel größer als der darstellbare Bereich ist. Beginnend von der oberen, linken Ecke, wird der Rest des Bildes aus der Sicht des Benutzers ausgeschnitten. Eine Rollansicht ist ein Widget, das auf der rechten und unteren Seite Rollbalken zur Verfügung stellt, mit deren Hilfe der Benutzer den Inhalt des Fensters verschieben kann. Tatsächlich wird das Dokument in seiner ganzen Größe gezeigt, aber die Ansicht kann innerhalb des Dokuments bewegt werden, sodaß jeder Teil mit Hilfe der Scrollbars angezeigt werden kann. Glücklicherweise gibt es in Qt die Klasse `QScrollView`,die selber von `QWidget` abgeleitet ist und die gleiche Grundfunktionalität wie ein gewöhnliches Widget bietet, jedoch den Inhalt der Ansicht bei der Verwendung von Scrollbars verwaltet - mit der zusätzlichen Option, daß der Programmierer entweder einfach nur eine Instanz von `QScrollView` verwendet, die zu verwaltenden Kindfenster mit der Rollansicht als Eltern erstellt und sie mit `addChild()` der Rollansicht hinzufügt, oder eine Ansicht von `QScrollView` ableitet und dann, statt direkt in das Widget, in den Viewport zeichnet, der ein definierter Bereich innerhalb der Rollansicht ist. Der Unterschied hier ist, daß `QScrollView` einen Satz von Ereignisbehandlungsroutinen, ähnlich denen von `QWidget`, extra für den Viewport bereitstellt. Das was also vorher `mousePressEvent()` in unserer Ansicht war, ist jetzt ein viewportMousePressEvent, ein `paintEvent()` wird zu einem viewportPaintEvent usw. . Die zweite der genannten Möglichkeiten passt auf unsere Anforderungen, wir werden also folgendene Modifikationen vornehmen, um KScribbleView zu einem rollbaren Widget zu machen:

- Zunächst müssen wir das Dokument der Größe seines Inhalts anpassen. Dies kann erreicht werden, indem wir uns die Größe des geöffneten Bildes holen, für ein neues Bild müssen wir eine Standardgröße definieren. In anderen Zeichenanwendungen kann diese Größe meistens durch ein Kommando des Benutzerinterfaces, z.B. in einem Dialog, der nach der neuen Höhe und Breite fragt, sowie einer Methode zur Anpassung des Bildes an diese Werte, geändert werden.

- Ändern der Elternklasse für KScribbleView von `QWidget` nach `QScrollView`.

- Umbenennen der virtuellen Methoden in die entsprechenden Viewportmethoden aus `QScrollView`.

- Adaptieren der virtuellen Event Handler, sodaß sie die Geometrie des Viewports beeinflussen. Das bedeutet, daß die alte Implementation auf die Geometrie von `QWidget` vertraut, die in der linken, oberen Ecke eines Widgets beginnt. Wenn die Ansicht gerollt wird und diese Ecke ist nicht sichtbar, müssen wir dafür sorgen, daß die von `QWidget` gelieferten Positionen in Viewportkoordinaten übersetzt werden.

#### Größe des Dokumentinhalts verändern

Wie bereits erwähnt, müssen wir sowohl die Größe eines Dokuments ändern, als auch diese Größe initialisieren, und wir müssen eine Methode bereitstellen, die diese Größe für die Ansichten holt. Dazu fügen wir die Variable `QSize size` sowie die Methode `docSize()` in `KScribbleDoc` ein:

    kscribbledoc.h:

    #include <qsize.h>

    ...
    public:
      const QSize docSize(){ return size;};

    private:
      QSize size;

Jetzt müssen wir alle Methoden, die Dokumentinhalte öffnen oder initialisieren, modifizieren.Dies sind `newDocument()` und `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()` initialisieren wir die Größe mit Standardwerten von 300 Pixeln Breite und 200 Pixeln Höhe. Das ist genug für ein kleines Bild und wir könnten auch immer noch einen Dialog zur Größenänderung hinzufügen. Wenn es daran geht, das Bild zu öffnen, müssen wir die Größe auf die des Bildes setzen. Dies kann man erreichen, indem man `QPixmap::size()` aufruft, was wir bereits in `openDocument()` verwendet haben. Dann sind mit dem Setzen der Größen fertig, und wir können damit beginnen, KScribbleView zu reimplementieren und eine Rollansicht daraus zu machen.

### Adaptieren der Ansicht

Wie oben gesagt, müssen wir zuerst einige Dinge in der Schnittstelle von KScribbleView ändern. Der folgende Code zeigt diese Änderungen:

    #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* );
    }

An dieser Stelle haben wir zuerst `QWidget` durch `QScrollView` als Basisklasse ersetzt und in die erforderliche Headerdatei eingesetzt.Außerdem haben wir alle implementierten Event Handler, die mit Interaktion bezüglich des Inhalts der Rollansicht zu tun haben, durch die entsprechenden Methoden aus `QScrollView` ersetzt und haben das resizeEvent auskommentiert. Nun können wir mit der Implementation dieser Methoden beginnen und die Größe unseres Bildes verwenden. Da eine Ansicht immer erst nach dem Dokument existiert, können wir sowohl die Größe des Widgets (die Viewportgröße), als auch die des Inhalts direkt im Konstruktor anpassen.

    #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);
    }

Beachten Sie, daß sich vorher `resizeEvent()` um die Gleichheit der Größe der Zeichenfläche und des Widgets gekümmert hat. Zur gleichen Zeit wurde dabei auch die Größe des Dokuments geändert, sodaß das Bild immer die gleiche Größe wie das Widget hatte. Da wir jetzt bereits die Größe des Dokuments initialisiert haben (in `newDocument()` und `openDocument()`), passen wir jetzt die Größe des Inhalts einfach durch Aufruf von `resizeContents()` aus `QScrollView` an die Größe des Dokuments an. Sie sehen auch, daß wir den Cursor für das Widget von dem allumfassenden Widget auf den Viewport geändert haben, den wir mit `viewport()` ermitteln können. Jetzt können wir wieder die Event Handler implementieren. Zuerst sollten wir auf das paintEvent achten, da dies eines der wichtigsten Events ist, weil es immer aufgerufen wird, wenn das Widget sichtbar wird oder seine Größe ändert.

Achtung: Denken Sie daran die `resizeEvent()` Implementation auszukommentieren!

Jetzt wird der paintEvent das Pixmap aus dem Puffer an die entsprechende Position der Ansicht kopieren müssen. Zu diesem Zweck müssen wir das Ziel von `bitBlt()` von this nach `viewport()` ändern, die linke, obere Position auf 0,0 setzen und das Ziel (den Puffer) so einstellen, daß von den contentsX und contentsY Positionen in den Viewport kopiert wird:

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

`contentsX()` ist die Position in X-Richtung der Rollansicht - die absolut der Position 0 des Viewports entspricht, und damit der linken, oberen Ecke der Ansicht. Das gleiche gilt auch für die Y-Richtung. Dieser Teil ist manchmal schwer zu verstehen und Sie müssen vielleicht ein wenig "Versuch und Irrtum" bei der Implementation Ihrer eigenen rollbaren Ansichten spielen. Der andere mögliche Aufruf von `bitBlt()` würde sein, die Werte der Positionen zu switchen und die Werte der Inhalte zu vertauschen:

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

Die letzte Änderung, die wir noch brauchen, ist die Änderung des Mauseventhandlers. Im Moment hat `mouseMoveEvent()`, das zu `viewportMouseMoveEvent()` wird, auch einen `bitBlt()` Aufruf. Hier müssen wir die gleichen Änderungen vornehmen, wie beim paint Event. Weiterhin haben wir im `mousePressEvent()` und dem `mouseMoveEvent()` die Position der Events mit `e->pos()` geholt. Diese Abfrage liefert uns nun die Position eines Widgets - nicht die des Inhalts, also müssen wir dies mit `viewportToContents()` übersetzen, damit an die korrekte Position des Dokuments gezeichnet wird:

      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 `viewportMouseMoveEvent()` mußten wir wieder das Ziel von this nach `viewport()` ändern - und damit die Positionen übersetzen. Diesmal haben wir die zweite Variante des Aufrufs, den wir in `viewportPaintEvent()` verwendet haben, benutzt, indem wir contentsX und contentsY subtrahiert haben um das Rechteck, das die aktuelle Zeichnung enthält, an die korrekte Stelle des Viewports zu kopieren.

Schließlich werden wir noch eine kleine Änderung an der `update()` Methode vornehmen: warum sollten wir jedesmal das ganze Widget neuzeichnen? Dies wird in den meisten Fällen die Performance herabsetzen und zu dem sogenannten "Flicker" Effekt führen. Dieser Effekt tritt manchmal bei Widgets auf, aber es gibt einige Möglichkeiten dieses Verhalten zu verringern. Statt `repaint()` aufzurufen, könnten wir ebensogut `repaint(false)` verwenden. Dadurch werden die Inhalte nicht vor dem Zeichnen gelöscht. Da wir den Inhalt des Dokuments direkt in das Widget kopieren, müssen wir die Daten nicht löschen, weil sie sowieso überschrieben werden. In Verbindung mit `QScrollView` können wir die Zeichenaktionen sogar noch weiter reduzieren: wir beschränken die update Methode darauf `repaint()` auf dem viewport() Widget aufzurufen, da dies wiederum `viewportPaintEvent()` aufruft. Außerdem können wir, wenn das Dokument kleiner als der Viewport ist, das paint Event noch auf das Rechteck des Viewports beschränken, in dem das Dokument angezeigt wird. Die sichtbare Höhe und Breite können wir uns holen und zu dem zu zeichnenden Rechteck zusammensetzen. Zusätzlich verwenden wir false als erase Parameter, so daß die Dokumentenfläche nicht gelöscht wird:

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

Jetzt sind wir fertig ! Dieses Kapitel war sicher eines, der am schwierigsten zu implementierenden und zu verstehenden - insbesondere dort, wo es um die, sich ändernden Geometrien ging. Andererseits haben wir unserer Anwendung durch die rollbaren und synchronisierten Ansichten, eine vollkommen andere Funktionalität verliehen.

Damit begeben wir uns in das letzte Kapitel unserer Einführung. Dort werden wir nur noch einige, wenige Änderungen vornehmen und Gebrauch von einigen neuen Methoden der KDE 2 Bibliotheken machen, doch wie immer, wird uns dies wieder eine interessante Funktionalität eröffnen - KScribble wird in der Lage sein, eine ganze Reihe von Bildformaten zu öffnen und zu speichern, und damit wird die Einschränkung, nur mit png Dateien arbeiten zu können, entfallen.

## Verwendung von KImageIO mit KScribble

Wenn es um Bilder geht, bieten die Qt und KDE Bibliotheken ein weites Feld an Operationen. Neben den reinen Zeichenroutinen unterstützen die Bibliotheken einen ganzen Satz an Bildformaten, die gelesen und geschrieben werden können- alles ohne Einschränkungen für Ihre Ansicht. Die Hauptklasse für diese Operationen ist `QImageIO`, die auch eine Supportbibliothek unter KDE hat: `KImageIO`. Zur Vorbereitung und bevor wir diese schönen Routinen verwenden können, müssen wir die erforderliche Bibliothek für KScribble verfügbar machen: nämlich libkimgio. Öffnen Sie "Projekt"->"Optionen" in KDevelop und fügen Sie die Zeile -libkimgio bei den zusätzlichen Bibliotheken ein.

### Vorbereiten des Dokuments

Nun können wir unsere Änderungen vornehmen. Als erstes müssen wir die Einschränkung, nur PNG Dateien lesen und schreiben zu können, entfernen. Ersetzen Sie in den folgenden Methoden, "PNG" durch "format", wie in den Methoden `openDocument()` und `saveDocument()`:

In der Methode `KScribbleDoc::openDocument()`:

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

In der Methode `KScribbleDoc:: saveDocument()`:

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

Jetzt haben wir ein Standardverhalten in diesen Methoden. Trotzdem stehen uns noch nicht alle möglichen Dateiformate zur Verfügung. Darum, und um die Adaptierung der Slots, die uns Dateinamen liefern, kümmern wir uns als nächstes.

### Registrierung von Dateiformaten

Um `KImageIO` zu benutzen, müssen wir erst die Bibliothek initialisieren. Dazu fügen wir einen Aufruf von `registerFormats()` in unsere `main()` Funktion ein:

    main.cpp:

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

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

Beachten Sie, daß dieser Aufruf nach der Instanzierung mit KApplication app erfolgt- ohne die Applikationsinstanz läuft unser Programm nicht, weil `KImageIO` dann nicht weiß, bei welcher Anwendung die Formate registriert werden sollen. Die Includedatei werden wir in kscribble.h eintragen, da wir einige der Methoden darin, in `KScribbleApp` verwenden werden:

kscribble.h:

&&num;;include &<;kimgio.h&>;

### Bilddateien öffnen

Jetzt, da wir `KImageIO` verwenden können, werden wir die erste Änderung in der wichtigsten Methode von `KScribbleApp` vornehmen: `openDocumentFile()`. Diese Methode öffnet uns bis jetzt jedes Dokument nur Anhand des Dateinamens. Sie läßt einfach die Erweiterung weg, da das Format normalerweise nicht von der Dokumentklasse benötigt wird. Aber da wir dies geändert haben, brauchen wir jetzt das Format und müssen den Aufruf für `KScribbleDoc::openDocument()` in der Methode `openDocumentFile()` adaptieren:

    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);
      	}
        ............
      }

Es funktioniert natürlich genauso wie die Verwendung von `QString format=QImageIO::imageFormat(file);`. Hier liefert uns `KImageIO` das Format des Bildes und wir können dem Dokument mitteilen, die Datei mit dem Dateinamen und dem Format zu öffnen ( eine weitere Möglichkeit wäre, das Format in der Dokumentklasse zu ermitteln).

### Setzen von Dateifiltern mit KImageIO

Mit diesem Kapitel endet unsere Einführung. Wir werden die Dateidialoge von KScribble dazu bringen, Dateifilter zu verwenden. Zu diesem Zweck bietet `KImageIO` Methoden, uns die Bezeichnungen aller Bilddateiformate zu holen, die man öffnen und speichern kann. Die folgende Implementierung ersetzt den Standardfilter (der eigentlich gar keiner ist - Sie müssen den Dateifilter Ihrer Anwendung den nötigen Mimetypen anpassen), indem sie die `pattern()` Methode von `KimageIO` verwendet:

      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."));
      }

Wir setzen hier den Modus in `pattern()` auf Lesen - was sich von dem zum Schreiben benutzten Patterns unterscheided. Jetzt haben wir das Öffnen der Datei mit Dateinamen und Format erledigt. Was noch zur Vervollständigung der Struktur fehlt, ist das Setzen der Patterns auch zum Schreiben. Dazu wird der Slot `slotFileSaveAs()` aufgerufen, der dann seinerseits den Dateidialog aufruft, in dem dann der Dateiname abgefragt wird. Dort werden wir den Patternmodus zum Schreiben setzen:

    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."));
    }

## Hinzufügen der Druckerfunktionalität

In diesem Kapitel werden wir Ihnen zeigen, wie einfach es ist, Druckerfunktionen mit Qt zu implementieren. Tatsächlich ist es nur eine Zeile Code, die wir schreiben müssen, aber wir werden verstehen, wer den Druckauftrag eigentlich ausführt. Wenn der Benutzer den "Drucken" Knopf in KScribble benutzt oder "Drucken" aus dem Dateimenü wählt, wird die `slotFilePrint()` Methode in `KScribbleApp` aufgerufen. Diese Methode prüft, welches Kindfenster gerade aktiv ist und erzeugt eine Instanz der Druckerklasse `QPrinter`. Dann ruft sie die Druckmethode des Widgets auf, nämlich `KScribbleView::print()`. Das Framework enthält bereits die Basisimplementation- was zeigt, daß Sie zum Drucken nur die Klasse `QPainter` verwenden müssen, die dann auf dem Drucker ausgibt. Diese Klasse ruft auch den Druckdialog auf.

Was wir hier tun müssen, ist `QPainter` Methoden zu benutzen, um das Pixmap des Dokuments zu drucken, das mit der Ansicht verbunden ist. Da `QPainter` bereits einen Satz an `drawPixmap()` Methoden anbietet, werden wir natürlich eine davon benutzen:

    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();
      }
    }

Hier drucken wir unser gepuffertes Pixmap des Dokuments in den Offset 0,0 der Druckerseite. Das ist alles!

Sie können es einfach einmal ausprobieren- Sie können nun jede Grafik drucken, die KScribble öffnen kann.

Dies ist das Ende unserer Tour durch die Erstellung einer KDE 2 Anwendung. Sie können das Quellpaket von KScribble inklusive einer Erweiterung, die Ausschneiden, Kopieren, Einfügen und Wiederrufen sowie Drag&&;Drop Funktionen hinzufügt, hier finden:

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

Das Beispieltarball ist lokal installiert und kann in Ihr Homerverzeichnis kopiert werden, wo Sie es auspacken und testen können. Nach dem Auspacken mit `tar zxvf kscribble-1.0.tar.gz` laden Sie das Projekt und rufen Sie "Automake und Autoconf" aus dem "Erstellen" Menü in KDevelop auf. Dann wählen Sie "Configure" aus dem gleichen Menü. Die Konfigurationen sind die meiner Installation von KDE 2 und Qt 2.1, Sie müssen sie also an Ihre Installation anpassen.

Der Anhang enthält ebenfalls den kompletten Quellcode für dieses Paket, so daß Sie ihn Online lesen können.

## Copyright

    KDevelop Copyright 1998,1999,2000 The KDevelop Team.

    Dieses Programm ist Freie Software; Sie können Sie
    weitergeben und/oder  verändern entsprechend den
    Bestimmungen der GNU General Public License, wie von der
    Free Software Foundation veröffentlicht, entweder
    entsprechend Version 2 der Bestimmungen oder (nach ihrer
    Wahl) jeder späteren Version. Dieses Programm wird in der
    Hoffnung, daß es nützlich ist, aber OHNE EINE GARANTIE
    verbreitet; sogar ohne die implizite Garantie der
    MARKTGÄNGIGKEIT oder EIGNUNG FüR EINEN BESTIMMTEN ZWECK.
    Lesen Sie die GNU General Public License, um weitere Details
    zu erfahren. Sie sollten eine Kopie der GNU General Public
    License zusammen mit diesem Programm erhalten haben; wenn
    nicht, so schreiben Sie an die Free Software Foundation,
    Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

## KScribble-1.0 Beispiel Quellcode

### Projekt Tarball

Das Beispiel Tarball ist lokal installiert und Sie können es sich in Ihr Homeverzeichnis kopieren, auspacken und testen. Nachdem Sie das Paket mit `tar zxvf kscribble-1.0.tar.gz` ausgepackt haben, rufen Sie "Automake und Autoconf" aus dem "Erstellen" Menü in KDevelop auf. Dann führen Sie "Configure" aus dem gleichen Menü aus. Die Configure-Optionen sind die meiner KDE 2 und Qt 2.1 Installation, Sie müssen sie also manuell bearbeiten, so das Sie zu Ihren Pfaden passen.

$(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