MIDI User Interface
Volume Number: 5
Issue Number: 5
Column Tag: MIDI Connections
By Kirk Austin, San Rafael, CA
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
MIDI and the User Interface
This is an article that takes a look at the Macintosh’s 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, I’ve 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. We’ll 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 Dialog’s RefCon field (whew). I set the dialog’s RefCon field to 200 in my
program, and if I were going to use additional dialog’s 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 there’s 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 UserItem’s. 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 ArrowEditTextCtl’s 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
UserItem’s 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 don’t 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 UserItem’s 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 UserItem’s 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 Dialog’s
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 item’s 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}