Produce your own textbased windowing software.
Back in 1993 when I needed some fast windowing support for some of my programs I decided to build my own. So I
whipped out the old trusty assembler (Speedware's TASM) and debugger and set off to program the job.
Below you will find the routines which I have ported to A86, since that assembler offered more features and
was more stable.
The general idea of this software was as follows:
In order to get some structure in the whole project I decided it was time to define a structure:
The Window Information Structure.
window_information_block STRUC
win_size dw 0 ; number of chars in window
win_topleft_x dw 0 ; top left coordinates
win_topleft_y dw 0
win_width dw 0 ; width in chars (including boxmarks)
win_height dw 0 ; height
win_box_attrib db 0 ; attributes of boxmarks
win_txt_attrib db 0 ; and of windowtext
win_box_border dw single_box ; pointer to box information table
win_text dw 0FFh ; address of text for this window
ENDS
Call it clumsy, but this was my first effort in text based windowing. Hey, that gates dude started out with
windows 1.0 which was even worse!
The Boxing Information Block.
Here comes an example of a boxing information block.
single_box db ' ' double_box db '+-+| |+-+' ASCII_box db '/-\| |\-/'The first three characters (DOS style characters, ruined by Windows character sets!) define the shape of the top border of the window. The third triplet define the bottom row of the windowborder. The second triplet define the outline of the body (all lines between top- and bottom borders).
How a box is defined.
main_box dw 80 * 23 * 2, 0, 1, 80, 23
db 01E, 01F
dw single_box, main_scrn
main_scrn dw 4
db 1, 23, 'Copyrights 1996/1997:', 0
db 27, 23, 'The Brilsmurf', 0
db 45, 23, '-5012 GH-', 0
db 64, 23, 'The Netherlands', 0
Below is an example of a routine that does some printing. It starts with the custom saving of registers on the
stack. We have BX pointing to the window information block. SI is pointing to the table with the text to
print.
Next we fetch the X-coordinate, xfer it into a word, add the topleft window value and stire the result in the
(absolute) X-coordinate Scr_X. We do the same for the absolute Y-coordinate Scry_Y.
Next the characters are fetched one by one and sent to the screen. If an ASCII zero is found, the current line
is ended and a new one is started. This is repeated until all cx lines were processed.
Fill a window with some data.
pr_win: push ax, cx ; build up screen
lodsw ; si = -> table & bx = -> window information block
mov cx, ax
P1: lodsb
cbw
add ax, [bx.win_topleft_x]
mov [scr_x], ax
lodsb
cbw
add ax, [bx.win_topleft_y]
mov [scr_y], ax
mov ah, [bx.win_txt_attrib]
P2: lodsb
cmp al, 0
je >P3
call put_char
jmp P2
P3: loop P1
pop cx, ax
ret
Follow the links in this source to see where the jumps and loops end up.
I guess it's coding time by now: here's the source for an empty program that can act as a framework to start with. Just add in features and screens and you will have a fully functional user interface in no-time. OK, nothing will happen outside the PC, but who cares? As if Windows is so productive! Colours is for the masses.
Coding time!
name Empty title Een leeg programma dat alleen maar wat print; de ideale basis page 80, 120 stdin = 0 stdout = 1 tab = 9 lf = 10 cr = 13 window_information_block struc win_size dw 0 ; number of bytes in window win_topleft_x dw 0 ; top left coordinates win_topleft_y dw 0 win_width dw 0 ; width in chars (including boxmarks) win_height dw 0 ; height win_box_attrib db 0 ; attributes of boxmarks win_txt_attrib db 0 ; and of windowtext win_box_border dw 0 ; pointer to box information table win_text dw 0 ; address of text for this window ends
The Justification information template.
justification_data struc
normal db 0
shortcut db 0
activity db 0
ends
If we need to have a bar of text on top of the screen showing which action keys are allowed, this structure
comes in handy. It describes the colours of the three states of a text. The position 'normal' describes what
colour most of the text will have. Operations and functions both will have some kind of HOT key. This hot key
is highlit by means of the 'shortcut' variable. This makes a Keyword like 'M'anual, where 'M' would be the
Shortcut colour and 'anual' would be the normal colour.
clr MACRO
xor #1, #1
#EM
A handy macro for quickly clearing a register. Although it can be tricky since it sets certain flags in the
process....
The volatile data segment.
DATA SEGMENT
scr_attribute db ?
hexdec_buffer db '......'
EVEN
v_mode dw ?
v_segm dw ?
v_page dw ?
v_lines dw ?
v_cols dw ?
lin_len dw ?
scr_mul dw ?
p_size dw ?
shift_bits dw ?
scr_x dw ?
scr_y dw ?
line_24 dw ?
digits dw ?
b_digit dw ?
h_digit dw ?
scratchpad dw 32 dup (?)
last_byte equ $
CODE SEGMENT
jmp main
mul_132:
mul_40: ret
Mul_132 and Mul_40 are not used, so they are just harbours for a return instruction. What follows is a routine
that sets up variables for these text-windows to be able to do some work.
Determine the current videomode and related parameters.
get_mode: push ax, bx, dx, bp
mov ah, 0F
int 010 ; get video mode
clr ah
mov [v_mode], ax ; store video mode
mov bl, bh
clr bh
mov [v_page], bx ; and current displaypage
mov [v_segm], 0B800
cmp ax, 7
IF e mov [v_segm], 0B000 ; store video segment
cmp ax, 04E
IF nb sub ax, 04E - 8
shl ax, 1
mov bx, ax ; use bx as index into several tables:
mov ax, [bx + video_rows]
mov [v_lines], ax ; store number of lines
mov ax, [bx + video_columns]
mov [v_cols], ax ; store number of chars per line
shl ax, 1
mov [lin_len], ax ; store number of bytes per line
mov ax, [bx + video_math]
mov [scr_mul], ax ; store dedicated multiplication routine
mov ax, [bx + video_page_size]
mov [p_size], ax
mov al, [bx + video_shifters]
mov [shift_bits], al
clr ax
mov [scr_x], ax ; initialize screen coordinates
mov [scr_y], ax
mov ax, [v_lines]
dec ax
mul [v_cols]
mov [line_24], ax ; compare-address for printing LF's
pop bp, dx, bx, ax
ret
mul_80: push ax, cx ; calculate char address in 80 columns
mov ax, [scr_y]
mov cl, 4
shl ax, cl
mov bx, ax ; bx = 16 x SCR_Y
shl ax, 2 ; ax = 64 x SCR_Y
add ax, bx ; ax = 80 x SCR_Y
add ax, [scr_x]
shl ax, 1
mov bx, [v_page]
mov cl, [shift_bits]
shl bx, cl
add bx, ax
pop cx, ax
ret ; with result in BX
put_char: push bx, es ; print one character/attribute word at (scr_x, scr_y)
mov es, [v_segm]
call word ptr [scr_mul]
es mov [bx], ax
inc [scr_x]
pop es, bx
ret
spaces: push ax ; print cx spaces from <scr_x, scr_y>
mov ah, [scr_attribute]
mov al, ' '
L0: call put_char
loop L0
pop ax
ret
cls: pushf ; clear text screen
push ax, cx, di, es
mov es, [v_segm]
mov di, [v_page]
mov cl, [shift_bits]
shl di, cl
mov ax, [v_cols]
mul [v_lines]
mov cx, ax
mov ah, [scr_attribute]
mov al, ' '
rep stosw
pop es, di, cx, ax
popf
ret
calc_win_addr: push cx ; calculate address of topleft window in v_page
mov ax, [v_page]
mov cl, [shift_bits]
shl ax, cl
mov cx, ax
mov ax, [bx.win_topleft_y]
mul [v_cols]
add ax, [bx.win_topleft_x]
shl ax, 1
add ax, cx
pop cx ; result in ax
ret
put_row: push ax, cx, si ; print cx character/attribute words at (scr_x, scr_y)
mov ah, [bx.win_box_attrib] ; bx = -> window information block
lodsb ; si = -> boxing information block
call put_char
sub cx, 2
lodsb
P1: call put_char
loop P1
lodsb
call put_char
pop si, cx, ax
ret
The actual datamoving routines behind the windows.
What follows is the source for storing the old contents of a screen section in the stack segment. First we do some math, set up the registers, initialize for the use of a double counterloop and move the screencontents around (from the video segment to the stack segment).
At the end of the screen-data I put the 5 most important words that are needed to restore the screen contents: the size of the window, it's original location on screen and the geometry of the newly opened window.
First we store the contents of the 'overlaid' window.
store_window: pushf ; bx = ptr -> window information block
push ax, cx, dx, si, di, es, ds
cld
call calc_win_addr ; uses window_information_block
mov si, ax
mov di, bp
mov dx, [v_cols]
shl dx, 1
mov ax, ss
mov es, ax
mov ax, [v_segm]
mov ds, ax
cs mov cx, [bx.win_height]
S0: push cx
cs mov cx, [bx.win_width]
push si
rep movsw ; store one box-row of text/attribute words
pop si
add si, dx
pop cx
loop S0
pop ds
mov si, bx
mov cx, 5
rep movsw ; add first five words of window info block to buffer
mov bp, di ; adjust bp
pop es, di, si, dx, cx, ax
popf ; restore variables and exit
ret
draw_box: ; make the windowborder, starting at (scr_x, scr_y)
push ax, cx, dx ; bx = -> window information block
mov dx, [scr_x] ; si = -> box information block
mov cx, [bx.win_width]
mov ax, cx ; store boxwidth
call put_row ; print one row
add si, 3
mov [scr_x], dx
inc [scr_y]
mov cx, [bx.win_height]
sub cx, 2
jcxz >D1
D0: push cx
mov cx, ax
call put_row
mov [scr_x], dx
inc [scr_y]
pop cx
loop D0
D1: add si, 3
mov cx, ax
call put_row
pop dx, cx, ax
ret
make_box: ; compose box around pop-up window
push ax, si, scr_x, scr_y ; bx = -> window information block
mov ax, [bx.win_topleft_x]
mov [scr_x], ax
mov ax, [bx.win_topleft_y]
mov [scr_y], ax
mov si, [bx.win_box_border]
call draw_box ; do the actual boxing
pop scr_y, scr_x, si, ax
ret
pop_up_window: push ax ; bx = -> window information block
mov ax, sp
sub ax, 256 ; minimum extra space for stack
sub ax, bp
cmp ax, [bx.win_size]
ja >P1
stc
pop ax
ret
P1: call store_window
call make_box
clc
pop ax
ret
Time to prepare for a restored view.
At this point we must be able to restore a saved window. We start out by checking if there is some data on the screenstack at all. If so, the math is started to find out where to put it to and two counterloops are setup. Next the data are moved by means of a REP MOVSW operation.
restore_window: ; close window and restore screen
push ax, bx, cx, dx, si, di, es
pushf
cmp bp, 0
jne >R1
popf
stc
R0: pop es, di, si, dx, cx, bx, ax
ret
R1: mov bx, offset scratchpad
mov ax, [bp - 8] ; topleft x
mov dx, [bp - 6] ; topleft y
mov cx, [bp - 4] ; window width
mov si, [bp - 2] ; window height
add ax, cx
dec ax ; compose bottom-right x
add dx, si
dec dx ; and bottom-right y
mov [bx + 2], ax ; make dummy window information block
mov [bx + 4], dx
mov [bx + 6], cx
mov [bx + 8], si
call calc_win_addr
mov di, ax
mov es, [v_segm]
mov cx, si
mov si, bp
sub si, 12 ; subtract saved header
std
mov ax, [v_cols]
shl ax, 1
mov dx, [bx.win_width]
push ds
mov bx, ss
mov ds, bx
R2: push cx
mov cx, dx
push di
rep movsw
pop di
sub di, ax
pop cx
loop R2
pop ds
mov bp, si
add bp, 2
popf
clc
jmp R0
pr_win: push ax, cx ; build up screen
lodsw ; si = -> table & bx = -> window information block
mov cx, ax
P1: lodsb
cbw
add ax, [bx.win_topleft_x]
mov [scr_x], ax
lodsb
cbw
add ax, [bx.win_topleft_y]
mov [scr_y], ax
mov ah, [bx.win_txt_attrib]
P2: lodsb
cmp al, 0
je >P3
call put_char
jmp P2
P3: loop P1
pop cx, ax
ret
clear_pad: ; clear scratchpad area
push ax, cx, di, es
pushf
cld
mov di, offset scratchpad
mov cx, (type scratchpad)/2
mov ax, ds
mov es, ax
clr ax
rep stosw
popf
pop es, di, cx, ax
ret
do_menu_bar: push bx, cx, dx, scr_x, scr_y, si ; put a menubar on top of the screen
clr cx ; si = -> menu-text (ASCIIZ)
clr bx ; CAPITALS are given different attributes
add si, type justification_data ; underscores '_' are printed as single spaces
D0: lodsb
cmp al, 0
je >D2 ; zero found => end of string
cmp al, ' '
jne >D1
inc cx ; increment space-counter
jmp D0
D1: inc bx ; increment character counts
jmp D0
D2: pop si
mov ax, [v_cols]
sub ax, bx ; ax = # of spaces in line
div cl
mov dx, ax ; dl = spaces between items
clr ax ; dh = spaces at end of line
mov [scr_x], ax
mov [scr_y], ax
mov bx, si
add si, type justification_data
D3: lodsb
cmp al, ' '
jne >D4
push cx
mov cl, dl
clr ch
push word ptr [scr_attribute]
mov al, [bx.normal]
mov [scr_attribute], al
call spaces
pop word ptr [scr_attribute]
pop cx
jmp D3
D4: cmp al, 0
je >D6
mov ah, [bx.normal]
cmp al, 'A'
jb D5
cmp al, 'Z'
IF na mov ah, [bx.shortcut]
D5: cmp al, '_'
IF e mov al, ' '
call put_char
jmp D3
D6: clr cx
mov cl, dh
jcxz >D7
push word ptr scr_attribute
mov al, [bx.normal]
mov [scr_attribute], al
call spaces
pop word ptr [scr_attribute]
D7: pop scr_y, scr_x, dx, cx, bx
ret
do_manual:
do_adres:
do_write:
do_data:
do_port:
do_quit:
do_read: ret
init: mov b[scr_attribute], 01A
mov ax, 3
int 010
call get_mode
ret
Copyrights.... they pet my ego.. :o)
copyrights:
mov dx, offset dontcopythis
mov cx, lendon
mov bx, stdout
mov ah, 040
int 021
ret
main: mov ax, cs
add ax, (last_byte + 15)/16
mov ss, ax
mov sp, 0FFFE
call init
call cls
mov bx, offset main_box
call make_box
mov si, [bx.win_text]
call pr_win
mov si, offset main_bar
call do_menu_bar
L0: mov ax, 0C07
int 021
cmp al, ' '
jne L0
call cls
call copyrights
mov ax, 04C00
int 021
single_box db 'ÚÄ¿³ ³ÀÄÙ'
double_box db 'ÉÍ»º ºÈͼ'
mixed_box1 db 'Õ͸³ ³Ô;'
mixed_box2 db 'ÖÄ·º ºÓĽ'
other_box db 'þ=þ| |þ=þ'
solid_box db 'ÛßÛÛ ÛÛÜÛ'
gate_box1 db 'ÅÄų ³ÅÄÅ'
gate_box2 db 'ÎÍκ ºÎÍÎ'
gate_box3 db '#=#ð ð#=#'
single_left db 'Ãij ³ÀÄÁ'
single_mid db 'Âij ³ÁÄÁ'
single_rite db 'ÂÄ´³ ³ÁÄÙ'
even
main_box dw 80 * 23 * 2, 0, 1, 80, 23
db 01E, 01F
dw single_box, main_scrn
main_bar db 00E, 002, 016
db 'DIT_IS_HET: Spatie=exit Any key & Esc = niks', 0
main_characters db 'ADLMPQSX', 0
even
action dw do_adres, do_data, do_read, do_manual, do_port, do_quit
dw do_write, do_quit
main_scrn dw 4
db 1, 23, 'Copyrights 1996/1997:', 0
db 27, 23, 'Jan Verhoeven', 0
db 45, 23, '-5012 GH 272-', 0
db 64, 23, 'The Netherlands', 0
dontcopythis:
db 'This software was developed by Jan Verhoeven, zipcode NL-5012 GH 272.', cr, lf
db "Would you like to know more about this or other programs, please write to", cr, lf, lf
db tab, 'Jan Verhoeven', cr, lf
db tab, 'NL-5012 GH 272', cr, lf, lf
lendon equ $ - dontcopythis
video_columns dw 40, 40, 80, 80, 0, 0, 0, 80, 80, 132, 132, 132
video_rows dw 25, 25, 25, 25, 0, 0, 0, 25, 60, 60, 25, 43
video_math dw mul_40, mul_40, mul_80, mul_80, 0, 0, 0
dw mul_80, mul_80, mul_132, mul_132, mul_132
video_page_size dw 00800, 00800, 01000, 01000, 00000, 00000
dw 00000, 01000, 04000, 04000, 02000, 04000
video_shifters db 11, 11, 12, 12, 0, 0, 0, 12, 14, 14, 13, 14
That's it for Windows. Now you roll your own.
You can access the sourcecode of this program via the download link in the navigator (in the rightside frame).
Click here to download the sourcecode.
Page created in the 20th century,
Page equipped with FroogleBuster technology