Initial commit: TS3J TeamSpeak 3 Java client
Swing desktop client (core/desktop/swing Maven modules) built on the ts3j protocol library, included as a submodule. Native Opus voice with voice-activation detection, push-to-talk, and audio pre-processing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
127
ts3-client/swing/src/main/java/com/ts3client/ui/ChatPanel.java
Normal file
127
ts3-client/swing/src/main/java/com/ts3client/ui/ChatPanel.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package com.ts3client.ui;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledDocument;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Chat log with an input line and a target selector (current channel or whole
|
||||
* server). Coloured styling mimics the TS3 chat pane.
|
||||
*/
|
||||
public final class ChatPanel extends JPanel {
|
||||
|
||||
/** Where an outgoing message should go. */
|
||||
public enum Target {CHANNEL, SERVER}
|
||||
|
||||
public interface SendHandler {
|
||||
void send(Target target, String text);
|
||||
}
|
||||
|
||||
private final JTextPane log = new JTextPane();
|
||||
private final JTextField input = new JTextField();
|
||||
private final JComboBox<String> targetBox = new JComboBox<>(new String[]{"Channel", "Server"});
|
||||
private final SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss");
|
||||
private SendHandler sendHandler;
|
||||
|
||||
public ChatPanel() {
|
||||
super(new BorderLayout());
|
||||
log.setEditable(false);
|
||||
log.setBackground(Theme.CHAT_BG);
|
||||
log.setFont(Theme.UI_FONT);
|
||||
log.setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6));
|
||||
|
||||
JScrollPane scroll = new JScrollPane(log);
|
||||
scroll.setBorder(BorderFactory.createLineBorder(new Color(0xD0D0D0)));
|
||||
add(scroll, BorderLayout.CENTER);
|
||||
|
||||
JPanel bottom = new JPanel(new BorderLayout(4, 0));
|
||||
bottom.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
|
||||
targetBox.setPreferredSize(new Dimension(90, 24));
|
||||
bottom.add(targetBox, BorderLayout.WEST);
|
||||
bottom.add(input, BorderLayout.CENTER);
|
||||
JButton send = new JButton("Send");
|
||||
bottom.add(send, BorderLayout.EAST);
|
||||
add(bottom, BorderLayout.SOUTH);
|
||||
|
||||
Runnable doSend = this::fireSend;
|
||||
send.addActionListener(e -> doSend.run());
|
||||
input.addActionListener(e -> doSend.run());
|
||||
|
||||
setInputEnabled(false);
|
||||
}
|
||||
|
||||
public void setSendHandler(SendHandler h) {
|
||||
this.sendHandler = h;
|
||||
}
|
||||
|
||||
public void setInputEnabled(boolean enabled) {
|
||||
input.setEnabled(enabled);
|
||||
targetBox.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private void fireSend() {
|
||||
String text = input.getText().trim();
|
||||
if (text.isEmpty() || sendHandler == null) return;
|
||||
Target t = targetBox.getSelectedIndex() == 1 ? Target.SERVER : Target.CHANNEL;
|
||||
sendHandler.send(t, text);
|
||||
input.setText("");
|
||||
}
|
||||
|
||||
// ---- append helpers (safe from any thread) ----
|
||||
|
||||
public void appendSystem(String text) {
|
||||
edt(() -> append("[" + time.format(new Date()) + "] ", Theme.CHAT_SYSTEM, false,
|
||||
text, Theme.CHAT_SYSTEM, false));
|
||||
}
|
||||
|
||||
public void appendMessage(String from, String text) {
|
||||
edt(() -> {
|
||||
append("[" + time.format(new Date()) + "] ", Theme.CHAT_SYSTEM, false, "", Theme.CHAT_SYSTEM, false);
|
||||
append(from + ": ", Theme.CHAT_NAME, true, text, Theme.CHAT_TEXT, false);
|
||||
});
|
||||
}
|
||||
|
||||
private void edt(Runnable r) {
|
||||
if (SwingUtilities.isEventDispatchThread()) r.run();
|
||||
else SwingUtilities.invokeLater(r);
|
||||
}
|
||||
|
||||
private void append(String prefix, Color prefixColor, boolean prefixBold,
|
||||
String body, Color bodyColor, boolean bodyBold) {
|
||||
StyledDocument doc = log.getStyledDocument();
|
||||
try {
|
||||
if (prefix != null && !prefix.isEmpty()) {
|
||||
doc.insertString(doc.getLength(), prefix, style(prefixColor, prefixBold));
|
||||
}
|
||||
if (body != null && !body.isEmpty()) {
|
||||
doc.insertString(doc.getLength(), body, style(bodyColor, bodyBold));
|
||||
}
|
||||
doc.insertString(doc.getLength(), "\n", style(bodyColor, false));
|
||||
log.setCaretPosition(doc.getLength());
|
||||
} catch (BadLocationException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static SimpleAttributeSet style(Color c, boolean bold) {
|
||||
SimpleAttributeSet a = new SimpleAttributeSet();
|
||||
StyleConstants.setForeground(a, c);
|
||||
StyleConstants.setBold(a, bold);
|
||||
StyleConstants.setFontFamily(a, "SansSerif");
|
||||
StyleConstants.setFontSize(a, 12);
|
||||
return a;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user