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>
102 lines
3.4 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|