238 lines
9.1 KiB
Java
238 lines
9.1 KiB
Java
package com.ts3client.ui;
|
|
|
|
import com.formdev.flatlaf.util.ScaledEmptyBorder;
|
|
import com.formdev.flatlaf.util.UIScale;
|
|
import com.ts3client.config.Settings;
|
|
import com.ts3client.net.HostButton;
|
|
import com.ts3client.net.PictureDownloads;
|
|
|
|
import javax.swing.Box;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JCheckBoxMenuItem;
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JPopupMenu;
|
|
import javax.swing.JSlider;
|
|
import javax.swing.JToggleButton;
|
|
import javax.swing.JToolBar;
|
|
import javax.swing.event.PopupMenuEvent;
|
|
import javax.swing.event.PopupMenuListener;
|
|
import java.awt.Component;
|
|
import java.awt.Dimension;
|
|
import java.awt.FlowLayout;
|
|
|
|
/**
|
|
* The main window's toolbar: connect/disconnect, the per-tab mic/speaker/away
|
|
* controls, and the Master Volume slider and the server's host button on the
|
|
* right. A right-click anywhere on the bar opens a customization menu for the
|
|
* window's optional chrome (this toolbar has no say over the status bar's own
|
|
* visibility beyond reporting the toggle).
|
|
*
|
|
* <p>Talks to {@link MainFrame} only through {@link Listener}, so it owns no
|
|
* connection/tab state itself — {@link #refresh} is handed everything it
|
|
* needs to redraw each time the selection changes.
|
|
*/
|
|
final class MainToolbar extends JToolBar {
|
|
|
|
interface Listener {
|
|
void onConnect();
|
|
|
|
void onDisconnect();
|
|
|
|
/** Moves the microphone to the tab on screen. */
|
|
void onActivate();
|
|
|
|
void onMicMuteToggle(boolean muted);
|
|
|
|
void onDeafenToggle(boolean deafened);
|
|
|
|
/** The plain toggle carries no message; the drop-down arrow is where messages are chosen. */
|
|
void onAwayToggle(boolean away);
|
|
|
|
JPopupMenu buildAwayMenu();
|
|
|
|
void onSettings();
|
|
|
|
/** The slider changed; push the new master volume to every open connection. */
|
|
void onMasterVolumeChanged();
|
|
|
|
void onStatusBarVisibilityChanged(boolean visible);
|
|
}
|
|
|
|
private final Settings settings;
|
|
private final Listener listener;
|
|
|
|
private final JButton connectButton;
|
|
private final JButton disconnectButton;
|
|
private final JToggleButton activeButton;
|
|
private final JToggleButton micButton;
|
|
private final JToggleButton speakerButton;
|
|
private final DropDownToggleButton awayButton;
|
|
private final JComponent masterVolumePanel;
|
|
private final HostButtonView hostButton;
|
|
|
|
MainToolbar(Settings settings, Listener listener, PictureDownloads pictures) {
|
|
this.settings = settings;
|
|
this.listener = listener;
|
|
|
|
setFloatable(false);
|
|
setBackground(Theme.toolbarBg());
|
|
setBorder(new ScaledEmptyBorder(4, 6, 4, 6));
|
|
setComponentPopupMenu(buildContextMenu());
|
|
|
|
connectButton = new JButton(Icons.connect());
|
|
connectButton.setToolTipText("Connect to a server");
|
|
connectButton.addActionListener(e -> listener.onConnect());
|
|
|
|
disconnectButton = new JButton(Icons.disconnect());
|
|
disconnectButton.setToolTipText("Disconnect");
|
|
disconnectButton.addActionListener(e -> listener.onDisconnect());
|
|
|
|
activeButton = new JToggleButton(Icons.micActive());
|
|
activeButton.setToolTipText("Speak on this server (moves the microphone to this tab)");
|
|
activeButton.addActionListener(e -> listener.onActivate());
|
|
|
|
micButton = new JToggleButton(Icons.mic());
|
|
micButton.setToolTipText("Mute / unmute microphone on this server");
|
|
micButton.addActionListener(e -> listener.onMicMuteToggle(micButton.isSelected()));
|
|
|
|
speakerButton = new JToggleButton(Icons.speaker());
|
|
speakerButton.setToolTipText("Deafen / undeafen (mute speakers) on this server");
|
|
speakerButton.addActionListener(e -> listener.onDeafenToggle(speakerButton.isSelected()));
|
|
|
|
awayButton = new DropDownToggleButton(Icons.away(),
|
|
"Away on this server (the arrow offers the global actions and presets)",
|
|
listener::buildAwayMenu);
|
|
awayButton.addActionListener(e -> listener.onAwayToggle(awayButton.isSelected()));
|
|
|
|
JButton settingsButton = new JButton(Icons.settings());
|
|
settingsButton.setToolTipText("Options");
|
|
settingsButton.addActionListener(e -> listener.onSettings());
|
|
|
|
add(connectButton);
|
|
add(disconnectButton);
|
|
addSeparator();
|
|
add(activeButton);
|
|
add(micButton);
|
|
add(speakerButton);
|
|
add(awayButton);
|
|
addSeparator();
|
|
add(settingsButton);
|
|
add(Box.createHorizontalGlue());
|
|
masterVolumePanel = buildMasterVolumeControl();
|
|
masterVolumePanel.setVisible(settings.showMasterVolumeSlider);
|
|
add(masterVolumePanel);
|
|
hostButton = new HostButtonView(pictures);
|
|
add(hostButton);
|
|
}
|
|
|
|
/** Shows the selected server's host button; {@code null} hides it. */
|
|
void showHostButton(HostButton button) {
|
|
hostButton.show(button);
|
|
}
|
|
|
|
/** Reflects the current tab's state on the buttons. */
|
|
void refresh(boolean connected, boolean anyConnected, boolean micMuted, boolean deafened,
|
|
boolean active, boolean away) {
|
|
disconnectButton.setEnabled(connected);
|
|
micButton.setEnabled(connected);
|
|
speakerButton.setEnabled(connected);
|
|
activeButton.setEnabled(connected);
|
|
// The away menu's actions are global, so the arrow stays live while any server is up.
|
|
awayButton.setEnabled(anyConnected);
|
|
awayButton.setToggleEnabled(connected);
|
|
|
|
micButton.setSelected(micMuted);
|
|
micButton.setIcon(micMuted ? Icons.micMutedLarge() : Icons.mic());
|
|
speakerButton.setSelected(deafened);
|
|
speakerButton.setIcon(deafened ? Icons.speakerMutedLarge() : Icons.speaker());
|
|
activeButton.setSelected(active);
|
|
awayButton.setSelected(away);
|
|
}
|
|
|
|
/** Simulates a click, for the "Toggle microphone" menu item. */
|
|
void clickMic() {
|
|
micButton.doClick();
|
|
}
|
|
|
|
/** Simulates a click, for the "Toggle speakers" menu item. */
|
|
void clickSpeaker() {
|
|
speakerButton.doClick();
|
|
}
|
|
|
|
/** The Master Volume slider: multiplies both voice and notification volume. */
|
|
private JComponent buildMasterVolumeControl() {
|
|
JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, UIScale.scale(4), 0));
|
|
panel.setOpaque(false);
|
|
panel.setAlignmentY(Component.CENTER_ALIGNMENT);
|
|
|
|
JLabel icon = new JLabel(Icons.speaker());
|
|
icon.setToolTipText("Master volume (voice + notifications)");
|
|
icon.setAlignmentY(Component.CENTER_ALIGNMENT);
|
|
|
|
JSlider slider = new JSlider(0, 200, (int) Math.round(settings.masterVolume * 100));
|
|
slider.setOpaque(false);
|
|
slider.setToolTipText("Master volume (voice + notifications) — scroll to adjust");
|
|
slider.setAlignmentY(Component.CENTER_ALIGNMENT);
|
|
slider.setPreferredSize(new Dimension(UIScale.scale(135), slider.getPreferredSize().height));
|
|
slider.addChangeListener(e -> {
|
|
settings.masterVolume = slider.getValue() / 100.0;
|
|
listener.onMasterVolumeChanged();
|
|
// Only touch the disk once the drag (or a wheel step) has settled.
|
|
if (!slider.getValueIsAdjusting()) settings.save();
|
|
});
|
|
slider.addMouseWheelListener(e -> {
|
|
int step = e.getWheelRotation() < 0 ? 5 : -5;
|
|
slider.setValue(slider.getValue() + step);
|
|
e.consume();
|
|
});
|
|
|
|
panel.add(icon);
|
|
panel.add(slider);
|
|
return panel;
|
|
}
|
|
|
|
/** Right-click on the toolbar: toggles for the window's optional chrome. */
|
|
private JPopupMenu buildContextMenu() {
|
|
JPopupMenu menu = new JPopupMenu();
|
|
|
|
JCheckBoxMenuItem statusBarItem = new JCheckBoxMenuItem("Show Status Bar", settings.showStatusBar);
|
|
statusBarItem.addActionListener(e -> {
|
|
settings.showStatusBar = statusBarItem.isSelected();
|
|
settings.save();
|
|
listener.onStatusBarVisibilityChanged(settings.showStatusBar);
|
|
});
|
|
|
|
JCheckBoxMenuItem volumeItem = new JCheckBoxMenuItem("Show Master Volume Slider", settings.showMasterVolumeSlider);
|
|
volumeItem.addActionListener(e -> {
|
|
settings.showMasterVolumeSlider = volumeItem.isSelected();
|
|
settings.save();
|
|
masterVolumePanel.setVisible(settings.showMasterVolumeSlider);
|
|
revalidate();
|
|
repaint();
|
|
});
|
|
|
|
menu.add(statusBarItem);
|
|
menu.add(volumeItem);
|
|
// Either toggle can also be flipped elsewhere (or via settings.properties), so
|
|
// re-sync the ticks each time the menu is about to be shown.
|
|
menu.addPopupMenuListener(new PopupMenuListener() {
|
|
@Override
|
|
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
|
statusBarItem.setSelected(settings.showStatusBar);
|
|
volumeItem.setSelected(settings.showMasterVolumeSlider);
|
|
}
|
|
|
|
@Override
|
|
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void popupMenuCanceled(PopupMenuEvent e) {
|
|
}
|
|
});
|
|
return menu;
|
|
}
|
|
}
|