diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/InfoPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/InfoPanel.java index 2c8564d..2868241 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/InfoPanel.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/InfoPanel.java @@ -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 "
"; - } - private void setHtml(String body) { pane.setText("" + body + ""); pane.setCaretPosition(0);