Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaRootPaneUI.java
41154 views
/*1* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;2627import java.awt.*;28import java.awt.event.*;29import java.beans.PropertyChangeEvent;3031import javax.swing.*;32import javax.swing.event.*;33import javax.swing.plaf.*;34import javax.swing.plaf.basic.BasicRootPaneUI;3536import com.apple.laf.AquaUtils.RecyclableSingleton;37import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;3839/**40* From AquaRootPaneUI.java41*42* The JRootPane manages the default button. There can be only one active rootpane,43* and one default button, so we need only one timer44*45* AquaRootPaneUI is a singleton object46*/47public class AquaRootPaneUI extends BasicRootPaneUI implements AncestorListener, WindowListener, ContainerListener {48private static final RecyclableSingleton<AquaRootPaneUI> sRootPaneUI = new RecyclableSingletonFromDefaultConstructor<AquaRootPaneUI>(AquaRootPaneUI.class);4950static final int kDefaultButtonPaintDelayBetweenFrames = 50;51JButton fCurrentDefaultButton = null;52Timer fTimer = null;53static final boolean sUseScreenMenuBar = AquaMenuBarUI.getScreenMenuBarProperty();5455public static ComponentUI createUI(final JComponent c) {56return sRootPaneUI.get();57}5859public void installUI(final JComponent c) {60super.installUI(c);61c.addAncestorListener(this);6263if (c.isShowing() && c.isEnabled()) {64updateDefaultButton((JRootPane)c);65}6667// for <rdar://problem/3689020> REGR: Realtime LAF updates no longer work68//69// because the JFrame parent has a LAF background set (why without a UI element I don't know!)70// we have to set it from the root pane so when we are coming from metal we will set it to71// the aqua color.72// This is because the aqua color is a magical color that gets the background of the window,73// so for most other LAFs the root pane changing is enough since it would be opaque, but for us74// it is not since we are going to grab the one that was set on the JFrame. :(75final Component parent = c.getParent();7677if (parent != null && parent instanceof JFrame) {78final JFrame frameParent = (JFrame)parent;79final Color bg = frameParent.getBackground();80if (bg == null || bg instanceof UIResource) {81frameParent.setBackground(UIManager.getColor("Panel.background"));82}83}8485// for <rdar://problem/3750909> OutOfMemoryError swapping menus.86// Listen for layered pane/JMenuBar updates if the screen menu bar is active.87if (sUseScreenMenuBar) {88final JRootPane root = (JRootPane)c;89root.addContainerListener(this);90root.getLayeredPane().addContainerListener(this);91}92}9394public void uninstallUI(final JComponent c) {95stopTimer();96c.removeAncestorListener(this);9798if (sUseScreenMenuBar) {99final JRootPane root = (JRootPane)c;100root.removeContainerListener(this);101root.getLayeredPane().removeContainerListener(this);102}103104super.uninstallUI(c);105}106107/**108* If the screen menu bar is active we need to listen to the layered pane of the root pane109* because it holds the JMenuBar. So, if a new layered pane was added, listen to it.110* If a new JMenuBar was added, tell the menu bar UI, because it will need to update the menu bar.111*/112public void componentAdded(final ContainerEvent e) {113if (e.getContainer() instanceof JRootPane) {114final JRootPane root = (JRootPane)e.getContainer();115if (e.getChild() == root.getLayeredPane()) {116final JLayeredPane layered = root.getLayeredPane();117layered.addContainerListener(this);118}119} else {120if (e.getChild() instanceof JMenuBar) {121final JMenuBar jmb = (JMenuBar)e.getChild();122final MenuBarUI mbui = jmb.getUI();123124if (mbui instanceof AquaMenuBarUI) {125final Window owningWindow = SwingUtilities.getWindowAncestor(jmb);126127// Could be a JDialog, and may have been added to a JRootPane not yet in a window.128if (owningWindow != null && owningWindow instanceof JFrame) {129((AquaMenuBarUI)mbui).setScreenMenuBar((JFrame)owningWindow);130}131}132}133}134}135136/**137* Likewise, when the layered pane is removed from the root pane, stop listening to it.138* If the JMenuBar is removed, tell the menu bar UI to clear the menu bar.139*/140public void componentRemoved(final ContainerEvent e) {141if (e.getContainer() instanceof JRootPane) {142final JRootPane root = (JRootPane)e.getContainer();143if (e.getChild() == root.getLayeredPane()) {144final JLayeredPane layered = root.getLayeredPane();145layered.removeContainerListener(this);146}147} else {148if (e.getChild() instanceof JMenuBar) {149final JMenuBar jmb = (JMenuBar)e.getChild();150final MenuBarUI mbui = jmb.getUI();151152if (mbui instanceof AquaMenuBarUI) {153final Window owningWindow = SwingUtilities.getWindowAncestor(jmb);154155// Could be a JDialog, and may have been added to a JRootPane not yet in a window.156if (owningWindow != null && owningWindow instanceof JFrame) {157((AquaMenuBarUI)mbui).clearScreenMenuBar((JFrame)owningWindow);158}159}160}161}162}163164/**165* Invoked when a property changes on the root pane. If the event166* indicates the {@code defaultButton} has changed, this will167* update the animation.168* If the enabled state changed, it will start or stop the animation169*/170public void propertyChange(final PropertyChangeEvent e) {171super.propertyChange(e);172173final String prop = e.getPropertyName();174if ("defaultButton".equals(prop) || "temporaryDefaultButton".equals(prop)) {175// Change the animating button if this root is showing and enabled176// otherwise do nothing - someone else may be active177final JRootPane root = (JRootPane)e.getSource();178179if (root.isShowing() && root.isEnabled()) {180updateDefaultButton(root);181}182} else if ("enabled".equals(prop) || AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) {183final JRootPane root = (JRootPane)e.getSource();184if (root.isShowing()) {185if (((Boolean)e.getNewValue()).booleanValue()) {186updateDefaultButton((JRootPane)e.getSource());187} else {188stopTimer();189}190}191}192}193194synchronized void stopTimer() {195if (fTimer != null) {196fTimer.stop();197fTimer = null;198}199}200201synchronized void updateDefaultButton(final JRootPane root) {202final JButton button = root.getDefaultButton();203//System.err.println("in updateDefaultButton button = " + button);204fCurrentDefaultButton = button;205stopTimer();206if (button != null) {207fTimer = new Timer(kDefaultButtonPaintDelayBetweenFrames, new DefaultButtonPainter(root));208fTimer.start();209}210}211212class DefaultButtonPainter implements ActionListener {213JRootPane root;214215public DefaultButtonPainter(final JRootPane root) {216this.root = root;217}218219public void actionPerformed(final ActionEvent e) {220final JButton defaultButton = root.getDefaultButton();221if ((defaultButton != null) && defaultButton.isShowing()) {222if (defaultButton.isEnabled()) {223defaultButton.repaint();224}225} else {226stopTimer();227}228}229}230231/**232* This is sort of like viewDidMoveToWindow:. When the root pane is put into a window233* this method gets called for the notification.234* We need to set up the listener relationship so we can pick up activation events.235* And, if a JMenuBar was added before the root pane was added to the window, we now need236* to notify the menu bar UI.237*/238public void ancestorAdded(final AncestorEvent event) {239// this is so we can handle window activated and deactivated events so240// our swing controls can color/enable/disable/focus draw correctly241final Container ancestor = event.getComponent();242final Window owningWindow = SwingUtilities.getWindowAncestor(ancestor);243244if (owningWindow != null) {245// We get this message even when a dialog is opened and the owning window is a window246// that could already be listened to. We should only be a listener once.247// adding multiple listeners was the cause of <rdar://problem/3534047>248// but the incorrect removal of them caused <rdar://problem/3617848>249owningWindow.removeWindowListener(this);250owningWindow.addWindowListener(this);251}252253// The root pane has been added to the hierarchy. If it's enabled update the default254// button to start the throbbing. Since the UI is a singleton make sure the root pane255// we are checking has a default button before calling update otherwise we will stop256// throbbing the current default button.257final JComponent comp = event.getComponent();258if (comp instanceof JRootPane) {259final JRootPane rp = (JRootPane)comp;260if (rp.isEnabled() && rp.getDefaultButton() != null) {261updateDefaultButton((JRootPane)comp);262}263}264}265266/**267* If the JRootPane was removed from the window we should clear the screen menu bar.268* That's a non-trivial problem, because you need to know which window the JRootPane was in269* before it was removed. By the time ancestorRemoved was called, the JRootPane has already been removed270*/271272public void ancestorRemoved(final AncestorEvent event) { }273public void ancestorMoved(final AncestorEvent event) { }274275public void windowActivated(final WindowEvent e) {276updateComponentTreeUIActivation((Component)e.getSource(), Boolean.TRUE);277}278279public void windowDeactivated(final WindowEvent e) {280updateComponentTreeUIActivation((Component)e.getSource(), Boolean.FALSE);281}282283public void windowOpened(final WindowEvent e) { }284public void windowClosing(final WindowEvent e) { }285286public void windowClosed(final WindowEvent e) {287// We know the window is closed so remove the listener.288final Window w = e.getWindow();289w.removeWindowListener(this);290}291292public void windowIconified(final WindowEvent e) { }293public void windowDeiconified(final WindowEvent e) { }294public void windowStateChanged(final WindowEvent e) { }295public void windowGainedFocus(final WindowEvent e) { }296public void windowLostFocus(final WindowEvent e) { }297298private static void updateComponentTreeUIActivation(final Component c, Object active) {299if (c instanceof javax.swing.JInternalFrame) {300active = (((JInternalFrame)c).isSelected() ? Boolean.TRUE : Boolean.FALSE);301}302303if (c instanceof javax.swing.JComponent) {304((javax.swing.JComponent)c).putClientProperty(AquaFocusHandler.FRAME_ACTIVE_PROPERTY, active);305}306307Component[] children = null;308309if (c instanceof javax.swing.JMenu) {310children = ((javax.swing.JMenu)c).getMenuComponents();311} else if (c instanceof Container) {312children = ((Container)c).getComponents();313}314315if (children == null) return;316317for (final Component element : children) {318updateComponentTreeUIActivation(element, active);319}320}321322@Override323public final void update(final Graphics g, final JComponent c) {324if (c.isOpaque()) {325AquaUtils.fillRect(g, c);326}327paint(g, c);328}329}330331332