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