Show server and channel group icons in the tree

Download the icons a server advertises for its groups over the file
transfer channel, cache them under the config directory, and draw them as
a badge strip on each client's row, right-aligned against the tree's
visible edge like the TeamSpeak client does. Groups without a custom icon
fall back to the bundled default set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 07:06:23 +00:00
parent 0333b92ce5
commit 7c86a1f166
18 changed files with 597 additions and 38 deletions

View File

@@ -31,6 +31,7 @@ final class ServerTab implements ConnectionListener, ServerTreePanel.Actions {
private final IdentityStore identities;
private final TeamspeakConnection conn;
private final GroupIcons groupIcons;
private final ServerTreePanel treePanel;
private final ChatPanel chatPanel;
private final InfoPanel infoPanel = new InfoPanel();
@@ -56,7 +57,8 @@ final class ServerTab implements ConnectionListener, ServerTreePanel.Actions {
this.settings = settings;
this.identities = identities;
this.conn = new TeamspeakConnection(settings, audio, this);
this.treePanel = new ServerTreePanel(conn.getModel(), this);
this.groupIcons = new GroupIcons(conn.getIcons());
this.treePanel = new ServerTreePanel(conn.getModel(), groupIcons, this);
this.chatPanel = new ChatPanel();
chatPanel.setSendHandler(this::onSendChat);
@@ -189,6 +191,11 @@ final class ServerTab implements ConnectionListener, ServerTreePanel.Actions {
if (conn.isConnected()) conn.disconnect();
}
/** Releases the background resources of a tab that is being thrown away. */
void dispose() {
conn.getIcons().shutdown();
}
/** Synchronous teardown for shutdown paths, so the server sees us leave. */
void shutdown() {
if (conn.isConnected()) conn.disconnectBlocking("Leaving");
@@ -361,7 +368,7 @@ final class ServerTab implements ConnectionListener, ServerTreePanel.Actions {
if (sel instanceof ChannelNode) {
infoPanel.showChannel((ChannelNode) sel);
} else if (sel instanceof ClientEntry) {
infoPanel.showClient((ClientEntry) sel, conn.getModel());
infoPanel.showClient((ClientEntry) sel, conn.getModel(), conn.getIcons());
} else {
infoPanel.clear();
}
@@ -425,6 +432,14 @@ final class ServerTab implements ConnectionListener, ServerTreePanel.Actions {
SwingUtilities.invokeLater(this::renderInfo);
}
@Override
public void onIconsUpdated() {
SwingUtilities.invokeLater(() -> {
treePanel.refreshRowSizes();
renderInfo();
});
}
@Override
public void onChat(ChatScope scope, int fromClientId, String fromName, String message) {
switch (scope) {