/* * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.apple.laf; import java.awt.*; import java.awt.event.*; import java.beans.PropertyChangeEvent; import javax.swing.*; import javax.swing.event.*; import javax.swing.plaf.*; import javax.swing.plaf.basic.BasicRootPaneUI; import com.apple.laf.AquaUtils.RecyclableSingleton; import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor; /** * From AquaRootPaneUI.java * * The JRootPane manages the default button. There can be only one active rootpane, * and one default button, so we need only one timer * * AquaRootPaneUI is a singleton object */ public class AquaRootPaneUI extends BasicRootPaneUI implements AncestorListener, WindowListener, ContainerListener { private static final RecyclableSingleton<AquaRootPaneUI> sRootPaneUI = new RecyclableSingletonFromDefaultConstructor<AquaRootPaneUI>(AquaRootPaneUI.class); final static int kDefaultButtonPaintDelayBetweenFrames = 50; JButton fCurrentDefaultButton = null; Timer fTimer = null; static final boolean sUseScreenMenuBar = AquaMenuBarUI.getScreenMenuBarProperty(); public static ComponentUI createUI(final JComponent c) { return sRootPaneUI.get(); } public void installUI(final JComponent c) { super.installUI(c); c.addAncestorListener(this); if (c.isShowing() && c.isEnabled()) { updateDefaultButton((JRootPane)c); } // for <rdar://problem/3689020> REGR: Realtime LAF updates no longer work // // because the JFrame parent has a LAF background set (why without a UI element I don't know!) // we have to set it from the root pane so when we are coming from metal we will set it to // the aqua color. // This is because the aqua color is a magical color that gets the background of the window, // so for most other LAFs the root pane changing is enough since it would be opaque, but for us // it is not since we are going to grab the one that was set on the JFrame. :( final Component parent = c.getParent(); if (parent != null && parent instanceof JFrame) { final JFrame frameParent = (JFrame)parent; final Color bg = frameParent.getBackground(); if (bg == null || bg instanceof UIResource) { frameParent.setBackground(UIManager.getColor("Panel.background")); } } // for <rdar://problem/3750909> OutOfMemoryError swapping menus. // Listen for layered pane/JMenuBar updates if the screen menu bar is active. if (sUseScreenMenuBar) { final JRootPane root = (JRootPane)c; root.addContainerListener(this); root.getLayeredPane().addContainerListener(this); } } public void uninstallUI(final JComponent c) { stopTimer(); c.removeAncestorListener(this); if (sUseScreenMenuBar) { final JRootPane root = (JRootPane)c; root.removeContainerListener(this); root.getLayeredPane().removeContainerListener(this); } super.uninstallUI(c); } /** * If the screen menu bar is active we need to listen to the layered pane of the root pane * because it holds the JMenuBar. So, if a new layered pane was added, listen to it. * If a new JMenuBar was added, tell the menu bar UI, because it will need to update the menu bar. */ public void componentAdded(final ContainerEvent e) { if (e.getContainer() instanceof JRootPane) { final JRootPane root = (JRootPane)e.getContainer(); if (e.getChild() == root.getLayeredPane()) { final JLayeredPane layered = root.getLayeredPane(); layered.addContainerListener(this); } } else { if (e.getChild() instanceof JMenuBar) { final JMenuBar jmb = (JMenuBar)e.getChild(); final MenuBarUI mbui = jmb.getUI(); if (mbui instanceof AquaMenuBarUI) { final Window owningWindow = SwingUtilities.getWindowAncestor(jmb); // Could be a JDialog, and may have been added to a JRootPane not yet in a window. if (owningWindow != null && owningWindow instanceof JFrame) { ((AquaMenuBarUI)mbui).setScreenMenuBar((JFrame)owningWindow); } } } } } /** * Likewise, when the layered pane is removed from the root pane, stop listening to it. * If the JMenuBar is removed, tell the menu bar UI to clear the menu bar. */ public void componentRemoved(final ContainerEvent e) { if (e.getContainer() instanceof JRootPane) { final JRootPane root = (JRootPane)e.getContainer(); if (e.getChild() == root.getLayeredPane()) { final JLayeredPane layered = root.getLayeredPane(); layered.removeContainerListener(this); } } else { if (e.getChild() instanceof JMenuBar) { final JMenuBar jmb = (JMenuBar)e.getChild(); final MenuBarUI mbui = jmb.getUI(); if (mbui instanceof AquaMenuBarUI) { final Window owningWindow = SwingUtilities.getWindowAncestor(jmb); // Could be a JDialog, and may have been added to a JRootPane not yet in a window. if (owningWindow != null && owningWindow instanceof JFrame) { ((AquaMenuBarUI)mbui).clearScreenMenuBar((JFrame)owningWindow); } } } } } /** * Invoked when a property changes on the root pane. If the event * indicates the <code>defaultButton</code> has changed, this will * update the animation. * If the enabled state changed, it will start or stop the animation */ public void propertyChange(final PropertyChangeEvent e) { super.propertyChange(e); final String prop = e.getPropertyName(); if ("defaultButton".equals(prop) || "temporaryDefaultButton".equals(prop)) { // Change the animating button if this root is showing and enabled // otherwise do nothing - someone else may be active final JRootPane root = (JRootPane)e.getSource(); if (root.isShowing() && root.isEnabled()) { updateDefaultButton(root); } } else if ("enabled".equals(prop) || AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) { final JRootPane root = (JRootPane)e.getSource(); if (root.isShowing()) { if (((Boolean)e.getNewValue()).booleanValue()) { updateDefaultButton((JRootPane)e.getSource()); } else { stopTimer(); } } } } synchronized void stopTimer() { if (fTimer != null) { fTimer.stop(); fTimer = null; } } synchronized void updateDefaultButton(final JRootPane root) { final JButton button = root.getDefaultButton(); //System.err.println("in updateDefaultButton button = " + button); fCurrentDefaultButton = button; stopTimer(); if (button != null) { fTimer = new Timer(kDefaultButtonPaintDelayBetweenFrames, new DefaultButtonPainter(root)); fTimer.start(); } } class DefaultButtonPainter implements ActionListener { JRootPane root; public DefaultButtonPainter(final JRootPane root) { this.root = root; } public void actionPerformed(final ActionEvent e) { final JButton defaultButton = root.getDefaultButton(); if ((defaultButton != null) && defaultButton.isShowing()) { if (defaultButton.isEnabled()) { defaultButton.repaint(); } } else { stopTimer(); } } } /** * This is sort of like viewDidMoveToWindow:. When the root pane is put into a window * this method gets called for the notification. * We need to set up the listener relationship so we can pick up activation events. * And, if a JMenuBar was added before the root pane was added to the window, we now need * to notify the menu bar UI. */ public void ancestorAdded(final AncestorEvent event) { // this is so we can handle window activated and deactivated events so // our swing controls can color/enable/disable/focus draw correctly final Container ancestor = event.getComponent(); final Window owningWindow = SwingUtilities.getWindowAncestor(ancestor); if (owningWindow != null) { // We get this message even when a dialog is opened and the owning window is a window // that could already be listened to. We should only be a listener once. // adding multiple listeners was the cause of <rdar://problem/3534047> // but the incorrect removal of them caused <rdar://problem/3617848> owningWindow.removeWindowListener(this); owningWindow.addWindowListener(this); } // The root pane has been added to the hierarchy. If it's enabled update the default // button to start the throbbing. Since the UI is a singleton make sure the root pane // we are checking has a default button before calling update otherwise we will stop // throbbing the current default button. final JComponent comp = event.getComponent(); if (comp instanceof JRootPane) { final JRootPane rp = (JRootPane)comp; if (rp.isEnabled() && rp.getDefaultButton() != null) { updateDefaultButton((JRootPane)comp); } } } /** * If the JRootPane was removed from the window we should clear the screen menu bar. * That's a non-trivial problem, because you need to know which window the JRootPane was in * before it was removed. By the time ancestorRemoved was called, the JRootPane has already been removed */ public void ancestorRemoved(final AncestorEvent event) { } public void ancestorMoved(final AncestorEvent event) { } public void windowActivated(final WindowEvent e) { updateComponentTreeUIActivation((Component)e.getSource(), Boolean.TRUE); } public void windowDeactivated(final WindowEvent e) { updateComponentTreeUIActivation((Component)e.getSource(), Boolean.FALSE); } public void windowOpened(final WindowEvent e) { } public void windowClosing(final WindowEvent e) { } public void windowClosed(final WindowEvent e) { // We know the window is closed so remove the listener. final Window w = e.getWindow(); w.removeWindowListener(this); } public void windowIconified(final WindowEvent e) { } public void windowDeiconified(final WindowEvent e) { } public void windowStateChanged(final WindowEvent e) { } public void windowGainedFocus(final WindowEvent e) { } public void windowLostFocus(final WindowEvent e) { } private static void updateComponentTreeUIActivation(final Component c, Object active) { if (c instanceof javax.swing.JInternalFrame) { active = (((JInternalFrame)c).isSelected() ? Boolean.TRUE : Boolean.FALSE); } if (c instanceof javax.swing.JComponent) { ((javax.swing.JComponent)c).putClientProperty(AquaFocusHandler.FRAME_ACTIVE_PROPERTY, active); } Component[] children = null; if (c instanceof javax.swing.JMenu) { children = ((javax.swing.JMenu)c).getMenuComponents(); } else if (c instanceof Container) { children = ((Container)c).getComponents(); } if (children == null) return; for (final Component element : children) { updateComponentTreeUIActivation(element, active); } } }