001 /* JMenu.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.Dimension;
043 import java.awt.GraphicsConfiguration;
044 import java.awt.GraphicsDevice;
045 import java.awt.GraphicsEnvironment;
046 import java.awt.Insets;
047 import java.awt.Point;
048 import java.awt.Rectangle;
049 import java.awt.Toolkit;
050 import java.awt.event.KeyEvent;
051 import java.awt.event.WindowAdapter;
052 import java.awt.event.WindowEvent;
053 import java.beans.PropertyChangeEvent;
054 import java.beans.PropertyChangeListener;
055 import java.io.Serializable;
056 import java.util.ArrayList;
057 import java.util.EventListener;
058
059 import javax.accessibility.Accessible;
060 import javax.accessibility.AccessibleContext;
061 import javax.accessibility.AccessibleRole;
062 import javax.accessibility.AccessibleSelection;
063 import javax.swing.event.ChangeEvent;
064 import javax.swing.event.ChangeListener;
065 import javax.swing.event.MenuEvent;
066 import javax.swing.event.MenuListener;
067 import javax.swing.plaf.MenuItemUI;
068
069 /**
070 * This class represents a menu that can be added to a menu bar or
071 * can be a submenu in some other menu. When JMenu is selected it
072 * displays JPopupMenu containing its menu items.
073 *
074 * <p>
075 * JMenu's fires MenuEvents when this menu's selection changes. If this menu
076 * is selected, then fireMenuSelectedEvent() is invoked. In case when menu is
077 * deselected or cancelled, then fireMenuDeselectedEvent() or
078 * fireMenuCancelledEvent() is invoked, respectivelly.
079 * </p>
080 */
081 public class JMenu extends JMenuItem implements Accessible, MenuElement
082 {
083 /**
084 * Receives notifications when the JMenu's ButtonModel is changed and
085 * fires menuSelected or menuDeselected events when appropriate.
086 */
087 private class MenuChangeListener
088 implements ChangeListener
089 {
090 /**
091 * Indicates the last selected state.
092 */
093 private boolean selected;
094
095 /**
096 * Receives notification when the JMenu's ButtonModel changes.
097 */
098 public void stateChanged(ChangeEvent ev)
099 {
100 ButtonModel m = (ButtonModel) ev.getSource();
101 boolean s = m.isSelected();
102 if (s != selected)
103 {
104 if (s)
105 fireMenuSelected();
106 else
107 fireMenuDeselected();
108 selected = s;
109 }
110 }
111 }
112
113 private static final long serialVersionUID = 4227225638931828014L;
114
115 /** A Popup menu associated with this menu, which pops up when menu is selected */
116 private JPopupMenu popupMenu = null;
117
118 /** Whenever menu is selected or deselected the MenuEvent is fired to
119 menu's registered listeners. */
120 private MenuEvent menuEvent = new MenuEvent(this);
121
122 /*Amount of time, in milliseconds, that should pass before popupMenu
123 associated with this menu appears or disappers */
124 private int delay;
125
126 /* PopupListener */
127 protected WinListener popupListener;
128
129 /**
130 * Location at which popup menu associated with this menu will be
131 * displayed
132 */
133 private Point menuLocation;
134
135 /**
136 * The ChangeListener for the ButtonModel.
137 *
138 * @see MenuChangeListener
139 */
140 private ChangeListener menuChangeListener;
141
142 /**
143 * Creates a new JMenu object.
144 */
145 public JMenu()
146 {
147 super();
148 setOpaque(false);
149 }
150
151 /**
152 * Creates a new <code>JMenu</code> with the specified label.
153 *
154 * @param text label for this menu
155 */
156 public JMenu(String text)
157 {
158 super(text);
159 popupMenu = new JPopupMenu();
160 popupMenu.setInvoker(this);
161 setOpaque(false);
162 }
163
164 /**
165 * Creates a new <code>JMenu</code> object.
166 *
167 * @param action Action that is used to create menu item tha will be
168 * added to the menu.
169 */
170 public JMenu(Action action)
171 {
172 super(action);
173 createActionChangeListener(this);
174 popupMenu = new JPopupMenu();
175 popupMenu.setInvoker(this);
176 setOpaque(false);
177 }
178
179 /**
180 * Creates a new <code>JMenu</code> with specified label and an option
181 * for this menu to be tear-off menu.
182 *
183 * @param text label for this menu
184 * @param tearoff true if this menu should be tear-off and false otherwise
185 */
186 public JMenu(String text, boolean tearoff)
187 {
188 // FIXME: tearoff not implemented
189 this(text);
190 }
191
192 /**
193 * Adds specified menu item to this menu
194 *
195 * @param item Menu item to add to this menu
196 *
197 * @return Menu item that was added
198 */
199 public JMenuItem add(JMenuItem item)
200 {
201 return getPopupMenu().add(item);
202 }
203
204 /**
205 * Adds specified component to this menu.
206 *
207 * @param component Component to add to this menu
208 *
209 * @return Component that was added
210 */
211 public Component add(Component component)
212 {
213 getPopupMenu().insert(component, -1);
214 return component;
215 }
216
217 /**
218 * Adds specified component to this menu at the given index
219 *
220 * @param component Component to add
221 * @param index Position of this menu item in the menu
222 *
223 * @return Component that was added
224 */
225 public Component add(Component component, int index)
226 {
227 return getPopupMenu().add(component, index);
228 }
229
230 /**
231 * Adds JMenuItem constructed with the specified label to this menu
232 *
233 * @param text label for the menu item that will be added
234 *
235 * @return Menu Item that was added to this menu
236 */
237 public JMenuItem add(String text)
238 {
239 return add(new JMenuItem(text));
240 }
241
242 /**
243 * Adds JMenuItem constructed using properties from specified action.
244 *
245 * @param action action to construct the menu item with
246 *
247 * @return Menu Item that was added to this menu
248 */
249 public JMenuItem add(Action action)
250 {
251 JMenuItem i = createActionComponent(action);
252 i.setAction(action);
253 add(i);
254 return i;
255 }
256
257 /**
258 * Removes given menu item from this menu. Nothing happens if
259 * this menu doesn't contain specified menu item.
260 *
261 * @param item Menu Item which needs to be removed
262 */
263 public void remove(JMenuItem item)
264 {
265 getPopupMenu().remove(item);
266 }
267
268 /**
269 * Removes component at the specified index from this menu
270 *
271 * @param index Position of the component that needs to be removed in the menu
272 */
273 public void remove(int index)
274 {
275 if (index < 0 || (index > 0 && getMenuComponentCount() == 0))
276 throw new IllegalArgumentException();
277
278 if (getMenuComponentCount() > 0)
279 popupMenu.remove(index);
280 }
281
282 /**
283 * Removes given component from this menu.
284 *
285 * @param component Component to remove
286 */
287 public void remove(Component component)
288 {
289 int index = getPopupMenu().getComponentIndex(component);
290 if (index >= 0)
291 getPopupMenu().remove(index);
292 }
293
294 /**
295 * Removes all menu items from the menu
296 */
297 public void removeAll()
298 {
299 if (popupMenu != null)
300 popupMenu.removeAll();
301 }
302
303 /**
304 * Creates JMenuItem with the specified text and inserts it in the
305 * at the specified index
306 *
307 * @param text label for the new menu item
308 * @param index index at which to insert newly created menu item.
309 */
310 public void insert(String text, int index)
311 {
312 this.insert(new JMenuItem(text), index);
313 }
314
315 /**
316 * Creates JMenuItem with the specified text and inserts it in the
317 * at the specified index. IllegalArgumentException is thrown
318 * if index is less than 0
319 *
320 * @param item menu item to insert
321 * @param index index at which to insert menu item.
322 * @return Menu item that was added to the menu
323 */
324 public JMenuItem insert(JMenuItem item, int index)
325 {
326 if (index < 0)
327 throw new IllegalArgumentException("index less than zero");
328
329 getPopupMenu().insert(item, index);
330 return item;
331 }
332
333 /**
334 * Creates JMenuItem with the associated action and inserts it to the menu
335 * at the specified index. IllegalArgumentException is thrown
336 * if index is less than 0
337 *
338 * @param action Action for the new menu item
339 * @param index index at which to insert newly created menu item.
340 * @return Menu item that was added to the menu
341 */
342 public JMenuItem insert(Action action, int index)
343 {
344 JMenuItem item = new JMenuItem(action);
345 this.insert(item, index);
346
347 return item;
348 }
349
350 /**
351 * This method sets this menuItem's UI to the UIManager's default for the
352 * current look and feel.
353 */
354 public void updateUI()
355 {
356 setUI((MenuItemUI) UIManager.getUI(this));
357 }
358
359 /**
360 * This method returns a name to identify which look and feel class will be
361 * the UI delegate for the menu.
362 *
363 * @return The Look and Feel classID. "MenuUI"
364 */
365 public String getUIClassID()
366 {
367 return "MenuUI";
368 }
369
370 /**
371 * Sets model for this menu.
372 *
373 * @param model model to set
374 */
375 public void setModel(ButtonModel model)
376 {
377 ButtonModel oldModel = getModel();
378 if (oldModel != null && menuChangeListener != null)
379 oldModel.removeChangeListener(menuChangeListener);
380
381 super.setModel(model);
382
383 if (model != null)
384 {
385 if (menuChangeListener == null)
386 menuChangeListener = new MenuChangeListener();
387 model.addChangeListener(menuChangeListener);
388 }
389 }
390
391 /**
392 * Returns true if the menu is selected and false otherwise
393 *
394 * @return true if the menu is selected and false otherwise
395 */
396 public boolean isSelected()
397 {
398 return super.isSelected();
399 }
400
401 /**
402 * Changes this menu selected state if selected is true and false otherwise
403 * This method fires menuEvents to menu's registered listeners.
404 *
405 * @param selected true if the menu should be selected and false otherwise
406 */
407 public void setSelected(boolean selected)
408 {
409 ButtonModel m = getModel();
410 if (selected != m.isSelected())
411 m.setSelected(selected);
412 }
413
414 /**
415 * Checks if PopupMenu associated with this menu is visible
416 *
417 * @return true if the popup associated with this menu is currently visible
418 * on the screen and false otherwise.
419 */
420 public boolean isPopupMenuVisible()
421 {
422 return getPopupMenu().isVisible();
423 }
424
425 /**
426 * Sets popup menu visibility
427 *
428 * @param popup true if popup should be visible and false otherwise
429 */
430 public void setPopupMenuVisible(boolean popup)
431 {
432 if (popup != isPopupMenuVisible() && (isEnabled() || ! popup))
433 {
434 if (popup && isShowing())
435 {
436 // Set location as determined by getPopupLocation().
437 Point loc = menuLocation == null ? getPopupMenuOrigin()
438 : menuLocation;
439 getPopupMenu().show(this, loc.x, loc.y);
440 }
441 else
442 getPopupMenu().setVisible(false);
443 }
444 }
445
446 /**
447 * Returns origin point of the popup menu. This takes the screen bounds
448 * into account and places the popup where it fits best.
449 *
450 * @return the origin of the popup menu
451 */
452 protected Point getPopupMenuOrigin()
453 {
454 // The menu's screen location and size.
455 Point screenLoc = getLocationOnScreen();
456 Dimension size = getSize();
457
458 // Determine the popup's size.
459 JPopupMenu popup = getPopupMenu();
460 Dimension popupSize = popup.getSize();
461 if (popupSize.width == 0 || popupSize.height == 0)
462 popupSize = popup.getPreferredSize();
463
464 // Determine screen bounds.
465 Toolkit tk = Toolkit.getDefaultToolkit();
466 Rectangle screenBounds = new Rectangle(tk.getScreenSize());
467 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
468 GraphicsDevice gd = ge.getDefaultScreenDevice();
469 GraphicsConfiguration gc = gd.getDefaultConfiguration();
470 Insets screenInsets = tk.getScreenInsets(gc);
471 screenBounds.x -= screenInsets.left;
472 screenBounds.width -= screenInsets.left + screenInsets.right;
473 screenBounds.y -= screenInsets.top;
474 screenBounds.height -= screenInsets.top + screenInsets.bottom;
475 screenLoc.x -= screenInsets.left;
476 screenLoc.y -= screenInsets.top;
477
478 Point point = new Point();
479 if (isTopLevelMenu())
480 {
481 // If menu in the menu bar.
482 int xOffset = UIManager.getInt("Menu.menuPopupOffsetX");
483 int yOffset = UIManager.getInt("Menu.menuPopupOffsetY");
484 // Determine X location.
485 if (getComponentOrientation().isLeftToRight())
486 {
487 // Prefer popup to the right.
488 point.x = xOffset;
489 // Check if it fits, otherwise place popup wherever it fits.
490 if (screenLoc.x + point.x + popupSize.width
491 > screenBounds.width + screenBounds.width
492 && screenBounds.width - size.width
493 < 2 * (screenLoc.x - screenBounds.x))
494 // Popup to the right if there's not enough room.
495 point.x = size.width - xOffset - popupSize.width;
496 }
497 else
498 {
499 // Prefer popup to the left.
500 point.x = size.width - xOffset - popupSize.width;
501 if (screenLoc.x + point.x < screenBounds.x
502 && screenBounds.width - size.width
503 > 2 * (screenLoc.x - screenBounds.x))
504 // Popup to the left if there's not enough room.
505 point.x = xOffset;
506 }
507 // Determine Y location. Prefer popping down.
508 point.y = size.height + yOffset;
509 if (screenLoc.y + point.y + popupSize.height >= screenBounds.height
510 && screenBounds.height - size.height
511 < 2 * (screenLoc.y - screenBounds.y))
512 // Position above if there's not enough room below.
513 point.y = - yOffset - popupSize.height;
514 }
515 else
516 {
517 // If submenu.
518 int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX");
519 int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY");
520 // Determine X location.
521 if (getComponentOrientation().isLeftToRight())
522 {
523 // Prefer popup to the right.
524 point.x = size.width + xOffset;
525 if (screenLoc.x + point.x + popupSize.width
526 >= screenBounds.x + screenBounds.width
527 && screenBounds.width - size.width
528 < 2 * (screenLoc.x - screenBounds.x))
529 // Position to the left if there's not enough room on the right.
530 point.x = - xOffset - popupSize.width;
531 }
532 else
533 {
534 // Prefer popup on the left side.
535 point.x = - xOffset - popupSize.width;
536 if (screenLoc.x + point.x < screenBounds.x
537 && screenBounds.width - size.width
538 > 2 * (screenLoc.x - screenBounds.x))
539 // Popup to the right if there's not enough room.
540 point.x = size.width + xOffset;
541 }
542 // Determine Y location. Prefer popping down.
543 point.y = yOffset;
544 if (screenLoc.y + point.y + popupSize.height
545 >= screenBounds.y + screenBounds.height
546 && screenBounds.height - size.height
547 < 2 * (screenLoc.y - screenBounds.y))
548 // Pop up if there's not enough room below.
549 point.y = size.height - yOffset - popupSize.height;
550 }
551 return point;
552 }
553
554 /**
555 * Returns delay property.
556 *
557 * @return delay property, indicating number of milliseconds before
558 * popup menu associated with the menu appears or disappears after
559 * menu was selected or deselected respectively
560 */
561 public int getDelay()
562 {
563 return delay;
564 }
565
566 /**
567 * Sets delay property for this menu. If given time for the delay
568 * property is negative, then IllegalArgumentException is thrown
569 *
570 * @param delay number of milliseconds before
571 * popup menu associated with the menu appears or disappears after
572 * menu was selected or deselected respectively
573 */
574 public void setDelay(int delay)
575 {
576 if (delay < 0)
577 throw new IllegalArgumentException("delay less than 0");
578 this.delay = delay;
579 }
580
581 /**
582 * Sets location at which popup menu should be displayed
583 * The location given is relative to this menu item
584 *
585 * @param x x-coordinate of the menu location
586 * @param y y-coordinate of the menu location
587 */
588 public void setMenuLocation(int x, int y)
589 {
590 menuLocation = new Point(x, y);
591 if (popupMenu != null)
592 popupMenu.setLocation(x, y);
593 }
594
595 /**
596 * Creates and returns JMenuItem associated with the given action
597 *
598 * @param action Action to use for creation of JMenuItem
599 *
600 * @return JMenuItem that was creted with given action
601 */
602 protected JMenuItem createActionComponent(Action action)
603 {
604 return new JMenuItem(action);
605 }
606
607 /**
608 * Creates ActionChangeListener to listen for PropertyChangeEvents occuring
609 * in the action that is associated with this menu
610 *
611 * @param item menu that contains action to listen to
612 *
613 * @return The PropertyChangeListener
614 */
615 protected PropertyChangeListener createActionChangeListener(JMenuItem item)
616 {
617 return new ActionChangedListener(item);
618 }
619
620 /**
621 * Adds separator to the end of the menu items in the menu.
622 */
623 public void addSeparator()
624 {
625 getPopupMenu().addSeparator();
626 }
627
628 /**
629 * Inserts separator in the menu at the specified index.
630 *
631 * @param index Index at which separator should be inserted
632 */
633 public void insertSeparator(int index)
634 {
635 if (index < 0)
636 throw new IllegalArgumentException("index less than 0");
637
638 getPopupMenu().insert(new JPopupMenu.Separator(), index);
639 }
640
641 /**
642 * Returns menu item located at the specified index in the menu
643 *
644 * @param index Index at which to look for the menu item
645 *
646 * @return menu item located at the specified index in the menu
647 */
648 public JMenuItem getItem(int index)
649 {
650 if (index < 0)
651 throw new IllegalArgumentException("index less than 0");
652
653 if (getItemCount() == 0)
654 return null;
655
656 Component c = popupMenu.getComponentAtIndex(index);
657
658 if (c instanceof JMenuItem)
659 return (JMenuItem) c;
660 else
661 return null;
662 }
663
664 /**
665 * Returns number of items in the menu including separators.
666 *
667 * @return number of items in the menu
668 *
669 * @see #getMenuComponentCount()
670 */
671 public int getItemCount()
672 {
673 return getMenuComponentCount();
674 }
675
676 /**
677 * Checks if this menu is a tear-off menu.
678 *
679 * @return true if this menu is a tear-off menu and false otherwise
680 */
681 public boolean isTearOff()
682 {
683 // NOT YET IMPLEMENTED
684 throw new Error("The method isTearOff() has not yet been implemented.");
685 }
686
687 /**
688 * Returns number of menu components in this menu
689 *
690 * @return number of menu components in this menu
691 */
692 public int getMenuComponentCount()
693 {
694 return getPopupMenu().getComponentCount();
695 }
696
697 /**
698 * Returns menu component located at the givent index
699 * in the menu
700 *
701 * @param index index at which to get the menu component in the menu
702 *
703 * @return Menu Component located in the menu at the specified index
704 */
705 public Component getMenuComponent(int index)
706 {
707 if (getPopupMenu() == null || getMenuComponentCount() == 0)
708 return null;
709
710 return popupMenu.getComponentAtIndex(index);
711 }
712
713 /**
714 * Return components belonging to this menu
715 *
716 * @return components belonging to this menu
717 */
718 public Component[] getMenuComponents()
719 {
720 return getPopupMenu().getComponents();
721 }
722
723 /**
724 * Checks if this menu is a top level menu. The menu is top
725 * level menu if it is inside the menu bar. While if the menu
726 * inside some other menu, it is considered to be a pull-right menu.
727 *
728 * @return true if this menu is top level menu, and false otherwise
729 */
730 public boolean isTopLevelMenu()
731 {
732 return getParent() instanceof JMenuBar;
733 }
734
735 /**
736 * Checks if given component exists in this menu. The submenus of
737 * this menu are checked as well
738 *
739 * @param component Component to look for
740 *
741 * @return true if the given component exists in this menu, and false otherwise
742 */
743 public boolean isMenuComponent(Component component)
744 {
745 return false;
746 }
747
748 /**
749 * Returns popup menu associated with the menu.
750 *
751 * @return popup menu associated with the menu.
752 */
753 public JPopupMenu getPopupMenu()
754 {
755 if (popupMenu == null)
756 {
757 popupMenu = new JPopupMenu();
758 popupMenu.setInvoker(this);
759 }
760 return popupMenu;
761 }
762
763 /**
764 * Adds MenuListener to the menu
765 *
766 * @param listener MenuListener to add
767 */
768 public void addMenuListener(MenuListener listener)
769 {
770 listenerList.add(MenuListener.class, listener);
771 }
772
773 /**
774 * Removes MenuListener from the menu
775 *
776 * @param listener MenuListener to remove
777 */
778 public void removeMenuListener(MenuListener listener)
779 {
780 listenerList.remove(MenuListener.class, listener);
781 }
782
783 /**
784 * Returns all registered <code>MenuListener</code> objects.
785 *
786 * @return an array of listeners
787 *
788 * @since 1.4
789 */
790 public MenuListener[] getMenuListeners()
791 {
792 return (MenuListener[]) listenerList.getListeners(MenuListener.class);
793 }
794
795 /**
796 * This method fires MenuEvents to all menu's MenuListeners. In this case
797 * menuSelected() method of MenuListeners is called to indicated that the menu
798 * was selected.
799 */
800 protected void fireMenuSelected()
801 {
802 MenuListener[] listeners = getMenuListeners();
803
804 for (int index = 0; index < listeners.length; ++index)
805 listeners[index].menuSelected(menuEvent);
806 }
807
808 /**
809 * This method fires MenuEvents to all menu's MenuListeners. In this case
810 * menuDeselected() method of MenuListeners is called to indicated that the menu
811 * was deselected.
812 */
813 protected void fireMenuDeselected()
814 {
815 EventListener[] ll = listenerList.getListeners(MenuListener.class);
816
817 for (int i = 0; i < ll.length; i++)
818 ((MenuListener) ll[i]).menuDeselected(menuEvent);
819 }
820
821 /**
822 * This method fires MenuEvents to all menu's MenuListeners. In this case
823 * menuSelected() method of MenuListeners is called to indicated that the menu
824 * was cancelled. The menu is cancelled when it's popup menu is close without selection.
825 */
826 protected void fireMenuCanceled()
827 {
828 EventListener[] ll = listenerList.getListeners(MenuListener.class);
829
830 for (int i = 0; i < ll.length; i++)
831 ((MenuListener) ll[i]).menuCanceled(menuEvent);
832 }
833
834 /**
835 * Creates WinListener that listens to the menu;s popup menu.
836 *
837 * @param popup JPopupMenu to listen to
838 *
839 * @return The WinListener
840 */
841 protected WinListener createWinListener(JPopupMenu popup)
842 {
843 return new WinListener(popup);
844 }
845
846 /**
847 * Method of the MenuElementInterface. It reacts to the selection
848 * changes in the menu. If this menu was selected, then it
849 * displayes popup menu associated with it and if this menu was
850 * deselected it hides the popup menu.
851 *
852 * @param changed true if the menu was selected and false otherwise
853 */
854 public void menuSelectionChanged(boolean changed)
855 {
856 // if this menu selection is true, then activate this menu and
857 // display popup associated with this menu
858 setSelected(changed);
859 }
860
861 /**
862 * Method of MenuElement interface. Returns sub components of
863 * this menu.
864 *
865 * @return array containing popupMenu that is associated with this menu
866 */
867 public MenuElement[] getSubElements()
868 {
869 return new MenuElement[] { popupMenu };
870 }
871
872 /**
873 * @return Returns reference to itself
874 */
875 public Component getComponent()
876 {
877 return this;
878 }
879
880 /**
881 * This method is overriden with empty implementation, s.t the
882 * accelerator couldn't be set for the menu. The mnemonic should
883 * be used for the menu instead.
884 *
885 * @param keystroke accelerator for this menu
886 */
887 public void setAccelerator(KeyStroke keystroke)
888 {
889 throw new Error("setAccelerator() is not defined for JMenu. Use setMnemonic() instead.");
890 }
891
892 /**
893 * This method process KeyEvent occuring when the menu is visible
894 *
895 * @param event The KeyEvent
896 */
897 protected void processKeyEvent(KeyEvent event)
898 {
899 MenuSelectionManager.defaultManager().processKeyEvent(event);
900 }
901
902 /**
903 * Programatically performs click
904 *
905 * @param time Number of milliseconds for which this menu stays pressed
906 */
907 public void doClick(int time)
908 {
909 getModel().setArmed(true);
910 getModel().setPressed(true);
911 try
912 {
913 java.lang.Thread.sleep(time);
914 }
915 catch (java.lang.InterruptedException e)
916 {
917 // probably harmless
918 }
919
920 getModel().setPressed(false);
921 getModel().setArmed(false);
922 popupMenu.show(this, this.getWidth(), 0);
923 }
924
925 /**
926 * A string that describes this JMenu. Normally only used
927 * for debugging.
928 *
929 * @return A string describing this JMenu
930 */
931 protected String paramString()
932 {
933 return super.paramString();
934 }
935
936 public AccessibleContext getAccessibleContext()
937 {
938 if (accessibleContext == null)
939 accessibleContext = new AccessibleJMenu();
940
941 return accessibleContext;
942 }
943
944 /**
945 * Implements support for assisitive technologies for <code>JMenu</code>.
946 */
947 protected class AccessibleJMenu extends AccessibleJMenuItem
948 implements AccessibleSelection
949 {
950 private static final long serialVersionUID = -8131864021059524309L;
951
952 protected AccessibleJMenu()
953 {
954 // Nothing to do here.
955 }
956
957 /**
958 * Returns the number of accessible children of this object.
959 *
960 * @return the number of accessible children of this object
961 */
962 public int getAccessibleChildrenCount()
963 {
964 Component[] children = getMenuComponents();
965 int count = 0;
966 for (int i = 0; i < children.length; i++)
967 {
968 if (children[i] instanceof Accessible)
969 count++;
970 }
971 return count;
972 }
973
974 /**
975 * Returns the accessible child with the specified <code>index</code>.
976 *
977 * @param index the index of the child to fetch
978 *
979 * @return the accessible child with the specified <code>index</code>
980 */
981 public Accessible getAccessibleChild(int index)
982 {
983 Component[] children = getMenuComponents();
984 int count = 0;
985 Accessible found = null;
986 for (int i = 0; i < children.length; i++)
987 {
988 if (children[i] instanceof Accessible)
989 {
990 if (count == index)
991 {
992 found = (Accessible) children[i];
993 break;
994 }
995 count++;
996 }
997 }
998 return found;
999 }
1000
1001 /**
1002 * Returns the accessible selection of this object. AccessibleJMenus handle
1003 * their selection themselves, so we always return <code>this</code> here.
1004 *
1005 * @return the accessible selection of this object
1006 */
1007 public AccessibleSelection getAccessibleSelection()
1008 {
1009 return this;
1010 }
1011
1012 /**
1013 * Returns the selected accessible child with the specified
1014 * <code>index</code>.
1015 *
1016 * @param index the index of the accessible selected child to return
1017 *
1018 * @return the selected accessible child with the specified
1019 * <code>index</code>
1020 */
1021 public Accessible getAccessibleSelection(int index)
1022 {
1023 Accessible selected = null;
1024 // Only one item can be selected, which must therefore have index == 0.
1025 if (index == 0)
1026 {
1027 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1028 MenuElement[] me = msm.getSelectedPath();
1029 if (me != null)
1030 {
1031 for (int i = 0; i < me.length; i++)
1032 {
1033 if (me[i] == JMenu.this)
1034 {
1035 // This JMenu is selected, find and return the next
1036 // JMenuItem in the path.
1037 do
1038 {
1039 if (me[i] instanceof Accessible)
1040 {
1041 selected = (Accessible) me[i];
1042 break;
1043 }
1044 i++;
1045 } while (i < me.length);
1046 }
1047 if (selected != null)
1048 break;
1049 }
1050 }
1051 }
1052 return selected;
1053 }
1054
1055 /**
1056 * Returns <code>true</code> if the accessible child with the specified
1057 * index is selected, <code>false</code> otherwise.
1058 *
1059 * @param index the index of the accessible child to check
1060 *
1061 * @return <code>true</code> if the accessible child with the specified
1062 * index is selected, <code>false</code> otherwise
1063 */
1064 public boolean isAccessibleChildSelected(int index)
1065 {
1066 boolean selected = false;
1067 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1068 MenuElement[] me = msm.getSelectedPath();
1069 if (me != null)
1070 {
1071 Accessible toBeFound = getAccessibleChild(index);
1072 for (int i = 0; i < me.length; i++)
1073 {
1074 if (me[i] == toBeFound)
1075 {
1076 selected = true;
1077 break;
1078 }
1079 }
1080 }
1081 return selected;
1082 }
1083
1084 /**
1085 * Returns the accessible role of this object, which is
1086 * {@link AccessibleRole#MENU} for the AccessibleJMenu.
1087 *
1088 * @return the accessible role of this object
1089 */
1090 public AccessibleRole getAccessibleRole()
1091 {
1092 return AccessibleRole.MENU;
1093 }
1094
1095 /**
1096 * Returns the number of selected accessible children. This will be
1097 * <code>0</code> if no item is selected, or <code>1</code> if an item
1098 * is selected. AccessibleJMenu can have maximum 1 selected item.
1099 *
1100 * @return the number of selected accessible children
1101 */
1102 public int getAccessibleSelectionCount()
1103 {
1104 int count = 0;
1105 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1106 MenuElement[] me = msm.getSelectedPath();
1107 if (me != null)
1108 {
1109 for (int i = 0; i < me.length; i++)
1110 {
1111 if (me[i] == JMenu.this)
1112 {
1113 if (i + 1 < me.length)
1114 {
1115 count = 1;
1116 break;
1117 }
1118 }
1119 }
1120 }
1121 return count;
1122 }
1123
1124 /**
1125 * Selects the accessible child with the specified index.
1126 *
1127 * @param index the index of the accessible child to select
1128 */
1129 public void addAccessibleSelection(int index)
1130 {
1131 Accessible child = getAccessibleChild(index);
1132 if (child != null && child instanceof JMenuItem)
1133 {
1134 JMenuItem mi = (JMenuItem) child;
1135 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1136 msm.setSelectedPath(createPath(JMenu.this));
1137 }
1138 }
1139
1140 /**
1141 * Removes the item with the specified index from the selection.
1142 *
1143 * @param index the index of the selected item to remove from the selection
1144 */
1145 public void removeAccessibleSelection(int index)
1146 {
1147 Accessible child = getAccessibleChild(index);
1148 if (child != null)
1149 {
1150 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1151 MenuElement[] oldSelection = msm.getSelectedPath();
1152 for (int i = 0; i < oldSelection.length; i++)
1153 {
1154 if (oldSelection[i] == child)
1155 {
1156 // Found the specified child in the selection. Remove it
1157 // from the selection.
1158 MenuElement[] newSel = new MenuElement[i - 1];
1159 System.arraycopy(oldSelection, 0, newSel, 0, i - 1);
1160 msm.setSelectedPath(newSel);
1161 break;
1162 }
1163 }
1164 }
1165 }
1166
1167 /**
1168 * Removes all possibly selected accessible children of this object from
1169 * the selection.
1170 */
1171 public void clearAccessibleSelection()
1172 {
1173 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1174 MenuElement[] oldSelection = msm.getSelectedPath();
1175 for (int i = 0; i < oldSelection.length; i++)
1176 {
1177 if (oldSelection[i] == JMenu.this)
1178 {
1179 // Found this menu in the selection. Remove all children from
1180 // the selection.
1181 MenuElement[] newSel = new MenuElement[i];
1182 System.arraycopy(oldSelection, 0, newSel, 0, i);
1183 msm.setSelectedPath(newSel);
1184 break;
1185 }
1186 }
1187 }
1188
1189 /**
1190 * AccessibleJMenu don't support multiple selection, so this method
1191 * does nothing.
1192 */
1193 public void selectAllAccessibleSelection()
1194 {
1195 // Nothing to do here.
1196 }
1197 }
1198
1199 protected class WinListener extends WindowAdapter implements Serializable
1200 {
1201 private static final long serialVersionUID = -6415815570638474823L;
1202
1203 /**
1204 * Creates a new <code>WinListener</code>.
1205 *
1206 * @param popup the popup menu which is observed
1207 */
1208 public WinListener(JPopupMenu popup)
1209 {
1210 // TODO: What should we do with the popup argument?
1211 }
1212
1213 /**
1214 * Receives notification when the popup menu is closing and deselects
1215 * the menu.
1216 *
1217 * @param event the window event
1218 */
1219 public void windowClosing(WindowEvent event)
1220 {
1221 setSelected(false);
1222 }
1223 }
1224
1225 /**
1226 * This class listens to PropertyChangeEvents occuring in menu's action
1227 */
1228 private class ActionChangedListener implements PropertyChangeListener
1229 {
1230 /** menu item associated with the action */
1231 private JMenuItem menuItem;
1232
1233 /** Creates new ActionChangedListener and adds it to menuItem's action */
1234 public ActionChangedListener(JMenuItem menuItem)
1235 {
1236 this.menuItem = menuItem;
1237
1238 Action a = menuItem.getAction();
1239 if (a != null)
1240 a.addPropertyChangeListener(this);
1241 }
1242
1243 /**This method is invoked when some change occures in menuItem's action*/
1244 public void propertyChange(PropertyChangeEvent evt)
1245 {
1246 // FIXME: Need to implement
1247 }
1248 }
1249
1250 /**
1251 * Creates an array to be feeded as argument to
1252 * {@link MenuSelectionManager#setSelectedPath(MenuElement[])} for the
1253 * specified element. This has the effect of selecting the specified element
1254 * and all its parents.
1255 *
1256 * @param leaf the leaf element for which to create the selected path
1257 *
1258 * @return the selected path array
1259 */
1260 MenuElement[] createPath(JMenu leaf)
1261 {
1262 ArrayList path = new ArrayList();
1263 MenuElement[] array = null;
1264 Component current = leaf.getPopupMenu();
1265 while (true)
1266 {
1267 if (current instanceof JPopupMenu)
1268 {
1269 JPopupMenu popupMenu = (JPopupMenu) current;
1270 path.add(0, popupMenu);
1271 current = popupMenu.getInvoker();
1272 }
1273 else if (current instanceof JMenu)
1274 {
1275 JMenu menu = (JMenu) current;
1276 path.add(0, menu);
1277 current = menu.getParent();
1278 }
1279 else if (current instanceof JMenuBar)
1280 {
1281 JMenuBar menuBar = (JMenuBar) current;
1282 path.add(0, menuBar);
1283 array = new MenuElement[path.size()];
1284 array = (MenuElement[]) path.toArray(array);
1285 break;
1286 }
1287 }
1288 return array;
1289 }
1290 }