In the following program (which depends on JOGL), the tooltip of the JLabel
is hidden behind the heavyweight GLCanvas
when the tooltip 'fits' inside the GLCanvas
.
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
import com.jogamp.opengl.awt.GLCanvas;
public class HeavyWeightTooltipTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);
try {
UIManager.setLookAndFeel(NimbusLookAndFeel.class.getName());
} catch (Exception aE) {
aE.printStackTrace();
}
showUI();
}
});
}
private static void showUI(){
JFrame frame = new JFrame("TestFrame");
JLabel label = new JLabel("Label with tooltip");
label.setToolTipText("A very long tooltip to ensure it overlaps with the heavyweight component");
frame.add(label, BorderLayout.WEST);
GLCanvas glCanvas = new GLCanvas();
frame.add(glCanvas, BorderLayout.CENTER);
frame.setVisible(true);
frame.setSize(300,300);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
Observations
java.awt.Canvas
, only with the JOGL GLCanvas
(which is an extension of java.awt.Canvas
)GLCanvas
. The problem starts as soon as the tooltip fits into the GLCanvas
(see screenshots at the end of the post)ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false)
or not. The problem is always reproducibleIn case it is relevant, I am using JOGL version 2.3.2 and Java version 1.8.0_65
java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)
Tooltip correctly shown Tooltip hidden behind GLCanvas
Edit: I logged this in the bug tracker of JOGL as bug 1306.
It seems that forcing the PopupFactory
to use heavyweight tooltips (instead of medium weight tooltips) fixes the issue.
This is non-trivial, and requires you to write your own PopupFactory
or use reflection to call PopupFactory#setPopupType
.
As I wasn't too keen on writing my own PopupFactory
, I used reflection:
final class HeavyWeightTooltipEnforcerMac {
private static final Object LOCK = new Object();
private static PropertyChangeListener sUIManagerListener;
private HeavyWeightTooltipEnforcerMac() {
}
/**
* <p>
* Tooltips which overlap with the GLCanvas
* will be painted behind the heavyweight component when the bounds of the tooltip are contained
* in the bounds of the application.
* </p>
*
* <p>
* In that case, {@code javax.swing.PopupFactory#MEDIUM_WEIGHT_POPUP} instances are used, and
* they suffer from this bug.
* Always using {@code javax.swing.PopupFactory#HEAVY_WEIGHT_POPUP} instances fixes the issue.
* </p>
*
* <p>
* Note that the bug is only present when not using the Aqua look-and-feel.
* Aqua uses its own {@code PopupFactory} which does not suffer from this.
* </p>
*
*/
static void install() {
synchronized (LOCK) {
if (sUIManagerListener == null && isMacOS()) {
installCustomPopupFactoryIfNeeded();
sUIManagerListener = new LookAndFeelChangeListener();
UIManager.addPropertyChangeListener(sUIManagerListener);
}
}
}
private static void installCustomPopupFactoryIfNeeded() {
if (!isAquaLookAndFeel()) {
PopupFactory.setSharedInstance(new AlwaysUseHeavyWeightPopupsFactory());
}
}
private static final class LookAndFeelChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if ("lookAndFeel".equals(propertyName)) {
installCustomPopupFactoryIfNeeded();
}
}
}
private static class AlwaysUseHeavyWeightPopupsFactory extends PopupFactory {
private boolean couldEnforceHeavyWeightComponents = true;
@Override
public Popup getPopup(Component owner, Component contents, int x, int y) throws IllegalArgumentException {
enforceHeavyWeightComponents();
return super.getPopup(owner, contents, x, y);
}
private void enforceHeavyWeightComponents() {
if (!couldEnforceHeavyWeightComponents) {
return;
}
try {
Method setPopupTypeMethod = PopupFactory.class.getDeclaredMethod("setPopupType", int.class);
setPopupTypeMethod.setAccessible(true);
setPopupTypeMethod.invoke(this, 2);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException aE) {
//If it fails once, it will fail every time. Do not try again
//Consequence is that tooltips which overlap with a heavyweight component will be painted behind that component iso
//on top of it
couldEnforceHeavyWeightComponents = false;
}
}
}
}
A similar fix can be found in the IntelliJ community edition: the LafManagerImpl
class sets its own factory in the fixPopupWeight
method, which enforces heavyweight popups.