Add the Edit Channel dialog
Recovers the official client's CreateChannelDialog layout so the widget set, labels and tab order match: name/icon/password/topic/description above Standard, Audio, Permissions and Advanced tabs. Core gains ChannelSettings (diffed against the original so channeledit only carries changed properties) and ChannelAdmin (channelinfo/edit, channel permissions, the server icon store). TeamspeakConnection now owns the event executor so it can drain queued notify events before reading a command's result, which channelpermlist needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
package com.ts3client.ui;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SpinnerNumberModel;
|
||||
import java.awt.Color;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The channel editor's "Permissions" tab: the needed-power permissions set on the channel
|
||||
* itself, which is the subset of channel permissions TeamSpeak surfaces here.
|
||||
*
|
||||
* <p>These are not channel properties but permissions, so they are read with
|
||||
* {@code channelpermlist} and written with {@code channeladdperm}. A power left at zero is
|
||||
* not a permission the channel carries: clearing a spinner back to zero removes the
|
||||
* permission again so the channel inherits it, which is how the official client behaves.
|
||||
*/
|
||||
final class ChannelPermissionsPanel extends JPanel {
|
||||
|
||||
/** The regular powers, in the order the official dialog lists them. */
|
||||
private static final String[][] REGULAR = {
|
||||
{"i_channel_needed_join_power", "Join:"},
|
||||
{"i_channel_needed_subscribe_power", "Subscribe:"},
|
||||
{"i_channel_needed_description_view_power", "Desc. View:"},
|
||||
{"i_channel_needed_modify_power", "Modify:"},
|
||||
{"i_channel_needed_delete_power", "Delete:"}};
|
||||
|
||||
private static final String[][] FILE_TRANSFER = {
|
||||
{"i_ft_needed_file_browse_power", "Browse:"},
|
||||
{"i_ft_needed_file_upload_power", "Upload:"},
|
||||
{"i_ft_needed_file_download_power", "Download:"},
|
||||
{"i_ft_needed_file_rename_power", "Rename:"},
|
||||
{"i_ft_needed_directory_create_power", "Dir. Create:"}};
|
||||
|
||||
private final Map<String, JSpinner> spinners = new LinkedHashMap<>();
|
||||
/** The values the server reported, to tell an edited power from an untouched one. */
|
||||
private final Map<String, Integer> original = new LinkedHashMap<>();
|
||||
private final JLabel status = new JLabel(" ");
|
||||
|
||||
ChannelPermissionsPanel() {
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||
|
||||
JPanel groups = new JPanel();
|
||||
groups.setLayout(new BoxLayout(groups, BoxLayout.X_AXIS));
|
||||
groups.add(group("Regular Needed Powers", REGULAR));
|
||||
groups.add(Box.createHorizontalStrut(8));
|
||||
groups.add(group("File Transfer Needed Powers", FILE_TRANSFER));
|
||||
groups.setAlignmentX(0f);
|
||||
add(groups);
|
||||
|
||||
status.setAlignmentX(0f);
|
||||
status.setBorder(BorderFactory.createEmptyBorder(6, 2, 0, 2));
|
||||
add(status);
|
||||
add(Box.createVerticalGlue());
|
||||
setEditable(false);
|
||||
}
|
||||
|
||||
/** Fills the spinners from a {@code channelpermlist} result and enables editing. */
|
||||
void read(Map<String, Integer> permissions) {
|
||||
original.clear();
|
||||
for (Map.Entry<String, JSpinner> entry : spinners.entrySet()) {
|
||||
int value = permissions.getOrDefault(entry.getKey(), 0);
|
||||
original.put(entry.getKey(), value);
|
||||
entry.getValue().setValue(value);
|
||||
}
|
||||
setEditable(true);
|
||||
setStatus(" ", false);
|
||||
}
|
||||
|
||||
/** Greys the tab out with a reason, e.g. when the server refuses to show the permissions. */
|
||||
void showUnavailable(String message) {
|
||||
setEditable(false);
|
||||
setStatus(message, true);
|
||||
}
|
||||
|
||||
/** The permissions whose power was changed to a non-zero value. */
|
||||
Map<String, Integer> changed() {
|
||||
Map<String, Integer> changed = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, JSpinner> entry : spinners.entrySet()) {
|
||||
int value = (Integer) entry.getValue().getValue();
|
||||
if (value != 0 && value != original.getOrDefault(entry.getKey(), 0)) {
|
||||
changed.put(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/** The permissions cleared back to zero, which are removed from the channel. */
|
||||
List<String> cleared() {
|
||||
List<String> cleared = new ArrayList<>();
|
||||
for (Map.Entry<String, JSpinner> entry : spinners.entrySet()) {
|
||||
int value = (Integer) entry.getValue().getValue();
|
||||
if (value == 0 && original.getOrDefault(entry.getKey(), 0) != 0) cleared.add(entry.getKey());
|
||||
}
|
||||
return cleared;
|
||||
}
|
||||
|
||||
void setStatus(String message, boolean error) {
|
||||
status.setText(message == null || message.isEmpty() ? " " : message);
|
||||
status.setForeground(error ? Color.RED.darker() : Theme.CHAT_SYSTEM);
|
||||
}
|
||||
|
||||
private void setEditable(boolean editable) {
|
||||
for (JSpinner spinner : spinners.values()) spinner.setEnabled(editable);
|
||||
}
|
||||
|
||||
private JPanel group(String title, String[][] permissions) {
|
||||
JPanel panel = new JPanel(new GridBagLayout());
|
||||
panel.setBorder(BorderFactory.createTitledBorder(title));
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.insets = new Insets(3, 4, 3, 4);
|
||||
c.anchor = GridBagConstraints.WEST;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
int row = 0;
|
||||
for (String[] permission : permissions) {
|
||||
JSpinner spinner = Spinners.compact(new JSpinner(new SpinnerNumberModel(0, 0, 9999, 1)));
|
||||
spinners.put(permission[0], spinner);
|
||||
c.gridx = 0;
|
||||
c.gridy = row;
|
||||
c.weightx = 0;
|
||||
panel.add(new JLabel(permission[1]), c);
|
||||
c.gridx = 1;
|
||||
c.weightx = 1;
|
||||
c.fill = GridBagConstraints.NONE;
|
||||
panel.add(spinner, c);
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
row++;
|
||||
}
|
||||
// Keeps the rows at the top of the group instead of centred in whatever height
|
||||
// the tab happens to give it.
|
||||
c.gridx = 0;
|
||||
c.gridy = row;
|
||||
c.weighty = 1;
|
||||
panel.add(Box.createGlue(), c);
|
||||
return panel;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user