Files
ts3j/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java
ericek111 acf0221837 Add Set Server/Channel Group context menus and dialog
Lets moderators assign or revoke server and channel groups for a
client directly from its context menu, with per-group checkboxes
gated by the client's actual TS3 permissions (i_group_needed_member_
add/remove_power vs. each group's n_member_addp/removep, including
TS3's -1 "unlimited" sentinel). A "Server Groups Dialog..." entry
shows every server group, graying out ones the client can't touch.

Group membership changes now update ClientEntry locally instead of
only logging them, so the checkboxes and dialog reflect a change
immediately instead of needing a reconnect. The server's default
channel group (e.g. "Guest") is hidden from the channel-group menu
since it's not something you assign directly.
2026-08-18 15:07:38 +00:00

232 lines
8.5 KiB
Java

package com.ts3client.ui;
import com.ts3client.net.ChannelNode;
import com.ts3client.net.ClientEntry;
import com.ts3client.net.Group;
import com.ts3client.net.TeamspeakConnection;
import com.ts3client.text.TsLink;
import javax.swing.JOptionPane;
import java.awt.Component;
/**
* Context-menu and drag/drop actions for one server's tree, plus tracking which
* node is selected so the info panel stays in sync. Also handles client/channel
* links clicked in the chat log, which open the same menus as the tree does.
*
* <p>Constructed before the {@link ServerTreePanel} it drives exists — the tree's
* constructor needs an {@link ServerTreePanel.Actions} up front — so {@link #attach}
* wires the tree back in once it has been built.
*/
final class ServerTabTreeActions implements ServerTreePanel.Actions {
private final MainFrame host;
private final ServerTab tab;
private final TeamspeakConnection conn;
private final ChatPanel chatPanel;
private final InfoPanel infoPanel;
private final GroupIcons groupIcons;
private ServerTreePanel treePanel;
private Object currentSelection;
/** The one open {@link ServerGroupsDialog}, if any, refreshed whenever the model changes. */
private ServerGroupsDialog groupsDialog;
ServerTabTreeActions(MainFrame host, ServerTab tab, TeamspeakConnection conn,
ChatPanel chatPanel, InfoPanel infoPanel, GroupIcons groupIcons) {
this.host = host;
this.tab = tab;
this.conn = conn;
this.chatPanel = chatPanel;
this.infoPanel = infoPanel;
this.groupIcons = groupIcons;
}
void attach(ServerTreePanel treePanel) {
this.treePanel = treePanel;
}
/** Called by {@link ServerTabConnectionEvents} whenever the model changes, to live-refresh an open dialog. */
void refreshGroupsDialog() {
if (groupsDialog != null) groupsDialog.refresh();
}
/** Refreshes the info panel for whatever is currently selected (or clears it). */
void renderInfo() {
Object sel = currentSelection;
if (sel instanceof ChannelNode) {
infoPanel.showChannel((ChannelNode) sel);
} else if (sel instanceof ClientEntry) {
infoPanel.showClient((ClientEntry) sel, conn.getModel(), conn.getIcons());
} else {
infoPanel.clear();
}
}
/** Drops the selection on disconnect, since the model it points into is gone. */
void clearSelection() {
currentSelection = null;
infoPanel.clear();
}
/** A client link in the chat log was clicked: show the same menu as the tree does. */
void handleClientLink(TsLink.Ref ref, Component source, int x, int y) {
ClientEntry client = conn.getModel().getClient(ref.id);
// The id is only valid for the session the link was made in; fall back to
// the unique id (and finally the nickname) so older links still resolve.
if (client == null || (!ref.uniqueId.isEmpty() && !ref.uniqueId.equals(client.uniqueId))) {
ClientEntry byUid = ref.uniqueId.isEmpty() ? null
: conn.getModel().findClientByUniqueId(ref.uniqueId);
if (byUid == null && !ref.name.isEmpty()) byUid = conn.getModel().findClientByName(ref.name);
if (byUid != null) client = byUid;
}
if (client == null) {
chatPanel.appendSystem("That client is no longer on the server.");
return;
}
ClientMenu.build(client, client.id == conn.getSelfClientId(), conn.getModel(), groupIcons, this)
.show(source, x, y);
}
void handleChannelLink(TsLink.Ref ref, Component source, int x, int y) {
ChannelNode channel = conn.getModel().getChannel(ref.id);
if (channel == null) {
chatPanel.appendSystem("That channel no longer exists.");
return;
}
ChannelMenu.build(channel, this).show(source, x, y);
}
// ---- ServerTreePanel.Actions ----
@Override
public void joinChannel(int channelId) {
if (conn.isConnected()) conn.joinChannel(channelId, null);
}
@Override
public void moveClientToChannel(ClientEntry client, ChannelNode target) {
if (!conn.isConnected()) return;
if (client.id == conn.getSelfClientId()) {
conn.joinChannel(target.id, null);
} else {
conn.moveClient(client.id, target.id, null);
}
}
@Override
public void moveChannel(ChannelNode channel, int newParentId, int orderPredecessorId) {
if (conn.isConnected()) conn.moveChannel(channel.id, newParentId, orderPredecessorId);
}
@Override
public void openPrivateChat(ClientEntry client) {
chatPanel.openPrivateChat(client.id, client.nickname);
}
@Override
public void pokeClient(ClientEntry client) {
String msg = JOptionPane.showInputDialog(host, "Poke message for " + client.nickname + ":", "Poke!");
if (msg != null) conn.poke(client.id, msg);
}
@Override
public void kickClientFromChannel(ClientEntry client) {
String reason = kickReason("Kick Client from Channel", client);
if (reason != null) conn.kickFromChannel(client.id, reason);
}
@Override
public void kickClientFromServer(ClientEntry client) {
String reason = kickReason("Kick Client from Server", client);
if (reason != null) conn.kickFromServer(client.id, reason);
}
private String kickReason(String title, ClientEntry client) {
if (!conn.isConnected()) return null;
return ReasonDialog.prompt(host, title, "Reason for kicking " + client.nickname + ":",
ReasonDialog.KICK_REASON_LIMIT);
}
@Override
public void banClient(ClientEntry client) {
if (!conn.isConnected()) return;
BanDialog dialog = new BanDialog(host, client.nickname);
dialog.setVisible(true);
if (dialog.isConfirmed()) conn.banClient(client.id, dialog.getSeconds(), dialog.getReason());
}
@Override
public void toggleClientMute(ClientEntry client) {
if (conn.getPlayback() == null) return;
boolean now = !conn.getPlayback().isClientMuted(client.id);
conn.getPlayback().setClientMuted(client.id, now);
}
@Override
public void findClientInTree(ClientEntry client) {
host.selectTab(tab);
treePanel.selectClient(client.id);
}
@Override
public void showConnectionInfo(ClientEntry client) {
if (!conn.isConnected()) return;
new ConnectionInfoDialog(host, conn, client.id, client.nickname).setVisible(true);
}
@Override
public void moveClientToOwnChannel(ClientEntry client) {
if (!conn.isConnected() || client.id == conn.getSelfClientId()) return;
ClientEntry self = conn.getModel().getClient(conn.getSelfClientId());
if (self != null) conn.moveClient(client.id, self.channelId, null);
}
@Override
public void setClientServerGroup(ClientEntry client, Group group, boolean assign) {
if (conn.isConnected()) conn.setClientServerGroup(client.databaseId, group.id, assign);
}
@Override
public void setClientChannelGroup(ClientEntry client, Group group) {
if (conn.isConnected()) conn.setClientChannelGroup(client.databaseId, client.channelId, group.id);
}
@Override
public void showServerGroupsDialog(ClientEntry client) {
if (!conn.isConnected()) return;
groupsDialog = new ServerGroupsDialog(host, conn, groupIcons, client, this);
groupsDialog.setVisible(true);
groupsDialog = null;
}
@Override
public void setChannelSubscribed(ChannelNode channel, boolean family, boolean subscribed) {
if (conn.isConnected()) conn.setChannelsSubscribed(channel.familyIds(family), subscribed);
}
@Override
public void browseFiles(ChannelNode channel) {
if (!conn.canTransferFiles()) return;
new FileBrowserDialog(host, conn, channel).setVisible(true);
}
@Override
public boolean isClientLocallyMuted(int clientId) {
return conn.getPlayback() != null && conn.getPlayback().isClientMuted(clientId);
}
@Override
public void onSelectionChanged(Object userObject) {
currentSelection = userObject;
renderInfo();
if (!conn.isConnected()) return;
if (userObject instanceof ChannelNode) {
ChannelNode ch = (ChannelNode) userObject;
if (!ch.descriptionLoaded) conn.requestChannelInfo(ch.id);
} else if (userObject instanceof ClientEntry) {
conn.requestClientInfo(((ClientEntry) userObject).id);
}
}
}