CPX: Dem variablen Kontrollfeld auf der Spur, Episode 1

Welcher ST-Besitzer hat noch nicht neidisch vor dem neuen TT gestanden und - mal abgesehen vom Gehäuse (nach neuesten Gerüchten wurde der immer noch flüchtige Designer zuletzt auf Nimbus V gesichtet) - das neue Desktop bewundert? Wer mal ein bißchen mit dem Desktop herumgespielt hat (oder auch nur die ST-Computer gelesen hat), weiß auch, daß das nicht alles ist, was Atari in Sachen Software für den TT getan hat. Zusätzlich zu dem neuen Desktop hat der Rechner auch noch ein völlig neues Kontrollfeld bekommen, über das bereits mehrfach berichtet wurde.

Deshalb dürfte es mittlerweile wohl auch bekannt sein, daß es sich dabei um ein sogenanntes variables oder modulares Kontrollfeld handelt, das maximal neunundneunzig Module verwalten kann. Mit anderen Worten: Der bisher äußerst knapp bemessene Platz für Accessories, von denen der Atari ja bekanntlich nur sechs verwalten kann, hat plötzlich durch das Modulkonzept einen Quantensprung nach vorne gemacht.

„Open hailing frequencies!“

Im folgenden möchten wir nun allen Programmierern eine Hilfestellung geben, die ihre eigenen CPX-Module entwickeln wollen; CPX steht dabei als Abkürzung für „Control Panel Extension“. Als Beispielprogramme werden dabei zwei Module dienen, die erstens das Kontrollfeld sinnvoll ergänzen und zweitens anschaulich die Programmierung von einerseits Pull-Down-Menüs und andererseits Slidern (Schiebern) demonstrieren; beides wird nämlich durch vom Kontrollfeld zur Verfügung gestellte Funktionen spielend einfach. Die hier vorgestellte Dokumentation beruht auf eigenen Nachforschungen und ist deshalb mit Sicherheit nicht mit einer vielleicht irgendwann in ferner Zukunft erscheinenden Atari-Dokumentation kompatibel. Nichtsdestoweniger ist sie jedoch zu mindestens 99 Prozent komplett.

Bevor sich nun alle möglichen Leute nach dem Lesen dieses Artikels auf die Programmierung eigener CPX-Module stürzen, sollte hier direkt zu Anfang erst einmal eine Warnung stehen: So schön es auch sein mag, bereits bestehende Accessories für das Kontrollfeld umzuschreiben, um Accessory-Slots freizubekommen, ist das auf gar keinen Fall Sinn der Sache! Das Kontrollfeld sollte ausschließlich zur Konfiguration des Rechners, weiterer Hardware oder zur Konfiguration von Programmen, wie z.B. dem Mausbeschleuniger, eingesetzt werden! So wäre es beispielsweise denkbar, mit Hilfe des Kontrollfelds eine RAM-Disk oder einen Drucker-Spooler zu konfigurieren. Mit anderen Worten: CPX-Module sollten ausschließlich als komfortable Möglichkeit zur Konfiguration von residenten Programmen eingesetzt werden, die immer zur Verfügung steht, jedoch nur bei Bedarf Speicherplatz verbraucht.

Mechanisches

Wie arbeitet das Kontrollfeld eigentlich? Zunächst einmal wird nach dem Laden des Kontrollfelds im CPX-Verzeichnis nach aktiven Modulen gesucht und deren Header eingeladen (Aufbau des Headers siehe unten). Bei entsprechender Kennzeichnung wird anschließend in jedem Modul eine Initialisierungsroutine mit gesetztem Initialisierungs-Flag aufgerufen, die dem Modul Gelegenheit gibt, die in ihm gesicherten Parameter einzustellen, d.h. eine separate Parameterdatei ist nicht erforderlich. Es ist auch möglich, CPX-Module zu schreiben, die ausschließlich Parameter setzen und dann für immer in der unendlichen Weite des Speichers verschwinden. Allerdings geht dies auch mit AUTO-Ordner-Programmen, weshalb diese Möglichkeit hier nur der Vollständigkeit halber erwähnt werden soll. Des weiteren können Module auch noch resident geladen werden, d.h. beim Booten wird nicht nur der Header, sondern das komplette CPX-Modul eingelesen. Nach der so durchgeführten Initialisierung wartet das Kontrollfeld wie jedes andere Accessory auch darauf, daß es aufgerufen wird.

Wird jetzt das Kontrollfeld angewählt, stellt es zunächst das bekannte Auswahlfenster dar, in dem die zur Verfügung stehenden Module mit Icon angeboten werden. Diese Informationen wurden den Headern der einzelnen Module entnommen und bleiben während der gesamten Laufzeit als einzige im Speicher. Erst wenn ein Modul mit Doppelklick geöffnet wird, lädt das Kontrollfeld das Modul in den Speicher („Beam me up, ScoTTy!“), sofern es nicht bereits beim Booten resident geladen wurde.

Anschließend wird die dem Modul zugehörige Initialisierungsroutine erneut aufgerufen (diesmal mit gelöschtem Initialisierungs-Flag, das weiter unten mit dem Namen booting bezeichnet wird), um globale Variablen zu initialisieren. Dazu gehören bei gesetztem Resource-Flag auch die Resource-Informationen, die im Modul enthalten sein müssen! Falls nämlich ein rsrc_load()-Aufruf erfolgen würde, würde dabei automatisch die vom gegenwärtig laufenden Programm benutzte Resource-Datei aus dem Speicher entfernt werden, was natürlich nicht Sinn der Sache sein kann.

An dieser Stelle wollen wir auch direkt darauf hinweisen, daß es nicht sinnvoll ist, statische Variablen vorzusehen, die beim nächsten Öffnen des Moduls noch ihren Wert haben sollen, denn beim erneuten Laden des Moduls werden natürlich die Variablen ebenso wie die Resource-Informationen neu initialisiert. Die einzige Ausnahme ist dabei der Fall, daß das Modul resident geladen wurde; dann behalten die statischen und globalen Variablen ihre Werte. Daher sollten bei jedem Neustart eines Moduls die einzustellenden Parameter aus den Systemvariablen oder den zu konfigurierenden Programmen neu eingelesen werden, zumal auch andere Programme diese Einstellungen verändern können.

Als Parameter bekommt die Initialisierungsroutine einen Zeiger auf die Struktur CPXPARAMS übergeben, die wie in XCONTROL.H definiert ist (siehe Listing). Dieser Zeiger ist unbedingt in einer globalen Variablen zu sichern, da das Modul ständig auf sie zugreifen muß. Die Struktur enthält verschiedene Flags sowie Zeiger auf Hilfsfunktionen, die das Kontrollfeld dem Modul zur Verfügung stellt. Alle Routinen erwarten ihre Parameter auf dem Stack und müssen deshalb unter Turbo-C als cdecl deklariert werden.

Die Initialisierungsroutine muß anschließend dem Kontrollfeld die Adresse der beim Öffnen des Moduls aufzurufenden Routine zurückgeben. Diese Routine muß ebenso wie die Initialisierungsroutine als cdecl deklariert werden. Außerdem lassen sich in der Rückgabestruktur auch noch die Adressen verschiedener Event-Routinen angeben; diese werden jedoch im Normalfall nicht benötigt und uns deshalb auch erst in einer späteren Folge beschäftigen.

**WORD do_form(OBJECT *tree, WORD start_obj, WORD *msg_buffer)**

Neue form_do()-Routine

Übergabeparameter:

tree Baumadresse (i.a. Adresse einer Dialogbox)
start_obj Index des Startobjekts
msg_buffer Adresse eines Messagebuffers

Rückgabe: Index des angeklickten Objekts oder -1 bei Eintreffen einer Message

**WORD do_pulldown(char *entries[], WORD num_items, WORD checked_item, WORD font, GRECT button_xywh, GRECT window_xywh)

Behandlung eines Pull-Down-Menüs

Übergabeparameter:

entries Adresse eines Arrays von Zeigern auf die Menüeinträge (Strings)
num_items Anzahl der Menüeinträge
checked_item Nummer des abgehakten Eintrags

0 <= checked_item < num_items
font verwendeter Zeichensatz
3 = normal 5 = klein

button_xywh Koordinaten und Größe des Buttons, aus dem ein Pull-Down-Menü heraus klappen soll
window_xywh Koordinaten und Größe der Dialogbox (= des Fensters)

Rückgabe: Index des angeklickten Menüeintrags, -1 sonst

**VOID do_resource(WORD num_obs, WORD num_frstr, WORD num_frimg, WORD num_tree, OBJECT *rs_object, TEDINFO *rs_tedinfo, BYTE *rs_strings[], ICONBLK *rs_iconblk, BITBLK *rs_bitblk, LONG *rs_frstr, LONG *rs_frimg, LONG rs_trindex, struct foobar rs_imdope)

„Relozieren“ der eingebunden Resource-Datei, d.h. Umrechnen der Zeichenkoordinaten in Bildschirmkoordinaten

Übergabeparameter:

num_obs, num_frstr, num_frimg, num_tree
die entsprechenden Konstanten NUM_OBS, NUM_FRSTR, NUM_FRIMG, NUM_TREE aus „*.RSH“

rs_object, rs_tedinfo, rs_strings, rs_iconbik, rs_bitblk, rs_frstr, rs_frimg, rs_trindex, rs_imdope
die gleichnamigen Strukturen aus „*.RSH“

Rückgabe: keine

*WORD find_cookie(LONG cookie, LONG version)

Durchsuchen des Cookie-Jars

Übergabeparameter:

cookie eindeutige Cookie-Identifizierung (vier ASCII-Zeichen als Langwort codiert)
version zurückgegebener Cookie-Wert

Rückgabe: TRUE falls Cookie gefunden, sonst FALSE

**GRECT rci_first(GRECT object_xywh)

Ermitteln des ersten Rechtecks der Rechteckliste des Kontrollfeldfensters, fertig geschnitten mit dem übergebenen Rechteck

Übergabeparameter:

object_xywh neu zu zeichnender Bereich

Rückgabe: tatsächlich zu zeichnender Bereich

*GRECT rci_next(VOID)

Ermitteln des nächsten Rechtecks der Rechteckliste des Kontrollfeldfensters, fertig geschnitten mit dem bei rci_first() übergebenen Rechteck

Übergabeparameter: keine

Rückgabe: tatsächlich zu zeichnender Bereich

Tabelle 1: Übersicht über die Kontrollfeldfunktionen (Teil 1)

Anatomisches

Die CPX-Datei besteht im wesentlich aus zwei Teilen, dem Header und dem eigentlichen Programm.

Der Header hat eine Länge von 512 Bytes und enthält Informationen über das Modul, wie z.B. das Icon, Versionsnummer und den Modulnamen. Die Definition des Headers in C können sie Abbildung 1 entnehmen. Hier jetzt eine etwas ausführlichere Erklärung der einzelnen Einträge im Header:

magic: Anhand dieses Eintrags überprüft das Kontrollfeld, ob es sich um ein CPX-Modul handelt oder nicht. Der korrekte Eintrag muß 100 (dezimal) lauten.

flags: Diese Struktur stellt ein Bit-Feld dar, dessen einzelne Bits als Flags für den Lade-Modus Verwendung finden. Das resident-Bit gibt an, ob ein CPX-Modul ständig im Speicher behalten oder ob es nach Verlassen wieder aus dem Speicher entfernt wird; residente Module sind im Normalfall nicht notwendig. An dieser Stelle ist vielleicht eine Warnung angebracht: Es sollten keine CPX-Module entwickelt werden, die sich darauf verlassen, daß sie immer resident sind, da der Anwender selbst dies im Kontrollfeld konfigurieren kann!

Das boot_init-Flag gibt Auskunft über die Behandlung der Initialisierungsroutine. Ist dieses Bit gesetzt, wird die Initialisierungsroutine sowohl beim Booten als auch bei jedem Einladen unmittelbar aufgerufen; die Variable booting in der dabei übergebenen Struktur (im Listing „DISK.C“ über par->booting angesprochen) hat in diesen Fällen den Wert TRUE. Soll das Modul beim Einladen jedoch nicht initialisiert werden, sondern erst bei der Anwahl mit Doppelklick, so muß das boot_init-Flag gelöscht sein. In diesem Fall ist auch booting bei Aufruf der Initialisierungsroutine gelöscht, d.h. FALSE.

Bei gesetztem set_only-Flag hat das CPX-Modul die Auswirkungen eines AUTO-Ordner-Programms, d.h. es wird lediglich die Initialisierungsroutine ausgeführt und das Modul anschließend wieder verlassen, so daß es nicht in der Auswahlliste des Kontrollfeldes erscheint. Der Rückgabewert der Initialisierungsroutine muß dabei NULL sein, d.h. es werden keine weiteren Funktionen im CPX mehr ausgeführt. Das set_only-Flag hat Vorrang vor dem boot_init-Flag.

typedef struct {
   WORD magic;	   /* CPX-Kennung */
   struct 
   {
      unsigned reserved:   13;
      unsigned resident:   1;
      unsigned boot_init:  1; 
      unsigned set_only:   1;
   } flags;	      /* diverse Flags */
   char cpx_id[4];	/*	eindeutige Modul-ID	*/
   WORD cpx_version;	/*	Versionsnummer */
   char icon_name[14];	/*	Icon-Name */
   LONG icon_data[24];	/*	Icon-Daten */
   WORD icon_info;	/*	Farbe, Buchstabe...	*/
   char cpx_name[18];	/*	Text neben dem Icon	*/
   WORD obj_state;	/*	Farben */
   BYTE reserved[370];	/*	nicht benutzt */
} CPXHEADER;
Abb. 1: Definition des Headers eines CPX-Moduls
Abb. 2: Das installierte Modul DISK.CPX
Abb. 3: Die Dialogbox des Moduls DISK.CPX

cpx_id: Hierbei handelt es sich um eine ASCII-ID, anhand derer das Kontrollfeld erkennen kann, ob das Modul bereits geladen ist oder nicht.

cpx_version: Hierin ist die aktuelle Versionsnummer des Moduls in hexadezimaler Form enthalten, die das Kontrollfeld auch bei CPX-Info... ausgibt; beispielsweise 0x 123 für die Versionsnummer 1.23.

icon_name: Dies ist der Name, der unter dem Icon im Auswahlfeld steht.

icon_data: Hierbei handelt es sich eigentlich um die Daten für ein Image; die Größe beträgt 32 Pixel waagerecht mal 24 Pixel senkrecht.

icon_info: In den Bits 12-15 ist die Farbe enthalten, in den Bits 0-7 steht der Icon-Buchstabe, sofern vorhanden (nicht sinnvoll).

cpx_name: ausgeschriebener Name des Moduls

obj_state: Die Bits 0-3 enthalten die Farbe des Objektinneren, die Bits 4-6 das Füllmuster; Bit 7 kennzeichnet den Schreibmodus (0 = transparent, 1 = deckend). Die Bits 8-11 enthalten die Text- und die Bits 12-15 die Rahmenfarbe; der normale Wert ist 0x1180.

Soweit zur Beschreibung des Headers. Nach dem Header folgt ein normales GEM-Programm, das jedoch keinen Startup-Code enthält. Hierbei ist jedoch zu beachten, daß die Initialisierungsroutine des CPX-Moduls direkt am Anfang des Textsegmentes stehen, also im Quelltext als erste Funktion definiert werden muß.

Physikalisches

Wie oben bereits erwähnt, bekommt man vom Kontrollfeld beim Aufruf der Hauptroutine einen Zeiger auf einen Parameterblock übergeben, in dem zahlreiche Funktionen vom Kontrollfeld zur Verfügung gestellt werden. Es ist also keinesfalls notwendig, das Rad neu zu erfinden. Ein erfreulicher Nebeneffekt dieser Funktionen ist außerdem, daß die Module relativ kurz sind, da man sich um viele Dinge nicht mehr selbst zu kümmern braucht. Unter diese Funktionen fallen u.a. Routinen zur Behandlung von Schiebern (Slider), Pull-Down-Menüs, Dialogen und Resources zum Suchen von Cookies im Cookie-Jar (es lebe das Krümelmonster!) und Routinen zum Zugriff auf die Rechteckliste des Kontrollfeldfensters.

Die Routine do_resource() dient beispielsweise dazu, die mittels Resource Construction Set erzeugte und in den Quelltext eingebundene „*.RSH“-Datei zu relozieren. Will man jedoch die Koordinaten eines Objektes unbedingt selbst vom Character- ins Pixel-Format umrechnen, so kann dazu die Funktion objc_adjust() benutzt werden.

Mancher wird sich vielleicht fragen, wozu eine neue form_do()-Format zur Verfügung gestellt wird. Des Rätsels Lösung ist ganz einfach: In einer normalen form_do()-Routine werden andere Ereignisse, die nicht im Zusammenhang mit der Dialogbox stehen, wie z.B. die Anwahl von Drop-Down-Menüs in der Menüzeile, ignoriert. Das würde bei einem Kontrollfeld, das ja parallel zu einem Programm in einem Fenster abläuft (Multitasking!), zum Blockieren des ganzen Systems führen. Die neue Routine do_form() arbeitet ähnlich wie form_do(), nur daß das Hauptprogramm nicht blockiert wird. Es kann nicht nur der Index des angewählten Objekts zurückgeliefert werden, sondern auch eine Message, die im angegebenen Messagepuffer abgelegt wird.

Abb. 4: Diese Mitteilung erhält man nach dem Parken der Festplatte.

Weiterhin gibt es zwei Funktionen zum Lesen der Rechteckliste, da das Window-Handle des Kontrollfeldfensters nicht bekannt ist.

Außerdem übernimmt diese Routine gleichzeitig auch das berühmt-berüchtigte rc_intersect(), so daß die benötigten Clipping-Bereiche direkt zurückgeliefert werden. Hier gleich eine Warnung: Die Funktionen rci_first() und rci_next() liefern einen Zeiger auf eine auf dem lokalen Stack der Funktionen angelegte Struktur, weshalb der Inhalt vor Benutzung in eine eigene Struktur kopiert werden sollte.

Eines der auffälligsten Features des modularen Kontrollfeldes sind die Pull-Down-Menüs, die man im Gegensatz zu den Drop-Down-Menüs erst durch Anklicken „herunterziehen" muß. Auch hierzu steht eine komfortable Routine zur Verfügung, die die Verwaltung von Pull-Down-Menüs zum Kinderspiel macht. Man übergibt ihr einfach ein Array, das die Adressen der einzelnen Einträge (Strings) enthält, sowie den Index des bei Aufruf abgehakten Eintrags. Damit für den Haken genügend Platz vorhanden ist. sollten je nach Pull-Down-Menü zwei oder drei Leerzeichen vor jedem Eintrag stehen. Außerdem müssen alle Einträge die gleiche Länge haben. Zurückgegeben wird schließlich der Index des angeklickten Eintrages oder -1. falls kein Eintrag angewählt wurde.

Visuelles

Was macht nun eigentlich das CPX-Modul. dessen Listing(s) Sie im Anschluß an diesen Artikel finden? Betrachten wir uns dazu einmal Abbildung 2. die das im Kontrollfeld installierte Modul zeigt. Wie der dort zu sehende Name Disk-Utilities schon aussagt, ermöglicht das Modul in erster Linie die Beeinflussung einiger Parameter zum Disketten- und Festplattenzugriff.

Abbildung 3 zeigt die durch Doppelklick in das Auswahlfenster geöffnete Dialogbox. die nähere Einzelheiten offenbart. Der erste Eintrag zeigt den im Rechner verwendeten CPU-Typ an. des weiteren können für zwei Diskettenlaufwerke die Step-Raten und das Verify-Flag eingestellt werden. Außerdem ist es noch möglich, zwei Festplatten zu parken: die Harddisk-Adressen lassen direkt in den Buttons eingeben. Die erste Ziffer gibt die Controller-, die zweite die Harddisk-ID an. Die erste angeschlossene Festplatte hat normalerweise die Adresse 00, die zweite (je nach System) entweder 01 oder 10. Da der Atari TT von Haus aus mit SCSI-Autopark-Platte geliefert wird, haben wir uns dazu entschlossen, eine Parkroutine für ACSI-Platten zu benutzen, um die beim ST weitverbreiteten nicht-autopark-fähigen Festplatten parken zu können. Abbildung 4 zeigt die Abschaltmeldung nach dem gelungenen Parken der Harddisk. Zusätzlich (eigentlich hatten wir nur noch ein bißchen Platz übrig) kann man in diesem Modul dann bei Farbdarstellung noch die Bildschirmfrequenz umschalten. Leider ist das nur beim ST möglich, denn das entsprechende Register ist im TT (zumindest an der alten Stelle) nicht mehr vorhanden. und eigene Versuche mit der Adresse, die in [ 1 ] genannt wird, haben immer nur zum Totalabsturz des TT geführt. Die ganze Bedienung der Dialogbox erfolgt wie in allen anderen CPX-Modulen auch über in der Abbildung nicht sichtbare Pull-Down-Menüs, die bei Anklicken der entsprechenden Buttons herunterklappen.

Literarisches

Kommen wir jetzt zur Programmbeschreibung. Da alle Listings durchweg ausführlich kommentiert und deswegen mehr oder weniger selbsterklärend sind, wollen wir an dieser Stelle nur einige Schwerpunkte herausgreifen (vergl. Listing "DISK.C").

Als erstes fällt vielleicht auf, daß die zu sichernden Variablen, die bei Anklicken des Buttons Sichern unmittelbar in die CPX-Datei geschrieben werden, in einer Struktur zusammengefaßt sind und als erste Variablen im Programm stehen. Dies sollte zunächst einmal so hingenommen werden: im zweiten Teil gehen wir dann bei der Beschreibung der entsprechenden Kontrollfeldfunktion noch auf die Gründe dafür ein.

Die Funktion init() wird - wie oben bereits erwähnt - insgesamt zweimal aufgerufen. Der erste Aufruf erfolgt nach Laden des Headers (wobei auch die gesicherten Parameter eingelesen werden), um das System mit Hilfe dieser Parameter entsprechend zu konfigurieren. Der zweite Aufruf erfolgt nach Öffnen des Moduls mit Doppelklick. An dieser Stelle werden für gewöhnlich die Resource-Datei reloziert und die globalen Variablen initialisiert, es sei denn, daß CPX-Modul wurde beim Booten resident geladen. Außerdem wird die Adresse von main() an das Kontrollfeld zurückgegeben, indem ihr Eintrag in die Struktur CPX_INFO erfolgt. Diese Struktur muß als static bzw. global deklariert sein, damit das Kontrollfeld jederzeit darauf zugreifen kann. Die übrigen Felder dieser Struktur sollen uns erst in der dritten Episode dieser Serie interessieren, zunächst ist nur wichtig, daß unbenutzte Einträge auf NULL gesetzt werden.

In main() müssen dann die Koordinaten der Dialogbox an die des Kontrollfeldfensters angepaßt werden; dazu bekommt man vom Kontrollfeld einen Zeiger auf eine entsprechende GRECT- Struktur übergeben. Die Dialogbox kann dann anschließend ganz normal gezeichnet und verwaltet werden. Einzige Ausnahme: Man sollte die vom Kontrollfeld zur Verfügung gestellte Funktion do_form() benutzen, um andere Prozesse nicht zu behindern. Das Listing zeigt, daß die Auswertung angeklickter Objekte ansonsten völlig normal erfolgt.

Weiterhin fällt auf, daß man sich um das Redraw der Dialogbox nicht selbst zu kümmern braucht; dies wird bereits komplett vom Kontrollfeld übernommen! Einzige Ausnahme (Ausnahmen bestätigen die Regel): Wenn man im Modul selbst Manipulationen an irgendwelchen Objekten vornimmt, beispielsweise das Zurücksetzen eines selektierten Buttons, hat man sich um das Redraw selber zu kümmern. Erwähnung finden sollte auch noch, daß bei jedem vom Kontrollfeld übernommenen Redraw das Modul anschließend zusätzlich noch eine Redraw-Message erhält, um eventuell außerhalb des Beobachtungsbereichs des Kontrollfeldes liegende Änderungen in der Dialogbox vorzunehmen. Im Normalfall ist dies jedoch nicht erforderlich!

Beachten sollte man auch, daß die Messages WM_REDRAW und AC_CLOSE ausgewertet werden müssen, damit sowohl auf das Schließen des Kontrollfeldfensters als auch auf das Beenden eines Hauptprogramms reagiert werden kann.

Wird das CPX-Modul verlassen (was beim Beenden von main() der Fall ist) und die Kontrolle wieder dem variablen Kontrollfeld zurückgegeben, so muß je nach Art der Dialogbehandlung ein entsprechender Wert zurückgegeben werden. Im Normalfall, d.h. bei der Dialogbehandlung mittels do_form(), handelt es sich dabei um den Wert FALSE. Auf den Ausnahmefall, nämlich die Behandlung über einen eigenen Event-Handler, werden wir später noch einmal zurückkommen.

Die Funktion into_resource() macht nichts anderes als die gerade aktuellen Parameter in die Dialogbox einzutragen. Auffällig ist hier nur die Verwendung der Kontrollfeldfunktion find_cookie(), die auch schon in init() benutzt wurde und es ermöglicht, den Cookie-Jar zu durchsuchen. Ist der gesuchte Cookie nicht vorhanden, erhält man eine Null zurück. Weitere Informationen zum Cookie-Jar-Prinzip können Sie [2] entnehmen.

redraw_object() übernimmt das Neuzeichnen eines beliebigen Objekts mit Hilfe der vom Kontrollfeld gelieferten Rechteckliste. Da man keinen Zugriff auf das Window-Handle des Kontrollfensters hat (was nur vernünftig ist), werden die Funktionen rci_first() und rci_next() vom Kontrollfeld zur Verfügung gestellt. Beide Funktionen übernehmen dabei direkt auch die Arbeit des leidigen rc_intersect() mit und liefern einen entsprechenden Clipping-Bereich fertig zurück. An dieser Stelle sollte man vielleicht auch erwähnen (sofern es bisher noch nicht aufgefallen ist), daß alle normalerweise lästigen „Kleinigkeiten“ wie das Ein- und Ausschalten der Maus, das Öffnen einer virtuellen Workstation, etc. ebenfalls vom Kontrollfeld übernommen werden und man sich somit mehr den eigentlichen Problemen der Programmierung zuwenden kann. Nichtsdestotrotz enthält die CPX_PARAMS-Struktur das VDI-Handle, so daß auch VDI-Aufrufen nichts im Wege steht.

Die Routine pulldown() generiert für jeden angeklickten Button ein entsprechendes Pull-Down-Menü, indem die Texte für die Menüeinträge generiert und deren Adressen in einem Übergabe-Array eingetragen werden. Außerdem muß man den Index des abgehakten Eintrags aus der aktuellen Einstellung ermitteln. Natürlich wäre es auch möglich, die Menüs in ihrer fertigen Form am Anfang des Programms statisch zu definieren, aber das ist wohl eine Geschmacks- und Platzfrage. Zusätzlich werden noch einige Koordinatenangaben ermittelt und alle Parameter dann an die Kontrollfeldfunktion do_pulldown() weiter delegiert. Nach Beendigung liefert die Funktion den Index des angeklickten Eintrags oder eine -1, falls kein Eintrag angeklickt wurde, zurück. Dem Modul bleibt es dann überlassen, entsprechend zu reagieren und beispielsweise den Button mit einem neuen Texteintrag zu versehen.

Für die Zentrierung kleinerer Dialogboxen, wie z.B. eigene Fehlermeldungen, kann die Funktion wind_center() benutzt werden, da man solche Funktionen wie form_alert() (mit automatischer Bildschirmzentrierung) oder auch form_center() nicht verwenden kann, ohne die zu zeichnende Dialogbox aus dem Kontrollfeldfenster zu beamen.

get_traddr() liefert die Adresse einer Dialogbox, da man aufgrund des fehlenden rsrc_load() die davon abhängige AES-Funktion rsrc_gaddr() natürlich nicht benutzen kann.

Alle Funktionen namens get_...() und set_...() lesen oder setzen die diversen Parameter und bedürfen wohl keinerlei weiterer Erklärung. Ein einziger wichtiger Hinweis sei an dieser Stelle gestattet: In der Funktion set_step(), die die Step-Rate des angeschlossenen Diskettenlaufwerkes setzt oder ermittelt, wird - sofern im Betriebssystem vorhanden (was ab dem Rainbow-TOS der Fall ist) - der XBIOS-Aufruf Floprate() verwendet. Existiert diese Funktion nicht, wird die zuständige Systemvariable direkt verändert. Um dabei ein Nachlaufen des Diskettenlaufwerks zu verhindern, erfolgt anschließend dann noch ein Getbpb()-Aufruf. Liegt zu diesem Zeitpunkt keine Diskette im Laufwerk, erfolgt eine Fehlermeldung des Betriebssystems, die allerdings mit „Abbruch“ bestätigt werden kann. Es handelt sich hierbei um ein völlig normales Verhalten und nicht etwa um einen Virus! Diese Abfrage erfolgt nicht nur beim expliziten Laden von DISK.CPX, sondern auch beim Booten des Systems (bzw. Laden der Accessories). Aber, wie schon erwähnt, tritt dies nur bei Betriebssystemen vor TOS 1.04 auf.

Abschließend noch ein Wort zur Funktion switch_off(). Sie testet, ob eine Harddisk geparkt werden soll, und gibt dann nötigenfalls noch eine Warnmeldung aus. Diese muß explizit bestätigt werden, weil man nach dem Parken der Festplatte den Rechner neu hochfahren muß, wenn er weiter benutzt werden soll. Sollen zwei Festplatten geparkt werden, und gelingt das Parken nur bei einer, befindet man sich im Prinzip in einem Deadlock, d.h. weder das Weiterarbeiten noch das Aufhören ist vernünftig realisierbar. Wir haben uns deshalb entschlossen, auch in diesem Fall das System abzuschalten. Die Parkroutine selbst ist dem Listing HD_PARK.S zu entnehmen und basiert auf einer Routine aus [3].

Die meisten im Listing DISK.C benutzten Kontrollfeldfunktionen sind noch einmal übersichtlich in alphabetischer Reihenfolge mit Parameterbeschreibung in Tabelle 1 aufgelistet; für den Rest müssen wir auf die nächste Folge verweisen. Zu beachten ist, daß es sich bei allen Funktionen tatsächlich um Zeiger auf Funktionen handelt und alle als cdecl deklariert sind: die in der Tabelle verwendete Schreibweise ist jedoch übersichtlicher.

Technisches

Frage: Wie erhalte ich nun aus den vielen abgedruckten Listings ein lauffähiges CPX-Modul? Antwort: Ganz einfach! Zunächst einmal ist natürlich alles abzutippen (fehlerfrei, versteht sich!). Anschließend wird mittels der Projekt-Datei DISK.PRJ aus den Dateien XCONTROL.H, DISK.H, DISK.RSH, HD_PARK.S sowie DISK.C ein (nicht ausführbares!!!) Programm namens DISK.PRG erzeugt. (Die dabei von Turbo-C ausgegebenen vier Warnings „Structure passed by value“ können Sie ignorieren.) Mittels DEFAULT.PRJ (wird mit Turbo-C mitgeliefert) ist daraufhin noch aus DBUILD.C und XCONTROL.H das Programm DBUILD.PRG zu erzeugen. Schließlich muß DBUILD.PRG noch gestartet werden, um aus DISK.PRG die Datei DISK.CPX zu erzeugen. Dazu müssen sich DBUILD.PRG und DISK.PRG im gleichen Verzeichnis befinden. Wird DISK.CPX fehlerfrei geBUILDet, steht dem Kopieren des erzeugten DISK.CPX ins CPX-Verzeichnis endlich nichts mehr im Wege. Wie gesagt: ganz einfach!

Soweit für dieses Mal, in der nächsten Folge gehen wir dann auf die hier noch nicht beschriebenen Funktionen ein und liefern dazu auch gleich wieder ein nützliches Beispielmodul, das die Programmierung der Schieber demonstriert. Bis dahin: „Live long and prosper!“

Uwe Hax & Oliver Scholz

Literaturverzeichnis:

[1] Martin A. Wielehinski: Stapellauf eines Flaggschiffs - Starthilfe für den TT, c't 1/91, S. 227 ff.

[2] Rolf Kotzian: STee-Gehäck - Das Cookie-Jar-Prinzip, ST-Computer 12/90, S. 151 ff.

[3] Claus Brod. Anton Stepper: Scheibenkleister. S. 325 ff., MAXON Computer GmbH

/**********************************************/
/* Datei: XCONTROL.H                          */
/* -----------------                          */
/* (C) 1990 by MAXON Computer                 */
/* Autoren: Uwe Hax & Oliver Scholz           */
/* Header-Datei für die Entwicklung eigener   */ 
/* CPX-Module                                 */
/**********************************************/

/* Header eines CPX-Module ------------*/

typedef struct
{
   WORD magic;
   struct
   {
      unsigned reserved: 13;
      unsigned resident: 1;
      unsigned boot_init: 1; 
      unsigned set_only: 1;
   } flags;
   char  cpx_id[ 4 ];
   WORD  cpx_version;
   char  icon_name[14];
   LONG  icon_data[24];
   WORD  icon_info;
   char  cpx_name[18];
   WORD  obj_state;
   BYTE  reserved[370];
} CPX_HEADER;

/* Übergabestruktur für Maus-Ereignisse -----*/

typedef struct
{
   WORD  flags;
   WORD  x,y,w,h;
} MOUSE_EVENT;

/* Ergebnisstruktur fur Maus-Ereignisse -----*/

typedef struct
{
   WORD  mx,my;
   WORD  mbutton;
   WORD  kbstate;
} MOUSE_RET;

/* Definition der Funktionen zur Ereignis-Behandlung -----*/

typedef struct
{
   WORD cdecl (*cpx_call)(GRECT *work);
   VOID cdecl (*cpx_draw)(GRECT *clip);
   VOID cdecl (*cpx_wmove)(GRECT *work);
   VOID cdecl (*cpx_timer)(WORD *event);
   VOID cdecl (*cpx_key)(WORD kbstate,
                         WORD key,
                         WORD *event);
   VOID cdecl (*cpx_button)(MOUSE_RET *mrets,
                         WORD nclicks,
                         WORD *event);
   VOID cdecl (*cpx_m1)(MOUSE_RET *mrets,
                         WORD *event);
   VOID cdecl (*cpx_m2)(MOUSE_RET »mrets,
                         WORD *event);
   WORD cdecl (*cpx_evhook)(WORD event,
                         WORD *msgbuff,
                         MOUSE_RET *mrets,
                         WORD *key,
                         WORD *nclicks);
   VOID cdecl (*cpx_close)(WORD app_term);
} CFX_INFO;

/* interne Struktur zur Verwaltung residenter CPX-Module ---------*/

typedef struct
{
   VOID *text_start;
   LONG text_len;
   VOID *data_start;
   LONG data_len;
   VOID *bss_start;
   LONG bss_len;
} CPX_SEGMENTS;

/* interne Struktur zum Speichern der Header -*/

typedef struct cpxblock
{
   char     filename[14];
   WORD     ok;
   WORD     valid;
   CPX_SEGMENTS *segments;
   struct cpxblock *next;
   CPX_HEADER header;
} CPX_BLOCK;

/* vom Kontrollfeld zur Verfügung gestellte Funktionen ---------*/

typedef struct
{
   WORD     vdi_handle;
   WORD     booting;
   WORD     reserved;
   WORD     rsc_init;
   CPX_BLOCK * cdecl (*get_rootblock)(VOID);
   WORD cdecl (*write_header)(CPX_BLOCK *header);
   VOID cdecl (*do_resource)(WORD num_obs,
                             WORD num_frstr,
                             WORD num_frimg,
                             WORD num_tree,
                             OBJECT *rs_object, 
                             TEDINFO *rs_tedinfo, 
                             BYTE *rs_strings[], 
                             ICONBLK *rs_iconblk, 
                             BITBLK *rs_bitblk, 
                             LONG *rs_frstr,
                             LONG *rs_frimg,
                             LONG *rs_trindex, 
                             struct foobar *rs_imdope);
   VOID cdecl (*objc_adjust)(OBJECT *tree,
                             WORD ob_index);
   WORD cdecl (*do_pulldown)(char *entries[],
                             WORD num_items,
                             WORD checked_item, 
                             WORD font,
                             GRECT *button_xywh, 
                             GRECT *window_xywh);
   VOID cdecl (*size_slider)(OBJECT *tree,
                             WORD box_index,
                             WORD slider_index, 
                             WORD total,
                             WORD seen,
                             WORD v_h_flag,
                             WORD min_size);
   VOID cdecl (*pos_hslider)(OBJECT *tree,
                             WORD box_index,
                             WORD slider_index, 
                             WORD slider_pos,
                             WORD start,
                             WORD total,
                             VOID (*function)());
   VOID cdecl (*pos_vslider)(OBJECT *tree,
                             WORD box_index,
                             WORD slider_index, 
                             WORD slider_pos,
                             WORD start,
                             WORD total,
                             VOID (*function)());
   VOID cdecl (*inc_slider)(OBJECT *tree,
                             WORD box_index,
                             WORD slider_index, 
                             WORD button_index, 
                             WORD increment,
                             WORD start,
                             WORD total,
                             WORD *slider_pos,
                             WORD v_h_flag,
                             VOID (*function)());
   VOID cdecl (*move_hslider)(OBJECT *tree,
                             WORD box_index,
                             WORD slider_index, 
                             WORD start,
                             WORD total,
                             WORD *slider_pos,
                             VOID (*function)());
   VOID cdecl (*move_vslider)(OBJECT *tree,
                             WORD box_index,
                             WORD slider_index, 
                             WORD start,
                             WORD total,
                             WORD *slider_pos,
                             VOID (*function)());
   WORD cdecl (*do_form)(OBJECT *tree,
                             WORD start_obj,
                             WORD *msg_buffer);
   GRECT * cdecl (*rci_first)(GRECT *object_xywh);
   GRECT * cdecl (*rci_next)(VOID);
   VOID cdecl (*multi)(WORD ev_flags,
                             MOUSE_EVENT *mm1, 
                             MOUSE_EVENT *mm2,
                             LONG timer);
   WORD cdecl (*alert)(WORD number);
   WORD cdecl (*write_config)(VOID *parameter,
                             LONG length);
   BYTE * cdecl (*get_resarea)(VOID);
   WORD cdecl (*find_cookie)(LONG cookie,
                             LONG *version);
   WORD dummy;
   VOID cdecl (*copy_bltparm)(WORD dir,
                             VOID *buffer);
} CPX_PARAMS;
/**********************************************/
/* Datei: DBUILD.C                            */
/* ---------------------                      */
/* Modul: DISK.CPX               Version 1.00 */
/* (C) 1990 by MAXON Computer                 */
/* Autoren: Uwe Hax & Oliver Scholz           */
/* verwendeter Compiler: Turbo-C 2.0          */
/**********************************************/


/* die üblichen Header-Dateien -------- */

#include <portab.h>
#include <tos.h>
#include <atring.h>
#include <stdlib.h>
#include <aes.h>

struct foobar     /* ist normalerweise in    */
{                 /* "*.rsh" definiert und   */
   WORD dummy;    /* wird in "xcontrol.h"    */
   WORD *image;   /* benötigt                */
};

#include "xcontrol.h"

/* Definitionen zur besseren Lesbarkeit ----- */

#define SOURCE      "DISK.PRG"
#define DESTINATION "DISK.CPX"

#define TRUE         1
#define FALSE        0

/* globale Variablen -------------- */

/* Header-Definiton */
CPX_HEADER header;

/* Image-Daten */
LONG data[24]={   0x00000000L, 0x03ffff80L, 
                  0x05100440L, 0x05103420L, 
                  0x05103420L, 0x05103420L, 
                  0x05103420L, 0x05103420L, 
                  0x05100420L, 0x05fffc20L, 
                  0x04000020L, 0x05ffffa0L, 
                  0x0S0000a0L, 0x0S0000a0L, 
                  0x051ff8a0L, 0x050420a0L, 
                  0x050420a0L, 0x050420a0L, 
                  0x050420a0L, 0x050420a0L, 
                  0x070000a0L, 0x0S0000a0L, 
                  0x03ffffc0L, 0x00000000L
             }:

/* Prototypen für Turbo-C ----------- */

VOID main(VOID);
VOID abort_main(VOID *buffer,WORD fd);

VOID main(VOID)
{
   VOID *buffer;
   DTA *dta=Fgetdta();
   WORD fd;
   WORD i;

   /* Kennung für *.CPX-Datei */ 
   header.magic=100;

   /* Bitvektor: Flags für Lade-Modus */ 
   header.flags.boot_init=TRUE; 
   header.flags.set_only=FALSE; 
   header.flags.resident=FALSE;

   /* Kurzkennung */
   strncpy(header.cpx_id,"DISK",4);

   /* Versionsnummer */
   header.cpx_version=0x100; /* Version 1.00 */

   /* Icon-Name */
   strcpy(header.icon_name,"UTILITIES");

   /* Image-Daten */ 
   for (i=0; i<24; i++)
      header.icon_data[i]=data[i];

   /* Icon: Farbe 4, kein Buchstabe */ 
   header.icon_info=0x4000;

   /* Programmname */
   strcpy (header.cpx_name,"Disk-Utilities");

   /* Farben */
   header.obj_state=0x1280;

   /* Header und Programm zusammenbauen */ 
   if (Fsfirst(SOURCE,0)<0)
      abort_main (0L, -1);

   if ((buffer=Malloc(dta->d_length))<0) 
      abort_main(0L,-1);

   if ((fd=Fopen(SOURCE,0))<0) 
      abort_main(buffer,-1);

   if (Fread(fd,dta->d_length,buffer)<0) 
      abort_main(buffer,fd);
   Fclose(fd);

   if ((fd=Fcreate(DESTINATION,0))<0) 
      abort_main(buffer, -1);

   if (Fwrite(fd,512L,(header)!=512L) 
      abort_main(buffer,fd);

   if (Fwrite(fd,dta->d_length,buffer)!=dta->d_length)
      abort_main(buffer,fd);

   Mfree(buffer);
   Fclose(fd);
   exit(0);
}

VOID abort_main(VOID *buffer,WORD fd)
{
   if (buffer)
      Mfree(buffer);
   if (fd>=0)
      Fclose(fd);
   form_alert(1,”[3][CPX-Datei konnte nicht|erzeugt werden!][ Abbruch ]");
   exit (1);
}
;*******************************************
;* Datei: DISK.PRJ                         *
;* -----------------------                 *
;* Modul: DISK.CPX           Version 1.00  *
;* (C) 1990 by MAXON Computer              *
;* Autoren: Uwe Hax & Oliver Scholz        *
;* Projektdatei für Turbo-C 2.0            *
;*******************************************

disk.prg
=
disk.c 
hd_park.s 
tcstdlib.lib 
tcgemlib.lib 
tctoslib.lib

/**********************************************/
/* Datei: DISK.RSH                            */
/* ----------------------                     */
/* Modul: DISK.CPX               Version 1.00 */
/* (C) 1990 by MAXON Computer                 */
/* Autoren: Uwe Hax & Oliver Scholz           */
/* Vom RCS aus Resource-Datei erstellte       */
/* Include-Datei                              */
/**********************************************/

#define NUM_FRSTR 0
#define NUM_FRIMG 0
#define NUM_OBS   40
#define NUM_TREE  4

BYTE *rs_strings[] =
{
   "(C) 1990 by Uwe Hax & Oliver Scholz",
   "","","CPU ist 68000”,"","","Steprate:","3 ms",
   "3 ms","Drive A","","","Drive B","","",
   "Verify","Ein","ACSI-Disk parken:","00",
   "Unit __","99”,"01","Unit __","99”,"Frequenz:",
   "50 Hz","Ok”,"Abbruch","Sichern",
   "Die Harddisk ist geparkt.",
   "Bitte schalten Sie jetzt",
   "Ihren Rechner aus!","Sind Sie sicher?”,
   "Alle laufenden Programme",
   "werden beim Parken der",
   "Harddisk abgebrochen•","Ok","Abbruch",
   "Parken fehlgeschlagen!","Mist!"
};

WORD IMAG0[] =
{
   0x001F, 0xFFFF, 0xFFF8, 0x0000, 0x0000, 0x0020,
   0x0000, 0x0004, 0x0000, 0x0000, 0x0040, 0x0000,
   0x0002, 0x0000, 0x0000, 0x0047, 0xFFFF, 0xFFE2,
   0x0000, 0x0000, 0x004F, 0xFFFF, 0xFFF2, 0x0000,
   0x0000, 0x004F, 0x8199, 0x81F2, 0x0000, 0x0000,
   0x004F, 0x8199, 0x81F2, 0x0000, 0x0000, 0x004F,
   0xE799, 0x9FF2, 0x0000, 0x0000, 0x004F, 0xE781,
   0x87F2, 0x0000, 0x0000, 0x004F, 0xE781, 0x87F2,
   0x0000, 0x0000, 0x004F, 0xE799, 0x9FF2, 0x0000,
   0x0000, 0x004F, 0xE799, 0x81F2, 0x0000, 0x0000,
   0x004F, 0xE799, 0x81F2, 0x0000, 0x0000, 0x004F,
   0xFFFF, 0xFFF2, 0x0000, 0x0000, 0x004F, 0xFFFF,
   0xFFF2, 0x0000, 0x0000, 0x004F, 0x8199, 0x83F2,
   0x0000, 0x0000, 0x004F, 0x8189, 0x81F2, 0x0000,
   0x0000, 0x004F, 0x9F81, 0x99F2, 0x0000, 0x0000,
   0x004F, 0x8781, 0x99F2, 0x0000, 0x0000, 0x004F,
   0x8791, 0x99F2, 0x0000, 0x0000, 0x004F, 0x9F99,
   0x99F2, 0x0000, 0x0000, 0x004F, 0x8199, 0x81F2,
   0x0000, 0x0000, 0x004F, 0x8199, 0x83F2, 0x0000,
   0x0000, 0x004F, 0xFFFF, 0xFFF2, 0x0000, 0x0000,
   0x0047, 0xFFFF, 0xFFE2, 0x0000, 0x0000, 0x0040,
   0x0000, 0x0002, 0x0000, 0x0000, 0x0020, 0x0000,
   0x0004, 0x0000, 0x0000, 0x001F, 0xFFFF, 0xFFF8,
   0x0000, 0x0000, 0x0000, 0x3FFF, 0xFC00, 0x0000,
   0x0000, 0x001F, 0xFFFF, 0xFFFB, 0xFFFC, 0x0000,
   0x0030, 0x0000, 0x000E, 0x0006, 0x0000, 0x0030,
   0x0000, 0x0F8E, 0x0006, 0x0000, 0x0030, 0x0000,
   0x3FEE, 0x0006, 0x0000, 0x0030, 0x0000, 0x0F8E,
   0x2006, 0x0000, 0x0032, 0xAA00, 0x000E, 0x4006,
   0x0000, 0x0030, 0x0000, 0x000E, 0x0006, 0x0000,
   0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x008A,
   0xAAAA, 0xAAA2, 0xA2A8, 0x8000, 0x0115, 0x5555,
   0x55D1, 0x5154, 0x4000, 0x020A, 0xFFFF, 0xFEA0,
   0x00FA, 0x2000, 0x0400, 0x0000, 0x0000, 0x0000,
   0x1000, 0x07FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800
};

WORD IMAG1[] =
{
   0x0000, 0x0000, 0x0000, 0x0003, 0x0000, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0xC000, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0xF000, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0x3C00, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0x0F00, 0x0000,
   0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x03C0, 0x0000,
   0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x30F0, 0x0000,
   0xC000, 0x0000, 0x0000, 0x0000, 0x3C3C, 0x0000,
   0xC000, 0x0000, 0x0000, 0x0000, 0x3F0F, 0x0000,
   0xCFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFC3, 0xC000,
   0xCFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0xF000,
   0xCFE1, 0xCCC0, 0xFCF3, 0x8733, 0xFFFC, 0x3C00,
   0xCFC0, 0xC4C0, 0xFCF3, 0x0333, 0xFFFF, 0x0F00,
   0xCFCC, 0xC0CF, 0xFCF3, 0x3333, 0xFFFF, 0xC3C0,
   0xCFCC, 0xC0C3, 0xFC93, 0x0303, 0xFFFF, 0xF0F0,
   0xCFCC, 0xC8C3, 0xFC03, 0x0387, 0xFFFF, 0xF0F0,
   0xCFCC, 0xCCCF, 0xFC03, 0x33CF, 0xFFFF, 0xC3C0,
   0xCFC0, 0xCCC0, 0xFC63, 0x33CF, 0xFFFF, 0x0F00,
   0xCFE1, 0xCCC0, 0xFCF3, 0x33CF, 0xFFFC, 0x3C00,
   0xCFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0xF000,
   0xCFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFC3, 0xC000,
   0xC000, 0x0000, 0x0000, 0x0000, 0x3F0F, 0x0000,
   0xC000, 0x0000, 0x0000, 0x0000, 0x3C3C, 0x0000,
   0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x30F0, 0x0000,
   0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x03C0, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0x0F00, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0x3C00, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0xF000, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0xC000, 0x0000,
   0x0000, 0x0000, 0x0000, 0x0003, 0x0000, 0x0000
};

LONG rs_frstr[] =
{
   0
};

BITBLK rs_bitblk[] =
{
   (WORD *)0L, 10, 42, 0, 0, 1,
   (WORD *)1L, 12, 30, 0, 0, 1
};

LONG rs_frimg[] =
{
   0
};

ICONBLK rs_iconblk[] =
{
   0
};

TEDINFO rs_tedinfo[] =
{
   (char *)0L, (char *)1L, (char *)2L, 5, 6, 0,
   0x1180, 0x0, 255, 36,1,
   (char *)3L, (char *)4L, (char *)5L, 3, 6, 2,
   0x1180, 0x0, 255, 14,1,
   (char *)9L, (char *)10L, (char *)11L, 5, 6, 0, 
   0x1180, 0x0, 255, 8,1,
   (char *)12L, (char *)13L, (char *)14L, 5, 6, 0,
   0x1180, 0x0, 255, 8,1,
   (char *)18L, (char *)19L, (char *)20L, 3, 6, 2,
   0x1180, 0x0, -1, 3,8,
   (char *)21L, (char *)22L, (char *)23L, 3, 6, 2,
   0x1180, 0x0, -1, 3,8
};

OBJECT rs_object[] = {
   -1, 1, 19, G_BOX, NONE, NORMAL, 0xFF1141L,
   0,0,32,11,
   2, -1, -1, G_TEXT, NONE, NORMAL, 0x0L,
   1282,0, 1306,2560,
   3, -1, -1 G_BOXTEXT, NONE, NORMAL, 0x1L, 
   1280,2816, 1310,513,
   9, 4, 8, G_BOX, NONE, NORMAL, 0xFF1100L,
   1280,2, 531,1539,
   5, -1, -1, G_STRING, NONE, NORMAL, 0x6L,
   517,768, 9,1,
   6, -1, -1, G_BUTTON, 0x41, SHADOWED, 0x7L,
   1,2049, 8,1,
   7, -1, -1, G_BUTTON, 0x41, SHADOWED, 0x8L,
   1801,2049, 8,1,
   8, -1, -1, G_TEXT, NONE, NORMAL, 0x2L,
   770,3074, 1029,2560,
   3, -1, -1, G_TEXT, NONE, NORMAL, 0x3L, 
   523,2818, 773,2304,
   12, 10, 11, G_BOX, NONE, NORMAL, 0xFF1100L,
   532,2, 11,1539,
   11, -1, -1, G_STRING, NONE, NORMAL, 0xFL,
   770,768, 7,1,
   9, -1, -1, G_BUTTON, 0x41, SHADOWED, 0x10L,
   1281,2049, 8,1,
   16, 13, 15, G_BOX, NONE, NORMAL, 0xFF1100L,
   1280,2309, 531,1283,
   14, -1, -1, G_STRING, NONE, NORMAL, 0x11L,
   257,1280, 1041,769,
   15, -1, -1, G_FBOXTEXT, 0x9, NORMAL, 0x4L,
   1792,3073, 8,513,
   12, -1, -1, G_FBOXTEXT, 0x9, NORMAL, 0x5L,
   1801,3073, 8,513,
   19, 17, 18, G_BOX, NONE, NORMAL, 0xFF1100L,
   532,2309, 11,1283,
   18, -1, -1, G_STRING, NONE, NORMAL, 0x18L,
   1,1280, 9,1,
   16, -1, -1, G_BUTTON, 0x41, SHADOWED, 0x19L,
   1025,3073, 8,1,
   0, 20, 22, G_BOX, NONE, NORMAL, 0xFF1100L, 
   0,777, 32,3329,
   21, -1, -1, G_BUTTON, 0x7, NORMAL, 0x1AL,
   1548,1792, 8,1,
   22, -1, -1, G_BUTTON, 0x5, NORMAL, 0x1BL,
   1046,1792, 8,1,
   19, 23, 23, G_BOX, NONE, NORMAL, 0xFF1100L, 
   0,0, 523,3329,
   22, -1, -1, G_BUTTON, 0x25, NORMAL, 0x1CL, 
   769,1536, 1032,513,
   -1, 1, 4, G_BOX, NONE, OUTLINED, 0x21100L, 
   0,0, 40,5,
   2, -1, -1, G_STRING, NONE, NORMAL, 0x1DL,
   12,1, 25,1,
   3, -1, -1 G_IMAGE, NONE, NORMAL, 0x0L, 
   513,1025, 10,2562,
   4, -1, -1, G_STRING, NONE, NORMAL, 0x1EL,
   12,2, 25,1,
   0, -1, -1, G_STRING, LASTOB, NORMAL, 0x1FL,
   15,3, 19,1,
   -1, 1, 7, G_BOX, NONE, OUTLINED, 0x21100L, 
   0,0, 30,10,
   2, -1, -1, G_IMAGE, NONE, NORMAL, 0x1L,
   9,2304, 12,3585,
   3, -1, -1, G_STRING, NONE, NORMAL, 0x20L,
   7,515, 16,1,
   4, -1, -1, G_STRING, NONE, NORMAL, 0x21L, 
   3,772, 536,1,
   5, -1, -1, G_STRING, NONE, NORMAL, 0x22L, 
   260,1029, 22,1,
   6, -1, -1, G_STRING, NONE, NORMAL, 0x23L, 
   772,1286, 277,257,
   7, -1, -1, G_BUTTON, 0x5, NORMAL, 0x24L,
   1797,776, 8,1,
   0, -1, -1, G_BUTTON, 0x27, NORMAL, 0x25L,
   16,776, 8,1,
   -1, 1, 2, G_BOX, NONE, OUTLINED, 0x21100L, 
   0,0, 28,5,
   2, -1, -1, G_STRING, NONE, NORMAL, 0x26L,
   3,1, 22,1,
   0, -1, -1, G_BUTTON, 0x27, NORMAL, 0x27L,
   10,3, 8,1
};

LONG rs_trindex[] =
{
   0L, 24L, 29L, 37L
};

struct foobar 
{
   WORD  dummy ;
   WORD  *image;
} rs_imdope[] = {
                  0, &IMAG0[0],
                  0, &IMAG1[0]
                };

/**********************************************/
/* Datei: DISK.H                              */
/* ----------------------                     */
/* Modul: DISK.CPX Version 1.00               */
/* (C) 1990 by MAXON Computer                 */
/* Autoren: Uwe Hax & Oliver Scholz           */
/* Vom RCS aus Resource-Datei erstellte       */
/* Include-Datei                              */
/**********************************************/

#define DISK 0       /* TREE */
#define CPU 2        /* OBJECT in TREE #0 */
#define STEPB 6      /* OBJECT in TREE #0 */
#define STEPA 5      /* OBJECT in TREE #0 */
#define VERIFY 11    /* OBJECT in TREE #0 */
#define UNIT0 14     /* OBJECT in TREE #0 */
#define UNIT1 15     /* OBJECT in TREE #0 */
#define FREQ 18      /* OBJECT in TREE #0 */
#define OK 20        /* OBJECT in TREE #0 */
#define CANCEL 21    /* OBJECT in TREE #0 */
#define SAVE 23      /* OBJECT in TREE #0 */
#define SWITCHOF 1   /* TREE */
#define SURE 2       /* TREE */
#define SUREOK 6     /* OBJECT in TREE #2 */
#define SURECANC 7   /* OBJECT in TREE #2 */
#define ERROR 3      /* TREE */
#define MIST 2       /* OBJECT in TREE #3 */
/**********************************************/
/* Datei: DISK.C                              */
/* ----------------------                     */
/* Modul: DISK.CPX               Version 1.00 */
/* (C) 1990 by MAXON Computer                 */
/* Autoren: Uwe Hax & Oliver Scholz           */
/* verwendeter Compiler: Turbo-C 2.0          */
/**********************************************/

/* die üblichen Header-Dateien --------- */

#include <portab.h>
#include <aes.h>
#include <tos.h>
#include <stdlib.h>
#include <string.h>
#include <vdi.h>

/* Definitionen zur besseren Lesbarkeit ---- */

#define PAL          1        /* 50 Hertz */
#define NTSC         0        /* 60 Hertz */

#define VERIFY_ON    1        /* Verify-Flag */
#define VERIFY_OFF   0

#define MS_2         2        /* Steprate */
#define MS_3         3
#define MS_6         0
#define MS_12        1

#define MESSAGE     -1        /* Message-Event */

#define TRUE         1        /* sonstige Def. */
#define FALSE        0
#define EOS          '\0'
#define OK_BUTTON    1

#define _nflops      0x4a6    /* Systemvariablen ‘/
#define sshiftmd     0x44c
#define hdv_init     0x46a
#define seekrate     0x440
#define _fverify     0x444
#define palmode      0x448
#define _sysbase     0x4f2
#define sync_mode    0xffff820aL

/* globale Variablen ------------- */

/* Deklaration der zu sichernden Variablen... */
typedef struct
{
   WORD step_a;      /* Steprate Laufwerk A */
   NORD step_b;      /* Steprate Laufwerk B */
   WORD verify;      /* Verify-Flag */
   WORD frequency;   /* Bildschirm-Frequenz */
   BYTE controller0; /* 1. Controller-ID */
   BYTE controller1; /* 2. Controller-ID */
   BYTE unit0;       /* 1. Harddisk-ID */
   BYTE unit1;       /* 2. Harddisk-ID */
} STATUS;

/* ...und Definition */
STATUS status*{ MS_3,MS_3,VERIFY_ON,PAL,
                0, 0, 0,1 } ;

/* Die zu sichernden Variablen müssen unbedingt 
   als erste definiert werden (=> erste Variable 
   im Datensegment)!
   (Achtling vor dubiosen Header-Dateien!) — */

/* Resource-Datei deshalb erst hier einladen */ 

#include "disk.rsh”
#include "disk.h" 
#include "xcontrol.h" /* darf erst nach "*.rsh" eingebunden werden */

/* sonstige globale Variablen -------- */

CPX_PARAMS *params; /* vom Kontrollfeld über-
                       gebener Zeiger auf die 
                       Kontrollfeld-Funktionen */

char ms2[]="2 ms"; /* Strings für Dialogbox */
char ms3[]="3 ms";
char ms6[]="6 ms";
char ms12[]="12 ms";
char hz50[]="50 Hz";
char hz60[]="60 Hz";
char ein[]="Ein";
char aus[]="Aus";
char empty[]="   ";

OBJECT *disk;     /* Zeiger auf Dialogboxen */
OBJECT *sure;
OBJECT *switchoff;
OBJECT *error;

/* Prototypen für Turbo-C ----------- */

VOID get_id(STATUS *work);
OBJECT *get_traddr(WORD tree_index);
VOID get_values(STATUS *work);
UWORD get_version(VOID);
CPX_INFO * cdecl init(CPX_PARAMS *params);
WORD hd_park(BYTE controller, BYTE unit);
VOID into_resource(STATUS *work);
WORD cdecl main(GRECT *curr_wind);
VOID pulldown(WORD button, STATUS *work);
VOID redraw_object(OBJECT *tree, WORD object); 
VOID set_id(STATUS *work);
WORD set_step(WORD drive, WORD step);
WORD set_verify(WORD verify);
WORD set_frequency(WORD frequency);
VOID set_yalues(STATUS status, STATUS work);
WORD switch_off(VOID);
VOID wind_center(OBJECT *tree, WORD *x, WORD *y, WORD *w, WORD *h);

/* Funktionen------------------*/

/**********************************************/
/* Initialisierung des Moduls:                */
/* Übergabeparameter: Zeiger auf die zur      */
/*    Verfügung stehenden Funktionen          */
/* 1. Aufruf bei Laden des Headers            */
/*    (par->booting = TRUE)                   */
/*    Rückgabe: 0 bei Set-Only, 1 sonst       */
/* 2. Aufruf bei Laden des eigentlichen       */
/*    Programms (par->booting = FALSE)        */
/*    Rückgabe: Adresse der CPX_INFO-Struktur */
/**********************************************/

CPX_INFO * cdecl init (CPX_PARAMS *par)
{
   char vdo[5]="_VDO";
   LONG version;
   static CPX_INFO info={ main,0L,0L,0L,0L,0L,
                          0L,0L,0L,0L );

   if (par->booting) /* bei Laden des Headers */
   {                 /* alle Parameter setzen */
      set_step(0,status.step_a); 
      set_step(1,status.step_b); 
      set_verify(status.verify);

      /* keine Frequenz auf dem TT setzen! */ 
      if (!(*par->find_cookie)(*(LONG *)vdo,&version)) 
         version=0L; 
      if (version<0x00020000L)
         set_frequency(status.frequency);

      return((CPX_INFO *)1L); /* weitermachen */
   }
   else  /* Aufruf bei Laden des Programms */
   {     /* => Löschen aller globalen Variablen! */
      params=par;    /* Zeiger retten! */

      /* Resource relozieren */ 
      if (!params->rsc_init)
      {
         (*(params->do_resource))(NUM_OBS,NUM_FRSTR, 
           NUM_FRIMG,NUM_TREE,rs_object,rs_tedinfo, 
           rs_strings,rs_iconblk,rs_bitblk,rs_frstr, 
           rs_frimg,rs_trindex,rs_imdope);

         /* globale Variablen initialisieren */ 
         disk=get_traddr(DISK); 
         sure=get_traddr(SURE); 
         switchoff=get_traddr(SWITCHOF); 
         error=get_traddr(ERROR);

         /* Harddisk-ID's in die Dialogbox eintragen */
         set_id(&status);
      }

      /* Adresse der CPX_INFO-Struktur zurück */ 
      return(&info);
   }
}

/***********************************************/
/* Aufruf nach Doppelclick auf das Icon im     */ 
/* Auswahlfenster: Zeichnen der Dialogbox,     */
/* Behandlung der Buttons                      */
/* Übergabeparameter: Koordinaten des Fenster- */
/*                    arbeitsbereichs          */
/* Rückgabe: FALSE, wenn der Dialog mittels    */ 
/*           do_form() abgearbeitet wird,      */
/*           TRUE, falls eigene Event-Routinen */
/*           benutzt werden sollen             */
/***********************************************/

WORD cdecl main(GRECT *curr_wind)
{
   STATUS work;
   WORD msg_buff[8];
   WORD button;
   WORD abort_flag=FALSE;

   /* aktuelle Systemparameter einiesen */ 
   get_values(&status); 
   work=status;

   /* Koordinaten der Dialogbox setzen */ 
   disk[ROOT].ob_x=curr_wind->g_x; 
   disk[ROOT].ob_y=curr_wind->g_y;

   /* Systemparameter in Dialogbox eintragen */ 
   into_resource(&work);

   /* und Dialogbox zeichnen */
   objc_draw(disk,ROOT,MAX_DEPTH,disk[ROOT].ob_x, 
             disk[ROOT].ob_y,disk[ROOT].ob_width, 
             disk[ROOT].ob_height);

   /* Dialogbox abarbeiten, bis ein Exit-Objekt 
      angeklickt wurde */

   do
   {
      /* neuer form_do()-Aufruf */ 
      button=(*params->do_form)(disk,UNIT0, msg_buff);

      /* Doppelklick ausmaskieren */ 
      if (button>=0)
         button &= 0x7fff;

      /* angeklicktes Objekt auswerten */ 
      switch (button)
      {
         case SAVE:
            /* Parameter in CPX-Datei speichern */ 
            get_id(&work);
            if ((*params->alert)(0)==OK_BUTTON)
               (*params->write_config)(&work, sizeof(STATUS)); 
            disk[SAVE].ob_state &= ~SELECTED; 
            redraw_object(disk,ROOT); 
            break;

         case OK:
            /* Harddisk parken? */ 
            if (!switch_off())
               disk[OK].ob_state &= ~SELECTED; 
            else {
               /* neue Parameter übernehmen */ 
               set_values(status,work);

               /* für "resident" notwendig */ 
               get_id(&work); 
               status=work; 
               abort_flag=TRUE;
            }
            break;

         case CANCEL:
            abort_flag=TRUE; 
            break;

         case VERIFY: 
         case FREQ: 
         case STEPA: 
         case STEPB:
            pulldown(button,&work); 
            break;

         case MESSAGE:
            switch (msg_buff[0])
            {
               case WM_REDRAW:
                  break;      /* nicht notwendig */
               
               case WM_CLOSED:
                  set_values(status,work);

                  /* für "resident" notwendig */
                  get_id(&work);
                  status=work;

               case AC_CLOSE: 
                  abort_flag=TRUE; 
                  break;
            }
            break;
      }
   }
   while (!abort_flag);
   disk[button].ob_state &= ~SELECTED;
   return (FALSE);
}

/**********************************************/
/* Parameter in die Dialogbox eintragen       */
/* Übergabeparameter: Zeiger auf Status       */
/* Rückgabe: keine                            */
/**********************************************/

VOID into_resource(STATUS *status)
{
   LONG ssp;
   WORD drives; 
   char cpu[5]="_CPU"; 
   char vdo[5]="_VDO";
   LONG version=0L;
   char *ms[4]={ ms2,ms3,ms6,ms12 };
   WORD MS[4]={ MS_2,MS_3,MS_6,MS_12 };
   WORD i;

   /* Stepraten eintragen */ 
   for (i=0; i<4; i++)
   {
      if (status->step_a=MS[i])
         disk[STEPA].ob_spec.free_string=ms[i]; 
      if (status->step_b=MS[i])
         disk[STEPB].ob_spec.free_string=ms[i];
   }

   /* Verify-Flag eintragen */ 
   disk[VERIFY].ob_spec.free_string=(status->verify==VERIFY_ON) ? ein : aus;

   /* Frequenz eintragen */ 
   disk[FREQ].ob_spec.free_string=(status->frequency=PAL) ? hz50 : hz60;

   /* alle Buttons initialisieren */ 
   disk[STEPA].ob_state |= DISABLED; 
   disk[STEPA].ob_flags &= ~TOUCHEXIT; 
   disk[STEPB].ob_state |= DISABLED; 
   disk[STEPB].ob_flags &= -TOUCHEXIT; 
   disk[VERIFY].ob_state &= ~DISABLED; 
   disk[VERIFY].ob_flags |* TOUCHEXIT;

   /* Anzahl der angeschlossenen Laufwerke ermitteln */ 
   ssp=Super((VOID *)0L); 
   drives=*(WORD *)_nflops;
   Super((VOID *)ssp);

   /* Buttons abhängig von der Anzahl der 
      Diskettenlaufwerke (de)aktivieren */ 
   switch (drives)
   {
      case 2:
         disk[STEPB].ob_state &= ~DISABLED; 
         disk[STEPB].ob_flags |= TOUCHEXIT;

      case 1:
         disk[STEPA].ob_state &= ~DISABLED; 
         disk[STEPA].ob_flags |= TOUCHEXIT; 
         break;

      case 0:
         disk[VERIFY].ob_state |= DISABLED; 
         disk[VERIFY].ob_flags &= ~TOUCHEXIT; 
         break;
   }

   /* Frequenz nur im Farbmodus und nicht auf 
      dem TT verfügbar */ 
   if (!(*params->find_cookie) (*(LONG *)vdo,&version)) 
      version=0L;

   ssp=Super((VOID *)0L); 
   if ((*(BYTE *)sshiftmd==2) || (version>=0x00020000L))
   {
      disk[FREQ].ob_state |= DISABLED; 
      disk[FREQ].ob_flags &= ~TOUCHEXIT;
   }
   else
   {
      disk[FREQ].ob_state &= ~DISABLED; 
      disk[FREQ].ob_flags |= TOUCHEXIT;
   }
   Super((VOID *)ssp);

   /* CPU-Typ im Cookie-Jar suchen */ 
   if (!(*params->find_cookie)(*(LONG *)cpu, &version)) 
      version=0L;

   disk[CPU].ob_spec.tedinfo->te_ptext[11]=(char)(version/10+'0');
}

/***********************************************/
/* Neuzeichnen eines Objekts mit Hilfe der vom */
/* Kontrollfeld gelieferten Rechteck-Liste.    */
/* Übergabeparameter: Zeiger auf Objektbaum,   */
/*                    Objekt-Index             */
/* Rückgabe: keine                             */
/***********************************************/

VOID redraw_object(OBJECT *tree, WORD object)
{
   GRECT *clip_ptr,clip,xywh;

   /* absolute Objekt-Koordinaten berechnen */ 
   objc_offset(tree,object,&xywh.g_x,&xywh.g_y); 
   xywh.g_w=tree[object].ob_width; 
   xywh.g_h=tree[object].ob_height;

   /* erstes Rechteck holen */ 
   clip_ptr=(*params->rci_first)(&xywh);

   /* solange noch Rechtecke da sind */ 
   while (clip_ptr)
   {
      /* clip_ptr: Zeiger auf lokale Variable!! */ 
      clip=*clip_ptr; /* deshalb kopieren */

      /* Objekt neu zeichnen */ 
      objc_draw(tree, object,MAX_DEPTH,clip.g_x,clip.g_y,clip.g_w,clip.g_h);

      /* nächstes Rechteck holen */ 
      clip_ptr=(*params->rci_next)();
   }
}

/***********************************************/ 
/* Pulldown-Menü generieren, darstellen und    */
/* auswerten.                                  */
/* Übergabeparameter: angeklickter Button, aus */
/*                    dem das Menü "heraus-    */
/*                    klappen" soll,           */
/*                    Zeiger auf aktuelle      */
/*                    Parameter                */
/* Rückgabe: keine                             */
/***********************************************/

VOID pulldown(WORD button, STATUS *work)
{
   WORD i;
   WORD num_items;
   WORD index,checked;
   WORD step;
   GRECT butt on_xywh,window_xywh; 
   char *pull_adr[4]; 
   char pull_buff[4][15];
   WORD ms[]={ MS_2,MS_3,MS_6,MS_12 };

   /* je nach Button entsprechendes Pull-Down-Menü generieren */ 
   switch (button)
   {
      case STEPA: 
      case STEPB:
         /* Texte eintragen; alle Einträge gleich lang machen */ 
         for (i=0; i<4; i++)
            strcpy(pull_buff[i],empty); 
         strcat(pull_buff[0],ms2); 
         strcat(pull_buff[1],ms3); 
         strcat(pull_buff[2],ms6); 
         strcat(pull_buff[3],ms12); 
         for (i=0; i<4; i++)
            strcat(pull_buff[i],empty); 
         pull_buff[3][10]=EOS;

         /* Anzahl der Einträge */ 
         num_items=4;

         /* Umrechnung von Steprate in Index */ 
         step=((button=STEPA) ? work->step_a : work->step_b);
         for (i=0; i<4; i++) 
            if (ms[i]==step) 
               break;

         /* Index abgehakter Eintrag */ 
         index=i; 
         break;

      case VERIFY:   /* wie oben */
         strcpy(pull_buff[0],empty); 
         strcat(pull_buff[0],ein); 
         strcat(pull_buff[0],empty);
         strcpy(pull_buff[1],empty); 
         strcat(pull_buff[1],aus); 
         strcat(pull_buff[1],empty); 
         pull_buff[0][8]=pull_buff[1][8]=EOS;

         num_items=2;
         index=((work->verify==VERIFY_ON) ? 0 : 1); 
         break;

      case FREQ:     /* wie oben */
         strcpy(pull_buff[0],empty); 
         strcat(pull_buff[0],hz50); 
         strcat(pull_buff[0],empty); 
         strcpy(pull_buff[1],empty); 
         strcat(pull_buff[1],hz60); 
         strcat(pull_buff[1],empty); 
         pull_buff[0][10]=pull_buff[1][10]=EOS;

         num_items=2;
         index=((work->frequency==PAL) ? 0 : 1); break;
   }

   /* absolute Button-Koordinaten berechnen */ 
   objc_offset(disk,button,&button_xywh.g_x,&button_xywh.g_y); 
   button_xywh.g_w=disk[button].ob_width; 
   button_xywh.g_h=disk[button].ob_height;

   /* absolute Koordinaten der Dialogbox ermitteln */ 
   objc_offset(disk,ROOT,&window_xywh.g_x,&window_xywh.g_y); 
   window_xywh.g_w=disk[ROOT].ob_width; 
   window_xywh.g_h=disk[ROOT].ob_height;

   /* Adressen der einzelnen Einträge in das Übergabe-Array eintragen */ 
   for(i=0;i<num_items;i++) 
      pull_adr[i]=pull_buff[i];

   /* Pull-Down-Menü zeichnen lassen und Index des 
      angeklickten Eintrags zurückliefern */ 
   checked=(*params->do_pulldown)(pull_adr,num_items,index,IBM, &button_xywh,&window_xywh);

   /* wenn Eintrag angeklickt wurde... */ 
   if (checked>*0)
   {
      /* ...dann entsprechend reagieren */ 
      switch (button)
      {
         case STEPA:
            work->step_a=ms[checked]; 
            if (get_version()<0x104)
            {
               work->step_b=ms[checked]; 
               into_resource(work); 
               redraw_object(disk,STEPB);
            }
            break;

         case STEPB:
            work->step_b=ms[checked]; 
            if (get_version()<0x104)
            {
               work->step_a=ms[checked]; 
               into_resource(work); 
               redraw_object(disk,STEPA);
            }
            break;

         case VERIFY:
            work->verify=((checked==0) ? VERIFY_ON : VERIFY_OFF);
            break;

         case FREQ:
            work->frequency=((checked=0) ? PAL : NTSC);
            break;
      }

      /* neue Werte in die Dialogbox eintragen */ 
      into_resource (work);
   }

   /* Button neu zeichnen */
   disk[button].ob_state &= ~SELECTED;
   redraw_object(disk,button);
}

/***********************************************/
/* Dialogbox im Fenster zentrieren             */
/* Übergabeparameter: Zeiger auf Dialogbox,    */
/*                    Koordinaten              */
/* Rückgabe: indirekt über Koordinaten         */
/***********************************************/

VOID wind_center(OBJECT *tree,WORD *x,WORD *y,WORD *w,WORD *h)
{
   tree[ROOT].ob_x=disk[ROOT].ob_x+(disk[ROOT].ob_width-tree[ROOT].ob_width)/2; 
   tree[ROOT].ob_y=disk[ROOT].ob_y+(disk[ROOT].ob_height-tree[ROOT].ob_height)/2;
   *x=tree[ROOT].ob_x;
   *y=tree[ROOT].ob_y;
   *w=tree[ROOT].ob_width;
   *h=tree[ROOT].ob_height;
}

/***********************************************/
/* Liefert Adresse einer Dialogbox             */
/* (neue rsrc_gaddr()-Routine)                 */
/* Übergabeparameter: Baum-Index               */
/* Rückgabe: Zeiger auf Dialogbox              */
/***********************************************/

OBJECT *get_traddr(WORD tree index)
{
   WORD i,j;

   for (i=0,j=0; i<=tree_index; i++) 
      while (rs_object[j++].ob_next!=-1);

   return(&rs_object[-j]);
}

/***********************************************/
/* Auslesen der Systemparameter */
/* Übergabeparameter: Zeiger auf Status */
/* Rückgabe: Systempar. indirekt über Status */
/***********************************************/

VOID get_values(STATUS *work)
{
   work->step_a=set_step(0,-1); 
   work->step_b=set_step(1,-1); 
   work->verify=set_verify(-1); 
   work->frequency=set_frequency(-1);
}

/***********************************************/ 
/* Harddisk-ID's aus der Dialogbox auslesen    */ 
/* Übergabeparameter: Zeiger auf Status        */
/* Rückgabe: Harddisk-IDs indirekt über Status */
/***********************************************/

VOID get_id(STATUS *work)
{
   work->controller0=disk[UNIT0].ob_spec.tedinfo->te_ptext[0]-'0'; 
   work->controller1=disk[UNIT1].ob_spec.tedinfo->te_ptext[0]-'0'; 
   work->unit0=disk[UNIT0].ob_spec.tedinfo->te_ptext[1]-'0'; 
   work->unit1=disk[UNIT1].ob_spec.tedinfo->te_ptext[1]-'0';
}

/***********************************************/
/* Ermitteln der Betriebssystem-Version        */
/* Übergabeparameter: keine                    */
/* Rückgabe: Tos-Version                       */
/***********************************************/

UWORD get_yersion(VOID)
{
   LONG ssp;
   SYSHDR **syshdr=(SYSHDR **)_sysbase;
   UWORD version;

   ssp=Super((VOID *)0L); 
   versions(*syshdr)->os_version;
   Super((VOID *)ssp); 
   return(version);
}

/***********************************************/ 
/* Neusetzen der veränderten Parameter         */
/* Übergabeparameter: Zeiger auf Status vor    */ 
/*                    und nach dem Dialog      */
/* Rückgabe: keine                             */
/***********************************************/

VOID set_values(STATUS status, STATUS work)
{
   if (status.step_a!=work.step_a) 
      set_step(0,work.step_a); 
   if (status.stepjb!=work.step_b) 
      set_step(1,work.step_b); 
   if (status.verify!=work.verify) 
      set_verify(work.verify); 
   if (status.frequency!=work.frequency) 
      set_frequency(work.frequency);
}

/***********************************************/
/* Harddisk-ID's in die Dialogbox eintragen    */
/* Übergabeparameter: Zeiger auf Status        */
/* Rückgabe: keine                             */
/***********************************************/

VOID set_id(STATUS *work)
{
   disk[UNIT0].ob_spec.tedinfo->te_ptext[0]=work->controller0+'0'; 
   disk[UNIT1].ob_spec.tedinfo->te_ptext[0]=work->controller1+'0'; 
   disk[UNIT0].ob_spec.tedinfo->te_ptext[1]=work->unit0+'0'; 
   disk[UNIT1].ob_spec.tedinfo->te_ptext[1]=work->unitl+'0’;
}

/***********************************************/
/* Setzen und Ermitteln der Steprate           */
/* Übergabeparameter: Laufwerksnummer,         */
/*                    Steprate oder -1         */
/* Rückgabe: (alte) Steprate                   */
/***********************************************/

WORD set step (WORD drive, WORD step)
{
   LONG ssp;
   WORD step_rate;
   VOID (**hdv)(VOID)=(VOID *)hdv_init;

   if (get_version()<0x104) 
      if (step>«0)
      {
         ssp=Super((VOID *)0L); 
         step_rate=*(WORD *)seekrate;
         *(WORD *)seekrate«step;
         (*hdv)();
         Super((VOID *)ssp);
         Getbpb(drive);    /* Nachlaufen verhindern */
      }
      else
      {
         ssp=Super((VOID *)0L); 
         step_rate=*(WORD *)seekrate;
         Super((VOID *)ssp);
      }
   else
      step_rate=Floprate(drive,step); 

   return(step_rate);
}

/***********************************************/
/* Setzen und Ermitteln des Verify-Flags       */
/* Übergabeparameter: neue Einstellung oder -1 */
/* Rückgabe: (altes) Verify-Flag               */
/***********************************************/

WORD set_verify(WORD verify)
{
   WORD old_verify;
   LONG ssp;

   ssp=Super((VOID *)0L); 
   old_verify=*(WORD * )_fverify ;

   if (verify>=0)
      *(WORD *)_fverify=verify;
   Super((VOID *)ssp);

   return(old_verify);
}

/***********************************************/
/* Setzen und Ermitteln der Bild-Frequenz      */
/* Übergabeparameter: neue Einstellung oder -1 */
/* Rückgabe: (alte) Frequenz                   */
/***********************************************/

WORD set_frequency(WORD frequency)
{
   WORD old_frequency;
   LONG ssp;

   ssp=Super((VOID *)0L); 
   old_frequency=*(WORD *)palmode;

   /* bei TT Setzen nicht möglich! */ 
   if (frequency>=0)
   {
      frequency &= 1;
      *(WORD *) palmode=frequency ;
      *(BYTE *) sync_mode=(BYTE)frequency<<1;
   }
   Super((VOID *)ssp); 

   return(old_frequency);
}

/***********************************************/
/* Parken der Festplatte und nach Parken       */ 
/* zum Abschalten auffordern.                  */
/* Obergabaparamater: keine                    */
/* Rückgabe: Erfolg/Mißerfolg                  */
/***********************************************/

WORD switch_off(VOID)
{
   WORD x,y,w,h;
   WORD button;
   WORD ret1=-1;
   WORD ret2=-1;
   STATUS work;

   /* Harddisk parken? */
   if ((disk[UNIT0].ob_state & SELECTED) || 
       (disk[UNIT1].ob_state & SELECTED))
   {
      /* ja, ID's auslesen */ 
      get_id(&work);

      /* Alertbox reichen */ 
      wind_center(sure,&x,&y,&w,&h); 
      form_dial(FMD_START,0,0,0,0,x-3,y-3,w+6,h+6); 
      objc_draw(sure,ROOT,MAX_DEPTH,x-3,y-3,w+6,h+6);
      button=form_do(sure,0); 
      form_dial(FMD_FINISH,0,0,0,0,x-3,y-3,w+6,h+6); 
      sure[button].ob_state &= ~SELECTED;

      /* angeklickten Button auswerten */ 
      switch (button)
      {
         case SUREOK:
            /* gewählte Harddisk(s) parken, sofern 
               ID's innerhalb des Bereiches */ 
            if (disk[UNIT0].ob_state & SELECTED) 
               if ((work.controller0>=0) && 
                   (work.controller0<=7) && 
                   (work.unit0>=0) &&
                   (work.unit0<=7)) 
                  ret1=hd_park(work.controller0,work.unit0);

            if (disk[UNIT1].ob_State & SELECTED) 
               if ((work.controller1>=0) && 
                   (work.controller1<=7) && 
                   (work.unit1>=0) && 
                   (work.unit1<=7)) 
                  ret2=hd_park(work.controller1,work.unit1);

            /* Parken bei mind, einer Harddisk gelungen? */ 
            if (!ret1 || !ret2)
            {
               /* Aufforderung zum Abschalten */ 
               form_center(switchoff,&x,&y,&w,&h); 
               form_dial(FMD_START,0,0,0,0,x,y,w,h); 
               objc_draw(switchoff,ROOT,MAX_DEPTH,x,y,w,h); 
               while (TRUE);
            }
            else
            {
               /* Fehlermeldung ausgeben */ 
               wind_center(error,4x,4y,4w,4h); 
               objc_draw(error,ROOT,MAX_DEPTH,x-3,y-3,w+6,h+6); 
               form_do(error,0);
               error[MIST].ob_state &= ~SELECTED;
            }
            break;

         case SURECANC:
            sure[SURECANC].ob_state &= ~SELECTED; 
            break;
      }
      disk[UNIT0].ob_state &= ~SELECTED; 
      disk[UNIT1].ob_state &= ~SELECTED; 
      return(FALSE);
   }
   else
      return (TRUE);
}
;************************************************ 
;* Datei: HD_PARK.S                             *
;* ------------------------                     *
;* Modul: DISK.CPX                 Version 1.00 *
;* (C) 1990 by MAXON Computer                   *
;* Autoren: Uwe Hax & Oliver Scholz             *
;* ------------------------                     *
;* Mehr oder weniger (eigentlich weniger) frei  * 
;* nach Claus Brod: "Scheibenkleister",         *
;* Seite 325 ff. (Das Buch ist fast noch besser * 
;* als HITCHHIKER u. DISCWORLD zusammen!)       *
;************************************************

;C-Deklaration: WORD hd_park(BYTE controller,
;                            BYTE unit);
;controller = 0-7 (in Register d0)
;unit = 0-7 (in Register d1)

GEMDOS         = 1
SUPER          = $20

flock          = $43e
hz_200         = $4ba

dmodus         = $ff8606
daccess        = $ff8604
gpip           = $fffa01

               EXPORT hd_park 
               TEXT

hd_park:       ;man weiß ja nie...
               movem.l d1-a6,-(sp)

               ;Parameter retten 
               clr.l    d2 
               clr.l    d3 
               move.b   d0,d2 
               move.b   d1,d3

               ;Rückgabewert: kein Fehler 
               clr      d5

               ;Supervisor-Modus einschalten 
               clr.l    -(sp)
               move     #SUPER,-(sp)
               trap     #GEMDOS 
               addq.l   #6,sp 
               move.l   d0,d6

               ;Floppy-VBL ausschalten 
               st       flock

               ;auf dem Bus kurz mal klingeln
               move     #$88,dmodus
               nop

               ;Controller-ID in Befehl einbauen 
               move.l   #$001b0088,d4 
               lsl.l    #5,d2
               swap     d2
               or.l     d2,d4

               ;erstes Kommandobyte übergeben
               move.l   d4,daccess
               nop

               ;auf Bestätigung warten 
               bsr      zeiteisen
               bmi      error

               ;Laufwerks-ID in Befehl einbauen
               move.l   #$0000008a,d4
               lsl.l    #5,d3
               swap     d3
               or.l     d3,d4

               ;alle weiteren Kommandobytes
               ;übergeben
               move.l   d4,daccess
               nop
               bsr      zeiteisen
               bmi      error

               move.l   #$0000008a,daccess 
               nop
               bsr      zeiteisen
               bmi      error

               move.l   #$0000008a,daccess 
               nop
               bsr      zeiteisen
               bmi      error

               move.l   #$0001008a,daccess 
               nop
               bsr      zeiteisen
               bmi      error

               ;letztes Kommandobyte übergeben 
               move.l   #$0000000a,daccess 
               nop
               bsr      wait_for_com
               bmi      error

               ;ACSI-Bus selektieren 
               move     #$8a,dmodus 
               nop

               ;ACSI-Status holen 
               move     daccess,d0

               ;nur Statusbits auswerten 
               andi     #$001f,d0

               ;kein Fehler
               beq      error

               ;Fehler: Rückgabewert -1 
               move     #-1,d5

error:         ;auf FDC umschalten
               move     #$80,dmodus 
               nop

               ;Floppy-VBL einschalten 
               clr      flock

               ;Supervisor-Modus ausschalten 
               move.l   d6,-(sp) 
               move     #SUPER,-(sp) 
               trap     #GEMDOS 
               addq.l   #6,sp

               ;Rückgabe: -1 = Fehler, 0 sonst 
               move     d5,d0 
               movem.l  (sp)+,d1-a6 
               rts


wait_for_com:  ;immer schön vorsichtig
               movem.l  d0/d1/a2,-(sp)

               ;Default: kein Fehler 
               clr d0

               ;800 Ticks warten 
               move.l   #800,d1 
               bra      get_timer


zeiteisen:     ;wie oben
               movem.l  d0/d1/a2,-(sp)

               ;Default: kein Fehler 
               clr      d0

               ;20 Ticks warten 
               moveq    #20,d1

get_timer:     ;200-Hz-Zähler addieren
               add.l    hz_200,d1

zeita:         ;auf HDC-IRQ testen
               btst     #5,gpip

               ;ist angekommen, dann fertig 
               beq      fix_und_fertig

               ;Timer-Zielwert erreicht? 
               cmp.l    hz_200,d1
               bne      zeita

               ;Fehlermeldung 
               moveq    #-1,d0
               move     d0,d5

fix_und_fertig: ;N-Flag aktualisieren 
               tst      d0

               movem.l  (sp)+,d0/d1/a2
               rts


Aus: ST-Computer 03 / 1991, Seite 95

Links

Copyright-Bestimmungen: siehe Über diese Seite