blob: aeacd9d61292548fcae9d0721048f8338464651e [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.swing.plaf.basic;
27
28import sun.swing.DefaultLookup;
29import sun.swing.UIAction;
30import java.awt.*;
31import java.awt.event.*;
32import java.beans.*;
33import javax.swing.*;
34import javax.swing.event.*;
35import javax.swing.plaf.*;
36import javax.swing.border.*;
37import java.util.Arrays;
38import java.util.ArrayList;
39
40
41/**
42 * A default L&F implementation of MenuUI. This implementation
43 * is a "combined" view/controller.
44 *
45 * @author Georges Saab
46 * @author David Karlton
47 * @author Arnaud Weber
48 */
49public class BasicMenuUI extends BasicMenuItemUI
50{
51 protected ChangeListener changeListener;
52 protected MenuListener menuListener;
53
54 private int lastMnemonic = 0;
55
56 /** Uses as the parent of the windowInputMap when selected. */
57 private InputMap selectedWindowInputMap;
58
59 /* diagnostic aids -- should be false for production builds. */
60 private static final boolean TRACE = false; // trace creates and disposes
61 private static final boolean VERBOSE = false; // show reuse hits/misses
62 private static final boolean DEBUG = false; // show bad params, misc.
63
64 private static boolean crossMenuMnemonic = true;
65
66 public static ComponentUI createUI(JComponent x) {
67 return new BasicMenuUI();
68 }
69
70 static void loadActionMap(LazyActionMap map) {
71 BasicMenuItemUI.loadActionMap(map);
72 map.put(new Actions(Actions.SELECT, null, true));
73 }
74
75
76 protected void installDefaults() {
77 super.installDefaults();
78 updateDefaultBackgroundColor();
79 ((JMenu)menuItem).setDelay(200);
80 crossMenuMnemonic = UIManager.getBoolean("Menu.crossMenuMnemonic");
81 }
82
83 protected String getPropertyPrefix() {
84 return "Menu";
85 }
86
87 protected void installListeners() {
88 super.installListeners();
89
90 if (changeListener == null)
91 changeListener = createChangeListener(menuItem);
92
93 if (changeListener != null)
94 menuItem.addChangeListener(changeListener);
95
96 if (menuListener == null)
97 menuListener = createMenuListener(menuItem);
98
99 if (menuListener != null)
100 ((JMenu)menuItem).addMenuListener(menuListener);
101 }
102
103 protected void installKeyboardActions() {
104 super.installKeyboardActions();
105 updateMnemonicBinding();
106 }
107
108 void installLazyActionMap() {
109 LazyActionMap.installLazyActionMap(menuItem, BasicMenuUI.class,
110 getPropertyPrefix() + ".actionMap");
111 }
112
113 void updateMnemonicBinding() {
114 int mnemonic = menuItem.getModel().getMnemonic();
115 int[] shortcutKeys = (int[])DefaultLookup.get(menuItem, this,
116 "Menu.shortcutKeys");
117 if (shortcutKeys == null) {
118 shortcutKeys = new int[] {KeyEvent.ALT_MASK};
119 }
120 if (mnemonic == lastMnemonic) {
121 return;
122 }
123 InputMap windowInputMap = SwingUtilities.getUIInputMap(
124 menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW);
125 if (lastMnemonic != 0 && windowInputMap != null) {
126 for (int i=0; i<shortcutKeys.length; i++) {
127 windowInputMap.remove(KeyStroke.getKeyStroke
128 (lastMnemonic, shortcutKeys[i], false));
129 }
130 }
131 if (mnemonic != 0) {
132 if (windowInputMap == null) {
133 windowInputMap = createInputMap(JComponent.
134 WHEN_IN_FOCUSED_WINDOW);
135 SwingUtilities.replaceUIInputMap(menuItem, JComponent.
136 WHEN_IN_FOCUSED_WINDOW, windowInputMap);
137 }
138 for (int i=0; i<shortcutKeys.length; i++) {
139 windowInputMap.put(KeyStroke.getKeyStroke(mnemonic,
140 shortcutKeys[i], false),
141 "selectMenu");
142 }
143 }
144 lastMnemonic = mnemonic;
145 }
146
147 protected void uninstallKeyboardActions() {
148 super.uninstallKeyboardActions();
149 lastMnemonic = 0;
150 }
151
152 protected MouseInputListener createMouseInputListener(JComponent c) {
153 return getHandler();
154 }
155
156 protected MenuListener createMenuListener(JComponent c) {
157 return null;
158 }
159
160 protected ChangeListener createChangeListener(JComponent c) {
161 return null;
162 }
163
164 protected PropertyChangeListener createPropertyChangeListener(JComponent c) {
165 return getHandler();
166 }
167
168 BasicMenuItemUI.Handler getHandler() {
169 if (handler == null) {
170 handler = new Handler();
171 }
172 return handler;
173 }
174
175 protected void uninstallDefaults() {
176 menuItem.setArmed(false);
177 menuItem.setSelected(false);
178 menuItem.resetKeyboardActions();
179 super.uninstallDefaults();
180 }
181
182 protected void uninstallListeners() {
183 super.uninstallListeners();
184
185 if (changeListener != null)
186 menuItem.removeChangeListener(changeListener);
187
188 if (menuListener != null)
189 ((JMenu)menuItem).removeMenuListener(menuListener);
190
191 changeListener = null;
192 menuListener = null;
193 handler = null;
194 }
195
196 protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) {
197 return getHandler();
198 }
199
200 protected MenuKeyListener createMenuKeyListener(JComponent c) {
201 return (MenuKeyListener)getHandler();
202 }
203
204 public Dimension getMaximumSize(JComponent c) {
205 if (((JMenu)menuItem).isTopLevelMenu() == true) {
206 Dimension d = c.getPreferredSize();
207 return new Dimension(d.width, Short.MAX_VALUE);
208 }
209 return null;
210 }
211
212 protected void setupPostTimer(JMenu menu) {
213 Timer timer = new Timer(menu.getDelay(), new Actions(
214 Actions.SELECT, menu,false));
215 timer.setRepeats(false);
216 timer.start();
217 }
218
219 private static void appendPath(MenuElement[] path, MenuElement elem) {
220 MenuElement newPath[] = new MenuElement[path.length+1];
221 System.arraycopy(path, 0, newPath, 0, path.length);
222 newPath[path.length] = elem;
223 MenuSelectionManager.defaultManager().setSelectedPath(newPath);
224 }
225
226 private static class Actions extends UIAction {
227 private static final String SELECT = "selectMenu";
228
229 // NOTE: This will be null if the action is registered in the
230 // ActionMap. For the timer use it will be non-null.
231 private JMenu menu;
232 private boolean force=false;
233
234 Actions(String key, JMenu menu, boolean shouldForce) {
235 super(key);
236 this.menu = menu;
237 this.force = shouldForce;
238 }
239
240 private JMenu getMenu(ActionEvent e) {
241 if (e.getSource() instanceof JMenu) {
242 return (JMenu)e.getSource();
243 }
244 return menu;
245 }
246
247 public void actionPerformed(ActionEvent e) {
248 JMenu menu = getMenu(e);
249 if (!crossMenuMnemonic) {
250 JPopupMenu pm = BasicPopupMenuUI.getLastPopup();
251 if (pm != null && pm != menu.getParent()) {
252 return;
253 }
254 }
255
256 final MenuSelectionManager defaultManager = MenuSelectionManager.defaultManager();
257 if(force) {
258 Container cnt = menu.getParent();
259 if(cnt != null && cnt instanceof JMenuBar) {
260 MenuElement me[];
261 MenuElement subElements[];
262
263 subElements = menu.getPopupMenu().getSubElements();
264 if(subElements.length > 0) {
265 me = new MenuElement[4];
266 me[0] = (MenuElement) cnt;
267 me[1] = (MenuElement) menu;
268 me[2] = (MenuElement) menu.getPopupMenu();
269 me[3] = subElements[0];
270 } else {
271 me = new MenuElement[3];
272 me[0] = (MenuElement)cnt;
273 me[1] = menu;
274 me[2] = (MenuElement) menu.getPopupMenu();
275 }
276 defaultManager.setSelectedPath(me);
277 }
278 } else {
279 MenuElement path[] = defaultManager.getSelectedPath();
280 if(path.length > 0 && path[path.length-1] == menu) {
281 appendPath(path, menu.getPopupMenu());
282 }
283 }
284 }
285
286 public boolean isEnabled(Object c) {
287 if (c instanceof JMenu) {
288 return ((JMenu)c).isEnabled();
289 }
290 return true;
291 }
292 }
293
294 /*
295 * Set the background color depending on whether this is a toplevel menu
296 * in a menubar or a submenu of another menu.
297 */
298 private void updateDefaultBackgroundColor() {
299 if (!UIManager.getBoolean("Menu.useMenuBarBackgroundForTopLevel")) {
300 return;
301 }
302 JMenu menu = (JMenu)menuItem;
303 if (menu.getBackground() instanceof UIResource) {
304 if (menu.isTopLevelMenu()) {
305 menu.setBackground(UIManager.getColor("MenuBar.background"));
306 } else {
307 menu.setBackground(UIManager.getColor(getPropertyPrefix() + ".background"));
308 }
309 }
310 }
311
312 /**
313 * Instantiated and used by a menu item to handle the current menu selection
314 * from mouse events. A MouseInputHandler processes and forwards all mouse events
315 * to a shared instance of the MenuSelectionManager.
316 * <p>
317 * This class is protected so that it can be subclassed by other look and
318 * feels to implement their own mouse handling behavior. All overridden
319 * methods should call the parent methods so that the menu selection
320 * is correct.
321 *
322 * @see javax.swing.MenuSelectionManager
323 * @since 1.4
324 */
325 protected class MouseInputHandler implements MouseInputListener {
326 // NOTE: This class exists only for backward compatability. All
327 // its functionality has been moved into Handler. If you need to add
328 // new functionality add it to the Handler, but make sure this
329 // class calls into the Handler.
330
331 public void mouseClicked(MouseEvent e) {
332 getHandler().mouseClicked(e);
333 }
334
335 /**
336 * Invoked when the mouse has been clicked on the menu. This
337 * method clears or sets the selection path of the
338 * MenuSelectionManager.
339 *
340 * @param e the mouse event
341 */
342 public void mousePressed(MouseEvent e) {
343 getHandler().mousePressed(e);
344 }
345
346 /**
347 * Invoked when the mouse has been released on the menu. Delegates the
348 * mouse event to the MenuSelectionManager.
349 *
350 * @param e the mouse event
351 */
352 public void mouseReleased(MouseEvent e) {
353 getHandler().mouseReleased(e);
354 }
355
356 /**
357 * Invoked when the cursor enters the menu. This method sets the selected
358 * path for the MenuSelectionManager and handles the case
359 * in which a menu item is used to pop up an additional menu, as in a
360 * hierarchical menu system.
361 *
362 * @param e the mouse event; not used
363 */
364 public void mouseEntered(MouseEvent e) {
365 getHandler().mouseEntered(e);
366 }
367 public void mouseExited(MouseEvent e) {
368 getHandler().mouseExited(e);
369 }
370
371 /**
372 * Invoked when a mouse button is pressed on the menu and then dragged.
373 * Delegates the mouse event to the MenuSelectionManager.
374 *
375 * @param e the mouse event
376 * @see java.awt.event.MouseMotionListener#mouseDragged
377 */
378 public void mouseDragged(MouseEvent e) {
379 getHandler().mouseDragged(e);
380 }
381
382 public void mouseMoved(MouseEvent e) {
383 getHandler().mouseMoved(e);
384 }
385 }
386
387 /**
388 * As of Java 2 platform 1.4, this previously undocumented class
389 * is now obsolete. KeyBindings are now managed by the popup menu.
390 */
391 public class ChangeHandler implements ChangeListener {
392 public JMenu menu;
393 public BasicMenuUI ui;
394 public boolean isSelected = false;
395 public Component wasFocused;
396
397 public ChangeHandler(JMenu m, BasicMenuUI ui) {
398 menu = m;
399 this.ui = ui;
400 }
401
402 public void stateChanged(ChangeEvent e) { }
403 }
404
405 private class Handler extends BasicMenuItemUI.Handler implements
406 MenuKeyListener {
407 //
408 // PropertyChangeListener
409 //
410 public void propertyChange(PropertyChangeEvent e) {
411 if (e.getPropertyName() == AbstractButton.
412 MNEMONIC_CHANGED_PROPERTY) {
413 updateMnemonicBinding();
414 }
415 else {
416 if (e.getPropertyName().equals("ancestor")) {
417 updateDefaultBackgroundColor();
418 }
419 super.propertyChange(e);
420 }
421 }
422
423 //
424 // MouseInputListener
425 //
426 public void mouseClicked(MouseEvent e) {
427 }
428
429 /**
430 * Invoked when the mouse has been clicked on the menu. This
431 * method clears or sets the selection path of the
432 * MenuSelectionManager.
433 *
434 * @param e the mouse event
435 */
436 public void mousePressed(MouseEvent e) {
437 JMenu menu = (JMenu)menuItem;
438 if (!menu.isEnabled())
439 return;
440
441 MenuSelectionManager manager =
442 MenuSelectionManager.defaultManager();
443 if(menu.isTopLevelMenu()) {
444 if(menu.isSelected() && menu.getPopupMenu().isShowing()) {
445 manager.clearSelectedPath();
446 } else {
447 Container cnt = menu.getParent();
448 if(cnt != null && cnt instanceof JMenuBar) {
449 MenuElement me[] = new MenuElement[2];
450 me[0]=(MenuElement)cnt;
451 me[1]=menu;
452 manager.setSelectedPath(me);
453 }
454 }
455 }
456
457 MenuElement selectedPath[] = manager.getSelectedPath();
458 if (selectedPath.length > 0 &&
459 selectedPath[selectedPath.length-1] != menu.getPopupMenu()) {
460
461 if(menu.isTopLevelMenu() ||
462 menu.getDelay() == 0) {
463 appendPath(selectedPath, menu.getPopupMenu());
464 } else {
465 setupPostTimer(menu);
466 }
467 }
468 }
469
470 /**
471 * Invoked when the mouse has been released on the menu. Delegates the
472 * mouse event to the MenuSelectionManager.
473 *
474 * @param e the mouse event
475 */
476 public void mouseReleased(MouseEvent e) {
477 JMenu menu = (JMenu)menuItem;
478 if (!menu.isEnabled())
479 return;
480 MenuSelectionManager manager =
481 MenuSelectionManager.defaultManager();
482 manager.processMouseEvent(e);
483 if (!e.isConsumed())
484 manager.clearSelectedPath();
485 }
486
487 /**
488 * Invoked when the cursor enters the menu. This method sets the selected
489 * path for the MenuSelectionManager and handles the case
490 * in which a menu item is used to pop up an additional menu, as in a
491 * hierarchical menu system.
492 *
493 * @param e the mouse event; not used
494 */
495 public void mouseEntered(MouseEvent e) {
496 JMenu menu = (JMenu)menuItem;
497 // only disable the menu highlighting if it's disabled and the property isn't
498 // true. This allows disabled rollovers to work in WinL&F
499 if (!menu.isEnabled() && !UIManager.getBoolean("MenuItem.disabledAreNavigable")) {
500 return;
501 }
502
503 MenuSelectionManager manager =
504 MenuSelectionManager.defaultManager();
505 MenuElement selectedPath[] = manager.getSelectedPath();
506 if (!menu.isTopLevelMenu()) {
507 if(!(selectedPath.length > 0 &&
508 selectedPath[selectedPath.length-1] ==
509 menu.getPopupMenu())) {
510 if(menu.getDelay() == 0) {
511 appendPath(getPath(), menu.getPopupMenu());
512 } else {
513 manager.setSelectedPath(getPath());
514 setupPostTimer(menu);
515 }
516 }
517 } else {
518 if(selectedPath.length > 0 &&
519 selectedPath[0] == menu.getParent()) {
520 MenuElement newPath[] = new MenuElement[3];
521 // A top level menu's parent is by definition
522 // a JMenuBar
523 newPath[0] = (MenuElement)menu.getParent();
524 newPath[1] = menu;
525 if (BasicPopupMenuUI.getLastPopup() != null) {
526 newPath[2] = menu.getPopupMenu();
527 }
528 manager.setSelectedPath(newPath);
529 }
530 }
531 }
532 public void mouseExited(MouseEvent e) {
533 }
534
535 /**
536 * Invoked when a mouse button is pressed on the menu and then dragged.
537 * Delegates the mouse event to the MenuSelectionManager.
538 *
539 * @param e the mouse event
540 * @see java.awt.event.MouseMotionListener#mouseDragged
541 */
542 public void mouseDragged(MouseEvent e) {
543 JMenu menu = (JMenu)menuItem;
544 if (!menu.isEnabled())
545 return;
546 MenuSelectionManager.defaultManager().processMouseEvent(e);
547 }
548 public void mouseMoved(MouseEvent e) {
549 }
550
551
552 //
553 // MenuDragHandler
554 //
555 public void menuDragMouseEntered(MenuDragMouseEvent e) {}
556 public void menuDragMouseDragged(MenuDragMouseEvent e) {
557 if (menuItem.isEnabled() == false)
558 return;
559
560 MenuSelectionManager manager = e.getMenuSelectionManager();
561 MenuElement path[] = e.getPath();
562
563 Point p = e.getPoint();
564 if(p.x >= 0 && p.x < menuItem.getWidth() &&
565 p.y >= 0 && p.y < menuItem.getHeight()) {
566 JMenu menu = (JMenu)menuItem;
567 MenuElement selectedPath[] = manager.getSelectedPath();
568 if(!(selectedPath.length > 0 &&
569 selectedPath[selectedPath.length-1] ==
570 menu.getPopupMenu())) {
571 if(menu.isTopLevelMenu() ||
572 menu.getDelay() == 0 ||
573 e.getID() == MouseEvent.MOUSE_DRAGGED) {
574 appendPath(path, menu.getPopupMenu());
575 } else {
576 manager.setSelectedPath(path);
577 setupPostTimer(menu);
578 }
579 }
580 } else if(e.getID() == MouseEvent.MOUSE_RELEASED) {
581 Component comp = manager.componentForPoint(e.getComponent(), e.getPoint());
582 if (comp == null)
583 manager.clearSelectedPath();
584 }
585
586 }
587 public void menuDragMouseExited(MenuDragMouseEvent e) {}
588 public void menuDragMouseReleased(MenuDragMouseEvent e) {}
589
590
591 //
592 // MenuKeyListener
593 //
594 /**
595 * Open the Menu
596 */
597 public void menuKeyTyped(MenuKeyEvent e) {
598 if (!crossMenuMnemonic && BasicPopupMenuUI.getLastPopup() != null) {
599 // when crossMenuMnemonic is not set, we don't open a toplevel
600 // menu if another toplevel menu is already open
601 return;
602 }
603
604 char key = Character.toLowerCase((char)menuItem.getMnemonic());
605 MenuElement path[] = e.getPath();
606 MenuSelectionManager manager = e.getMenuSelectionManager();
607 if (key == Character.toLowerCase(e.getKeyChar())) {
608 JPopupMenu popupMenu = ((JMenu)menuItem).getPopupMenu();
609 ArrayList newList = new ArrayList(Arrays.asList(path));
610 newList.add(popupMenu);
611 MenuElement subs[] = popupMenu.getSubElements();
612 MenuElement sub =
613 BasicPopupMenuUI.findEnabledChild(subs, -1, true);
614 if(sub != null) {
615 newList.add(sub);
616 }
617 MenuElement newPath[] = new MenuElement[0];;
618 newPath = (MenuElement[]) newList.toArray(newPath);
619 manager.setSelectedPath(newPath);
620 e.consume();
621 } else if (((JMenu)menuItem).isTopLevelMenu()
622 && BasicPopupMenuUI.getLastPopup() == null) {
623 manager.clearSelectedPath();
624 }
625 }
626
627 public void menuKeyPressed(MenuKeyEvent e) {}
628 public void menuKeyReleased(MenuKeyEvent e) {}
629 }
630}