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,89 @@
|
||||
package com.ts3client.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Persistent list of away-message presets, stored alongside the settings file.
|
||||
* Frontend-agnostic: no UI dependencies.
|
||||
*/
|
||||
public final class AwayMessages {
|
||||
|
||||
private static final File DIR = new File(System.getProperty("user.home"), ".ts3jclient");
|
||||
private static final File FILE = new File(DIR, "away.properties");
|
||||
|
||||
private final List<String> entries = new ArrayList<>();
|
||||
|
||||
public List<String> all() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void add(String message) {
|
||||
entries.add(message == null ? "" : message);
|
||||
}
|
||||
|
||||
public void set(int index, String message) {
|
||||
if (index >= 0 && index < entries.size()) entries.set(index, message == null ? "" : message);
|
||||
}
|
||||
|
||||
public void remove(int index) {
|
||||
if (index >= 0 && index < entries.size()) entries.remove(index);
|
||||
}
|
||||
|
||||
public static AwayMessages load() {
|
||||
AwayMessages m = new AwayMessages();
|
||||
if (!FILE.isFile()) return m.withDefaults();
|
||||
Properties p = new Properties();
|
||||
try (FileInputStream in = new FileInputStream(FILE)) {
|
||||
p.load(in);
|
||||
} catch (Exception e) {
|
||||
return m.withDefaults();
|
||||
}
|
||||
int count = parseInt(p.getProperty("count"), 0);
|
||||
for (int i = 0; i < count; i++) {
|
||||
String text = p.getProperty("message." + i);
|
||||
if (text != null && !text.isBlank()) m.entries.add(text);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
Properties p = new Properties();
|
||||
p.setProperty("count", Integer.toString(entries.size()));
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
p.setProperty("message." + i, entries.get(i));
|
||||
}
|
||||
try {
|
||||
if (!DIR.isDirectory()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
DIR.mkdirs();
|
||||
}
|
||||
try (FileOutputStream out = new FileOutputStream(FILE)) {
|
||||
p.store(out, "TS3J client away messages");
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/** First run: seed the presets a fresh client offers. */
|
||||
private AwayMessages withDefaults() {
|
||||
entries.add("Away from keyboard");
|
||||
entries.add("Be right back");
|
||||
entries.add("Lunch");
|
||||
entries.add("Busy");
|
||||
return this;
|
||||
}
|
||||
|
||||
private static int parseInt(String v, int def) {
|
||||
if (v == null) return def;
|
||||
try {
|
||||
return Integer.parseInt(v.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,8 @@ public final class ClientEntry {
|
||||
public boolean inputMuted; // microphone muted (client_input_muted)
|
||||
public boolean outputMuted; // speakers muted / deafened (client_output_muted)
|
||||
public boolean away;
|
||||
/** The message published with the away state, empty when there is none. */
|
||||
public String awayMessage = "";
|
||||
public boolean channelCommander;
|
||||
public boolean self;
|
||||
|
||||
|
||||
@@ -361,6 +361,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
e.inputMuted = cl.isInputMuted();
|
||||
e.outputMuted = cl.isOutputMuted();
|
||||
e.away = cl.isAway();
|
||||
e.awayMessage = orEmpty(cl.get("client_away_message"));
|
||||
e.uniqueId = cl.getUniqueIdentifier();
|
||||
e.serverGroupIds = cl.getServerGroups();
|
||||
e.channelGroupId = cl.getChannelGroupId();
|
||||
@@ -581,7 +582,10 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
|
||||
public void setAway(boolean away, String message) {
|
||||
sound(away ? SoundEvent.STATUS_SET_AWAY : SoundEvent.STATUS_SET_PRESENT);
|
||||
updateSelf(self -> self.away = away);
|
||||
updateSelf(self -> {
|
||||
self.away = away;
|
||||
self.awayMessage = away && message != null ? message : "";
|
||||
});
|
||||
selfUpdate(cmd -> {
|
||||
cmd.add(new CommandSingleParameter("client_away", away ? "1" : "0"));
|
||||
cmd.add(new CommandSingleParameter("client_away_message", away && message != null ? message : ""));
|
||||
@@ -648,6 +652,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
c.inputMuted = e.isClientInputMuted();
|
||||
c.outputMuted = e.isClientOutputMuted();
|
||||
c.away = e.isClientAway();
|
||||
c.awayMessage = orEmpty(e.get("client_away_message"));
|
||||
c.uniqueId = orEmpty(e.getUniqueClientIdentifier());
|
||||
c.serverGroupIds = parseIntList(e.getClientServerGroups());
|
||||
c.channelGroupId = e.getClientChannelGroupId();
|
||||
@@ -814,7 +819,14 @@ public final class TeamspeakConnection implements TS3Listener {
|
||||
if (renamed) c.nickname = e.get("client_nickname");
|
||||
if (has(e, "client_input_muted")) c.inputMuted = e.getBoolean("client_input_muted");
|
||||
if (has(e, "client_output_muted")) c.outputMuted = e.getBoolean("client_output_muted");
|
||||
if (has(e, "client_away")) c.away = e.getBoolean("client_away");
|
||||
if (has(e, "client_away")) {
|
||||
c.away = e.getBoolean("client_away");
|
||||
// Both fields travel together, so an absent message here means "no message"
|
||||
// — which `has` cannot tell from "not reported".
|
||||
c.awayMessage = c.away ? orEmpty(e.get("client_away_message")) : "";
|
||||
} else if (has(e, "client_away_message")) {
|
||||
c.awayMessage = e.get("client_away_message");
|
||||
}
|
||||
if (has(e, "client_talk_power")) c.talkPower = e.getInt("client_talk_power");
|
||||
if (has(e, "client_is_channel_commander"))
|
||||
c.channelCommander = e.getBoolean("client_is_channel_commander");
|
||||
|
||||
Reference in New Issue
Block a user