Meter Control
Volume Number: 5
Issue Number: 11
Column Tag: Advanced Mac'ing
Inside Controls 
By Ken Earle, Toronto, Canada
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
[Ken Earle has been programming the Mac in C for about two years and still
thinks it is fun. In between Mac contracts, he waits for the price of Finale to drop]
If the Control Manager chapter of Inside Macintosh has you resignedly scrolling
back to reread QuickDraw, here’s a different point of view that might help. Or if scroll
bars have lost their thrill and you’re looking for a new toy, absorbing the code from
this article will let you design your own custom controllable rotary-indicator
digital-readout gizmos, guaranteeing hours of harmless entertainment.
First we’ll take a look at implementing meters (aw, you guessed) in a way that’s
functionally very close to the Control Manager (see the listings for Meter.h and
Meter.c). Then, after a strategy session on scroll bars, you’ll be ready for MeterTest,
an application with fully-functional scroll bars and windows that can serve as a design
rig for meters or as a skeleton for something grander (see MeterMain.c).
If you’re not using Think C, you’ll need to at least #include the standard Mac
header files, and check usage of the QuickDraw global ‘thePort’ (called ‘qd.thePort’ in
MPW C).
The Meter Manager
I don’t know about you, but if I’m given the choice between a detailed description
of a routine and the source code for it, I’d rather have the source code. E specially for
those darn scroll bars, where, as Ted Nelson might put it, everything gets deeply
intertwingled. To show how the Control Manager calls work on the inside and still end
up with relatively useful code, let’s take a shot at creating a cousin of the scroll bar,
namely something like that nifty meter on page 312 of Inside Macintosh Volume 1. To
better illustrate what the Control Manager does, the approach used is not to write a
CDEF but rather to imitate most of the Control Manager routines, with Meters
replacing Controls. The variations are minor enough that you should be able to get a
good idea of what a “Control” routine does by looking at the corresponding “Meter”
routine. For example, if you’re interested in TrackControl(), take a look at
TrackMeter() in the listing for Meter.c.
The routines in Meter.c do not duplicate all of the Control Manager’s functions
(but enough, I hope), and are virtually guaranteed not to resemble Apple’s source code
except in functionality since I didn’t peek at it, honest. Meter.c is meant to be read
with Inside Macintosh on your lap and should be otherwise self-explanatory, provided
you introduce yourself to what a meter is first.
Prepare To Make Your Meter
Figure 1 shows a typical meter. It has a needle that wiggles around in response to
a continual input, a set point indicator that can be used for an alarm or for closed-loop
control, four up and down “arrows” to move the set point around, a digital display, a
title, and some background drawing to make it look like a meter. The most important
difference between a meter and a scroll bar is that a meter doesn’t have a true
thumb-- the set point indicator would be the natural candidate, but it is too small and
slippery a target to be picked up and dragged around, e specially if it is used for
something like controlling the temperature in a nuclear reactor. Instead, meters have
a “background” part; a mouse down in anything except the up and down arrows will
cause FindMeter() to return a part code for the background, and TrackMeter() will
switch the digital display from the current input value to the set point value. For a
look inside the scroll bar thumb see “Where’s the thumb?” below.
Meters keep track of two values at once; the “meterValue”, which corresponds to
a regular “controlValue”, represents the set point indicator which is under the user’s
control by means of the up and down arrows, and the “needleValue” is the current
input to the meter and of course drives the ever-wandering needle. The needle has no
analog (bad pun intended) in a regular control.
Meters can be any size above a reasonable minimum, but their proportions are
fixed since they are delicate creatures to draw ( the dial looks funny if it isn’t round).
You supply the top, left, and right for a new meter, and NewMeter() calculates the
bottom for you. Many aspects of a meter’s appearance can be modified by playing with
the #defined constants at the top of the Meter.c listing, but for radical changes you’ll
want to dig into the details of CreateMeterBackground(), DrawNeedle(), etc. I like to
think I’ve deliberately left lots of room for improvement....
Although you can treat meters mostly the same way as scroll bars in an
application, there are two important differences. First, the meter needle usually needs
to be driven constantly, so you’ll probably want to call SetNeedleValue() at the top of
your main event loop--you’ll see an example of this in MeterMain.c. And second,
meters attach themselves as a linked list to their window’s refCon field, in the same
way that controls attach themselves to the window’s controlList field. If you’re
already using this field to hold a reference to your own data structure, you should add a
long int field “meterRef” to your data structure and replace the references in Meter.c
to
(MeterHandle)(wPk->refCon)
with something like
(MeterHandle)(myDataStructurePointer->meterRef).
Scroll Bars and Drawing
Scroll bars by themselves just keep track of the value of one integer, and show
you a rough indication of how big it is relative to a maximum and minimum. What you
do with that value is up to you. The code in MeterMain.c demonstrates in a general way
how to use scroll bars for controlling the position of fixed-size documents that contain
graphics within fully-functional windows. Meters are used for the graphics, and when
you build all of the listings into the application MeterTest you’ll also have a tool for
designing meters (see About MeterTest below).
The key to tying scroll bars, windows, and drawing together is to introduce
another system of coordinates (as if local and global weren’t enough). Think of the
graphics as being attached beneath the window to a piece of paper which we’ll call the
document. “Document” coordinates are for that piece of paper, running from 0,0 at
left top down to ‘page width’, ‘page height’ at right bottom, and coincide with
unscrolled coordinates when no scrolling has taken place. These numbers are usually
in pixels, since they then describe positions in the grafPort directly.
Scroll bars are used to relate local and document coordinates in the natural way
that comes about if you think of dropping the window on top of the document. The scroll
bar minimums are set to zero, corresponding to the top left of the document. The scroll
bar values record the horizontal and vertical coordinates of the top left of the window
in document coordinates (which are always at least as large as local coordinates). The
scroll bar maximums are equal to the amount of the document left unseen, so the
vertical maximum for example is equal to Document Height less the PortRect Height
less the 15 pixels covered by the horizontal scroll bar. To put it another way, see
Figure 2.
The overall strategy is to always draw in document coordinates by taking care of
scrolling before the low-level drawing routines are called. Done properly, your
drawing routines can pretend that they are always drawing to a giant screen that shows
the whole (unscrolled) document at once. “Taking care of scrolling” is very easy.
Before doing any drawing, shift the whole window into document coordinates by means
of SetOrigin(hBarValue, vBarValue), and shift the clipping region too with
OffsetRgn(thePort->clipRgn, hBarValue, vBarValue)--then shift back when you’re
done. When you’re deciphering where the mouse is, local points from GetMouse() can
be translated to document points by adding the scroll bar values to the appropriate
coordinates (see DocToLocal()).
Your drawing routines will draw to the right place if they take a point or
rectangle in document coordinates to start from, and then draw relative to the point or
rectangle without referring to the scroll bars. For example, the body of a function to
draw a straight horizontal line could look like
{MoveTo(docPoint.h, docPoint.v);
Line(widthInPixels, 0);}
However, something like
MoveTo(docPoint.h + hBarValue, docPoint.v + vBarValue);
probably won’t do what you intended, since it is trying to shift from local to document
coordinates, but that’s already been done beforehand. If you need to refer to the window
frame, the left edge of the window is still at thePort->portRect.left on the screen, but
remember that this value isn’t necessarily zero.
Drawing needs to be done whenever the user wants something new (eg mouse
down with a “pencil” tool, or -drag to resize a meter in MeterTest), and
when events cause a hidden part of the document to be exposed (eg activating a different
window, or scrolling). This redrawing as opposed to original drawing can all be
handled by one routine, and this same routine can even draw to the clipboard or
printer if you plan ahead. The strategy here is to: determine what part of the window
needs redrawing; shift to document coordinates; and draw only those objects that have
been newly exposed by calling the low-level drawing routines for those objects.
UpdateContents() is the redrawing function in MeterMain.c.
The easiest way to keep track of what part of the window needs redrawing is to
use a global region and set this region to the appropriate area just before calling the
redraw function. If you glance through MeterMain.c you’ll see that the region
updateRgn is always set just before calling UpdateContents(). In principle the
updateRgn can always be figured out exactly (see eg DoGrowWindow() and
ScrollProc()), but sometimes it is acceptably fast to just cop out by invalidating the
whole window and letting the generated update event take care of the drawing (see
DoZoomWindow(), and “case updateEvt” in main()).
I first ran into the idea of using document coordinates and a global update region
in “Hidden Powers of the Macintosh” by Christopher Morgan (The Waite Group), still
one of the best introductions to programming the Mac in spite of all the code being in
Pascal.
The redrawing for the arrow and paging parts of scroll bars is done in
MeterMain.c by the function ScrollProc(), where both the moving of the screen image
and the accumulation of the newly exposed region are managed by a single call to
ScrollRect(). You could if you prefer handle paging by invalidating the whole window
(less scroll bars) and calling UpdateContents()--this is faster, but less visually
appealing.
That just leaves the scroll bar thumb. I could have made it real easy for you, but
some people just can’t leave well enough alone....
Where’s the thumb?
If you call TrackControl() with no action procedure in response to a mouse down
in a scroll bar thumb, everything is taken care of for you. After the call, you just
redraw the window if the new control setting differs from the old one. You can even get
fancy and call ScrollRect() and UpdateContents() if the move was a small one, or
invalidate the whole window if it was a biggy.
One thing you can’t do this way, however, is keep track of where the thumb is
while the user is dragging it to and fro. In order to do this you have to know the
“potential” new control value for each new mouse position, but not even
TrackControl() bothers to figure this out. It just drags an outline of the thumb around
without regard for value, and only calculates a new value when the mouse button is
released. You could write an action procedure for TrackControl() that figures out
where the thumb is, but in the spirit of getting inside things I have written a
replacement for TrackControl() to be called for the thumb. ThumbControl() in
MeterMain.c does all that TrackControl() does for the thumb, and also calculates the
potential new control value for you (as well as the thumb’s physical position). You can
insert your custom routine calls at the places marked in the code, or modify the
function to accept pointers to functions if you can’t stand inelegance.
While you could use this approach to place a page number directly inside the
thumb, the approach used by WriteNow for example of putting the page number
alongside a scroll bar is simpler and also better--try putting a four-digit page
number inside the scroll box! MeterMain.c by the way shows how to put a numeric
display next to a scroll bar, something that’s e specially useful when designing or
debugging.
About MeterTest
If you build an application from the files in this article according to the
instructions at the top of the listing for MeterMain.c you’ll have MeterTest, which
sums everything up by presenting meters as graphic objects inside of windows with
scroll bars. The underlying documents which contain the meters are fixed in size (8
by 10"), and all the usual scrolling and window functions are implemented in a general
way except for the lowest-level drawing routines.
MeterTest can also serve as a test rig for modifying the appearance and function
of meters. You’ll have use an edit-and-recompile approach, but fortunately MeterTest
is small and Think C is fast. Many changes can be made by altering the #defines at the
top of Meter.c, but others will require changing the routines that create the meter’s
background and draw the other parts. The titles for the four default meters and two
windows are at the top of MeterMain.c, and other standard parameters for the meters
are set inside the function MakeTwoMeters().
To move a meter around, Option-drag it. Command-drag on a meter to resize it
constantly while the button is down. The display box at lower right of the window
shows the horizontal and vertical scroll bar values unless you resize a meter, in which
case it shows the meter width and height. Click on an up or down triangle in a meter to
move the set point indicator up and down, or click anywhere else on a meter to change
the meter’s display from the current needle input to the set point value.
Bonus round
In the accompanying code you’ll also find examples of simple animation, using
integer tables to represent transcendental functions, ring-shaped clipping, how to
shadow any polygon, handling linked lists and pointers to functions...and after you’ve
worked through it all, you’ll never again be intimidated by a stupid thumb.
Listing: Meter.h
/*Meter.h - MeterRecord definition & interface function protoypes*/
/* special partCode for meter */
#define inBackground 24
struct MeterRecord