001 /* BasicPopupMenuUI.java
002 Copyright (C) 2002, 2004, 2005 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 package javax.swing.plaf.basic;
039
040 import java.awt.Component;
041 import java.awt.Dimension;
042 import java.awt.KeyboardFocusManager;
043 import java.awt.event.ActionEvent;
044 import java.awt.event.ComponentEvent;
045 import java.awt.event.ComponentListener;
046 import java.awt.event.MouseEvent;
047 import java.util.EventListener;
048
049 import javax.swing.AbstractAction;
050 import javax.swing.Action;
051 import javax.swing.ActionMap;
052 import javax.swing.BoxLayout;
053 import javax.swing.InputMap;
054 import javax.swing.JApplet;
055 import javax.swing.JComponent;
056 import javax.swing.JFrame;
057 import javax.swing.JMenu;
058 import javax.swing.JMenuBar;
059 import javax.swing.JMenuItem;
060 import javax.swing.JPopupMenu;
061 import javax.swing.JRootPane;
062 import javax.swing.LookAndFeel;
063 import javax.swing.MenuElement;
064 import javax.swing.MenuSelectionManager;
065 import javax.swing.SwingUtilities;
066 import javax.swing.UIManager;
067 import javax.swing.event.ChangeEvent;
068 import javax.swing.event.ChangeListener;
069 import javax.swing.event.PopupMenuEvent;
070 import javax.swing.event.PopupMenuListener;
071 import javax.swing.plaf.ActionMapUIResource;
072 import javax.swing.plaf.ComponentUI;
073 import javax.swing.plaf.PopupMenuUI;
074
075 /**
076 * UI Delegate for JPopupMenu
077 */
078 public class BasicPopupMenuUI extends PopupMenuUI
079 {
080 /**
081 * Handles keyboard navigation through menus.
082 */
083 private static class NavigateAction
084 extends AbstractAction
085 {
086
087 /**
088 * Creates a new NavigateAction instance.
089 *
090 * @param name the name of the action
091 */
092 NavigateAction(String name)
093 {
094 super(name);
095 }
096
097 /**
098 * Actually performs the action.
099 */
100 public void actionPerformed(ActionEvent event)
101 {
102 String name = (String) getValue(Action.NAME);
103 if (name.equals("selectNext"))
104 navigateNextPrevious(true);
105 else if (name.equals("selectPrevious"))
106 navigateNextPrevious(false);
107 else if (name.equals("selectChild"))
108 navigateParentChild(true);
109 else if (name.equals("selectParent"))
110 navigateParentChild(false);
111 else if (name.equals("cancel"))
112 cancel();
113 else if (name.equals("return"))
114 doReturn();
115 else
116 assert false : "Must not reach here";
117 }
118
119 /**
120 * Navigates to the next or previous menu item.
121 *
122 * @param dir <code>true</code>: navigate to next, <code>false</code>:
123 * navigate to previous
124 */
125 private void navigateNextPrevious(boolean dir)
126 {
127 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
128 MenuElement path[] = msm.getSelectedPath();
129 int len = path.length;
130 if (len >= 2)
131 {
132
133 if (path[0] instanceof JMenuBar &&
134 path[1] instanceof JMenu && len == 2)
135 {
136
137 // A toplevel menu is selected, but its popup not yet shown.
138 // Show the popup and select the first item
139 JPopupMenu popup = ((JMenu)path[1]).getPopupMenu();
140 MenuElement next =
141 findEnabledChild(popup.getSubElements(), -1, true);
142 MenuElement[] newPath;
143
144 if (next != null)
145 {
146 newPath = new MenuElement[4];
147 newPath[3] = next;
148 }
149 else
150 {
151 // Menu has no enabled items, show the popup anyway.
152 newPath = new MenuElement[3];
153 }
154 System.arraycopy(path, 0, newPath, 0, 2);
155 newPath[2] = popup;
156 msm.setSelectedPath(newPath);
157 }
158 else if (path[len - 1] instanceof JPopupMenu &&
159 path[len - 2] instanceof JMenu)
160 {
161 // Select next item in already shown popup menu.
162 JMenu menu = (JMenu) path[len - 2];
163 JPopupMenu popup = menu.getPopupMenu();
164 MenuElement next =
165 findEnabledChild(popup.getSubElements(), -1, dir);
166
167 if (next != null)
168 {
169 MenuElement[] newPath = new MenuElement[len + 1];
170 System.arraycopy(path, 0, newPath, 0, len);
171 newPath[len] = next;
172 msm.setSelectedPath(newPath);
173 }
174 else
175 {
176 // All items in the popup are disabled.
177 // Find the parent popup menu and select
178 // its next item. If there's no parent popup menu , do nothing.
179 if (len > 2 && path[len - 3] instanceof JPopupMenu)
180 {
181 popup = ((JPopupMenu) path[len - 3]);
182 next = findEnabledChild(popup.getSubElements(),
183 menu, dir);
184 if (next != null && next != menu)
185 {
186 MenuElement[] newPath = new MenuElement[len - 1];
187 System.arraycopy(path, 0, newPath, 0, len - 2);
188 newPath[len - 2] = next;
189 msm.setSelectedPath(newPath);
190 }
191 }
192 }
193 }
194 else
195 {
196 // Only select the next item.
197 MenuElement subs[] = path[len - 2].getSubElements();
198 MenuElement nextChild =
199 findEnabledChild(subs, path[len - 1], dir);
200 if (nextChild == null)
201 {
202 nextChild = findEnabledChild(subs, -1, dir);
203 }
204 if (nextChild != null)
205 {
206 path[len-1] = nextChild;
207 msm.setSelectedPath(path);
208 }
209 }
210 }
211 }
212
213 private MenuElement findEnabledChild(MenuElement[] children,
214 MenuElement start, boolean dir)
215 {
216 MenuElement found = null;
217 for (int i = 0; i < children.length && found == null; i++)
218 {
219 if (children[i] == start)
220 {
221 found = findEnabledChild(children, i, dir);
222 }
223 }
224 return found;
225 }
226
227 /**
228 * Searches the next or previous enabled child menu element.
229 *
230 * @param children the children to search through
231 * @param start the index at which to start
232 * @param dir the direction (true == forward, false == backward)
233 *
234 * @return the found element or null
235 */
236 private MenuElement findEnabledChild(MenuElement[] children,
237 int start, boolean dir)
238 {
239 MenuElement result = null;
240 if (dir)
241 {
242 result = findNextEnabledChild(children, start + 1, children.length-1);
243 if (result == null)
244 result = findNextEnabledChild(children, 0, start - 1);
245 }
246 else
247 {
248 result = findPreviousEnabledChild(children, start - 1, 0);
249 if (result == null)
250 result = findPreviousEnabledChild(children, children.length-1,
251 start + 1);
252 }
253 return result;
254 }
255
256 /**
257 * Finds the next child element that is enabled and visible.
258 *
259 * @param children the children to search through
260 * @param start the start index
261 * @param end the end index
262 *
263 * @return the found child, or null
264 */
265 private MenuElement findNextEnabledChild(MenuElement[] children, int start,
266 int end)
267 {
268 MenuElement found = null;
269 for (int i = start; i <= end && found == null; i++)
270 {
271 if (children[i] != null)
272 {
273 Component comp = children[i].getComponent();
274 if (comp != null && comp.isEnabled() && comp.isVisible())
275 {
276 found = children[i];
277 }
278 }
279 }
280 return found;
281 }
282
283 /**
284 * Finds the previous child element that is enabled and visible.
285 *
286 * @param children the children to search through
287 * @param start the start index
288 * @param end the end index
289 *
290 * @return the found child, or null
291 */
292 private MenuElement findPreviousEnabledChild(MenuElement[] children,
293 int start, int end)
294 {
295 MenuElement found = null;
296 for (int i = start; i >= end && found == null; i--)
297 {
298 if (children[i] != null)
299 {
300 Component comp = children[i].getComponent();
301 if (comp != null && comp.isEnabled() && comp.isVisible())
302 {
303 found = children[i];
304 }
305 }
306 }
307 return found;
308 }
309
310 /**
311 * Navigates to the parent or child menu item.
312 *
313 * @param selectChild <code>true</code>: navigate to child,
314 * <code>false</code>: navigate to parent
315 */
316 private void navigateParentChild(boolean selectChild)
317 {
318 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
319 MenuElement path[] = msm.getSelectedPath();
320 int len = path.length;
321
322 if (selectChild)
323 {
324 if (len > 0 && path[len - 1] instanceof JMenu
325 && ! ((JMenu) path[len-1]).isTopLevelMenu())
326 {
327 // We have a submenu, open it.
328 JMenu menu = (JMenu) path[len - 1];
329 JPopupMenu popup = menu.getPopupMenu();
330 MenuElement[] subs = popup.getSubElements();
331 MenuElement item = findEnabledChild(subs, -1, true);
332 MenuElement[] newPath;
333
334 if (item == null)
335 {
336 newPath = new MenuElement[len + 1];
337 }
338 else
339 {
340 newPath = new MenuElement[len + 2];
341 newPath[len + 1] = item;
342 }
343 System.arraycopy(path, 0, newPath, 0, len);
344 newPath[len] = popup;
345 msm.setSelectedPath(newPath);
346 return;
347 }
348 }
349 else
350 {
351 int popupIndex = len-1;
352 if (len > 2
353 && (path[popupIndex] instanceof JPopupMenu
354 || path[--popupIndex] instanceof JPopupMenu)
355 && ! ((JMenu) path[popupIndex - 1]).isTopLevelMenu())
356 {
357 // We have a submenu, close it.
358 MenuElement newPath[] = new MenuElement[popupIndex];
359 System.arraycopy(path, 0, newPath, 0, popupIndex);
360 msm.setSelectedPath(newPath);
361 return;
362 }
363 }
364
365 // If we got here, we have not selected a child or parent.
366 // Check if we have a toplevel menu selected. If so, then select
367 // another one.
368 if (len > 1 && path[0] instanceof JMenuBar)
369 {
370 MenuElement currentMenu = path[1];
371 MenuElement nextMenu = findEnabledChild(path[0].getSubElements(),
372 currentMenu, selectChild);
373
374 if (nextMenu != null && nextMenu != currentMenu)
375 {
376 MenuElement newSelection[];
377 if (len == 2)
378 {
379 // Menu is selected but its popup not shown.
380 newSelection = new MenuElement[2];
381 newSelection[0] = path[0];
382 newSelection[1] = nextMenu;
383 }
384 else
385 {
386 // Menu is selected and its popup is shown.
387 newSelection = new MenuElement[3];
388 newSelection[0] = path[0];
389 newSelection[1] = nextMenu;
390 newSelection[2] = ((JMenu) nextMenu).getPopupMenu();
391 }
392 msm.setSelectedPath(newSelection);
393 }
394 }
395 }
396
397 /**
398 * Handles cancel requests (ESC key).
399 */
400 private void cancel()
401 {
402 // Fire popup menu cancelled event. Unfortunately the
403 // firePopupMenuCancelled() is protected in JPopupMenu so we work
404 // around this limitation by fetching the listeners and notifying them
405 // directly.
406 JPopupMenu lastPopup = (JPopupMenu) getLastPopup();
407 EventListener[] ll = lastPopup.getListeners(PopupMenuListener.class);
408 for (int i = 0; i < ll.length; i++)
409 {
410 PopupMenuEvent ev = new PopupMenuEvent(lastPopup);
411 ((PopupMenuListener) ll[i]).popupMenuCanceled(ev);
412 }
413
414 // Close the last popup or the whole selection if there's only one
415 // popup left.
416 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
417 MenuElement path[] = msm.getSelectedPath();
418 if(path.length > 4)
419 {
420 MenuElement newPath[] = new MenuElement[path.length - 2];
421 System.arraycopy(path,0,newPath,0,path.length-2);
422 MenuSelectionManager.defaultManager().setSelectedPath(newPath);
423 }
424 else
425 msm.clearSelectedPath();
426 }
427
428 /**
429 * Returns the last popup menu in the current selection or null.
430 *
431 * @return the last popup menu in the current selection or null
432 */
433 private JPopupMenu getLastPopup()
434 {
435 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
436 MenuElement[] p = msm.getSelectedPath();
437 JPopupMenu popup = null;
438 for(int i = p.length - 1; popup == null && i >= 0; i--)
439 {
440 if (p[i] instanceof JPopupMenu)
441 popup = (JPopupMenu) p[i];
442 }
443 return popup;
444 }
445
446 /**
447 * Handles ENTER key requests. This normally opens submenus on JMenu
448 * items, or activates the menu item as if it's been clicked on it.
449 */
450 private void doReturn()
451 {
452 KeyboardFocusManager fmgr =
453 KeyboardFocusManager.getCurrentKeyboardFocusManager();
454 Component focusOwner = fmgr.getFocusOwner();
455 if((focusOwner == null || (focusOwner instanceof JRootPane)))
456 {
457 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
458 MenuElement path[] = msm.getSelectedPath();
459 MenuElement lastElement;
460 if(path.length > 0)
461 {
462 lastElement = path[path.length - 1];
463 if(lastElement instanceof JMenu)
464 {
465 MenuElement newPath[] = new MenuElement[path.length + 1];
466 System.arraycopy(path,0,newPath,0,path.length);
467 newPath[path.length] = ((JMenu) lastElement).getPopupMenu();
468 msm.setSelectedPath(newPath);
469 }
470 else if(lastElement instanceof JMenuItem)
471 {
472 JMenuItem mi = (JMenuItem)lastElement;
473 if (mi.getUI() instanceof BasicMenuItemUI)
474 {
475 ((BasicMenuItemUI)mi.getUI()).doClick(msm);
476 }
477 else
478 {
479 msm.clearSelectedPath();
480 mi.doClick(0);
481 }
482 }
483 }
484 }
485 }
486 }
487
488 /**
489 * Installs keyboard actions when a popup is opened, and uninstalls the
490 * keyboard actions when closed. This listens on the default
491 * MenuSelectionManager.
492 */
493 private class KeyboardHelper
494 implements ChangeListener
495 {
496 private MenuElement[] lastSelectedPath = new MenuElement[0];
497 private Component lastFocused;
498 private JRootPane invokerRootPane;
499
500 public void stateChanged(ChangeEvent event)
501 {
502 MenuSelectionManager msm = (MenuSelectionManager) event.getSource();
503 MenuElement[] p = msm.getSelectedPath();
504 JPopupMenu popup = getActivePopup(p);
505 if (popup == null || popup.isFocusable())
506 {
507 if (lastSelectedPath.length != 0 && p.length != 0 )
508 {
509 if (! invokerEquals(p[0], lastSelectedPath[0]))
510 {
511 uninstallKeyboardActionsImpl();
512 lastSelectedPath = new MenuElement[0];
513 }
514 }
515
516 if (lastSelectedPath.length == 0 && p.length > 0)
517 {
518 JComponent invoker;
519 if (popup == null)
520 {
521 if (p.length == 2 && p[0] instanceof JMenuBar
522 && p[1] instanceof JMenu)
523 {
524 // A menu has been selected but not opened.
525 invoker = (JComponent)p[1];
526 popup = ((JMenu)invoker).getPopupMenu();
527 }
528 else
529 {
530 return;
531 }
532 }
533 else
534 {
535 Component c = popup.getInvoker();
536 if(c instanceof JFrame)
537 {
538 invoker = ((JFrame) c).getRootPane();
539 }
540 else if(c instanceof JApplet)
541 {
542 invoker = ((JApplet) c).getRootPane();
543 }
544 else
545 {
546 while (!(c instanceof JComponent))
547 {
548 if (c == null)
549 {
550 return;
551 }
552 c = c.getParent();
553 }
554 invoker = (JComponent)c;
555 }
556 }
557
558 // Remember current focus owner.
559 lastFocused = KeyboardFocusManager.
560 getCurrentKeyboardFocusManager().getFocusOwner();
561
562 // Install keybindings used for menu navigation.
563 invokerRootPane = SwingUtilities.getRootPane(invoker);
564 if (invokerRootPane != null)
565 {
566 invokerRootPane.requestFocus(true);
567 installKeyboardActionsImpl();
568 }
569 }
570 else if (lastSelectedPath.length != 0 && p.length == 0)
571 {
572 // menu hidden -- return focus to where it had been before
573 // and uninstall menu keybindings
574 uninstallKeyboardActionsImpl();
575 }
576 }
577
578 // Remember the last path selected
579 lastSelectedPath = p;
580 }
581
582 private JPopupMenu getActivePopup(MenuElement[] path)
583 {
584 JPopupMenu active = null;
585 for (int i = path.length - 1; i >= 0 && active == null; i--)
586 {
587 MenuElement elem = path[i];
588 if (elem instanceof JPopupMenu)
589 {
590 active = (JPopupMenu) elem;
591 }
592 }
593 return active;
594 }
595
596 private boolean invokerEquals(MenuElement el1, MenuElement el2)
597 {
598 Component invoker1 = el1.getComponent();
599 Component invoker2 = el2.getComponent();
600 if (invoker1 instanceof JPopupMenu)
601 invoker1 = ((JPopupMenu) invoker1).getInvoker();
602 if (invoker2 instanceof JPopupMenu)
603 invoker2 = ((JPopupMenu) invoker2).getInvoker();
604 return invoker1 == invoker2;
605 }
606 }
607
608 /* popupMenu for which this UI delegate is for*/
609 protected JPopupMenu popupMenu;
610
611 /* PopupMenuListener listens to popup menu events fired by JPopupMenu*/
612 private transient PopupMenuListener popupMenuListener;
613
614 /* ComponentListener listening to popupMenu's invoker.
615 * This is package-private to avoid an accessor method. */
616 TopWindowListener topWindowListener;
617
618 /**
619 * Counts how many popup menus are handled by this UI or a subclass.
620 * This is used to install a KeyboardHelper on the MenuSelectionManager
621 * for the first popup, and uninstall this same KeyboardHelper when the
622 * last popup is uninstalled.
623 */
624 private static int numPopups;
625
626 /**
627 * This is the KeyboardHelper that listens on the MenuSelectionManager.
628 */
629 private static KeyboardHelper keyboardHelper;
630
631 /**
632 * Creates a new BasicPopupMenuUI object.
633 */
634 public BasicPopupMenuUI()
635 {
636 popupMenuListener = new PopupMenuHandler();
637 topWindowListener = new TopWindowListener();
638 }
639
640 /**
641 * Factory method to create a BasicPopupMenuUI for the given {@link
642 * JComponent}, which should be a {@link JMenuItem}.
643 *
644 * @param x The {@link JComponent} a UI is being created for.
645 *
646 * @return A BasicPopupMenuUI for the {@link JComponent}.
647 */
648 public static ComponentUI createUI(JComponent x)
649 {
650 return new BasicPopupMenuUI();
651 }
652
653 /**
654 * Installs and initializes all fields for this UI delegate. Any properties
655 * of the UI that need to be initialized and/or set to defaults will be
656 * done now. It will also install any listeners necessary.
657 *
658 * @param c The {@link JComponent} that is having this UI installed.
659 */
660 public void installUI(JComponent c)
661 {
662 super.installUI(c);
663
664 // Install KeyboardHelper when the first popup is initialized.
665 if (numPopups == 0)
666 {
667 keyboardHelper = new KeyboardHelper();
668 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
669 msm.addChangeListener(keyboardHelper);
670 }
671 numPopups++;
672
673 popupMenu = (JPopupMenu) c;
674 popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS));
675 popupMenu.setBorderPainted(true);
676 JPopupMenu.setDefaultLightWeightPopupEnabled(true);
677
678 installDefaults();
679 installListeners();
680 installKeyboardActions();
681 }
682
683 /**
684 * This method installs the defaults that are defined in the Basic look
685 * and feel for this {@link JPopupMenu}.
686 */
687 public void installDefaults()
688 {
689 LookAndFeel.installColorsAndFont(popupMenu, "PopupMenu.background",
690 "PopupMenu.foreground", "PopupMenu.font");
691 LookAndFeel.installBorder(popupMenu, "PopupMenu.border");
692 popupMenu.setOpaque(true);
693 }
694
695 /**
696 * This method installs the listeners for the {@link JMenuItem}.
697 */
698 protected void installListeners()
699 {
700 popupMenu.addPopupMenuListener(popupMenuListener);
701 }
702
703 /**
704 * This method installs the keyboard actions for this {@link JPopupMenu}.
705 */
706 protected void installKeyboardActions()
707 {
708 // We can't install the keyboard actions here, because then all
709 // popup menus would have their actions registered in the KeyboardManager.
710 // So we install it when the popup menu is opened, and uninstall it
711 // when it's closed. This is done in the KeyboardHelper class.
712 // Install InputMap.
713 }
714
715 /**
716 * Called by the KeyboardHandler when a popup is made visible.
717 */
718 void installKeyboardActionsImpl()
719 {
720 Object[] bindings;
721 if (popupMenu.getComponentOrientation().isLeftToRight())
722 {
723 bindings = (Object[])
724 SharedUIDefaults.get("PopupMenu.selectedWindowInputMapBindings");
725 }
726 else
727 {
728 bindings = (Object[]) SharedUIDefaults.get
729 ("PopupMenu.selectedWindowInputMapBindings.RightToLeft");
730 }
731 InputMap inputMap = LookAndFeel.makeComponentInputMap(popupMenu, bindings);
732 SwingUtilities.replaceUIInputMap(popupMenu,
733 JComponent.WHEN_IN_FOCUSED_WINDOW,
734 inputMap);
735
736 // Install ActionMap.
737 SwingUtilities.replaceUIActionMap(popupMenu, getActionMap());
738 }
739
740 /**
741 * Creates and returns the shared action map for JTrees.
742 *
743 * @return the shared action map for JTrees
744 */
745 private ActionMap getActionMap()
746 {
747 ActionMap am = (ActionMap) UIManager.get("PopupMenu.actionMap");
748 if (am == null)
749 {
750 am = createDefaultActions();
751 UIManager.getLookAndFeelDefaults().put("PopupMenu.actionMap", am);
752 }
753 return am;
754 }
755
756 /**
757 * Creates the default actions when there are none specified by the L&F.
758 *
759 * @return the default actions
760 */
761 private ActionMap createDefaultActions()
762 {
763 ActionMapUIResource am = new ActionMapUIResource();
764 Action action = new NavigateAction("selectNext");
765 am.put(action.getValue(Action.NAME), action);
766 action = new NavigateAction("selectPrevious");
767 am.put(action.getValue(Action.NAME), action);
768 action = new NavigateAction("selectParent");
769 am.put(action.getValue(Action.NAME), action);
770 action = new NavigateAction("selectChild");
771 am.put(action.getValue(Action.NAME), action);
772 action = new NavigateAction("return");
773 am.put(action.getValue(Action.NAME), action);
774 action = new NavigateAction("cancel");
775 am.put(action.getValue(Action.NAME), action);
776
777 return am;
778 }
779
780 /**
781 * Performs the opposite of installUI. Any properties or resources that need
782 * to be cleaned up will be done now. It will also uninstall any listeners
783 * it has. In addition, any properties of this UI will be nulled.
784 *
785 * @param c The {@link JComponent} that is having this UI uninstalled.
786 */
787 public void uninstallUI(JComponent c)
788 {
789 uninstallListeners();
790 uninstallDefaults();
791 uninstallKeyboardActions();
792 popupMenu = null;
793
794 // Install KeyboardHelper when the first popup is initialized.
795 numPopups--;
796 if (numPopups == 0)
797 {
798 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
799 msm.removeChangeListener(keyboardHelper);
800 }
801
802 }
803
804 /**
805 * This method uninstalls the defaults and sets any objects created during
806 * install to null
807 */
808 protected void uninstallDefaults()
809 {
810 popupMenu.setBackground(null);
811 popupMenu.setBorder(null);
812 popupMenu.setFont(null);
813 popupMenu.setForeground(null);
814 }
815
816 /**
817 * Unregisters all the listeners that this UI delegate was using.
818 */
819 protected void uninstallListeners()
820 {
821 popupMenu.removePopupMenuListener(popupMenuListener);
822 }
823
824 /**
825 * Uninstalls any keyboard actions.
826 */
827 protected void uninstallKeyboardActions()
828 {
829 // We can't install the keyboard actions here, because then all
830 // popup menus would have their actions registered in the KeyboardManager.
831 // So we install it when the popup menu is opened, and uninstall it
832 // when it's closed. This is done in the KeyboardHelper class.
833 // Install InputMap.
834 }
835
836 /**
837 * Called by the KeyboardHandler when a popup is made invisible.
838 */
839 void uninstallKeyboardActionsImpl()
840 {
841 SwingUtilities.replaceUIInputMap(popupMenu,
842 JComponent.WHEN_IN_FOCUSED_WINDOW, null);
843 SwingUtilities.replaceUIActionMap(popupMenu, null);
844 }
845
846 /**
847 * This method returns the minimum size of the JPopupMenu.
848 *
849 * @param c The JComponent to find a size for.
850 *
851 * @return The minimum size.
852 */
853 public Dimension getMinimumSize(JComponent c)
854 {
855 return null;
856 }
857
858 /**
859 * This method returns the preferred size of the JPopupMenu.
860 *
861 * @param c The JComponent to find a size for.
862 *
863 * @return The preferred size.
864 */
865 public Dimension getPreferredSize(JComponent c)
866 {
867 return null;
868 }
869
870 /**
871 * This method returns the minimum size of the JPopupMenu.
872 *
873 * @param c The JComponent to find a size for.
874 *
875 * @return The minimum size.
876 */
877 public Dimension getMaximumSize(JComponent c)
878 {
879 return null;
880 }
881
882 /**
883 * Return true if given mouse event is a platform popup trigger, and false
884 * otherwise
885 *
886 * @param e MouseEvent that is to be checked for popup trigger event
887 *
888 * @return true if given mouse event is a platform popup trigger, and false
889 * otherwise
890 */
891 public boolean isPopupTrigger(MouseEvent e)
892 {
893 return false;
894 }
895
896 /**
897 * This listener handles PopupMenuEvents fired by JPopupMenu
898 */
899 private class PopupMenuHandler implements PopupMenuListener
900 {
901 /**
902 * This method is invoked when JPopupMenu is cancelled.
903 *
904 * @param event the PopupMenuEvent
905 */
906 public void popupMenuCanceled(PopupMenuEvent event)
907 {
908 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
909 manager.clearSelectedPath();
910 }
911
912 /**
913 * This method is invoked when JPopupMenu becomes invisible
914 *
915 * @param event the PopupMenuEvent
916 */
917 public void popupMenuWillBecomeInvisible(PopupMenuEvent event)
918 {
919 // remove listener that listens to component events fired
920 // by the top - level window that this popup belongs to.
921 Component invoker = popupMenu.getInvoker();
922 Component rootContainer = SwingUtilities.getRoot(invoker);
923 if (rootContainer != null)
924 rootContainer.removeComponentListener(topWindowListener);
925 }
926
927 /**
928 * This method is invoked when JPopupMenu becomes visible
929 *
930 * @param event the PopupMenuEvent
931 */
932 public void popupMenuWillBecomeVisible(PopupMenuEvent event)
933 {
934 // Adds topWindowListener to top-level window to listener to
935 // ComponentEvents fired by it. We need to cancel this popup menu
936 // if topWindow to which this popup belongs was resized or moved.
937 Component invoker = popupMenu.getInvoker();
938 Component rootContainer = SwingUtilities.getRoot(invoker);
939 if (rootContainer != null)
940 rootContainer.addComponentListener(topWindowListener);
941
942 // if this popup menu is a free floating popup menu,
943 // then by default its first element should be always selected when
944 // this popup menu becomes visible.
945 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
946
947 if (manager.getSelectedPath().length == 0)
948 {
949 // Set selected path to point to the first item in the popup menu
950 MenuElement[] path = new MenuElement[2];
951 path[0] = popupMenu;
952 Component[] comps = popupMenu.getComponents();
953 if (comps.length != 0 && comps[0] instanceof MenuElement)
954 {
955 path[1] = (MenuElement) comps[0];
956 manager.setSelectedPath(path);
957 }
958 }
959 }
960 }
961
962 /**
963 * ComponentListener that listens to Component Events fired by the top -
964 * level window to which popup menu belongs. If top-level window was
965 * resized, moved or hidded then popup menu will be hidded and selected
966 * path of current menu hierarchy will be set to null.
967 */
968 private class TopWindowListener implements ComponentListener
969 {
970 /**
971 * This method is invoked when top-level window is resized. This method
972 * closes current menu hierarchy.
973 *
974 * @param e The ComponentEvent
975 */
976 public void componentResized(ComponentEvent e)
977 {
978 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
979 manager.clearSelectedPath();
980 }
981
982 /**
983 * This method is invoked when top-level window is moved. This method
984 * closes current menu hierarchy.
985 *
986 * @param e The ComponentEvent
987 */
988 public void componentMoved(ComponentEvent e)
989 {
990 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
991 manager.clearSelectedPath();
992 }
993
994 /**
995 * This method is invoked when top-level window is shown This method
996 * does nothing by default.
997 *
998 * @param e The ComponentEvent
999 */
1000 public void componentShown(ComponentEvent e)
1001 {
1002 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1003 manager.clearSelectedPath();
1004 }
1005
1006 /**
1007 * This method is invoked when top-level window is hidden This method
1008 * closes current menu hierarchy.
1009 *
1010 * @param e The ComponentEvent
1011 */
1012 public void componentHidden(ComponentEvent e)
1013 {
1014 MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1015 manager.clearSelectedPath();
1016 }
1017 }
1018
1019 }