<!DOCTYPE book PUBLIC "-//KDE//DTD DocBook V3.1-Based Variant V1.0//EN" [
 <!ENTITY kdevelop "<application>kdevelop</application>">
 <!ENTITY % addindex "IGNORE">
 <![ %addindex; [ <!ENTITY docindex SYSTEM "docindex.docbook"> ]]>
]>
<book lang="de">
<bookinfo>
<title>KDE Anwendungs Tutorials
</title>
<subtitle>Der KDE Anwendungs-Tutorial Leitfaden für die Integrierte Entwicklungsumgebung KDevelop</subtitle>
<authorgroup>
<author>
<surname>Ralf Nolden </surname>
<affiliation>
<orgname>Das KDevelop Team</orgname>
<address><email>Ralf.Nolden@post.rwth-aachen.de</email></address>
</affiliation>
</author>
<othercredit role="translator">
<firstname>Ralf</firstname>
<surname>Funken</surname>
<affiliation>
<address><email>RFunken@kdevelop.de</email></address>
</affiliation>
</othercredit>
<othercredit role="translator">
<firstname> Stephan</firstname>
<surname>Johach</surname> <affiliation>
<address><email>lucardus@onlinehome.de</email></address>
</affiliation>
</othercredit>
</authorgroup>
<date>04/03/2000</date>
<releaseinfo>1.00.00</releaseinfo>
<abstract>
<para>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 <link linkend="Copyright">Copyright</link>. </para>
</abstract>
<keywordset>
<keyword>KDE</keyword>
</keywordset>
</bookinfo>

<chapter id="einleitung">
<title>Einleitung</title>

<para>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.</para>

<para>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.</para>

<para>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.</para>

<para>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.</para>

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

<para>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.</para>

<para>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.      </para>

<sect1 id="welche-kenntnisse-sollten-sie-mitbringen">
<title>Welche Kenntnisse sollten Sie mitbringen?</title>

<para>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. </para>

<para>Desweiteren setzen wir voraus, daß Sie <ulink url="../manual/index.html">Das Benutzerhandbuch zu  KDevelop</ulink> und <ulink url="../programming/index.html">The KDevelop Programming Handbook</ulink> gelesen haben und 
wissen, wie die KDevelop IDE grundsätzlich funktioniert und 
was erstellte Projekte sind.</para>
</sect1>
</chapter>

<chapter id="erste-schritte">
<title>Erste Schritte</title>

<para>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.</para>

<para>"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: </para>

<para><itemizedlist>
<listitem>
<para>Eine Basisklasse für Anwendungen (<literal remap="tt">QApplication</literal>)
</para>
</listitem>
<listitem>
<para>Eine Widget Bibliothek für grafische 
Benutzerschnittstellen.
</para>
</listitem>
<listitem>
<para>Ein Sortiment weiterer Hilfsklassen für Grafiken, 
Datei- und Datenverwaltung.
</para>
</listitem>
<listitem>
<para>Den signal-slot Mechanismus für die Kommunikation 
zwischen Klassen.
</para>
</listitem>
<listitem>
<para>Ereignis Überwachung durch event loops und virtuelle 
Methoden.</para>
</listitem>
</itemizedlist>
</para>

<para>Wenn Sie damit jetzt noch gar nichts anfangen können, 
sollten Sie in <ulink url="../programming/index-2.html#ss2.1">The KDevelop Programming Handbook</ulink> 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. </para>

<para>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.</para>

<para>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. </para>

<para>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. </para>

<para>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. </para>

<para>Da die einzelnen Tutorial Schritte jeweils auf  
dem in den vorherigen Schritten erstellten Code 
aufbauen, beginnen wir mit dem Projektnamen <literal remap="tt">MyWidget</literal> 
. 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. </para>

<para>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. </para>

<para>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.</para>

<para>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.</para>

<para>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.</para>

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

<para>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 <literal remap="tt">main()</literal> besitzt, 
in der die Ausführung des Programmes beginnt.</para>

<para>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.</para>
</chapter>

<chapter id="das-erstellen-der-qt-tutorials">
<title>Das Erstellen der QT Tutorials</title>

<sect1 id="schritt-1-hallo-welt">
<title>Schritt 1: Hallo Welt!</title>

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

<para>Um in den Quelltext der Funktion <literal remap="tt">main()</literal> 
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 <literal remap="tt">main()</literal> öffnet sich 
die Datei <literal remap="tt">main.cpp</literal> im Fenster "C/C++ Dateien", und der 
Cursor steht am Anfang der ersten Zeile von <literal remap="tt">main()</literal>.     </para>

<sect2 id="die-erste-nderung">
<title>Die erste Änderung</title>

<para>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 
<literal remap="tt">main()</literal> 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.</para>

<para>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.</para>

<para>Nun, wer den einfachen Weg gehen möchte, markiert den Inhalt 
der <literal remap="tt">main.()</literal> 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 <literal remap="tt">main.()</literal> Funktion eingefügt 
werden. </para>

<para>Abschließend fügen Sie die Einträge zum Einbinden der 
entsprechenden Headerdateien außerhalb (und vor!)der 
<literal remap="tt">main()</literal> Funktion hinzu.  </para>

<para><screen> 
#include &lt;qapplication.h&gt;
#include &lt;qpushbutton.h&gt; 
</screen>
 </para>

<para>Das war's. Da haben Sie den Quellcode für Ihre erste 
Tutorial Anwendung!</para>
</sect2>

<sect2 id="wir-bersetzen-hello-world">
<title>Wir übersetzen Hello World!</title>

<para>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. </para>

<para>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.  </para>
</sect2>

<sect2 id="bungen">
<title>Übungen</title>

<para>Ü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. </para>

<para>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.</para>

<para><orderedlist><listitem>
<para>Ändern Sie den Pushbutton in ein QLabel. 
</para>
</listitem>
<listitem>
<para>Verzichten Sie auf das direkte Setzen des Textes für 
den Pushbutton im Konstruktor, und verwenden Sie stattdessen 
<literal remap="tt">setText()</literal>.
</para>
</listitem>
<listitem>
<para>Verwenden Sie <literal remap="tt">QPushButton::resize ( const QSize & s )</literal>
anstelle des direkten Setzens von Höhe und Breite. Legen Sie 
dazu zunächst eine Instanz von <literal remap="tt">QSize</literal> mit der 
gewünschten Größe an. 
</para>
</listitem>
<listitem>
<para>Spielen Sie mit den Größen herum, um zu sehen, welche 
Höhe oder Breite beeinflußt.</para>
</listitem>
</orderedlist>
 </para>
</sect2>
</sect1>

<sect1 id="schritt-2-und-3-erweiterungen">
<title>Schritt 2 und 3: Erweiterungen</title>

<para>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. </para>

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

<para>Beachten Sie auch, daß der Button die Instanz von <literal remap="tt">QApplication</literal> 
durch Aufruf von <literal remap="tt">quit()</literal> beendet. Dieses <literal remap="tt">quit()</literal> 
begegnet Ihnen wieder, wenn wir uns mit dem KDE Pendant von 
<literal remap="tt">QApplication</literal>, der Klasse <literal remap="tt">KApplication</literal> 
beschäftigen.</para>
</sect1>

<sect1 id="schreiben-sie-ihre-eigenen-widgets">
<title>Schreiben Sie Ihre eigenen Widgets</title>

<para>Nachdem Sie nun die <literal remap="tt">main()</literal> 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.</para>

<para>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 
<literal remap="tt">main.cpp</literal> hinzufügt, in die Headerdatei und die 
Implementierungsdatei der Klasse <literal remap="tt">MyWidget</literal> eintragen. </para>

<para>Glücklicherweise müssen Sie die Implementierung nur noch dem 
Konstruktor von <literal remap="tt">MyWidget</literal> 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 <literal remap="tt">qfont.h</literal> kümmern. <literal remap="tt">QFont</literal> wird 
ausschließlich im Konstruktor von <literal remap="tt">MyWidget</literal> benutzt. 
Darum fügen wir die Zeile <literal remap="tt">&&num;;include &&lt;;qfont.h&&gt;;</literal> am 
Anfang von <literal remap="tt">mywidget.cpp</literal> ein. </para>

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

<sect1 id="wir-erweitern-die-widget-klasse">
<title>Wir Erweitern die Widget Klasse</title>

<para>Ü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 <literal remap="tt">resizeEvent()</literal> 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.</para>

<para>Darum sollten Sie sich mit den folgenden Klassen der QT 
Bibliothek vertraut machen:</para>

<para><itemizedlist>
<listitem>
<para>QEvent </para>
</listitem>
<listitem>
<para>QChildEvent</para>
</listitem>
<listitem>
<para>QCloseEvent</para>
</listitem>
<listitem>
<para>QFocusEvent </para>
</listitem>
<listitem>
<para>QKeyEvent</para>
</listitem>
<listitem>
<para>QMouseEvent</para>
</listitem>
<listitem>
<para>QMoveEvent </para>
</listitem>
<listitem>
<para>QPaintEvent</para>
</listitem>
<listitem>
<para>QResizeEvent</para>
</listitem>
<listitem>
<para>QTimerEvent </para>
</listitem>
</itemizedlist>
 </para>

<para>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.</para>

<para>Dazu gehört beispielsweise <literal remap="tt">virtual void mousePressEvent ( QMouseEvent * )</literal>, die für die Verarbeitung der 
Maus-Ereignisse zuständig ist, die ein Kontextmenü 
aufklappen lassen. </para>

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

<sect1 id="wir-fgen-eine-neue-klasse-hinzu">
<title>Wir fügen eine neue Klasse hinzu</title>

<para>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.</para>

<para>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.</para>

<para>Zunächst müssen wir den Namen der Klasse angeben. Das 
Tutorial verwendet den Namen <literal remap="tt">LCDRange</literal>, also geben wir 
diesen ein.</para>

<para>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 <literal remap="tt">QWidget</literal>. Weil fast alle benötigten GUI 
Klassen von <literal remap="tt">QWidget</literal> abgeleitet werden, macht uns der 
Klassengenerator die Angelegenheit noch leichter. </para>

<para>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 <literal remap="tt">Q&&lowbar;;OBJECT</literal> 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. </para>

<para>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 <literal remap="tt">LCDRange</literal>, 
keine genauere Auskunft über den Verwendungszweck des 
Widgets gibt.            </para>
</sect1>

<sect1 id="bung-7-14">
<title>Übung 7-14</title>

<para>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. </para>

<para>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 <emphasis>KScribble</emphasis> 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. </para>
</sect1>
</chapter>

<chapter id="kde-applikationen-erstellen">
<title>KDE Applikationen erstellen</title>

<para>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.</para>

<para>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: 
<itemizedlist>
<listitem>
<para>Installation von KDE 2 (Bibliotheken) und Einstellungen in KDevelop zur Erzeugung von KDE 2 Anwendungen.</para>
</listitem>
<listitem>
<para>Verstehen der grundlegenden Konzepte der MDI und Document View Modelle.</para>
</listitem>
<listitem>
<para>Erzeugen des generischen Layouts für <emphasis>KScribble</emphasis> und Erklärung des Layoutkonzeptes.</para>
</listitem>
<listitem>
<para>Implementierung der Document Klasse, die die Schnittstelle zum Lesen und Schreiben der Dokumentdaten zur 
Verfügung stellt, sowie das Dokument verwaltet.</para>
</listitem>
<listitem>
<para>Implementierung der view Klasse zur Visualisierung der Daten. Diese Klasse übernimmt auch die Interaktion mit dem 
Benutzer.</para>
</listitem>
<listitem>
<para>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.</para>
</listitem>
<listitem>
<para>Ändern der Ansicht der Standard <literal remap="tt">QWidget</literal> Vererbung in eine rollbare Ansicht.</para>
</listitem>
</itemizedlist>

Das Beispiel selbst basiert auf dem Code, den Sie in der <emphasis>scribble</emphasis> 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. </para>
</chapter>

<chapter id="installation-von-kde-2">
<title>Installation von KDE 2 </title>

<para> 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: 
<itemizedlist>
<listitem>
<para>ein aktueller Qt-2.1 Snapshot erhältlich auf <ulink url="http://www.troll.no">http://www.troll.no</ulink></para>
</listitem>
<listitem>
<para>ein aktueller KDE 2 Snapshot der Pakete <literal remap="tt">kdesupport</literal> and <literal remap="tt">kdelibs</literal></para>
</listitem>
</itemizedlist>

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: 
<itemizedlist>
<listitem>
<para>Installieren der Qt Bibliothek </para>
</listitem>
<listitem>
<para>Installieren der KDE Bibliothek</para>
</listitem>
<listitem>
<para>KDevelop einstellen</para>
</listitem>
</itemizedlist>
 
Dann sind Sie bereit und wir werden im nächsten Kapitel mit dem ersten Schritt zur Erstellung des Beispielprogramm <emphasis>KScribble</emphasis>
fortfahren.</para>

<sect1 id="vorbereitung-von-qt-2.1">
<title>Vorbereitung von Qt-2.1</title>

<para>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.</para>

<para>Nun, um die neueste Qt Bibliothek zu bekommen, holen Sie sich eine neue Version von der Troll Tech Website unter
<ulink url="http://www.troll.no">http://www.troll.no</ulink>, 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 <literal remap="tt">/usr/lib</literal>. 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</para>

<para><literal remap="tt">tar zxvf qtxxx.tar.gz</literal></para>

<para>abhängig vom Namen des Tarballs. Setzen Sie dann die Umgebungsvariable <literal remap="tt">QTDIR</literal> so, daß sie auf das Verzeichnis zeigt, in dem
sich Ihre neue Qt-2.1 Bibliothek befindet, z.B. mit <literal remap="tt">setenv QTDIR=/usr/lib/qt-2.1</literal>.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.</para>

<para>Wechseln Sie in das Qt-2.1 Verzeichnis und geben Sie ein:
<screen>./configure
make
</screen>
</para>

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

<para>Wechseln Sie nun in das Verzeichnis <literal remap="tt">/usr/lib/qt-2.1/lib</literal> und kopieren Sie alle erzeugten Bibliotheken <emphasis remap="bf">außer</emphasis> dem
symbolischen Link <literal remap="tt">libqt.so</literal> nach <literal remap="tt">/usr/lib</literal>.</para>

<para>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 <link linkend="Einrichten-von-KDevelop">Einrichten von KDevelop</link>.</para>
</sect1>

<sect1 id="einrichten-der-kde-2-bibliotheken">
<title>Einrichten der KDE 2 Bibliotheken</title>

<para>Der zweite Schritt ist das Installieren der benötigten KDE Bibliotheken. Laden Sie einen neuen Snapshot der Bibliothekspakete
<literal remap="tt">kdesupport</literal> und <literal remap="tt">kdelibs</literal> von <ulink url="http://www.kde.org">http://www.kde.org</ulink> 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:</para>

<para><screen>./configure --with-qt-dir=/usr/lib/qt-2.1 --prefix=/opt/kde2
make

und als root:

make install
</screen>
</para>

<para>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.</para>

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

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

<para>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 <literal remap="tt">makekdedoc</literal> ein,
und Kdoc erstellt die Bibliotheksdokumentation im Unterverzeichnis <literal remap="tt">srcdoc</literal>. In KDevelop können Sie online auf die Dokumentation
zugreifen, wenn Sie im Setup Dialog den Pfad zur KDE Bibliotheksdokumentation auf <literal remap="tt">kdelibs/srcdoc</literal> einstellen. Auf die gleiche
Art und Weise geben Sie auch den Pfad zu Ihrer Qt Dokumentation an, indem Sie <literal remap="tt">/usr/lib/qt-2.1/html</literal> einstellen. Sie können so die
Dokumentation direkt aus KDevelop heraus erreichen.</para>

<para>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.</para>
</sect1>

<sect1 id="einrichten-von-kdevelop">
<title>Einrichten von KDevelop </title>

<para>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"-&gt;"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 <literal remap="tt">/usr/lib/qt-2.1</literal> für den Qt-2.x
Bibliothekspfad und <literal remap="tt">/opt/kde2</literal> für KDE 2. Drücken Sie dann den "OK" Knopf, und Sie sind fertig.</para>

<para>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.</para>

<para>Wir können nun zum nächsten Kapitel weitergehen, in dem wir den ersten Schritt des <emphasis>KScribble</emphasis> Beispielprogrammes erstellen
werden.</para>
</sect1>
</chapter>

<chapter id="anwendungskonzepte">
<title>Anwendungskonzepte</title>

<para>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 
<ulink url="../programming/index.html">KDevelop Programming Handbook</ulink> 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 <ulink url="../kde_libref/index.html">The KDE Library Reference Guide</ulink> 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.</para>

<para>Sie sollten bereits wissen:
<itemizedlist>
<listitem>
<para>Daß jede KDE Applikation eine Instanz von <literal remap="tt">KApplication</literal> (bei Qt eine Instanz von <literal remap="tt">QApplication</literal>) benötigt</para>
</listitem>
<listitem>
<para>Generell ein Main Fenster von <literal remap="tt">KTMainWindow</literal> bei KDE (for Qt von <literal remap="tt">QMainWindow</literal>) abgeleitet ist</para>
</listitem>
<listitem>
<para>Daß Ereignisse (z.B. Tastendrücke oder Mausbewegungen) von darunterliegenden Window System an die Anwendung gesendet 
werden</para>
</listitem>
<listitem>
<para>Daß <literal remap="tt">QWidget</literal> als Basisklasse für alle Graphical User Interface (GUI) Komponenten spezialisierte Event Handler durch 
virtuelle Funktionen bereitstellt, die die entsprechenden Events durch Reimplementation von <literal remap="tt">QObject::event()</literal> ausfiltern </para>
</listitem>
<listitem>
<para>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.</para>
</listitem>
<listitem>
<para>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.</para>
</listitem>
</itemizedlist>
</para>

<sect1 id="das-document-view-modell">
<title>Das Document-View Modell</title>

<para>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:</para>

<para>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: <literal remap="tt">setView(your&&lowbar;;view)</literal> bei KDE Applikationen die <literal remap="tt">KTMainWindow</literal> verwenden,
<literal remap="tt">setCentralWidget(your&&lowbar;;view)</literal> bei Qt Applikationen die <literal remap="tt">QMainWindow</literal> 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 <literal remap="tt">QMultiLineEdit</literal> 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:</para>

<para>Während Sie die Menüleiste erstellen, möchten Sie eine Methode für das Kommando "Cut" im "Edit" Menü bereitstellen:
<screen>  pEditMenu-&gt;insertItem(BarIcon("editcut"), i18n("Cu&&amp;;t"),view, SLOT(cut()),KAccel::Cut, ID_EDIT_CUT);
</screen>
</para>

<para>Dies erzeugt einen Menüeintrag im "Edit" Menü, der, wenn aktiviert, direkt den Slot <literal remap="tt">cut()</literal> der Instanz 
<literal remap="tt">view</literal> aufruft, wobei wir davon ausgehen, daß Sie diese Instanz als <literal remap="tt">QMultiLineEdit</literal> 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 
<literal remap="tt">QMultiLineEdit</literal> 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ß. </para>

<para>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 <literal remap="tt">QMultiLineEdit</literal> Ansichtsbereich lesen und schreiben zu können, und zwar <emphasis remap="bf">innerhalb</emphasis> 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. </para>

<para>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 <emphasis remap="bf">zwei</emphasis> oder sogar mehr 
Ansichten zu arbeiten? Solche Dinge kann man sogar mit nur einem Hauptfenster durch Trenner erreichen, wobei zwei Instanzen 
<emphasis remap="bf">einer</emphasis> 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 <emphasis remap="bf">anderen</emphasis> 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.</para>

<para>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 <emphasis remap="bf">ein</emphasis> Dokument in <emphasis remap="bf">einem</emphasis> 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.</para>
</sect1>

<sect1 id="das-multiple-document-interface-mdi">
<title>Das Multiple Document Interface (MDI)</title>

<para>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. </para>

<para>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</para>

<para><screen>Applikations Instanz -&gt; Hauptfenster -&gt; Ansicht

nach

Applikations Instanz -&gt; Hauptfenster -&gt; Ansicht -&gt; Aktives Kind Fenster
</screen>

ändert.</para>

<para>Die Ansicht beherrscht nun verschiedene Aktionen:
<itemizedlist>
<listitem>
<para>Erzeugen von so vielen Kindfenstern, wie der Benutzer anfordert</para>
</listitem>
<listitem>
<para>Zur Verfügung stellen von Methoden, zur Ermittlung des aktiven Kindfensters</para>
</listitem>
<listitem>
<para>Verwaltung einer Liste offener Ansichten</para>
</listitem>
<listitem>
<para>Steuern des Verhaltens des Kindfensters bei Maximieren, Minimieren, so wie es der Fenstermanager für top-level 
Fenster macht</para>
</listitem>
</itemizedlist>
 </para>

<para>Nun, Sie können komplette Widgets wie <literal remap="tt">QMultiLineEdit</literal> 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.</para>

<para>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.</para>

<para>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 <emphasis>KScribble</emphasis> generieren und die praktischen Aspekte der Programmierung von MDI 
Applikationen beschreiben.</para>
</sect1>
</chapter>

<chapter id="das-mdi-framework">
<title>Das MDI Framework</title>

<para>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, <emphasis>ein</emphasis> Fenster mit <emphasis>einem</emphasis> 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.</para>

<para>Zur Erstellung des ersten Schrittes der Beispielanwendung <emphasis>KScribble</emphasis>, 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).</para>

<sect1 id="erzeugen-eines-mdi-frameworks">
<title>Erzeugen eines MDI Frameworks</title>

<para>Nun werden wir uns in die Entwicklung stürzen, indem wir den Rahmen für <emphasis>KScribble</emphasis> 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 <emphasis>KScribble</emphasis> 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 <emphasis>KScribble</emphasis> in 
andere Sprachen übersetzen.</para>
</sect1>

<sect1 id="bersicht">
<title>Übersicht</title>

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

<para>Zunächst einmal sehen Sie in der Klassenansicht drei, bereits erzeugte Klassen, <literal remap="tt">KScribbleApp</literal>, <literal remap="tt">KScribbleDoc</literal> und
<literal remap="tt">KScribbleView</literal>. 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 <literal remap="tt">QWidget</literal> 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. <literal remap="tt">QMainWindow</literal> oder <literal remap="tt">KTMainWindow</literal> als zu beerbende Klasse zu verwenden 
und eine andere Klasse für der Verwaltung der Ansicht zu benutzen.</para>

<para>Im nächsten Kapitel werden wir fortfahren <emphasis>KScribble</emphasis> 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.</para>
</sect1>
</chapter>

<chapter id="definition-der-dokumente">
<title>Definition der Dokumente</title>

<para>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 <emphasis>KScribble</emphasis> eine einfache Zeichenanwendung sein wird, die mit graphischen Daten umgeht, werden wir die Qt Klasse
<literal remap="tt">QPixmap</literal> dazu verwenden, unsere Daten zu speichen während wir editieren. <literal remap="tt">QPixmap</literal> 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!</para>

<para>Daher fügen wir zur Definition der Arbeitsweise unserer Dokumentklasse eine Instanz von <literal remap="tt">QPixmap</literal> und eine von
<literal remap="tt">QPen</literal> hinzu und editieren die Methoden <literal remap="tt">newDocument()</literal>, <literal remap="tt">openDocument()</literal> und <literal remap="tt">saveDocument()</literal>.</para>

<sect1 id="hinzufgen-der-instanzen">
<title>Hinzufügen der Instanzen</title>

<para>Öffnen Sie die Datei <literal remap="tt">kscribbledoc.h</literal> durch Auswahl in einer der Baumansichten oder durch einen Klick in der Klassenansicht
über <literal remap="tt">KScribbleDoc</literal>. Dann fügen Sie die mit -&gt; markierten Zeilen aus dem folgenden Codeausschnit hinzu:</para>

<para><programlisting>-&gt;  #include &lt;qpixmap.h&gt;
-&gt;  #include &lt;qpen.h&gt;

    class KScribbleDoc
    {

-&gt;    	protected:

-&gt;        QPen currentPen(){ return pen;};	
    		
-&gt;    		int penWidth()
-&gt;    			{ return pen.width(); }

      public slots:
        void updateAllViews(KScribbleView *sender);
    	
    	protected:
    	
-&gt;    		QPixmap buffer;
     	
      private:
-&gt;    		QPen pen;
        /** the modified flag of the current document */
        bool modified;</programlisting>
</para>

<para>Wie Sie sehen, haben wir pen und buffer, sowie <literal remap="tt">currentPen()</literal> und <literal remap="tt">penWidth()</literal> 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.</para>
</sect1>

<sect1 id="initialisierung-des-dokuments">
<title>Initialisierung des Dokuments</title>

<para>Wählen Sie die Methode <literal remap="tt">newDocument()</literal> in der <literal remap="tt">KScribbleDoc</literal>-Klasse um zur Methodendeklaration zu springen. Hier fügen wir
nur eine, mit dem Pfeil markierte Zeile ein:</para>

<para><programlisting>  kscribbledoc.cpp

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

<para>Dies initialisiert den Stift mit der Farbe Schwarz und einer Weite von drei Pixeln; die <literal remap="tt">QPen</literal> Klasse hat noch einige weitere
Konstruktoren, aber dieser hier reicht für unsere Zwecke.</para>
</sect1>

<sect1 id="implementierung-der-serialisation">
<title>Implementierung der Serialisation</title>

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

<para><programlisting>    bool KScribbleDoc::openDocument(const QString &&amp;;filename, const char *format /*=0*/)
    {

    	QFile f( filename );
-&gt;     //if ( !f.open( IO_ReadOnly ) )
-&gt;     //	return false;
     /////////////////////////////////////////////////
     // TODO: Add your document opening code here
-&gt;     if(!buffer.load( filename, "PNG" ))
-&gt;     	return false;
     /////////////////////////////////////////////////
-&gt;     //f.close();


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

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

-&gt;      //f.close();
</programlisting>
</para>

<para>Fügen Sie wieder die Zeilen mit dem Pfeil Ihrem Code hinzu. Was wir getan haben, ist die Zeilen, in denen <literal remap="tt">Dateiname</literal> geöffnet
wird, auszukommentieren, weil dies automatisch von den Laden- und Speichermethoden von <literal remap="tt">QPixmap</literal> erledigt wird, die wir
stattdessen hinzufügen. Andere Dokumente mögen Dateien öffnen und deren Inhalt ( Textzeilen oder was auch immer) lesen, weshalb
die <literal remap="tt">QFile</literal> Methoden schon im Frame enthalten sind.Da <literal remap="tt">save()</literal> und <literal remap="tt">load()</literal> 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 <literal remap="tt">load()</literal> und <literal remap="tt">save()</literal> Methoden sind nun schon in <literal remap="tt">QPixmap</literal> enthalten.
Sie benötigen den Dateinamen und das Format als Argument; die Quellen rufen die Dokument Methoden jedoch noch <emphasis remap="bf">nicht</emphasis> mit dem
Format auf. Wenn nur <emphasis>ein</emphasis> 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
<literal remap="tt">QImageIO</literal> nach, um mehr über Bildformate zu erfahren.</para>

<para>Nun sind wir fast mit der Definition unserer Dokumentstruktur fertig. Der <literal remap="tt">QPixmap</literal> 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 <literal remap="tt">newDocument</literal> 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.</para>

<para>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 !</para>
</sect1>
</chapter>

<chapter id="definition-der-ansicht">
<title>Definition der Ansicht</title>

<sect1 id="interaktion-mit-dem-benutzer">
<title>Interaktion mit dem Benutzer</title>

<para>In diesem Kapitel werden wir uns der View Klasse von <emphasis>KScribble</emphasis> zuwenden, um zu definieren, wie die Kindfenster arbeiten sollen. 
Als erstes sehen wir, daß KScribbleView standarmäßig von <literal remap="tt">QWidget</literal> 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 <literal remap="tt">QWidget</literal> ü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 
<literal remap="tt">QPointArray</literal> und einen boolschen Wert mousePressed hinzufügen. Fügen Sie den, mit dem Pfeil gekennzeichneten Code, Ihrer Klasse 
KScribbleView hinzu:</para>

<para><programlisting>   kscribbleview.h

-&gt;   #include &lt;qpointarray.h&gt;

    class KScribbleView
    {
    .
    .
     protected:
        virtual void closeEvent(QCloseEvent* );

-&gt;     	virtual void mousePressEvent( QMouseEvent * );
-&gt;     	virtual void mouseReleaseEvent( QMouseEvent * );
-&gt;     	virtual void mouseMoveEvent( QMouseEvent * );
-&gt;     	virtual void resizeEvent( QResizeEvent * );
-&gt;     	virtual void paintEvent( QPaintEvent * );
   	
   	  KScribbleDoc *doc;
     		
-&gt;     private:
-&gt;   		bool mousePressed;
-&gt;   		QPointArray polyline;

     }</programlisting>
</para>
</sect1>

<sect1 id="reimplementierung-von-event-handlern">
<title>Reimplementierung von Event Handlern</title>

<para>Wir kommen jetzt zu der tatsächlichen Implementierung der Ereignis Handler. Wie in <ulink url="../kde_libref/index.html">The KDE Library Reference Guide</ulink> erkärt, hat Qt gute Methoden, Ereignisse zu behandeln, besonders wenn die Ziele Widgets sind. 
<literal remap="tt">QWidget</literal> 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 <literal remap="tt">closeEvent()</literal> 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.</para>

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

<para><programlisting>    kscribbleview.cpp


    KScribbleView::KScribbleView(KScribbleDoc* pDoc, QWidget *parent, const char* name, int wflags)
     : QWidget(parent, name, wflags)
    {
        doc=pDoc;

-&gt;      setBackgroundMode( QWidget::NoBackground );
-&gt;      setCursor( Qt::crossCursor );
-&gt;    	mousePressed=false;
-&gt;      polyline=QPointArray(3);
    }</programlisting>
</para>

<para>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, <literal remap="tt">mousePressEvent()</literal>, um zu erkennen wann der Benutzer 
die Maus drückt und wo.</para>

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

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

<para>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 
<literal remap="tt">QPointArray</literal> ist, kann es Werte vom Typ <literal remap="tt">QPoint</literal> speichern (die selber wieder einen x und y Wert enthalten). Wir werden in 
diesem Feld Mauspositionen speichern und daraus die Zeichenroutine im mouseMoveEvent entwickeln:</para>

<para><programlisting>void KScribbleView::mouseMoveEvent( QMouseEvent *e )
{
  if ( mousePressed ) {
		
    QPainter painter;
    painter.begin( &&amp;;doc-&gt;buffer );
    painter.setPen( doc-&gt;currentPen() );
    polyline[2] = polyline[1];
    polyline[1] = polyline[0];
    polyline[0] = e-&gt;pos();
    painter.drawPolyline( polyline );
    painter.end();

    QRect r = polyline.boundingRect();
    r = r.normalize();
    r.setLeft( r.left() - doc-&gt;penWidth() );
    r.setTop( r.top() - doc-&gt;penWidth() );
    r.setRight( r.right() + doc-&gt;penWidth() );
    r.setBottom( r.bottom() + doc-&gt;penWidth() );

	  doc-&gt;setModified();
    bitBlt( this, r.x(), r.y(), &&amp;;doc-&gt;buffer, r.x(), r.y(), r.width(), r.height() );
  }
}</programlisting>
</para>

<para>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 <literal remap="tt">mousePressEvent()</literal> Handler bereits gemacht, daher brauchen wir uns darum nicht weiter zu kümmern. Nun 
beginnen wir zu zeichnen. Als erstes erzeugen wir einen <literal remap="tt">QPainter</literal> 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 <literal remap="tt">currentPen()</literal> aufrufen. Die nächsten drei 
Zeilen weisen die Werte des polyline <literal remap="tt">QPoint</literal> 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 <literal remap="tt">drawPolyline()</literal> und <literal remap="tt">painter.end()</literal>), erstellen wir 
ein Rechteck r und verwenden <literal remap="tt">boundingRect()</literal> von <literal remap="tt">QPointArray</literal> um ein <literal remap="tt">QRect</literal> zu erhalten, das alle drei 
Punkte enthält. Daher brauchen wir die drei Punkte, um ein fast fertiges Rechteck zu erhalten. Dann verwenden wir 
<literal remap="tt">normalize()</literal>, 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 
<literal remap="tt">penWidth()</literal> 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 <literal remap="tt">bitBlt()</literal> 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 
<emphasis remap="bf">this</emphasis> 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:</para>

<para><programlisting>void KScribbleView::mouseReleaseEvent( QMouseEvent * ) {
	mousePressed = FALSE;
}</programlisting>
</para>

<para>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. </para>
</sect1>

<sect1 id="zeichnen-und-gre-des-dokuments-ndern.">
<title>Zeichnen und Größe des Dokuments ändern.</title>

<para>Ü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: </para>

<para><programlisting>void KScribbleView::paintEvent( QPaintEvent *e )
{
  QWidget::paintEvent( e );

  QRect r = e-&gt;rect();

  bitBlt( this, r.x(), r.y(), &&amp;;doc-&gt;buffer, r.x(), r.y(), r.width(), r.height() );
}</programlisting>
</para>

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

<para>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 <literal remap="tt">resizeEvent()</literal>:</para>

<para><programlisting>void KScribbleView::resizeEvent( QResizeEvent *e )
{
  QWidget::resizeEvent( e );

  int w = width() &gt; doc-&gt;buffer.width() ?
  width() : doc-&gt;buffer.width();
  int h = height() &gt; doc-&gt;buffer.height() ?
  height() : doc-&gt;buffer.height();

  QPixmap tmp( doc-&gt;buffer );
  doc-&gt;buffer.resize( w, h );
  doc-&gt;buffer.fill( Qt::white );
  bitBlt( &&amp;;doc-&gt;buffer, 0, 0, &&amp;;tmp, 0, 0, tmp.width(), tmp.height() );
}</programlisting>
</para>

<para>Hier wird zunächst der resizeEvent Handler von <literal remap="tt">QWidget</literal> 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.</para>

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

<chapter id="erweiterung-der-gui">
<title>Erweiterung der GUI</title>

<para>Wie wir gesehen haben, haben wir <emphasis>KScribble</emphasis> 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 <literal remap="tt">QPen</literal> 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:</para>

<para><itemizedlist>
<listitem>
<para>Das Hinzufügen eines neuen Menüs zur Menüleiste</para>
</listitem>
<listitem>
<para>Erweiterung der Werkzeugleiste um Icons, für die in der Menüleiste definierten Methoden</para>
</listitem>
<listitem>
<para>Die Erstellung eines neuen Dialoges mit dem KDevelop Dialogeditor</para>
</listitem>
<listitem>
<para>Verbinden der neuen Kommandos der Menü- und Werkzeugleiste mit Aufrufen der Dialoge</para>
</listitem>
<listitem>
<para>Ermöglichen der Änderung der Stiftgröße in der Dokumentklasse</para>
</listitem>
</itemizedlist>
</para>

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

<sect1 id="hinzufgen-des-stift-mens">
<title>Hinzufügen des "Stift" Menüs</title>

<para>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 <literal remap="tt">QPopupMenu</literal>, und Sie können einen Blick darauf werfen, wie die 
aktuelle Menüleiste aufgebaut ist, wenn Sie zur <literal remap="tt">KScribbleApp</literal> Klasse, Methode 
<literal remap="tt">initMenubar()</literal> 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: 
<itemizedlist>
<listitem>
<para>In den Menüs, die Reihenfolge der Einträge</para>
</listitem>
<listitem>
<para>In der Menüleiste, die Reihenfolge der Popupmenüs</para>
</listitem>
</itemizedlist>
</para>

<para>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:</para>

<para><programlisting>kscribbleapp.h

class KScribbleApp
{
.
.
  private:
    QPopupMenu* pPenMenu;

}</programlisting>
</para>

<para>Jetzt werden wir das Menü selbst erzeugen. Ändern Sie die Implementation der Methode <literal remap="tt">KScribbleApp::initMenuBar()</literal> und 
editieren Sie die, mit dem Pfeil markierten Zeilen:</para>

<para><programlisting>void KScribbleApp::initMenuBar()
{
..

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

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


-&gt;  connect(pPenMenu, SIGNAL(activated(int)), SLOT(commandCallback(int)));
-&gt;  connect(pPenMenu, SIGNAL(highlighted(int)), SLOT(statusCallback(int)));
}</programlisting>
</para>

<para>Sie sehen, daß wir zunächst das Menü mit <literal remap="tt">new QPopupMenu()</literal> erzeugen. Dann verwenden wir 
die insertItem Methode, zum Hinzufügen der Menüeinträge Color und Brush. Die sichtbaren 
Kommandos werden mit der Methode <literal remap="tt">i18n()</literal> 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 <literal remap="tt">i18n()</literal> deklarieren sollten. Nur-Qt Programme, die 
Qt &gt; 2.0 verwenden, benutzen dazu die Methode <literal remap="tt">tr()</literal>, 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 
<literal remap="tt">insertItem()</literal> der Menüleiste hinzugefügt, und zwar mit einem Zeiger auf das Menü als 
zweitem Argument. Beachten Sie, daß wir das Popupmenü <emphasis>nach</emphasis> dem Edit- und <emphasis>vor</emphasis> 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 "&&amp;;" setzen, das "Pen" Menü wird also später durch 
drücken von ALT+P erreichbar sein. Innerhalb des Menüs kann der Benutzer auch <emphasis>nur</emphasis> 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). </para>

<para>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:     </para>

<para><programlisting> 
resource.h ///////////////////////////////////////////////////////////////////
// Pen-menu entries
#define ID_PEN_COLOR                14010
#define ID_PEN_BRUSH                14020</programlisting>
</para>

<para>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.</para>

<para>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.</para>
</sect1>

<sect1 id="hinzufgen-von-knpfen-zur-werkzeugleiste">
<title>Hinzufügen von Knöpfen zur Werkzeugleiste</title>

<para>Wenn Sie neue Kommandos über Knöpfe in der Werkzeugleiste zur Verfügung stellen möchten, können 
Sie dies leicht mit der <literal remap="tt">initToolbar()</literal> Methode der <literal remap="tt">App</literal> 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 
<emphasis>KScribble</emphasis> 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:</para>

<para><literal remap="tt">$(kde&&lowbar;;datadir)/kscribble/toolbar/pencolor.xpm</literal></para>

<para>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. </para>

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

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

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

<para>Wir verwenden an dieser Stelle die Methoden von KToolBar um die Knöpfe einzufügen. Das erste 
Argument, <literal remap="tt">BarIcon()</literal>, 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,<emphasis>KScribble</emphasis> !). </para>

<para>Das zweite Argument ist wiederum die ID. Die Kommandos werden dann automatisch aktiviert, da 
<literal remap="tt">toolBar()</literal> bereits mit denselben Methoden verbunden ist wie die Menübar, über das Signal 
<literal remap="tt">activated()</literal>. 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 <literal remap="tt">i18n()</literal> einschließen, um die 
Internationalisierung zu ermöglichen. </para>

<para>Nun sind Sie für's erste fertig. Die GUI ist, zumindest visuell, erweitert. Sie können 
<emphasis>KScribble</emphasis> 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 <emphasis>KScribble</emphasis> nicht 
installiert haben, und so werden sie nicht gefunden. Alle anderen Icons werden bereits mit den KDE 
Bibliotheken geliefert, deshalb sind sie schon sichtbar.</para>
</sect1>

<sect1 id="erzeugen-des-dialogs-fr-die-stiftweite">
<title>Erzeugen des Dialogs für die Stiftweite</title>

<para>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 <literal remap="tt">kpenbrushdlg</literal> 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.</para>

<para><emphasis remap="bf">Das Label:</emphasis> Wählen Sie <literal remap="tt">QLabel</literal> 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.</para>

<para><emphasis remap="bf">Die Spinbox:</emphasis> Wählen Sie <literal remap="tt">QSpinbox</literal> 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.</para>

<para><emphasis remap="bf">Die Knöpfe:</emphasis> Schließlich brauchen wir noch unsere drei Knöpfe. Der linke Knopf soll der 
Standardknopf sein. Erzeugen Sie einen <literal remap="tt">QPushbutton</literal> 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 "&&amp;;OK" und "&&amp;;Cancel".</para>

<para>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 <literal remap="tt">QDialog</literal>. 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.</para>
</sect1>

<sect1 id="verbinden-und-einstellen">
<title>Verbinden und Einstellen</title>

<para>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. </para>

<para>In der erzeugten Klasse <literal remap="tt">KPenBrushDlg</literal> können Sie neben dem Konstruktor und dem Destruktor 
noch die Methode <literal remap="tt">initDialog()</literal> 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: </para>

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

-&gt;	connect(default_btn, SIGNAL(clicked()), this, SLOT(slotDefault()));
-&gt;	connect(ok_btn, SIGNAL(clicked()), this, SLOT(accept()));
-&gt;	connect(cancel_btn, SIGNAL(clicked()), this, SLOT(reject()));
}</programlisting>
</para>

<para>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 <literal remap="tt">slotDefault()</literal> ausführt. Dieser 
Slot, in dem wir den Standardwert für die Spinbox direkt setzen, muß noch implementiert werden. </para>

<para>Der zweite <literal remap="tt">connect()</literal> Aufruf verbindet den "OK" Knopf mit dem Slot <literal remap="tt">accept()</literal>, der 
von <literal remap="tt">QDialog</literal> bereitgestellt wird, genauso wie der <literal remap="tt">reject()</literal> 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.</para>

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

<para><programlisting>void setCurrent(int curr){ width_spbox-&gt;setValue(curr); }
int width() { return width_spbox-&gt;value(); };</programlisting>
</para>

<para>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 <literal remap="tt">setCurrent()</literal> wird zum Setzen des 
aktuellen Stiftwertes verwendet, die <literal remap="tt">width()</literal> Methode liefert den Wert zurück, den die 
Spinbox hat, wenn der Benutzer "OK" drückt.</para>

<para>Schließlich müssen wir noch die Methode <literal remap="tt">slotDefault()</literal> implementieren:</para>

<para><programlisting>//kpenbrushdlg.h:
//method declaration:

public slots:
  void slotDefault();

//kpenbrushdlg.cpp:
//method implementation:

void KPenBrushDlg::slotDefault()
{
  width_spbox-&gt;setValue(3);
}</programlisting>
</para>

<para>Dies setzt den Standardwert für die Stiftbreite auf 3 Pixel.</para>

<para>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.</para>
</sect1>

<sect1 id="aufrufen-der-dialoge">
<title>Aufrufen der Dialoge</title>

<para>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 <literal remap="tt">slotPenBrush()</literal> in der Klasse <literal remap="tt">KScribbleApp</literal>:</para>

<para><programlisting>void KScribbleApp::slotPenBrush()
{
  slotStatusMsg(i18n("Setting brush width..."));

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

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

<para>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.</para>

<para>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 
<literal remap="tt">dlg-&gt;setCurrent()</literal>, die wir ja dem Dialog hinzugefügt haben. Durch Aufruf von 
<literal remap="tt">dlg-&gt;exec()</literal> zeigen wir den Dialog an. Die <literal remap="tt">if()</literal> 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. </para>

<para>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 <literal remap="tt">for()</literal> 
Schleife und setzen in jedem Dokument die entsprechende Variable, die wir zuvor mit 
<literal remap="tt">dlg-&gt;width()</literal> geholt haben. </para>

<para>Wir haben die Methode <literal remap="tt">setPenWidth()</literal> noch nicht in der Dokumentklasse 
implementiert, also werden wir das jetzt nachholen: </para>

<para><programlisting>
kscribbledoc.h:

public:
  void setPenWidth( int w ){ pen.setWidth( w ); }</programlisting>
</para>

<para>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 
<literal remap="tt">commandCallback()</literal> eintragen, der die entsprechenden Methoden auswählt und 
ausführt:   </para>

<para><programlisting>void KScribbleApp::commandCallback(int id_)
{
  switch (id_)
  {
    case ID_PEN_BRUSH:
      slotPenBrush();
      break;

    case ID_PEN_COLOR:
      slotPenColor();
      break;
....
  }
}</programlisting>
</para>

<para>Diese Erweiterung fügt auch die Methode <literal remap="tt">slotPenColor()</literal> zum Setzen der Stiftfarbe 
in die Liste der auszuführenden Methoden ein. Diese Methode werden wir nun implementieren: </para>

<para><programlisting>void KScribbleApp::slotPenColor()
{
  slotStatusMsg(i18n("Selecting pen color..."));

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

<para>Wenn wir uns den Code ansehen, erkennen wir, daß wir eine weitere neue Methode von 
<literal remap="tt">KScribbleDoc</literal> verwenden, um die Farbe des Stifts zu setzen. Diese Methode müssen 
wir ebenfalls implementieren: </para>

<para><programlisting>kscribbledoc.h:

    /** sets the pen color */
    void setPenColor( const QColor &&amp;;c ){ pen.setColor( c ); }</programlisting>
</para>

<para>Denken Sie daran, die Deklarationen für <literal remap="tt">slotPenBrush()</literal> und 
<literal remap="tt">slotPenColor()</literal> auch einzufügen, damit Ihre Klasse <literal remap="tt">KScribbleApp</literal> sie 
überhaupt kennt. </para>

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

<para><itemizedlist>
<listitem>
<para> Zunächst haben wir Menü- und Werkzeugleistenkommandos und -elemente für zwei 
Dialoge, die wir aufrufen wollen, eingefügt.</para>
</listitem>
<listitem>
<para> Dann haben wir mit dem Dialogeditor den Dialog zum Einstellen der Stiftweite 
erstellt.</para>
</listitem>
<listitem>
<para> Als nächstes haben wir die Methoden geschrieben, die die Dialoge aufrufen und die 
Eigenschaften setzen die wir ändern wollen.</para>
</listitem>
<listitem>
<para> 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.</para>
</listitem>
</itemizedlist>
</para>

<para>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.</para>
</sect1>
</chapter>

<chapter id="erweiterte-ansichten">
<title>Erweiterte Ansichten</title>

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

<sect1 id="synchronisierung-der-ansichten">
<title>Synchronisierung der Ansichten</title>

<para>Lassen Sie uns zuerst erklären, was uns das Ganze bringen wird und wie wir es erreichen 
können. Während Sie mit <emphasis>KScribble</emphasis> experimentiert haben, werden Sie vielleicht 
bemerkt haben, daß, wenn Sie eine neue Ansicht des Dokuments durch Aufruf von 
"Fenster"-&gt;"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 <literal remap="tt">KScribbleView::paintEvent()</literal> 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 
<literal remap="tt">updateAllViews()</literal>, welche die Methode <literal remap="tt">update()</literal> 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 <literal remap="tt">bitBlt()</literal> 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 <literal remap="tt">update()</literal> nur ausgeführt werden soll, 
wenn Sender und Empfänger verschieden sind.   </para>

<para>Was Sie hier tun müssen, ist nur updateAllViews(this) am Ende der virtuellen Methoden 
<literal remap="tt">mousePressEvent()</literal>, <literal remap="tt">mouseMoveEvent()</literal> und 
<literal remap="tt">mouseReleaseEvent()</literal> 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 <literal remap="tt">updateAllViews()</literal> auf. Wie die Aktualisierung 
durchgeführt wird, muß in der <literal remap="tt">update()</literal> 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 <literal remap="tt">repaint()</literal> auf, die ein paint-Event 
auslöst und den Inhalt des Dokuments wieder in die Ansicht kopiert.</para>
</sect1>

<sect1 id="rollbare-ansichten">
<title>Rollbare Ansichten</title>

<para>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 <literal remap="tt">QScrollView</literal>,die selber von <literal remap="tt">QWidget</literal> 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 <literal remap="tt">QScrollView</literal> verwendet, die zu 
verwaltenden Kindfenster mit der Rollansicht als Eltern erstellt und sie mit 
<literal remap="tt">addChild()</literal> der Rollansicht hinzufügt, oder eine Ansicht von <literal remap="tt">QScrollView</literal> 
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ß 
<literal remap="tt">QScrollView</literal> einen Satz von Ereignisbehandlungsroutinen, ähnlich denen von 
<literal remap="tt">QWidget</literal>, extra für den Viewport bereitstellt. Das was also vorher 
<literal remap="tt">mousePressEvent()</literal> in unserer Ansicht war, ist jetzt 
ein viewportMousePressEvent, ein <literal remap="tt">paintEvent()</literal> 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:   </para>

<para><itemizedlist>
<listitem>
<para> 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.</para>
</listitem>
<listitem>
<para> Ändern der Elternklasse für KScribbleView von <literal remap="tt">QWidget</literal> nach 
<literal remap="tt">QScrollView</literal>. </para>
</listitem>
<listitem>
<para> Umbenennen der virtuellen Methoden in die entsprechenden Viewportmethoden aus 
<literal remap="tt">QScrollView</literal>. </para>
</listitem>
<listitem>
<para> Adaptieren der virtuellen Event Handler, sodaß sie die Geometrie des Viewports 
beeinflussen. Das bedeutet, daß die alte Implementation auf die Geometrie von 
<literal remap="tt">QWidget</literal> 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 <literal remap="tt">QWidget</literal> gelieferten Positionen in Viewportkoordinaten übersetzt werden. </para>
</listitem>
</itemizedlist>
</para>

<sect2 id="gre-des-dokumentinhalts-verndern">
<title>Größe des Dokumentinhalts verändern</title>

<para>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 <literal remap="tt">QSize size</literal> sowie die Methode 
<literal remap="tt">docSize()</literal> in <literal remap="tt">KScribbleDoc</literal> ein:</para>

<para><programlisting>kscribbledoc.h:

#include &lt;qsize.h&gt;

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

private:
  QSize size;
</programlisting>
</para>

<para>Jetzt müssen wir alle Methoden, die Dokumentinhalte öffnen oder initialisieren, modifizieren.Dies sind <literal remap="tt">newDocument()</literal> und
<literal remap="tt">openDocument()</literal>:</para>

<para><programlisting>  bool KScribbleDoc::newDocument()
  {
    /////////////////////////////////////////////////
    // TODO: Add your document initialization code here
-&gt;  size=QSize(300,200 );
   	pen=QPen( Qt::black, 3 );
-&gt;  buffer.resize(size);
-&gt;  buffer.fill( Qt::white );
    /////////////////////////////////////////////////
    modified=false;
    return true;
  }

  bool KScribbleDoc::openDocument(const QString &&amp;;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;
-&gt;  size=buffer.size();
    /////////////////////////////////////////////////
  //	f.close();
  	
    modified=false;
    m_filename=filename;
  	m_title=QFileInfo(f).fileName();
    return true;
  }</programlisting>
</para>

<para>In <literal remap="tt">newDocument()</literal> 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 <literal remap="tt">QPixmap::size()</literal> aufruft, was 
wir bereits in <literal remap="tt">openDocument()</literal> 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. </para>
</sect2>
</sect1>

<sect1 id="adaptieren-der-ansicht">
<title>Adaptieren der Ansicht</title>

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

<para><programlisting>#include &lt;qscrollview.h&gt;

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* );
}</programlisting>
</para>

<para>An dieser Stelle haben wir zuerst <literal remap="tt">QWidget</literal> durch <literal remap="tt">QScrollView</literal> 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 <literal remap="tt">QScrollView</literal> 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.</para>

<para><programlisting>#include &lt;qsize.h&gt;

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

-&gt;  setResizePolicy ( QScrollView::ResizeOne );
-&gt;  viewport()-&gt;setCursor( Qt::crossCursor );

-&gt;    QSize size=doc-&gt;docSize();
      // resize the viewport - this makes the resizeEvent obsolete
-&gt;    resizeContents(size.width(), size.height());
      // resize the widget to show up with the document size
-&gt;    resize(size);
}</programlisting>
</para>

<para>Beachten Sie, daß sich vorher <literal remap="tt">resizeEvent()</literal> 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 <literal remap="tt">newDocument()</literal> und <literal remap="tt">openDocument()</literal>), passen 
wir jetzt die Größe des Inhalts einfach durch Aufruf von <literal remap="tt">resizeContents()</literal> aus <literal remap="tt">QScrollView</literal> 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 <literal remap="tt">viewport()</literal> 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. </para>

<para><emphasis remap="bf">Achtung:</emphasis> Denken Sie daran die <literal remap="tt">resizeEvent()</literal> Implementation auszukommentieren!</para>

<para>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 <literal remap="tt">bitBlt()</literal> von this nach <literal remap="tt">viewport()</literal> ä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:</para>

<para><programlisting>void KScribbleView::viewportPaintEvent( QPaintEvent *e )
{
  bitBlt( viewport(),0,0, &&amp;;doc-&gt;buffer,contentsX() ,contentsY() );
}</programlisting>
</para>

<para><literal remap="tt">contentsX()</literal> 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 <literal remap="tt">bitBlt()</literal> würde sein, die Werte der Positionen zu switchen und die Werte der Inhalte zu 
vertauschen:</para>

<para>bitBlt( viewport(), -contentsX(), -contentsY(), &&amp;;doc-&gt;buffer, 0, 0 );</para>

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

<para><programlisting>  void KScribbleView::viewportMousePressEvent( QMouseEvent *e )
  {
    mousePressed = TRUE;
-&gt;  doc-&gt;polyline[2] = doc-&gt;polyline[1] = doc-&gt;polyline[0] = viewportToContents(e-&gt;pos());
    doc-&gt;updateAllViews(this);
  }

  void KScribbleView::viewportMouseMoveEvent( QMouseEvent *e )
  {
    if ( mousePressed ) {
  ....
      doc-&gt;polyline[1] = doc-&gt;polyline[0];
-&gt;    doc-&gt;polyline[0] = viewportToContents(e-&gt;pos());
      painter.drawPolyline( doc-&gt;polyline );
  ....
      r.setBottom( r.bottom() + doc-&gt;penWidth() );

  	  doc-&gt;setModified();
-&gt;    bitBlt(viewport(), r.x()-contentsX(), r.y()-contentsY() ,
-&gt;            &&amp;;doc-&gt;buffer, r.x(), r.y(), r.width(), r.height() );
  	  doc-&gt;updateAllViews(this);
    }
  }</programlisting>
</para>

<para>In <literal remap="tt">viewportMouseMoveEvent()</literal> mußten wir wieder das Ziel von this nach <literal remap="tt">viewport()</literal> ändern - und damit die 
Positionen übersetzen. Diesmal haben wir die zweite Variante des Aufrufs, den wir in <literal remap="tt">viewportPaintEvent()</literal> 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.</para>

<para>Schließlich werden wir noch eine kleine Änderung an der <literal remap="tt">update()</literal> 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 
<literal remap="tt">repaint()</literal> aufzurufen, könnten wir ebensogut <literal remap="tt">repaint(false)</literal> 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 <literal remap="tt">QScrollView</literal> können wir die Zeichenaktionen sogar noch weiter reduzieren: 
wir beschränken die update Methode darauf <literal remap="tt">repaint()</literal> auf dem viewport() Widget aufzurufen, da dies wiederum 
<literal remap="tt">viewportPaintEvent()</literal> 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: </para>

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

<para>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.</para>

<para>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 - <emphasis>KScribble</emphasis> 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.</para>
</sect1>
</chapter>

<chapter id="verwendung-von-kimageio-mit-kscribble">
<title>Verwendung von KImageIO mit KScribble</title>

<para>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 <literal remap="tt">QImageIO</literal>, die auch eine 
Supportbibliothek unter KDE hat: <literal remap="tt">KImageIO</literal>. Zur Vorbereitung und bevor wir diese 
schönen Routinen verwenden können, müssen wir die erforderliche Bibliothek für <emphasis>KScribble</emphasis> 
verfügbar machen: nämlich libkimgio. Öffnen Sie "Projekt"-&gt;"Optionen" in KDevelop und fügen Sie 
die Zeile -libkimgio bei den zusätzlichen Bibliotheken ein. </para>

<sect1 id="vorbereiten-des-dokuments">
<title>Vorbereiten des Dokuments</title>

<para>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 <literal remap="tt">openDocument()</literal> und <literal remap="tt">saveDocument()</literal>:</para>

<para>In der Methode <literal remap="tt">KScribbleDoc::openDocument()</literal>:</para>

<para><literal remap="tt">	if(!buffer.load( filename, format ))</literal></para>

<para>In der Methode <literal remap="tt">KScribbleDoc:: saveDocument()</literal>:</para>

<para><literal remap="tt">	if(!buffer.save( filename, format ))</literal></para>

<para>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.</para>
</sect1>

<sect1 id="registrierung-von-dateiformaten">
<title>Registrierung von Dateiformaten</title>

<para>Um <literal remap="tt">KImageIO</literal> zu benutzen, müssen wir erst die Bibliothek initialisieren. Dazu fügen wir 
einen Aufruf von <literal remap="tt">registerFormats()</literal> in unsere <literal remap="tt">main()</literal> Funktion ein:</para>

<para><programlisting> 	
main.cpp:

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

  if (app.isRestored())
............</programlisting>
</para>

<para>Beachten Sie, daß dieser Aufruf nach der Instanzierung mit KApplication app erfolgt- ohne die 
Applikationsinstanz läuft unser Programm nicht, weil <literal remap="tt">KImageIO</literal> 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 <literal remap="tt">KScribbleApp</literal> verwenden 
werden: </para>

<para>kscribble.h:</para>

<para>&&num;;include &&lt;;kimgio.h&&gt;;</para>
</sect1>

<sect1 id="bilddateien-ffnen">
<title>Bilddateien öffnen</title>

<para>Jetzt, da wir <literal remap="tt">KImageIO</literal> verwenden können, werden wir die erste Änderung in der 
wichtigsten Methode von <literal remap="tt">KScribbleApp</literal> vornehmen: <literal remap="tt">openDocumentFile()</literal>. 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 
<literal remap="tt">KScribbleDoc::openDocument()</literal> in der Methode <literal remap="tt">openDocumentFile()</literal> adaptieren:</para>

<para><programlisting>kscribble.cpp:

  void KScribbleApp::openDocumentFile(const char* file)
  {
  	...........
  	
  	else
    {
-&gt;    QString format=KImageIO::type(file);
-&gt;	  if(!doc-&gt;openDocument(file,format))
    		KMessageBox::error (this,i18n("Could not open document !"), i18n("Error !"));
  		addRecentFile(file);
  	}
    ............
  }</programlisting>
</para>

<para>Es funktioniert natürlich genauso wie die Verwendung von <literal remap="tt">QString format=QImageIO::imageFormat(file);</literal>. Hier liefert uns <literal remap="tt">KImageIO</literal> 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).</para>
</sect1>

<sect1 id="setzen-von-dateifiltern-mit-kimageio">
<title>Setzen von Dateifiltern mit KImageIO</title>

<para>Mit diesem Kapitel endet unsere Einführung. Wir werden die Dateidialoge von <emphasis>KScribble</emphasis> dazu 
bringen, Dateifilter zu verwenden. Zu diesem Zweck bietet <literal remap="tt">KImageIO</literal> 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 <literal remap="tt">pattern()</literal> Methode von <literal remap="tt">KimageIO</literal> 
verwendet:</para>

<para><programlisting>  void KScribbleApp::slotFileOpen()
  {
    slotStatusMsg(i18n("Opening file..."));
  	
-&gt;    QString fileToOpen=KFileDialog::getOpenFileName(QDir::currentDirPath(),
-&gt;              KImageIO::pattern(KImageIO::Reading), this, i18n("Open File..."));
    if(!fileToOpen.isEmpty())
    {
  		openDocumentFile(fileToOpen);		
    }

    slotStatusMsg(i18n("Ready."));
  }</programlisting>
</para>

<para>Wir setzen hier den Modus in <literal remap="tt">pattern()</literal> 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 <literal remap="tt">slotFileSaveAs()</literal> aufgerufen, der dann seinerseits den 
Dateidialog aufruft, in dem dann der Dateiname abgefragt wird. Dort werden wir den Patternmodus zum 
Schreiben setzen: </para>

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

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

<chapter id="hinzufgen-der-druckerfunktionalitt">
<title>Hinzufügen der Druckerfunktionalität</title>

<para>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 <emphasis>KScribble</emphasis> benutzt oder "Drucken" aus dem Dateimenü wählt, wird die <literal remap="tt">slotFilePrint()</literal> 
Methode in <literal remap="tt">KScribbleApp</literal> aufgerufen. Diese Methode prüft, welches Kindfenster gerade aktiv ist und erzeugt eine Instanz 
der Druckerklasse <literal remap="tt">QPrinter</literal>. Dann ruft sie die Druckmethode des Widgets auf, nämlich <literal remap="tt">KScribbleView::print()</literal>. Das 
Framework enthält bereits die Basisimplementation- was zeigt, daß Sie zum Drucken nur die Klasse <literal remap="tt">QPainter</literal> verwenden 
müssen, die dann auf dem Drucker ausgibt. Diese Klasse ruft auch den Druckdialog auf.</para>

<para>Was wir hier tun müssen, ist <literal remap="tt">QPainter</literal> Methoden zu benutzen, um das Pixmap des Dokuments zu drucken, das mit der Ansicht 
verbunden ist. Da <literal remap="tt">QPainter</literal> bereits einen Satz an <literal remap="tt">drawPixmap()</literal> Methoden anbietet, werden wir natürlich eine davon 
benutzen: </para>

<para><programlisting>void KScribbleView::print(QPrinter *pPrinter)
{
  if (pPrinter-&gt;setup(this))
  {
    QPainter p;
    p.begin(pPrinter);
    	  	
    ///////////////////////////////
    // TODO: add your printing code here
-&gt;	p.drawPixmap(0,0,doc-&gt;buffer);
    ///////////////////////////////
    p.end();
  }
}</programlisting>
</para>

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

<para>Sie können es einfach einmal ausprobieren- Sie können nun jede Grafik drucken, die <emphasis>KScribble</emphasis> öffnen kann.</para>

<para>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&&amp;;Drop Funktionen hinzufügt, hier finden:</para>

<para><ulink url="../../../../../apps/kdevelop/examples/kscribble-1.0.tar.gz">$(KDEDIR)/share/apps/kdevelop/examples/kscribble-1.0.tar.gz</ulink> </para>

<para>Das Beispieltarball ist lokal installiert und kann in Ihr Homerverzeichnis kopiert werden, wo Sie es auspacken und testen können. 
Nach dem Auspacken mit <literal remap="tt">tar zxvf kscribble-1.0.tar.gz</literal> 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.</para>

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

<chapter id="copyright">
<title>Copyright  </title>

<para><screen> 

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. 

</screen>
</para>
</chapter>
<appendix id="kscribble-1.0-beispiel-quellcode">
<title>KScribble-1.0 Beispiel Quellcode</title>

<sect1 id="projekt-tarball">
<title>Projekt Tarball</title>

<para>Das Beispiel Tarball ist lokal installiert und Sie können es sich in Ihr Homeverzeichnis kopieren, auspacken und testen. Nachdem 
Sie das Paket mit <literal remap="tt">tar zxvf kscribble-1.0.tar.gz</literal> 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. </para>

<para><ulink url="../../../../../apps/kdevelop/examples/kscribble-1.0.tar.gz">$(KDEDIR)/share/apps/kdevelop/examples/kscribble-1.0.tar.gz</ulink>   	</para>
</sect1>

<sect1 id="main.cpp">
<title>main.cpp</title>

<para><programlisting>/***************************************************************************
                          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 &lt;kcmdlineargs.h&gt;
#include &lt;kaboutdata.h&gt;
#include &lt;klocale.h&gt;

#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, &&amp;;aboutData );
   KCmdLineArgs::addCmdLineOptions( options ); // Add our own options.

   KApplication app;
   KImageIO::registerFormats();

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

      KCmdLineArgs *args = KCmdLineArgs::parsedArgs();

      if (args-&gt;count())
        for(int i=0;i&lt;args-&gt;count();i++)
          kscribble-&gt;openDocumentFile(args-&gt;arg(i));
      else
        kscribble-&gt;openDocumentFile();

      args-&gt;clear();
   }

   return app.exec();
}</programlisting>
</para>
</sect1>

<sect1 id="kscribble.h">
<title>kscribble.h</title>

<para><programlisting>/***************************************************************************
                          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 &lt;config.h&gt;
#endif

// include files for Qt
#include &lt;qstrlist.h&gt;
#include &lt;qworkspace.h&gt;

// include files for KDE
#include &lt;kapp.h&gt;
#include &lt;ktmainwindow.h&gt;
#include &lt;kaccel.h&gt;
#include &lt;kimgio.h&gt;

// 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 &&amp;;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 &&amp;;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 &&amp;;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"-&gt;"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&lt;KScribbleDoc&gt; *pDocList;  

};

#endif // KSCRIBBLE_H</programlisting>
</para>
</sect1>

<sect1 id="kscribble.cpp">
<title>kscribble.cpp</title>

<para><programlisting>/***************************************************************************
                          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 &lt;qdir.h&gt;
#include &lt;qprinter.h&gt;
#include &lt;qvbox.h&gt;
#include &lt;qwhatsthis.h&gt;
#include &lt;qtooltip.h&gt;
#include &lt;qtoolbutton.h&gt;
#include &lt;qimage.h&gt;
#include &lt;qdragobject.h&gt;


// include files for KDE
#include &lt;kiconloader.h&gt;
#include &lt;kmessagebox.h&gt;
#include &lt;kfiledialog.h&gt;
#include &lt;kcolordlg.h&gt;
#include &lt;kmenubar.h&gt;
#include &lt;klocale.h&gt;
#include &lt;kconfig.h&gt;

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


KScribbleApp::KScribbleApp()
{
  config=kapp-&gt;config();
  printer = new QPrinter;
  untitledCount=0;
  pDocList = new QList&lt;KScribbleDoc&gt;();
  pDocList-&gt;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-&gt;connectItem(KStdAccel::New, this, SLOT(slotFileNew()));
  keyAccel-&gt;connectItem(KStdAccel::Open, this, SLOT(slotFileOpen()));
  keyAccel-&gt;connectItem(KStdAccel::Save, this, SLOT(slotFileSave()));
  keyAccel-&gt;connectItem(KStdAccel::Close, this, SLOT(slotFileClose()));
  keyAccel-&gt;connectItem(KStdAccel::Print, this, SLOT(slotFilePrint()));
  keyAccel-&gt;connectItem(KStdAccel::Quit, this, SLOT(slotFileQuit()));
  // editMenu accelerators
  keyAccel-&gt;connectItem(KStdAccel::Cut, this, SLOT(slotEditCut()));
  keyAccel-&gt;connectItem(KStdAccel::Copy, this, SLOT(slotEditCopy()));
  keyAccel-&gt;connectItem(KStdAccel::Paste, this, SLOT(slotEditPaste()));

  keyAccel-&gt;connectItem(KStdAccel::Help, this, SLOT(appHelpActivated()));
      
  keyAccel-&gt;changeMenuAccel(pFileMenu, ID_FILE_NEW, KStdAccel::New);
  keyAccel-&gt;changeMenuAccel(pFileMenu, ID_FILE_OPEN, KStdAccel::Open);
  keyAccel-&gt;changeMenuAccel(pFileMenu, ID_FILE_SAVE, KStdAccel::Save);
  keyAccel-&gt;changeMenuAccel(pFileMenu, ID_FILE_CLOSE, KStdAccel::Close);
  keyAccel-&gt;changeMenuAccel(pFileMenu, ID_FILE_PRINT, KStdAccel::Print);
  keyAccel-&gt;changeMenuAccel(pFileMenu, ID_FILE_QUIT, KStdAccel::Quit);

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

  keyAccel-&gt;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-&gt;insertItem(BarIcon("filenew"), i18n("&&amp;;New"), ID_FILE_NEW);
  pFileMenu-&gt;insertItem(BarIcon("fileopen"), i18n("&&amp;;Open..."), ID_FILE_OPEN);
  pFileMenu-&gt;insertItem(i18n("Open &&amp;;recent"), pRecentFileMenu, ID_FILE_OPEN_RECENT);

  pFileMenu-&gt;insertItem(i18n("&&amp;;Close"), ID_FILE_CLOSE);
  pFileMenu-&gt;insertSeparator();
  pFileMenu-&gt;insertItem(BarIcon("filefloppy") ,i18n("&&amp;;Save"), ID_FILE_SAVE);
  pFileMenu-&gt;insertItem(i18n("Save &&amp;;As..."), ID_FILE_SAVE_AS);
  pFileMenu-&gt;insertSeparator();
  pFileMenu-&gt;insertItem(BarIcon("fileprint"), i18n("&&amp;;Print..."), ID_FILE_PRINT);
  pFileMenu-&gt;insertSeparator();
  pFileMenu-&gt;insertItem(i18n("E&&amp;;xit"), ID_FILE_QUIT);
  
  ///////////////////////////////////////////////////////////////////
  // menuBar entry edit-Menu
  pEditMenu = new QPopupMenu(this);
  pEditMenu-&gt;insertItem(BarIcon("undo"), i18n("&&amp;;Undo"), ID_EDIT_UNDO);
  pEditMenu-&gt;insertSeparator();
  pEditMenu-&gt;insertItem(BarIcon("editcut"), i18n("Cu&&amp;;t"), ID_EDIT_CUT);
  pEditMenu-&gt;insertItem(BarIcon("editcopy"), i18n("&&amp;;Copy"), ID_EDIT_COPY);
  pEditMenu-&gt;insertItem(BarIcon("editpaste"), i18n("&&amp;;Paste"), ID_EDIT_PASTE);
  pEditMenu-&gt;insertItem(BarIcon("delete"),i18n("&&amp;;Clear All"), ID_EDIT_CLEAR_ALL);

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

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

  ///////////////////////////////////////////////////////////////////
  // menuBar entry window-Menu
  pWindowMenu = new QPopupMenu(this);
  pWindowMenu-&gt;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()-&gt;insertItem(i18n("&&amp;;File"), pFileMenu);
  menuBar()-&gt;insertItem(i18n("&&amp;;Edit"), pEditMenu);
  menuBar()-&gt;insertItem(i18n("&&amp;;Pen"), pPenMenu);
  menuBar()-&gt;insertItem(i18n("&&amp;;View"), pViewMenu);
  menuBar()-&gt;insertItem(i18n("&&amp;;Window"), pWindowMenu );
  menuBar()-&gt;insertItem(i18n("&&amp;;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()-&gt;insertButton(BarIcon("filenew"), ID_FILE_NEW, true, i18n("New File"));
  toolBar()-&gt;insertButton(BarIcon("fileopen"), ID_FILE_OPEN, true, i18n("Open File"));
  toolBar()-&gt;insertButton(BarIcon("filefloppy"), ID_FILE_SAVE, true, i18n("Save File"));
  toolBar()-&gt;insertButton(BarIcon("fileprint"), ID_FILE_PRINT, true, i18n("Print"));
  toolBar()-&gt;insertSeparator();
  toolBar()-&gt;insertButton(BarIcon("editcut"), ID_EDIT_CUT, true, i18n("Cut"));
  toolBar()-&gt;insertButton(BarIcon("editcopy"), ID_EDIT_COPY, true, i18n("Copy"));
  toolBar()-&gt;insertButton(BarIcon("editpaste"), ID_EDIT_PASTE, true, i18n("Paste"));
  toolBar()-&gt;insertSeparator();
  toolBar()-&gt;insertButton(BarIcon("pencolor"), ID_PEN_COLOR, true, i18n("Color") );
  toolBar()-&gt;insertButton(BarIcon("penwidth"), ID_PEN_BRUSH, true, i18n("Width") );
  toolBar()-&gt;insertSeparator();
  toolBar()-&gt;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()-&gt;insertWidget(ID_HELP_WHATS_THIS, btnwhat-&gt;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()-&gt;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-&gt;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-&gt;activeWindow()-&gt;caption());
}

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

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

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

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

  pRecentFileMenu-&gt;clear();

  for ( int i =0 ; i &lt; (int)recentFiles.count(); i++){
    pRecentFileMenu-&gt;insertItem(recentFiles.at(i));
  }

}

void KScribbleApp::createClient(KScribbleDoc* doc)
{
  KScribbleView* w = new KScribbleView(doc, pWorkspace,0,WDestructiveClose);
  w-&gt;installEventFilter(this);
  doc-&gt;addView(w);
  w-&gt;setIcon(kapp-&gt;miniIcon());
  if ( pWorkspace-&gt;windowList().isEmpty() ) // show the very first window in maximized mode
    w-&gt;showMaximized();
  else
    w-&gt;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-&gt;first(); doc &gt; 0; doc=pDocList-&gt;next())
  {
    if(doc-&gt;pathName()==file)
    {
       KScribbleView* view=doc-&gt;firstView();  
       view-&gt;setFocus();
       return;
     }
  }
  doc = new KScribbleDoc();
  pDocList-&gt;append(doc);
  doc-&gt;newDocument();
  // Creates an untitled window if file is 0  
  if(!file)
  {
    untitledCount+=1;
    QString fileName=QString(i18n("Untitled%1")).arg(untitledCount);
    doc-&gt;setPathName(fileName);
    doc-&gt;setTitle(fileName);
  }
  // Open the file
  else
  {
    QString format=QImageIO::imageFormat(file);
    if(!doc-&gt;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-&gt;setGroup("General Options");
  config-&gt;writeEntry("Geometry", size());
  config-&gt;writeEntry("Show Toolbar", toolBar()-&gt;isVisible());
  config-&gt;writeEntry("Show Statusbar",statusBar()-&gt;isVisible());
  config-&gt;writeEntry("ToolBarPos", (int) toolBar()-&gt;barPos());
  config-&gt;writeEntry("Recent Files", recentFiles);
}


void KScribbleApp::readOptions()
{
  
  config-&gt;setGroup("General Options");

  // bar status settings
  bool bViewToolbar = config-&gt;readBoolEntry("Show Toolbar", true);
  menuBar()-&gt;setItemChecked(ID_VIEW_TOOLBAR, bViewToolbar);
  if(!bViewToolbar)
  {
     enableToolBar(KToolBar::Hide);
  }
  
  bool bViewStatusbar = config-&gt;readBoolEntry("Show Statusbar", true);
  menuBar()-&gt;setItemChecked(ID_VIEW_STATUSBAR, bViewStatusbar);
  if(!bViewStatusbar)
  {
    enableStatusBar(KStatusBar::Hide);
  }

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

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

  for (int i=0; i &lt; (int) recentFiles.count(); i++)
  {
    pRecentFileMenu-&gt;insertItem(recentFiles.at(i));
  }

  QSize size=config-&gt;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-&gt;isEmpty())
    return true;

  for(doc=pDocList-&gt;first(); doc!=0;doc=pDocList-&gt;next()){
    if(doc-&gt;isModified())
      saveFiles.append(doc-&gt;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-&gt;first(); doc!=0;doc=pDocList-&gt;next()){
        if(doc-&gt;title().contains(i18n("Untitled")))
          slotFileSaveAs();
        else
        {
          if(!doc-&gt;saveDocument(doc-&gt;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-&gt;type() == QEvent::Close)
  {
    QCloseEvent* e=(QCloseEvent*)event;
    KScribbleView* pView=(KScribbleView*)object;
    KScribbleDoc* pDoc=pView-&gt;getDocument();
    if(pDoc-&gt;canCloseFrame(pView))
    {
       pDoc-&gt;removeView(pView);
       if(!pDoc-&gt;firstView())
         pDocList-&gt;remove(pDoc);
       
      e-&gt;accept();
      //////////////  
      if(pWorkspace-&gt;windowList().count()==1)
        setPlainCaption(kapp-&gt;caption());
      else
        setCaption(pWorkspace-&gt;activeWindow()-&gt;caption());      
      //////////////
    }
    else
      e-&gt;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-&gt;text(id_));
  
  slotStatusMsg(i18n("Ready."));
}

void KScribbleApp::slotFileSave()
{
  slotStatusMsg(i18n("Saving file..."));
  KScribbleView* m = (KScribbleView*)pWorkspace-&gt;activeWindow();
  if( m )
  {
    KScribbleDoc* doc =  m-&gt;getDocument();
    if(doc-&gt;title().contains(i18n("Untitled")))
     slotFileSaveAs();
    else
      if(!doc-&gt;saveDocument(doc-&gt;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-&gt;activeWindow();
    if( m )
    {
      KScribbleDoc* doc =  m-&gt;getDocument();
      QString format=QFileInfo(newName).extension();
      format=format.upper();
      if(!doc-&gt;saveDocument(newName,format))
      {
        KMessageBox::error (this,i18n("Could not save the current document !"), i18n("I/O Error !"));
        return;
      }
      doc-&gt;changedViewList();
      setWndTitle(m);
    }
    
  }

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

void KScribbleApp::slotFileClose()
{
  slotStatusMsg(i18n("Closing file..."));
  
  KScribbleView* m = (KScribbleView*)pWorkspace-&gt;activeWindow();
  if( m )
  {
    KScribbleDoc* doc=m-&gt;getDocument();
    doc-&gt;closeDocument();
  }

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

void KScribbleApp::slotFilePrint()
{
  slotStatusMsg(i18n("Printing..."));
  
  KScribbleView* m = (KScribbleView*) pWorkspace-&gt;activeWindow();
  if ( m )
    m-&gt;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-&gt;first(); w!=0; w=memberList-&gt;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-&gt;close())
      break;
    }
  }  
  slotStatusMsg(i18n("Ready."));
}

void KScribbleApp::slotEditUndo()
{
  slotStatusMsg(i18n("Reverting last action..."));
  
  KScribbleView* m = (KScribbleView*) pWorkspace-&gt;activeWindow();
  if ( m )
//    m-&gt;undo();

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

void KScribbleApp::slotEditCut()
{
  slotStatusMsg(i18n("Cutting selection..."));
  
  KScribbleView* m = (KScribbleView*) pWorkspace-&gt;activeWindow();
  if ( m )
    m-&gt;cutSelection();  

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

void KScribbleApp::slotEditCopy()
{
  slotStatusMsg(i18n("Copying selection to clipboard..."));
  
  KScribbleView* m = (KScribbleView*) pWorkspace-&gt;activeWindow();
  if ( m )
    m-&gt;copySelection();
    
  slotStatusMsg(i18n("Ready."));
}

void KScribbleApp::slotEditPaste()
{
  slotStatusMsg(i18n("Inserting clipboard contents..."));
  
  KScribbleView* m = (KScribbleView*) pWorkspace-&gt;activeWindow();
  if ( m )
    m-&gt;pasteSelection();
    
  slotStatusMsg(i18n("Ready."));
}

void KScribbleApp::slotEditClearAll()
{
  slotStatusMsg(i18n("Clearing the document contents..."));
  
  KScribbleView* m = (KScribbleView*) pWorkspace-&gt;activeWindow();
  if ( m ){
    KScribbleDoc* pDoc = m-&gt;getDocument();
    pDoc-&gt;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-&gt;windowList();
  KScribbleView* m = (KScribbleView*)windows.at(0);
  KScribbleDoc* pDoc = m-&gt;getDocument();
  int curr_width=pDoc-&gt;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-&gt;exec()){
    int width=dlg-&gt;width();
    for ( int i = 0; i &lt; int(windows.count()); ++i )
    {
      m = (KScribbleView*)windows.at(i);
      if ( m )
      {
        pDoc = m-&gt;getDocument();
        pDoc-&gt;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-&gt;windowList();
    KScribbleDoc* pDoc;
    KScribbleView* m;
    for ( int i = 0; i &lt; int(windows.count()); ++i )
    {
      m = (KScribbleView*)windows.at(i);
      if ( m )
      {
        pDoc = m-&gt;getDocument();
        pDoc-&gt;setPenColor(myColor);
      }
    }
  }
  slotStatusMsg(i18n("Ready."));
}


void KScribbleApp::slotViewToolBar()
{
  slotStatusMsg(i18n("Toggle the toolbar..."));
  ///////////////////////////////////////////////////////////////////
  // turn Toolbar on or off
  if( menuBar()-&gt;isItemChecked(ID_VIEW_TOOLBAR))
  {
    menuBar()-&gt;setItemChecked(ID_VIEW_TOOLBAR, false);
    enableToolBar(KToolBar::Hide);
  }
  else
  {
    menuBar()-&gt;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()-&gt;isItemChecked(ID_VIEW_STATUSBAR))
  {
    menuBar()-&gt;setItemChecked(ID_VIEW_STATUSBAR, false);
    enableStatusBar(KStatusBar::Hide);
  }
  else
  {
    menuBar()-&gt;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-&gt;activeWindow();
  if ( m ){
     KScribbleDoc* doc = m-&gt;getDocument();
    createClient(doc);
  }

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

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


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

void KScribbleApp::windowMenuAboutToShow()
{
  pWindowMenu-&gt;clear();
  
  pWindowMenu-&gt;insertItem(i18n("&&amp;;New Window"), ID_WINDOW_NEW_WINDOW);
  pWindowMenu-&gt;insertItem(i18n("&&amp;;Cascade"),
                          pWorkspace, SLOT(cascade() ),0 , ID_WINDOW_CASCADE );
  pWindowMenu-&gt;insertItem(i18n("&&amp;;Tile"),
                          pWorkspace, SLOT(tile() ),0 , ID_WINDOW_TILE );
  
  if ( pWorkspace-&gt;windowList().isEmpty() ) {
    disableCommand(ID_WINDOW_NEW_WINDOW);
    disableCommand(ID_WINDOW_CASCADE);
    disableCommand(ID_WINDOW_TILE);
  }
  
  pWindowMenu-&gt;insertSeparator();
  
  QWidgetList windows = pWorkspace-&gt;windowList();
  for ( int i = 0; i &lt; int(windows.count()); ++i ) {
    int id = pWindowMenu-&gt;insertItem(QString("&&amp;;%1 ").arg(i+1)+windows.at(i)-&gt;caption(),
                                     this, SLOT( windowMenuActivated( int ) ) );
    pWindowMenu-&gt;setItemParameter( id, i );
    pWindowMenu-&gt;setItemChecked( id, pWorkspace-&gt;activeWindow() == windows.at(i) );
  }
}

void KScribbleApp::windowMenuActivated( int id )
{
  QWidget* w = pWorkspace-&gt;windowList().at( id );
  if ( w )
    w-&gt;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-&gt;setPathName(fileName);
    doc-&gt;setTitle(fileName);
    doc-&gt;newDocument();
    pDocList-&gt;append(doc);
    KPixmap tmp;
    tmp.resize(img.size());
    tmp.convertFromImage(img);
    doc-&gt;setPixmap(tmp);
    doc-&gt;resizeDocument(tmp.size());
    doc-&gt;setModified();
    createClient(doc);
  }
}
/** accepts drag events for images */
void KScribbleApp::dragEnterEvent( QDragEnterEvent* e){
  e-&gt;accept(QImageDrag::canDecode(e));
}</programlisting>
</para>
</sect1>

<sect1 id="kscribbledoc.h">
<title>kscribbledoc.h</title>

<para><programlisting>/***************************************************************************
                          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 &lt;config.h&gt;
#endif

// include files for QT
#include &lt;qobject.h&gt;
#include &lt;qstring.h&gt;
#include &lt;qlist.h&gt;

#include &lt;qsize.h&gt;
#include &lt;qpen.h&gt;
#include &lt;qpoint.h&gt;
//#include &lt;qpixmap.h&gt;
#include &lt;qpointarray.h&gt;

#include &lt;kpixmap.h&gt;


// 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-&gt;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 &&amp;;filename, const char *format=0);
    /** saves the document under filename and format.*/  
    bool saveDocument(const QString &&amp;;filename, const char *format=0);
    /** sets the path to the file connected with the document */
    void setPathName(const QString &&amp;;name);
    /** returns the pathname of the current document file*/
    const QString&&amp;; pathName() const;

    /** sets the filename of the document */
    void setTitle(const QString &&amp;;title);
    /** returns the title of the document */
    const QString&&amp;; 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 &&amp;;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&lt;KScribbleView&gt; *pViewList;  
    QSize size;
};

#endif // KSCRIBBLEDOC_H
</programlisting>
</para>
</sect1>

<sect1 id="kscribbledoc.cpp">
<title>kscribbledoc.cpp</title>

<para><programlisting>/***************************************************************************
                          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 &lt;qdir.h&gt;
#include &lt;qfileinfo.h&gt;
#include &lt;qwidget.h&gt;

// include files for KDE
#include &lt;klocale.h&gt;
#include &lt;kmessagebox.h&gt;
#include &lt;kfiledialog.h&gt;

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


KScribbleDoc::KScribbleDoc()
{
  pViewList = new QList&lt;KScribbleView&gt;;
  pViewList-&gt;setAutoDelete(false);
}

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

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

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

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

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


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

}

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

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

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

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


void KScribbleDoc::closeDocument()
{
  KScribbleView *w;
  if(!isLastView())
  {
    for(w=pViewList-&gt;first(); w!=0; w=pViewList-&gt;next())
    {
        if(!w-&gt;close())
         break;
    }
  }
  if(isLastView())
  {
    w=pViewList-&gt;first();
    w-&gt;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 &&amp;;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 &&amp;;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);
}
</programlisting>
</para>
</sect1>

<sect1 id="kscribbleview.h">
<title>kscribbleview.h</title>

<para><programlisting>/***************************************************************************
                          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 &lt;config.h&gt;
#endif

// include files for Qt
#include &lt;qscrollview.h&gt;
#include &lt;kpixmap.h&gt;

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
</programlisting>
</para>
</sect1>

<sect1 id="kscribbleview.cpp">
<title>kscribbleview.cpp</title>

<para><programlisting>/***************************************************************************
                          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 &lt;iostream.h&gt;

// include files for Qt
#include &lt;qprinter.h&gt;
#include &lt;qpainter.h&gt;
#include &lt;qdir.h&gt;
#include &lt;qsize.h&gt;
#include &lt;qclipboard.h&gt;
#include &lt;qimage.h&gt;
#include &lt;qdragobject.h&gt;

// include files for KDE
#include &lt;kiconloader.h&gt;

// 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()-&gt;setAcceptDrops(true);
  setDragAutoScroll(true);
  doc=pDoc;
  action=IDLE;
  viewport()-&gt;setCursor( Qt::crossCursor );
  QSize size=doc-&gt;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()-&gt;repaint(0,0,visibleWidth(), visibleHeight(), false);
}

void KScribbleView::print(QPrinter *pPrinter)
{
  if (pPrinter-&gt;setup(this))
  {
    QPainter p;
    p.begin(pPrinter);
          
    ///////////////////////////////
    // TODO: add your printing code here
    p.drawPixmap(0,0,doc-&gt;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(&&amp;;cb_pix, 0, 0,
         &&amp;;doc-&gt;buffer, select.x()+contentsX(),  select.y()+contentsY(), cb_pix.width(), cb_pix.height());
  cb-&gt;setPixmap(cb_pix);
  // fill cb_pix with white and copy to selection area
  cb_pix.fill(Qt::white);
  bitBlt(&&amp;;doc-&gt;buffer, select.x()+contentsX(), select.y()+contentsY(),
         &&amp;;cb_pix, 0, 0, cb_pix.width(), cb_pix.height());
  action = IDLE;
  doc-&gt;setModified();
  doc-&gt;updateAllViews(this);
  viewport()-&gt;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(&&amp;;cb_pix, 0, 0,
         &&amp;;doc-&gt;buffer, select.x()+contentsX(),  select.y()+contentsY(),cb_pix.width(), cb_pix.height());
  cb-&gt;setPixmap(cb_pix);
  action = IDLE;
  viewport()-&gt;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-&gt;pixmap().rect();
  action = PASTE;
  viewport()-&gt;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-&gt;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()-&gt;height()-viewport()-&gt;height());
      break;
    case Key_PageUp:
      scrollBy( 0, -viewport()-&gt;height() );
      break;
    case Key_PageDown:
      scrollBy( 0, viewport()-&gt;height() );
      break;
  }

}

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

void KScribbleView::viewportMouseReleaseEvent( QMouseEvent *e )
{
  if ( action == DRAW )
  {
    action = IDLE;
    doc-&gt;updateAllViews(this);
  }
  if ( action == SELECT)
  {
    QPoint pt=e-&gt;pos();
    int x = pt.x() &gt; 0 ? pt.x() : 0;
    int y = pt.y() &gt; 0 ? pt.y() : 0;
    select.setRight(x);
    select.setBottom(y);
    QSize size=doc-&gt;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-&gt;pos());
    viewport()-&gt;setCursor( Qt::crossCursor );
    QPixmap cb_pix;
    cb_pix.resize(cb-&gt;pixmap().size());
    cb_pix=cb-&gt;pixmap();
    bitBlt( &&amp;;doc-&gt;buffer, contentsX()+select.x(), contentsY()+select.y(),
            &&amp;;cb_pix, 0,0 , select.width(),select.height() );
    viewport()-&gt;repaint(0,0,visibleWidth(), visibleHeight(), false);
    doc-&gt;setModified();
    doc-&gt;updateAllViews(this);
  }

}

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

    QRect r = doc-&gt;polyline.boundingRect();
    r = r.normalize();
    r.setLeft( r.left() - doc-&gt;penWidth() );
    r.setTop( r.top() - doc-&gt;penWidth() );
    r.setRight( r.right() + doc-&gt;penWidth() );
    r.setBottom( r.bottom() + doc-&gt;penWidth() );

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

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

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

  if( action == PASTE )
  {
    tmp.resize(cb-&gt;pixmap().size());
    tmp=cb-&gt;pixmap();
  }
  if( action == PASTE || action == DRAG )
  {
    QSize size=doc-&gt;docSize();
    select = select.intersect(QRect(0,0,size.width(), size.height()));  
    if(select.intersects(e-&gt;rect()))
      bitBlt(viewport(), select.x(), select.y(), &&amp;;tmp, 0, 0, select.width(), select.height());
  }
  if( action == PASTE || action == DRAG || action == SELECT )
  {
//    if(select.intersects(e-&gt;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-&gt;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-&gt;pos());
    viewport()-&gt;repaint(0,0,visibleWidth(), visibleHeight(), false);
  }
}

void  KScribbleView::viewportDragLeaveEvent ( QDragLeaveEvent * )
{
  action = IDLE;
  viewport()-&gt;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-&gt;pos());
    bitBlt(&&amp;;doc-&gt;buffer, select.x()+contentsX(), select.y()+contentsY(),
           &&amp;;tmp, 0, 0, tmp.width(), tmp.height());
    doc-&gt;setModified();
    doc-&gt;updateAllViews(this);
  }
  action = IDLE;
    viewport()-&gt;repaint(0,0,visibleWidth(), visibleHeight(), false);
}
</programlisting>
</para>
</sect1>

<sect1 id="kpenbrushdlg.h">
<title>kpenbrushdlg.h</title>

<para><programlisting>/***************************************************************************
                          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 &lt;qwidget.h&gt;
#include &lt;qspinbox.h&gt;
#include &lt;qlabel.h&gt;
#include &lt;qpushbutton.h&gt;
//Generated area. DO NOT EDIT!!!(end)

#include &lt;qdialog.h&gt;
#include &lt;klocale.h&gt;

/**
  *@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-&gt;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

</programlisting>
</para>
</sect1>

<sect1 id="kpenbrushdlg.cpp">
<title>kpenbrushdlg.cpp</title>

<para><programlisting>/***************************************************************************
                          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 &lt;qwhatsthis.h&gt;
#include &lt;kapp.h&gt;

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-&gt;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-&gt;setValue(3);
}
</programlisting>
</para>
</sect1>

<sect1 id="kpenbrushdlgdata.cpp">
<title>kpenbrushdlgdata.cpp</title>

<para><programlisting>/**********************************************************************
            --- 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 &lt;kapp.h&gt;
#include "kpenbrushdlg.h"

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

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

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

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

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

}</programlisting>
</para>
</sect1>

<sect1 id="resource.h">
<title>resource.h</title>

<para><programlisting>/***************************************************************************
                          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 &lt;config.h&gt;
#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
</programlisting>
</para>
</sect1>
  </appendix>
<![ %addindex; [ &docindex; ]]>
</book>
<!--
Local Variables:
mode: sgml
sgml-omittag: nil
sgml-shorttag: t
sgml-general-insert-case: lower
End:
-->
