001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2008 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: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.autoupdate.ui.actions;
043:
044: import java.awt.AlphaComposite;
045: import java.awt.Color;
046: import java.awt.Composite;
047: import java.awt.Cursor;
048: import java.awt.Dimension;
049: import java.awt.Graphics;
050: import java.awt.Graphics2D;
051: import java.awt.GridBagConstraints;
052: import java.awt.GridBagLayout;
053: import java.awt.Image;
054: import java.awt.Insets;
055: import java.awt.Rectangle;
056: import java.awt.Shape;
057: import java.awt.event.ActionEvent;
058: import java.awt.event.ActionListener;
059: import java.awt.event.ComponentEvent;
060: import java.awt.event.ComponentListener;
061: import java.awt.event.MouseEvent;
062: import java.awt.event.MouseListener;
063: import java.awt.geom.AffineTransform;
064: import java.awt.geom.Area;
065: import java.awt.geom.GeneralPath;
066: import java.awt.geom.RoundRectangle2D;
067: import javax.swing.Action;
068: import javax.swing.BorderFactory;
069: import javax.swing.Icon;
070: import javax.swing.ImageIcon;
071: import javax.swing.JButton;
072: import javax.swing.JComponent;
073: import javax.swing.JLabel;
074: import javax.swing.JLayeredPane;
075: import javax.swing.JPanel;
076: import javax.swing.SwingUtilities;
077: import javax.swing.UIManager;
078: import org.openide.util.RequestProcessor;
079: import org.openide.util.Utilities;
080:
081: /**
082: * Shows, hides balloon-like tooltip windows.
083: *
084: * @author S. Aubrecht
085: */
086: public class BalloonManager {
087:
088: private static Balloon currentBalloon;
089: private static JLayeredPane currentPane;
090: private static ComponentListener listener;
091: private static RequestProcessor.Task hideToolTipTask = null;
092:
093: /**
094: * Show balloon-like tooltip pointing to the given component. The balloon stays
095: * visible until dismissed by clicking its 'close' button or by invoking its default action.
096: * @param owner The component which the balloon will point to
097: * @param content Content to be displayed in the balloon.
098: * @param defaultAction Action to invoked when the balloon is clicked, can be null.
099: */
100: public static synchronized void show(final JComponent owner,
101: JComponent content, Action defaultAction, int timeout) {
102: assert null != owner;
103: assert null != content;
104:
105: //hide current balloon (if any)
106: dismiss();
107: if (timeout > 0) {
108: if (hideToolTipTask == null || hideToolTipTask.isFinished()) {
109: hideToolTipTask = RequestProcessor.getDefault().post(
110: new Runnable() {
111: public void run() {
112: SwingUtilities
113: .invokeLater(new Runnable() {
114: public void run() {
115: dismiss();
116: }
117: });
118: }
119: }, timeout);
120: }
121: }
122:
123: currentBalloon = new Balloon(content, defaultAction);
124: currentPane = JLayeredPane.getLayeredPaneAbove(owner);
125:
126: listener = new ComponentListener() {
127: public void componentResized(ComponentEvent e) {
128: dismiss();
129: }
130:
131: public void componentMoved(ComponentEvent e) {
132: dismiss();
133: }
134:
135: public void componentShown(ComponentEvent e) {
136: }
137:
138: public void componentHidden(ComponentEvent e) {
139: dismiss();
140: }
141: };
142: currentPane.addComponentListener(listener);
143: configureBalloon(currentBalloon, currentPane, owner);
144: currentPane.add(currentBalloon, new Integer(
145: JLayeredPane.POPUP_LAYER - 1));
146: }
147:
148: /**
149: * Dismiss currently showing balloon tooltip (if any)
150: */
151: public static synchronized void dismiss() {
152: if (null != currentBalloon) {
153: currentBalloon.setVisible(false);
154: currentPane.remove(currentBalloon);
155: currentPane.repaint();
156: currentPane.removeComponentListener(listener);
157: currentBalloon = null;
158: currentPane = null;
159: listener = null;
160: }
161: }
162:
163: private static void configureBalloon(Balloon balloon,
164: JLayeredPane pane, JComponent ownerComp) {
165: Rectangle ownerCompBounds = ownerComp.getBounds();
166: ownerCompBounds = SwingUtilities.convertRectangle(ownerComp
167: .getParent(), ownerCompBounds, pane);
168:
169: int paneWidth = pane.getWidth();
170: int paneHeight = pane.getHeight();
171:
172: Dimension balloonSize = balloon.getPreferredSize();
173: balloonSize.height += Balloon.ARC;
174:
175: //first try lower right corner
176: if (ownerCompBounds.x + ownerCompBounds.width
177: + balloonSize.width < paneWidth
178: && ownerCompBounds.y + ownerCompBounds.height
179: + balloonSize.height + Balloon.ARC < paneHeight) {
180:
181: balloon.setArrowLocation(GridBagConstraints.SOUTHEAST);
182: balloon.setBounds(ownerCompBounds.x + ownerCompBounds.width
183: - Balloon.ARC / 2, ownerCompBounds.y
184: + ownerCompBounds.height, balloonSize.width
185: + Balloon.ARC, balloonSize.height);
186:
187: //upper right corner
188: } else if (ownerCompBounds.x + ownerCompBounds.width
189: + balloonSize.width < paneWidth
190: && ownerCompBounds.y - balloonSize.height - Balloon.ARC > 0) {
191:
192: balloon.setArrowLocation(GridBagConstraints.NORTHEAST);
193: balloon.setBounds(ownerCompBounds.x + ownerCompBounds.width
194: - Balloon.ARC / 2, ownerCompBounds.y
195: - balloonSize.height, balloonSize.width
196: + Balloon.ARC, balloonSize.height);
197:
198: //lower left corner
199: } else if (ownerCompBounds.x - balloonSize.width > 0
200: && ownerCompBounds.y + ownerCompBounds.height
201: + balloonSize.height + Balloon.ARC < paneHeight) {
202:
203: balloon.setArrowLocation(GridBagConstraints.SOUTHWEST);
204: balloon.setBounds(ownerCompBounds.x - balloonSize.width
205: + Balloon.ARC / 2, ownerCompBounds.y
206: + ownerCompBounds.height, balloonSize.width
207: + Balloon.ARC, balloonSize.height);
208: //upper left corent
209: } else {
210: balloon.setArrowLocation(GridBagConstraints.NORTHWEST);
211: balloon.setBounds(ownerCompBounds.x - balloonSize.width
212: + Balloon.ARC / 2, ownerCompBounds.y
213: - balloonSize.height, balloonSize.width
214: + Balloon.ARC, balloonSize.height);
215: }
216: }
217:
218: private static class Balloon extends JPanel {
219:
220: private static final int Y_OFFSET = 16;
221: private static final int ARC = 15;
222: private static final int SHADOW_SIZE = 10;
223:
224: private JComponent content;
225: private Action defaultAction;
226: private JButton btnDismiss;
227: private int arrowLocation = GridBagConstraints.SOUTHEAST;
228:
229: public Balloon(final JComponent content,
230: final Action defaultAction) {
231: super (new GridBagLayout());
232: this .content = content;
233: this .defaultAction = defaultAction;
234: content.setOpaque(false);
235:
236: btnDismiss = new DismissButton();
237: btnDismiss.addActionListener(new ActionListener() {
238: public void actionPerformed(ActionEvent e) {
239: BalloonManager.dismiss();
240: }
241: });
242:
243: add(content, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0,
244: GridBagConstraints.NORTH, GridBagConstraints.BOTH,
245: new Insets(4, 4, 4, 4), 0, 0));
246: add(new JLabel(), new GridBagConstraints(0, 1, 1, 1, 0.0,
247: 1.0, GridBagConstraints.NORTH,
248: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0,
249: 0));
250: add(btnDismiss, new GridBagConstraints(1, 0, 1, 1, 0.0,
251: 0.0, GridBagConstraints.NORTHEAST,
252: GridBagConstraints.NONE, new Insets(4, 0, 4, 4), 0,
253: 0));
254:
255: setOpaque(false);
256:
257: if (null != defaultAction) {
258: content.addMouseListener(new MouseListener() {
259:
260: public void mouseClicked(MouseEvent e) {
261: BalloonManager.dismiss();
262: defaultAction.actionPerformed(new ActionEvent(
263: Balloon.this , 0, "", e.getWhen(), e
264: .getModifiers()));
265: }
266:
267: public void mousePressed(MouseEvent e) {
268: }
269:
270: public void mouseReleased(MouseEvent e) {
271: }
272:
273: public void mouseEntered(MouseEvent e) {
274: content
275: .setCursor(Cursor
276: .getPredefinedCursor(Cursor.HAND_CURSOR));
277: }
278:
279: public void mouseExited(MouseEvent e) {
280: content.setCursor(Cursor.getDefaultCursor());
281: }
282:
283: });
284: }
285: setBorder(BorderFactory.createEmptyBorder(SHADOW_SIZE, 0,
286: 0, SHADOW_SIZE));
287: }
288:
289: void setArrowLocation(int arrowLocation) {
290: this .arrowLocation = arrowLocation;
291: if (arrowLocation == GridBagConstraints.NORTHEAST
292: || arrowLocation == GridBagConstraints.NORTHWEST) {
293: setBorder(BorderFactory.createEmptyBorder(SHADOW_SIZE,
294: 0, 0, SHADOW_SIZE));
295: } else {
296: setBorder(BorderFactory.createEmptyBorder(Y_OFFSET, 0,
297: 0, SHADOW_SIZE));
298: }
299: }
300:
301: private Shape getMask(int w, int h) {
302: w--;
303: w -= SHADOW_SIZE;
304: GeneralPath path = new GeneralPath();
305: Area area = null;
306: switch (arrowLocation) {
307: case GridBagConstraints.SOUTHEAST:
308: area = new Area(new RoundRectangle2D.Float(0, Y_OFFSET,
309: w, h - Y_OFFSET - SHADOW_SIZE, ARC, ARC));
310: path.moveTo(ARC / 2, 0);
311: path.lineTo(ARC / 2 + Y_OFFSET / 2, Y_OFFSET);
312: path
313: .lineTo(ARC / 2 + Y_OFFSET / 2 + Y_OFFSET,
314: Y_OFFSET);
315: break;
316: case GridBagConstraints.NORTHEAST:
317: area = new Area(new RoundRectangle2D.Float(0,
318: SHADOW_SIZE, w, h - Y_OFFSET - SHADOW_SIZE,
319: ARC, ARC));
320: path.moveTo(ARC / 2, h - 1);
321: path.lineTo(ARC / 2 + Y_OFFSET / 2, h - 1 - Y_OFFSET);
322: path.lineTo(ARC / 2 + Y_OFFSET / 2 + Y_OFFSET, h - 1
323: - Y_OFFSET);
324: break;
325: case GridBagConstraints.SOUTHWEST:
326: area = new Area(new RoundRectangle2D.Float(0, Y_OFFSET,
327: w, h - Y_OFFSET - SHADOW_SIZE, ARC, ARC));
328: path.moveTo(w - ARC / 2, 0);
329: path.lineTo(w - ARC / 2 - Y_OFFSET / 2, Y_OFFSET);
330: path.lineTo(w - ARC / 2 - Y_OFFSET / 2 - Y_OFFSET,
331: Y_OFFSET);
332: break;
333: case GridBagConstraints.NORTHWEST:
334: area = new Area(new RoundRectangle2D.Float(0,
335: SHADOW_SIZE, w, h - Y_OFFSET - SHADOW_SIZE,
336: ARC, ARC));
337: path.moveTo(w - ARC / 2, h - 1);
338: path.lineTo(w - ARC / 2 - Y_OFFSET / 2, h - 1
339: - Y_OFFSET);
340: path.lineTo(w - ARC / 2 - Y_OFFSET / 2 - Y_OFFSET, h
341: - 1 - Y_OFFSET);
342: break;
343: }
344:
345: path.closePath();
346: area.add(new Area(path));
347: return area;
348: }
349:
350: private Shape getShadowMask(Shape parentMask) {
351: Area area = new Area(parentMask);
352:
353: AffineTransform tx = new AffineTransform();
354: if (arrowLocation == GridBagConstraints.NORTHEAST
355: || arrowLocation == GridBagConstraints.NORTHWEST) {
356: tx.translate(SHADOW_SIZE, -SHADOW_SIZE);//Math.sin(ANGLE)*(getHeight()+SHADOW_SIZE), 0);
357: } else {
358: tx.translate(SHADOW_SIZE, SHADOW_SIZE);//Math.sin(ANGLE)*(getHeight()+SHADOW_SIZE), 0);
359: }
360: area.transform(tx);
361: return area;
362: }
363:
364: @Override
365: protected void paintBorder(Graphics g) {
366: }
367:
368: @Override
369: protected void paintComponent(Graphics g) {
370: Graphics2D g2d = (Graphics2D) g;
371: Composite oldC = g2d.getComposite();
372: Shape s = getMask(getWidth(), getHeight());
373:
374: g2d.setComposite(AlphaComposite.getInstance(
375: AlphaComposite.SRC_OVER, 0.25f));
376: g2d.setColor(Color.black);
377: g2d.fill(getShadowMask(s));
378:
379: g2d.setColor(UIManager.getColor("ToolTip.background")); //NOI18N
380: // g2d.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 0.5f ) );
381: g2d.setComposite(oldC);
382: g2d.fill(s);
383: g2d.setColor(Color.black);
384: g2d.draw(s);
385: }
386: }
387:
388: private static class DismissButton extends JButton {
389:
390: public DismissButton() {
391: Image img = Utilities
392: .loadImage("org/netbeans/modules/autoupdate/ui/resources/dismiss_enabled.png");
393: setIcon(new ImageIcon(img));
394: img = Utilities
395: .loadImage("org/netbeans/modules/autoupdate/ui/resources/dismiss_rollover.png");
396: setRolloverIcon(new ImageIcon(img));
397: img = Utilities
398: .loadImage("org/netbeans/modules/autoupdate/ui/resources/dismiss_pressed.png");
399: setPressedIcon(new ImageIcon(img));
400:
401: setBorder(BorderFactory.createEmptyBorder());
402: setBorderPainted(false);
403: setFocusable(false);
404: setOpaque(false);
405: setRolloverEnabled(true);
406: }
407:
408: @Override
409: public void paint(Graphics g) {
410: Icon icon = null;
411: if (getModel().isArmed() && getModel().isPressed()) {
412: icon = getPressedIcon();
413: } else if (getModel().isRollover()) {
414: icon = getRolloverIcon();
415: } else {
416: icon = getIcon();
417: }
418: icon.paintIcon(this , g, 0, 0);
419: }
420:
421: }
422: }
|