com.pekinsoft.desktop.balloontip.BalloonTip Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of application-framework-api Show documentation
Show all versions of application-framework-api Show documentation
A simple platform on which Java/Swing desktop applications may be built.
This updated version has packaged the entire library into a single JAR
file. We have also made the following changes:
ToolBarGenerator should now create ButtonGroups properly for state actions.
ApplicationContext has accessors for the WindowManager, DockingManager,
StatusDisplayer, and ProgressHandler implementations. It defaults to
testing the Application's MainView and the MainView's StatusBar, then
uses the Lookup, if the MainView and its StatusBar do not implement the
desired interfaces.
StatusMessage now uses the com.pekinsoft.desktop.error.ErrorLevel instead
of the java.util.logging.Level, so that the levels will no longer need to
be cast in order to be used.
The newest version!
/**
* Copyright (c) 2011-2013 Bernhard Pauler, Tim Molderez.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the 3-Clause BSD License
* which accompanies this distribution, and is available at
* http://www.opensource.org/licenses/BSD-3-Clause
*/
package com.pekinsoft.desktop.balloontip;
import java.awt.AlphaComposite;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.pekinsoft.desktop.balloontip.positioners.BalloonTipPositioner;
import com.pekinsoft.desktop.balloontip.positioners.BasicBalloonTipPositioner;
import com.pekinsoft.desktop.balloontip.positioners.LeftAbovePositioner;
import com.pekinsoft.desktop.balloontip.positioners.LeftBelowPositioner;
import com.pekinsoft.desktop.balloontip.positioners.RightAbovePositioner;
import com.pekinsoft.desktop.balloontip.positioners.RightBelowPositioner;
import com.pekinsoft.desktop.balloontip.styles.BalloonTipStyle;
import com.pekinsoft.desktop.balloontip.styles.RoundedBalloonStyle;
/**
* A balloon tip Swing component that is attached to a JComponent and uses
* another JComponent as contents
*
* @author Bernhard Pauler
* @author Tim Molderez
* @author Thierry Blind
*/
public class BalloonTip extends JPanel {
/**
* Should the balloon be placed above, below, right or left of the attached
* component?
*/
public enum Orientation {
LEFT_ABOVE, RIGHT_ABOVE, LEFT_BELOW, RIGHT_BELOW
}
/**
* Where should the balloon's tip be located, relative to the attached
* component ; ALIGNED makes sure the balloon's edge is aligned with the
* attached component
*/
public enum AttachLocation {
ALIGNED, CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST
}
protected JComponent contents = null;
protected JButton closeButton = null;
protected VisibilityControl visibilityControl = new VisibilityControl();
protected BalloonTipStyle style; // Determines the balloon's looks
protected int padding = 0; // Amount of pixels padding around the contents
protected float opacity = 1.0f; // The balloon tip's opacity (1.0 is opaque)
protected BalloonTipPositioner positioner; // Determines the balloon tip's position
protected JLayeredPane topLevelContainer = null; // The balloon tip is drawn on this pane
protected JComponent attachedComponent; // The balloon tip is attached to this component
private static Icon defaultCloseIcon = new ImageIcon(BalloonTip.class.getResource("/net/java/balloontip/images/close_default.png"));
private static Icon rolloverCloseIcon = new ImageIcon(BalloonTip.class.getResource("/net/java/balloontip/images/close_rollover.png"));
private static Icon pressedCloseIcon = new ImageIcon(BalloonTip.class.getResource("/net/java/balloontip/images/close_pressed.png"));
// Only show a balloon tip when the component it's attached to is visible
private final ComponentListener componentListener = new ComponentListener() {
@Override
public void componentMoved(ComponentEvent e) {
refreshLocation();
}
@Override
public void componentResized(ComponentEvent e) {
/*
* We're assuming here that components can only resize when they are
* visible! (If we would use isAttachedComponentShowing(), the
* JApplet test will fail. Perhaps this indicates a bug in
* Component.isShowing() when using components in a JApplet..)
*/
visibilityControl.setCriterionAndUpdate("attachedComponentShowing",
attachedComponent.getWidth() > 0 && attachedComponent.getHeight() > 0);
refreshLocation();
}
@Override
public void componentShown(ComponentEvent e) {
visibilityControl.setCriterionAndUpdate("attachedComponentShowing", isAttachedComponentShowing());
refreshLocation();
}
@Override
public void componentHidden(ComponentEvent e) {
visibilityControl.setCriterionAndUpdate("attachedComponentShowing", false);
}
};
// Adjust the balloon tip when the top-level container is resized
private final ComponentAdapter topLevelContainerListener = new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
refreshLocation();
}
};
// Adjust the balloon tip's visibility when switching tabs
private ComponentAdapter tabbedPaneListener = null;
// Hide the balloon tip when its tip is outside a viewport
protected NestedViewportListener viewportListener = null;
// Behaviour when the balloon tip is clicked
private MouseAdapter clickListener = null;
// Delays construction of a balloon tip in case the top-level container is not available yet
private AncestorListener ancestorListener = null;
/**
* Constructor The simplest constructor, a balloon tip with some text and a
* default look
*
* @param attachedComponent attach the balloon tip to this component (may
* not be null)
* @param text the contents of the balloon tip (may contain
* HTML)
*/
public BalloonTip(JComponent attachedComponent, String text) {
this(attachedComponent, text, new RoundedBalloonStyle(5, 5, Color.WHITE, Color.BLACK), true);
}
/**
* Constructor A simple constructor for a balloon tip containing text, a
* custom look and optionally a close button
*
* @param attachedComponent attach the balloon tip to this component (may
* not be null)
* @param text the contents of the balloon tip (may contain
* HTML)
* @param style the balloon tip's looks (may not be null)
* @param useCloseButton if true, the balloon tip gets a default close
* button
*/
public BalloonTip(JComponent attachedComponent, String text, BalloonTipStyle style, boolean useCloseButton) {
this(attachedComponent, new JLabel(text), style, useCloseButton);
}
/**
* Constructor
*
* @param attachedComponent attach the balloon tip to this component (may
* not be null)
* @param contents the balloon tip's contents (may be null)
* @param style the balloon tip's looks (may not be null)
* @param useCloseButton if true, the balloon tip gets a close button
*/
public BalloonTip(JComponent attachedComponent, JComponent contents, BalloonTipStyle style, boolean useCloseButton) {
this(attachedComponent, contents, style, Orientation.LEFT_ABOVE, AttachLocation.ALIGNED, 15, 15, useCloseButton);
}
/**
* Constructor
*
* @param attachedComponent attach the balloon tip to this component (may
* not be null)
* @param contents the balloon tip's contents (may be null)
* @param style the balloon tip's looks (may not be null)
* @param orientation orientation of the balloon tip
* @param attachLocation location of the balloon's tip within the
* attached component
* @param horizontalOffset horizontal offset for the balloon's tip
* @param verticalOffset vertical offset for the balloon's tip
* @param useCloseButton if true, the balloon tip gets a close button
*/
public BalloonTip(final JComponent attachedComponent, final JComponent contents, final BalloonTipStyle style, Orientation orientation, AttachLocation attachLocation,
int horizontalOffset, int verticalOffset, final boolean useCloseButton) {
super();
setup(attachedComponent, contents, style, setupPositioner(orientation, attachLocation, horizontalOffset, verticalOffset),
useCloseButton ? getDefaultCloseButton() : null);
}
/**
* Constructor - the most customizable balloon tip constructor
*
* @param attachedComponent attach the balloon tip to this component (may
* not be null)
* @param contents the contents of the balloon tip (may be null)
* @param style the balloon tip's looks (may not be null)
* @param positioner determines the way the balloon tip is positioned
* (may not be null)
* @param closeButton the close button to be used for the balloon tip
* (may be null)
*/
public BalloonTip(JComponent attachedComponent, JComponent contents, BalloonTipStyle style, BalloonTipPositioner positioner, JButton closeButton) {
super();
setup(attachedComponent, contents, style, positioner, closeButton);
}
/**
* Sets the contents of this balloon tip (Calling this method will fire a
* "contents" property change event.)
*
* @param contents a JComponent that represents the balloon tip's contents
* If the contents is null, the balloon tip will not be
* shown
*/
public void setContents(JComponent contents) {
JComponent oldContents = this.contents;
if (oldContents != null) {
remove(this.contents);
}
this.contents = contents;
if (contents != null) {
setPadding(getPadding());
add(this.contents, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
visibilityControl.setCriterionAndUpdate("hasContents", true);
} else {
visibilityControl.setCriterionAndUpdate("hasContents", false);
}
// Notify property listeners that the contents has changed
firePropertyChange("contents", oldContents, this.contents);
refreshLocation();
}
/**
* Sets the contents of this balloon tip (Calling this method will fire a
* "contents" property change event.)
*
* @param text the text to be shown in the balloon tip (may contain HTML)
*/
public void setTextContents(String text) {
setContents(new JLabel(text));
}
/**
* Retrieve this balloon tip's contents
*
* @return the JComponent representing the contents of this balloon tip (can
* be null)
*/
public JComponent getContents() {
return this.contents;
}
/**
* Set the amount of padding in this balloon tip (by attaching an empty
* border to the balloon tip's contents...)
*
* @param padding the amount of padding in pixels
*/
public void setPadding(int padding) {
this.padding = padding;
contents.setBorder(BorderFactory.createEmptyBorder(padding, padding, padding, padding));
refreshLocation();
}
/**
* Get the amount of padding in this balloon tip
*
* @return the amount of padding in pixels
*/
public int getPadding() {
return padding;
}
/**
* Set the balloon tip's style (Calling this method will fire a "style"
* property change event.)
*
* @param style a BalloonTipStyle (may not be null)
*/
public void setStyle(BalloonTipStyle style) {
BalloonTipStyle oldStyle = this.style;
this.style = style;
setBorder(this.style);
// Notify property listeners that the style has changed
firePropertyChange("style", oldStyle, style);
refreshLocation();
}
/**
* Get the balloon tip's style
*
* @return the balloon tip's style
*/
public BalloonTipStyle getStyle() {
return style;
}
/**
* Set a new BalloonTipPositioner, repsonsible for the balloon tip's
* positioning (Calling this method will fire a "positioner" property change
* event.)
*
* @param positioner a BalloonTipPositioner (may not be null)
*/
public void setPositioner(BalloonTipPositioner positioner) {
BalloonTipPositioner oldPositioner = this.positioner;
this.positioner = positioner;
this.positioner.setBalloonTip(this);
// Notify property listeners that the positioner has changed
firePropertyChange("positioner", oldPositioner, positioner);
refreshLocation();
}
/**
* Retrieve the BalloonTipPositioner that is used by this balloon tip
*
* @return The balloon tip's positioner
*/
public BalloonTipPositioner getPositioner() {
return positioner;
}
/**
* If you want to permanently close the balloon, you can use this method.
* (It will be called automatically once Java's garbage collector can clean
* up this balloon tip...) Please note, you shouldn't use this instance
* anymore after calling this method! (If you just want to hide the balloon
* tip, simply use setVisible(false);)
*/
public void closeBalloon() {
forceSetVisible(false);
setCloseButton(null); // Remove the close button
for (MouseListener m : getMouseListeners()) {
removeMouseListener(m);
}
tearDownHelper();
}
/**
* Sets this balloon tip's close button Note that this method will not alter
* the button's behaviour. You're expected to set it yourself.
*
* @param button the new close button; if null, the balloon tip's close
* button is removed (if it had one)
*/
public void setCloseButton(JButton button) {
// Remove the current button
if (closeButton != null) {
for (ActionListener a : closeButton.getActionListeners()) {
closeButton.removeActionListener(a);
}
remove(closeButton);
closeButton = null;
}
// Set the new button
if (button != null) {
closeButton = button;
add(closeButton, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
}
refreshLocation();
}
/**
* Sets this balloon tip's close button and sets its behaviour to either
* close or hide the balloon tip
*
* @param button the new close button; if null, the balloon tip's
* close button is removed (if it had one)
* @param permanentClose if true, the button's behaviour is to close the
* balloon tip permanently by calling closeBalloon()
* if false, the button's behaviour is to hide the
* balloon tip by calling setVisible(false)
*/
public void setCloseButton(JButton button, boolean permanentClose) {
if (button != null) {
if (permanentClose) {
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
closeBalloon();
}
});
} else {
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
}
}
setCloseButton(button);
}
/**
* Retrieve this balloon tip's close button
*
* @return the close button (null if not present)
*/
public JButton getCloseButton() {
return closeButton;
}
/**
* Creates a default close button (without any behaviour)
*
* @return the close button
*/
public static JButton getDefaultCloseButton() {
JButton button = new JButton();
button.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
button.setContentAreaFilled(false);
button.setIcon(defaultCloseIcon);
button.setRolloverIcon(rolloverCloseIcon);
button.setPressedIcon(pressedCloseIcon);
return button;
}
/**
* Set the icons for the default close button (This only affects balloon
* tips created after calling this method.)
*
* @param normal regular icon
* @param pressed icon when clicked
* @param rollover icon when hovering over the button
*/
public static void setDefaultCloseButtonIcons(Icon normal, Icon pressed, Icon rollover) {
defaultCloseIcon = normal;
rolloverCloseIcon = rollover;
pressedCloseIcon = pressed;
}
/**
* Adds a mouse listener that will close this balloon tip when clicked.
*
* @param permanentClose if true, the default behaviour is to close the
* balloon tip permanently by calling closeBalloon()
* if false, the default behaviour is to just hide the
* balloon tip by calling setVisible(false)
*/
public void addDefaultMouseListener(boolean permanentClose) {
removeMouseListener(clickListener);
if (permanentClose) {
clickListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
e.consume();
closeBalloon();
}
};
} else {
clickListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
e.consume();
setVisible(false);
}
};
}
addMouseListener(clickListener);
}
/**
* Change the component this balloon tip is attached to (The top-level
* container will be re-determined during this process; if you set it
* manually, you'll have to set it again...) (Calling this method will fire
* an "attachedComponent" property change event.)
*
* @param newComponent the new component to attach to (may not be null)
*
* @exception NullPointerException if parameter newComponent is null
*/
public void setAttachedComponent(JComponent newComponent) {
JComponent oldComponent = this.attachedComponent;
tearDownHelper(); // Remove any listeners related to the old attached component
this.attachedComponent = newComponent;
setupHelper(); // Reinstall the listeners
// Notify property listeners that the attached component has changed
firePropertyChange("attachedComponent", oldComponent, attachedComponent);
refreshLocation();
}
/**
* Retrieve the component this balloon tip is attached to
*
* @return The attached component
*/
public JComponent getAttachedComponent() {
return attachedComponent;
}
/**
* Set the container on which this balloon tip should be drawn
*
* @param tlc the top-level container; must be valid (isValid() must return
* true) () (may not be null)
*/
public void setTopLevelContainer(JLayeredPane tlc) {
if (topLevelContainer != null) {
topLevelContainer.remove(this);
topLevelContainer.removeComponentListener(topLevelContainerListener);
}
this.topLevelContainer = tlc;
// If the window is resized, we should check if the balloon still fits
topLevelContainer.addComponentListener(topLevelContainerListener);
topLevelContainer.add(this);
// We use the popup layer of the top level container (frame or dialog) to show the balloon tip
topLevelContainer.setLayer(this, JLayeredPane.POPUP_LAYER);
}
/**
* Retrieve the container this balloon tip is drawn on If the balloon tip
* hasn't determined this container yet, null is returned
*
* @return The balloon tip's top level container
*/
public JLayeredPane getTopLevelContainer() {
return topLevelContainer;
}
/**
* Retrieves the rectangle to which this balloon tip is attached
*
* @return the rectangle to which this balloon tip is attached, in the
* coordinate system of the balloon tip
*/
public Rectangle getAttachedRectangle() {
Point location = SwingUtilities.convertPoint(attachedComponent, getLocation(), this);
return new Rectangle(location.x, location.y, attachedComponent.getWidth(), attachedComponent.getHeight());
}
/**
* Refreshes the balloon tip's location (Is able to update balloon tip's
* location even if the balloon tip is not shown.)
*/
public void refreshLocation() {
if (topLevelContainer != null) {
positioner.determineAndSetLocation(getAttachedRectangle());
}
}
/**
* Sets the opacity of this balloon tip and repaints it Note: Setting the
* opacity to 0 won't make isVisible() return false.
*
* @param opacity the opacity, where 0.0f is completely invisible and 1.0f
* is opaque
*/
public void setOpacity(float opacity) {
this.opacity = opacity;
repaint();
}
/**
* Get the opacity of this balloon tip
*
* @return the opacity, where 0.0f is completely invisible and 1.0f is
* opaque
*/
public float getOpacity() {
return this.opacity;
}
public void paintComponent(Graphics g) {
if (opacity != 1.0f) {
((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
}
super.paintComponent(g);
}
/**
* Set this balloon tip's visibility
*
* @param visible visible if true (and if the listeners associated with this
* balloon tip have no reason to hide the balloon tip! For
* example, it makes no sense to show balloon tip if the
* component it's attached to is hidden...); invisible
* otherwise
*/
public void setVisible(boolean visible) {
visibilityControl.setCriterionAndUpdate("manual", visible);
}
protected void finalize() throws Throwable {
closeBalloon(); // This will remove all of the listeners a balloon tip uses...
super.finalize();
}
/*
* Sets the balloon tip's visibility by calling super.setVisible() (This
* bypasses the balloon tip's visibility control.) @param visible true if
* the balloon tip should be visible
*/
protected void forceSetVisible(boolean visible) {
super.setVisible(visible);
}
/*
* Helper method that checks whether the attached component is visible or
* not (i.e. its area is greater than 0 and it really is visible..) @return
* true if the component is showing; false otherwise
*/
protected boolean isAttachedComponentShowing() {
return attachedComponent.isShowing()
&& attachedComponent.getWidth() > 0
&& attachedComponent.getHeight() > 0; // The area of the attached component must be > 0 in order to be visible..
}
/*
* Fire a state change event to the viewportlistener (if any)
*/
protected void notifyViewportListener() {
if (viewportListener != null) {
viewportListener.stateChanged(null);
}
}
/*
* Default constructor; does nothing but call the super-constructor
*/
protected BalloonTip() {
super();
}
/*
* Helper method to construct the right positioner given a particular
* orientation, attach location and offset
*/
protected BalloonTipPositioner setupPositioner(Orientation orientation, AttachLocation attachLocation, int horizontalOffset, int verticalOffset) {
BasicBalloonTipPositioner positioner = null;
float attachX = 0.0f;
float attachY = 0.0f;
boolean fixedAttachLocation = true;
switch (attachLocation) {
case ALIGNED:
fixedAttachLocation = false;
break;
case CENTER:
attachX = 0.5f;
attachY = 0.5f;
break;
case NORTH:
attachX = 0.5f;
break;
case NORTHEAST:
attachX = 1.0f;
break;
case EAST:
attachX = 1.0f;
attachY = 0.5f;
break;
case SOUTHEAST:
attachX = 1.0f;
attachY = 1.0f;
break;
case SOUTH:
attachX = 0.5f;
attachY = 1.0f;
break;
case SOUTHWEST:
attachY = 1.0f;
break;
case WEST:
attachY = 0.5f;
break;
case NORTHWEST:
break;
}
switch (orientation) {
case LEFT_ABOVE:
positioner = new LeftAbovePositioner(horizontalOffset, verticalOffset);
break;
case LEFT_BELOW:
positioner = new LeftBelowPositioner(horizontalOffset, verticalOffset);
break;
case RIGHT_ABOVE:
positioner = new RightAbovePositioner(horizontalOffset, verticalOffset);
break;
case RIGHT_BELOW:
positioner = new RightBelowPositioner(horizontalOffset, verticalOffset);
break;
}
positioner.enableFixedAttachLocation(fixedAttachLocation);
positioner.setAttachLocation(attachX, attachY);
return positioner;
}
/*
* Sets up a BalloonTip instance
*/
protected void setup(final JComponent attachedComponent, JComponent contents, BalloonTipStyle style, BalloonTipPositioner positioner, JButton closeButton) {
this.attachedComponent = attachedComponent;
this.contents = contents;
this.style = style;
this.positioner = positioner;
positioner.setBalloonTip(this);
setBorder(this.style);
setOpaque(false);
setLayout(new GridBagLayout());
setPadding(4);
add(this.contents, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
setCloseButton(closeButton, true);
// Don't allow to click 'through' the balloon tip
clickListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
e.consume();
}
};
addMouseListener(clickListener);
// Attempt to run setupHelper() ...
if (attachedComponent.isDisplayable()) {
setupHelper();
} else {
/*
* We can't determine the top-level container yet. We'll just have
* to wait until the parent is set and try again...
*/
ancestorListener = new AncestorListener() {
public void ancestorAdded(AncestorEvent e) {
setupHelper();
e.getComponent().removeAncestorListener(this); // Remove yourself
ancestorListener = null;
}
public void ancestorMoved(AncestorEvent e) {
}
public void ancestorRemoved(AncestorEvent e) {
}
};
attachedComponent.addAncestorListener(ancestorListener);
}
}
/*
* Helper method for setup() and changeAttachedComponent()
*/
private void setupHelper() {
// Set the pane on which the balloon tip is drawn
if (topLevelContainer == null) {
setTopLevelContainer(attachedComponent.getRootPane().getLayeredPane());
}
// If the attached component is moved/hidden/shown, the balloon tip should act accordingly
attachedComponent.addComponentListener(componentListener);
// Update balloon tip's visibility
visibilityControl.setCriterionAndUpdate("attachedComponentShowing", isAttachedComponentShowing());
// Follow the path of parent components to see if there are any we should listen to
Container current = attachedComponent.getParent();
Container previous = attachedComponent;
while (current != null) {
if (current instanceof JTabbedPane || current.getLayout() instanceof CardLayout) {
/*
* Switching tabs only tells the JPanel representing the
* contents of each tab whether it went invisible or not. It
* doesn't propagate such events to each and every component
* within each tab. Because of this, we'll have to add a
* listener to the JPanel of this tab. If it goes invisible, so
* should the balloon tip.
*/
if (tabbedPaneListener == null) {
tabbedPaneListener = getTabbedPaneListener();
}
previous.addComponentListener(tabbedPaneListener);
} else if (current instanceof JViewport) {
if (viewportListener == null) {
viewportListener = new NestedViewportListener();
}
viewportListener.viewports.add((JViewport) current);
((JViewport) current).addChangeListener(viewportListener);
} else if (current instanceof BalloonTip) {
// In the rare case where this balloon tip is attached to a component within another balloon tip...
// Monitor the parent balloon tip's movements and visibility
current.addComponentListener(componentListener);
// Draw this balloon tip one layer higher; otherwise it would be overlapping the parent balloon tip
topLevelContainer.setLayer(this, JLayeredPane.getLayer(this) + 1);
// Quit the loop here; any other parent components that should be listened to are meant for the parent balloon tip
break;
}
previous = current;
current = current.getParent();
}
// We can now calculate and set the balloon tip's initial position
refreshLocation();
// Check whether the balloon tip is currently visible within its viewports (if there are any)
if (viewportListener != null) {
viewportListener.stateChanged(new ChangeEvent(this));
}
}
/*
* Helper method for closeBalloon() and changeAttachedComponent() Removes a
* number of listeners attached to the balloon tip.
*/
private void tearDownHelper() {
// In case you're trying to close a balloon tip before it's fully constructed
if (ancestorListener != null) {
attachedComponent.removeAncestorListener(ancestorListener);
ancestorListener = null;
}
attachedComponent.removeComponentListener(componentListener);
// Remove any listeners that were attached to parent components
if (tabbedPaneListener != null) {
Container current = attachedComponent.getParent();
Container previous = attachedComponent;
while (current != null) {
if (current instanceof JTabbedPane || current.getLayout() instanceof CardLayout) {
previous.removeComponentListener(tabbedPaneListener);
} else if (current instanceof BalloonTip) {
current.removeComponentListener(componentListener);
break;
}
previous = current;
current = current.getParent();
}
tabbedPaneListener = null;
}
if (topLevelContainer != null) {
topLevelContainer.remove(this);
topLevelContainer.removeComponentListener(topLevelContainerListener);
topLevelContainer = null;
}
if (viewportListener != null) {
for (JViewport viewport : viewportListener.viewports) {
viewport.removeChangeListener(viewportListener);
}
viewportListener.viewports.clear();
viewportListener = null;
}
// Clean up our criterias
visibilityControl.criteria.clear();
}
/*
* Creates a Component Listener that will adjust this balloon tip's
* visibility when switching tabs @return the tabbed pane listener
*/
private ComponentAdapter getTabbedPaneListener() {
return new ComponentAdapter() {
public void componentShown(ComponentEvent e) {
visibilityControl.setCriterionAndUpdate("tabShowing", true);
/*
* We must also recheck whether the attached component is
* visible! While this tab *was* invisible, the component
* might've been resized, hidden, shown, ... , but no events
* were fired because the tab was hidden!
*/
visibilityControl.setCriterionAndUpdate("attachedComponentShowing", isAttachedComponentShowing());
refreshLocation();
}
public void componentHidden(ComponentEvent e) {
visibilityControl.setCriterionAndUpdate("tabShowing", false);
}
};
}
/*
* If a balloon tip is nested in one or more viewports, this listener
* ensures the balloon tip is hidden if it is no longer visible within the
* viewports' boundaries
*/
protected class NestedViewportListener implements ChangeListener {
private Vector viewports = new Vector();
public void stateChanged(ChangeEvent e) {
refreshLocation();
Point tipLocation = positioner.getTipLocation();
boolean isWithinViewport = false;
for (JViewport viewport : viewportListener.viewports) {
Rectangle view = new Rectangle(SwingUtilities.convertPoint(viewport, viewport.getLocation(), getTopLevelContainer()), viewport.getSize());
// If the viewport is embedded in a JScrollPane, take into acount the column and row headers
if (viewport.getParent() instanceof JScrollPane) {
JScrollPane scrollPane = (JScrollPane) viewport.getParent();
if (scrollPane.getColumnHeader() != null) {
view.y -= scrollPane.getColumnHeader().getHeight();
}
if (scrollPane.getRowHeader() != null) {
view.x -= scrollPane.getColumnHeader().getWidth();
}
}
if (tipLocation.y >= view.y - 1 // -1 because we still want to allow balloons that are attached to the very top...
&& tipLocation.y <= (view.y + view.height)
&& (tipLocation.x) >= view.x
&& (tipLocation.x) <= (view.x + view.width)) {
isWithinViewport = true;
} else {
isWithinViewport = false;
break;
}
}
if (!viewports.isEmpty()) {
visibilityControl.setCriterionAndUpdate("withinViewport", isWithinViewport);
}
}
}
/*
* Controls when a balloon tip should be shown or hidden
*/
protected class VisibilityControl {
private HashMap criteria = new HashMap(); // A list of criteria determining a balloon tip's visibility
/**
* Sets the value of a particular visibility criterion and checks
* whether the balloon tip should still be visible or not
*
* @param criterion the visibility criterion
* @param value value of the criterion
*/
public void setCriterionAndUpdate(String criterion, Boolean value) {
criteria.put(criterion, value);
update();
}
/**
* Makes sure the balloon tip's visibility is updated by checking all
* visibility criteria If any of the visibility criteria is false, the
* balloon tip should be invisible. Only if all criteria are true, the
* balloon tip can be visible.
*/
public void update() {
Iterator i = criteria.values().iterator();
while (i.hasNext()) {
if (!i.next()) {
forceSetVisible(false);
return;
}
}
forceSetVisible(true);
}
}
private static final long serialVersionUID = 8883837312240932652L;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy