package com.ts3client.ui;
import com.ts3client.config.ClientVersion;
import com.ts3client.config.Settings;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
/**
* Options page for the version and operating system reported to the server, which
* is what other clients see next to the nickname ("3.6.2 on Windows").
*
*
Servers verify TeamSpeak's signature over the version and platform and refuse
* the connection ("client is modified") when it does not match, so the page is
* built around the signed versions in {@link ClientVersion#PRESETS}. The fields
* behind them can still be edited by hand for a version whose signature is known
* from somewhere else. Changes take effect on the next connect.
*/
final class ClientVersionPanel extends JPanel {
/** Combo entry that leaves the three fields to the user. */
private static final String CUSTOM = "Custom…";
private final Settings settings;
private final JCheckBox enabled = new JCheckBox("Report a custom version and operating system");
private final JComboBox presetCombo = new JComboBox<>();
private final JTextField versionField = new JTextField(24);
private final JComboBox platformCombo = new JComboBox<>();
private final JTextField signField = new JTextField(24);
/** Set while a preset is being copied into the fields, so that does not count as an edit. */
private boolean filling;
ClientVersionPanel(Settings settings) {
super(new BorderLayout(0, 8));
this.settings = settings;
setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
add(buildForm(), BorderLayout.NORTH);
load();
enabled.addActionListener(e -> updateEnabled());
updateEnabled();
}
/** Copies the edited version into the settings; the caller saves them. */
void apply() {
settings.customVersion = enabled.isSelected();
settings.clientVersion = versionField.getText().trim();
settings.clientPlatform = String.valueOf(platformCombo.getEditor().getItem()).trim();
settings.clientVersionSign = signField.getText().trim();
}
private JComponent buildForm() {
JPanel p = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(2, 2, 2, 2);
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
presetCombo.addItem(CUSTOM);
for (ClientVersion preset : ClientVersion.PRESETS) presetCombo.addItem(preset);
presetCombo.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList> list, Object value, int index,
boolean selected, boolean focus) {
super.getListCellRendererComponent(list, value, index, selected, focus);
setText(value instanceof ClientVersion v ? v.label() : String.valueOf(value));
return this;
}
});
presetCombo.setToolTipText("Versions with a signature TeamSpeak issued for them");
presetCombo.addActionListener(e -> onPresetSelected());
platformCombo.setEditable(true);
for (String platform : ClientVersion.PLATFORMS) platformCombo.addItem(platform);
versionField.setToolTipText("Version string, e.g. 3.6.2 [Build: 1695203293]");
platformCombo.setToolTipText("Operating system shown to other clients");
signField.setToolTipText("TeamSpeak's signature for this version and platform");
versionField.getDocument().addDocumentListener(new EditListener());
signField.getDocument().addDocumentListener(new EditListener());
platformCombo.addActionListener(e -> onFieldEdited());
int row = 0;
c.gridx = 0;
c.gridy = row++;
c.gridwidth = 2;
p.add(enabled, c);
c.gridwidth = 1;
addRow(p, c, row++, new JLabel("Version:"), presetCombo);
addRow(p, c, row++, new JLabel("Version string:"), versionField);
addRow(p, c, row++, new JLabel("Operating system:"), platformCombo);
addRow(p, c, row++, new JLabel("Signature:"), signField);
c.gridx = 0;
c.gridy = row;
c.gridwidth = 2;
p.add(new JLabel("The three fields belong together: the "
+ "signature is TeamSpeak's, over that version and operating system. A server "
+ "answers \"client is modified\" and refuses the connection when it does not "
+ "accept the version — made up, or older than the server's minimum — so prefer "
+ "a recent one from the list. Takes effect the next time you connect."
+ ""), c);
return p;
}
private void load() {
enabled.setSelected(settings.customVersion);
ClientVersion preset = ClientVersion.match(
settings.clientVersion, settings.clientPlatform, settings.clientVersionSign);
fill(preset != null ? preset
: new ClientVersion("", settings.clientVersion, settings.clientPlatform, settings.clientVersionSign));
presetCombo.setSelectedItem(preset != null ? preset : CUSTOM);
}
private void onPresetSelected() {
if (presetCombo.getSelectedItem() instanceof ClientVersion preset) fill(preset);
}
/** A hand-edited field no longer describes the selected preset. */
private void onFieldEdited() {
if (filling) return;
ClientVersion preset = ClientVersion.match(versionField.getText().trim(),
String.valueOf(platformCombo.getEditor().getItem()).trim(), signField.getText().trim());
presetCombo.setSelectedItem(preset != null ? preset : CUSTOM);
}
private void fill(ClientVersion version) {
filling = true;
try {
versionField.setText(version.version());
platformCombo.setSelectedItem(version.platform());
platformCombo.getEditor().setItem(version.platform());
signField.setText(version.sign());
} finally {
filling = false;
}
}
private void updateEnabled() {
boolean on = enabled.isSelected();
presetCombo.setEnabled(on);
versionField.setEnabled(on);
platformCombo.setEnabled(on);
signField.setEnabled(on);
}
/** Notices any typing in the version or signature field. */
private final class EditListener implements javax.swing.event.DocumentListener {
@Override
public void insertUpdate(javax.swing.event.DocumentEvent e) {
onFieldEdited();
}
@Override
public void removeUpdate(javax.swing.event.DocumentEvent e) {
onFieldEdited();
}
@Override
public void changedUpdate(javax.swing.event.DocumentEvent e) {
onFieldEdited();
}
}
private static void addRow(JPanel p, GridBagConstraints c, int row, JLabel label, Component field) {
c.gridx = 0;
c.gridy = row;
c.weightx = 0;
p.add(label, c);
c.gridx = 1;
c.weightx = 1;
p.add(field, c);
}
}