Float the info panel's "Show in chat tab" link in its corner
As a real label over the text, it stays reachable however long a description runs, instead of trailing off the end of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,16 @@ import com.ts3client.text.BBCode;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.JEditorPane;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JLayeredPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@@ -18,7 +27,7 @@ import java.util.List;
|
||||
* 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 {
|
||||
public final class InfoPanel extends JPanel {
|
||||
|
||||
/** Moves the whole info panel's content out to a persistent chat tab. */
|
||||
public interface DescriptionHandler {
|
||||
@@ -32,9 +41,13 @@ public final class InfoPanel extends JScrollPane {
|
||||
void setPanelHidden(boolean hidden);
|
||||
}
|
||||
|
||||
private static final String TOGGLE_HREF = "app:toggle-info-tab";
|
||||
/** Gap between the floating link and the panel's edges. */
|
||||
private static final int TOGGLE_MARGIN = 6;
|
||||
|
||||
private final JEditorPane pane = HtmlStyles.pane("font-family:sans-serif; font-size:11px;");
|
||||
private final JScrollPane scroll = new JScrollPane(pane);
|
||||
/** Floats over the bottom-right corner of the details, out of the text's way. */
|
||||
private final JLabel toggle = new JLabel("Show in chat tab");
|
||||
private DescriptionHandler descriptionHandler;
|
||||
private ChannelNode shownChannel;
|
||||
private ClientEntry shownClient;
|
||||
@@ -43,26 +56,57 @@ public final class InfoPanel extends JScrollPane {
|
||||
private boolean inChatTab;
|
||||
|
||||
public InfoPanel() {
|
||||
super(new BorderLayout());
|
||||
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();
|
||||
Links.open(e.getDescription(), this);
|
||||
});
|
||||
HtmlStyles.fillViewport(pane);
|
||||
|
||||
toggle.setFont(Theme.uiSmall());
|
||||
toggle.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
toggle.setToolTipText("Show these details in a chat tab instead");
|
||||
toggle.setOpaque(true);
|
||||
toggle.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
|
||||
toggle.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
inChatTab = true;
|
||||
render();
|
||||
} else {
|
||||
Links.open(e.getDescription(), this);
|
||||
}
|
||||
});
|
||||
setViewportView(pane);
|
||||
HtmlStyles.fillViewport(pane);
|
||||
|
||||
// The link floats on top of the text rather than following it, so it stays
|
||||
// in the corner however long the description is.
|
||||
JLayeredPane layers = new JLayeredPane() {
|
||||
@Override
|
||||
public void doLayout() {
|
||||
scroll.setBounds(0, 0, getWidth(), getHeight());
|
||||
Insets in = scroll.getInsets();
|
||||
Dimension d = toggle.getPreferredSize();
|
||||
toggle.setBounds(getWidth() - in.right - TOGGLE_MARGIN - d.width,
|
||||
getHeight() - in.bottom - TOGGLE_MARGIN - d.height, d.width, d.height);
|
||||
}
|
||||
};
|
||||
layers.add(scroll, JLayeredPane.DEFAULT_LAYER);
|
||||
layers.add(toggle, JLayeredPane.PALETTE_LAYER);
|
||||
add(layers, BorderLayout.CENTER);
|
||||
applyTheme();
|
||||
clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUI() {
|
||||
super.updateUI();
|
||||
setBorder(BorderFactory.createLineBorder(Theme.border()));
|
||||
// Runs from the superclass constructor too, before our own fields exist.
|
||||
if (scroll != null) applyTheme();
|
||||
}
|
||||
|
||||
private void applyTheme() {
|
||||
scroll.setBorder(BorderFactory.createLineBorder(Theme.border()));
|
||||
toggle.setBackground(pane.getBackground());
|
||||
toggle.setForeground(Theme.link());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
@@ -100,10 +144,11 @@ public final class InfoPanel extends JScrollPane {
|
||||
boolean selected = shownChannel != null || shownClient != null;
|
||||
boolean hide = inChatTab && selected;
|
||||
if (descriptionHandler != null) descriptionHandler.setPanelHidden(hide);
|
||||
toggle.setVisible(selected && !hide);
|
||||
if (hide) {
|
||||
descriptionHandler.showInfoTab(body(), this::onInfoTabClosed);
|
||||
} else {
|
||||
setHtml(body() + (selected ? toggleLink(true) : ""));
|
||||
setHtml(body());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,12 +217,6 @@ public final class InfoPanel extends JScrollPane {
|
||||
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) {
|
||||
pane.setText("<html><body>" + body + "</body></html>");
|
||||
pane.setCaretPosition(0);
|
||||
|
||||
Reference in New Issue
Block a user