Use a real tab strip for servers instead of a button bar
ServerTabPane replaces ServerTabBar: the views live in a JTabbedPane whose strip sits under the toolbar, with the native tab shapes and the same look as the chat tabs. Each tab carries its server name, the microphone marker and a close glyph. With a single connection the server view is mounted directly, so the strip appears only once a second server is open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package com.ts3client.ui;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTabbedPane;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Container for the open server connections. With a single connection the
|
||||
* server view fills the window as it always did; from the second connection on
|
||||
* the views move into a {@link JTabbedPane} whose tab strip sits directly under
|
||||
* the toolbar.
|
||||
*/
|
||||
final class ServerTabPane extends JPanel {
|
||||
|
||||
interface Listener {
|
||||
void selectTab(ServerTab tab);
|
||||
|
||||
void closeTab(ServerTab tab);
|
||||
}
|
||||
|
||||
private final Listener listener;
|
||||
private final JTabbedPane tabbed = new JTabbedPane();
|
||||
private final List<ServerTab> tabs = new ArrayList<>();
|
||||
|
||||
/** True while the tab strip is in use, i.e. more than one connection is open. */
|
||||
private boolean tabbedMode;
|
||||
/** Suppresses selection callbacks while we rearrange the pane ourselves. */
|
||||
private boolean updating;
|
||||
|
||||
ServerTabPane(Listener listener) {
|
||||
super(new BorderLayout());
|
||||
this.listener = listener;
|
||||
tabbed.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
tabbed.setFocusable(false);
|
||||
tabbed.addChangeListener(e -> {
|
||||
if (updating) return;
|
||||
int i = tabbed.getSelectedIndex();
|
||||
if (i >= 0 && i < tabs.size()) listener.selectTab(tabs.get(i));
|
||||
});
|
||||
}
|
||||
|
||||
void addTab(ServerTab tab) {
|
||||
tabs.add(tab);
|
||||
relayout();
|
||||
}
|
||||
|
||||
void removeTab(ServerTab tab) {
|
||||
tabs.remove(tab);
|
||||
relayout();
|
||||
}
|
||||
|
||||
void setSelected(ServerTab tab) {
|
||||
int i = tabs.indexOf(tab);
|
||||
if (!tabbedMode || i < 0 || tabbed.getSelectedIndex() == i) return;
|
||||
updating = true;
|
||||
try {
|
||||
tabbed.setSelectedIndex(i);
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Refreshes the tab labels; {@code micTab} is marked as owning the microphone. */
|
||||
void refresh(ServerTab micTab) {
|
||||
if (!tabbedMode) return;
|
||||
for (int i = 0; i < tabs.size(); i++) {
|
||||
ServerTab tab = tabs.get(i);
|
||||
tabbed.setTitleAt(i, tab.title());
|
||||
tabbed.setToolTipTextAt(i, tab.status());
|
||||
tabbed.setTabComponentAt(i, buildTabComponent(tab, tab == micTab));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the views between the plain single-server layout and the tabbed one,
|
||||
* keeping whatever is currently selected on screen.
|
||||
*/
|
||||
private void relayout() {
|
||||
boolean wantTabs = tabs.size() > 1;
|
||||
updating = true;
|
||||
try {
|
||||
removeAll();
|
||||
tabbed.removeAll();
|
||||
if (wantTabs) {
|
||||
for (ServerTab tab : tabs) tabbed.addTab(tab.title(), tab.component());
|
||||
add(tabbed, BorderLayout.CENTER);
|
||||
} else if (!tabs.isEmpty()) {
|
||||
add(tabs.get(0).component(), BorderLayout.CENTER);
|
||||
}
|
||||
tabbedMode = wantTabs;
|
||||
} finally {
|
||||
updating = false;
|
||||
}
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
/** A tab's label: title, microphone marker and a close button. */
|
||||
private JComponent buildTabComponent(ServerTab tab, boolean hasMic) {
|
||||
JPanel cell = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
|
||||
cell.setOpaque(false);
|
||||
cell.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 0));
|
||||
|
||||
JLabel label = new JLabel(tab.title(), hasMic ? Icons.micActive() : null, JLabel.LEADING);
|
||||
label.setFont(Theme.UI_FONT);
|
||||
label.setToolTipText(hasMic ? "Speaking on this server" : tab.status());
|
||||
// A label with a tooltip swallows mouse events, so select the tab explicitly.
|
||||
label.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
listener.selectTab(tab);
|
||||
}
|
||||
});
|
||||
cell.add(label);
|
||||
|
||||
JButton close = new JButton("✕");
|
||||
close.setFont(Theme.UI_FONT);
|
||||
close.setFocusable(false);
|
||||
close.setBorder(BorderFactory.createEmptyBorder());
|
||||
close.setContentAreaFilled(false);
|
||||
close.setMargin(new Insets(0, 0, 0, 0));
|
||||
close.setForeground(Theme.CHAT_SYSTEM);
|
||||
close.setPreferredSize(new Dimension(14, 14));
|
||||
close.setToolTipText("Close this connection");
|
||||
close.addActionListener(e -> listener.closeTab(tab));
|
||||
cell.add(close);
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user