Extract the Playback/Capture and Voice Activation tabs (device pickers, gain/volume sliders, noise reduction, VAD/PTT tuning, microphone test) into DevicesPanel and VoiceActivationPanel, alongside the existing NotificationsPanel/HotkeysPanel/IconPackPanel/ClientVersionPanel. A new FormPanel base holds the shared GridBagLayout scaffolding. SettingsDialog is now a thin coordinator (753 -> 187 lines); its public API and all settings read/write behavior are unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
188 lines
7.1 KiB
Java
188 lines
7.1 KiB
Java
package com.ts3client.ui;
|
|
|
|
import com.ts3client.audio.OpusParameters;
|
|
import com.ts3client.audio.VoiceInput;
|
|
import com.ts3client.audio.VoiceOutput;
|
|
import com.ts3client.config.Settings;
|
|
import com.ts3client.sound.SoundNotifier;
|
|
|
|
import javax.swing.JButton;
|
|
import javax.swing.JDialog;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JTabbedPane;
|
|
import java.awt.BorderLayout;
|
|
import java.awt.Dimension;
|
|
import java.awt.Frame;
|
|
import java.util.function.Consumer;
|
|
|
|
/**
|
|
* Options dialog: a tabbed coordinator over the individual settings pages. Each tab owns
|
|
* its own controls and reads/writes {@link Settings} on {@link #apply()}; this class wires
|
|
* the tabs that preview themselves live against the running audio subsystem (the device
|
|
* and voice-activation tabs, which share a single microphone test) and owns the
|
|
* OK/Cancel/Apply plumbing.
|
|
*/
|
|
public final class SettingsDialog extends JDialog {
|
|
|
|
private final Settings settings;
|
|
private final VoiceInput liveMic;
|
|
private final VoiceOutput livePlayback;
|
|
private final Runnable onApply;
|
|
|
|
private final DevicesPanel devicesPanel;
|
|
private final VoiceActivationPanel voiceActivationPanel;
|
|
private final NotificationsPanel notificationsPanel;
|
|
private final IconPackPanel iconPackPanel;
|
|
private final HotkeysPanel hotkeysPanel;
|
|
private final ClientVersionPanel clientVersionPanel;
|
|
|
|
public SettingsDialog(Frame owner, Settings settings,
|
|
VoiceInput liveMic, VoiceOutput livePlayback,
|
|
SoundNotifier sounds, HotkeyService hotkeys, Runnable onApply) {
|
|
super(owner, "Options", true);
|
|
this.settings = settings;
|
|
this.liveMic = liveMic;
|
|
this.livePlayback = livePlayback;
|
|
this.onApply = onApply;
|
|
|
|
notificationsPanel = new NotificationsPanel(settings, sounds);
|
|
iconPackPanel = new IconPackPanel(settings);
|
|
hotkeysPanel = new HotkeysPanel(hotkeys);
|
|
clientVersionPanel = new ClientVersionPanel(settings);
|
|
devicesPanel = new DevicesPanel(settings, livePlayback,
|
|
this::applyLive, this::restartTest, this::setTestOutputDevice);
|
|
voiceActivationPanel = new VoiceActivationPanel(settings, liveMic, hotkeys,
|
|
hotkeysPanel, this::audioSnapshot);
|
|
|
|
JTabbedPane tabs = new JTabbedPane();
|
|
tabs.addTab("Playback / Capture", scrollable(devicesPanel));
|
|
tabs.addTab("Voice Activation", scrollable(voiceActivationPanel));
|
|
tabs.addTab("Notifications", notificationsPanel);
|
|
tabs.addTab("Design", iconPackPanel);
|
|
tabs.addTab("Hotkeys", hotkeysPanel);
|
|
tabs.addTab("Client Version", scrollable(clientVersionPanel));
|
|
|
|
JPanel buttons = new JPanel(new BorderLayout());
|
|
JPanel right = new JPanel();
|
|
JButton ok = new JButton("OK");
|
|
JButton cancel = new JButton("Cancel");
|
|
ok.addActionListener(e -> {
|
|
apply();
|
|
close();
|
|
});
|
|
cancel.addActionListener(e -> cancel());
|
|
right.add(ok);
|
|
right.add(cancel);
|
|
buttons.add(right, BorderLayout.EAST);
|
|
|
|
getContentPane().setLayout(new BorderLayout());
|
|
getContentPane().add(tabs, BorderLayout.CENTER);
|
|
getContentPane().add(buttons, BorderLayout.SOUTH);
|
|
|
|
Dialogs.closeOnEscape(this, this::cancel);
|
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
|
addWindowListener(new java.awt.event.WindowAdapter() {
|
|
@Override
|
|
public void windowClosed(java.awt.event.WindowEvent e) {
|
|
voiceActivationPanel.stopTest();
|
|
}
|
|
});
|
|
|
|
pack();
|
|
setSize(new Dimension(480, 540));
|
|
setMinimumSize(new Dimension(420, 360));
|
|
setLocationRelativeTo(owner);
|
|
}
|
|
|
|
private static javax.swing.JScrollPane scrollable(JPanel content) {
|
|
javax.swing.JScrollPane sp = new javax.swing.JScrollPane(content,
|
|
javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
|
|
javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
|
sp.setBorder(null);
|
|
sp.getVerticalScrollBar().setUnitIncrement(16);
|
|
return sp;
|
|
}
|
|
|
|
/** Copies the audio tabs into {@code target}, without touching anything else. */
|
|
private void writeAudioSettings(Settings target) {
|
|
devicesPanel.writeInto(target);
|
|
voiceActivationPanel.writeInto(target);
|
|
}
|
|
|
|
/**
|
|
* The audio tabs as a standalone {@link Settings}, so the microphone test chain runs
|
|
* with the values currently on screen rather than the ones last saved.
|
|
*
|
|
* <p>The test always runs voice activation: it exists to tune the gate, and push-to-talk
|
|
* would need the global hotkey, which belongs to the connected microphone.
|
|
*/
|
|
private Settings audioSnapshot() {
|
|
Settings snapshot = new Settings();
|
|
writeAudioSettings(snapshot);
|
|
if (snapshot.inputMode == Settings.InputMode.PUSH_TO_TALK) {
|
|
snapshot.inputMode = Settings.InputMode.VOICE_ACTIVATION;
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
private void apply() {
|
|
writeAudioSettings(settings);
|
|
notificationsPanel.apply();
|
|
iconPackPanel.apply();
|
|
clientVersionPanel.apply();
|
|
hotkeysPanel.apply();
|
|
settings.save();
|
|
|
|
applyLive(m -> {
|
|
m.setMode(settings.inputMode);
|
|
m.setVadMode(settings.vadMode);
|
|
m.setThresholdDb(settings.vadThresholdDb);
|
|
m.setSpeechThreshold(settings.speechThreshold);
|
|
m.setVadOverPtt(settings.vadOverPtt);
|
|
m.setInputGain(settings.inputVolume);
|
|
m.setNoiseSuppression(settings.denoise);
|
|
m.setDenoiserLevel(settings.denoiserLevel);
|
|
m.setTypingAttenuation(settings.typingAttenuation);
|
|
m.setAgc(settings.agc);
|
|
m.setOpusParameters(OpusParameters.from(settings));
|
|
});
|
|
if (livePlayback != null) {
|
|
livePlayback.setMasterVolume(settings.outputVolume);
|
|
livePlayback.setOutputDevice(settings.outputDevice);
|
|
}
|
|
if (onApply != null) onApply.run();
|
|
}
|
|
|
|
/** Leaves without applying, putting back the settings that preview themselves live. */
|
|
private void cancel() {
|
|
notificationsPanel.revert();
|
|
iconPackPanel.revert();
|
|
close();
|
|
}
|
|
|
|
private void close() {
|
|
voiceActivationPanel.stopTest();
|
|
dispose();
|
|
}
|
|
|
|
/**
|
|
* Applies a live change to the connected microphone and to the microphone test alike.
|
|
*
|
|
* <p>Guarded against a null {@code voiceActivationPanel}: the Devices tab applies its
|
|
* controls once as they are built, which happens before that tab exists.
|
|
*/
|
|
private void applyLive(Consumer<VoiceInput> change) {
|
|
if (liveMic != null) change.accept(liveMic);
|
|
if (voiceActivationPanel != null) voiceActivationPanel.configureTest(change);
|
|
}
|
|
|
|
/** Restarts the microphone test, if running, so a device change takes effect. */
|
|
private void restartTest() {
|
|
if (voiceActivationPanel != null) voiceActivationPanel.restartTest();
|
|
}
|
|
|
|
private void setTestOutputDevice(String deviceId) {
|
|
if (voiceActivationPanel != null) voiceActivationPanel.setTestOutputDevice(deviceId);
|
|
}
|
|
}
|