|
Allegro CL version 11.0 |
This document contains the following sections:
7.1 Some menu notation
7.2 Usual menu behavior
7.3 Pull-down and pop-up menus
7.4 Menu bars
7.5 The menu editor
7.6 Getting rid of a menu bar
7.7 Specifying menu titles and shortcuts
7.8 Access keys
7.9 Working with the menu pane
7.10 More on the menu editor toolbar and some anomalies
7.11 The Menu Item group
7.12 How choosing a menu command causes something to happen
7.13 Some examples of getting menus to do something
7.14 Changing a menu or command (menu-item) programmatically
7.15 Doing something just before a menu is displayed
7.16 Menu Classes
7.17 Creating and modifying menus at run time
7.18 Menu tutorial
7.19 Displaying Pop-up Menus
7.20 Shortcut menu tutorial
7.21 Pop-up menu
This is chapter 7 of the User Guide for the Allegro CL 11.0 Integrated Development Environment (IDE).
The chapters of the IDE User Guide are:
Chapter 1: Introduction to the IDE
Chapter 2: The Allegro CL Development Environment (IDE)
Chapter 3: An example
Chapter 4: Projects
Chapter 5: Components
Chapter 6: Designing a user interface using forms
Chapter 7: Menus (this chapter)
Chapter 8: Events
A menu is a pull-down or pop-up list of commands. Because menus can contain any (reasonable) number of commands and are displayed only by user action, they provide a convenient way to group related commands in a way that they are available at any time to users.
Only top-level windows can have a menu bar. A top-level window is a window that is owned by the screen or another top-level window and is not a child window.
In the following illustration, the window has a menu bar with two menus: File and Edit. Each menu can have zero or more menu-items. For the File menu, there are 6 menu items. The horizontal line is a special type of menu-item called a separator. The menus of a menu bar are of type pull-down-menu because as you click on the title of the menu its menu items are displayed.
Menus contain commands. The object corresponding to a command is a menu-item. However, we use menu-item when describing the programmatic aspects of menu but command when discussing the appearance of menus in an application. There are special kinds of menu-items, such as the separator in the illustration above.
A user can point to a command while the menu is visible. The command pointed to is highlighted and if it names a submenu, the submenu is displayed.
You click a menu command not associated with a submenu to effect it (choose can be used in place of click). When a command is clicked, the menu disappears and the action associated with the command is performed.
In the title of a menu or menu item, you can define an access key. An access key lets your user open the menu or access the menu item using the keyboard. If you wanted to open the File menu in the illustration above, you would press ALT+F. To invoke the Exit menu item, you would press "x". Within a particular menu, you should have each access key be as unique and mnemonic as possible.
Shortcut keys are another way for your users to access your menu commands. A shortcut key is (typically) a combination of a key like Control or Alt and a letter or number. It runs a menu item immediately when pressed. In the illustration above, Control-O is the shortcut key for the Open command on the File menu. You should assign shortcut keys to frequently used commands. Windows reserves some shortcut keys for cross-application commands like Cut, Copy and Paste.
A menu command may be associated with another menu (called a submenu). Pointing to (putting the mouse over) the menu command causes the submenu to be displayed and the user can point to or click on a command from the submenu just as with the main menu. (Submenus allow very many commands to be presented rather conveniently since most commands are not displayed at any one time.)
In this document, we specify a menu and command with the menu title followed by a horizontal bar (|) followed by the command name, all in bold. Thus File | Save is the Save command on the File menu. Commands from submenus are shown with additional |'s, thus View | Manage Windows | Iconize is the Iconize command on the Manage Windows submenu of the View menu. (We have been using this notation in other chapters of this document.)
Clicking or choosing a command causes some action. Menus are rarely used simply to display information (although a command can cause an informative dialog to appear). Information can be conveyed incidentally, however. The fact that a command is unavailable (cannot be pointed to or chosen) can be informative. Some editors make the Save command on the File menu unavailable if there are no changes to save and Paste can be made unavailable when there is nothing on the clipboard.
A user can click on no command and cause the menu to disappear. Typically nothing happens when no item is selected and the state of the program is typically the same as if the menu had never been displayed. (We say typically twice in the last sentence because the application programmer can associate any action displaying the menu or pointing to a command or whatever with any effect. If you are programming a game, you might have a Hint menu and cause the game player to lose points if the Hint menu is looked at, whether or not a command is chosen. Or an application designed to study user interfaces may record how often menus are displayed and items chosen as a way of learning about user behavior, perhaps with an eye to improving the interface.)
A pull-down menu is displayed on a menu bar. Menu bars are displayed only on a top-level window. The menu bar is typically always visible and the user can display a menu any time the mouse is available.
Pop-up menus are displayed by specific user action but have no specific location and no title. Pressing or clicking the right mouse button in various locations in the Allegro CL development environment displays a pop-up menu with context-related items. Another pop-up menu is the symbol completion menu displayed by choosing Search | Complete Symbol. While typing in the Debug window or in an editor window. For example, at a prompt in the Debug window, type
string>
and, leaving the cursor right after the >, choose Search | Complete
Symbol. A pop-up menu appears with items string>
and string>=
(the two symbols whose names begin with string>
).
This chapter is concerned with pull-down menus. For information on pop-up menus, see the online help topics pop-up-shortcut-menu and pop-up-lettered-menu.
To add a menubar to a form, select the form, and then either (1) click on Form | Add Menu Bar or (2) select the menu property in the Inspector, and click on the Extended Editor button:
This displays the menu editor.
To have a pull-down menu on a window, you must:
Do all that and you will have a working menu. Here are some more things that you can do:
Each one of these issues will be dealt with below. The basic tool for placing menu bars on top-level windows and creating pull-down menus is the Menu Editor, which we describe next.
The menu editor looks like this:
You are given a default starter menu bar. You can make changes and accept the menu bar by clicking on the OK button. As you work in the menu editor, you will see the menu bar update on your form. If you click the Cancel button, the editor is closed and the menu property is left unchanged (meaning no menu bar if it was nil before, the previous menubar if there was one before). The starter menu bar has a File menu and an Edit menu containing some Windows-standard commands.
If a form has a menu bar and you decide you do not want one on that form, you can get rid of it by clicking Form | Remove Menu Bar while the form is selected. You can also do it by selecting the contents of the menu property when the form is being inspected and replacing those contents with nil (by typing n i l).
The menubar will also not be displayed if you
nil
.
The menu titles, command names, and shortcuts are displayed in the Menu group in the center of the menu editor. The display is an outline control, with leaves (terminal entries with no subentries) corresponding to commands and nodes (entries with subentries) corresponding to menus or submenus. The title of a menu appears in the menu bar or, if the menu is a submenu, as an item in the parent menu. The title is not a command. Clicking on it displays the menu whose title it is.
A shortcut is a key combination which when pressed has the same effect as choosing the menu command. The shortcuts are shown in the second column. Menus do not have shortcuts, only commands, shown as leaves in the display.
An access key in combination with the Alt key lets you display a menu and choose a command from the keyboard. A tilde (~) is a non-displaying character in a menu title or command name which marks the next character as the one to press when the Alt key is down to display a menu or submenu (in a menu or submenu title) or choose a command. Thus, Alt-F displays the File menu (whose title is ~File). Pressing Alt and keeping it down while pressing F (displays the File menu) and N (chooses the New command) is the same as choosing New from the File menu. The character after the tilde is underlined when the menu is displayed. If no tilde appears, the command cannot be selected using Alt and other keys. Note: because of an apparent Windows bug in some versions of Windows (but not in WIndows 2000), access keys may not work when running a project with a form that has a menubar but no other controls. If that happens, then to see access keys working, add any control (e.g., a button) to the form.
The Menu group is the portion of the menu editor labeled Menu Name:
You cannot type in the Menu group. (You make changes that require typing changing a title of a command name, adding or removing a tilde, etc. in the Menu Item pane described below.) When a line is selected (as the ~File line is in the illustration), its details are displayed in the Menu Item group described below and the toolbar buttons will affect it as follows:
If you add an entry (by clicking on the Add button -- with a +), the new entry has the same indentation as the currently selected entry and becomes the selected entry.
If you delete all entries, the menu bar ceases to be visible but still exists. (Make the menu property nil if you want no menubar.)
The Menu Item pane, at the bottom of the Menu Editor, allows you to specify the details of the entries in the Menu group. The details of the selected entry in the Menu Pane are displayed in the Menu Item pane and changes affect that entry only. Only one entry can be selected at a time. Here is the Menu Item pane from the default menus (i.e. the File and Edit menus displayed when the Menu Editor first appears) with the third (~Open) entry selected:
The elements of this pane are:
If you want a tilde displayed, enter two tildes. Thus, if the title is ~~Open, it will display as ~Open and it cannot be chosen using the Alt key.
When you click on a menu command, the system knows what you have done: it knows which window contained the menu bar, it knows which menu contained the command, and it knows which command was chosen. But it doesn't know what to do with this information other than pass it to the application owning the window, so that is what it does. In an Allegro CL application, every menu has an on-click function. When a menu command is clicked, the menu on-click function is called with three arguments: the menu object, the menu-item corresponding to the command clicked on, and the window owning the menubar.
You can specify the menu on-click function in the On Click field of the menu-item group. This field is only active when a menu or submenu (rather than a command) is selected.
The default selection function for the default menus is funcall-menu-item-with-window. This function examines the menu-item, extracts the menu-item value (which in this case is a symbol naming a function of one argument, a window), and funcalls that function with the window (owning the menu bar) as the argument.
It is common to have the menu-item value contain the information about what should be done when the user clicks on a menu command. Menu-item values can be any Lisp object but symbols naming functions are common choices. Since you define the function, you have great flexibility about what it does. The menu on-click function is then just a function which funcalls the menu-item value with whatever arguments you want, often no arguments or just the owning window as an argument.
The menu-item value is specified in the menu-item group when a menu item is selected.
We have already mentioned the predefined function funcall-menu-item-with-window. Another useful predefined selection function is funcall-menu-item, which funcalls the menu-item value with no arguments.
The function pop-up-message-dialog takes arguments stream, title, text, icon, button-label. We are going to construct a menu whose command pops-up a message using that function where the title and text say what command you chose. Now, of course, this is likely not very useful in a real application but if you can get a menu command to pop-up a message like this, you can probably get it to do many other things by emulation.
Our menu will be called Messages and have two commands: Hello and Goodbye. The menu
on-click function will be funcall-menu-item
and the menu-item-values will be the symbols hello-function
and goodbye-function
,
which are defined as follows:
(defun hello-function () (pop-up-message-dialog (screen *system*) "Hello" "Clicked Hello Command!!!" warning-icon "OK"))
(defun goodbye-function () (pop-up-message-dialog (screen *system*) "Goodbye" "Clicked Goodbye Command!!!" warning-icon "OK"))
We define these to the system (by, for example, evaluating them in the debug window). (screen
*system*)
is the screen (meaning the dialog can pop-up anywhere), warning-icon is a
picture of a yellow triangle with an exclamation point, and OK is the label for the
button. Our menu editor after we have defined the new menu (and deleted the default File
and Edit menus) looks like this:
Note that the On Click field when Messages is selected is funcall-menu-item. The On Click field for Messages will only be available after you have added and indented the Hello and Goodbye items, and when it is available, its value starts as funcall-menu-item-with-window. You must edit that to be funcall-menu-item for this example to work.
If we run the form and click on the Hello command in the Messages menu, we see:
If we click on Goodbye, we see:
There is not in this example much information in the menu-items. We could define a selection function that extracts information from the menu-item but processes it itself rather than having the menu-item value do the work. This can be useful when you define menu commands on the fly, based on runtime information. In this case, the menu-item values are strings "Hello" and "Goodbye". The menu-selection function is the following:
(defun my-menu-on-click-function (menu menu-item window) (declare (ignore menu window)) (let ((title (value menu-item)) (message (concatenate 'string "You clicked " (value menu-item) "!!!!"))) (pop-up-message-dialog (screen *system*) title message warning-icon "OK")))
The behavior is the same as before.
Commands are what appear on menus. On our example menus, the commands are Hello and Goodbye. However, the programmatic object associated with a command is a menu-item, which is an instance of class menu-item and has various properties. One is the available property. If mi-var is a variable whose value is a menu item,
(available mi-var)
returns true or false as the menu-item is or is not available. You can use setf with available to change (or set) the availability of a menu-item. Evaluating the following makes the menu-item mi-var unavailable:
(setf (available mi-var) nil)
So, the only issue is how to find a menu-item programatically. There are various ways
to do this, but one of the easiest is to give the object a name and get it using
find-named-object and that name. Here is the menu editor again with our Messages menu. We
have given the Hello menu-item the name :hello-item
(illustrated) and the
Goodbye menu-item the name :goodbye-item
(not shown):
Now let us get a handle on the the form or window. When developing, click on Get Component on the Tools menu and then click on the form (or window, if you are running the form) and then in the debug window, enter
(setq mywin *)
Programmatically, you can use find-window.
Now that mywin is the window with the Messages menu,
(find-named-object :hello-item mywin)
will return the Hello menu-item,
(available (find-named-object :hello-item mywin))
returns true or false as the Hello item is or is not available, and
(setf (available (find-named-object :hello-item mywin)) nil)
makes it unavailable.
The system calls about-to-show-menu with arguments the window and the menu when user action should cause a menu to appear (user action being, e.g., clicking on the menu title on a menu bar or pressing Alt and the underlined letter of the menu title). about-to-show-menu is a generic function. You can write a before method that will run before the system call to the function (making items unavailable, registering the fact that the menu was looked at, or whatever). If you do this, it is best to make your own class of windows and specialize the method on that class. This avoids paying the overhead every time any menu is displayed anywhere within Allegro CL or your application.
There are several classes related to menus. The following is a class diagram showing the menu classes.
window ^ |-- basic-pane | |-- menu ^ |-- windows-menu ^ |---------- menu-bar | |---------- pop-up-menu ^ | |----- pull-down-menu | |----- shortcut-menu | |----- builder-menu
Key: ^ = "is a subclass of", reading up.
Notice that all menus inherit from window. That is a very primitive window class that provides a handle to an OS window object. The menu-item class is not shown because it does not inherit from menu because menu-items are not an OS visible object. Instead, menu-items inherit from standard-object.
There are several different functions related to creating an instance of a menu; the table shows which function should be used for each menu class. menu-item is created with make-instance. A menu-bar is created with make-window. A pop-up-menu, a pull-down-menu, and a shorcut-menu are all created with open-menu.
Code similar to the following is generated if you accept the default menu bar that is created when you open the Menu Editor. It provides the value of the menu keyword argument to make-window. If you want to see the code for yourself, then toggle the menu property on a form without a menu bar to on. In the Menu Editor, click on OK to accept the default menu bar. With the focus on the form, save it using the File | Save command. In the editor, open the .bil file and search for open-menu.
(open-menu (list (make-instance 'menu-item :name :file-menu :title "~File" :value (open-menu (list (make-instance 'menu-item :name 'new-text-editor :title "~New" :value 'new-text-editor :selected nil :available t :event-synonym '(control-key #\N) :help-string "New editor") (make-instance 'menu-item :name 'open-text-file :title "~Open" :value 'open-text-file :selected nil :available t :event-synonym '(control-key #\O) :help-string "Open a file") (make-instance 'menu-item :name :save :title "~Save" :value 'save-text-file :selected nil :available t :event-synonym '(control-key #\S) :help-string "Save to file") (make-instance 'menu-item :name 'save-as-text-file :title "Save ~As..." :value 'save-as-text-file :selected nil :available t :event-synonym nil :help-string "Save to new file") (make-instance 'menu-item :name nil :title "-" :value nil :selected nil :available nil :event-synonym nil :help-string nil) (make-instance 'menu-item :name 'user-close :title "E~xit" :value 'user-close :selected nil :available t :event-synonym '(alt-key vk-f4) :help-string "Exit application")) 'pull-down-menu (screen *system*) :name :file-menu :show-help-strings-as-tooltips nil :on-click 'funcall-menu-item-with-window) :selected nil :available t :event-synonym nil :help-string nil) (make-instance 'menu-item :name :edit-menu :title "~Edit" :value (open-menu (list (make-instance 'menu-item :name 'cut-command :title "~Cut" :value 'cut-command :selected nil :available t :event-synonym '(control-key #\X) :help-string "Copy contents to clipboard and delete") (make-instance 'menu-item :name 'copy-command :title "C~opy" :value 'copy-command :selected nil :available t :event-synonym '(control-key #\C) :help-string "Copy contents to clipboard") (make-instance 'menu-item :name 'paste-command :title "~Paste" :value 'paste-command :selected nil :available t :event-synonym '(control-key #\V) :help-string "Paste contents from clipboard")) 'pull-down-menu (screen *system*) :name :edit-menu :show-help-strings-as-tooltips nil :on-click 'funcall-menu-item-with-window) :selected nil :available t :event-synonym nil :help-string nil)) 'menu-bar (screen *system*) :name :default-menu :show-help-strings-as-tooltips nil :on-click 'funcall-menu-item)
open-menu
(open-menu (list ...) 'pull-down-menu (screen *system*) :on-click 'funcall-menu-item-with-window ...)
Use the open-menu function to create a pop-up-menu or pull-down-menu. The arguments to open-menu are the list of menu-items, the type of menu, the screen, and zero or more keyword arguments for the properties of the menu.
name | The symbolic name of the menu item |
title | The title of the command. Place a "~" before the
access key of the command. Use "-" to indicate a menu separator. |
on-click | The event handler that is invoked when the user clicks on a menu item in the menu |
make-instance
(make-instance 'menu-item :name :new :title "~New" ...)
For every command on a menu, you need to create an instance of menu-item. In general, you need to specify most of the properties of a menu-item to have it perform properly.
allow-during-modality | A Boolean value indicating whether or not the command can be invoked when a modal dialog is displayed |
available | A Boolean value indicating whether the command is displayed as available or dimmed |
event-synonym | The shortcut key(s) that can invoke the command. Values can be nil or a list of key constants/characters. Here are two examples, '(vk-f8) and '(control-key #\N). |
help-string | A string that is displayed when the user points to the menu item |
name | The symbolic name of the menu item |
selected | A Boolean value indicating whether or not the menu item is displayed with a check mark or not. |
title | The title of the command. Place a "~" before the access key of the command. Use "-" to indicate a menu separator. |
value | The value of the menu item. If the menu item has sub-items, then the value is a pull-down or pop-up menu. |
(make-instance 'menu-item :name nil :title "-")
Creates a menu separator. When the title of a menu-item is "-", then a menu separator is displayed as a horizontal line in the menu. You can also use the constant, menu-separator, instead of creating a menu-item. Menu items can be placed on more than one menu.
This short tutorial demonstrates creating and customizing an application's menu bar. The sample application is a mini-text editor that extends the default menu bar.
(defclass my-window (text-edit-window) ())
(defclass my-pane (text-edit-pane) ()) (defmethod default-pane-class ((window my-window)) 'my-pane)
(defmethod new-text-editor ((window my-pane)) (setf (file window) nil) (clear-page window))
(defmethod open-text-file ((window my-pane)) (let* ((pathname (ask-user-for-existing-pathname "Edit File" :stream window))) (cond ((or (null pathname) (not (probe-file pathname))) nil) (t (load-file window pathname)))))
(defmethod save-text-file ((window my-pane)) (let ((file-to-save (file (parent window)))) (if (null file-to-save) (setq file-to-save (ask-user-for-new-pathname "Save file as" :allowed-types '(("Text Files" . "*.txt") ("All Files" . "*.*"))))) (if file-to-save (save-file window file-to-save)))) (defmethod save-as-text-file ((window my-pane)) (let ((file-to-save (ask-user-for-new-pathname "Save file as" :allowed-types '(("Text Files" . "*.txt") ("All Files" . "*.*"))))) (if file-to-save (save-file window file-to-save))))
(defmethod about-to-show-menu ((window my-window) menu) (case (name menu) (:edit-menu (multiple-value-bind (start end) (get-selection (frame-child window)) (setf (available (find-named-object 'cut-command menu)) (not (eq start end))) (setf (available (find-named-object 'copy-command menu)) (not (eq start end))) (setf (available (find-named-object 'paste-command menu)) (first (first *clipboard*))))) (t (call-next-method))))
(defmethod background-color-on-click (menu menu-item (window my-window)) (declare (ignore menu)) (setf (background-color (frame-child window)) (value menu-item)))
(defmethod about-to-show-menu ((window my-window) menu) (case (name menu) (:edit-menu (multiple-value-bind (start end) (get-selection (frame-child window)) (setf (available (find-named-object 'cut-command menu)) (not (equalp start end))) (setf (available (find-named-object 'copy-command menu)) (not (equalp start end))) (setf (available (find-named-object 'paste-command menu)) (first (first *clipboard*))))) (:background-menu (let ((color (background-color (frame-child window)))) (dolist (item (menu-items menu)) (setf (selected item) (and (null (string-equal (title item) "-")) (or (and (null color) (null (value item))) (if (and color (value item)) (rgb-equal (symbol-value (value item)) color)))))))) (t (call-next-method))))
Floating menus are implemented using pop-up-menus. A pop-up-menu is similar to a pull-down-menu because it can have one or more menu items. The difference is that a pop-up menu is not activated by clicking on the menu's title or by using the ALT key. Usually, pop-up-menus are opened by using the right mouse click. You cannot use the Menu Editor to specify pop-up-menus.
Pop-up menus that are opened using a mouse right click are called shortcut menus. If you want to define a shortcut menu for your window, you do the following:
There is a more general exported function called pop-up-menu. If you use pop-up-menu, you are responsible for opening the menu, creating the menu items, and positioning the pop-up menu.
The utility function called pop-up-lettered-menu takes a list of strings and displays a pop-up-menu with alphabetized accelerator keys. If the user clicks on an option, then the string of the selected item is returned. Otherwise, nil is returned if no item is selected. pop-up-lettered-menu enables an end user to type a single key to select an item. It is used by the Search | Complete Symbol command in the Integrated Development Environment.
The following steps extend the previous mini-text editor example. For shortcut menus, you cannot use the Menu Editor and will have to work programmatically. We use the menu-example project created in section 7.18 above.
(defclass my-shortcut-menu (shortcut-menu) ())
(defmethod shortcut-menu-class ((window my-pane)) 'my-shortcut-menu)
(defmethod mouse-right-down ((window my-pane) buttons data) (declare (ignore buttons data)) (pop-up-shortcut-menu window))
(defmethod shortcut-commands ((window my-pane) (menu my-shortcut-menu)) (list (make-instance 'menu-item :name :inspect :value 'inspect-command :title "Inspect") menu-separator (make-instance 'menu-item :name :system-background :value 'system-background-color-command :title "System Background" :selected (not (background-color window))) (make-instance 'menu-item :name :blue-background :value 'blue-background-color-command :title "Blue Background" :selected (rgb-equal (background-color window) blue)))) (defmethod inspect-command ((window my-pane)) (inspect window)) (defmethod system-background-color-command ((window my-pane)) (setf (background-color window) nil)) (defmethod blue-background-color-command ((window my-pane)) (setf (background-color window) blue))
Sometimes you may want to use the more general pop-up-menu function rather than pop-up-shortcut-menu. Here we use the menu-example project created in section 7.18 above.
In the menu-example editor, you would need to rewrite the mouse-right-down function to use pop-up-menu instead. Since you are not using the shortcut menu functions, you are responsible for creating the menu and its menu items. The pop-up-menu function displays a pop-up menu at a specified location.
;; Redefined mouse-right-down that calls pop-up-menu. (defmethod mouse-right-down ((window my-pane) buttons data) (declare (ignore buttons data)) (let ((menu (open-menu (list (make-instance 'menu-item :name :inspect :value 'inspect-command :title "Inspect") menu-separator (make-instance 'menu-item :name :system-background :value 'system-background-color-command :title "System Specified Background" :selected (not (background-color window))) (make-instance 'menu-item :name :blue-background :value 'blue-background-color-command :title "Blue Background" :selected (rgb-equal (background-color window) blue))) 'my-shortcut-menu (screen *system*) :window window))) (prog1 (pop-up-menu menu (screen *system*)) (close menu))))
Go to chapter 8. Go to the beginning of this chapter.
Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.
|
Allegro CL version 11.0 |