001    /* JMenuBar.java --
002       Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import java.awt.Component;
042    import java.awt.Graphics;
043    import java.awt.Insets;
044    import java.awt.event.KeyEvent;
045    import java.awt.event.MouseEvent;
046    
047    import javax.accessibility.Accessible;
048    import javax.accessibility.AccessibleContext;
049    import javax.accessibility.AccessibleRole;
050    import javax.accessibility.AccessibleSelection;
051    import javax.accessibility.AccessibleStateSet;
052    import javax.swing.plaf.MenuBarUI;
053    
054    import javax.swing.border.Border;
055    
056    /**
057     * JMenuBar is a container for menu's. For a menu bar to be seen on the
058     * screen, at least one menu should be added to it. Just like adding
059     * components to container, one can use add() to add menu's to the menu bar.
060     * Menu's will be displayed in the menu  bar in the order they were added.
061     * The JMenuBar uses selectionModel to keep track of selected menu index.
062     * JMenuBar's selectionModel will fire ChangeEvents to its registered 
063     * listeners when the selected index changes.
064     */
065    public class JMenuBar extends JComponent implements Accessible, MenuElement
066    {
067      /**
068       * Provides accessibility support for <code>JMenuBar</code>.
069       * 
070       * @author Roman Kennke (kennke@aicas.com)
071       */
072      protected class AccessibleJMenuBar extends AccessibleJComponent
073        implements AccessibleSelection
074      {
075    
076        /**
077         * Returns the number of selected items in the menu bar. Possible values
078         * are <code>0</code> if nothing is selected, or <code>1</code> if one
079         * item is selected.
080         *
081         * @return the number of selected items in the menu bar
082         */
083        public int getAccessibleSelectionCount()
084        {
085          int count = 0;
086          if (getSelectionModel().getSelectedIndex() != -1)
087            count = 1;
088          return count;
089        }
090    
091        /**
092         * Returns the selected with index <code>i</code> menu, or
093         * <code>null</code> if the specified menu is not selected.
094         *
095         * @param i the index of the menu to return
096         *
097         * @return the selected with index <code>i</code> menu, or
098         *         <code>null</code> if the specified menu is not selected
099         */
100        public Accessible getAccessibleSelection(int i)
101        {
102          if (getSelectionModel().getSelectedIndex() != i)
103            return null;
104          return getMenu(i);
105        }
106    
107        /**
108         * Returns <code>true</code> if the specified menu is selected,
109         * <code>false</code> otherwise.
110         *
111         * @param i the index of the menu to check
112         *
113         *@return <code>true</code> if the specified menu is selected,
114         *        <code>false</code> otherwise
115         */
116        public boolean isAccessibleChildSelected(int i)
117        {
118          return getSelectionModel().getSelectedIndex() == i;
119        }
120    
121        /**
122         * Selects the menu with index <code>i</code>. If another menu is already
123         * selected, this will be deselected.
124         *
125         * @param i the menu to be selected
126         */
127        public void addAccessibleSelection(int i)
128        {
129          getSelectionModel().setSelectedIndex(i);
130        }
131    
132        /**
133         * Deselects the menu with index <code>i</code>.
134         *
135         * @param i the menu index to be deselected
136         */
137        public void removeAccessibleSelection(int i)
138        {
139          if (getSelectionModel().getSelectedIndex() == i)
140            getSelectionModel().clearSelection();
141        }
142    
143        /**
144         * Deselects all possibly selected menus.
145         */
146        public void clearAccessibleSelection()
147        {
148          getSelectionModel().clearSelection();
149        }
150    
151        /**
152         * In menu bars it is not possible to select all items, so this method
153         * does nothing.
154         */
155        public void selectAllAccessibleSelection()
156        {
157          // In menu bars it is not possible to select all items, so this method
158          // does nothing.
159        }
160    
161        /**
162         * Returns the accessible role of <code>JMenuBar</code>, which is
163         * {@link AccessibleRole#MENU_BAR}.
164         *
165         * @return the accessible role of <code>JMenuBar</code>, which is
166         *         {@link AccessibleRole#MENU_BAR}
167         */
168        public AccessibleRole getAccessibleRole()
169        {
170          return AccessibleRole.MENU_BAR;
171        }
172    
173        /**
174         * Returns the <code>AccessibleSelection</code> for this object. This
175         * method returns <code>this</code>, since the
176         * <code>AccessibleJMenuBar</code> manages its selection itself.
177         *
178         * @return the <code>AccessibleSelection</code> for this object
179         */
180        public AccessibleSelection getAccessibleSelection()
181        {
182          return this;
183        }
184    
185        /**
186         * Returns the state of this <code>AccessibleJMenuBar</code>.
187         *
188         * @return the state of this <code>AccessibleJMenuBar</code>.
189         */
190        public AccessibleStateSet getAccessibleStateSet()
191        {
192          AccessibleStateSet stateSet = super.getAccessibleStateSet();
193          // TODO: Figure out what state must be added to the super state set.
194          return stateSet;
195        }
196      }
197    
198      private static final long serialVersionUID = -8191026883931977036L;
199    
200      /** JMenuBar's model. It keeps track of selected menu's index */
201      private transient SingleSelectionModel selectionModel;
202    
203      /* borderPainted property indicating if the menuBar's border will be painted*/
204      private boolean borderPainted;
205    
206      /* margin between menu bar's border and its menues*/
207      private Insets margin;
208    
209      /**
210       * Creates a new JMenuBar object.
211       */
212      public JMenuBar()
213      {
214        selectionModel = new DefaultSingleSelectionModel();
215        borderPainted = true;
216        updateUI();
217      }
218    
219      /**
220       * Adds menu to the menu bar
221       *
222       * @param c menu to add
223       *
224       * @return reference to the added menu
225       */
226      public JMenu add(JMenu c)
227      {
228        c.setAlignmentX(Component.LEFT_ALIGNMENT);
229        super.add(c);
230        return c;
231      }
232    
233      /**
234       * This method overrides addNotify() in the Container to register
235       * this menu bar with the current keyboard manager.
236       */
237      public void addNotify()
238      {
239        super.addNotify();
240        KeyboardManager.getManager().registerJMenuBar(this);
241      }
242    
243      public AccessibleContext getAccessibleContext()
244      {
245        if (accessibleContext == null)
246          accessibleContext = new AccessibleJMenuBar();
247        return accessibleContext;
248      }
249    
250      /**
251       * Returns reference to this menu bar
252       *
253       * @return reference to this menu bar
254       */
255      public Component getComponent()
256      {
257        return this;
258      }
259    
260      /**
261       * Returns component at the specified index.
262       *
263       * @param i index of the component to get
264       *
265       * @return component at the specified index. Null is returned if
266       * component at the specified index doesn't exist.
267       * @deprecated Replaced by getComponent(int)
268       */
269      public Component getComponentAtIndex(int i)
270      {
271        return getComponent(i);
272      }
273    
274      /**
275       * Returns index of the specified component
276       *
277       * @param c Component to search for
278       *
279       * @return index of the specified component. -1 is returned if
280       * specified component doesnt' exist in the menu bar.
281       */
282      public int getComponentIndex(Component c)
283      {
284        Component[] comps = getComponents();
285    
286        int index = -1;
287    
288        for (int i = 0; i < comps.length; i++)
289          {
290            if (comps[i].equals(c))
291              {
292                index = i;
293                break;
294              }
295          }
296    
297        return index;
298      }
299    
300      /**
301       * This method is not implemented and will throw an {@link Error} if called.
302       *
303       * @return This method never returns anything, it throws an exception.
304       */
305      public JMenu getHelpMenu()
306      {
307        // the following error matches the behaviour of the reference 
308        // implementation...
309        throw new Error("getHelpMenu() is not implemented");
310      }
311    
312      /**
313       * Returns the margin between the menu bar's border and its menus.  If the
314       * margin is <code>null</code>, this method returns 
315       * <code>new Insets(0, 0, 0, 0)</code>.
316       *
317       * @return The margin (never <code>null</code>).
318       * 
319       * @see #setMargin(Insets)
320       */
321      public Insets getMargin()
322      {
323        if (margin == null)
324          return new Insets(0, 0, 0, 0);
325        else
326          return margin;
327      }
328    
329      /**
330       * Return menu at the specified index. If component at the
331       * specified index is not a menu, then null is returned.
332       *
333       * @param index index to look for the menu
334       *
335       * @return menu at specified index, or null if menu doesn't exist
336       * at the specified index.
337       */
338      public JMenu getMenu(int index)
339      {
340        if (getComponentAtIndex(index) instanceof JMenu)
341          return (JMenu) getComponentAtIndex(index);
342        else
343          return null;
344      }
345    
346      /**
347       * Returns number of menu's in this menu bar
348       *
349       * @return number of menu's in this menu bar
350       */
351      public int getMenuCount()
352      {
353        return getComponentCount();
354      }
355    
356      /**
357       * Returns selection model for this menu bar. SelectionModel
358       * keeps track of the selected menu in the menu bar. Whenever
359       * selected property of selectionModel changes, the ChangeEvent
360       * will be fired its ChangeListeners.
361       *
362       * @return selection model for this menu bar.
363       */
364      public SingleSelectionModel getSelectionModel()
365      {
366        return selectionModel;
367      }
368    
369      /**
370       * Method of MenuElement interface. It returns subcomponents
371       * of the menu bar, which are all the menues that it contains.
372       *
373       * @return MenuElement[] array containing menues in this menu bar
374       */
375      public MenuElement[] getSubElements()
376      {
377        MenuElement[] subElements = new MenuElement[getComponentCount()];
378    
379        int j = 0;
380        boolean doResize = false;
381        MenuElement menu;
382        for (int i = 0; i < getComponentCount(); i++)
383          {
384            menu = getMenu(i);
385            if (menu != null)
386              {
387                subElements[j++] = (MenuElement) menu;
388              }
389            else
390              doResize = true;
391          }
392    
393        if (! doResize)
394          return subElements;
395        else
396          {
397            MenuElement[] subElements2 = new MenuElement[j];
398            for (int i = 0; i < j; i++)
399              subElements2[i] = subElements[i];
400    
401            return subElements2;
402          }
403      }
404    
405      /**
406        * Set the "UI" property of the menu bar, which is a look and feel class
407        * responsible for handling the menuBar's input events and painting it.
408        *
409        * @return The current "UI" property
410        */
411      public MenuBarUI getUI()
412      {
413        return (MenuBarUI) ui;
414      }
415    
416      /**
417       * This method returns a name to identify which look and feel class will be
418       * the UI delegate for the menu bar.
419       *
420       * @return The Look and Feel classID. "MenuBarUI"
421       */
422      public String getUIClassID()
423      {
424        return "MenuBarUI";
425      }
426    
427      /**
428       * Returns true if menu bar paints its border and false otherwise
429       *
430       * @return true if menu bar paints its border and false otherwise
431       */
432      public boolean isBorderPainted()
433      {
434        return borderPainted;
435      }
436    
437      /**
438       * Returns true if some menu in menu bar is selected.
439       *
440       * @return true if some menu in menu bar is selected and false otherwise
441       */
442      public boolean isSelected()
443      {
444        return selectionModel.isSelected();
445      }
446    
447      /**
448       * This method does nothing by default. This method is need for the
449       * MenuElement interface to be implemented.
450       *
451       * @param isIncluded true if menuBar is included in the selection
452       * and false otherwise
453       */
454      public void menuSelectionChanged(boolean isIncluded)
455      {
456        // Do nothing - needed for implementation of MenuElement interface
457      }
458    
459      /**
460       * Paints border of the menu bar, if its borderPainted property is set to
461       * true.
462       *
463       * @param g The graphics context with which to paint the border
464       */
465      protected void paintBorder(Graphics g)
466      {
467        if (borderPainted)
468          {
469            Border border = getBorder();
470            if (border != null)
471              getBorder().paintBorder(this, g, 0, 0, getSize(null).width,
472                                      getSize(null).height);
473          }
474      }
475    
476      /**
477       * A string that describes this JMenuBar. Normally only used
478       * for debugging.
479       *
480       * @return A string describing this JMenuBar
481       */
482      protected String paramString()
483      {
484        StringBuffer sb = new StringBuffer();
485        sb.append(super.paramString());
486        sb.append(",margin=");
487        if (getMargin() != null)
488          sb.append(getMargin());
489        sb.append(",paintBorder=").append(isBorderPainted());
490        return sb.toString();
491      }
492    
493      /**
494       * Process key events forwarded from MenuSelectionManager. This method
495       * doesn't do anything. It is here to conform to the MenuElement interface.
496       *
497       * @param e event forwarded from MenuSelectionManager
498       * @param path path to the menu element from which event was generated
499       * @param manager MenuSelectionManager for the current menu hierarchy
500       *
501       */
502      public void processKeyEvent(KeyEvent e, MenuElement[] path,
503                                  MenuSelectionManager manager)
504      {
505        // Do nothing - needed for implementation of MenuElement interface
506      }
507    
508      /**
509       * This method overrides JComponent.processKeyBinding to allow the 
510       * JMenuBar to check all the child components (recursiveley) to see 
511       * if they'll consume the event.
512       * 
513       * @param ks the KeyStroke for the event
514       * @param e the KeyEvent for the event
515       * @param condition the focus condition for the binding
516       * @param pressed true if the key is pressed 
517       */
518      protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition,
519                                          boolean pressed)
520      {
521        // See if the regular JComponent behavior consumes the event
522        if (super.processKeyBinding(ks, e, condition, pressed))
523          return true;
524        
525        // If not, have to recursively check all the child menu elements to see 
526        // if they want it    
527        MenuElement[] children = getSubElements();
528        for (int i = 0; i < children.length; i++)
529          if (processKeyBindingHelper(children[i], ks, e, condition, pressed))
530            return true;
531        return false;
532      }
533      
534      /**
535       * This is a helper method to recursively check the children of this
536       * JMenuBar to see if they will consume a key event via key bindings.  
537       * This is used for menu accelerators.
538       * @param menuElement the menuElement to check (and check all its children)
539       * @param ks the KeyStroke for the event
540       * @param e the KeyEvent that may be consumed
541       * @param condition the focus condition for the binding
542       * @param pressed true if the key was pressed
543       * @return true <code>menuElement</code> or one of its children consume
544       * the event (processKeyBinding returns true for menuElement or one of
545       * its children).
546       */
547      static boolean processKeyBindingHelper(MenuElement menuElement, KeyStroke ks,
548                                             KeyEvent e, int condition,
549                                             boolean pressed)
550      {
551        if (menuElement == null)
552          return false;
553    
554        // First check the menuElement itself, if it's a JComponent
555        if (menuElement instanceof JComponent
556            && ((JComponent) menuElement).processKeyBinding(ks, e, condition,
557                                                            pressed))
558          return true;
559        
560        // If that didn't consume it, check all the children recursively
561        MenuElement[] children = menuElement.getSubElements();
562        for (int i = 0; i < children.length; i++)
563          if (processKeyBindingHelper(children[i], ks, e, condition, pressed))
564            return true;
565        return false;
566      }
567      
568      /**
569       * Process mouse events forwarded from MenuSelectionManager. This method
570       * doesn't do anything. It is here to conform to the MenuElement interface.
571       *
572       * @param event event forwarded from MenuSelectionManager
573       * @param path path to the menu element from which event was generated
574       * @param manager MenuSelectionManager for the current menu hierarchy
575       *
576       */
577      public void processMouseEvent(MouseEvent event, MenuElement[] path,
578                                    MenuSelectionManager manager)
579      {
580        // Do nothing - needed for implementation of MenuElement interface
581      }
582    
583      /**
584       * This method overrides removeNotify() in the Container to
585       * unregister this menu bar from the current keyboard manager.
586       */
587      public void removeNotify()
588      {
589        KeyboardManager.getManager().unregisterJMenuBar(this);
590        super.removeNotify();
591      }
592    
593      /**
594       * Sets painting status of the border. If 'b' is true then menu bar's
595       * border will be painted, and it will not be painted otherwise.
596       *
597       * @param b indicates if menu bar's border should be painted.
598       */
599      public void setBorderPainted(boolean b)
600      {
601        if (b != borderPainted)
602          {
603            boolean old = borderPainted;
604            borderPainted = b;
605            firePropertyChange("borderPainted", old, b);
606            revalidate();
607            repaint();
608          }
609      }
610    
611      /**
612       * Sets help menu for this menu bar
613       *
614       * @param menu help menu
615       *
616       * @specnote The specification states that this method is not yet implemented
617       *           and should throw an exception.
618       */
619      public void setHelpMenu(JMenu menu)
620      {
621        // We throw an Error here, just as Sun's JDK does.
622        throw new Error("setHelpMenu() not yet implemented.");
623      }
624    
625      /**
626       * Sets the margin between the menu bar's border and its menus (this is a
627       * bound property with the name 'margin').
628       *
629       * @param m  the margin (<code>null</code> permitted).
630       * 
631       * @see #getMargin()
632       */
633      public void setMargin(Insets m)
634      {
635        if (m != margin)
636          {
637            Insets oldMargin = margin;
638            margin = m;
639            firePropertyChange("margin", oldMargin, margin);
640          }
641      }
642    
643      /**
644       * Changes menu bar's selection to the specified menu.
645       * This method updates selected index of menu bar's selection model,
646       * which results in a model firing change event.
647       *
648       * @param sel menu to select
649       */
650      public void setSelected(Component sel)
651      {
652        int index = getComponentIndex(sel);
653        selectionModel.setSelectedIndex(index);
654      }
655    
656      /**
657       * Sets menuBar's selection model to the one specified
658       *
659       * @param model SingleSelectionModel that needs to be set for this menu bar
660       */
661      public void setSelectionModel(SingleSelectionModel model)
662      {
663        if (selectionModel != model)
664          {
665            SingleSelectionModel oldModel = selectionModel;
666            selectionModel = model;
667            firePropertyChange("model", oldModel, selectionModel);
668          }
669      }
670    
671      /**
672       * Set the "UI" property of the menu bar, which is a look and feel class
673       * responsible for handling menuBar's input events and painting it.
674       *
675       * @param ui The new "UI" property
676       */
677      public void setUI(MenuBarUI ui)
678      {
679        super.setUI(ui);
680      }
681    
682      /**
683       * Set the "UI" property to a class constructed, via the {@link
684       * UIManager}, from the current look and feel.
685       */
686      public void updateUI()
687      {
688        setUI((MenuBarUI) UIManager.getUI(this));
689      }
690    }