[comp.sys.mac.programmer] Scheme to manage a "windows" menu

zben@umd5.umd.edu (Ben Cranston) (03/09/90)

Back in August of 1989 there was a discussion here on methods for implementing
a "Windows" menu, one with an entry for each window currently displayed by the
application.  The act of selection would bring that window to the fore, etc.

David Phillip Oster asked: "what happens if you have multiple files open,
all with the same name?" and then suggested "perhaps the simplest solution is
to just use the item position in the windows menu, rather than the title,
to determine which window the user wishes to select -- a problem with this
is that you can get identical menu items, but at least the program can
distinguish among them, if not the user."

This posting describes a simple scheme to implement this paradigm.
Please feel free to use your "junk" key if you are not interested.


The scheme comprises two parts:

1. A linked list of the windows, sorted by the window names, is kept from a
   global cell through a window pointer variable in each window data area.
   Note: this list is in addition to the Window Manager's list.

2. A small integer in the window data area holds the index in the menu
   of that window's item.  I called it MRefNum ("Menu Reference Number),
   for lack of a better name.

The management algorithms are:

When a new window is created, the proper place in the linked list must be
found.  We scan the linked list until we find either the end of the list or
a window whose name is lexically greater than that of our new window.  We
are then interested in the item BEFORE that position which is either the
root or a window whose name is lexically less than the new name.

The MRefNum of this preceeding entry determines the position in the menu
that the new window's name is inserted.  It is also incremented and becomes
the MRefNum of the new window.  The new window is inserted into the chain at
this point, and all succeeding windows in the chain have their MRefNum's
incremented, corresponding to the fact that their menu entries were pushed
down by the insert of the new window name.

If the previous item was the root, the new MRefNum becomes 1, and the same
things are done.

When a window is destroyed, the menu item corresponding to its MRefNum is
deleted, the window is removed from the chain, and all succeeding windows
in the chain have their MRefNum's decremented.

I did have an algorithm for changing the name, which broke down into two
possibilities corresponding to moving a name UP in the chain (incrementing
the MRefNums between the new and old position) or moving a name DOWN in the
chain (decrementing the MRefNums between the old and new position) but in
the actual program I deleted and re-inserted the window :-)

Getting from a window to it's menu item is easy, as the MRefNum designates
the appropriate menu index.  Getting from a menu item to the window is just
searching for the appropriate MRefNum value.

Here's the code for creating a window:

/* Insert a window into the proper point in the window chain.
 * Also makes appropriate entry in the window menu.
 */

InsertWind(RWPtr newwind)
{
    RWPtr   lastw = nil;
    RWPtr   nextw = AFWind;  /* the root */
    short   refn;

    while ( (nil!=nextw) && (0<CompHand(newwind->wname,nextw->wname)) ) {
	lastw = nextw;
	nextw = nextw->next;
    }

    refn = (nil!=lastw)?lastw->mrefn:0;
    newwind->mrefn = refn+1;
    newwind->next = nextw;

    if (lastw!=nil)
	lastw->next = newwind;
    else
	AFWind = newwind;

    InsMenuItem(WMenu,"\pa",WMBASE+refn);
    SetItem(WMenu,WMBASE+newwind->mrefn,*newwind->wname);

    while (nil != nextw) {
	nextw->mrefn++;
	nextw = nextw->next;
    }

}

Here's the code for destroying the window:

/* This routine removes a window from the window chain.
 * It also removes the appropriate window menu entry.
 */

RemoveWind(RWPtr mortwind)
{
    RWPtr   lastw = nil;
    RWPtr   nextw = AFWind;  /* the root */

    while ( (nil!=nextw) && (nextw!=mortwind) ) {
	lastw = nextw;
	nextw = nextw->next;
    }

    if (nil!=nextw) {
	if (nil!=lastw)
	    lastw->next = nextw->next;
	else
	    AFWind = nextw->next;
	DelMenuItem(WMenu,WMBASE+mortwind->mrefn);
	while (nil!=nextw) {
	    nextw->mrefn--;
	    nextw = nextw->next;
	}
    } else
	SysBeep(30);
}

Here's some code (case WIMENU) that goes from the menu item to the window.

/* Process selection from menu.
 * Apple menu:
 * 	Note: MUST do DA stuff so MF can switch from Apple menu...
 * 	Note: no "about" box is implemented.
 * FILE menu:
 *	...
 * SHOW menu:
 *	Flip view bits or change displayed resource type.
 * WIND menu:
 *	Iconize/deiconize or bring window to front.
 */

DoMenu(int menuparam)
{
    char       daname[256];
    int        menuitem = LoWord(menuparam);
    WindowPtr  windp = FrontWindow();
    RWPtr      mywind = windp;
    short      wkind = WindowKind(windp);

    switch ( HiWord(menuparam) ) {
    case APMENU:
	if ( menuitem == 1 )
	    SysBeep(30);
	else {
	    GetItem(AMenu,menuitem,daname);
	    OpenDeskAcc(daname);
	    AdjustMenus();
	}
	break;
    case EDMENU:
	SystemEdit(menuitem-1);
	break;
    case FIMENU:
	switch (menuitem) {
	case FMOPEN:
	    OpenWindow();
	    break;
	case FMCLOS:
	    if (wkind < 0)
		CloseDeskAcc(wkind);
	    else
		KillWindow(windp);
	    break;
	case FMPSET:
	    PrintSetup(windp);
	    break;
	case FMPRNT:
	    PrintWindow(windp);
	    break;
	case FMQUIT:
	    ExitToShell();
	}
	break;
    case SHMENU:
	if (nil != windp) {
	    if (menuitem >= SMBASE) {
		OpenType = menuitem-SMBASE;
		SetWindType(windp,OpenType);
	    } else switch (menuitem) {
	    case SMRNUM:
		mywind->wview.show.rnum = mywind->wview.show.rnum ^ 1;
		break;
	    case SMRNAME:
		mywind->wview.show.rname = mywind->wview.show.rname ^ 1;
		break;
	    case SMMASK:
		mywind->wview.show.mask = mywind->wview.show.mask ^ 1;
	    }
	    PostWindowSize(mywind);
	    SetPort(windp);
	    InvalRect(&windp->portRect);
	    AdjustMenus();
	}
	break;
    case WIMENU:
	if (menuitem<WMBASE) {
	    if (nil != windp) {
		switch (menuitem) {
		case WMICZE:
		    if (mywind->wview.show.icize)
			DeIconizeWindow(mywind);
		    else
			IconizeWindow(mywind);
		}
		SetPort(windp);
		InvalRect(&windp->portRect);
		AdjustMenus();
	    }
	} else {
	    for ( mywind=AFWind ; mywind!=nil ; mywind=mywind->next)
		if (WMBASE+mywind->mrefn == menuitem)
		    SelectWindow( (WindowPtr) mywind);
	}

    }
    HiliteMenu(0);
}

Here's some code (last for loop in this proc) that gets from window to menu:

/* Set checkmarks of view and window menus according to mode of front window.
 * There are three cases:
 * If FrontWindow() is nil then there are no windows open (degenerate case).
 * If FrontWindow() is not nil but windowKind is negative then the window is
 *  a Desk Accessory opened by (single) Finder or a DA opened by MultiFinder
 *  within our application heap (option launch).  In this case we want to
 *  make the EDIT menu active but not any of our own menus, since the window
 *  will not have the data items our code assumes are there.
 * If windowKind is positive we assume it is one of our own normal windows,
 *  since we do not have any modeless dialogs.
 */

AdjustMenus()
{
    WindowPtr  windp = FrontWindow();
    RWPtr      mywind = windp;
    short      indx;

    if (nil == windp) {
	DisableItem(FMenu,FMCLOS);
	DisableItem(FMenu,FMPSET);
	DisableItem(FMenu,FMPRNT);
	DisableItem(EMenu,0);
	DisableItem(SMenu,0);
	CheckItem(SMenu,SMRNUM,false);
	CheckItem(SMenu,SMRNAME,false);
	CheckItem(SMenu,SMMASK,false);
	for (indx=0 ; indx<NRType ; indx++)
	    CheckItem(SMenu,SMBASE+indx,false);
	DisableItem(WMenu,0);
	CheckItem(WMenu,WMICZE,false);
    } else if ( WindowKind(windp) < 0 ) {
	EnableItem(FMenu,FMCLOS);
	DisableItem(FMenu,FMPSET);
	DisableItem(FMenu,FMPRNT);
	EnableItem(EMenu,0);
	DisableItem(SMenu,0);
	CheckItem(SMenu,SMRNUM,false);
	CheckItem(SMenu,SMRNAME,false);
	CheckItem(SMenu,SMMASK,false);
	for (indx=0 ; indx<NRType ; indx++)
	    CheckItem(SMenu,SMBASE+indx,false);
	DisableItem(WMenu,0);
	CheckItem(WMenu,WMICZE,false);
    } else {
	EnableItem(FMenu,FMCLOS);
	EnableItem(FMenu,FMPSET);
	EnableItem(FMenu,FMPRNT);
	DisableItem(EMenu,0);
	EnableItem(SMenu,0);
	CheckItem(SMenu,SMRNUM,mywind->wview.show.rnum);
	CheckItem(SMenu,SMRNAME,mywind->wview.show.rname);
	CheckItem(SMenu,SMMASK,mywind->wview.show.mask);
	for (indx=0 ; indx<NRType ; indx++)
	    CheckItem(SMenu,SMBASE+indx,indx==mywind->wdata.dtype);
	EnableItem(WMenu,0);
	CheckItem(WMenu,WMICZE,mywind->wview.show.icize);
    }

    for ( mywind=AFWind ; mywind!=nil ; mywind=mywind->next)
	CheckItem(WMenu,WMBASE+mywind->mrefn,( (RWPtr) windp == mywind ));
	
    DrawMenuBar();
}

These are subroutines used by the above:

/* Compare string handles
 */

CompHand(Handle s1,Handle s2)
{
    int ans;

    HLock(s1);
    HLock(s2);
    ans = IUCompString(*s1,*s2);
    HUnlock(s1);
    HUnlock(s2);
    return(ans);
}

/* Get the windowKind field from the window record.  This is used to
 * decide if a window belongs to the program, or if it is a desk
 * accessory called either from the old Finder environment or from
 * the MultiFinder environment with the option key down.
 */

WindowKind(WindowPtr windp)
{
    int wkind = 0;

    if (nil != windp)
	wkind = ((WindowPeek) windp)->windowKind;
    return(wkind);
}

Actually, it occurs to me that there is no need to explicitly store the
MRefNum at all, as it should be identical to the position of that window in
the list (that is, reading the list should return 1, 2, 3, etc).  Extension
to remove the MRefNum cell is left as an exercise to the reader...

-- 

"It's all about Power, it's all about Control
 All the rest is lies for the credulous"
-- Man-in-the-street interview in Romania one week after Ceaucescu execution.