001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s): */
025: package org.netbeans.swing.plaf.aqua;
026:
027: import java.awt.Color;
028: import java.awt.Component;
029: import java.awt.Graphics;
030: import java.awt.Graphics2D;
031: import java.awt.Insets;
032: import java.awt.Shape;
033: import java.awt.geom.AffineTransform;
034: import java.awt.geom.GeneralPath;
035: import java.awt.image.BufferedImage;
036: import java.util.HashMap;
037: import java.util.Map;
038: import javax.imageio.ImageIO;
039: import javax.swing.border.Border;
040:
041: /**
042: * Replacement for original DropShadowBorder - uses a set of backing bitmaps
043: * to draw shadows instead of allocating a huge raster.
044: *
045: * @author Tim Boudreau
046: */
047: public class FakeDropShadowBorder implements Border {
048:
049: public FakeDropShadowBorder() {
050: }
051:
052: private static final int WIDTH = 17;
053: private static final int HEIGHT = 17;
054: public static final int ARC = 12;
055:
056: public Insets getBorderInsets(Component c) {
057: return new Insets(1, 13, 25, 13);
058: }
059:
060: /**
061: * Fill the area we are *not* going to paint with the translucent shadow,
062: * so the windows behind the popup window do not show through the gaps
063: * between the rectangle of the image and the shaped, rounded perimeter
064: * where the shadow is drawn.
065: */
066: public void fillBackground(Component c, Graphics2D gg, int x,
067: int y, int w, int h) {
068: Shape clip = gg.getClip();
069: gg.setColor(Color.WHITE);
070: Insets ins = getBorderInsets(c);
071: //y offset for the bottom of the window
072: int bottom = h - ins.bottom + 6; //517
073: //y offset for the end of the curves around the bottom, from which
074: //the edges ascend
075: int bottomOffCurve = bottom - 20; //497
076: //The top of the inner part of the border
077: int top = ins.top + 3; //34
078: //The left edge
079: int left = ins.left - 2; //41
080: //The level of the "shoulders" in the border shape
081: int shoulderTop = top + 16; //50
082: //The y coordinate at which the edge segment stops and the shoulder
083: //curve stops
084: int shoulderTopOffCurve = shoulderTop + 9; //59
085: //the x position it which the top right curve begins to curl around
086: //the right corner
087: int rightOffCurve = x + w - 34; //329
088: //the right edge of the perimeter
089: int right = rightOffCurve + 24; //352
090:
091: //Calculate a shape to fill, which matches the perceived perimeter of
092: //the border (larger than the actual component displayed to make room
093: //for our rounded borders
094: GeneralPath gp = new GeneralPath();
095: //start at the bottom after the left edge curve - first segment is a line
096: //upward - the left edge
097: gp.moveTo(left, bottomOffCurve);
098: gp.lineTo(left, shoulderTopOffCurve);
099: //relatively flat bezier curve making the left shoulder of the window border
100: gp.curveTo(left, shoulderTop, left + 6, shoulderTop, left + 8,
101: shoulderTop + 1);
102: //and steeper, up and around to the top edge
103: gp.curveTo(left + 11, top, left + 19, top + 1, left + 25, top);
104: //top edge
105: gp.lineTo(rightOffCurve, top);
106: //steep curve back down to the right shoulder
107: gp.curveTo(rightOffCurve + 6, top, rightOffCurve + 17, top + 5,
108: rightOffCurve + 16, shoulderTop);
109: //shallower curve out to the right to make the left shoulder
110: gp.curveTo(right - 4, shoulderTop + 1, right, shoulderTop,
111: right, shoulderTop + 9);
112: //the right edge of the window border
113: gp.lineTo(right, bottomOffCurve);
114: //curve around to the left to the bottom edge
115: gp.curveTo(right + 1, bottom, right - 1, bottom + 1,
116: right - 12, bottom);
117: //the bottom edge
118: gp.lineTo(left + 14, bottom);
119: //curve to the left and up to come back to where we started
120: gp.curveTo(left + 1, bottom, left - 1, bottom, left,
121: bottomOffCurve);
122: gp.closePath();
123: //fill this with white; the window will overpaint it; this fills in so
124: //we don't see the windows underneath it peeking between the rounded
125: //border's edges and the smaller rectangle of the component inside it
126: gg.fill(gp);
127: }
128:
129: public void paintBorder(Component c, Graphics g, int x, int y,
130: int w, int h) {
131: Graphics2D gg = (Graphics2D) g;
132: //Fill in the space between the component rect and the border
133: //perimeter
134: fillBackground(c, gg, x, y, w, h);
135: //Tile the shadow pngs around the shape
136: BufferedImage b = getImage(upLeft);
137: int yoff = b.getHeight();
138: int topL = b.getWidth();
139: draw(gg, b, x, y);
140:
141: b = getImage(downRight);
142: draw(gg, b, x + w - b.getWidth(), y + h - b.getHeight());
143: int woff = b.getWidth();
144:
145: b = getImage(upRight);
146: draw(gg, b, x + w - b.getWidth(), y);
147: int topR = b.getWidth();
148:
149: b = getImage(downLeft);
150: int hoff = b.getHeight();
151: int xoff = b.getWidth();
152: draw(gg, b, x, y + h - b.getHeight());
153:
154: b = getImage(leftEdge);
155: tileVertical(x, y, yoff, hoff, h, b, gg);
156:
157: b = getImage(rightEdge);
158: tileVertical(x + w - (b.getWidth()), y, yoff, hoff, h, b, gg);
159:
160: b = getImage(bottom);
161: tileHorizontal(x, y + h - (b.getHeight() + 0), xoff, woff, w,
162: b, gg);
163:
164: b = getImage(top);
165: tileHorizontal(x, y, xoff, woff, w, b, gg);
166:
167: }
168:
169: private final Color xpar = new Color(255, 255, 255, 0);
170:
171: private void draw(Graphics2D g, BufferedImage b, int x, int y) {
172: g.setColor(xpar);
173: g.fillRect(x, y, b.getWidth(), b.getHeight());
174: g.drawRenderedImage(b, AffineTransform.getTranslateInstance(x,
175: y));
176: }
177:
178: private void tileVertical(int x, int y, int yoff, int hoff, int h,
179: BufferedImage img, Graphics2D g) {
180: h -= (hoff + yoff);
181: int times = h / img.getHeight();
182: int rem = h % img.getHeight();
183: y = y + yoff;
184:
185: for (int i = 0; i < times; i++) {
186: g.drawRenderedImage(img, AffineTransform
187: .getTranslateInstance(x, y));
188: y += img.getHeight();
189: }
190: if (rem > 0) {
191: img = img.getSubimage(0, 0, img.getWidth(), rem);
192: g.drawRenderedImage(img, AffineTransform
193: .getTranslateInstance(x, y));
194: }
195: }
196:
197: private void tileHorizontal(int x, int y, int xoff, int woff,
198: int w, BufferedImage img, Graphics2D g) {
199: w -= (woff + xoff);
200: int times = w / img.getWidth();
201: int rem = w % img.getWidth();
202: x += xoff;
203:
204: for (int i = 0; i < times; i++) {
205: draw(g, img, x, y);
206: x += img.getWidth();
207: }
208: if (rem > 0) {
209: img = img.getSubimage(0, 0, rem, img.getHeight());
210: draw(g, img, x, y);
211: }
212: }
213:
214: public boolean isBorderOpaque() {
215: return false;
216: }
217:
218: private static final String upLeft = "upLeft.png"; //NOI18N
219: private static final String downRight = "downRight.png"; //NOI18N
220: private static final String downLeft = "upRight.png"; //NOI18N
221: private static final String upRight = "downLeft.png"; //NOI18N
222: private static final String bottom = "bottom.png"; //NOI18N
223: private static final String leftEdge = "leftEdge.png"; //NOI18N
224: private static final String rightEdge = "rightEdge.png"; //NOI18N
225: private static final String top = "top.png";
226:
227: //Only one instance in VM, so perfectly safe to use instance cache - won't
228: //be populated unless used
229: private static Map<String, BufferedImage> imgs = new HashMap<String, BufferedImage>();
230:
231: private static BufferedImage getImage(String s) {
232: BufferedImage result = imgs.get(s);
233: if (result == null) {
234: Exception e1 = null;
235: try {
236: result = ImageIO.read(FakeDropShadowBorder.class
237: .getResourceAsStream(s));
238: } catch (Exception e) {
239: result = new BufferedImage(1, 1,
240: BufferedImage.TYPE_INT_ARGB);
241: e1 = e;
242: }
243: imgs.put(s, result);
244: if (e1 != null) {
245: throw new IllegalStateException(e1);
246: }
247: }
248: return result;
249: }
250: }
|