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 }