MIDI User Interface
Volume Number: | | 5
|
Issue Number: | | 5
|
Column Tag: | | MIDI Connections
|
Related Info: List Manager Dialog Manager
MIDI User Interface
By Kirk Austin, San Rafael, CA
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
MIDI and the User Interface
This is an article that takes a look at the Macintoshs Dialog Box routines, and how they can be used effectively. The PopUp menus and ArrowEditText controls described here are an attempt at getting the greatest use out of limited space in a Dialog Box. Techniques like this are important in programs that make use of MIDI like sequencers and patch librarians, for example. Extensive use of dialogs can be found in these type of programs.
The significance of the user interface
This article is going to take a little bit of a different slant from the previous MIDI articles because I think it is important to talk about the human interface considerations for a bit. It seems that the thing that separates good Macintosh programs from average ones is the way that the user interface is implemented. A program has to look and feel like a Macintosh program should. The best way to accomplish this is to try to follow the Apple guidelines for doing things, and only using alternate techniques when absolutely nothing else will work. Probably the first book that aspiring Macintosh programmers should read is Human Interfaced Guidelines published by Addison Wesley.
Dialogs in MIDI programs
Most of the things that take place in a MIDI program happen in Dialog Boxes. These structures have been made very simple to use by Apple, and they can lead the user through a great deal of setup information that would otherwise be difficult to deal with. Getting the most out of a particular dialog box can be a challenge, though. There is limited space and a lot of things to be accomplished, so what is the best way to communicate with the user while making the most efficient use of space? In addressing this problem, Ive found the pop-up menu idea to be very useful. It takes up very little space in a dialog box until it is selected. At that point it can display a large range of choices. Now, this can also be done with the list manager by displaying a scrolling list of choices, but that would take up more room than the pop-up menu solution. Another technique that I have made use of is what I call an Edit Text Arrow Control. I use this for the input of numbers. It can be used like a standard Edit Text Item, but, in addition, there is an up/down arrow control associated with the item that allows the user to select a number with the mouse instead of having to use the keyboard. In order to provide an example of these two techniques I have written a sample application that lets the user change the MIDI program number on a synthesizer. Well get to the application in a short time, but first we should talk about the Pop-up Menus and the Arrow Edit Text Control.
The RefCon field
Fortunately for us, Apple gave us the RefCon field of a window record. This is a longint that we can use however we want. I use this field to give each Dialog a unique number that can be used to calculate a number for the User Items in a Dialog. Both the Pop-Up Menus and the ArrowEditTextCtl use this number to identify themselves.
Figure 1. Example Dialog box
PopUp Menus
These routines are based on some samples distributed by Apple, and produce the standard Pop-Up Menus as described in the User Interface Guidelines. The way I have implemented them is closely related with the items in the dialog box that I created with the Resource Editor. This is because the PopUp menu is made up of two dialog items, a static text item and a user item. My routines expect the UserItem item Number to be one greater then the staticText item number. This works out to be a logical way to create the PopUp menu location from the Resource Editor. Just create the StaticText first, then create the UserItem. In addition to the items in the dialog box you have to define the menu as a resource with the Resource Editor. The important thing here is that the menu resource number has to be the UserItem item number plus the value in the Dialogs RefCon field (whew). I set the dialogs RefCon field to 200 in my program, and if I were going to use additional dialogs I could assign RefCon values of 300, 400, 500, etc. to them. If these numbering relationships are kept straight then it is pretty easy to create and use PopUp Menus. There are only two routines to use for all of the PopUp Menus in your application:
{1}
procedure DrawPopUp(TheDialog : DialogPtr; theItem : integer);
function PUMenuAction(theDialog : DialogPtr; PopUpItem : integer) : integer;
The DrawPopUp procedure is called by PUMenuAction and also by the Dialog Manager after you install it as your UserItem drawing proc. Otherwise, you never actually call this routine yourself. PUMenuAction is what you call when theres a mousedown in the UserItem that is associated with the particular PopUp menu. PUMenuAction returns the menu item number as its result.
The ArrowEditTextCtl
EditText items in dialog boxes are great! They are pretty easy to use, and provide a standard way for the user to enter information into the Mac. The only thing about them that bothers me sometimes is that they require the user to type in the information, thereby taking the hand off of the mouse. I wanted a way to change text in an EditText item that would work from the mouse, so I came up with the ArrowEditTextCtl. The ArrowEditTextCtl is a regular EditText item with a frame drawn around it. Along the right side of the frame are the up and down arrows that increment or decrement the number in the EditText item. The ArrowEditTextCtl is a structure that also requires two dialog items defined for it. There is an EditText box and a UserItem. The EditText item number must be one less that the UserItem item number. Some care must be taken when creating the EditText and UserItems. First create your EditText box (which must be 16 pixels high), then create the UserItem to be the same coordinates except -3 top, -3 left, +3 bottom, and +13 right. Once you do that the control will draw itself properly. There are two routines that are used to implement the ArrowEditTextCtls in a program:
{2}
procedure DrawArrowETCtl(TheDialog : DialogPtr: theItem : integer);
function ArrowCtlAction(TheDialog : DialogPtr; theItem : integer; limitLo
: integer; limitHi : integer) : integer;
All you have to do with the DrawArrowETCtl procedure is install it as the UserItems drawing proc. Then you call ArrowCtlAction in response to a mousedown in the UserItem. The way it is written this routine will return the integer that is selected by either using the up and down arrows, or typing it in the EditText box.
Using the MIDI library
The MIDI library is a set of routines that were described in the December issue of MacTutor, so I dont think we need to spend any more time on them here. All the program is doing as far as MIDI is concerned is sending a program change message, which is pretty trivial, really. [There are some updates to Kirk's MIDI library from the last published version. They are not printed in the article, but they are included on the source code disk for this issue. -ed]
The MIDI Ctls Application
The application is pretty simple, really. It just gives you a menu with one item in it. When you select the menu item a dialog box appears on the screen. This is the dialog that demonstrates the use of the PopUp menu and the ArrowEditTextCtl. The dialog box is created as not visible by the Resource Editor. This lets us do our GetNewDialog call and install all of the drawing procedures for the UserItems as well as set up the RefCon field of the dialog. After all of that is done we do a call to ShowWindow to make the dialog visible. This works out to be much faster than watching the dialog draw all of its items on the screen. The function MyDialogFilter is used to watch for the mouse clicks in our UserItems as well as check for keyboard input and making sure that a valid number gets typed into the EditText item. The rest of the program is your standard vanilla Macintosh application.
Listing: MIDICtls
{ Kirk Austin, 4/9/88 }
{This shows how to use a dialog box in a MIDI program}
PROGRAM ShellExample;
USES
ROM85, ColorQuickDraw, ColorMenuMgr, LSPMIDI;
{ Global Constants }
CONST
Null = ;
AppleMenuID = 1;
FileMenuID = 2;
EditMenuID = 3;
MIDIMenuID = 4;
PopMenuID = 206;{Item number plus the value in the Dialogs RefCon}
AboutID = 200;
MIDIDialog = 201;
MIDIDialogRefCon = 200;
{Items in our dialog box}
OKOutline = 8;
AEditText = 3;
ArrowETCtl = 4;
iPopPrompt = 5; {the Prompt staticText}
iPopUp = 6;{the Pop-up userItem}
ModemCheckBox = 9;
PrinterCheckBox = 10;
{ Global Types }
TYPE
MIDIPrgData = RECORD
MIDIChan : integer;
MIDIProg : integer;
ModemActive : boolean;
PrinterActive : boolean;
END;
MPDPtr = ^MIDIPrgData;
MPDHdle = ^MPDPtr;
{ Global Variables }
VAR
myMenus : ARRAY[AppleMenuID..MIDIMenuID] OF MenuHandle;
Done : Boolean; { true when user selects quit}
TextCursor : CursHandle; {handle to the text entry cursor}
ClockCursor : CursHandle; {handle to the waiting watch cursor}
PopMenuHdle : MenuHandle;
TheMPDPtr : MPDPtr;
TheMPDHdle : MPDHdle;
TheResHdle : handle;
TheResRefNum : integer;
TheType : integer;
TheHandle : Handle;
TheRect : Rect;
PROCEDURE ShowAbout;
VAR
theDlog : DialogPtr;
oldPort : GrafPtr;
BEGIN
GetPort(oldPort);
theDlog := GetNewDialog(AboutID, NIL, Pointer(-1));
SetPort(theDlog);
DrawDialog(theDlog);
WHILE NOT Button DO
SystemTask;
DisposDialog(theDlog);
SetPort(oldPort);
END;
PROCEDURE LaunchIt (mode : integer;
VAR fName : Str255);
{The compiler has just pushed a word for the mode, and a pointer to the
string}
INLINE
$204F,{movea.l a7,a0;(a0) is ptr to string, 4(a0) is mode}
$A9F2; {_Launch}
PROCEDURE DoXfer;
VAR
where : Point;
reply : SFReply;
vRef : integer;
thefName : Str255;
textType : SFTypeList;
BEGIN
where.h := 80;
where.v := 55;
textType[0] := APPL;
SFGetFile(where, Null, NIL, 1, textType, NIL, reply);
WITH reply DO
IF NOT good THEN
thefName := Null
ELSE
BEGIN
thefName := fName;
vRef := vRefNum
END;
IF thefName <> Null THEN
BEGIN
Done := true;
IF SetVol(NIL, vRef) = noErr THEN
LaunchIt(0, thefName)
END
END;
PROCEDURE DrawOKOutline (theDialog : DialogPtr;
theItem : INTEGER);
VAR
savePen : PenState;
BEGIN
GetPenState(savePen); {save the old pen state}
GetDItem(TheDialog, TheItem, TheType, TheHandle, TheRect); {get the
items rect}
PenSize(3, 3); {make the pen fatter}
InsetRect(TheRect, -4, -4);
FrameRoundRect(TheRect, 16, 16); {draw the ring}
SetPenState(savePen); {restore the pen state}
END; {DrawOKOutline}
{DrawPopUp procedure was made to be as general as possible}
{The main thing to remember is that it expects PopUpMenuID to}
{be TheItem + TheRefCon of the Dialog.Also,Prompt item number}
{must be 1 less than the Pop Up Menu item number}
PROCEDURE DrawPopUp (TheDialog : DialogPtr;
TheItem : integer);
CONST
{constants for positioning the default item within its box}
leftSlop = 13; {leave this much space on left of title}
rightSlop = 5; { this much on right}
botSlop = 5; { this much below baseline}
VAR
TheType : integer;
TheHandle : handle;
r : Rect;
TheString : Str255;
newWid, newLen, wid : INTEGER;
TheMenuHdle : MenuHandle;
TheMenuItem : integer;
i : integer;
TheChar : char;
TheRefCon : integer;
MenuItemsCount : integer;
BEGIN
{Get the menu that is associated with this Dialog Item (TheItem + TheRefCon)}
TheRefCon := LoWord(GetWRefCon(WindowPtr(TheDialog)));
TheMenuHdle := MenuHandle(GetResource(MENU, TheItem + TheRefCon));
{Now, figure out which menu item is the current selection by scanning
for a check mark}
MenuItemsCount := CountMItems(TheMenuHdle);
TheMenuItem := 0;
i := 1;
REPEAT
GetItemMark(TheMenuHdle, i, TheChar);
IF TheChar = char(CheckMark) THEN
TheMenuItem := i;
i := i + 1;
UNTIL (TheMenuItem <> 0) OR (i = MenuItemsCount + 1);
IF TheMenuItem = 0 THEN
BEGIN
SetItemMark(TheMenuHdle, 1, CHR(checkMark));
{check the first item}
TheMenuItem := 1;
END;
GetItem(TheMenuHdle, TheMenuItem, TheString);
{get currently-selected item}
GetDItem(TheDialog, TheItem, TheType, TheHandle, r); {set up the
rectangle}
WITH r DO
BEGIN
InsetRect(r, -1, -1); {make it a little bigger}
{Make sure title fits. Truncate it add an ellipses ( )}
{if it doesnt (by the way, is option-semicolon)}
wid := (right - left) - (leftSlop + rightSlop); {available string area}
newWid := StringWidth(TheString); {get current width}
IF newWid > wid THEN
BEGIN {doesnt fit - truncate it}
newLen := LENGTH(TheString);
{current length in characters}
wid := wid - CharWidth( );
{subtract width of ellipses}
REPEAT {until fits (or we run out of characters)}
{drop the last character and its width}
newWid := newWid- CharWidth(TheString[newLen]);
newLen := PRED(newLen);
UNTIL (newWid <= wid) OR (LENGTH(TheString) = 0);
{add the ellipses character}
newLen := SUCC(newLen); {one more char}
TheString[newLen] := ; {its the ellipses}
TheString[0] := CHR(newLen); {fix the length}
END;
{draw the box and its drop shadow}
FrameRect(r);
MoveTo(right, top + 2);
LineTo(right, bottom);
LineTo(left + 2, bottom);
{draw the string}
MoveTo(left + LeftSlop, bottom - BotSlop);
DrawString(TheString);
END;
END; {DrawPopUp}
FUNCTION PUMenuAction (TheDialog : DialogPtr;
PopUpItem : integer) : integer;
VAR
popLoc : Point;
newChoice : INTEGER;
chosen, ignoreLong : LongInt;
TheString : Str255;
TheItem : integer;
TheType : integer;
TheHandle : handle;
PromptRect : rect;
PopUpRect : rect;
TheMenuHdle : MenuHandle;
TheRefCon : integer;
TheMenuID : integer;
TheMenuItem : integer;
i : integer;
TheChar : char;
MenuItemsCount : integer;
BEGIN
PUMenuAction := 0;
{Get the menu that is associated with this Dialog Item (PopUpItem +
TheRefCon)}
TheRefCon := LoWord(GetWRefCon(WindowPtr(TheDialog)));
TheMenuID := PopUpItem + TheRefCon;
TheMenuHdle := MenuHandle(GetResource(MENU, PopUpItem + TheRefCon));
{Now, figure out which menu item is the current selection by scanning
for a check mark}
MenuItemsCount := CountMItems(TheMenuHdle);
TheMenuItem := 0;
i := 1;
REPEAT
GetItemMark(TheMenuHdle, i, TheChar);
IF TheChar = char(CheckMark) THEN
TheMenuItem := i;
i := i + 1;
UNTIL (TheMenuItem <> 0) OR (i = MenuItemsCount + 1);
{Call PopUpMenuSelect and let user drag around. Note that }
{(top,left) parameters to PopUpMenuSelect are our items, }
{converted to global coordinates.}
GetDItem(TheDialog, PopUpItem - 1, TheType, TheHandle, PromptRect);
GetDItem(TheDialog, PopUpItem, TheType, TheHandle, PopUpRect);
InvertRect(PromptRect); {hilight the prompt}
InsertMenu(TheMenuHdle, -1);{insert our menu in menu list}
PopLoc := PopUpRect.TopLeft; {copy our items topleft}
LocalToGlobal(PopLoc); {convert back to global coords}
CalcMenuSize(TheMenuHdle); {Work around Menu Mgr bug}
WITH popLoc DO
chosen := PopUpMenuSelect(TheMenuHdle, v, h, TheMenuItem);
InvertRect(PromptRect); {unhilight the prompt}
DeleteMenu(TheMenuID); {remove our menu from menu list}
{Was something chosen?}
IF chosen <> 0 THEN
BEGIN {yep, something was chosen}
newChoice := LoWord(chosen); {get chosen item number}
IF newChoice <> TheMenuItem THEN
BEGIN
{the user chose an item other than the current one}
SetItemMark(TheMenuHdle, TheMenuItem, ); {unmark the old choice}
SetItemMark(TheMenuHdle, newChoice, CHR(checkMark)); {mark the new choice}
PUMenuAction := newChoice;
{Draw the new title}
EraseRect(PopUpRect);
DrawPopUp(theDialog, iPopUp);
END; {if this choice was not the current choice}
END; {if something was chosen}
END;{of PUMenuAction}
{EditText number must be 1 less than ArrowUserItem number}
PROCEDURE DrawArrowETCtl (TheDialog : DialogPtr;
TheItem : integer);
VAR
theType : Integer;{ the type of dlog item }
theHandle : Handle; { Handle to the item }
theRect : Rect; { rect which encloses the item}
Height : Integer;
HalfHeight : integer;
ArrowRect : rect;
BEGIN
GetDItem(TheDialog, TheItem, theType, theHandle, theRect); {get handle
to control}
FrameRect(TheRect);
InsetRect(TheRect, 2, 2);
TheRect.right := TheRect.right - 10;
FrameRect(TheRect);
GetDItem(TheDialog, TheItem, theType, theHandle, theRect); {get handle
to control}
Height := TheRect.bottom - TheRect.top;
HalfHeight := Height DIV 2;
HalfHeight := TheRect.bottom - HalfHeight;
TheRect.left := TheRect.right - 11;
EraseRect(TheRect);
FrameRect(TheRect);
MoveTo(TheRect.left, HalfHeight);{draw bold center line}
LineTo(TheRect.right - 1, HalfHeight);
MoveTo(TheRect.left, HalfHeight - 1);
LineTo(TheRect.right - 1, HalfHeight - 1);
ArrowRect.top := TheRect.top + 4; {draw up arrow}
ArrowRect.bottom := HalfHeight - 2;
ArrowRect.left := TheRect.left + 3;
ArrowRect.right := TheRect.right - 3;
FillRect(ArrowRect, black);
MoveTo(ArrowRect.left - 1, ArrowRect.top + 1);
LineTo(ArrowRect.right, ArrowRect.top + 1);
MoveTo(ArrowRect.left + 1, ArrowRect.top - 1);
LineTo(ArrowRect.right - 2, ArrowRect.top - 1);
MoveTo(ArrowRect.left + 2, ArrowRect.top - 2);
LineTo(ArrowRect.left + 2, ArrowRect.top - 2);
ArrowRect.top := HalfHeight + 2;
ArrowRect.bottom := TheRect.bottom - 4;
ArrowRect.left := TheRect.left + 3;
ArrowRect.right := TheRect.right - 3;
FillRect(ArrowRect, black);
MoveTo(ArrowRect.left - 1, ArrowRect.bottom - 2);
LineTo(ArrowRect.right, ArrowRect.bottom - 2);
MoveTo(ArrowRect.left + 1, ArrowRect.bottom);
LineTo(ArrowRect.right - 2, ArrowRect.bottom);
MoveTo(ArrowRect.left + 2, ArrowRect.bottom + 1);
LineTo(ArrowRect.left + 2, ArrowRect.bottom + 1);
END;
FUNCTION ArrowCtlAction (TheDialog : DialogPtr;
TheItem : integer;
LimitLo : integer;
LimitHi : integer) : integer;
VAR
theType : Integer;
theHandle : Handle;
theRect : Rect;
TheNum : longint;
TheString : Str255;
Height : Integer;
HalfHeight : integer;
ArrowRect : rect;
ThePoint : point;
Inverted : boolean;
HowLong : longint;
TickResult : longint;
UpArrowRect : rect;
DnArrowRect : rect;
TheTEHandle : handle;
GoingUp : boolean;
PROCEDURE BtnDelay (ticks : integer);
VAR
dummy : longint;
i : integer;
BEGIN
i := ticks;
IF ticks = 0 THEN
i := 1;
REPEAT
Delay(1, dummy);
i := i - 1;
UNTIL (i = 0) OR NOT button;
END;
BEGIN
GetDItem(TheDialog, TheItem - 1, theType, theHandle, theRect); {get
handle to control}
TheTEHandle := TheHandle;
GetIText(TheTEHandle, TheString);
StringToNum(TheString, TheNum);
ArrowCtlAction := loword(TheNum);
GetDItem(TheDialog, TheItem, theType, theHandle, theRect); {get handle
to control}
Height := TheRect.bottom - TheRect.top;
HalfHeight := Height DIV 2;
HalfHeight := TheRect.bottom - HalfHeight;
TheRect.left := TheRect.right - 11;
UpArrowRect := TheRect;
UpArrowRect.bottom := HalfHeight;
DnArrowRect := TheRect;
DnArrowRect.top := HalfHeight;
Inverted := false;
HowLong := 22;
GetMouse(ThePoint);
IF (PtInRect(ThePoint, TheRect)) AND Stilldown THEN {we need to hilite
an arrow}
BEGIN
IF PtInRect(ThePoint, UpArrowRect) THEN
BEGIN
ArrowRect := UpArrowRect;
GoingUp := true;
END
ELSE
BEGIN
ArrowRect := DnArrowRect;
GoingUp := false;
END;
REPEAT
GetMouse(ThePoint);
IF NOT PtInRect(ThePoint, ArrowRect) THEN
BEGIN
IF inverted THEN
BEGIN
TheRect := ArrowRect;
InsetRect(TheRect, 1, 1);
InvertRect(TheRect);
Inverted := false;
END;
END
ELSE
BEGIN
IF NOT Inverted THEN
BEGIN
TheRect := ArrowRect;
InsetRect(TheRect, 1, 1);
InvertRect(TheRect);
Inverted := true;
END;
GetIText(TheTEHandle, TheString);
StringToNum(TheString, TheNum);
IF GoingUp THEN
BEGIN
IF TheNum <> LimitHi THEN
BEGIN
TheNum := TheNum + 1;
IF TheNum > LimitHi THEN
TheNum := LimitHi;
ArrowCtlAction := loword(TheNum);
NumToString(TheNum, TheString);
SetIText(TheTEHandle, TheString);
SelIText(TheDialog, TheItem - 1, 0, 32767);
END;
END
ELSE
BEGIN
IF TheNum <> LimitLo THEN
BEGIN
TheNum := TheNum - 1;
IF TheNum < LimitLo THEN
TheNum := LimitLo;
ArrowCtlAction := loword(TheNum);
NumToString(TheNum, TheString);
SetIText(TheTEHandle, TheString);
SelIText(TheDialog, TheItem - 1, 0, 32767);
END;
END;
BtnDelay(HowLong);
IF HowLong > 3 THEN
HowLong := HowLong - 2;
END;
UNTIL NOT StillDown;
DrawArrowETCtl(TheDialog, TheItem);
END;
END; {ArrowCtlAction}
FUNCTION MyDialogFilter (theDialog : DialogPtr;
VAR theEvent : EventRecord;
VAR item : integer) : Boolean;
{function called by ModalDialog for every event that occurs}
{ while in control. It is used to filter events so you}
{ can do things when certain events occur. It is used to }
{ change cursor to an I beam when editing text. The routine }
{ also handles keyboard entry; limiting text input to numbers}
{ and making return & enter the same as clicking OK button }
CONST
CrCode = 13; {ASCII code for RETURN}
EnterCode = 3; {ASCII ccode for ENTER}
BsCode = 8; {ASCII code for Back Space}
TabCode = 9; {ASCII ccode for Tab}
VAR
mouseLocation : point; {holds coordinates of mouse loc.}
TheHandle : Handle; {used for dummy purpose here}
TheType : Integer; {used for dummy purpose here}
TheRect : Rect; {used for dummy purpose here}
TextBox1 : Rect;{defines area to test for cursor change}
TheString : Str255;
TheNum : longint;
TheDlogRecPtr : DialogPeek;
TheItem : integer;
TheChoice : integer;
TheMenuItem : integer;
i : integer;
TheChar : char;
TheMenuHdle : menuHandle;
ignorelong : longint;
BEGIN
MyDialogFilter := false; {let modalDialog handle event}
GetDItem(theDialog, AEditText, TheType, TheHandle, TextBox1); {chg cur
in area}
CASE theEvent.what OF
nullEvent : {nothing happening chk if cursor to change}
BEGIN
GetMouse(mouseLocation);
IF PtinRect(mouseLocation, TextBox1) THEN
SetCursor(TextCursor^^)
ELSE
SetCursor(arrow);
GetIText(TheHandle, TheString); {see if someone typed in an invalid
number}
StringToNum(TheString, TheNum);
IF TheNum > 128 THEN
BEGIN
SysBeep(10);
TheNum := 128;
NumToString(TheNum, TheString);
SetIText(TheHandle, TheString);
SelIText(TheDialog, AEditText, 0, 32767);
{hilite the editable text}
END;
IF TheNum < 1 THEN
BEGIN
SysBeep(10);
TheNum := 1;
NumToString(TheNum, TheString);
SetIText(TheHandle, TheString);
SelIText(TheDialog, AEditText, 0, 32767);
{hilite the editable text}
END;
END;
mouseDown :
BEGIN {Click!}
mouseLocation := theEvent.where;
{copy the mouse position}
GlobalToLocal(mouseLocation); {convert to local coordinates}
{Was the click in a user item?}
IF (FindDItem(theDialog, mouseLocation) + 1) = 4 THEN
BEGIN
TheMPDPtr^.MIDIProg := ArrowCtlAction(TheDialog, 4, 1, 128);
END; {if clicked in ArrowEditTextCtl userItem}
IF (FindDItem(theDialog, mouseLocation) + 1) = iPopUp THEN
BEGIN {Clicked in the pop-up box}
TheChoice := PUMenuAction(TheDialog, iPopUp);
IF TheChoice <> 0 THEN
BEGIN
MyDialogFilter := TRUE; {dialog is over}
TheMPDPtr^.MIDIChan := TheChoice;
item := iPopUp; {have ModalDialog return that the user changed items}
END;
END; {if clicked in our userItem}
END; {mousedown case}
keyDown, autokey : {to follow std. procedure, chk if RETURN or ENTER
was pressed}
BEGIN
IF (theEvent.message MOD 256) IN [crCode, enterCode] THEN
BEGIN
GetDItem(theDialog, 1, TheType, TheHandle, TheRect);
HiLiteControl(ControlHandle(TheHandle), 1);
{hilite the OK button}
Delay(3, ignoreLong);
HiliteControl(ControlHandle(TheHandle), 0);
MyDialogFilter := true;
Item := 1;
END
ELSE IF (theEvent.message MOD 256) IN [bsCode, tabCode] THEN
BEGIN
END
ELSE IF (Char(theEvent.message MOD 256) >= 0) AND (Char(theEvent.message
MOD 256) <= 9) THEN
BEGIN
TheDlogRecPtr := DialogPeek(theDialog);
TheItem := TheDlogRecPtr^.editField + 1;
{ find out which EditText Item we are in }
GetDItem(theDialog, TheItem, TheType, TheHandle, TheRect);
GetIText(TheHandle, TheString);
IF (Length(TheString) > 2) THEN
SetIText(TheHandle, Null);
{ set it to Null if there are more than 3 characters }
END
ELSE
MyDialogFilter := true;
END;
OTHERWISE
;
END; {of the CASE statment}
END;
PROCEDURE SendMIDI;
VAR
dummy : longint;
BEGIN
IF TheMPDPtr^.ModemActive THEN
BEGIN
InitSCCA;
TXMIDIA(TheMPDPtr^.MIDIChan + 191);
TXMIDIA(TheMPDPtr^.MIDIProg - 1);
Delay(1, dummy);
ResetSCCA;
END;
IF TheMPDPtr^.PrinterActive THEN
BEGIN
InitSCCB;
TXMIDIB(TheMPDPtr^.MIDIChan + 191);
TXMIDIB(TheMPDPtr^.MIDIProg - 1);
Delay(1, dummy);
ResetSCCB;
END;
END; {of SendMIDI}
PROCEDURE ProcessMenu (codeWord : Longint);{ menu selec}
VAR
menuNum : Integer;
itemNum : Integer;
NameHolder : str255;
dummy : Integer;
yuck : boolean;
oldPort : GrafPtr;
aDialog : DialogPtr;
ItemHit : integer;
TheItemHandle : handle;
TheItemType : integer; {type of the selected item}
TheItemRect : rect; {bounding box of the selected item}
TheNum : longint;
TheString : Str255;
BEGIN
IF codeWord <> 0 THEN { nothing was selected}
BEGIN
menuNum := HiWord(codeWord);
itemNum := LoWord(codeWord);
CASE menuNum OF { the different menus}
AppleMenuID :
BEGIN
IF itemNum < 3 THEN
BEGIN
ShowAbout;
END
ELSE
BEGIN
GetItem(myMenus[AppleMenuID], itemNum, NameHolder);
dummy := OpenDeskAcc(NameHolder);
END;
END;
FileMenuID :
BEGIN
CASE ItemNum OF
1 :
BEGIN
DoXfer;
END;
2 :
BEGIN
Done := true;
END;
END;
END;
EditMenuID :
BEGIN
yuck := SystemEdit(itemNum - 1);
END;
MIDIMenuID :
BEGIN
GetPort(oldPort);
{Get a menu}
PopMenuHdle := GetMenu(PopMenuID); {Create a menu (its title is ignored)}
SetItemMark(PopMenuHdle, TheMPDPtr^.MIDIChan, CHR(checkMark)); {check
it}
aDialog := GetNewDialog(MIDIDialog, NIL, WindowPtr(-1));
SetPort(aDialog);
SetWRefCon(WindowPtr(aDialog), MIDIDialogRefCon); {set the defaults}
GetDItem(aDialog, AEditText, theType, TheHandle, TheRect);
TheNum := TheMPDPtr^.MIDIProg;
NumToString(TheNum, TheString);
SetIText(TheHandle, TheString);
GetDItem(aDialog, ModemCheckBox, theType, TheHandle, TheRect);
IF TheMPDPtr^.ModemActive THEN
SetCtlValue(ControlHandle(TheHandle), 1);
GetDItem(aDialog, PrinterCheckBox, theType, TheHandle, TheRect);
IF TheMPDPtr^.PrinterActive THEN
SetCtlValue(ControlHandle(TheHandle), 1);
{Find out where our UserItems are, set their item handles to }
{a pointer to the drawing procedures}
GetDItem(aDialog, ArrowETCtl, theType, TheHandle, TheRect);
SetDItem(aDialog, ArrowETCtl, theType, @DrawArrowETCtl, TheRect);
GetDItem(aDialog, iPopUp, theType, TheHandle, TheRect);
SetDItem(aDialog, iPopUp, theType, @DrawPopUp, TheRect);
GetDItem(aDialog, OKOutline, theType, TheHandle, TheRect);
SetDItem(aDialog, OKOutline, theType, @DrawOKOutline, TheRect);
SelIText(aDialog, 3, 0, 32767);{hilite editable text}
ShowWindow(WindowPtr(aDialog)); {show window}
REPEAT
ModalDialog(@MyDialogFilter, ItemHit); {will process all events
while dialog is up}
CASE itemHit OF
1 :
BEGIN {this is the Send Button item}
SendMIDI;
END;
2 :
BEGIN {this is the Done Button item}
DisposDialog(aDialog);
SetPort(oldPort);
END;
iPopUp :
BEGIN {this is the PopUpMenu item}
SysBeep(1);
END;
ModemCheckBox :
BEGIN {this is the Modem Check Box item}
GetDItem(aDialog, ModemCheckBox, theType, TheHandle, TheRect);
IF TheMPDPtr^.ModemActive THEN
BEGIN
TheMPDPtr^.ModemActive := false;
SetCtlValue(ControlHandle(TheHandle), 0);
END
ELSE
BEGIN
TheMPDPtr^.ModemActive := true;
SetCtlValue(ControlHandle(TheHandle), 1);
END;
END;
PrinterCheckBox :
BEGIN {this is Printer Check Box item}
GetDItem(aDialog, PrinterCheckBox, theType, TheHandle, TheRect);
IF TheMPDPtr^.PrinterActive THEN
BEGIN
TheMPDPtr^.PrinterActive:= false;
SetCtlValue(ControlHandle(TheHandle), 0);
END
ELSE
BEGIN
TheMPDPtr^.PrinterActive := true;
SetCtlValue(ControlHandle(TheHandle), 1);
END;
END;
OTHERWISE
;
END; {case of item hit}
UNTIL ItemHit = 2;{Were done with Dialog}
END;
END;
HiliteMenu(0);
END;
END;
PROCEDURE DealWithMouseDowns (theEvent : EventRecord);
VAR
location : Integer;
windowPointedTo : WindowPtr;
mouseLoc : point;
windowLoc : integer;
VandH : Longint;
Height : Integer;
Width : Integer;
BEGIN
mouseLoc := theEvent.where;
windowLoc := FindWindow(mouseLoc, windowPointedTo);
CASE windowLoc OF
inMenuBar :
BEGIN
ProcessMenu(MenuSelect(mouseLoc));
END;
inSysWindow :
BEGIN
SystemClick(theEvent, windowPointedTo);
END;
inContent :
BEGIN
IF windowPointedTo <> FrontWindow THEN
BEGIN
SelectWindow(windowPointedTo);
END;
END;
OTHERWISE
BEGIN
END;
END;
END;
PROCEDURE DealWithKeyDowns (theEvent : EventRecord);
TYPE
Trick = PACKED RECORD
CASE boolean OF
true : (
long : Longint
);
false : (
chr3, chr2, chr1, chr0 : char
)
END;
VAR
CharCode : char;
TrickVar : Trick;
BEGIN
TrickVar.long := theEvent.message;
CharCode := TrickVar.chr0;
IF BitAnd(theEvent.modifiers, CmdKey) = CmdKey THEN {check for a menu
selection}
BEGIN
ProcessMenu(MenuKey(CharCode));
END
END;
PROCEDURE DealWithActivates (theEvent : EventRecord);
VAR
TargetWindow : WindowPtr;
BEGIN
TargetWindow := WindowPtr(theEvent.message);
IF Odd(theEvent.modifiers) THEN
BEGIN
SetPort(TargetWindow);
END
ELSE
BEGIN
END;
END;
PROCEDURE DealWithUpdates (theEvent : EventRecord);
VAR
UpDateWindow : WindowPtr;
tempPort : WindowPtr;
BEGIN
UpDateWindow := WindowPtr(theEvent.message);
GetPort(tempPort);
SetPort(UpDateWindow);
BeginUpDate(UpDateWindow);
EndUpDate(UpDateWindow);
SetPort(tempPort);
END;
PROCEDURE MainEventLoop;
VAR
Event : EventRecord;
ProcessIt : boolean;
x : byte;
BEGIN
REPEAT
SystemTask;
ProcessIt := GetNextEvent(everyEvent, Event); { get the next event in
queue}
IF ProcessIt THEN
BEGIN
CASE Event.what OF
mouseDown :
DealWithMouseDowns(Event);
AutoKey :
DealWithKeyDowns(Event);
KeyDown :
DealWithKeyDowns(Event);
ActivateEvt :
DealWithActivates(Event);
UpdateEvt :
DealWithUpdates(Event);
OTHERWISE
BEGIN
END;
END;
END;
UNTIL Done;
END;
PROCEDURE MakeMenus;{ get the menus & display them}
VAR
index : Integer;
BEGIN
FOR index := AppleMenuID TO MIDIMenuID DO
BEGIN
myMenus[index] := GetMenu(index);
InsertMenu(myMenus[index], 0);
END;
AddResMenu(myMenus[AppleMenuID], DRVR);
DrawMenuBar;
END;
{ Program Starts Here }
BEGIN
Done := false;
FlushEvents(everyEvent, 0);
{ initialize routines go here }
{get the cursors we use and lock them down - no clutter}
ClockCursor := GetCursor(watchCursor);
TextCursor := GetCursor(iBeamCursor);
HLock(Handle(ClockCursor));
HLock(Handle(TextCursor));
MakeMenus;
TheResHdle := GetResource(MIDI, 128);
HLock(TheResHdle);
TheMPDHdle := MPDHdle(TheResHdle);
TheMPDPtr := TheMPDHdle^;
InitCursor;
MainEventLoop;
ChangedResource(TheResHdle);
TheResRefNum := HomeResFile(TheResHdle);
UpdateResFile(TheResRefNum);
ReleaseResource(TheResHdle);
END.