Mar 89 Letters
Volume Number: | | 5
|
Issue Number: | | 3
|
Column Tag: | | Letters
|
Letters ![](img001.gif)
By David E. Smith, Editor & Publisher, MacTutor
INIT29 & Hpat Virsus Wanted!
Steve Sequist is requesting an infected disk with the new INIT29 and Hpat (nVir type) virus so he can update last month's Security Patrol anit-virus program to detect and destroy them. If you have one or both, please send an infected disk, clearly marked with a skull and crossbones, to MacTutor, PO Box 400, Placentia, CA 92670, attn: Virus! -Ed
Watch Cursor Revisited
Jan Eugenides
Waterbury Center, VT
David Stoops Animated Cursors from the December issue was interesting, but it had two deficiencies:
1) It was hard-coded for eight cursors, instead of obtaining the real number of cursors from the acur resource,
2) It used SetUpA5 and RestoreA5, which should no longer be used.
I recoded the example in MPW C 3.0b1, with the modifications, and here is the result. I also changed to an acur resource with an ID of 128, so as not to conflict with any system resources. The Rez source code for the watch acur and the associated cursors are as follows:
/* 1 */
#include <Types.h>
#include <Resources.h>
#include <QuickDraw.h>
#include <OSUtils.h>
#include <Retrace.h>
#include <Memory.h>
#include <ToolUtils.h>
#include <Strings.h>
#include <Errors.h>
typedef struct {
short numCurs; /*number of cursor frames */
short whichCur;/*used as frame counter*/
CursHandle Curs[1]; /*can actually be any number of cursors here*/
}acur;
typedef acur*acurPtr,**acurHandle;
static acurHandleCurList;
static VBLTask VBL;
pascal void AnimateWatch() /*the VBL routine*/
{
long oldA5;
oldA5 = SetCurrentA5();
if((*CurList)->whichCur >= (*CurList)->numCurs-1 )
(*CurList)->whichCur = 0;
else
(*CurList)->whichCur ++;
SetCursor(*((*CurList)->Curs[(*CurList)->whichCur]));
VBL.vblCount = 10;
SetA5(oldA5);
}
InitWatch()
{
short curCount;
CurList = (acurHandle)GetResource(acur,128);
for(curCount = 0; curCount < (*CurList)->numCurs; curCount++)
(*CurList)->Curs[curCount] = GetCursor(HiWord((long)(*CurList)->Curs[curCount]));
(*CurList)->whichCur = 0;
}
StartWatch()
{
OSErr err;
VBL.qType = vType;
VBL.vblAddr = (VBLProcPtr)AnimateWatch;
VBL.vblCount = 10;
VBL.vblPhase = 0;
err = VInstall((QElemPtr)&VBL);
}
StopWatch()
{
OSErr err;
err = VRemove((QElemPtr)&VBL);
}
/* 2 */
#include types.r
data acur (128, locked, preload) {
$00 08 00 00 01 00 00 00 01 01 00 00 01 02 00 00"
$01 03 00 00 01 04 00 00 01 05 00 00 01 06 00 00"
$01 07 00 00"
};
resource CURS (263, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 80 40 90 40 88 60"
$9C 60 80 40 80 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
resource CURS (262, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 80 40 80 40 80 60"
$BC 60 80 40 80 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
resource CURS (261, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 80 40 80 40 80 60"
$9C 60 88 40 90 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
resource CURS (260, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 80 40 80 40 80 60"
$9C 60 84 40 84 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
resource CURS (259, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 80 40 80 40 80 60"
$9C 60 82 40 80 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
resource CURS (258, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 80 40 80 40 80 60"
$9F 60 80 40 80 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
resource CURS (257, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 80 40 81 40 82 60"
$9C 60 80 40 80 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
resource CURS (256, locked, preload) {
$3F 00 3F 00 3F 00 3F 00 40 80 84 40 84 40 84 60"
$9C 60 80 40 80 40 40 80 3F 00 3F 00 3F 00 3F,
$3F 00 3F 00 3F 00 3F 00 7F 80 FF C0 FF C0 FF C0"
$FF C0 FF C0 FF C0 7F 80 3F 00 3F 00 3F 00 3F,
{8, 8}
};
Animated Cursors Again
Erny Tontlinger
Steinfort, Luxembourg
Here are some comments to David Stoops Animated Cursors from the December 1988 Letters Column.
There is an error in the procedure InitWatch. The cursor id should be replaced by the cursor handle in the loop this:
/* 3 */
Watch[watchcount] := GetCursor(HiWord(longint(Watch[watchcount])));
The correct structure of a acur resource is:
/* 4 */
acur = record
frames: integer; {Number of frames(cursors) }
whichWatch : integer;
Watch : array[1..99] of CursHandle
{ size depends on frames }
end;
Then instead of using a fixed number of eight cursors, use Watch^^.frames, which happens to be eight for the Finder.
The acur and CURS resources should be locked, otherwise the VBL task runs into troubles with the memory Manager. The Finder uses the CURS id 4 (classic watch cursor), which in the System file is not locked. This cursor is also in ROM, from the Mac Plus machines on, so it would be good to set RomMapInsert to mapTrue before calling GetCursor.
SetUpA5 and RestoreA5 might not work as expected under MultiFinder. The value of A5 should be placed in a position, other than CurrentA5 in low memory globals, where the application can find it from within the VBL task. See Programmers Guide to MultiFinder (APDA# KMB017) the chapter The A5 world/VBL tasks.
Now something different. Why is it so difficult to print READABLE program listing in MacTutor? Your desktop publishing program seems to have problems with regular spaced tab positions. Anyhow I appreciate MacTutor very much. [Basically, we convert the source into WriteNow in a 3 1/2" width single column with tabs set at every eighth of an inch. Then we place it into PageMaker. Here is where a number of problems occur as you surmised. PageMaker doesn't exactly import tab information correctly and we have to manually fix it. -Ed]
More Animated Watch Cursors
Rob Terrell
Chapel Hill, NC
In the December issue, you printed a letter that showed how to animate the watch cursor as a VBL task, which I thought was strange since a few issues back you printed another letter that gave a very good reason not to: a VBL task can go on and on while the application has crashed, and the poor user will be stuck thinking everything is okay.
A better way to implement the animated watch is through a unit that just takes a single call to increment the cursor. Support for this exists in TML Pascal II; for those not fortunate as to own TML, heres a unit that does essentially the same thing:
{5}
Unit WatchItSpin;
(*Make sure you have copied the acur and CURS *)
(* resources from the Finder into your application *)
(* ResEdit is quite handy for this *)
Interface
Uses MemTypes, QuickDraw, OSintf, Toolintf;
type
aCur = record
whichWatch : longint;
watch : array[1..8] of CursHandle;
end;
acurPtr = ^ acur;
acurHandle = ^acurPtr;
var
currentWatch : integer;
WatchList : acurHandle;
Procedure InitSpinner;
Procedure SpinWatch;
Implementation
Procedure InitSpinner;
var
count : integer;
begin
watchList := acurHandle(GetResource(acur,0));
with watchList^^ do begin
for count := 1 to 8 do
Watch[count] := GetCursor(HiWord(longint( Watch[count])));
whichWatch := 0;
end;
end; (* InitSpinner *)
Procedure SpinWatch;
begin
with WatchList^^ do begin
if whichWatch >= 8 then whichWatch := 1
else whichWatch := whichWatch + 1;
SetCursor(Watch[whichWatch]^^);
end;
end; (* SpinWatch *)
end.
Most of this code was simply taken from David Stoops letter. However, to use this, you call InitSpinner when you initialize everything else at the beginning of your program; then you call SpinWatch each time through a loop or time-consuming process to increment it. Not quite as nice as having a VBL take care of it, but this way, if you crash, the hands will stop.
Correction to CompareString
Steve Brecher
Sunnyvale, CA
In John S. Stokes III's CompareString routine (Jan 89 Letters), use of the Ext.W instruction introduces an error:
Move.B (A0),D1 ; length of the first string
Ext..W D1; dbra uses word
Ext.W propagates the value in bit 15 of the register through bits 16..31. Therefore, it will not have the desired effect of clearing bits 8..15. Ext.B would not work either for cases in which the string length is greater than 127,i.e. , negative considered as a signed byte; in such cases Ext.B would set bits 8..15, making the unsigned word value very large. To convert an unsigned byte value to a word in a register, first clear the word and then move the byte into it:
;6
Clr.W D1
Move.B (A0),D1
Often programmers will write MoveQ #0,Dn instead of Clr.W Dn when the value in the upper word of the register either needs to be cleared also or is not of concern. MoveQ is always a longword operation.
A corrected CompareString would look like this:
;7
; A0 --> Pascal string 1
; A1 --> Pascal string 2
; Returns D0.L=1 if equal, 0 if not equal
; Alters A0-A1
CompareString:
MoveQ #0,D0
Move.B (A0),D0 ;gross len - 1
@0 Cmp.B(A0)+,(A1)+ ;loop until...
DBne D0,@0 ;...not equal or end
Seq D0;D0 := $FF if equal, else 0
Neg.B D0;D0 := 1 if equal, else 0
Rts
The Move.B instruction puts the length byte of string 1 into D0. We want to compare the entire string structures, including their length bytes. The length of the entire structure--the gross length--of a string is the the number of text bytes, plus one for the length byte. For a DBxx instruction, we want the gross length - 1, which is just the value of the length byte.
We use the DBne instruction to control the loop. DBxx will branch to the start of the loop if either the test condition (in this case, ne) is false or the loop count is exhausted. Thus DBxx should be read as branch until xx or counter expired. When control falls through the DBne, we use the Seq instruction to condition D0 to the result of the last Cmp.B; DBxx instructions do not alter the condition codes. If the strings (including their lengths) are equal, the result of the last Cmp.B will be eq and the DBne will have not branched because the D1 counter expired. If the length bytes or contents of the strings are not equal, the last Cmp.B result will have been ne and the DBne will have not branched because its until condition was met.
[Steve Brecher is a long time friend of MacTutor. We welcome his comments and hope to hear more from him in the coming months. -Ed]