TweetFollow Us on Twitter

MultiWindow DA
Volume Number:7
Issue Number:7
Column Tag:Pascal Procedures

Related Info: Menu Manager Resource Manager Window Mgr

Multi-Window/Menu DA

By Lincoln Stein, MD, Boston, MA

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

[Lincoln Stein, MD, is a Harvard Medical School Graduate. He is currently developing medical education software with the Decision Systems Group at Brigham and Women’s Hospital in Boston.]

Once Upon a Desktop

A long long time ago (well, only about two years, really) Macintosh applications and desk accessories were quite easy to tell apart. Applications supported multiple windows, owned all the menus in the menu bar, and ran one at a time. Desk accessories, in contrast, only had one window and a single menu if any. In partial compensation for these limitations, however, multiple desk accessories could run simultaneously with each other and the underlying application.

Things changed considerably with the advent of Multifinder. Now multiple applications can run simultaneously, nullifying desk accessories’ major advantage. The distinction will become even more murky with the advent of System 7, which promises to allow applications to be placed in the apple menu and to let desk accessories be launched from the desktop. The widespread predictions of the DA’s demise turned out to be premature, however. Indeed, desk accessories are proliferating as rapidly as ever and are reaching heights of sophistication that rival full-blown applications. At the same time that Multifinder has made applications more desk accessory-like, savvy developers have been making their desk accessories more and more application-like.

You’ve probably noticed that some commercial and public domain desk accessories are not content with the single window that imprisoned the classic desk accessories. These applications let you spawn new windows wantonly, a distinctly application-like thing to do. This article will show you how to repeat this multi-window trick. It will also show you how desk accessories can take over the entire menu bar in order to display Multiple menus, something that I haven’t seen done by any desk accessory, commercial or otherwise!

This article will not discuss the basics of how to write a desk accessory, which has been covered well in past articles (see MacTutor volumes II no. 4, II no. 5, II no. 6, and III no. 10).

A DA With a Difference

The example desk accessory, MultiDA, serves no useful function. It was written like this in order to keep the code simple. When the DA is invoked it takes over the menu bar and replaces the application’s menu bar with its own Apple, File, Edit and Windows menus. The DA allows you to open up an unlimited number of windows, type into a text edit field, cut and paste between windows, stack the windows up neatly, and close the windows. When the last window is closed, or when you choose “Quit” from the File menu, the desk accessory closes itself. If you select MultiDA from the apple menu when it is already open, it brings all its windows to the front. The desk accessory’s menus are switched in and out as appropriate so that there’s never any conflict between MultiDA’s menus and the application’s. MultiDA works under Unifinder and, surprisingly, under Multifinder as well (with a small caveat discussed later). Figure 1 shows MultiDA in action: notice that the Apple, File, Edit and Window menus all belong to the DA.

The code is written in Think Pascal. Unlike other development environments, Think lets you write desk accessories that use global variables and multiple segments. If you use a different development environment, you will have to make some changes to overcome these differences.

Figure 1. MultiDA.

Multiplying Menus

In principle it is as easy for a desk accessory to put up multiple menus as it is to put up a single menu. All the work of putting up and taking down a single menu occurs during activate and deactivate events. Whenever its window is activated, the desk accessory calls the Menu Manager routine InsertMenu to add its menu to the menu bar. Similarly, whenever the desk accessory window is deactivated, the DA calls DeleteMenu to remove its menu. In order to let the system know that the DA has put a menu up, the DA must store its menu’s ID in the dCtlMenu field of the driver’s driver control entry (this is a relocatable data structure created for each desk accessory when the DA is opened; see previous MacTutor articles for further information). Once this is done, the system refers all selections from that menu to the DA’s code.

Similarly, all the work of putting up multiple menus occurs during activate/deactivate events. In the source code, most of the fancy stuff occurs in the routines DoActivate, which handles both activate and deactivate events. The required steps are described in Inside Macintosh volume 1, pages 446-447. First the DA must save a copy of the application’s menu bar in its globals using the Menu Manager procedure GetMenuBar. Then it must clear the menu bar and add its own menu using ClearMenuBar followed by calls to InsertMenu. As before, it stores the ID of one of its menus into the dCtlMenu field of the driver control entry (any of the menu IDs will do for this purpose). Finally, and this is the key part, the desk accessory must save this same menu ID into the low memory global mBarEnable ($A20). This tells the system that the desk accessory now owns the entire menu bar directs the system to send all menu choices to the desk accessory rather than to the underlying application. Whenever the desk accessory window is deactivated, this process should be reversed. The menu bar should be cleared and the application’s menus put back using SetMenuBar with the previously stored copy. The mBarEnable global should then be cleared.

While the desk accessory’s menus are in the menu bar, the system handles all the tedious work of pulling down the menu and tracking the user’s selections. All the DA ever sees is an accMenu control call. When this call is received, the DA should examine the I/O parameter block (another desk accessory data structure: see Inside Macintosh for details) to determine the menu and item chosen: param[0] will contain the ID number of the menu chose, and param[1] will contain the item number of the selected menu item.

This is essentially all there is to giving a desk accessory multiple menus, and just a few details remain to be ironed out. The first detail is determining the IDs of the desk accessory’s menus. Usually, a menu is stored as a ‘MENU’ resource; its menu ID is usually identical to the resource ID number. The problem with desk accessories is that their resource numbers get shifted around by Font/DA Mover and by such utilities as Suitcase in order to avoid numbering conflicts with other desk accessories. In order to get around the problem of shifty resource ID’s, each resource used by the desk accessory is numbered as an “owned resource” (as described in the resource manager chapter of Inside Macintosh); The desk accessory’s own resource ID is mathematically combined with each owned resource’s “sub-ID” to form the true resource ID. While the ID number of the resources change each time the desk accessory’s ID changes, the sub-ID number never changes. The runtime resource ID can be derived from the driver’s reference number and the (constant) resource sub-ID using a “magic formula” which is given in the source code within the function GetResID.

When MultiDA initializes during its Open routine, it calculates its menu resource numbers using GetResID. It then reads the menus into memory one at a time (using GetMenu), and stores the returned menuHandles into a global array. It then stores the run-time ID into the MenuID field of the MenuHandle so that Menu Manager routines find the correct menu when required to. For convenience, the example DA stores the list of menu IDs in a global array so that the run-time IDs do not have to be calculated more than once.

A second bit of ironing concerns handling the command keys in DA-owned menus. Unfortunately the system does NOT handle menu command key selections for DA-owned menus. When the desk accessory receives a keydown event, it must check if the command key was held down, and if so, determine whether this corresponds to a menu selection. The natural thing to do would be to call the toolbox routine MenuKey in order to determine the corresponding menu and item number. Unfortunately, this call fails from within a desk accessory. The problem here is that MenuKey returns 0 if the associated menu item belongs to a desk accessory. Since a DA owns all the menus, MenuKey always returns 0. To circumvent this problem, you can use the routine DAMenuKey, given in the example program, to accomplish the same thing as MenuKey.

A third detail concerns the Apple menu. It’s nice for the desk accessory to put up an Apple menu so that the user can view the about box, use Multifinder, or choose other desk accessories. The problem here is that it isn’t very nice for an essentially parasitic desk accessory to open up other desk accessories without asking the underlying application’s permission. As far as I have been able to determine, only one popular application objects to this behavior; unfortunately that application happens to be Multifinder’s DA Handler. Apparently DA Handler counts the number of desk accessories opened while it is active. Whenever that count drops down to 0, DA Handler closes up its Multifinder partitition and quits. If several desk accessories are opened from within MultiDA, however, DA Handler can get confused and miscount. This can result in DA Handler exiting even though there are still desk accessories active. To avoid this problem, MultiDA checks to see if DA Handler is active, and if so, greys out the desk accessories items in the apple menu. It does not do this if Multifinder is not active or if the option key was held down when the DA was opened, forcing the desk accessory into the application’s heap.

The final detail is a merely cosmetic one. In a desk accessory with a single window and multiple menus, it is appropriate for the desk accessory to put up its menus when its window becomes active, and take down its menus when the window deactivates. However, when the DA has multiple windows, it isn’t necessary to mess with the menu bar on a deactivate event when all that’s happening is another one of the DA’s windows is becoming active. In order to avoid unnecessary menu bar flickering, MultiDA checks the event queue every time it receives a deactivate event. If it finds a pending activate event pertaining to one of its other windows, it leaves the menu bar as is.

Spawning Windows

Multiple desk accessory windows are slightly easier to implement than multiple menus. Opening desk accessory windows is easy: all you do is call NewWindow (or GetNewWindow) and store the DA’s driver reference number in the windowKind field of the newly-created window. This tells the system that the newly-opened window belongs to the desk accessory. This is demonstrated in the routine OpenAWindow in the example code.

The dCtlWindow field of the desk accessory’s driver control entry is supposed to point to the desk accessory’s window. When the DA has multiple windows, this field should point to the topmost DA window. A good time to update this field is when a window becomes active. In the example code, the routine DoActivate, in addition to swapping in the DA’s menus, places the activating window’s windowPtr into the dCtlWindow field.

Closing a window in a multi-window desk accessory is somewhat trickier. When the user clicks in any of the DA’s windows’ close boxes, the desk accessory receives a Close call. Ordinarily this means that the desk accessory should bank its fires and quit. However this is NOT what the user wants to happen when he’s only closing the topmost of several DA windows. The correct thing to do when a Close call is received is to determine how many DA windows are open. If more than one window is open, then the DA closes the topmost window (the pointer to which can be obtained from the dCtlWindow field) and informs the system that the DA didn’t close by returning a CloseErr (-24). If only one of the desk accessory’s windows is open, then the DA should go ahead and close up (it can, if it wishes, put up an “Are you sure?” alert and allow the user to change his mind).

There is one circumstance, however, in which the DA has no choice about whether to close. This occurs when the underlying application is quitting or when the Mac is shutting down. To handle this case, MultiDA has the dNeedGoodbye bit set in its header. When the heap is about to be reinitialized, the system sends MultiDA a “goodbye kiss” call. This gives the DA a chance to close all its windows and clean up its data.

The other thing to be careful about when one of the desk accessory’s multiple windows is closed is to make sure that the dCtlWindow field of the device control entry always points to a valid window. The CloseAWindow routine in the example source code contains a subroutine called UpdateDCE. This routine runs through the DA’s windows and updates the device control entry to point to the currently topmost DA window immediately after a window is closed. Never leave a pointer to a disposed window in the dCtlWindow field! This field must always remain valid. It is no use waiting for an activate event to fix the dCtlWindow field, since the Mac may well crash long before an activate event has had a chance to occur.

What should a multi-window desk accessories do when the user chooses the already-open desk accessory from the Apple menu? When this happens, the desk accessory receives a second Open call. One option is to activate just the topmost window (the one stored in the dCtlWindow field of the driver control entry). I chose the alternative behavior of bringing all the desk accessory’s windows to the front using the ModifyWindows routine. This behavior, simulating as it does the Multifinder “layers” effect, seemed more in keeping with what people expect.

Most Multi-window desk accessories will want to store a list of their currently open windows in some sort of global array. This would let them, for example, keep a parallel array of associated data structures, or create and maintain a Windows menu that lists the open windows and lets the user bring them to the front selectively. To keep the code simple, I didn’t do this in the example DA. Instead, whenever I want to perform a repetitive action on all the windows, such as bringing them all forward, stacking them up nicely, or closing them all, I call a routine called ModifyWindows. This routine takes a procedure pointer as one of its parameters. It searches through the linked window list for each of the DA’s windows (which it recognizes by the windowKind field), and then calls the procedure pointer for each window in turn. While playing with this way of doing things, I realized that some operations, such as stacking the windows up, would look much nicer if the operation were performed starting at the furthest back window and working forwards. The elegant way to do this was to make ModifyWindows a recursive procedure; see the code for details.

Compiling the Example Code

To compile the desk accessory in from the listing that follows you need Think Pascal and access to MPW rez or ResEdit. You must first use MPW rez (or ResEdit) to create a resource file. Then set up a Think Pascal project with the build order shown in Figure 2. You must use the drvrRuntime library rather than the standard runtime library or you will get link errors! Set Think’s runtime options to use the resource file you created. Then set up the project type dialog as shown in Figure 3.

Figure 2. Build Order.

Figure 3. Project Type.

Compile the project as a desk accessory. This will create a suitcase-type file that can be installed with Font/DA mover, or used with Suitcase or Font/DA juggler.

Concluding Remarks

MultiDA demonstrates how to implement multiple windows and menus from within a desk accessory. With these techniques, and with Think Pascal’s support for global data and multiple segments in DA’s, who needs applications?

{File Multi DA.p}
{Example of a desk accessory with multiple windows and menus.}
{© 1990, Lincoln D. Stein}
unit MenuDA;

interface

function Main (DCtlE: DCtlPtr;
      IOPB: ParmBlkPtr;
      driveCall: Integer): OSErr;

implementation

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

OpenErr = -23;
CloseErr = -24;

{Offsets to resource IDs for main dialog and alert}
DlogID = 0;
AboutID = 0;

{Subitem resource numbers of our menus}
AppleMenu = 0;
AboutItem = 1;

FileMenu = 1;
NewWindowItem = 1;
CloseItem = 2;
QuitItem = 4;

EditMenu = 2;
undoItem = 1;
cutItem = 3;
copyItem = 4;
pasteItem = 5;
clearItem = 6;

WindowMenu = 3;
CleanupItem = 1;
BeepItem = 2;

{Dialog itemlist}
NewWindowButton = 1;
EraseButton = 2;
EditText = 3;

type
MenuBar = (DAMenus, AppMenus);

var
done: boolean;
SavedMenuList: handle;
MenuIDs: array[0..WindowMenu] of integer;
OurMenus: array[0..WindowMenu] of MenuHandle;
OurMenuBar: boolean;

DCE: DCtlPtr;
OurName: str255;
NumWindows: integer;
WindowCounter: integer;

{ ********************************************** }
{ ************Global Utility Functions********** }
{ ********************************************** }
{The actual resource IDs of our resources depends on our desk accessory’s 
run-time device control reference number (this gets switched around by 
Font/DA Mover and Suitcase.  The “owned resource” sub-ID never changes, 
however.  Use our dCtlRefNum to get the run-time ID of our resources}
function GetResID (SubID: integer): integer;
begin
 GetResID := BOR($C000, SubID + (BSL((Abs(DCE^.dCtlRefNum) - 1), 5)));
end;

{----------------------DAMenuKey--------------}
{MenuKey does not work from within a desk accessory because the system 
does not respond correctly to the meta-keys used in DA menus. This procedure 
is a substitute for MenuKey that performs the function correctly. Given 
a character, this function will determine if it is a menu meta-character 
and returns the menu ID in the high order word and the item in the low 
order word just as MenuKey does.  Note that this code makes use of information 
stored in our globals, and is NOT directly transferable to other desk 
accessories.}
function DAMenuKey (cmd: char): longint;
var
 i, item: integer;
 key: char;
begin
{Return a ‘0’ as default}
 DAMenuKey := 0;
{Capitalize lowercase letters}
 if (cmd >= ‘a’) & (cmd <= ‘z’) then
  cmd := Chr(Ord(cmd) - (Ord(‘a’) - Ord(‘A’)));

{Loop through each menu, looking for matches}
 for i := 0 to WindowMenu do

{If we find an enabled menu then examine each enabled item in turn until 
we find a matching command key.}
  if BTST(OurMenus[i]^^.enableFlags, 0) then
   for item := 1 to CountMItems(OurMenus[i]) do

    if BTST(OurMenus[i]^^.enableFlags,item) then
     begin
      GetItemCmd(OurMenus[i], item, key);
      if key <> Cmd then
       cycle
      else
       DAMenuKey := BOR(item,BSL(MenuIDs[i],16));
      HiliteMenu(MenuIDs[i]);
      Exit(DAMenuKey);
     end

end; {of FUNCTION MyMenuKey}

{**********************************************}
{************ Menu Handling Routines **********}
{**********************************************}

{------------------InitMenus--------------}
{Fetch and install our menus, remembering the application’s menu bar 
for later switching. We return the menu ID of any of our menus for installation 
into the dCtlMenu field of the device control entry. It doesn’t matter 
exactly which of our menus we return.}
function InitMenus: integer;
const
 CurApName = $910; {Low memory global}
{Hard code the name of the desk accessory layer in multifinder.  Note 
that there is a non-breaking space between DA and Handler.}
 DALayer = ‘DAHandler’;
var
 i: integer;
 name: str255;
begin
 for i := 0 to WindowMenu do
  begin
   MenuIDs[i] := GetResID(i);
   OurMenus[i] := GetMenu(MenuIDs[i]);
   OurMenus[i]^^.MenuID := MenuIDs[i];
  end;

 AddResMenu(OurMenus[AppleMenu], ‘DRVR’);
 InitMenus := MenuIDs[AppleMenu];

{If we’ve been loaded into DA Handler, then we dim out our desk accessories. 
This is done because DA Handler does not like desk accessories opening 
other desk accessories!}
 if StringPtr(CurApName)^ = DALayer then
  for i:=1 to CountMItems(OurMenus[AppleMenu]) do
   begin
    GetItem(OurMenus[AppleMenu], i, name);
    if name[1] = char($00) then
     DisableItem(OurMenus[AppleMenu], i);
   end;
end;

{----------------SaveMenuBar------------------}
{This procedure is called in order to make a copy of, and save, the current 
menu bar data structure. It is conceivable that it may be called twice 
in a row, so dispose of any previously saved menubars.}
procedure SaveMenuBar;
begin
 if SavedMenuList <> nil then
  Disposhandle(SavedMenuList);
 SavedMenuList := GetMenuBar;
end;

{------------------InsertDAMenus----------------}
procedure InsertDAMenus;
var
 i: integer;
begin
 ClearMenuBar;
 for i := 0 to WindowMenu do
  InsertMenu(OurMenus[i], 0);
end;

{------------------AdjustMenus------------------}
{This procedure adjusts the menus periodically to allow for changes It 
would typically be used enabling and disabling menuitems, changing item 
names, etc. as appropriate for the DA’s state.}
procedure AdjustMenus;
begin
 if NumWindows > 1 then
  enableItem(OurMenus[Filemenu], CloseItem)
 else
  disableItem(OurMenus[FileMenu], CloseItem)
end;

{--------------------SetMenu--------------------}
{This procedure installs our menubar when one of our windows becomes 
active. Pass it “DAMenus” to install our DA’s menubar. Pass it “AppMenus” 
to restore the application’s menubar.}
procedure SetMenu (which: MenuBar);
var
 mBarEnable: ^integer;

{----}
{FUNCTION OursIsActivating is used to determine if one of our windows 
is about to come to the top. If this is going to happen, then there is 
no use switching menus when one of our windows is deactivated. It calls 
EventAvail, looking for an activate event in one of our own windows.}
function OursIsActivating: boolean;
 var
  Evt: eventRecord;
  kind: integer;
 begin
  OursIsActivating := false;
  if EventAvail(activMask, Evt) &
(BitAnd(Evt.modifiers,activeFlag)<>0) then
    begin
     kind := WindowPeek(Evt.message)^.windowkind;
      OursIsActivating := (kind = DCE^.dctlRefNum)
    end
 end;

{----}
begin {PROCEDURE SetMenu}
 mBarEnable := Pointer($A20);
  {Install DA’s menubar if requested to and our menu bar isn’t there 
already.}
 if (which = DAMenus) then
  begin
   if not OurMenuBar then
    begin
     SaveMenuBar;
     InsertDAMenus;
     MBarEnable^ := DCE^.dctlMenu;
     OurMenuBar := true;
     DrawMenuBar
    end
  end
 else if not OursIsActivating then
  begin
   SetMenuBar(SavedMenuList);
   MBarEnable^ := 0;
   OurMenuBar := False;
   DrawMenuBar
  end
end;

{**********************************************}
{ ********* Window Handling Routines ********* }
{********************************************* }

{----------------ModifyWindows---------------- }
{PROCEDURE ModifyWindows is passed a procedure parameter. It loops through 
our windows performing the passed procedure on each of our windows, starting 
with the window passed in Start. By calling itself recursively, it performs 
the action on the bottom-most window first and the top-most window last. 
A neat trick! }
procedure ModifyWindows (Start: UNIV WindowPeek;
       procedure DoSomething (aW: Windowptr));
begin
 if Start = nil then
  Exit(ModifyWindows);
 WindowCounter := 1;
 ModifyWindows(Start^.NextWindow, DoSomething);
 if Start^.windowKind = DCE^.dctlRefNum then
  begin
   DoSomething(WindowPtr(Start));
   WindowCounter := succ(WindowCounter)
  end;
end;

{------------------------------}
{The following procedures are used in calls to the ModifyWindows procedure 
to do the same task to each of our windows in turn...}

{PROCEDURE CleanupProc stacks the windows in place one by one, using 
the WindowCounter global to keep track of which window we’re working 
on.}
procedure CleanupProc (theWind: windowptr);
const
 spacing = 10;
 vStart = 40;
 hStart = 5;
begin
 HideWindow(theWind);
 MoveWindow(theWind, WindowCounter * spacing + hStart, WindowCounter 
* spacing + vStart, true);
 ShowWindow(theWind)
end;

{------------------------------}
procedure BringWindowForward (aW: Windowptr);
begin
 BringToFront(aW)
end;

{--------------------OpenAWindow----------------}
{FUNCTION OpenAWindow opens up a new window, stores our DA’s dCtlRefNum 
into its windowKind field so that the system knows it’s a DA’s window, 
and sets the title and initial location in an appropriate manner. We 
use the run-time name of our desk accessory to form the window title. 
We also bump up our window counter to keep track of how many windows 
are open. This would be a good place to store the windowptr into an array 
or linked list, in order to keep track of the windows more carefully.}
function OpenAWindow: Windowptr;
var
 aW: windowptr;
 WindowNo: str255;
 theID:integer;
begin
 theID:=GetResID(DlogID);
 aW:= GetNewDialog(theID, nil, pointer(-1));
 if aW <> nil then
  begin
   windowpeek(aW)^.windowKind := DCE^.dCtlRefNum;
   NumToString(NumWindows, WindowNo);
   SetWTitle(aW, concat(OurName, ‘ ‘, WindowNo));
   WindowCounter := NumWindows;
   CleanUpProc(aW);
   NumWindows := NumWindows + 1;
  end;
 OpenAWindow := aW
end;


{----------------CloseAWindow------------------}
{PROCEDURE CloseAWindow closes down one of our windows and decrements 
the NumWindows counter, in a more sophisticated desk accessory, it would 
handle disposing of the various data structures, files, etc. associated 
with the window. Note that if the window we’re closing is the same as 
that stored in the dCtlWindow field, we must update the field to point 
to a current valid window. Local procedure UpdateDCE handles this task.}
procedure CloseAWindow (aWindow: WindowPtr);

{----}
{Procedure UpdateDCE sets the DCE^.dCtlWindow field to point to our topmost 
open window.  The dCtlWindow field should always point to a valid window 
or the desk accessory will die horribly in a matter of ticks.}
procedure UpdateDCE;
 var
  aWindow: WindowPeek;
 begin
  DCE^.dCtlWindow := nil ;
  aWindow := WindowPeek(FrontWindow);
  while aWindow <> nil do
   if aWindow^.WindowKind = DCE^.dCtlRefNum then
    begin
     DCE^.dCtlWindow := pointer(aWindow);
     Exit(UpdateDCE);
    end
   else
    aWindow := aWindow^.NextWindow;
 end; {PROCEDURE UpdateDCE}

{----}
begin {PROCEDURE CloseAWindow}
 DisposDialog(aWindow);
 NumWindows := NumWindows - 1;

 if DCE^.dCtlWindow = aWindow then
  UpdateDCE;
 SetMenu(AppMenus);
end;

{ ********************************************** }
{ ***************** Menu Handlers*************** }
{ ********************************************** }

{------------------DoApple----------------------}
{PROCEDURE DoApple handles the apple menu}
procedure DoApple (itemNo: integer);
var
 dummy: integer;
 name: str255;
begin
 if itemNo = AboutItem then
  dummy := Alert(GetResId(AboutID), nil )
 else
  begin
   GetItem(OurMenus[applemenu], itemNo, name);
   dummy := OpenDeskAcc(name);
  end
end;

{--------------------DoFile--------------------}
{PROCEDURE DoFile handles the file menu}
procedure DoFile (ItemNo: integer);
var
 dummy: windowptr;
begin
 case ItemNo of
  NewWindowItem:
   dummy := OpenAWindow;
  CloseItem:
   CloseAWindow(FrontWindow);
  QuitItem:
   done := true;
 end;
end;

{------------------DoEdit----------------------}
{PROCEDURE DoEdit handles the Edit Menu}
procedure DoEdit (ItemNo: integer);
var
 OurDlog: DialogPtr;
 dummy: integer;
begin
 OurDlog := FrontWindow;
 if OurDlog <> nil then
  begin
   {Move the public scrap to the TE scrap for the dialog manager’s use.}
   dummy := TEFromScrap;
   case ItemNo of
    undoItem:
     sysbeep(5);
    cutItem:
     DlgCut(OurDlog);
    copyItem:
     DlgCopy(OurDlog);
    pasteItem:
     DlgPaste(OurDlog);
    clearItem:
     DlgDelete(OurDlog);
   otherwise
   end;
   {Move the TE scrap to the public scrap.}
   dummy := ZeroScrap;
   dummy := TEToScrap
  end
 else
  sysbeep(5);
end;

{----------------DoWindowMenu------------------}
{PROCEDURE DoWindowMenu handles the Windows menu}
procedure DoWindowMenu (itemNo: integer);
begin
 case itemNo of
  CleanupItem:
   modifyWindows(FrontWindow, CleanupProc);
  BeepItem:
   sysbeep(20);
 end;
end;

{--------------------DoMenus----------------}
{PROCEDURE DoMenus is the main dispatch for all menu selections. Since 
the menu IDs are determined onlyat run time, we cannot use a CASE constant 
structure here. Instead, we use a series of IF ELSE statements to determine 
which of our menus was chosen.}
procedure DoMenus (MenuNo, ItemNo: integer);
var
 Str1, Str2: str255;
 dummy: integer;
 OurDlog: dialogptr;
begin
 if MenuNo = MenuIDs[AppleMenu] then
  DoApple(ItemNo)
 else if MenuNo = MenuIDs[FileMenu] then
  DoFile(ItemNo)
 else if MenuNo = MenuIDs[EditMenu] then
  DoEdit(ItemNo)
 else if MenuNo = MenuIDs[WindowMenu] then
  DoWindowMenu(ItemNo);
 Hilitemenu(0);
end;

{***********************************************}
{*********** Event Handling Routines ***********}
{***********************************************}

{------------------DoActivate------------------}
{PROCEDURE DoActivate handles activate/deactivate events in our DA’s 
windows. On an activate event we install the DA’s menu bar and update 
DCE^.dCtlWindow field to point to the active window. This assures that 
if our DA is later selected from the application’s apple menu, our current 
active window will be brought to the foreground.}
procedure DoActivate (var Evt: eventrecord);
var
 active: boolean;
 theWindow: Windowptr;
 kind: integer;
begin
 active := BitAnd(Evt.modifiers, activeFlag) <> 0;
 if active then
  begin
   theWindow := pointer(Evt.message);
   kind := windowPeek(theWindow)^.windowkind;
   if kind <> DCE^.dctlRefNum then
    Exit(DoActivate);
   DCE^.dCtlWindow := theWindow;
   SetMenu(DAMenus);
  end
 else
  SetMenu(AppMenus);
end;

{------------------MetaKey------------------}
{FUNCTION MetaKey checks whether the cloverleaf key was pressed during 
keypress. If so, it gets the menu and item from DAMenuKey and passes 
the menu selection on to DoMenus. If the cloverleaf was not depressed, 
then MetaKey returns FALSE so that the caller knows to handle the keypress 
normally.}
function MetaKey(var Evt: eventrecord): boolean;
var
 aChr: char;
 Tangled: longint;
begin
 if BitAnd(cmdKey, Evt.modifiers) <> 0 then
  begin
   aChr:= Char(BitAnd(Evt.message, charCodeMask));
   Tangled:= DAMenuKey(aChr);
   DoMenus(HiWord(Tangled), LoWord(Tangled));
   MetaKey:= true
  end
 else
  MetaKey:= false;
end;

{--------------------DoIdle----------------------}
{PROCEDURE DoIdle gets called for null events. It does two things:
{ 1. It checks the cursor and changes it into an I-beam when the cursor 
is over the window’s text edit field. }
{ 2. It manufacturer’s a “dummy” null event and calls DialogSelect so 
that the text edit cursor gets flashed.}
{This is the place to do any other background processing.}
procedure DoIdle;
var
 theDlog: DialogPtr;
 itemHit: integer;
 event: EventRecord;
 dummy: boolean;
 aPt: point;
begin
 theDlog := DCE^.dctlWindow;

{First make the mouse into an I-Beam if we’re above the text field}
 GetMouse(aPt);
 with dialogPeek(theDlog)^ do
  begin
    if textH <> nil then
     if PtInRect(aPt, textH^^.viewRect) then
        SetCursor(GetCursor(IBeamCursor)^^)
      else
        initCursor
  end;

{Next call DialogSelect with a null event in order to blink the cursor}
 event.what := NullEvent;
 dummy := dialogSelect(event, theDlog, Itemhit)
end;

{------------------HandleEvents----------------}
{PROCEDURE HandleEvents is the main dispatcher for all events appertaining 
to our DA.}
procedure HandleEvents (var Evt: EventRecord);
var
 dummy: windowptr;
 itemHit: integer;
 WhichDialog: DialogPtr;
begin
 AdjustMenus;

{We do some pre-processing before calling DialogSelect}
 case Evt.what of
  ActivateEvt:
   DoActivate(Evt);
  KeyDown, AutoKey:
   if MetaKey(Evt) then
    Exit(HandleEvents);
  otherwise
 end;

{Here we call DialogSelect to do most of the housekeeping window tasks}
 if DialogSelect(Evt, whichDialog, Itemhit) then
  case itemHit of
   NewWindowButton:
    dummy := OpenAWindow;
   EraseButton:
    begin
     SelIText(whichDialog, EditText, 0, 10000);
     DlgDelete(WhichDialog);
    end;
   otherwise
  end;
end; {procedure HandleEvents}

{************************************************}
{**************** Main DA Routines **************}
{************************************************}

{ ================== CLOSE ======================}
function CLOSE: OSErr;
var
 i: integer;
begin
 Close := NoErr;

{If we don’t have any window, then we haven’t been opened and Close is 
being called}
{inappropriately.}
 if DCE^.dCtlWindow = nil then
  Exit(Close);

{Clean up after ourselves}
 with DCE^ do
  begin
   ModifyWindows(FrontWindow, CloseAWindow);
   dctlWindow := nil ;

   for i := 0 to WindowMenu do
    ReleaseResource(handle(OurMenus[i]));
   Disposhandle(SavedMenuList);
   dCtlMenu := 0;

  end;
end; {of function CLOSE}

{ ================ OPEN =====================}
function OPEN (DCTlE: DCtlPtr;
       IOPB: ParmBlkPtr): OsErr;
var
 aWind: WindowPeek;
begin
 open := NoErr;
 OurName := IOPB^.ioNamePtr^;
 with DCE^ do
  begin

   if dctlWindow = nil then

{The window is nil, so initialize and allocate everything!}
    begin
     if dCtlStorage = nil then
      begin
       Sysbeep(20);
       Open := OpenErr;
      end
     else
      begin
       dctlWindow := OpenAWindow;
       dCtlMenu := InitMenus;
      end
    end

   else
    begin
{If we get here, then we are already open.  We must bring all our windows 
forward. First bring forward all windows below our topmost DA window. 
This has the effect of bringing forward ALL our windows when the DA is 
selected without changing their relative order. Next select our topmost 
window to bring it to the front.  The topmost window is already stored 
in the DCE^.dctlWindow field.}

     aWind:= WindowPeek(dctlWindow)^.nextWindow;
     ModifyWindows(aWind, BringWindowForward);

 {Now select our topmost window}
     SelectWindow(dctlWindow);
    end;
  end; {of WITH clause}
end; {of OPEN}

{=================== CONTROL =================== }
function CONTROL (IOPB: ParmBlkPtr): OsErr;
const
 accGoodBye = -1;
type
 EventPtr = ^EventRecord;
var
 APort: GrafPtr;

begin {functon CONTROL}

 Control := NoErr;
 GetPort(aPort);
 SetPort(DCE^.dctlWindow);

{Dispatch for all the different control calls}
 case IOPB^.csCode of
  accEvent:
   HandleEvents(EventPtr(IOPB^.ioMisc)^);
  accCursor:
   DoIdle;
  accMenu:
   DoMenus(IOPB^.csParam[0], IOPB^.csParam[1]);
  accGoodbye:
   Control := Close;
  otherwise
 end; {of case statement}

 SetPort(APort);
end; {function CONTROL}

{******************************************}
{************MAIN FUNCTION*****************}
{******************************************}
{FUNCTION Main is the entry point for the DA. THINK Pascal will intercept 
the driver (OPEN/CLOSE/CONTROL) call and funnel it through this routine, 
along with a pointer to the device control entry, the parameter block, 
and a selector indicating which driver call was made. Pascal very nicely 
sets up a global storage block}
function MAIN (DCtlE: DCtlPtr;
       IOPB: ParmBlkPtr;
       driveCall: Integer): OSErr;
begin
{Need to call RememberA4 up front if our dialogs have user items or if 
we get called at interrupt time.  This code doesn’t use these features, 
but call it just in case.}
 RememberA4;
 Done := false;
 DCE := DCtlE;

 case driveCall of
  DriverOpen:
   Main := Open(DCtlE, IOPB);
  DriverControl:
   with DCE^ do
    begin
{Turn off further control calls while servicing this one in order to 
avoid re-entrancy issues}
     dCtlFlags := BitAnd(dCtlFlags, $FBFF);
     Main := Control(IOPB);
     dCtlFlags := BitOr(dCtlFlags, $0400);
   end;
  DriverClose:
   if NumWindows > 1 then
{We have more than one window open, so just close topmost}
    begin
     CloseAWindow(FrontWindow);
     Main := CloseErr
    end
   else
    Main := Close;
  DriverStatus, DriverPrime:
   Main := NoErr;
 end; {case statement}

{This happens when the user selects “quit” from the menus or there is 
a fatal error.}
 if Done then
  begin
   Main := Close;
   CloseDeskAcc(DCE^.dctlRefnum);
  end
end; {of MAIN function}
end. {of the whole unit}

 
AAPL
$102.50
Apple Inc.
+0.25
MSFT
$45.43
Microsoft Corpora
+0.55
GOOG
$571.60
Google Inc.
+2.40

MacTech Search:
Community Search:

Software Updates via MacUpdate

Skype 6.19.0.450 - Voice-over-internet p...
Skype allows you to talk to friends, family and co-workers across the Internet without the inconvenience of long distance telephone charges. Using peer-to-peer data transmission technology, Skype... Read more
VueScan 9.4.41 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Cloud 3.0.0 - File sharing from your men...
Cloud is simple file sharing for the Mac. Drag a file from your Mac to the CloudApp icon in the menubar and we take care of the rest. A link to the file will automatically be copied to your clipboard... Read more
LibreOffice 4.3.1.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
SlingPlayer Plugin 3.3.20.505 - Browser...
SlingPlayer is the screen interface software that works hand-in-hand with the hardware inside the Slingbox to make your TV viewing experience just like that at home. It features an array of... Read more
Get Lyrical 3.8 - Auto-magically adds ly...
Get Lyrical auto-magically add lyrics to songs in iTunes. You can choose either a selection of tracks, or the current track. Or turn on "Active Tagging" to get lyrics for songs as you play them.... Read more
Viber 4.2.2 - Send messages and make cal...
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,... Read more
Cocktail 7.6 - General maintenance and o...
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
LaunchBar 6.1 - Powerful file/URL/email...
LaunchBar is an award-winning productivity utility that offers an amazingly intuitive and efficient way to search and access any kind of information stored on your computer or on the Web. It provides... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more

Latest Forum Discussions

See All

Rhonna Designs Magic (Photography)
Rhonna Designs Magic 1.0 Device: iOS Universal Category: Photography Price: $1.99, Version: 1.0 (iTunes) Description: Want to sprinkle *magic* on your photos? With RD Magic, you can add colors, filters, light leaks, bokeh, edges,... | Read more »
This Week at 148Apps: August 25-29, 2014
Shiny Happy App Reviews   | Read more »
Qube Kingdom – Tips, Tricks, Strategies,...
Qube Kingdom is a tower defense game from DeNA. You rally your troops – magicians, archers, knights, barbarians, and others – and fight against an evil menace looking to dominate your kingdom of tiny squares. Planning a war isn’t easy, so here are a... | Read more »
Qube Kingdom Review
Qube Kingdom Review By Nadia Oxford on August 29th, 2014 Our Rating: :: KIND OF A SQUARE KINGDOMUniversal App - Designed for iPhone and iPad Qube Kingdom has cute visuals, but it’s a pretty basic tower defense game at heart.   | Read more »
Fire in the Hole Review
Fire in the Hole Review By Rob Thomas on August 29th, 2014 Our Rating: :: WALK THE PLANKUniversal App - Designed for iPhone and iPad Seafoam’s Fire in the Hole looks like a bright, 8-bit throwback, but there’s not enough booty to... | Read more »
Alien Creeps TD is Now Available Worldwi...
Alien Creeps TD is Now Available Worldwide Posted by Ellis Spice on August 29th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Dodo Master Review
Dodo Master Review By Jordan Minor on August 29th, 2014 Our Rating: :: NEST EGGiPad Only App - Designed for the iPad Dodo Master is tough but fair, and that’s what makes it a joy to play.   | Read more »
Motorsport Manager Review
Motorsport Manager Review By Lee Hamlet on August 29th, 2014 Our Rating: :: MARVELOUS MANAGEMENTUniversal App - Designed for iPhone and iPad Despite its depth and sense of tactical freedom, Motorsport Manager is one of the most... | Read more »
Motorsport Manager – Beginner Tips, Tric...
The world of Motorsport management can be an unforgiving and merciless one, so to help with some of the stress that comes with running a successful race team, here are a few hints and tips to leave your opponents in the dust. | Read more »
CalPal Update Brings the App to 2.0, Add...
CalPal Update Brings the App to 2.0, Adds Lots of New Stuff Posted by Ellis Spice on August 29th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

Save up to $300 on an iMac with Apple refurbi...
The Apple Store has Apple Certified Refurbished iMacs available for up to $300 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. These are the best prices on... Read more
The Rise of Phablets
Carlisle & Gallagher Consulting Group, a businesses and technology consulting firm focused solely on the financial services industry, has released an infographic depicting the convergence of... Read more
Eddy – Cloud Music Player for iPhone/iPad Fre...
Ukraine based CapableBits announces the release of Eddy, its tiny, but smart and powerful cloud music player for iPhone and iPad that allows users to stream or download music directly from cloud... Read more
A&D Medical Launches Its WellnessConnecte...
For consumers and the healthcare providers and loved ones who care for them, A&D Medical, a leader in connected health and biometric measurement devices and services, has launched its... Read more
Anand Lal Shimpi Retires From AnandTech
Anand Lal Shimpi, whose AnandTech Website is famous for its meticulously detailed and thoroughgoing reviews and analysis, is packing it in. Lal Shimpi, who founded the tech site at age 14 in 1997,... Read more
2.5GHz Mac mini, Apple refurbished, in stock...
The Apple Store has Apple Certified Refurbished 2.5GHz Mac minis available for $509, $90 off MSRP. Apple’s one-year warranty is included, and shipping is free. Read more
13-inch 2.5GHz MacBook Pro on sale for $999,...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $999.99 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
Labor Day Weekend MacBook Pro sale; 15-inch m...
B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for up to $125 off MSRP. Shipping is free, and B&H charges NY sales tax only. They’ll also include free copies of Parallels Desktop... Read more
Labor Day Weekend iPad mini sale; $50 to $100...
Best Buy has the iPad mini with Retina Display (WiFi models) on sale for $50 off MSRP on their online store for Labor Day Weekend. Choose free shipping or free local store pick up. Price is for... Read more
13-inch 1.4GHz MacBook Air on sale for $899,...
Adorama has the new 2014 13″ 1.4GHz/128GB MacBook Air on sale for $899.99 including free shipping plus NY & NJ tax only. Their price is $100 off MSRP. Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.