Menu Manager Behavior
"Menu Manager" behavior
Each menu has one Menu Manager that resides/presides in the Score above the Menu Elements of the menu, just like a window's Window Manager resides/presides in the Score above its Window Elements. Menu Managers coordinate the behavior of menus. And, as with Window Managers, while Menu Managers reside in the Score above their own elements, they are (in the Score) below the elements and Managers of their ancestors.
Check out scenes 5, 6, and 7 in the demo for WFS menus.
The code for this behavior is, like the "3: Window Manager" code, relatively long, as scripts go. Note that you only need one copy of this behavior in your Cast.
Differences between menus and windows
The code for the Menu Manager is very similar to the code for the Window Manager. But menus behave a bit different from windows.
The main behavioral difference is that when you have a submenu open and you click away from it, not on it, the submenu closes. Submenus in applications do not normally have 'close' buttons on them, unlike windows and most dialog boxes. Menus are closed either as above or by making a selection from the menu, in WFS. Menus are designed for quick access to menu items and they get out of the way once you make a selection, unlike most windows.
Most applications display the root menu all the time (File Edit View...). WFS gives you this option. But you can also configure the root menu to close when the user makes a menu selection. In such case, the user can subsequently access the root menu if you attach a copy of the "Menu Verb" behavior or "7: Open a Window" behavior to a sprite configured to open the root menu.
Another behavioral difference between windows and menus is that when you open a submenu of menu X, all other open submenus opened by other menu items close. If we think of the menu system as a tree, then what is visible, at any particular point, contains no forks. In WFS, the "Menu Manager" behavior works in conjunction with the "Menu Verb" behavior to support this typical menu behavior.
The 'history' of the menu is instructive. In videos of demonstrations of some of the brilliant and famous Xerox Park innovations of the sixties and seventies in user interface design, instead of menus, there was an additional keypad (on the left side of the keyboard) that functioned as the menu system. Very quick and two-handed computing (left hand on menu system, right hand on mouse). Contemporary keyboard shortcuts, with their torturous ctrl+alt+shift+clickness, and so forth, do not compare in rapid ease of use. At the moment, we use menus, however, in the design of contemporary applications. They are easier to learn to use than the two-handed approach.
Keyboard shortcuts, however, are not supported. I thought about such support, but in addition to being time consuming to program, it would have eaten up CPU cycles to be monitoring keyboard activity. Also, Director developers often have their own plans for keyboard interactivity; I didn't want WFS keyboard shortcuts to get in the way.
Attach this behavior to the "Menu Manager bitmap". See documentation on the "Menu Manager bitmap" concerning positioning in the Score relative to Menu Elements.
The "Menu Manager" behavior contains an 'isOKToAttach' handler that requires the behavior to be attached to the "Menu Manager bitmap".
The "Menu Manager " Parameter Dialog Box
When you drag the "Menu Manager" behavior onto the "Menu Manager bitmap", the following dialog box opens. Click on a
particular item to go to documentation on it.
Names of multi-sprites should be unique across the entire movie. Names of multi-sprites can consist of any string of ASCII symbols with one exception: don't put any "+" symbols in names. They are used in a special way in dynamic multi-sprite names, which are automatically generated by WFS to ensure uniqueness regardless of how many dynamic copies of the multi-sprite are made.
Multi-sprites add their name and spriteNum to the global gWFSMultiSpriteList list of windows and menus currently instantiated. They also delete this entry on endSprite, ie, when they end.
WFS prompts you to rename a particular multi-sprite if there is a name conflict with another multi-sprite.
The way I name mine is as follows. Suppose the movie consists of scenes separate in the Score. The first multi-sprite in scene one is named "1.1". The second multi-sprite in scene one is named "1.2". The first multi-sprite in scene 2 is named "2.1". And so on. But, you know, whatever gets you through the movie so that they're named uniquely will be fine with WFS.
If you check this box, then the menu will be visible when it becomes instantiated in the Score without the user having to open it. If you don't check this box, then the menu will be invisible when it becomes instantiated.
Generally, you only check this box, if at all, when you configure the root menu of the menu system. Each menu system you construct has one and only one root menu. In applications, the root menu generally is "File Edit View...".
If you don't check this box, then for the root menu to become visible, you need to drop a copy of the "Menu Verb" behavior or the "7: Open a Window" behavior on some sprite or sprites you want to open the root menu.
Is this the root of the menu system?
Each menu system you construct must have one and only one root menu. In applications, the root menu generally is "File Edit View...".
The root menu must, in the Score, be above all its submenus.
Each submenu must have a parent, and the parent must be a menu. But the root menu can either have no parent, or have a window as its parent. Generally, in applications, the parent of the root menu is the window in which the root menu appears. In such case, the root menu and the submenus will maintain their position relative to the parent window when it moves.
If this is a root menu, close it when user selects from menu?
Check this box, if at all, only when you are configuring the root menu. It does not hurt to check it if you are not configuring the root menu, but neither does it have effect, in such case. It only matters concerning configuration of the root menu.
If this box is checked, then when the user selects a menu item, the whole menu system will be made invisible. If this box is unchecked, then all but the root menu will be made invisible when the user selects a menu item.
In some applications, you want the root menu visible all the time. In others, you don't.
If you check this box, then you will need to provide some means for the user to reopen the root menu. This is accomplished by dropping a copy of the "Menu Verb" behavior or "7: Open a Window" behavior on, typically, a Window Element that is part of the window in which the root menu appears.
Name the parent or leave blank.
You specify the name of the parent of the menu here.
The root menu of the system may have a window as parent or no parent at all. If the root menu has a window as its parent, then when the user moves the window around (if they can), then the menu system will maintain its position relative to the window parent. If it has no parent, then the menu system will stay put.
If you are configuring a non-root menu, then it must have a menu as parent. Submenus of the root menu should have the root menu as parent. Submenus of submenu X should have submenu X as parent.
A menu X cannot have menu X as parent. Nor can menu X have one of its
submenus as parent, nor one of the descendents of its submenus as parent.
If this sort of time-travel self-incest were possible, it would create
infinite loops when menus were closed, for instance. WFS will prompt
you to correct such things should you accidentally specify such sci-fi
OPENING, CLOSING, LOCZ
MOVING MENUS, FAMILIES, & ALIGNMENT
"3: Window Manager" API
"Menu Manager" Public Handlers (for Programmers)
The "Menu Manager" API is almost identical to the "3: Window Manager" API.
This section is for programmers who want to do things beyond what the WFS drag and drop behaviors permit you to do with windows and menus. The "Menu Manager" behavior contains the below public handlers that are useful for opening, closing and changing the locZ of menus. The public handlers also permit developers to move menus and also families of menus around and to align them.
The public handlers also provide handlers for setting parent-child relationships between Managers. These last handlers are the most powerful of the public handlers. Why? Because they go beyond the notion of windows and menus. WFS is actually not so much about windows and menus as multi-sprite objects. Being able to set parent-child relationships amongst multi-sprite objects allows you to combine multi-sprite objects to make larger objects.
All WFS handlers start with "wfs". This is to ensure no intrusions into your own programming name space. Just don't name your own handlers such that they start with "wfs" and you're guaranteed no handler name-space intrusion. All WFS globals start with "gWFS". All WFS properties start with "pWFS". So you are guaranteed no name-space intrusions in your globals and properties as long as they don't start with "gWFS" or "pWFS".
OPENING, CLOSING, AND LOCZ OF MENUS
This opens the menu whose Manager's spriteNum is the integer managerSpriteNum and brings it to front. This handler is called by any WFS behaviors that open menus. Make sure the menu is instantiated. This handler has a tiny (but important) bit more code in it than the same-named handler in the "3: Window Manager" behavior. Look at the source code for the difference if you are modifying WFS.
This brings the window to the front. It is called when the window is opened. It's also called when you click on a Window Element and you have the window configured to come to front when clicked. Even if you don't have it so configured, this handler is called when you click an Element that has the "6: Handle" behavior attached to it. This handler also brings the children to front--in front of the current window. The idea is that children stay in front of their parents. If you call this handler with a positive integer parameter for doNotBringChildrenToFront, the children will not be brought to front.
The above also brings the children to front.
The above brings the window to front but does not bring the children to front.
Menus are closed a bit different than windows are closed. You use the wfsCloseWindow handler to close a window but you use the wfsCloseMenuSystem handler to close a menu.
The Menu Manager Parameter Dialog Box contains an item that is labelled "If this is a root menu, close menu root when user selects from menu?" If this is checked then when you call wfsCloseMenuSystem, the entire menu system of which the current sprite is part will be made invisible. If the above mentioned check box is not checked, then when you call wfsCloseMenuSystem, all but the root menu of the current menu system will be made invisible.
This restores the LocZ of each sprite in the menu. It is called in the wfsCloseWindow handler.
MOVING MENUS, FAMILIES OF MENUS, AND ALIGNMENT
This handler moves the referenced menu to the center of the stage, and moves descendant menus (that are configured to be moved with their parents) accordingly, not necessarily centering them, but moving them so that their position relative to the centered menu is preserved.
This handler first calculates the current position of the menu. But what is that, since the menu is really just an aggragate of sprites with possibly different locations? So it is a bit arbitrary: it defines the position of the menu as the left (horizontal) and top (vertical) values of the first menu element (not the menu manager). Hence it is a good idea to make that menu element be the top leftmost element in the menu since, by convention, that is generally what we mean when we move something to a point. Note that the menu Manager sprite is not moved.
deltaPoint is a parameter of type Point. This handler moves the referenced menu and its descendent menus by deltaPoint.locH pixels horizontally and deltaPoint.locV pixels vertically. These can be negative or positive integers or 0. This handler is recursive; it moves descendants, not just children.
absolutePoint is a parameter of type Point. This handler moves the top left point of this menu to absolutePoint and moves all its descendent menus (that are configured to be moved with their parents) accordingly so that they are in the same position relative to the referenced menu. The top left point of this menu is considered to be the top left point of the second menu element of the menu. This handler is recursive; it moves descendants, not just children.
absolutePoint is a parameter of type Point. This handler moves this menu to the point specified by absolutePoint. This handler first calculates the current position of the menu. But what is that, since the menu is really just an aggragate of sprites with possibly different locations? So it is a bit arbitrary: it defines the position of the menu as the left (horizontal) and top (vertical) values of the first menu element (not the menu manager). Hence it is a good idea to make that menu element be the top leftmost element in the menu since, by convention, that is generally what we mean when we move something to a point. Note that the menu Manager sprite is not moved.
deltaPoint is a parameter of type Point. This handler moves the referenced menu and all its menu elements by deltaPoint.locH pixels horizontally and deltaPoint.locV pixels vertically. These can be negative integers as well as positive or 0. It doesn't move the menu Manager sprite.
elementSpriteNum is an integer parameter indicating the spritenum of the menu element to center horizontally. This handler checks where the regpoint is in elementSpritenum and centers the element horizontally relative to the menu's background, which is assumed to be the first menu element in the menu (after the menu Manager, which isn't considered to be an element of the menu).
elementSpriteNum is an integer parameter indicating the spritenum of the menu element to center vertically. This handler checks where the regpoint is in elementSpritenum and centers the element vertically relative to the menu's background, which is assumed to be the first menu element in the menu (after the menu Manager, which isn't considered to be an element of the menu).
wfsPositionElementLocVRelative (elementSpritenum, theLocV)
This handler positions the locv of elementSpritenum theLocv pixels below the menu's background, which is assumed to be the first menu element (below the Menu Manager in the Score).
wfsPositionElementLocVAbsolute (elementSpritenum, theLocV)
This handler positions the locV of elementSpritenum at theLocV.
This handler returns the name of the parent or "" if it has no parent.
This handler returns the spritenum of the parent or 0 if it has no parent.
wfsSetChild (theChild, moveTheChildWhenIMove)
Almost like people, a multi-sprite can have many children but, at most, one parent.
theChild parameter is an integer or string. If it is a string, it denotes the name of a multi-sprite. If it is an integer, it denotes the spriteNum of a manager of a multi-sprite.
The moveChildWhenIMove parameter is a boolean. If it is TRUE, then the child will move when the parent is moved. If it is FALSE, then the child will not be moved when the parent is moved.
wfsSetChild returns 1 (TRUE) if the operation was successful, and 0 (FALSE) if the operation was not successful and was cancelled. If wfsSetChild returns 0, that means that the child was identical to the parent (a child cannot be its own parent) or the child or the parent were not instantiated or theChild spriteNum <=0 or parentManagerSpritenum < 0. Setting parentManagerSpriteNum=0 is OK; it results in theChild having no parent. But it might be more intutive to use wfsDeleteChild for such an operation.
We see in the below diagram that a call to wfsSetChild is equivalent to a call to wfsSetParent, only with the parameters reversed. The moveChildWhenIMove parameter has been omitted to clarify the main point. The "I" in 'moveChildWhenIMove' refers to the parent.
A Manager is not allowed to be among its own ancestors. In other words, a Manager cannot be its own parent or grandparent etc. It would cause infinite loops when you move or close families of multi-sprites. The diagram below illustrates how wfsSetChild handles such a situation.
What we see in the above diagram is how the call sprite(G).wfsSetChild(B) is handled. It does indeed result in B becoming a child of G. But the parent of B is changed to be G (as you would expect) and, also, D is no longer a child of B. The relation between B and D has had to be severed, or B would become its own granddaddy. The above operation returns 1. The moveChildWhenIMove parameter is omitted from the above diagram to make the main points clearer.
This handler sets the child's data and also the parent's data, so that if you want to set a child, this is the only call you need to make.
Note that if the child already has a parent, this handler changes the parent. A child can have, at most, one parent though a parent can have multiple children.
See the included .DIR for example usage of wfsSetChild and wfsSetParent.
This handler deletes theChild from the Manager whose spriteNum is parentManagerSpriteNum (see below), ie, it makes the child no longer a child of the parent and sets the child so that it has no parent.
This handler returns 1 if the operation was successful, 0 if the operation was not successful and the operation was cancelled. It will return 0 if theChild is not instantiated or is not a child of the parent.
theChild can be a string or an integer. If it is an integer, it refers to the spriteNum of the Manager of the child. If it is a string, it refers to the name of the child.
This handler adjusts data in the child and in the parent Manager, so if you want to delete a child, this is the only call you need to make.
wfsSetParent (theParent, moveWhenParentMoves)
theParent is a string or integer. If it is a string, it is the name of the proposed parent window or menu. If it is an integer, it is the spriteNum of the proposed parent window or menu's Manager.
If you want to set the current window or menu to have no parent, then use theParent="" or theParent=0. Or call wfsDeleteChild.
MoveWhenParentMoves is a boolean indicating whether the current window should be dragged when its proposed new parent is moved.
A call to wfsSetParent is equivalent to a call to wfsSetChild, only the parameters are different. In other words, if you make window A the parent of window B, you also make window B the child of window A. Use whichever one you like. See the documentation (above) on wfsSetChild. It is more detailed than the documentation on wfsSetParent. In particular, look at the diagrams.
wfsSetParent returns 1 if the operation was successful, 0 if the operation was not successful and was cancelled. A Manager is not allowed to be one of its own ancestors. Make sure that the Manager with spritenum=managerSpriteNum and theParent are instantiated when you call this.
A Manager can have, at most, one parent. If the Manager with spritenum=managerSpritenum already has a parent, then wfsSetParent makes theParent the sole parent.
This handler sets data in both the managerSpritenum and theParent. You don't need to call any other handlers to set the parent.
This returns an integer indicating the number of children.
This returns a linear list containing the spritenums of the managers of child multi-sprites. This list is a duplicate of pWFSChildList, a property of each manager. Since the list is a duplicate of pWFSChildList, changing it does not change the status of the multi-sprite's children.
If theBoolean is TRUE (or 1) then wfsSetMoveWithParent sets the manager to move when its parent moves. If theBoolean is FALSE (or 0) then wfsSetMoveWithParent sets the manager to not move when its parent moves.
Returns 0 if there is no parent or it is not instantiated. Even so, should the manager acquire a parent at a later time, wfsSetMoveWithParent(1) sets it so that it will indeed move with its new parent, should it acquire one.
Returns 1 otherwise.
MISCELLANEOUS PUBLIC HANDLERS
Returns a duplicate of the element list, a linear list that contains the spritenums of the elements of the multi-sprite. The first entry in the list is always the spritenum of the manager of the multi-sprite, which you shouldn't move.
wfsGetElementNamed (theName, returnList)
Finds an element or all elements named theName. If returnList=VOID (or is not specified), then the handler will search for the first occurrence amid the elements of the multi-sprite for an element named theName. If returnList<>VOID, then the handler will search for all occurrences of elements named theName. This handler does not look at the names of managers. It looks at the names of elements, which in this context are never managers.
If returnList=VOID (or is unspecified), then returns an integer indicating the spritenum of the first element in the multi-sprite named theName. If there is no element named theName, then the handler returns 0. If returnList <> VOID, then returns a linear list indicating the spritenums of all elements in the multi-sprite named theName. Returns the empyty list [ ] if there are no elements named theName.
theName: A string denoting the name of the element
returnList: is an optional parameter. If returnList=VOID or is unspecified, then the handler will look for only the first occurrence of an element in the multi-sprite named theName and the handler will return an integer. If returnList <> VOID, then the handler will return a linear list indicating the spritenums of elements in the multi-sprite named theName.
Suppose that sprite 5 is a Manager of a multi-sprite, and that two of its elements, sprites 9 and 11, are named "bobo".
will return 9
will return [9, 11]
You set the name of elements when you drop the "4: Window/Menu Element" behavior on a sprite and you can change the name at run time. That behavior contains two handlers relevant to element names: wfsGetElementName and wfsSetElementName.
The "1: prepareMovie" script contains a handler named wfsGetElementNamed. This is a bit different from the current handler. It will search multiple multi-sprites for elements with a specified name (in various ways)
This returns a boolean that indicates whether the referenced menu is currently open. Open is TRUE.
This returns the rect of the window. What is that? It is the rect of the background element, which is the first element below the manager.
The background of a multi-sprite is, by WFS convention, defined as pWFSElementList. In other words, it's the element right after the manager. If pWFSElementList contains only one entry, 0 is returned.
Example Usage of Public Handlers
The "Menu Manager" behavior, if you look at the source code, contains many public handlers. These handlers are called by other sprites that wish to perform some operation on a menu.
Let's look at the code of the "Menu Verb" behavior, in the "openAMenu" handler, shown below. openAMenu is conditionally called by the mouse handlers in the "Menu Verb" behavior when a window or menu is to be opened. It makes calls to various public handlers in the Menu Manager behavior or, if a window is being opened, to same-named handlers in the "3: Window Manager" behavior. Let's have a look at the code to see how it calls the public handlers.
on wfsOpenAMenu me
--This handler is conditionally called in the mouse event handlers of the Menu
--Verb behavior when a menu or window is to be opened.
--wfsGetManagerSpriteNum is a handler in the "1: prepareMovie" script.
--It returns the spriteNum of the Manager named pWFSNameOfMenuToOpen.
--Or it returns 0 if the Manager is not currently instantiated, which needs
--to be checked at this point before attempting to open the menu or window.
if spriteNumOfMenuToOpen >0 then
--Then the manger of the menu to be opened is instantiated.
case pWFSMenuOpeningLocation of
--pWFSMenuOpeningLocation is a property of the "Menu Verb" behavior. This
--property is set in the Menu Verb Parameter box when you drop the
--Menu Verb behavior on a sprite.
"Don't want one opened.":
"Do not move it, just open it.":
sendSprite (spriteNumOfMenuToOpen, #wfsOpenWindow)
sendSprite (spriteNumOfMenuToOpen, #wfsCenterMultiSprite)
sendSprite (spriteNumOfMenuToOpen, #wfsOpenWindow)
"Open it where mouse is.":
sendSprite (spriteNumOfMenuToOpen, #wfsMoveFamilyTo, the mouseLoc)
sendSprite (spriteNumOfMenuToOpen, #wfsOpenWindow)
sendSprite (spriteNumOfMenuToOpen, #wfsMoveFamilyTo, point(pWFSMenuAbsoluteLocH, pWFSMenuAbsoluteLocV))
--pWFSMenuAbsoluteLocH and pWFSMenuAbsoluteLocV are properties of the
--Menu Verb behavior. These properties are set in the Menu Verb
--Parameter Dialog Box when you drop the Menu Verb behavior on a
--sprite. There are sliders you adjust to set those properties.
sendSprite (spriteNumOfMenuToOpen, #wfsOpenWindow)
--In this case, the pWFSMenuOpeningLocation property has somehow
alert("Use the Property Inspector on sprite " & spritenum & " concerning its 'Menu Verb' behavior. Look at the parameter that asks you to specify where you want the menu or window opened. You need to configure that parameter.")
--In this case, the multi-sprite this handler is supposed to open
--is not instantiated.
alert("Sprite " & spritenum & " is supposed to open a menu or window called " & QUOTE & pWFSNameOfMenuToOpen & QUOTE & " via its Menu Verb behavior. But that menu or window isn't currently instantiated.")
Note that the handler first checks to see if the window or menu it is supposed to open is instantiated. There is quite a bit of this sort of error checking in WFS to help you debug your apps that use WFS.
If the window or menu you want to open is instantiated, ie, its Manager is listed in gWFSMultiSpriteList, then the "case" statement executes. The case statement examines the value of pMenuOpeningLocation, which is set when you drop the "Menu Verb" behavior on a sprite and configure the Parameter Dialog Box.
Then, depending on the value of pMenuOpeningLocation, the window or menu is not moved, or is first centered, or moved to where the mouse is, or moved to an ablsolute location, and then opened.
Below, we see the mouseUp handler from the "Menu Verb" behavior. It is standard to close menus on mouseUp when the user makes a menu selection, so this is a good handler to show concerning how to close menus using Lingo. I've documented the code heavily so you can read the code and see what's going on. Actually, there is only one line in the below handler that makes a call to the public handlers of the Menu Manager behavior. The line is:
That is how you close a menu system. What it does is make the entire menu system invisible if you have configured the root menu to do so. Otherwise, it closes all but the root menu.
on mouseUp me
--The mouseUp handler is a lot like the other mouse handlers.
--So I've documented it heavily so you can figure it out and the
--rest of the mouse handlers.
if pWFSOpenAMenu="mouseUp" then
--Then open the multi-sprite this behavior is
--configured to open.
if pWFSCloseMenuOnMouseEvent = "mouseUp" then
--If you've configured this behavior to close the menu
--system on mouseUp, then let's do it.
--pWFSManagerSpriteNum is a property of the "4: Window/Menu
--Element" behavior which should also be attached to this sprite.
if wfsManagerIsInstantiated(myManager) then
--wfsManagerIsInstantiated is a handler in the "1: prepareMovie"
--movie script. It returns a positive value if myManager is instantiated.
--This makes a call to the Menu Manager's wfsCloseMenuSystem
--handler which closes the menu system either completely
--or leaves the root menu visible, depending on how you have
--configured the Parameter Dialog Box of the root menu.
alert("Sprite " & spritenum & " is supposed to close the menu on mouseUp. But the Menu Manager of sprite " & string(spritenum) & " is not instantiated. The Manager is supposed to be sprite " & string(myManager))
--In this case, the Manager of this menu item is not instantiated.
|Menu Manager Behavior|