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
26package com.apple.laf;
27
28import sun.lwawt.macosx.CMenuBar;
29
30import java.awt.*;
31import java.awt.event.*;
32import java.util.*;
33
34import javax.swing.*;
35
36import static sun.awt.AWTAccessor.*;
37
38@SuppressWarnings("serial") // JDK implementation class
39public 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
51    public ScreenMenuBar(final JMenuBar swingBar) {
52        fSwingBar = swingBar;
53        fSubmenus = new Hashtable<JMenu, ScreenMenu>(fSwingBar.getMenuCount());
54    }
55
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
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     */
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     */
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     */
131    public void componentResized(final ComponentEvent e)  {}
132
133    /**
134        * Invoked when the component's position changes.
135     */
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     */
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     */
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     */
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
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
187    public void setIcon(final Icon i) {}
188    public void setLabel(final String s) {}
189
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
197    public void setAccelerator(final KeyStroke ks) {}
198    public void setToolTipText(final String tooltip) {}
199
200    // only check and radio items can be indeterminate
201    public void setIndeterminate(boolean indeterminate) { }
202
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     */
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
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