The Swing client menu decided on its own whether a group can be (un)assigned and whether a client's channel can be joined, and the server groups dialog borrowed the menu's check. These are rules about the server, not about menus, so ServerModel answers them now and any frontend asks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
3.9 KiB
Java
112 lines
3.9 KiB
Java
package com.ts3client.ui;
|
|
|
|
import com.formdev.flatlaf.util.ScaledEmptyBorder;
|
|
import com.formdev.flatlaf.util.UIScale;
|
|
import com.ts3client.net.ClientEntry;
|
|
import com.ts3client.net.Group;
|
|
import com.ts3client.net.TeamspeakConnection;
|
|
|
|
import javax.swing.BoxLayout;
|
|
import javax.swing.GrayFilter;
|
|
import javax.swing.ImageIcon;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JCheckBox;
|
|
import javax.swing.JDialog;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.SwingConstants;
|
|
import java.awt.BorderLayout;
|
|
import java.awt.Dimension;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* Every regular server group on the server, as a scrollable checkbox list. Groups
|
|
* the local client has no power to (un)assign are shown too, so the client's full
|
|
* membership is visible, but greyed out and disabled.
|
|
*/
|
|
final class ServerGroupsDialog extends JDialog {
|
|
|
|
private final TeamspeakConnection conn;
|
|
private final GroupIcons groupIcons;
|
|
private final int clientId;
|
|
private final ServerTreePanel.Actions actions;
|
|
|
|
private final JPanel list = new JPanel();
|
|
private final JLabel header = new JLabel();
|
|
/** Cached grayscale icons, keyed by the source icon's identity. */
|
|
private final Map<ImageIcon, ImageIcon> grayscale = new HashMap<>();
|
|
|
|
ServerGroupsDialog(MainFrame owner, TeamspeakConnection conn, GroupIcons groupIcons,
|
|
ClientEntry client, ServerTreePanel.Actions actions) {
|
|
super(owner, "Server Groups", true);
|
|
this.conn = conn;
|
|
this.groupIcons = groupIcons;
|
|
this.clientId = client.id;
|
|
this.actions = actions;
|
|
|
|
list.setLayout(new BoxLayout(list, BoxLayout.Y_AXIS));
|
|
JScrollPane scroll = new JScrollPane(list);
|
|
scroll.setPreferredSize(UIScale.scale(new Dimension(280, 360)));
|
|
scroll.getVerticalScrollBar().setUnitIncrement(16);
|
|
|
|
header.setBorder(new ScaledEmptyBorder(8, 10, 4, 10));
|
|
header.setHorizontalAlignment(SwingConstants.LEFT);
|
|
|
|
JButton close = new JButton("Close");
|
|
close.addActionListener(a -> dispose());
|
|
JPanel buttons = new JPanel();
|
|
buttons.add(close);
|
|
getRootPane().setDefaultButton(close);
|
|
|
|
getContentPane().setLayout(new BorderLayout());
|
|
getContentPane().add(header, BorderLayout.NORTH);
|
|
getContentPane().add(scroll, BorderLayout.CENTER);
|
|
getContentPane().add(buttons, BorderLayout.SOUTH);
|
|
|
|
Dialogs.closeOnEscape(this);
|
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
|
refresh();
|
|
pack();
|
|
setLocationRelativeTo(owner);
|
|
}
|
|
|
|
/** Rebuilds the list from the current model state; closes the dialog if the client left. */
|
|
void refresh() {
|
|
ClientEntry client = conn.getModel().getClient(clientId);
|
|
if (client == null) {
|
|
dispose();
|
|
return;
|
|
}
|
|
header.setText(client.nickname);
|
|
|
|
List<Group> groups = conn.getModel().allServerGroups();
|
|
|
|
list.removeAll();
|
|
for (Group g : groups) {
|
|
boolean assigned = client.inServerGroup(g.id);
|
|
boolean allowed = conn.getModel().canToggleGroup(g, assigned);
|
|
|
|
ImageIcon icon = groupIcons.iconOf(g);
|
|
JCheckBox box = new JCheckBox(g.name, allowed ? icon : grayscale(icon));
|
|
box.setSelected(assigned);
|
|
box.setEnabled(allowed);
|
|
box.setAlignmentX(0f);
|
|
if (allowed) {
|
|
box.addActionListener(a -> actions.setClientServerGroup(client, g, box.isSelected()));
|
|
}
|
|
list.add(box);
|
|
}
|
|
list.revalidate();
|
|
list.repaint();
|
|
}
|
|
|
|
private ImageIcon grayscale(ImageIcon icon) {
|
|
if (icon == null) return null;
|
|
return grayscale.computeIfAbsent(icon,
|
|
i -> new ImageIcon(GrayFilter.createDisabledImage(i.getImage())));
|
|
}
|
|
}
|