KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > applemenu > ApplePopupFactory


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.applemenu;
21
22 import java.awt.Component JavaDoc;
23 import java.awt.Container JavaDoc;
24 import java.awt.Dimension JavaDoc;
25 import java.awt.Point JavaDoc;
26 import java.awt.Rectangle JavaDoc;
27 import java.lang.ref.Reference JavaDoc;
28 import java.lang.ref.SoftReference JavaDoc;
29 import java.lang.reflect.Method JavaDoc;
30 import java.util.HashSet JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.Set JavaDoc;
33 import javax.swing.JComponent JavaDoc;
34 import javax.swing.JDialog JavaDoc;
35 import javax.swing.JFrame JavaDoc;
36 import javax.swing.JWindow JavaDoc;
37 import javax.swing.Popup JavaDoc;
38 import javax.swing.PopupFactory JavaDoc;
39 import javax.swing.SwingUtilities JavaDoc;
40 import org.openide.ErrorManager;
41
42
43
44 /**
45  * Heavyweight popups created using PopupFactory are on mac-os will have a
46  * drop-shadow, which is exactly what we don't want for explorer tooltips that
47  * are supposed to look like a seamless part of the component they appear
48  * over.
49  *
50  * This class decides accurately if a heavyweight popup is needed, and if it
51  * is, uses a background color hack (which alas, only works about 60% of the
52  * time) to attempt to eliminate the drop shadow on the window it uses.
53  *
54  * Caveats: 1. Heavyweight popups from ViewTooltips are currently simply
55  * disabled by default. Enable them by setting the system property
56  * "nb.explorer.hw.completions" to "true". This only affects macintosh.
57  * They are off by default because sometimes the popup window will have a
58  * drop shadow no matter what we do.
59  *
60  * If using a macintosh and "nb.explorer.hw.completions" is "true" then,
61  * "nb.explorer.hw.cocoahack" can also be set true. If it is true one of
62  * two things will happen: 1. If System/Library/Java is on NetBeans' classpath
63  * (so the cocoa java classes are accessible), then we will have heavyweight
64  * popups that have no drop shadow 100% of the time. 2. If the cocoa java
65  * classes cannot be loaded, the standard Swing PopupFactory will be used,
66  * which means the drop-shadow problem will be visible.
67  *
68  * @see org.openide.explorer.view.ViewTooltips
69  *
70  * @author Tim Boudreau
71  */

72 public class ApplePopupFactory extends PopupFactory JavaDoc {
73     private static final boolean APPLE_HEAVYWEIGHT =
74             Boolean.getBoolean ("nb.explorer.hw.completions"); //NOI18N
75

76     private static final boolean APPLE_COCOA_HACK = APPLE_HEAVYWEIGHT &&
77             Boolean.getBoolean ("nb.explorer.hw.cocoahack"); //NOI18N
78

79     private static Set JavaDoc windowPool = new HashSet JavaDoc();
80     
81     //As is, the background color hack in this class works about 60% of
82
//the time to get rid of the drop shadow on heavyweight popups, and
83
//this class will reliably prefer a lightweight popup wherever possible,
84
//which Apple's implementation doesn't. So it is useful without the
85
//egregious hack...it will just work 100% with it.
86
//To be continued...
87
public ApplePopupFactory() {
88     }
89     
90     public Popup JavaDoc getPopup(Component JavaDoc owner, Component JavaDoc contents,
91                           int x, int y) throws IllegalArgumentException JavaDoc {
92         assert owner instanceof JComponent JavaDoc;
93         Dimension JavaDoc d = contents.getPreferredSize();
94         Container JavaDoc c = ((JComponent JavaDoc) owner).getTopLevelAncestor();
95         if (c == null) {
96             throw new IllegalArgumentException JavaDoc ("Not onscreen: " + owner);
97         }
98         Point JavaDoc p = new Point JavaDoc (x, y);
99         SwingUtilities.convertPointFromScreen(p, c);
100         Rectangle JavaDoc r = new Rectangle JavaDoc (p.x, p.y, d.width, d.height);
101         if (c.getBounds().contains(r)) {
102             //XXX need API to determine if editor area comp is heavyweight,
103
//and if so, return a "medium weight" popup of a java.awt.Component
104
//that embeds the passed contents component
105
return new LWPopup (owner, contents, x, y);
106         } else {
107             return APPLE_HEAVYWEIGHT ?
108                 (Popup JavaDoc) new HWPopup (owner, contents, x, y) :
109                 (Popup JavaDoc) new NullPopup();
110         }
111     }
112     
113     private static final class NullPopup extends Popup JavaDoc {
114         public void show() {}
115         public void hide() {}
116     }
117     
118     private static abstract class OurPopup extends Popup JavaDoc {
119         protected Component JavaDoc owner = null;
120         protected Component JavaDoc contents = null;
121         protected int x = -1;
122         protected int y = -1;
123         public OurPopup (Component JavaDoc owner, Component JavaDoc contents, int x, int y) {
124             configure (owner, contents, x, y);
125         }
126         
127         final void configure (Component JavaDoc owner, Component JavaDoc contents, int x, int y) {
128             this.owner = owner;
129             this.contents = contents;
130             this.x = x;
131             this.y = y;
132         }
133         
134         protected abstract void prepareResources();
135         protected abstract void doShow();
136         public abstract boolean isShowing();
137         protected abstract void doHide();
138         
139         public final void show() {
140             prepareResources();
141             doShow();
142         }
143         
144         public final void hide() {
145             doHide();
146         }
147         
148         void dispose() {
149             owner = null;
150             contents = null;
151             x = -1;
152             y = -1;
153         }
154         
155         private boolean canReuse = false;
156         public final void clear() {
157             canReuse = true;
158             dispose();
159         }
160         
161         boolean isInUse() {
162             return canReuse;
163         }
164     }
165     
166     private static class LWPopup extends OurPopup {
167         public LWPopup (Component JavaDoc owner, Component JavaDoc contents, int x, int y) {
168             super (owner, contents, x, y);
169         }
170
171         private Rectangle JavaDoc bounds = null;
172         protected void prepareResources() {
173             JComponent JavaDoc jc = (JComponent JavaDoc) owner;
174             Container JavaDoc w = jc.getTopLevelAncestor();
175             JComponent JavaDoc pane = null;
176             if (w instanceof JFrame JavaDoc) {
177                 pane = (JComponent JavaDoc) ((JFrame JavaDoc) w).getGlassPane();
178             } else if (w instanceof JDialog JavaDoc) {
179                 pane = (JComponent JavaDoc) ((JDialog JavaDoc) w).getGlassPane();
180             } else if (w instanceof JWindow JavaDoc) {
181                 pane = (JComponent JavaDoc) ((JWindow JavaDoc) w).getGlassPane();
182             }
183             if (w == null) {
184                 throw new IllegalArgumentException JavaDoc ("Not a JFrame/" + //NOI18N
185
"JWindow/JDialog: " + owner); //NOI18N
186
}
187             Point JavaDoc p = new Point JavaDoc (x, y);
188             SwingUtilities.convertPointFromScreen(p, pane);
189             if (pane.getLayout() != null) {
190                 pane.setLayout (null);
191             }
192             pane.setVisible(true);
193             contents.setVisible (false);
194             Dimension JavaDoc d = contents.getPreferredSize();
195             pane.add (contents);
196             bounds = new Rectangle JavaDoc (p.x, p.y, d.width, d.height);
197             contents.setBounds (p.x, p.y, d.width, d.height);
198         }
199         
200         protected void doShow() {
201             contents.setVisible (true);
202         }
203         
204         public boolean isShowing() {
205             return contents != null && contents.isShowing();
206         }
207         
208         protected void doHide() {
209             Container JavaDoc parent = contents.getParent();
210             if (parent != null) {
211                 contents.getParent().remove (contents);
212                 parent.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
213             }
214             //If doShow() was never called, we've modified the visibility
215
//of the contents component, which could cause problems elsewhere
216
contents.setVisible (true);
217         }
218     }
219     
220     private static class HWPopup extends OurPopup {
221         private JWindow JavaDoc window = null;
222         public HWPopup (Component JavaDoc owner, Component JavaDoc contents, int x, int y) {
223             super (owner, contents, x, y);
224         }
225         
226         public boolean isShowing() {
227             return window != null && window.isShowing();
228         }
229         
230         void dispose() {
231             if (window != null) {
232                 checkInWindow (window);
233                 window = null;
234             }
235             super.dispose();
236         }
237         
238         protected void prepareResources() {
239             window = checkOutWindow();
240             window.getContentPane().add (contents);
241             window.setLocation (new Point JavaDoc (x, y));
242             window.pack();
243             window.setBackground (new java.awt.Color JavaDoc (255, 255, 255, 0));
244         }
245         
246         protected void doShow() {
247             window.setVisible(true);
248         }
249         
250         protected void doHide() {
251             if (window != null) {
252                 window.setVisible(false);
253                 window.getContentPane().remove (contents);
254                 //Try to force a reset
255
dispose();
256             }
257         }
258     }
259     
260     private static JWindow JavaDoc checkOutWindow() {
261         if (windowPool != null) {
262             if (!windowPool.isEmpty()) {
263                 for (Iterator JavaDoc i=windowPool.iterator(); i.hasNext();) {
264                     Reference JavaDoc ref = (Reference JavaDoc) i.next();
265                     JWindow JavaDoc win = (JWindow JavaDoc) ref.get();
266                     i.remove();
267                     if (win != null) {
268                         assert !win.isShowing();
269                         win.setBounds (0, 0, 1, 1);
270                         win.getContentPane().removeAll();
271                         win.setBackground (new java.awt.Color JavaDoc (255, 255, 255, 0));
272                         return win;
273                     }
274                 }
275             }
276         }
277         JWindow JavaDoc nue = APPLE_COCOA_HACK ? (JWindow JavaDoc) new HackedJWindow() : new JWindow JavaDoc();
278         
279         nue.setBackground (new java.awt.Color JavaDoc (255, 255, 255, 0));
280         return nue;
281     }
282     
283     private static void checkInWindow (JWindow JavaDoc win) {
284         if (!APPLE_COCOA_HACK) {
285             win.dispose();
286         }
287         windowPool.add (new SoftReference JavaDoc (win));
288     }
289     
290     //A counter for unique window ids (used only if APPLE_COCOA_HACK is true)
291
private static int ct = 0;
292     //A flag if our reflection-based hack doesn't work, so we don't try
293
//again
294
private static boolean hackBroken = false;
295     //Make sure we've logged a warning
296
private static boolean warned = false;
297     
298     static boolean broken() {
299         return hackBroken;
300     }
301     
302     /**
303      * A JWindow which can (maybe) look up the native cocoa window that
304      * corresponds to it and hack its shadow property. No guarantees
305      * this will continue working, but once it fails it will fall back
306      * gracefully.
307      *
308      * This class is ONLY used if both system properties,
309      * nb.explorer.hw.completions and nb.explorer.hw.cocoahack
310      * are intentionally set to true. If the cocoa classes are not
311      * available, it will log a warning and fail gracefully.
312      *
313      */

314     private static final class HackedJWindow extends JWindow JavaDoc {
315         private String JavaDoc title = "none";
316         HackedJWindow() {}
317         
318         public void addNotify() {
319             super.addNotify();
320             hackTitle();
321             hackNativeWindow();
322         }
323         
324         private void hackTitle() {
325             if (!hackBroken) {
326                 try {
327                     //First we set a unique title on the peer - JWindow
328
//doesn't have a title, but apple.awt.CWindow does.
329
//Later we will use it to identify the right window in
330
//the array of windows owned by the application.
331
//This ain't pretty.
332
Object JavaDoc o = getPeer();
333                     if (o != null) {
334                         Method JavaDoc m = o.getClass().getDeclaredMethod ("setTitle",
335                                 new Class JavaDoc[] { String JavaDoc.class });
336                         m.setAccessible(true);
337                         title = "hw popup" + (ct++);
338                         m.invoke (o, new Object JavaDoc[] { title });
339                     }
340                 } catch (Exception JavaDoc e) {
341                     warn(e);
342                 }
343             }
344         }
345         
346         private void hackNativeWindow() {
347             if (!hackBroken) {
348                 try {
349                     //First, lookup the global singleton NSApplication
350
Class JavaDoc c = Class.forName ("com.apple.cocoa.application." +
351                             "NSApplication");
352                     
353                     Method JavaDoc m = c.getDeclaredMethod ("sharedApplication", null);
354                     Object JavaDoc nsapplication = m.invoke (null, null);
355                     
356                     //Now we'll get an NSArray array wrapper of NSWindow objects
357
m = nsapplication.getClass().getMethod ("windows", null);
358                     Object JavaDoc nsarray_of_nswindows = m.invoke (nsapplication, null);
359                     //Get the array size
360
m = nsarray_of_nswindows.getClass().getMethod("count", null);
361                     int arrSize = ((Integer JavaDoc) m.invoke (nsarray_of_nswindows,
362                             null)).intValue();
363                     
364                     //Allocate an array to copy into
365
Object JavaDoc[] windows = new Object JavaDoc [arrSize];
366                     m = nsarray_of_nswindows.getClass().getMethod(
367                             "getObjects", new Class JavaDoc[] { Object JavaDoc[].class });
368                             
369                     //Gets us an NSWindow[]
370
m.invoke (nsarray_of_nswindows, new Object JavaDoc[] { windows });
371                     if (windows.length > 0) {
372                         //Lookup the methods we'll need first, to reduce
373
//overhead inside the loop
374
c = windows[0].getClass();
375                         Method JavaDoc titleMethod = c.getMethod("title", null);
376                         Method JavaDoc setHasShadowMethod = c.getMethod ("setHasShadow",
377                                 new Class JavaDoc[] { Boolean.TYPE});
378                                 
379                         for (int i=0; i < windows.length; i++) {
380                             //Get the title
381
String JavaDoc ttl = (String JavaDoc) titleMethod.invoke (windows[i],
382                                     null);
383                             
384                             if (title.equals (ttl)) {
385                                 //We have the right method, set hasShadow to
386
//false
387
setHasShadowMethod.invoke (windows[i],
388                                         new Object JavaDoc[] { Boolean.FALSE });
389                             }
390                         }
391                     }
392                 } catch (Exception JavaDoc e) {
393                     warn(e);
394                 }
395             }
396         }
397         
398         private void warn(Exception JavaDoc e) {
399             hackBroken = true;
400             if (!warned) {
401                 warned = true;
402                 ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL,
403                         "Cannot turn off popup drop shadow, " +
404                         "reverting to standard swing popup factory");
405                 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
406                   e.printStackTrace();
407             }
408         }
409     }
410 }
411
Popular Tags