Answer what the local client may do from the model
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>
This commit is contained in:
@@ -67,7 +67,7 @@ final class ClientMenu {
|
||||
menu.addSeparator();
|
||||
if (!fromTree) {
|
||||
JMenuItem findInTree = new JMenuItem("Find Client in Channel Tree", Icons.of("CHANNEL_SWITCH"));
|
||||
findInTree.setEnabled(isVisible(client, model));
|
||||
findInTree.setEnabled(model.isChannelVisible(client.channelId));
|
||||
findInTree.addActionListener(a -> actions.findClientInTree(client));
|
||||
menu.add(findInTree);
|
||||
}
|
||||
@@ -80,7 +80,7 @@ final class ClientMenu {
|
||||
if (!self) {
|
||||
menu.addSeparator();
|
||||
JMenuItem joinChannel = new JMenuItem("Join Channel of Client", Icons.of("CHANNEL_SWITCH"));
|
||||
joinChannel.setEnabled(canJoinChannelOf(client, model));
|
||||
joinChannel.setEnabled(model.canJoinChannel(client.channelId));
|
||||
joinChannel.addActionListener(a -> actions.joinChannel(client.channelId));
|
||||
menu.add(joinChannel);
|
||||
JMenuItem moveHere = new JMenuItem("Move Client to own Channel", Icons.of("MOVE_CLIENT_TO_OWN_CHANNEL"));
|
||||
@@ -114,28 +114,6 @@ final class ClientMenu {
|
||||
menu.add(remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the client's channel is actually rendered in the tree — it won't be if
|
||||
* we're not subscribed to it (e.g. it needs more subscribe power than we have).
|
||||
*/
|
||||
private static boolean isVisible(ClientEntry client, ServerModel model) {
|
||||
ChannelNode channel = model.getChannel(client.channelId);
|
||||
return channel != null && channel.subscribed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we can expect a join to succeed: the channel must be visible to us, and
|
||||
* not already full. This can't account for a join-power permission requirement —
|
||||
* unlike group membership, TS3 doesn't expose a channel's join-power threshold as
|
||||
* a plain property, only as a permission resolved through channel/channel-group/
|
||||
* server-group inheritance, which isn't something a regular client can query.
|
||||
*/
|
||||
private static boolean canJoinChannelOf(ClientEntry client, ServerModel model) {
|
||||
ChannelNode channel = model.getChannel(client.channelId);
|
||||
if (channel == null || !channel.subscribed) return false;
|
||||
return channel.maxClients < 0 || channel.clients.size() < channel.maxClients;
|
||||
}
|
||||
|
||||
private static JMenu buildServerGroupMenu(ClientEntry client, ServerModel model, GroupIcons groupIcons,
|
||||
ServerTreePanel.Actions actions) {
|
||||
JMenu menu = new JMenu("Set Server Groups");
|
||||
@@ -145,11 +123,9 @@ final class ClientMenu {
|
||||
menu.add(dialog);
|
||||
menu.addSeparator();
|
||||
|
||||
int addPower = model.selfPermissionValue("i_group_needed_member_add_power");
|
||||
int removePower = model.selfPermissionValue("i_group_needed_member_remove_power");
|
||||
for (Group g : model.allServerGroups()) {
|
||||
boolean assigned = contains(client.serverGroupIds, g.id);
|
||||
if (!canAssign(g, assigned, addPower, removePower)) continue;
|
||||
boolean assigned = client.inServerGroup(g.id);
|
||||
if (!model.canToggleGroup(g, assigned)) continue;
|
||||
JCheckBoxMenuItem item = new JCheckBoxMenuItem(g.name, groupIcons.iconOf(g));
|
||||
item.setSelected(assigned);
|
||||
item.addActionListener(a -> actions.setClientServerGroup(client, g, !assigned));
|
||||
@@ -162,13 +138,11 @@ final class ClientMenu {
|
||||
ServerTreePanel.Actions actions) {
|
||||
JMenu menu = new JMenu("Set Channel Group");
|
||||
menu.setIcon(Icons.of("PERMISSIONS_CHANNEL_GROUPS"));
|
||||
int addPower = model.selfPermissionValue("i_group_needed_member_add_power");
|
||||
int removePower = model.selfPermissionValue("i_group_needed_member_remove_power");
|
||||
int defaultGroupId = model.defaultChannelGroupId();
|
||||
for (Group g : model.allChannelGroups()) {
|
||||
if (g.id == defaultGroupId) continue;
|
||||
boolean assigned = client.channelGroupId == g.id;
|
||||
if (!canAssign(g, assigned, addPower, removePower)) continue;
|
||||
if (!model.canToggleGroup(g, assigned)) continue;
|
||||
JCheckBoxMenuItem item = new JCheckBoxMenuItem(g.name, groupIcons.iconOf(g));
|
||||
item.setSelected(assigned);
|
||||
item.addActionListener(a -> actions.setClientChannelGroup(client, g));
|
||||
@@ -176,27 +150,4 @@ final class ClientMenu {
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the local client has enough power to (un)assign this group: the add
|
||||
* power always gates the checkbox, and the remove power additionally gates
|
||||
* unassigning an already-held group. {@code -1} is TS3's "unlimited" sentinel on
|
||||
* either side: an unlimited local power always passes, and a group that needs
|
||||
* unlimited power can only be touched by a local client that has it.
|
||||
*/
|
||||
static boolean canAssign(Group g, boolean assigned, int addPower, int removePower) {
|
||||
return hasPower(g.neededMemberAddPower, addPower)
|
||||
&& (!assigned || hasPower(g.neededMemberRemovePower, removePower));
|
||||
}
|
||||
|
||||
private static boolean hasPower(int needed, int own) {
|
||||
if (own == -1) return true;
|
||||
if (needed == -1) return false;
|
||||
return own >= needed;
|
||||
}
|
||||
|
||||
private static boolean contains(int[] ids, int id) {
|
||||
for (int i : ids) if (i == id) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,14 +82,12 @@ final class ServerGroupsDialog extends JDialog {
|
||||
}
|
||||
header.setText(client.nickname);
|
||||
|
||||
int addPower = conn.getModel().selfPermissionValue("i_group_needed_member_add_power");
|
||||
int removePower = conn.getModel().selfPermissionValue("i_group_needed_member_remove_power");
|
||||
List<Group> groups = conn.getModel().allServerGroups();
|
||||
|
||||
list.removeAll();
|
||||
for (Group g : groups) {
|
||||
boolean assigned = contains(client.serverGroupIds, g.id);
|
||||
boolean allowed = ClientMenu.canAssign(g, assigned, addPower, removePower);
|
||||
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));
|
||||
@@ -110,9 +108,4 @@ final class ServerGroupsDialog extends JDialog {
|
||||
return grayscale.computeIfAbsent(icon,
|
||||
i -> new ImageIcon(GrayFilter.createDisabledImage(i.getImage())));
|
||||
}
|
||||
|
||||
private static boolean contains(int[] ids, int id) {
|
||||
for (int i : ids) if (i == id) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user