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.