Mirrors the TeamSpeak 3 client's Contacts: clients can be filed as friend,
blocked or neutral from their context menu or by dragging them into the
Contacts window, given a custom and phonetic nickname, shown under either
name or both, and per contact muted automatically, ignored in server/channel
or private chat and pokes, have their away message hidden and whispers
allowed or denied. Friends are green and blocked clients red in the tree
(optional), new contacts start from per-type defaults ("Set Defaults"), and
the global whisper policy lives in Options -> Contacts. "Change Volume..."
adds a per-client dB gain that a contact entry remembers.
Contacts are stored in ~/.ts3jclient/contacts.txt using the exact record
format the official client keeps in the Contacts table of its settings.db
(field names and codes recovered from the binary), so entries move between
the two verbatim. A small read-only pure-Java SQLite reader imports the
official client's list on first run and on demand, without a driver.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
203 lines
6.9 KiB
Java
203 lines
6.9 KiB
Java
package com.ts3client.ui;
|
|
|
|
import com.ts3client.net.ChannelNode;
|
|
import com.ts3client.net.ClientEntry;
|
|
import com.ts3client.net.ConnectionListener;
|
|
import com.ts3client.net.TeamspeakConnection;
|
|
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.SwingUtilities;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Marshals {@link ConnectionListener} callbacks onto the EDT and fans them out to
|
|
* this tab's views (tree, chat, info panel) and to {@link MainFrame}.
|
|
*
|
|
* <p>Constructed before the {@link TeamspeakConnection} it listens to exists — the
|
|
* connection's constructor needs a listener up front — so {@link #attach} wires the
|
|
* rest of the tab in once everything else has been built.
|
|
*/
|
|
final class ServerTabConnectionEvents implements ConnectionListener {
|
|
|
|
/** The handful of {@link ServerTab} fields this class updates but can't reach directly. */
|
|
interface Listener {
|
|
void setStatus(String status);
|
|
|
|
void setTitle(String title);
|
|
|
|
void setConnecting(boolean connecting);
|
|
}
|
|
|
|
private final MainFrame host;
|
|
private final ServerTab tab;
|
|
private final Listener listener;
|
|
|
|
private TeamspeakConnection conn;
|
|
private ServerTreePanel treePanel;
|
|
private ChatPanel chatPanel;
|
|
private ServerTabSelfState selfState;
|
|
private ServerTabTreeActions treeActions;
|
|
|
|
ServerTabConnectionEvents(MainFrame host, ServerTab tab, Listener listener) {
|
|
this.host = host;
|
|
this.tab = tab;
|
|
this.listener = listener;
|
|
}
|
|
|
|
void attach(TeamspeakConnection conn, ServerTreePanel treePanel, ChatPanel chatPanel,
|
|
ServerTabSelfState selfState, ServerTabTreeActions treeActions) {
|
|
this.conn = conn;
|
|
this.treePanel = treePanel;
|
|
this.chatPanel = chatPanel;
|
|
this.selfState = selfState;
|
|
this.treeActions = treeActions;
|
|
}
|
|
|
|
/** Name of the channel we are in, or null while that is not known yet. */
|
|
private String currentChannelName() {
|
|
ClientEntry self = conn.getModel().getClient(conn.getSelfClientId());
|
|
if (self == null) return null;
|
|
ChannelNode ch = conn.getModel().getChannel(self.channelId);
|
|
return ch == null ? null : ch.name;
|
|
}
|
|
|
|
@Override
|
|
public void onStatus(String text) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
listener.setStatus(text);
|
|
host.tabUpdated(tab);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onConnected() {
|
|
SwingUtilities.invokeLater(() -> {
|
|
listener.setConnecting(false);
|
|
treePanel.setSelfClientId(conn.getSelfClientId());
|
|
selfState.resetOnConnect();
|
|
chatPanel.setInputEnabled(true);
|
|
chatPanel.appendSystem("Connected.");
|
|
host.tabConnected(tab);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onDisconnected(String reason) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
listener.setConnecting(false);
|
|
conn.getModel().clear();
|
|
treePanel.showDisconnected();
|
|
treeActions.clearSelection();
|
|
chatPanel.setInputEnabled(false);
|
|
chatPanel.closePrivateChats();
|
|
chatPanel.closeNoteTabs();
|
|
chatPanel.setServerName(null);
|
|
chatPanel.setChannelName(null);
|
|
chatPanel.appendSystem("Disconnected" + (reason == null || reason.isEmpty() ? "." : ": " + reason));
|
|
tab.banListServerChanged(false);
|
|
host.tabDisconnected(tab);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onModelChanged() {
|
|
SwingUtilities.invokeLater(() -> {
|
|
treePanel.rebuild();
|
|
treeActions.renderInfo();
|
|
treeActions.refreshGroupsDialog();
|
|
String name = conn.getModel().getServerName();
|
|
if (conn.isConnected() && name != null && !name.isBlank() && !name.equals(tab.title())) {
|
|
listener.setTitle(name);
|
|
host.tabUpdated(tab);
|
|
tab.banListServerChanged(true);
|
|
}
|
|
if (conn.isConnected()) {
|
|
chatPanel.setServerName(name);
|
|
chatPanel.setChannelName(currentChannelName());
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onInfoUpdated() {
|
|
SwingUtilities.invokeLater(treeActions::renderInfo);
|
|
}
|
|
|
|
@Override
|
|
public void onIconsUpdated() {
|
|
SwingUtilities.invokeLater(() -> {
|
|
treePanel.refreshRowSizes();
|
|
treeActions.renderInfo();
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onChat(ChatScope scope, int fromClientId, String fromUniqueId, String nickname, String message) {
|
|
String fromName = conn.getContacts().displayName(fromUniqueId, nickname);
|
|
SwingUtilities.invokeLater(() -> {
|
|
switch (scope) {
|
|
case PRIVATE:
|
|
chatPanel.appendPrivateMessage(fromClientId, fromUniqueId, fromName,
|
|
fromClientId, fromUniqueId, fromName, message);
|
|
break;
|
|
case SERVER:
|
|
chatPanel.appendServerMessage(fromClientId, fromUniqueId, fromName, message);
|
|
break;
|
|
default:
|
|
chatPanel.appendChannelMessage(fromClientId, fromUniqueId, fromName, message);
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onChatHistory(ChatScope scope, List<String> htmlEntries) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
if (scope == ChatScope.SERVER) chatPanel.setServerHistory(htmlEntries);
|
|
else if (scope == ChatScope.CHANNEL) chatPanel.setChannelHistory(htmlEntries);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onPrivateChatHistory(int clientId, String nickname, List<String> htmlEntries) {
|
|
SwingUtilities.invokeLater(() -> chatPanel.setPrivateHistory(clientId, nickname, htmlEntries));
|
|
}
|
|
|
|
@Override
|
|
public void onTalkStateChanged(int clientId, boolean talking) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
treePanel.refreshVisual();
|
|
if (clientId == conn.getSelfClientId()) host.selfStateChanged(tab);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onError(String message) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
chatPanel.appendSystem("Error: " + message);
|
|
listener.setStatus(message);
|
|
host.tabUpdated(tab);
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onServerLog(String message) {
|
|
SwingUtilities.invokeLater(() -> chatPanel.appendServerLog(message));
|
|
}
|
|
|
|
@Override
|
|
public void onServerLogHtml(String html) {
|
|
SwingUtilities.invokeLater(() -> chatPanel.appendServerLogHtml(html));
|
|
}
|
|
|
|
@Override
|
|
public void onPoke(String fromName, String message) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
chatPanel.appendSystem("You were poked by " + fromName + ": " + message);
|
|
host.selectTab(tab);
|
|
JOptionPane.showMessageDialog(host, fromName + " poked you:\n\n" + message,
|
|
"Poke", JOptionPane.INFORMATION_MESSAGE);
|
|
});
|
|
}
|
|
}
|