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