TweetFollow Us on Twitter

Designing an Object
Volume Number:6
Issue Number:8
Column Tag:C Workshop

Designing an Object with THINK C

By Enrico Colombini, Brescia, Italy

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Lost and found

As soon as my THINK C 4.0 copy arrived from Symantec, I eagerly dived into the manual, hastily skimmed through the ‘standard’ section and started reading with religious care Part Four: Object-Oriented Programming. Many hours later, I was suffering from a bad case of “Oh, no! Not again!”. It seemed I had to reprogram my brain from scratch, repeating the painful experience of my first encounter with Inside Macintosh (loose-leaf version).

Fortunately, people at Symantec correctly predicted this particular illness and included the appropriate cure in the package: example programs. At page 203, the manual says: “The easiest way to [create an application] is to take the Starter demo program and build from it”.

So I heeded the advice and decided to try and write a real application (there is no sense in writing do-nothing programs). I have to say, it WORKS! In a couple of weeks all the pieces fell into place and now I am using the Class Library with ease.

This article describes the first object (I should more correctly say: the first class) I created using the TCL (THINK Class Library), along with some of the problems I encountered, and how I dealt with them.

But first, let me heartily praise people at Symantec for giving us not only the example programs, but all the source code of the Library itself! Apart from being a wonderful (and wonderfully commented) piece of code, it is there to look at every time you have a problem (“how can I do it? Let me see how THEY did it”). These listings alone are worth much more than the price of the the package (I hope there aren’t any Symantec executives around, or they’ll promptly raise it).

The THINK Class Library

MacTutor readers already know about object-oriented programming and C++, so I’ll pass directly to Symantec’s implementation. Let’s start with the bad news: it’s not C++, but only a “C with Classes”. You can define classes, create objects (i.e. instances of these classes) and destroy them. That’s all.

Now the good news: with ‘only’ these enhancements over classic C, Michael Kahl (the brain behind THINK C) & Co. created the THINK Class Library (TCL). Or, how to make the life of the Mac developer much easier.

(The TCL seems to be a MacApp competitor. Unfortunately, as I can’t stand Pascal, I know nothing of MacApp yet: I am waiting for Apple’s C++).

In short, the TCL is a library. It doesn’t contain functions: it contains classes from which you can create objects. Suppose you need a button: you just create a new object of class CButton (C stands for Class):

/* 1 */

  CButton *b1;  /*b1 points to a button object*/

  b1 = new(CButton);  /*create new object*/

Since the THINK C does not provide for automatic constructor functions (as C++ does), you have to ask this new object to initialize itself, by sending it the message IButton:

!codeexamples

tart/* 2 */

  b1->IButton();  /*initialize button*/

You’ll notice that an object behaves syntactically like a structure. In fact, it is a special kind of structure containing both variables (instance variables) and functions (methods) which operate on these variables.

The TCL is composed of a number of different classes, closely related to everyday Macintosh objects (a button, for example).

What is really great about the TCL is that all the classes in the library are carefully designed to interact among themselves by exchanging messages (implemented as function calls) unseen by the programmer. For example, when an update event occurs, the objects that need to be redrawn automatically receive a Draw message. You may choose to ignore the redraw problem. Just another example: you may assign a command number to a button:

/* 3 */

  #define B1_PRESSED  1234L

  b1->SetClickCmd(B1_PRESSED);

When the user presses this button, the supervisor of the button (that is, the object to which the button pertains) automatically receives a DoCommand() message with B1_PRESSED as argument.

Many programmers (including myself) usually don’t like the word ‘automatically’. It generally means that you lose control over what’s happening in your program. In this case, however, you still have complete control over the behaviour of every part of the program: if you aren’t satisfied, you may change or redesign it as you like.

To modify a class you just declare a subclass, that is a new class which is ‘almost like’ the parent class, except for all the differences that you specify. You can add new instance variables, add new methods (for example, an AutoClick() method to make a button press itself for a given amount of time), or redefine (override) existing methods. You may redesign the entire library, if you want to.

Put a pane in your window

When you initialize a TCL object, you must usually declare a supervisor of that object A supervisor is another object which will handle the messages that your object doesn’t know how to handle (got a problem? Pass it on to your boss). It works just like HyperCard, except that there is no connection with the visual relationship of objects.

The enclosure of an object, on the other hand, is the object visually enclosing the object you create. It may be the object’s supervisor, but it need not be.

So there are two hierarchies in the program, instead of the one of HyperCard: the visual hierarchy (object enclosed in object enclosed in object ...) which is used for purposes of redrawing and assigning mouse clicks, and the chain of command (object supervised by object supervised by object ...) which is used to pass on commands (keypresses and menu choices, but also button clicks and other specialized kinds of messages).

The visual hierarchy is based on panes. A pane is (obviously) something you put into a window. All visible objects of the TCL which you put inside a window are created from subclasses of class CPane.

For example, a button is a special kind of pane including a Mac control and the methods to operate on it. Thus the CButton class is a descendant of the CPane class, from which it inherits all instance variables and methods.

A pane defines a rectangular area which sits completely inside another pane, with the only exception of the main pane that usually occupies all the window’s interior. Panes may overlay, as when you create a button (which is a pane) inside a pane.

When you have fully digested these few concepts, you are more than half way towards fully understanding the THINK Class Library. Now, as I am not here to write a tutorial for the TCL, let me start with the description of my class.

A DigitalControl object

In the educational program I was designing, the user had to be able to set some values with 1-digit precision. A scroll bar was too coarse for the job, and not very appealing, so I decided to create a new kind of control.

When I say “control”, I don’t refer to a Macintosh CNTL like a scroll bar or a button, but to a generic device for controlling (selecting) a value: think of the Cache Memory size selector in the Control Panel, for example.

I needed many of these controls, so I decided it was time to create a new class, from which objects could be created with the “new” operator.

Figure 1

Figure 1 shows how I wanted my DigitalControl to be: basically, two buttons and a digital display. The right-hand button raises the displayed value, while the left-hand button lowers it.

Figure 2

Figure 2 shows how a DigitalControl can be built using standard TCL objects: a Pane including two Buttons and a Border, which in turn includes a StaticText. All of the classes from which these objects are created are subclasses of the CPane class.

Add a gray rectangle inside the base pane of the object and you get the actual appearence of a DigitalControl, as shown in Figure 3.

Figure 3

You may say that the visual effect could be better if the gray rectangle filled all the base pane, instead of leaving white corners around the buttons. In fact, the first version of my DigitalControl was transparent (as in Figure 1), so as to show the picture I put beneath it (using a Picture, another sort of Pane). Unfortunately this first release of the TCL has problems in correctly redrawing a button over a gray background: the corners are correctly redesigned if you move another window overlaying them, but not if you close it.

This seems to have something to do with the fact that all panes are rectangles, not regions. Anyway, to avoid aesthetical problems I restricted the gray rectangle to the area between the two buttons.

Listing 1 is the header (.h) of my DigitalControl class, that is the only part of the class that another program needs to know to be able to use it. Listing 2 is the implementation (.c) of the DigitalControl class.

In search of a back door

Once the object is designed, all is simple and straightforward: you just start to write code and in ten minutes you have the object up and running. If you believe this, please send me your address: I have a beautiful Venice palace to sell you at a very interesting price. This sort of thing happens only in SF novels (in fact, it could be the last of SF utopias to materialize).

In the real world of programming, there is always something between you and a simple solution, and this something is usually a big problem.

In the case of my DigitalControl object, here’s the problem: when the user holds down the mouse into one of the two buttons, the digital display should increase its displayed value. But the CButton class is designed to send a DoCommand() message when the user releases the mouse, not while the mouse is held down in the button.

Sure, you can assign to the button a procedure to be called from the Toolbox while the button is held down, but this is a Pascal procedure, not a message to an object. Such a procedure should in turn call back the button or its supervisor, sending it a message of, say, Track().

All right, then? Not at all! You can’t call back the object from a Pascal procedure, because you don’t know from which object the original call came!

To send a message to an object, you must have a pointer to the object (which is actually a handle in THINK C), or you must send the message from inside the object itself. But the Pascal procedure is outside the object and has no way to retrieve a pointer to it.

There would be no problem if you could define a Pascal procedure inside an object, as a special kind of method, but you can’t. In fact, it would be useless anyway: the Toolbox wants the address of the procedure to call while tracking a button, but an object can be freely relocated by the memory manager, so it is not possible to rely on the address of one of its methods (or instance variables, for that matter). This is stated very clearly in the THINK C manual.

How did I solve the problem? Well, I lied before (never trust a programmer) when I said that the Pascal procedure has no way to find out what the object that called the Toolbox was. There is a global variable in the TCL which records the last object clicked. Fortunately, the recording is made when the user presses the mouse, before telling the object that the mouse has gone down inside it.

Here is my back door for reentering the object: the Pascal procedure (Track) consults the TCL global variable gLastViewHit, which contains a pointer to the button currently pressed. It uses this pointer to identify the supervisor of the button (by peeking at its itsSupervisor instance variable), which is the DigitalControl object.

Having finally acquired a pointer to the DigitalControl object (a cast is needed here to satisfy the compiler), the Pascal procedure sends it a TrackValue message with the button pointer as parameter (to identify which button is currently pressed). By sending the message directly to the button’s supervisor (the DigitalControl), I avoided designing a subclass of CButton (it is only lazyness of my part, maybe it would have been more elegant to design a new class, say a CTrackButton).

We are almost through: the last thing to do (before returning) is to send a Prepare() message to the button to restore its drawing environment. The poor button is quite unaware that a lot of things happened while it was pressed, and would get confused if its environment were suddenly changed (in fact, it would redraw itself at a different place).

The whole process I just described is implemented in my DigitalControl class by the Track procedure and the TrackValue method (see listing 2).

If this all seems a rather tricky solution, perhaps it is because it is a rather tricky solution. If you find a better one, please let me know (I like polishing my code to have it shine in the morning sun).

By the way, there is a bug in the TCL concerning button tracking: if you move the mouse outside a button (while still holding it down) the button is correctly deselected but the tracking procedure continues to be called! I’ve not been able to spot the problem by looking at the TCL code; maybe it has something to do with the TCL manipulation of regions.

A refreshing display

The value displayed in a DigitalControl (using a StaticText object) should change continuously while a button is held down (if it has not reached a limit, of course). One may think that a Refresh() message sent to the StaticText object (at every change of the value) would be enough to have the number redrawn inside it.

That is not correct. If you send a Refresh() message, the TCL updates what is displayed on the screen only in response to the next update event. The correct message to send to the StaticText object is Draw(), which redraws its text (that is, the representation of the DigitalControl’s value) immediately.

Unfortunately, the Draw() message doesn’t adjust the update region, so when the button is relased the display is redrawn yet another time. Since the display area is cleared to white before this last redrawing of the text which it contains, the visual effect is far from appealing.

To avoid the last redraw of the text in the StaticText object (our display), the Draw() message should be bracketed with a BeginUpdate() and an EndUpdate() call. Moreover, for the process to work properly, a Prepare() message has to be sent to the StaticText before the Draw() message.

The UpdateDisplayNow() method in listing 2 does this real time redrawing of the text inside the display.

An automatic gearbox

The dramatic increase in speed from the old times when the 128K Mac was first introduced can lead to new problems in the user interface. If you’ve ever tried to scroll a text up a single line in (say) MS Word on a Mac IIci, you know what I mean: the shortest click actually scrolls much more than just one line.

To avoid this sort of problems, the stepping rate of the DigitalControl display is controlled by a tick-related counter, keeping the rate constant on every flavour of Macintosh.

However, having a fixed delay between display steps has its drawbacks too: the user cannot move rapidly to another displayed value, but has to wait patiently for the display to move a digit at a time. This is clearly not acceptable.

The problem has been solved with a three-speed automatic gearbox. When the ‘Up’ button is pressed, the TrackValue() method (see listing 2) moves the display up one digit (actually, it adds stepSlow to the current value). If the mouse is still down, TrackValue() ignores it for firstDelay ticks, then starts updating the display at a faster rate (reptDelay). After a given number of these steps (the threshold), the step rate is changed from stepSlow to stepFast while the step rate is kept constant (stepFast).

From the user’s point of view, a single click moves the display up one digit (actually, stepSlow digits). Holding the mouse down, the display starts rolling digit by digit (stepSlow by stepSlow). After a number of steps, the display seems to move much faster (actually, the speed hasn’t changed, but the value is now changed by stepFast at every step).

Summing this up, a single click is guaranteed to move the display up (or down) by the small possible increment, while by simply holding the mouse down the upper (lower) limit of the display value can be reached in a reasonable amount of time.

By exprimenting, I choose 300 msec (18 ticks) and 100 msec (6 ticks) for the first delay (single click) and the repeating delay (mouse held down) respectively. Step rates and threshold can be selected via a SetSteps() message.

How to send boss a message

Objects should be designed as to be reusable. Why make all the effort of designing a reliable DigitalControl object, to have it restricted to display and control integer numbers only?

In fact, a DigitalControl is much more flexible than that: it is able to display all sorts of information, as long as every displayed value can be paired to an integer.

In other words, the internal value of a DigitalControl is an integer, but what is displayed for every possible value of this integer is up to the programmer. For example, one could choose to have the value move between 1 and 12, and to display month names instead.

By default, the SetValue() method displays the value as a five-digit number, with leading zeros. How can this be changed by the programmer?

It would of course be impossible to provide for all possible sorts of display in the DigitalControl object itself: the object has to call somebody else, to do the job of setting the actual text to display for the current value.

The trouble is: how can the DigitalControl object send a message (say, a ConvertValue() message) to somebody, if this somebody is not known by the DigitalControl? Moreover, the DigitalControl class (CDigitalControl) should be recompiled #including the target’s header, to let the compiler know of the existance of the ConvertValue() message in the target class, and thus allow the sending of the message. The whole concept of isolation between objects would fall to pieces!

In plain words, how could the DigitalControl object know that another object (the target) could accept a given message, if the DigitalControl class has been already compiled without knowlege of the target class?

Passing a message down (i.e. to an object created by the sender) is simple. Passing a message up (i.e. to the object which created the sender) is much more difficult, unless provisions for this were taken in the earlier stages of design of the whole structure.

And this is indeed the case. A solution can be found by looking at the structure of the TCL, and in particular at the chain of command: every object has a supervisor. More exactly, every Bureaucrat has a supervisor (all visible objects are Bureaucrats). This is used not only to pass up keypresses (including menu command keys), but also for local communication, as in this case.

I said before that a Button sends a DoCommand() message to its supervisor every time it gets pressed. The command number to pass as argument in the DoCommand() message has been set beforehand via a SetClickCommand() message.

Since every Bureaucrat can be a supervisor, every Bureaucrat has a DoCommand() method. The supervisor of the DigitalControl object has one, too. So a DoCommand() message is a safe way of passing a message up to the boss.

The SetDisplayCmd() method sets up the command number for the DigitalControl object. If it is not NULL, this number will be passed to the supervisor (as argument of a DoCommand() message) every time that the DigitalControl’s value changes. The supervisor has the responsibility of building the string to display for the current value, and to pass it back to the object with a SetDisplayText() message.

Conflicting Commands

There is a point, in the TCL, which seems to lead to possible trouble. I must say that my experience with the TCL is limited, and therefore maybe it is only a misunderstanding of my part. Anyway, I’ll state the problem as I can see it.

Command numbers are global to the application. ‘Global’ is not a nice word in programming, especially in object-oriented programming. Using something global means often asking for trouble.

Global command numbers are all right as long as they are used for menu items, which are indeed global to the application. But using command numbers for local communication is another matter.

Here is an example of possible trouble: you design an object (say, a Grunt) which includes a Button. The button will communicate to the Grunt object via the DoCommand() message, sending it a command number (say, 12345L) when it gets pressed. This command number never goes farther than the supervisor of the button (the Grunt object): it is intercepted and used at that level.

Now suppose you are developing an application which uses a Grunt object: you will assign command numbers to your menu items (that is a nice idea in TCL: forget about stupid positional codes for menu items). If the chain of command includes the Grunt object, all Command-key equivalents send a DoCommand() message to the Grunt object. If they are not recognized, they are passed up the chain of command until somebody handles them (for instance, the Bartender which handles the menu bar).

What if, by chance, the command number you assigned to one of your menu items is the same 12345L used internally by the Grunt object to receive commands from its private button? Pressing the Command-key will send a button-pressed message to the Grunt instead of selecting a menu item!

The solution chosen by the TCL is to allocate command numbers at the highest level (the Application) and send them down the chain when objects are created. This, however, would be quite annoying if there are many hidden layers of objects.

A better solution would be to use a message different from DoCommand() for local communication, but this doesn’t seem to be designed into the TCL.

A simpler solution would be to reserve some command numbers for local communication only. For example numbers beginning with $4C (that is, ‘L’), so that local command numbers could be specified as ‘L001’ or ‘Lbut’ (the THINK C compiler accepts a four-character initializer for long integers).

This is a decision Symantec should make, however, if complex objects has to be shareable among developers.

How to use a DigitalControl

Listing 3 (header) and Listing 4 (implementation) show an example of usage of the DigitalControl class.

They are part of a class I used in my educational program: the WavePanel class. Only the parts regarding DigitalControl objects are included here. An object of WavePanel class should be declared and initialized in the Document class file.

Pointers to five DigitalControl objects are declared as instance variables in the WavePanel class header file, and initialized in the WavePanel implementation file.

The DoCommand() method displays the values of the five DigitalControls in three different ways, according to their usage. If a command isn’t coming from a DigitalControl object, it is passed up to the supervisor.

The five DigitalControls make use of the same set of resources, detailed in Table 1. These resources had been built using a copy of ResEdit modified with the special templates included in the THINK C package.

Figure 4

Figure 4 shows the result of all this work: a control panel including five DigitalControl objects. The picture of the panel background comes from the PICT resource specified by the WavePanel creator (i.e. the Document) using the IWavePanel() message. The white spaces in the panel are of course for CheckBoxes and RadioButtons.

As I said before, this is not a tutorial explaining how to use the Think Class Library. It is a working example intended to show how to design and build a new class using existing ones.

Designing a new class is not always simple, but using classes and the TCL can really improve the quality of life of a Mac programmer. At least, I hope so.

Table 1: resources for my DigitalControl:

- Pane (class: CPane) -

Visible: True

Active: True

Wants Clicks: True

Width: 83

Height: 20

Horiz Location: 0

Vert Location: 0

Horiz Sizing: 0

Vert Sizing: 0

Auto Refresh: True

Print Clip: 1

- Bord (class: CBorder) -

Visible True

Active True

Wants Clicks True

Width 37

Height 16

Horiz Location 0

Vert Location 0

Horiz Sizing 0

Vert Sizing 0

Auto Refresh True

Print Clip 1

Thickness 1

Drop Shadow Len 0

- StTx (class: CStaticText) -

Visible True

Active True

Wants Clicks True

Width 0

Height 0

Horiz Location 0

Vert Location 0

Horiz Sizing 0

Vert Sizing 0

Auto Refresh True

Print Clip 1

Bounds 1,1,13,34

Horizontal Scale 1

Vertical Scale 1

Vert Position 0

Horiz Position 0

Line Width 0

Whole Lines False

About the author

A former electronic designer, Enrico Colombini is a writer and game author that currently earns a decent living by writing interactive programming courses for (ahem) MS-DOS. He is very, very patiently waiting for Apple to return (in Europe) into the low-end educational market.

Listing 1:  CDigitalControl.h

/*************************************************
 CDigitalControl.h
 
 Interface for the DigitalControl Class
 
 © 1989 Enrico Colombini. All rights reserved.
*************************************************/
#define _H_CDigitalControl/*include only once*/

#include <CPane.h>
#include <CButton.h>
#include <CBorder.h>
#include <CStaticText.h>

 /*** class declaration: ***/

struct CDigitalControl : CPane {

 /* instance variables: */

 CButton*dnBtn;  /*down/up buttons*/
 CButton*upBtn;
 Rect   grayRect;/*rect between buttons*/
 CBorder*border; /*border around display*/
 CStaticText*disp; /*digital display*/
 short  value;   /*current value*/
 short  minValue;/*allowed limits*/
 short  maxValue;
 short  stepSlow;/*sigle & lowspeed rate*/
 short  stepFast;/*high-speed step rate*/
 short  threshold; /*steps to rate change*/
 short  firstDelay;/*delay after 1st step*/
 short  reptDelay; /*after following steps*/
 long   lastDown;/*last mouse dn event*/
 long   nextWhen;/*time for next step*/
 short  trackSpeed;/*1=single,2=slow,3=fast*/
 short  trackCount;/*steps from spd change*/
 long   displayCmd;/*issue before display*/

 /* overridden methods: */
 
 void Draw(Rect *area);   /*draw object*/

 /* new methods: */

 void IDigitalControl(    /*init object*/
 short resID,
 CView *anEnclosure,
 CBureaucrat *aSupervisor);
 void TrackValue(CView *btn); /*internal use*/
 void SetValue(short val);/*set value*/
 void SetDisplayText(char *txt); /*displ. text*/
 void UpdateDisplayNow(void); /*update displ.*/
 void SetMinValue(short val); /*set low limit*/
 void SetMaxValue(short val); /*set hi limit*/
 void SetSteps(  /*step rates*/
 short slow,short fast,
 short thr);
 void SetDisplayCmd(long cmd);/*set command*/
 short GetValue(void);    /*return value*/
};
Listing 2:  CDigitalControl.c

/*************************************************
 CDigitalControl.c
 
 The DigitalControl Class
 
 This is NOT a Macintosh control with a CNTL
 resource. It is a specialized sort of ‘control’
 with two buttons and a digital display, useful
 for selecting a value when high precision and
 interactive value display are needed. A variable-
 speed mechanism allows for fast selection of the
 desired value, while a tick-related delay control
 keeps the reaction time independent from CPU
 speed. 

 SUPERCLASS = CPane

 © 1989 Enrico Colombini. All rights reserved.
*************************************************/

#include “CDigitalControl.h”
#include <stdio.h> /*for sprintf*/
#include <string.h>

/*************************************************
 Class library globals accessed by CDigitalControl
*************************************************/
 /*the view the mouse went down into*/
extern CView *gLastViewHit; 
 /*event record of last mouse down*/
extern EventRecord gLastMouseDown; 

/*************************************************
 Toolbox-called local procedure, not part of the
 CDigitalControl object 
*************************************************/
static pascal void Track(
 ControlHandle macControl,short whichPart);

/*************************************************
 IDigitalControl
 
 Initialize a CDigitalControl object. The
 intialization is typical of an object descending
 from the CView class. However, there is no rType
 parameter and the resID parameter is used to load
 four related object templates from the resource
 fork: a Pane, a Button, a Border and a Static
 Text. They must all have the same resID.
*************************************************/
void CDigitalControl::IDigitalControl(
 short resID,    /*for Pane, btn, Bord, StTx*/
 CView *anEnclosure,
 CBureaucrat *aSupervisor)
{
 Rect r;

 inherited::IViewRes(/*first, init main pane*/
 ‘Pane’,resID,anEnclosure,aSupervisor);

 dnBtn = new(CButton); /*create down button, « */
 dnBtn->IButton(resID,this,this);
 dnBtn->SetTitle((unsigned char *)”\p«”);
 dnBtn->SetActionProc(Track); /*set re-entry*/
 
 GetFrame(&grayRect);/*prepare gray rectangle*/
 dnBtn->GetFrame(&r);
 InsetRect(&grayRect,r.right,0); /*between btns*/
 
 upBtn = new(CButton);  /*create up button, » */
 upBtn->IButton(resID,this,this);
 upBtn->Offset(grayRect.right,0,FALSE); /*align*/
 upBtn->SetTitle((StringPtr)”\p»”);
 upBtn->SetActionProc(Track); /*set re-entry*/
 
 border = new(CBorder);   /*create border*/
 border->IViewRes(‘Bord’,resID,this,this);
 border->CenterWithinEnclosure(TRUE,TRUE);
 
 disp = new(CStaticText); /*create display*/
 disp->IViewRes(‘StTx’,resID,border,this);
 disp->FitToEnclosure(TRUE,TRUE); /*inside Bord*/
 disp->SetFontName((StringPtr)”\pMonaco”);
 disp->SetFontSize(12);
 disp->SetAlignment(teJustCenter); /*center*/
 
 minValue = 0;   /*set defaults*/
 maxValue = 100;
 SetValue(minValue); /*set current value*/
 stepSlow = 1;
 stepFast = 5;
 threshold = 20;
 firstDelay = 18;/*300 msec*/
 reptDelay = 6;  /*100 msec*/
 
 lastDown = -1L; /*ensure difference*/
 nextWhen = 0L;  /*ensure acceptance*/
 trackSpeed = trackCount = 0; /*not tracking*/
 
 displayCmd = 0L;/*don’t disturb boss*/
}

/*************************************************
 Draw {OVERRIDE}
 
 Draw the CDigitalControl object. Apart from the
 gray rectangle between the two buttons, the
 initialization is done automatically by the
 methods inherited from the superclass(es). Note
 that, as FillRect may move memory, it is unsafe
 to pass the address of the instance variable
 grayRect. Passing a local variable (r) is ok.
*************************************************/
void CDigitalControl::Draw(Rect *area)
{
 Rect r = grayRect;/*FillRect may move mem!*/

 FillRect(&r,gray);
 inherited::Draw(area);
}

/************************************************
 Track {NOT AN OBJECT METHOD - called by Toolbox}
 
 Called by Toolbox’s TrackControl when the user
 holds down the mouse button inside one of the
 DigitalControl buttons. It can re-enter this
 object by referring to the TCL global variable
 gLastViewHit, which record the object the mouse
 went down into (it). The supervisor of this
 button is our DigitalControl object. The pointer
 to (it) is passed to the TrackValue method to
 tell it what button is currently pressed. After
 the tracking, the drawing environment of the
 original button must be restored for the Toolbox
 to work properly.
*************************************************/
static pascal void Track(
 ControlHandle macControl, short whichPart)
{
 CView *it;
 
 it = gLastViewHit;/*identify caller*/
 
 ((CDigitalControl *)(it->itsSupervisor))
 ->TrackValue(it); /*Track!*/

 it->Prepare();  /*restore environment*/
}

/*************************************************
 TrackValue

 Called repeatedly (by way of pascal Track, above)
 while the mousebutton is hold down inside one of
 the DigitalControl buttons. The global variable
 gLastMouseDown is used to differentiate the first
 call after the mouse was pressed, to implement
 the 3-speed step control of the value change.
*************************************************/
void CDigitalControl::TrackValue(CView *btn)
{
 short first;
 short step;
 short dir;
 
 /*check if 1st entry, exit if not yet time*/
 first = (gLastMouseDown.when != lastDown);
 if (! first && TickCount() < nextWhen)
 return;
 
 /*if 1st entry: record time, set 1st delay*/
 if (first) {
 lastDown = gLastMouseDown.when;
 nextWhen = TickCount()+firstDelay;
 trackSpeed = 1;
 } else { /*not 1st entry, set repeat delay*/
 nextWhen = TickCount()+reptDelay;
 if (trackSpeed == 1) {   /*2nd speed*/
 trackSpeed = 2;
 trackCount = threshold-1;/*countdown*/
 } else {
 if (--trackCount == 0) { /*3rd speed*/
 trackSpeed = 3;
 }
 }
 }
 
 /*choose step, reverse if down button*/
 step = (trackSpeed < 3) ? stepSlow : stepFast;
 if (btn == dnBtn)  step = -step;
 
 SetValue(value+step);    /*set display value*/
 UpdateDisplayNow(); /*update immediately*/
}

/*************************************************
 SetValue
 
 Set current value, update display text in memory.
 Screen update will happen at the next update
 event. If no displayCmd has been set, a default
 5-digit numerical display is used. If a
 displayCmd has been set, a DoCommand message is
 sent to the DigitalControl’s supervisor. It may
 set the text to display as it likes by means of
 the SetDisplayText method.
*************************************************/
void CDigitalControl::SetValue(short val)
{
 char v[7]; /*display buffer*/
 
 value = val;    /*set new value, check it*/
 if (value < minValue)  value = minValue;
 if (value > maxValue)  value = maxValue;
 
 if (displayCmd == 0L) {  /*in-house display*/
 sprintf(v,”%05d”,value); /*build disp. text*/
 SetDisplayText(v);/*set for display*/
 } else { /*ask boss to display as it pleases*/
 itsSupervisor->DoCommand(displayCmd);
 }
}

/*************************************************
 SetDisplayText
 
 Set the text to display. Called by SetValue,
 directly or indirectly through the
 DigitalControl’s supervisor DoCommand method.
*************************************************/
void CDigitalControl::SetDisplayText(char *txt)
{
 /*update internal text*/
 disp->SetTextPtr(txt,strlen(txt));
}

/*************************************************
 UpdateDisplayNow
 
 Update the text shown on the screen without
 waiting for an update event. Used during the
 tracking of one of the two buttons. Make sure
 that the text won’t be updated again when the
 mouse button is released. A subclass of
 DigitalControl may choose to update the display
 faster by drawing the text directly using a
 Tollbox call, thus also removing the slight
 flicker during the update.
*************************************************/
void CDigitalControl::UpdateDisplayNow(void)
{
 Rect   r;
 GrafPtr gp;

 disp->GetFrame(&r); /*ready to draw*/
 gp = disp->GetMacPort();

 BeginUpdate(gp);/*don’t redraw later*/
 disp->Prepare();/* but draw now*/
 disp->Draw(&r);
 EndUpdate(gp);
}

/*************************************************
 SetMinValue
 
 Set the minimum allowed value, check ¾ max.
*************************************************/
void CDigitalControl::SetMinValue(short val)
{
 minValue = val;
 if (minValue > maxValue)  minValue = maxValue;
 if (value < minValue)  SetValue(minValue);
}

/*************************************************
 SetMaxValue
 
 Set the maximum value allowed, check   min.
*************************************************/
void CDigitalControl::SetMaxValue(short val)
{
 maxValue = val;
 if (maxValue < minValue)  maxValue = minValue;
 if (value > maxValue)   SetValue(maxValue);
}

/*************************************************
 SetSteps
 
 Set single & slow speed step, high speed step,
 and number of steps before changing speed
 (threshold).
*************************************************/
void CDigitalControl::SetSteps(
 short slow, short fast, short thr)
{
 stepSlow = slow;
 stepFast = fast;
 threshold = thr;
}

/*************************************************
 SetDisplayCmd
 
 Set command number to send to supervisor while
 tracking.
*************************************************/
void CDigitalControl::SetDisplayCmd(long cmd)
{
 displayCmd = cmd; /*setup callback*/
 SetValue(value);/*boss, please update*/
}

/*************************************************
 GetValue
 
 Return current value: so this object does
 something, after all!
*************************************************/
short CDigitalControl::GetValue(void)
{
 return value;
}
Listing 3:  CWavePanel.h

/*************************************************
 CWavePanel.h
 
 Part of the interface for the WavePanel Class,
 to show how to use the DigitalControl class.
 
 © 1989 Enrico Colombini. All rights reserved.
*************************************************/
#define _H_CWavePanel.h   /*include only once*/

#include <CPicture.h>
#include “CDigitalControl.h”

 /*** class declaration ***/

struct CWavePanel : CPicture {

 /* instance variables: */
 
 CDigitalControl *ctrAmpA;/*A amplitude*/
 CDigitalControl *ctrFreqA; /*A frequency*/
 CDigitalControl *ctrAmpB;/*B amplitude*/
 CDigitalControl *ctrFreqB; /*B frequency*/
 CDigitalControl *ctrPhaseB;/*B phase*/
 
 /* overridden methods: */
 
 void DoCommand(long theCommand);
 
 /* new methods: */

 void IWavePanel(short resID,
 CView *anEnclosure,
 CBureaucrat *aSupervisor);
};

Listing 4:  CWavePanel.c

/*************************************************
 CWavePanel.c
 
 Part of the WavePanel class, to show how to use
 the DigitalControl class.

 SUPERCLASS = CPicture

 © 1989 Enrico Colombini. All rights reserved.
*************************************************/
#include “CWavePanel.h”
#include <stdio.h>

 /*rsrc ID for DigitalControl object:*/
#define digitalControlResID 600

 /*command numbers for display update*/
#define DISPLAY_AMPA_CMD  2000L
#define DISPLAY_FREQA_CMD 2001L
#define DISPLAY_AMPB_CMD  2002L
#define DISPLAY_FREQB_CMD 2003L
#define DISPLAY_PHASEB_CMD2004L

 /*DigitalControls positions*/
#define CTR_HPOS 38
#define CTR_AMPA_VPOS31
#define CTR_FREQA_VPOS    56
#define CTR_AMPB_VPOS154
#define CTR_FREQB_VPOS    179
#define CTR_PHASEB_VPOS 204

 /*frequency limits and display steps*/
#define MIN_FREQ 50
#define MAX_FREQ 1800
#define FREQ_STEP40
#define FREQ_THRESHOLD    40

 /*non-zero starting values*/
#define START_AMPA 100
#define START_FREQA600
#define START_FREQB600

/*************************************************
 IWavePanel
 
 Initialize a CWavePanel object. The intialization
 is typical of an object descending from the
 CPicture class. However, there is no rType
 parameter, and ‘PctP’ is used instead.
*************************************************/
void CWavePanel::IWavePanel(
 short  resID,
 CView  *anEnclosure,
 CBureaucrat*aSupervisor)
{
 /*first, init itself*/
 inherited::IViewRes(
 ‘PctP’,resID,anEnclosure,aSupervisor);
 
 /*create a DigitalControl for Amplitude A*/
 ctrAmpA = new(CDigitalControl);
 ctrAmpA->IDigitalControl(
 digitalControlResID,this,this);
 ctrAmpA->Place(CTR_HPOS,CTR_AMPA_VPOS,FALSE);
 ctrAmpA->SetDisplayCmd(DISPLAY_AMPA_CMD);
 ctrAmpA->SetValue(START_AMPA);

 /*create a DigitalControl for Frequency A*/
 ctrFreqA = new(CDigitalControl);
 ctrFreqA->IDigitalControl(
 digitalControlResID,this,this);
 ctrFreqA->Place(CTR_HPOS,CTR_FREQA_VPOS,FALSE);
 ctrFreqA->SetDisplayCmd(DISPLAY_FREQA_CMD);
 ctrFreqA->SetMinValue(MIN_FREQ);
 ctrFreqA->SetMaxValue(MAX_FREQ);
 ctrFreqA->SetSteps(1,FREQ_STEP,FREQ_THRESHOLD);
 ctrFreqA->SetValue(START_FREQA);
 
 /*create a DigitalControl for Amplitude B*/
 ctrAmpB = new(CDigitalControl);
 ctrAmpB->IDigitalControl(
 digitalControlResID,this,this);
 ctrAmpB->Place(CTR_HPOS,CTR_AMPB_VPOS,FALSE);
 ctrAmpB->SetDisplayCmd(DISPLAY_AMPB_CMD);

 /*create a DigitalControl for Frequency B*/
 ctrFreqB = new(CDigitalControl);
 ctrFreqB->IDigitalControl(
 digitalControlResID,this,this);
 ctrFreqB->Place(CTR_HPOS,CTR_FREQB_VPOS,FALSE);
 ctrFreqB->SetDisplayCmd(DISPLAY_FREQB_CMD);
 ctrFreqB->SetMinValue(MIN_FREQ);
 ctrFreqB->SetMaxValue(MAX_FREQ);
 ctrFreqB->SetSteps(1,FREQ_STEP,FREQ_THRESHOLD);
 ctrFreqB->SetValue(START_FREQB);

 /*create a DigitalControl for relative Phase*/
 ctrPhaseB = new(CDigitalControl);
 ctrPhaseB->IDigitalControl(
 digitalControlResID,this,this);
 ctrPhaseB->Place(
 CTR_HPOS,CTR_PHASEB_VPOS,FALSE);
 ctrPhaseB->SetDisplayCmd(DISPLAY_PHASEB_CMD);
 ctrPhaseB->SetMinValue(-180);
 ctrPhaseB->SetMaxValue(180);
}

/*************************************************
 DoCommand {OVERRIDE}
 
 Handle commands from enclosed DigitalControls
 (display update), or pass command to supervisor
 if not recognized.
*************************************************/
void CWavePanel::DoCommand(long theCommand)
{
 enum   type { AMPL, FREQ, PHASE };
 CDigitalControl *dctrl = NULL;
 short  dispType;
 short  val;
 char   buf[6];
 
 switch(theCommand) {

 case DISPLAY_AMPA_CMD:
 dctrl = ctrAmpA;
 dispType = AMPL;
 break;
 case DISPLAY_FREQA_CMD:
 dctrl = ctrFreqA;
 dispType = FREQ;
 break;
 case DISPLAY_AMPB_CMD:
 dctrl = ctrAmpB;
 dispType = AMPL;
 break;
 case DISPLAY_FREQB_CMD:
 dctrl = ctrFreqB;
 dispType = FREQ;
 break;
 case DISPLAY_PHASEB_CMD:
 dctrl = ctrPhaseB;
 dispType = PHASE;
 break;
 
 default:
 inherited::DoCommand(theCommand); /*not mine*/
 break;
 }
 
 if (dctrl != NULL) {/*display to refresh*/
 val = dctrl->GetValue();
 sprintf(buf,”%04d”,val); /*build text*/
 if (dispType == AMPL) {  /*AMPL: fake point*/
 buf[0] = buf[1];
 buf[1] = ‘.’;
 } else if  (dispType == PHASE) { /*add sign*/
 buf[0] = (val > 0) ? ‘+’ :
 ((val < 0) ? ‘-’ : ‘ ‘);
 }
 dctrl->SetDisplayText(buf);/*set new text*/
 }
}

 
AAPL
$108.00
Apple Inc.
+1.02
MSFT
$46.95
Microsoft Corpora
+0.90
GOOG
$559.08
Google Inc.
+8.77

MacTech Search:
Community Search:

Software Updates via MacUpdate

Vitamin-R 2.20b1 - Personal productivity...
Vitamin-R creates the optimal conditions for your brain to work at its best by structuring your work into short bursts of distraction-free, highly focused activity alternating with opportunities for... Read more
Dropbox 2.10.44 - Cloud synchronization...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
Sandvox 2.9.2 - Easily build eye-catchin...
Sandvox is for Mac users who want to create a professional looking website quickly and easily. With Sandvox, you don't need to be a Web genius to build a stylish, feature-rich, standards-compliant... Read more
Cocktail 8.0.1 - General maintenance and...
Cocktail is a general purpose utility for OS X that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
LibreOffice 4.3.3.2 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
VMware Fusion 7.0.1 - Run Windows apps a...
VMware Fusion allows you to create a Virtual Machine on your Mac and run Windows (including Windows 8.1) and Windows software on your Mac. Run your favorite Windows applications alongside Mac... Read more
OneNote 15.3.2 - Free digital notebook f...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
Audio Hijack Pro 2.11.4 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Iridient Developer 3.0.0 beta 3 - Powerf...
Iridient Developer (was RAW Developer) is a powerful image conversion application designed specifically for OS X. Iridient Developer gives advanced photographers total control over every aspect of... Read more
TextWrangler 4.5.11 - Free general purpo...
TextWrangler is the powerful general purpose text editor, and Unix and server administrator's tool. Oh, and also, like the best things in life, it's free. TextWrangler is the "little brother" to... Read more

Latest Forum Discussions

See All

Monster Flash Review
Monster Flash Review By Jordan Minor on October 31st, 2014 Our Rating: :: ALONE IN THE DARKUniversal App - Designed for iPhone and iPad Solid shooting and a surprising amount of spooky tension make Monster Flash a great portable... | Read more »
Retry Review
Retry Review By Rob Thomas on October 31st, 2014 Our Rating: :: SOARING HIGHUniversal App - Designed for iPhone and iPad Flappy who? Let Retry wash all those bad bird-related memories away on a cool retro-flavored flight… right... | Read more »
Dementia: Book of the Dead Review
Dementia: Book of the Dead Review By Lee Hamlet on October 31st, 2014 Our Rating: :: A TOUGH READUniversal App - Designed for iPhone and iPad A witch hunter is sent after a demonic book in the spooky but short-lived Dementia: Book... | Read more »
Card Dungeon, the Semi-Board Game Roguel...
Card Dungeon, the Semi-Board Game Roguelike, Has Been Renovated Posted by Jessica Fisher on October 31st, 2014 [ permalink ] | Read more »
Logitech Protection + Power iPhone5/5S C...
Made by: Logitech Price: $99.99 Hardware/iOS Integration Rating: 3 out of 5 stars Usability Rating: 0.5 out of 5 stars Reuse Value Rating: 0.75 out of 5 stars Build Quality Rating: 0.75 out of 5 stars Overall Rating: 1.25 out of 5 stars | Read more »
This Is Not a Test Goes Free, Permanentl...
This Is Not a Test Goes Free, Permanently Posted by Jessica Fisher on October 31st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Swap Heroes Review
Swap Heroes Review By Campbell Bird on October 31st, 2014 Our Rating: :: STRATEGIC SWAPPINGUniversal App - Designed for iPhone and iPad Rotate a cast of heroes to fend of waves of monsters in this difficult, puzzle rpg.   | Read more »
Night Sky Pro™ (Reference)
Night Sky Pro™ 3.0.1 Device: iOS Universal Category: Reference Price: $2.99, Version: 3.0.1 (iTunes) Description: Night Sky Pro™Wonder No More™ Night Sky Pro™ is the ultimate stargazing experience. From the creators of the original... | Read more »
Audio Defence : Zombie Arena (Games)
Audio Defence : Zombie Arena 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: A zombie shooter audio game. Made from gut-wrenching 3D binaural sound, for a new kind of weird immersion. You... | Read more »
RPG Asdivine Hearts (Games)
RPG Asdivine Hearts 1.1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.1.0 (iTunes) Description: SPECIAL PRICE50% OFF (USD 7.99 -> USD 3.99)!!! Travel alongside four companions and a cat in the adventure of a... | Read more »

Price Scanner via MacPrices.net

Apple now offering refurbished 2014 13-inch R...
The Apple Store is now offering Apple Certified Refurbished 2014 13″ Retina MacBook Pros for up to $270 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple Regains Momentum As Windows Stutters An...
The latest smartphone sales data from Kantar Worldpanel ComTech, for the three months to March 2014, shows Apple performing strongly in the first quarter of the year, with sales bouncing back in... Read more
Worldwide Smartphone Shipments Increase 25.2%...
New smartphone releases and an increased emphasis on emerging markets drove global smartphone shipments above 300 million units for the second consecutive quarter, according to preliminary data from... Read more
Apple now offering refurbished 2014 15-inch M...
The Apple Store is now offering Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple drops prices on refurbished 2013 Retina...
The Apple Store has dropped prices on 2013 Apple Certified Refurbished 13″ and 15″ Retina MacBook Pros, with Retina models now available starting at $999. Apple’s one-year warranty is standard, and... Read more
New 2.8GHz Mac mini on sale for $949, save $5...
Abt Electronics has the new 2.8GHz Mac mini in stock and on sale for $949.05 including free shipping. Their price is $50 off MSRP, and it’s the lowest price available for this model from any reseller... Read more
Sale! 3.7GHz Quad Core Mac Pro available for...
 B&H Photo has the 3.7GHz Quad Core Mac Pro on sale for $2649 including free shipping plus NY sales tax only. Their price is $350 off MSRP, and it’s the lowest price for this model from any... Read more
Mujjo Steps Up The Game With Refined Touchscr...
Netherlands based Mujjo have just launched their Refined Touchscreen Gloves, stepping up their game. The gloves feature a updated elegant design that takes these knitted gloves to the next level. A... Read more
Sale! Preorder the new 27-inch 5K iMac for $2...
 Abt Electronics has the new 27″ 3.5GHz 5K iMac on sale and available for preorder for $2374.05 including free shipping. Their price is $125 off MSRP, and it’s the lowest price available for this... Read more
Simplex Solutions Inc. Brings Secure Web Surf...
New York based Simplex Solutions Inc. has announced the release and immediate availability of Private Browser 1.0, its revolutionary new secure web browser developed for iPhone, iPad and iPod touch... Read more

Jobs Board

Solutions Specialist with *Apple* Knowledge...
Company Description: We are an Apple Authorized Sales and Service Provider. We have been selling and servicing Apple computers in the Fairfield County area for over Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** Every day, business customers come to the Apple Store to discover what powerful, easy-to-use Apple products can do for them. As a Business Leader, Read more
Sr. Manager, *Apple* Deployment Programs fo...
**Job Summary** Apple is seeking candidates for a new position on the Education Content and Technology team. iPad and Mac is in the hands of millions of teachers and Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.