Files
ts3java/ts3-client/swing/src/main/java/com/ts3client/ui/ReasonDialog.java
ericek111 0f8de796ea Kick and ban clients from the context menu
The client menu gains "Kick Client from Channel", "Kick Client from
Server" and "Ban Client", each asking for a reason first. Kicks use a
shared prompt that enforces the protocol's 40-character limit; the ban
dialog adds a duration, whose unit dropdown ends in "Permanent" — the
zero-length ban the server understands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:29:21 +00:00

102 lines
3.4 KiB
Java

package com.ts3client.ui;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
import java.awt.Component;
import java.awt.Dimension;
/**
* Asks for the reason text that goes with a moderation action. The server caps
* such messages, so the field refuses anything longer.
*/
final class ReasonDialog {
/** The longest reason the protocol carries with a kick. */
static final int KICK_REASON_LIMIT = 40;
private ReasonDialog() {
}
/** @return the reason (possibly empty), or {@code null} when the dialog was cancelled */
static String prompt(Component owner, String title, String message, int maxLength) {
JTextField field = reasonField(maxLength);
focusWhenShown(field);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(label(message));
panel.add(field);
int result = JOptionPane.showConfirmDialog(owner, panel, title,
JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
return result == JOptionPane.OK_OPTION ? field.getText().trim() : null;
}
static JTextField reasonField(int maxLength) {
JTextField field = new JTextField(24);
field.setDocument(new LimitedDocument(maxLength));
field.setAlignmentX(Component.LEFT_ALIGNMENT);
fixHeight(field);
return field;
}
/** A left-aligned label with a little air below it, for use in a box layout. */
static JLabel label(String text) {
JLabel label = new JLabel(text);
label.setAlignmentX(Component.LEFT_ALIGNMENT);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));
return label;
}
/** Keeps a component from stretching taller than it needs in a box layout. */
static void fixHeight(Component c) {
c.setMaximumSize(new Dimension(Integer.MAX_VALUE, c.getPreferredSize().height));
}
/** Puts the caret in a field as soon as its dialog appears. */
static void focusWhenShown(JComponent field) {
field.addAncestorListener(new AncestorListener() {
@Override
public void ancestorAdded(AncestorEvent event) {
field.requestFocusInWindow();
}
@Override
public void ancestorRemoved(AncestorEvent event) {
}
@Override
public void ancestorMoved(AncestorEvent event) {
}
});
}
/** A document that silently drops anything past its length limit. */
private static final class LimitedDocument extends PlainDocument {
private final int limit;
LimitedDocument(int limit) {
this.limit = limit;
}
@Override
public void insertString(int offset, String text, AttributeSet attrs) throws BadLocationException {
if (text == null) return;
int room = limit - getLength();
if (room <= 0) return;
super.insertString(offset, text.length() > room ? text.substring(0, room) : text, attrs);
}
}
}