Add a Permissions menu with privilege keys: list, create, remove and use

Permissions > Use Privilege Key redeems a key (tokenuse). Permissions >
Privilege Keys opens a per-tab window listing the server's keys with
their group, channel, creation date and description; keys can be created,
removed, copied and reloaded. The create dialog takes a server group, or a
channel group in a chosen channel, plus a description, offers only groups
we have the power to add members to, and shows each new key for copying.

tokenlist and tokenadd answer with notifytokenlist / notifytokenadd ahead
of the command's completion, so core collects them the way it does the
ban list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 23:00:27 +00:00
parent 2c1b37ee30
commit 4911542d39
12 changed files with 806 additions and 9 deletions

View File

@@ -283,6 +283,21 @@ public final class MainFrame extends JFrame implements ServerTabPane.Listener {
if (selected != null) selected.showBanList(banReasonPresets());
}
@Override
public boolean canListPrivilegeKeys() {
return selected != null && selected.session().canListPrivilegeKeys();
}
@Override
public void onShowPrivilegeKeys() {
if (selected != null) selected.showPrivilegeKeys();
}
@Override
public void onUsePrivilegeKey() {
if (selected != null) selected.usePrivilegeKey();
}
@Override
public void onShowSettings() {
showSettings();

View File

@@ -18,7 +18,7 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
/**
* The main window's menu bar: Connections, Bookmarks, Self, Tools and Help.
* The main window's menu bar: Connections, Bookmarks, Self, Permissions, Tools and Help.
* Talks to {@link MainFrame} only through {@link Listener}; the away/commander
* item states are pushed in by {@link #refresh}, mirroring how {@link MainToolbar}
* is kept in sync.
@@ -66,6 +66,13 @@ final class MainMenuBar extends JMenuBar {
void onShowBanList();
/** Whether the current server lets us list its privilege keys; asked as the Permissions menu opens. */
boolean canListPrivilegeKeys();
void onShowPrivilegeKeys();
void onUsePrivilegeKey();
void onShowSettings();
void onShowAbout();
@@ -88,6 +95,8 @@ final class MainMenuBar extends JMenuBar {
private JMenuItem avatarItem;
private JMenuItem deleteAvatarItem;
private JMenuItem banListItem;
private JMenuItem privilegeKeysItem;
private JMenuItem usePrivilegeKeyItem;
MainMenuBar(Bookmarks bookmarks, Listener listener) {
this.bookmarks = bookmarks;
@@ -171,6 +180,28 @@ final class MainMenuBar extends JMenuBar {
}
});
JMenu permissions = new JMenu("Permissions");
privilegeKeysItem = new JMenuItem("Privilege Keys", Icons.of("TOKEN"));
privilegeKeysItem.addActionListener(e -> listener.onShowPrivilegeKeys());
usePrivilegeKeyItem = new JMenuItem("Use Privilege Key", Icons.of("TOKEN_USE"));
usePrivilegeKeyItem.addActionListener(e -> listener.onUsePrivilegeKey());
permissions.add(privilegeKeysItem);
permissions.add(usePrivilegeKeyItem);
permissions.addMenuListener(new MenuListener() {
@Override
public void menuSelected(MenuEvent e) {
privilegeKeysItem.setVisible(!privilegeKeysItem.isEnabled() || listener.canListPrivilegeKeys());
}
@Override
public void menuDeselected(MenuEvent e) {
}
@Override
public void menuCanceled(MenuEvent e) {
}
});
JMenu tools = new JMenu("Tools");
JMenuItem identitiesItem = new JMenuItem("Identities…", Icons.of("IDENTITY_MANAGER"));
identitiesItem.addActionListener(e -> listener.onShowIdentities());
@@ -214,6 +245,7 @@ final class MainMenuBar extends JMenuBar {
add(connections);
add(bookmarksMenu);
add(self);
add(permissions);
add(tools);
add(help);
}
@@ -226,6 +258,8 @@ final class MainMenuBar extends JMenuBar {
avatarItem.setEnabled(connected);
deleteAvatarItem.setEnabled(connected);
banListItem.setEnabled(connected);
privilegeKeysItem.setEnabled(connected);
usePrivilegeKeyItem.setEnabled(connected);
awayItem.setSelected(away);
commanderItem.setSelected(commander);
}

View File

@@ -0,0 +1,168 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ChannelNode;
import com.ts3client.net.Group;
import com.ts3client.net.ServerModel;
import com.ts3client.net.TeamspeakConnection;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.StringSelection;
/**
* Creates privilege keys, as the official client's "Create Privilege Key" dialog: a server
* group, or a channel group in a chosen channel, and an optional description. Each key
* created is shown for copying, and the dialog stays open for the next one.
*/
final class PrivilegeKeyCreateDialog extends JDialog {
private final TeamspeakConnection conn;
private final Runnable onCreated;
private final JRadioButton serverGroup = new JRadioButton("Server Group", true);
private final JRadioButton channelGroup = new JRadioButton("Channel Group");
private final JComboBox<Group> groupBox = new JComboBox<>();
private final JComboBox<ChannelNode> channelBox = new JComboBox<>();
private final JTextField descriptionField = new JTextField(28);
private final JTextField keyField = new JTextField(28);
private final JButton copy = new JButton("Copy", Icons.of("COPY"));
private final JButton create = new JButton("Create");
/** @param onCreated runs on the UI thread after each key is created */
PrivilegeKeyCreateDialog(Window owner, TeamspeakConnection conn, GroupIcons groupIcons, Runnable onCreated) {
super(owner, "Create Privilege Key", ModalityType.APPLICATION_MODAL);
this.conn = conn;
this.onCreated = onCreated;
ButtonGroup type = new ButtonGroup();
type.add(serverGroup);
type.add(channelGroup);
serverGroup.addActionListener(e -> typeChanged());
channelGroup.addActionListener(e -> typeChanged());
JPanel typeRow = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
typeRow.add(serverGroup);
typeRow.add(Box.createHorizontalStrut(UIScale.scale(12)));
typeRow.add(channelGroup);
groupBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean selected, boolean focused) {
super.getListCellRendererComponent(list, value, index, selected, focused);
if (value instanceof Group g) {
setText(g.name);
setIcon(groupIcons.iconOf(g));
}
return this;
}
});
groupBox.addActionListener(e -> updateCreate());
ServerModel model = conn.getModel();
channelBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean selected, boolean focused) {
super.getListCellRendererComponent(list, value, index, selected, focused);
if (value instanceof ChannelNode c) setText(model.channelPath(c.id));
return this;
}
});
for (ChannelNode c : model.channelsInTreeOrder()) channelBox.addItem(c);
channelBox.setToolTipText("When creating a privilege key for a channel group, specify the channel here");
descriptionField.setToolTipText("Optional description for this privilege key");
descriptionField.addActionListener(e -> create());
keyField.setEditable(false);
copy.setEnabled(false);
copy.addActionListener(e -> Toolkit.getDefaultToolkit().getSystemClipboard()
.setContents(new StringSelection(keyField.getText()), null));
JPanel keyRow = new JPanel(new BorderLayout(4, 0));
keyRow.add(keyField, BorderLayout.CENTER);
keyRow.add(copy, BorderLayout.EAST);
FormPanel form = new FormPanel();
GridBagConstraints c = FormPanel.gbc();
FormPanel.addRow(form, c, 0, new JLabel("Group Type:"), typeRow);
FormPanel.addRow(form, c, 1, new JLabel("Group:"), groupBox);
FormPanel.addRow(form, c, 2, new JLabel("Channel:"), channelBox);
FormPanel.addRow(form, c, 3, new JLabel("Description:"), descriptionField);
FormPanel.addRow(form, c, 4, new JLabel("Privilege Key:"), keyRow);
create.addActionListener(e -> create());
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
buttons.setBorder(new ScaledEmptyBorder(0, 8, 4, 8));
buttons.add(create);
buttons.add(close);
getRootPane().setDefaultButton(create);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(form, BorderLayout.CENTER);
getContentPane().add(buttons, BorderLayout.SOUTH);
typeChanged();
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setResizable(false);
setLocationRelativeTo(owner);
}
private boolean forChannelGroup() {
return channelGroup.isSelected();
}
private void typeChanged() {
groupBox.removeAllItems();
for (Group g : conn.getModel().privilegeKeyGroups(forChannelGroup())) groupBox.addItem(g);
channelBox.setEnabled(forChannelGroup());
updateCreate();
}
private void updateCreate() {
create.setEnabled(groupBox.getSelectedItem() != null
&& (!forChannelGroup() || channelBox.getSelectedItem() != null));
}
private void create() {
if (!create.isEnabled()) return;
Group group = (Group) groupBox.getSelectedItem();
ChannelNode channel = (ChannelNode) channelBox.getSelectedItem();
create.setEnabled(false);
conn.addPrivilegeKey(forChannelGroup(), group.id, channel == null ? 0 : channel.id,
descriptionField.getText().trim(), (key, error) -> SwingUtilities.invokeLater(() -> {
if (!isDisplayable()) return;
updateCreate();
if (error != null) {
JOptionPane.showMessageDialog(this, "Failed to add Privilege Key: " + error,
getTitle(), JOptionPane.ERROR_MESSAGE);
return;
}
keyField.setText(key);
keyField.selectAll();
keyField.requestFocusInWindow();
copy.setEnabled(true);
onCreated.run();
}));
}
}

View File

@@ -0,0 +1,355 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.Group;
import com.ts3client.net.PrivilegeKey;
import com.ts3client.net.ServerModel;
import com.ts3client.net.TeamspeakConnection;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableRowSorter;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* The server's privilege keys, as the official client's Permissions &gt; Privilege Keys
* window: Create and Remove above the list, Reload below it.
*
* <p>One per server tab and modeless, like the ban list; the tab closes it when the
* connection goes away.
*/
final class PrivilegeKeysDialog extends JDialog {
private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private enum Column {
KEY("Privilege Key", 320),
TYPE("Type", 110),
GROUP("Group", 140),
CHANNEL("Channel", 140),
CREATED("Created", 150),
DESCRIPTION("Description", 180);
final String title;
final int width;
Column(String title, int width) {
this.title = title;
this.width = width;
}
}
private final TeamspeakConnection conn;
private final GroupIcons groupIcons;
private final KeyTableModel tableModel = new KeyTableModel();
private final JTable table = new JTable(tableModel);
private final JButton createButton = new JButton("Create", Icons.of("ADD"));
private final JButton removeButton = new JButton("Remove", Icons.of("DELETE"));
private final JLabel status = new JLabel(" ");
private boolean loading;
PrivilegeKeysDialog(Frame owner, TeamspeakConnection conn, GroupIcons groupIcons) {
super(owner, "Privilege Keys", false);
this.conn = conn;
this.groupIcons = groupIcons;
JPanel content = new JPanel(new BorderLayout(0, UIScale.scale(6)));
content.setBackground(Theme.windowBg());
content.setBorder(new ScaledEmptyBorder(10, 12, 10, 12));
content.add(buildToolbar(), BorderLayout.NORTH);
content.add(buildTable(), BorderLayout.CENTER);
content.add(buildBottom(), BorderLayout.SOUTH);
setContentPane(content);
bindKeys();
serverNameChanged();
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setPreferredSize(UIScale.scale(new Dimension(1040, 400)));
pack();
setLocationRelativeTo(owner);
reload();
}
// ---- construction ----
private JPanel buildToolbar() {
createButton.setToolTipText("<html>Create new privilege key<br>(Ctrl+N)");
removeButton.setToolTipText("<html>Remove selected privilege keys<br>(Delete)");
createButton.addActionListener(e -> createKey());
removeButton.addActionListener(e -> removeKeys());
JPanel bar = new JPanel();
bar.setLayout(new BoxLayout(bar, BoxLayout.X_AXIS));
bar.setOpaque(false);
bar.add(createButton);
bar.add(Box.createHorizontalStrut(UIScale.scale(6)));
bar.add(removeButton);
bar.add(Box.createHorizontalGlue());
return bar;
}
private JScrollPane buildTable() {
table.setFont(Theme.uiFont());
table.setRowHeight(UIScale.scale(20));
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setFillsViewportHeight(true);
table.setRowSorter(new TableRowSorter<>(tableModel));
table.getSelectionModel().addListSelectionListener(e -> updateButtons());
table.setComponentPopupMenu(tablePopup());
table.getTableHeader().setReorderingAllowed(true);
KeyCellRenderer renderer = new KeyCellRenderer();
for (Column c : Column.values()) {
table.getColumnModel().getColumn(c.ordinal()).setPreferredWidth(UIScale.scale(c.width));
table.getColumnModel().getColumn(c.ordinal()).setCellRenderer(renderer);
}
JScrollPane scroll = new JScrollPane(table);
scroll.getViewport().setBackground(Theme.treeBg());
return scroll;
}
private JPanel buildBottom() {
JButton reload = new JButton("Reload", Icons.of("REFRESH"));
reload.setToolTipText("<html>Reload privilege keys<br>(F5)");
reload.addActionListener(e -> reload());
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
JPanel row = new JPanel(new BorderLayout());
row.setOpaque(false);
row.add(reload, BorderLayout.WEST);
row.add(close, BorderLayout.EAST);
JPanel bottom = new JPanel(new BorderLayout(0, UIScale.scale(8)));
bottom.setOpaque(false);
bottom.add(status, BorderLayout.NORTH);
bottom.add(row, BorderLayout.SOUTH);
status.setForeground(Theme.chatEvent());
return bottom;
}
private JPopupMenu tablePopup() {
JPopupMenu menu = new JPopupMenu();
menu.add(item("Create", "ADD", this::createKey));
menu.add(item("Remove", "DELETE", this::removeKeys));
menu.add(item("Copy", "COPY", this::copyKeys));
menu.addSeparator();
menu.add(item("Reload", "REFRESH", this::reload));
return menu;
}
private static JMenuItem item(String text, String icon, Runnable action) {
JMenuItem item = new JMenuItem(text, Icons.of(icon));
item.addActionListener(e -> action.run());
return item;
}
private void bindKeys() {
bind(getRootPane(), JComponent.WHEN_IN_FOCUSED_WINDOW, KeyStroke.getKeyStroke("control N"), this::createKey);
bind(getRootPane(), JComponent.WHEN_IN_FOCUSED_WINDOW, KeyStroke.getKeyStroke("F5"), this::reload);
bind(getRootPane(), JComponent.WHEN_IN_FOCUSED_WINDOW, KeyStroke.getKeyStroke("control W"), this::dispose);
bind(table, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke("control C"), this::copyKeys);
bind(table, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),
this::removeKeys);
Dialogs.closeOnEscape(this);
}
private static void bind(JComponent target, int condition, KeyStroke key, Runnable action) {
String name = "ts3-privilegekeys-" + key;
target.getInputMap(condition).put(key, name);
target.getActionMap().put(name, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
action.run();
}
});
}
// ---- data ----
/** Follows the server's name, as the ban list's title does. */
void serverNameChanged() {
String name = conn.getModel().getServerName();
setTitle(name == null || name.isBlank() ? "Privilege Keys" : "Privilege Keys - " + name);
}
void connectionLost() {
dispose();
}
private void reload() {
if (loading) return;
loading = true;
status.setText("Loading…");
conn.requestPrivilegeKeys((keys, error) -> SwingUtilities.invokeLater(() -> {
loading = false;
if (!isDisplayable()) return;
if (error != null) {
tableModel.setKeys(List.of());
status.setText("Failed to request Privilege Key list: " + error);
} else {
tableModel.setKeys(keys);
status.setText(keys.size() == 1 ? "1 privilege key" : keys.size() + " privilege keys");
}
updateButtons();
}));
}
private List<PrivilegeKey> selectedKeys() {
List<PrivilegeKey> selected = new ArrayList<>();
for (int row : table.getSelectedRows()) selected.add(tableModel.get(table.convertRowIndexToModel(row)));
return selected;
}
private void updateButtons() {
ServerModel model = conn.getModel();
createButton.setEnabled(model.permits("b_virtualserver_token_add"));
removeButton.setEnabled(!selectedKeys().isEmpty() && model.permits("b_virtualserver_token_delete"));
}
// ---- actions ----
private void createKey() {
if (!createButton.isEnabled()) return;
new PrivilegeKeyCreateDialog(this, conn, groupIcons, this::reload).setVisible(true);
}
private void removeKeys() {
if (!removeButton.isEnabled()) return;
int answer = JOptionPane.showConfirmDialog(this, "Do you really want to delete the selected privilege key(s)?",
"Delete Privilege Keys", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (answer != JOptionPane.YES_OPTION) return;
List<String> tokens = selectedKeys().stream().map(PrivilegeKey::token).collect(Collectors.toList());
conn.deletePrivilegeKeys(tokens, error -> SwingUtilities.invokeLater(() -> {
if (!isDisplayable()) return;
if (error != null) {
JOptionPane.showMessageDialog(this, "Failed to delete Privilege Key: " + error, getTitle(),
JOptionPane.ERROR_MESSAGE);
}
reload();
}));
}
private void copyKeys() {
List<PrivilegeKey> keys = selectedKeys();
if (keys.isEmpty()) return;
String text = keys.stream().map(PrivilegeKey::token).collect(Collectors.joining("\n"));
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(text), null);
}
// ---- table ----
private Group groupOf(PrivilegeKey key) {
ServerModel model = conn.getModel();
return key.channelGroup() ? model.channelGroup(key.groupId()) : model.serverGroup(key.groupId());
}
private final class KeyTableModel extends AbstractTableModel {
private List<PrivilegeKey> keys = List.of();
void setKeys(List<PrivilegeKey> keys) {
this.keys = new ArrayList<>(keys);
fireTableDataChanged();
}
PrivilegeKey get(int row) {
return keys.get(row);
}
@Override
public int getRowCount() {
return keys.size();
}
@Override
public int getColumnCount() {
return Column.values().length;
}
@Override
public String getColumnName(int column) {
return Column.values()[column].title;
}
@Override
public Class<?> getColumnClass(int column) {
return Column.values()[column] == Column.CREATED ? Long.class : String.class;
}
@Override
public Object getValueAt(int row, int column) {
PrivilegeKey key = keys.get(row);
if (Column.values()[column] == Column.CREATED) return key.createdEpochSeconds();
return text(key, Column.values()[column]);
}
String text(PrivilegeKey key, Column column) {
switch (column) {
case KEY:
return key.token();
case TYPE:
return key.channelGroup() ? "Channel Group" : "Server Group";
case GROUP:
Group group = groupOf(key);
return group != null ? group.name : Integer.toString(key.groupId());
case CHANNEL:
if (!key.channelGroup()) return "";
String path = conn.getModel().channelPath(key.channelId());
return path.isEmpty() ? Integer.toString(key.channelId()) : path;
case CREATED:
return key.createdEpochSeconds() <= 0 ? "" : DATE.format(
Instant.ofEpochSecond(key.createdEpochSeconds()).atZone(ZoneId.systemDefault()));
case DESCRIPTION:
return key.description();
default:
return "";
}
}
}
/** Shows each column's text, with the group's icon beside its name. */
private final class KeyCellRenderer extends DefaultTableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable t, Object value, boolean selected,
boolean focused, int row, int column) {
PrivilegeKey key = tableModel.get(t.convertRowIndexToModel(row));
Column c = Column.values()[t.convertColumnIndexToModel(column)];
super.getTableCellRendererComponent(t, tableModel.text(key, c), selected, focused, row, column);
setIcon(c == Column.GROUP ? groupIcons.iconOf(groupOf(key)) : null);
return this;
}
}
}

View File

@@ -16,7 +16,9 @@ import com.ts3client.sound.SoundNotifier;
import com.ts3client.text.TsLink;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import java.awt.Component;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
@@ -52,6 +54,7 @@ final class ServerTab {
/** The one open ban list window for this server, if any. */
private BanListDialog banList;
private PrivilegeKeysDialog privilegeKeys;
ServerTab(MainFrame host, Settings settings, IdentityStore identities, AudioBackend audio,
SoundNotifier sounds, ContactStore contacts, PictureDownloads pictures) {
@@ -187,11 +190,39 @@ final class ServerTab {
banList.toFront();
}
/** The server's name changed or the connection went; the ban list window follows. */
void banListServerChanged(boolean connected) {
if (banList == null) return;
if (connected) banList.serverNameChanged();
else banList.connectionLost();
/** Asks for a privilege key and redeems it on this server. */
void usePrivilegeKey() {
if (!session.isConnected()) return;
String key = (String) JOptionPane.showInputDialog(host, "Privilege Key:", "Use Privilege Key",
JOptionPane.PLAIN_MESSAGE, Icons.of("TOKEN_USE"), null, null);
if (key == null || key.isBlank()) return;
conn.usePrivilegeKey(key.trim(), error -> {
if (error == null) return;
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(host, error,
"Error using Privilege Key", JOptionPane.ERROR_MESSAGE));
});
}
/** Opens the server's privilege keys, or brings the already open window forward. */
void showPrivilegeKeys() {
if (!session.isConnected()) return;
if (privilegeKeys == null || !privilegeKeys.isDisplayable()) {
privilegeKeys = new PrivilegeKeysDialog(host, conn, groupIcons);
}
privilegeKeys.setVisible(true);
privilegeKeys.toFront();
}
/** The server's name changed or the connection went; the ban list and privilege keys windows follow. */
void serverWindowsChanged(boolean connected) {
if (banList != null) {
if (connected) banList.serverNameChanged();
else banList.connectionLost();
}
if (privilegeKeys != null) {
if (connected) privilegeKeys.serverNameChanged();
else privilegeKeys.connectionLost();
}
}
/** The contact list changed: names, colours and per-client playback follow. */

View File

@@ -50,7 +50,7 @@ final class ServerTabConnectionEvents implements ServerSession.Listener {
@Override
public void onSessionChanged() {
SwingUtilities.invokeLater(() -> {
if (session.isConnected()) tab.banListServerChanged(true);
if (session.isConnected()) tab.serverWindowsChanged(true);
host.tabUpdated(tab);
});
}
@@ -97,7 +97,7 @@ final class ServerTabConnectionEvents implements ServerSession.Listener {
chatPanel.closeNoteTabs();
chatPanel.setServerName(null);
chatPanel.setChannelName(null);
tab.banListServerChanged(false);
tab.serverWindowsChanged(false);
host.tabDisconnected(tab);
});
}