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</code> 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 }
--- EOF ---