TweetFollow Us on Twitter

OOP Code Resources
Volume Number:7
Issue Number:11
Column Tag:Object Workshop

Related Info: Device Manager Control Manager List Manager

OOP & Code Resources

By Peter Blum, Essex, MA

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

[Peter Blum is the Macintosh Project Leader at TIMESLIPS Corporation where he develops Timeslips III For the Mac and related products. He is a graduate of the University of Massachusetts at Amherst (BS CSE).]

Code Resources And Their Limitations

One of the most interesting programming techniques that the Macintosh offers is the use of code resources to dynamically extend system behavior. The Toolbox itself has several managers that use code resources. Window Manager (WDEF), Menu Manager (MDEF), Control Manager (CDEF), and List Manager (LDEF) are the most common. Additionally, programmers are adding code resource hooks into their products to make them extendable by outside programmers. HyperCard offers XCMDs, MORE II provides file format translators in code resources, and After Dark v2.0’s screen savers are code resources.

There are some basic facts to know about code resources:

• They are stored as a single segment of code

• The caller jumps to the code segment at its first byte address

• They can be coded in high level language compilers like Think and MPW C and Pascal with passing parameters

• They do not support global variables

• They do not support object oriented code from C or Pascal

CDEF Code Resource Example

A code resource for a CDEF in Think Pascal takes the following form:

{1}

unit CDEFCode;

interface
 function Main(varCode : integer;
 theControl : ControlHandle;
 message : integer; param : LongInt) : LongInt;

implementation

 function Main(varCode : integer;
 theControl : ControlHandle;
 message : integer; param : LongInt) : LongInt;
 var
 result : LongInt;
{*** various routines here ***}
 begin
 case message of
 drawCntl:
 result  := MyDrawRoutine;
 testCntl:
 result  := MyTestRoutine;
 calcCRgns:
 result  := MyCalcRoutine;
 initCntl:
 result  := MyInitRoutine;
 dispCntl:
 result  := MyDispRoutine;
 posCntl:
 result  := MyPosRoutine;
 thumbCntl:
 result  := MyThumbRoutine;
 dragCntl:
 result  := MyDragRoutine;
 autoTrack:
 result  := MyTrackRoutine;
 end; { case }
 Main:= result;
 end; { Main }
end.  { unit CDEFCode }

The CDEF code resource demonstrates three basic solutions to limitations of code resources. The parameters passed each offer the code resource a solution:

varCode - allows the code resource to select one of several variations to execute.

theControl - contains information that must be retained between calls to the resource. This is a substitution for global variables.

message - provides the resource with a way to select a process or specialized routine. Typically, there are messages to initialize and dispose plus several to handle the various operations specific to the resource.

Working Around Code Resource Limitations

Specific Toolbox managers like the Control Manager restrict the global variable structure for their own use; it usually contains configuration information and some private fields. So, the data structure isn’t setup to hold globals for the code resource.

However, most Toolbox Managers add an extra 4 byte field for the code resource to store data for globals.

Toolbox Manager Resource type Field Name

Window Manager WDEF dataHandle

Control Manager CDEF contrlData

List Manager LDEF userData

The Menu Manager’s MDEF doesn’t offer this type of field. When our code resource is initialized, we will allocate memory to hold our globals and store it in the appropriate field.

As you can see, the CDEF supports variations. This way, you can write one CDEF that handles several similar cases. In fact, the built in controls: button, checkbox, and radio are all variations in one CDEF. With a growing appreciation for object oriented programming which is well suited for programming variations, wouldn’t it be nice to code a CDEF or any other code resource using objects?

Well, object oriented programming within MPW and Think Pascal and C cannot be used for code resources! Why? Because they need internal “method tables” which demand true global variable storage - not the artificial globals provided by datastructures passed by the caller. Additionally, they tend to require more than one code segment. But don’t give up hope because Think Pascal can offer a solution.

Using Drivers In Think Pascal

Although Think Pascal cannot compile globals or multiple segments for code resources, it can for drivers. The driver is a special type of code resource with a header containing a specialized jump table and other data. The Device Manager maintains drivers. Think Pascal builds the header and adds intermediate code that interprets each message from a Device Manager routine and passes it to our Pascal code. Additionally, Think’s driver code support multiple segments, true global variables, and objects!

So, we can write the code resource to load and call a driver resource written in Think Pascal. The driver resource contains most of the code including any objects. The code resource is simply a shell that manages and calls the driver resource. Here is the format for a driver routine in Think Pascal.

{2}

unit DriverCode;

interface
 function Main(devCtlEnt : DCtlPtr;
 paramBlock : ParmBlkPtr;
 sel : integer) : integer;

implementation

 function Main(devCtlEnt : DCtlPtr;
 paramBlock : ParmBlkPtr;
 sel : integer) : integer;
 begin
 case sel of
 0:{ Open - initialize }
 1:{ Prime - read/write blocks }
 2:{ Control - various messages }
 3:{ Status - inquiries of the device }
 4:{ Close - dispose }
 end; { Main }
end.  { unit DriverCode}

The first parameter of Main, devCtlEnt, points to the device control entry record which follows (IM II-202):

{3}

type
 DCtlHandle =  ^DCtlPtr;
 DCtlPtr= ^DCtlEntry;
 DCtlEntry= record
 dCtlDriver:Ptr; { pointer to ROM driver }
 { or handle to RAM driver }
 dCtlFlags: integer;
 dCtlQHdr:QHdl;  {driver I/O queue header}
 dCtlPosition: LongInt;
 { byte position used by Read and Write calls}
 dCtlStorage:  Handle;
 { handle to RAM driver’s private storage }
 dCtlRefNum:integer;
 { driver’s reference Number }
 dCtlCurTimes: LongInt; { used internally }
 dCtlWindow:WindowPtr;
 { pointer to driver’s window }
 dCtlDelay: integer;
 { number of ticks between periodic actions }
 dCtlEMask: integer;
 { desk accessory event mask }
 dCtlMenu:integer;
 { menu ID of menu associated with driver }
 end;

The second parameter is the standard ParamBlockRec that is called in low-level disk routines. See IM IV-116 for details.

Before we get into detailing the code, lets look at some of the complexities that drivers add and how we will work around them. When you read Inside Macintosh Vol 2, Device Manager, you learn that the Macintosh only supports up to 32 drivers (including Desk Accessories) and there are almost 32 installed at any time. They are given reference numbers from 0 to 31. Most are taken up by Printer, Sound, Disk, Serial ports, and DAs (See IM II-192). So, adding a Device Manager driver virtually requires you to replace existing ones. Even if there is one or two free reference numbers for our drivers to use, most applications use more than one CDEF, LDEF or other code resource. Additionally, the Device Manager adds its own overhead by linking our driver into the device queue.

Home Brewed Device Manager

So, lets NOT use the Device Manager. Instead, we’ll call the Think Pascal driver simulating the Device Manager through a short assembly language routine for which we’ll use an inline Pascal routine.

{4}

function CallDriver (devCtlEnt: DCtlPtr;
 paramBlock: ParmBlkPtr;
 theDriverOfs: Ptr): integer;
 inline
 $2F0A, { MOVEA.LA2,-(A7) 
 ; preserve A2 in the function return }
 $246F, $0004, { MOVEA.L  4(A7),A2 
 ; Routine to jump to }
 $206F, $0008, { MOVEA.L  $8(A7), A0
 ; parmBlkPtr must go in A0 }
 $226F, $000C, { MOVEA.L  $C(A7), A1
 ; dCtlPtr must go in A1 }
 $4E92, { JSR (A2)
 ; call the driver-D0 will contain the result }
 $245F, { MOVEA.L(A7)+,A2
 ; restore A2 before setting return value }
 $DEFC, $000C, { ADDA.W #$C, A7
 ; restore the stack except for function 
 return value }
 $3E80; { MOVEA.LD0, (A7)
 ; return value on stack }

When called, we pass the address of the routine within our driver that handles one of the basic routines for a driver: Open, Close, Prime, Control, or Status. To do this, we need to understand more about the header of the driver. The following figure will help (see Inside Macintosh Volume 2: Device Manager for more detail) on the next page.

Figure 1:Device Header Block

The top address is where our driver is loaded in memory. We get this address by loading our driver, locking it, and dereferencing it once. When we want to call a routine within the driver, we must load the offset for that routine in drvrOpen, drvrPrime, drvrCtl, drvrStatus, drvrClose. These are each words which is a data type not supported by Think Pascal. Instead, we can get a word by using the Hiword and Loword functions applied to an array of LongInts. The following shows how to get the final address, driverOfs, and call the driver:

{5}

type
 LongArr = array[0..10] of LongInt;
 LongArrPtr = ^LongArr;
 LongArrHdl = ^LongArrPtr;

const
{ getting the offsets }
 drvrSelOpenOffset = 2;
 { HiWord(LongArrHdl(myDriver)^^[2]) }
 drvrSelPrimeOffset = 2;
 { LoWord(LongArrHdl(myDriver)^^[2]) }
 drvrSelControlOffset = 3;
 { HiWord(LongArrHdl(myDriver)^^[3]) }
 drvrSelStatusOffset = 3;
 { LoWord(LongArrHdl(myDriver)^^[3]) }
 drvrSelCloseOffset = 4;
 { HiWord(LongArrHdl(myDriver)^^[4]) }

var
 myDriver : Handle;
 driverOfs : Ptr;
 myLongArr : LongArrHdl;
begin
 myDriver := GetResource(‘DRVR’, drvrResID);
 MoveHHi(myDriver);
 HLock(myDriver);

 myLongArr := LongArrHdl(myDriver);
 driverOfs := Ptr(ord4(myDriver) +
 hiword(myLongArr^^[drvrSelOpenOffset]));

 {*** more setup of parameters required! ***}

 { now call the driver }
 theOSErr := CallDriver(theDCEHdl^, @dummyIOPB,
 driverOfs);
end;

While the driver remains open, we must preserve the DCtlEntry record because Think Pascal sets it up with special storage for objects and globals when the driver’s open message is called. Remember that code resources don’t maintain globals, but their callers offer some storage for a handle. For our CDEF, we will allocate a handle to the DCtlEntry and store it in the contrlData field of the ControlHandle. The other parameter passed to the driver is ParamBlockRec. After some research, I have determined that Think Pascal’s driver header doesn’t use it. It just passes it to our driver code. So, we won’t preserve the ParamBlockRec.

The next problem is communication between the code resource and the driver. The code resource must pass variation codes, routine selection messages, and parameters passed to it by its caller. In the case of CDEFs, we must pass the Param field and we must return a long integer value for the function. So, we must find a way to use the DCtlEntry to pass this data between the code resource and the driver.

The ParamBlockRec has fields specifically setup to pass messages and parameters. These are csCode for messages to Control and Status routines of drivers and csParam which can hold up to 22 bytes of user data. However, the ParamBlockRec has two disadvantages; csParam’s 22 bytes may be too small for our parameters (not in the case of CDEFs) and since we don’t preserve it between calls, we have to set it up with every call.

Here is a datastructure to hold all parameters passed between the Code Resource and Driver:

{6}

type
 CtoDHandle =  ^CtoDPtr;
 CtoDPtr= ^CodeToDriver;
 CodeToDriver  = record
 fMessage:integer; { the routine message }
 fVarCode:integer; { which variation to use }
 fCallerHdl:Handle;
 { handle to the caller’s storage }
 { CDEFs=ControlHandle, LDEF=ListHandle, etc.}
 fParam:{ your parameters }
 end;

You may have to enhance it by specifying your own parameters in place of fParam. Store the caller’s storage like the ControlHandle or ListHandle in fCallerHdl so our driver can use it.

Now, we can store our CodeToDriver record in the DCtlEntry record. The field dCtlStorage is designed for this. However, Think Pascal uses this field to make drivers support globals and objects. dCtlPosition will not be used for its designated purpose by our driver because we are not going to use read or write messages like a disk driver. Also, it does not affect the behavior of Think’s driver header code unlike some other fields. So, the handle to CodeToDriver record is stored in dCtlPosition.

Next, we have to determine how to convert messages passed to the code resource for use by the driver. The code resource can have any number of messages including Initialize and Dispose but most are specific to the implementation. The driver has its own messages: Open, Prime, Control, Status, and Close. We could pass all code resource messages to a single driver message through the CodeToDriver record. However, Think Pascal requires that we call Open so it can initialize the driver’s global world and call Close so it can dispose the same. We are free to send the rest of the messages into one driver routine, Control, with the fMessage field assigned to the code resource message.

Driver
Code Message CDEFs LDEFs Routine

initialize initCntl lInitMsg Open

dispose dispCntl lCloseMsg Close

all others all others all others ControlMaking

Device Manager Compatible Calls To Think’s Drivers

Even with all we’ve done, we still cannot successfully call the driver from our code resource for two reasons. 1) Think’s driver header requires a resource of type ‘DATA’ to be a specific ID. 2) Think’s driver header expects some fields of our parameters, DCtlEntry and ParamBlockRec, to be set up by Device Manager.

What is this ‘DATA’ resource and how does it get created? ‘DATA’ contains all structures that make up the global world for the driver including the object look-up tables and global variable storage. It is created when you compile a driver or desk accessory. It uses the same resource ID as our ‘DRVR’ resource. Unfortunately, the driver header is designed to handle ‘DRVR’ resources with IDs from -1 to -32. When we pass DCtlEntry to the driver, its dCtlRefNum is used to calculate the ID of the DATA resource using the following formula:

{7}

resID := -(dCtlRefNum + 1) * 32

If we compiler our driver with a resource ID of 500, ‘DATA’ will also have an ID of 500. But there is no way to set dCtlRefNum to a value which will calculate to 500. So, we must pick a value for dCtlRefNum, calculate the final resID using the formula, and change the ID in ResEdit. For this example, dCtlRefNum is 0 and the ID calculates to -32. This is the single most important trick to making this whole technique work.

Now, before calling the driver, lets set up the parameters to simulate the Device Manager. In fact, most fields in DCtlEntry and ParamBlockRec are not required by either Think’s driver header or our driver. We only need to setup the DCtlEntry record once:

{8}

with theDCEHdl^^ do
 begin
 dCtlDriver := Ptr(theDriver);
 dCtlFlags := $4400; { allow Control }
 dCtlQHdr.qFlags := 0;  { not used }
 dCtlQHdr.qHead := nil; { not used }
 dCtlQHdr.qTail := nil; { not used }
 dCtlPosition := ord4(theCtoDHdl);
 { passing parameter block }
 dCtlStorage := nil;
 { for Think Pascal to set it up during Open }
 dCtlRefNum := dataInternalID;
 { Think will calculate an ID of }
 { -(dataInternalID + 1) * 32 for its }
 { DATA resource }
 dCtlCurTicks := 0;{ not used }
 dCtlWindow := nil;{ not used }
 dCtlDelay := 0; { not used }
 dCtlEMask := 0; { not used }
 dCtlMenu := 0;  { not used }
 end; { with theDCEHdl^^ }

Although the ParamBlockRec doesn’t seem to require any setup to satisfy the Think driver header, we will set most of the fields that the Device Manager sets.

Coding A CDEF For Standard Buttons

Lets take a look at the code for a CDEF that simulates the three standard controls, Button, CheckBox, and Radio Button. This implementation will not try to be a complete simulation because our goal is to demonstrate code resources interacting with drivers. There are three Units. Because all code is either in a driver or code resource, there are no Program files.

Unit CDEFShell contains our code resource. It is simply a shell to call the driver with the message passed by its caller, the Control Manager. You can reuse this code for almost any CDEF without changing anything except for the constants that identify the resource IDs to the ‘DRVR’ and ‘DATA’ resources. For other code resource types, you need to change the CodeToDriver record to pass the parameters that you will use. In each routine that calls the driver, the parameters must be set up and any returned value must be returned to the caller.

Unit BasicCDEFDriver contains the essential code to handling any driver that will be called by a code resource: the driver messages for Open, Close, and Control. For your code, you may need to replace the object classes, their instantiation, and handling methods. Instantiation of the object based on the variation code occurs in routine OpenDRVRRtn. ControlDRVRRtn uses the CodeToDriver.fMessage to call the desired method. CloseDRVRRtn simply disposes of the object.

Unit ControlObjUnit contains the implementation of the object classes for all controls (tControlObj) and those that make up my specific controls. If you are writing controls, you could create a class to override any methods shown here. Remember that my implementation of the standard controls isn’t perfect and should be improved before using them in a commercial application.

To compile the driver, set up the project as follows:

Figure 2: Driver Project window

All segments can remain in segment 0. Then, set the Project Type dialog to compile Drivers. You must check the Multi-Segment option or Object Pascal cannot compile. This DRVR will have an ID of 500. The Flags must be $4400. Once compiled, change the ID of DATA in ResEdit according to your use of dCtlRefNum, in this case, use -32.

To compile the code resource, set up the project as follows:

Figure 3: Code Resource Project window

Set the Project Type dialog to compile code resources. The resource should be ‘CDEF’ ID = 0. A word of warning: CDEF 0 is already used by the system for the standard buttons. However, we are writing a replacement for these controls which will override the basic controls in any application that this CDEF is added to.

All that remains is to install the CDEF, DRVR, and DATA resources into your application with ResEdit and run your application.

Variation Codes For LDEFs

The LDEF resource and some others do not support a variation code. However, there is a simple workaround. Using the fact that resources remain in memory until purged or released, we can share a single resource just by calling GetResource on it. We will create a resource to hold the variation code. It will be a 2 byte resource. Before the main application needs the LDEF, it loads our varCode resource and assigns an integer value for the code. Then, the code resource or the driver can call GetResource on the varCode resource to retrieve its value.

{9}

const
 varCode1 = 1;
type
 varCodePtr = ^integer;
 varCodeHdl = ^varCodePtr;
var
 varCodeHolder : varCodeHdl;
 myList : ListHandle;
begin
 varCodeHolder :=
 varCodeHdl(GetResource(‘VARC’, 500));
 varCodeHolder^^ := varCode1;

 myList := LNew(...);
end;

Summary

Certainly, you can use the technique of objects in drivers on any code resource including the Macintosh Toolbox Managers. However, be aware that adding OOP with Think Pascal will increase the size of the intended code - and the calling application may not have enough memory to work with it. So, it is wise to add special error handling to check for low memory situations before loading or running the driver. Also, there is more code overhead which means that each message takes longer to process. However, there are many applications which contain code resources where objects will greatly improve the overall coding effort.

Listing: ControlObjUnit
{ This Unit contains the objects to implement }
{ look-alikes of the standard Macintosh }
{ controls: Button, Checkbox,  and Radio button. }
{ The standard controls are much more robust.  }
{ These are used as an example for Object }
{ Programming within drivers }

unit ControlObjUnit;
interface

 type
{ tControlObj is our base class object }
{ for all controls }
 tControlObj = object
 fControl: ControlHandle;
 { the control we are using }
 procedure mInit (theControl:
 ControlHandle); { InitCntl }
 procedure mFree;{ DispCntl }
 procedure mDraw (param: LongInt); { DrawCntl }
 function mTest (param: LongInt) : LongInt;        { TestCntl }
 procedure mCalcRgn (param: LongInt;
 message: integer); { CalcCRgns, calcCntlRgn }
 procedure mPosition (param: LongInt); { PosCntl }
 procedure mCalcThumb (param: LongInt); { ThumbCntl }
 function mDrag (param: LongInt)
 : LongInt; { DragCntl }
 procedure mAutoTrack (param: LongInt); { autoTrack }
 end; { object tControlObj }

{ Class to handle the basic button types }
 tButtonMainObj = object(tControlObj)
 fOldFont, fOldSize: integer;
 function mTest(param: LongInt) : LongInt;
 override;
 procedure mCalcRgn (param: LongInt;
 message: integer);
 override;
 function mGetPartCode: integer;
 procedure mDrawTitle(hOffset: integer);
 procedure SetFont;
 procedure RestoreFont;
 end; { object tButtonMainObj }

 { button routine }
 tPushButObj = object(tButtonMainObj)
 procedure mDraw (param: LongInt);
 override;
 function mGetPartCode: integer;
 override;
 end; { object tPushButObj }

 { checkBox routine }
 tCheckBoxObj = object(tButtonMainObj)
 procedure mDraw (param: LongInt);
 override;
 procedure mDrawCheckFrame(frame: Rect);
 end; { object tCheckBoxObj }

 { radio button routine }
 tRadioButObj = object(tCheckBoxObj)
 procedure mDrawCheckFrame(frame: Rect);
 override;
 end; { object tRadioButObj }

implementation
{----------------------}
 procedure tControlObj.mInit
 (theControl: ControlHandle);
 begin
 fControl := theControl;
 end; { tControlObj.mInit }
{----------------------}
 procedure tControlObj.mFree;
 begin
 Dispose(SELF);
 end; { tControlObj.mFree }
{----------------------}
 procedure tControlObj.mDraw;
 begin
 end; { tControlObj.mDraw }
{----------------------}
 function tControlObj.mTest
 (param: LongInt) : LongInt;
 begin
 mTest := 0;
 end; { tControlObj.mTest }
{----------------------}
 procedure tControlObj.mCalcRgn
 (param: LongInt; message: integer);
 begin
 end; { tControlObj.mCalcRgn }
{----------------------}
 procedure tControlObj.mPosition (param: LongInt);
 begin
 end; { tControlObj.mPosition }
{----------------------}
 procedure tControlObj.mCalcThumb (param: LongInt);
 begin
 end; { tControlObj.mCalcThumb }
{----------------------}
 function tControlObj.mDrag (param: LongInt): LongInt;
 begin
 mDrag := 0;
 end; { tControlObj.mDrag }
{----------------------}
 procedure tControlObj.mAutoTrack (param: LongInt);
 begin
 end; { tControlObj.mAutoTrack }
{----------------------}
 function tButtonMainObj.mTest (param: LongInt): LongInt;
 var
 thePoint: Point;
 begin
 mTest := 0;
 thePoint := Point(param);

{ Valid as long as its within the }
{ contrlRect and the button isn’t dimmed }
 if fControl^^.contrlHilite < 254 then
 if PtInRect(thePoint,
 fControl^^.contrlRect) then
 mTest := mGetPartCode;
 end; { tButtonMainObj.mTest }
{----------------------}
 procedure tButtonMainObj.mCalcRgn
 (param: LongInt; message: integer);
{ These controls use their full rectangle }
{ as there region }
 var
 TheRgnHdl: Rgnhandle;
 begin
 theRgnHdl := RgnHandle(Param);
 if message = CalcCRgns then
 { clear the high bit for 32 bit compatibility }
 BitClr(Ptr(theRgnHdl^), 0);
 RectRgn(theRgnHdl, fControl^^.contrlRect);
 end; { tButtonMainObj.mCalcRgn }
{----------------------}
 function tButtonMainObj.mGetPartCode : integer;
 begin
{ both checkbox and radio button return }
{ inCheckBox. Override for regular button}
 mGetPartCode := inCheckBox;
 end; { tButtonMainObj.mGetPartCode }
{----------------------}
 procedure tButtonMainObj.SetFont;
 var
 curPort: GrafPtr;
 begin
 GetPort(curPort);
 fOldFont := curPort^.txFont;
 fOldSize := curPort^.txSize;
 TextFont(0);
 TextSize(12);
 end; { tButtonMainObj.SetFont}
{----------------------}
 procedure tButtonMainObj.RestoreFont;
 begin
 TextFont(fOldFont);
 TextSize(fOldSize);
 end; { tButtonMainObj.RestoreFont}
{----------------------}
 procedure tButtonMainObj.mDrawTitle
 (hOffset: integer);
{ draws and optionally dims the title of }
{ the control }
{ Assumes the caller has erased the area }
 var
 dispRect: Rect;
 theRgn: RgnHandle;
 theFontInfo: FontInfo;
 rectHeight, textHeight : integer;
 vOffset: integer;
 GrayPat: Pattern;
 begin
 SetFont;
 dispRect := fControl^^.contrlRect;
 TheRgn := NewRgn; { set up a clip }
 GetClip(TheRgn);
 ClipRect(dispRect);

 GetFontInfo(theFontInfo);
 rectHeight := (dispRect.bottom - dispRect.top);
 textHeight := theFontInfo.ascent + 
 theFontInfo.leading + theFontInfo.descent;
 vOffset := (rectHeight - textHeight) div 2;

 dispRect.left := dispRect.left + hOffset;
 MoveTo(dispRect.Left, dispRect.Top + vOffset +
 theFontInfo.ascent + theFontInfo.leading);
 DrawString(fControl^^.contrlTitle);

 if fControl^^.ContrlHilite = 255 then
 begin  { dim the text }
 GetIndPattern(GrayPat, 0, 4);
 PenPat(GrayPat);
 PenMode(PatBic);
 PaintRect(dispRect);
 PenNormal;
 end;

 SetClip(TheRgn);
 DisposeRgn(theRgn);
 RestoreFont;
 end; { tButtonMainObj.mDrawTitle }
{----------------------}
 procedure tPushButObj.mDraw (param: LongInt);
 { draws the standard button }
 var
 dispRect: Rect;
 hOffset: integer;
 begin
 if (fControl^^.contrlVis <> 0)
 & (param <> 128) then
 begin
 dispRect := fControl^^.contrlRect;
 hOffset := (dispRect.right - dispRect.left 
 - StringWidth(fControl^^.contrlTitle))
 div 2;

 { now draw everything }
 EraseRect(dispRect);
 mDrawTitle(hOffset);
 { draw frame }
 FrameRoundRect(dispRect, 16, 16);
 if fControl^^.contrlHilite = InButton then
 begin  { invert it }
 InsetRect(dispRect, 1, 1);
 InvertRoundRect(dispRect, 16, 16);
 end;
 end; { if fControl^^.contrlVis }
 end; { tPushButObj.mDraw }
{----------------------}
 function tPushButObj.mGetPartCode : integer;
 begin
 mGetPartCode := inButton;
 end; { tPushButObj.mGetPartCode }
{----------------------}
 procedure tCheckBoxObj.mDraw(param: LongInt);
 const
 cBoxSize = 12; { size of the checkbox }
 cTextStart = 17;
 { Text starts after the box }
 var
 dispRect, boxRect: Rect;
 Gray: Pattern;
 TheFInfo: FontInfo;
 vOffset, vBoxOffset : integer;
 rectHeight, textHeight: integer;
 begin
 if (fControl^^.contrlVis <> 0)
 & (param <> 128) then
 begin
 dispRect := fControl^^.contrlRect;
 vBoxOffset := (dispRect.bottom -
 dispRect.top - cBoxSize) div 2;

 { Draw the checkbox without}
 { changing the existing text }
 BoxRect := dispRect;
 BoxRect.top := BoxRect.top + vBoxOffset;
 BoxRect.Bottom := BoxRect.top + cBoxSize;
 BoxRect.Right := BoxRect.left + cBoxSize;
 EraseRect(BoxRect);
 mDrawCheckFrame(BoxRect);

 { Draw the text }
 if (fControl^^.contrlHilite <> inCheckBox)
 & (fControl^^.contrlTitle <> ‘’)
 & (Param = 0) then
 begin  { we have text }
 dispRect.left := dispRect.left
 + cTextStart;
 { erase old text }
 EraseRect(dispRect);
 mDrawTitle(cTextStart);
 end; { if (Title <> ‘’) }
 end; { if fControl^^.contrlVis }
 end; { tCheckBoxObj.mDraw }
{----------------------}
 procedure tCheckBoxObj.mDrawCheckFrame (frame: Rect);
{ draws the checkbox image and }
{ highlights it if needed }
 begin
 FrameRect(frame);

 if fControl^^.contrlValue = 1 then
 { draw the checkbox }
 begin
 MoveTo(frame.left, frame.Top);
 LineTo(frame.right - 1,
 frame.bottom - 1);
 MoveTo(frame.right - 1,
 frame.top);
 LineTo(frame.left,
 frame.bottom - 1);
 end;

 if fControl^^.contrlHilite = inCheckBox then
 { when drawing hiliting, }
 { we don’t need to change the text }
 begin
 InsetRect(frame, 1, 1);
 FrameRect(frame);
 end;
 end; { tCheckBoxObj.mDrawFrame }
{----------------------}
 procedure tRadioButObj.mDrawCheckFrame (frame: Rect);
{ draws the radio button image and }
{ highlights it if needed }
 begin
 FrameOval(frame);

 if fControl^^.contrlValue = 1 then
 { radio hilite }
 begin
 InsetRect(frame, 3, 3);
 PaintOval(frame);
 { draw the indicator }
 InsetRect(frame, -3, -3);
 end;

 if fControl^^.contrlHilite = inCheckBox then
 { when drawing hiliting, }
 { we don’t need to change the text }
 begin
 InsetRect(frame, 1, 1);
 FrameOval(frame);
 end;

 end; { tRadioButObj.mDrawFrame }
{----------------------}
end.  { unit ControlObjUnit }
Listing: BasicCDEFDriver
{ THIS CODE REQUIRES Think Pascal v2.0 OR ABOVE }
{ TO COMPILE USING THE MULTISEGMENT AND DRIVER }
{ OPTIONS. }
{ Source for a driver that is used as a Control }
{ Manager CDEF using Object Pascal. YOU MUST }
{ PROVIDE YOUR OWN CONTROL OBJECTS }
{ This driver is called and maintained by a code }
{ resource shell that is in turn called and }
{ maintained by the Control Manager. }

unit BasicCDEFDriver;
interface
 uses
 ControlObjUnit;

 function Main (theDCE: DCtlPtr;
 IOPB: ParmBlkPtr; sel: Integer) : OSErr;

implementation

 const
{ Define all of the possible driver calls}
 DriverOpen = 0;
 DriverPrime = 1;
 DriverControl = 2;
 DriverStatus = 3;
 DriverClose = 4;

 type
{ CodeToDriver passes parameters from }
{ the code resource into driver. }
{ Add or change any parameters that apply}
{ to your code resource }
 CodeToDriver = record
 fMessage: integer;
 fVarCode: integer;
 fControl: ControlHandle; { theControl }
 fParam: LongInt;{ Param }
 fResult: LongInt; { result }
 { to return to Control Manager }
 end;
 CtoDPtr = ^CodeToDriver;
 CtoDHdl = ^CtoDPtr;

 var
{ Think Pascal initializes all globals }
{ to 0 before the Open message is called }
 gOpenFlag: integer; { when 0, }
 { driver hasn’t initialized }

{ your globals and objects }
 gButton: tControlObj;
{----------------------}
 function Main (theDCE: DCtlPtr;
 IOPB: ParmBlkPtr; sel: Integer) : OSErr;

 {***********************}
 function OpenDRVRRtn: OSErr;
{ create and initialize the object given }
{ the varcode }
 var
 theCtoDHdl: CtoDHdl;
 begin
 OpenDRVRRtn := noErr;  { init }

 if gOpenFlag = 0 then
 {DA not already opened}
 begin
 theCtoDHdl :=
 CtoDHdl(theDCE^.dCtlPosition);

 { create and initialize the main }
 { object based on VarCode }
 gButton := nil;
 case theCtoDHdl^^.fVarCode of
 pushButProc:  { simple button }
 New(tPushButObj(gButton));
 checkBoxProc: { check box }
 New(tCheckBoxObj(gButton));
 radioButProc: { radio button }
 New(tRadioButObj(gButton));
 otherwise
 end; { case }

 if gButton <> nil then
 begin
 { call the initialize routine }
 gButton.mInit(theCtoDHdl^^.fControl);

 gOpenFlag := 1;
 { we’re OK for Control calls }
 end
 else
 OpenDRVRRtn := -1;{ error! }
 end;
 end;   { OpenDRVRRtn }

 {***********************}
 function CloseDRVRRtn: OSErr;
 { Free the object }
 begin { Close }
 CloseDRVRRtn := noErr; { init }
 if gOpenFlag <> 0 then
 gButton.mFree;
 end;   { CloseDRVRRtn }

 {***********************}
 function ControlDRVRRtn: OSErr;
 { processes a code resource message }
 const
 calcCntlRgn = 10; {32 bit clean}
 var
 theCtoDHdl: CtoDHdl;
 begin
 theCtoDHdl :=
 CtoDHdl(theDCE^.dCtlPosition);

 with theCtoDHdl^^ do
 { theCtoDHdl is locked by caller }
 case fMessage of
 drawCntl: gButton.mDraw(fParam);
 { fParam contains the part code }
 testCntl: 
 fResult := gButton.mTest(fParam);
 { fParam is a mouse point }
 calcCRgns, calcCntlRgn: 
 gButton.mCalcRgn(fParam,
 fMessage);
 { fParam is a region handle }
 posCntl: 
 gButton.mPosition(fParam);
 { fParam is position offset }
 thumbCntl: 
 gButton.mCalcThumb(fParam);
 { fParam is a pointer to a record }
 dragCntl: 
 fResult := gButton.mDrag(fParam);
 { fParam indicates the type of drag}
 autoTrack: 
 gButton.mAutoTrack(fParam);
 { fParam contains the part code }
 otherwise
 end; { case fMessage }
 end;   { ControlDRVRRtn }

 {***********************}
 begin
 { Think Pascal sets dCtlStorage to its A4 }
 { world before the Open message is called. If }
 { nil, test here and fail }
 if theDCE^.dCtlStorage = nil then
 { A4 world isn’t there }
 begin
 {*** need an alert here ***}
 Main := -1;
 Exit(Main);
 end;

 case sel of
 DriverOpen: 
 Main := OpenDRVRRtn;
 DriverPrime: 
 Main := noErr;
 DriverControl: 
 if gOpenFlag <> 0 then
 main := ControlDRVRRtn;
 DriverStatus: 
 Main := noErr;
 DriverClose: 
 Main := CloseDRVRRtn;
 end;
 end;   { Main }

{----------------------}
end.  { unit BasicCDEFDriver }
Listing: Shell Code Resource
{ This Code Resource is a CDEF that acts as a }
{ shell to a driver resource. }
{ The driver resource is ‘DRVR’, ID=500.}
{ NOTE: The DATA resource also compiled with the }
{ driver resource must have its ID changed to -32}

unit ShellCDEF;
interface

 function Main (VarCode: Integer;
 TheControl: ControlHandle; message: integer; 
 Param: Longint) : Longint;

implementation
 const
{ these are the byte offsets into the}
{ driver header for the jmp offset table }
 drvrSelOpenOffset = 2;
 drvrSelCloseOffset = 4;
 drvrSelControlOffset = 3;

 drvrResType = ‘DRVR’;
 drvrResID = 500;
 dataInternalID = 0; { to DATA res }
 { your DATA resource must have a res } 
 { ID of -(dataInternalID + 1) * 32 }

 type
{ this array typecasts the driver to }
{ access its offset table.  }
{ Because Think Pascal has no word type,}
{ we can extract the low or high word of}
{ LongInts using Loword and Hiword fctns}
 LongArr = array[0..10] of LongInt;
 LongArrPtr = ^LongArr;
 LongArrHdl = ^LongArrPtr;
{ Open is in hiword of LongArrHdl^^[2] }
{ Close is in hiword of LongArrHdl^^[4] }
{ Control is in hiword of LongArrHdl^^[3]}

 function CallDriver (devCtlEnt: DCtlPtr;
 paramBlock: ParmBlkPtr; theDriverOfs: Ptr)
 : integer;
 inline
 $2F0A, { MOVEA.LA2,-(A7) }
 { preserve A2 in the function return }
 $246F, $0004, { MOVEA.L  4(A7),A2 }
 { Routine to jump to }
 $206F, $0008, { MOVEA.L  $8(A7), A0 }
 { parmBlkPtr must go in A0 }
 $226F, $000C, { MOVEA.L  $C(A7), A1 }
 { dCtlPtr must go in A1 }
 $4E92, { JSR    (A2) }
 { call the driver - result is in D0 }
 $245F, { MOVEA.L(A7)+,A2 }
 { restore A2 before setting return value }
 $DEFC, $000C, { ADDA.W #$C, A7 }
 { restore the stack except for }
 { function return value }
 $3E80; { MOVEA.LD0, (A7) }
 { return value on stack }
{----------------------}
 function Main (VarCode: Integer;
 TheControl: ControlHandle; message: integer;
 Param: Longint): Longint;

 type
 { CodeToDriver passes parameters from the code }
 { resource into driver add or change any }
 { parameters that apply to your code resource }
 CodeToDriver = record
 fMessage: integer;
 fVarCode: integer;
 fControl: ControlHandle; { theControl }
 fParam: LongInt;{ Param }
 fResult: LongInt; { result }
 { to return to Control Manager }
 end;
 CtoDPtr = ^CodeToDriver;
 CtoDHdl = ^CtoDPtr;
 {***********************}
 procedure InitCDEF;
{ 1. Allocate and initialize dCtlEntry. }
{  Store it in theControl^^.contrlData. }
{ 2. Load the driver and store it in }
{  dCtlEntry.dCtlDriver }
{ 3. Call the driver with Open message }
 var
 theDCEHdl: DCtlHandle;
 theDriver: Handle;
 theCtoDHdl: CtoDHdl;
 theOSErr: OSErr;
 dummyIOPB: ParamBlockRec;
 { used as a dummy field }
 driverOfs: Ptr;
 name: Str255;
 begin
 { allocate the Driver Control Entry }
 theDCEHdl :=
 DCtlHandle(NewHandle(sizeof(DCtlEntry)));
 {** memory error handling here **}
 MoveHHi(handle(theDCEHdl));
 HLock(handle(theDCEHdl));

 { allocate CodeToDriver and set it up}
 { Fields fVarCode and fControl only }
 { need to be set once }
 theCtoDHdl :=
 CtoDHdl(NewHandle(sizeof(CodeToDriver)));
 {** memory error handling here **}
 MoveHHi(handle(theCtoDHdl));
 HLock(handle(theCtoDHdl));
 with theCtoDHdl^^ do
 begin
 fMessage := initCntl;
 fVarCode := varCode;
 fControl := theControl;
 fParam := param;
 end; { with theCtoDHdl^^ }

 { load the driver }
 theDriver := GetResource(drvrResType,drvrResID);
 {** memory error handling here **}
 MoveHHi(theDriver);
 HLock(theDriver);
 HNoPurge(theDriver);

 { fill in the Driver Control Entry }
 { store it in the control }
 { Most fields not used }
 with theDCEHdl^^ do
 begin
 dCtlDriver := Ptr(theDriver);
 dCtlFlags := $4400;
 { allow Control messages }
 dCtlQHdr.qFlags := 0;  { n/u }
 dCtlQHdr.qHead := nil; { n/u }
 dCtlQHdr.qTail := nil; { n/u }
 dCtlPosition := ord4(theCtoDHdl);
 { passing parameter block }
 dCtlStorage := nil;
 { used by Think Pascal }
 dCtlRefNum := dataInternalID;
 { Think will calculate an ID }
 { of -32 for its DATA resource }
 dCtlCurTicks := 0;{ n/u }
 dCtlWindow := nil;{ n/u }
 dCtlDelay := 0; { n/u }
 dCtlEMask := 0; { n/u }
 dCtlMenu := 0;  { n/u }
 end; { with theDCEHdl^^ }
 theControl^^.contrlData := handle(theDCEHdl);

 { now call the driver with the Open } 
 { selector. theDCEHdl and theDriver }
 { are already locked }
 driverOfs := Ptr(ord4(theDriver^) +
 hiword(LongArrHdl(theDriver)^^[drvrSelOpenOffset]));

 { Although we fill in some of }
 { dummyIOPB, the Think Pascal 2.0 }
 { driver structure doesn’t use it }
 with dummyIOPB do
 begin
 qLink := nil;
 qType := Ord(ioQType);
 ioCmdAddr := driverOfs;
 ioTrap := 0;
 ioCompletion := nil;
 ioRefNum := drvrResID + 1;
 { driver refNum + 1 }
 end; { with dummyIOPB }
 theOSErr := CallDriver(theDCEHdl^,
 @dummyIOPB, driverOfs);
 {** error handling here **}
 end; { InitCDEF }

 {***********************}

 procedure DisposeCDEF;
{ 1. Call the Driver with Close message }
{  so it can unload its storage and segments }
{ 2. Dispose our own storage }
 var
 theDCEHdl: DCtlHandle;
 theDriver: Handle;
 theCtoDHdl: CtoDHdl;
 theOSErr: OSErr;
 dummyIOPB: ParamBlockRec;{ dummy field }
 driverOfs: Ptr;
 begin
 theDCEHdl := 
 DCtlHandle(theControl^^.contrlData);
 theDriver := 
 handle(theDCEHdl^^.dCtlDriver);
 LoadResource(theDriver);
 { in case it was purged after another}
 { code resource using it was disposed}
 HNoPurge(theDriver);
 HLock(theDriver);
 { in case it was unlocked by Think }
 theCtoDHdl :=
 CtoDHdl(theDCEHdl^^.dCtlPosition);

 { Call the driver with the Close }
 { selector. theDCEHdl and theDriver }
 { are already locked }
 theCtoDHdl^^.fMessage := dispCntl;
 { not really used, but... }
 driverOfs := Ptr(ord4(theDriver^) +
 hiword(LongArrHdl(theDriver)^^
 [drvrSelCloseOffset]));

 { Although we fill in some of dummyIOPB, Think }
 { Pascal 2.0’s driver structure doesn’t use it }
 with dummyIOPB do
 begin
 qLink := nil;
 qType := Ord(ioQType);
 ioCmdAddr := driverOfs;
 ioTrap := 0;
 ioCompletion := nil;
 ioRefNum := drvrResID + 1;
 { driver refNum + 1 }
 end; { with dummyIOPB }
 theOSErr := CallDriver(theDCEHdl^,
 @dummyIOPB, driverOfs);

 { now dispose structures }
 DisposHandle(handle(theCtoDhdl));
 DisposHandle(handle(theDCEHdl));

 { don’t dispose the driver since we may be }
 { sharing it with many instances of this CDEF. }
 { Instead, mark it to be purged and always }
 { call LoadResource before using it elsewhere }
 HPurge(theDriver);
 end; { DisposeCDEF }
 {***********************}
 function CDEFMessages: LongInt;
{ 1. setup message }
{ 2. call driver with Control command }
{ 3. return function value }
 var
 theDCEHdl: DCtlHandle;
 theDriver: Handle;
 theCtoDHdl: CtoDHdl;
 theOSErr: OSErr;
 dummyIOPB: ParamBlockRec;
 { used as a dummy field }
 driverOfs: Ptr;
 begin
 theDCEHdl := 
 DCtlHandle(theControl^^.contrlData);
 theDriver :=
 handle(theDCEHdl^^.dCtlDriver);

 LoadResource(theDriver);
 { in case it was purged after another}
 { code resource using it was disposed}

 HNoPurge(theDriver);
 HLock(theDriver);
 { in case it was unlocked by Think }
 theCtoDHdl := CtoDHdl(theDCEHdl^^.dCtlPosition);

 with theCtoDHdl^^ do
 begin
 fMessage := message;
 fParam := param;
 fResult := 0; { init }
 end; { with }

 { Call the driver with the Control }
 { selector. theDCEHdl and theDriver }
 { are already locked }
 driverOfs := Ptr(ord4(theDriver^) +
 hiword(LongArrHdl(theDriver)^^
 [drvrSelControlOffset]));

 { Although we fill in some of }
 { dummyIOPB, the Think Pascal 2.0 }
 { driver structure doesn’t use it }
 with dummyIOPB do
 begin
 qLink := nil;
 qType := Ord(ioQType);
 ioCmdAddr := driverOfs;
 ioTrap := 0;
 ioCompletion := nil;
 ioRefNum := drvrResID + 1;
 { driver refNum + 1 }
 end; { with dummyIOPB }
 theOSErr := CallDriver(theDCEHdl^,
 @dummyIOPB, driverOfs);

 CDEFMessages := theCtoDHdl^^.fResult;
 end; { CDEFMessages }
 {***********************}
 begin
 Main := 0; { initialize result }
 case Message of
 InitCntl: 
 InitCDEF;
 DispCntl: 
 DisposeCDEF;
 otherwise
 Main := CDEFMessages;
 end; { CASE }
 end; { Main }
{----------------------}
end.  { UNIT ShellCDEF }

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Viber 11.9.1 - Send messages and make fr...
Viber lets you send free messages and make free calls to other Viber users, on any device and network, in any country! Viber syncs your contacts, messages and call history with your mobile device, so... Read more
Vallum 3.3.2 - $15.00
Vallum is a little tool that helps you monitor and block apps connections and throttle apps bandwidth. It is able to intercept connections at the application layer, and hold them while you decide... Read more
Microsoft OneNote 16.31 - Free digital n...
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
Apple Pages 8.2.1 - Apple's word pr...
Apple Pages is a powerful word processor that gives you everything you need to create documents that look beautiful. And read beautifully. It lets you work seamlessly between Mac and iOS devices, and... Read more
Numbers 6.2.1 - Apple's spreadsheet...
With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
f.lux 39.9873 - Adjusts the color of you...
f.lux makes the color of your computer's display adapt to the time of day, warm at night and like sunlight during the day. Ever notice how people texting at night have that eerie blue glow? Or wake... Read more
Deeper 2.5.0 - Enable hidden features in...
Deeper is a personalization utility for macOS which allows you to enable and disable the hidden functions of the Finder, Dock, QuickTime, Safari, iTunes, login window, Spotlight, and many of Apple's... Read more
NTFS 15.5.71 - Provides full read and wr...
NTFS breaks down the barriers between Windows and macOS. Paragon NTFS effectively solves the communication problems between the Mac system and NTFS. Write, edit, copy, move, delete files on NTFS... Read more
MTR 5.3.0.0 - The Mac's oldest and...
MTR (was MacTheRipper)--the Mac's oldest and smartest DVD-backup app. MTR - the complete toolbox, not a one-trick, point-and-click extractor. MTR is intended for making fair-use, backup copies of... Read more
Keynote 9.2.1 - Apple's presentatio...
Easily create gorgeous presentations with the all-new Keynote, featuring powerful yet easy-to-use tools and dazzling effects that will make you a very hard act to follow. The Theme Chooser lets you... Read more

Latest Forum Discussions

See All

Black Desert Mobile gets an official rel...
Pearl Abyss has just announced that its highly-anticipated MMO, Black Desert Mobile, will launch globally for iOS and Android on December 11th. [Read more] | Read more »
Another Eden receives new a episode, cha...
Another Eden, WFS' popular RPG, has received another update that brings new story content to the game alongside a few new heroes to discover. [Read more] | Read more »
Overdox guide - Tips and tricks for begi...
Overdox is a clever battle royale that changes things up by adding MOBA mechanics and melee combat to the mix. This new hybrid game can be quite a bit to take in at first, so we’ve put together a list of tips to help you get a leg up on the... | Read more »
Roterra Extreme - Great Escape is a pers...
Roterra Extreme – Great Escape has been described by developers Dig-It Games as a mini-sequel to their acclaimed title Roterra: Flip the Fairytale. It continues that game's tradition of messing with which way is up, tasking you with solving... | Read more »
Hearthstone: Battlegrounds open beta lau...
Remember earlier this year when auto battlers were the latest hotness? We had Auto Chess, DOTA Underlords, Chess Rush, and more all gunning for our attention. They all had their own reasons to play, but, at least from where I'm standing, most... | Read more »
The House of Da Vinci 2 gets a new gamep...
The House of Da Vinci launched all the way back in 2017. Now, developer Blue Brain Games is gearing up to deliver a second dose of The Room-inspired puzzling. Some fresh details have now emerged, alongside the game's first official trailer. [Read... | Read more »
Shoot 'em up action awaits in Battl...
BattleBrew Productions has just introduced another entry into its award winning, barrelpunk inspired, BattleSky Brigade series. Whilst its previous title BattleSky Brigade TapTap provided fans with idle town building gameplay, this time the... | Read more »
Arcade classic R-Type Dimensions EX blas...
If you're a long time fan of shmups and have been looking for something to play lately, Tozai Games may have just released an ideal game for you on iOS. R-Type Dimensions EX brings the first R-Type and its sequel to iOS devices. [Read more] | Read more »
Intense VR first-person shooter Colonicl...
Our latest VR obsession is Colonicle, an intense VR FPS, recently released on Oculus and Google Play, courtesy of From Fake Eyes and Goboogie Games. It's a pulse-pounding multiplayer shooter which should appeal to genre fanatics and newcomers alike... | Read more »
PUBG Mobile's incoming update bring...
PUGB Mobile's newest Royale Pass season they're calling Fury of the Wasteland arrives tomorrow and with it comes a fair chunk of new content to the game. We'll be seeing a new map, weapon and even a companion system. [Read more] | Read more »

Price Scanner via MacPrices.net

New 2019 16″ MacBook Pros on sale for $100 of...
Apple Authorized Reseller Adorama has new 2019 16″ MacBook Pros on sale today for $100 off Apple’s MSRP, each including free shipping. In addition, Adorama charges sales tax for NY & NJ residents... Read more
Apple Watch Series 3 GPS models on sale for l...
Amazon has Apple Watch Series 3 GPS models on sale starting at only $179. There prices are the lowest we’ve ever seen for these models from any Apple reseller. Choose Amazon as the seller rather than... Read more
iOS Bug In Facebook News Feed Lets Device Ca...
NEWS: 11.15.19- Users of the Facebook social media platform’s mobile app running on iOS devices won’t, like, this piece of news one bit in where a bug in the News Feed gave access to the camera... Read more
16″ MacBook Pros on sale! Preorder at Amazon...
Apple’s new 16″ MacBook Pros were only introduced yesterday, but Amazon is already offering a $100 discount on preorders. Prices for the base 6-Core 16″ MacBook Pros start at $2299: – 2019 16″ 2.6GHz... Read more
Use our exclusive MacBook Price Trackers to f...
Our Apple award-winning MacBook price trackers are the best place to look for the best sales & lowest prices on new and clearance MacBook Airs and MacBook Pros–including Apple’s new 16″ MacBook... Read more
New November Verizon iPhone deal: Get an iPho...
Verizon has the 64GB iPhone Xr on sale for 50% off for a limited time, plus they will include a free $200 prepaid MasterCard and a free Amazon Echo Dot. That reduces their price for the 64GB iPhone... Read more
Apple cuts prices on clearance, refurbished 2...
Apple has clearance 2018 15″ 6-Core Touch Bar MacBook Pros, Certified Refurbished, now available starting at only $1829. Each model features a new outer case, shipping is free, and an Apple 1-year... Read more
Up to $450 price drop on clearance 15″ MacBoo...
B&H Photo has dropped prices Apple’s 2019 15″ 6-Core and 8-Core MacBook Pros by $400-$450 off original MSRP, starting at $1999, with free overnight shipping available to many addresses in the US... Read more
Here’s how to save $200 on Apple’s new 16″ Ma...
Apple has released details of their Education discount associated with the new 2019 16″ 6-Core and 8-Core MacBook Pros. Take $200 off the price of the new 8-Core model (now $2599) and $200 off the 16... Read more
Price drop! 2019 15″ 2.6GHz 6-Core MacBook Pr...
Focus Camera has dropped their price for clearance 2019 15″ 2.6GHz 6-Core Space Gray MacBook Pros by $400 to $1999 shipped. Apple’s original MSRP for this model was $2399. Focus charges sales tax for... Read more

Jobs Board

Best Buy *Apple* Computing Master - Best Bu...
**746655BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Sales **Store NUmber or Department:** 002518-Atlantic Center-Store **Job Description:** Read more
*Apple* Mobility Pro - Best Buy (United Stat...
**744973BR** **Job Title:** Apple Mobility Pro **Job Category:** Store Associates **Store NUmber or Department:** 000949-Rochester Hills-Store **Job Description:** Read more
AV Systems Engineer at *Apple* - Theorem, L...
Job Summary Apple Retail Technology is looking for an Audio Visual Systems Engineer to design and implement scalable, next-generation A/V solutions for Apple ?s Read more
Nurse Practitioner - Field Based (San Bernard...
Nurse Practitioner - Field Based (San Bernardino, CA, Apple Valley, Hesperia) **Location:** **United States** **New** **Requisition #:** PS30312 **Post Date:** 3 Read more
Best Buy *Apple* Computing Master - Best Bu...
**746510BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Store NUmber or Department:** 001407-Milford-Store **Job Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.