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.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Insets; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; 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 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)); }); tabbed.addMouseWheelListener(this::onWheel); } /** * Scrolling over the tab strip steps through the connections. The content * area keeps its own scrolling, so only wheel events on the strip itself * (and on the tab labels, which forward theirs) change tabs. */ private void onWheel(MouseWheelEvent e) { if (!tabbedMode) return; Component content = tabbed.getSelectedComponent(); if (content != null && content.getBounds().contains(e.getPoint())) return; int steps = e.getWheelRotation(); if (steps == 0) return; int target = Math.max(0, Math.min(tabs.size() - 1, tabbed.getSelectedIndex() + steps)); if (target != tabbed.getSelectedIndex()) listener.selectTab(tabs.get(target)); e.consume(); } 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.micActiveSmall() : 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; } }