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); } }