;----------------------------------------------------------------------------;
;                               winclock.ASM                                 ;
;----------------------------------------------------------------------------;
;                                                                            ;
; Sample MASM 6.1 program using the Windows API Interface. It heavily relies ;
; on the MASM 6.x high level constructs like INVOKE and .IF/.ELSE/.ENDIF.    ;
; The program, although simple enough, actually does quite a bit. Double     ;
; clicking on the window will bring up a popup menu with other options,      ;
; including setting an alarm.                                                ;
;                                                                            ;
; Adding to this code should be simple enough. New features could include    ;
; making sure that fonts are displayed with the appropiate aspect ratio.     ;
;                                                                            ;
; Note that this program doesn't attempt to teach how to program in Windows. ;
; For that you should consult the Windows Software Developers Kit or other   ;
; reference books.                                                           ;
;                                                                            ;
;----------------------------------------------------------------------------;

                .model  small, pascal, nearstack
                .286

                ?WINPROLOGUE = 1
                NOKERNEL = 1
                NOSOUND = 1
                NOCOMM = 1
                NODRIVERS = 1
                include win.inc                 ; Converted from WINDOWS.H

;----------------------------------------------------------------------------;
;                        Prototypes & External Definitions                   ;
;----------------------------------------------------------------------------;

WinMain          PROTO PASCAL, hInstance:HANDLE,  hPrevInstance:HANDLE,
                        lpszCmdLine:LPSTR, nCmdShow:SWORD
WndProc          PROTO FAR PASCAL,  :HWND, :WORD, :SWORD, :SDWORD
Initialize       PROTO,             :PINT, :PINT, :PINT,  :PINT
Resize           PROTO,             :HWND
SetupTimer       PROTO,             :HWND, :WORD

extern __astart:proc            ; When Windows load an app, it expects astart
                                ; to have the necessary start-up code. We get
                                ; astart from APPENTRY.ASM

;----------------------------------------------------------------------------;
;                             Numeric Equates                                ;
;----------------------------------------------------------------------------;

INIT_FONT        EQU    30t             ; initial font height
MAX_HEIGHT       EQU    100             ; Maximum Font Height
TOP_CORNER       EQU    1               ; Set to 0 for Lower-Right Corner Clock

IDM_DATE         EQU    1t              ; definitions for Menu items
IDM_ALARM        EQU    2t
IDM_SET          EQU    3t
IDM_MENU         EQU    4t
IDM_EXIT         EQU    5t
IDM_ABOUT        EQU    6t
IDM_MINIMIZE     EQU    7t
IDM_ONTOP        EQU    8t
AMPM             EQU    3t              ; length of am/pm strings
SCROLLCHILD      EQU    30t             ; identifies scroll: any number will do
MAXTIME          EQU    1439t           ; max # of minutes
TIMER_SECS       EQU    1000t           ; timer interval: 1000 mill = 1 second
TIMER_MINS       EQU    60000t          ; interval for 1 minute: 1000 * 60
ICON_LEN         EQU    7t              ; # of chars to display icon time

;----------------------------------------------------------------------------;
;                               Data Segments                                ;
;----------------------------------------------------------------------------;

                .const          

szAppName       SBYTE   "WINClock",0                   

DateFmt         SBYTE   " %s %2d, %04d ",13,10,0
TimeFmt         SBYTE   " %d:%02d:%02d %s ",0
IconFmt         SBYTE   " %d:%02d ",0
AlarmFmt        SBYTE   " %d:%02d %s ",0
szAMPM          SBYTE   "am",0,"pm",0                   
szTooManyTimers SBYTE   "Too many clocks or timers!",0
szAlarmMsg      SBYTE   "Remember your Appointment!",0
szMonths        SBYTE   "Jan",0,"Feb",0,"Mar",0,"Apr",0,"May",0,"Jun",0,
                        "Jul",0,"Aug",0,"Sep",0,"Oct",0,"Nov",0,"Dec",0
szDateCmd       SBYTE   "Enable &Date", 0
szSetCmd        SBYTE   "S&et Alarm",0
szAlarmCmd      SBYTE   "Enable &Alarm", 0
szOnTopCmd      SBYTE   "Always on &Top",0
szMinimizeCmd   SBYTE   "&Minimize", 0
szExitCmd       SBYTE   "E&xit", 0
szAboutCmd      SBYTE   "About &Clock...", 0
szAboutText     SBYTE   "Assembler Program Using the Windows API", 0
szDisplay       SBYTE   "DISPLAY", 0    ; to get a handle to entire display
szScrollBar     SBYTE   "scrollbar", 0  ; to create scrollbar 'class'                

                .data

EnableDate      BYTE    MF_CHECKED      ; Date initially enabled
EnableAlarm     BYTE    MF_UNCHECKED    ; Alarm initially disabled
AlwaysOnTop     BYTE    MF_CHECKED      ; Window will be on topmost
Iconized        BYTE    FALSE           ; Clock initially Normal Size
TestAlarm       BYTE    FALSE           ; so that we know to test for the alarm
SetAlarm        BYTE    FALSE           ; signalling while we set the alarm
AlarmTime       WORD    (MAXTIME/2)+1   ; initial alarm time: 12:00 pm

                .data?

cBuffer         SBYTE   40 dup (?)      ; buffer to receive text for drawing
hMenu           HMENU   ?               ; handle to Popup Menu
logfont         LOGFONT { }             ; logical font structure
hWndScrol       HWND    ?               ; handle to the scroll window
TextRect        RECT    { }             ; rectangle to draw text in

;----------------------------------------------------------------------------;
;                               Code Segment                                 ;
;----------------------------------------------------------------------------;

                .code

;----------------------------------------------------------------------------;
;                               WinMain                                      ;
;----------------------------------------------------------------------------;
;                                                                            ;
; Main routine called by Windows in program start. If no previous instances, ;
; sets up a window class and registers it. Initializes the program with      ;
; Initialize, creates a top window with the coordinates from Initialize, sets;
; up a child scroll bar control, and sets up the message loop.               ;
;                                                                            ;
;----------------------------------------------------------------------------;

WinMain         PROC,   hInstance:HANDLE,  hPrevInstance:HANDLE,
                        lpszCmdLine:LPSTR, nCmdShow:SWORD
                LOCAL   msg:MSG, wndclass:WNDCLASS, xStart:SWORD,
                        yStart:SWORD, xClient:SWORD, yClient:SWORD

                ; Local variables: msg: message to be used in the message loop
                ;                  wndclass: temp. to store window class
                ;                  x,y Start-Client: Size of Initial Window
;
;--- Check for previous instances
;
                .IF (hPrevInstance == 0)

                        lea     di, wndclass    ; because we use a NEARSTACK,
                        ASSUME  di:PTR WNDCLASS ; ss=ds

                        mov     ax, CS_HREDRAW OR CS_VREDRAW OR CS_DBLCLKS
                        mov     [di].style, ax 
                        mov     WORD PTR [di].lpfnWndProc,   LROFFSET WndProc
                        mov     WORD PTR [di].lpfnWndProc+2, SEG WndProc
                        xor     ax,ax
                        mov     [di].cbClsExtra, ax
                        mov     [di].cbWndExtra, ax

                        mov     [di].hIcon, ax  ; null icon: we will draw it

                        mov     ax, hInstance
                        mov     [di].hInstance, ax

                        INVOKE  LoadCursor, NULL, IDC_ARROW
                        mov     [di].hCursor, ax

                        INVOKE  GetStockObject, WHITE_BRUSH
                        mov     [di].hbrBackground, ax

                        xor     ax, ax
                        mov     WORD PTR [di].lpszMenuName,   ax
                        mov     WORD PTR [di].lpszMenuName+2, ax

                        mov     WORD PTR [di].lpszClassName,   OFFSET szAppName
                        mov     WORD PTR [di].lpszClassName+2, ds

                        INVOKE  RegisterClass, di
                        .IF (ax == 0)
                                mov     ax, FALSE
                                jmp     doRet
                        .ENDIF

                        ASSUME  di:NOTHING        

                .ENDIF     ;--- End of IF (hPrevInstance == 0)

;
;---- Initialize
;

                INVOKE  Initialize, ADDR xStart,  ADDR yStart,
                                    ADDR xClient, ADDR yClient

;
;---- Create Top Window
;

                INVOKE  CreateWindowEx, WS_EX_TOPMOST, ADDR szAppName,
                        ADDR szAppName, WS_BORDER OR WS_POPUP OR WS_THICKFRAME,
                        xStart, yStart, xClient, yClient, NULL, NULL, 
                        hInstance, NULL
                mov     si, ax          ; keep hWnd in SI, since SI doesn't
                                        ; change after function calls

                INVOKE  ShowWindow,    si, SW_SHOWNOACTIVATE
                INVOKE  UpdateWindow,  si
                                
                
;
;----Create Scroll Child Window
;

                INVOKE  CreateWindow, ADDR szScrollBar, NULL,
                        WS_CHILD OR WS_VISIBLE OR WS_TABSTOP OR SBS_HORZ,
                        0, 0, 0, 0, si, SCROLLCHILD, hInstance, NULL 
                mov     hWndScrol, ax

                INVOKE  SetScrollRange, ax, SB_CTL, 0, MAXTIME, FALSE
                INVOKE  SetScrollPos,   hWndScrol, SB_CTL, AlarmTime, FALSE

                INVOKE  ShowScrollBar,  hWndScrol, SB_CTL, TRUE
                INVOKE  ShowWindow,    hWndScrol, SW_SHOWNOACTIVATE
                INVOKE  UpdateWindow,  hWndScrol

;
;---- Message Loop
;

                .WHILE TRUE

                        INVOKE  GetMessage,    ADDR msg, NULL, 0, 0

                        .BREAK .IF (ax == 0)

                        INVOKE  TranslateMessage, ADDR msg
                        INVOKE  DispatchMessage,  ADDR msg

                .ENDW

;
;---- Return to Windows
;

                mov     ax, msg.wParam
doRet:
                ret

WinMain         ENDP


;----------------------------------------------------------------------------;
;                                Initialize                                  ;
;----------------------------------------------------------------------------;
;                                                                            ;
; Initializes the logfont struct, sizes the Initial Window, Makes the Menu.  ;  
;                                                                            ;
; For the size of the initial window: We get the DC for the entire display,  ;
; and calculate the size of the font into tmetric. With that, we allow for   ;
; the necessary distance from the top right corner so that there's enough    ;
; space for the text.                                                        ;
;                                                                            ;
;----------------------------------------------------------------------------;

Initialize      PROC USES si di, pxStart:PINT, pyStart:PINT, 
                                 pxClient:PINT, pyClient:PINT
                ; px,py,Start,Client will hold the dimensions of the initial
                ; window to create

                LOCAL hFont:HFONT, hDC:HDC, tmetric:TEXTMETRIC
                ; locals: a handle to a font, one to a device context, 
                ;         and a textmetric structure

;
;---- Initialize the logfont structure
;
        
                mov     bx, OFFSET logfont

                ASSUME  bx:PTR LOGFONT

                xor     ax, ax
                mov     [bx].lfHeight, INIT_FONT; Initial Font Height
                mov     [bx].lfWidth,  ax       ; width's set according to hght
                mov     [bx].lfEscapement, ax
                mov     [bx].lfOrientation, ax
                mov     [bx].lfWeight, FW_NORMAL
                mov     [bx].lfItalic, al
                mov     [bx].lfUnderline, al
                mov     [bx].lfStrikeOut, al
                mov     [bx].lfCharSet, ANSI_CHARSET
                mov     [bx].lfOutPrecision, al
                mov     [bx].lfClipPrecision, al
                mov     [bx].lfQuality, al
                mov     [bx].lfPitchAndFamily, DEFAULT_PITCH OR FF_SWISS
                mov     [bx].lfFaceName, NULL

                ASSUME  bx:NOTHING

;---- Get Initial Size for the Window Based on Font

                xor     dx, dx
                INVOKE  CreateIC, ADDR szDisplay, dx::dx, dx::dx, dx::dx
                mov     hDC, ax

                INVOKE  CreateFontIndirect, ADDR logfont
                mov     hFont, ax

                INVOKE  SelectObject, hDC, ax
                mov     hFont, ax

                INVOKE  GetTextMetrics, hDC, ADDR tmetric

                INVOKE  SelectObject, hDC, hFont

                INVOKE  DeleteObject, ax

                INVOKE  DeleteDC, hDC

                INVOKE  GetSystemMetrics, SM_CXDLGFRAME ; Set window width
                shl     ax, 1                           ; frame*2       
                mov     bx, tmetric.tmAveCharWidth      ; width of font
                mov     cl, 4                           ; to shift
                shl     bx, cl                          ; width*16
                add     ax, bx                          ; frame*2 + width*16
                mov     si, pxClient                    ; address to store in
                mov     [si], ax                        ; store

                INVOKE  GetSystemMetrics, SM_CXSCREEN   ; screen x-length
                mov     si, pxClient                    ; window width
                sub     ax, [si]                        ; start=corner-winWdth
                mov     si, pxStart                     ; store result
                mov     [si], ax

                INVOKE  GetSystemMetrics, SM_CYDLGFRAME ; Set Height
                shl     ax, 1                           ; frame*2
                mov     bx, tmetric.tmHeight            ; height*2
                shl     bx, 1
                add     ax, bx                          ; add
                mov     si, pyClient                    ; store in pyClient
                mov     [si], ax

                IF TOP_CORNER                           ; if Top Corner,
                        xor     ax, ax                  ; yStart=0
                ELSE                                    ; else,
                        INVOKE  GetSystemMetrics, SM_CYSCREEN   
                        mov     si, pyClient            ; yStart = ScreenHgth
                        sub     ax, [si]                ; minus yHeight
                ENDIF
                mov     si, pyStart                     ; set yStart
                mov     [si], ax


;
;---- Initialize the Menu
;

                INVOKE CreatePopupMenu
                mov hMenu, ax
                                                ; Date is Initially Enabled
                INVOKE  AppendMenu, hMenu, MF_STRING OR MF_CHECKED, IDM_DATE,
                                    ADDR szDateCmd
                INVOKE  AppendMenu, hMenu, MF_STRING, IDM_ALARM,ADDR szAlarmCmd
                INVOKE  AppendMenu, hMenu, MF_STRING, IDM_SET, ADDR szSetCmd
                INVOKE  AppendMenu, hMenu, MF_SEPARATOR, 0, NULL
                INVOKE  AppendMenu, hMenu, MF_STRING OR MF_CHECKED, IDM_ONTOP,
                                    ADDR szOnTopCmd
                INVOKE  AppendMenu, hMenu, MF_STRING, IDM_MINIMIZE, 
                                    ADDR szMinimizeCmd
                INVOKE  AppendMenu, hMenu, MF_STRING, IDM_EXIT, ADDR szExitCmd
                INVOKE  AppendMenu, hMenu, MF_SEPARATOR, 0, NULL
                INVOKE  AppendMenu, hMenu, MF_STRING, IDM_ABOUT,ADDR szAboutCmd
                ret

Initialize      ENDP

;----------------------------------------------------------------------------;
;                                SetupTimer                                  ;
;----------------------------------------------------------------------------;
;                                                                            ;
; Setup a timer with the specified interval. If we can't set up the timer,   ;
; output a message box and exit the program.                                 ;
;----------------------------------------------------------------------------;

SetupTimer PROC NEAR, hWnd:HWND, Interval:WORD
                ; hWnd is the Handle of the Window to associate the timer with
                ; Interval is the interval in milliseconds

                INVOKE  SetTimer, hWnd, 1, Interval, NULL
                .IF (ax == 0)
                        INVOKE  MessageBox, hWnd,ADDR szTooManyTimers, 
                                            ADDR szAppName,
                                            MB_ICONEXCLAMATION OR MB_OK
                        mov     ax, FALSE
                        INVOKE PostQuitMessage, 0               ; Quit.
                .ENDIF

                ret
SetupTimer ENDP

;----------------------------------------------------------------------------;
;                                AlarmSetup                                  ;
;----------------------------------------------------------------------------;
;                                                                            ;
; If we're going to set the alarm, kill the timer, show the scroll bar, and  ;
; resize the fonts. Enable the Alarm after it has been re-set.               ;
;                                                                            ;
; If we just set up the alarm, get a new timer,                              ;
; check the menu to enable the alarm, hide the scrollbar, resize the fonts   ;
; When creating the top window this is called to set up the initial timer.   ;
;                                                                            ;
;----------------------------------------------------------------------------;

AlarmSetup      PROC,   hWnd:HWND
                ; hWnd is the handle of the window that received WinPaint 


                .IF SetAlarm
                        INVOKE  KillTimer, hWnd, 1
                        INVOKE  Resize, hWnd
                        INVOKE  ShowScrollBar, hWndScrol, SB_CTL, TRUE
                        INVOKE  SetFocus, hWndScrol
                        INVOKE  InvalidateRect, hWnd, NULL, TRUE
                        mov     EnableAlarm, MF_CHECKED
                        mov     TestAlarm, TRUE         

                .ELSE

                        INVOKE  SetupTimer, hWnd, TIMER_SECS
                        INVOKE  CheckMenuItem, hMenu, IDM_ALARM, EnableAlarm
                        INVOKE  ShowScrollBar,  hWndScrol, SB_CTL, FALSE
                        INVOKE  Resize,         hWnd
                        INVOKE  InvalidateRect, hWnd, NULL, TRUE

                .ENDIF          

                ret

AlarmSetup      ENDP
                
        
;----------------------------------------------------------------------------;
;                                    Resize                                  ;
;----------------------------------------------------------------------------;
;                                                                            ;
; Simple resizing, without taking into account aspect ratio.                 ;
; If we're setting the alarm, the scroll bar height will be (client hgt)/16, ;
; the length will be (client lenght)-(2*Scroll Bar Height), and the top will ;
; will start (in Client coordinates), at one SB Height right and 2 SBHeights ;
; up from the left bottom corner. Fonts width will be length/10, height will ;
; be (client height)-(3*Scroll bar height). The Scroll bar is also displayed.;
;                                                                            ;
; If we're not setting the alarm, and we're not minimized, font width will be;
; length/TIME_LEN and height will be either client height or client height/2,;
; depending on the date being enabled or not.                                ;
;                                                                            ;
; If font height is more than MAX_HEIGHT, then height is MAX_HEIGHT, and the ;
; TextRect.top is computed so that Drawing the text on the client area will  ;
; be centered.                                                               ;
;                                                                            ;
; If we're minimized, width=length/ICON_LEN, height is client height.        ;
;                                                                            ;
; The TextRect is used so that it's not recomputed everytime the Window is   ;
; repainted.                                                                 ;
;                                                                            ;
;----------------------------------------------------------------------------;

Resize          PROC,   hWnd:HWND
                ; hWnd is the handle of the window that received WinPaint 
                LOCAL   rect:RECT
                ; holds a rectangle structure

                INVOKE  SetFocus, hWnd  ; take focus out of child window

                INVOKE  GetClientRect, hWnd, ADDR rect ; get new rect.size
                

;---- Find desired Text Height

                .IF SetAlarm    

;---- Calculate New TextRect allowing for Scroll Bar

                        mov     bx, rect.bottom   ; SXstart is length / 8
                        shr     bx, 3
                
                        mov     cx, rect.right    ; SLength=CLenght-2*SHeight
                        sub     cx, bx 
                        sub     cx, bx

                        mov     dx, rect.bottom   ; SYstart will be CHgt-2*SHgt
                        sub     dx, bx
                        sub     dx, bx

                        mov     TextRect.bottom, dx  ; bottom = CHgt - 3*SHgt
                        sub     TextRect.bottom, bx

                        INVOKE  MoveWindow, hWndScrol, bx, dx, cx, bx, FALSE
                        INVOKE  SetScrollPos,hWndScrol,SB_CTL,AlarmTime,TRUE

                        mov     ax,TextRect.bottom   ; Try to use Full Height
                                                     ; for font height

;---- Else (not setting the alarm)

                .ELSE

                        mov     ax, rect.bottom      ; Full or Half Height
                        mov     TextRect.bottom, ax
                        .IF EnableDate && !Iconized
                                shr     ax, 1        ; height/2
                        .ENDIF
                .ENDIF          


;---- Test if desired height is allowed or not
;---- If height>MAX, then height=MAX_HEIGHT. Adjust TextRect.top by
;---- substracting from the bottom the font height (or twice that if Date),
;---- dividing by two, and adding to the TextRect.top.

                .IF (ax > MAX_HEIGHT)   
                        mov     logfont.lfHeight, MAX_HEIGHT
                        mov     ax, TextRect.bottom
                        mov     bx, MAX_HEIGHT
                        .IF EnableDate && !Iconized
                                shl     bx, 1
                        .ENDIF
                        sub     ax, bx
                        shr     ax, 1
                        mov     TextRect.top, ax
                .ELSE
                        mov     logfont.lfHeight, ax
                        mov     TextRect.top, 0

                .ENDIF

                mov     TextRect.left, 0                ; Left is Zero
                mov     ax, rect.right                  ; Same Rect length
                mov     TextRect.right, ax

;---- Set font width according to Iconized or not.

                .IF Iconized    
                        xor     dx, dx
                        mov     bx, ICON_LEN
                        div     bx
                .ELSE
                        shr     ax, 4        ; length/16
                .ENDIF
                mov     logfont.lfWidth, ax

                ret

Resize          ENDP
                
;----------------------------------------------------------------------------;
;                                  PaintAlarm                                ;
;----------------------------------------------------------------------------;
;                                                                            ;
; Painting the alarmtime. Get the hours and minutes from Alarmtime, get AM   ;
; or PM by going into szAMPM either at 0 or 3, to get the appropiate string. ;
; Then we simply draw the text into the TextRect calculated by Resize, with  ;
; the font that we get from using the logfont structure.                     ;
;                                                                            ;
;----------------------------------------------------------------------------;

PaintAlarm      PROC USES si di,  hWnd:HWND, hDC:HDC
                ; hWnd is the handle of the window that received WinPaint
                ; hDC is the Device context of the window

                LOCAL   nLength:SWORD, hFont:HFONT,
                        hour:WORD, minutes:WORD, seconds:WORD
                ; Locals: nLength is the current length of the buffer
                ;         hFont is a handle to a font.  

;---- Get Time from AlarmTime

                xor     dx, dx
                mov     ax, AlarmTime
                mov     bx, 60t
                div     bx                      ; ax holds the hours
                mov     bx, dx                  ; bx holds the minutes

                mov     si, ax                  ; si holds the hours
                xor     dx, dx
                mov     di, 12
                div     di                      ; will have ax=0 or 1 (am/pm)
                xor     dx, dx
                mov     cx, AMPM                ; AMPM = 3: 'am'+\0
                mul     cx
                mov     di, ax
                add     di,offset szAMPM        ; get into di the correct
                                                ; memory address        

                mov     ax, si                  ; ax == tm_hour
                xor     dx, dx
                mov     cx, 12
                div     cx
                .IF (dx == 0)                   ; so we don't have 00:45
                        mov cx, 12
                .ELSE
                        mov cx, dx
                .ENDIF
                
;---- on using wsprintf below: since the wsprintf prototype has VARARG,
;---- we can't tell the assemble the distance of those arguments, so we
;---- have to do a specific far pointer to the data. wsprintf expects all
;---- pointers to be far pointers, no exceptions.

                INVOKE  wsprintf, ADDR cBuffer, ADDR AlarmFmt,
                                  cx, bx, ds::di
                mov     nLength,ax

;---- Set Font & Draw Text

                INVOKE  CreateFontIndirect, ADDR logfont
                mov     hFont, ax
                INVOKE  SelectObject, hDC, ax
                mov     hFont, ax
                INVOKE  DrawText, hDC, ADDR cBuffer, nLength, ADDR TextRect,
                                       (DT_CENTER)
                INVOKE  SelectObject, hDC, hFont
                INVOKE  DeleteObject, ax

                ret

PaintAlarm      ENDP

;----------------------------------------------------------------------------;
;                                  PaintTime                                 ;
;----------------------------------------------------------------------------;
;                                                                            ;
; Painting the time. We get a index to szMonths by multiplying the [0-11]    ;
; month by 4 ('jan'+\0). We print everything to a string and store the length;
; as an index. We do this only if Date's enabled.                            ;
; For the time, we do the same AM/PM thing as in PaintAlarm, and print it to ;
; the same string after the Date's carriage return (included in DateFmt.     ;
; Then we simply draw the text into the TextRect calculated by Resize, with  ;
; the font that we get from using the logfont structure.                     ;
; If we're minimized, we also draw a rectangle around the time. We get a pen ;
; of width 2, black, and a hollow (transparent) brush. We save the pen ID in ;
; the stack so that we can deselect it later.                                ;
; Then, we check to see if we have to spring the alarm. If TestAlarm is not  ;
; enabled, check the time and re-enable TestAlarm if it's NOT the AlarmTime  ;
; (i.e. so that after we ring it it will ring in 24 hours). If TestAlarm is  ;
; set, check if EnableAlarm is set. If it is, multiply the hours by 60, add  ;
; the minutes, compare with AlarmTime. Notice that the 'mov bx, datetime' is ;
; necessary because the wsprintf and DrawText will trash ax, bx, cx. If it's ;
; alarm time, disable TestAlarm, invert the rectangle, sound a beep, revert  ;
; the rect, and do a message box. This way, TestAlarm enables us to always   ;
; have at least one check of the alarm time.                                 ;
;                                                                            ;
;----------------------------------------------------------------------------;

PaintTime       PROC USES si di,  hWnd:HWND, hDC:HDC
                ; hWnd is the handle of the window that received WinPaint
                ; hDC is the Device context of the window

                LOCAL   nLength:SWORD, hFont:HFONT, pen:WORD,
                        hour:WORD, minutes:WORD, seconds:WORD
                ; Locals: nLength is the current length of the buffer
                ;         hFont is a handle to a font.  

;---- Set Date Variables

                .IF (EnableDate && !Iconized) 

                       mov     ah, 2Ah               ; function: Get Date
                       INVOKE  DOS3Call              ; do the interrupt
                                                     ; dh will have months
                                                     ; dl will have days
                                                     ; cx will have years
 
                        mov     al, dh               ; months (1-12)
                        xor     ah, ah
                        xor     dh, dh               ; day-of-month (1-31)
                

                        mov     si, ax               ; (Month-1) * 4
                        dec     si
                        shl     si, 2
                        add     si, offset szMonths

;---- For note on wsprintf below, see PaintAlarm

                        INVOKE  wsprintf, ADDR cBuffer, ADDR DateFmt,
                                  ds::si, dx, cx
                        mov nLength, ax
                .ELSE
                        mov nLength, 0

                .ENDIF ; of EnableDate

;---- Get Time from CPU clock
                xor     dx, dx

                mov     ah, 2Ch                      ; function: Get Time
                INVOKE  DOS3Call                     ; do the interrupt
                                                     ; ch will have hours
                                                     ; cl will have minutes
                                                     ; dh will have seconds

                mov     al, ch                       ; hours (0-23)
                xor     ah, ah
                mov     hour, ax         
                mov     al, cl                       ; minutes (0-59)
                mov     minutes, ax          
                mov     al, dh                       ; seconds (0-59)
                mov     seconds, ax          
  
                mov     ax, hour                     ; divide hour/12, multiply
                xor     dx, dx                       ; by 3 to get offset into
                mov     bx, 12                       ; szAMPM, then add the 
                div     bx                           ; szAMPM offset
                mov     si, dx                       ; dx would have hours
                xor     dx, dx
                mov     cx, AMPM
                mul     cx
                mov     bx, ax
                add     bx, offset szAMPM            ; get into di the correct
                                                     ; memory address
                
                .IF (si == 0)                        ; get hour into cx
                        mov  cx, 12                  ; or 12 if hour=0
                .ELSE
                        mov  cx, si
                .ENDIF
                
                mov     si, nLength
                .IF !Iconized
                        INVOKE  wsprintf, ADDR cBuffer[si], ADDR TimeFmt,
                                          cx, minutes, seconds, ds::bx
                .ELSE
                        INVOKE  wsprintf, ADDR cBuffer[si], ADDR IconFmt,
                                          cx, minutes        
                .ENDIF

                add     nLength, ax

;---- Set Font, Draw the Text

                INVOKE  CreateFontIndirect, ADDR logfont
                mov     hFont, ax
                INVOKE  SelectObject, hDC, ax
                mov     hFont, ax
                INVOKE  DrawText, hDC, ADDR cBuffer, nLength, ADDR TextRect,
                                        DT_NOCLIP OR DT_CENTER
                INVOKE  SelectObject, hDC, hFont
                INVOKE  DeleteObject, ax

;---- If Iconized, draw rectangle with transparent background

                .IF Iconized

                        INVOKE  GetStockObject, HOLLOW_BRUSH
                        INVOKE  SelectObject, hDC, ax

                        INVOKE  CreatePen, PS_SOLID OR PS_INSIDEFRAME, 2, 0
                        INVOKE  SelectObject, hDC, ax
                        push    ax

                        INVOKE  Rectangle, hDC, TextRect.left, TextRect.top,
                                        TextRect.right, TextRect.bottom

                        INVOKE  GetStockObject, HOLLOW_BRUSH
                        INVOKE  SelectObject, hDC, ax
                        INVOKE  DeleteObject, ax

                        pop     ax
                        INVOKE  SelectObject, hDC, ax
                        INVOKE  DeleteObject, ax
                .ENDIF

;---- Check for Alarm

                xor     dx, dx
                mov     ax, hour
                mov     cx, 60t
                imul    cx
                add     ax, minutes          ; ax now holds the time in minutes

                .IF (TestAlarm)
                        .IF (EnableAlarm && (ax == AlarmTime))
                                mov     TestAlarm, FALSE
                                INVOKE  SetFocus, hWnd
                                INVOKE  InvertRect, hDC, ADDR TextRect
                                INVOKE  MessageBeep, MB_ICONASTERISK
                                INVOKE  InvertRect, hDC, ADDR TextRect
                                INVOKE  MessageBox, hWnd, ADDR szAlarmMsg,
                                    ADDR szAppName,MB_ICONEXCLAMATION OR MB_OK
                        .ENDIF
                .ELSE ; of TestAlarm                    ; if TestAlarm=0, means
                        .IF (ax != AlarmTime)           ; we sounded the alarm. 
                                mov     TestAlarm, TRUE ; but, if time 
                        .ENDIF                          ; changed, we should to
                .ENDIF                                  ; check again (allows 
                                                        ; alarm every 24 hours)

                ret

PaintTime       ENDP

;----------------------------------------------------------------------------;
;                                  WndProc                                   ;
;----------------------------------------------------------------------------;
;                                                                            ;
; The routine called on by Windows through WinMain's dispatch message loop.  ;
; Different actions according to messages                                    ;
;       Create: Set the timer with AlarmSetup                                ;
;       Timer: Repaint the window (Invalidate it)                            ;
;       Paint: BeginPaint, call on the correct paint routine                 ;
;       SetFocus: Redraw the Window Frame                                    ;
;       Resize: Resize the fonts and scroll bar, redraw the window           ;
;       Destroy: Close window and exit program                               ;
;       LeftDouble or RightClick: If setting alarm, set it; otherwise pop up ;
;           menu.  Mouse position is in the high & low words of lParam, in   ;
;           client coords. Change them to Screen coords and show menu.       ;
;       LeftClick: Move window by tricking Windows into thinking that we've  ;
;                  hit the Caption Bar of the window. It doesn't matter that ;
;                  there isn't a caption bar.                                ;
;       Commands: Date: Toggle the switch, check the menu, resize and paint  ;
;                 EnableAlarm: Sound the alarm or not                        ;
;                 Set: Start setting the alarm                               ;
;                 About: Display a Message Box. A dialog box is up to you    ;
;                 AlwaysOnTop: Sets the Window Position to be Topmost or not ;
;                 Minimize: make Windows think we hit the 'Minimize' command ;
;                    from the system menu.                                   ;
;                 Exit: Exit program.                                        ;
;       ScrollBar: Page Moves indicate hours, lines minutes. If thumb, the   ;
;                  lParam low word holds the position. Check that we don't   ;
;                  go out of range, set the scroll position, and repaint.    ;
;       Otherwise, call the default window procedure.                        ;
;                                                                            ;
;       Notice that most of the repaints do not erase the backgrnd, only     ;
;               RedrawWindow of Resize and SetFocus. This speeds up exec,    ;
;               and drawtext will erase over everything automatically.       ; 
;                                                                            ;
;----------------------------------------------------------------------------;

WndProc         PROC FAR PASCAL, hWnd:HWND, iMessage:WORD, wParam:SWORD,
                                 lParam:SDWORD
                ; Windows gives us: the handle of the Window, the Message ID,
                ; and two parameters for the message

                LOCAL   hDC:HDC, ps:PAINTSTRUCT, point:POINT
                ; locals: a handle to a device context, a paint structure,
                ;         a point structure

                .IF     (iMessage == WM_CREATE)
                        INVOKE  SetupTimer, hWnd, TIMER_SECS
                        INVOKE  Resize, hWnd
                        INVOKE  InvalidateRect, hWnd, NULL, FALSE

                .ELSEIF (iMessage == WM_TIMER)
                        INVOKE  InvalidateRect, hWnd, NULL, FALSE

                .ELSEIF (iMessage == WM_PAINT)
                        INVOKE  BeginPaint, hWnd, ADDR ps
                        mov     hDC, ax
                        .IF SetAlarm
                                INVOKE  PaintAlarm, hWnd, hDC
                        .ELSE
                                INVOKE  PaintTime, hWnd, hDC
                        .ENDIF
                        INVOKE  EndPaint, hWnd, ADDR ps

                .ELSEIF (iMessage == WM_SETFOCUS)
                        INVOKE  RedrawWindow, hWnd, NULL, NULL, 
                                              RDW_FRAME OR RDW_UPDATENOW

                .ELSEIF (iMessage == WM_SIZE)
                        .IF ((wParam != SIZE_MINIMIZED) && Iconized)
                                mov     Iconized, FALSE
                                INVOKE  KillTimer, hWnd, 1
                                INVOKE  SetupTimer, hWnd, TIMER_SECS
                        .ENDIF
                        INVOKE  Resize, hWnd
                        INVOKE  RedrawWindow, hWnd, NULL, NULL,
                                              RDW_ERASE OR RDW_INVALIDATE
                
                .ELSEIF (iMessage == WM_DESTROY)
                        INVOKE  KillTimer, hWnd, 1
                        INVOKE  PostQuitMessage, 0

                .ELSEIF (iMessage==WM_LBUTTONDBLCLK)||(iMessage==WM_RBUTTONUP)
                        .IF SetAlarm
                                xor SetAlarm, TRUE
                                INVOKE  AlarmSetup, hWnd
                        .ELSE
                                mov     di, word ptr lParam
                                mov     point.x, di
                                mov     di, word ptr (lParam+2)
                                mov     point.y, di
                                INVOKE  ClientToScreen, hWnd, ADDR point
                                INVOKE  TrackPopupMenu, hMenu, TPM_LEFTALIGN,
                                        point.x, point.y, 0, hWnd, NULL          
                                INVOKE  InvalidateRect, hWnd, NULL, TRUE
                        .ENDIF

                .ELSEIF (iMessage == WM_LBUTTONDOWN)
                        INVOKE DefWindowProc, hWnd, WM_NCLBUTTONDOWN, 
                                HTCAPTION, lParam

                .ELSEIF (iMessage == WM_COMMAND)

                        .IF (wParam == IDM_DATE)
                                xor     EnableDate, MF_CHECKED
                                INVOKE  CheckMenuItem,hMenu,wParam,EnableDate
                                INVOKE  Resize, hWnd
                                INVOKE  InvalidateRect, hWnd, NULL, FALSE                       

                        .ELSEIF (wParam == IDM_ALARM)
                                xor     EnableAlarm, MF_CHECKED
                                mov     TestAlarm, TRUE
                                INVOKE  CheckMenuItem,hMenu,wParam,EnableAlarm

                        .ELSEIF (wParam == IDM_SET)
                                mov     SetAlarm, TRUE
                                INVOKE  AlarmSetup, hWnd

                        .ELSEIF (wParam == IDM_ABOUT)
                                INVOKE  MessageBox, hWnd, ADDR szAboutText,
                                     ADDR szAppName, MB_ICONASTERISK OR MB_OK

                        .ELSEIF (wParam == IDM_ONTOP)
                                xor     AlwaysOnTop, MF_CHECKED
                                INVOKE  CheckMenuItem,hMenu,wParam,AlwaysOnTop
                                .IF AlwaysOnTop
                                        mov     ax, HWND_TOPMOST
                                .ELSE
                                        mov     ax, HWND_NOTOPMOST
                                .ENDIF

                                INVOKE  SetWindowPos, hWnd, ax,
                                        0, 0, 0, 0, SWP_NOMOVE OR SWP_NOSIZE
                        
                        .ELSEIF (wParam == IDM_EXIT)
                                INVOKE  KillTimer, hWnd, 1
                                INVOKE  PostQuitMessage, 0

                        .ELSEIF (wParam == IDM_MINIMIZE)
                                mov     Iconized, TRUE
                                INVOKE  KillTimer, hWnd, 1
                                INVOKE  SetupTimer, hWnd, TIMER_MINS
                                INVOKE  DefWindowProc, hWnd, WM_SYSCOMMAND, 
                                                       SC_ICON, NULL

                        .ENDIF
                
                .ELSEIF (iMessage == WM_HSCROLL)
                        .IF (wParam == SB_PAGEDOWN)
                                add     AlarmTime, 10t
                        .ELSEIF (wParam == SB_LINEDOWN)
                                inc     AlarmTime
                        .ELSEIF (wParam == SB_PAGEUP)
                                sub     AlarmTime, 10t
                        .ELSEIF (wParam == SB_LINEUP)
                                dec     AlarmTime
                        .ELSEIF (wParam == SB_TOP)
                                mov     AlarmTime, MAXTIME
                        .ELSEIF (wParam == SB_BOTTOM)
                                mov     AlarmTime, 0t
                        .ELSEIF (wParam == SB_THUMBPOSITION)
                                mov     ax, word ptr lParam
                                mov     AlarmTime, ax
                        .ELSEIF (wParam == SB_THUMBTRACK)
                                mov     ax, word ptr lParam
                                mov     AlarmTime, ax
                        .ELSEIF (wParam == SB_LINEDOWN)
                                mov     ax, word ptr lParam
                                mov     AlarmTime, ax
                        .ENDIF
                                .IF (AlarmTime < 0)
                                        mov     AlarmTime, 0t
                                .ELSEIF (AlarmTime > MAXTIME)
                                        mov     AlarmTime, MAXTIME
                                .ENDIF
                        
                                INVOKE  SetScrollPos, hWndScrol, SB_CTL,
                                        AlarmTime, TRUE
                                INVOKE  InvalidateRect, hWnd,
                                                        ADDR TextRect, FALSE

                .ELSE
                                INVOKE  DefWindowProc, hWnd, iMessage,
                                                       wParam,lParam
                                jmp     doRet

                .ENDIF

                mov ax, 0
                cwd
doRet:
                ret

WndProc         ENDP


                END  __astart   ; so that the code of the application will
                                ; start with the Windows start-up code

