Popup text windows under DOS
---
Written for the A86 assembler

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:

Now, the problems were as follows: I decided to use the stack segment for storing old screen data. The Basepointer register (BP) was sitting along doing nothing all the times I ran my softwares. So it was time to get an application for it: the BP would point to the top of the last saved window-part. In order to keep ample space for the stack, one full 64 Kb segment was arranged for both stack and screendata. Plus a check before each window was saved in order to make sure enough stack space would remain available after the next screen would be saved.

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!
Anyway, the structure defined some keypoints for windowcontrol: the size, coordinates on the full screen, widt and height, colours and the place where the window outline was stored. Plus the place where the text for that windows is stored.
This table would accompany every window and an index register was meant to be pointing to it throughout the "life" of the window. I chose BX to perform this task. I could have assigned SI or DI, but these were already assigned for more flexible and demanding tasks.

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).
This is a flexible arrangement since it requires only 9 bytes to define a window style.
Below is an example of how such a windowing scheme is used. The main_box defines the position of th (main) window. It has the pointer to main_scrn built-in.
The main_scrn block starts with a word entry which defines how many lines will follow. Then come the lines which are all built-up x, y, text. First comes the horizontal position within the current window, next comes the vertical position and the text tops it off.

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.
We start out by loading the number of lines to process. This value is immediately stored in the CX register.

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.
The "activity" colour has yet to be implemented. Something for U?
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