package com.ts3client.ui; import com.ts3client.net.ChannelNode; import com.ts3client.net.ClientEntry; import com.ts3client.net.Group; import com.ts3client.net.IconRepository; import com.ts3client.net.ServerModel; import com.ts3client.text.BBCode; import javax.swing.BorderFactory; import javax.swing.JEditorPane; import javax.swing.JScrollPane; import java.io.File; import java.util.List; /** * Read-only detail view for the currently selected channel or client, mirroring * the TeamSpeak 3 info box: channel topic/description, or a client's server and * channel groups, platform and version. */ public final class InfoPanel extends JScrollPane { private final JEditorPane pane = new JEditorPane(); public InfoPanel() { javax.swing.text.html.HTMLEditorKit kit = new javax.swing.text.html.HTMLEditorKit(); javax.swing.text.html.StyleSheet css = new javax.swing.text.html.StyleSheet(); css.addStyleSheet(kit.getStyleSheet()); css.addRule("a { color:" + hex(Theme.CHAT_NAME) + "; text-decoration:none; }"); // Client references look exactly like a message author's name. css.addRule("a." + BBCode.IDENTITY_LINK_CLASS + " { color:" + hex(Theme.CHAT_NAME) + "; font-weight:bold; text-decoration:none; }"); // External links are underlined so they can't be mistaken for an identity. css.addRule("a." + BBCode.EXTERNAL_LINK_CLASS + " { color:" + hex(Theme.LINK) + "; text-decoration:underline; }"); kit.setStyleSheet(css); pane.setEditorKit(kit); pane.setEditable(false); pane.setBackground(Theme.CHAT_BG); pane.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8)); pane.addHyperlinkListener(e -> { if (e.getEventType() == javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) { Links.open(e.getDescription(), this); } }); setViewportView(pane); setBorder(BorderFactory.createLineBorder(new java.awt.Color(0xD0D0D0))); clear(); } public void clear() { setHtml("Select a channel or client to see details."); } public void showChannel(ChannelNode ch) { StringBuilder sb = new StringBuilder(); sb.append(heading(esc(ch.name))); row(sb, "Type", ch.permanent ? "Permanent" : "Temporary"); if (ch.maxClients >= 0) row(sb, "Max clients", Integer.toString(ch.maxClients)); if (ch.hasPassword) row(sb, "Password", "protected"); if (!ch.topic.isEmpty()) row(sb, "Topic", esc(ch.topic)); sb.append("
"); if (ch.description != null && !ch.description.isEmpty()) { sb.append("
").append(multiline(ch.description)).append("
"); } else if (ch.descriptionLoaded) { sb.append("No description."); } else { sb.append("Loading description…"); } setHtml(sb.toString()); } public void showClient(ClientEntry cl, ServerModel model, IconRepository icons) { StringBuilder sb = new StringBuilder(); sb.append(heading(esc(cl.nickname) + (cl.self ? " (you)" : ""))); List serverGroups = model.serverGroupsOf(cl.serverGroupIds); if (serverGroups.isEmpty()) { List names = model.serverGroupNames(cl.serverGroupIds); row(sb, "Server groups", names.isEmpty() ? "—" : esc(String.join(", ", names))); } else { StringBuilder groups = new StringBuilder(); for (Group g : serverGroups) { if (groups.length() > 0) groups.append(", "); groups.append(iconTag(g, icons)).append(esc(g.name)); } row(sb, "Server groups", groups.toString()); } Group channelGroup = model.channelGroup(cl.channelGroupId); row(sb, "Channel group", channelGroup != null ? iconTag(channelGroup, icons) + esc(channelGroup.name) : "#" + cl.channelGroupId); if (cl.talkPower != 0) row(sb, "Talk power", Integer.toString(cl.talkPower)); if (!cl.platform.isEmpty()) row(sb, "Platform", esc(cl.platform)); if (!cl.version.isEmpty()) row(sb, "Version", esc(cl.version)); if (cl.away) row(sb, "Status", "Away"); if (!cl.inputHardware) row(sb, "Microphone", "disabled"); else if (cl.inputMuted) row(sb, "Microphone", "muted"); if (!cl.outputHardware) row(sb, "Speakers", "disabled"); else if (cl.outputMuted) row(sb, "Speakers", "muted"); if (!cl.uniqueId.isEmpty()) row(sb, "Unique ID", esc(cl.uniqueId)); if (cl.description != null && !cl.description.isEmpty()) { sb.append("
").append(multiline(cl.description)).append("
"); } setHtml(sb.toString()); } private void setHtml(String body) { pane.setText("" + body + ""); pane.setCaretPosition(0); } /** * An inline {@code } for a group's icon, or nothing while the icon is missing * or still downloading. Swing's HTML renderer only loads images by URL, so the icon * is served from the repository's on-disk cache. */ private static String iconTag(Group group, IconRepository icons) { if (group.iconId == 0) return ""; File file = icons.file(group.iconId); if (file == null) return ""; return " "; } private static String heading(String text) { return "
" + text + "
"; } private static void row(StringBuilder sb, String label, String value) { sb.append("
") .append(label).append(": ").append(value).append("
"); } /** Descriptions may carry BBCode markup. */ private static String multiline(String text) { return BBCode.toHtml(text); } private static String esc(String s) { return BBCode.escape(s); } private static String hex(java.awt.Color c) { return String.format("#%06X", c.getRGB() & 0xFFFFFF); } }