Tabbed servers: multiple simultaneous connections
Each connection now lives in its own ServerTab with its own tree, info and chat views plus its own mic-mute, deafen, away and commander state. MainFrame keeps only the shell (menus, toolbar, status bar) and shows one tab at a time; the tab bar below the toolbar appears once a second server is open. Incoming voice from every connection mixes into the same output — each connection already renders one line per speaker. Capture is exclusive: a connection only opens the microphone while it holds it, and a toolbar button moves it to the visible tab. The mic follows a disconnect to another connected tab, and push-to-talk drives whoever holds it. Startup bookmarks are no longer mutually exclusive: every flagged bookmark opens its own tab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package com.ts3client.ui;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JToggleButton;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Insets;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Strip of open server connections, shown between the toolbar and the server
|
||||
* view. The bar only appears while more than one connection is open; with a
|
||||
* single server the client looks exactly as it did before.
|
||||
*/
|
||||
final class ServerTabBar extends JPanel {
|
||||
|
||||
interface Listener {
|
||||
void selectTab(ServerTab tab);
|
||||
|
||||
void closeTab(ServerTab tab);
|
||||
}
|
||||
|
||||
private final Listener listener;
|
||||
|
||||
ServerTabBar(Listener listener) {
|
||||
super(new FlowLayout(FlowLayout.LEFT, 3, 2));
|
||||
this.listener = listener;
|
||||
setBackground(Theme.TOOLBAR_BG);
|
||||
setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Theme.WINDOW_BG));
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param micTab the connection holding the microphone, marked with an icon
|
||||
*/
|
||||
void rebuild(List<ServerTab> tabs, ServerTab selected, ServerTab micTab) {
|
||||
removeAll();
|
||||
for (ServerTab tab : tabs) {
|
||||
add(buildCell(tab, tab == selected, tab == micTab, tabs.size() > 1));
|
||||
}
|
||||
setVisible(tabs.size() > 1);
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private JPanel buildCell(ServerTab tab, boolean selected, boolean hasMic, boolean closable) {
|
||||
JPanel cell = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
||||
cell.setOpaque(false);
|
||||
|
||||
JToggleButton button = new JToggleButton(tab.title(), hasMic ? Icons.micActive() : null);
|
||||
button.setSelected(selected);
|
||||
button.setFocusable(false);
|
||||
button.setFont(selected ? Theme.UI_BOLD : Theme.UI_FONT);
|
||||
button.setMargin(new Insets(2, 8, 2, 8));
|
||||
button.setToolTipText(tab.status());
|
||||
button.addActionListener(e -> listener.selectTab(tab));
|
||||
cell.add(button);
|
||||
|
||||
if (closable) {
|
||||
JButton close = new JButton("✕");
|
||||
close.setFocusable(false);
|
||||
close.setFont(Theme.UI_FONT);
|
||||
close.setMargin(new Insets(2, 5, 2, 5));
|
||||
// Keep the cell compact but never so small that the glyph is clipped.
|
||||
Dimension size = close.getPreferredSize();
|
||||
close.setPreferredSize(new Dimension(size.width, button.getPreferredSize().height));
|
||||
close.setToolTipText("Close this connection");
|
||||
close.addActionListener(e -> listener.closeTab(tab));
|
||||
cell.add(close);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user