The counts sat on a line of their own below the choices they belong to, which also made the Advanced tab the tallest of the four and so decided how much of the dialog the tab strip took. Each box now keeps its natural height, and the free space collects below them. Advanced went from 250px to 220px of preferred height, leaving the tab strip about a third of the dialog rather than half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
201 lines
8.1 KiB
Java
201 lines
8.1 KiB
Java
package com.ts3client.ui;
|
|
|
|
import com.ts3client.net.ChannelSettings;
|
|
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.Box;
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.ButtonGroup;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JCheckBox;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JRadioButton;
|
|
import javax.swing.JSpinner;
|
|
import javax.swing.JTextField;
|
|
import javax.swing.SpinnerNumberModel;
|
|
import java.awt.Dimension;
|
|
import java.awt.FlowLayout;
|
|
import java.awt.GridBagConstraints;
|
|
import java.awt.GridBagLayout;
|
|
import java.awt.Insets;
|
|
|
|
/**
|
|
* The channel editor's "Advanced" tab: the phonetic name, the delete delay of a temporary
|
|
* channel, voice encryption, and the two client limits.
|
|
*/
|
|
final class ChannelAdvancedPanel extends JPanel implements ChannelDialog.Tab {
|
|
|
|
/** TeamSpeak's ceiling for {@code channel_delete_delay}, in seconds. */
|
|
private static final int MAX_DELETE_DELAY = 604800;
|
|
|
|
private final JTextField phoneticName = new JTextField();
|
|
private final JSpinner deleteDelay =
|
|
Spinners.compact(new JSpinner(new SpinnerNumberModel(0, 0, MAX_DELETE_DELAY, 1)));
|
|
private final JButton deleteDelayMax = new JButton("max");
|
|
private final JCheckBox encrypted = new JCheckBox("Voice Data encrypted");
|
|
|
|
private final JRadioButton maxUsersUnlimited = new JRadioButton("Unlimited");
|
|
private final JRadioButton maxUsersLimited = new JRadioButton("Limited");
|
|
private final JSpinner maxUsers = Spinners.compact(new JSpinner(new SpinnerNumberModel(16, 0, 65535, 1)));
|
|
|
|
private final JRadioButton familyInherited = new JRadioButton("Inherited");
|
|
private final JRadioButton familyUnlimited = new JRadioButton("Unlimited");
|
|
private final JRadioButton familyLimited = new JRadioButton("Limited");
|
|
private final JSpinner familyMaxUsers =
|
|
Spinners.compact(new JSpinner(new SpinnerNumberModel(16, 0, 65535, 1)));
|
|
|
|
ChannelAdvancedPanel() {
|
|
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
|
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
|
|
|
phoneticName.setToolTipText("Set phonetic nickname used for this channel by text-to-speech.");
|
|
deleteDelay.setToolTipText("<html><p>Delay in seconds after temporary channels are deleted"
|
|
+ " after the last user left the channel.</p></html>");
|
|
deleteDelayMax.setToolTipText("Set delete delay to maximum value");
|
|
deleteDelayMax.addActionListener(a -> deleteDelay.setValue(MAX_DELETE_DELAY));
|
|
encrypted.setToolTipText("Voice data in this channel will be encrypted.");
|
|
|
|
ButtonGroup users = new ButtonGroup();
|
|
users.add(maxUsersUnlimited);
|
|
users.add(maxUsersLimited);
|
|
maxUsersUnlimited.setToolTipText("Unlimited number of clients can join this channel.");
|
|
maxUsersLimited.setToolTipText("Limit number of clients in this channel.");
|
|
maxUsersUnlimited.addActionListener(a -> syncLimits());
|
|
maxUsersLimited.addActionListener(a -> syncLimits());
|
|
|
|
ButtonGroup family = new ButtonGroup();
|
|
family.add(familyInherited);
|
|
family.add(familyUnlimited);
|
|
family.add(familyLimited);
|
|
familyInherited.setToolTipText("Inherit the client limit by parent channel.");
|
|
familyUnlimited.setToolTipText("The channel subtree can be joined by unlimited number of clients.");
|
|
familyLimited.setToolTipText("Limit the number of clients in the channel subtree.");
|
|
for (JRadioButton radio : new JRadioButton[]{familyInherited, familyUnlimited, familyLimited}) {
|
|
radio.addActionListener(a -> syncLimits());
|
|
}
|
|
|
|
JPanel other = otherSettings();
|
|
other.setAlignmentX(0f);
|
|
fixHeight(other);
|
|
add(other);
|
|
add(Box.createVerticalStrut(6));
|
|
|
|
JPanel limits = new JPanel();
|
|
limits.setLayout(new BoxLayout(limits, BoxLayout.X_AXIS));
|
|
limits.add(limitBox("Max Users", new JRadioButton[]{maxUsersUnlimited, maxUsersLimited}, maxUsers));
|
|
limits.add(Box.createHorizontalStrut(8));
|
|
limits.add(limitBox("Family Max Users",
|
|
new JRadioButton[]{familyInherited, familyUnlimited, familyLimited}, familyMaxUsers));
|
|
limits.setAlignmentX(0f);
|
|
fixHeight(limits);
|
|
add(limits);
|
|
add(Box.createVerticalGlue());
|
|
}
|
|
|
|
/** The delete delay only means anything for a channel that disappears on its own. */
|
|
void setChannelType(ChannelSettings.Type type) {
|
|
boolean temporary = type == ChannelSettings.Type.TEMPORARY;
|
|
deleteDelay.setEnabled(temporary);
|
|
deleteDelayMax.setEnabled(temporary);
|
|
}
|
|
|
|
@Override
|
|
public void read(ChannelSettings settings) {
|
|
phoneticName.setText(settings.phoneticName);
|
|
deleteDelay.setValue(Math.min(MAX_DELETE_DELAY, Math.max(0, settings.deleteDelay)));
|
|
encrypted.setSelected(settings.encrypted);
|
|
|
|
if (settings.maxClientsUnlimited) maxUsersUnlimited.setSelected(true);
|
|
else maxUsersLimited.setSelected(true);
|
|
maxUsers.setValue(settings.maxClients);
|
|
|
|
if (settings.familyInherited) familyInherited.setSelected(true);
|
|
else if (settings.familyUnlimited) familyUnlimited.setSelected(true);
|
|
else familyLimited.setSelected(true);
|
|
familyMaxUsers.setValue(settings.maxFamilyClients);
|
|
|
|
setChannelType(settings.type);
|
|
syncLimits();
|
|
}
|
|
|
|
@Override
|
|
public void write(ChannelSettings settings) {
|
|
settings.phoneticName = phoneticName.getText();
|
|
settings.deleteDelay = (Integer) deleteDelay.getValue();
|
|
settings.encrypted = encrypted.isSelected();
|
|
settings.maxClientsUnlimited = maxUsersUnlimited.isSelected();
|
|
settings.maxClients = (Integer) maxUsers.getValue();
|
|
settings.familyInherited = familyInherited.isSelected();
|
|
settings.familyUnlimited = familyUnlimited.isSelected();
|
|
settings.maxFamilyClients = (Integer) familyMaxUsers.getValue();
|
|
}
|
|
|
|
private void syncLimits() {
|
|
maxUsers.setEnabled(maxUsersLimited.isSelected());
|
|
familyMaxUsers.setEnabled(familyLimited.isSelected());
|
|
}
|
|
|
|
private JPanel otherSettings() {
|
|
JPanel panel = new JPanel(new GridBagLayout());
|
|
panel.setBorder(BorderFactory.createTitledBorder("Other Settings"));
|
|
GridBagConstraints c = new GridBagConstraints();
|
|
c.insets = new Insets(3, 4, 3, 4);
|
|
c.anchor = GridBagConstraints.WEST;
|
|
c.fill = GridBagConstraints.HORIZONTAL;
|
|
|
|
c.gridx = 0;
|
|
c.gridy = 0;
|
|
panel.add(new JLabel("Phonetic Name:"), c);
|
|
c.gridx = 1;
|
|
c.weightx = 1;
|
|
panel.add(phoneticName, c);
|
|
|
|
c.gridx = 0;
|
|
c.gridy = 1;
|
|
c.weightx = 0;
|
|
panel.add(new JLabel("Delete delay:"), c);
|
|
JPanel delay = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
|
|
delay.add(deleteDelay);
|
|
delay.add(deleteDelayMax);
|
|
c.gridx = 1;
|
|
c.weightx = 1;
|
|
panel.add(delay, c);
|
|
|
|
c.gridx = 1;
|
|
c.gridy = 2;
|
|
panel.add(encrypted, c);
|
|
return panel;
|
|
}
|
|
|
|
/**
|
|
* A limit box: its choices stacked, with the count beside the last of them —
|
|
* "Limited", the only choice it belongs to.
|
|
*/
|
|
private JPanel limitBox(String title, JRadioButton[] choices, JSpinner spinner) {
|
|
JPanel box = new JPanel();
|
|
box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS));
|
|
box.setBorder(BorderFactory.createTitledBorder(title));
|
|
for (int i = 0; i < choices.length; i++) {
|
|
JRadioButton radio = choices[i];
|
|
radio.setAlignmentX(0f);
|
|
if (i < choices.length - 1) {
|
|
box.add(radio);
|
|
continue;
|
|
}
|
|
JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
|
row.setAlignmentX(0f);
|
|
row.add(radio);
|
|
row.add(spinner);
|
|
box.add(row);
|
|
}
|
|
box.add(Box.createVerticalGlue());
|
|
return box;
|
|
}
|
|
|
|
/** Keeps a section at its natural height, so the free space collects at the bottom. */
|
|
private static void fixHeight(JPanel panel) {
|
|
panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, panel.getPreferredSize().height));
|
|
}
|
|
}
|