使用非Aqua外观时,在OS X上隐藏在JOGL GLCanvas后面的工具提示

时间:2016-05-03 10:42:15

标签: java macos swing tooltip jogl

在以下程序(取决于JOGL)中,JLabel的工具提示隐藏在重量级GLCanvas后面,当工具提示符合“#{1}}时。在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);
  }
}

观察

  • 只有在不使用Aqua外观时才会发生这种情况。我可以用Nimbus和Metal的外观和感觉重现它,但不是Aqua的外观和感觉。
  • 使用常规java.awt.Canvas时,只有JOGL GLCanvasjava.awt.Canvas的扩展名
  • )才会发生这种情况
  • 当工具提示宽于GLCanvas时,工具提示会正确呈现。一旦工具提示符合GLCanvas(见帖子末尾的屏幕截图),问题就会开始。
  • 我是否打电话给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)
    

工具提示正确显示 Tooltip correctly shown 隐藏在GLCanvas背后的工具提示 Tooltip hidden behind GLCanvas

编辑:我在JOGL的错误跟踪器中将其记录为bug 1306

1 个答案:

答案 0 :(得分:1)

似乎强制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类在fixPopupWeight方法中设置自己的工厂,这会强制执行重量级弹出窗口。