Almetare-Logo

Programmierhandbuch

von Friedemann Seebass, Mai 2003
letzte Änderung: 20.4.2018

Inhalt

1 Einleitung
2 Designziele
3 Software-Struktur
    3.1 Klassen
    3.2 Datenstrukturen
    3.3 Dateien
4 Programmablauf
    4.1 Initialisierung
    4.2 Hauptschleife
    4.3 Grafische Oberfläche
5 Programmierkonventionen
    5.1 Namen und Bezeichner
    5.2 Organisation der Quelltexte
6 Anlegen des Almetare-Projektes unter Linux mit KDevelop 2.1
    6.1 Vorbereitungen
    6.2 Projekt und Projektordner mit KDevelop anlegen
    6.3 Makefiles erstellen
    6.4 Unterprojekte in KDevelop einrichten
    6.5 Projekt kompilieren
    6.6 Erstellen der Programmierer-Dokumentation mit Doxygen
7 Anlegen des Almetare-Projektes unter Windows mit Visual Studio 6
    7.1 Arbeitsbereich anlegen
    7.2 Projekte anlegen
    7.3 Quelltexte kopieren
    7.4 Projektdateien zu VS hinzufügen
    7.5 Projekteinstellungen in VS
    7.6 Kompilieren
    7.7 Zusätzliche Projekteinstellungen
    7.8 Binary patchen, falls mit VS Autorenversion kompiliert wurde
    7.9 Erstellen der Programmierer-Dokumentation mit Doxygen
8 Anlegen des Almetare-Projektes unter Linux und Qt 3 mit KDevelop 3
    8.1 Qt 3 vs. Qt 2
    8.2 Projekt und Unterprojekte anlegen
    8.3 Projektdateien kopieren
    8.4 Projektdateien hinzufügen
    8.5 Projekteinstellungen
    8.6 Makefile modifizieren
    8.7 Kompilieren


Hauptseite der Doxygen-Klassendokumentation (die Seite, die Sie gerade betrachten,  ist dort eingebettet; die Doxygen-Seiten sind allerdings nur in der Quelltext-Distribution vorhanden)


1 Einleitung

Almetare ist im wesentlichen eine Ansammlung von C++-Klassen, mit deren Hilfe man relativ einfach Taschenrechner mit verschiedenen Oberflächen implementieren kann. Entstanden ist sie aus meinem Wunsch heraus, die Klassenbibliothek Qt kennenzulernen. Zugegeben, die ganze Sache ist dann etwas ausgeufert ...

Das Projekt wird - hoffentlich - nach und nach alle meine alten Taschenrechner beinhalten. Es ist zu erwarten, daß sich dieser Prozeß über einen längeren Zeitraum erstreckt. Das Handbuch soll daher in erster Linie mir selbst als Gedächtnisstütze und Nachschlagewerk dienen. Vielleicht haben auch andere Leute Interesse daran, die Klassen zu verwenden; daher ist die Dokumentation etwas sauberer ausgefallen als ich sie sonst gestaltet hätte.

Die verwendete Qt-Version ist Qt 2. Mittlerweile (April 2018) liegt Qt 5.10.1 vor. Leider habe ich es zeitlich nie geschafft, das Projekt auf die neueren Qt-Versionen zu portieren. Unter Windows ist das kein Problem, weil man auch Qt-2-Projekte noch kompilieren kann. Unter Linux klappt das leider nicht, weil man an die alten Bibliotheken - jedenfalls mit den Bordmitteln der Distributionen - nicht mehr herankommt. Aktuelle Linux-Binaries des Projekts gibt es daher derzeit nicht.

Zur Zeit sind drei Taschenrechner realisiert, der Texas Instruments TI-30, der Casio fx-85v und der Casio fx-3600P. Die Konzepte der Software sind damit, wie ich glaube, ausreichend erkennbar. Die Dokumentation wird wahrscheinlich nicht erweitert werden, auch wenn später noch andere Taschenrechner implementiert sein sollten.

Das Programmierhandbuch besteht aus zwei Teilen: Diese erste Seite beschreibt die allgemeinen Konzepte der Software und wurde von Hand mit einem HTML-Editor erstellt, alle anderen Seiten sind mit Hilfe des Dokumentationstools "Doxygen" von Dimitri van Heesch (danke!) automatisch aus den Quelltexten generiert worden und beschreiben Klassen und Funktionen im Detail. (Genaugenommen wurde auch die erste Seite dann von Doxygen in die Hauptseite inkludiert.)

2 Designziele

Folgende Ziele sollten mit dem Design der Software erreicht werden: Wie immer waren natürlich auch ein paar Kompromisse nötig, z.B. was die beiden letzten Punkte angeht. Auch die Lauffähigkeit unter Linux und Windows ließ sich nur mit Hilfe einiger #ifdef _WIN32 u.ä. erreichen.

Eines war ausdrücklich nicht mein Ziel: Die Algorithmik der Taschenrechner (also nach welcher Methode etwa ein Sinus ausgerechnet wird) sollte nicht simuliert werden. Es kommt die ganz normale mathematische Bibliothek des Compilers zum Einsatz, grundsätzlich mit doppelter Genauigkeit. Dies sind ca. 15 Dezimalstellen und übertrifft die Genauigkeit der Rechner, die i.d.R. mit nur 8 bis 12 Stellen arbeiten. Zuweilen sind also geringfügige Abweichungen in der letzten Stelle einer Berechnung zwischen Almetare-Rechner und Original zu beobachten. Im Zweifel hat (hoffentlich) der Almetare-Rechner recht.

3 Software-Struktur

In diesem Abschnitt wird lediglich ein grober Überblick über die Software gegeben. Für Einzelheiten verweise ich auf die mit Doxygen erstellte Dokumentation.

3.1 Klassen

Beim Design der Klassenhierarchie habe ich mich vom realen Aufbau eines Taschenrechners leiten lassen: Ein Taschenrechner besteht im wesentlichen aus Dementsprechend gibt es die Klassen aus denen jeder Rechner besteht. Dabei hat der Calculator die beiden anderen Klassen - wie in der Natur - als Members (bzw. Zeiger darauf) "im Bauch". Für die grafische Oberfläche gibt es dann noch eine weitere Klasse CalculatorDlg; nur in dieser werden Elemente der Grafikbibliothek (Qt) verwendet. Diese Klasse enthält wiederum den Calculator als Element und kommuniziert mit ihm über eine Funktion des Calculators, die Strings entgegennimmt und verarbeitet:

Prinzipielle
          Almetare-Klassenstruktur

Abb. 3.1: Prinzipielle Almetare-Klassenstruktur

Hinweis: Die Komposition (schwarze Raute in UML-Notation) ist dabei logisch zu sehen: Z.B. enthält der Calculator aus Gründen der leichteren Erzeugbarkeit nur Zeiger auf seinen Computer und sein Display, obwohl die Raute eine Referenz suggeriert. Jedoch werden in der Tat ein Computer und Display immer zusammen mit ihrem Calculator erzeugt und vernichtet, so daß es sich - logisch gesehen - um eine Komposition handelt. Dies wird in allen noch gezeigten Diagrammen so behandelt.

In Wahrheit ist die Sache natürlich etwas komplizierter, denn es sollte ja nicht nur ein Rechner simuliert werden, sondern alle meine Taschenrechner! Daher gibt es eine Klassenhierarchie, die die verschiedenen Arten von Taschenrechnern widerspiegelt. In voller Blüte läßt sich diese bei den Computer-Klassen, den Rechenwerken, beobachten:

Ein Bild mag das verdeutlichen:

Hierarchie der
          Computer-Klassen

Abb. 3.2: Hierarchie der "Computer"-Klassen

Eine ähnliche Hierarchie gibt es auch für die Display- und Calculator-Klassen, wobei dort nicht alle Zwischenebenen benötigt werden.

Die vierte wichtige Klassenhierachie neben Calculator, Computer und Display ist die der Symbole:

Ausschnitt aus der Hierarchie der Symbol-Klassen

Abb. 3.3: Hierarchie der "Symbol"-Klassen (anklicken zum Vergrößern)

Im Prinzip entspricht jeder Taste eines Taschenrechners (besser gesagt, jeder Funktion eines Taschenrechners; denn Tasten sind in aller Regel über Shift-Ebenen mehrfach belegt) ein Symbol: "+", "-", "=", "sin" usw. Für jedes Symbol gibt es eine eigene Klasse und von jeder Klasse wiederum genau ein Objekt im Taschenrechner; die Symbol-Objekte werden mit "new" im Freispeicher angelegt, der Computer hält in einer Symboltabelle Zeiger auf diese Objekte und kann unter Ausnutzung der Polymorphie ihre Methoden aufrufen. Das Wissen zur Bearbeitung eines Symbols steckt nun in jedem Symbol selbst: So weiß z.B. das Symbol "+" (Klasse OpAdd), daß es zwei Operanden hat und eine Addition ausführen muß. Dies wird jeweils in der virtuellen Funktion process() des Symbols erledigt, die vom Computer aufgerufen wird, wenn die entsprechende Taste gedrückt wurde.

Daneben gibt es natürlich noch eine ganze Menge an Hilfs- und Arbeitsklassen; die meisten werden von der Computer-Klasse benötigt, die naturgemäß die meiste Arbeit erledigen muß. Die (hoffentlich weitgehend) komplette Klassenhierarchie, aus Platzgründen ohne die Symbolklassen (Abb. 3.3), zeigt die folgende Abbildung:

Alle Almetare-Klassen

Abb. 3.4: Alle Almetare-Klassen (ohne Symbole) (anklicken zum Vergrößern)

Die drei oben angedeuteten Klassen Calculator, Computer und Display befinden sich im Bild zentral auf der Mittellinie, die Hilfsklassen im oberen linken Bereich, die Dialogklassen oben rechts; die Symbolklassen schließen sich in Form der Symboltabelle als Elemente der Klasse ComputerBase an, das Bindeglied zwischen Abb. 3.3 und 3.4 ist die Komposition von CalculatorBase und SymTabT links in der Mitte.

Bitte beachten Sie, daß der Übersichtlichkeit halber nicht alle Abhängigkeiten zwischen den Klassen dargestellt werden konnten.

3.2 Datenstrukturen

Die wichtigsten Datenstrukturen eines Almetare-Rechners sind zwei Stacks, die Elemente der Klasse ComputerBase sind: Der eine Stack nimmt eine Menge von Operanden auf (das sind Zahlen), die andere eine Menge von Operatoren (alle anderen Symbole). Auf den Stacks werden eingegebene Operanden und Operatoren zwischengespeichert bis sie berechnet werden können.

Beispiel: Auf dem Rechner wird die Tastenfolge "2 + 3 * 4 + 5 =" eingegeben. Wie sehen die beiden Stacks nach jedem Tastendruck aus?

Taste NumStack SymStack
Erläuterung
2
2
  Die Zahl 2 wird auf den Zahlenstack gelegt.
+
2
+
Der Operator + wird auf den Symbolstack gelegt.
3
3
2
+
Die Zahl 3 wird zusätzlich oben auf den Zahlenstack gelegt.
*
3
2
*
+
Der Operator * wird zusätzlich oben auf den Symbolstack gelegt; die Addition kann noch nicht ausgeführt werden, weil * eine höhere Priorität hat als + ("Punkt vor Strich")
4
4
3
2
*
+
4 zusätzlich auf Zahlenstack.

(Schritt 1)
12
2
+
Da + niedrigere Priorität hat als *, kann der Rechner die Operation 3*4 = 12 ausführen, bevor das zweite + auf den Symbolstack gelegt wird. Er holt also die beiden Operanden 3 und 4 vom Zahlenstack, den Operator * vom Symbolstack und führt die Operation aus. Das Ergebnis wird wieder auf den Zahlenstack gelegt.
(Schritt 2)
14
+
Das + aus Schritt 1 liegt noch nicht auf dem Symbolstack; vielmehr wird zuvor wieder der oberste Operator auf dem Symbolstack angeschaut. Das dort liegende + hat dieselbe Priorität wie das auf den Stack zu legende (ei was), daher wird auch die Operation 2+12 sofort ausgeführt; also werden 12 und 2 wieder vom Zahlenstack und + vom Symbolstack geholt, die Operation wird berechnet und das Ergebnis 14 auf den Zahlenstack gelegt. Abschließend gelangt das neue + auf den Symbolstack
5
5
14
+
5 zusätzlich auf den Zahlenstack.
=
19

Mit = wird die Operation abgeschlossen; also die Addition 14+5 ausgeführt, + vom Symbolstack genommen und das Ergebnis 19 auf den Zahlenstack gelegt.

Tabelle 1: Stackoperationen

Wer manipuliert die Stacks? Oben wurde bereits gesagt, daß das Wissen zur Bearbeitung eines Symbols jedes Symbol selbst besitzt. Das bedeutet nämlich, daß, wenn z.B. "*" eingegeben wird, die in Abschnitt 3.1 genannte abstrakte Funktion process() des Symbols aufgerufen wird. Diese untersucht nun zunächst den obersten Operator auf dem Symbolstack (wegen der Priorität), weiß, daß sie zwei Operanden vom Stack holen und multiplizieren und das Ergebnis dort wieder ablegen muß. Um die Stacks zu finden, hat ein Symbol einen Zeiger auf ComputerBase (diese Klasse besitzt die Stacks) als Membervariable.

In Wahrheit ist die Sache auch hier wieder etwas komplizierter. ComputerBase enthält nämlich nicht die oben vereinfacht beschriebenen Stacks, sondern vielmehr zwei Listen von Stacks: mNumStackList vom Typ NumStackList und mSymStackList vom Typ SymStackList. Eine solche Stackliste besteht nun aus mehreren der beschriebenen Stacks, in der Regel jedoch aus nur einem (so daß die Verhältnisse dann so sind wie oben beschrieben). Allerdings wird der Liste immer dann ein neuer Stack hinzugefügt, wenn eine Klammer geöffnet wird, und der oberste Stack wird entfernt (nachdem er berechnet wurde), wenn eine Klammer geschlossen wird. Dies machen, man ahnt es schon, die process()-Funktionen der Symbole "(" und ")".

Für die Stacks habe ich ein eigenes Klassen-Template Stack erfunden, das im wesentlichen ein Vector aus der C++-STL ist, aber zusätzlich zu Debug-Zwecken schöne Log-Ausgaben machen kann.

Die weiteren Datenstrukturen sind weniger elaboriert und sollten aus der Doxygen-Doku und den Quelltexten ersichtlich sein.

3.3 Dateien

Die Dateien befinden sich in Ordnern, die die Software nach logischen Gesichtspunkten in einzelne Pakete aufteilen. Zunächst gibt es die eigentlichen C++-Quelltexte, die sich in den Paketen lib, base und special befinden. Dann gibt es zu jedem Taschenrechner noch einen Zubehör-Ordner (..._acc), der die zum Rechner gehörenden Pixmaps, Tastaturgeräusche in Form von Wave-Dateien und die Anwenderdokumentation enthält. Die Zubehör-Ordner sind für den Betrieb des Rechners nicht notwendig und können ersatzlos entfallen; der Rechner startet dann mit einer einfachen Dialogbox-Oberfläche.

Almetare-Pakete

Abb. 3.5: Almetare-Pakete (anklicken zum Vergrößern)

Im Paket lib befinden sich Klassendateien, die auch unabhängig von Almetare verwendet werden könnten. Das sind u.a. Dateien für das Stack-Template oder eine Klasse zum Einlesen und Aktualisieren von Konfigurationsdateien. Die Dateien in diesem Paket sind nicht abhängig von Dateien aus anderen Paketen (umgekehrt natürlich schon).

Das Paket base enthält alle Almetare-Dateien, die für alle verschiedenen Taschenrechner gleichermaßen benötigt werden. Sie enthalten daher die Basisklassen der verschiedenen Klassenhierarchien. Über virtuelle Funktionen ergibt sich eine Abhängigkeit zum Paket special.

Im Paket special sind schließlich alle Dateien beheimatet, die sich auf spezielle Taschenrechner beziehen. Darin gibt es wieder für jede Taschenrechnergattung einen Ordner (z.B. den Ordner ti_sci für die wissenschaflichen Rechner von Texas Instruments) und hierin wiederum die Ordner für jeden konkreten Rechner.

Den kompletten Dateibaum finden Sie hier.

4 Programmablauf

Im folgenden wird genauer beschrieben, wie der TI-30-Almetare-Rechner arbeitet und wann welche Objekte erzeugt und verwendet werden. Dabei wird die grafische Oberfläche zunächst außen vor gelassen und Almetare als Konsolenanwendung beschrieben. (In der Tat ist der Kern eines Almetare-Rechners eine Konsolenanwendung. Mit dem Kommandozeilenparameter "-c" kann man das unter Linux leicht nachprüfen; unter Windows muß man dafür beim Kompilieren besondere Projekteinstellungen vornehmen. So sollte es ein Leichtes sein, statt Qt auch andere Oberflächenbibliotheken wie z.B. MFC zu benutzen.)

4.1 Initialisierung

In der Main-Funktion in main.cpp wird zunächst die Kommandozeile geparsed und dann je nach Parameter der Hilfetext ausgegeben, der Rechner als Konsolenanwendung oder mit grafischer Oberfläche gestartet.

Die passende Rechnerklasse wird im Makro CALC_CLASS (Konsole) bzw. CALC_DLG_CLASS (grafische Oberfläche) bereits zur Kompilierzeit abgelegt. Die Makros sind in der Datei calc_type.h definiert, wobei jeder Rechner seine eigene Datei calc_type.h verwendet. CALC_CLASS ist im Falle des TI-30 die Klasse CalculatorTi30; ein Objekt dieser Klasse wird in der Funktion startAsCnsApp() als lokale Variable angelegt und erst zerstört, wenn das Programm beendet wird. Die weitere Initialisierung spielt sich nun ausschließlich in den Konstruktoren der im folgenden erzeugten Objekte ab. So erzeugt der Konstruktor von CalculatorTi30 je ein Objekt der Klassen ComputerTi30, DisplayTi30 und Cfgs. Die ersten beiden Klassen wurden oben bereits erwähnt und enthalten das Rechenwerk und das Display des Rechners. Die Klasse Cfgs besteht aus allen konfigurierbaren Parametern eines Rechners (z.B. Position und Größe der Dialogbox) und Funktionen zum Speichern und Lesen dieser Daten in/aus der Konfigurationsdatei.

Der Konstruktor von ComputerTi30 macht gar nichts (da sich sowohl ComputerTi30 als auch ComputerTi45 - sobald es den TI-45 mal geben wird - von ComputerTiSci ableiten und beide Rechner intern identisch sind); stattdessen wird die weitere Initialisierung vom Konstruktor der Basisklasse ComputerTiSci, deren oberste Basisklasse ComputerBase ist, übernommen. Entsprechend den C++-Regeln werden nacheinander alle Konstruktoren der Hierarchie aufgerufen: Zunächst erzeugt der Konstruktor von ComputerBase die Symboltabelle mit den Symbolen, die von allen Rechnern benötigt werden. Dann werden die Stacklisten und Member-Variablen initialisiert. Die Konstruktoren der Tochterklassen ComputerSci und ComputerTiSci fügen dann der Symboltabelle lediglich noch die Symbole hinzu, die die entsprechenden Rechner zusätzlich benötigen.

Ähnlich verhält es sich mit den Konstruktoren der Display-Hierarchie. Die Display-Klassen sind für die Generierung der Anzeige-Strings (Ergebnisse und z.B. Winkelmodi) zuständig und können sie auch mittels des überladenen Ausgabeoperators auf der Konsole ausgeben. Um mit den Computer-Klassen kommunizieren zu können, erhält die Display-Basisklasse DisplayBase einen Zeiger auf die Computer-Klasse ComputerTi30.

Die Klasse Cfgs besteht - wie erwähnt - aus allen konfigurierbaren Parametern eines Rechners und kümmert sich im Konstruktor auch gleich darum, diese aus der Konfigurationsdatei einzulesen oder - falls keine Datei gefunden wird - die Parameter mit sinnvollen Default-Werten vorzubelegen und eine neue Konfigurationsdatei anzulegen.

4.2 Hauptschleife

Nach dem Anlegen und der Initialisierung des Calculators wird von startAsCnsApp() die Funktion calculate() von CalculatorBase aufgerufen. Sie nimmt (in einer Endlosschleife) von der Konsole einen String entgegen, der von der Funktion CalculatorBase::process() als einzelnes Symbol (Zahl, Operator oder Funktion etc.) interpretiert und verarbeitet wird. Die Schleife wird verlassen, wenn man auf der Konsole den String "quit" eingibt. Abbildung 4.1 zeigt die Aktivitäten in der Hauptschleife:

Abb. 4.1: Almetare-Hauptschleife

Was in der Aktivität "Eingabe verarbeiten" geschieht, zeigt vereinfacht die folgende Verfeinerung:

Prozessierung von
            Eingaben

Abb. 4.2: Prozessierung von Eingaben (anklicken zum Vergrößern)

Zunächst wird der Eingabestring vorprozessiert. Dieser Schritt erlaubt es, eine Eingabe noch zu verändern, bevor die Verarbeitung beginnt. Das ist vor allem nötig, wenn der Rechner nicht im Konsolenmodus, sondern im Grafikmodus betrieben wird, und sich in einem Shift-Modus befindet. Wenn etwa die "INV"-Taste gedrückt wurde und dann z.B. die "sin"-Taste, darf der Rechner nicht den Sinus ausrechnen, sondern muß den Arcussinus liefern (auf der Konsole kann man natürlich "asin" auch direkt eingeben). In der Funktion preProcess() wird daher in diesem Fall ein Mapping durchgeführt, das den String "sin" durch "asin" ersetzt, mit dem dann die weitere Verarbeitung durchgeführt wird. Bei anderen Symbolen muß möglicherweise dann gar nichts mehr gemacht werden, das Symbol wäre dann schon komplett prozessiert und alle weiteren Schritte könnten entfallen. Da z.B. das Mapping sehr rechnerspezifisch ist, ist die Funktion preProcess() virtuell und wird für jede Computer-Klasse separat bereitgestellt.

Im nächsten Schritt wird aus der Symboltabelle der Symbolzeiger geholt, der zu dem Eingabe-String gehört; zum String "sin" gehört beispielsweise der Zeiger auf das (einzige) Objekt vom Typ FnSin. Dazu wird die Symboltabelle nach dem eingegebenen String durchsucht. Mit Hilfe dieses Zeigers kann man dann die virtuelle Funktion process() ausführen, von der jedes Symbol eine eigene Implementierung besitzt. Zwischen Zahlen und anderen Symbolen muß dabei unterschieden werden, weil natürlich nicht sämtliche möglichen Zahlen (das wären fast unendlich viele: 1, 2, 3.4, 5.678 z.B.) in der Tabelle gehalten werden können. Stattdessen gibt es nur ein Objekt vom Typ NumDmy, das alle Zahlen repräsentiert, und dessen process()-Funktion die Zahl einfach auf den Zahlen-Stack legt. Die process()-Funktionen der anderen Symbole sind dagegen sehr spezifisch: Sie manipulieren die Stacks, rechnen Werte aus, verändern Zustände des Rechners u.v.m.

Für die Ausgabe des Ergebnisses wird grundsätzlich die oberste Zahl vom Zahlen-Stack geholt. Dann wird für manche Symbole noch eine Nachprozessierung durchgeführt. Dieser Schritt schaltet beispielsweise den Invers-Modus wieder aus, da er bei meinen Rechnern nach jedem Tastendruck wieder deaktiviert wird (es sei denn, die Invers-Taste wurde selbst gedrückt).

Der letzte Schritt bereitet die Ausgabe-Strings (Status- und Ergebniszeile) so auf, daß sie angenähert so aussehen, wie sie auch auf den Originalrechnern erscheinen würden. Dies wird von der virtuellen Funktion makeDisplay() der Display-Klasse eines Rechners erledigt und ist durchaus kompliziert. Z.B. zeigt der TI-30 seinen Winkelmodus (DEG, RAD, GRA) durch eine unterschiedliche Anzahl von Hochkommata vor der Ergebniszahl an.

Die Vorgänge sind hier sehr vereinfacht dargestellt, Einzelheiten bitte ich der Dokumentation zu den einzelnen Funktionen zu entnehmen.

4.3 Grafische Oberfläche

Wenn der Rechner mit grafischer Oberfläche gestartet wird, wird CalculatorTi30 nicht direkt angelegt, sondern mittelbar über die Klasse Ti30mainDlg, die CalculatorTi30 als Member enthält. Die Hauptschleife wird nun von Qt gestellt, die auf Ereignisse wartet. Dies können hier Tastendrücke oder Timer-Ereignisse sein (etwa um den Rechner nach einer gewissen Zeit "auszuschalten"). Die Tastendrücke wurden mit Hilfe des Qt-Designers mit den Slot-Funktionen but...() verknüpft. So ruft z.B. der Druck auf eine Zifferntaste die Slot-Funktion butDigit() auf, die meisten Tasten sind jedoch mit der Funktion butAction() verbunden. Diese ermittelt nun mit Hilfe zweier Qt-Funktionen zunächst den Namen des gedrückten Buttons. Die Buttons wurden nun geschickterweise gerade so benannt, daß ihre Namen den Symbolen aus der Symboltabelle entsprechen. Auf diese Weise kann man wie bei der Konsolenanwendung in der Symboltabelle nach dem Symbol suchen gehen und, falls gefunden (hier findet man natürlich immer was, da Tippfehler nicht vorkommen können), wieder die process()-Funktion des Symbols aufrufen, die die notwendigen Berechnungen und Stack-Manipulationen durchführt.

Die Darstellung des Ergebnisses ist jetzt freilich deutlich komplizierter, da nicht mehr nur ein String auf der Konsole ausgegeben wird, sondern dieser String in eine grafische Anzeige umgewandelt werden muß. Dazu nur soviel: Es gibt im Accessory-Ordner des Rechners eine Grafik (digits.png), die alle Zeichen enthält, die der Rechner darstellen kann, und die als Pixmap bei der Initialisierung des Rechners geladen wird. Jedes Zeichen hat darin eine x- und y-Koordinate. Mit Hilfe dieser Koordinaten kann die Funktion Ti30mainDlg::setLed() aus dem String, den die Display-Klasse des Rechners passend aufbereitet hat, die einzelnen Ziffern und Zeichen an die passende Stelle in die Hintergrund-Pixmap des Rechners kopieren.

5 Programmierkonventionen

5.1 Namen und Bezeichner

Folgende Konventionen bei der Vergabe von Variablen-, Klassen-, Funktionsnamen etc. wurden verwendet:

5.2 Organisation der Quelltexte

Klassen und Funktionen werden durch eine Zeile von Sternchen mit 80 Zeichen Breite voneinander abgetrennt. Die Zeile enthält möglicherweise eine Anzahl von Leerzeichen, die die Aufrufhierarchie der Funktionen widerspiegelt: Eine Funktion, die durch eine Sternchenzeile mit einem Leerzeichen in der Mitte von der darüberstehenden abgetrennt ist, wird von dieser aufgerufen. Ruft sie wieder eine Funktion auf, wird diese durch eine Sternchenzeile mit zwei Leerzeichen abgetrennt usw. Ruft eine Funktion mehrere Unterfunktionen auf, werden alle Funktionen mit derselben Anzahl von Leerzeichen in der Sternchenzeile abgetrennt.

//******************************************************************************

/// Schaut im Computer, welche Modes gesetzt sind, und baut den Mode-String des
/// Displays entsprechend zusammen.
//  fse, 12.04.02

void DisplaySci::setModes()
{
 setModesSci();
 mModes =  mInv + " " + mMem + " " + mK + " " + mHyp + " "
         + mDeg + " " + mRad + " " + mGra + " " + mSci + " " + mFix;
}

//************************************** ***************************************

/// Setzt die Modus-Strings auf die eingestellten Werte.
//  Aus setModes() ausgelagert, weil auch von den abgeleiteten Klassen
//  augerufen.
//  fse, 13.04.02

void DisplaySci::setModesSci()
{
 bool         isInv = mCompP->getIsInv();
 bool         ishyp = mCompP->getIsHyp();
 AngMode      am    = mCompP->getAngMode();

 setModesBase();
 setInv(isInv);
 setHyp(ishyp);
 setAngMode(am);
 setDspMode(mDspMode);
}

//************************* ************************* **************************

/// Setzt die Winkelmodus-Strings.
/// \param am eingestellter Winkelmodus

void DisplaySci::setAngMode(AngMode am)
{
 switch(am)
 {
  case DEG: setDeg(); break;
  case RAD: setRad(); break;
  case GRA: setGra(); break;
  default: setDeg();
 }
}

//************************* ************************* **************************

/// Setzt die Display-Modus-Strings.
/// \param dm eingestellter Display-Modus

void DisplaySci::setDspMode(DspMode dm)
{
 mDspMode = dm;
 switch(dm)
 {
  case NRM: setNrm(); break;
  case SCI: setSci(); break;
  case FIX: setFix(); break;
  default: setNrm();
 }
}

//******************************************************************************

Listing 5.1: Trennung von Funktionen durch Sternchenzeilen mit "Löchern"

In der gleichen Weise wird mit Klassen und deren abgeleiteten Klassen in den Header-Dateien verfahren.

Als Dokumentations-Tool wird Doxygen eingesetzt. Kommentare, die durch Doxygen erkannt werden sollen, werden daher durch besondere Zeichen kenntlich gemacht ("///", "\param" etc.).

6 Anlegen des Almetare-Projektes unter Linux mit KDevelop 2.1

In diesem Abschitt wird beschrieben, wie man das Almetare-Projekt unter Linux mit KDevelop 2 "von Hand" aufsetzt, wenn nur die Quelltexte und die Accessory-Ordner vorhanden sind.

Leider kennt KDevelop im Unterschied zu Microsofts Visual Studio nur "Projekte" und keine "Arbeitsbereiche", in denen sich dann mehrere Projekte befinden können. Daher ist es nicht ohne weiteres möglich, mit KDevelop einen Almetare-Arbeitsbereich anzulegen, in dem sich dann die verschiedenen Taschenrechner als separate Projekte befinden. Mit ein paar Tricks kann man es aber trotzdem schaffen, alle Dateien ohne Duplizierung in einem KDevelop-Projekt zu halten und auf die für jeden Taschenrechner spezifischen Dateien separat zuzugreifen. Wie das geht, wird im folgenden gezeigt.

Die verwendete KDevelop-Version ist 2.1.4, die Linux-Version ist SuSE 8.1. SuSE 8.1 installiert standardmäßig Qt3, zusätzlich müssen daher noch Qt2 (mit Developer-Paket) und tmake installiert werden. Es wird das Vorgehen beschrieben, wenn zwei Taschenrechner, TI-30 und fx85v, im Projekt vorhanden sind.

6.1 Vorbereitungen

6.1.1 Qt2 installieren

Qt2 installiert man am besten mit dem Installationstool der Linux-Distribution; es besteht aus den Paketen qt und qt-devel. Bei Bedarf kann man auch noch qt-designer und qt-devel-doc nachrüsten. Man kann die Almetare-Rechner zwar auch mit Qt3 kompilieren und linken, die Buttons sehen dann aber aus unbekannten Gründen sehr klobig aus.

6.1.2 tmake installieren

tmake ist ein Programm zur Erzeugung von Makefiles; dabei werden die Besonderheiten von Qt-Programmen, wie Aufruf von User-Interface-Compiler (UIC) und Meta-Object-Compiler (MOC), berücksichtigt. Im tmake-Paket ist auch das Programm progen enthalten, das eine Liste von Quelltexten erstellt, die dann von tmake verwendet wird, um das Makefile zu erzeugen.

Hinweis zu Qt3: Das bei Qt3 mitgelieferte Programm qmake soll eigentlich tmake aus Qt2 ersetzen; die Generierung von Quelltextlisten ist in qmake integriert, so daß ein Programm wie progen nicht mehr nötig sein sollte. Leider funktioniert die Generierung der Quelltextliste bei Almetare nicht, da anscheinend die Verschachtelungstiefe der Ordner zu groß ist; qmake findet nicht alle Dateien. Wahrscheinlich ist das auch der Grund, weshalb KDevelop den UIC nicht richtig benutzt und aus den *.uic-Dateien im "special"-Ordner keine C-Dateien generiert, wenn man versucht, Almetare in KDevelop als normales Qt-Projekt anzulegen (KDevelop 2.1.3 mit SuSE 8.1).  Daher wird hier das bewährte tmake verwendet, das derartige Probleme nicht kennt.

Installieren Sie das tmake-Paket mit dem Installations-Tool Ihrer Linux-Distribution.

6.1.2 Umgebungsvariablen setzen

tmake (bzw. qmake) und make benötigen folgende Umgebungsvariablen, die man am besten in die Shell-Konfigurationsdatei einträgt. Z.B. in ".bashrc" bei Verwendung von Qt2 und tmake:

    export TMAKEPATH=/usr/lib/tmake/linux-g++
    export QTDIR=/usr/lib/qt2

Oder bei Verwendung von Qt3 und qmake:

    export QMAKESPEC=linux-g++
    export QTDIR=/usr/lib/qt3

Achtung: KDevelop kennt diese Variablen nur dann, wenn man es direkt durch Aufruf von "kdevelop" von der Konsole aus startet. KDE kennt diese Variablen nicht, und daher findet sie auch KDevelop nicht, wenn man es vom KDE-Startmenü aus aufruft. Beim Kompilieren erhält man dann Fehlermeldungen, daß z.B. /bin/uic nicht gefunden worden sei. Leider habe ich noch nicht herausgefunden, wie man KDE Umgebungsvariablen bekannt machen kann. Also: KDevelop immer von der Konsole aus starten! *

*Inzwischen hat sich das Problem aufgeklärt: In meiner SuSE-8.1-Distribution wird im KDE-Start-Skript "startkde" aus unbekannten Gründen explizit ein "unset KDEDIRS" und unset "QTDIR" gemacht. Also kein Wunder, daß diese Variablen immer wieder verschwunden waren, wenn man wie sonst üblich in "etc/profile.local" diese Variablen gesetzt hatte. Nach Entfernen der unset-Kommandos funktioniert alles wie erwartet.

6.2 Projekt und Projektordner mit KDevelop anlegen

Um erst einmal einen Projektordner zu erzeugen, startet man KDevelop und legt mit Menüpunkt "Projekt - Neu" ein "Eigenes Projekt" an:

Abb. 6.1: Anlegen eines Projektes mit KDevelop
Abb. 6.1: Anlegen eines Projektes mit KDevelop

Mit dem Anwendungsassistenten sind dann die einzelnen Schritte zu durchlaufen. Zunächst wird der Projektname "Almetare" angegeben:

Abb. 6.2: Projekteinstellungen - Teil 1

Abb. 6.2: Projekteinstellungen - Teil 1

Vorlagen für Header-Dateien (und ebenso Cpp-Dateien) sollte man deaktivieren:

Abb. 6.3: Projekteinstellungen - Fortsetzung

Abb. 6.3: Projekteinstellungen - Fortsetzung

Im letzten Schritt wird das Projekt generiert:

Abb. 6.4: Projekteinstellungen - Abschluss

Abb. 6.4: Projekteinstellungen - Abschluß

Als Ergebnis erhält man einen Ordner "almetare", in dem sich u.a. ein weiterer Ordner "almetare" und die Projektdatei "almetare.kdevprj" befindet. Der innere "almetare"-Ordner wird jetzt gelöscht und durch den "almetare"-Ordner ersetzt, der die Quelltexte enthält.

Dann kopiert man die Ordner "ti30" und "fx85v" mit den Pixmaps und Handbüchern ebenfalls in das Projektverzeichnis; KDevelop wird später so konfiguriert, daß in jenen das Binary erzeugt wird.
Außerdem legt man noch einen leeren Ordner "Linux" an für die noch zu erstellenden alternativen Projekt- und die Objektdateien:

Ordner/Datei Beschreibung
almetare KDevelop-Projektordner
 +-almetare Ordner mit allen Quelltexten
 |  +-base Ordner enthält Basisklassen
 |  +-lib enthält Bibliotheksklassen
 |  `-special enthält Taschenrechner-spezifische Klassen
 +-doc
Ordner enthält Programmierhandbuch
 +-fx85v Ordner mit Pixmaps und Handbuch für fx85v
 +-ti30 Ordner mit Pixmaps und Handbuch für TI-30
 +-Linux Ordner für Projektdateien und Objektdateien
 `-almetare.kdevprj initiale KDevelop-Projektdatei
zusätzlich noch einige Dateien, die KDevlop standardmäßig anlegt (INSTALL, README etc.), wenn man in Abb. 6.2 die Checkboxen aktiviert hat.

Tabelle 6.1: Dateibaum

6.3 Makefiles erstellen

Für jeden Taschenrechner benötigt man ein eigenes Makefile. Diese lassen sich mit den Qt-Tools progen und tmake erzeugen.

6.3.1 Liste der Quelldateien mit progen erzeugen

a) Konsolenfenster öffnen und in Almetare-Projektordner wechseln.

b) Mit progen Vorlagendatei mit Liste der Dateien erstellen:
    progen -o almetare.pro

c) entstandene Datei "almetare.pro" in folgender Weise verändern:
- falls eine Debug-Version erstellt werden soll, Config-Zeile ändern in
    CONFIG  = qt warn_on debug
- Zeile einfügen, die ein Verzeichnis angibt, in dem die Objekt-Dateien erzeugt werden sollen (sonst werden sie im Ordner der Quelltexte erzeugt; für eine etwaige Datensicherung ist es einfacher, die Quelltextordner "sauber" zu halten):
    OBJECTS_DIR    = Linux
- Pfade zu allen Header-Dateien angeben (da beim Generieren der Quelltexte aus den *.uic-Dateien ebenfalls Header-Dateien entstehen, müssen auch die Pfade zu allen *.uic-Dateien angegeben werden, falls solche noch woanders liegen sollten):
    INCLUDEPATH = almetare/base \
                  almetare/lib \
                  almetare/special \
                  almetare/special/simple \
                  almetare/special/casio_sci \
                  almetare/special/casio_sci/fx85v \
                  almetare/special/ti_sci \
                  almetare/special/ti_sci/ti30
- Datei speichern. "almetare.pro" ist jetzt eine Vorlagendatei, aus der man die Dateiliste eines einzelnen Taschenrechners durch Entfernen der Bezüge zu allen anderen Taschenrechnern leicht erzeugen kann.
- "ti30.pro" erzeugen: Aus "almetare.pro" alle Zeilen entfernen, in denen "casio" vorkommt, und unter "ti30.pro" speichern. Dabei darauf achten, daß nicht versehentlich mit einem abschließenden "\" zwei Sektionen miteinander verbunden werden.
- "fx85v.pro" erzeugen: m.m. wie vorstehend.

6.3.2 Makefiles mit tmake erzeugen

Aus den soeben erzeugten Dateien "ti30.pro" und "fx85v.pro" werden jetzt die Makefiles der einzelnen Taschenrechnerprojekte erzeugt.

a) Auf der Konsole (wieder im Almetare-Projekt-Ordner) eingeben:
    tmake -o Makefile_ti30  ti30.pro
    tmake -o Makefile_fx85v fx85v.pro
Die *.pro-Dateien kann man jetzt der Übersichtlichkeit halber in das "Linux"-Verzeichnis verschieben.

b) In den beiden generierten Dateien "Makefile_ti30" und "Makefile_fx85v" folgende Ergänzungen durchführen:
- Die Abschnitte "tmake" und "Makefile_ti30" bzw. "Makefile_fx85v" entfernen oder auskommentieren (sonst müssen die *.pro-Dateien auch beim Kompilieren vorhanden sein).
- Im Abschnitt "clean" die Zeilen
    -rm -f *.h
    -rm -f ti30/*log.txt
    -rm -f ti30/*.bak
bzw.
    -rm -f *.h
    -rm -f fx85v/*log.txt
    -rm -f fx85v/*.bak
hinzufügen, da beim Kompilieren Header-Dateien auch im Projektverzeichnis erzeugt werden und auch die Log-Dateien nebst ihren Backups der Taschenrechner aufgeräumt werden sollen.
- Die Target-Zeile so abwandeln, daß die Binaries im passenden Verzeichnis erzeugt werden:
    TARGET = ti30/ti30
bzw.
    TARGET = fx85v/fx85v

Auf der Konsole lassen sich die Rechner jetzt bereits durch Eingabe von
    make -f Makefile_ti30
bzw.
    make -f Makefile_fx85v
übersetzen.

6.4 Unterprojekte in KDevelop einrichten

Im nächsten Schritt werden in KDevelop zwei "Unterprojekte" eingerichtet. Dies geschieht, indem man das oben erzeugte Projekt-File "almetare.kdevprj" zunächst unter anderem Namen abspeichert, dieses dann mit KDevelop durch "Projekt öffnen" lädt und dann die zu dem Projekt gehörigen Dateien in KDevelop registriert. Doch der Reihe nach:

a) "almetare.kdevprj" nach "ti30.kdevprj" kopieren und mit Menüpunkt "Projekt - Öffnen..." mit KDevelop laden.

Abb. 6.5: Projekt öffnen

Abb. 6.5: Projekt öffnen

Dabei entsteht in jedem Ordner, in dem sich *.cpp-Dateien befinden, eine Datei "Makefile.am" mit 0 Bytes. Diese Dateien können sofort wieder gelöscht werden.

b) Die Dateien werden nun dem Projekt hinzugefügt. Dazu klickt man den Datei-Browser "Dateien" an, öffnet den Ordner "almetare", markiert nacheinander jede *.cpp-, *.h- und *.ui-Datei in den Ordnern "base" und "lib" mit der rechten Maustaste und fügt sie im dann erscheinenden Kontextmenü dem Projekt hinzu. Eine hinzugefügte Datei erhält im Browser das Attribut "registriert ".

Abb. 6.6: Dateien zum Projekt hinzufügen
Abb. 6.6: Dateien zum Projekt hinzufügen

Im Ordner "special" fügt man nur die Dateien hinzu, die sich in den entsprechenden Ordnern des Taschenrechnes (hier: TI-30) befinden.

Alternativ kann man auch den Menüpunkt "Projekt - Vorhandene Datei(en) hinzufügen" zum Registrieren der Dateien verwenden. Dabei muß man jedoch darauf achten, daß man auch den Zielordner angeben muß, und dieser muß identisch mit dem Quellordner sein; sonst kopiert KDevelop die Dateien ins Wurzelverzeichnis des Projekts, und die Dateien sind doppelt vorhanden.

c) Zuletzt sind mit Menüpunkt "Projekt - Optionen..." noch zwei Projekteinstellungen zu ändern:

Abb. 6.7: Make-Optionen ändern: Makefile auswählen

Abb. 6.7: Make-Optionen ändern: Makefile auswählen

Bei den Make-Optionen muß das passende Makefile angegeben werden, und als ausführbare Datei wird das Binary im entsprechenden Taschenrechner-Verzeichnis eingetragen:

Abb. 6.8: Make-Optionen ändern: Ausführbare Datei

Abb. 6.8: Make-Optionen ändern: Ausführbare Datei

KDevelop ist allerdings beleidigt, wenn man wie in Abb. 6.7 gezeigt ein Makefile einträgt, das nicht "Makefile" heißt, und behauptet beim Kompilieren, es sei kein Makefile vorhanden. Daher legt man im Projektverzeichnis noch eine Datei beliebigen Inhalts mit Namen "Makefile" an. Diese darf ruhig 0 Bytes groß sein.

d) Projekt schließen und KDevelop schließen. KDevelop wieder öffnen und alle Schritte für andere Taschenrechner wiederholen. (Wenn man nur das Projekt schließt [und nicht auch KDevelop], merkt sich KDevelop anscheinend noch Dinge des vorherigen Projektes; jedenfalls tauchen in der Klassenansicht dann noch Klassen des vorherigen Projektes auf, was man ja gerade nicht will.)

=> Damit ist nun erreicht, daß man einen einheitlichen "almetare"-Projektordner für alle Taschenrechner hat. Wenn man einen bestimmten Taschenrechner bearbeiten will, öffnet man mit KDevelop einfach die entsprechende Projektdatei. Zuvor sollte im aktuellen Projekt ein "Clean" gemacht werden, da sonst die speziellen Objektdateien des Taschenrechners mit einem "Clean" des neuen Taschenrechners nicht entfernt werden würden.

6.5 Projekt kompilieren

Das aktuelle Projekt kann nun ganz normal mit Menüpunkt "Erstellen - Erstellen" übersetzt werden. Dabei ist eine Warning "taking address of  temporary" in Datei "calc_dlg_base.cpp" normal.

6.6 Erstellen der Programmierer-Dokumentation mit Doxygen

Die Quelltexte enthalten Kommentare, die mit dem Tool "Doxygen" (http://www.stack.nl/~dimitri/doxygen/) automatisch extrahiert und ins HTML-Format  konvertiert werden können. Am besten besorgen Sie sich auch die "Graphviz"-Software (http://www.research.att.com/sw/tools/graphviz/), die das "dot"-Tool enthält; dann erzeugt Doxygen nämlich auch schöne Klassengraphen usw.

Doxygen ist ein Kommandozeilen-Tool, das durch eine Konfigurationsdatei gesteuert wird. Eine für Almetare passende ist "almetare/doc/doxygen.cfg". Öffnen Sie also eine Konsole und bewegen Sie sich in den Ordner "almetare/doc", in dem die Konfigurationsdatei "doxygen.cfg" liegt. Dort geben Sie das Shell-Kommando
          doxygen doxygen.cfg
ein, und je nach Doxygen-Version werden ca. 700 bis 1600 HTML-, Grafik- und sonstige Dateien erzeugt.

Es gibt auch unter Linux das grafische Frontend "Doxywizard", das im Abschnitt 7.9 beschrieben ist.

7 Anlegen des Almetare-Projektes unter Windows mit Visual Studio 6

In diesem Abschitt wird beschrieben, wie man das Almetare-Projekt in Visual Studio "von Hand" aufsetzt, wenn nur die Quelltexte und die Accessory-Ordner vorhanden sind. Das Qt-Utility tmake kann zwar Projektdateien auch automatisch generieren, was jedoch bei mir nicht korrekt funktioniert hat.

Die folgende Beschreibung geht davon aus, daß Visual Studio mit Service-Pack 5 (wichtig!) installiert und die "Qt 2.3.0 Non-Commercial Edition" in Visual Studio eingebunden ist. Letzteres ist in Abb. 7.1 an den Qt-Symbolen rechts oben erkennbar.

Die Autoren-Version von VC++, die diversen Büchern zu Visual Studio beiliegt (meine stammte von einer Zeitschrift mit C++-Kurs), ist übrigens auch geeignet; das Service-Pack 5 wird hier ebenfalls benötigt.

7.1 Arbeitsbereich anlegen:

Visual Studio starten und mit Menüpunkt "Datei - Neu" einen neuen Arbeitsbereich mit Namen "almetare" erzeugen (der Name ist eigentlich egal, aber Achtung: Weder Name noch Pfad dürfen Leerzeichen enthalten, sonst funktioniert der Qt-User-Interface-Compiler UIC nicht):

Abb. 7.1: Anlegen eines Arbeitsbereiches mit Visual
          Studio

Abb. 7.1: Anlegen eines Arbeitsbereiches mit Visual Studio

=> Ein Ordner "almetare" mit Workspace-Datei "almetare.dsw" und den Dateien "almetare.ncb" und "almetare.opt" wird auf der Festplatte erzeugt. Die letzteren beiden Dateien brauchen übrigens nicht vorhanden sein, VS legt sie bei Bedarf automatisch wieder neu an.

7.2 Projekte anlegen

Zur Zeit gibt es zwei Taschenrechner, entsprechend werden zwei Projekte angelegt: "ti30" und "fx85v". Dazu betätigt man den Qt-Button "New Qt Project" und erzeugt nacheinander die beiden Projekte:

Abb. 7.2: Anlegen des Qt-Projekts
          "fx85v"
Abb. 7.2: Anlegen des Qt-Projekts "fx85v"

Anschließend poppt immer der Qt-Designer hoch, den kann man erstmal wieder wegklicken.

=> Im Arbeitsbereichsfenster von VS erscheinen die Projekte, auf der Platte die entsprechenden Ordner.

Bei der eben beschriebenen Prozedur geht es jedoch nur darum, die von VS und Qt benötigten Projektdateien anzulegen. Alle anderen Dateien müssen jetzt wieder sowohl aus dem Projekt als auch von der Platte gelöscht werden. Das gilt für alle *.cpp-, *.h-, *.ui und *.txt-Dateien. Dazu öffnet man im Arbeitsbereich alle Ordner und entfernt sämtliche Dateien mit Hilfe der Taste "Entf". Der Arbeitsbereich muß anschließend aussehen wie in Abb. 7.3 gezeigt:

Abb. 7.3: VS-Arbeitsbereich nach Löschen aller
          Dateien
Abb. 7.3: VS-Arbeitsbereich nach Löschen aller Dateien

Anschließend speichert man den Arbeitsbereich mit Menüpunkt "Datei - Arbeitsbereich speichern" und löscht auch von der Festplatte alle entsprechenden Dateien, so daß in den Projektordnern nur noch "ti30.dsp" und "fx85v.dsp" und die (ohnehin leeren) "Debug"-Ordner übrigbleiben.

7.3 Quelltexte kopieren

In den von VS erzeugten Almetare-Ordner wird nun der Almetare-Ordner mit den Quelltexten kopiert, in die Projektordner kommen jeweils die zugehörigen Accessory-Ordner usw., so daß sich letztendlich dieser Dateibaum der Quelltextdistribution ergibt.

Diese Hierarchie erlaubt es, daß die von beiden Projekten gemeinsam genutzten Dateien nur einmal vorhanden sein müssen. Für zukünftige Erweiterungen sind auch schon zwei leere Ordner für die Rechner TI-45 und fx-3600P dabei.

7.4 Projektdateien zu VS hinzufügen

Die Dateien befinden sich jetzt am richtigen Ort auf der Platte, aber VS weiß davon noch nichts. Um die Dateien VS bekannt zu machen, fügt man die benötigten Dateien mit Menüpunkt "Projekt - Dem Projekt hinzufügen - Dateien:" zunächst z.B. dem fx85v-Projekt hinzu:

Abb. 7.4: Hinzufügen von Dateien zu einem Projekt

Abb. 7.4: Hinzufügen von Dateien zu einem Projekt

Dies wiederholt man der Reihe nach für alle *.cpp-, *.h- und *.rc-Dateien in den Ordnern "almetare\base", "almetare\lib", "almetare\special\casio_sci" und "almetare\special\casio_sci\fx85v".

Dann muß man dem Qt-User-Interface-Compiler noch sagen, welche Dialoge er übersetzen soll. Das geschieht, indem man den "UIC"-Button drückt und der Reihe nach die *.ui-Dateien "almetare\base\about_base_dlg.ui ", "almetare\base\cfg_base_dlg.ui" und "almetare\special\casio_sci\fx85v\fx85v_main_base_dlg.ui" öffnet:

Abb. 7.5: Markieren der *.ui-Dateien für den
          Qt-User-Interface-Compiler

Abb. 7.5: Markieren der *.ui-Dateien für den Qt-User-Interface-Compiler

Zuletzt muß auch der Qt-Meta-Object-Compiler noch einige Dateien kennenlernen. Das geschieht am praktischsten, indem man die Dateien "almetare\base\about_dlg.h", "almetare\base\cfg_dlg.h" und "almetare\special\casio_sci\fx85v\fx85v_main_dlg.h " einzeln in VS öffnet und dann den MOC-Button betätigt:

Abb. 7.6: Markieren von drei Header-Dateien für den
          MOC

Abb. 7.6: Markieren von drei Header-Dateien für den MOC

=> Die Dateien für das Projekt "fx85v" sind jetzt in VS bekannt.

Alles, was in diesem Abschnitt bzgl. des Projekts "fx85v" beschrieben wurde, muß nun für das Projekt "ti30" wiederholt werden. Dazu muß man es zunächst zum aktiven Projekt machen, indem es im Arbeitsbereich mit der rechten Maustaste angeklickt und der entsprechende Menüpunkt ausgewählt wird:

Abb. 7.7: "ti30" zum aktiven Projekt
          machen

Abb. 7.7: "ti30" zum aktiven Projekt machen

Die Dateien in den Ordnern "almetare\base" und "almetare\lib" werden auch in diesem Projekt benötigt. Statt den Dateien in "almetare\special\casio_sci" müssen hier allerdings natürlich die in "almetare\special\ti_sci" verwendet werden.

=> Jetzt sind VS alle Projektdateien bekannt.

7.5 Projekteinstellungen in VS

Um die Projekte kompilieren zu können, sind abschließend noch einige Projekteinstellungen vorzunehmen. Dabei ist zu beachten, daß es hier verschiedene Kombinationen gibt, da für jedes Projekt Einstellungen für die Debug- und die Release-Konfiguration zu machen sind. Man wählt Menüpunkt "Projekt - Einstellungen..." und ändert in der Dialogbox unter den Reitern "C/C++" und "Bearbeiten nach dem Erstellen" die folgenden Werte.

a) Unter dem Reiter "C/C++" für beide Projekte sowohl bei Release- als auch Debug-Konfiguration in Kategorie "Programmiersprache C++"  RTT aktivieren:

Abb. 7.8: Projekteinstellungen verändern

Abb. 7.8: Projekteinstellungen verändern

b) Für Projekt "fx85v" sowohl bei Release- als auch Debug-Konfiguration in Kategorie "Präprozessor" zu dem bereits vorhandenen "$(QTDIR)\include" noch die folgenden durch Kommata getrennten Verzeichnisse hinzufügen:
..\almetare\base,..\almetare\lib,..\almetare\special\casio_sci,..\almetare\special\casio_sci\fx85v

Abb. 7.9: Hinzufügen von Include-Verzeichnissen

Abb. 7.9: Hinzufügen von Include-Verzeichnissen

c) Für Projekt "ti30" sowohl bei Release- als auch Debug-Konfiguration in Kategorie "Präprozessor" zu dem bereits vorhandenen "$(QTDIR)\include" noch die folgenden durch Kommata getrennten Verzeichnisse hinzufügen:
..\almetare\base,..\almetare\lib,..\almetare\special\ti_sci,..\almetare\special\ti_sci\ti30

d) Für beide Projekte bei Release-Konfiguration in Kategorie "Optimierungen" "Größe minimieren" einstellen (das geht nicht in der VS-Autoren-Edition, ist aber nicht weiter schlimm, dann sind halt nur die Exe-Dateien etwas größer):

Abb.7.10: Für die Release-Konfigurationen die Größe
          minimieren

Abb.7.10: Für die Release-Konfigurationen die Größe minimieren

e) Für beide Projekte bei Debug-Konfiguration in Kategorie "Allgemein" die Warnstufe auf "Keine" einstellen. Sonst produziert der Compiler jede Menge Warnungen, die aus seinen eigenen STL-Bibliotheken kommen!? Nachteil: Warnungen sieht man nur noch beim Kompilieren der Release-Konfiguration; leider nicht zu ändern ...

Abb. 7.11: Für die Debug-Konfigurationen alle
          Warnungen ausschalten

Abb. 7.11: Für die Debug-Konfigurationen alle Warnungen ausschalten

f) Die ausführbare Datei wird vom Visual Studio im Ordner "Debug" bzw. "Release" abgelegt. Das ist okay, wenn man den Taschenrechner mit dem Menüpunkt "Erstellen - Ausführen" aus Visual Studio heraus startet. Wenn man die Exe-Datei aber per Doppelklick aus dem Explorer heraus ausführt, findet der Taschenrechner seine Bitmap-Grafiken nicht, weil er sie in seinem Accessory-Ordner in demselben Verzeichnis erwartet, und startet als schlichte Dialogbox ohne Hintergrundbild.

Daher veranlassen wir Visual Studio, die Exe-Datei nach dem Kompilieren und Linken noch an die passende Stelle zu kopieren. Das geschieht unter dem Reiter "Bearbeiten nach dem Erstellen" nur für die Release-Versionen, indem man dort noch das DOS-Kommande "copy release\*.exe *.exe" einträgt:

Abb. 7.12: Nach dem Kompilieren die Exe-Datei neben
        den Accessory-Ordner kopieren

Abb. 7.12: Nach dem Kompilieren die Exe-Datei neben den Accessory-Ordner kopieren


Abschließend können wir die Dialogbox mit "OK" verlassen.

Hinweis: Diese Kopie der Exe-Datei wird bei einem Cleanup mit Menüpunkt "Erstellen - Bereinigen" nicht entfernt! Sie muß daher bei Bedarf von Hand gelöscht werden.

7.6 Kompilieren

Jetzt können alle vier Konfigurationen kompiliert werden. Es sollten weder Fehler noch Warnungen ausgegeben werden.

7.7 Zusätzliche Projekteinstellungen

Wenn Konsolenausgaben gemacht werden sollen:

7.8 Binary patchen, falls mit VS Autorenversion kompiliert wurde

Die Visual Studio Autorenversion gibt zum Programmstart eine störende Dialogbox aus. Diese gilt es zu entfernen, indem man das Binary so patcht, daß der Aufruf der entsprechenden Funktion unterbleibt. Das kann man mit dem Tool trw2000 machen, das auf der Shareware-CD des c't-Magazins 14/2000 mitgeliefert wurde. Leider scheint dieses Tool nicht mehr gepflegt zu werden; alle Links, die ich im Internet gefunden habe, führen ins Leere.
  1. Datum des Rechners auf Mai 2000 stellen, da die Demo-Version zeitbegrenzt war.
  2. trw2000.exe starten
  3. in der Dialogbox das Binary (z.B. "ti30.exe") eingeben (am besten über den "Browse"-Button) und "Load" drücken => Programm wird geladen, in schwarzer DOS-Box erscheint der Assemblercode
  4. F10 (Single-Step) so oft drücken (ca. 35-mal), bis die störende Dialog-Box erscheint => das aufrufende Call ist gefunden (z. B. 00401111 CALL 00401B33)
  5. Speicher editieren: "e 00401111" in Kommandozeile eingeben
  6. diese Stelle mit "E9" patchen (e9 ist der Code für "JMP")
  7. die folgende Speicherstelle mit "e 00401112" und "00" beschreiben
  8. die folgende Speicherstelle mit "e 00401113" und "00" beschreiben => der CALL ist durch JMP 00401114 ersetzt
  9. trw2000  mit "x" auf der Kommandozeileverlassen => gepatchtes Programm ist gespeichert

7.9 Erstellen der Programmierer-Dokumentation mit Doxygen

Die Quelltexte enthalten Kommentare, die mit dem Tool "Doxygen" (http://www.stack.nl/~dimitri/doxygen/) automatisch extrahiert und ins HTML-Format  konvertiert werden können. Am besten besorgen Sie sich auch die "Graphviz"-Software (http://www.research.att.com/sw/tools/graphviz/), die das "dot"-Tool enthält; dann erzeugt Doxygen nämlich auch schöne Klassengraphen usw.

Doxygen ist ein Kommandozeilen-Tool, das durch eine Konfigurationsdatei gesteuert wird. Am bequemsten bedient man es aber mit dem grafischen Frontend "Doxywizard", das der Windows-Version beiliegt. Öffnen Sie damit die Datei "almetare/doc/doxygen.cfg":

Abb. 7.13: Mit dem Doxywizard die
        Doxy-Konfigurationsdatei für Almetare öffnen

Abb. 7.13: Mit dem Doxywizard die Doxy-Konfigurationsdatei für Almetare öffnen

Achten Sie darauf, daß der Pfad zum dot-Binary in Ihrer PATH-Environment-Variable enthalten ist (davon geht meine doxygen.cfg-Datei aus). Falls nicht, müssen Sie diesen Pfad noch unter dem Reiter "Dot" im Doxywizard eintragen. Wenn Sie die Graphviz-Software nicht installiert haben, müssen Sie dort die Checkbox "HAVE_DOT" deaktivieren, weil Doxygen sonst jede Menge Fehlermeldungen generiert.

Dann kann die Dokumentation mit dem Run-Button erzeugt werden:

Abb. 7.14: Erzeugen der
        HTML-Programmierer-Dokumentation mit Doxygen

Abb. 7.14: Erzeugen der HTML-Programmierer-Dokumentation mit Doxygen

Dabei entstehen je nach Doxygen-Version ca. 700 bis 1600 HTML-, Grafik- und sonstige Dateien.

8 Anlegen des Almetare-Projektes unter Linux und Qt 3 mit KDevelop 3

Die Free-Edition von Qt für Windows liegt leider nur in der Version 2 vor. Daher wird Almetare auch in Zukunft auf Qt 2 festgelegt bleiben. Neuere Linux-Distributionen werden aber ausschließlich mit Qt 3 ausgeliefert, und es ist mir wegen nicht auflösbarer Paket-Inkompatibilitäten nicht gelungen, mit meiner Festplatteninstallation von Knoppix 3.7 zusätzlich Qt 2 zu installieren. Daher wird im folgenden beschrieben, wie man Almetare mit KDevelop 3.1.1 unter Knoppix 3.7 und Qt 3 neu einrichtet und kompiliert, wenn nur die Quelltexte und die Accessory-Ordner der drei Taschenrechner TI-30, Casio fx-85v und Casio fx-3600P vorhanden sind. KDevelop 3 erlaubt es endlich, auch Unterprojekte anzulegen, so daß man - anders als mit KDevelop 2 - seine Makefiles nicht mehr umständlich selbst generieren muß.
Knoppix bringt die komplette Entwicklungsumgebung bereits mit. Allerdings sollte man das CTags-Paket (exuberant-ctags_5.5.4-1_i386.deb) und die Doxygen-Pakete (doxygen_1.4.1-1_i386.deb, doxygen-doc_1.4.1-1_all.deb, doxygen-gui_1.4.1-1_i386.deb) noch nachinstallieren, um bequem nach Funktionsdefinitionen etc. suchen und die Programierdokumentation erstellen zu können.

8.1 Qt 3 vs. Qt 2

Leider hat es ein paar Nachteile, wenn man das Almetare-Projekt mit Qt 3 linkt:

8.2 Projekt und Unterprojekte anlegen

Auch KDevelop 3 hat einen Assistenten, mit dem man neue Projekte anlegen kann. Man ruft ihn mit Menüpunkt "Projekt - Neues Projekt..." auf. Wir wollen ein C++-QMake-Projekt erzeugen, ein Hello-World-Programm reicht:

neues Projekt mit
        KDevelop 3

Abb. 8.1: Anlegen eines Projektes mit KDevelop 3 und Projekteinstellungen - Teil 1


Der Reihe nach durchläuft man die Dialoge des Assistenten:

Assistenten-Dialog

Abb. 8.2: Projekteinstellungen - Teil 2



Teil 3 und Teil 4 legen eine Vorlage für neu zu erstellende Quell-Code-Dateien fest. Die nicken wir einfach mal ab, gebraucht werden sie nicht:

Projekteinstellungen -
        Teil 4

Abb. 8.3: Projekteinstellungen - Teil 4

KDevelop legt jetzt einen Projektordner mit den nötigen Administrationsdateien an. Sowohl im Projekt als auch später auf der Festplatte kann man einiges wieder aufräumen. Zunächst kann man das Unterprojekt "src" entfernen. Dazu aktiviert man den QMake-Manager und entfernt es aus dem Projekt:
QMake-Manager

Abb. 8.4: Standardunterprojekt entfernen


Anschließend löscht man mit dem Dateiselektor-Tool oder mit dem Konqueror die Verzeichnisse "src" und "Template" aus dem Projektverzeichnis "almetare", da beide nicht gebraucht werden.
Dann legt man in KDevelop drei neue Unterprojekte "fx3600p", "fx85v" und "ti30" für die drei Taschenrechner an:


Unterprojekte anlegen

Abb. 8.5: Unterprojekte anlegen


8.3 Projektdateien kopieren

In das Projektverzeichnis kopiert man jetzt das "almetare"-Verzeichnis, das alle Quelltexte (in den Ordnern "base", "lib" und "special") enthält, die Ordner "ti30", "fx85v" und fx3600P mit den Pixmaps und Handbüchern, den "Linux"- und den "doc"-Ordner. Der Dateibaum sollte danach aussehen wie in Tabelle 6.1.

8.4 Projektdateien hinzufügen

Diesen Projekten müssen nun die jeweils passenden Quelltexte zugeordnet werden. Dazu klickt man im QMake-Manager auf "Vorhandene Dateien hinzufügen" und fügt alle *.cpp-, *.h- und *.ui Dateien dem jeweiligen Projekt hinzu (achten Sie darauf, in der File-Select-Box unten in der Drop-Down-Liste die "relativen Pfade" auszuwählen; nur dann kann man das Projekt auch in anderen Ordnern übersetzen, wenn man es mal verschieben muß):
Dateien hinzufügen

Abb. 8.6: Dateien hinzufügen

Es gilt die folgende Zuordnung:

Alle Dateien im Ordner ...

8.5 Projekteinstellungen

Dann muß man für jedes Projekt noch die Projekteinstellungen vornehmen, wie in den folgenden Abbildungen gezeigt:

Projekteinstellungen -
        Teil 1

Abb. 8.7: Projekteinstellungen - Teil 1

Die Include-Verzeichnisse sind natürlich wieder rechnerabhängig, hier wieder z. B. für das ti30-Projekt:

Projekteinstellungen -
        Teil 2

Abb. 8.8: Projekteinstellungen - Teil 2

Die Zwischendateien werden bei allen Rechnern in das Linux-Verzeichnis geleitet, damit die Quelltextordner sauber bleiben - das ist für die Datensicherung ganz nützlich, weil man dann vorher kein "make clean" machen muß:

Projekteinstellungen -
        Teil 3

Abb. 8.9: Projekteinstellungen - Teil 3


Achten Sie wieder überall auf relative Pfadangaben.

Abschließend sollte man noch in den allgemeinen Projekteinstellungen, die man unter dem Menüpunkt "Projekt - Projekt-Einstellungen" erreicht, in der Rubrik "Komponenten" die gewünschten Komponenten aktivieren. Empfehlenswert sind auf jeden Fall die "Klassenansicht" und die "Debugger-Oberfläche", weil man sonst keinen Klassen-Browser und keinen Debugger zur Verfügung hat. Auch auf die CTags-Unterstützung sollte man nicht verzichten. Die neuen Komponenten erscheinen allerdings erst nach einem Neustart von KDevelop.
Zum Debuggen eines Unterprojekt-Binaries muß man übrigens bei der Komponente "Laufzeit-Optionen" als Hauptprogramm eben dieses Binary eintragen.
Um die Makefiles zu erstellen klickt man jetzt auf Menüpunkt "Erstellen - Projekt erstellen"; das Kompilieren kann man gleich wieder abbrechen, weil es sowieso mit einem Linker-Fehler abbricht - siehe den nächsten Abschnitt.

8.6 Makefile modifizieren

KDevelop hat jetzt sowohl in jedem Unterprojektordner als auch im Projektordner jeweils ein Makefile angelegt. Das Makefile im Projektordner muß jetzt von Hand noch ein wenig modifiziert werden. Sonst klappt nämlich ein "make all" nicht, weil alle Unterprojekte ein eigenes main.o linken müssen, das Makefile aber nur für den ersten Rechner ein main.o erzeugt und dasselbe für die beiden anderen verwenden will - das führt zu Linker-Fehlern.
Deshalb muß man die Zeile

    rm -f Linux/main.o

an drei Stellen im Makefile des Hauptprojekts einfügen:

all: Makefile $(SUBTARGETS)

fx3600p/$(MAKEFILE):
    @$(CHK_DIR_EXISTS) "fx3600p" || $(MKDIR) "fx3600p"
    cd fx3600p && $(QMAKE) fx3600p.pro -o $(MAKEFILE)
sub-fx3600p: fx3600p/$(MAKEFILE) FORCE
    rm -f Linux/main.o
    cd fx3600p && $(MAKE) -f $(MAKEFILE)

fx85v/$(MAKEFILE):
    @$(CHK_DIR_EXISTS) "fx85v" || $(MKDIR) "fx85v"
    cd fx85v && $(QMAKE) fx85v.pro -o $(MAKEFILE)
sub-fx85v: fx85v/$(MAKEFILE) FORCE
    rm -f Linux/main.o
    cd fx85v && $(MAKE) -f $(MAKEFILE)

ti30/$(MAKEFILE):
    @$(CHK_DIR_EXISTS) "ti30" || $(MKDIR) "ti30"
    cd ti30 && $(QMAKE) ti30.pro -o $(MAKEFILE)
sub-ti30: ti30/$(MAKEFILE) FORCE
    rm -f Linux/main.o
    cd ti30 && $(MAKE) -f $(MAKEFILE)

Achtung: KDevelop schreibt das Makefile neu, wenn man Projekteinstellungen ändert. Ggf. muß man die Änderungen dann erneut vornehmen. Leider habe ich bisher noch keine bessere Möglichkeit gefunden.

8.7 Kompilieren

Man kann jetzt entweder jedes Unterprojekt einzeln mit Menüpunkt "Erstellen - Unterprojekt erstellen" erstellen oder alle Taschenrechner auf einmal mit Menüpunkt "Erstellen - Projekt erstellen" bauen. Außer der üblichen Warnung "taking address of temporary" in Datei calc_dlg_base.cpp sollten keine schlimmen Dinge ausgegeben werden.
Auf der Konsole kann man einfach "make" eingeben, um alle Unterprojekte zu übersetzen; "make sub-ti30", "make sub-fx85v" oder "make sub-fx3600p" baut die Unterprojekte.
Achtung: Das Makefile im Hauptprojektordner beißt sich mit KDevelop 2! KDevelop 2 braucht hier ein leeres Makefile, damit man aus der IDE heraus kompilieren kann.

Nach oben.