	PAGE 59, 132

	TITLE MsHelper -- Windowing help module for VT100 emulation on IBM PC

; Update 6 Jan 86

IF1
 %OUT >> Starting pass 1
ELSE
 %OUT >> Starting pass 2
ENDIF

	PUBLIC Helper
	.SALL

;***************************************************************************
; *Definitions* ...

; Global defs

	INCLUDE MsDefs.H

DataS	SEGMENT PUBLIC 'DataS'

	EXTRN Which_page:BYTE, Screen_flags:BYTE
	EXTRN Video_page_addresses:WORD, m7171:BYTE
	EXTRN Force_mono:BYTE, m7171:BYTE

Screen	EQU 10h			; BIOS screen call
Kb	EQU 16h			; BIOS keyboard call

Old_7171_mode DB ?		; If we were here before, which mode was it?

; Screen stuff

Number_of_columns EQU 80	; Width in characters
Number_of_rows_PC EQU 25	; Length in rows including mode line
Number_of_chars_on_PC_screen EQU Number_of_columns*Number_of_rows_PC
				; Number of chars in IBM screen

; Flag set in Screen_flags

Screen_changed EQU 8		; Data on more than one line changed

Saved_mode_line DW Number_of_columns DUP (?) ; Saved image of Mode line

; Scan codes for keys we have predefined ...

Escape_key EQU 1
Space_key EQU 57
F6_key	EQU 64
ALT_F6_key EQU 109
End_key	EQU 79
Down_key EQU 80
PgDn_key EQU 81
Left_key EQU 75
Right_key EQU 77
Home_key EQU 71
Up_key EQU 72
PgUp_key EQU 73

Characters LABEL BYTE
	DB 	Space_key,	Escape_key,	F6_key
	DB	Home_key,	End_key,	PgUp_key
	DB	PgDn_key,	Up_key,		Down_key
	DB	Left_key,	Right_key,	ALT_F6_key
N_Characters EQU $-Characters
Actions	LABEL WORD
	DW 	CHK_bye,	CHK_bye,	CHK_bye
	DW 	Do_Home,	Do_End,		Do_PgUp
	DW	Do_PgDn,	Do_Up,		Do_Down
	DW	Do_Up,		Do_Down,	CHK_bye
	DW	Do_boop

	PUBLIC Help_file_name, Characters, Actions

Help_file_name LABEL BYTE
	Program_name
	DB '.HLP',0		; Name of our help file

Help_file_7171 LABEL BYTE
	Program_name
	DB '.H71',0		; Name of our help file on IBM 7171

No_memory DB '      ***  Not enough memory for HELP  --  Press any key to continue ***$'

Cant_find_file DB '        *****   Missing '
	Program_name
	DB '.HLP  --  Press any key to continue  *****$'

Cant_find_7171 DB '        *****   Missing '
	Program_name
	DB '.H71  --  Press any key to continue  *****$'

DataS	ENDS


; Dummy segment for allocated memory

HAlloc	SEGMENT AT 0

First_line DW ?			; Ptr to first line in database
Last_line DW ?			; Ptr to last line in database
Last_top_line DW ?		; Ptr to line on top of last frame
Current_line DW ?		; Ptr to line currently at top of window

Which_row DW ?			; Which row are we writing in?

	PUBLIC Our_Image, Our_cursor, Which_row, Current_line, First_line

Our_cursor DW ?			; Our position
Our_Image DW Number_of_chars_on_PC_screen DUP (?) ; Our buffer

	PUBLIC Help_database, Help_file_buffer,End_HAlloc

Zero	DB ?			; Make backward searches stop here ...
Help_database DB 300*74 DUP (?)	; Where we store the help text

Help_file_buffer DB 2048 DUP (?) ; Buffer for reading Help file from disk

End_HAlloc LABEL BYTE		; Ptr to end of what we need

HAlloc	ENDS


Code	SEGMENT PUBLIC

	EXTRN Put_Screen:NEAR, Get_Screen_Segment:NEAR, Boop:NEAR
	EXTRN SPath:NEAR, Check_table:NEAR, Do_mode_line:NEAR
	EXTRN Get_memory_block:NEAR, PutErr_PC:NEAR, Screen_Image_ptr:DWORD
	EXTRN Which_card:BYTE, Mode_line_ptr:DWORD

    ASSUME CS:Code, DS:DataS, ES:DataS

Normal_ds DW ?			; Ptr back to ds on entry
HAlloc_ptr DW ?			; Address of HAlloc segment

	PUBLIC HFlags

HFlags	DB 0			; Internal flags, kept in code segment
				;  for easy access

Bye_bye EQU 1			; Flag to leave
Been_here EQU 2			; Already did once-only initialization

Helper	PROC

	and HFlags, NOT Bye_bye	; Flag that we aren't leaving yet

	test HFlags, Been_here	; Been here before?
	 jz HLP_go		;  No ...

; We have been here before, but maybe with the wrong help file ...

	mov al, m7171		; Pick up current 7171 mode
	cmp al, Old_7171_mode	; Same as last time?
	 je HLP_1		;  Same, just display our buffer

; We have been here before, so we already have memory allocated ...
;  We just need to reload the database

	mov ax, HAlloc_ptr	; Reuse old memory
	jmp SHORT H_ok		; Join common code

HLP_go:

; First, get some memory to work in

; *** The silly assembler won't let me do this, saying
;		Constant was expected
;	or something like that ...

;	mov bx, (OFFSET End_HAlloc+15) SHR 4 ; Lowest location we don't use,
				;  round up to next higher paragraph,
				;  then convert to number of paragraphs

	mov bx, OFFSET End_HAlloc+15 ; Round up to next paragraph
	mov cl, 4		; Shift count
	shr bx, cl		; Shift to get number of paras we need

	call Get_memory_block	; Get a memory block of the right size
	 jnc H_OK		;  Ok so far ...

	mov dx, OFFSET No_memory ; Complain
	jmp Do_mode_line_error	; Display noisy error message, wait for
				;  keystroke, then exit, restoring
				;  clobbered mode line

H_OK:	mov HAlloc_ptr, ax	; Save the address of our block

	mov bx, ds		; Set up ptr back to ds
	mov Normal_ds, bx	; Save ptr back to old one

; Switch to HAlloc segment ...

	mov ds, ax		; Point ds to HAlloc
	mov es, ax		;  es also ...

    ASSUME ds:HAlloc, es:HAlloc	; Tell assembler we have a new ds and es

	mov Zero, 0		; Store a zero

	call Build_Help_database ; Build the database
	test HFlags, Bye_bye	; Time to leave?
	 jz HLP_0		;  Not yet

; *** Properly speaking, we really ought to release the memory we
;	were just allocated, since we can't use it ... maybe later ***

	jmp Back_to_normal_ds	; Oh well ...

HLP_0:	or HFlags, Been_here	; Flag for later

	call Back_to_normal_ds

    ASSUME ds:DataS, es:DataS

	mov al, m7171		; Pick up current 7171 mode
	mov Old_7171_mode, al	; Store it for later test

	call Use_our_own_ds

    ASSUME ds:HAlloc, es:HAlloc

	jmp SHORT HLP_1a

    ASSUME ds:DataS, es:DataS

HLP_1:	call Use_our_own_ds	; Switch to HAlloc segment

    ASSUME ds:HAlloc, es:HAlloc

HLP_1a:	call Build_box		; Build outline / background for Help window

	mov ax, First_line	; Address of start at Help text
	mov Current_line, ax	; Set up as current position

HLP_2:	call Fill_frame		; Get frame ready
	call Do_the_screen	; Display this frame

	call Check_keystrokes	; See what to do next
	test HFlags, Bye_bye	; Time to leave?
	 jz HLP_2		;  No, go update the frame

	jmp Back_to_normal_ds	; Done here


	PUBLIC Bombed

    ASSUME ds:DataS, es:DataS
Bombed:	call Boop		; Bombed, make a noise
	or HFlags, Bye_bye	; Go home
	ret
    ASSUME ds:HAlloc, es:HAlloc


	PUBLIC Build_Help_database

Build_Help_database:

	call Back_to_normal_ds	; Need old ds to open file

    ASSUME ds:DataS, es:DataS

	mov ax, OFFSET Help_file_name ; Name of file to get
	cmp m7171, 0		; In 7171 mode?
	 je BUI_0

	mov ax, OFFSET Help_file_7171 ; Name of the other file

BUI_0:	call spath		; Search path for file, DSK: first
	 jnc BUI_ok		;  Got it, go work with it

	mov dx, OFFSET Cant_find_file ; Complain
	cmp m7171, 0		; In 7171 mode?
	 je BUI_1

	mov dx, OFFSET Cant_find_7171 ; Complain about missing 7171 file

BUI_1:	call Do_mode_line_error ; Wait for a key
	jmp Use_our_own_ds	; Reset ds and return NG

BUI_ok:
	mov dx, ax		; Copy ptr to name to dx
	mov ax, (Open2*100h) + 0 ; Open file for input
	int DOS			; Try to open the file
	 jc Bombed		;  Huh?

	mov bx, ax		; Copy handle to bx

	call Use_our_own_ds	; Back to our own version

    ASSUME ds:HAlloc, es:HAlloc

	mov di, OFFSET Help_database ; Ptr to start of our database
	mov First_line, di	; Save ptr to start for later
	mov Current_line, di	; Also a ptr for making Last_line later
	cld			; Build forwards

BUI_disk_read:
	mov ah, ReadF2		; Code to read from file, handle still in bx
	mov cx, SIZE Help_file_buffer ; Number of characters to try for
	mov dx, OFFSET Help_file_buffer ; Where to put them
	int DOS			; Read from the help file
	 jc BUI_finish		;  Stop on error

	or ax, ax		; Check for zero bytes read
	 jz BUI_finish		; Hit EOF, go finish it up

	mov cx, ax		; Byte count for this record
	inc cx			; We decr once too many times
	mov si, OFFSET Help_file_buffer ; Ptr to buffer

BUI_lp:	loop BUI_next_char	; If more chars, go get one
	jmp SHORT BUI_disk_read	; Buffer is empty, try to refill it

BUI_next_char:
	lodsb			; Get the next byte

	cmp al, CtlZ		; Control-Z?
	 jl BUI_control		;  Less, check for control char
	 je BUI_lp		;  Ignore control Z

	stosb			; Store the char
	jmp BUI_lp		; Go do another one

BUI_control:
	cmp al, Lf		; Line feed?
	 jne BUI_lp		;  No

	sub al, al		; Make a zero
	stosb			; Tie off the last line

	mov ax, Current_line	; Pick up trailing ptr
	mov Last_line, ax	; Assume it's the last line, for now
	mov Current_line, di	; Update trailing ptr for next round

	jmp BUI_lp		; Go get another character

BUI_finish:
	sub al, al		; Make a zero
	mov cx, 25		; Make several blank lines after help database
	rep stosb		; Write a string of zeros

	mov ah, 3eh		; Code to close a file
	int DOS			; Close the file

	mov di, Last_line
	mov Current_line, di
	mov cx, Len - 3		; Maximum number of lines to move
	call PgUp_lp		; Get a new DI pointing to proper line
	mov Last_top_line, di	; Save for later

	ret			; Done here

	PUBLIC Build_box

Build_box:

	cld			; Forwards
	mov di, OFFSET Our_Image ; Our own buffer
	mov cx, Number_of_chars_on_PC_screen ; How many chars
	lds si, Screen_Image_ptr ; Addr of copy of screen at entry
	rep movsw		; Install incoming screen as background
	push es			; Our segment
	pop ds			; Set up ds again

Wid=Number_of_columns-(2*Help_window_left) ; How many columns
Len=Number_of_rows_PC-(2*Help_window_top) ; How many rows

Color_attr=1eh			; Bright yellow on blue
Mono_attr=70h			; Black on white


 %OUT >> About half way through source file


Upper_left=218			; Box drawing chars
Upper_right=191
Lower_left=192
Lower_right=217
Horizontal=196
Vertical=179

	mov ax, Help_window_top	; Row number of top of window
	mov Which_row, ax	; Save it for later
	call Make_new_ptr	; Get a new DI


; Set up proper attribute based on card type

	mov bh, Mono_attr	; Assume mono
	call Back_to_normal_ds
    ASSUME ds:DataS, es:DATAS

	cmp Force_mono, 0	; Are we stuck with mono?
	 jne MON_0		;  Yes

	call Get_Screen_Segment
	cmp ax, 0b000h		; Mono?
	 je MON_0		;  It is, go use it

	mov bh, Color_attr	; Color, use it instead

	PUBLIC MON_0

MON_0:	mov ah, bh		; Install in ah
	call Use_our_own_ds
    ASSUME ds:HAlloc, es:HAlloc

; Do top row of help window

	mov al, Upper_left
	stosw

	mov al, Horizontal
	mov cx, Wid - 2
	rep stosw

	mov al, Upper_right
	stosw

	inc Which_row		; Bump to next row
	call Make_new_ptr	; Point to it

; Do the middle rows of the help window

	mov cx, Len - 2		; Number of rows to do

	PUBLIC LP_1

LP_1:	push cx

	mov al, Vertical
	stosw

	mov al, Space
	mov cx, Wid - 2
	rep stosw

	mov al, Vertical
	stosw

	pop cx

	inc Which_row		; Bump to next row
	call Make_new_ptr	; Point to it

	loop LP_1

; Do bottom row of help window

	mov al, Lower_left
	stosw

	mov al, Horizontal
	mov cx, Wid - 2
	rep stosw

	mov al, Lower_right
	stosw

	mov ax, (100h * Help_window_top) + Help_window_left ; New position
	mov Our_cursor, ax	; Set it for Put_Screen

	ret


; Set up a pointer to this row

	PUBLIC Make_new_ptr

Make_new_ptr:
	push ax			; Save reg
	mov ax, Which_row	; Row number of topmost row
	mov bh, 2 * Number_of_columns ; Convert to bytes 
	mul bh
	add ax, (2 * Help_window_left) + OFFSET Our_Image ; Point to our
				;  image, proper column
	mov di, ax		; Set up as destination
	pop ax			; Restore reg
	ret			; Go use it


	PUBLIC Fill_frame

Fill_frame:
	cld			; Build forwards
	mov ax, Help_window_top + 1 ; Row number to start writing to
	mov Which_row, ax	; Save it
	call Make_new_ptr	; Get a new DI
	add di, 4		; Skip over border, and another space

	mov si, Current_line	; Start at current position in help text
	mov cx, Len - 2		; Number of rows to write

FIL_lp_outer:
	push cx			; Save outer loop counter
	mov cx, Wid - 4		; Number of chars we can fit in a line

FIL_lp_inner:
	lodsb			; Pick up the next byte
	or al, al		; Check for zero
	 jnz FIL_inner_bot	;  Char is ok, use it

	dec si			; Back up to re-read the zero byte
	mov al, Space		; Pad character

FIL_inner_bot:
	stosb			; Store this char
	inc di			; Skip attribute
	loop FIL_lp_inner	; Go do another char

; End of inner loop, skip chars until we hit a zero byte

FIL_end_of_inner:
	lodsb			; Pick up the next byte
	or al, al		; Is it zero?
	 jnz FIL_end_of_inner	;  Skip chars until we hit a zero

	inc Which_row		; Move to next row
	call Make_new_ptr	; Get a new DI
	add di, 4		; Skip over border and first column

	pop cx			; Restore outer loop counter
	loop FIL_lp_outer	; Go try for another line

	ret


	PUBLIC Check_keystrokes

Check_keystrokes:

	call Back_to_normal_ds
	call Do_mode_line	; Update the mode line
	call Use_our_own_ds

	mov di, OFFSET Our_image + (24 * 160) ; Where our copy is
	mov cx, Number_of_columns ; Size of mode line
	lds si, Mode_line_ptr	; Point to real mode line
    ASSUME ds:Nothing
	cld			; Forwards
	rep movsw		; Copy the mode line to our buffer
	push es			; Restore ds
	pop ds
    ASSUME ds:HAlloc

	call Do_the_screen	; Install image

	mov ah, 1		; Code to see if a char is ready
	int Kb			; Have BIOS tell us
	 jz Check_keystrokes	;  None, waste time on mode line

	sub ah, ah		; Zero is code to read the char
	int Kb			; Have BIOS get it for us

	call Back_to_normal_ds	; Check_Table requires normal ds
	mov al, ah		; Copy scan code to al
	mov bx, OFFSET Actions	; Action table
	mov cx, N_Characters	; Number of scan codes to check
	mov dx, OFFSET Characters ; The character table
	jmp Check_Table		; Go dispatch to the right routine

	PUBLIC Do_the_screen, Do_boop

Do_the_screen:
	mov bx, OFFSET Our_Image ; Set ptr to our screen

	mov ax, Normal_ds	; Put_Screen likes normal ds
	mov ds, ax
    ASSUME ds:DataS
	or Screen_flags, Screen_changed ; Flag that Put_Screen has to work
	call Put_Screen		; Update screen

	push es			; Reset ds
	pop ds	
    ASSUME ds:HAlloc
	ret			; Done here

Do_boop:
	call Back_to_normal_ds	; Boop requires this
	call Boop		; Give low beep
	call Use_our_own_ds	; Ours again
	jmp Check_keystrokes	; Try again

Do_Home:
	call Use_our_own_ds	; Ours again
	mov di, First_line	; Button to go to first line
	cmp di, Current_line	; Are we on it already?
	 je Do_boop		;  Yeah, complain for consistency

	mov Current_line, di	; Otherwise, go to first line
	ret

Do_End:	call Use_our_own_ds	; Ours again
	mov di, Last_top_line	; Button to go to last frame line
	cmp di, Current_line	; Are we on it already?
	 je Do_boop		;  Yeah, complain for consistency

	mov Current_line, di	; Otherwise, go to it
	ret

Do_PgUp:
	call Use_our_own_ds	; Ours again
	mov di, Current_line	; Line we are on
	cmp di, First_line	; First line we can be on
	 je Do_boop		;  Can't go up

	mov cx, Len - 3		; Maximum number of lines to move
	jmp SHORT PgUp_lp

Do_PgDn:
	call Use_our_own_ds	; Ours again
	mov di, Current_line	; Line we are on
	cmp di, Last_line	; Last line we can be on
	 je Do_boop		;  Can't go down

	mov cx, Len - 3		; Maximum number of lines to move
	jmp SHORT PgDn_lp

Do_Up:	call Use_our_own_ds	; Ours again
	mov di, Current_line	; Line we are on
	cmp di, First_line	; First line we can be on
	 je Do_boop		;  Can't go up
	jmp SHORT Up

Do_Down:
	call Use_our_own_ds	; Ours again
	mov di, Current_line	; Line we are on
	cmp di, Last_line	; Last line we can be on
	 je Do_boop		;  Can't go down
	jmp SHORT Down

CHK_bye:
	call Use_our_own_ds	; Ours again
	or HFlags, Bye_bye	; Flag the exit
	ret			; Done here

PgUp_lp:
	push cx			; Other routine clobbers this
	call Up			; Try to move up
	mov di, Current_line	; Line we are on
	cmp di, First_line	; First line we can be on
	 je CHK_pop		;  Can't go up, pop reg and return
	pop cx			; Restore reg
	loop PgUp_lp		; Do the rest
	ret			; Done here

PgDn_lp:
	push cx			; Other routine clobbers this
	call Down		; Try to move down
	mov di, Current_line	; Line we are on
	cmp di, Last_line	; Last line we can be on
	 je CHK_pop		;  Can't go up, pop reg and return
	pop cx			; Restore reg
	loop PgDn_lp		; Do the rest
	ret			; Done here

Down:	mov cx, 80		; Max number of chars to scan
	sub al, al		; Make a zero
	cld			; Forwards
	repne scasb		; Go looking for a zero byte

	mov Current_line, di	; Now pointing to beginning of next line
	ret

Up:	sub di, 2		; Skip back over terminator
	mov cx, 80		; Max number of chars to scan
	sub al, al		; Make a zero
	std			; Backwards
	repne scasb		; Go looking for a zero byte

	add di, 2		; Skip forwards over terminator
	mov Current_line, di	; Now pointing to beginning of previous line
	ret

CHK_pop:
	pop cx			; Restore reg
	ret

Back_to_normal_ds:
	push ax			; Save reg
	mov ax, Normal_ds	; Pick up addr of DataS segment
	mov ds, ax		; Copy to ds
	mov es, ax		;  and es
	pop ax			; Restore reg
	ret			;  and return

    ASSUME ds:DataS, es:DataS

Use_our_own_ds:
	push ax			; Save reg
	mov ax, HAlloc_ptr	; Pick up addr of HAlloc segment
	mov ds, ax		; Copy to ds
	mov es, ax		;  and es
	pop ax			; Restore reg
	ret			;  and return

Helper	ENDP

Do_mode_line_error PROC

	call Save_mode_line	; Ask for a save
	call PutErr_PC		; Play with mode line
	call Bombed		; Make noise
	sub ah, ah		; Code to read a character
	int Kb			; Wait until user hits key
	jmp Restore_mode_line	; Ask for a restore, ret from there

Do_mode_line_error ENDP


; Save the mode line for later restoral

Save_mode_line PROC

	push ds			; Save ds
	call Get_address	; Get new ds into ax
	mov ds, ax		; Store the new seg addr
	mov si, (Number_of_rows_PC-1)*2*Number_of_columns ; Offset of line 25
	mov di, OFFSET Saved_mode_line ; Where to copy it
	mov cx, Number_of_columns
	cld			; Forwards
	rep movsw		; Save the line
	pop ds			; Restore ds
	ret

Save_mode_line ENDP


; Restore the mode line

Restore_mode_line PROC

	push es			; Save es
	call Get_address	; Get new es into ax
	mov es, ax		; Store the new seg addr
	mov si, OFFSET Saved_mode_line ; Where copy is
	mov di, (Number_of_rows_PC-1)*2*Number_of_columns ; Offset of line 25
	mov cx, Number_of_columns
	cld			; Forwards
	rep movsw		; Restore the line
	pop es			; Restore es
	ret

Restore_mode_line ENDP


; Get address of segment of currently-displayed video page

Get_address PROC

	mov ax, 0B000h		; Mono, maybe
	cmp Which_card, 0 	; Is it?
	 je Go_back		;  Yes

	sub bh, bh		; Zero high half of bx
	mov bl, Which_page	; The page we are on in the color card
	shl bx, 1		; Double for word offset into table
	mov ax, Video_page_addresses[bx] ; Pick up the address for the page

Go_back:
	ret

Get_address ENDP

Code	ENDS

	END

