Update a menu with a list of open windows
This lesson demonstrates how to show a list of open windows in a menu.
The mouseDown message
The mouseDown message is sent when the user clicks an object. If a menu was clicked, the message is sent before the menu is displayed, so if you make changes to the button in the mouseDown handler, the changes appear in the menu. Remember that in LiveCode, menus are implemented as buttons, and a menu bar is a group of menu buttons. When the mouseDown message is sent to the menu button, it triggers this handler.
Cross-platform note: When a menu button is being displayed in the Mac OS menu bar, it does not receive mouseDown messages, but its group does. For this reason, this example handler should be placed in the script of the menu bar group, rather than in the menu button. The first line of the handler should be updated to:
if the short name of the target is "myMenuBar" then
assuming that your menubar group is named "myMenuBar". This ensures that the handler will be called only if the user clicks on the menubar.
The mouseDown handler
This mouseDown handler builds a list of open stack windows whenever the user clicks the menu. Recreating the list every time the menu is clicked ensures that the menu is always current. Any menu that is constructed dynamically must be created in a mouseDown handler, since that is the only way to ensure that the button's contents are updated before the menu is displayed.
The openStacks function
The handler begins with the openStacks function, which returns a list of the short name of each stack whose window is open.
The handler could put this list into the menu just as it is. However, this would list the name property of the stacks, rather than the title property. The title is the text shown in a stack's title bar, and is often more descriptive than the stack's name. It's also the name the user sees on the screen, so for most situations, it's better to show the user the title instead of the name.
The stack titles
To get the stack titles, the handler walks through the list of stack names in a repeat loop, using the repeat for each form. This form of repeat creates a variable (in this handler, the variable is called "thisStack") and places each stack name in turn into the variable. The loop repeats once for each open stack, gathering the title of each stack into a variable called "windowsList" that will be used as the menu's contents.
repeat for each line tStack in the openStacks
put the title of stack tStack into tStackTitle
...
end repeat
Checking for palettes and dialog boxes
In most cases, a standard Window menu does not include the names of palettes and dialog boxes. To filter out such windows, the handler checks the mode property of each of the open stacks, and skips over that stack if its mode is greater than 2. The mode property describes what kind of window a stack is in: 1 is an unlocked stack in an editable window, 2 is a locked stack in an editable window, and higher numbers are palettes, dialog boxes, or other ways of displaying a stack. So a mode of 1 or 2 indicates that the stack is in an editable window: the kind of window we want to show in the menu. A stack with a mode of zero is closed but loaded into memory. However, we don't need to worry about this sort of stack, because closed but loaded stacks are not returned in the openStacks function to begin with. For more information on stack modes, see "Scripting a User Interface" in the User's Guide.
As an extra, if the Alt key (Unix or Windows) or Option key (Mac OS or OS X) is held down, this step is skipped and all open stacks are shown, whatever their mode.
if the mode of stack tStack > 2 and the optionKey is not down then
next repeat
end if
Storing the stack name
The handler now stores the stack's name in a variable called tStacksList. This variable is used to collect all the stack names, in the same order as the stack titles. Later on, we'll store this list in a custom property of the menu. The reason for doing this is so that in another handler, we can take a menu choice and get the corresponding stack name. If we want to do anything with a stack chosen from the menu, the stack name (rather than the title) is needed, because two open stacks can have the same title. Without the stored list of names, we wouldn't necessarily be able to tell which stack was being referred to.
put tStack & return after tStacksList
Storing the stack title
Next, the handler stores the title of the stack in the variable tStackTitle, and then checks to see if the number of words in the title is greater than zero. It checks for the number of words, rather than just checking whether the title is empty, because LiveCode uses a one-space title to indicate that a stack should have no title at all in its title bar. Because of this, some stacks may have a title that consists of a single space, instead of being empty, and since showing a space in a menu isn't very helpful, we want to show such a stack's name instead of its title. If the title contains at least one word, the handler adds it to a variable called tMenuContents, which will become the contents of the Window menu. If the title has no words, the handler adds the stack's name, instead of the title.
put the title of stack tStack into tStackTitle
if the number of words in tStackTitle <> zero then
put tStackTitle & return after tWindowList
else
## no title, so use the stack name instead:
put tStack & return after tWindowList
end if
Sorting the stack list
When the repeat loop is done, the variable tWindowList contains a list of all the open windows, in front-to-back order. The windows list is in this order because that's the order the openStacks function returns them in, and our list is based on the value returned by the openStacks. If we wanted to use another order, such as alphabetical, we would use the sort command at this point to sort the list:
sort lines of theMenuContents
But what if there are no open windows (that aren't palettes or dialog boxes)? In this case, the variable will be empty, but we don't want the Window menu to be empty, so in this case we put "(No windows open" into the variable. The leading parenthesis makes this a disabled menu item, and the menu will display this grayed-out explanation if there are no windows to list.
if tWindowList is empty then
## no stack windows open
put "(No windows open" into tWindowList
end if
Setting up the menu
Finally, the handler puts the variable tWindowList into the Window button. Because this handler is in the menu bar group's script, it can refer to the group using the me keyword. The expression button "Window" of me refers to the Window button that's part of the menu bar group. The menu contents is now set to the list of stack titles:  when the menu is displayed a moment later, it will show this list.
put tWindowList into button "Window" of me
The mouseDown handler code
on mouseDown
local tStacksList, tStackTitle, tWindowList
## goes in the menubar group script
if the short name of the target is "Window" then
## the user clicked the Window menu
repeat for each line tStack in the openStacks
## skip palettes and dialog boxes
## (unless Option or Alt key is pressed):
if the mode of stack tStack > 2 and the optionKey is not down then
next repeat
end if
## List the stack names for later:
put tStack & return after tStacksList
## List the stack by its title, if available:
put the title of stack tStack into tStackTitle
if the number of words in tStackTitle <> zero then
put tStackTitle & return after tWindowList
else
## no title, so use the stack name instead:
put tStack & return after tWindowList
end if
end repeat
if tWindowList is empty then
## no stack windows open
put "(No windows open" into tWindowList
end if
## put the list into the menu:
put tWindowList into button "Window" of me
##set a custom property for later use:
set the cCurrentStacksList of the target to tStacksList
end if
end mouseDown
This mouseDown handler only takes care of updating the menu for display; it doesn't do anything when the user chooses a menu item. Typically, a Window menu's script includes a menuPick handler that brings the chosen stack to the front (or does whatever other action is appropriate).
Dynamic menus
Dynamic menus, whose contents may change depending on the context, are often used in applications. For example, the wording of the Cut and Copy menu items may change depending on what's currently selected, or certain menu items may be enabled or disabled depending on which window is active. You can use the basic technique demonstrated in this example to create other changes in menus as needed.
Richard Gaskin
The mouseDown handler to update the menu items is incorrect. See this thread from the use-livecode list:
http://lists.runrev.com/pipermail/use-livecode/2020-February/258663.html
Tore Nilsen
The script in this example will not work properly on MacOS. The problem here is that when a group is set to behave as a menubar on MacOS, the target becomes the group and not any of the individual buttons. Therefore the script as it is written in the tutorial will not work, since the update of the menu will only happen if the short name of the target is «window». It will work if you remove the encapsulating if-statement.
Removing the if-statement will update the content of the «Window» menu regardless of which menu you choose, provided that the script is placed in the group script. This way the user may well choose another menu initially, slide to the «Window» menu and be presented with an updated menu. So, while this may be a bug, the way around it is to write less code. If only all problems could be solved this way.
Andrew Taylor
So, not only do you have to remove the outside "if" statement, you need to actually name this handler something else (I named it makeMenuWindowList) because the mouseDown handlers of individual menus are NOT called. So you need to get at the "on mouseDown" handler of the menubar group - which you have to create too. You find the scripts of the menubar by bringing up the Menu Builder. Now to actually get the code to execute and put that custom property where the code can get at it, you need the following in the menubar mouseDown handler:
on mouseDown
dispatch "makeMenuWindowList" to button "Window"
end mouseDown
To be complete:
Using Menu Builder:
First create the Window menu, then with the Window menu selected, click the Auto Script button. Then click Edit Script to edit that script. Add the code from the lesson, renaming it. Then you can move on to the next lesson where you will need to fix that code too!
Andrew Taylor
OK, yet ANOTHER PROBLEM!
If you put in a mouseDown handler for the menubar, it gets called any time you click the mouse. OK, Livecode experts, how does one restrict mouseDown handling to the menus to only when you click on a menu or menubar instead of every bloody button ending up calling this code, WITHOUT having a mouseDown handler for every bloody button? (and no, putting the if statement to check if the target is "Window" does not help - apparently despite having NOT clicked on the menubar - but on a button in a window - not named window, the target is "Window".
Panos Merakos
Hello Andrew,
Thank you for the feedback. We will update the lesson asap.
Kind regards,
Panos
--