
/**
 *  I've written this class to provide menus that behave
 *  a little more like menus in xWindows.  An xMenu has
 *  a function, doCommand, that is called when the user selects
 *  an item from the menu.  (This works only for items that
 *  are added with the various "add" methods that are defined
 *  in this class, not with the ones inherited from the JMenu
 *  class.)  It also has an "update" function that
 *  is called just before the menu is about to appear.
 *  The "doCommand" and "update" methodsare  empty.  They
 *  should ordinarily be defined in a subclass (unless they
 *  have nothing to do).
 *  <p>A few other utility routines are provided for manipulating
 *  items in the menu, such as enabling, disabling, setting the
 *  text of the item, and checking/changing the state of check boxes
 *  and radio group items.
 */

package mb;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

public class xMenu extends JMenu implements ActionListener, PopupMenuListener {

   //-------------- Listener methods -----------------------
   public void actionPerformed(ActionEvent evt) { 
      Object src = evt.getSource();
      int ct = getMenuComponentCount();
      for (int i = 0; i < ct; i++) {
         if (getMenuComponent(i) instanceof JMenu) {  
            JMenu m = (JMenu)getMenuComponent(i);
            int mct = m.getMenuComponentCount();
            for (int j = 0; j < mct; j++) {
               if (m.getMenuComponent(j) == src) {
                  doSubmenuCommand(i+1,j+1);
                  return;
               }
            }
         }
         else if (getMenuComponent(i) == src) {
            doCommand(i+1);
            return;  
         }
      }
   }
   public void popupMenuCanceled(PopupMenuEvent evt) {
   }
   public void popupMenuWillBecomeInvisible(PopupMenuEvent evt) {
   }
   public void popupMenuWillBecomeVisible(PopupMenuEvent evt) {
      update();
   }
   //----------------------------------------------------

   /**
    * Create a menu with the specified title, which will
    * appear in the menu bar, if this is a top-level menu,
    * or as the name of the item in the parent menu, if this
    * is a sub-menu.
    */
   public xMenu(String title) {
      super(title);
      getPopupMenu().addPopupMenuListener(this);
      create();
      //getPopupMenu().setLightWeightPopupEnabled(false);
         // The previous line was needed because I turned
         // off double-buffered drawing in XPlorMath3D, and
         // if lightweight popups are used, they flicker
         // terribly during an animation.
   }
   
   /**
    * Called by the xMenu Constructor.  Subclasses can define this function
    * to, for example, add items to the menu.  (So, overriding this is an
    * alternative to defining a constructor in the subclass.)
    */
   protected void create() {
   }
   
   /**
    *  This is called when the user selects an item from the
    *  menu.  The parameter is the number of the item in the 
    *  menu, where the number of the first item is 1 (NOT ZERO!).
    *  (1-based numbering is used for campatibility with Mac menus.)
    *  The version in this class does nothing.
    */
   protected void doCommand(int itemNum) {
   }
   
   /**
    *  If a submenu has been added to the xMenu USING THE
    *  addSubmenu METHOD IN THIS CLASS, then this method is
    *  called when the user selects an item from that
    *  menu.  The first parameter is the item number of the
    *  submenu in the main menu.  The second parameter  is the
    *  item number of the selected command in the submenu.
    *  Both item numbers are 1-bases, not 0-based.  The 
    *  version in this class does nothing.
    */
   protected void doSubmenuCommand(int itemInMainMenu, int itemInSubMenu) {
   }
   
   /**
    * This is called just before the menu is to be popped up
    * on the screen.  The idea is to give you a chance to make
    * oany necessary changes to the appearance of the menu, such
    * as enabling and disabling menu items.  (Note that it is
    * NOT called before accelerators are processed!  You can call
    * it programmatically, if your program depends on changes made
    * here in the state of the window.)
    */
   public void update() {
   }
   
   /**
    * Create a basic item and add it to the menu.
    * Returns the JMenuItem that is created.  This will
    * be needed if, for exhibit, you want to change the
    * text of the item or
    */
   public JMenuItem add(String itemName) {
      JMenuItem item = super.add(itemName);
      item.addActionListener(this);  
      return item;
   }
   
   /**
    * Creates a JMenu (not xMenu) and adds it to this xMenu
    * as a submenu.  The first parameter is the text that
    * appears in this xMenu.  The second paramter is a non-empty
    * list of strings that become the items in the submenu.
    * When the user choses an item from the submenu, the
    * method doSubmenCommand in this class is called.
    * To add a separator to the submenu, use a null in
    * in the list of item names.
    */
   public JMenu addSubmenu(String title, String[] itemNames) {
      JMenu menu = new JMenu(title);
      for (int i = 0; i < itemNames.length; i++)
         if (itemNames[i] == null)
            menu.addSeparator();
         else
            menu.add(itemNames[i]).addActionListener(this);
      menu.getPopupMenu().setLightWeightPopupEnabled(false);
      add(menu);
      return menu;
   }
   
   /**
    * Create one item for each string in the array.
    */
   public void add(String[] itemNames) {
      if (itemNames != null)
         for (int i = 0; i < itemNames.length; i++)
            add(itemNames[i]);
   }
   
   /**
    *  Create an item with an accelerator.
    *  @param itemName The text that shows in the menu.
    *  @param acceleratorDescription A string that specifies
    *     the accelerator using the syntax from the class
    *     javax.swing.KeyStroke.  For exhibit:  "control A",
    *     "alt shift X", "shift INSERT".
    */
   public void add(String itemName, String acceleratorDescription) {
      JMenuItem item = super.add(itemName);
      item.addActionListener(this);  
      item.setAccelerator(KeyStroke.getKeyStroke(acceleratorDescription));
   }
   
   /**
    *  Adds a JCheckBoxMenuItem to the menu, and returns it.
    *  The menu item will trigger a call to doCommand when the
    *  user selects it.  (Checking and unchecking is taken care
    *  of automatically.)
    */
   public JCheckBoxMenuItem addCheckItem(String itemName) {
      JCheckBoxMenuItem item = new JCheckBoxMenuItem(itemName);
      add(item);
      item.addActionListener(this);
      return item;
   }
   
   /**
    * Adds an entire radio group to the menu, with one
    * JRadioButtonMenuItem for each string in the array.
    * Each menu item will trigger a call to doCommand when
    * the user selects it.  (Checking and unchecking is 
    * taken care of automatically.)  The second parameter
    * gives the zero-based index of the initially selected item.
    * If it has an illegal value, such as -1, then no
    * item is initially selected.  The return value is
    * the ButtonGroup that controls the selection.
    */
   public ButtonGroup addRadioGroup(String[] itemNames, int selected) {
      ButtonGroup group = new ButtonGroup();
      if (itemNames != null) {
         for (int i = 0; i < itemNames.length; i++) {
            JRadioButtonMenuItem item = new JRadioButtonMenuItem(itemNames[i]);
            add(item);
            item.addActionListener(this);
            group.add(item);
            if (i == selected)
               item.setSelected(true);
         }
      }
      return group;
   }
   
   /**
    * Set the item name displayed for a menu item.  The item
    * number for the first item in the menu is 1, NOT ZERO.
    */
   public void setItemName(int itemNum, String itemName) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JMenuItem)
           ((JMenuItem)c).setText(itemName);
      }
   }
   
   /**
    * Gets the item name displayed for a menu item.  The item
    * number for the first item in the menu is 1, NOT ZERO.
    * If there is no such item, null is returned.
    */
   public String getItemName(int itemNum) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JMenuItem)
           return ((JMenuItem)c).getText();
      }
      return null;
   }
   
   /**
    * Enable a specified menu item.  The item
    * number for the first item in the menu is 1, NOT ZERO.
    */
   public void enableItem(int itemNum) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JMenuItem)
           ((JMenuItem)c).setEnabled(true);
      }
   }
   
   /**
    * Disable a specified menu item.  The item
    * number for the first item in the menu is 1, NOT ZERO.
    */
   public void disableItem(int itemNum) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JMenuItem)
           ((JMenuItem)c).setEnabled(false);
      }
   }
   
   /**
    * Set the check box state of a JCheckBoxMenuItem.  
    * The item number for the first item in the menu is 1, NOT ZERO.
    * If the item number in the check box is not a JCheckBoxMenuItem,
    * then the method call is ignored.
    */
   public void setChecked(int itemNum, boolean checked) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JCheckBoxMenuItem)
           ((JCheckBoxMenuItem)c).setState(checked);
      }
   }
   
   /**
    * Get the check box state of a JCheckBoxMenuItem.  
    * The item number for the first item in the menu is 1, NOT ZERO.
    * (If the item number in the check box is not a JCheckBoxMenuItem,
    * then the return value if false.)
    */
   public boolean getChecked(int itemNum) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JCheckBoxMenuItem)
           return ((JCheckBoxMenuItem)c).getState();
      }
      return false;
   }
   
   /**
    *  Select one of a group of JRadioButtonMenuItems.  The item
    *  number for the first item in the menu is 1, NOT ZERO.
    *  If the item number does not correspond to a 
    *  JRadioButtonMenuItem, the return value is false.
    */
   public boolean getRadioState(int itemNum) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JRadioButtonMenuItem)
           return ((JRadioButtonMenuItem)c).isSelected();
      }
      return false;
   }

   /**
    *  Check the state of one of a group of JRadioButtonMenuItems.  
    *  The item number for the first item in the menu is 1, NOT ZERO.
    *  If the item number does not correspond to a 
    *  JRadioButtonMenuItem, the method call is ignored.
    *  (Note that the currently selected item in the group, if 
    *  any, is automatically de-selected.)
    */
   public void selectRadioItem(int itemNum) {
      int ct = getMenuComponentCount();
      itemNum--;  // Change from 1-based to 0-based numbering.
      if (itemNum >= 0 && itemNum < ct) {
         Component c = getMenuComponent(itemNum);
         if (c instanceof JRadioButtonMenuItem)
           ((JRadioButtonMenuItem)c).setSelected(true);
      }
   }

   
   
   
}

