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:
@@ -0,0 +1,198 @@
|
||||
package com.ts3client.ui;
|
||||
|
||||
import com.ts3client.config.Settings;
|
||||
import com.ts3client.gfx.IconPack;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.DefaultListCellRenderer;
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingConstants;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Options page for icon packs: which pack the user interface is drawn with, where
|
||||
* to look for more of them, and a viewer showing everything the pack contains.
|
||||
*
|
||||
* <p>Picking a pack applies it right away so the change can be seen in the window
|
||||
* behind the dialog; cancelling puts the previous one back.
|
||||
*/
|
||||
final class IconPackPanel extends JPanel {
|
||||
|
||||
/** Size of the tiles in the icon viewer. */
|
||||
private static final int PREVIEW_SIZE = 32;
|
||||
private static final int TILE = 44;
|
||||
|
||||
/** Stands for "no pack": the icons the client draws itself. */
|
||||
private static final String BUILT_IN = "Built-in icons";
|
||||
|
||||
private final Settings settings;
|
||||
private final String originalPackId;
|
||||
private final String originalPackDir;
|
||||
|
||||
private final JComboBox<Object> packCombo = new JComboBox<>();
|
||||
private final JLabel packInfo = new JLabel();
|
||||
private final JTextField packDirField;
|
||||
private final DefaultListModel<String> previewModel = new DefaultListModel<>();
|
||||
private final JList<String> preview = new JList<>(previewModel);
|
||||
|
||||
IconPackPanel(Settings settings) {
|
||||
super(new BorderLayout(0, 8));
|
||||
this.settings = settings;
|
||||
this.originalPackId = settings.iconPack;
|
||||
this.originalPackDir = settings.iconPackDir;
|
||||
this.packDirField = new JTextField(settings.iconPackDir, 18);
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
|
||||
add(buildHeader(), BorderLayout.NORTH);
|
||||
add(buildPreview(), BorderLayout.CENTER);
|
||||
|
||||
reloadPacks();
|
||||
}
|
||||
|
||||
/** Copies the chosen pack into the settings; the caller saves them. */
|
||||
void apply() {
|
||||
settings.iconPackDir = packDirField.getText().trim();
|
||||
Object selected = packCombo.getSelectedItem();
|
||||
settings.iconPack = selected instanceof IconPack pack ? pack.id() : "";
|
||||
}
|
||||
|
||||
/** Puts back the pack the dialog started with, for a cancelled edit. */
|
||||
void revert() {
|
||||
settings.iconPack = originalPackId;
|
||||
settings.iconPackDir = originalPackDir;
|
||||
IconTheme.get().reload(settings);
|
||||
}
|
||||
|
||||
// ---- layout ----
|
||||
|
||||
private JComponent buildHeader() {
|
||||
JPanel p = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.insets = new Insets(2, 2, 2, 2);
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
packCombo.setToolTipText("Icon packs installed here or in a TeamSpeak 3 client");
|
||||
packCombo.addActionListener(e -> onPackSelected());
|
||||
packCombo.setRenderer(new DefaultListCellRenderer() {
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
|
||||
boolean selected, boolean focus) {
|
||||
super.getListCellRendererComponent(list, value, index, selected, focus);
|
||||
setText(value instanceof IconPack pack ? pack.name() : String.valueOf(value));
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
int row = 0;
|
||||
addRow(p, c, row++, new JLabel("Icon pack:"), packCombo);
|
||||
c.gridx = 1;
|
||||
c.gridy = row++;
|
||||
packInfo.setEnabled(false);
|
||||
p.add(packInfo, c);
|
||||
|
||||
JPanel dirRow = new JPanel(new BorderLayout(6, 0));
|
||||
JButton browse = new JButton("Browse…");
|
||||
browse.addActionListener(e -> browseForPackFolder());
|
||||
dirRow.add(packDirField, BorderLayout.CENTER);
|
||||
dirRow.add(browse, BorderLayout.EAST);
|
||||
packDirField.setToolTipText("Extra folder to look for icon packs in");
|
||||
addRow(p, c, row, new JLabel("Extra pack folder:"), dirRow);
|
||||
return p;
|
||||
}
|
||||
|
||||
private JComponent buildPreview() {
|
||||
preview.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
||||
preview.setVisibleRowCount(-1);
|
||||
preview.setFixedCellWidth(TILE);
|
||||
preview.setFixedCellHeight(TILE);
|
||||
preview.setCellRenderer(new DefaultListCellRenderer() {
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
|
||||
boolean selected, boolean focus) {
|
||||
super.getListCellRendererComponent(list, value, index, selected, focus);
|
||||
String key = String.valueOf(value);
|
||||
ImageIcon icon = Icons.of(key, PREVIEW_SIZE);
|
||||
setText("");
|
||||
setIcon(icon);
|
||||
setToolTipText(key);
|
||||
setHorizontalAlignment(SwingConstants.CENTER);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
JScrollPane scroll = new JScrollPane(preview);
|
||||
scroll.setPreferredSize(new Dimension(360, 200));
|
||||
scroll.setBorder(BorderFactory.createTitledBorder("Icons in this pack"));
|
||||
return scroll;
|
||||
}
|
||||
|
||||
// ---- behaviour ----
|
||||
|
||||
private void reloadPacks() {
|
||||
IconTheme theme = IconTheme.get();
|
||||
theme.reload(settings);
|
||||
// Filling the combo box changes its selection, and with it the active pack,
|
||||
// so remember the one the settings asked for before touching it.
|
||||
IconPack selected = theme.activePack();
|
||||
|
||||
packCombo.removeAllItems();
|
||||
packCombo.addItem(BUILT_IN);
|
||||
for (IconPack pack : theme.packs()) packCombo.addItem(pack);
|
||||
packCombo.setSelectedItem(selected == null ? BUILT_IN : selected);
|
||||
onPackSelected();
|
||||
}
|
||||
|
||||
private void onPackSelected() {
|
||||
Object selected = packCombo.getSelectedItem();
|
||||
IconPack pack = selected instanceof IconPack p ? p : null;
|
||||
IconTheme.get().setPack(pack);
|
||||
|
||||
previewModel.clear();
|
||||
if (pack == null) {
|
||||
packInfo.setText("Drawn by the client itself");
|
||||
} else {
|
||||
for (String key : pack.keys()) previewModel.addElement(key);
|
||||
packInfo.setText(pack.keys().size() + " icons"
|
||||
+ (pack.fallsBack() ? ", missing ones from the default pack" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
private void browseForPackFolder() {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
chooser.setDialogTitle("Folder containing icon packs");
|
||||
String current = packDirField.getText().trim();
|
||||
if (!current.isEmpty()) chooser.setCurrentDirectory(new File(current));
|
||||
if (chooser.showOpenDialog(this) != JFileChooser.APPROVE_OPTION) return;
|
||||
packDirField.setText(chooser.getSelectedFile().getAbsolutePath());
|
||||
settings.iconPackDir = packDirField.getText();
|
||||
reloadPacks();
|
||||
}
|
||||
|
||||
private static void addRow(JPanel p, GridBagConstraints c, int row, JLabel label, Component field) {
|
||||
c.gridx = 0;
|
||||
c.gridy = row;
|
||||
c.weightx = 0;
|
||||
p.add(label, c);
|
||||
c.gridx = 1;
|
||||
c.weightx = 1;
|
||||
p.add(field, c);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user