1 /* 2 * Copyright (c) 2011, 2012, 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 java.awt.*; 29 import java.awt.event.*; 30 import java.beans.PropertyChangeEvent; 31 32 import javax.swing.*; 33 import javax.swing.event.*; 34 import javax.swing.plaf.*; 35 import javax.swing.plaf.basic.BasicRootPaneUI; 36 37 import com.apple.laf.AquaUtils.RecyclableSingleton; 38 import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor; 39 40 /** 41 * From AquaRootPaneUI.java 42 * 43 * The JRootPane manages the default button. There can be only one active rootpane, 44 * and one default button, so we need only one timer 45 * 46 * AquaRootPaneUI is a singleton object 47 */ 48 public class AquaRootPaneUI extends BasicRootPaneUI implements AncestorListener, WindowListener, ContainerListener { 49 private static final RecyclableSingleton<AquaRootPaneUI> sRootPaneUI = new RecyclableSingletonFromDefaultConstructor<AquaRootPaneUI>(AquaRootPaneUI.class); 50 51 static final int kDefaultButtonPaintDelayBetweenFrames = 50; 52 JButton fCurrentDefaultButton = null; 53 Timer fTimer = null; 54 static final boolean sUseScreenMenuBar = AquaMenuBarUI.getScreenMenuBarProperty(); 55 56 public static ComponentUI createUI(final JComponent c) { 57 return sRootPaneUI.get(); 58 } 59 60 public void installUI(final JComponent c) { 61 super.installUI(c); 62 c.addAncestorListener(this); 63 64 if (c.isShowing() && c.isEnabled()) { 65 updateDefaultButton((JRootPane)c); 66 } 67 68 // for <rdar://problem/3689020> REGR: Realtime LAF updates no longer work 69 // 70 // because the JFrame parent has a LAF background set (why without a UI element I don't know!) 71 // we have to set it from the root pane so when we are coming from metal we will set it to 72 // the aqua color. 73 // This is because the aqua color is a magical color that gets the background of the window, 74 // so for most other LAFs the root pane changing is enough since it would be opaque, but for us 75 // it is not since we are going to grab the one that was set on the JFrame. :( 76 final Component parent = c.getParent(); 77 78 if (parent != null && parent instanceof JFrame) { 79 final JFrame frameParent = (JFrame)parent; 80 final Color bg = frameParent.getBackground(); 81 if (bg == null || bg instanceof UIResource) { 82 frameParent.setBackground(UIManager.getColor("Panel.background")); 83 } 84 } 85 86 // for <rdar://problem/3750909> OutOfMemoryError swapping menus. 87 // Listen for layered pane/JMenuBar updates if the screen menu bar is active. 88 if (sUseScreenMenuBar) { 89 final JRootPane root = (JRootPane)c; 90 root.addContainerListener(this); 91 root.getLayeredPane().addContainerListener(this); 92 } 93 } 94 95 public void uninstallUI(final JComponent c) { 96 stopTimer(); 97 c.removeAncestorListener(this); 98 99 if (sUseScreenMenuBar) { 100 final JRootPane root = (JRootPane)c; 101 root.removeContainerListener(this); 102 root.getLayeredPane().removeContainerListener(this); 103 } 104 105 super.uninstallUI(c); 106 } 107 108 /** 109 * If the screen menu bar is active we need to listen to the layered pane of the root pane 110 * because it holds the JMenuBar. So, if a new layered pane was added, listen to it. 111 * If a new JMenuBar was added, tell the menu bar UI, because it will need to update the menu bar. 112 */ 113 public void componentAdded(final ContainerEvent e) { 114 if (e.getContainer() instanceof JRootPane) { 115 final JRootPane root = (JRootPane)e.getContainer(); 116 if (e.getChild() == root.getLayeredPane()) { 117 final JLayeredPane layered = root.getLayeredPane(); 118 layered.addContainerListener(this); 119 } 120 } else { 121 if (e.getChild() instanceof JMenuBar) { 122 final JMenuBar jmb = (JMenuBar)e.getChild(); 123 final MenuBarUI mbui = jmb.getUI(); 124 125 if (mbui instanceof AquaMenuBarUI) { 126 final Window owningWindow = SwingUtilities.getWindowAncestor(jmb); 127 128 // Could be a JDialog, and may have been added to a JRootPane not yet in a window. 129 if (owningWindow != null && owningWindow instanceof JFrame) { 130 ((AquaMenuBarUI)mbui).setScreenMenuBar((JFrame)owningWindow); 131 } 132 } 133 } 134 } 135 } 136 137 /** 138 * Likewise, when the layered pane is removed from the root pane, stop listening to it. 139 * If the JMenuBar is removed, tell the menu bar UI to clear the menu bar. 140 */ 141 public void componentRemoved(final ContainerEvent e) { 142 if (e.getContainer() instanceof JRootPane) { 143 final JRootPane root = (JRootPane)e.getContainer(); 144 if (e.getChild() == root.getLayeredPane()) { 145 final JLayeredPane layered = root.getLayeredPane(); 146 layered.removeContainerListener(this); 147 } 148 } else { 149 if (e.getChild() instanceof JMenuBar) { 150 final JMenuBar jmb = (JMenuBar)e.getChild(); 151 final MenuBarUI mbui = jmb.getUI(); 152 153 if (mbui instanceof AquaMenuBarUI) { 154 final Window owningWindow = SwingUtilities.getWindowAncestor(jmb); 155 156 // Could be a JDialog, and may have been added to a JRootPane not yet in a window. 157 if (owningWindow != null && owningWindow instanceof JFrame) { 158 ((AquaMenuBarUI)mbui).clearScreenMenuBar((JFrame)owningWindow); 159 } 160 } 161 } 162 } 163 } 164 165 /** 166 * Invoked when a property changes on the root pane. If the event 167 * indicates the {@code defaultButton} has changed, this will 168 * update the animation. 169 * If the enabled state changed, it will start or stop the animation 170 */ 171 public void propertyChange(final PropertyChangeEvent e) { 172 super.propertyChange(e); 173 174 final String prop = e.getPropertyName(); 175 if ("defaultButton".equals(prop) || "temporaryDefaultButton".equals(prop)) { 176 // Change the animating button if this root is showing and enabled 177 // otherwise do nothing - someone else may be active 178 final JRootPane root = (JRootPane)e.getSource(); 179 180 if (root.isShowing() && root.isEnabled()) { 181 updateDefaultButton(root); 182 } 183 } else if ("enabled".equals(prop) || AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) { 184 final JRootPane root = (JRootPane)e.getSource(); 185 if (root.isShowing()) { 186 if (((Boolean)e.getNewValue()).booleanValue()) { 187 updateDefaultButton((JRootPane)e.getSource()); 188 } else { 189 stopTimer(); 190 } 191 } 192 } 193 } 194 195 synchronized void stopTimer() { 196 if (fTimer != null) { 197 fTimer.stop(); 198 fTimer = null; 199 } 200 } 201 202 synchronized void updateDefaultButton(final JRootPane root) { 203 final JButton button = root.getDefaultButton(); 204 //System.err.println("in updateDefaultButton button = " + button); 205 fCurrentDefaultButton = button; 206 stopTimer(); 207 if (button != null) { 208 fTimer = new Timer(kDefaultButtonPaintDelayBetweenFrames, new DefaultButtonPainter(root)); 209 fTimer.start(); 210 } 211 } 212 213 class DefaultButtonPainter implements ActionListener { 214 JRootPane root; 215 216 public DefaultButtonPainter(final JRootPane root) { 217 this.root = root; 218 } 219 220 public void actionPerformed(final ActionEvent e) { 221 final JButton defaultButton = root.getDefaultButton(); 222 if ((defaultButton != null) && defaultButton.isShowing()) { 223 if (defaultButton.isEnabled()) { 224 defaultButton.repaint(); 225 } 226 } else { 227 stopTimer(); 228 } 229 } 230 } 231 232 /** 233 * This is sort of like viewDidMoveToWindow:. When the root pane is put into a window 234 * this method gets called for the notification. 235 * We need to set up the listener relationship so we can pick up activation events. 236 * And, if a JMenuBar was added before the root pane was added to the window, we now need 237 * to notify the menu bar UI. 238 */ 239 public void ancestorAdded(final AncestorEvent event) { 240 // this is so we can handle window activated and deactivated events so 241 // our swing controls can color/enable/disable/focus draw correctly 242 final Container ancestor = event.getComponent(); 243 final Window owningWindow = SwingUtilities.getWindowAncestor(ancestor); 244 245 if (owningWindow != null) { 246 // We get this message even when a dialog is opened and the owning window is a window 247 // that could already be listened to. We should only be a listener once. 248 // adding multiple listeners was the cause of <rdar://problem/3534047> 249 // but the incorrect removal of them caused <rdar://problem/3617848> 250 owningWindow.removeWindowListener(this); 251 owningWindow.addWindowListener(this); 252 } 253 254 // The root pane has been added to the hierarchy. If it's enabled update the default 255 // button to start the throbbing. Since the UI is a singleton make sure the root pane 256 // we are checking has a default button before calling update otherwise we will stop 257 // throbbing the current default button. 258 final JComponent comp = event.getComponent(); 259 if (comp instanceof JRootPane) { 260 final JRootPane rp = (JRootPane)comp; 261 if (rp.isEnabled() && rp.getDefaultButton() != null) { 262 updateDefaultButton((JRootPane)comp); 263 } 264 } 265 } 266 267 /** 268 * If the JRootPane was removed from the window we should clear the screen menu bar. 269 * That's a non-trivial problem, because you need to know which window the JRootPane was in 270 * before it was removed. By the time ancestorRemoved was called, the JRootPane has already been removed 271 */ 272 273 public void ancestorRemoved(final AncestorEvent event) { } 274 public void ancestorMoved(final AncestorEvent event) { } 275 276 public void windowActivated(final WindowEvent e) { 277 updateComponentTreeUIActivation((Component)e.getSource(), Boolean.TRUE); 278 } 279 280 public void windowDeactivated(final WindowEvent e) { 281 updateComponentTreeUIActivation((Component)e.getSource(), Boolean.FALSE); 282 } 283 284 public void windowOpened(final WindowEvent e) { } 285 public void windowClosing(final WindowEvent e) { } 286 287 public void windowClosed(final WindowEvent e) { 288 // We know the window is closed so remove the listener. 289 final Window w = e.getWindow(); 290 w.removeWindowListener(this); 291 } 292 293 public void windowIconified(final WindowEvent e) { } 294 public void windowDeiconified(final WindowEvent e) { } 295 public void windowStateChanged(final WindowEvent e) { } 296 public void windowGainedFocus(final WindowEvent e) { } 297 public void windowLostFocus(final WindowEvent e) { } 298 299 private static void updateComponentTreeUIActivation(final Component c, Object active) { 300 if (c instanceof javax.swing.JInternalFrame) { 301 active = (((JInternalFrame)c).isSelected() ? Boolean.TRUE : Boolean.FALSE); 302 } 303 304 if (c instanceof javax.swing.JComponent) { 305 ((javax.swing.JComponent)c).putClientProperty(AquaFocusHandler.FRAME_ACTIVE_PROPERTY, active); 306 } 307 308 Component[] children = null; 309 310 if (c instanceof javax.swing.JMenu) { 311 children = ((javax.swing.JMenu)c).getMenuComponents(); 312 } else if (c instanceof Container) { 313 children = ((Container)c).getComponents(); 314 } 315 316 if (children == null) return; 317 318 for (final Component element : children) { 319 updateComponentTreeUIActivation(element, active); 320 } 321 } 322 323 @Override 324 public final void update(final Graphics g, final JComponent c) { 325 if (c.isOpaque()) { 326 AquaUtils.fillRect(g, c); 327 } 328 paint(g, c); 329 } 330 }