title POP-CAL - popup calendar for 1583 - 9999 AD.
comment ~
          Use Alt-C, to pop up and terminate calendar generator.
          Use left/right cursor keys to change months.
          Use Up/Down cursor keys to change years.
        ~
cseg     segment
         assume cs:cseg
;-----------------------------------------------------------------------------
ppfdata         struc           ;Variable data is stored in PPF area.
rom16h          dw      ?,?     ;Holds old Interrupt 16h BIOS vector.
savss           dw      ?       ;Holds caller's stack segment
savsp           dw      ?       ;Holds caller's stack pointer
video_seg       dw      ?       ;Holds base segment of video buffer
mnth            db      ?       ;calendar's month
year            dw      ?       ;calendar's year
weekday         db      ?       ;1st day of month 0=Sun,,,,6=Sat
left            dw      ?       ;Offset to left edge of calendar, row 0
hilite          db      ?       ;Calendar display attribute.
busy            db      ?       ;Busy flag, for our stack
crt_mode        db      ?       ;Holds current crt mode
crt_cols        dw      ?       ;Number of columns on screen
status_port     dw      ?       ;video card status port address
ppfdata         ends
                org     100h-status_port
v    ppfdata <>                 ;Locate ppfdata and assign prefix "v".
;-----------------------------------------------------------------------------
        org     100h
begin:  jmp     Install         ;Initialize calendar; go resident.
	db	"Copyright 1986 Ziff-Davis Publishing Co.",1Ah
days    db      'SunMonTueWedThrFriSat'
months  db      'JanFebMarAprMayJunJulAugSepOctNovDec'
numdays db      31,28,31,30,31,30,31,31,30,31,30,31 ;Jan-Dec days/month
bios            dw      40h     ;Points to BIOS data segment
scan_code       dw      2e00h   ;(Alt)(C) scan code to start/stop program.
;---------------- Intercept keystroke reading --------------------------------
int16h: sti                             ;Turn interrupts back on.
        cmp     ah,0                    ;See if was request for an actual key.
        je      come_back               ;If so, we'll check it first.
skip_us:jmp     dword ptr v.rom16h      ;Otherwise, let go straight to caller.
come_back:
        cmp     v.busy,0                ;Is calendar routine busy?
        jne     skip_us                 ;If so, pass all keys along.
getkey: pushf                           ;Else, call BIOS keyboard routine,
        call    dword ptr v.rom16h      ;so it will return the key to us.
        cmp     ax,scan_code            ;Is scan code correct?
        je      now_busy                ;If so, go to work.
        iret                            ;Else just pass on key to caller.
now_busy:
        mov     v.busy,1                ;Set busy flag, to protect our stack.
        mov     v.savss,ss              ;Save caller's stack segment.
        mov     v.savsp,sp              ;save caller's stack pointer.
        mov     sp,cs
        cli                             ;Avoid interrupt right now.
        mov     ss,sp                   ;Reset stack to our code segment.
        mov     sp,offset v.rom16h      ;Start our stack below ROM16 addr.
        sti
        push    ax
        push    bx
        push    cx
        push    dx
        push    bp
        push    si                      ;Save all user's registers.
        push    di
        push    ds
        push    es
        call    begin_calendars         ;Go do the calendars.
        pop     es                      ;When finished,...
        pop     ds
        pop     di
        pop     si
        pop     bp                      ;Restore all user's registers
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        cli
        mov     ss,v.savss              ;restore old stack
        mov     sp,v.savsp
        sti
        mov     ah,0                    ;Reset AH to BIOS key read function.
        mov     v.busy,0                ;Allow reuse of calendar stack.
        jmp     getkey                  ;Go get new key from BIOS.
;---------------  Read keys input.  Process calendars.  ----------------------
begin_calendars:
        push    cs
        push    cs
        pop     ds              ;Set DS and ES to our code segment.
        pop     es
        cld                     ;Set for standard "up" direction.
        assume  ds:cseg         ;Let Assembler use DS addressing, now.
        call    wndo            ;Save Screen data. Put old calendar on it.
        jmp     showcal         ;Go fill in calendar, in case none made yet.
nxtcal: mov     v.mnth,dh       ;Store new month value.
        mov     v.year,cx       ;Store new year.
showcal:call    fill            ;Format and display new calendar.
input:  mov     ah,0            ;Wait for key stroke.
        int     16h
        cmp     ax,scan_code    ;Another (Alt)(C)?
        jne     getdate         ;If not, initialize CX (year) and DH (month).
exit:   call    wndo            ;Restore original screen data and
        ret                     ;  go restore caller's stack... all done.
getdate:mov     dh,v.mnth       ;Load calendar month into DH.
        mov     cx,v.year       ;Load calendar year into CX.
trylft: cmp     ah,75           ;Was key a left arrow?
        jne     tryrgt          ;If not, go see if was a right arrow.
        dec     dh              ;  If a left arrow, reduce month by 1.
        jns     nxtcal          ;  When pass Jan, switch
        mov     dh,11           ;  to Dec of prior year.
        dec     cx              ;  Reduce year by one.
        jmp     chkloyr         ;  Go check that its still in range.
tryrgt: cmp     ah,77           ;Right arrow? (increase month)
        jne     tryup           ;If not, see if an Up arrow.
        inc     dh              ;  Was right arrow, so increase month.
        cmp     dh,11           ;  See if past December.
        jng     nxtcal          ;  If not, go make the calendar.
        mov     dh,0            ;  If was, reset month to January
        inc     cx              ;  and move up to next year.
        jmp     chkhiyr         ;  Then insure year is still in range.
tryup:  cmp     ah,72           ;Up arrow?  (increase year)
        jne     trydwn          ;If not, see if was a down key.
        inc     cx              ;   If Up, increase year by 1.
chkhiyr:cmp     cx,10000        ;   Year can only be 4 digits.
        jge     input           ;   Ignore key, if date goes out of range.
        jmp     nxtcal          ;   Otherwise go make new calendar.
trydwn: cmp     ah,80           ;Down arrow? (decrease year)
        jne     input           ;If not, ignore key and go get new one.
        dec     cx              ;   Down key reduces year by 1.
chkloyr:cmp     cx,1582         ;Calendar calculations only good for 11/1582
        jle     input           ;   on, so ignore key, if year now < 1583.
        jmp     nxtcal          ;   Otherwise, go make new calendar.
;------------------  Fill in the new calendar   ------------------------------
fill:   call    firstday        ;Calculate weekday of 1st of month.
        mov     es,v.video_seg  ;Point ES to write to screen.
        mov     al,v.mnth       ;Put current month into AL.
        cbw                     ;Zeros out AH.
        mov     cx,3            ;Zero out CH. Put 3 in CL.
        mul     cl              ;AX = # bytes to current month's name
        mov     si,offset months;   from start of "months".
        add     si,ax           ;SI=offset to month's name
        mov     di,v.crt_cols   ;Put screen width (# columns) in DI.
        add     di,v.left       ;Move to left edge, second row.
        add     di,22           ;Move on to center month in the row.
movname:lodsb                   ;Load character from month's name.
        call    writebyte       ;Output char of month's name to screen.
        loop    movname         ;Do three times, since cx=3.
        add     di,2            ;Skip a space, after the month's name.
        mov     ax,v.year       ;Load year into AX.
        mov     bl,100          ;Will use to split year into centuries
        div     bl              ;  portion and remaining years in century.
        mov     bh,1            ;Set flag, so will retain leading zero.
        call    unpack          ;Display the centuries portion of year.
        mov     al,ah           ;Put remainder in al.
        call    unpack          ;Display remaining 2 digits in year.
setdi:  mov     ax,v.crt_cols   ;Load current screen width.
        mov     bl,4            ;Mult by # rows to skip.
        mul     bl              ;Calculate offset to 4th row of calendar.
        add     ax,4            ;Skip in 2 columns on that row.
        mov     di,v.left       ;Set to offset of top left corner of calendar.
        add     di,ax           ;Move to location of 1st date on screen.
        mov     dl,v.weekday    ;Retrieve week day of 1st of cal's month.
        mov     cl,dl           ;Store as count of days to blank in 1st week.
        mov     dh,6            ;DH = # weeks left to fill in.
        jcxz    start_current_month ;If Sunday, CX = 0 and no blanking needed.
blnkrw1:call    blank2          ;Blank out 1st part of 1st week.
        loop    blnkrw1
start_current_month:
        mov     cl,7            ;Number of days to display in 1st week
        sub     cl,dl           ;   = 7 - weekday of 1st day.
        mov     bl,v.mnth       ;Set index for days-per-month array.
        xor     bh,bh           ;Set flag to suppress leading zeros.
        mov     dl,numdays[bx]  ;Get # days in current month.
        mov     al,1            ;Set AL to 1st day of month.
showdays:
        call    unpack          ;Format and display the date value.
        inc     al              ;Increment to next day.
        add     di,4            ;Skip to next day position.
        dec     dl              ;Reduce # days left in month.
        jz      blnkrst         ;If last day, blank rest of the 6 weeks.
        loop    showdays        ;Otherwise continue displaying dates in week.
        call    nxtweek         ;When reach end of a week, space to next one.
        jmp     showdays        ;  and continue on new row of calendar.
blnkrw4:call    blank2          ;Blank out remaining days in week.
blnkrst:loop    blnkrw4         ;Blank to end of current week.
        call    nxtweek         ;Relocate DI to 1st day of next week.
        jnz     blnkrw4         ;If not past last week, go blank week out.
        push    cs              ;When all done, restore ES to CS.
        pop     es
        ret                     ;Return to keyboard reading routine.
;---------------------- Move DI to 1st date in next week ---------------------
nxtweek:add     di,v.left       ;Move to left edge of next row of calendar.
        add     di,4            ;Move in an additional 2 columns.
        mov     cl,7            ;Restart week count.
        dec     dh              ;Reduce weeks left in calendar.
        ret
;---------------------- Compute weekday of 1st day of month ------------------
firstday:
        mov     si,v.year               ;Hold year in SI, of quick access.
        mov     numdays[1],28           ;Assume this isn't a leap year.
        test    si,3                    ;Now see if year divisible by 4.
        jnz     getday                  ;If not, was actually not a leap year.
feb29:  mov     numdays[1],29           ;Else was leap year with 29 days.
getday: mov     bx,6                    ;BX=Saturday,(weekday of 1/1/1583)
        mov     ax,si                   ;Calendar year to AX.
        sub     ax,1583                 ;Calc. # years since our base year.
        add     bx,ax                   ;Calendar advances 1 weekday per
                                        ;  year unless leap years intervene.
        add     ax,2                    ;Adj diff, so even #, if the calendar
        mov     cx,4                    ;  year was year after a leap year.
        cwd                             ;Zero out DX.
        div     cx                      ;Calculate # leap years before this.
        add     bx,ax                   ;Add 1 day to week, for each leap yr.
        cmp     si,1700                 ;Things work normally 'til 1700, which
        jl      cnvrt                   ;not a leap year, per Gregorian cal.
        mov     ax,si                   ;Year back to AX, again.
        sub     ax,1600                 ;Get # years since 1600.
        mov     cl,100
        cwd                             ;Convert to number of centuries.
        div     cx
        cmp     dx,0                    ;If remainder=0 this is centennial yr.
        jne     deduct
        test    ax,3                    ;If century is evenly divisible by
        jz      decax                   ; 400, it is also still a leap year.
        mov     numdays[1],28           ;Other centenial years have 28 days.
decax:  dec     ax                      ;If centenial yr, sub 1 from centuries
deduct: sub     bx,ax                   ;Subtract 1 weekday per century.
        cwd
        mov     cl,4
        div     cx                      ;Calculate centuries mod 400.
        add     bx,ax                   ;Add back 1 day per 400 years.
cnvrt:  mov     si,offset numdays       ;Add in the days per month, this yr.
        xor     ah,ah                   ;First clear hi byte of accumulator
        mov     cl,v.mnth               ;Load cx with month counter
        jcxz    final                   ;If January, are ready for final calc.
addmnth:lodsb                           ;Get low byte into accumulator.
        add     bx,ax                   ;Add to days count.
        loop    addmnth                 ;Continue until prior months added in.
final:  mov     ax,bx                   ;AX=# days advanced since 1/1/1583.
        cwd                             ;Clear out DX.
        mov     cl,7                    ;Find # full weeks since 1583.
        div     cx                      ;DL=weekday of 1st of calendar's month
        mov     v.weekday,dl            ;Save results of this work.
        ret
;-------------- Convert binary # in AL to ASCII and output to ES:DI ----------
unpack: push    ax              ;(AL should binary # from 0-99.)
        push    bx
        aam                     ;Divide al by 10.
        add     ax,3030h        ;Convert digits to ASCII.
        cmp     ah,30h          ;See if 10's digit was a zero.
        jne     putnum          ;If not, go output it to screen.
        cmp     bh,1            ;If BH=1, leading zero is ok, so
        je      putnum          ;   go write it.  Otherwise, replace
        mov     ah,20h          ;   leading zero with a blank.
putnum: mov     bl,al           ;Need to write high order digit first,
        mov     al,ah           ;  so save low digit and move hi into AL.
        call    writebyte       ;Output 10's digit to screen.
        mov     al,bl           ;Put 1's digit back in AL.
        call    writebyte       ;Write 1's digit.
        pop     bx              ;Restore BX.
        pop     ax              ;Retrieve old binary value.
        ret                     ;All done.
;------------------- Blank out 2 digit calendar date field.  Update DI. ------
blank2: mov     al,20h          ;Put a blank in AL
        call    writebyte       ;Write a blank over 1st digit.
        call    writebyte       ;Write a blank over 2nd digit.
        add     di,4            ;Skip 2 spaces, to next date.
        ret
;------------------- Write char AL on screen at ES:DI. Update DI. ------------
writebyte:
        push    dx                      ;Save DX.
        mov     dx,v.status_port        ;Reset to video status port address.
        mov     ah,al                   ;Move character to AH, for now.
on1:    in      al,dx                   ;Wait until not doing a
        test    al,1                    ;  horizontal scan retrace.
        jnz     on1
        cli
on2:    in      al,dx                   ;Now wait until next horizontal
        test    al,1                    ;  retrace begins, so KNOW will have
        jz      on2                     ;  entire retrace time period to work.
        mov     al,ah                   ;Retrieve character from AH.
        stosb                           ;Store character on screen.
        sti
        inc     di                      ;Skip over attribute byte on screen.
        pop     dx                      ;Restore old DX value.
        ret
;---------------------- Switch memory field with screen data -----------------
wndo:   call    getprms                 ;Get current video parms
        mov     dx,v.status_port        ;Point DX to Status Port.
        sub     dx,2                    ;Back up to control port.
        mov     es,bios                 ;Point to ROM BIOS data segment
        mov     bh,es:[65h]             ;Get current setting of video card.
        mov     al,bh                   ;Move to AL, for output, after reset.
        and     al,0f7h                 ;Turn off video enable bit only.
        out     dx,al                   ;Turn off screen.
        mov     si,offset data_strt     ;Point to calendar data in memory.
        mov     di,v.left               ;Set DI=upper left corner of calendar.
        mov     es,v.video_seg          ;Set ES to video buffer segment.
        mov     bl,11                   ;11 rows in the calendar.
nxtlin: mov     cx,30                   ;There are 30 words per calendar row.
getchr: mov     ax,es:[di]              ;Get next word from screen buffer.
        movsw                           ;Move data word to screen.
        mov     [si-2],ax               ;Store screen character in our data.
        loop    getchr                  ;Continue across row.
        add     di,v.left               ;Move to start of next calendar row.
        dec     bl                      ;Reduce rows-to-go count.
        jnz     nxtlin                  ;Do next row, if more to go.
        mov     al,bh                   ;Restore video setting to original
        out     dx,al                   ;  and turn on screen again.
        push    cs
        pop     es                      ;Restore ES to our code segment.
        ret
;------------------- Get video display parameters ----------------------------
getprms:push    es
        mov     es,bios                 ;Point ES to BIOS data segment.
        mov     ax,es:[4ah]             ;Put current column width.
        shl     ax,1                    ;Multiply by 2, for attribute bytes.
        mov     v.crt_cols,ax           ;Store in our data, for easy access.
        sub     al,30*2                 ;Back off from right side, by width of
        mov     v.left,ax               ;calendar. Equals offset to left edge.
        mov     ax,es:[63h]             ;Get base address of video card.
        add     ax,6                    ;Calculate status port address and
        mov     v.status_port,ax        ;  save for checking horiz scans.
        mov     v.hilite,70h            ;Black on white background.
        mov     bl,es:[49h]             ;Get video mode into BL.
        cmp     bl,7                    ;In Monochrome video mode?
        mov     ax,0B000h               ;Monochrome buffer seg at 0B000h.
        je      setseg                  ;If monochrome, go store video seg.
        mov     ax,0B800h               ;If graphics card, video seg=0B800h.
        test    bl,1                    ;1,3 are color text on graphics card.
        jz      setseg
        mov     v.hilite,1fh            ;Use bright white on blue, if color.
setseg: mov     v.video_seg,ax          ;Store video_segment value
        pop     es                      ;Restore Es.
        ret                             ;Done.
;------------------ Create calendar form and become resident. ---------------
Install:call    getprms                 ;Call, to get correct color attribute.
        mov     di,offset data_strt     ;Point to end of our code segment.
        mov     ah,v.hilite             ;Set attribute byte in AH.
        mov     al,20h                  ;Will store blanks in calendar area.
        mov     cx,11*30                ;11 rows of 30 columns each.
        rep     stosw                   ;Blank out calendar work area
        push    di                      ;Save end of calendar offset.
        mov     si,offset days          ;Now store day's names in calendar.
        mov     bl,7                    ;Set names-to-go counter to 7.
        mov     di,offset data_strt     ;Point to start of calendar
        add     di,3*30*2+4             ;Skip in 3 rows + 2 columns.
movday: mov     cx,3                    ;There are 3 chars per name.
movchr: movsb                           ;Transfer character into calendar.
        inc     di                      ;Skip over attribute byte.
        loop    movchr                  ;Continue for three characters.
        inc     di
        inc     di                      ;Add 2 to DI, to skip space.
        dec     bl                      ;Reduce names-to-go count.
        jnz     movday                  ;Continue until all stored.
        mov     ah,2ah                  ;Get current date from DOS.
        int     21h
        dec     dh                      ;Convert months,so Jan=0, etc.
        mov     v.mnth,dh               ;Save month and year values.
        mov     v.year,cx
go_resident:
        xor     ax,ax
        mov     es,ax                   ;Set ES to 0.
        mov     ax,es:[16h*4]
        mov     v.rom16h,ax             ;Store rom interrupt 16h address.
        mov     ax,es:[16h*4+2]
        mov     v.rom16h+2,ax
        mov     ax,offset int16h
        cli
        mov     es:[16h*4],ax           ;Point Interrupt 16 to our code.
        mov     es:[16h*4+2],cs
        sti
        pop     dx                      ;Retrieve address of end of data.
        int     27h                     ;Now become resident.
;----------------- Calendar data area  --------------------------------
data_strt        equ     this word
cseg    ends
        end     begin
                                                                                                                               