Source file: /~heha/Mikrocontroller/avr-inc.zip/input.i90

;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
Wrong umlauts? - Assume file is ANSI (CP1252) encoded