;
; BILDSCHIRMTREIBER FUER CP/M EMULATOR
;
;
; (C) 1990,1991 by Jrgen Weber
;

LOCALS

INCLUDE DOS.INC

WS_KEY equ TRUE

EMU_SCR_PAGE EQU 0
DOS_SCR_PAGE EQU 1

HIGHLIGHT_ATTR EQU 1000b
UNDERLINE_ATTR EQU 110b
BLINK_ATTR     EQU 10000000b

SCRNWIDTH EQU 79
LASTLINE  EQU 24

emudata_seg segment para public 'DATA'


IF WS_KEY
after_ctrl_q db 0

CTRL_MASK EQU 111111B
ext_keys db 72, 'E' AND CTRL_MASK,0   ; up
         db 73, 'R' AND CTRL_MASK,0   ; page up
         db 75, 'S' AND CTRL_MASK,0   ; left
         db 77, 'D' AND CTRL_MASK,0   ; right
         db 80, 'X' AND CTRL_MASK,0   ; down
         db 81, 'C' AND CTRL_MASK,0   ; page down
         db 82, 'V' AND CTRL_MASK,0   ; ins
         db 83, 'G' AND CTRL_MASK,0   ; del
         db 115, 'A' AND CTRL_MASK,0  ; ctrl-left
         db 116, 'F' AND CTRL_MASK,0  ; ctrl-right
         db 71, 'Q' AND CTRL_MASK,'S' ; home
         db 79, 'Q' AND CTRL_MASK,'D' ; end
         db 117, 'Q' AND CTRL_MASK,'X' ; ctrl-end
         db 118, 'Q' AND CTRL_MASK,'C'  ; ctrl-pgdn
         db 119, 'Q' AND CTRL_MASK,'E'  ; ctrl-home
         db 132, 'Q' AND CTRL_MASK,'R'  ; ctrl-pgup
         db 0,0 ; 2. Null damit bei not found was geladen
ENDIF

tr_usa             db '#','$','@','[','\',']'
                   db '^','`','{','|','}','~'
tr_french          db '#','$','…','ø','‡',21
                   db '^','`','‚','—','Š','~'
tr_german          db '#','$', 21,'Ž','™','š'
                   db '^','`','„','”','','á'
tr_english         db 'œ','$','@','[','\',']'
                   db '^','`','{','|','}','~'
tr_danish          db '#','$','@','’','é',''
                   db '^','`','‘','í','†','~'
tr_swedish         db '#','$','','Ž','™',''
                   db 'š','‚','„','”','†',''
tr_italian         db '#','$','@','ø','\','‚'
                   db '^','—','…','•','Š',''
tr_spanish         db 'ž','$','@','Œ','¥','¨'
                   db '^','`','~','¤','}','~'
tr_end equ this byte

tr_tab_poi         dw offset tr_usa
char_tran_flag     db FALSE


cursor_size        dw (?)
cursor_on_flag     db TRUE
cursor_pos         dw 0

txt_attribute           db 7


ESC_flag           db FALSE
ctrl_paramcount    db 0
ctrl_address       dw (?)
ctrl_buf_entries   db 0
ctrl_buf_poi       dw offset ctrl_buf
ctrl_buf           db 10 dup (?)

; Struktur zur beschreibung der einzelnen Bildschirmtreiberfunktionen

termfkt  struc
         db          (?)          ; Funktionsnummer
         db          (?)          ; Paramameterzahl
         dw offset   (?)          ; Adresse des zugehoerigen UPs
termfkt  ends

; Tabelle fuer Terminalfunktionen des Schneider CPC
; (noch nicht alle Funktionen implementiert)

CPC_tab  termfkt <0,0,dummy_rout>
         termfkt <1,0,dummy_rout>
         termfkt <2,0,cursor_off>
         termfkt <3,0,cursor_on>
         termfkt <4,0,dummy_rout>
         termfkt <5,0,dummy_rout>
         termfkt <6,0,dummy_rout>
         termfkt <7,0,char_out>    ;  Bell macht Bios
         termfkt <8,0,char_out>    ;  csr left macht bios
         termfkt <9,0,csr_right>
         termfkt <10,0,char_out>   ;  csr down macht bios
         termfkt <11,0,csr_up>
         termfkt <12,0,clear_screen>
         termfkt <13,0,csr_to_lstart>
         termfkt <14,0,dummy_rout>
         termfkt <15,0,dummy_rout>
         termfkt <16,0,clr_chr_at_cursor>
         termfkt <17,0,del_line_to_cursor>
         termfkt <18,0,del_line_from_cursor>
         termfkt <19,0,clear_window_to_cursor>
         termfkt <20,0,clear_window_from_cursor>
         termfkt <21,0,dummy_rout>
         termfkt <22,0,dummy_rout>
         termfkt <23,0,dummy_rout>
         termfkt <24,0,txt_inverse>
         termfkt <25,0,dummy_rout>
         termfkt <26,0,dummy_rout>
         termfkt <27,0,use_esc_tab>
         termfkt <28,0,dummy_rout>
         termfkt <29,0,dummy_rout>
         termfkt <30,0,cursor_home>
         termfkt <31,2,set_cursor>

         db 0ffh                  ; sentinel

; Tabelle fuer Terminalfunktionen von CP/M Plus
; (noch nicht alle Funktionen implementiert)


ESC_tab   label byte

; die erste Routine stammt nicht von CP/M Plus, sondern
; ist eigendefiniert (entsprechend zu Epson Druckern)

          termfkt <'@',0,Reset_Screen>

          termfkt <'0',0,dummy_rout> ; Statuszeile aus
          termfkt <'1',0,dummy_rout> ; Statuszeile an
          termfkt <'2',1,select_country>
          termfkt <'3',1,dummy_rout> ; waehle Bildschirmmodus
          termfkt <'A',0,csr_up>
          termfkt <'B',0,ESC_csr_down>
          termfkt <'C',0,ESC_csr_right>
          termfkt <'D',0,ESC_csr_left>
          termfkt <'E',0,ESC_clear_screen>
          termfkt <'H',0,cursor_home>
          termfkt <'I',0,csr_up_scr_down>
          termfkt <'J',0,clear_window_from_cursor>
          termfkt <'K',0,del_line_from_cursor>
          termfkt <'L',0,insert_line>
          termfkt <'M',0,delete_line>
          termfkt <'N',0,delete_char_at_cursor>
          termfkt <'Y',2,ESC_set_cursor>
          termfkt <'b',0,dummy_rout> ; setze Zeichenhelligkeit
          termfkt <'c',0,dummy_rout> ; setze Hintergrundhelligkeit
          termfkt <'d',0,del_line_to_cursor>
          termfkt <'e',0,cursor_on>
          termfkt <'f',0,cursor_off>
          termfkt <'j',0,push_cursor_pos>
          termfkt <'k',0,pop_cursor_pos>
          termfkt <'l',0,dummy_rout> ; loesche Zeile des Cursors
          termfkt <'o',0,del_line_to_cursor>
          termfkt <'p',0,inverse_on>
          termfkt <'q',0,inverse_off>
          termfkt <'r',0,underline_on>
          termfkt <'s',0,blink_on>
          termfkt <'t',0,blink_off>
          termfkt <'u',0,underline_off>
          termfkt <'v',0,dummy_rout>
          termfkt <'w',0,dummy_rout>
          termfkt <'x',0,dummy_rout> ; setze 24x80
          termfkt <'y',0,dummy_rout> ; 24x80 wieder weg

          db 0ffh                 ; sentinel

emudata_seg ends


emulator_seg segment para public 'CODE'

assume  cs:emulator_seg,ds:emudata_seg,es:nothing,ss:nothing

PUBLIC reset_conout,conout,conin,sel_scr_page,cursor_off,cursor_on
EXTRN prg_exit:far
EXTRN breakflag:byte

reset_conout proc
     mov  ctrl_buf_entries,0
     mov  ctrl_paramcount,0
     mov  ctrl_buf_poi,offset ctrl_buf
    ret
reset_conout endp

;
conout proc
;
; console character output aus register c
;
; es gibt zwei Sprungtabellen: ESC_tab fr mit ESC eingeleitete
;                              CP/M Plus Steuercodes und
;                              CPC_tab fr die Steuercodes des CPC
       PUSHR <si,di,bp,ax>
       mov al,ctrl_paramcount
       cmp al,0
       jnz short @@not_empty
         cmp byte ptr ESC_flag,TRUE
         mov byte ptr ESC_flag,FALSE
         mov di,offset ESC_tab

         jz  short @@search_entrie
         cmp cl,' '
         jnc short @@write_out
       mov di,offset CPC_tab
@@search_entrie:
       cmp byte ptr [di],0ffh
       jz  short @@execon         ; nicht gefunden
       cmp cl,[di]
       jz short @@entrie_found
       add di,4                   ; jeder Eintrag belegt 4 Bytes
     jmp short @@search_entrie

@@entrie_found:
       mov al,[di+1]              ; benoetigte Parameter
       cmp al,0
     jz  short @@execute
       mov ctrl_paramcount,al
       mov di,[di+2]
       mov ctrl_address,di        ; Adresse des zugehoerigen UP merken
      jmp short @@exit

@@not_empty:
       mov bx,ctrl_buf_poi
       mov [bx],cl                ; Parameter merken
       inc bx
       mov ctrl_buf_poi,bx
       inc ctrl_buf_entries
       mov al,ctrl_paramcount
       cmp al,ctrl_buf_entries    ; alle Parameter komplett ?
       jnz short @@exit
       mov di,offset ctrl_address - 2

@@execute:
       call [di+2]
@@execon:
       call reset_conout
       jmp  short @@exit

@@write_out:
       call char_out
@@exit:
       POPR <ax,bp,di,si>
       ret
conout endp

;
; display 0 terminated string in ds:si
;

use_esc_tab proc
       mov  byte ptr ESC_flag,TRUE
       ret
use_esc_tab endp
;
dummy_rout proc
       ret
dummy_rout endp

Reset_Screen proc
          call ESC_clear_screen
          call cursor_home
          call inverse_off
          call cursor_on
          call underline_off
          mov  char_tran_flag,FALSE
          ret
Reset_Screen endp

push_cursor_pos proc
      call get_cursor_pos
      mov  cursor_pos,dx
      ret
push_cursor_pos endp

pop_cursor_pos proc
      mov dx,cursor_pos
      call bios_set_cursor
      ret
pop_cursor_pos endp

get_cursor_pos proc
       PUSHR <ax,si,di,bp>
         mov ah,3
         mov bh,EMU_SCR_PAGE
         int 10h
       POPR  <bp,di,si,ax>
       ret
get_cursor_pos endp

cursor_off proc
       cmp cursor_on_flag,FALSE
       jz  short @@exit
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         mov  cursor_size,cx
         mov ah,1                 ; define cursor
         mov cx,0ffffh            ; unmoeglicher Wert = Cursor aus
         mov bh,EMU_SCR_PAGE
         int 10h
         mov cursor_on_flag,FALSE
       POPR  <bp,di,si,cx,bx,ax>
@@exit:
       ret
cursor_off endp

cursor_on proc
       cmp cursor_on_flag,TRUE
       jz  short @@exit
       PUSHR <ax,bx,cx,si,di,bp>
         mov  cx,cursor_size
         mov ah,1                 ; define cursor
         mov bh,EMU_SCR_PAGE
         int 10h
       mov cursor_on_flag,TRUE
       POPR  <bp,di,si,cx,bx,ax>
@@exit:
       ret
cursor_on endp

csr_up_scr_down proc              ; ESC I
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         cmp  dh,0                ; vorher schon oben ?
         jz short @@is_up
           dec dh                 ; y
           call bios_set_cursor
           jmp short @@exit
   @@is_up:
         push dx
         mov ah,7                 ; scroll down window
         mov al,1                 ; um eine Zeile
         mov bh,txt_attribute
         mov cx,0
         mov dh,LASTLINE
         mov dl,SCRNWIDTH
         int 10h
         pop dx
         call bios_set_cursor
@@exit:
       POPR  <bp,di,si,cx,bx,ax>
       ret
csr_up_scr_down endp

insert_line proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         push dx
; scrolle den Rest des Bildschirms ab Cursorpos eine Zeile nach unten
; Cursorpos bleibt erhalten
         mov ah,7                 ; scroll down window
         mov al,1                 ; eine Zeile
         mov bh,txt_attribute
         mov ch,dh
         mov cl,0
         mov dh,LASTLINE
         mov dl,SCRNWIDTH
         int 10h
         pop dx
         call bios_set_cursor
       POPR  <bp,di,si,cx,bx,ax>
       ret
insert_line endp

delete_line proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         push dx
; scrolle den Rest des Bildschirms ab Cursorzeile+1 eine Zeile nach unten
; Cursorpos bleibt erhalten
         mov ah,6                 ; scroll up window
         mov al,1                 ; um eine Zeile
         mov bh,txt_attribute
         mov ch,dh
         mov cl,0
         mov dh,LASTLINE
         mov dl,SCRNWIDTH
         int 10h
         pop dx
         call bios_set_cursor
       POPR  <bp,di,si,cx,bx,ax>
       ret
delete_line endp

delete_char_at_cursor proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         push dx
@@while:                          ; WHILE XPos <SCRNWIDTH DO
                                  ; schlrfe Rest der Zeile ein
         inc dl
         cmp dl,SCRNWIDTH
         ja  short @@endwhile
         call bios_set_cursor
         call read_from_screen
         push ax
         dec  dl
         call bios_set_cursor
         pop  ax
         mov  bl,ah ; attribute
         mov  bh,EMU_SCR_PAGE
         mov  cx,1
         mov  ah,9 ; write char
         int  10h
         inc  dl
       jmp short @@while
@@endwhile:
         pop dx
         call bios_set_cursor
       POPR  <bp,di,si,cx,bx,ax>
       ret
delete_char_at_cursor endp

read_from_screen proc             ; nach ax
       PUSHR <si,di,bp>
         mov ah,8                 ; bios read from screen
         mov bh,EMU_SCR_PAGE
         int 10h
       POPR  <bp,di,si>
       ret
read_from_screen endp

csr_up proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         cmp  dh,0                ; vorher schon oben ?
         jz short @@dont
           dec dh
           call bios_set_cursor
         @@dont:
       POPR  <bp,di,si,cx,bx,ax>
       ret
csr_up endp

ESC_csr_down proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         cmp  dh,LASTLINE         ; vorher schon unten ?
         jz short @@dont
           inc dh
           call bios_set_cursor
         @@dont:
       POPR  <bp,di,si,cx,bx,ax>
       ret
ESC_csr_down endp

ESC_csr_right proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         cmp  dl,SCRNWIDTH
         jz short @@dont
           inc dl
           call bios_set_cursor
         @@dont:
       POPR  <bp,di,si,cx,bx,ax>
       ret
ESC_csr_right endp

ESC_csr_left proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         cmp  dl,0
         jz short @@dont
           dec dl
           call bios_set_cursor
         @@dont:
       POPR  <bp,di,si,cx,bx,ax>
       ret
ESC_csr_left endp

csr_right proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         cmp  dl,SCRNWIDTH
         jz short @@dont
           inc dl
           call bios_set_cursor
         @@dont:
       POPR  <bp,di,si,cx,bx,ax>
       ret
csr_right endp

clear_window proc
       PUSHR <ax,bx,cx,si,di,bp>
         mov ah,6                 ; scroll window
         mov al,0                 ; clear window
         mov bh,txt_attribute
         int 10h
       POPR  <bp,di,si,cx,bx,ax>
       ret
clear_window endp

ESC_clear_screen proc
       PUSHR <cx,dx>
         mov ch,0                 ; y oben
         mov dh,25                ; y unten
         mov cl,0                 ; x links
         mov dl,SCRNWIDTH         ; x rechts
         call clear_window
       POPR  <dx,cx>
       ret
ESC_clear_screen endp

clear_screen proc
       call ESC_clear_screen
clear_screen endp                 ; und weiter mit Home

cursor_home proc
       push dx
       mov  dx,0                  ; x=y=0
       call bios_set_cursor
       pop dx
       ret
cursor_home endp

set_cursor proc
       PUSHR <dx,si>
         mov si,offset ctrl_buf
         mov dx,[si]
         dec dh
         dec dl                   ; offset 1 beim CPC
         call bios_set_cursor
       POPR  <si,dx>
       ret
set_cursor endp

ESC_set_cursor proc
       PUSHR <dx,si>
         mov si,offset ctrl_buf
         mov dx,[si]
         sub dh,20h
         sub dl,20h               ; offset 32 bei CP/M Plus
         cmp dl,LASTLINE
         jna short @@yok
         mov dl,LASTLINE
@@yok:
         cmp dh,SCRNWIDTH
         jna short @@xok
         mov dh,SCRNWIDTH
@@xok:
         xchg dh,dl
         call bios_set_cursor
       POPR  <si,dx>
       ret
ESC_set_cursor endp

;
; dh=y
; dl=x
;
bios_set_cursor proc
       PUSHR <ax,cx,si,di,bp>
         mov bh,EMU_SCR_PAGE
         mov ah,2
         int 10h
       POPR  <bp,di,si,cx,ax>
       ret
bios_set_cursor endp

sel_scr_page proc
       PUSHR <ax,si,di,bp>
       mov ah,5
       int 10h
       POPR  <bp,di,si,ax>
       ret
sel_scr_page endp

csr_to_lstart proc                ; Cursor an Zeilenanfang
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         mov bh,EMU_SCR_PAGE
         xor dl,dl                ; x:= 0
         call bios_set_cursor
       POPR  <bp,di,si,cx,bx,ax>
       ret
csr_to_lstart endp

clr_chr_at_cursor proc
       mov cx,1                   ; 1 char
       call clr_chars
       ret
clr_chr_at_cursor endp

; Zeichenzahl in cx
clr_chars proc
       PUSHR <ax,bx,cx,si,di,bp>
         mov al,' '
         mov bh,EMU_SCR_PAGE
         mov bl,txt_attribute
         mov ah,9
         int 10h
       POPR  <bp,di,si,cx,bx,ax>
       ret
clr_chars endp

del_line_to_cursor proc
       PUSHR <ax,bx,cx,si,di,bp>
         call get_cursor_pos
         push dx
           mov cl,dl              ; x
           mov dl,0
           call bios_set_cursor
           mov ch,0
           call clr_chars
         pop dx
         call bios_set_cursor
       POPR  <bp,di,si,cx,bx,ax>
       ret
del_line_to_cursor endp

del_line_from_cursor proc
       PUSHR <cx,dx>
         call get_cursor_pos
         mov cl,SCRNWIDTH
         sub cl,dl
         mov ch,0
         call clr_chars
       POPR  <dx,cx>
       ret
del_line_from_cursor endp


clear_window_to_cursor proc
       PUSHR <cx,dx>
         call get_cursor_pos

         dec dh                   ; y
         mov ch,0
         mov cl,0
         mov dl,SCRNWIDTH
         call clear_window
         call del_line_to_cursor
       POPR  <dx,cx>
       ret
clear_window_to_cursor endp

clear_window_from_cursor proc
       PUSHR <cx,dx>
         call del_line_from_cursor
         call get_cursor_pos
         inc dh                   ; y++
         mov ch,dh
         mov dh,24
         mov cl,0
         mov dl,SCRNWIDTH
         call clear_window
       POPR  <dx,cx>
       ret
clear_window_from_cursor endp

txt_inverse proc
         xor txt_attribute,HIGHLIGHT_ATTR
         ret
txt_inverse endp

inverse_on proc
         or txt_attribute,HIGHLIGHT_ATTR
         ret
inverse_on endp

inverse_off proc
         and txt_attribute,not HIGHLIGHT_ATTR
         ret
inverse_off endp

underline_on proc
         and txt_attribute,not UNDERLINE_ATTR
         ret
underline_on endp

underline_off proc
         or txt_attribute,UNDERLINE_ATTR
         ret
underline_off endp


blink_on proc
         or txt_attribute,BLINK_ATTR
         ret
blink_on  endp

blink_off proc
         and txt_attribute,not BLINK_ATTR
         ret
blink_off endp


;
; jedes Zeichen wird 2 mal ausgegeben, da Funktion 9 nicht den Cursor
; bewegt und Funktion 14 kein Attribut aendert
; dies selbst zu erledigen waere zu aufwendig
; Fkt 14 wird auf jeden Fall benoetigt, da sie u.a. piepsen kann
;
char_out proc
       PUSHR <si,di,bp,ax,cx>
       mov al,cl     ;get to accumulator
       cmp al,' '    ; Piepser oder CR ?
       jb short @@dont
       call char_tran
        push ax
         mov ah,9
         mov bl,txt_attribute
         mov bh,EMU_SCR_PAGE
         mov cx,1
         int 10h                  ; erst mal Attribut setzen
        pop ax
@@dont:
       mov ah,14                  ; character output
       mov bl,txt_attribute
       int 10h                    ; und nochmal um den Cursor weiterzusetzen

       POPR <cx,ax,bp,di,si>
       ret
char_out endp

char_tran proc
       cmp char_tran_flag,FALSE
       jz  short @@exit
       push es
       push ds
       pop  es
       mov  di,offset tr_usa
       mov  cx,tr_french-tr_usa
; muss Zeichen uebersetzt werden ?
       repnz scasb
       pop  es
       jnz  short @@exit
       sub di,offset tr_usa       ; di:= # zu uebersetzendes Zeichen
       dec di                     ; da scas eins zuviel inct
       mov bx,tr_tab_poi
       mov al,bx[di]
@@exit:
       ret
char_tran endp

select_country proc
       mov si,offset ctrl_buf
       mov al,[si]
       cmp al,7
       ja  short @@exit
       mov byte ptr char_tran_flag,FALSE
       cmp al,0
       jz  @@noflag
       mov byte ptr char_tran_flag,TRUE
@@noflag:
       mov bl,tr_french-tr_usa ; Eintraege pro Land
       mul bl
       add ax,offset tr_usa
       mov tr_tab_poi,ax
@@exit:
       ret
select_country endp


conin proc
;
; console character nach register a
;
; Out: al=ascii
;      ah zerstoert
;
IF WS_KEY
       mov al,after_ctrl_q
       mov after_ctrl_q,0
       cmp al,0
       jnz @@end_conin
ENDIF
       mov ah,0                   ; keybord input
       push bx
       int 16h
       pop bx
       cmp breakflag,TRUE
       mov breakflag,FALSE
       jnz short @@nobreak
       call prg_exit
       jmp conin
@@nobreak:

IF WS_KEY    ; erweiterte Codes nach WS bersetzen
       cmp al,0
       jnz short @@no_extended

       push bx

       mov bx,offset ext_keys
       mov cx,ax
@@cmp_loop:
       mov al,[bx]            ; extended code
       mov after_ctrl_q,0     ; damit es bei Aussprung richtig
       cmp al,0
       jz  short @@no_key_tr  ; z = Tabellen ende
       cmp al,ch
       pushf
       inc bx
       mov al,[bx]            ; CTRL-...
       inc bx
       mov ah,[bx]            ; CTRL-Q-...
       inc bx
       mov after_ctrl_q,ah
       popf
       jnz @@cmp_loop

@@no_key_tr:
       pop bx
@@no_extended:
ENDIF

; Umlaute werden in CP/M Form bersetzt
       push di
       push cx
       push es
        push ds
        pop  es
       mov  di,offset tr_french
       mov  cx,tr_end-tr_french
; muss Zeichen uebersetzt werden ?
       repnz scasb
       pop  es
       jnz  short @@exit
       sub di,offset tr_french    ; di:= # zu uebersetzendes Zeichen
       dec di                     ; da scas eins zuviel inct
       mov ax,di
       mov bx,tr_french-tr_usa
       mov dx,0
       div bx
       mov di,dx
       mov bx,offset tr_usa
       mov al,bx[di]
@@exit:
       pop cx
       pop di
     ;  and al,7fh                ;strip parity bit von DR vorgeschlagen
@@end_conin:
       ret
conin endp

emulator_seg ends

end

