概要

JPopupMenuTaskBarと重なる場合、マウスカーソル位置がその右下隅になるよう表示位置を調整し、内部のJMenuItemの配置も反転します。

サンプルコード

List<Component> list = Arrays.asList(
    new JMenuItem("JMenuItem 1"),
    new JMenuItem("JMenuItem 2"),
    new JMenuItem("JMenuItem 3"),
    new JMenuItem("JMenuItem 4"),
    new JMenuItem("JMenuItem 5"));
JPopupMenu popup = new JPopupMenu() {
  @Override public void show(Component c, int x, int y) {
    Point popupLocation = getInvokerOrigin(x, y, c.getLocationOnScreen());
    Rectangle scrBounds = getScreenBounds(c, popupLocation);
    Dimension popupSize = getPreferredSize();
    long popupBottomY = (long) popupLocation.y + (long) popupSize.height;
    Point p = new Point(x, y);
    removeAll();
    if (popupBottomY > scrBounds.y + scrBounds.height) {
      p.translate(-popupSize.width, -popupSize.height);
      for (int i = list.size() - 1; i >= 0; i--) {
        add(list.get(i));
      }
    } else {
      list.forEach(this::add);
    }
    super.show(c, p.x, p.y);
  }
};
list.forEach(popup::add);
setComponentPopupMenu(popup);
View in GitHub: Java, Kotlin

解説

  • JPopupMenuの親コンポーネントが表示されているGraphicsConfigurationを取得
    • JPopupMenu#getCurrentGraphicsConfiguration(Point)を参考
GraphicsConfiguration getCurrentGraphicsConfiguration2(Component c, Point p) {
  GraphicsConfiguration gc = null;
  GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  GraphicsDevice[] gd = ge.getScreenDevices();
  for (GraphicsDevice graphicsDevice : gd) {
    if (graphicsDevice.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
      GraphicsConfiguration dgc = graphicsDevice.getDefaultConfiguration();
      if (dgc.getBounds().contains(p)) {
        gc = dgc;
        break;
      }
    }
  }
  // If not found, and we have invoker, ask invoker about his gc
  if (gc == null && c != null) {
    gc = c.getGraphicsConfiguration();
  }
  return gc;
}
  • 取得したGraphicsConfigurationからスクリーン領域を取得しTaskBarなどの余白を除去
    • デフォルトのJPopupMenuではJPopupMenu#adjustPopupLocationToFitScreen(int xPosition, int yPosition)メソッド内で( (SunToolkit) Toolkit.getDefaultToolkit() ).canPopupOverlapTaskBar()falseの場合TaskBarなどの余白を除去してスクリーン領域サイズを計算しているがWindows 10環境では常にtrueでこれを変更する方法が不明
    • このためこのサンプルでは以下のように( (SunToolkit) Toolkit.getDefaultToolkit() ).canPopupOverlapTaskBar()を無視してTaskBar領域をスクリーン領域サイズから除去している
private static Rectangle getScreenBounds(Component c, Point popupLocation) {
  Rectangle scrBounds;
  GraphicsConfiguration gc = getCurrentGraphicsConfiguration2(c, popupLocation);
  Toolkit toolkit = Toolkit.getDefaultToolkit();
  if (gc != null) {
    // If we have GraphicsConfiguration use it to get screen bounds
    scrBounds = gc.getBounds();
  } else {
    scrBounds = new Rectangle(toolkit.getScreenSize());
  }
  Insets scrInsets = toolkit.getScreenInsets(gc);
  scrBounds.x += scrInsets.left;
  scrBounds.y += scrInsets.top;
  scrBounds.width -= scrInsets.left + scrInsets.right;
  scrBounds.height -= scrInsets.top + scrInsets.bottom;
  return scrBounds;
}
  • JPopupMenu#show(...)をオーバーライドして取得したスクリーン領域の外にJPopupMenuの下辺が表示される場合は、マウスカーソル位置がJPopupMenuの右下隅になるよう表示位置を調整
    • 左上隅から右下隅にマウスカーソル配置が変更されるのでJPopupMenu内部のJMenuItemの配置もマウスカーソル移動距離が短くなるよう昇順から降順に変更
    • JPopupMenu#show(...)内などで参照されているpopupPostionFixDisabledpopupPositionFixDisabledtypo

参考リンク

コメント