diff --git a/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java b/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java index 5cf5787..cb3a023 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java @@ -292,6 +292,10 @@ final class ConnectionEventHandler implements TS3Listener { c.awayMessage = e.get("client_away_message"); } if (has(e, "client_talk_power")) c.talkPower = e.getInt("client_talk_power"); + if (has(e, "client_description")) { + c.description = e.get("client_description"); + conn.ui.onInfoUpdated(); + } if (has(e, "client_is_channel_commander")) c.channelCommander = e.getBoolean("client_is_channel_commander"); announceClientUpdate(e, c, renamed, oldName); diff --git a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java index 32f5702..edfb775 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java @@ -887,6 +887,45 @@ public final class TeamspeakConnection implements TS3Listener { }, "ts3j-clientinfo").start(); } + /** + * Reads a client's description straight from the server, so an editor opens on the + * live text instead of whatever an earlier info request cached. + */ + public void requestClientDescription(int clientId, BiConsumer callback) { + run("ts3j-client-description", callback, () -> { + Client c = client.getClientInfo(clientId); + String description = c == null ? "" : orEmpty(c.get("client_description")); + ClientEntry e = model.getClient(clientId); + if (e != null) e.description = description; + return description; + }); + } + + /** + * Changes a client's description. Unlike the nickname or the away state, this is not + * a {@code clientupdate} property even for ourselves: the server only takes it + * through {@code clientedit}, gated by {@code b_client_modify_own_description}. + * + * @param callback given {@code null} on success, or the failure message + */ + public void setClientDescription(int clientId, String description, Consumer callback) { + boolean self = clientId == selfClientId; + run("ts3j-client-description-set", (ignored, error) -> callback.accept(error), () -> { + SingleCommand cmd = new SingleCommand("clientedit", ProtocolRole.CLIENT); + cmd.add(new CommandSingleParameter("clid", Integer.toString(clientId))); + cmd.add(new CommandSingleParameter("client_description", description)); + client.executeCommand(cmd).complete(); + // Only the other clients are told about our own update, so the local entry + // (and the info panel showing it) has to be caught up here. + if (self) { + ClientEntry e = model.getClient(clientId); + if (e != null) e.description = description; + ui.onInfoUpdated(); + } + return null; + }); + } + // ---- channel administration ---- /** diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java index db660aa..0763f7e 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java @@ -65,6 +65,9 @@ final class ClientMenu { 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); JMenuItem info = new JMenuItem("Connection Info", Icons.of("INFO")); info.addActionListener(a -> actions.showConnectionInfo(client)); menu.add(info); diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java index d510abc..bb6464a 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java @@ -8,6 +8,7 @@ import com.ts3client.net.TeamspeakConnection; import com.ts3client.text.TsLink; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import java.awt.Component; /** @@ -201,6 +202,32 @@ final class ServerTabTreeActions implements ServerTreePanel.Actions { groupsDialog = null; } + /** + * Reads the description from the server before prompting, since the model only + * carries one for clients whose info has been looked at. + */ + @Override + public void changeClientDescription(ClientEntry client) { + if (!conn.isConnected()) return; + conn.requestClientDescription(client.id, (description, error) -> SwingUtilities.invokeLater(() -> { + if (error != null) { + JOptionPane.showMessageDialog(host, "Could not read the description: " + error, + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + String edited = (String) JOptionPane.showInputDialog(host, + "Description for " + client.nickname + ":", "Change Description", + JOptionPane.PLAIN_MESSAGE, Icons.of("EDIT"), null, description); + if (edited == null || edited.equals(description)) return; + conn.setClientDescription(client.id, edited, failure -> SwingUtilities.invokeLater(() -> { + if (failure != null) { + JOptionPane.showMessageDialog(host, "Could not change the description: " + failure, + "Error", JOptionPane.ERROR_MESSAGE); + } + })); + })); + } + @Override public void setChannelSubscribed(ChannelNode channel, boolean family, boolean subscribed) { if (conn.isConnected()) conn.setChannelsSubscribed(channel.familyIds(family), subscribed); diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java index ceaad23..0f87dad 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java @@ -99,6 +99,9 @@ public final class ServerTreePanel extends JScrollPane { /** Opens the full server-groups list dialog for a client. */ void showServerGroupsDialog(ClientEntry client); + + /** Edits a client's description — our own included. */ + void changeClientDescription(ClientEntry client); } private final DropIndicatorTree tree;