Hauptseite der Doxygen-Klassendokumentation (die Seite, die Sie gerade betrachten, ist dort eingebettet)
Das Projekt wird - hoffentlich - nach und nach alle meine alten Taschenrechner beinhalten. Es ist zu erwarten, dass sich dieser Prozess ü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.
Zur Zeit (Mai 2003) sind zwei Taschenrechner realisiert, der Texas Instruments TI-30 und der Casio fx85v. 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 dass 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ässt 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), dass es zwei Operanden hat und eine Addition ausführen muss. 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 muss. 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, dass 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, dass das Wissen zur Bearbeitung eines Symbols jedes Symbol selbst besitzt. Das bedeutet nämlich, dass, 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), weiss, dass sie zwei Operanden vom Stack holen und multiplizieren und das Ergebnis dort wieder ablegen muss. 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 dass 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 muss 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 muss 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 muss 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, dass 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 muss. 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 dass 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,
dass 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, dass 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 - Abschluss
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, dass 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, dass 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, dass 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 muss man jedoch darauf achten, dass man auch den Zielordner angeben muss, und dieser muss 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 muss 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, dass 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, dass 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 muss 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 dass in den Projektordnern nur noch "ti30.dsp" und "fx85v.dsp" und die (ohnehin leeren) "Debug"-Ordner übrigbleiben.
Diese Hierarchie erlaubt es, dass 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 muss 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 muss 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, muss nun für das Projekt "ti30" wiederholt werden. Dazu muss 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 muss daher
bei Bedarf von Hand gelöscht werden.










