使用非 Aqua 外观时,工具提示隐藏在 OS X 上的 JOGL GLCanvas 后面
Tooltip hidden behind JOGL GLCanvas on OS X when using non-Aqua look and feel
在下面的程序中(依赖于JOGL),JLabel
的tooltip隐藏在重量级GLCanvas
的后面,而GLCanvas
里面的tooltip 'fits' .
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);
}
}
观察结果
- 只有在不使用 Aqua 外观时才会发生。我可以用 Nimbus 和 Metal 的外观和感觉来重现它,但不能用 Aqua 的外观和感觉。
- 使用常规
java.awt.Canvas
时不会发生,只有 JOGL GLCanvas
(它是 java.awt.Canvas
的扩展)
- 当工具提示比
GLCanvas
宽时,工具提示可以正确呈现。一旦工具提示适合 GLCanvas
(请参阅 post 末尾的屏幕截图) ,问题就开始了
- 叫不叫
ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false)
都无所谓。问题总是可重现的
- 适用于 Linux 和 Windows
如果相关,我使用的是 JOGL 2.3.2 版和 Java 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)
工具提示正确显示
隐藏在 GLCanvas 后面的工具提示
编辑:我在 JOGL 的错误跟踪器中记录为 bug 1306。
似乎强制 PopupFactory
使用重量级工具提示(而不是中等重量工具提示)可以解决此问题。
这很重要,需要您自己编写 PopupFactory
或使用反射调用 PopupFactory#setPopupType
.
因为我不太热衷于编写自己的 PopupFactory
,所以我使用了反射:
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;
}
}
}
}
在 IntelliJ 社区版中可以找到类似的修复:LafManagerImpl
class 在 fixPopupWeight
方法中设置自己的工厂,强制执行重量级弹出窗口。
在下面的程序中(依赖于JOGL),JLabel
的tooltip隐藏在重量级GLCanvas
的后面,而GLCanvas
里面的tooltip 'fits' .
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);
}
}
观察结果
- 只有在不使用 Aqua 外观时才会发生。我可以用 Nimbus 和 Metal 的外观和感觉来重现它,但不能用 Aqua 的外观和感觉。
- 使用常规
java.awt.Canvas
时不会发生,只有 JOGLGLCanvas
(它是java.awt.Canvas
的扩展) - 当工具提示比
GLCanvas
宽时,工具提示可以正确呈现。一旦工具提示适合GLCanvas
(请参阅 post 末尾的屏幕截图) ,问题就开始了
- 叫不叫
ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false)
都无所谓。问题总是可重现的 - 适用于 Linux 和 Windows
如果相关,我使用的是 JOGL 2.3.2 版和 Java 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)
工具提示正确显示
编辑:我在 JOGL 的错误跟踪器中记录为 bug 1306。
似乎强制 PopupFactory
使用重量级工具提示(而不是中等重量工具提示)可以解决此问题。
这很重要,需要您自己编写 PopupFactory
或使用反射调用 PopupFactory#setPopupType
.
因为我不太热衷于编写自己的 PopupFactory
,所以我使用了反射:
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;
}
}
}
}
在 IntelliJ 社区版中可以找到类似的修复:LafManagerImpl
class 在 fixPopupWeight
方法中设置自己的工厂,强制执行重量级弹出窗口。