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 { /** Moves the whole info panel's content out to a persistent chat tab. */ public interface DescriptionHandler { /** Shows/updates {@code html} in the info chat tab, creating it if needed. */ void showInfoTab(String html, Runnable onClose); /** Closes the info chat tab (the user chose to show details in the panel again). */ void closeInfoTab(); /** Hides (or restores) the panel itself, freeing its space, while its content lives in the chat tab. */ void setPanelHidden(boolean hidden); } private static final String TOGGLE_HREF = "app:toggle-info-tab"; private final JEditorPane pane = HtmlStyles.pane("font-family:sans-serif; font-size:11px;"); private DescriptionHandler descriptionHandler; private ChannelNode shownChannel; private ClientEntry shownClient; private ServerModel shownModel; private IconRepository shownIcons; private boolean inChatTab; public InfoPanel() { pane.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8)); pane.addHyperlinkListener(e -> { if (e.getEventType() != javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) return; if (TOGGLE_HREF.equals(e.getDescription())) { inChatTab = !inChatTab; if (!inChatTab && descriptionHandler != null) descriptionHandler.closeInfoTab(); render(); } else { Links.open(e.getDescription(), this); } }); setViewportView(pane); HtmlStyles.fillViewport(pane); clear(); } @Override public void updateUI() { super.updateUI(); setBorder(BorderFactory.createLineBorder(Theme.border())); } public void clear() { shownChannel = null; shownClient = null; inChatTab = false; render(); } public void setDescriptionHandler(DescriptionHandler handler) { this.descriptionHandler = handler; } /** Runs when the user closes the info chat tab; shows details in the panel again. */ private void onInfoTabClosed() { inChatTab = false; render(); } public void showChannel(ChannelNode ch) { shownChannel = ch; shownClient = null; render(); } public void showClient(ClientEntry cl, ServerModel model, IconRepository icons) { shownClient = cl; shownChannel = null; shownModel = model; shownIcons = icons; render(); } private void render() { boolean selected = shownChannel != null || shownClient != null; boolean hide = inChatTab && selected; if (descriptionHandler != null) descriptionHandler.setPanelHidden(hide); if (hide) { descriptionHandler.showInfoTab(body(), this::onInfoTabClosed); } else { setHtml(body() + (selected ? toggleLink(true) : "")); } } private String body() { if (shownChannel != null) return channelBody(shownChannel); if (shownClient != null) return clientBody(shownClient, shownModel, shownIcons); return "Select a channel or client to see details."; } private static String channelBody(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("