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.
232 lines
8.5 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|