Log server activity, scrollable chat tabs, and move info panel to a chat tab
Native TS3 logs client joins/leaves/moves, group changes and channel edits to the server tab; minor local-only notices (mic mute, tray) no longer spam the log. Chat tabs scroll by mouse wheel like server tabs. The info panel can now be moved into a persistent, generically-labelled chat tab that tracks tree selection and collapses the panel's space entirely; a tree rebuild no longer briefly fires a null selection that reset this. Added "Find Client in Channel Tree" to the client context menu, and tree selection now survives model rebuilds. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,27 @@ import java.util.List;
|
||||
*/
|
||||
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 = new JEditorPane();
|
||||
private DescriptionHandler descriptionHandler;
|
||||
private ChannelNode shownChannel;
|
||||
private ClientEntry shownClient;
|
||||
private ServerModel shownModel;
|
||||
private IconRepository shownIcons;
|
||||
private boolean inChatTab;
|
||||
|
||||
public InfoPanel() {
|
||||
javax.swing.text.html.HTMLEditorKit kit = new javax.swing.text.html.HTMLEditorKit();
|
||||
@@ -39,7 +59,12 @@ public final class InfoPanel extends JScrollPane {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
@@ -49,10 +74,54 @@ public final class InfoPanel extends JScrollPane {
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
setHtml("<i style='color:#8a8a8a'>Select a channel or client to see details.</i>");
|
||||
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 "<i style='color:#8a8a8a'>Select a channel or client to see details.</i>";
|
||||
}
|
||||
|
||||
private static String channelBody(ChannelNode ch) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(heading(esc(ch.name)));
|
||||
row(sb, "Type", ch.permanent ? "Permanent" : "Temporary");
|
||||
@@ -67,10 +136,10 @@ public final class InfoPanel extends JScrollPane {
|
||||
} else {
|
||||
sb.append("<i style='color:#8a8a8a'>Loading description…</i>");
|
||||
}
|
||||
setHtml(sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void showClient(ClientEntry cl, ServerModel model, IconRepository icons) {
|
||||
private static String clientBody(ClientEntry cl, ServerModel model, IconRepository icons) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(heading(esc(cl.nickname) + (cl.self ? " <span style='color:#8a8a8a'>(you)</span>" : "")));
|
||||
|
||||
@@ -105,7 +174,13 @@ public final class InfoPanel extends JScrollPane {
|
||||
if (cl.description != null && !cl.description.isEmpty()) {
|
||||
sb.append("<hr><div>").append(multiline(cl.description)).append("</div>");
|
||||
}
|
||||
setHtml(sb.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** The link that toggles between showing details here and in a chat tab. */
|
||||
private static String toggleLink(boolean toChat) {
|
||||
return "<div style='margin-top:8px'><a href='" + TOGGLE_HREF + "' style='font-size:10px'>"
|
||||
+ (toChat ? "Show in chat tab" : "Show here") + "</a></div>";
|
||||
}
|
||||
|
||||
private void setHtml(String body) {
|
||||
|
||||
Reference in New Issue
Block a user