Download the icons a server advertises for its groups over the file transfer channel, cache them under the config directory, and draw them as a badge strip on each client's row, right-aligned against the tree's visible edge like the TeamSpeak client does. Groups without a custom icon fall back to the bundled default set. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
3.2 KiB
Java
102 lines
3.2 KiB
Java
package com.ts3client.ui;
|
|
|
|
import com.ts3client.net.Group;
|
|
import com.ts3client.net.IconRepository;
|
|
|
|
import javax.swing.Icon;
|
|
import javax.swing.ImageIcon;
|
|
import java.awt.Component;
|
|
import java.awt.Graphics;
|
|
import java.awt.Image;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
/**
|
|
* Turns the {@link IconRepository}'s icon bytes into Swing icons for one server's
|
|
* groups, caching the decoded images. Icons that still have to be downloaded simply
|
|
* do not render yet; the repository repaints the view once they arrive.
|
|
*/
|
|
public final class GroupIcons {
|
|
|
|
/** Tree rows are 16×16 like TeamSpeak's own icon set. */
|
|
private static final int SIZE = 16;
|
|
|
|
private final IconRepository repository;
|
|
private final Map<Long, ImageIcon> decoded = new ConcurrentHashMap<>();
|
|
|
|
public GroupIcons(IconRepository repository) {
|
|
this.repository = repository;
|
|
}
|
|
|
|
/** @return the group's icon, or {@code null} if it has none or it is not loaded yet */
|
|
public ImageIcon iconOf(Group group) {
|
|
if (group == null || group.iconId == 0) return null;
|
|
return icon(group.iconId);
|
|
}
|
|
|
|
public ImageIcon icon(long iconId) {
|
|
ImageIcon cached = decoded.get(iconId);
|
|
if (cached != null) return cached;
|
|
|
|
byte[] data = repository.get(iconId);
|
|
if (data == null) return null;
|
|
|
|
ImageIcon icon = new ImageIcon(data);
|
|
if (icon.getIconWidth() <= 0) return null;
|
|
if (icon.getIconWidth() != SIZE || icon.getIconHeight() != SIZE) {
|
|
icon = new ImageIcon(icon.getImage().getScaledInstance(SIZE, SIZE, Image.SCALE_SMOOTH));
|
|
}
|
|
decoded.put(iconId, icon);
|
|
return icon;
|
|
}
|
|
|
|
/** Collects the icons of a client's server groups plus its channel group, in display order. */
|
|
public List<Icon> iconsOf(List<Group> serverGroups, Group channelGroup) {
|
|
List<Icon> icons = new ArrayList<>();
|
|
for (Group g : serverGroups) {
|
|
ImageIcon icon = iconOf(g);
|
|
if (icon != null) icons.add(icon);
|
|
}
|
|
ImageIcon channelIcon = iconOf(channelGroup);
|
|
if (channelIcon != null) icons.add(channelIcon);
|
|
return icons;
|
|
}
|
|
|
|
/** Lays several icons out in a row, so a single tree cell can show a badge strip. */
|
|
public static final class Row implements Icon {
|
|
|
|
private static final int GAP = 2;
|
|
|
|
private final List<Icon> icons;
|
|
|
|
public Row(List<Icon> icons) {
|
|
this.icons = icons;
|
|
}
|
|
|
|
@Override
|
|
public void paintIcon(Component c, Graphics g, int x, int y) {
|
|
int offset = x;
|
|
for (Icon icon : icons) {
|
|
icon.paintIcon(c, g, offset, y + (getIconHeight() - icon.getIconHeight()) / 2);
|
|
offset += icon.getIconWidth() + GAP;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getIconWidth() {
|
|
int width = 0;
|
|
for (Icon icon : icons) width += icon.getIconWidth() + GAP;
|
|
return Math.max(0, width - GAP);
|
|
}
|
|
|
|
@Override
|
|
public int getIconHeight() {
|
|
int height = 0;
|
|
for (Icon icon : icons) height = Math.max(height, icon.getIconHeight());
|
|
return height;
|
|
}
|
|
}
|
|
}
|