2
; This is Steve Capp's clock program, written in the early days of Macintosh.
4
; The original program had every kind of display dependency: base address,
5
; rowbytes, and bounds. As an added bonus, the code segment was self-
6
; modifying. (The _code_ wasn't self-modifying, but the code segment had
7
; an embedded variable area.) I disassembled it with MacNosy, fixed all(?)
8
; the compatibility bugs, threw in a few optimizations, and here it is.
9
; Somewhere along the way, I lost most of it: the code size (bytes in the
10
; CODE 1 resource) dropped from 3478 bytes to less than 1100.
12
; I've cleared out most of the "magic numbers" from the code, but I still
13
; don't understand how the drawing works. Good luck.
15
; Tested on a Mac Plus and a MegaScreen.
19
; uucp: decvax!wanginst!wang!ephraim
21
; US snail: P.O. Box 1357
22
; East Arlington, MA 02174
24
; Miscellaneous things I discovered:
25
; When the mouse is below row 280 on the screen, an informative
26
; message is displayed. In this version, the string displayed
27
; depends on the the mouse column (horizontal coordinate) reduced
28
; modulo the number of strings.
30
; Pressing the mouse while the cursor is above row 280 causes the
31
; date to be displayed instead of the time. Pressing the mouse
32
; while the cursor is below row 280 exits to the shell.
34
; To prevent screen burn-in, the digits move down and up through
35
; a range of 75 rows. The rate of motion is one row every sixteen
38
Include Traps.D ; Use System and ToolBox traps
39
Include SysEquX.D ; Use System equates
40
Include ToolEqu.D ; Use ToolBox equates
41
Include QuickEquX.D ; Use QuickDraw equates
45
qdGlobals equ -4 ; qdGlobals are at -4(A5)
47
NumNum equ 6 ; six numerals
48
NumWidth equ 8 ; numeral is eight bytes wide (64 pixels)
49
DispWidth equ NumNum*NumWidth ; width of display area (bytes)
50
DispRows equ 72 ; display is 72 rows high
51
DispBytes equ DispWidth*DispRows
52
Steps equ 9 ; Nine steps in transformation
53
WorkBytes equ DispBytes*Steps ; size of work area
55
StringBase equ 1000 ; first 'STR ' resource
56
NumStrings equ 4 ; number of 'STR ' resources
58
StartRow equ 60 ; initial row for clock
59
CursorBound equ 280 ; cursor transition is at row 280
60
MessageRow equ CursorBound+30 ; Starting row for announcements
65
MouseRow ds.w 1 ; MousePoint.v
66
MouseCol ds.w 1 ; MousePoint.h
67
ShowDate ds.b 1 ; 0 = Show Time, 1 = Show Date
68
LowCursor ds.b 1 ; cursor is low on screen
70
Year ds.w 1 ; This is a date-time record
76
WeekDay ds.w 1 ; (not used by us, but set by _Secs2Date)
78
MyPort ds.b portRec ; our GrafPort record
79
MyBounds equ MyPort+portBits+bounds
80
MyTopLeft equ MyBounds+topLeft
82
MyLeft equ MyTopLeft+h
83
MyBotRight equ MyBounds+botRight
84
MyBottom equ MyBotRight+v
85
MyRight equ MyBotRight+h
86
MyBaseAddr equ MyPort+portBits+baseAddr
87
MyRowBytes equ MyPort+portBits+rowBytes
89
TrendDown ds.b 1 ; 1 = Down, 0 = Up
91
PaintPtr ds.l 1 ; screen address to start painting
93
OldData ds.l NumNum ; previous date or time, expanded
94
NewData ds.l NumNum ; new date or time, expanded
96
WorkArea ds.l 1 ; pointer to work area
98
glob39 ds.l NumNum ; pointers to mask resource (RAWB 20)
100
glob46 ds.l 10 ; pointers to numeral data (RAWB 1-10)
102
MyTempRect ds.w 4 ; rectangle used in CHECKCUR
109
; Calculate starting point for painting the screen
110
MOVEQ #StartRow,D0 ; rows down from top
111
mulu MyRowBytes(A5),D0 ; times bytes-per-row
112
ADD.L MyBaseAddr(A5),D0 ; plus start of bitmap
113
move.w MyRight(A5),D1 ; D1.W = right bound
114
sub.w MyLeft(A5),D1 ; D1.W = screen width
115
sub.w #DispWidth*8,D1 ; width of our drawing (pixels)
116
lsr.w #4,D1 ; divide by two to center,
117
ext.l D1 ; and by eight for bytes
118
add.l D1,D0 ; center the clock
119
MOVE.L D0,PaintPtr(A5)
122
_HideCursor ; put the cursor away
124
; blacken the entire screen
125
PEA MyBounds(A5) ; rectangle for screen
126
PEA qdGlobals+black(a5) ; fill with black
129
; One-time setup for main loop
135
Moveq #NumNum-1,D0 ; copy NewData to OldData
138
@1 MOVE.L (A0)+,(A1)+
142
CLR.B TrendDown(A5) ; establish a trend
143
BRA @2 ; hop into loop
145
; This is the top of the main loop
147
@3 move.L Time,D6 ; D6 = old time
150
; Wait for the second to change
151
@4 cmp.L Time,D6 ; compare old time to current time
152
beq @4 ; loop until the seconds change
153
JSR GetTime ; and then get the full time
155
JSR SetData ; convert time/date to data
157
; The following stuff moves the image up or down by one row
158
; every sixteen seconds to avoid screen burn-in.
160
@6 MOVE.B Time+3,D0 ; Get low order byte of time
161
and.b #$F,D0 ; every 16 secs, shift
162
BNE @9 ; branch if non-zero, don't shift
164
MOVE.W MyRowBytes(A5),D0
165
EXT.L D0 ; D0.L = bytes per row
166
TST.B TrendDown(A5) ; Should we move up or down?
167
BEQ @7 ; branch if up
168
ADD.L D0,PaintPtr(A5) ; move down one row
170
@7 SUB.L D0,PaintPtr(A5) ; move up one row
172
@8 DBRA D7,@9 ; every once in a while, switch
173
@2 MOVE.W #DispRows+2,D7 ; recharge the trend counter
174
BCHG.B #0,TrendDown(A5) ; flip the trend flag
176
@9 JSR CHECKCUR ; check the mouse position
178
PEA glob46(A5) ; pointers to numeral data
179
PEA OldData(A5) ; old time or date
180
PEA NewData(A5) ; new time or date
181
Move.L WorkArea(A5),-(A7) ; pointer to work area
182
PEA glob39(A5) ; offsets into mask resource
183
JSR PROC30 ; build new images
185
; If Button, Show Date
186
; If Not Button, Show Time
187
; If LowCursor & Button, Fall out
189
_Button ; (A7) = Button
190
MOVE.B (A7),ShowDate(A5) ; If button, Show Date
192
MOVE.B LowCursor(A5),D0
193
AND.B (A7)+,D0 ; D0 = LowCursor & Button
196
RTS ; Exit to wherever we came from...
199
StringPtr set 8 ; single parameter for ANNOUNCE
209
CLR -(A7) ; for string width
210
MOVE.L StringPtr(A6),-(A7) ; string pointer
211
_StringWidth ; (A7) = string width
213
move.w MyRight(A5),D1 ; D1.W = right bound
214
sub.w MyLeft(A5),D1 ; D1.W = screen width
215
SUB.W (A7)+,D1 ; D1.W = excess screen width
216
LSR.W #1,D1 ; D1 = half of excess screen width
218
move.w #MessageRow,-(A7) ; row for drawing text
221
MOVE.L StringPtr(A6),-(A7) ; string pointer
225
Move.L (A7)+,(A7) ; remove parameter
234
CMPI #CursorBound,MouseRow(A5)
235
BLE @4 ; branch if mouse in upper screen
237
BSET.B #0,LowCursor(A5) ; mouse is low now
238
BNE @3 ; skip if it was already
240
_ShowCursor ; else show cursor and message
242
CLR.L -(A7) ; for string handle
243
move.w MouseCol(A5),D0 ; Get mouse column
245
divu #NumStrings,D0 ; equal chance of each message
246
swap D0 ; D0.W = remainder
247
add.w #StringBase,D0 ; D0.W = STR resource number
248
move.w D0,-(A7) ; string #
250
move.L (A7)+,A0 ; string handle
251
move.L (A0),-(A7) ; string pointer
255
@4 BCLR.B #0,LowCursor(A5) ; Mouse is high now
256
BEQ @5 ; skip if it was already
258
_HideCursor ; else hide cursor and message
260
LEA MyTempRect(A5),A1 ; A1 = our rectangle
261
Move.L A1,-(A7) ; push for _FillRect
262
move.w #CursorBound,(A1)+ ; top of rect
263
move.w MyLeft(a5),(A1)+ ; left of screen
264
move.l MyBotRight(a5),(A1)+ ; bottom right of screen
265
PEA qdGlobals+black(a5) ; fill with black
274
; Build array of pointers to RAWB 1-10
292
MOVE.L (A0),A4 ; RAWB 20 pointer
294
LEA glob39(A5),A0 ; glob39[0]
295
MOVE.L A3,(A0)+ ; glob39[0]
296
MOVE.L A4,(A0)+ ; glob39[1]
297
MOVE.L A3,(A0)+ ; glob39[2]
298
MOVE.L A4,(A0)+ ; glob39[3]
299
MOVE.L A3,(A0)+ ; glob39[4]
300
MOVE.L A4,(A0)+ ; glob39[5]
302
move.L #WorkBytes,D0 ; space for building images
304
move.L A0,WorkArea(a5) ; start of allocated memory
318
move.w #applFont,-(A7) ; use application font
320
move.w #18,-(A7) ; in 18 point size
326
MELTTICKS equ 4 ; ticks to delay in MELT
331
MOVEM.L D6-D7/A4,-(A7)
333
Move.L WorkArea(A5),A4 ; A4 = work area
335
@1 MOVE.L #MELTTICKS,D6 ; D6 = ticks to delay
336
ADD.L Ticks,D6 ; D6 = tick time to delay until
338
move.L A4,-(A7) ; push pointer to painting area
339
TST.B ShowDate(A5) ; Show Date?
340
BEQ @2 ; branch if not
341
JSR PROC34 ; paint squares for showing date
344
@2 JSR PROC33 ; paint circles for showing time
346
@3 move.L A4,-(A7) ; push pointer into work area
347
move.L PaintPtr(A5),-(A7) ; push pointer to screen image
348
JSR PROC31 ; copy image to screen
354
CMP.L Ticks,D6 ; Time to go on yet?
357
LEA DispBytes(A4),A4 ; next image in work area
363
@8 MOVE.L (A0)+,(A1)+
366
MOVEM.L (A7)+,D6-D7/A4
373
lea Year(A5),A0 ; input area
374
TST.B ShowDate(A5) ; Show date?
376
lea Hour(A5),A0 ; else show time
377
@1 LEA NewData(A5),A1 ; output area
378
moveq #2,D1 ; 3 fields to process
379
@0 move (a0)+,D0 ; D0 = field to process
380
ext.l D0 ; clear high word
381
divu #10,D0 ; divide by ten
382
clr.w (a1)+ ; clear high word of output
383
move.w D0,(A1)+ ; set decades
385
clr.w (A1)+ ; clear high word of output
386
move.w D0,(A1)+ ; set units
395
param1 set 76 ; numeral data (glob46)
396
param2 set 72 ; old data (OldData)
397
param3 set 68 ; new data (NewData)
398
param4 set 64 ; work area (WorkArea)
399
param5 set 60 ; mask table pointers (glob39)
403
move.L (A7)+,A0 ; A0 = return address
404
MOVEM.L D0-D7/A0-A6,-(A7) ; sixty bytes onto stack
406
Move.L param4(A7),A5 ; A5 = start of work area
407
Lea (NumNum-1)*NumWidth(A5),A3 ; offset to right-most digit
409
MOVE #20,D0 ; index to unit seconds or days
410
@1 MOVEA.L param1(A7),A4 ; A4 = base of numeral data ptr array
411
MOVEA.L param2(A7),A5 ; old data
412
MOVE.L 0(A5,D0.W),D6 ; D6 = old digit
414
MOVEA.L 0(A4,D6.W),A1 ; A1 = data for old digit
415
MOVEA.L param3(A7),A5 ; new data
416
MOVE.L 0(A5,D0.W),D6 ; D6 = new digit
418
MOVEA.L 0(A4,D6.W),A2 ; A2 = data for new digit
419
MOVEA.L param5(A7),A5
420
MOVEA.L 0(A5,D0.W),A0 ; A0 = ptr to RAWB 20 (perhaps +64)
455
ADDA.W #DispBytes-NumWidth,A4 ; next image
490
ADDA.W #DispBytes-NumWidth,A4 ; next image
493
ADDA.W #DispWidth,A3 ; next row
496
SUB.W #DispBytes+NumWidth,A3 ; back to top, previous numeral
497
SUBQ #3,D0 ; previous numeral
500
MOVEM.L (A7)+,D0-D7/A0-A6 ; restore all registers
501
ADDA.W #ParamSize,A7 ; clear parameters
509
move.L (A7)+,D0 ; D0 = return address
510
move.L (A7)+,A0 ; A0 = place to draw
511
move.L (A7)+,A1 ; A1 = stuff to draw
512
move.L D0,-(A7) ; restore return address
514
MOVEQ #DispRows-1,D0 ; rows in drawing
515
@1 move.l A0,-(A7) ; save start of row
516
MOVEQ #DispWidth/4-1,D1 ; width of drawing, in longs
521
move.l (A7)+,A0 ; restore start of row
522
ADDA.W MyRowBytes(A5),A0 ; skip to next row
527
; PROC33 paints the colons which separate portions of the time
530
Circle dc.w %0000001111000000
531
dc.w %0000011111100000
532
dc.w %0000111111110000
533
dc.w %0000111111110000
534
dc.w %0000111111110000
535
dc.w %0000111111110000
536
dc.w %0000011111100000
537
dc.w %0000001111000000
540
move.L (A7)+,D0 ; D0 = return address
541
move.L (A7)+,A0 ; A0 = place to paint
542
LEA Circle,A1 ; A1 = Circle image
544
ADDA.W #22*DispWidth+2*NumWidth-1,A0
545
; 22 rows down, 2 chars in, back 8 pixels
547
@1 MOVE.B (A1)+,D1 ; D1.B = byte of image
548
MOVE.B D1,2*NumWidth(A0) ; top right (2 chars over)
549
MOVE.B D1,18*DispWidth(A0) ; bottom left (18 rows down)
550
MOVE.B D1,18*DispWidth+2*NumWidth(A0) ; bottom right (18 down, 2 over)
551
MOVE.B D1,(A0)+ ; top left
553
MOVE.B (A1)+,D1 ; D1.B = byte of image
554
MOVE.B D1,2*NumWidth(A0) ; top right (2 chars over)
555
MOVE.B D1,18*DispWidth(A0) ; bottom left (18 rows down)
556
MOVE.B D1,18*DispWidth+2*NumWidth(A0) ; bottom right (18 down, 2 over)
557
MOVE.B D1,(A0)+ ; top left
559
ADDA.W #DispWidth-2,A0 ; next row
566
; PROC34 paints the squares which separate portions of the date
569
Square dc.w %0000000000000000 ; actually a rectangle
570
dc.w %0000000000000000
571
dc.w %0000111111110000
572
dc.w %0000111111110000
573
dc.w %0000111111110000
574
dc.w %0000111111110000
575
dc.w %0000111111110000
576
dc.w %0000111111110000
579
move.L (A7)+,D0 ; D0 = return address
580
move.L (A7)+,A0 ; A0 = place to paint
581
LEA Square,A1 ; A1 = Square
583
ADDA.W #32*DispWidth+2*NumWidth-1,A0
584
; 32 rows down, 2 chars in, 8 pixels back
586
@1 MOVE.B (A1)+,D1 ; D1.B = byte of square
587
MOVE.B D1,2*NumWidth(A0) ; right (2 chars right)
588
MOVE.B D1,(A0)+ ; left
590
MOVE.B (A1)+,D1 ; D1.B = byte of square
591
MOVE.B D1,2*NumWidth(A0) ; right (2 chars right)
592
MOVE.B D1,(A0)+ ; left
594
ADDA.W #DispWidth-2,A0 ; next row
608
Move.W (A0),D0 ; D0 = year
609
Ext.L D0 ; clear high word
610
Divu #100,D0 ; divide by century
611
Swap D0 ; D0.W = year within century
612
Move.W D0,(A0) ; set decade+year, no centuries