com.holub.ui
Class MenuSite

java.lang.Object
  extended by com.holub.ui.MenuSite

public final class MenuSite
extends Object

A MenuSite is a frame that holds a menu bar. Other objects in the system (which do not have to be visual objects) can negotiate with the MenuSite to have menu's placed on the site's menu bar (or within submenus already found on the menu bar). These objects can easily remove the menu modifications they've made.

The MenuSite is a static Singleton. You cannot create one with new---It's made up entirely of static methods which you can access globally.

The first thing you must do is tell the system which JFrame will host the menu bar by calling MenuSite.establish(mainFrame). For example:

  public class mainFrame extends JFrame
  {     public mainFrame()
        {       MenuSite.establish( this );
                        //...
        }
  }
  
Once a menu site is established, objects that create user interfaces can add items to the menu site by calling addLine(...) (or more rarely, addMenu(...)). In the following example, an EmployeeManager object adds an "Employee" menu that has two submenus: "Hire" and "Fire"
  public class EmployeeManager
  {
        public EmployeeManager()
        {       MenuSite.addLine( this, "Employee",              null )
                MenuSite.addLine( this, "Employee:Hire," HireListener );
                MenuSite.addLine( this, "Employee:Fire," FireListener );
        }

  }
  
The HireListener and FireListener objects implement ActionListener, and are notified when the menu item is selected.

There's (deliberatly) no way to remove individual menu items. When the object that added items shuts down it's user interface, it issues a single call to removeMyMenus to remove all the menus and line items it added.

The creating object can also issue a setEnable(java.lang.Object, boolean) call to disable (or enable) all menu and menus it created.

Important: A referece to the object that asked for the menu item to be inserted is passed into most of the methods of this class as the requester argument. The requester is used as a "key" in a hash table, and the hash-table lookup method uses both the object's equals() and hashcode() methods to do the lookup. For things to work properly, the object used as a requester should not implement equals(). Java visual objects like JComponent work fine as a requester, but be careful if you use an object of a class of your own devising. Solve the problem as follows:

  class MyClass
  {     public boolean equals() { ... }

                private final Object requesterId = new Object();
                public f()
                {       // Use requesterId instead of "this."
                        MenuSite.addLine( requesterId, ... );
                }
        }
  

If you haven't worked with menus before, bear in mind that menu items and menus have both a "name" and also a "label." The label is visible to the program's user, the name is an arbitrary internal string. In an internationalized application, the label will change with the local, but the name will be fixed. The current classes use only the menu "name." If you don't do anything special, the name is used as the label. You can provide a file that maps names to arbitrary strings (and also defines menu shortcuts) by calling mapNames(...).


Nested Class Summary
static class MenuSite.Test
          This inner class tests the MenuSite.
 
Method Summary
static void addLine(Object requester, String toThisMenu, String name, ActionListener listener)
          Adds a line item to a menu.
static void addMapping(String name, String label, String shortcut)
          Add a name-to-label mapping manually.
static void addMenu(Object requester, String menuSpecifier)
          Create and add an empty menu to the menu bar.
static void establish(JFrame container)
          Establish a JFrame as the program's menu site.
static JMenuItem getMyMenuItem(Object requester, String menuSpecifier, String name)
          Get a menu item for external modification.
static void mapNames(URL table)
          Establishes a "map" of (hidden) names to (visible) labels and shortcuts.
static void removeMyMenus(Object requester)
          Remove all items that were added by this requester.
static void setEnable(Object requester, boolean enable)
          Disable or enable all menus and menu items added by a specific requester.
 
Methods inherited from class java.lang.Object
equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Method Detail

establish

public static void establish(JFrame container)
Establish a JFrame as the program's menu site. This method must be called before any of the other menu-site methods may be called. (Most of these will throw a NullPointerException if you try to use them when no menu site has been established.)


addMenu

public static void addMenu(Object requester,
                           String menuSpecifier)
Create and add an empty menu to the menu bar. Menus are generally created by addLine(...). This method is provided for situations where one "requester" creates a menu structure and other requesters will add line items to this structure. By using one requester for the main menu, and other requesters for the line items on the menu, the requesters that added the line items can remove those items without removing the menu that contained the items.

Menus are inserted on the menu bar just to the left of the "Help" menu. (The "help" menu [a menu whose name is the string "help"---case is ignored] is special in that it always appears on the far right of the menu bar.)

Use addLine(...) to add line items to the menu created by the current call. The name-to-label substitution described in addLine(...) is done here as well. As in that method, the name string also defines the (visible) label if no mapping is found.

If the requested menu already exists, this method silently does nothing.

Parameters:
requester - The object that requested that this menu be added. All menus (and line items) added by a specific requster are removed by a single removeMyMenus(...) call. The requester need not be the actual object that adds the menu---there may not be a single one. It is simply used to identify a group of menu items that will be removed in bulk. All items that have the same requester object are removed at once.
menuSpecifier - The menu to create. A simple specifier (with no colons in it) creates an item on the menu bar itself. Submenus are specified using the syntax "main:sub". For example, addMenu( this, "File:New" ) creates a "New" submenu under the "File" menu. If the supermenu (in this example, "File") doesn't exist, it's created. You can have more than one colon if you want to go down more than one level (e.g. "Edit:Text:Size"). Up to six levels below the menu bar (six colons) are supported. (If you have more than that, you should seriously reconsider your menu structure.) Intermediate menus are added as necessary.
Throws:
IllegalArgumentException - if the menuSpecifier is malformed (e.g. has spaces in it) or if the specifier identifies an existing line item (as compared to a menu).

addLine

public static void addLine(Object requester,
                           String toThisMenu,
                           String name,
                           ActionListener listener)
Adds a line item to a menu. The menu is created if it does not already exist.

This method is the preferred way to both create menus and add line items to existing menus. See addMenu(...) for the rules of menu creation.

By default, the "name" is used for the "label." However, when there is a name map (see mapNames(java.net.URL)), then the name parameter is used for the name, and the associated labels and shortcuts specified in the map are used. If there is a map, but the map has no entry for the item named by the name parameter, then the name is used for the label and a warning is logged to the com.holub.ui stream using the standard java Logging APIs.

Parameters:
requester - The object that requested that this line item be added.
name - The (hidden) name text for this item. When there's no name map, the same string is used for both the name and the label (and there is no shortcut), otherwise the name argument specifies the name only, and the associated label (and shortcut) is taken from the map.

Use the name "-" to place a separator into a menu. The listener argument is not used in this case, and can be null.

toThisMenu - The specifier of the menu to which you're adding the line item. (See addMenu(...) for a discussion of specifiers.) The specified menu is created if it doesn't already exist.
listener - The ActionListener to notify when the menu item is selected.
See Also:
addMenu(java.lang.Object, java.lang.String), mapNames(java.net.URL)

removeMyMenus

public static void removeMyMenus(Object requester)
Remove all items that were added by this requester.

For the time being, the case of "foreign" items being placed on a menu created by another requester is not handled. Consider a program in which two object both add an item to the "File" menu. The first object to add an item will be the official "owner" of the menu, since it created the menu. When you call removeMyMenus() for this first object, you want to remove the line item it added to the "File" menu, but you don't want to remove the "File" menu itself because it's not empty. Right now, the only solution to this problem is for a third requester to create the menu itself using addMenu(...).


setEnable

public static void setEnable(Object requester,
                             boolean enable)
Disable or enable all menus and menu items added by a specific requester. You can disable a single menu item by using MenuSite.getMyMenuItem(requester,"parent:spec", "name") .setEnabled(FALSE);

Parameters:
enable - true to enable all the requester's menu items.

getMyMenuItem

public static JMenuItem getMyMenuItem(Object requester,
                                      String menuSpecifier,
                                      String name)
Get a menu item for external modification. You can use this method to get the JMenuItem that was created by addLine(...) under the covers. Use the returned reference to do things like disable the line item. Do not manipulate the menu structure (by adding and removing items), however.

Parameters:
requester - the object that inserted the menu or item
menuSpecifier - the menuSpecifier passed to the original addMenu(...) or addLine(...) call.
name - the name passed to addLine(...). null if you want a menu rather than a line item within the menu.
Returns:
the underlying JMenu or JMenuItem. Returns null if the item doesn't exist.

mapNames

public static void mapNames(URL table)
                     throws IOException
Establishes a "map" of (hidden) names to (visible) labels and shortcuts. Establishing a map changes the behavior of addLine(...) and addMenu(...) and in that the specified label and shortcut are installed automatically for all names specified in the table. A map must be specified before the item named in the map are added to the menu site. You may call this method multiple times to load multiple maps, but the "name" component of each entry must be unique across all maps.

Parameters:
table - a Properties-style file that maps named keys to labels, along with an optional shortcuts. The general form is:
  name.1 = label One; C
  name.2 = label Two; Alt X
  
The shortcut can be specified in one of two ways. If it's a single character, as in the first example, above, the platform-default modifier is used. For example, in the first example, the shortcut will be a Ctrl-C in Windows, a Command-C on the Mac, etc. Otherwise, the shortcut specifier must take the form described in KeyStroke.getKeyStroke(String). For example:
  F1
  control DELETE
  alt shift X
  alt shift released X
  typed a
  
Names like DELETE and F1 are shorthand for VK_DELETE and VK_F1. (The complete set of VK_xxx constants are found in the KeyEvent class.) You can use any of these "virtual" keys simply by removing the VK_.

For reasons that are mysterious to me, F10 is hard mapped to display the main menu (so that you can navigate the menus with the arrow keys). You could probably defeat this behavior with a key binding, but it's easier to just accept it as a fait accompli, and not try to define F10 as a keyboard "shortcut."

The input file is a standard "Properties" file, which is assumed to be ISO 8859-1 (not Unicode) encoded. See Properties.load(java.io.InputStream) for a full description of the file format.

Throws:
IOException - if it can't load the table
See Also:
KeyEvent, Properties

addMapping

public static void addMapping(String name,
                              String label,
                              String shortcut)
Add a name-to-label mapping manually. A mapping must be specified before the item is added to the menu site.

Parameters:
name - The menu-item name passed to addMenu(...) or addLine(...).
label - The visible label for that item.
shortcut - The shortcut, if any. Should be an empty string ("") if no shortcut is required. See mapNames(...) for information on how to form this string.
See Also:
mapNames(java.net.URL)