;Eingabezeile für ATmega auf (Hyper)Terminal
#ifndef INPUT_I90
#define INPUT_I90
;Benötigt:
; putchar(r16) Zeichen schreiben (blockierend)
; strlen(Z) Standard-Routine r17:r16=strlen(Z)
; UART_InChar Register >=R18
;In "vt100.h"
; SendByte(r16) Ausgabe 1 Byte dezimal
; SendSpace() Leerzeichen senden
; SendVisible(r16)`if (r16==0x7F || r16<' ') r16='_';´ und ausgeben
; GetKey() Tastenkode lesen mit VT100-Umrechnung der Kursortasten
; GotoXY(x,y) Gehe zur Position
; SendColor(r17) Farbe setzen
; SendM()
;Bietet (Eingabezeile):
; InputInitLen(Z,x,y,m,w) Eingabe-Kontext initialisieren
; InputInit(Z,x,y,m,w,b,l)Eingabe-Kontext feiner initialisieren
; InputRedraw(k) Eingabefeld neu zeichnen
; InputSetCursor(k) Kursor (nach Tastenbehandlung u.ä.) setzen
; InputHandleKey(k) Tastenbehandlung
; InputModal(Z,x,y,m,w) Beispiel-Eingabezeile mit Standardverhalten
; InputLoop() Seiteneinstieg in InputModal
;Falls INPUT_USE_CONTEXT definiert:
; k=InputLoadContext(Z) Kontext (=R9..R15) aus Speicher laden
; Z=InputSaveContext(k) Kontext (=R9..R15) in Speicher retten
;k=Kontext, Z=ZH:ZL, x,y=Koordinaten, m=StringMax, w=Eingabefeldbreite
.include "vt100.i90"
.include "strlen.i90"
#ifndef COLOR_INPUT_STD
# define COLOR_INPUT_STD 0x4F ;Weiß auf Blau
# define COLOR_INPUT_MARK 0x2F ;Weiß auf Grün
# define COLOR_INPUT_ARROW 0x4A ;Grün auf Blau
# define INPUT_CHAR_PASSWD '*' ;Passwort-Zeichen
#endif//COLOR_INPUT_STD
;##################
;## Eingabezeile ##
;##################
;Das linke und rechte Leerfeld der Eingabezeile ist für die Anzeige
;grüner Überlaufpfeile reserviert; das rechte außerdem zur Darstellung
;des Kursors.
;Farben sind festgelegt wie in Turbo Pascal (TP), können jedoch per
; #define umdefiniert werden.
;Register-Verwendung bei Eingaberoutinen
.def rLen = r0 ;Init=strlen(inputstring)
.def rBits = r1 ;Init=0x80
;7=Markierung (für gesamte Zeichenkette, flüchtig bei jeglicher Eingabe)
;6=Überschreibmodus: Immer 1 Zeichen markiert
;5=NoDel: Zeichen-Löschen nicht möglich, Backstep wie Pfeil links
;4=Unterstreichung: An Kursorposition (zusätzlich) Unterstrich ausgeben
;3=Uhrzeitmodus: Jedes dritte Zeichen überspringen (auch für Hexdumps gut)
;2=Passwort: Sternchen statt Buchstaben
;1=Nur Ziffern: übrige Zeichen abblocken (auch '-',',','.',' ')
;0=nicht nullterminiert: Zeichenkette ohne \0, Länge in R0 (Pascal-String)
.def rPos = r2 ;Init=0 (Turbo Pascal, Windows=strlen(inputstring))
.def rFirst = r3 ;Init=0 Erstes sichtbares Zeichen
.def rX = r4 ;Init linker Rand des Eingabefeldes (0..77)
.def rY = r5 ;Init Zeilennummer (0..23)
.def rMax = r6 ;Init maximale Stringlänge exkl. Terminierung
.def rWidth = r7 ;Init Breite des Fensters (1..78-rX)
;Außerdem ZH:ZL = Zeiger auf nullterminierte Zeichenkette
;Der Pufferspeicher muss rMax+1 Bytes haben (rMax Bytes falls rBits.0=1)
;Ein markierbarer Bereich (mit Anfang und Ende) wie in TP/Windows üblich
; wird nicht verwaltet, weil via Terminal die Bedienung umständlich wäre.
; Stattdessen kann beim Einstieg der gesamte Text markiert werden.
;"Natürlich" kümmert sich HandleKey() darum, bei Zeicheneingabe so wenig wie
; möglich auf dem Bildschirm zu aktualisieren, und kommt ohne Pufferspeicher
; aus. Damit dürften auch sehr kleine ATmegas bedient werden.
; Die Benutzung ist auch mit 1200 Baud noch akzeptabel.
;Die Unterstreichung (Bit4) sollte verwendet werden, wenn in einer
; ("nicht-modalen") Eingabezeile andere Ausgaben laufen und das
; Weghüpfen des Kursors (Carets) optisch irritiert.
;InputHandleKey() verlässt sich jedoch auf eine korrekt eingestellte
; Kursorposition, also vorher InputSetCursor() aufrufen.
;Damit die Zeichenzahl konstant bleibt, muss rLen=rMax sein und
;das NoDel-Flag (Bit6) gesetzt werden.
;Für die Eingabe einer Uhrzeit in der Form "TT.MM.JJ hh:mm:ss"
; folgende Initialisierungen:
; rLen=rMax=17, rBits=0b011?101?
; Bit4=1 für Unterstrich (s.o.), Bit0=1 wenn Nullterminiernung unnötig.
;Für die Eingabe einer Hexdump-Zeile muss natürlich Bit1=0 sein.
; Eine filigranere Zeichen-Abblockung kann gut vor InputHandleKey erfolgen
; (im Falle des Hexeditors also einfach alle Nicht-Hex-Zeichen).
InputOutChar:
ldi r16,' '
brts ed_spc
ld r16,Z+
sbrc rBits,2 ;Passwort?
ldi r16,INPUT_CHAR_PASSWD ;Zeichen durch '*' ersetzen
ed_spc: rjmp SendVisible ;Alle "unangenehmen" Zeichen ersetzen!
SendUnderlineOff:
rcall SendCSI ;Unterstreichung AUS
ldi r16,'2'
rcall putchar
rjmp Send4M
SendUnderlineOn: ;Unterstereichung EIN
rcall SendCSI
Send4M: ldi r16,'4'
rcall putchar
rjmp SendM
;Alle anderen Zeichenattribute sind über das Farbbyte abgedeckt
InputOutCharDecide:
brne InputOutChar
InputOutCharCaret:
;Einzelzeichen mit Kursor-Hervorhebung ausgeben
;T=0: *Z++, T=1: ' '
sbrc rBits,4
rcall SendUnderlineOn
sbrs rBits,7 ;Wenn alles markiert ist, kein Farbenwechsel
sbrs rBits,6 ;Nur im Falle von 01xx xxxx Farben wechseln
rjmp ed_eq
push r17
ldi r17,COLOR_INPUT_MARK
rcall SendColor
rcall InputOutChar
rcall SendStdColor
pop r17
rjmp ed_ne
ed_eq: rcall InputOutChar
ed_ne: sbrc rBits,4
rcall SendUnderlineOff
ret
InputRedraw:
;Eingabezeile komplett neu zeichnen (bspw. nach ^L)
;PE: R0..R7: Registersatz für Eingabezeile
; Z = inputstring
;VR: R16-R17
;Kursor positionieren (eigentlich auch: ausschalten!)
movw r17:r16,rY:rX
rcall GotoXY
;Linken Überlaufpfeil ausgeben
tst rFirst
breq ir1 ;keinen Linkspfeil hinsetzen
ldi r17,COLOR_INPUT_ARROW ;Grün auf Blau
rcall SendColor
ldi r16,'<'
rcall putchar
rjmp ir2
ir1:
rcall SendStdColor ;Standard: Weiß auf Blau
rcall SendSpace
ir2: ;Text ausgeben
ldi r17,COLOR_INPUT_STD ;Standard: Weiß auf Blau
sbrc rBits,7
ldi r17,COLOR_INPUT_MARK ;Markierung: Weiß auf Grün
rcall SendColor
mov r16,rFirst ;Ab da String ausgeben
rcall InputDrawFrom ;(gibt ggf. Caret hervorgehoben aus)
;Leerzeichen ausgeben (Zeilenrest löschen)
mov r17,rWidth
add r17,rFirst
sub r17,rLen
brlo SendRightArrow ;Überlaufpfeil!
inc r17
jbrc rBits,7, ir8 ;Keine Farbe (nochmal) setzen wenn unmarkiert
push r17
rcall SendStdColor ;Standard: Weiß auf Blau (Kursorfarbe!)
pop r17
ir8: cp rPos,rLen ;Kursor am Ende?
brlo ir6 ;nein
set ;"besonderes" Leerzeichen ausgeben
rcall InputOutCharCaret
rjmp ir7
ir6: rcall SendSpace
ir7: dec r17
brne ir6
clc
ret
SendRightArrow: ;Rechten Überlaufpfeil ausgeben
ldi r17,COLOR_INPUT_ARROW ;Grün auf Blau
rcall SendColor
ldi r16,'>'
rcall putchar
SendStdColor:
ldi r17,COLOR_INPUT_STD ;Weiß auf Blau
rcall SendColor
clc
ret
InputSetCursor:
;Kursor an entsprechende Stelle in Eingabezeile auf Bildschirm setzen
;PE: R0..R7: Registersatz für Eingabezeile
movw r17:r16,rY:rX
add r16,rPos
sub r16,rFirst ;sollte nie überlaufen
inc r16
rjmp GotoXY
InputDrawFromDecide:
jbrc rBits,6, InputDrawFrom
;Im Überschreibmodus nur Zeichen von R16 bis (einschließlich) rPos
;aktualisieren! Aber Achtung, wenn Kursor hinten steht!
mov r17,rPos
cp r17,rLen
inc r17
brlo ed_drawfrom ;Cursor am Ende? (Quasi Überschreibmodus?)
InputDrawFrom:
;Ab Kursorposition und ab gegebenem Index String ausgeben, Farbe muss stehen
;PE: R0..R7: Registersatz für Eingabezeile
; R16 = Startindex in Z (muss nicht rPos entsprechen)
;VR: R16-R17
mov r17,rLen
ed_drawfrom:
sub r17,r16
breq ir3 ;das KANN vorkommen!
push ZH
push ZL
push rX
clt ;Zeichen, keine Leerzeichen ausgeben
mov rX,rPos
sub rX,r16
inc rX
add ZL,r16
adc0 ZH ;CY addieren
neg r16
add r16,rFirst
add r16,rWidth ;r16 = maximale Ausgabelänge für sichtbaren Teil
cp r17,r16 ;Zu viel?
brlo ir4
mov r17,r16 ;Begrenzen auf Ausgabebreite
ir4: dec rX ;rX=0 wenn Kursorposition erreicht
rcall InputOutCharDecide
djnz r17,ir4
pop rX
pop ZL
pop ZH
ir3: ret
InputOnChr:
;Nach Zeicheneingabe (nur) Zeichen rechts davon ausgeben
;Bei Gerade-Überschreitung des rechten Randes Ausgabe des Überlauf-Pfeils
;(Extra-Routine zur Verminderung des seriellen Verkehrs)
;PE: R0..R7: Registersatz für Eingabezeile
; R16=Index, ab dem aktualisiert werden soll
; rPos ist bereits erhöht (hinter dem neuen Zeichen)
rcall InputDrawFromDecide
mov r16,rLen ;String-Länge
sub r16,rFirst ;Erstes dargestelletes Zeichen
sub r16,rWidth ;Darstellungsbreite
dec r16 ;Ergebnis=1?
breq SendRightArrow ;ja, neuen Pfeil ausgeben
ret
InputDelete:
;Eingabefeld komplett löschen, aber NOCH NICHT neu zeichnen
;PE: Registersatz für Eingabezeile
clr rLen
clr rPos
clr rFirst
sbrs rBits,0
st Z,rLen ;Nullterminierung aufrechterhalten
ret
InputClear:
;Eingabefeld komplett löschen (Markierung gesetzt und DEL oder BS gedrückt)
;PE: Registersatz für Eingabezeile
sec
tst rLen
breq ic_e ;raus mit Fehler: nichts zu löschen!
rcall InputDelete
InputUnmark:
ldi r16,~(1<<7)
and rBits,r16 ;Markierung löschen
rcall InputRedraw
clc
ic_e: ret
InputPosValid:
;Testet, ob rPos eine ungültige Position darstellt
;Z=1 wenn ungültig (Kursorbewegung wiederholen)
clz
sbrs rBits,3
ret
mov r17,rPos
ed_pv: subi r17,3 ;Division durch Drei
brcc ed_pv
inc r17 ;Null nach INC wenn Rest 2 war
ret
ed_ovr: ;Überschreibmodus
push ZH
push ZL
add ZL,rPos ;Position adressieren
adc0 ZH
st Z,UART_InChar ;Zeichen einsetzen
pop ZL
pop ZH
rjmp ed_rig
InputHandleKey:
;PE: Registersatz für Eingabezeile
;VR: T-Bit
clt
mov r16,rPos ;Merker, woher der Kursor kam
cpi UART_InChar,0x01 ;^A, Pos1
breq ed_home
cpi UART_InChar,0x02 ;^B, Pfeil links
breq ed_left
cpi UART_InChar,0x05 ;^E, Ende
breq ed_end
cpi UART_InChar,0x06 ;^F, Pfeil rechts
breq ed_right
cpi UART_InChar,0x08 ;^H, Rückschritt
breq ed_bs0
cpi UART_InChar,0x7F ;DEL, Delete
breq ed_del0
cpi UART_InChar,' '
brlo ed_beep
jbrc rBits,1, ed_k3 ;Alle Zeichen erlaubt?
cpi UART_InChar,'0' ;auf Ziffern prüfen!
brlo ed_beep
cpi UART_InChar,'9'+1
brsh ed_beep
ed_k3:
;if (HighLight) HighLight=Z=InputLen=0;
sbrc rBits,7 ;Markierung vorhanden?
rcall InputDelete ;alles löschen, aber das Bit noch nicht!
;if (rPos<rLen && Overtype) goto HandleOvertype;
cp rPos,rLen
sbrc rBits,6
brlo ed_ovr
;if (InputLen<InputMax) InputLen++;
cp rLen,rMax
brsh ed_beep ;(kann nicht passieren wenn Bit gesetzt)
inc rLen ;rLen nun größer als rPos
;memmove(inputstring+rPos+1,inputstring+rPos,rLen-rPos [-1!] )
push ZH
push ZL
mov r17,rLen
sbrc rBits,0
dec r17 ;keine Nullterminierung einrechnen
add ZL,r17 ;Ende adressieren
adc0 ZH
sub r17,rPos ;Null im nicht-nullterminierten Modus am Ende
breq ed_k2
ed_moveup:
ld r16,-Z
std Z+1,r16
djnz r17,ed_moveup
ed_k2: st Z,UART_InChar ;Zeichen einsetzen
pop ZL
pop ZH
set ;Am Ende nach rechts aktualisieren!
rjmp ed_rig
ed_bs0: rjmp ed_bs ;Zwei Trampolins
ed_del0:rjmp ed_del
ed_beep:
sec
ret ;unbehandeltes Zeichen (auch TAB)
ed_rig:
mov r16,rPos ;Merker, woher Kursor kam
ed_right:
cp rPos,rLen ;Nicht über Stringlänge hinaus
brsh ed_setcursorright
inc rPos
rcall InputPosValid ;VR:R17
brne ed_setcursorright
rjmp ed_right ;weiterrücken
ed_left:
tst rPos
breq ed_setcursorleft
dec rPos
rcall InputPosValid ;VR:R17
brne ed_setcursorleft
rjmp ed_left ;weiterrücken
ed_home:
clr rPos
ed_setcursorleft:
cp rPos,rFirst
brsh ed_ok
mov rFirst,rPos
rjmp InputRedraw
ed_end:
mov rPos,rLen
ed_setcursorright:
mov r17,rWidth
add r17,rFirst
sub r17,rPos
brsh ed_ok
sub rFirst,r17 ;lies: rFirst vergrößern!
rjmp InputRedraw
ed_bs:
jbrs rBits,7, InputClear ;Markierung? Löschen! (Unterschied zu TP)
tst rPos
breq ed_beep ;nichts zu löschen (BS)
dec rPos
cp rPos,rFirst
brlo ed_del
ldi r16,0x08 ;Backstep
rcall putchar ;Kursor "korrigieren" vor Ausgabe
ed_del:
jbrs rBits,7, InputClear ;Markierung? Löschen! (wie Turbo Pascal)
jbrs rBits,5, ed_beep ;Löschen verboten!
tst rLen
breq ed_beep ;nichts zu löschen!
;memmove(inputstring+x,inputstring+x+1,inlen-x)
mov r17,rLen
sub r17,rPos
breq ed_beep ;nichts zu löschen (DEL)
sbrc rBits,0
dec r17 ;keine Nullterminierung einrechnen
breq ed_k1
push ZL
push ZH
add ZL,rPos
adc0 ZH
ed_movedown:
ldd r16,Z+1
st Z+,r16
djnz r17,ed_movedown
pop ZH
pop ZL
ed_k1: dec rLen
cp rPos,rFirst ;Komplette Verschiebung erforderlich?
brsh InputOnDel ;nein, nur rechtsseitig zeichnen (rücken)
mov rFirst,rPos
rjmp InputRedraw
ed_ok:
;rPos=neue, R16=alte Kursorposition
jbrs rBits,7, InputUnmark ;Markierung vorhanden?
cp r16,rPos ;Position verändert?
breq ed_ok1 ;nein (Jede Eingabe verändert Position!?)
mov r17,rPos
brlo ed_ok2
mov r17,r16
mov r16,rPos ;stets r17>r16
;brtc ed_ok1
;mov r16,rPos
;dec r16 ;Ab Zeichen vor Kursor ausgeben
ed_ok2: rcall InputOnChr ;Nach rechts aktualisieren bei Zeichen
ed_ok1: clc
ret
InputOnDel:
;Nach Zeichenlöschen (nur) Zeichen rechts davon und 1 Leerzeichen ausgeben
;(Extra-Routine zur Verminderung des seriellen Verkehrs)
;PE: R0..R7: Registersatz für Eingabezeile
mov r16,rPos
rcall InputDrawFrom
mov r16,rWidth
add r16,rFirst
sub r16,rLen ;Ergebnis >=0?
brsh SendSpace0 ;Zeichen oder Überlaufpfeil löschen
ret ;sonst Überlaufpfeil stehen lassen
SendSpace0:
rjmp SendSpace
;############################
;## Eingabezeilen-Schleife ##
;############################
InputInitLen:
;Initialisiert einen Input-Kontext
;PE: R16: X-Koordinate (linker Rand) der Eingabezeile 0..77
; R17: Y-Koordinate der Eingabezeile 0..23
; R18: maximale Länge der eingebbaren Zeichenkette, ohne terminierende Null
; R19: Breite des Eingabefeldes für die Zeichen
; Es werden links und rechts je ein Zeichen für Überlaufsymbole angefügt;
; der tatsächliche Platzbedarf ist zwei Zeichen mehr.
; Z (ZH:ZL): Zeiger auf nullterminierte Zeichenkette
; Für die Kontextspeicherung wird empfohlen, vor der Zeichenkette
; einen Puffer von 7 (oder mehr) Bytes einzurichten.
;PA: Registersatz für Eingabezeile ("Kontext")
;VR: R0-R7
movw rY:rX, r17:r16
rcall strlen
mov rLen,r16
ldi r16,1<<7
mov rBits,r16 ;Markierung setzen
InputInit:
;Seiteneinstieg mit mehr Parametern
;PE: R1: Bits
; R0: String-Länge von Z
; R4,R5: Kursorposition
movw rWidth:rMax, r19:r18
clr rFirst
clr rPos
rjmp InputRedraw ;alles zeichnen
#ifdef USE_INPUT_CONTEXT
InputLoadContext:
;PE: Z=Zeiger auf Eingabe-Datenstruktur im Speicher (7 Bytes)
;PA: Z=Zeiger hinter Kontext (bspw. auf Zeichenkette)
ld rLen,Z+
ld rBits,Z+
ld rPos,Z+
ld rFirst,Z+
ld rX,Z+
ld rY,Z+
ld rMax,Z+
ld rWidth,Z+
ret
InputSaveContext:
;PE: Z=Zeiger hinter Kontext (bspw. auf Zeichenkette)
;PA: Z=Zeiger auf Eingabe-Datenstruktur im Speicher
st -Z,rWidth ;sollte sich nicht ändern!
st -Z,rMax ;sollte sich nicht ändern!
st -Z,rY ;sollte sich nicht ändern!
st -Z,rX ;sollte sich nicht ändern!
st -Z,rFirst
st -Z,rPos
st -Z,rBits
st -Z,rLen
ret
#endif//USE_INPUT_CONTEXT
InputModal:
;Modale Schleife einer Eingabezeile, ohne Extras
;PE: Siehe InputInitLen()
;PA: UART_InChar=Tastenkode, 3=ESC,^C (abbrechen), 13=Enter (übernehmen)
;Der Nachteil dieser modalen Schleife ist, dass nicht auf TAB, PfeilAuf
;und PfeilAb reagiert werden kann, ebenso nicht auf ^L (global) und
;Funktionstasten (F1..F4 werden von VT100.I90 unterstützt).
;Mit den Kontexten ist die Verwaltung mehrerer Eingabezeilen
;bspw. im Dialog relativ leicht möglich
;Extra-Bits können via InputInit und InputLoop gesetzt werden...
rcall InputInitLen
InputLoop:
rcall InputSetCursor
rcall GetKey
cpi UART_InChar,13
breq InputOk
cpi UART_InChar,3
breq InputCancel
rcall InputHandleKey
brcc InputLoop
ldi r16,0x07 ;BEL, Piep
rcall putchar
rjmp InputLoop
InputOk:
InputCancel:
ret
.undef rPos
.undef rLen
.undef rFirst
.undef rX
.undef rY
.undef rMax
.undef rWidth
#endif//INPUT_I90
Detected encoding: ANSI (CP1252) | 4
|
|