From: jesjones@stein.u.washington.edu (Jesse Jones)
Newsgroups: comp.sys.mac.programmer
Subject: Disabling editBox items (long)
Date: 22 Jan 1993 03:59:27 GMT
Organization: University of Washington, Seattle
NNTP-Posting-Host: stein.u.washington.edu
Summary: Modula-2 code to disable editBox's.


   There have been several posts asking how to disable editBox's in the last
year or so. I had some code that did the trick for a dialog with one editBox,
but it was a bit of a hack involving a userItem and a hidden editBox. A few
days ago I modified another of the dialogs in my program so that it required
two editBox's to be disabled. Naturaly I took the opportunity to rewrite my
code in a more general fashion. 

   The new code requires that the dialog procedure call only two procedures,
works with any number of editBox's, can disable/enable any number of editBox's,
and doesn't require any modifications to the DITL list. The two required
procedures are listed below:

PROCEDURE EnableEditBox (dptr: DialogPtr; item: INTEGER; enable: BOOLEAN);

PROCEDURE GreyFilter (dptr: DialogPtr; VAR Event: EventRecord; 
	                         VAR item: INTEGER): BOOLEAN;
                             
EnableEditBox should be called once at the start of the dialog procedure to
either disable the item or enable it (it may have been disabled the last
time the dialog procedure was called). The GreyFilter is a filter proc passed 
to ModalDialog. It's used to grey out the editBox's when an update event occurs.
You can, of course, use your own filter proc as long as you also call 
GreyFilter. And that's all there is to using the code! 

   The code itself is a bit more complex. To keep track of the disabled 
editBox's I place a handle to an array of booleans in the dialog's window refCon.
The size of the array is determined by the number of items in the dialog. If an 
element in the array is TRUE then the editBox with corresponding item number is 
disabled. The type declaration for the array is shown below.

TYPE
    GreyHand = POINTER TO GreyPtr;
    GreyPtr  = POINTER TO GreyList;
    GreyList = ARRAY [1..1000] OF BOOLEAN;        
    
   When the EnableEditBox procedure disables an editBox it first checks to see
if the editBox is the currently selected editBox. If it is selected the below
procedure is called to find another editBox to select.

PROCEDURE GetAnotherEditBox (dptr: DialogPtr; item: INTEGER): INTEGER;
    VAR
        peek : DialogPeek;
        index: INTEGER;
        type : INTEGER;
        data : HANDLE;
        box  : Rect;
BEGIN
    peek := DialogPeek(dptr);
    FOR index := 1 TO INT(peek^.items^^)+1 DO
        GetDItem(dptr, index, type, data, box);
        type := And(type, 127);                (* mask out itemDisable flag *)
        IF (type = editText) AND (index <> item) THEN RETURN index END;
    END;
    RETURN 0;                                    (* return 0 if can't find an editBox *)
END GetAnotherEditBox;

   And now we come to the central procedure, EnableEditBox. This procedure does 
the following:
    1) Init the array of disabled editBox's. NewHandleClear is used so that all
        the of the elements start out FALSE.
    2) GetDItem is called to find the type for the item. The itemDisable flag
        is then masked out so that we can use a simple equality test with editBox.
    3) To disable an editBox it is first changed to a staticText item. This 
        prevents the user from selecting or tabbing to the disabled editBox. If
        the editBox is the currently selected editBox we either select a different
        editBox or tell the Dialog Manager that there are no longer any editBox's 
        by setting editField to -1.
    4) Enabling an editBox is simpler: its type is set back to editText and its
        text is selected (which resets editField). Both enable and disable 
        invalidate the items rectangle so that the Dialog Manager and GreyFilter
        can redraw the item.

PROCEDURE EnableEditBox (dptr: DialogPtr; item: INTEGER; enable: BOOLEAN);
    VAR
        type : INTEGER;
        new  : INTEGER;
        data : HANDLE;
        box  : Rect;
        list : GreyHand;
        peek : DialogPeek;
        sPort: GrafPtr;
BEGIN
    GetPort(sPort);
    SetPort(dptr);
    peek := DialogPeek(dptr);
    list := GetWRefCon(dptr);
    IF list = NIL THEN                        (* create the array *)
        list := NewHandleClear(LONG(peek^.items^^)+1);    
        SetWRefCon(dptr, list);
    END;
    GetDItem(dptr, item, type, data, box);    (* get the item type *)
    type := And(type, $7F);                   (* remove itemDisable flag *)
    IF (enable = FALSE) AND (type = editText) THEN     
        SetDItem(dptr, item, statText+itemDisable, data, box);    
        IF peek^.editField = item-1 THEN      (* is it the selected editBox? *)
            new := GetAnotherEditBox(dptr, item);        
            IF item > 0 THEN
                SelIText(dptr, new, 0, 20);   (* select a different editBox *)        
            ELSE                                                        
                SelIText(dptr, item, 0, 0);                    
                peek^.editField := -1;        (* there are no more editBox's! *)        
            END;                                                        
        END;
        InvalRect(box);                                            
        list^^[item] := TRUE;                                    
    ELSIF enable AND list^^[item] THEN                            
        SetDItem(dptr, item, editText, data, box);        
        SelIText(dptr, item, 0, 20);                        
        InvalRect(box);                                            
        list^^[item] := FALSE;                                    
    END;
    SetPort(sPort);
END EnableEditBox;

    GreyFilter calls the below procedure to grey out the text in a disabled
editBox. GetBounds is a utility procedure I wrote to get the bounds for
a dialog item. You can just as easily use GetDItem.

PROCEDURE GreyEditBox (dptr: DialogPtr; item: INTEGER);
    VAR
        box  : Rect;
        sPen : PenState;
        sPort: GrafPtr;
BEGIN    
    GetPort(sPort);
    SetPort(dptr);
    GetPenState(sPen);
    box := GetBounds(dptr, item);
    InsetRect(box, -3, -3);        (* grey out the text *)
    PenPat(gray);
    PenMode(patBic);
    PaintRect(box);
    PenPat(black);                    (* draw the frame *)
    PenMode(patCopy);
    FrameRect(box); 
    SetPenState(sPen);
    SetPort(sPort);
END GreyEditBox;

    All of my dialogs use a filter proc called DefaultFilter. If the dialog
needs to do some extra filtering it uses a different filter proc that first
calls DefaultFilter. GreyFilter is no exception: it calls DefaultFilter and
takes action only when DefaultFilter returns FALSE.
    The DefaultFilter is used to keep my dialogs consistent: Return/Enter always
work like a click on the OK button, and Escape/Tilde work like a click on the
Cancel button. Even more important, DefaultFilter updates the windows in my
application. If this isn't done its possible that applications in the background
will get no time while the dialog is up (see TechNote 304 for details). 

PROCEDURE DefaultFilter (dptr: DialogPtr; VAR Event: EventRecord; 
                                 VAR item: INTEGER): BOOLEAN;
    VAR 
        ch, key: CHAR;
        wptr   : WindowPtr;
        control: ControlHandle;
        button : INTEGER;
        handled: BOOLEAN;
BEGIN
    handled := FALSE;
    IF Event.what = keyDown THEN
        ch := Event.msgChars[3];
        key := Event.msgChars[2];
        IF (key = ReturnKey) OR (key = EnterKey) THEN 
            button := 1;
            handled := TRUE;
        ELSIF (key = TildeKey) OR (key = EscapeKey) THEN 
            button := 2;
            handled := TRUE;
        ELSIF (cmd IN Event.modiFlags) AND (ch = '.') THEN 
            button := 2;
            handled := TRUE;
        END;
        IF handled THEN                    (* make sure the button is enabled *)
            control := GetControl(dptr, button);
            IF control^^.contrlHilite = 0 THEN item := button END;
        END;
    ELSIF Event.what = updateEvt THEN
        wptr := Event.message;
        IF wptr <> dptr THEN
            WindEvent(Event);                        (* update my windows *)
        END;
    END;
    RETURN handled;
END DefaultFilter;

   The GreyFilter takes care of greying out all of the disabled editBox's. This
is a simple matter except for one minor detail: filter procs are called before
the Dialog Manager takes any action. So, it's not sufficient to just grey out 
the editBox when we get an updateEvt. What we would have to do is first draw
the text in the editBx, grey it out, and then validate the editBox so the
Dialog Manager doesn't overwrite all of our work.
   The GreyFilter procedure takes a simpler approach. Update events are handled
normally by the Dialog Manager, but when an update event occurs GreyFilter adds
a special key to the Event queue to signal that an update occured. After the
Dialog Manager processes the update event it will call GreyFilter again with
the special key so that the editBox's may be greyed out.

PROCEDURE GreyFilter (dptr: DialogPtr; VAR Event: EventRecord; 
                             VAR item: INTEGER): BOOLEAN;
    VAR 
        list   : GreyHand;
        peek   : DialogPeek;
        index  : INTEGER;
        handled: BOOLEAN;
        err    : OSErr;
BEGIN
    handled := DefaultFilter(dptr, Event, item);
    IF NOT handled THEN
        IF Event.what = updateEvt THEN        
            err := PostEvent(keyDown, 0);        
        ELSIF Event.what = keyDown THEN
            IF LoWord(Event.message) = 0 THEN
                list := GetWRefCon(dptr);        
                peek := DialogPeek(dptr);
                IF list <> NIL THEN
                    FOR index := 1 TO INT(peek^.items^^)+1 DO
                        IF list^^[index] THEN GreyEditBox(dptr, index) END;
                    END;
                END;
                handled := TRUE;                            
            END;
        END;                                
    END;
    RETURN handled;
END GreyFilter;

  --Jesse Jones
  
jesjones@u.washington.edu
Newsgroups: comp.sys.mac.programmer