JTabbedPaneのTabAreaで開くJPopupMenuを設定する
Total: 462
, Today: 2
, Yesterday: 0
Posted by aterai at
Last-modified:
概要
JTabbedPane
のタブ上とTabArea
内では異なるJPopupMenu
を開くよう設定します。
Screenshot
Advertisement
サンプルコード
JTabbedPane tabbedPane = new JTabbedPane() {
private final JPopupMenu popup1 = makeTabPopupMenu();
private final JPopupMenu popup2 = makeTabAreaPopupMenu();
@Override public void updateUI() {
super.updateUI();
EventQueue.invokeLater(() -> {
SwingUtilities.updateComponentTreeUI(popup1);
SwingUtilities.updateComponentTreeUI(popup2);
setComponentPopupMenu(popup1);
});
}
@Override public Point getPopupLocation(MouseEvent e) {
int idx = indexAtLocation(e.getX(), e.getY());
if (idx < 0 && getTabAreaBounds().contains(e.getPoint())) {
setComponentPopupMenu(popup2);
} else {
setComponentPopupMenu(popup1);
}
return super.getPopupLocation(e);
}
private Rectangle getTabAreaBounds() {
Rectangle r = SwingUtilities.calculateInnerArea(this, null);
Rectangle cr = Optional.ofNullable(getSelectedComponent())
.map(Component::getBounds)
.orElseGet(Rectangle::new);
int tp = getTabPlacement();
// Note: don't call BasicTabbedPaneUI#getTabAreaInsets(),
// because it causes rotation.
Insets i1 = UIManager.getInsets("TabbedPane.tabAreaInsets");
Insets i2 = UIManager.getInsets("TabbedPane.contentBorderInsets");
if (tp == TOP || tp == BOTTOM) {
r.height -= cr.height + i1.top + i1.bottom + i2.top + i2.bottom;
// r.x += i1.left;
r.y += tp == TOP ? i1.top : cr.y + cr.height + i1.bottom + i2.bottom;
} else {
r.width -= cr.width + i1.top + i1.bottom + i2.left + i2.right;
r.x += tp == LEFT ? i1.top : cr.x + cr.width + i1.bottom + i2.right;
// r.y += i1.left;
}
return r;
}
};
View in GitHub: Java, Kotlin解説
- タブ上用
popup1
とTabArea
内用popup2
に2
種類のJPopupMenu
を用意 JTabbedPane#getPopupLocation(MouseEvent)
をオーバーライドしてクリック位置によって使用するJPopupMenu
を切り替えるJTabbedPane#getPopupLocation(MouseEvent)
はJTabbedPane#getComponentPopupMenu()
がnull
で未設定の場合は実行されないので、初期状態で適当なJPopupMenu
を設定しておく必要があるTabArea
内、かつJTabbedPane#indexAtLocation(...)
が負でクリック位置がタブ上でない場合はTabArea
内用のpopup2
をJTabbedPane#setComponentPopupMenu(popup2)
で設定- それ以外の場合はタブ上用の
popup1
をJTabbedPane#setComponentPopupMenu(popup1)
で設定- タブコンテナ内のコンポーネントにマウスリスナーが未設定の場合タブ上用の
popup1
がJPopupMenu
として使用される
- タブコンテナ内のコンポーネントにマウスリスナーが未設定の場合タブ上用の
- ひとつの
JPopupMenu
でJPopupMenu#show(invoker, x, y)
をオーバーライドし、このxy
座標で使用するJMenuItem
を入れ替える方法もある- どちらを使用する場合も動作中に
LookAndFeel
を変更する場合は手動でSwingUtilities.updateComponentTreeUI(...)
を実行して現在使用していないJPopupMenu
やJMenuItem
のLookAndFeel
を更新する必要がある
- どちらを使用する場合も動作中に
JTabbedPane#setTabComponentAt(...)
で設定したタブコンポーネントにJPopupMenu
を設定して以下のようにJTabbedPane#getComponentPopupMenu()
で切り替える方法もあるが、マウスクリックによるタブ移動が不可になる場合がある
@Override public JPopupMenu getComponentPopupMenu() {
int idx = getSelectedIndex();
Component c = getTabComponentAt(idx);
JPopupMenu popup;
if (idx>= 0 && c instanceof JComponent) {
popup = ((JComponent) c).getComponentPopupMenu();
} else {
popup = super.getComponentPopupMenu();
}
return popup;
}
TabbedPane.tabAreaInsets
とTabbedPane.contentBorderInsets
の余白を考慮してTabArea
領域を計算するよう修正TabbedPane.tabAreaInsets
はタブ配置位置によって上下左右の余白が入れ替わる場合があるTabbedPane.contentBorderInsets
は変化しない- 入れ替え方法は
BasicTabbedPaneUI#rotateInsets(...)
を参照
protected static void rotateInsets(
Insets topInsets, Insets targetInsets, int targetPlacement) {
switch(targetPlacement) {
case LEFT:
targetInsets.top = topInsets.left;
targetInsets.left = topInsets.top;
targetInsets.bottom = topInsets.right;
targetInsets.right = topInsets.bottom;
break;
case BOTTOM:
targetInsets.top = topInsets.bottom;
targetInsets.left = topInsets.left;
targetInsets.bottom = topInsets.top;
targetInsets.right = topInsets.right;
break;
case RIGHT:
targetInsets.top = topInsets.left;
targetInsets.left = topInsets.bottom;
targetInsets.bottom = topInsets.right;
targetInsets.right = topInsets.top;
break;
case TOP:
default:
targetInsets.top = topInsets.top;
targetInsets.left = topInsets.left;
targetInsets.bottom = topInsets.bottom;
targetInsets.right = topInsets.right;
}
}
- スクロールタブレイアウトの場合のみで十分な場合は、以下のように名前が
TabbedPane.scrollableViewport
のJViewport
を検索してエッジボタンを除くTabArea
領域を取得する方法もある
private static Rectangle getTabAreaBounds2(JTabbedPane tabbedPane) {
return descendants(tabbedPane)
.filter(JViewport.class::isInstance)
.map(JComponent.class::cast)
.filter(v -> "TabbedPane.scrollableViewport".equals(v.getName()))
.findFirst()
.map(c -> {
Rectangle r = SwingUtilities.calculateInnerArea(c, null);
// Note: BasicTabbedPaneUI#getTabAreaInsets() causes rotation.
Insets tabAreaInsets = UIManager.getInsets(
"TabbedPane.tabAreaInsets");
Insets targetInsets = new Insets(0, 0, 0, 0);
rotateInsets(
tabAreaInsets, targetInsets, tabbedPane.getTabPlacement());
if (r != null) {
r.x += tabAreaInsets.left;
r.y += tabAreaInsets.top;
r.width -= tabAreaInsets.left + tabAreaInsets.right;
r.height -= tabAreaInsets.top + tabAreaInsets.bottom;
r = SwingUtilities.convertRectangle(c, r, tabbedPane);
}
return r;
})
.orElseGet(Rectangle::new);
}
private static Stream<Component> descendants(Container parent) {
return Stream.of(parent.getComponents())
.filter(Container.class::isInstance).map(Container.class::cast)
.flatMap(c -> Stream.concat(Stream.of(c), descendants(c)));
}
参考リンク
- JTabbedPaneのタブをドラッグ&ドロップ
TabArea
領域の取得方法は上記のリンク先のサンプルと同一
- JTabbedPaneのタブ選択をマウスホイールで変更する
- こちらの
TabArea
領域の取得方法はJTabbedPane.SCROLL_TAB_LAYOUT
限定かつ内余白内でのマウスホイールイベントも使用するため、名前がTabbedPane.scrollableViewport
のJViewport
の領域を使用している
- こちらの