1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
package cuchaz.enigma.gui.panels;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.accessibility.AccessibleContext;
import javax.annotation.Nullable;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeListener;
public class ClosableTabTitlePane {
private final JPanel ui;
private final JButton closeButton;
private final JLabel label;
private ChangeListener cachedChangeListener;
private JTabbedPane parent;
public ClosableTabTitlePane(String text, Runnable onClose) {
this.ui = new JPanel(new FlowLayout(FlowLayout.CENTER, 2, 2));
this.ui.setOpaque(false);
this.label = new JLabel(text);
this.ui.add(this.label);
// Adapted from javax.swing.plaf.metal.MetalTitlePane
this.closeButton = new JButton();
this.closeButton.setFocusPainted(false);
this.closeButton.setFocusable(false);
this.closeButton.setOpaque(true);
this.closeButton.setIcon(UIManager.getIcon("InternalFrame.closeIcon"));
this.closeButton.putClientProperty("paintActive", Boolean.TRUE);
this.closeButton.setBorder(new EmptyBorder(0, 0, 0, 0));
this.closeButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, "Close");
this.closeButton.setMaximumSize(new Dimension(this.closeButton.getIcon().getIconWidth(), this.closeButton.getIcon().getIconHeight()));
this.ui.add(this.closeButton);
// Use mouse listener here so that it also works for disabled buttons
closeButton.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e)) {
onClose.run();
}
}
});
this.ui.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isMiddleMouseButton(e)) {
onClose.run();
}
}
@Override
public void mousePressed(MouseEvent e) {
// for some reason registering a mouse listener on this makes
// events never go to the tabbed pane, so we have to redirect
// the event for tab selection and context menu to work
if (parent != null) {
Point pt = new Point(e.getXOnScreen(), e.getYOnScreen());
SwingUtilities.convertPointFromScreen(pt, parent);
MouseEvent e1 = new MouseEvent(parent, e.getID(), e.getWhen(), e.getModifiersEx(), (int) pt.getX(), (int) pt.getY(), e.getXOnScreen(), e.getYOnScreen(), e.getClickCount(), e.isPopupTrigger(), e.getButton());
parent.dispatchEvent(e1);
}
}
});
this.ui.putClientProperty(ClosableTabTitlePane.class, this);
}
public void setTabbedPane(JTabbedPane pane) {
if (this.parent != null) {
pane.removeChangeListener(cachedChangeListener);
}
if (pane != null) {
updateState(pane);
cachedChangeListener = e -> updateState(pane);
pane.addChangeListener(cachedChangeListener);
}
this.parent = pane;
}
public void setText(String text) {
this.label.setText(text);
}
public String getText() {
return this.label.getText();
}
private void updateState(JTabbedPane pane) {
int selectedIndex = pane.getSelectedIndex();
boolean isActive = selectedIndex != -1 && pane.getTabComponentAt(selectedIndex) == this.ui;
this.closeButton.setEnabled(isActive);
this.closeButton.putClientProperty("paintActive", isActive);
this.ui.repaint();
}
public JPanel getUi() {
return ui;
}
@Nullable
public static ClosableTabTitlePane byUi(Component c) {
if (c instanceof JComponent) {
Object prop = ((JComponent) c).getClientProperty(ClosableTabTitlePane.class);
if (prop instanceof ClosableTabTitlePane) {
return (ClosableTabTitlePane) prop;
}
}
return null;
}
}
|