使用非 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);
  }
}

观察结果

工具提示正确显示 隐藏在 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 方法中设置自己的工厂,强制执行重量级弹出窗口。