Add an away button with global actions and message presets
The toolbar gains an away toggle with a drop-down: it toggles away on the
current server with no message, while the menu carries the global actions
("Set Globally Away", "Set Globally Away Status"), the saved presets and
their editor. Presets live in ~/.ts3jclient/away.properties and are
managed in a list with add/remove and double-click renaming.
Clients in the channel tree now show their away message in brackets after
the nickname instead of their primary server group.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package com.ts3client.ui;
|
||||
|
||||
import com.ts3client.config.AwayMessages;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Frame;
|
||||
|
||||
/**
|
||||
* Editor for the away-message presets: a plain list of entries that are renamed
|
||||
* by double-clicking them, plus Add and Remove.
|
||||
*
|
||||
* <p>Backed by a one-column table because that is what gives Swing's list look
|
||||
* inline editing for free. Every change is written straight through to the
|
||||
* {@link AwayMessages} store.
|
||||
*/
|
||||
public final class AwayMessagesDialog extends JDialog {
|
||||
|
||||
private final AwayMessages messages;
|
||||
private final Runnable onChanged;
|
||||
private final Model model = new Model();
|
||||
private final JTable table = new JTable(model);
|
||||
|
||||
public AwayMessagesDialog(Frame owner, AwayMessages messages, Runnable onChanged) {
|
||||
super(owner, "Away Message Presets", true);
|
||||
this.messages = messages;
|
||||
this.onChanged = onChanged;
|
||||
|
||||
table.setTableHeader(null);
|
||||
table.setShowGrid(false);
|
||||
table.setFillsViewportHeight(true);
|
||||
table.setRowHeight(Math.max(20, table.getRowHeight()));
|
||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
// Single click selects, double click starts editing — like a renameable list.
|
||||
table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
|
||||
|
||||
JScrollPane scroll = new JScrollPane(table);
|
||||
scroll.setBorder(BorderFactory.createEmptyBorder(8, 8, 4, 8));
|
||||
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
getContentPane().add(scroll, BorderLayout.CENTER);
|
||||
getContentPane().add(buildButtons(), BorderLayout.SOUTH);
|
||||
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
setSize(new Dimension(360, 300));
|
||||
setLocationRelativeTo(owner);
|
||||
}
|
||||
|
||||
private JPanel buildButtons() {
|
||||
JPanel buttons = new JPanel();
|
||||
buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
|
||||
buttons.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
|
||||
addButton(buttons, "Add", this::addEntry);
|
||||
addButton(buttons, "Remove", this::removeSelected);
|
||||
buttons.add(Box.createHorizontalGlue());
|
||||
addButton(buttons, "Close", this::closeDialog);
|
||||
return buttons;
|
||||
}
|
||||
|
||||
private void addButton(JPanel panel, String text, Runnable action) {
|
||||
JButton b = new JButton(text);
|
||||
b.addActionListener(e -> action.run());
|
||||
panel.add(b);
|
||||
panel.add(Box.createHorizontalStrut(4));
|
||||
}
|
||||
|
||||
private void addEntry() {
|
||||
stopEditing();
|
||||
messages.add("New away message");
|
||||
int row = messages.all().size() - 1;
|
||||
model.fireTableRowsInserted(row, row);
|
||||
table.setRowSelectionInterval(row, row);
|
||||
table.scrollRectToVisible(table.getCellRect(row, 0, true));
|
||||
persist();
|
||||
table.editCellAt(row, 0);
|
||||
if (table.getEditorComponent() != null) table.getEditorComponent().requestFocusInWindow();
|
||||
}
|
||||
|
||||
private void removeSelected() {
|
||||
stopEditing();
|
||||
int row = table.getSelectedRow();
|
||||
if (row < 0) return;
|
||||
messages.remove(row);
|
||||
model.fireTableRowsDeleted(row, row);
|
||||
if (!messages.all().isEmpty()) {
|
||||
int next = Math.min(row, messages.all().size() - 1);
|
||||
table.setRowSelectionInterval(next, next);
|
||||
}
|
||||
persist();
|
||||
}
|
||||
|
||||
private void closeDialog() {
|
||||
stopEditing();
|
||||
dispose();
|
||||
}
|
||||
|
||||
/** Flushes a cell that is still being edited so its text is not lost. */
|
||||
private void stopEditing() {
|
||||
if (table.isEditing()) table.getCellEditor().stopCellEditing();
|
||||
}
|
||||
|
||||
private void persist() {
|
||||
messages.save();
|
||||
if (onChanged != null) onChanged.run();
|
||||
}
|
||||
|
||||
private final class Model extends AbstractTableModel {
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return messages.all().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int column) {
|
||||
return messages.all().get(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object value, int row, int column) {
|
||||
String text = value == null ? "" : value.toString().trim();
|
||||
if (text.isEmpty()) return; // an emptied entry keeps its old text
|
||||
messages.set(row, text);
|
||||
persist();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user