
; This is Steve Capp's clock program, written in the early days of Macintosh.

; The original program had every kind of display dependency: base address,
; rowbytes, and bounds.  As an added bonus, the code segment was self-
; modifying.  (The _code_ wasn't self-modifying, but the code segment had
; an embedded variable area.)  I disassembled it with MacNosy, fixed all(?)
; the compatibility bugs, threw in a few optimizations, and here it is.
; Somewhere along the way, I lost most of it: the code size (bytes in the
; CODE 1 resource) dropped from 3478 bytes to less than 1100.

; I've cleared out most of the "magic numbers" from the code, but I still
; don't understand how the drawing works.  Good luck.

; Tested on a Mac Plus and a MegaScreen.

; Ephraim Vishniac
; February 22, 1987
;     uucp:	decvax!wanginst!wang!ephraim
; MCI mail:	masstech
; US snail:	P.O. Box 1357
;		East Arlington, MA 02174

; Miscellaneous things I discovered:
;	When the mouse is below row 280 on the screen, an informative
;	message is displayed.  In this version, the string displayed
;	depends on the the mouse column (horizontal coordinate) reduced
;	modulo the number of strings.
;
;	Pressing the mouse while the cursor is above row 280 causes the
;	date to be displayed instead of the time.  Pressing the mouse
;	while the cursor is below row 280 exits to the shell.
;
;	To prevent screen burn-in, the digits move down and up through
;	a range of 75 rows.  The rate of motion is one row every sixteen
;	seconds.

	Include Traps.D		; Use System and ToolBox traps
	Include SysEquX.D	; Use System equates
	Include	ToolEqu.D	; Use ToolBox equates
	Include	QuickEquX.D	; Use QuickDraw equates

;	Some equates

qdGlobals	equ	-4		; qdGlobals are at -4(A5)

NumNum		equ	6		; six numerals
NumWidth	equ	8		; numeral is eight bytes wide (64 pixels)
DispWidth	equ	NumNum*NumWidth	; width of display area (bytes)
DispRows	equ	72		; display is 72 rows high
DispBytes	equ	DispWidth*DispRows
Steps		equ	9		; Nine steps in transformation
WorkBytes	equ	DispBytes*Steps	; size of work area 

StringBase	equ	1000		; first 'STR ' resource
NumStrings	equ	4		; number of 'STR ' resources

StartRow	equ	60		; initial row for clock
CursorBound	equ	280		; cursor transition is at row 280
MessageRow	equ	CursorBound+30	; Starting row for announcements

;	Globals


MouseRow	ds.w	1		; MousePoint.v
MouseCol	ds.w	1		; MousePoint.h
ShowDate	ds.b	1		; 0 = Show Time, 1 = Show Date
LowCursor	ds.b	1		; cursor is low on screen

Year		ds.w	1		; This is a date-time record
Month		ds.w	1		; 
Day		ds.w	1		; 
Hour		ds.w	1		; 
Minute		ds.w	1		; 
Second		ds.w	1		; 
WeekDay		ds.w	1		; (not used by us, but set by _Secs2Date)

MyPort		ds.b	portRec		; our GrafPort record
MyBounds	equ	MyPort+portBits+bounds
MyTopLeft	equ	MyBounds+topLeft
MyTop		equ	MyTopLeft+v
MyLeft		equ	MyTopLeft+h
MyBotRight	equ	MyBounds+botRight
MyBottom	equ	MyBotRight+v
MyRight		equ	MyBotRight+h
MyBaseAddr	equ	MyPort+portBits+baseAddr
MyRowBytes	equ	MyPort+portBits+rowBytes

TrendDown	ds.b	1		; 1 = Down, 0 = Up

PaintPtr	ds.l	1		; screen address to start painting

OldData		ds.l	NumNum		; previous date or time, expanded
NewData		ds.l	NumNum		; new date or time, expanded

WorkArea	ds.l	1		; pointer to work area

glob39		ds.l	NumNum		; pointers to mask resource (RAWB 20)

glob46		ds.l	10		; pointers to numeral data (RAWB 1-10)

MyTempRect	ds.w	4		; rectangle used in CHECKCUR

;	Start here!

MAIN
	JSR	INIT

	; Calculate starting point for painting the screen
	MOVEQ	#StartRow,D0		; rows down from top
	mulu	MyRowBytes(A5),D0	; times bytes-per-row
	ADD.L	MyBaseAddr(A5),D0	; plus start of bitmap
	move.w	MyRight(A5),D1		; D1.W = right bound
	sub.w	MyLeft(A5),D1		; D1.W = screen width
	sub.w	#DispWidth*8,D1		; width of our drawing (pixels)
	lsr.w	#4,D1			; divide by two to center,
	ext.l	D1			;   and by eight for bytes
	add.l	D1,D0			; center the clock
	MOVE.L	D0,PaintPtr(A5)

	; Blackout!
	_HideCursor			; put the cursor away

	; blacken the entire screen
	PEA	MyBounds(A5)		; rectangle for screen
	PEA	qdGlobals+black(a5)	; fill with black
	_FillRect

; One-time setup for main loop

	JSR	GetTime

	JSR	SetData

	Moveq	#NumNum-1,D0		; copy NewData to OldData
	LEA	NewData(A5),A0
	LEA	OldData(A5),A1
@1	MOVE.L	(A0)+,(A1)+
	dbra	D0,@1


	CLR.B	TrendDown(A5)		; establish a trend
	BRA	@2			; hop into loop

; This is the top of the main loop

@3	move.L	Time,D6			; D6 = old time
	JSR	MELT

	; Wait for the second to change
@4	cmp.L	Time,D6			; compare old time to current time
	beq	@4			; loop until the seconds change
	JSR	GetTime			; and then get the full time

	JSR	SetData			; convert time/date to data

	; The following stuff moves the image up or down by one row
	; every sixteen seconds to avoid screen burn-in.

@6	MOVE.B	Time+3,D0		; Get low order byte of time
	and.b	#$F,D0			; every 16 secs, shift
	BNE	@9			; branch if non-zero, don't shift

	MOVE.W	MyRowBytes(A5),D0
	EXT.L	D0			; D0.L = bytes per row
	TST.B	TrendDown(A5)		; Should we move up or down?
	BEQ	@7			; branch if up
	ADD.L	D0,PaintPtr(A5)		; move down one row
	BRA	@8
@7	SUB.L	D0,PaintPtr(A5)		; move up one row

@8	DBRA	D7,@9			; every once in a while, switch
@2	MOVE.W	#DispRows+2,D7		; recharge the trend counter
	BCHG.B	#0,TrendDown(A5)	; flip the trend flag

@9	JSR	CHECKCUR		; check the mouse position

	PEA	glob46(A5)		; pointers to numeral data
	PEA	OldData(A5)		; old time or date
	PEA	NewData(A5)		; new time or date
	Move.L	WorkArea(A5),-(A7)	; pointer to work area
	PEA	glob39(A5)		; offsets into mask resource
	JSR	PROC30			; build new images

	; If Button, Show Date
	; If Not Button, Show Time
	; If LowCursor & Button, Fall out
	CLR	-(A7)
	_Button				; (A7) = Button
	MOVE.B	(A7),ShowDate(A5)	; If button, Show Date

	MOVE.B	LowCursor(A5),D0
	AND.B	(A7)+,D0		; D0 = LowCursor & Button
	BEQ	@3

	RTS				; Exit to wherever we came from...


StringPtr	set	8		; single parameter for ANNOUNCE

;-refs - CHECKCUR	

ANNOUNCE
	LINK	A6,#-0

	move.w	#SrcBic,-(A7)
	_TextMode

	CLR	-(A7)			; for string width
	MOVE.L StringPtr(A6),-(A7)	; string pointer
	_StringWidth			; (A7) = string width

	move.w	MyRight(A5),D1		; D1.W = right bound
	sub.w	MyLeft(A5),D1		; D1.W = screen width
	SUB.W	(A7)+,D1		; D1.W = excess screen width
	LSR.W	#1,D1			; D1 = half of excess screen width
	move.w	D1,-(A7)
	move.w	#MessageRow,-(A7)	; row for drawing text
	_MoveTo

	MOVE.L StringPtr(A6),-(A7)	; string pointer
	_DrawString

	UNLK	A6
	Move.L	(A7)+,(A7)		; remove parameter
	RTS	


;-refs - MELT	MAIN	

CHECKCUR
	PEA	MouseRow(A5)
	_GetMouse
	CMPI	#CursorBound,MouseRow(A5)
	BLE	@4			; branch if mouse in upper screen

	BSET.B	#0,LowCursor(A5)	; mouse is low now
	BNE	@3			; skip if it was already

	_ShowCursor			; else show cursor and message

	CLR.L	-(A7)			; for string handle
	move.w	MouseCol(A5),D0		; Get mouse column
	ext.l	D0
	divu	#NumStrings,D0		; equal chance of each message
	swap	D0			; D0.W = remainder
	add.w	#StringBase,D0		; D0.W = STR resource number
	move.w	D0,-(A7)		; string #
	_GetString
	move.L	(A7)+,A0		; string handle
	move.L	(A0),-(A7)		; string pointer
	JSR	ANNOUNCE
@3	BRA	@5

@4	BCLR.B	#0,LowCursor(A5)	; Mouse is high now
	BEQ	@5			; skip if it was already

	_HideCursor			; else hide cursor and message

	LEA	MyTempRect(A5),A1	; A1 = our rectangle
	Move.L	A1,-(A7)		; push for _FillRect
	move.w	#CursorBound,(A1)+	; top of rect
	move.w	MyLeft(a5),(A1)+	; left of screen
	move.l	MyBotRight(a5),(A1)+	; bottom right of screen
	PEA	qdGlobals+black(a5)	; fill with black
	_FillRect

@5	RTS	


;-refs - MAIN	

INIT
	; Build array of pointers to RAWB 1-10
	CLR	D7
	LEA	glob46(A5),A3
@1	ADDQ	#1,D7
	CLR.L	-(A7)
	move.L	#'RAWB',-(A7)
	move.w	D7,-(A7)
	_GetResource
	move.L	(A7)+,A0
	MOVE.L	(A0),(A3)+
	CMPI	#9,D7
	BLE	@1

	CLR.L	-(A7)
	move.L	#'RAWB',-(A7)
	move.w	#20,-(A7)
	_GetResource
	move.L	(A7)+,A0
	MOVE.L	(A0),A4			; RAWB 20 pointer
	LEA	64(A4),A3
	LEA	glob39(A5),A0		; glob39[0]
	MOVE.L	A3,(A0)+		; glob39[0]
	MOVE.L	A4,(A0)+		; glob39[1]
	MOVE.L	A3,(A0)+		; glob39[2]
	MOVE.L	A4,(A0)+		; glob39[3]
	MOVE.L	A3,(A0)+		; glob39[4]
	MOVE.L	A4,(A0)+		; glob39[5]

	move.L	#WorkBytes,D0		; space for building images
	_NewPtr
	move.L	A0,WorkArea(a5)		; start of allocated memory

	CLR.B	LowCursor(A5)
	CLR.B	ShowDate(A5)

	PEA	qdGlobals(A5)
	_InitGraf

	PEA	MyPort(A5)
	_OpenPort

	_InitCursor

	_InitFonts
	move.w	#applFont,-(A7)		; use application font
	_TextFont
	move.w	#18,-(A7)		; in 18 point size
	_TextSize

	RTS	


MELTTICKS	equ	4		; ticks to delay in MELT

;-refs - MAIN	

MELT
	MOVEM.L D6-D7/A4,-(A7)
	moveq	#Steps-1,D7
	Move.L	WorkArea(A5),A4		; A4 = work area

@1	MOVE.L	#MELTTICKS,D6		; D6 = ticks to delay
	ADD.L	Ticks,D6		; D6 = tick time to delay until

	move.L	A4,-(A7)		; push pointer to painting area
	TST.B	ShowDate(A5)		; Show Date?
	BEQ	@2			; branch if not
	JSR	PROC34			; paint squares for showing date
	BRA	@3

@2	JSR	PROC33			; paint circles for showing time

@3	move.L	A4,-(A7)		; push pointer into work area
	move.L	PaintPtr(A5),-(A7)	; push pointer to screen image
	JSR	PROC31			; copy image to screen

@4	IF	MELTTICKS>1
	JSR	CHECKCUR
	ENDIF

	CMP.L	Ticks,D6		; Time to go on yet?
	BGT	@4			; Loop if not

	LEA	DispBytes(A4),A4	; next image in work area
	dbra	D7,@1

	moveq	#NumNum-1,D7
	LEA	NewData(A5),A0
	LEA	OldData(A5),A1
@8	MOVE.L	(A0)+,(A1)+
	DBRA	D7,@8

	MOVEM.L (A7)+,D6-D7/A4
	RTS


;-refs - MAIN	

SetData
	lea	Year(A5),A0		; input area
	TST.B	ShowDate(A5)		; Show date?
	bne	@1			; skip if so	
	lea	Hour(A5),A0		; else show time
@1	LEA	NewData(A5),A1		; output area
	moveq	#2,D1			; 3 fields to process
@0	move	(a0)+,D0		; D0 = field to process
	ext.l	D0			; clear high word
	divu	#10,D0			; divide by ten
	clr.w	(a1)+			; clear high word of output
	move.w	D0,(A1)+		; set decades
	swap	D0
	clr.w	(A1)+			; clear high word of output
	move.w	D0,(A1)+		; set units
	dbra	D1,@0

	RTS



;-refs - MAIN	

param1	set	76		; numeral data (glob46)
param2	set	72		; old data (OldData)
param3	set	68		; new data (NewData)
param4	set	64		; work area (WorkArea)
param5	set	60		; mask table pointers (glob39)
paramsize	set	20

PROC30
	move.L	(A7)+,A0		; A0 = return address
	MOVEM.L D0-D7/A0-A6,-(A7)	; sixty bytes onto stack
	
	Move.L	param4(A7),A5		; A5 = start of work area
	Lea	(NumNum-1)*NumWidth(A5),A3	; offset to right-most digit

	MOVE	#20,D0			; index to unit seconds or days
@1	MOVEA.L param1(A7),A4		; A4 = base of numeral data ptr array
	MOVEA.L param2(A7),A5		; old data
	MOVE.L	0(A5,D0.W),D6		; D6 = old digit
	LSL	#2,D6
	MOVEA.L 0(A4,D6.W),A1		; A1 = data for old digit
	MOVEA.L param3(A7),A5		; new data
	MOVE.L	0(A5,D0.W),D6		; D6 = new digit
	LSL	#2,D6
	MOVEA.L 0(A4,D6.W),A2		; A2 = data for new digit
	MOVEA.L param5(A7),A5
	MOVEA.L 0(A5,D0.W),A0		; A0 = ptr to RAWB 20 (perhaps +64)

	MOVE	#DispRows-1,D1
@2	MOVEA.L A3,A4
	MOVE	(A1)+,D2
	MOVE	(A1)+,D3
	MOVE	(A2)+,D4
	MOVE	(A2)+,D5
	ADDI	#15,D5
	SUB	D2,D4
	ASR	#3,D4
	SUB	D3,D5
	ASR	#3,D5

	MOVEQ	#Steps-1,D7
@3	MOVE	D2,D6
	LSR	#1,D6
	ANDI	#$FFF8,D6
	MOVEA.L A0,A5
	ADDA.W	D6,A5
	MOVE	D3,D6
	LSR	#1,D6
	ANDI	#$FFF8,D6
	MOVEA.L A0,A6
	ADDA.W	D6,A6
	MOVE.L	(A6)+,D6
	NOT.L	D6
	AND.L	(A5)+,D6
	MOVE.L	D6,(A4)+
	MOVE.L	(A6)+,D6
	NOT.L	D6
	AND.L	(A5)+,D6
	MOVE.L	D6,(A4)+
	ADD	D4,D2
	ADD	D5,D3
	ADDA.W	#DispBytes-NumWidth,A4	; next image
	DBRA	D7,@3

	MOVEA.L A3,A4
	MOVE	(A1)+,D2
	MOVE	(A1)+,D3
	MOVE	(A2)+,D4
	MOVE	(A2)+,D5
	ADDI	#15,D5
	SUB	D2,D4
	ASR	#3,D4
	SUB	D3,D5
	ASR	#3,D5

	MOVEQ	#Steps-1,D7
@4	MOVE	D2,D6
	LSR	#1,D6
	ANDI	#$FFF8,D6
	MOVEA.L A0,A5
	ADDA.W	D6,A5
	MOVE	D3,D6
	LSR	#1,D6
	ANDI	#$FFF8,D6
	MOVEA.L A0,A6
	ADDA.W	D6,A6
	MOVE.L	(A6)+,D6
	NOT.L	D6
	AND.L	(A5)+,D6
	OR.L	D6,(A4)+
	MOVE.L	(A6)+,D6
	NOT.L	D6
	AND.L	(A5)+,D6
	OR.L	D6,(A4)+
	ADD	D4,D2
	ADD	D5,D3
	ADDA.W	#DispBytes-NumWidth,A4	; next image
	DBRA	D7,@4

	ADDA.W	#DispWidth,A3		; next row
	DBRA	D1,@2

	SUB.W	#DispBytes+NumWidth,A3	; back to top, previous numeral
	SUBQ	#3,D0			; previous numeral
	DBMI	D0,@1

	MOVEM.L (A7)+,D0-D7/A0-A6	; restore all registers
	ADDA.W	#ParamSize,A7		; clear parameters
	JMP	(A0)



;-refs - MELT	

PROC31
	move.L	(A7)+,D0		; D0 = return address
	move.L	(A7)+,A0		; A0 = place to draw
	move.L	(A7)+,A1		; A1 = stuff to draw
	move.L	D0,-(A7)		; restore return address

	MOVEQ	#DispRows-1,D0		; rows in drawing
@1	move.l	A0,-(A7)		; save start of row
	MOVEQ	#DispWidth/4-1,D1	; width of drawing, in longs
@2	NOT.L	(A1)
	MOVE.L	(A1)+,(A0)+
	DBRA	D1,@2

	move.l	(A7)+,A0		; restore start of row
	ADDA.W	MyRowBytes(A5),A0	; skip to next row
	DBRA	D0,@1

	RTS	

; PROC33 paints the colons which separate portions of the time
;-refs - MELT	

Circle	dc.w	%0000001111000000
	dc.w	%0000011111100000
	dc.w	%0000111111110000
	dc.w	%0000111111110000
	dc.w	%0000111111110000
	dc.w	%0000111111110000
	dc.w	%0000011111100000
	dc.w	%0000001111000000

PROC33
	move.L	(A7)+,D0		; D0 = return address
	move.L	(A7)+,A0		; A0 = place to paint
	LEA	Circle,A1		; A1 = Circle image

	ADDA.W	#22*DispWidth+2*NumWidth-1,A0
				; 22 rows down, 2 chars in, back 8 pixels
	MOVE	#7,D2
@1	MOVE.B	(A1)+,D1		; D1.B = byte of image
	MOVE.B	D1,2*NumWidth(A0)	; top right (2 chars over)
	MOVE.B	D1,18*DispWidth(A0)	; bottom left (18 rows down)
	MOVE.B	D1,18*DispWidth+2*NumWidth(A0)	; bottom right (18 down, 2 over)
	MOVE.B	D1,(A0)+		; top left

	MOVE.B	(A1)+,D1		; D1.B = byte of image
	MOVE.B	D1,2*NumWidth(A0)	; top right (2 chars over)
	MOVE.B	D1,18*DispWidth(A0)	; bottom left (18 rows down)
	MOVE.B	D1,18*DispWidth+2*NumWidth(A0)	; bottom right (18 down, 2 over)
	MOVE.B	D1,(A0)+		; top left

	ADDA.W	#DispWidth-2,A0		; next row
	DBRA	D2,@1

	MOVEA.L D0,A0
	JMP	(A0)


; PROC34 paints the squares which separate portions of the date
;-refs - MELT	

Square	dc.w	%0000000000000000	; actually a rectangle
	dc.w	%0000000000000000
	dc.w	%0000111111110000
	dc.w	%0000111111110000
	dc.w	%0000111111110000
	dc.w	%0000111111110000
	dc.w	%0000111111110000
	dc.w	%0000111111110000

PROC34
	move.L	(A7)+,D0		; D0 = return address
	move.L	(A7)+,A0		; A0 = place to paint
	LEA	Square,A1		; A1 = Square

	ADDA.W	#32*DispWidth+2*NumWidth-1,A0
				; 32 rows down, 2 chars in, 8 pixels back
	MOVE	#7,D2
@1	MOVE.B	(A1)+,D1		; D1.B = byte of square
	MOVE.B	D1,2*NumWidth(A0)	; right (2 chars right)
	MOVE.B	D1,(A0)+		; left

	MOVE.B	(A1)+,D1		; D1.B = byte of square
	MOVE.B	D1,2*NumWidth(A0)	; right (2 chars right)
	MOVE.B	D1,(A0)+		; left

	ADDA.W	#DispWidth-2,A0		; next row
	DBRA	D2,@1

	MOVEA.L D0,A0
	JMP	(A0)



;-refs - MAIN	

GetTime
	move.l	Time,D0
	LEA	Year(A5),A0
	_Secs2Date
	Move.W	(A0),D0			; D0 = year
	Ext.L	D0			; clear high word
	Divu	#100,D0			; divide by century
	Swap	D0			; D0.W = year within century
	Move.W	D0,(A0)			; set decade+year, no centuries
	RTS	
