Bei dieser Routine handelt es sich um ein Assembler-Progrämmchen, das es erlaubt, auch aus Hochsprachen (BASIC, C, Pascal, Modula) ein absolut nachlauffreies Text-Scrolling zu realisieren. Um ein großes Maß an Flexibilität zu gewährleisten, teilt sich die Routine in drei verschiedene Teile ein.
Als erstes gibt es einen Programmteil, der einen ganzen Bildschirm (25 Zeilen) auf einmal ausgibt.
An dieser Stelle kommt natürlich kein Text-Scrolling zum Tragen, vielmehr macht sich hier die Geschwindigkeit der Routine beim Ausgeben von Strings bemerkbar. Ca. 0.1 Sekunde muß für einen ganzen Schirm einkalkuliert werden. Diese Zeit ist etwa um den Faktor 4.5 gegenüber der normalen Textausgabe über das Betriebssystem schneller. Des weiteren erledigen zwei andere Programmteile das Scrolling nach oben und unten. Auch hier sorgen die Routinen für einen richtigen Turbo-Effekt, und störendes Flimmern, wie etwa bei den Betriebssystem-Routinen, kann man lange suchen.
Nun aber zum Aufruf des Assembler-Bröckchens. Wie aus dem Beispiel-Programm zu ersehen, verwende ich die Routinen in Verbindung mit GFA-BASIC.
Deshalb werde ich auch meine Erklärungen in der Syntax dieser Sprache halten. Um jedoch auch den Freunden anderer Sprachen die Möglichkeit zum Verständnis zu geben, möchte ich zunächst den hier verwendeten Befehl “C:adr%(L:string%,W:mode%)” erläutern. Mit seiner Hilfe lassen sich von GFA-BASIC aus Maschinen-Routinen aufrufen. Dazu wird in der Variablen adr% die Adresse der entsprechenden Routine abgelegt. In string% findet sich beim Aufruf der beiden Scroll-Prozeduren die Adresse des auszugebenden Strings wieder. Dieser String sollte normalerweise die Länge von 80 Zeichen = einer Bildschirmzeile besitzen. Um jedoch auch kürzere Strings ausgeben zu können, läßt sich das Ende des Strings mit einem Null-Byte markieren. Der Rest des Bildschirms hinter dem Null-Byte wird dann gelöscht.
Der Parameter mode% (Wortlänge) bestimmt dabei die aufzurufende Funktion. Bei einer negativen Zahl (-1) wird der Bildschirm nach unten verschoben und der String in der obersten Zeile dargestellt.
Es wird also nach oben gescrollt. Genau die umgekehrte Richtung, nämlich nach unten, nimmt die Routine, wenn mode% auf einen positiven Wert gesetzt wird. Der Schirm wird dann nämlich nach oben verschoben und der String erscheint am unteren Rand Ihres Monitors.
Wenn Sie nun einen ganzen Bildschirm darstellen möchten, müssen Sie zuerst eine Tabelle erstellen, in der die Adressen aller auszugebender Strings aufgeführt sind. Wie man das in GFA-BASIC erledigt, kann man sich in Listing 1 ansehen.
An die Scroll-Routine übergeben Sie dann nicht mehr die Adressen der einzelnen Strings, sondern die Adresse dieser Tabelle. Für die Länge der Strings gelten die gleichen Bedingungen wie bei den eigentlichen Scroll-Routinen. Wie Sie bei dem Aufruf der Routine schon sehen können, ist der Parameter mode% in diesem Falle auf Null zu setzen.
Es ist nicht nötig, vor dem Aufruf den Mauszeiger oder den Cursor auszuschalten. Diese Aufgaben erledigt die Routine ohne Ihr Zutun.
Nach diesen Erläuterungen zur Benutzung nun aber weiter zur Arbeitsweise.
Als erstes werden der Maus- und der Textcursor ausgeschaltet, danach die Adressen des Font-Images und des logischen Bildschirmes ermittelt. Das Font-Image enthält die Daten, aus denen sich die einzelnen Zeichen zusammensetzen. Ein Zeichen belegt dort 16 Byte. Da es im Zeichensatz des ATARI 256 verschiedene Zeichen des 8*16 Systemfonts gibt, ist diese Tabelle folglich 4096 Bytes lang. Die einzelnen Bytes des Zeichens liegen dabei immer um 256 Bytes verschoben in diesem Image. Will man also z.B. ein “A” ausgeben (ASCII-Code 65), muß zur Adresse des Font-Images noch diese 65 addiert werden, und schon ergibt sich die Adresse der ersten Pixelzeile des Zeichens. Die weiteren Pixelzeilen folgen um je- weils 256 Bytes versetzt. Da direkt auf den Zeichensatz zugegriffen wird, ist es möglich, alle Zeichen aus dem Zeichensatz darzustellen. Somit lassen sich auch die Steuercodes kleiner als 32 in Form von Symbolen ausgeben.
Danach wird, in Abhängigkeit vom Parameter mode%, in die einzelnen Teile der Scroll-Routine verzweigt. Diese Programmteile rufen dann, eventuell nach dem Scrollen des Bildschirms, die Routine “AUSGABE” auf. Dort wird ein String auf den Bildschirm geschrieben. Schließlich werden dann der Maus- und Textcursor wieder sichtbar gemacht und die Routine verlassen.
Um das Prinzip besser zu verstehen und um diese Routinen in der Anwendung zu sehen, habe ich ein kleines Programm geschrieben, das ein Gerüst für einen Text-Editor darstellt (s. Listing 2).
Benchmarks
a$="" ! a$ löschen
FOR a%=anfang% to anfang%2 ! Zählschleife
a$=a$+MKL$(VARPTR(feld$(a%))) ! Tab.d.Adr.erstellen
NEXT a% ! nächst. Feldelement
tab%=VARPTR(a$) ! Adr.d.Tab.ermitteln
a%=C:adr%(L:tab%,0) ! Routine aufrufen
Listing 1: Ermitteln der Adressen der auszugebenden Strings
' © 1988 Martin Fangmeyer
' Wilmeresch 60
' 4430 Steinfurt 1
'
RESTORE scroll ! Datazeiger setzen
READ len% ! Länge der Routine lesen
scroll$=SPACE$(len%*4) ! Speicher für die Routine reservieren
scroll%=VARPTR(scroll$) ! Adr.d.Speichers holen
FOR a%=1 TO len%
READ wert%
LPOKE scroll%,wert% ! Routine i.d.Speicher (L)poken
ADD scroll%, 4 ! Nächstes Langwort
NEXT a%
'
DIM text$(1000) ! Stringfeld dimensionieren
FOR a%=0 TO 1000 ! Dieses Feld mit zufälligem Inhalt füllen
text$(a%)=LEFT$(STR$(a%)+" "+STRING$(78,RANDOM (223)+32),80)
NEXT a%
'
a$=SPACE$(100) ! Platz für 25*4 Byte (eine Bildschirmseite)
adr%=VARPTR(a$) ! Adresse des Strings ermitteln
FOR a%=0 TO 24 ! Die Adressen der Text-Strings festhalten
LPOKE adr%+a%*4,VARPTR(text$(a%))
NEXT a%
'
scroll%=VARPTR(scroll$) ! Die Adresse der Scroll-Routine holen
VOID C:scroll%(L:adr%,0) ! Die erste Textseite ausgeben
PRINT CHR$(27);CHR$(101); ! Cursor einschalten
REPEAT
a%=INP(2) ! Auf Tastendruck warten
IF a%=200 ! Hoch (scrollen)
DEC zeile%
IF zeile%<0 ! Eventuell bei 1000 neu anfangen
zeile%=1000
ENDIF
IF CRSLIN=1 ! Scrollen notwendig
scroll%=VARPTR(scroll$)
VOID C:scroll%(L:VARPTR(text$(zeile%)),-1)
! Scroll-Routine aufrufen
ELSE
PRINT CHR$(27);CHR$(65); ! Andernfalls Cursor eins höher
ENDIF
ELSE
IF a%=208 ! Runter (scrollen)
INC zeile%
IF zeile%>1000 ! Evtl, bei 0 neu anfangen
CLR zeile%
ENDIF
IF CRSLIN=25 ! Scrollen notwendig
scroll%=VARPTR(scroll$)
VOID C:scroll%(L:VARPTR(text$(zeile%)),1)
! Scrollroutine aufrufen
ELSE
PRINT CHR$(27);CHR$(66); !Sonst nur Cursor höher
ENDIF
ELSE
IF a%=20 ! Links
PRINT CHR$(27);CHR$(68); ! Cursor eine Pos nach links
ELSE
IF a%=205 ! Rechts
PRINT CHR$(27);CHR$(67); ! Cursor eine nach rechts
ELSE ! Taste ist kein Steuerzei-
OUT 2,a% ! Daher das Zeichen auf dem Schirm ausgeben
ENDIF
ENDIF
ENDIF
ENDIF
UNTIL a%=27 ! Solange, bis ESC gedrückt
'
scroll:
DATA 152
DATA 809238555,1627390504,809238630, 1627390496, -1610595334,37364392
DATA -1400822,1060896771,1313756303,1140458028, 578833007,551680
DATA 9725696,18620480,-772014080,83914815, 1289240318,1223180030
DATA -87012136,1056852200,1056897744,1289240318, 1223180030,-87012136
DATA 1056852200,1056897744,1289240318,1223180030, -87012136,1056852200
DATA 1056897744,1289240318,1223180030,-87012136, 1056852200,1056897744
DATA 1289240318,1223180030,-87012136,1056852200, 1056897744,1372127130
DATA 745472004,611975588,-704905216,2013275196,79, 1627390136,1610613116
DATA 541118972,30240,1883196632,1056852200, 1056834768,1289240318
DATA 1223180030,80760024,1056852200,1056834768, 1289240318,1223180030
DATA 80760024,1056852200,1056834768,1289240318, 1223180030,80760024
DATA 1056852200,1056834768,1289240318,1223180030, 80760024,1056852200
DATA 1056834768,1289240318,1223180030,80777724,960, 1372127124
DATA 745472004,611975440,607911936,5202218, 1610612976,574357504
DATA 1584128,544145412,608578648,607911936,5202190, -591659008
DATA 83907017,-1286144,13247098,14041728,270420480, 1734792128
DATA 345052521,16777296,359203328,10491241, 50331888,359203840
DATA 20977001,83886480,359204352,31462761, 117441072,359204864
DATA 41948521,150995664,359205376,52434281, 184550256,359205888
DATA 62920041,218104848,359206400,73405801, 251659440,1380602314
DATA -7188875,1108492842,5259818,10502698,15745578, 20988458,26231338
DATA 31474218,36717098,41959978,47202858,52445738, 57688618,62931498
DATA 68174378,73417258,78664330,1372258238, 1316306688,1060896770
DATA 1060896771,1313692815,1316302908,1794538, 809238629,1642373129
DATA 1316290560,0,0,0,0
Listing 2: Kleines Grundgerüst für einen Text-Editor
MOVE #27,D0
BSR BCONOUT
MOVE #102,D0 ; Cursor ausschalten
BSR BCONOUT
DC.W $A000 ; LINE-A installieren
LEA CHARS(PC),A1 ; Umständlich, aber PC-relativ
MOVE.L -$16(A0),(A1) ; Adresse des Zeichensatzes speichern
DC.W $A00A ; Maus ausschalten
MOVE #3,-(SP)
TRAP #14
ADDQ.L #2,SP
; In D0 findet man nun die Bildschirm-Adresse
LEA LOGBASE(PC),A1
MOVE.L D0,(A1) ; Bildschirmadresse speichern
TST 8 (SP) ; Flag:
BMI RAUF ; Kleiner als 0: Raufscrollen
BEQ SEITE ; Gleich 0: Ganze Seite darstellen
MOVE.L D0,A0 ; Ansonsten: Runterscrollen
ADD.L #1280,A0 ; 1. Zeile löschen
MOVEQ.L #63,D0 ; 64 Durchläufe => 24 Zeilen bewegen
SCROLL_DOWN:
MOVEM.L (a0)+,D1-D7/A1-A5 ; mit 2 Zeilen werden 48 Byte kopiert
MOVEM.L D1-D7/A1-A5,-1328(A0) ; 1
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 2
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 3
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 4
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 5
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 6
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 7
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 8
MOVEM.L (a0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 9
MOVEM.L (a0)+,D1-D7/Al-A5
MOVEM.L D1-D7/A1-A5,-1328(a0) ; 10
DBRA D0,SCROLL_DOWN ; insgesamt: 64*10*48=30720 Byte kopieren
MOVE.L 4(SP),A6 ; Adresse des Strings vom Stack holen
MOVE.L LOGBASE(PC),A2 ; Bildschirmadresse in A2
ADD.L #30720,A2 ; Nur unterste Zeile schreiben
MOVE.L #79,D2 ; 80 Zeichen
BSR AUSGABE ; Zeile ausgeben
BRA ZURÜCK ; Und zurück zum aufrufenden Programm
RAUF: ; Raufscrollen
MOVE.L D0,A0 ; Bildschirmadresse in A0
ADD.L #30240,A0 ; Unterste Zeile löschen
MOVEQ #63,D0 ; 64 Durchläufe (0 zählt mit)
SCROLL_UP:
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 1
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 2
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 3
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 4
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 5
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 6
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 7
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 8
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 9
MOVEM.L (A0)+,D1-D7/A1-A5
MOVEM.L D1-D7/A1-A5,1232(A0) ; 10
SUB.L #960,A0
DBRA D0,SCROLL_UP ; 64*10*48=30720 Byte kop.
MOVE.L 4(SP),A6 ; Adresse des Strings
MOVE.L LOGBASE(PC),A2 ; Bildschirmadresse
MOVE.L #79,D2 ; 80 Zeichen
BSR.S AUSGABE ; Oberste Zeile schreiben
BRA ZURÜCK ; Zurück zum aufrufenden Programm
SEITE: ; Eine ganze Seite schreiben
MOVE.L #24,D1 ; 25 Bildschirmzeilen
MOVE.L D0,D6 ; Bildschirmadr.in D6 speichern
MOVE.L 4(SP),A0 ; Adresse der Stringtabelle 25*4 Byte
NÄCHSTE_ZEILE:
MOVE.L D6,A2 ; Bildschirmadresse
MOVE.L (A0)+,A6 ; eine Zeile holen
MOVE.L #79,D2 ; 80 Buchstaben
BSR.S AUSGABE ; eine Zeile ausgeben
ADD.L #1280,D6 ; eine Cursorzeile tiefer
DBRA D1,NÄCHSTE_ZEILE ; nächste Zeile
BRA ZURÜCK ; Zurück zum aufrufenden Programm
AUSGABE:
MOVE.L CHARS(PC),A1 ; Adr.d.Zeichensatzdaten
CLR.L D0
MOVE.B (A6)+,D0 ; Ein Byte holen
TST.B D0 ; Byte testen
BEQ.S ZEILE_ZU_ENDE ; Zeile zu Ende (Byte =0)?
ADD.L D0,A1 ; + Offset
MOVE.B (A1),(A2) ; Zeichen auf den Bildschirm bringen
MOVE.B 256(A1),80(A2)
MOVE.B 512(A1),160(A2)
MOVE.B 768(A1),240(A2)
MOVE.B 1024(A1),320(A2)
MOVE.B 1280(A1),400(A2)
MOVE.B 1536(A1),480(A2)
MOVE.B 1792(A1),560(A2)
MOVE.B 2048(A1),640(A2)
MOVE.B 2304(A1),720(A2)
MOVE.B 2560(A1),800(A2)
MOVE.B 2816(A1),880(A2)
MOVE.B 3072(A1),960(A2)
MOVE.B 3328(A1),1040(A2)
MOVE.B 3584(A1),1120(A2)
MOVE.B 3840(A1),1200(A2) ; 16 MOVE-Befehle
NÄCHSTES_ZEICHEN:
ADDQ #1,A2 ; eine Zeichenposition weiter
DBRA D2,AUSGABE ; und nächstes Zeichen ausgeben
RTS
ZEILE_ZU_ENDE: ; Nach einem Null-Byte wird die
CLR.B (A2) ; Zeile bis zum rechten Rand gelöscht
CLR.B 80(A2)
CLR.B 160(A2)
CLR.B 240(A2)
CLR.B 320(A2)
CLR.B 400(A2)
CLR.B 480(A2)
CLR.B 560(A2)
CLR.B 640(A2)
CLR.B 720(A2)
CLR.B 800(A2)
CLR.B 880(A2)
CLR.B 960(A2)
CLR.B 1040(A2)
CLR.B 1120(A2)
CLR.B 1200(A2)
ADDQ.L #1,A2
DBRA D2,ZEILE ZU_ENDE ; Weiter bis zum rechten Rand
RTS
BCONOUT: ; ein Zeichen 'normal' ausgeben, z.B.
; um den Cursor ein- und auszuschalten
MOVE D0,-(SP) ; Zeichen auf den Stack
MOVE #2,-(SP) ; 'Zielgerät' Bildschirm (Console)
MOVE #3,-(SP) ; Funktionsnummer
TRAP #13 ; Bios aufrufen
ADDQ.L #6,SP ; Stack reparieren
RTS
ZURÜCK: ; Hier kehrt die Routine zu Basic zurück
MOVE #27,D0 ; Escape-Sequenz einläuten
BSR.S BCONOUT
MOVE #101,D0 ; Cursor einschalten
BSR.S BCONOUT
DC.W $A009 ; Maus wieder einschalten
RTS
CHARS: DS.L 1 ; Speicher für Adresse des Zeichensatzes
LOGBASE: DS.L 1 ; Speicher für Bildschirmadr.
END
Listing 3: Die Assembler-Routine