Hauptseite der Doxygen-Klassendokumentation (die Seite, die Sie gerade betrachten, ist dort eingebettet; die Doxygen-Seiten sind allerdings nur in der Quelltext-Distribution vorhanden)
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.)
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.
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:
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:
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:
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.
Beispiel: Auf dem Rechner wird die Tastenfolge "2 + 3 * 4 + 5 =" eingegeben. Wie sehen die beiden Stacks nach jedem Tastendruck aus?
Taste | NumStack | SymStack |
|
---|---|---|---|
|
|
Die Zahl 2 wird auf den Zahlenstack gelegt. | |
|
|
|
Der Operator + wird auf den Symbolstack gelegt. |
|
2 |
|
Die Zahl 3 wird zusätzlich oben auf den Zahlenstack gelegt. |
|
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") |
|
3 2 |
+ |
4 zusätzlich auf Zahlenstack. |
(Schritt 1) |
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. |
|
|
|
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 |
|
14 |
|
5 zusätzlich auf den Zahlenstack. |
|
|
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.
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.
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.
Abb. 4.1: Almetare-Hauptschleife
Was in der Aktivität "Eingabe verarbeiten" geschieht, zeigt vereinfacht die folgende Verfeinerung:
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.
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.
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.).
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.
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.
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.
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
Vorlagen für Header-Dateien (und ebenso Cpp-Dateien) sollte man deaktivieren:
Abb. 6.3: Projekteinstellungen - Fortsetzung
Im letzten Schritt wird das Projekt generiert:
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. |
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.
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.
a) "almetare.kdevprj" nach "ti30.kdevprj" kopieren und mit Menüpunkt "Projekt - Öffnen..." mit KDevelop laden.
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
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
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
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.
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.
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.
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
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.
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.
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
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
=> 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
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.
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
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
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
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
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
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.