Repaint the whole tree viewport while scrolling

The right-aligned icon strip is positioned against the viewport rather than
the row, so the pixels a blit scroll reuses are stale and the icons leave
ghosts behind. Scroll the viewport in simple mode, and widen every repaint
request to the full visible width so a row whose label changes size cannot
leave its icons behind either.

Also stop blacklisting an icon whose download failed only because the file
transfer channel was not up yet: channel rows are painted during connect,
well before that point, so their icons would never be retried.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 07:28:09 +00:00
parent 610dce1bd9
commit f334153e63
2 changed files with 18 additions and 0 deletions

View File

@@ -118,6 +118,9 @@ public final class IconRepository {
icons.put(id, data); icons.put(id, data);
writeDiskCache(id, data); writeDiskCache(id, data);
if (onIconLoaded != null) onIconLoaded.run(); if (onIconLoaded != null) onIconLoaded.run();
} catch (IllegalStateException e) {
// Not connected (yet): a channel icon can be asked for while the tree is
// first painted, before file transfers are up. Leave it retryable.
} catch (Exception e) { } catch (Exception e) {
// Missing or permission-denied icons are common; don't ask again. // Missing or permission-denied icons are common; don't ask again.
failed.add(id); failed.add(id);

View File

@@ -11,6 +11,7 @@ import javax.swing.ImageIcon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTree; import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.TransferHandler; import javax.swing.TransferHandler;
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultMutableTreeNode;
@@ -96,6 +97,9 @@ public final class ServerTreePanel extends JScrollPane {
tree.setCellRenderer(new Renderer()); tree.setCellRenderer(new Renderer());
setViewportView(tree); setViewportView(tree);
getViewport().setBackground(Theme.TREE_BG); getViewport().setBackground(Theme.TREE_BG);
// The icon strip is drawn against the viewport's right edge, so the blitted
// pixels a scroll would reuse are stale; repaint the whole viewport instead.
getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
// Within the tree a drag moves the client or channel; dropped elsewhere it // Within the tree a drag moves the client or channel; dropped elsewhere it
// yields the TS3 link BBCode, which the chat input accepts as plain text. // yields the TS3 link BBCode, which the chat input accepts as plain text.
@@ -426,6 +430,17 @@ public final class ServerTreePanel extends JScrollPane {
super(model); super(model);
} }
/**
* Widens every repaint request to the full visible width. Swing only asks for
* the row rectangle, which stops short of the right-aligned icon strip and would
* leave it behind when a row's label changes width.
*/
@Override
public void repaint(long tm, int x, int y, int width, int height) {
Rectangle visible = getVisibleRect();
super.repaint(tm, visible.x, y, visible.width, height);
}
void highlightChannel(TreePath path) { void highlightChannel(TreePath path) {
if (path == highlight || (path != null && path.equals(highlight))) return; if (path == highlight || (path != null && path.equals(highlight))) return;
highlight = path; highlight = path;