<< Inhalt >> Workshops Inhalt

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: (//) ;

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 
  #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

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/ oder PROGDIR:Catalogs/ 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 

<< >>