001: /*
002: * Copyright (c) 2005 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package contrib.com.jgoodies.looks.common;
032:
033: import java.awt.*;
034: import java.awt.image.BufferedImage;
035: import java.util.ArrayList;
036: import java.util.List;
037:
038: import javax.swing.*;
039: import javax.swing.border.Border;
040:
041: import org.jvnet.lafwidget.animation.FadeKind;
042:
043: /**
044: * Does all the magic for getting popups with drop shadows. It adds the drop
045: * shadow border to the Popup, in <code>#show</code> it snapshots the screen
046: * background as needed, and in <code>#hide</code> it cleans up all changes
047: * made before.
048: *
049: * @author Andrej Golovnin
050: * @version $Revision: 1.10 $
051: *
052: * @see com.jgoodies.looks.common.ShadowPopupBorder
053: * @see com.jgoodies.looks.common.ShadowPopupFactory
054: */
055: public final class ShadowPopup extends Popup {
056:
057: /**
058: * Max number of items to store in the cache.
059: */
060: private static final int MAX_CACHE_SIZE = 5;
061:
062: /**
063: * The cache to use for ShadowPopups.
064: */
065: private static List cache;
066:
067: /**
068: * The singleton instance used to draw all borders.
069: */
070: private static final Border SHADOW_BORDER = ShadowPopupBorder
071: .getInstance();
072:
073: /**
074: * The size of the drop shadow.
075: */
076: private static final int SHADOW_SIZE = 5;
077:
078: /**
079: * Indicates whether we can make snapshots from screen or not.
080: */
081: private static boolean canSnapshot = true;
082:
083: /**
084: * The component mouse coordinates are relative to, may be null.
085: */
086: private Component owner;
087:
088: /**
089: * The contents of the popup.
090: */
091: private Component contents;
092:
093: /**
094: * The desired x and y location of the popup.
095: */
096: private int x, y;
097:
098: /**
099: * The real popup. The #show() and #hide() methods will delegate all calls
100: * to these popup.
101: */
102: private Popup popup;
103:
104: /**
105: * The border of the contents' parent replaced by SHADOW_BORDER.
106: */
107: private Border oldBorder;
108:
109: /**
110: * The old value of the opaque property of the contents' parent.
111: */
112: private boolean oldOpaque;
113:
114: /**
115: * The heavy weight container of the popup contents, may be null.
116: */
117: private Container heavyWeightContainer;
118:
119: protected static final FadeKind MENU_APPEAR = new FadeKind(
120: "substancelaf.internal.menuAppear");
121:
122: /**
123: * Returns a previously used <code>ShadowPopup</code>, or a new one if
124: * none of the popups have been recycled.
125: */
126: static Popup getInstance(Component owner, Component contents,
127: int x, int y, Popup delegate) {
128: ShadowPopup result;
129: synchronized (ShadowPopup.class) {
130: if (cache == null) {
131: cache = new ArrayList(MAX_CACHE_SIZE);
132: }
133: if (cache.size() > 0) {
134: result = (ShadowPopup) cache.remove(0);
135: } else {
136: result = new ShadowPopup();
137: }
138: }
139: result.reset(owner, contents, x, y, delegate);
140: return result;
141: }
142:
143: /**
144: * Recycles the ShadowPopup.
145: */
146: private static void recycle(ShadowPopup popup) {
147: synchronized (ShadowPopup.class) {
148: if (cache.size() < MAX_CACHE_SIZE) {
149: cache.add(popup);
150: }
151: }
152: }
153:
154: public static boolean canSnapshot() {
155: return canSnapshot;
156: }
157:
158: /**
159: * Hides and disposes of the <code>Popup</code>. Once a
160: * <code>Popup</code> has been disposed you should no longer invoke
161: * methods on it. A <code>dispose</code>d <code>Popup</code> may be
162: * reclaimed and later used based on the <code>PopupFactory</code>. As
163: * such, if you invoke methods on a <code>disposed</code> <code>Popup</code>,
164: * indeterminate behavior will result.
165: * <p>
166: *
167: * In addition to the superclass behavior, we reset the stored horizontal
168: * and vertical drop shadows - if any.
169: */
170: public void hide() {
171: if (contents == null)
172: return;
173:
174: final JComponent parent = (JComponent) contents.getParent();
175: // final int width = contents.getWidth();
176: // final int height = contents.getHeight();
177: // FadeTracker.getInstance().trackFadeOut(MENU_APPEAR, parent, false,
178: // new FadeTracker.FadeTrackerCallback() {
179: // public void fadePerformed(FadeKind fadeKind,
180: // final float fadeCycle10) {
181: // Component c = contents;
182: // System.out.println(fadeCycle10);
183: // while (c != null) {
184: // System.out.println(c.getClass().getName());
185: // if (c instanceof JPopupMenu)
186: // break;
187: // c = c.getParent();
188: // }
189: // if (c != null) {
190: // c.setSize((int) (width * fadeCycle10 / 10.0),
191: // (int) (height * fadeCycle10 / 10.0));
192: // c.repaint();
193: // }
194: // }
195: //
196: // public void fadeEnded(FadeKind fadeKind) {
197: popup.hide();
198: if (parent.getBorder() == SHADOW_BORDER) {
199: parent.setBorder(oldBorder);
200: parent.setOpaque(oldOpaque);
201: oldBorder = null;
202: if (heavyWeightContainer != null) {
203: parent.putClientProperty(
204: ShadowPopupFactory.PROP_HORIZONTAL_BACKGROUND,
205: null);
206: parent.putClientProperty(
207: ShadowPopupFactory.PROP_VERTICAL_BACKGROUND,
208: null);
209: heavyWeightContainer = null;
210: }
211: }
212: owner = null;
213: contents = null;
214: popup = null;
215: recycle(ShadowPopup.this );
216: // }
217: // });
218: }
219:
220: /**
221: * Makes the <code>Popup</code> visible. If the popup has a heavy-weight
222: * container, we try to snapshot the background. If the <code>Popup</code>
223: * is currently visible, it remains visible.
224: */
225: public void show() {
226: if (heavyWeightContainer != null) {
227: snapshot();
228: }
229: popup.show();
230: }
231:
232: /**
233: * Reinitializes this ShadowPopup using the given parameters.
234: *
235: * @param owner
236: * component mouse coordinates are relative to, may be null
237: * @param contents
238: * the contents of the popup
239: * @param x
240: * the desired x location of the popup
241: * @param y
242: * the desired y location of the popup
243: * @param popup
244: * the popup to wrap
245: */
246: private void reset(Component owner, Component contents, int x,
247: int y, Popup popup) {
248: this .owner = owner;
249: this .contents = contents;
250: this .popup = popup;
251: this .x = x;
252: this .y = y;
253: if (owner instanceof JComboBox) {
254: return;
255: }
256: for (Container p = contents.getParent(); p != null; p = p
257: .getParent()) {
258: if ((p instanceof JWindow) || (p instanceof Panel)) {
259: // Workaround for the gray rect problem.
260: p.setBackground(contents.getBackground());
261: heavyWeightContainer = p;
262: break;
263: }
264: }
265: JComponent parent = (JComponent) contents.getParent();
266: oldOpaque = parent.isOpaque();
267: oldBorder = parent.getBorder();
268: parent.setOpaque(false);
269: parent.setBorder(SHADOW_BORDER);
270: // Pack it because we have changed the border.
271: if (heavyWeightContainer != null) {
272: heavyWeightContainer.setSize(heavyWeightContainer
273: .getPreferredSize());
274: } else {
275: parent.setSize(parent.getPreferredSize());
276: }
277: }
278:
279: /**
280: * The 'scratch pad' objects used to calculate dirty regions of the screen
281: * snapshots.
282: *
283: * @see #snapshot()
284: */
285: private static final Point point = new Point();
286:
287: private static final Rectangle rect = new Rectangle();
288:
289: /**
290: * Snapshots the background. The snapshots are stored as client properties
291: * of the contents' parent. The next time the border is drawn, this
292: * background will be used.
293: * <p>
294: *
295: * Uses a robot on the default screen device to capture the screen region
296: * under the drop shadow. Does <em>not</em> use the window's device,
297: * because that may be an outdated device (due to popup reuse) and the
298: * robot's origin seems to be adjusted with the default screen device.
299: *
300: * @see #show()
301: * @see com.jgoodies.looks.common.ShadowPopupBorder
302: */
303: private void snapshot() {
304: try {
305: Robot robot = new Robot(); // uses the default screen device
306:
307: Dimension size = heavyWeightContainer.getPreferredSize();
308: int width = size.width;
309: int height = size.height;
310:
311: rect.setBounds(x, y + height - SHADOW_SIZE, width,
312: SHADOW_SIZE);
313: BufferedImage hShadowBg = robot.createScreenCapture(rect);
314:
315: rect.setBounds(x + width - SHADOW_SIZE, y, SHADOW_SIZE,
316: height - SHADOW_SIZE);
317: BufferedImage vShadowBg = robot.createScreenCapture(rect);
318:
319: JComponent parent = (JComponent) contents.getParent();
320: parent.putClientProperty(
321: ShadowPopupFactory.PROP_HORIZONTAL_BACKGROUND,
322: hShadowBg);
323: parent.putClientProperty(
324: ShadowPopupFactory.PROP_VERTICAL_BACKGROUND,
325: vShadowBg);
326:
327: Container layeredPane = getLayeredPane();
328: if (layeredPane == null) {
329: // This could happen if owner is null.
330: return;
331: }
332:
333: int layeredPaneWidth = layeredPane.getWidth();
334: int layeredPaneHeight = layeredPane.getHeight();
335:
336: point.x = x;
337: point.y = y;
338: SwingUtilities.convertPointFromScreen(point, layeredPane);
339:
340: // If needed paint dirty region of the horizontal snapshot.
341: rect.x = point.x;
342: rect.y = point.y + height - SHADOW_SIZE;
343: rect.width = width;
344: rect.height = SHADOW_SIZE;
345:
346: if ((rect.x + rect.width) > layeredPaneWidth) {
347: rect.width = layeredPaneWidth - rect.x;
348: }
349: if ((rect.y + rect.height) > layeredPaneHeight) {
350: rect.height = layeredPaneHeight - rect.y;
351: }
352: if (!rect.isEmpty()) {
353: Graphics g = hShadowBg.createGraphics();
354: g.translate(-rect.x, -rect.y);
355: g.setClip(rect);
356: if (layeredPane instanceof JComponent) {
357: JComponent c = (JComponent) layeredPane;
358: boolean doubleBuffered = c.isDoubleBuffered();
359: c.setDoubleBuffered(false);
360: c.paintAll(g);
361: c.setDoubleBuffered(doubleBuffered);
362: } else {
363: layeredPane.paintAll(g);
364: }
365: g.dispose();
366: }
367:
368: // If needed paint dirty region of the vertical snapshot.
369: rect.x = point.x + width - SHADOW_SIZE;
370: rect.y = point.y;
371: rect.width = SHADOW_SIZE;
372: rect.height = height - SHADOW_SIZE;
373:
374: if ((rect.x + rect.width) > layeredPaneWidth) {
375: rect.width = layeredPaneWidth - rect.x;
376: }
377: if ((rect.y + rect.height) > layeredPaneHeight) {
378: rect.height = layeredPaneHeight - rect.y;
379: }
380: if (!rect.isEmpty()) {
381: Graphics g = vShadowBg.createGraphics();
382: g.translate(-rect.x, -rect.y);
383: g.setClip(rect);
384: if (layeredPane instanceof JComponent) {
385: JComponent c = (JComponent) layeredPane;
386: boolean doubleBuffered = c.isDoubleBuffered();
387: c.setDoubleBuffered(false);
388: c.paintAll(g);
389: c.setDoubleBuffered(doubleBuffered);
390: } else {
391: layeredPane.paintAll(g);
392: }
393: g.dispose();
394: }
395: } catch (AWTException e) {
396: canSnapshot = false;
397: } catch (SecurityException e) {
398: canSnapshot = false;
399: }
400: }
401:
402: /**
403: * @return the top level layered pane which contains the owner.
404: */
405: private Container getLayeredPane() {
406: // The code below is copied from PopupFactory#LightWeightPopup#show()
407: Container parent = null;
408: if (owner != null) {
409: parent = owner instanceof Container ? (Container) owner
410: : owner.getParent();
411: }
412: // Try to find a JLayeredPane and Window to add
413: for (Container p = parent; p != null; p = p.getParent()) {
414: if (p instanceof JRootPane) {
415: if (p.getParent() instanceof JInternalFrame) {
416: continue;
417: }
418: parent = ((JRootPane) p).getLayeredPane();
419: // Continue, so that if there is a higher JRootPane, we'll
420: // pick it up.
421: } else if (p instanceof Window) {
422: if (parent == null) {
423: parent = p;
424: }
425: break;
426: } else if (p instanceof JApplet) {
427: // Painting code stops at Applets, we don't want
428: // to add to a Component above an Applet otherwise
429: // you'll never see it painted.
430: break;
431: }
432: }
433: return parent;
434: }
435:
436: }
|