With b_client_avatar_delete_other, the client's context menu and the menu on their avatar offer "Delete Avatar", as in TeamSpeak 3. The server takes the deletion as ftdeletefile on /avatar_<unique id> (not the stored file's name, which it refuses with "convert error") and clears the client's avatar flag for everyone. Our own avatar's file is now deleted the same way, which only such clients are allowed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
7.6 KiB
Java
159 lines
7.6 KiB
Java
package com.ts3client.ui;
|
|
|
|
import com.ts3client.contacts.ContactType;
|
|
import com.ts3client.net.ChannelNode;
|
|
import com.ts3client.net.ClientEntry;
|
|
import com.ts3client.net.Group;
|
|
import com.ts3client.net.ServerModel;
|
|
|
|
import javax.swing.JCheckBoxMenuItem;
|
|
import javax.swing.JMenu;
|
|
import javax.swing.JMenuItem;
|
|
import javax.swing.JPopupMenu;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* The context menu for a client, shared by the server tree and the clickable
|
|
* names in the chat log.
|
|
*/
|
|
final class ClientMenu {
|
|
|
|
private ClientMenu() {
|
|
}
|
|
|
|
/**
|
|
* @param fromTree whether this menu was opened from the server tree itself, as
|
|
* opposed to a client link elsewhere (chat log, etc.) — "Find
|
|
* Client in Channel Tree" is redundant in the former case.
|
|
*/
|
|
static JPopupMenu build(ClientEntry client, boolean self, boolean fromTree, ServerModel model,
|
|
GroupIcons groupIcons, ServerTreePanel.Actions actions) {
|
|
JPopupMenu menu = new JPopupMenu();
|
|
if (!self) {
|
|
JMenuItem pm = new JMenuItem("Open text chat", Icons.of("PLAYER_CHAT"));
|
|
pm.addActionListener(a -> actions.openPrivateChat(client));
|
|
menu.add(pm);
|
|
JMenuItem poke = new JMenuItem("Poke", Icons.of("POKE"));
|
|
poke.addActionListener(a -> actions.pokeClient(client));
|
|
menu.add(poke);
|
|
menu.addSeparator();
|
|
JMenuItem kickChannel = new JMenuItem("Kick Client from Channel", Icons.of("KICK_FROM_CHANNEL"));
|
|
kickChannel.addActionListener(a -> actions.kickClientFromChannel(client));
|
|
menu.add(kickChannel);
|
|
JMenuItem kickServer = new JMenuItem("Kick Client from Server", Icons.of("KICK_FROM_SERVER"));
|
|
kickServer.addActionListener(a -> actions.kickClientFromServer(client));
|
|
menu.add(kickServer);
|
|
JMenuItem ban = new JMenuItem("Ban Client", Icons.of("BAN_CLIENT"));
|
|
ban.addActionListener(a -> actions.banClient(client));
|
|
menu.add(ban);
|
|
boolean muted = actions.isClientLocallyMuted(client.id);
|
|
JMenuItem mute = new JMenuItem(muted ? "Unmute client" : "Mute client",
|
|
Icons.of(muted ? "PLAYER_ON" : "INPUT_MUTED"));
|
|
mute.addActionListener(a -> actions.toggleClientMute(client));
|
|
menu.add(mute);
|
|
JMenuItem volume = new JMenuItem("Change Volume…", Icons.of("VOLUME"));
|
|
volume.addActionListener(a -> actions.changeClientVolume(client));
|
|
menu.add(volume);
|
|
menu.addSeparator();
|
|
addContactItems(menu, client, actions);
|
|
} else {
|
|
JMenuItem me = new JMenuItem("This is you", Icons.of("PLAYER_OFF"));
|
|
me.setEnabled(false);
|
|
menu.add(me);
|
|
}
|
|
menu.addSeparator();
|
|
menu.add(buildServerGroupMenu(client, model, groupIcons, actions));
|
|
menu.add(buildChannelGroupMenu(client, model, groupIcons, actions));
|
|
menu.addSeparator();
|
|
if (!fromTree) {
|
|
JMenuItem findInTree = new JMenuItem("Find Client in Channel Tree", Icons.of("CHANNEL_SWITCH"));
|
|
findInTree.setEnabled(model.isChannelVisible(client.channelId));
|
|
findInTree.addActionListener(a -> actions.findClientInTree(client));
|
|
menu.add(findInTree);
|
|
}
|
|
JMenuItem description = new JMenuItem("Change Description", Icons.of("EDIT"));
|
|
description.addActionListener(a -> actions.changeClientDescription(client));
|
|
menu.add(description);
|
|
if (!self && !client.avatarFlag.isEmpty() && actions.canDeleteOtherAvatars()) {
|
|
JMenuItem avatar = new JMenuItem("Delete Avatar", Icons.of("DELETE_AVATAR_OTHER"));
|
|
avatar.addActionListener(a -> actions.deleteClientAvatar(client));
|
|
menu.add(avatar);
|
|
}
|
|
JMenuItem info = new JMenuItem("Connection Info", Icons.of("INFO"));
|
|
info.addActionListener(a -> actions.showConnectionInfo(client));
|
|
menu.add(info);
|
|
if (!self) {
|
|
menu.addSeparator();
|
|
JMenuItem joinChannel = new JMenuItem("Join Channel of Client", Icons.of("CHANNEL_SWITCH"));
|
|
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"));
|
|
moveHere.addActionListener(a -> actions.moveClientToOwnChannel(client));
|
|
menu.add(moveHere);
|
|
}
|
|
return menu;
|
|
}
|
|
|
|
/**
|
|
* The contact entries, as TeamSpeak offers them: a client not on the list can be
|
|
* added as a friend or blocked; one on it can be edited or removed.
|
|
*/
|
|
private static void addContactItems(JPopupMenu menu, ClientEntry client, ServerTreePanel.Actions actions) {
|
|
ContactType type = actions.contactTypeOf(client);
|
|
if (type == null) {
|
|
JMenuItem friend = new JMenuItem("Add as Friend", Icons.of("ADD_FRIEND"));
|
|
friend.addActionListener(a -> actions.addContact(client, ContactType.FRIEND));
|
|
menu.add(friend);
|
|
JMenuItem blocked = new JMenuItem("Add as Blocked", Icons.of("ADD_FOE"));
|
|
blocked.addActionListener(a -> actions.addContact(client, ContactType.BLOCKED));
|
|
menu.add(blocked);
|
|
return;
|
|
}
|
|
JMenuItem edit = new JMenuItem("Edit in Contacts", Icons.of("EDIT_FRIEND_FOE_STATUS"));
|
|
edit.addActionListener(a -> actions.editContact(client));
|
|
menu.add(edit);
|
|
JMenuItem remove = new JMenuItem("Remove from Contacts",
|
|
Icons.of(type == ContactType.BLOCKED ? "REMOVE_FOE" : "REMOVE_FRIEND"));
|
|
remove.addActionListener(a -> actions.removeContact(client));
|
|
menu.add(remove);
|
|
}
|
|
|
|
private static JMenu buildServerGroupMenu(ClientEntry client, ServerModel model, GroupIcons groupIcons,
|
|
ServerTreePanel.Actions actions) {
|
|
JMenu menu = new JMenu("Set Server Groups");
|
|
menu.setIcon(Icons.of("PERMISSIONS_SERVER_GROUPS"));
|
|
JMenuItem dialog = new JMenuItem("Server Groups Dialog...");
|
|
dialog.addActionListener(a -> actions.showServerGroupsDialog(client));
|
|
menu.add(dialog);
|
|
menu.addSeparator();
|
|
|
|
for (Group g : model.allServerGroups()) {
|
|
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));
|
|
menu.add(item);
|
|
}
|
|
return menu;
|
|
}
|
|
|
|
private static JMenu buildChannelGroupMenu(ClientEntry client, ServerModel model, GroupIcons groupIcons,
|
|
ServerTreePanel.Actions actions) {
|
|
JMenu menu = new JMenu("Set Channel Group");
|
|
menu.setIcon(Icons.of("PERMISSIONS_CHANNEL_GROUPS"));
|
|
int defaultGroupId = model.defaultChannelGroupId();
|
|
for (Group g : model.allChannelGroups()) {
|
|
if (g.id == defaultGroupId) continue;
|
|
boolean assigned = client.channelGroupId == g.id;
|
|
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));
|
|
menu.add(item);
|
|
}
|
|
return menu;
|
|
}
|
|
}
|