Draw the interface with TeamSpeak icon packs

Icon packs are read in TeamSpeak's own format — a zip (or unpacked folder)
of SVG/PNG art plus a settings.ini mapping icon keys to files — so the packs
of an installed client and the ones from its add-on site work unchanged.
Legacy packs without that mapping (default.zip) are resolved by their file
naming convention instead, picking the resolution closest to the drawn size.

The pack draws the tree, toolbar, menus, context menus, file browser and the
default group icons; a pack's FALLBACK option decides whether what it lacks
comes from default.zip, and anything still missing falls back to the icons
the client draws itself. Options → Design picks the pack and shows a viewer
of everything in it.

Vector art is rasterised with JSVG, and the pack search roots are shared
with the sound packs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 12:46:39 +00:00
parent e1f62ab50d
commit 676e95023e
21 changed files with 1268 additions and 195 deletions

View File

@@ -52,9 +52,9 @@ public final class FileBrowserDialog extends JDialog {
private final FileTableModel tableModel = new FileTableModel();
private final JTable table = new JTable(tableModel);
private final JLabel pathLabel = new JLabel("/");
private final JButton upButton = new JButton("Up");
private final JButton downloadButton = new JButton("Download");
private final JButton deleteButton = new JButton("Delete");
private final JButton upButton = new JButton("Up", Icons.of("FILE_UP"));
private final JButton downloadButton = new JButton("Download", Icons.of("DOWNLOAD"));
private final JButton deleteButton = new JButton("Delete", Icons.of("DELETE"));
private final JPanel transfersPanel = new JPanel();
private volatile String currentPath = "/";
@@ -88,11 +88,11 @@ public final class FileBrowserDialog extends JDialog {
bar.setBackground(Theme.WINDOW_BG);
upButton.addActionListener(e -> navigateUp());
JButton refresh = new JButton("Refresh");
JButton refresh = new JButton("Refresh", Icons.of("FILE_REFRESH"));
refresh.addActionListener(e -> refresh());
JButton mkdir = new JButton("New folder");
JButton mkdir = new JButton("New folder", Icons.of("ADD_FOLDER"));
mkdir.addActionListener(e -> createDirectory());
JButton upload = new JButton("Upload…");
JButton upload = new JButton("Upload…", Icons.of("UPLOAD"));
upload.addActionListener(e -> chooseUpload());
downloadButton.addActionListener(e -> downloadSelected());
deleteButton.addActionListener(e -> deleteSelected());
@@ -122,6 +122,7 @@ public final class FileBrowserDialog extends JDialog {
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setFillsViewportHeight(true);
table.getColumnModel().getColumn(0).setPreferredWidth(300);
table.getColumnModel().getColumn(0).setCellRenderer(new NameRenderer());
table.getColumnModel().getColumn(1).setPreferredWidth(90);
table.getColumnModel().getColumn(2).setPreferredWidth(70);
table.getColumnModel().getColumn(3).setPreferredWidth(150);
@@ -437,6 +438,18 @@ public final class FileBrowserDialog extends JDialog {
}
}
/** Marks folders and files with the icon pack's own glyphs. */
private final class NameRenderer extends javax.swing.table.DefaultTableCellRenderer {
@Override
public java.awt.Component getTableCellRendererComponent(JTable t, Object value, boolean selected,
boolean focus, int row, int column) {
super.getTableCellRendererComponent(t, value, selected, focus, row, column);
boolean directory = row >= 0 && row < tableModel.getRowCount() && tableModel.get(row).isDirectory();
setIcon(Icons.of(directory ? "FOLDER" : "DEFAULT"));
return this;
}
}
// ---- formatting ----
private static String formatBytes(long bytes) {