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.BorderFactory;
|
||||||
import javax.swing.JEditorPane;
|
import javax.swing.JEditorPane;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JLayeredPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
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.io.File;
|
||||||
import java.util.List;
|
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
|
* the TeamSpeak 3 info box: channel topic/description, or a client's server and
|
||||||
* channel groups, platform and version.
|
* 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. */
|
/** Moves the whole info panel's content out to a persistent chat tab. */
|
||||||
public interface DescriptionHandler {
|
public interface DescriptionHandler {
|
||||||
@@ -32,9 +41,13 @@ public final class InfoPanel extends JScrollPane {
|
|||||||
void setPanelHidden(boolean hidden);
|
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 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 DescriptionHandler descriptionHandler;
|
||||||
private ChannelNode shownChannel;
|
private ChannelNode shownChannel;
|
||||||
private ClientEntry shownClient;
|
private ClientEntry shownClient;
|
||||||
@@ -43,26 +56,57 @@ public final class InfoPanel extends JScrollPane {
|
|||||||
private boolean inChatTab;
|
private boolean inChatTab;
|
||||||
|
|
||||||
public InfoPanel() {
|
public InfoPanel() {
|
||||||
|
super(new BorderLayout());
|
||||||
pane.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
pane.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
|
||||||
pane.addHyperlinkListener(e -> {
|
pane.addHyperlinkListener(e -> {
|
||||||
if (e.getEventType() != javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) return;
|
if (e.getEventType() != javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) return;
|
||||||
if (TOGGLE_HREF.equals(e.getDescription())) {
|
Links.open(e.getDescription(), this);
|
||||||
inChatTab = !inChatTab;
|
});
|
||||||
if (!inChatTab && descriptionHandler != null) descriptionHandler.closeInfoTab();
|
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();
|
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();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateUI() {
|
public void updateUI() {
|
||||||
super.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() {
|
public void clear() {
|
||||||
@@ -100,10 +144,11 @@ public final class InfoPanel extends JScrollPane {
|
|||||||
boolean selected = shownChannel != null || shownClient != null;
|
boolean selected = shownChannel != null || shownClient != null;
|
||||||
boolean hide = inChatTab && selected;
|
boolean hide = inChatTab && selected;
|
||||||
if (descriptionHandler != null) descriptionHandler.setPanelHidden(hide);
|
if (descriptionHandler != null) descriptionHandler.setPanelHidden(hide);
|
||||||
|
toggle.setVisible(selected && !hide);
|
||||||
if (hide) {
|
if (hide) {
|
||||||
descriptionHandler.showInfoTab(body(), this::onInfoTabClosed);
|
descriptionHandler.showInfoTab(body(), this::onInfoTabClosed);
|
||||||
} else {
|
} else {
|
||||||
setHtml(body() + (selected ? toggleLink(true) : ""));
|
setHtml(body());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,12 +217,6 @@ public final class InfoPanel extends JScrollPane {
|
|||||||
return 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) {
|
private void setHtml(String body) {
|
||||||
pane.setText("<html><body>" + body + "</body></html>");
|
pane.setText("<html><body>" + body + "</body></html>");
|
||||||
pane.setCaretPosition(0);
|
pane.setCaretPosition(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user