[comp.sys.next] How do I make dynamically changing menus?

fellman@celece.ucsd.edu (Ronald Fellman) (07/13/90)

I am trying to dynamically change a submenu but cannot figure out how to
get the id for the submenu object that was created by Interface Builder.
MenuCells don't seem to keep a record of what menu they are in. Menu
objects don't seem to keep any records of submenus of items.  I must be
overlooping something.  Can anyone help??

Thanks,
-ron fellman
(e-mail : rfellman@ucsd.edu)

bruce@atncpc.UUCP (Bruce Henderson) (07/15/90)

In article <11858@sdcc6.ucsd.edu>, fellman@celece.ucsd.edu (Ronald Fellman) writes:
> I am trying to dynamically change a submenu but cannot figure out how to
> get the id for the submenu object that was created by Interface Builder.
> MenuCells don't seem to keep a record of what menu they are in. Menu
> objects don't seem to keep any records of submenus of items.  I must be
> overlooping something.  Can anyone help??
> 
> Thanks,
> -ron fellman
> (e-mail : rfellman@ucsd.edu)

bruce@atncpc.UUCP (Bruce Henderson) (07/15/90)

Sorry about the previous mail-blunder!
here is the naughty code segment

Ron,

Here is what I do:

in my -appDidInit method, I have a line that looks like this:
[self InitMainMenu:[NXApp mainMenu]];
This is a method that walks through the menu structure and allows me to grab 
various menu ID's that I find interesting, cache them, and set individual Cell's
updateAction so that I can enable them and disable them when approprate to 
the program's context.

It looks alot like this: (much of this code comes from Bruce Blumberg's DrawApp,
a wonderful guide on how to do lots of neat thigs.....)

/* these instance varibles are mentioned in the code fragment, but they are */
/* never defined, they belong to the application subClass that these methods */
/* belong to.... They hold the id's of the 3 submenus we want to do things to */

id submn1;
id submn2;
id submn3;

/* these are cell tags that I have set up in Interface Builder */
/* they correspond to items in the main menu that have sub */
/* menus attached to them                                                 */

#define         SubMenu1                1000
#define         SubMenu2                2000
#define         SubMenu3                3000


- initMainMenu: menu
/*
 * Sets the updateAction for every menu item which sends to the
 * First Responder (i.e. their target is nil).  When autoupdate is on,
 * every event will be followed by an update of each of the menu items
 * which is visible.  This keep all unavailable menu items dimmed out
 * so that the user knows what options are available at any given time.
 */ 
{
        int count;
        int celltag;
        const char *s;
        id matrix, cell;
        id matrixTarget, cellTarget;
        id activateMenu = nil;
  
        /* retreives the main menu's Matrix */
        matrix = [menu itemList];
        matrixTarget = [matrix target];

        /* walls therough the menu cells in the main menu */
        /* the cells that have sub menus have been modified */
        /* in Interface Builder to have cell tags that I have defined */
        /* above,  These will be used to tell if we have on of the */
        /* menus we want to cache               */

        count = [matrix cellCount];
        while (count--) 
                {
                        cell = [matrix cellAt:count :0];
                        cellTarget = [cell target];
                        celltag = [cell tag];

                        if (!matrixTarget && !cellTarget) 
                        {/*  means that the cell doesn't have a submenu */
                          [cell setUpdateAction:@selector(mainMenuUpdate:) forMenu:menu];
                        } 
                        else 
                           if ([cell hasSubmenu]) 
                              {
                                switch(celltag) /* tells us which submenu to deal with */
                                  { 
                                        case SubMenu1:
                                        /* dispatch to a method to do */
                                        /* initalization to the 1st sub- */
                                        /* menu, store the menu id in  */
                                        /* an instance variable     */
                                                submn1 = [self initSubMenu1 :cellTarget];
                                        break;
                                                                        
                                        case SubMenu2:
                                                submn2 = [self initSubMenu2 :cellTarget];
                                        break;
                                                                        
                                        case SubMenu3:
                                                submn3 = [self initSubMenu3 :cellTarget];
                                        break;
                                                                
                                        }
                                }
        }

    return self;
}
        
/* the next routine is an example of a method that sets up one of the sub menus, note the */
/* the setting of the menu cell's updateAction: to a selector of an application method  */
/* the menu cell will call this method, passing in itself (id) whenever it thinks it might */
/* need to be changed (enabled, disabled, change of text, etc)  This is MEGA handy! */

- initSubMenu1: menu
{
        int count;
        int celltag;
        const char *s;
        id matrix, cell;

        matrix = [menu itemList];
        matrixTarget = [matrix target];

        count = [matrix cellCount];
        while (count--) 
                {
                        cell = [matrix cellAt:count :0];

                        [cell setUpdateAction:@selector(subMenu1Update:) forMenu:menu];
                }
        return menu
}

/* now, still another application method.  This is an example of an update method  */
/* we are going to interigate the firstResponder to see if it know how to respond to */
/* the method we might want to pass it.  We are including the #defs for the menu        */
/* cell tags.... */

#define menuItem1       100
#define menuItem2       101
#define menuItem3       102
#define menuItem4       104


- (BOOL)subMenu1Update: menuCell
/*
 * Method called by all menu items which send their actions to the
 * First Responder.  First, if the object which would respond were the
 * action sent down the responder chain also responds to the message
 * validateCommand:, then it is sent validateCommand: to determine
 * whether that command is valid now, otherwise, if there is a responder
 * to the message, then it is assumed that the item is valid.
 * The method returns YES if the cell has changed its appearance (so that
 * the caller (a Menu) knows to redraw it).
 */
{
    SEL action;
    id responder, target;
    BOOL enable;

    target = [menuCell target];
    enable = [menuCell isEnabled];

    if (!target) 
        {
                action = [menuCell action];
                responder = [self calcTargetForAction:action];

                /* note, when you are unsure about the class of the target of a method */
                /* such as we are here, it is wise to ask the possible resonder if it can */
                /* take the method you are about to pass it.  This is one of the beautiful */
                /* things about Objective C,  because we have no idea what class this */
                /* validateCommand: may end up in.... */

                if ([responder respondsTo:@selector(validateCommand:)]) 
                        {       /* ask the first responder if this command is approprate at this time */
                                enable = [responder validateCommand:menuCell];
                        } 
                else 
                        {       /* does the responder have a (whatever) method ?: */
                                enable = responder ? YES : NO;
                        }
         }

    if ([menuCell isEnabled] != enable) 
        {       /* we changed the appearance.  Update the display */
                [menuCell setEnabled:enable];
                return YES;
         } 
    else 
        {       /* menu cell does not need to change appearance */
                return NO;
         }
}

Now,  enabling and disabling are not the only things you can do in one of these
update methods.  Take for instance this one:

- (BOOL)subMenu1Update: menuCell
{

        SEL             action;
        id              responder, target;
        BOOL            enable;
        BOOL            targState;
        BOOL            isAResponder;
        BOOL            changeName;
        int             celltag;
        char*           celltitle;
        char            targName[26];

        
        enable = [menuCell isEnabled];
        action = [menuCell action];
        responder = [self calcTargetForAction:action];
        celltitle = (char*)[menuCell title];
        celltag = [menuCell tag];
        isAResponder = (responder != NULL);

        switch(celltag)
                {
                        
                        case menuItem1:         
                                /* change a cell title... */
                                if( canDoItem1() )
                                        strcpy( targName, "Bake A Potato" );
                                else
                                        strcpy( targName, "Mash A Potato" );
                                targState = strcmp( targName, celltitle );                                      
                                break;                                          
                                                
                                        
                        case menuItem2:
                                                {
                                                        /* enable if there is an open window that responds to */
                                                        /* this cell's action */
                                                        if(enable != isAResponder)
                                                                {
                                                                        [menuCell setEnabled:isASheet];
                                                                        return YES;
                                                                }
                                                        else
                                                                return NO;
                                                }
                                break;          
                                                
                        case menuItem3:
                                        /* change title and enable/disable... */
                                        if((canDoItem3())&&(enable != isAResponder))
                                                {
                                                        if(!enable)
                                                                [menuCell setEnabled:TRUE];
                                                        if(item3State1())
                                                                sprintf(targName,"Idaho Potatoes");
                                                        else
                                                                if(! item3State2())
                                                                        sprintf(targName,"Georga Potatoes");
                                                                else
                                                                        sprintf(targName,"Irish Potatoes");
                                                                        
                                                        targState = (strcmp(targName, celltitle))||(!enable);
                                                }
                                        else
                                                if(enable)
                                                        {
                                                                [menuCell setEnabled:FALSE];
                                                                return YES;
                                                        }
                                                else return NO;
                                break;          
                                                
                                                
                        default:
                                        {
                                                if(enable != isAResponder)
                                                        {
                                                                [menuCell setEnabled:isASheet];
                                                                return YES;
                                                        }
                                                else
                                                        return NO;
                                        }
                                break;                          
                }

        if (targState) 
                {
                        [menuCell setTitle:targName]; /* set the title here */
                        targState = 0;
                        return YES;
        } 
        else 
                {
                        return NO;
                }

}

Got it??
Any Questions?

Bruce Henderson
Ashton-Tate NeXTeam
User Interface KGB