1 /* 2 * Copyright (c) 2011, 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 com.apple.laf; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.lang.reflect.*; 31 import java.security.*; 32 import java.util.*; 33 34 import javax.swing.*; 35 36 @SuppressWarnings("serial") // JDK implementation class 37 public class ScreenMenuBar extends MenuBar implements ContainerListener, ScreenMenuPropertyHandler, ComponentListener { 38 static boolean sJMenuBarHasHelpMenus = false; //$ could check by calling getHelpMenu in a try block 39 40 JMenuBar fSwingBar; 41 Hashtable<JMenu, ScreenMenu> fSubmenus; 42 43 ScreenMenuPropertyListener fPropertyListener; 44 ScreenMenuPropertyListener fAccessibleListener; 45 46 public ScreenMenuBar(final JMenuBar swingBar) { 47 fSwingBar = swingBar; 48 fSubmenus = new Hashtable<JMenu, ScreenMenu>(fSwingBar.getMenuCount()); 49 } 50 51 public void addNotify() { 52 super.addNotify(); 53 54 fSwingBar.addContainerListener(this); 55 fPropertyListener = new ScreenMenuPropertyListener(this); 56 fSwingBar.addPropertyChangeListener(fPropertyListener); 57 fAccessibleListener = new ScreenMenuPropertyListener(this); 58 fSwingBar.getAccessibleContext().addPropertyChangeListener(fAccessibleListener); 59 60 // We disable component events when the menu bar is not parented. So now we need to 61 // sync back up with the current state of the JMenuBar. We first add the menus we 62 // don't have and then remove the items that are no longer on the JMenuBar. 63 final int count = fSwingBar.getMenuCount(); 64 for(int i = 0; i < count ; i++) { 65 final JMenu m = fSwingBar.getMenu(i); 66 if (m != null) { 67 addSubmenu(m); 68 } 69 } 70 71 final Enumeration<JMenu> e = fSubmenus.keys(); 72 while (e.hasMoreElements()) { 73 final JMenu m = e.nextElement(); 74 if (fSwingBar.getComponentIndex(m) == -1) { 75 removeSubmenu(m); 76 } 77 } 78 } 79 80 public void removeNotify() { 81 // KCH - 3974930 - We do null checks for fSwingBar and fSubmenus because some people are using 82 // reflection to muck about with our ivars 83 if (fSwingBar != null) { 84 fSwingBar.removePropertyChangeListener(fPropertyListener); 85 fSwingBar.getAccessibleContext().removePropertyChangeListener(fAccessibleListener); 86 fSwingBar.removeContainerListener(this); 87 } 88 89 fPropertyListener = null; 90 fAccessibleListener = null; 91 92 if (fSubmenus != null) { 93 // We don't listen to events when the menu bar is not parented. 94 // Remove all the component listeners. 95 final Enumeration<JMenu> e = fSubmenus.keys(); 96 while (e.hasMoreElements()) { 97 final JMenu m = e.nextElement(); 98 m.removeComponentListener(this); 99 } 100 } 101 102 super.removeNotify(); 103 } 104 105 /** 106 * Invoked when a component has been added to the container. 107 */ 108 public void componentAdded(final ContainerEvent e) { 109 final Component child = e.getChild(); 110 if (!(child instanceof JMenu)) return; 111 addSubmenu((JMenu)child); 112 } 113 114 /** 115 * Invoked when a component has been removed from the container. 116 */ 117 public void componentRemoved(final ContainerEvent e) { 118 final Component child = e.getChild(); 119 if (!(child instanceof JMenu)) return; 120 removeSubmenu((JMenu)child); 121 } 122 123 /** 124 * Invoked when the component's size changes. 125 */ 126 public void componentResized(final ComponentEvent e) {} 127 128 /** 129 * Invoked when the component's position changes. 130 */ 131 public void componentMoved(final ComponentEvent e) {} 132 133 /** 134 * Invoked when the component has been made visible. 135 * See componentHidden - we should still have a MenuItem 136 * it just isn't inserted 137 */ 138 public void componentShown(final ComponentEvent e) { 139 final Object source = e.getSource(); 140 if (!(source instanceof JMenuItem)) return; 141 setChildVisible((JMenuItem)source, true); 142 } 143 144 /** 145 * Invoked when the component has been made invisible. 146 * MenuComponent.setVisible does nothing, 147 * so we remove the ScreenMenuItem from the ScreenMenu 148 * but leave it in fItems 149 */ 150 public void componentHidden(final ComponentEvent e) { 151 final Object source = e.getSource(); 152 if (!(source instanceof JMenuItem)) return; 153 setChildVisible((JMenuItem)source, false); 154 } 155 156 /* 157 * MenuComponent.setVisible does nothing, 158 * so we just add or remove the child from the ScreenMenuBar 159 * but leave it in the list 160 */ 161 public void setChildVisible(final JMenuItem child, final boolean b) { 162 if (child instanceof JMenu) { 163 if (b) { 164 addSubmenu((JMenu)child); 165 } else { 166 final ScreenMenu sm = fSubmenus.get(child); 167 if (sm != null) 168 remove(sm); 169 } 170 } 171 } 172 173 public void removeAll() { 174 synchronized (getTreeLock()) { 175 final int nitems = getMenuCount(); 176 for (int i = nitems-1 ; i >= 0 ; i--) { 177 remove(i); 178 } 179 } 180 } 181 182 public void setIcon(final Icon i) {} 183 public void setLabel(final String s) {} 184 185 public void setEnabled(final boolean b) { 186 final int count = fSwingBar.getMenuCount(); 187 for (int i = 0; i < count; i++) { 188 fSwingBar.getMenu(i).setEnabled(b); 189 } 190 } 191 192 public void setAccelerator(final KeyStroke ks) {} 193 public void setToolTipText(final String tooltip) {} 194 195 // only check and radio items can be indeterminate 196 public void setIndeterminate(boolean indeterminate) { } 197 198 ScreenMenu addSubmenu(final JMenu m) { 199 ScreenMenu sm = fSubmenus.get(m); 200 201 if (sm == null) { 202 sm = new ScreenMenu(m); 203 m.addComponentListener(this); 204 fSubmenus.put(m, sm); 205 } 206 207 sm.setEnabled(m.isEnabled()); 208 209 // MenuComponents don't support setVisible, so we just don't add it to the menubar 210 if (m.isVisible() && sm.getParent() == null) { 211 int newIndex = 0, currVisibleIndex = 0; 212 JMenu menu = null; 213 final int menuCount = fSwingBar.getMenuCount(); 214 for (int i = 0; i < menuCount; i++) { 215 menu = fSwingBar.getMenu(i); 216 if (menu == m) { 217 newIndex = currVisibleIndex; 218 break; 219 } 220 if (menu != null && menu.isVisible()) { 221 currVisibleIndex++; 222 } 223 } 224 add(sm, newIndex); 225 } 226 227 return sm; 228 } 229 230 /** 231 * Remove the screen menu associated with the specifiec menu. This 232 * also removes any associated component listener on the screen menu 233 * and removes the key/value (menu/screen menu) from the fSubmenus cache. 234 * 235 * @param menu The swing menu we want to remove the screen menu for. 236 */ 237 private void removeSubmenu(final JMenu menu) { 238 final ScreenMenu screenMenu = fSubmenus.get(menu); 239 if (screenMenu == null) return; 240 241 menu.removeComponentListener(this); 242 remove(screenMenu); 243 fSubmenus.remove(menu); 244 } 245 246 private static Field[] stolenFields = null; 247 248 static { 249 stolenFields = AccessController.doPrivileged(new PrivilegedAction<Field[]>() { 250 public Field[] run() { 251 try { 252 final Field[] localFields = new Field[2]; 253 localFields[0] = MenuBar.class.getDeclaredField("menus"); 254 localFields[1] = MenuComponent.class.getDeclaredField("parent"); 255 AccessibleObject.setAccessible(localFields, true); 256 return localFields; 257 } catch (final NoSuchFieldException nsf) { 258 // If this happens, Sun changed the definition of MenuBar and MenuComponent! 259 nsf.printStackTrace(System.err); 260 return null; 261 } 262 } 263 }); 264 }; 265 266 public Menu add(final Menu m, final int index) { 267 synchronized (getTreeLock()) { 268 if (m.getParent() != null) { 269 m.getParent().remove(m); 270 } 271 272 // Use nasty reflection to get at the menus array and parent fields. 273 try { 274 if (stolenFields == null) return m; 275 276 @SuppressWarnings("unchecked") 277 final Vector<Menu> menus = (Vector<Menu>)stolenFields[0].get(this); 278 menus.insertElementAt(m, index); 279 280 stolenFields[1].set(m, this); 281 282 final sun.lwawt.macosx.CMenuBar peer = (sun.lwawt.macosx.CMenuBar)getPeer(); 283 if (peer == null) return m; 284 285 peer.setNextInsertionIndex(index); 286 if (m.getPeer() == null) { 287 m.addNotify(); 288 } 289 290 peer.setNextInsertionIndex(-1); 291 } catch (final IllegalAccessException iae) { 292 iae.printStackTrace(System.err); 293 } 294 295 return m; 296 } 297 } 298 }