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: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.ui.components;
042:
043: import java.awt.*;
044: import java.awt.event.*;
045: import java.beans.PropertyChangeEvent;
046: import java.beans.PropertyChangeListener;
047: import javax.swing.*;
048: import org.netbeans.lib.profiler.global.Platform;
049:
050: public class CellTipManager implements MouseListener,
051: MouseMotionListener {
052: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
053:
054: private class MoveBeforeEnterListener extends MouseMotionAdapter {
055: //~ Methods --------------------------------------------------------------------------------------------------------------
056:
057: public void mouseMoved(MouseEvent e) {
058: initiateCellTip(e);
059: }
060: }
061:
062: private class UniversalCellTipListener implements
063: ComponentListener, KeyListener, FocusListener,
064: PropertyChangeListener, HierarchyListener,
065: HierarchyBoundsListener {
066: //~ Methods --------------------------------------------------------------------------------------------------------------
067:
068: public void ancestorMoved(HierarchyEvent e) {
069: hideCellTipForOwner(e.getSource());
070: }
071:
072: public void ancestorResized(HierarchyEvent e) {
073: hideCellTipForOwner(e.getSource());
074: }
075:
076: public void componentHidden(ComponentEvent e) {
077: hideCellTipForOwner(e.getSource());
078: }
079:
080: public void componentMoved(ComponentEvent e) {
081: hideCellTipForOwner(e.getSource());
082: }
083:
084: public void componentResized(ComponentEvent e) {
085: hideCellTipForOwner(e.getSource());
086: }
087:
088: public void componentShown(ComponentEvent e) {
089: hideCellTipForOwner(e.getSource());
090: }
091:
092: public void focusGained(FocusEvent e) {
093: //
094: }
095:
096: public void focusLost(FocusEvent e) {
097: hideCellTipForOwner(e.getSource());
098: }
099:
100: public void hierarchyChanged(HierarchyEvent e) {
101: hideCellTipForOwner(e.getSource());
102: }
103:
104: public void keyPressed(KeyEvent e) {
105: hideCellTipAlways();
106: }
107:
108: public void keyReleased(KeyEvent e) {
109: //
110: }
111:
112: public void keyTyped(KeyEvent e) {
113: //
114: }
115:
116: public void propertyChange(PropertyChangeEvent e) {
117: hideCellTipForOwner(e.getSource());
118: }
119:
120: void registerForComponent(JComponent component) {
121: if (component == null) {
122: return;
123: }
124:
125: component.addComponentListener(this );
126: component.addKeyListener(this );
127: component.addFocusListener(this );
128: component.addPropertyChangeListener(this );
129: component.addHierarchyListener(this );
130: component.addHierarchyBoundsListener(this );
131: }
132:
133: void unregisterForComponent(JComponent component) {
134: if (component == null) {
135: return;
136: }
137:
138: component.removeComponentListener(this );
139: component.removeKeyListener(this );
140: component.removeFocusListener(this );
141: component.removePropertyChangeListener(this );
142: component.removeHierarchyListener(this );
143: component.removeHierarchyBoundsListener(this );
144: }
145:
146: private void hideCellTipAlways() {
147: hideCellTip();
148: }
149:
150: private void hideCellTipForOwner(Object owner) {
151: if (cellTipComponent == owner) {
152: hideCellTip();
153: }
154: }
155: }
156:
157: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
158:
159: private static final CellTipManager sharedInstance = new CellTipManager();
160:
161: //~ Instance fields ----------------------------------------------------------------------------------------------------------
162:
163: private JComponent cellTipComponent;
164: private JToolTip cellTip;
165: private transient Popup cellTipPopup;
166: private MouseMotionListener moveBeforeEnterListener = new MoveBeforeEnterListener();
167: private Rectangle popupFrameRect;
168: private Rectangle popupRect;
169: private UniversalCellTipListener universalCellTipListener = new UniversalCellTipListener();
170: private Window cellTipWindow;
171: private boolean enabled = true;
172: private boolean heavyweightPopupClosed = false;
173: private boolean internalMousePressed = false;
174:
175: //~ Methods ------------------------------------------------------------------------------------------------------------------
176:
177: // --- Public interface ------------------------------------------------------
178: public static CellTipManager sharedInstance() {
179: return sharedInstance;
180: }
181:
182: public void setEnabled(boolean enabled) {
183: this .enabled = enabled;
184:
185: if (!enabled) {
186: hideCellTip();
187: }
188: }
189:
190: public boolean isEnabled() {
191: return enabled;
192: }
193:
194: public void hideCellTip() {
195: hideCellTipWindow();
196: cellTipComponent = null;
197: }
198:
199: public void mouseClicked(MouseEvent event) {
200: }
201:
202: public void mouseDragged(MouseEvent event) {
203: }
204:
205: public void mouseEntered(MouseEvent event) {
206: initiateCellTip(event);
207: }
208:
209: public void mouseExited(MouseEvent event) {
210: boolean shouldHide = true;
211:
212: if ((cellTipWindow != null)
213: && (event.getSource() == cellTipWindow)) {
214: Container cellTipComponentWindow = cellTipComponent
215: .getTopLevelAncestor();
216: Point location = event.getPoint();
217: SwingUtilities
218: .convertPointToScreen(location, cellTipWindow);
219:
220: location.x -= cellTipComponentWindow.getX();
221: location.y -= cellTipComponentWindow.getY();
222:
223: location = SwingUtilities.convertPoint(null, location,
224: cellTipComponent);
225:
226: if ((location.x >= 0)
227: && (location.x < cellTipComponent.getWidth())
228: && (location.y >= 0)
229: && (location.y < cellTipComponent.getHeight())) {
230: shouldHide = false;
231: } else {
232: shouldHide = true;
233: }
234: } else if ((event.getSource() == cellTipComponent)
235: && (cellTipPopup != null)) {
236: Window win = SwingUtilities
237: .getWindowAncestor(cellTipComponent);
238:
239: if (win != null) {
240: Point location = SwingUtilities.convertPoint(
241: cellTipComponent, event.getPoint(), win);
242: Rectangle bounds = cellTipComponent
243: .getTopLevelAncestor().getBounds();
244: location.x += bounds.x;
245: location.y += bounds.y;
246:
247: Point loc = new Point(0, 0);
248: SwingUtilities.convertPointToScreen(loc, cellTip);
249: bounds.x = loc.x;
250: bounds.y = loc.y;
251: bounds.width = cellTip.getWidth();
252: bounds.height = cellTip.getHeight();
253:
254: if ((location.x >= bounds.x)
255: && (location.x < (bounds.x + bounds.width))
256: && (location.y >= bounds.y)
257: && (location.y < (bounds.y + bounds.height))) {
258: shouldHide = false;
259: } else {
260: shouldHide = true;
261: }
262: }
263: }
264:
265: if (shouldHide) {
266: if (cellTipComponent != null) {
267: cellTipComponent.removeMouseMotionListener(this );
268: }
269:
270: hideCellTip();
271: }
272: }
273:
274: public void mouseMoved(MouseEvent event) {
275: if (heavyweightPopupClosed) {
276: heavyweightPopupClosed = false;
277:
278: return;
279: }
280:
281: JComponent component = (JComponent) event.getSource();
282: cellTipComponent = component;
283: showCellTipWindow();
284: }
285:
286: public void mousePressed(MouseEvent event) {
287: if (internalMousePressed) {
288: return;
289: }
290:
291: JComponent component = cellTipComponent;
292: hideCellTip();
293:
294: Object source = event.getSource();
295:
296: if (source instanceof Component
297: && !JComponent
298: .isLightweightComponent((Component) source)) {
299: heavyweightPopupClosed = true;
300: internalMousePressed = true;
301: ((CellTipAware) component).processMouseEvent(SwingUtilities
302: .convertMouseEvent((Component) event.getSource(),
303: event, component));
304: internalMousePressed = false;
305: } else {
306: heavyweightPopupClosed = false;
307: }
308: }
309:
310: public void mouseReleased(MouseEvent event) {
311: }
312:
313: public void registerComponent(JComponent component) {
314: if (Platform.isMac())
315: return; // CellTips don't work reliably on Mac (see Issue 89216) => disabled
316:
317: if (!(component instanceof CellTipAware)) {
318: throw new RuntimeException(
319: "Only components implementing org.netbeans.lib.profiler.ui.components.CellTipAware interface can be registered!"); // NOI18N
320: }
321:
322: unregisterComponent(component);
323:
324: component.addMouseListener(this );
325: component.addMouseMotionListener(moveBeforeEnterListener);
326:
327: universalCellTipListener.registerForComponent(component);
328: }
329:
330: public void unregisterComponent(JComponent component) {
331: if (Platform.isMac())
332: return; // CellTips don't work reliably on Mac (see Issue 89216) => disabled
333:
334: if (!(component instanceof CellTipAware)) {
335: throw new RuntimeException(
336: "Only components implementing org.netbeans.lib.profiler.ui.components.CellTipAware interface can be unregistered!"); // NOI18N
337: }
338:
339: component.removeMouseListener(this );
340: component.removeMouseMotionListener(moveBeforeEnterListener);
341:
342: universalCellTipListener.unregisterForComponent(component);
343: }
344:
345: private static Frame frameForComponent(Component component) {
346: while (!(component instanceof Frame)) {
347: component = component.getParent();
348: }
349:
350: return (Frame) component;
351: }
352:
353: private int getHeightAdjust(Rectangle a, Rectangle b) {
354: if ((b.y >= a.y) && ((b.y + b.height) <= (a.y + a.height))) {
355: return 0;
356: } else {
357: return (((b.y + b.height) - (a.y + a.height)) + 5);
358: }
359: }
360:
361: private int getPopupFitHeight(Rectangle popupRectInScreen,
362: Component invoker) {
363: if (invoker != null) {
364: Container parent;
365:
366: for (parent = invoker.getParent(); parent != null; parent = parent
367: .getParent()) {
368: if (parent instanceof JFrame
369: || parent instanceof JDialog
370: || parent instanceof JWindow) {
371: return getHeightAdjust(parent.getBounds(),
372: popupRectInScreen);
373: } else if (parent instanceof JApplet
374: || parent instanceof JInternalFrame) {
375: if (popupFrameRect == null) {
376: popupFrameRect = new Rectangle();
377: }
378:
379: Point p = parent.getLocationOnScreen();
380: popupFrameRect.setBounds(p.x, p.y, parent
381: .getBounds().width,
382: parent.getBounds().height);
383:
384: return getHeightAdjust(popupFrameRect,
385: popupRectInScreen);
386: }
387: }
388: }
389:
390: return 0;
391: }
392:
393: private int getPopupFitWidth(Rectangle popupRectInScreen,
394: Component invoker) {
395: if (invoker != null) {
396: Container parent;
397:
398: for (parent = invoker.getParent(); parent != null; parent = parent
399: .getParent()) {
400: if (parent instanceof JFrame
401: || parent instanceof JDialog
402: || parent instanceof JWindow) {
403: return getWidthAdjust(parent.getBounds(),
404: popupRectInScreen);
405: } else if (parent instanceof JApplet
406: || parent instanceof JInternalFrame) {
407: if (popupFrameRect == null) {
408: popupFrameRect = new Rectangle();
409: }
410:
411: Point p = parent.getLocationOnScreen();
412: popupFrameRect.setBounds(p.x, p.y, parent
413: .getBounds().width,
414: parent.getBounds().height);
415:
416: return getWidthAdjust(popupFrameRect,
417: popupRectInScreen);
418: }
419: }
420: }
421:
422: return 0;
423: }
424:
425: private int getWidthAdjust(Rectangle a, Rectangle b) {
426: if ((b.x >= a.x) && ((b.x + b.width) <= (a.x + a.width))) {
427: return 0;
428: } else {
429: return (((b.x + b.width) - (a.x + a.width)) + 5);
430: }
431: }
432:
433: private void hideCellTipWindow() {
434: if (cellTipPopup != null) {
435: if (cellTipWindow != null) {
436: cellTipWindow.removeMouseListener(this );
437: cellTipWindow = null;
438: }
439:
440: cellTipPopup.hide();
441: cellTipPopup = null;
442: cellTip = null;
443: }
444: }
445:
446: private void initiateCellTip(MouseEvent event) {
447: if (event.getSource() == cellTipWindow) {
448: return;
449: }
450:
451: JComponent component = (JComponent) event.getSource();
452: component.removeMouseMotionListener(moveBeforeEnterListener);
453:
454: Point location = event.getPoint();
455:
456: if ((location.x < 0) || (location.x >= component.getWidth())
457: || (location.y < 0)
458: || (location.y >= component.getHeight())) {
459: return;
460: }
461:
462: component.removeMouseMotionListener(this );
463: component.addMouseMotionListener(this );
464:
465: boolean sameComponent = (cellTipComponent == component);
466:
467: cellTipComponent = component;
468:
469: if ((cellTipPopup != null) && !sameComponent) {
470: showCellTipWindow();
471: }
472: }
473:
474: // ---------------------------------------------------------------------------
475: private void showCellTipWindow() {
476: if ((cellTipComponent == null) || !cellTipComponent.isShowing()) {
477: return;
478: }
479:
480: for (Container p = cellTipComponent.getParent(); p != null; p = p
481: .getParent()) {
482: if (p instanceof JPopupMenu) {
483: break;
484: }
485:
486: if (p instanceof Window) {
487: if (!((Window) p).isFocused()) {
488: return;
489: }
490:
491: break;
492: }
493: }
494:
495: if (enabled) {
496: Dimension size;
497: Point screenLocation = cellTipComponent
498: .getLocationOnScreen();
499: Point location = new Point();
500: Rectangle sBounds = cellTipComponent
501: .getGraphicsConfiguration().getBounds();
502:
503: hideCellTipWindow();
504:
505: if (!(cellTipComponent instanceof CellTipAware)) {
506: return;
507: }
508:
509: CellTipAware cellTipAware = (CellTipAware) cellTipComponent;
510:
511: Point cellTipLocation = cellTipAware.getCellTipLocation();
512:
513: if (cellTipLocation == null) {
514: return;
515: }
516:
517: cellTip = cellTipAware.getCellTip();
518: size = cellTip.getPreferredSize();
519:
520: location.x = screenLocation.x + cellTipLocation.x;
521: location.y = screenLocation.y + cellTipLocation.y;
522:
523: if (popupRect == null) {
524: popupRect = new Rectangle();
525: }
526:
527: popupRect.setBounds(location.x, location.y, size.width,
528: size.height);
529:
530: if (location.x < sBounds.x) {
531: location.x = sBounds.x;
532: } else if ((location.x - sBounds.x + size.width) > sBounds.width) {
533: location.x = sBounds.x
534: + Math.max(0, sBounds.width - size.width);
535: }
536:
537: if (location.y < sBounds.y) {
538: location.y = sBounds.y;
539: } else if ((location.y - sBounds.y + size.height) > sBounds.height) {
540: location.y = sBounds.y
541: + Math.max(0, sBounds.height - size.height);
542: }
543:
544: PopupFactory popupFactory = PopupFactory
545: .getSharedInstance();
546: cellTipPopup = popupFactory.getPopup(cellTipComponent,
547: cellTip, location.x, location.y);
548: cellTipPopup.show();
549:
550: Window componentWindow = SwingUtilities
551: .windowForComponent(cellTipComponent);
552: cellTipWindow = SwingUtilities.windowForComponent(cellTip);
553:
554: if ((cellTipWindow != null)
555: && (cellTipWindow != componentWindow)) {
556: cellTipWindow.addMouseListener(this);
557: } else {
558: cellTipWindow = null;
559: }
560: }
561: }
562: }
|