Text-Scrolling im GEM-Fenster

Das folgende Listing zeigt, wie man eine beliebige ASCII-Text-Datei lädt und diese in einem einfachen GEM-Fenster vertikal scrollt. Die routine lässt sich in beliebige Programme einbinden und bietet besonders bei Programminfos einen besonderen Reiz, da man hier ja mit Showeffekten glänzen darf.

Zuerst werden einige Initialisierungen durchgeführt: Die maximal zulässige Zeilenanzahl ist durch die Anzahl der Elemente des Stringfelds Zl$() begrenzt, hier wurde 1000 gewählt, was für die meisten ASCII-Texte ausreichen dürfte. Man könnte zur variablen Felddimensionierung natürlich auch die Häufigkeit des ASCII-Zeichens Nr. 13 (=Carriage return) feststellen, das zusammen mit dem ASCII-Zeichen Nr. 10(=Line feed) ein Zeilenende markiert. Dessen Anzahl entspräche dann der Zeilenanzahl der ASCII-Datei. Mit diesem Wert könnte dann das Stringfeld variabel dimensioniert werden. Das Feld wird zunächst mit 74 Leerzeichen pro Feldelement gefüllt, soviele Zeichen pro Zeile haben im Fenster Platz. In diese ‘Leerzeilen' werden die Zeilen der ASCII-Datei später linksbündig eingepaßt. Die folgenden Programmzeilen dienen der Fensterverwaltung, der Übersichtlichkeit wegen bekommen die benutzten Window-Messages Namen. Nach der Auswahl einer ASCII-Datei wird diese zeilenweise in das Feld Zl$() geladen. Für das Scrollen ist es wichtig, daß jede Zeile die max. Länge von 74 Zeichen hat, sonst wird die alte Zeile nicht komplett überschrieben und rechts bleiben Textreste stehen. Aus diesem Grund werden die Zeilen mit dem Befehl LSET linksbündig in das Stringfeld eingepaßt. Mit Wind_Create(...) werden das Aussehen und die max. Größe des Fensters festgelegt. Der Wert '211' = binär 11010011 ist eine Bitmaske. Durch das Setzen der einzelnen Bits werden die entsprechenden Bedienungselemente des Fensters eingeschaltet. Es sind maximal 12 Bits möglich:

Titelzeile             %1
Closer                %10
Fuller               %100
Mover               %1000
Infozeile          %10000
Sizer             %100000
Uparrow          %1000000
Dnarrow         %10000000
Vslide         %100000000
Lfarrow       %1000000000
Rtarrow      %10000000000
Hslide      %100000000000

Das hier benutzte Fenster besteht nur aus Titelzeile, Infozeile. Uparrow. Dnarrow und Closer. In den letzen Parameter schreibt GEM das sogenannte Window-Handle. Das ist die Kennziffer des Fensters, damit beim Arbeiten mit mehreren Fenstern jedes einzelne angesprochen werden kann. Das Desktop erhält das Handle 0. alle weiteren Fenster die Handles 1-7. Sollte kein Fenster mehr verfügbar sein (AES verwaltet bis zu 8 Fenster), wird der Wert-1 zurückgeliefert.

Die Prozedur Show_Window setzt Titel- und Infozeile (Wind_Set(...)). das Fenster wird mit Wind_Open(...) geöffnet und der Fensterinhalt gelöscht. Jetzt wird der Textanfang im Fenster ausgegeben und die Kontrolle an das AES übergeben. In der ‘REPEAT... UNTIL'-Schleife werden die Fensterelemente abgefragt. Wurde Uparrow oder Dnarrow angeklickt, wird zu den Scrollroutinen verzweigt, wurde der Closer angeklickt, wird das Fenster geschlossen. Jetzt kommt der Kern der Sache, die Scroll-Routinen. Im Fenster werden 20 Textzeilen dargestellt. Um nun z.B. nach oben zu scrollen, müssen die oberen 19 Zeilen nach unten geschoben und die neue Zeile 1 ausgegeben werden. Die Ausgabe der neuen Zeile 1 sollte mit einer einfachen Textausgabe wohl keine Probleme bereiten. Und das Verschieben der oberen 19 Zeilen erledigt man mit dem fixen BITBLT-Befehl. -Denkste ! - Der Text schlingert in wellenförmigen Bewegungen (“An der Nordseeküste...") derart lustlos über den Bildschirm, daß man nur mit Mühe die aufkommende Übelkeit unterdrücken kann. Erst wenn man den Blitter einschaltet (hat aber nicht jeder), kann man damit leben. Also begab ich mich auf die Suche nach einer schnelleren Möglichkeit, den Bildschirm durch die Gegend zu schieben und fand diese in Gestalt des (übrigens nicht im Handbuch dokumentierten) Befehls MEMORY_MOVE (B). Mit diesem Befehl kann man recht schnell Speicherbereiche kopieren; eigentlich müßte er MEMORY_COPY heißen, da der Speicherbereich ab der Quelladresse ja nicht gelöscht wird. Der Befehl MEMORY_MOVE <Quelle>, <Anzahl> TO <Ziel> verschiebt eine Anzahl Bytes von der Quell- zur Zieladresse. Da dieser Befehl intern wortweise arbeitet, müssen alle drei Werte gerade Zahlen sein. Falls dies nicht möglich ist. gibt's den Befehl MEMORY_MOVEB <Quelle>, <Anzahl> TO <Ziel>, der intern byteweise arbeitet, dadurch auch aber erheblich langsamer ist. Der Aufbau des Bildschirmspeichers wurde schon öfters in verschiedenen Artikeln und Büchern erklärt, deshalb setze ich ihn als bekannt voraus.

Da am linken Rand ein Stück des Hintergrundes und des Fensterrahmens zu sehen ist, die natürlich nicht verschoben werden sollen, fängt der Scrollbereich ab dem 2. Datenwort einer Pixelzeile an. Die Zeile ist 37 Wörter = 74 Bytes lang, und die rechte Grenze liegt beim 38. Wort. Dieser Bereich wird nun 16 Pixelzeilen (= 1280 Bytes) nach unten kopiert: nachdem man 16 Pi-xel-Zl. kopiert hat, ist 1 Textzeile verschoben worden. Doch da tritt ein ziemlich fieser Denkfehler auf; Die 1. Textzeile wird in die 2. Textzeile kopiert, die 2. Textzeile wird in die 3. - HALT! In der vorher 2. Textzeile steht ja jetzt die 1. Textzeile! Ergebnis des ersten Scrollversuchs: Auf dem Bildschirm steht 20mal die 1. Textzeile. Der Fehler ist umso gemeiner, da er beim Verschieben in die andere Richtung nicht auftritt. Um diesen Fehler zu verhindern, muß der Schleifenzähler gegen die Verschieberichtung laufen, d.h. beim Verschieben nach unten muß man in der letzten Zeile anfangen und sich nach ‘oben arbeiten', beim Verschieben nach oben läuft der Schleifenzähler wie gewohnt von oben nach unten. Mit MEMORY_MOVE ist das Scrolling etwas schneller als mit BITBLT, doch richtig ab geht's erst, wenn das Programm compiliert wird: das Scrolling erreicht dann etwa die 5fache Geschwindigkeit. Noch etwas schneller geht's in Assembler: ersetzt man die FOR...NEXT-Schleife in den Scrollroutinen durch den INLINE-Befehl, so kann man auch im Interpreter schnell scrollen. Damit der Compiler die INLINE-Strings verdaut, darf man nur die Register d1-d5 und a0-a2 benutzen. Außerdem darf am Ende der INLINE-Routine kein RTS stehen. wie es beim Aufruf mit CALL notwendig ist (scheinbar erledigt INLINE das selbst). REPEAT...UNTIL MOUSEBUT=0 sorgt dafür, daß bei festgehaltener linker Maustaste der Text immer weiter scrollt: da das Betriebssystem bei up_arrow und dn_arrow keine festgehaltene Maustaste bemerkt. Erst ab der TOS-Version 1.4 funktioniert das, dort kann die Schleife weggelassen werden. Nachdem der Text um eine Zeile gescrollt worden ist, wird die Variable Topline um 1 erhöht/erniedrigt, da ja jetzt eine andere Textzeile in der 1. Zeile steht. Die Nr. der neuen Zeile 1 wird mit TEXT... direkt in die Infozeile des Fensters geschrieben. Wer jetzt schreit, daß das keine saubere GEM-Programmierung ist. der kann ja ‘mal die Infozeile mit WIND_SET(...) aktualisieren (ei, wie das flackert und lange dauert...).

Noch ein paar Bemerkungen zur Ausgabe mit TEXT: Solange man die Ausgabe horizontal auf Bytegrenzen legt. d.h. die x-Ordinate ohne Rest durch 8 teilbar ist, und auf Textattribute (kursiv, fett, hell...) verzichtet, ist der Befehl recht schnell. Wer’s nicht glaubt, kann ja mal die x-Ordinate von 16 auf 17 setzen und ein beliebiges Attribut mit TEXT STYLE setzen (Kaffeemaschine einschalten wird empfohlen). Tja und dann muß an dieses Listing natürlich noch die GEMLIB angehängt werden. Es werden jedoch nur einige der GEM-Prozeduren benötigt. Da sich die GEM-Prozeduren teilweise gegenseitig aufrufen, ist das Aussortieren ziemlich nervtötend (eigentlich soll das Programm GEMSEL.BAS auf der Omikron.BASIC-Diskette das können, aber bei mir hat es bisher versagt). Nur die folgenden Prozeduren/Funktionen werden benötigt, wobei die Zahlen dahinter die Anzahl der Übergabeparameter angeben (einige Prozeduren mit gleichen Namen unterscheiden sich nur durch die Anzahl der Parameter; keine besonders elegante Lösung...).

So, das war’s zum Thema Text-Scrolling in Omikron. BASIC. Wer unbedingt mehr als 74 Zeichen pro Zeile verarbeiten möchte, kann ja eine Routine für horizontales Scrolling mit Lfarrow und Rtarrow programmieren.

**Benötigte Prozeduren:**

Appl_Init, Appl_Exit, Evnt_Mesag(l), Menu_Bar, Form_Dial(5), Form_Dial(9), Graf_Handle(5), Graf_ Mouse(l), Wind_Create(6), Wind_Open (5), Wind_Close(l), Wind_Delete(1), Wind_Get(6), Wind_Set(4), Wind_Update(1), Rsrc_Free. Vst_ Height(5), V_Clswk, V_Clsvwk, Vs_Ndc, Patch_Basic(4).

Benötigte Funktionen:

Wind_Check(2), Rsrc_Check(1), V_Opn_Check(1), V_Opnv_Check(1), Reserved(1).

'***********************************************
'*          Text-Scrolling im GEM-Fenster      *
'* Autor:     Andreas Hollmann, Paderborn      *
'*            (c) MAXON Computer GmbH          *
'* Sprache:   Omikron.BASIC                    *
'***********************************************

DEFINTL "A-Z"

Prg_Init ' einige Initialisierungen durchführen

F_Select(Load$,Button%L)' ASCII-Text auswählen 
IF Button%L=1 THEN ' 'OK' Button gewählt
    Load_Ascii(Load$,F_Len%L)'ASCII-Datei laden 
    Appl_Init' GEM-Anwendung anmelden
    MOUSEOFF
    Wind_Create(211,0,0,640,400,W1%L)' Fenster entwerfen
    F_Len$= SPACE$(22)+"Länge: "+ STR$(F_Len%L)+" Bytes"
    Wf_Name$=" "+Load$+" "' Inhalt Titelzeile
    Wf_Info$=" Zeile: 1"+F_Len$' Inhalt Infozeile
    Show_Window(W1%L,630,370,Wf_Name$,Wf_Info$) 
    Show_Text(Topline%L)' Textanfang ausgeben
    MOUSEON
    Watch_Mouse(W1%L)' jetzt kann gescrollt werden 
    Appl_Exit' GEM-Anwendung abmelden
ENDIF
PRINT ""' Cursor ausschalten
END ' Programm beenden
'========================================================
DEF PROC Prg_Init
    PRINT ""' Cursor ausschalten
    XBIOS (Scr%L,2)' Bildschirm-Adresse in Scr 
    DIM Zl$ (1000) ' Platz für 1000 Zeilen reservieren 
    FOR Offset%L=0 TO 999' Feld mit Leerzeichen füllen 
        Zl$(Offset%L)= SPACE$(74)
    NEXT Offset%L
    DIM Msg%L(10)' Platz für Message-Puffer reservieren 
    Name_Mem%L= MEMORY(128)' Platz für Titelzeile und 
    Info_Mem%L= Name_Mem%L+64' Infozeile reservieren
    Wm_Closed%L=22' Window-Messages benennen...
    Wm_Arrowed%L=24 
    Wa_Upline%L=2 
    Wa_Dnline%L=3 
    Wf_Name%L=2 
    Wf_Info%L=3 
    Wf_Workxywh%L=4 
RETURN
'--------------------------------------------------------
DEF PROC F_Select(R Path_File$,R Button%L)
    LOCAL Drive%L,Drive$,Path$,F_Name$,Cut%L 
    GEMDOS (Drive%L,25)' aktuelles Laufwerk ermitteln
    Path$= CHR$(65+Drive%L)+":"' Pfad = aktuelles Laufwerk
    MOUSEON
    FILESELECT (Path$,F_Name$,Button%L)
    MOUSEOFF
    Cut%L= INSTR( MIRROR$(Path$),".")+1' *.EXT abschneiden
    Path_File$= LEFT$(Path$,( LEN (Path$)-Cut%L))+F_Name$
RETURN
'--------------------------------------------------------
DEF PROC Load_Ascii(Load$,R F_Len%L)'ASCII-Datei laden
    LOCAL Buffer$
    CLS
    TEXT 240,192,"Datei wird geladen..."
    OPEN "i",1,Load$
    F_Len%L= LOF(1)
    Lines%L=1 
    WHILE NOT EOF(1)
        LINE INPUT #1,Buffer$' 1 Zeile in Puffer laden
        LSET Zl$(Lines%L)=Buffer$' auf 74 Zeichen bringen
        Lines%L=Lines%L+1' Zeilen-Anzahl um 1 erhöhen
    WEND 
    CLOSE 1 
    CLS 
RETURN
'--------------------------------------------------------
DEF PROC Watch_Mouse(W_Handle%L)' auf Maus-Klick warten
    LOCAL I%L,Message$,Msg%L
    REPEAT ' solange in der Schleife bleiben,... 
        Evnt_Mesag(Message$)
        FOR I%L=0 TO 7
            Msg%L(I%L)= CVI( MID$(Message$,I%L*2+1)) 
        NEXT I%L
        IF Msg%L(0)=Wm_Arrowed%L THEN ' Pfeil wurde angeklickt 
            IF Msg%L(4)=Wa_Upline%L THEN 'Pfeil nach oben 
                Scroll_Up(Topline%L)' 1 Zeile nach oben
            ENDIF
            IF Msg%L(4)=Wa_Dnline%L THEN 'Pfeil nach unten 
                Scroll_Dn(Topline%L) ' 1 Zeile nach unten
            ENDIF
        ENDIF
    UNTIL Msg%L(0)=Wm_Closed%L'...bis Closer angeklickt wird
    Wind_Close(W_Handle%L)
RETURN
'--------------------------------------------------------
DEF PROC Show_Text(R Topline%L) ' Text-Anfang ausgeben
    LOCAL I%L
    TEXT HEIGHT =13' normale Textgröße
    TEXT STYLE =0' sonst wird die Textausgabe zu langsam
    FOR I%L=0 TO 9
        TEXT 16,((13-I%L)*16+13),Zl$(10-I%L)
        TEXT 16,((14+I%L)*16+13),Zl$(11+I%L)
    NEXT I%L
    Topline%L=l' oberste Zeile ist Zeile 1
RETURN
'--------------------------------------------------------
DEF PROC Scroll_Up(R Topline%L)'
    LOCAL Adr%L,Scr_Dn%L
    MOUSEOFF ' sonst gibt's viele Mauspfeile
    REPEAT ' solange scrollen, ...
        IF Topline%L=1 THEN EXIT ' Text-Anfang erreicht
        'mit Basic-Befehlen:
        'FOR Adr=29442 TO 5122 STEP -80'nach unten schieben
        'MEMORY_MOVE Scr+Adr,74 TO Scr+Adr+1280
        'NEXT Adr
        'oder Assembler:
        INLINE "22790000044E43E9730245E90500303C012F4CD9013E48D2013E4CD9013E4 8EA013E00184CD9013E48EA013E00303559004843E9FF6645EAFFB051C8FFD6"
        Topline%L=Topline%L-1' oberste Zeile 1 Zeile höher 
        TEXT 16,77,Zl$(Topline%L)' neue oberste Zeile
        TEXT 64,57, STR$ (Topline%L)+"  " ' Zeile anzeigen
    UNTIL MOUSEBUT =0' ...bis Mausknopf losgelassen wird
    MOUSEON
RETURN
'--------------------------------------------------------

DEF PROC Scroll_Dn(R Topline%L) 
    LOCAL Adr%L 
    MOUSEOFF
    REPEAT ' solange scrollen, ...
        IF Topline%L=Lines%L-20 THEN EXIT ' Text-Ende erreicht 
        'mit Basic-Befehlen:
        'FOR Adr=6402 TO 30722 STEP 80' nach oben schieben
        'MEMORY_MOVE Scr+Adr,74 TO Scr+Adr-1280 
        'NEXT Adr 
        'oder Assembler
        INLINE ”22790000044E43E9190245E9FB00303C012F4CD9013E48D2013E4CD9013E48EA013 E00184CD9013E48EA013E00303559004843E9000645EA005051C8FFD6" 
        Topline%L=Topline%L+1' oberste Zeile 1 Zeile tiefer 
        TEXT 16,381,Zl$(Topline%L+20)' neue unterste Zeile
        TEXT 64,57,STR$(Topline%L)+" "' Zeile anzeigen
    UNTIL MOUSEBUT =0' ...bis Mausknopf losgelassen wird
    MOUSEON
RETURN
'--------------------------------------------------------
DEF PROC Show_Window(W_Handle%L,W%L,H%L,Name$, Info$)'
    LOCAL X%L,Y%L,W%L,H%L
    X%L=(640-W%L)/2    ' hor. zentrieren
    Y%L=((400-H%L)/2)+9' ver. zentrieren, +9 wegen Menüzeile 
    Wind_Set(W_Handle%L,Wf_Name%L,Name$,Name_Mem%L)'Titelzeile 
    Wind_Set(W_Handle%L,Wf_Info%L,Info$,Info_Mem%L)'Infozeile 
    Wind_Open(W_Handle%L,X%L,Y%L,W%L,H%L)'Fenster öffnen 
    Wind_Get(W_Handle%L,Wf_Workxywh%L,Wx%L,Wy%L,Ww%L,Wh%L)'Gr. holen
    MOUSEOFF
    FILL STYLE =0,1' Füllmuster = weiße Fläche 
    PBOX Wx%L-1,Wy%L-1,Ww%L+2,Wh%L+2'Fensterinhalt löschen
    MOUSEON
RETURN

'========================================================
' Hier müssen die Prozeduren der GEM-Library stehen !
'--------------------------------------------------------

Andreas Hollmann
Aus: ST-Computer 10 / 1989, Seite 90

Links

Copyright-Bestimmungen: siehe Über diese Seite