Workshops / Hintergrundberichte / Buchvorstellungen
---------------------------------------------------

1. Einführung in die Reaction-Programmierung (von Martin R. Elsner)

Teil 5: Ressourcen


Heute kommen wir zu einem etwas anderen Teil der Programmentwicklung, der nicht
direkt mit Reaction zusammenhängt, den man aber auch für unsere Objekte benötigt
(ich hoffe, die Puristen unter euch werden mir das nicht übel nehmen ;).
Zusätzlich zum eigentlichen Quellcode benötigt man meist weitere Daten;
man spricht auch von Ressourcen (wörtlich "Vorräte" oder "Hilfsmittel").
Dabei handelt es sich weitgehend um Texte und Grafiken.

Dabei kommt den Texten eine besondere Bedeutung zu, da diese
möglichst immer in der richtigen Sprache erscheinen sollen! Gerade im
Amiga-Bereich sollte man darauf achten, dass eigene Programme mindestens
die Voraussetzungen dafür bieten. Die eigentliche Übersetzung (d.h. die
Erstellung sogenannter "Catalogs") kann mit geringen Kenntnissen von fast jedem
Interessierten angefertigt werden, was den Programmierer wieder sehr entlastet.
Die Lokalisierung eines Programms soll der Hauptteil des heutigen Kurses werden,
zuvor aber ein paar Worte zu den


Grafiken

Grafiken müssen im Allgemeinen nicht sprachspezifisch sein, aber verwöhnte User
möchten gerne auch diesen Teil des Programms selbst konfigurieren. Zum Teil ist
es auch nötig, dem Anwender die Auswahl zwischen verschieden großen Grafiken zu bieten
(siehe z.B. die Bilder in den Listern von ClassAction, die 16 oder 11 Pixels hoch
sein können), da verschiedene Bildschirmauflösungen oder Schriften Aussehen und
Benutzbarkeit des Programms beeinträchtigen.

Da das Einbinden von Grafiken in den Quelltext nicht ohne Hilfsmittel möglich ist,
empfehle ich dafür den Einsatz von ReActor. Dort können Images angelegt und
direkt mit Binärdaten gefüllt werden, die dann in der von ReActor erzeugten
Objektdatei abgelegt werden. Vorteile sind hier die einfache Handhabung
und die Kompaktheit des Programms, Nachteil ist allerdings die statische Festlegung,
die ein Ändern der Grafiken nur durch Neukompilieren erlaubt. Außerdem ist es nicht
möglich, selten benutzte Grafiken erst bei Bedarf zu laden.

Statt mit ReActor kann man Bilder aber auch im Quelltext anlegen, und zwar mit
dem Bitmap-Objekt. Es erlaubt die Angabe einer Grafikdatei (BITMAP_SourceFile),
aus der mittels Datatypes die benötigten Informationen ausgelesen werden. Wichtig
ist hierbei, dass der Bildschirm mit BITMAP_Screen übergeben wird, damit die
Farbinformationen richtig zusammengestellt werden. Daran sollte man auch denken,
wenn das Programm verschiedene Bildschirme benutzt; ein Bitmap kann nicht für
mehrere Screens benutzt werden, ohne die Bildinformationen neu zu berechnen.
Übrigens erlauben nicht alle Datatypes die Erzeugung einer Transparenz-Maske
(siehe BITMAP_Masking/BITMAP_MaskPlane), so dass manche Grafiken immer als
Rechtecke erscheinen. Hier empfiehlt sich ein Test mit verschiedenen
Grafikformaten, wobei das altgediente IFF-Format immer brauchbare Ergebnisse liefert
und auch mit allen Grafikprogrammen am Amiga bearbeitet werden kann.

Während alle BOOPSI-Objekte, die Schalter und Eingabemöglichkeiten kapseln, im Kern
aus einer "struct Gadget" bestehen, bauen Bitmaps, Labels etc. auf einer
"struct Image" auf (dies wird auch in der Klassenhierarchie deutlich: die
"rootclass" als Basis ist Mutterklasse für "gadgetclass", "imageclass" und "icclass",
wobei letztere eher unbedeutend ist). Dies hat den Vorteil, dass an allen Stellen,
an denen ein Image erwartet wird (u.a. bei den Knoten der Listbrowser-Labels),
ein Zeiger z.B. auf das Bitmap-Objekt verwendet werden kann, denn die ersten Bytes
dieses Objekts enthalten eine "struct Image".

Dadurch ist es ohne Probleme möglich, Grafikdateien aus einem Verzeichnis einzulesen
und zu verwenden, die dann auch ausgetauscht werden können, z.B. um ganze "Skins",
also einheitliche Oberflächen, zu realisieren (siehe auch WINDOW_BackFillName der
Window-Klasse).

Soweit zu den Grafiken, nun zum schwierigeren Teil:


Catalog Descriptions

Die Vorbereitung eines Programms für die Übersetzung nennt man Lokalisierung.
Grundlage ist am Amiga immer eine Catalog-Description-Datei (#?.cd).
Wer das Tool ReActor verwendet, hat schon Bekanntschaft mit dieser Datei gemacht,
denn in ihr werden sämtliche anzuzeigenden Texte abgelegt (z.B. die Werte für
GA_Text, WA_Title usw.). Deswegen fordert ReActor an diesen Stellen auch schon
die Eingabe einer ID, mit der dieser Text später automatisch durch einen
entsprechenden übersetzen String ersetzt werden kann.

Wenn weitere Texte im Programm benötigt werden (Fehlermeldungen, Warnhinweise,
Menüpunkte,...), sollte man diese der Einfachheit halber in ReActor einbinden,
da die Koordination sonst etwas aufwendiger ist. Dazu kann man z.B. unter "Images"
Labels hinzufügen, die dann in LABEL_Text den entsprechenden Text
(z.B. "Are you sure?"), unter "Locale ID" einen aussagekräftigen Namen
("MSG_AREYOUSURE") und als "Object Name" eine ähnliche Bezeichnung mit Wiedererkennungswert
erhalten ("LABEL_AREYOUSURE"). Sortiert man die Einträge alphabetisch, so erhält
man dadurch eine gute Übersicht über die vorhandenen Texte.

Doch die .cd-Datei lässt sich genauso gut auch per Hand anlegen, wenn man ReActor nicht
nutzen möchte. Sie sieht beispielsweise so aus:

; *** catalog description file of "ClassAction.res"
;
MSG_HELP (272//)
Help
;
MSG_ICONIFY (273//)
Iconify
;
MSG_PROJECT (274//)
Project
;
...

Der Aufbau ist recht einfach:
<Name> (<Nummer>/<Mindestlänge>/<Höchstlänge>)
<Standardtext>
;

Die Werte in der Klammer können jeweils entfallen, was dann bedeutet:
keine Nummer: Nummer des letzten Eintrags + 1
keine Mindest-/Höchstlänge: keine Beschränkung des Strings.

(Zu den Namenskonventionen siehe CatComp-Anleitung)

Allerdings sollte man an der von ReActor generierten .cd-Datei nichts per Hand
ändern, da diese beim nächsten Speichern in ReActor überschrieben wird.


Von der Beschreibung zum Quelltext

Mit dem Programm CatComp, das u.a. auf der DevCD zu finden ist, oder ähnlichen
Programmen (siehe unten) lassen sich nun aus der .cd-Datei Quelltexte erzeugen.
Der Aufruf:

  catcomp myproject.cd CFILE myproject.h

Dies erzeugt eine Header-Datei myproject.h, in der Nummern und Strings enthalten sind.
Dies reicht aus, um im Quelltext einen lokalisierten Text anzusprechen. ReActor benötigt
allerdings auch eine Objektdatei mit den nötigen Informationen, sodass eine .asm-Datei
erzeugt werden muss:

  catcomp myproject.cd CFILE myproject.h ASMFILE myproject.asm XDEF

Die zusätzliche Assembler-Datei muss nun noch übersetzt und zum Programm gelinkt werden.

In StormC kann die .cd-Datei einfach zum Projekt hinzugefügt werden, als
Übersetzungsskript wählt man "catcomp.srx", das man per Hand ändern sollte
("Makefile editieren"):

  PARSE ARG '"' filename '"' '"' projectname '"' .

  /* .h und .asm-Datei erzeugen, wobei "CD" angehängt wird, um Verwechslungen zu vermeiden */
  objectname_h = LEFT(filename,LASTPOS('.cd',filename)-1)||"CD.h"
  objectname_asm = LEFT(filename,LASTPOS('.cd',filename)-1)||"CD.asm"

  OBJECTS filename objectname_h objectname_asm

  ADDRESS COMMAND "catcomp "||filename||" CFILE "||objectname_h||" ASMFILE "||objectname_asm||" XDEF"

  ADDFILE objectname_h QUIET
  ADDFILE objectname_asm QUIET

Für die zusätzliche .asm-Datei, die nach dem Kompilieren im Projekt auftaucht,
kann das Übersetzungsskript "phxass.srx" ausgewählt werden; folgende Änderung
bietet sich an:

ADDRESS COMMAND 'StormC:StormSYS/PhxAss '||filename||' TO '||objectname||' SET "CATCOMP_ARRAY" NOEXE I stormc:asm_include QUIET'

In stormc:asm_include habe ich die Assembler-Includes (auf der DevCD:
"ADCD_2.1:NDK/NDK_3.5/Include/include_i/#?") hinterlegt, die PhxAss benötigt.


Verwendung der lokalisierten Texte

Das Projekt sollte jetzt schon übersetzt werden können und laufen.
In allen Dateien, in denen wir lokalisierte Texte verwenden wollen, müssen wir
die nötigen Header-Dateien einfügen:

  #include <proto/locale.h>
  #define CATCOMP_NUMBERS
  #define CATCOMP_STRINGS
  #include "MyProjectCD.h"

Im Quelltext müssen wir die Locale.library und den entsprechenden Catalog öffnen:

  struct LocaleBase *LocaleBase;
  struct Catalog *MyCatalog;
  *LocaleBase = (struct LocaleBase*)OpenLibrary( "locale.library",39 );
  *MyCatalog = OpenCatalog( NULL,"MyProject.catalog",OC_BuiltInLanguage,"english",
    OC_Version,1,CATVERS,TAG_DONE );

und natürlich am Ende mit CloseCatalog(MyCatalog) und CloseLibrary(LocaleBase)
wieder schließen.
OC_BuiltInLanguage gibt die Sprache der Texte an, die unser Projekt bereits enthält.
Man sollte diese Texte immer in Englisch angeben, damit man im Falle fehlender Catalogs
immer noch die Chance hat, alle Beschriftungen und Meldungen zu verstehen.
OC_Version ist von Interesse, falls mehrere Programmversionen existieren und
Veränderungen bei den Texten stattgefunden haben. Unveränderte Texte werden nur dann
richtig dargestellt, wenn auch die Nummern identisch sind, was bei der Verwendung von
ReActor nicht unbedingt der Fall ist. Auf die Version des Catalogs werde ich unten noch
kurz eingehen.

Während das Ergebnis von OpenLibrary geprüft werden sollte, kann uns der Rückgabewert
von OpenCatalog fast egal sein - solange wir nur die Funktion GetCatalogStr() benutzen,
reicht auch ein NULL-Wert, sodass das Programm in jedem Fall mit den Standardtexten
benutzt werden kann!

Um die von ReActor erzeugten Objekte brauchen wir uns nicht mehr zu kümmern, nur unsere
eigenen Texte müssen noch eingebaut werden: z.B.

  ShowMessage( GetCatalogStr(MyCatalog,MSG_AREYOUSURE,MSG_AREYOUSURE_STR) );

(vorausgesetzt, es existiert eine Funktion ShowMessage(), die einen Text anzeigt).
Dies kann mit einem Makro auch abgekürzt werden: mit
  #define GETSTR(id) GetCatalogStr(MyCatalog,id,id ## _STR)
reicht dann
  GETSTR(MSG_AREYOUSURE)

Das bedeutet natürlich, dass sämtliche Quelltextdateien nach solchen Texten
durchsucht, die Texte in ReActor oder direkt in die .cd-Datei eingetragen und dann
im Quelltext mit GetCatalogStr() ausgelesen werden müssen ...


Catalog erzeugen

Damit auch die richtigen Texte im Programm angezeigt werden, müssen natürlich zunächst
Catalogs erzeugt werden. Zu diesem Zweck empfehle ich das Programm ReCatIt, das die Arbeit
sehr erleichtert. Entscheidet man sich für die manuelle Methode, so sind 3 Schritte nötig:

1. Übersetzungsdatei #?.ct erzeugen:

  catcomp MyProject.cd CTFILE MyProject.ct

2. Übersetzungsdatei übersetzen und anpassen; Beispiel:

  ## version $VER: ClassAction.catalog 44.0 (05-05-02)
  ## codeset 0
  ## language deutsch
  ;
  MSG_HELP
  Hilfe
  ;
  MSG_ICONIFY
  Verbergen
  ;
  MSG_PROJECT
  Projekt
  ;

3. Catalog erzeugen:

  catcomp MyProject.cd MyProject.ct CATALOG MyProject.catalog

... und fertig. ReCatIt vereint alle drei Schritte und bietet eine praktische
Oberfläche zur Bearbeitung der Texte.

Screenshot:
Screenshot

Diesen Catalog muss man allerdings im richtigen Verzeichnis hinterlegen,
denn der Name "MyProject.catalog" sagt nichts über die Sprache aus. Sie wird im
Verzeichnis Locale:Catalogs/<Sprache> oder PROGDIR:Catalogs/<Sprache> abgelegt,
also z.B. in Locale:Catalogs/deutsch/MyProject.catalog.

Für weitergehende Fragen verweise ich auf die Docs von locale.library,
CatComp und ReCatIt.


Ich hoffe, das hat wieder ein paar Probleme gelöst, Fragen beantwortet und
den Anstoß zu vielen Übersetzungen geliefert!
Übrigens ist die Lokalisierung am Amiga meiner Meinung nach besser gelöst als
unter Windows, selbst wenn man dort eine sonst recht komfortable
Entwicklungsoberfläche wie Delphi nutzt.
Ein kleiner Hinweis in eigener Sache: ClassAction erlaubt es recht einfach,
.cd/.ct-Dateien mit mehreren Cli-Befehl oder ARexx-Skripten zu verbinden, so
dass man den Aufruf von CatComp oder ReCatIt mit einem Mausklick
(oder zweien ;) erledigen kann.

So, genug der Werbung, jetzt kann ausprobiert werden,

euer Martin R. Elsner <email@martin-elsner.de>


Bezugsquellen:

CatComp:
  DevCD: ADCD_2.1:NDK/NDK_3.5/Tools/CatComp
  (leider nicht im Aminet - falls jemand eine Adresse kennt, unter der man
  CatComp herunterladen kann, wäre ich über einen Hinweis dankbar!)
FlexCat (Alternative zu CatComp mit kleineren Unterschieden):
  Aminet: dev/misc/FlexCat.lha
KitCat (kompatible, aber etwas eingeschränkte - deutsche - Alternative zu CatComp):
  Aminet: dev/misc/KitCat.lha
ReCatIt:
  Aminet: dev/misc/ReCatIt.lha oder dev/misc/ReCatItPro.lha
CatEdit (Alternative zu ReCatIt):
  Aminet: dev/misc/CatEdit.lha