Update connection and chat handling

This commit is contained in:
2026-09-06 19:14:42 +00:00
parent 62d5af7cf9
commit ad62f5fc92
6 changed files with 316 additions and 27 deletions

View File

@@ -29,6 +29,7 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
@@ -138,13 +139,30 @@ public final class ChatPanel extends JPanel {
/** Opens (or focuses) the private chat with {@code clientId}. */
public void openPrivateChat(int clientId, String nickname) {
openPrivateChat(clientId, "", nickname);
}
/** Opens (or focuses) the private chat with {@code clientId}. */
public void openPrivateChat(int clientId, String uniqueId, String nickname) {
edt(() -> {
Tab tab = privateTab(clientId, nickname);
Tab tab = privateTab(clientId, uniqueId, nickname);
tabs.setSelectedComponent(tab.scroll);
input.requestFocusInWindow();
});
}
public void setServerHistory(List<String> htmlEntries) {
edt(() -> serverTab.setHistory(htmlEntries));
}
public void setChannelHistory(List<String> htmlEntries) {
edt(() -> channelTab.setHistory(htmlEntries));
}
public void setPrivateHistory(int clientId, String nickname, List<String> htmlEntries) {
edt(() -> privateTab(clientId, "", nickname).setHistory(htmlEntries));
}
/** Drops every private chat tab, e.g. after disconnecting. */
public void closePrivateChats() {
edt(() -> {
@@ -214,17 +232,35 @@ public final class ChatPanel extends JPanel {
+ BBCode.linksToHtml(text) + "</span>"));
}
public void appendServerLogHtml(String html) {
edt(() -> serverTab.appendRawHtml(html));
}
public void appendServerMessage(int fromId, String from, String text) {
edt(() -> serverTab.appendMessage(fromId, from, text));
appendServerMessage(fromId, "", from, text);
}
public void appendServerMessage(int fromId, String uniqueId, String from, String text) {
edt(() -> serverTab.appendMessage(fromId, uniqueId, from, text));
}
public void appendChannelMessage(int fromId, String from, String text) {
edt(() -> channelTab.appendMessage(fromId, from, text));
appendChannelMessage(fromId, "", from, text);
}
public void appendChannelMessage(int fromId, String uniqueId, String from, String text) {
edt(() -> channelTab.appendMessage(fromId, uniqueId, from, text));
}
/** Appends to the private chat with {@code peerId}, creating the tab if needed. */
public void appendPrivateMessage(int peerId, String peerName, int fromId, String from, String text) {
edt(() -> privateTab(peerId, peerName).appendMessage(fromId, from, text));
appendPrivateMessage(peerId, "", peerName, fromId, "", from, text);
}
/** Appends to the private chat with {@code peerId}, creating the tab if needed. */
public void appendPrivateMessage(int peerId, String peerUniqueId, String peerName,
int fromId, String fromUniqueId, String from, String text) {
edt(() -> privateTab(peerId, peerUniqueId, peerName).appendMessage(fromId, fromUniqueId, from, text));
}
private String stamp() {
@@ -232,11 +268,17 @@ public final class ChatPanel extends JPanel {
}
private Tab privateTab(int clientId, String nickname) {
return privateTab(clientId, "", nickname);
}
private Tab privateTab(int clientId, String uniqueId, String nickname) {
Tab tab = privateTabs.get(clientId);
if (tab == null) {
tab = new Tab(Target.PRIVATE, clientId, nickname, Icons.privateChat());
tab = new Tab(Target.PRIVATE, clientId, uniqueId, nickname, Icons.privateChat());
privateTabs.put(clientId, tab);
addTab(tab, true);
} else {
tab.setClientUniqueId(uniqueId);
}
return tab;
}
@@ -301,6 +343,7 @@ public final class ChatPanel extends JPanel {
final Target target;
final int clientId;
private String clientUniqueId;
private String title;
final JEditorPane log = HtmlStyles.pane("font-family:sans-serif; font-size:11px; margin:4px 6px;");
final JScrollPane scroll;
@@ -311,17 +354,23 @@ public final class ChatPanel extends JPanel {
private JLabel titleLabel;
Tab(Target target, int clientId, String title, Icon icon) {
this(target, clientId, title, icon, null, null);
this(target, clientId, "", title, icon, null, null);
}
Tab(Target target, int clientId, String clientUniqueId, String title, Icon icon) {
this(target, clientId, clientUniqueId, title, icon, null, null);
}
/** A static-content tab: no send target, closing it runs {@code onClose}. */
Tab(String noteKey, Icon icon, String title, Runnable onClose) {
this(null, 0, title, icon, noteKey, onClose);
this(null, 0, "", title, icon, noteKey, onClose);
}
private Tab(Target target, int clientId, String title, Icon icon, String noteKey, Runnable onClose) {
private Tab(Target target, int clientId, String clientUniqueId, String title,
Icon icon, String noteKey, Runnable onClose) {
this.target = target;
this.clientId = clientId;
this.clientUniqueId = clientUniqueId == null ? "" : clientUniqueId;
this.title = title;
this.icon = icon;
this.noteKey = noteKey;
@@ -338,6 +387,10 @@ public final class ChatPanel extends JPanel {
scroll.setBorder(BorderFactory.createEmptyBorder());
}
void setClientUniqueId(String uniqueId) {
if (uniqueId != null && !uniqueId.isBlank()) clientUniqueId = uniqueId;
}
private void onLink(String href) {
if (href == null) return;
TsLink.Ref ref = TsLink.parse(href);
@@ -405,16 +458,27 @@ public final class ChatPanel extends JPanel {
titleLabel.setForeground(unread ? Theme.accent() : null);
}
void appendMessage(int fromId, String from, String text) {
void appendMessage(int fromId, String fromUniqueId, String from, String text) {
String name = BBCode.escape(from);
String sender = fromId > 0
? "<a class=\"" + BBCode.IDENTITY_LINK_CLASS + "\" href=\""
+ BBCode.escape(TsLink.clientHref(fromId, "", from)) + "\">" + name + "</a>"
+ BBCode.escape(TsLink.clientHref(fromId,
fromUniqueId == null || fromUniqueId.isBlank() ? clientUniqueId : fromUniqueId,
from)) + "\">" + name + "</a>"
: "<span class=\"name\">" + name + "</span>";
appendLine("<span class=\"muted\">" + stamp() + "</span>"
+ sender + ": " + BBCode.toHtml(text));
}
void setHistory(List<String> htmlEntries) {
StringBuilder html = new StringBuilder();
if (htmlEntries != null) {
for (String entry : htmlEntries) html.append(normalizeTs3LogEntry(entry));
}
log.setText("<html><body><div id=\"chatlog\">" + html + "</div></body></html>");
log.setCaretPosition(log.getDocument().getLength());
}
/** Replaces the whole log with fixed content; used by static-content tabs. */
void setStaticContent(String html) {
log.setText("<html><body><div id=\"chatlog\">" + html + "</div></body></html>");
@@ -422,13 +486,26 @@ public final class ChatPanel extends JPanel {
}
void appendLine(String html) {
appendRawHtml("<div>" + html + "</div>");
}
void appendRawHtml(String html) {
try {
HTMLDocument doc = (HTMLDocument) log.getDocument();
doc.insertBeforeEnd(doc.getElement("chatlog"), "<div>" + html + "</div>");
doc.insertBeforeEnd(doc.getElement("chatlog"), normalizeTs3LogEntry(html));
log.setCaretPosition(doc.getLength());
} catch (BadLocationException | IOException ignored) {
}
if (tabs.getSelectedComponent() != scroll) setUnread(true);
}
private String normalizeTs3LogEntry(String html) {
if (html == null || html.isBlank()) return "";
String normalized = html
.replaceAll("(?i)<img\\b[^>]*>", "")
.replaceFirst("(?i)^\\s*<p\\b", "<div")
.replaceFirst("(?i)</p>\\s*$", "</div>");
return normalized.startsWith("<div") ? normalized : "<div>" + normalized + "</div>";
}
}
}

View File

@@ -7,6 +7,7 @@ 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
@@ -129,18 +130,34 @@ final class ServerTabConnectionEvents implements ConnectionListener {
}
@Override
public void onChat(ChatScope scope, int fromClientId, String fromName, String message) {
switch (scope) {
case PRIVATE:
chatPanel.appendPrivateMessage(fromClientId, fromName, fromClientId, fromName, message);
break;
case SERVER:
chatPanel.appendServerMessage(fromClientId, fromName, message);
break;
default:
chatPanel.appendChannelMessage(fromClientId, fromName, message);
break;
}
public void onChat(ChatScope scope, int fromClientId, String fromUniqueId, String fromName, String message) {
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
@@ -165,6 +182,11 @@ final class ServerTabConnectionEvents implements ConnectionListener {
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(() -> {

View File

@@ -123,7 +123,8 @@ final class ServerTabTreeActions implements ServerTreePanel.Actions {
@Override
public void openPrivateChat(ClientEntry client) {
chatPanel.openPrivateChat(client.id, client.nickname);
chatPanel.openPrivateChat(client.id, client.uniqueId, client.nickname);
conn.loadPrivateChatHistory(client.id, client.uniqueId);
}
@Override