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