- * To allow Maven shading, the main method has to be separated from the
- * {@link Startup} class which extends {@link Application}.
+ * To allow Maven shading, the main method has to be separated from the {@link Startup} class which
+ * extends {@link Application}.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
@@ -25,8 +25,7 @@ public final class Main {
/**
* Starts the application.
*
- * @param args the command line arguments are processed by the
- * client configuration
+ * @param args the command line arguments are processed by the client configuration
* @since Envoy Client v0.1-beta
*/
public static void main(String[] args) {
diff --git a/client/src/main/java/envoy/client/data/Cache.java b/client/src/main/java/envoy/client/data/Cache.java
index e638f7b..aef6454 100644
--- a/client/src/main/java/envoy/client/data/Cache.java
+++ b/client/src/main/java/envoy/client/data/Cache.java
@@ -35,7 +35,9 @@ public final class Cache implements Consumer, Serializable {
}
@Override
- public String toString() { return String.format("Cache[elements=" + elements + "]"); }
+ public String toString() {
+ return String.format("Cache[elements=" + elements + "]");
+ }
/**
* Sets the processor to which cached elements are relayed.
@@ -52,7 +54,8 @@ public final class Cache implements Consumer, Serializable {
* @since Envoy Client v0.3-alpha
*/
public void relay() {
- if (processor == null) throw new IllegalStateException("Processor is not defined");
+ if (processor == null)
+ throw new IllegalStateException("Processor is not defined");
elements.forEach(processor::accept);
elements.clear();
}
@@ -62,5 +65,7 @@ public final class Cache implements Consumer, Serializable {
*
* @since Envoy Client v0.2-beta
*/
- public void clear() { elements.clear(); }
+ public void clear() {
+ elements.clear();
+ }
}
diff --git a/client/src/main/java/envoy/client/data/CacheMap.java b/client/src/main/java/envoy/client/data/CacheMap.java
index cf9d501..4d850a1 100644
--- a/client/src/main/java/envoy/client/data/CacheMap.java
+++ b/client/src/main/java/envoy/client/data/CacheMap.java
@@ -4,8 +4,7 @@ import java.io.Serializable;
import java.util.*;
/**
- * Stores a heterogeneous map of {@link Cache} objects with different type
- * parameters.
+ * Stores a heterogeneous map of {@link Cache} objects with different type parameters.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
@@ -24,7 +23,9 @@ public final class CacheMap implements Serializable {
* @param cache the cache to store
* @since Envoy Client v0.1-beta
*/
- public void put(Class key, Cache cache) { map.put(key, cache); }
+ public void put(Class key, Cache cache) {
+ map.put(key, cache);
+ }
/**
* Returns a cache mapped by a class.
@@ -34,7 +35,9 @@ public final class CacheMap implements Serializable {
* @return the cache
* @since Envoy Client v0.1-beta
*/
- public Cache get(Class key) { return (Cache) map.get(key); }
+ public Cache get(Class key) {
+ return (Cache) map.get(key);
+ }
/**
* Returns a cache mapped by a class or any of its subclasses.
@@ -64,5 +67,7 @@ public final class CacheMap implements Serializable {
*
* @since Envoy Client v0.2-beta
*/
- public void clear() { map.values().forEach(Cache::clear); }
+ public void clear() {
+ map.values().forEach(Cache::clear);
+ }
}
diff --git a/client/src/main/java/envoy/client/data/Chat.java b/client/src/main/java/envoy/client/data/Chat.java
index 0683d2d..892b5ca 100644
--- a/client/src/main/java/envoy/client/data/Chat.java
+++ b/client/src/main/java/envoy/client/data/Chat.java
@@ -22,17 +22,19 @@ import envoy.client.net.WriteProxy;
*/
public class Chat implements Serializable {
- protected final Contact recipient;
-
- protected transient ObservableList messages = FXCollections.observableArrayList();
+ protected boolean disabled;
/**
* Stores the last time an {@link envoy.event.IsTyping} event has been sent.
*/
protected transient long lastWritingEvent;
+ protected transient ObservableList messages = FXCollections.observableArrayList();
+
protected int unreadAmount;
- protected static IntegerProperty totalUnreadAmount = new SimpleIntegerProperty(0);
+ protected static IntegerProperty totalUnreadAmount = new SimpleIntegerProperty();
+
+ protected final Contact recipient;
private static final long serialVersionUID = 2L;
@@ -61,8 +63,12 @@ public class Chat implements Serializable {
@Override
public String toString() {
- return String.format("%s[recipient=%s,messages=%d]", getClass().getSimpleName(), recipient,
- messages.size());
+ return String.format(
+ "%s[recipient=%s,messages=%d,disabled=%b]",
+ getClass().getSimpleName(),
+ recipient,
+ messages.size(),
+ disabled);
}
/**
@@ -197,4 +203,22 @@ public class Chat implements Serializable {
public void lastWritingEventWasNow() {
lastWritingEvent = System.currentTimeMillis();
}
+
+ /**
+ * Determines whether messages can be sent in this chat. Should be {@code true} i.e. for chats
+ * whose recipient deleted this client as a contact.
+ *
+ * @return whether this chat has been disabled
+ * @since Envoy Client v0.3-beta
+ */
+ public boolean isDisabled() { return disabled; }
+
+ /**
+ * Determines whether messages can be sent in this chat. Should be true i.e. for chats whose
+ * recipient deleted this client as a contact.
+ *
+ * @param disabled whether this chat should be disabled
+ * @since Envoy Client v0.3-beta
+ */
+ public void setDisabled(boolean disabled) { this.disabled = disabled; }
}
diff --git a/client/src/main/java/envoy/client/data/ClientConfig.java b/client/src/main/java/envoy/client/data/ClientConfig.java
index acd3216..e15d400 100644
--- a/client/src/main/java/envoy/client/data/ClientConfig.java
+++ b/client/src/main/java/envoy/client/data/ClientConfig.java
@@ -5,8 +5,8 @@ import static java.util.function.Function.identity;
import envoy.data.Config;
/**
- * Implements a configuration specific to the Envoy Client with default values
- * and convenience methods.
+ * Implements a configuration specific to the Envoy Client with default values and convenience
+ * methods.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
@@ -20,7 +20,8 @@ public final class ClientConfig extends Config {
* @since Envoy Client v0.1-beta
*/
public static ClientConfig getInstance() {
- if (config == null) config = new ClientConfig();
+ if (config == null)
+ config = new ClientConfig();
return config;
}
@@ -47,5 +48,7 @@ public final class ClientConfig extends Config {
* @return the amount of minutes after which the local database should be saved
* @since Envoy Client v0.2-beta
*/
- public Integer getLocalDBSaveInterval() { return (Integer) items.get("localDBSaveInterval").get(); }
+ public Integer getLocalDBSaveInterval() {
+ return (Integer) items.get("localDBSaveInterval").get();
+ }
}
diff --git a/client/src/main/java/envoy/client/data/Context.java b/client/src/main/java/envoy/client/data/Context.java
index a022ac3..2a78b14 100644
--- a/client/src/main/java/envoy/client/data/Context.java
+++ b/client/src/main/java/envoy/client/data/Context.java
@@ -36,7 +36,8 @@ public class Context {
* @since Envoy Client v0.2-beta
*/
public void initWriteProxy() {
- if (localDB == null) throw new IllegalStateException("The LocalDB has to be initialized!");
+ if (localDB == null)
+ throw new IllegalStateException("The LocalDB has to be initialized!");
writeProxy = new WriteProxy(client, localDB);
}
diff --git a/client/src/main/java/envoy/client/data/GroupChat.java b/client/src/main/java/envoy/client/data/GroupChat.java
index 188709c..45be856 100644
--- a/client/src/main/java/envoy/client/data/GroupChat.java
+++ b/client/src/main/java/envoy/client/data/GroupChat.java
@@ -2,14 +2,14 @@ package envoy.client.data;
import java.time.Instant;
-import envoy.client.net.WriteProxy;
import envoy.data.*;
import envoy.data.Message.MessageStatus;
import envoy.event.GroupMessageStatusChange;
+import envoy.client.net.WriteProxy;
+
/**
- * Represents a chat between a user and a group
- * as a list of messages.
+ * Represents a chat between a user and a group as a list of messages.
*
* @author Maximilian Käfer
* @since Envoy Client v0.1-beta
@@ -25,7 +25,7 @@ public final class GroupChat extends Chat {
* @param recipient the group whose members receive the messages
* @since Envoy Client v0.1-beta
*/
- public GroupChat(User sender, Contact recipient) {
+ public GroupChat(User sender, Group recipient) {
super(recipient);
this.sender = sender;
}
@@ -34,11 +34,14 @@ public final class GroupChat extends Chat {
public void read(WriteProxy writeProxy) {
for (int i = messages.size() - 1; i >= 0; --i) {
final GroupMessage gmsg = (GroupMessage) messages.get(i);
- if (gmsg.getSenderID() != sender.getID()) if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break;
- else {
- gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
- writeProxy.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, Instant.now(), sender.getID()));
- }
+ if (gmsg.getSenderID() != sender.getID())
+ if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ)
+ break;
+ else {
+ gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
+ writeProxy.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(),
+ MessageStatus.READ, Instant.now(), sender.getID()));
+ }
}
unreadAmount = 0;
}
diff --git a/client/src/main/java/envoy/client/data/LocalDB.java b/client/src/main/java/envoy/client/data/LocalDB.java
index 13db9df..e3c4ac7 100644
--- a/client/src/main/java/envoy/client/data/LocalDB.java
+++ b/client/src/main/java/envoy/client/data/LocalDB.java
@@ -1,29 +1,34 @@
package envoy.client.data;
+import static java.util.function.Predicate.not;
+
import java.io.*;
import java.nio.channels.*;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.*;
import java.util.logging.*;
+import java.util.stream.Stream;
import javafx.application.Platform;
import javafx.collections.*;
-import envoy.client.event.*;
-import envoy.data.*;
-import envoy.data.Message.MessageStatus;
-import envoy.event.*;
-import envoy.exception.EnvoyException;
-import envoy.util.*;
-
import dev.kske.eventbus.Event;
import dev.kske.eventbus.EventBus;
import dev.kske.eventbus.EventListener;
+import envoy.data.*;
+import envoy.data.Message.MessageStatus;
+import envoy.event.*;
+import envoy.event.contact.*;
+import envoy.exception.EnvoyException;
+import envoy.util.*;
+
+import envoy.client.event.*;
+
/**
- * Stores information about the current {@link User} and their {@link Chat}s.
- * For message ID generation a {@link IDGenerator} is stored as well.
+ * Stores information about the current {@link User} and their {@link Chat}s. For message ID
+ * generation a {@link IDGenerator} is stored as well.
*
* The managed objects are stored inside a folder in the local file system.
*
@@ -39,6 +44,7 @@ public final class LocalDB implements EventListener {
private IDGenerator idGenerator;
private CacheMap cacheMap = new CacheMap();
private String authToken;
+ private boolean contactsChanged;
// Auto save timer
private Timer autoSaver;
@@ -68,8 +74,11 @@ public final class LocalDB implements EventListener {
EventBus.getInstance().registerListener(this);
// Ensure that the database directory exists
- if (!dbDir.exists()) dbDir.mkdirs();
- else if (!dbDir.isDirectory()) throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
+ if (!dbDir.exists())
+ dbDir.mkdirs();
+ else if (!dbDir.isDirectory())
+ throw new IOException(
+ String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
// Lock the directory
lock();
@@ -88,8 +97,7 @@ public final class LocalDB implements EventListener {
}
/**
- * Ensured that only one Envoy instance is using this local database by creating
- * a lock file.
+ * Ensured that only one Envoy instance is using this local database by creating a lock file.
* The lock file is deleted on application exit.
*
* @throws EnvoyException if the lock cannot by acquired
@@ -98,17 +106,19 @@ public final class LocalDB implements EventListener {
private synchronized void lock() throws EnvoyException {
final var file = new File(dbDir, "instance.lock");
try {
- final var fc = FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+ final var fc = FileChannel.open(file.toPath(), StandardOpenOption.CREATE,
+ StandardOpenOption.WRITE);
instanceLock = fc.tryLock();
- if (instanceLock == null) throw new EnvoyException("Another Envoy instance is using this local database!");
+ if (instanceLock == null)
+ throw new EnvoyException("Another Envoy instance is using this local database!");
} catch (final IOException e) {
throw new EnvoyException("Could not create lock file!", e);
}
}
/**
- * Loads the local user registry {@code users.db}, the id generator
- * {@code id_gen.db} and last login file {@code last_login.db}.
+ * Loads the local user registry {@code users.db}, the id generator {@code id_gen.db} and last
+ * login file {@code last_login.db}.
*
* @since Envoy Client v0.2-beta
*/
@@ -133,10 +143,45 @@ public final class LocalDB implements EventListener {
* @since Envoy Client v0.3-alpha
*/
public synchronized void loadUserData() throws ClassNotFoundException, IOException {
- if (user == null) throw new IllegalStateException("Client user is null, cannot initialize user storage");
+ if (user == null)
+ throw new IllegalStateException("Client user is null, cannot initialize user storage");
userFile = new File(dbDir, user.getID() + ".db");
try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
- chats = FXCollections.observableList((List) in.readObject());
+ chats = FXCollections.observableList((List) in.readObject());
+
+ // Some chats have changed and should not be overwritten by the saved values
+ if (contactsChanged) {
+ final var contacts = user.getContacts();
+
+ // Mark chats as disabled if a contact is no longer in this users contact list
+ final var changedUserChats = chats.stream()
+ .filter(not(chat -> contacts.contains(chat.getRecipient())))
+ .peek(chat -> {
+ chat.setDisabled(true);
+ logger.log(Level.INFO,
+ String.format("Deleted chat with %s.", chat.getRecipient()));
+ });
+
+ // Also update groups with a different member count
+ final var changedGroupChats =
+ contacts.stream().filter(Group.class::isInstance).flatMap(group -> {
+ final var potentialChat = getChat(group.getID());
+ if (potentialChat.isEmpty())
+ return Stream.empty();
+ final var chat = potentialChat.get();
+ if (group.getContacts().size() != chat.getRecipient().getContacts()
+ .size()) {
+ logger.log(Level.INFO, "Removed one (or more) members from " + group);
+ return Stream.of(chat);
+ } else
+ return Stream.empty();
+ });
+ Stream.concat(changedUserChats, changedGroupChats)
+ .forEach(chat -> chats.set(chats.indexOf(chat), chat));
+
+ // loadUserData can get called two (or more?) times during application lifecycle
+ contactsChanged = false;
+ }
cacheMap = (CacheMap) in.readObject();
lastSync = (Instant) in.readObject();
} finally {
@@ -145,31 +190,34 @@ public final class LocalDB implements EventListener {
}
/**
- * Synchronizes the contact list of the client user with the chat and user
- * storage.
+ * Synchronizes the contact list of the client user with the chat and user storage.
*
* @since Envoy Client v0.1-beta
*/
private void synchronize() {
- user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), (User) u));
+ user.getContacts().stream()
+ .filter(u -> u instanceof User && !users.containsKey(u.getName()))
+ .forEach(u -> users.put(u.getName(), (User) u));
users.put(user.getName(), user);
// Synchronize user status data
for (final var contact : user.getContacts())
if (contact instanceof User)
- getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); });
+ getChat(contact.getID()).ifPresent(chat -> {
+ ((User) chat.getRecipient()).setStatus(((User) contact).getStatus());
+ });
// Create missing chats
user.getContacts()
.stream()
.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
- .map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, c))
+ .map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, (Group) c))
.forEach(chats::add);
}
/**
- * Initializes a timer that automatically saves this local database after a
- * period of time specified in the settings.
+ * Initializes a timer that automatically saves this local database after a period of time
+ * specified in the settings.
*
* @since Envoy Client v0.2-beta
*/
@@ -184,68 +232,112 @@ public final class LocalDB implements EventListener {
autoSaver.schedule(new TimerTask() {
@Override
- public void run() { save(); }
+ public void run() {
+ save();
+ }
}, 2000, ClientConfig.getInstance().getLocalDBSaveInterval() * 60000);
}
/**
- * Stores all users. If the client user is specified, their chats will be stored
- * as well. The message id generator will also be saved if present.
+ * Stores all users. If the client user is specified, their chats will be stored as well. The
+ * message id generator will also be saved if present.
*
* @throws IOException if the saving process failed
* @since Envoy Client v0.3-alpha
*/
- @Event(eventType = EnvoyCloseEvent.class, priority = 1000)
+ @Event(eventType = EnvoyCloseEvent.class, priority = 500)
private synchronized void save() {
- EnvoyLog.getLogger(LocalDB.class).log(Level.INFO, "Saving local database...");
+ EnvoyLog.getLogger(LocalDB.class).log(Level.FINER, "Saving local database...");
// Save users
try {
SerializationUtils.write(usersFile, users);
// Save user data and last sync time stamp
- if (user != null) SerializationUtils
- .write(userFile, new ArrayList<>(chats), cacheMap, Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
+ if (user != null)
+ SerializationUtils
+ .write(userFile, new ArrayList<>(chats), cacheMap,
+ Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
// Save last login information
- if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken);
+ if (authToken != null)
+ SerializationUtils.write(lastLoginFile, user, authToken);
// Save ID generator
- if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
+ if (hasIDGenerator())
+ SerializationUtils.write(idGeneratorFile, idGenerator);
} catch (final IOException e) {
- EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ", e);
+ EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ",
+ e);
}
}
- @Event(priority = 150)
- private void onMessage(Message msg) { if (msg.getStatus() == MessageStatus.SENT) msg.nextStatus(); }
+ @Event(priority = 500)
+ private void onMessage(Message msg) {
+ if (msg.getStatus() == MessageStatus.SENT)
+ msg.nextStatus();
+ }
- @Event(priority = 150)
+ @Event(priority = 500)
private void onGroupMessage(GroupMessage msg) {
// TODO: Cancel event once EventBus is updated
if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
}
- @Event(priority = 150)
- private void onMessageStatusChange(MessageStatusChange evt) { getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get())); }
+ @Event(priority = 500)
+ private void onMessageStatusChange(MessageStatusChange evt) {
+ getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get()));
+ }
- @Event(priority = 150)
+ @Event(priority = 500)
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
- this.getMessage(evt.getID()).ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
+ this.getMessage(evt.getID())
+ .ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
}
- @Event(priority = 150)
+ @Event(priority = 500)
private void onUserStatusChange(UserStatusChange evt) {
- getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast).ifPresent(u -> u.setStatus(evt.get()));
+ getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast)
+ .ifPresent(u -> u.setStatus(evt.get()));
}
- @Event(priority = 150)
- private void onGroupResize(GroupResize evt) { getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast).ifPresent(evt::apply); }
+ @Event(priority = 500)
+ private void onUserOperation(UserOperation operation) {
+ final var eventUser = operation.get();
+ switch (operation.getOperationType()) {
+ case ADD:
+ Platform.runLater(() -> chats.add(0, new Chat(eventUser)));
+ break;
+ case REMOVE:
+ getChat(eventUser.getID()).ifPresent(chat -> chat.setDisabled(true));
+ break;
+ }
+ }
- @Event(priority = 150)
+ @Event
+ private void onGroupCreationResult(GroupCreationResult evt) {
+ final var newGroup = evt.get();
+
+ // The group creation was not successful
+ if (newGroup == null)
+ return;
+
+ // The group was successfully created
+ else
+ Platform.runLater(() -> chats.add(new GroupChat(user, newGroup)));
+ }
+
+ @Event(priority = 500)
+ private void onGroupResize(GroupResize evt) {
+ getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast)
+ .ifPresent(evt::apply);
+ }
+
+ @Event(priority = 500)
private void onNameChange(NameChange evt) {
- chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny().ifPresent(c -> c.setName(evt.get()));
+ chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny()
+ .ifPresent(c -> c.setName(evt.get()));
}
/**
@@ -255,14 +347,16 @@ public final class LocalDB implements EventListener {
* @since Envoy Client v0.2-beta
*/
@Event
- private void onNewAuthToken(NewAuthToken evt) { authToken = evt.get(); }
+ private void onNewAuthToken(NewAuthToken evt) {
+ authToken = evt.get();
+ }
/**
* Deletes all associations to the current user.
*
* @since Envoy Client v0.2-beta
*/
- @Event(eventType = Logout.class, priority = 100)
+ @Event(eventType = Logout.class, priority = 50)
private void onLogout() {
autoSaver.cancel();
autoSaveRestart = true;
@@ -289,16 +383,28 @@ public final class LocalDB implements EventListener {
// once a message was removed
final var messageID = message.get();
for (final var chat : chats)
- if (chat.remove(messageID)) break;
+ if (chat.remove(messageID))
+ break;
});
}
@Event(priority = 500)
- private void onOwnStatusChange(OwnStatusChange statusChange) { user.setStatus(statusChange.get()); }
+ private void onOwnStatusChange(OwnStatusChange statusChange) {
+ user.setStatus(statusChange.get());
+ }
+
+ @Event(eventType = ContactsChangedSinceLastLogin.class, priority = 500)
+ private void onContactsChangedSinceLastLogin() {
+ contactsChanged = true;
+ }
+
+ @Event(priority = 500)
+ private void onContactDisabled(ContactDisabled event) {
+ getChat(event.get().getID()).ifPresent(chat -> chat.setDisabled(true));
+ }
/**
- * @return a {@code Map} of all users stored locally with their
- * user names as keys
+ * @return a {@code Map} of all users stored locally with their user names as keys
* @since Envoy Client v0.2-alpha
*/
public Map getUsers() { return users; }
@@ -311,7 +417,8 @@ public final class LocalDB implements EventListener {
* @since Envoy Client v0.1-beta
*/
public Optional getMessage(long id) {
- return (Optional) chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
+ return (Optional) chats.stream().map(Chat::getMessages).flatMap(List::stream)
+ .filter(m -> m.getID() == id).findAny();
}
/**
@@ -321,11 +428,12 @@ public final class LocalDB implements EventListener {
* @return an optional containing the chat
* @since Envoy Client v0.1-beta
*/
- public Optional getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
+ public Optional getChat(long recipientID) {
+ return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny();
+ }
/**
- * @return all saved {@link Chat} objects that list the client user as the
- * sender
+ * @return all saved {@link Chat} objects that list the client user as the sender
* @since Envoy Client v0.1-alpha
**/
public ObservableList getChats() { return chats; }
@@ -359,7 +467,9 @@ public final class LocalDB implements EventListener {
* @return {@code true} if an {@link IDGenerator} is present
* @since Envoy Client v0.3-alpha
*/
- public boolean hasIDGenerator() { return idGenerator != null; }
+ public boolean hasIDGenerator() {
+ return idGenerator != null;
+ }
/**
* @return the cache map for messages and message status changes
diff --git a/client/src/main/java/envoy/client/data/Settings.java b/client/src/main/java/envoy/client/data/Settings.java
index 9b317cd..a3b4cca 100644
--- a/client/src/main/java/envoy/client/data/Settings.java
+++ b/client/src/main/java/envoy/client/data/Settings.java
@@ -5,16 +5,16 @@ import java.util.*;
import java.util.logging.Level;
import java.util.prefs.Preferences;
-import envoy.client.event.EnvoyCloseEvent;
-import envoy.util.*;
-
import dev.kske.eventbus.*;
import dev.kske.eventbus.EventListener;
+import envoy.util.*;
+
+import envoy.client.event.EnvoyCloseEvent;
+
/**
- * Manages all application settings, which are different objects that can be
- * changed during runtime and serialized them by using either the file system or
- * the {@link Preferences} API.
+ * Manages all application settings, which are different objects that can be changed during runtime
+ * and serialized them by using either the file system or the {@link Preferences} API.
*
* @author Leon Hofmeister
* @author Maximilian Käfer
@@ -29,7 +29,8 @@ public final class Settings implements EventListener {
/**
* Settings are stored in this file.
*/
- private static final File settingsFile = new File(ClientConfig.getInstance().getHomeDirectory(), "settings.ser");
+ private static final File settingsFile =
+ new File(ClientConfig.getInstance().getHomeDirectory(), "settings.ser");
/**
* Singleton instance of this class.
@@ -37,8 +38,8 @@ public final class Settings implements EventListener {
private static Settings settings = new Settings();
/**
- * The way to instantiate the settings. Is set to private to deny other
- * instances of that object.
+ * The way to instantiate the settings. Is set to private to deny other instances of that
+ * object.
*
* @since Envoy Client v0.2-alpha
*/
@@ -68,7 +69,7 @@ public final class Settings implements EventListener {
* @throws IOException if an error occurs while saving the themes
* @since Envoy Client v0.2-alpha
*/
- @Event(eventType = EnvoyCloseEvent.class, priority = 900)
+ @Event(eventType = EnvoyCloseEvent.class)
private void save() {
EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");
@@ -76,20 +77,27 @@ public final class Settings implements EventListener {
try {
SerializationUtils.write(settingsFile, items);
} catch (final IOException e) {
- EnvoyLog.getLogger(Settings.class).log(Level.SEVERE, "Unable to save settings file: ", e);
+ EnvoyLog.getLogger(Settings.class).log(Level.SEVERE, "Unable to save settings file: ",
+ e);
}
}
private void supplementDefaults() {
- items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
- items.putIfAbsent("hideOnClose", new SettingsItem<>(false, "Hide on close", "Hides the chat window when it is closed."));
- items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
+ items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send",
+ "Sends a message by pressing the enter key."));
+ items.putIfAbsent("hideOnClose",
+ new SettingsItem<>(false, "Hide on close", "Hides the chat window when it is closed."));
+ items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name",
+ "The name of the currently selected theme."));
items.putIfAbsent("downloadLocation",
- new SettingsItem<>(new File(System.getProperty("user.home") + "/Downloads/"), "Download location",
- "The location where files will be saved to"));
- items.putIfAbsent("autoSaveDownloads", new SettingsItem<>(false, "Save without asking?", "Should downloads be saved without asking?"));
+ new SettingsItem<>(new File(System.getProperty("user.home") + "/Downloads/"),
+ "Download location",
+ "The location where files will be saved to"));
+ items.putIfAbsent("autoSaveDownloads", new SettingsItem<>(false, "Save without asking?",
+ "Should downloads be saved without asking?"));
items.putIfAbsent("askForConfirmation",
- new SettingsItem<>(true, "Ask for confirmation", "Will ask for confirmation before doing certain things"));
+ new SettingsItem<>(true, "Ask for confirmation",
+ "Will ask for confirmation before doing certain things"));
}
/**
@@ -104,7 +112,9 @@ public final class Settings implements EventListener {
* @param themeName the name to set
* @since Envoy Client v0.2-alpha
*/
- public void setCurrentTheme(String themeName) { ((SettingsItem) items.get("currentTheme")).set(themeName); }
+ public void setCurrentTheme(String themeName) {
+ ((SettingsItem) items.get("currentTheme")).set(themeName);
+ }
/**
* @return true if the currently used theme is one of the default themes
@@ -116,9 +126,8 @@ public final class Settings implements EventListener {
}
/**
- * @return {@code true}, if pressing the {@code Enter} key suffices to send a
- * message. Otherwise it has to be pressed in conjunction with the
- * {@code Control} key.
+ * @return {@code true}, if pressing the {@code Enter} key suffices to send a message. Otherwise
+ * it has to be pressed in conjunction with the {@code Control} key.
* @since Envoy Client v0.2-alpha
*/
public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); }
@@ -126,26 +135,27 @@ public final class Settings implements EventListener {
/**
* Changes the keystrokes performed by the user to send a message.
*
- * @param enterToSend If set to {@code true} a message can be sent by pressing
- * the {@code Enter} key. Otherwise it has to be pressed in
- * conjunction with the {@code Control} key.
+ * @param enterToSend If set to {@code true} a message can be sent by pressing the {@code Enter}
+ * key. Otherwise it has to be pressed in conjunction with the
+ * {@code Control} key.
* @since Envoy Client v0.2-alpha
*/
- public void setEnterToSend(boolean enterToSend) { ((SettingsItem) items.get("enterToSend")).set(enterToSend); }
+ public void setEnterToSend(boolean enterToSend) {
+ ((SettingsItem) items.get("enterToSend")).set(enterToSend);
+ }
/**
- * @return whether Envoy will prompt a dialogue before saving an
- * {@link envoy.data.Attachment}
+ * @return whether Envoy will prompt a dialogue before saving an {@link envoy.data.Attachment}
* @since Envoy Client v0.2-beta
*/
- public Boolean isDownloadSavedWithoutAsking() { return (Boolean) items.get("autoSaveDownloads").get(); }
+ public Boolean isDownloadSavedWithoutAsking() {
+ return (Boolean) items.get("autoSaveDownloads").get();
+ }
/**
- * Sets whether Envoy will prompt a dialogue before saving an
- * {@link envoy.data.Attachment}.
+ * Sets whether Envoy will prompt a dialogue before saving an {@link envoy.data.Attachment}.
*
- * @param autosaveDownload whether a download should be saved without asking
- * before
+ * @param autosaveDownload whether a download should be saved without asking before
* @since Envoy Client v0.2-beta
*/
public void setDownloadSavedWithoutAsking(boolean autosaveDownload) {
@@ -164,7 +174,9 @@ public final class Settings implements EventListener {
* @param downloadLocation the path to set
* @since Envoy Client v0.2-beta
*/
- public void setDownloadLocation(File downloadLocation) { ((SettingsItem) items.get("downloadLocation")).set(downloadLocation); }
+ public void setDownloadLocation(File downloadLocation) {
+ ((SettingsItem) items.get("downloadLocation")).set(downloadLocation);
+ }
/**
* @return the current on close mode.
@@ -178,21 +190,24 @@ public final class Settings implements EventListener {
* @param hideOnClose whether the application should be minimized on close
* @since Envoy Client v0.3-alpha
*/
- public void setHideOnClose(boolean hideOnClose) { ((SettingsItem) items.get("hideOnClose")).set(hideOnClose); }
+ public void setHideOnClose(boolean hideOnClose) {
+ ((SettingsItem) items.get("hideOnClose")).set(hideOnClose);
+ }
/**
- * @return whether a confirmation dialog should be displayed before certain
- * actions
+ * @return whether a confirmation dialog should be displayed before certain actions
* @since Envoy Client v0.2-alpha
*/
- public Boolean isAskForConfirmation() { return (Boolean) items.get("askForConfirmation").get(); }
+ public Boolean isAskForConfirmation() {
+ return (Boolean) items.get("askForConfirmation").get();
+ }
/**
- * Changes the behavior of calling certain functionality by displaying a
- * confirmation dialog before executing it.
+ * Changes the behavior of calling certain functionality by displaying a confirmation dialog
+ * before executing it.
*
- * @param askForConfirmation whether confirmation dialogs should be displayed
- * before certain actions
+ * @param askForConfirmation whether confirmation dialogs should be displayed before certain
+ * actions
* @since Envoy Client v0.2-alpha
*/
public void setAskForConfirmation(boolean askForConfirmation) {
diff --git a/client/src/main/java/envoy/client/data/SettingsItem.java b/client/src/main/java/envoy/client/data/SettingsItem.java
index 3837071..590f965 100644
--- a/client/src/main/java/envoy/client/data/SettingsItem.java
+++ b/client/src/main/java/envoy/client/data/SettingsItem.java
@@ -6,8 +6,7 @@ import java.util.function.Consumer;
import javax.swing.JComponent;
/**
- * Encapsulates a persistent value that is directly or indirectly mutable by the
- * user.
+ * Encapsulates a persistent value that is directly or indirectly mutable by the user.
*
* @param the type of this {@link SettingsItem}'s value
* @author Kai S. K. Engelbart
@@ -23,9 +22,8 @@ public final class SettingsItem implements Serializable {
private static final long serialVersionUID = 1L;
/**
- * Initializes a {@link SettingsItem}. The default value's class will be mapped
- * to a {@link JComponent} that can be used to display this {@link SettingsItem}
- * to the user.
+ * Initializes a {@link SettingsItem}. The default value's class will be mapped to a
+ * {@link JComponent} that can be used to display this {@link SettingsItem} to the user.
*
* @param value the default value
* @param userFriendlyName the user friendly name (short)
@@ -42,17 +40,20 @@ public final class SettingsItem implements Serializable {
* @return the value
* @since Envoy Client v0.3-alpha
*/
- public T get() { return value; }
+ public T get() {
+ return value;
+ }
/**
- * Changes the value of this {@link SettingsItem}. If a {@code ChangeHandler} if
- * defined, it will be invoked with this value.
+ * Changes the value of this {@link SettingsItem}. If a {@code ChangeHandler} if defined, it
+ * will be invoked with this value.
*
* @param value the value to set
* @since Envoy Client v0.3-alpha
*/
public void set(T value) {
- if (changeHandler != null && value != this.value) changeHandler.accept(value);
+ if (changeHandler != null && value != this.value)
+ changeHandler.accept(value);
this.value = value;
}
@@ -66,7 +67,9 @@ public final class SettingsItem implements Serializable {
* @param userFriendlyName the userFriendlyName to set
* @since Envoy Client v0.3-alpha
*/
- public void setUserFriendlyName(String userFriendlyName) { this.userFriendlyName = userFriendlyName; }
+ public void setUserFriendlyName(String userFriendlyName) {
+ this.userFriendlyName = userFriendlyName;
+ }
/**
* @return the description
@@ -81,9 +84,8 @@ public final class SettingsItem implements Serializable {
public void setDescription(String description) { this.description = description; }
/**
- * Sets a {@code ChangeHandler} for this {@link SettingsItem}. It will be
- * invoked with the current value once during the registration and every time
- * when the value changes.
+ * Sets a {@code ChangeHandler} for this {@link SettingsItem}. It will be invoked with the
+ * current value once during the registration and every time when the value changes.
*
* @param changeHandler the changeHandler to set
* @since Envoy Client v0.3-alpha
diff --git a/client/src/main/java/envoy/client/data/audio/AudioPlayer.java b/client/src/main/java/envoy/client/data/audio/AudioPlayer.java
index ec0440d..854f6bb 100644
--- a/client/src/main/java/envoy/client/data/audio/AudioPlayer.java
+++ b/client/src/main/java/envoy/client/data/audio/AudioPlayer.java
@@ -22,7 +22,9 @@ public final class AudioPlayer {
*
* @since Envoy Client v0.1-beta
*/
- public AudioPlayer() { this(AudioRecorder.DEFAULT_AUDIO_FORMAT); }
+ public AudioPlayer() {
+ this(AudioRecorder.DEFAULT_AUDIO_FORMAT);
+ }
/**
* Initializes the player with a given audio format.
diff --git a/client/src/main/java/envoy/client/data/audio/AudioRecorder.java b/client/src/main/java/envoy/client/data/audio/AudioRecorder.java
index 38475ab..37c3be2 100644
--- a/client/src/main/java/envoy/client/data/audio/AudioRecorder.java
+++ b/client/src/main/java/envoy/client/data/audio/AudioRecorder.java
@@ -20,7 +20,8 @@ public final class AudioRecorder {
*
* @since Envoy Client v0.1-beta
*/
- public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat(16000, 16, 1, true, false);
+ public static final AudioFormat DEFAULT_AUDIO_FORMAT =
+ new AudioFormat(16000, 16, 1, true, false);
/**
* The format in which audio files will be saved.
@@ -38,7 +39,9 @@ public final class AudioRecorder {
*
* @since Envoy Client v0.1-beta
*/
- public AudioRecorder() { this(DEFAULT_AUDIO_FORMAT); }
+ public AudioRecorder() {
+ this(DEFAULT_AUDIO_FORMAT);
+ }
/**
* Initializes the recorder with a given audio format.
diff --git a/client/src/main/java/envoy/client/data/audio/package-info.java b/client/src/main/java/envoy/client/data/audio/package-info.java
index 5e80e64..569abd9 100644
--- a/client/src/main/java/envoy/client/data/audio/package-info.java
+++ b/client/src/main/java/envoy/client/data/audio/package-info.java
@@ -1,6 +1,6 @@
/**
* Contains classes related to recording and playing back audio clips.
- *
+ *
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
diff --git a/client/src/main/java/envoy/client/data/commands/Callable.java b/client/src/main/java/envoy/client/data/commands/Callable.java
index cd8c146..3e7285e 100644
--- a/client/src/main/java/envoy/client/data/commands/Callable.java
+++ b/client/src/main/java/envoy/client/data/commands/Callable.java
@@ -3,8 +3,7 @@ package envoy.client.data.commands;
import java.util.List;
/**
- * This interface defines an action that should be performed when a system
- * command gets called.
+ * This interface defines an action that should be performed when a system command gets called.
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
@@ -12,11 +11,9 @@ import java.util.List;
public interface Callable {
/**
- * Performs the instance specific action when a {@link SystemCommand} has been
- * called.
+ * Performs the instance specific action when a {@link SystemCommand} has been called.
*
- * @param arguments the arguments that should be passed to the
- * {@link SystemCommand}
+ * @param arguments the arguments that should be passed to the {@link SystemCommand}
* @since Envoy Client v0.2-beta
*/
void call(List arguments);
diff --git a/client/src/main/java/envoy/client/data/commands/SystemCommand.java b/client/src/main/java/envoy/client/data/commands/SystemCommand.java
index 7a788dd..66ba0de 100644
--- a/client/src/main/java/envoy/client/data/commands/SystemCommand.java
+++ b/client/src/main/java/envoy/client/data/commands/SystemCommand.java
@@ -4,15 +4,12 @@ import java.util.*;
import java.util.function.Consumer;
/**
- * This class is the base class of all {@code SystemCommands} and contains an
- * action and a number of arguments that should be used as input for this
- * function.
- * No {@code SystemCommand} can return anything.
- * Every {@code SystemCommand} must have as argument type {@code List}
- * so that the words following the indicator String can be used as input of the
- * function. This approach has one limitation:
- * Order matters! Changing the order of arguments will likely result in
- * unexpected behavior.
+ * This class is the base class of all {@code SystemCommands} and contains an action and a number of
+ * arguments that should be used as input for this function. No {@code SystemCommand} can return
+ * anything. Every {@code SystemCommand} must have as argument type {@code List} so that the
+ * words following the indicator String can be used as input of the function. This approach has one
+ * limitation:
+ * Order matters! Changing the order of arguments will likely result in unexpected behavior.
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
@@ -28,8 +25,8 @@ public final class SystemCommand implements Callable {
/**
* This function takes a {@code List} as argument because automatically
- * {@code SystemCommand#numberOfArguments} words following the necessary command
- * will be put into this list.
+ * {@code SystemCommand#numberOfArguments} words following the necessary command will be put
+ * into this list.
*
* @see String#split(String)
*/
@@ -48,7 +45,8 @@ public final class SystemCommand implements Callable {
* @param description the description of this {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
- public SystemCommand(Consumer> action, int numberOfArguments, List defaults, String description) {
+ public SystemCommand(Consumer> action, int numberOfArguments,
+ List defaults, String description) {
this.numberOfArguments = numberOfArguments;
this.action = action;
this.defaults = defaults == null ? new ArrayList<>() : defaults;
@@ -92,20 +90,27 @@ public final class SystemCommand implements Callable {
public List getDefaults() { return defaults; }
@Override
- public int hashCode() { return Objects.hash(action); }
+ public int hashCode() {
+ return Objects.hash(action);
+ }
@Override
public boolean equals(Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
final var other = (SystemCommand) obj;
return Objects.equals(action, other.action);
}
@Override
public String toString() {
- return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments + ", "
- + (description != null ? "description=" + description + ", " : "") + (defaults != null ? "defaults=" + defaults : "") + "]";
+ return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments
+ + ", "
+ + (description != null ? "description=" + description + ", " : "")
+ + (defaults != null ? "defaults=" + defaults : "") + "]";
}
}
diff --git a/client/src/main/java/envoy/client/data/commands/SystemCommandBuilder.java b/client/src/main/java/envoy/client/data/commands/SystemCommandBuilder.java
index 0768b5f..eb11c3d 100644
--- a/client/src/main/java/envoy/client/data/commands/SystemCommandBuilder.java
+++ b/client/src/main/java/envoy/client/data/commands/SystemCommandBuilder.java
@@ -20,18 +20,21 @@ public final class SystemCommandBuilder {
private final SystemCommandMap commandsMap;
/**
- * Creates a new {@code SystemCommandsBuilder} without underlying
- * {@link SystemCommandMap}.
+ * Creates a new {@code SystemCommandsBuilder} without underlying {@link SystemCommandMap}.
*
* @since Envoy Client v0.2-beta
*/
- public SystemCommandBuilder() { this(null); }
+ public SystemCommandBuilder() {
+ this(null);
+ }
/**
* @param commandsMap the map to use when calling build (optional)
* @since Envoy Client v0.2-beta
*/
- public SystemCommandBuilder(SystemCommandMap commandsMap) { this.commandsMap = commandsMap; }
+ public SystemCommandBuilder(SystemCommandMap commandsMap) {
+ this.commandsMap = commandsMap;
+ }
/**
* @param numberOfArguments the numberOfArguments to set
@@ -104,12 +107,14 @@ public final class SystemCommandBuilder {
* @return the built {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
- public SystemCommand build() { return build(true); }
+ public SystemCommand build() {
+ return build(true);
+ }
/**
* Builds a {@code SystemCommand} based upon the previously entered data.
- * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
- * previous value.
+ * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the previous
+ * value.
* At the end, this {@code SystemCommandBuilder} will be reset.
*
* @return the built {@code SystemCommand}
@@ -122,8 +127,8 @@ public final class SystemCommandBuilder {
/**
* Builds a {@code SystemCommand} based upon the previously entered data.
- * {@code SystemCommand#numberOfArguments} will be set to use the rest of the
- * string as argument, regardless of the previous value.
+ * {@code SystemCommand#numberOfArguments} will be set to use the rest of the string as
+ * argument, regardless of the previous value.
* At the end, this {@code SystemCommandBuilder} will be reset.
*
* @return the built {@code SystemCommand}
@@ -136,27 +141,25 @@ public final class SystemCommandBuilder {
/**
* Builds a {@code SystemCommand} based upon the previously entered data.
- * Automatically adds the built object to the given map.
- * At the end, this {@code SystemCommandBuilder} can be reset but must
- * not be.
+ * Automatically adds the built object to the given map. At the end, this
+ * {@code SystemCommandBuilder} can be reset but must not be.
*
- * @param reset whether this {@code SystemCommandBuilder} should be reset
- * afterwards.
- * This can be useful if another command wants to execute something
- * similar
+ * @param reset whether this {@code SystemCommandBuilder} should be reset afterwards.
+ * This can be useful if another command wants to execute something similar
* @return the built {@code SystemCommand}
* @since Envoy Client v0.2-beta
*/
public SystemCommand build(boolean reset) {
final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
sc.setRelevance(relevance);
- if (reset) reset();
+ if (reset)
+ reset();
return sc;
}
/**
- * Builds a {@code SystemCommand} based upon the previously entered data.
- * Automatically adds the built object to the given map.
+ * Builds a {@code SystemCommand} based upon the previously entered data. Automatically adds the
+ * built object to the given map.
*
* @param command the command under which to store the SystemCommand in the
* {@link SystemCommandMap}
@@ -164,13 +167,14 @@ public final class SystemCommandBuilder {
* @throws NullPointerException if no map has been assigned to this builder
* @since Envoy Client v0.2-beta
*/
- public SystemCommand build(String command) { return build(command, true); }
+ public SystemCommand build(String command) {
+ return build(command, true);
+ }
/**
* Builds a {@code SystemCommand} based upon the previously entered data.
- * Automatically adds the built object to the given map.
- * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
- * previous value.
+ * Automatically adds the built object to the given map. {@code SystemCommand#numberOfArguments}
+ * will be set to 0, regardless of the previous value.
* At the end, this {@code SystemCommandBuilder} will be reset.
*
* @param command the command under which to store the SystemCommand in the
@@ -186,9 +190,8 @@ public final class SystemCommandBuilder {
/**
* Builds a {@code SystemCommand} based upon the previously entered data.
- * Automatically adds the built object to the given map.
- * {@code SystemCommand#numberOfArguments} will be set to use the rest of the
- * string as argument, regardless of the previous value.
+ * Automatically adds the built object to the given map. {@code SystemCommand#numberOfArguments}
+ * will be set to use the rest of the string as argument, regardless of the previous value.
* At the end, this {@code SystemCommandBuilder} will be reset.
*
* @param command the command under which to store the SystemCommand in the
@@ -204,17 +207,13 @@ public final class SystemCommandBuilder {
/**
* Builds a {@code SystemCommand} based upon the previously entered data.
- * Automatically adds the built object to the given map.
- * At the end, this {@code SystemCommandBuilder} can be reset but must
- * not be.
+ * Automatically adds the built object to the given map. At the end, this
+ * {@code SystemCommandBuilder} can be reset but must not be.
*
* @param command the command under which to store the SystemCommand in the
* {@link SystemCommandMap}
- * @param reset whether this {@code SystemCommandBuilder} should be reset
- * afterwards.
- * This can be useful if another command wants to execute
- * something
- * similar
+ * @param reset whether this {@code SystemCommandBuilder} should be reset afterwards.
+ * This can be useful if another command wants to execute something similar
* @return the built {@code SystemCommand}
* @throws NullPointerException if no map has been assigned to this builder
* @since Envoy Client v0.2-beta
@@ -222,9 +221,12 @@ public final class SystemCommandBuilder {
public SystemCommand build(String command, boolean reset) {
final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
sc.setRelevance(relevance);
- if (commandsMap != null) commandsMap.add(command, sc);
- else throw new NullPointerException("No map in SystemCommandsBuilder present");
- if (reset) reset();
+ if (commandsMap != null)
+ commandsMap.add(command, sc);
+ else
+ throw new NullPointerException("No map in SystemCommandsBuilder present");
+ if (reset)
+ reset();
return sc;
}
}
diff --git a/client/src/main/java/envoy/client/data/commands/SystemCommandMap.java b/client/src/main/java/envoy/client/data/commands/SystemCommandMap.java
index 831de0d..1d2f635 100644
--- a/client/src/main/java/envoy/client/data/commands/SystemCommandMap.java
+++ b/client/src/main/java/envoy/client/data/commands/SystemCommandMap.java
@@ -14,11 +14,9 @@ import javafx.scene.control.Alert.AlertType;
import envoy.util.EnvoyLog;
/**
- * Stores all {@link SystemCommand}s used.
- * SystemCommands can be called using an activator char and the text that needs
- * to be present behind the activator.
- * Additionally offers the option to request recommendations for a partial input
- * String.
+ * Stores all {@link SystemCommand}s used. SystemCommands can be called using an activator char and
+ * the text that needs to be present behind the activator. Additionally offers the option to request
+ * recommendations for a partial input String.
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
@@ -27,96 +25,100 @@ public final class SystemCommandMap {
private final Character activator;
private final Map systemCommands = new HashMap<>();
- private final Pattern commandPattern = Pattern.compile("^[a-zA-Z0-9_:!/\\(\\)\\?\\.\\,\\;\\-]+$");
+ private final Pattern commandPattern =
+ Pattern.compile("^[a-zA-Z0-9_:!/\\(\\)\\?\\.\\,\\;\\-]+$");
private static final Logger logger = EnvoyLog.getLogger(SystemCommandMap.class);
/**
- * Creates a new {@code SystemCommandMap} with the given char as activator.
- * If this Character is null, any text used as input will be treated as a system
- * command.
+ * Creates a new {@code SystemCommandMap} with the given char as activator. If this Character is
+ * null, any text used as input will be treated as a system command.
*
* @param activator the char to use as activator for commands
* @since Envoy Client v0.3-beta
*/
- public SystemCommandMap(Character activator) { this.activator = activator; }
+ public SystemCommandMap(Character activator) {
+ this.activator = activator;
+ }
/**
* Creates a new {@code SystemCommandMap} with '/' as activator.
*
* @since Envoy Client v0.3-beta
*/
- public SystemCommandMap() { activator = '/'; }
+ public SystemCommandMap() {
+ activator = '/';
+ }
/**
* Adds a new command to the map if the command name is valid.
*
- * @param command the input string to execute the
- * given action
- * @param systemCommand the command to add - can be built using
- * {@link SystemCommandBuilder}
+ * @param command the input string to execute the given action
+ * @param systemCommand the command to add - can be built using {@link SystemCommandBuilder}
* @see SystemCommandMap#isValidKey(String)
* @since Envoy Client v0.2-beta
*/
public void add(String command, SystemCommand systemCommand) {
- if (isValidKey(command)) systemCommands.put(command.toLowerCase(), systemCommand);
+ if (isValidKey(command))
+ systemCommands.put(command.toLowerCase(), systemCommand);
}
/**
- * This method checks if the input String is a key in the map and returns the
- * wrapped System command if present.
+ * This method checks if the input String is a key in the map and returns the wrapped System
+ * command if present.
*
* Usage example:
* {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}
- * {@code systemCommands.add("example", new SystemCommand(text -> {}, 1, null,
- * ""));}
+ * {@code systemCommands.add("example", new SystemCommand(text -> {}, 1, null, ""));}
* {@code ....}
* user input: {@code "*example xyz ..."}
* {@code systemCommands.get("example xyz ...")} or
- * {@code systemCommands.get("*example xyz ...")}
- * result: {@code Optional.get() != null}
+ * {@code systemCommands.get("*example xyz ...")} result:
+ * {@code Optional.get() != null}
*
* @param input the input string given by the user
* @return the wrapped system command, if present
* @since Envoy Client v0.2-beta
*/
- public Optional get(String input) { return Optional.ofNullable(systemCommands.get(getCommand(input.toLowerCase()))); }
+ public Optional get(String input) {
+ return Optional.ofNullable(systemCommands.get(getCommand(input.toLowerCase())));
+ }
/**
- * This method ensures that the activator of a {@link SystemCommand} is
- * stripped.
- * It only checks the word beginning from the first non-blank position in the
- * input.
- * It returns the command as (most likely) entered as key in the map for the
- * first word of the text.
+ * This method ensures that the activator of a {@link SystemCommand} is stripped.
+ * It only checks the word beginning from the first non-blank position in the input. It returns
+ * the command as (most likely) entered as key in the map for the first word of the text.
* Activators in the middle of the word will be disregarded.
*
* @param raw the input
* @return the command as entered in the map
* @since Envoy Client v0.2-beta
- * @apiNote this method will (most likely) not return anything useful if
- * whatever is entered after the activator is not a system command.
- * Only exception: for recommendation purposes.
+ * @apiNote this method will (most likely) not return anything useful if whatever is entered
+ * after the activator is not a system command. Only exception: for recommendation
+ * purposes.
*/
public String getCommand(String raw) {
final var trimmed = raw.stripLeading();
// Entering only the activator should not throw an error
- if (trimmed.length() == 1 && activator != null && activator.equals(trimmed.charAt(0))) return "";
+ if (trimmed.length() == 1 && activator != null && activator.equals(trimmed.charAt(0)))
+ return "";
else {
final var index = trimmed.indexOf(' ');
- return trimmed.substring(activator != null && activator.equals(trimmed.charAt(0)) ? 1 : 0, index < 1 ? trimmed.length() : index);
+ return trimmed.substring(
+ activator != null && activator.equals(trimmed.charAt(0)) ? 1 : 0,
+ index < 1 ? trimmed.length() : index);
}
}
/**
- * Examines whether a key can be put in the map and logs it with
- * {@code Level.WARNING} if that key violates API constrictions.
+ * Examines whether a key can be put in the map and logs it with {@code Level.WARNING} if that
+ * key violates API constrictions.
* (allowed chars are a-zA-Z0-9_:!/()?.,;-)
*
- * The approach to not throw an exception was taken so that an ugly try-catch
- * block for every addition to the system commands map could be avoided, an
- * error that should only occur during implementation and not in production.
+ * The approach to not throw an exception was taken so that an ugly try-catch block for every
+ * addition to the system commands map could be avoided, an error that should only occur during
+ * implementation and not in production.
*
* @param command the key to examine
* @return whether this key can be used in the map
@@ -124,17 +126,18 @@ public final class SystemCommandMap {
*/
public boolean isValidKey(String command) {
final var valid = commandPattern.matcher(command).matches();
- if (!valid) logger.log(Level.WARNING,
+ if (!valid)
+ logger.log(Level.WARNING,
"The command \"" + command
- + "\" is not valid. As it might cause problems when executed, it will not be entered into the map. Only the characters "
- + commandPattern + "are allowed");
+ + "\" is not valid. As it might cause problems when executed, it will not be entered into the map. Only the characters "
+ + commandPattern + "are allowed");
return valid;
}
/**
- * Takes a 'raw' string (the whole input) and checks if the activator is the
- * first visible character and then checks if a command is present after that
- * activator. If that is the case, it will be executed.
+ * Takes a 'raw' string (the whole input) and checks if the activator is the first visible
+ * character and then checks if a command is present after that activator. If that is the case,
+ * it will be executed.
*
* @param raw the raw input string
* @return whether a command could be found and successfully executed
@@ -144,24 +147,26 @@ public final class SystemCommandMap {
// possibly a command was detected and could be executed
final var raw2 = raw.stripLeading();
- final var commandFound = activator == null || raw2.startsWith(activator.toString()) ? executeAvailableCommand(raw2) : false;
+ final var commandFound = activator == null || raw2.startsWith(activator.toString())
+ ? executeAvailableCommand(raw2)
+ : false;
// the command was executed successfully - no further checking needed
- if (commandFound) logger.log(Level.FINE, "executed system command " + getCommand(raw2));
+ if (commandFound)
+ logger.log(Level.FINE, "executed system command " + getCommand(raw2));
return commandFound;
}
/**
* Retrieves the recommendations based on the current input entered.
- * The first word is used for the recommendations and
- * it does not matter if the activator is at its beginning or not.
+ * The first word is used for the recommendations and it does not matter if the activator is at
+ * its beginning or not.
* If recommendations are present, the given function will be executed on the
* recommendations.
* Otherwise nothing will be done.
*
* @param input the input string
- * @param action the action that should be taken for the recommendations, if any
- * are present
+ * @param action the action that should be taken for the recommendations, if any are present
* @since Envoy Client v0.2-beta
*/
public void requestRecommendations(String input, Consumer> action) {
@@ -169,27 +174,28 @@ public final class SystemCommandMap {
// Get the expected commands
final var recommendations = recommendCommands(partialCommand);
- if (recommendations.isEmpty()) return;
+ if (recommendations.isEmpty())
+ return;
// Execute the given action
- else action.accept(recommendations);
+ else
+ action.accept(recommendations);
}
/**
- * This method checks if the input String is a key in the map and executes the
- * wrapped System command if present.
+ * This method checks if the input String is a key in the map and executes the wrapped System
+ * command if present.
*
* Usage example:
* {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}
* {@code Button button = new Button();}
- * {@code systemCommands.add("example", new SystemCommand(text ->
- * {button.setText(text.get(0))}, 1, null,
- * ""));}
+ * {@code systemCommands.add("example", new SystemCommand(text -> {button.setText(text.get(0))},
+ * 1, null, ""));}
* {@code ....}
* user input: {@code "*example xyz ..."}
* {@code systemCommands.executeIfPresent("example xyz ...")} or
- * {@code systemCommands.executeIfPresent("*example xyz ...")}
- * result: {@code button.getText()=="xyz"}
+ * {@code systemCommands.executeIfPresent("*example xyz ...")} result:
+ * {@code button.getText()=="xyz"}
*
* @param input the input string given by the user
* @return whether a command could be found and successfully executed
@@ -211,9 +217,9 @@ public final class SystemCommandMap {
systemCommand.call(arguments);
} catch (final NumberFormatException e) {
logger.log(Level.INFO,
- String.format(
- "System command %s could not be performed correctly because the user is a dumbass and could not write a parseable number.",
- command));
+ String.format(
+ "System command %s could not be performed correctly because the user is a dumbass and could not write a parseable number.",
+ command));
Platform.runLater(() -> {
final var alert = new Alert(AlertType.ERROR);
alert.setContentText("Please enter a readable number as argument.");
@@ -224,7 +230,8 @@ public final class SystemCommandMap {
logger.log(Level.WARNING, "System command " + command + " threw an exception: ", e);
Platform.runLater(() -> {
final var alert = new Alert(AlertType.ERROR);
- alert.setContentText("Could not execute system command: Internal error. Please insult the responsible programmer.");
+ alert.setContentText(
+ "Could not execute system command: Internal error. Please insult the responsible programmer.");
alert.showAndWait();
});
commandExecuted.set(false);
@@ -245,7 +252,8 @@ public final class SystemCommandMap {
// no more arguments follow after the command (e.g. text = "/DABR")
final var indexOfSpace = input.indexOf(" ");
- if (indexOfSpace < 0) return supplementDefaults(new String[] {}, systemCommand);
+ if (indexOfSpace < 0)
+ return supplementDefaults(new String[] {}, systemCommand);
// the arguments behind a system command
final var remainingString = input.substring(indexOfSpace + 1);
@@ -253,15 +261,17 @@ public final class SystemCommandMap {
// splitting those arguments and supplying default values
final var textArguments = remainingString.split(" ", -1);
- final var originalArguments = numberOfArguments >= 0 ? Arrays.copyOfRange(textArguments, 0, numberOfArguments) : textArguments;
+ final var originalArguments =
+ numberOfArguments >= 0 ? Arrays.copyOfRange(textArguments, 0, numberOfArguments)
+ : textArguments;
final var arguments = supplementDefaults(originalArguments, systemCommand);
return arguments;
}
/**
* Recommends commands based upon the currently entered input.
- * In the current implementation, all that gets checked is whether a key
- * contains this input. This might be updated later on.
+ * In the current implementation, all that gets checked is whether a key contains this input.
+ * This might be updated later on.
*
* @param partialCommand the partially entered command
* @return a set of all commands that match this input
@@ -274,36 +284,41 @@ public final class SystemCommandMap {
return systemCommands.keySet()
.stream()
.filter(command -> command.contains(partialCommand))
- .sorted((command1, command2) -> Integer.compare(systemCommands.get(command1).getRelevance(), systemCommands.get(command2).getRelevance()))
+ .sorted(
+ (command1, command2) -> Integer.compare(systemCommands.get(command1).getRelevance(),
+ systemCommands.get(command2).getRelevance()))
.collect(Collectors.toSet());
}
/**
- * Supplies the default values for arguments if none are present in the text for
- * any argument.
+ * Supplies the default values for arguments if none are present in the text for any argument.
+ *
*
* @param textArguments the arguments that were parsed from the text
* @param toEvaluate the system command whose default values should be used
* @return the final argument list
* @since Envoy Client v0.2-beta
- * @apiNote this method will insert an empty String if the size of the list
- * given to the {@code SystemCommand} is smaller than its argument
- * counter and no more text arguments could be found.
+ * @apiNote this method will insert an empty String if the size of the list given to the
+ * {@code SystemCommand} is smaller than its argument counter and no more text
+ * arguments could be found.
*/
private List supplementDefaults(String[] textArguments, SystemCommand toEvaluate) {
final var defaults = toEvaluate.getDefaults();
final var numberOfArguments = toEvaluate.getNumberOfArguments();
final List result = new ArrayList<>();
- if (toEvaluate.getNumberOfArguments() > 0) for (var index = 0; index < numberOfArguments; index++) {
- String textArg = null;
- if (index < textArguments.length) textArg = textArguments[index];
+ if (toEvaluate.getNumberOfArguments() > 0)
+ for (var index = 0; index < numberOfArguments; index++) {
+ String textArg = null;
+ if (index < textArguments.length)
+ textArg = textArguments[index];
- // Set the argument at position index to the current argument of the text, if it
- // is present. Otherwise the default for that argument will be taken if present.
- // In the worst case, an empty String will be used.
- result.add(!(textArg == null) && !textArg.isBlank() ? textArg : index < defaults.size() ? defaults.get(index) : "");
- }
+ // Set the argument at position index to the current argument of the text, if it
+ // is present. Otherwise the default for that argument will be taken if present.
+ // In the worst case, an empty String will be used.
+ result.add(!(textArg == null) && !textArg.isBlank() ? textArg
+ : index < defaults.size() ? defaults.get(index) : "");
+ }
return result;
}
diff --git a/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java b/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java
index 14a110c..44504e1 100644
--- a/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java
+++ b/client/src/main/java/envoy/client/data/shortcuts/EnvoyShortcutConfig.java
@@ -2,6 +2,8 @@ package envoy.client.data.shortcuts;
import javafx.scene.input.*;
+import envoy.data.User.UserStatus;
+
import envoy.client.data.Context;
import envoy.client.helper.ShutdownHelper;
import envoy.client.ui.SceneContext.SceneInfo;
@@ -28,17 +30,48 @@ public class EnvoyShortcutConfig {
// Add the option to exit with "Control" + "Q" or "Alt" + "F4" as offered by
// some desktop environments
- instance.add(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN), ShutdownHelper::exit);
+ instance.add(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN),
+ ShutdownHelper::exit);
// Add the option to logout using "Control"+"Shift"+"L" if not in login scene
- instance.addForNotExcluded(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN),
- UserUtil::logout,
- SceneInfo.LOGIN_SCENE);
+ instance.addForNotExcluded(
+ new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN,
+ KeyCombination.SHIFT_DOWN),
+ UserUtil::logout,
+ SceneInfo.LOGIN_SCENE);
// Add option to open settings scene with "Control"+"S", if not in login scene
instance.addForNotExcluded(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN),
- () -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE),
- SceneInfo.SETTINGS_SCENE,
- SceneInfo.LOGIN_SCENE);
+ () -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE),
+ SceneInfo.SETTINGS_SCENE,
+ SceneInfo.LOGIN_SCENE);
+
+ // Add option to change to status away
+ instance.addForNotExcluded(
+ new KeyCodeCombination(KeyCode.A, KeyCombination.CONTROL_DOWN,
+ KeyCombination.SHIFT_DOWN),
+ () -> UserUtil.changeStatus(UserStatus.AWAY),
+ SceneInfo.LOGIN_SCENE);
+
+ // Add option to change to status busy
+ instance.addForNotExcluded(
+ new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN,
+ KeyCombination.SHIFT_DOWN),
+ () -> UserUtil.changeStatus(UserStatus.BUSY),
+ SceneInfo.LOGIN_SCENE);
+
+ // Add option to change to status offline
+ instance.addForNotExcluded(
+ new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN,
+ KeyCombination.SHIFT_DOWN),
+ () -> UserUtil.changeStatus(UserStatus.OFFLINE),
+ SceneInfo.LOGIN_SCENE);
+
+ // Add option to change to status online
+ instance.addForNotExcluded(
+ new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN,
+ KeyCombination.SHIFT_DOWN),
+ () -> UserUtil.changeStatus(UserStatus.ONLINE),
+ SceneInfo.LOGIN_SCENE);
}
}
diff --git a/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java b/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java
index c3643ee..05ef3b7 100644
--- a/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java
+++ b/client/src/main/java/envoy/client/data/shortcuts/GlobalKeyShortcuts.java
@@ -14,7 +14,8 @@ import envoy.client.ui.SceneContext.SceneInfo;
*/
public final class GlobalKeyShortcuts {
- private final EnumMap> shortcuts = new EnumMap<>(SceneInfo.class);
+ private final EnumMap> shortcuts =
+ new EnumMap<>(SceneInfo.class);
private static GlobalKeyShortcuts instance = new GlobalKeyShortcuts();
@@ -36,16 +37,16 @@ public final class GlobalKeyShortcuts {
* @param action the action to perform
* @since Envoy Client v0.3-beta
*/
- public void add(KeyCombination keys, Runnable action) { shortcuts.values().forEach(collection -> collection.put(keys, action)); }
+ public void add(KeyCombination keys, Runnable action) {
+ shortcuts.values().forEach(collection -> collection.put(keys, action));
+ }
/**
- * Adds the given keyboard shortcut and its action to all scenes that are not
- * part of exclude.
+ * Adds the given keyboard shortcut and its action to all scenes that are not part of exclude.
*
* @param keys the keys to press to perform the given action
* @param action the action to perform
- * @param exclude the scenes that should be excluded from receiving this
- * keyboard shortcut
+ * @param exclude the scenes that should be excluded from receiving this keyboard shortcut
* @since Envoy Client v0.3-beta
*/
public void addForNotExcluded(KeyCombination keys, Runnable action, SceneInfo... exclude) {
@@ -53,10 +54,10 @@ public final class GlobalKeyShortcuts {
// Computing the remaining sceneInfos
final var include = new SceneInfo[SceneInfo.values().length - exclude.length];
int index = 0;
- outer:
- for (final var sceneInfo : SceneInfo.values()) {
+ outer: for (final var sceneInfo : SceneInfo.values()) {
for (final var excluded : exclude)
- if (sceneInfo.equals(excluded)) continue outer;
+ if (sceneInfo.equals(excluded))
+ continue outer;
include[index++] = sceneInfo;
}
@@ -72,5 +73,7 @@ public final class GlobalKeyShortcuts {
* @return all stored keyboard shortcuts for this scene
* @since Envoy Client v0.3-beta
*/
- public Map getKeyboardShortcuts(SceneInfo sceneInfo) { return shortcuts.get(sceneInfo); }
+ public Map getKeyboardShortcuts(SceneInfo sceneInfo) {
+ return shortcuts.get(sceneInfo);
+ }
}
diff --git a/client/src/main/java/envoy/client/data/shortcuts/KeyboardMapping.java b/client/src/main/java/envoy/client/data/shortcuts/KeyboardMapping.java
index 4d56f8e..6666f61 100644
--- a/client/src/main/java/envoy/client/data/shortcuts/KeyboardMapping.java
+++ b/client/src/main/java/envoy/client/data/shortcuts/KeyboardMapping.java
@@ -7,10 +7,9 @@ import javafx.scene.input.KeyCombination;
import envoy.client.ui.SceneContext;
/**
- * Provides methods to set the keyboard shortcuts for a specific scene.
- * Should only be implemented by controllers of scenes so that these methods can
- * automatically be called inside {@link SceneContext} as soon
- * as the underlying FXML file has been loaded.
+ * Provides methods to set the keyboard shortcuts for a specific scene. Should only be implemented
+ * by controllers of scenes so that these methods can automatically be called inside
+ * {@link SceneContext} as soon as the underlying FXML file has been loaded.
*
* @author Leon Hofmeister
* @since Envoy Client v0.3-beta
diff --git a/client/src/main/java/envoy/client/event/ContactDisabled.java b/client/src/main/java/envoy/client/event/ContactDisabled.java
new file mode 100644
index 0000000..4b73a92
--- /dev/null
+++ b/client/src/main/java/envoy/client/event/ContactDisabled.java
@@ -0,0 +1,23 @@
+package envoy.client.event;
+
+import envoy.data.Contact;
+import envoy.event.Event;
+
+/**
+ * Signifies that the chat of a contact should be disabled.
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.3-beta
+ */
+public class ContactDisabled extends Event {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param contact the contact that should be disabled
+ * @since Envoy Client v0.3-beta
+ */
+ public ContactDisabled(Contact contact) {
+ super(contact);
+ }
+}
diff --git a/client/src/main/java/envoy/client/event/EnvoyCloseEvent.java b/client/src/main/java/envoy/client/event/EnvoyCloseEvent.java
index dfe15ec..c7266a8 100644
--- a/client/src/main/java/envoy/client/event/EnvoyCloseEvent.java
+++ b/client/src/main/java/envoy/client/event/EnvoyCloseEvent.java
@@ -3,9 +3,8 @@ package envoy.client.event;
import envoy.event.Event.Valueless;
/**
- * This event notifies various Envoy components of the application being about
- * to shut down. This allows the graceful closing of connections, persisting
- * local data etc.
+ * This event notifies various Envoy components of the application being about to shut down. This
+ * allows the graceful closing of connections, persisting local data etc.
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
diff --git a/client/src/main/java/envoy/client/event/MessageDeletion.java b/client/src/main/java/envoy/client/event/MessageDeletion.java
index e3f5ada..46a435a 100644
--- a/client/src/main/java/envoy/client/event/MessageDeletion.java
+++ b/client/src/main/java/envoy/client/event/MessageDeletion.java
@@ -16,5 +16,7 @@ public class MessageDeletion extends Event {
* @param messageID the ID of the deleted message
* @since Envoy Common v0.3-beta
*/
- public MessageDeletion(long messageID) { super(messageID); }
+ public MessageDeletion(long messageID) {
+ super(messageID);
+ }
}
diff --git a/client/src/main/java/envoy/client/event/OwnStatusChange.java b/client/src/main/java/envoy/client/event/OwnStatusChange.java
index 8962d6f..3c144ef 100644
--- a/client/src/main/java/envoy/client/event/OwnStatusChange.java
+++ b/client/src/main/java/envoy/client/event/OwnStatusChange.java
@@ -17,5 +17,7 @@ public class OwnStatusChange extends Event {
* @param value the new user status of the client user
* @since Envoy Client v0.3-beta
*/
- public OwnStatusChange(UserStatus value) { super(value); }
+ public OwnStatusChange(UserStatus value) {
+ super(value);
+ }
}
diff --git a/client/src/main/java/envoy/client/helper/AlertHelper.java b/client/src/main/java/envoy/client/helper/AlertHelper.java
index 6b053f7..71c8cfb 100644
--- a/client/src/main/java/envoy/client/helper/AlertHelper.java
+++ b/client/src/main/java/envoy/client/helper/AlertHelper.java
@@ -15,20 +15,19 @@ public final class AlertHelper {
private AlertHelper() {}
/**
- * Asks for a confirmation dialog if {@link Settings#isAskForConfirmation()}
- * returns {@code true}.
- * Immediately executes the action if no dialog was requested or the dialog was
- * exited with a confirmation.
- * Does nothing if the dialog was closed without clicking on OK.
+ * Asks for a confirmation dialog if {@link Settings#isAskForConfirmation()} returns
+ * {@code true}. Immediately executes the action if no dialog was requested or the dialog was
+ * exited with a confirmation. Does nothing if the dialog was closed without clicking on OK.
*
- * @param alert the (customized) alert to show. Should not be shown
- * already
+ * @param alert the (customized) alert to show. Should not be shown already
* @param action the action to perform in case of success
* @since Envoy Client v0.2-beta
*/
public static void confirmAction(Alert alert, Runnable action) {
alert.setHeaderText("");
- if (Settings.getInstance().isAskForConfirmation()) alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
- else action.run();
+ if (Settings.getInstance().isAskForConfirmation())
+ alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
+ else
+ action.run();
}
}
diff --git a/client/src/main/java/envoy/client/helper/ShutdownHelper.java b/client/src/main/java/envoy/client/helper/ShutdownHelper.java
index 0371f50..8997736 100644
--- a/client/src/main/java/envoy/client/helper/ShutdownHelper.java
+++ b/client/src/main/java/envoy/client/helper/ShutdownHelper.java
@@ -1,11 +1,11 @@
package envoy.client.helper;
+import dev.kske.eventbus.EventBus;
+
import envoy.client.data.*;
import envoy.client.event.EnvoyCloseEvent;
import envoy.client.ui.StatusTrayIcon;
-import dev.kske.eventbus.EventBus;
-
/**
* Simplifies shutdown actions.
*
@@ -22,18 +22,21 @@ public final class ShutdownHelper {
*
* @since Envoy Client v0.2-beta
*/
- public static void exit() { exit(false); }
+ public static void exit() {
+ exit(false);
+ }
/**
- * Exits Envoy immediately if {@code force = true},
- * else it can exit or minimize Envoy, depending on the current state of
- * {@link Settings#isHideOnClose()} and {@link StatusTrayIcon#isSupported()}.
+ * Exits Envoy immediately if {@code force = true}, else it can exit or minimize Envoy,
+ * depending on the current state of {@link Settings#isHideOnClose()} and
+ * {@link StatusTrayIcon#isSupported()}.
*
* @param force whether to close in any case.
* @since Envoy Client v0.2-beta
*/
public static void exit(boolean force) {
- if (!force && Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported()) Context.getInstance().getStage().setIconified(true);
+ if (!force && Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported())
+ Context.getInstance().getStage().setIconified(true);
else {
EventBus.getInstance().dispatch(new EnvoyCloseEvent());
System.exit(0);
diff --git a/client/src/main/java/envoy/client/net/Client.java b/client/src/main/java/envoy/client/net/Client.java
index 0db43ef..fe1c9e2 100644
--- a/client/src/main/java/envoy/client/net/Client.java
+++ b/client/src/main/java/envoy/client/net/Client.java
@@ -5,18 +5,19 @@ import java.net.Socket;
import java.util.concurrent.TimeoutException;
import java.util.logging.*;
-import envoy.client.data.*;
-import envoy.client.event.EnvoyCloseEvent;
+import dev.kske.eventbus.*;
+import dev.kske.eventbus.Event;
+
import envoy.data.*;
import envoy.event.*;
import envoy.util.*;
-import dev.kske.eventbus.*;
-import dev.kske.eventbus.Event;
+import envoy.client.data.*;
+import envoy.client.event.EnvoyCloseEvent;
/**
- * Establishes a connection to the server, performs a handshake and delivers
- * certain objects to the server.
+ * Establishes a connection to the server, performs a handshake and delivers certain objects to the
+ * server.
*
* @author Kai S. K. Engelbart
* @author Maximilian Käfer
@@ -44,26 +45,31 @@ public final class Client implements EventListener, Closeable {
*
* @since Envoy Client v0.2-beta
*/
- public Client() { eventBus.registerListener(this); }
+ public Client() {
+ eventBus.registerListener(this);
+ }
/**
- * Enters the online mode by acquiring a user ID from the server. As a
- * connection has to be established and a handshake has to be made, this method
- * will block for up to 5 seconds. If the handshake does exceed this time limit,
- * an exception is thrown.
+ * Enters the online mode by acquiring a user ID from the server. As a connection has to be
+ * established and a handshake has to be made, this method will block for up to 5 seconds. If
+ * the handshake does exceed this time limit, an exception is thrown.
*
* @param credentials the login credentials of the user
* @param cacheMap the map of all caches needed
* @throws TimeoutException if the server could not be reached
* @throws IOException if the login credentials could not be written
- * @throws InterruptedException if the current thread is interrupted while
- * waiting for the handshake response
+ * @throws InterruptedException if the current thread is interrupted while waiting for the
+ * handshake response
*/
- public void performHandshake(LoginCredentials credentials, CacheMap cacheMap) throws TimeoutException, IOException, InterruptedException {
- if (online) throw new IllegalStateException("Handshake has already been performed successfully");
+ public void performHandshake(LoginCredentials credentials, CacheMap cacheMap)
+ throws TimeoutException, IOException, InterruptedException {
+ if (online)
+ throw new IllegalStateException("Handshake has already been performed successfully");
+ rejected = false;
// Establish TCP connection
- logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
+ logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...",
+ config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort());
logger.log(Level.FINE, "Successfully established TCP connection to server");
@@ -75,8 +81,6 @@ public final class Client implements EventListener, Closeable {
receiver.registerProcessor(User.class, sender -> this.sender = sender);
receiver.registerProcessors(cacheMap.getMap());
- rejected = false;
-
// Start receiver
receiver.start();
@@ -95,7 +99,10 @@ public final class Client implements EventListener, Closeable {
return;
}
- if (System.currentTimeMillis() - start > 5000) throw new TimeoutException("Did not log in after 5 seconds");
+ if (System.currentTimeMillis() - start > 5000) {
+ rejected = true;
+ throw new TimeoutException("Did not log in after 5 seconds");
+ }
Thread.sleep(500);
}
@@ -104,14 +111,12 @@ public final class Client implements EventListener, Closeable {
}
/**
- * Initializes the {@link Receiver} used to process data sent from the server to
- * this client.
+ * Initializes the {@link Receiver} used to process data sent from the server to this client.
*
- * @param localDB the local database used to persist the current
- * {@link IDGenerator}
+ * @param localDB the local database used to persist the current {@link IDGenerator}
* @param cacheMap the map of all caches needed
- * @throws IOException if no {@link IDGenerator} is present and none could be
- * requested from the server
+ * @throws IOException if no {@link IDGenerator} is present and none could be requested from the
+ * server
* @since Envoy Client v0.2-alpha
*/
public void initReceiver(LocalDB localDB, CacheMap cacheMap) throws IOException {
@@ -127,7 +132,8 @@ public final class Client implements EventListener, Closeable {
cacheMap.get(GroupMessageStatusChange.class).setProcessor(eventBus::dispatch);
// Request a generator if none is present or the existing one is consumed
- if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIDGenerator();
+ if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext())
+ requestIDGenerator();
// Relay caches
cacheMap.getMap().values().forEach(Cache::relay);
@@ -146,14 +152,14 @@ public final class Client implements EventListener, Closeable {
logger.log(Level.FINE, "Sending " + obj);
try {
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
- } catch (IOException e) {
+ } catch (final IOException e) {
throw new RuntimeException(e);
}
}
/**
- * Sends a message to the server. The message's status will be incremented once
- * it was delivered successfully.
+ * Sends a message to the server. The message's status will be incremented once it was delivered
+ * successfully.
*
* @param message the message to send
* @since Envoy Client v0.3-alpha
@@ -174,10 +180,12 @@ public final class Client implements EventListener, Closeable {
}
@Event(eventType = HandshakeRejection.class, priority = 1000)
- private void onHandshakeRejection() { rejected = true; }
+ private void onHandshakeRejection() {
+ rejected = true;
+ }
@Override
- @Event(eventType = EnvoyCloseEvent.class, priority = 800)
+ @Event(eventType = EnvoyCloseEvent.class, priority = 50)
public void close() {
if (online) {
logger.log(Level.INFO, "Closing connection...");
@@ -199,7 +207,10 @@ public final class Client implements EventListener, Closeable {
* @throws IllegalStateException if the client is not online
* @since Envoy Client v0.3-alpha
*/
- private void checkOnline() throws IllegalStateException { if (!online) throw new IllegalStateException("Client is not online"); }
+ private void checkOnline() throws IllegalStateException {
+ if (!online)
+ throw new IllegalStateException("Client is not online");
+ }
/**
* @return the {@link User} as which this client is logged in
diff --git a/client/src/main/java/envoy/client/net/Receiver.java b/client/src/main/java/envoy/client/net/Receiver.java
index 81e84fb..84d1e22 100644
--- a/client/src/main/java/envoy/client/net/Receiver.java
+++ b/client/src/main/java/envoy/client/net/Receiver.java
@@ -6,13 +6,12 @@ import java.util.*;
import java.util.function.Consumer;
import java.util.logging.*;
-import envoy.util.*;
-
import dev.kske.eventbus.*;
+import envoy.util.*;
+
/**
- * Receives objects from the server and passes them to processor objects based
- * on their class.
+ * Receives objects from the server and passes them to processor objects based on their class.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
@@ -40,8 +39,7 @@ public final class Receiver extends Thread {
}
/**
- * Starts the receiver loop. When an object is read, it is passed to the
- * appropriate processor.
+ * Starts the receiver loop. When an object is read, it is passed to the appropriate processor.
*
* @since Envoy Client v0.3-alpha
*/
@@ -66,15 +64,19 @@ public final class Receiver extends Thread {
// Server has stopped sending, i.e. because he went offline
if (bytesRead == -1) {
isAlive = false;
- logger.log(Level.INFO, "Lost connection to the server. Exiting receiver...");
+ logger.log(Level.INFO,
+ "Lost connection to the server. Exiting receiver...");
continue;
}
logger.log(Level.WARNING,
- String.format("LV encoding violated: expected %d bytes, received %d bytes. Discarding object...", len, bytesRead));
+ String.format(
+ "LV encoding violated: expected %d bytes, received %d bytes. Discarding object...",
+ len, bytesRead));
continue;
}
- try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
+ try (ObjectInputStream oin =
+ new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
final Object obj = oin.readObject();
logger.log(Level.FINE, "Received " + obj);
@@ -83,12 +85,17 @@ public final class Receiver extends Thread {
final Consumer processor = processors.get(obj.getClass());
// Dispatch to the processor if present
- if (processor != null) processor.accept(obj);
+ if (processor != null)
+ processor.accept(obj);
// Dispatch to the event bus if the object is an event without a processor
- else if (obj instanceof IEvent) eventBus.dispatch((IEvent) obj);
+ else if (obj instanceof IEvent)
+ eventBus.dispatch((IEvent) obj);
// Notify if no processor could be located
- else logger.log(Level.WARNING,
- String.format("The received object has the %s for which no processor is defined.", obj.getClass()));
+ else
+ logger.log(Level.WARNING,
+ String.format(
+ "The received object has the %s for which no processor is defined.",
+ obj.getClass()));
}
} catch (final SocketException | EOFException e) {
// Connection probably closed by client.
@@ -100,14 +107,16 @@ public final class Receiver extends Thread {
}
/**
- * Adds an object processor to this {@link Receiver}. It will be called once an
- * object of the accepted class has been received.
+ * Adds an object processor to this {@link Receiver}. It will be called once an object of the
+ * accepted class has been received.
*
* @param processorClass the object class accepted by the processor
* @param processor the object processor
* @since Envoy Client v0.3-alpha
*/
- public void registerProcessor(Class processorClass, Consumer processor) { processors.put(processorClass, processor); }
+ public void registerProcessor(Class processorClass, Consumer processor) {
+ processors.put(processorClass, processor);
+ }
/**
* Adds a map of object processors to this {@link Receiver}.
@@ -115,12 +124,16 @@ public final class Receiver extends Thread {
* @param processors the processors to add the processors to add
* @since Envoy Client v0.1-beta
*/
- public void registerProcessors(Map, ? extends Consumer>> processors) { this.processors.putAll(processors); }
+ public void registerProcessors(Map, ? extends Consumer>> processors) {
+ this.processors.putAll(processors);
+ }
/**
* Removes all object processors registered at this {@link Receiver}.
*
* @since Envoy Client v0.3-alpha
*/
- public void removeAllProcessors() { processors.clear(); }
+ public void removeAllProcessors() {
+ processors.clear();
+ }
}
diff --git a/client/src/main/java/envoy/client/net/WriteProxy.java b/client/src/main/java/envoy/client/net/WriteProxy.java
index 5ffd056..5cf62b5 100644
--- a/client/src/main/java/envoy/client/net/WriteProxy.java
+++ b/client/src/main/java/envoy/client/net/WriteProxy.java
@@ -2,15 +2,15 @@ package envoy.client.net;
import java.util.logging.*;
-import envoy.client.data.*;
import envoy.data.Message;
import envoy.event.MessageStatusChange;
import envoy.util.EnvoyLog;
+import envoy.client.data.*;
+
/**
- * Implements methods to send {@link Message}s and
- * {@link MessageStatusChange}s to the server or cache them inside a
- * {@link LocalDB} depending on the online status.
+ * Implements methods to send {@link Message}s and {@link MessageStatusChange}s to the server or
+ * cache them inside a {@link LocalDB} depending on the online status.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
@@ -23,12 +23,11 @@ public final class WriteProxy {
private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class);
/**
- * Initializes a write proxy using a client and a local database. The
- * corresponding cache processors are injected into the caches.
+ * Initializes a write proxy using a client and a local database. The corresponding cache
+ * processors are injected into the caches.
*
* @param client the client instance used to send messages and events if online
- * @param localDB the local database used to cache messages and events if
- * offline
+ * @param localDB the local database used to cache messages and events if offline
* @since Envoy Client v0.3-alpha
*/
public WriteProxy(Client client, LocalDB localDB) {
@@ -47,34 +46,39 @@ public final class WriteProxy {
}
/**
- * Sends cached {@link Message}s and {@link MessageStatusChange}s to the
- * server.
+ * Sends cached {@link Message}s and {@link MessageStatusChange}s to the server.
*
* @since Envoy Client v0.3-alpha
*/
- public void flushCache() { localDB.getCacheMap().getMap().values().forEach(Cache::relay); }
+ public void flushCache() {
+ localDB.getCacheMap().getMap().values().forEach(Cache::relay);
+ }
/**
- * Delivers a message to the server if online. Otherwise the message is cached
- * inside the local database.
+ * Delivers a message to the server if online. Otherwise the message is cached inside the local
+ * database.
*
* @param message the message to send
* @since Envoy Client v0.3-alpha
*/
public void writeMessage(Message message) {
- if (client.isOnline()) client.sendMessage(message);
- else localDB.getCacheMap().getApplicable(Message.class).accept(message);
+ if (client.isOnline())
+ client.sendMessage(message);
+ else
+ localDB.getCacheMap().getApplicable(Message.class).accept(message);
}
/**
- * Delivers a message status change event to the server if online. Otherwise the
- * event is cached inside the local database.
+ * Delivers a message status change event to the server if online. Otherwise the event is cached
+ * inside the local database.
*
* @param evt the event to send
* @since Envoy Client v0.3-alpha
*/
public void writeMessageStatusChange(MessageStatusChange evt) {
- if (client.isOnline()) client.send(evt);
- else localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
+ if (client.isOnline())
+ client.send(evt);
+ else
+ localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
}
}
diff --git a/client/src/main/java/envoy/client/ui/Restorable.java b/client/src/main/java/envoy/client/ui/Restorable.java
index 11d2a90..a7d404d 100644
--- a/client/src/main/java/envoy/client/ui/Restorable.java
+++ b/client/src/main/java/envoy/client/ui/Restorable.java
@@ -1,8 +1,8 @@
package envoy.client.ui;
/**
- * This interface defines an action that should be performed when a scene gets
- * restored from the scene stack in {@link SceneContext}.
+ * This interface defines an action that should be performed when a scene gets restored from the
+ * scene stack in {@link SceneContext}.
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
@@ -12,8 +12,7 @@ public interface Restorable {
/**
* This method is getting called when a scene gets restored.
- * Hence, it can contain anything that should be done when the underlying scene
- * gets restored.
+ * Hence, it can contain anything that should be done when the underlying scene gets restored.
*
* @since Envoy Client v0.1-beta
*/
diff --git a/client/src/main/java/envoy/client/ui/SceneContext.java b/client/src/main/java/envoy/client/ui/SceneContext.java
index 70bb662..8e42c33 100644
--- a/client/src/main/java/envoy/client/ui/SceneContext.java
+++ b/client/src/main/java/envoy/client/ui/SceneContext.java
@@ -9,20 +9,19 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.stage.Stage;
+import dev.kske.eventbus.*;
+
+import envoy.util.EnvoyLog;
+
import envoy.client.data.Settings;
import envoy.client.data.shortcuts.*;
import envoy.client.event.*;
-import envoy.util.EnvoyLog;
-
-import dev.kske.eventbus.*;
/**
- * Manages a stack of scenes. The most recently added scene is displayed inside
- * a stage. When a scene is removed from the stack, its predecessor is
- * displayed.
+ * Manages a stack of scenes. The most recently added scene is displayed inside a stage. When a
+ * scene is removed from the stack, its predecessor is displayed.
*
- * When a scene is loaded, the style sheet for the current theme is applied to
- * it.
+ * When a scene is loaded, the style sheet for the current theme is applied to it.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
@@ -63,7 +62,9 @@ public final class SceneContext implements EventListener {
*/
public final String path;
- SceneInfo(String path) { this.path = path; }
+ SceneInfo(String path) {
+ this.path = path;
+ }
}
private final Stage stage;
@@ -97,7 +98,8 @@ public final class SceneContext implements EventListener {
loader.setController(null);
try {
- final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
+ final var rootNode =
+ (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
final var scene = new Scene(rootNode);
final var controller = loader.getController();
controllerStack.push(controller);
@@ -106,10 +108,13 @@ public final class SceneContext implements EventListener {
stage.setScene(scene);
// Supply the global custom keyboard shortcuts for that scene
- scene.getAccelerators().putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(sceneInfo));
+ scene.getAccelerators()
+ .putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(sceneInfo));
// Supply the scene specific keyboard shortcuts
- if (controller instanceof KeyboardMapping) scene.getAccelerators().putAll(((KeyboardMapping) controller).getKeyboardShortcuts());
+ if (controller instanceof KeyboardMapping)
+ scene.getAccelerators()
+ .putAll(((KeyboardMapping) controller).getKeyboardShortcuts());
// The LoginScene is the only scene not intended to be resized
// As strange as it seems, this is needed as otherwise the LoginScene won't be
@@ -119,7 +124,8 @@ public final class SceneContext implements EventListener {
applyCSS();
stage.show();
} catch (final IOException e) {
- EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE, String.format("Could not load scene for %s: ", sceneInfo), e);
+ EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE,
+ String.format("Could not load scene for %s: ", sceneInfo), e);
throw new RuntimeException(e);
}
}
@@ -144,7 +150,8 @@ public final class SceneContext implements EventListener {
// If the controller implements the Restorable interface,
// the actions to perform on restoration will be executed here
final var controller = controllerStack.peek();
- if (controller instanceof Restorable) ((Restorable) controller).onRestore();
+ if (controller instanceof Restorable)
+ ((Restorable) controller).onRestore();
}
stage.show();
}
@@ -154,7 +161,8 @@ public final class SceneContext implements EventListener {
final var styleSheets = stage.getScene().getStylesheets();
final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css";
styleSheets.clear();
- styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), getClass().getResource(themeCSS).toExternalForm());
+ styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(),
+ getClass().getResource(themeCSS).toExternalForm());
}
}
@@ -165,7 +173,9 @@ public final class SceneContext implements EventListener {
}
@Event(priority = 150, eventType = ThemeChangeEvent.class)
- private void onThemeChange() { applyCSS(); }
+ private void onThemeChange() {
+ applyCSS();
+ }
/**
* @param the type of the controller
diff --git a/client/src/main/java/envoy/client/ui/Startup.java b/client/src/main/java/envoy/client/ui/Startup.java
index a93bf81..76fa54b 100644
--- a/client/src/main/java/envoy/client/ui/Startup.java
+++ b/client/src/main/java/envoy/client/ui/Startup.java
@@ -10,6 +10,12 @@ import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.stage.Stage;
+import envoy.data.*;
+import envoy.data.User.UserStatus;
+import envoy.event.*;
+import envoy.exception.EnvoyException;
+import envoy.util.EnvoyLog;
+
import envoy.client.data.*;
import envoy.client.data.shortcuts.EnvoyShortcutConfig;
import envoy.client.helper.ShutdownHelper;
@@ -17,11 +23,6 @@ import envoy.client.net.Client;
import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.LoginScene;
import envoy.client.util.IconUtil;
-import envoy.data.*;
-import envoy.data.User.UserStatus;
-import envoy.event.*;
-import envoy.exception.EnvoyException;
-import envoy.util.EnvoyLog;
/**
* Handles application startup.
@@ -47,8 +48,8 @@ public final class Startup extends Application {
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
/**
- * Loads the configuration, initializes the client and the local database and
- * delegates the rest of the startup process to {@link LoginScene}.
+ * Loads the configuration, initializes the client and the local database and delegates the rest
+ * of the startup process to {@link LoginScene}.
*
* @since Envoy Client v0.1-beta
*/
@@ -57,7 +58,8 @@ public final class Startup extends Application {
// Initialize config and logger
try {
- config.loadAll(Startup.class, "client.properties", getParameters().getRaw().toArray(new String[0]));
+ config.loadAll(Startup.class, "client.properties",
+ getParameters().getRaw().toArray(new String[0]));
EnvoyLog.initialize(config);
} catch (final IllegalStateException e) {
new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
@@ -97,7 +99,8 @@ public final class Startup extends Application {
logger.info("Attempting authentication with token...");
localDB.loadUserData();
if (!performHandshake(
- LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync())))
+ LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(),
+ VERSION, localDB.getLastSync())))
sceneContext.load(SceneInfo.LOGIN_SCENE);
} else
// Load login scene
@@ -117,7 +120,8 @@ public final class Startup extends Application {
cacheMap.put(GroupMessage.class, new Cache());
cacheMap.put(MessageStatusChange.class, new Cache());
cacheMap.put(GroupMessageStatusChange.class, new Cache());
- final var originalStatus = localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
+ final var originalStatus =
+ localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
try {
client.performHandshake(credentials, cacheMap);
if (client.isOnline()) {
@@ -127,7 +131,8 @@ public final class Startup extends Application {
loadChatScene();
client.initReceiver(localDB, cacheMap);
return true;
- } else return false;
+ } else
+ return false;
} catch (IOException | InterruptedException | TimeoutException e) {
logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
return attemptOfflineMode(credentials.getIdentifier());
@@ -135,8 +140,8 @@ public final class Startup extends Application {
}
/**
- * Attempts to load {@link envoy.client.ui.controller.ChatScene} in offline mode
- * for a given user.
+ * Attempts to load {@link envoy.client.ui.controller.ChatScene} in offline mode for a given
+ * user.
*
* @param identifier the identifier of the user - currently his username
* @return whether the offline mode could be entered
@@ -146,7 +151,8 @@ public final class Startup extends Application {
try {
// Try entering offline mode
final User clientUser = localDB.getUsers().get(identifier);
- if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
+ if (clientUser == null)
+ throw new EnvoyException("Could not enter offline mode: user name unknown");
client.setSender(clientUser);
loadChatScene();
return true;
@@ -187,7 +193,9 @@ public final class Startup extends Application {
} catch (final FileNotFoundException e) {
// The local database file has not yet been created, probably first login
} catch (final Exception e) {
- new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait();
+ new Alert(AlertType.ERROR,
+ "Error while loading local database: " + e + "\nChats will not be stored locally.")
+ .showAndWait();
logger.log(Level.WARNING, "Could not load local database: ", e);
}
@@ -197,7 +205,8 @@ public final class Startup extends Application {
context.getWriteProxy().flushCache();
// Inform the server that this user has a different user status than expected
- if (!user.getStatus().equals(UserStatus.ONLINE)) client.send(new UserStatusChange(user));
+ if (!user.getStatus().equals(UserStatus.ONLINE))
+ client.send(new UserStatusChange(user));
} else
// Set all contacts to offline mode
@@ -211,7 +220,8 @@ public final class Startup extends Application {
final var stage = context.getStage();
// Pop LoginScene if present
- if (!context.getSceneContext().isEmpty()) context.getSceneContext().pop();
+ if (!context.getSceneContext().isEmpty())
+ context.getSceneContext().pop();
// Load ChatScene
stage.setMinHeight(400);
@@ -221,15 +231,21 @@ public final class Startup extends Application {
// Exit or minimize the stage when a close request occurs
stage.setOnCloseRequest(
- e -> { ShutdownHelper.exit(); if (Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported()) e.consume(); });
+ e -> {
+ ShutdownHelper.exit();
+ if (Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported())
+ e.consume();
+ });
if (StatusTrayIcon.isSupported()) {
// Initialize status tray icon
final var trayIcon = new StatusTrayIcon(stage);
Settings.getInstance().getItems().get("hideOnClose").setChangeHandler(c -> {
- if ((Boolean) c) trayIcon.show();
- else trayIcon.hide();
+ if ((Boolean) c)
+ trayIcon.show();
+ else
+ trayIcon.hide();
});
}
diff --git a/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java b/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java
index 475d210..680074a 100644
--- a/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java
+++ b/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java
@@ -8,19 +8,19 @@ import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.skin.VirtualFlow;
+import envoy.data.Message;
+import envoy.data.User.UserStatus;
+import envoy.util.EnvoyLog;
+
import envoy.client.data.Context;
import envoy.client.data.commands.*;
import envoy.client.helper.ShutdownHelper;
import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.ChatScene;
import envoy.client.util.*;
-import envoy.data.Message;
-import envoy.data.User.UserStatus;
-import envoy.util.EnvoyLog;
/**
- * Contains all {@link SystemCommand}s used for
- * {@link envoy.client.ui.controller.ChatScene}.
+ * Contains all {@link SystemCommand}s used for {@link envoy.client.ui.controller.ChatScene}.
*
* @author Leon Hofmeister
* @since Envoy Client v0.3-beta
@@ -29,12 +29,13 @@ public final class ChatSceneCommands {
private final ListView messageList;
private final SystemCommandMap messageTextAreaCommands = new SystemCommandMap();
- private final SystemCommandBuilder builder = new SystemCommandBuilder(messageTextAreaCommands);
+ private final SystemCommandBuilder builder =
+ new SystemCommandBuilder(messageTextAreaCommands);
- private static final String messageDependantCommandDescription = " the given message. Use s/S to use the selected message. Otherwise expects a number relative to the uppermost completely visible message.";
+ private static final String messageDependantCommandDescription =
+ " the given message. Use s/S to use the selected message. Otherwise expects a number relative to the uppermost completely visible message.";
/**
- *
* @param messageList the message list to use for some commands
* @param chatScene the instance of {@code ChatScene} that uses this object
* @since Envoy Client v0.3-beta
@@ -43,25 +44,33 @@ public final class ChatSceneCommands {
this.messageList = messageList;
// Error message initialization
- builder.setAction(text -> { throw new RuntimeException(); }).setDescription("Shows an error message.").buildNoArg("error");
+ builder.setAction(text -> { throw new RuntimeException(); })
+ .setDescription("Shows an error message.").buildNoArg("error");
// Do A Barrel roll initialization
final var random = new Random();
- builder.setAction(text -> chatScene.doABarrelRoll(Integer.parseInt(text.get(0)), Double.parseDouble(text.get(1))))
- .setDefaults(Integer.toString(random.nextInt(3) + 1), Double.toString(random.nextDouble() * 3 + 1))
+ builder
+ .setAction(text -> chatScene.doABarrelRoll(Integer.parseInt(text.get(0)),
+ Double.parseDouble(text.get(1))))
+ .setDefaults(Integer.toString(random.nextInt(3) + 1),
+ Double.toString(random.nextDouble() * 3 + 1))
.setDescription("See for yourself :)")
.setNumberOfArguments(2)
.build("dabr");
// Logout initialization
- builder.setAction(text -> UserUtil.logout()).setDescription("Logs you out.").buildNoArg("logout");
+ builder.setAction(text -> UserUtil.logout()).setDescription("Logs you out.")
+ .buildNoArg("logout");
// Exit initialization
- builder.setAction(text -> ShutdownHelper.exit()).setNumberOfArguments(0).setDescription("Exits the program.").build("exit", false);
+ builder.setAction(text -> ShutdownHelper.exit()).setNumberOfArguments(0)
+ .setDescription("Exits the program.").build("exit", false);
builder.build("q");
// Open settings scene initialization
- builder.setAction(text -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE))
+ builder
+ .setAction(
+ text -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE))
.setDescription("Opens the settings screen")
.buildNoArg("settings");
@@ -74,81 +83,106 @@ public final class ChatSceneCommands {
alert.setContentText("Please provide an existing status");
alert.showAndWait();
}
- }).setDescription("Changes your status to the given status.").setNumberOfArguments(1).setDefaults("").build("status");
+ }).setDescription("Changes your status to the given status.").setNumberOfArguments(1)
+ .setDefaults("").build("status");
// Selection of a new message initialization
messageDependantAction("s",
- m -> { messageList.getSelectionModel().clearSelection(); messageList.getSelectionModel().select(m); },
- m -> true,
- "Selects");
+ m -> {
+ messageList.getSelectionModel().clearSelection();
+ messageList.getSelectionModel().select(m);
+ },
+ m -> true,
+ "Selects");
// Copy text of selection initialization
- messageDependantAction("cp", MessageUtil::copyMessageText, m -> !m.getText().isEmpty(), "Copies the text of");
+ messageDependantAction("cp", MessageUtil::copyMessageText, m -> !m.getText().isEmpty(),
+ "Copies the text of");
// Delete selection initialization
messageDependantAction("del", MessageUtil::deleteMessage, m -> true, "Deletes");
// Save attachment of selection initialization
- messageDependantAction("save-att", MessageUtil::saveAttachment, Message::hasAttachment, "Saves the attachment of");
+ messageDependantAction("save-att", MessageUtil::saveAttachment, Message::hasAttachment,
+ "Saves the attachment of");
}
- private void messageDependantAction(String command, Consumer action, Predicate additionalCheck, String description) {
+ private void messageDependantAction(String command, Consumer action,
+ Predicate additionalCheck, String description) {
builder.setAction(text -> {
final var positionalArgument = text.get(0).toLowerCase();
// the currently selected message was requested
if (positionalArgument.startsWith("s")) {
- final var relativeString = positionalArgument.length() == 1 ? "" : positionalArgument.substring(1);
+ final var relativeString =
+ positionalArgument.length() == 1 ? "" : positionalArgument.substring(1);
// Only s has been used as input
if (positionalArgument.length() == 1) {
final var selectedMessage = messageList.getSelectionModel().getSelectedItem();
- if (selectedMessage != null && additionalCheck.test(selectedMessage)) action.accept(selectedMessage);
+ if (selectedMessage != null && additionalCheck.test(selectedMessage))
+ action.accept(selectedMessage);
return;
// Either s++ or s-- has been requested
- } else if (relativeString.equals("++") || relativeString.equals("--")) selectionNeighbor(action, additionalCheck, positionalArgument);
+ } else if (relativeString.equals("++") || relativeString.equals("--"))
+ selectionNeighbor(action, additionalCheck, positionalArgument);
// A message relative to the currently selected message should be used (i.e.
// s+4)
- else useRelativeMessage(command, action, additionalCheck, relativeString, true);
+ else
+ useRelativeMessage(command, action, additionalCheck, relativeString, true);
// Either ++s or --s has been requested
} else if (positionalArgument.equals("--s") || positionalArgument.equals("++s"))
selectionNeighbor(action, additionalCheck, positionalArgument);
// Just a number is expected: ((+)4)
- else useRelativeMessage(command, action, additionalCheck, positionalArgument, false);
- }).setDefaults("s").setNumberOfArguments(1).setDescription(description.concat(messageDependantCommandDescription)).build(command);
+ else
+ useRelativeMessage(command, action, additionalCheck, positionalArgument, false);
+ }).setDefaults("s").setNumberOfArguments(1)
+ .setDescription(description.concat(messageDependantCommandDescription)).build(command);
}
- private void selectionNeighbor(Consumer action, Predicate additionalCheck, final String positionalArgument) {
- final var wantedIndex = messageList.getSelectionModel().getSelectedIndex() + (positionalArgument.contains("+") ? 1 : -1);
+ private void selectionNeighbor(Consumer action, Predicate additionalCheck,
+ final String positionalArgument) {
+ final var wantedIndex = messageList.getSelectionModel().getSelectedIndex()
+ + (positionalArgument.contains("+") ? 1 : -1);
messageList.getSelectionModel().clearAndSelect(wantedIndex);
final var selectedMessage = messageList.getItems().get(wantedIndex);
- if (selectedMessage != null && additionalCheck.test(selectedMessage)) action.accept(selectedMessage);
+ if (selectedMessage != null && additionalCheck.test(selectedMessage))
+ action.accept(selectedMessage);
}
- private void useRelativeMessage(String command, Consumer action, Predicate additionalCheck, final String positionalArgument,
- boolean useSelectedMessage) throws NumberFormatException {
- final var stripPlus = positionalArgument.startsWith("+") ? positionalArgument.substring(1) : positionalArgument;
+ private void useRelativeMessage(String command, Consumer action,
+ Predicate additionalCheck, final String positionalArgument,
+ boolean useSelectedMessage) throws NumberFormatException {
+ final var stripPlus =
+ positionalArgument.startsWith("+") ? positionalArgument.substring(1)
+ : positionalArgument;
final var incDec = Integer.valueOf(stripPlus);
try {
// The currently selected message is the base message
if (useSelectedMessage) {
- final var messageToUse = messageList.getItems().get(messageList.getSelectionModel().getSelectedIndex() + incDec);
- if (messageToUse != null && additionalCheck.test(messageToUse)) action.accept(messageToUse);
+ final var messageToUse = messageList.getItems()
+ .get(messageList.getSelectionModel().getSelectedIndex() + incDec);
+ if (messageToUse != null && additionalCheck.test(messageToUse))
+ action.accept(messageToUse);
// The currently upmost completely visible message is the base message
} else {
final var messageToUse = messageList.getItems()
- .get(((VirtualFlow>) messageList.lookup(".virtual-flow")).getFirstVisibleCell().getIndex() + 1 + incDec);
- if (messageToUse != null && additionalCheck.test(messageToUse)) action.accept(messageToUse);
+ .get(((VirtualFlow>) messageList.lookup(".virtual-flow"))
+ .getFirstVisibleCell().getIndex() + 1 + incDec);
+ if (messageToUse != null && additionalCheck.test(messageToUse))
+ action.accept(messageToUse);
}
} catch (final IndexOutOfBoundsException e) {
EnvoyLog.getLogger(ChatSceneCommands.class)
- .log(Level.INFO, " A non-existing message was requested by the user for System command " + command);
+ .log(Level.INFO,
+ " A non-existing message was requested by the user for System command "
+ + command);
}
}
diff --git a/client/src/main/java/envoy/client/ui/chatscene/TextInputContextMenu.java b/client/src/main/java/envoy/client/ui/chatscene/TextInputContextMenu.java
index e11b6ca..b653240 100644
--- a/client/src/main/java/envoy/client/ui/chatscene/TextInputContextMenu.java
+++ b/client/src/main/java/envoy/client/ui/chatscene/TextInputContextMenu.java
@@ -7,8 +7,8 @@ import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
/**
- * Displays a context menu that offers an additional option when one of
- * its menu items has been clicked.
+ * Displays a context menu that offers an additional option when one of its menu items has been
+ * clicked.
*
* Current options are:
*
@@ -24,9 +24,8 @@ import javafx.scene.input.Clipboard;
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
- * @apiNote please refrain from using
- * {@link ContextMenu#setOnShowing(EventHandler)} as this is already
- * used by this component
+ * @apiNote please refrain from using {@link ContextMenu#setOnShowing(EventHandler)} as this is
+ * already used by this component
*/
public class TextInputContextMenu extends ContextMenu {
@@ -40,8 +39,8 @@ public class TextInputContextMenu extends ContextMenu {
private final MenuItem selectAllMI = new MenuItem("Select all");
/**
- * Creates a new {@code TextInputContextMenu} with an optional action when
- * this menu was clicked. Currently shows:
+ * Creates a new {@code TextInputContextMenu} with an optional action when this menu was
+ * clicked. Currently shows:
*
*
undo
*
redo
@@ -53,14 +52,12 @@ public class TextInputContextMenu extends ContextMenu {
*
Select all
*
*
- * @param control the text input component to display this
- * {@code ContextMenu}
- * @param menuItemClicked the second action to perform when a menu item of this
- * context menu has been clicked
+ * @param control the text input component to display this {@code ContextMenu}
+ * @param menuItemClicked the second action to perform when a menu item of this context menu has
+ * been clicked
* @since Envoy Client v0.2-beta
- * @apiNote please refrain from using
- * {@link ContextMenu#setOnShowing(EventHandler)} as this is already
- * used by this component
+ * @apiNote please refrain from using {@link ContextMenu#setOnShowing(EventHandler)} as this is
+ * already used by this component
*/
public TextInputContextMenu(TextInputControl control, Consumer menuItemClicked) {
@@ -100,7 +97,11 @@ public class TextInputContextMenu extends ContextMenu {
getItems().add(selectAllMI);
}
- private EventHandler addAction(Consumer originalAction, Consumer additionalAction) {
- return e -> { originalAction.accept(e); additionalAction.accept(e); };
+ private EventHandler addAction(Consumer originalAction,
+ Consumer additionalAction) {
+ return e -> {
+ originalAction.accept(e);
+ additionalAction.accept(e);
+ };
}
}
diff --git a/client/src/main/java/envoy/client/ui/chatscene/package-info.java b/client/src/main/java/envoy/client/ui/chatscene/package-info.java
index 12938c6..24b78b0 100644
--- a/client/src/main/java/envoy/client/ui/chatscene/package-info.java
+++ b/client/src/main/java/envoy/client/ui/chatscene/package-info.java
@@ -1,6 +1,6 @@
/**
* Contains classes that influence the appearance and behavior of ChatScene.
- *
+ *
* @author Leon Hofmeister
* @since Envoy Client v0.3-beta
*/
diff --git a/client/src/main/java/envoy/client/ui/control/AudioControl.java b/client/src/main/java/envoy/client/ui/control/AudioControl.java
index fc2263b..75e6076 100644
--- a/client/src/main/java/envoy/client/ui/control/AudioControl.java
+++ b/client/src/main/java/envoy/client/ui/control/AudioControl.java
@@ -6,10 +6,11 @@ import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.layout.HBox;
-import envoy.client.data.audio.AudioPlayer;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
+import envoy.client.data.audio.AudioPlayer;
+
/**
* Enables the play back of audio clips through a button.
*
@@ -18,7 +19,7 @@ import envoy.util.EnvoyLog;
*/
public final class AudioControl extends HBox {
- private AudioPlayer player = new AudioPlayer();
+ private final AudioPlayer player = new AudioPlayer();
private static final Logger logger = EnvoyLog.getLogger(AudioControl.class);
diff --git a/client/src/main/java/envoy/client/ui/control/ChatControl.java b/client/src/main/java/envoy/client/ui/control/ChatControl.java
index 353bdaf..de50085 100644
--- a/client/src/main/java/envoy/client/ui/control/ChatControl.java
+++ b/client/src/main/java/envoy/client/ui/control/ChatControl.java
@@ -9,8 +9,8 @@ import envoy.client.data.*;
import envoy.client.util.IconUtil;
/**
- * Displays a chat using a contact control for the recipient and a label for the
- * unread message count.
+ * Displays a chat using a contact control for the recipient and a label for the unread message
+ * count.
*
* @see ContactControl
* @author Leon Hofmeister
@@ -19,7 +19,7 @@ import envoy.client.util.IconUtil;
public final class ChatControl extends HBox {
private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
- groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
+ groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
/**
* Creates a new {@code ChatControl}.
@@ -32,7 +32,8 @@ public final class ChatControl extends HBox {
setPadding(new Insets(0, 0, 3, 0));
// Profile picture
- final var contactProfilePic = new ProfilePicImageView(chat instanceof GroupChat ? groupIcon : userIcon, 32);
+ final var contactProfilePic =
+ new ProfilePicImageView(chat instanceof GroupChat ? groupIcon : userIcon, 32);
getChildren().add(contactProfilePic);
// Spacing
diff --git a/client/src/main/java/envoy/client/ui/control/ContactControl.java b/client/src/main/java/envoy/client/ui/control/ContactControl.java
index 93b33d1..a4bec8a 100644
--- a/client/src/main/java/envoy/client/ui/control/ContactControl.java
+++ b/client/src/main/java/envoy/client/ui/control/ContactControl.java
@@ -6,9 +6,8 @@ import javafx.scene.layout.VBox;
import envoy.data.*;
/**
- * Displays information about a contact in two rows. The first row contains the
- * name. The second row contains the online status (user) or the member count
- * (group).
+ * Displays information about a contact in two rows. The first row contains the name. The second row
+ * contains the online status (user) or the member count (group).
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-beta
@@ -29,23 +28,24 @@ public final class ContactControl extends VBox {
getChildren().add(nameLabel);
// Online status (user) or member count (group)
- getChildren().add(contact instanceof User ? new UserStatusLabel((User) contact) : new GroupSizeLabel((Group) contact));
+ getChildren().add(contact instanceof User ? new UserStatusLabel((User) contact)
+ : new GroupSizeLabel((Group) contact));
getStyleClass().add("list-element");
}
/**
- * Replaces the info label of this {@code ContactControl} with an updated
- * version.
+ * Replaces the info label of this {@code ContactControl} with an updated version.
*
- * This method should be called when the status of the underlying user or the
- * size of the underlying group has changed.
+ * This method should be called when the status of the underlying user or the size of the
+ * underlying group has changed.
*
* @since Envoy Client v0.3-beta
- * @apiNote will produce buggy results if contact control gets updated so that
- * the info label is no longer on index 1.
+ * @apiNote will produce buggy results if contact control gets updated so that the info label is
+ * no longer on index 1.
*/
public void replaceInfoLabel() {
- getChildren().set(1, contact instanceof User ? new UserStatusLabel((User) contact) : new GroupSizeLabel((Group) contact));
+ getChildren().set(1, contact instanceof User ? new UserStatusLabel((User) contact)
+ : new GroupSizeLabel((Group) contact));
}
}
diff --git a/client/src/main/java/envoy/client/ui/control/GroupSizeLabel.java b/client/src/main/java/envoy/client/ui/control/GroupSizeLabel.java
index 8413744..b6e284c 100644
--- a/client/src/main/java/envoy/client/ui/control/GroupSizeLabel.java
+++ b/client/src/main/java/envoy/client/ui/control/GroupSizeLabel.java
@@ -16,5 +16,8 @@ public final class GroupSizeLabel extends Label {
* @param recipient the group whose members to show
* @since Envoy Client v0.3-beta
*/
- public GroupSizeLabel(Group recipient) { super(recipient.getContacts().size() + " members"); }
+ public GroupSizeLabel(Group recipient) {
+ super(recipient.getContacts().size() + " member"
+ + (recipient.getContacts().size() != 1 ? "s" : ""));
+ }
}
diff --git a/client/src/main/java/envoy/client/ui/control/MessageControl.java b/client/src/main/java/envoy/client/ui/control/MessageControl.java
index 0cd14c4..8a947eb 100644
--- a/client/src/main/java/envoy/client/ui/control/MessageControl.java
+++ b/client/src/main/java/envoy/client/ui/control/MessageControl.java
@@ -11,13 +11,14 @@ import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
-import envoy.client.data.*;
-import envoy.client.net.Client;
-import envoy.client.util.*;
import envoy.data.*;
import envoy.data.Message.MessageStatus;
import envoy.util.EnvoyLog;
+import envoy.client.data.*;
+import envoy.client.net.Client;
+import envoy.client.util.*;
+
/**
* This class transforms a single {@link Message} into a UI component.
*
@@ -33,13 +34,15 @@ public final class MessageControl extends Label {
private final Client client = context.getClient();
private static final Context context = Context.getInstance();
- private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
- .withZone(ZoneId.systemDefault());
- private static final Map statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
- private static final Logger logger = EnvoyLog.getLogger(MessageControl.class);
+ private static final DateTimeFormatter dateFormat =
+ DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
+ .withZone(ZoneId.systemDefault());
+ private static final Map statusImages =
+ IconUtil.loadByEnum(MessageStatus.class, 16);
+ private static final Logger logger =
+ EnvoyLog.getLogger(MessageControl.class);
/**
- *
* @param message the message that should be formatted
* @since Envoy Client v0.1-beta
*/
@@ -107,7 +110,9 @@ public final class MessageControl extends Label {
switch (message.getAttachment().getType()) {
case PICTURE:
vbox.getChildren()
- .add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true)));
+ .add(new ImageView(
+ new Image(new ByteArrayInputStream(message.getAttachment().getData()),
+ 256, 256, true, true)));
break;
case VIDEO:
break;
@@ -138,7 +143,8 @@ public final class MessageControl extends Label {
hBoxBottom.setAlignment(Pos.BOTTOM_RIGHT);
getStyleClass().add("own-message");
hbox.setAlignment(Pos.CENTER_RIGHT);
- } else getStyleClass().add("received-message");
+ } else
+ getStyleClass().add("received-message");
vbox.getChildren().add(hBoxBottom);
// Adjusting height and weight of the cell to the corresponding ListView
paddingProperty().setValue(new Insets(5, 20, 5, 20));
@@ -146,11 +152,13 @@ public final class MessageControl extends Label {
setGraphic(vbox);
}
- private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); }
+ private void loadMessageInfoScene(Message message) {
+ logger.log(Level.FINEST, "message info scene was requested for " + message);
+ }
/**
- * @return whether the message stored by this {@code MessageControl} has been
- * sent by this user of Envoy
+ * @return whether the message stored by this {@code MessageControl} has been sent by this user
+ * of Envoy
* @since Envoy Client v0.1-beta
*/
public boolean isOwnMessage() { return ownMessage; }
diff --git a/client/src/main/java/envoy/client/ui/control/ProfilePicImageView.java b/client/src/main/java/envoy/client/ui/control/ProfilePicImageView.java
index 778b026..1cfa5d5 100644
--- a/client/src/main/java/envoy/client/ui/control/ProfilePicImageView.java
+++ b/client/src/main/java/envoy/client/ui/control/ProfilePicImageView.java
@@ -16,7 +16,9 @@ public final class ProfilePicImageView extends ImageView {
*
* @since Envoy Client v0.2-beta
*/
- public ProfilePicImageView() { this(null); }
+ public ProfilePicImageView() {
+ this(null);
+ }
/**
* Creates a new {@code ProfilePicImageView}.
@@ -24,17 +26,20 @@ public final class ProfilePicImageView extends ImageView {
* @param image the image to display
* @since Envoy Client v0.2-beta
*/
- public ProfilePicImageView(Image image) { this(image, 40); }
+ public ProfilePicImageView(Image image) {
+ this(image, 40);
+ }
/**
* Creates a new {@code ProfilePicImageView}.
*
* @param image the image to display
- * @param sizeAndRounding the size and rounding for a circular
- * {@code ProfilePicImageView}
+ * @param sizeAndRounding the size and rounding for a circular {@code ProfilePicImageView}
* @since Envoy Client v0.2-beta
*/
- public ProfilePicImageView(Image image, double sizeAndRounding) { this(image, sizeAndRounding, sizeAndRounding); }
+ public ProfilePicImageView(Image image, double sizeAndRounding) {
+ this(image, sizeAndRounding, sizeAndRounding);
+ }
/**
* Creates a new {@code ProfilePicImageView}.
diff --git a/client/src/main/java/envoy/client/ui/control/QuickSelectControl.java b/client/src/main/java/envoy/client/ui/control/QuickSelectControl.java
index 0e1b2e5..b9c0f39 100644
--- a/client/src/main/java/envoy/client/ui/control/QuickSelectControl.java
+++ b/client/src/main/java/envoy/client/ui/control/QuickSelectControl.java
@@ -8,25 +8,26 @@ import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.shape.Rectangle;
+import envoy.data.User;
+
import envoy.client.util.IconUtil;
-import envoy.data.*;
/**
- * Displays an {@link User} as a quick select control which is used in the
- * quick select list.
- *
+ * Displays an {@link User} as a quick select control which is used in the quick select list.
+ *
* @author Maximilian Käfer
* @since Envoy Client v0.3-beta
*/
public class QuickSelectControl extends VBox {
- private User user;
+ private final User user;
/**
* Creates an instance of the {@code QuickSelectControl}.
- *
- * @param user the contact whose data is used to create this instance.
- * @param action the action to perform when a contact is removed with this control as a parameter
+ *
+ * @param user the contact whose data is used to create this instance.
+ * @param action the action to perform when a contact is removed with this control as a
+ * parameter
* @since Envoy Client v0.3-beta
*/
public QuickSelectControl(User user, Consumer action) {
@@ -44,7 +45,8 @@ public class QuickSelectControl extends VBox {
picHold.setPrefHeight(35);
picHold.setMaxHeight(35);
picHold.setMinHeight(35);
- var contactProfilePic = new ImageView(IconUtil.loadIconThemeSensitive("user_icon", 32));
+ var contactProfilePic =
+ new ImageView(IconUtil.loadIconThemeSensitive("user_icon", 32));
final var clip = new Rectangle();
clip.setWidth(32);
clip.setHeight(32);
diff --git a/client/src/main/java/envoy/client/ui/controller/ChatScene.java b/client/src/main/java/envoy/client/ui/controller/ChatScene.java
index c2b0ef1..c7d6034 100644
--- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java
+++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java
@@ -24,6 +24,17 @@ import javafx.scene.shape.Rectangle;
import javafx.stage.FileChooser;
import javafx.util.Duration;
+import dev.kske.eventbus.*;
+import dev.kske.eventbus.Event;
+
+import envoy.data.*;
+import envoy.data.Attachment.AttachmentType;
+import envoy.data.Message.MessageStatus;
+import envoy.event.*;
+import envoy.event.contact.UserOperation;
+import envoy.exception.EnvoyException;
+import envoy.util.EnvoyLog;
+
import envoy.client.data.*;
import envoy.client.data.audio.AudioRecorder;
import envoy.client.event.*;
@@ -33,16 +44,6 @@ import envoy.client.ui.chatscene.*;
import envoy.client.ui.control.*;
import envoy.client.ui.listcell.*;
import envoy.client.util.*;
-import envoy.data.*;
-import envoy.data.Attachment.AttachmentType;
-import envoy.data.Message.MessageStatus;
-import envoy.event.*;
-import envoy.event.contact.ContactOperation;
-import envoy.exception.EnvoyException;
-import envoy.util.EnvoyLog;
-
-import dev.kske.eventbus.*;
-import dev.kske.eventbus.Event;
/**
* Controller for the chat scene.
@@ -91,9 +92,6 @@ public final class ChatScene implements EventListener, Restorable {
@FXML
private Label topBarStatusLabel;
- @FXML
- private MenuItem deleteContactMenuItem;
-
@FXML
private ImageView attachmentView;
@@ -142,9 +140,11 @@ public final class ChatScene implements EventListener, Restorable {
private final WriteProxy writeProxy = context.getWriteProxy();
private final SceneContext sceneContext = context.getSceneContext();
private final AudioRecorder recorder = new AudioRecorder();
- private final Tooltip onlyIfOnlineTooltip = new Tooltip("You need to be online to do this");
+ private final Tooltip onlyIfOnlineTooltip =
+ new Tooltip("You need to be online to do this");
- private static Image DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
+ private static Image DEFAULT_ATTACHMENT_VIEW_IMAGE =
+ IconUtil.loadIconThemeSensitive("attachment_present", 20);
private static final Settings settings = Settings.getInstance();
private static final EventBus eventBus = EventBus.getInstance();
@@ -165,19 +165,24 @@ public final class ChatScene implements EventListener, Restorable {
// Initialize message and user rendering
messageList.setCellFactory(MessageListCell::new);
- chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
+ chatList.setCellFactory(ChatListCell::new);
// JavaFX provides an internal way of populating the context menu of a text
// area.
// We, however, need additional functionality.
- messageTextArea.setContextMenu(new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
+ messageTextArea.setContextMenu(
+ new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
// Set the icons of buttons and image views
- settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
- voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
- attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
+ settingsButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
+ voiceButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
+ attachmentButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
- messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
+ messageSearchButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
onlyIfOnlineTooltip.setShowDelay(Duration.millis(250));
final var clip = new Rectangle();
@@ -191,7 +196,6 @@ public final class ChatScene implements EventListener, Restorable {
// Set the design of the box in the upper-left corner
settingsButton.setAlignment(Pos.BOTTOM_RIGHT);
- HBox.setHgrow(spaceBetweenUserAndSettingsButton, Priority.ALWAYS);
generateOwnStatusControl();
Platform.runLater(() -> {
@@ -199,15 +203,19 @@ public final class ChatScene implements EventListener, Restorable {
// no check will be performed in case it has already been disabled - a negative
// GroupCreationResult might have been returned
- if (!newGroupButton.isDisabled()) newGroupButton.setDisable(!online);
+ if (!newGroupButton.isDisabled())
+ newGroupButton.setDisable(!online);
newContactButton.setDisable(!online);
- if (online) try {
- Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
- contactSearchTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
- groupCreationTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
- } catch (final IOException e) {
- logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
- }
+ if (online)
+ try {
+ Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
+ contactSearchTab.setContent(new FXMLLoader()
+ .load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
+ groupCreationTab.setContent(new FXMLLoader()
+ .load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
+ } catch (final IOException e) {
+ logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
+ }
else {
Tooltip.install(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
updateInfoLabel("You are offline", "info-label-warning");
@@ -216,7 +224,9 @@ public final class ChatScene implements EventListener, Restorable {
}
@Event(eventType = BackEvent.class)
- private void onBackEvent() { tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); }
+ private void onBackEvent() {
+ tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal());
+ }
@Event(includeSubtypes = true)
private void onMessage(Message message) {
@@ -225,7 +235,9 @@ public final class ChatScene implements EventListener, Restorable {
// Exceptions: this user is the sender (sync) or group message (group is
// recipient)
final var ownMessage = message.getSenderID() == localDB.getUser().getID();
- final var recipientID = message instanceof GroupMessage || ownMessage ? message.getRecipientID() : message.getSenderID();
+ final var recipientID =
+ message instanceof GroupMessage || ownMessage ? message.getRecipientID()
+ : message.getSenderID();
localDB.getChat(recipientID).ifPresent(chat -> {
Platform.runLater(() -> {
@@ -235,13 +247,15 @@ public final class ChatScene implements EventListener, Restorable {
if (chat.equals(currentChat)) {
currentChat.read(writeProxy);
scrollToMessageListEnd();
- } else if (!ownMessage && message.getStatus() != MessageStatus.READ) chat.incrementUnreadAmount();
+ } else if (!ownMessage && message.getStatus() != MessageStatus.READ)
+ chat.incrementUnreadAmount();
// Move chat with most recent unread messages to the top
chats.getSource().remove(chat);
((ObservableList) chats.getSource()).add(0, chat);
- if (chat.equals(currentChat)) chatList.getSelectionModel().select(0);
+ if (chat.equals(currentChat))
+ chatList.getSelectionModel().select(0);
});
});
}
@@ -251,9 +265,10 @@ public final class ChatScene implements EventListener, Restorable {
// Update UI if in current chat and the current user was the sender of the
// message
- if (currentChat != null) localDB.getMessage(evt.getID())
- .filter(msg -> msg.getSenderID() == client.getSender().getID())
- .ifPresent(msg -> Platform.runLater(messageList::refresh));
+ if (currentChat != null)
+ localDB.getMessage(evt.getID())
+ .filter(msg -> msg.getSenderID() == client.getSender().getID())
+ .ifPresent(msg -> Platform.runLater(messageList::refresh));
}
@Event
@@ -271,18 +286,25 @@ public final class ChatScene implements EventListener, Restorable {
}
@Event
- private void onContactOperation(ContactOperation operation) {
- final var contact = operation.get();
- switch (operation.getOperationType()) {
- case ADD:
- if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
- final var chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
- Platform.runLater(() -> ((ObservableList) chats.getSource()).add(0, chat));
- break;
- case REMOVE:
- Platform.runLater(() -> chats.getSource().removeIf(c -> c.getRecipient().equals(contact)));
- break;
- }
+ private void onUserOperation(UserOperation operation) {
+
+ // All ADD dependent logic resides in LocalDB
+ if (operation.getOperationType().equals(ElementOperation.REMOVE))
+ Platform.runLater(() -> disableChat(new ContactDisabled(operation.get())));
+ }
+
+ @Event
+ private void onGroupResize(GroupResize resize) {
+ final var chatFound = localDB.getChat(resize.getGroupID());
+ chatFound.ifPresent(chat -> Platform.runLater(() -> {
+ chatList.refresh();
+
+ // Update the top-bar status label if all conditions apply
+ if (currentChat != null && currentChat.getRecipient().equals(chat.getRecipient()))
+ topBarStatusLabel
+ .setText(chat.getRecipient().getContacts().size() + " member"
+ + (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
+ }));
}
@Event(eventType = NoAttachments.class)
@@ -298,31 +320,43 @@ public final class ChatScene implements EventListener, Restorable {
});
}
- @Event
- private void onGroupCreationResult(GroupCreationResult result) { Platform.runLater(() -> newGroupButton.setDisable(!result.get())); }
+ @Event(priority = 150)
+ private void onGroupCreationResult(GroupCreationResult result) {
+ Platform.runLater(() -> newGroupButton.setDisable(result.get() == null));
+ }
@Event(eventType = ThemeChangeEvent.class)
private void onThemeChange() {
- settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
- voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
- attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
+ settingsButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
+ voiceButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
+ attachmentButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
- attachmentView.setImage(isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
- messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
+ attachmentView.setImage(
+ isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
+ messageSearchButton.setGraphic(
+ new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
messageList.setCellFactory(MessageListCell::new);
- // TODO: cache image
if (currentChat != null)
- if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
- else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
+ if (currentChat.getRecipient() instanceof User)
+ recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
+ else
+ recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
}
@Event(eventType = Logout.class, priority = 200)
- private void onLogout() { eventBus.removeListener(this); }
+ private void onLogout() {
+ eventBus.removeListener(this);
+ }
@Override
- public void onRestore() { updateRemainingCharsLabel(); }
+ public void onRestore() {
+ updateRemainingCharsLabel();
+ }
/**
* Actions to perform when the list of contacts has been clicked.
@@ -331,9 +365,13 @@ public final class ChatScene implements EventListener, Restorable {
*/
@FXML
private void chatListClicked() {
- if (chatList.getSelectionModel().isEmpty()) return;
+ if (chatList.getSelectionModel().isEmpty())
+ return;
+ final var chat = chatList.getSelectionModel().getSelectedItem();
+ if (chat == null)
+ return;
- final var user = chatList.getSelectionModel().getSelectedItem().getRecipient();
+ final var user = chat.getRecipient();
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
@@ -345,7 +383,6 @@ public final class ChatScene implements EventListener, Restorable {
final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount();
messageList.scrollTo(scrollIndex);
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
- deleteContactMenuItem.setText("Delete " + user.getName());
// Read the current chat
currentChat.read(writeProxy);
@@ -353,7 +390,8 @@ public final class ChatScene implements EventListener, Restorable {
// Discard the pending attachment
if (recorder.isRecording()) {
recorder.cancel();
- voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
+ voiceButton.setGraphic(new ImageView(
+ IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
voiceButton.setText(null);
}
pendingAttachment = null;
@@ -361,22 +399,32 @@ public final class ChatScene implements EventListener, Restorable {
remainingChars.setVisible(true);
remainingChars
- .setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
+ .setText(String.format("remaining chars: %d/%d",
+ MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
}
- messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
- voiceButton.setDisable(!recorder.isSupported());
- attachmentButton.setDisable(false);
+
+ // Enable or disable the necessary UI controls
+ final var chatEditable = currentChat == null || currentChat.isDisabled();
+ messageTextArea.setDisable(chatEditable || postingPermanentlyDisabled);
+ voiceButton.setDisable(!recorder.isSupported() || chatEditable);
+ attachmentButton.setDisable(chatEditable);
chatList.refresh();
+ // Design the top bar
if (currentChat != null) {
topBarContactLabel.setText(currentChat.getRecipient().getName());
+ topBarContactLabel.setVisible(true);
+ topBarStatusLabel.setVisible(true);
if (currentChat.getRecipient() instanceof User) {
final var status = ((User) currentChat.getRecipient()).getStatus().toString();
topBarStatusLabel.setText(status);
+ topBarStatusLabel.getStyleClass().clear();
topBarStatusLabel.getStyleClass().add(status.toLowerCase());
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
} else {
- topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members");
+ topBarStatusLabel
+ .setText(currentChat.getRecipient().getContacts().size() + " member"
+ + (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
topBarStatusLabel.getStyleClass().clear();
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
}
@@ -386,7 +434,6 @@ public final class ChatScene implements EventListener, Restorable {
clip.setArcHeight(43);
clip.setArcWidth(43);
recipientProfilePic.setClip(clip);
-
messageSearchButton.setVisible(true);
}
}
@@ -397,7 +444,9 @@ public final class ChatScene implements EventListener, Restorable {
* @since Envoy Client v0.1-beta
*/
@FXML
- private void settingsButtonClicked() { sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); }
+ private void settingsButtonClicked() {
+ sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE);
+ }
/**
* Actions to perform when the "Add Contact" - Button has been clicked.
@@ -405,10 +454,14 @@ public final class ChatScene implements EventListener, Restorable {
* @since Envoy Client v0.1-beta
*/
@FXML
- private void addContactButtonClicked() { tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal()); }
+ private void addContactButtonClicked() {
+ tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal());
+ }
@FXML
- private void groupCreationButtonClicked() { tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal()); }
+ private void groupCreationButtonClicked() {
+ tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal());
+ }
@FXML
private void voiceButtonClicked() {
@@ -417,15 +470,19 @@ public final class ChatScene implements EventListener, Restorable {
if (!recorder.isRecording()) {
Platform.runLater(() -> {
voiceButton.setText("Recording");
- voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
+ voiceButton.setGraphic(new ImageView(
+ IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
});
recorder.start();
} else {
pendingAttachment = new Attachment(recorder.finish(), "Voice_recording_"
- + DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss").format(LocalDateTime.now()) + "." + AudioRecorder.FILE_FORMAT,
- AttachmentType.VOICE);
+ + DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss")
+ .format(LocalDateTime.now())
+ + "." + AudioRecorder.FILE_FORMAT,
+ AttachmentType.VOICE);
Platform.runLater(() -> {
- voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
+ voiceButton.setGraphic(new ImageView(
+ IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
voiceButton.setText(null);
checkPostConditions(false);
updateAttachmentView(true);
@@ -433,7 +490,8 @@ public final class ChatScene implements EventListener, Restorable {
}
} catch (final EnvoyException e) {
logger.log(Level.SEVERE, "Could not record audio: ", e);
- Platform.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
+ Platform
+ .runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
}
}).start();
}
@@ -447,15 +505,16 @@ public final class ChatScene implements EventListener, Restorable {
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
fileChooser.getExtensionFilters()
.addAll(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"),
- new FileChooser.ExtensionFilter("Videos", "*.mp4"),
- new FileChooser.ExtensionFilter("All Files", "*.*"));
+ new FileChooser.ExtensionFilter("Videos", "*.mp4"),
+ new FileChooser.ExtensionFilter("All Files", "*.*"));
final var file = fileChooser.showOpenDialog(sceneContext.getStage());
if (file != null) {
// Check max file size
if (file.length() > 16E6) {
- new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!").showAndWait();
+ new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!")
+ .showAndWait();
return;
}
@@ -477,7 +536,8 @@ public final class ChatScene implements EventListener, Restorable {
checkPostConditions(false);
// Setting the preview image as image of the attachmentView
if (type == AttachmentType.PICTURE) {
- attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes), DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
+ attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes),
+ DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
isCustomAttachmentImage = true;
}
attachmentView.setVisible(true);
@@ -488,8 +548,7 @@ public final class ChatScene implements EventListener, Restorable {
}
/**
- * Rotates every element in our application by {@code rotations}*360° in
- * {@code an}.
+ * Rotates every element in our application by {@code rotations}*360° in {@code an}.
*
* @param rotations the amount of times the scene is rotated by 360°
* @param animationTime the time in seconds that this animation lasts
@@ -506,7 +565,8 @@ public final class ChatScene implements EventListener, Restorable {
final var rotatableNodes = ReflectionUtil.getAllDeclaredNodeVariables(this);
for (final var node : rotatableNodes) {
// Sets the animation duration to {animationTime}
- final var rotateTransition = new RotateTransition(Duration.seconds(animationTime), node);
+ final var rotateTransition =
+ new RotateTransition(Duration.seconds(animationTime), node);
// rotates every element {rotations} times
rotateTransition.setByAngle(rotations * 360);
rotateTransition.play();
@@ -517,9 +577,8 @@ public final class ChatScene implements EventListener, Restorable {
}
/**
- * Checks the text length of the {@code messageTextArea}, adjusts the
- * {@code remainingChars} label and checks whether to send the message
- * automatically.
+ * Checks the text length of the {@code messageTextArea}, adjusts the {@code remainingChars}
+ * label and checks whether to send the message automatically.
*
* @param e the key event that will be analyzed for a post request
* @since Envoy Client v0.1-beta
@@ -532,29 +591,33 @@ public final class ChatScene implements EventListener, Restorable {
// Sending an IsTyping event if none has been sent for
// IsTyping#millisecondsActive
- if (client.isOnline() && currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
+ if (client.isOnline() && currentChat.getLastWritingEvent()
+ + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID()));
currentChat.lastWritingEventWasNow();
}
// KeyPressed will be called before the char has been added to the text, hence
// this is needed for the first char
- if (messageTextArea.getText().length() == 1 && e != null) checkPostConditions(e);
+ if (messageTextArea.getText().length() == 1 && e != null)
+ checkPostConditions(e);
// This is needed for the messageTA context menu
- else if (e == null) checkPostConditions(false);
+ else if (e == null)
+ checkPostConditions(false);
}
/**
- * Returns the id that should be used to send things to the server: the id of
- * 'our' {@link User} if the recipient of that object is another User, else the
- * id of the {@link Group} 'our' user is sending to.
+ * Returns the id that should be used to send things to the server: the id of 'our' {@link User}
+ * if the recipient of that object is another User, else the id of the {@link Group} 'our' user
+ * is sending to.
*
* @return an id that can be sent to the server
* @since Envoy Client v0.2-beta
*/
private long getChatID() {
- return currentChat.getRecipient() instanceof User ? client.getSender().getID() : currentChat.getRecipient().getID();
+ return currentChat.getRecipient() instanceof User ? client.getSender().getID()
+ : currentChat.getRecipient().getID();
}
/**
@@ -564,21 +627,25 @@ public final class ChatScene implements EventListener, Restorable {
@FXML
private void checkPostConditions(KeyEvent e) {
final var enterPressed = e.getCode() == KeyCode.ENTER;
- final var messagePosted = enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown() : false;
+ final var messagePosted =
+ enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown()
+ : false;
if (messagePosted) {
// Removing an inserted line break if added by pressing enter
final var text = messageTextArea.getText();
final var textPosition = messageTextArea.getCaretPosition() - 1;
if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n')
- messageTextArea.setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
+ messageTextArea
+ .setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
}
// if control is pressed, the enter press is originally invalidated. Here it'll
// be inserted again
else if (enterPressed && e.isControlDown()) {
var caretPosition = messageTextArea.getCaretPosition();
- messageTextArea.setText(new StringBuilder(messageTextArea.getText()).insert(caretPosition, '\n').toString());
+ messageTextArea.setText(new StringBuilder(messageTextArea.getText())
+ .insert(caretPosition, '\n').toString());
messageTextArea.positionCaret(++caretPosition);
}
checkPostConditions(messagePosted);
@@ -586,8 +653,10 @@ public final class ChatScene implements EventListener, Restorable {
private void checkPostConditions(boolean postMessage) {
if (!postingPermanentlyDisabled) {
- if (!postButton.isDisabled() && postMessage) postMessage();
- postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null || currentChat == null);
+ if (!postButton.isDisabled() && postMessage)
+ postMessage();
+ postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null
+ || currentChat == null);
} else {
final var noMoreMessaging = "Go online to send messages";
if (!infoLabel.getText().equals(noMoreMessaging))
@@ -621,13 +690,14 @@ public final class ChatScene implements EventListener, Restorable {
private void updateRemainingCharsLabel() {
final var currentLength = messageTextArea.getText().length();
final var remainingLength = MAX_MESSAGE_LENGTH - currentLength;
- remainingChars.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
+ remainingChars
+ .setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
}
/**
- * Sends a new {@link Message} or {@link GroupMessage} to the server based on
- * the text entered in the {@code messageTextArea} and the given attachment.
+ * Sends a new {@link Message} or {@link GroupMessage} to the server based on the text entered
+ * in the {@code messageTextArea} and the given attachment.
*
* @since Envoy Client v0.1-beta
*/
@@ -644,8 +714,9 @@ public final class ChatScene implements EventListener, Restorable {
final var text = messageTextArea.getText().strip();
if (!commands.getChatSceneCommands().executeIfPresent(text)) {
// Creating the message and its metadata
- final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
- .setText(text);
+ final var builder = new MessageBuilder(localDB.getUser().getID(),
+ currentChat.getRecipient().getID(), localDB.getIDGenerator())
+ .setText(text);
// Setting an attachment, if present
if (pendingAttachment != null) {
builder.setAttachment(pendingAttachment);
@@ -653,8 +724,9 @@ public final class ChatScene implements EventListener, Restorable {
updateAttachmentView(false);
}
// Building the final message
- final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
- : builder.build();
+ final var message = currentChat.getRecipient() instanceof Group
+ ? builder.buildGroupMessage((Group) currentChat.getRecipient())
+ : builder.build();
// Send message
writeProxy.writeMessage(message);
@@ -665,14 +737,15 @@ public final class ChatScene implements EventListener, Restorable {
Platform.runLater(() -> {
chats.getSource().remove(currentChat);
((ObservableList) chats.getSource()).add(0, currentChat);
- chatList.getSelectionModel().select(0);
localDB.getChats().remove(currentChat);
localDB.getChats().add(0, currentChat);
+ chatList.getSelectionModel().select(0);
});
scrollToMessageListEnd();
// Request a new ID generator if all IDs were used
- if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIDGenerator();
+ if (!localDB.getIDGenerator().hasNext() && client.isOnline())
+ client.requestIDGenerator();
}
// Clear text field and disable post button
@@ -687,14 +760,16 @@ public final class ChatScene implements EventListener, Restorable {
*
* @since Envoy Client v0.1-beta
*/
- private void scrollToMessageListEnd() { messageList.scrollTo(messageList.getItems().size() - 1); }
+ private void scrollToMessageListEnd() {
+ messageList.scrollTo(messageList.getItems().size() - 1);
+ }
/**
* Updates the {@code infoLabel}.
*
* @param text the text to use
- * @param infoLabelID the id the the {@code infoLabel} should have so that it
- * can be styled accordingly in CSS
+ * @param infoLabelID the id the the {@code infoLabel} should have so that it can be styled
+ * accordingly in CSS
* @since Envoy Client v0.1-beta
*/
private void updateInfoLabel(String text, String infoLabelID) {
@@ -705,14 +780,16 @@ public final class ChatScene implements EventListener, Restorable {
/**
* Updates the {@code attachmentView} in terms of visibility.
- * Additionally resets the shown image to {@code DEFAULT_ATTACHMENT_VIEW_IMAGE}
- * if another image is currently present.
+ * Additionally resets the shown image to {@code DEFAULT_ATTACHMENT_VIEW_IMAGE} if another image
+ * is currently present.
*
* @param visible whether the {@code attachmentView} should be displayed
* @since Envoy Client v0.1-beta
*/
private void updateAttachmentView(boolean visible) {
- if (!attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)) attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
+ if (!(attachmentView.getImage() == null
+ || attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)))
+ attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
attachmentView.setVisible(visible);
}
@@ -727,19 +804,66 @@ public final class ChatScene implements EventListener, Restorable {
// Else prepend it to the HBox children
final var ownUserControl = new ContactControl(localDB.getUser());
ownUserControl.setAlignment(Pos.CENTER_LEFT);
+ HBox.setHgrow(ownUserControl, Priority.NEVER);
ownContactControl.getChildren().add(0, ownUserControl);
}
}
- // Context menu actions
+ /**
+ * Redesigns the UI when the {@link Chat} of the given contact has been marked as disabled.
+ *
+ * @param event the contact whose chat got disabled
+ * @since Envoy Client v0.3-beta
+ */
+ @Event
+ public void disableChat(ContactDisabled event) {
+ chatList.refresh();
+ final var recipient = event.get();
- @FXML
- private void deleteContact() { try {} catch (final NullPointerException e) {} }
+ // Decrement member count for groups
+ if (recipient instanceof Group)
+ topBarStatusLabel.setText(recipient.getContacts().size() + " member"
+ + (recipient.getContacts().size() != 1 ? "s" : ""));
+ if (currentChat != null && currentChat.getRecipient().equals(recipient)) {
+ messageTextArea.setDisable(true);
+ voiceButton.setDisable(true);
+ attachmentButton.setDisable(true);
+ pendingAttachment = null;
+ messageList.getStyleClass().clear();
+ messageList.getStyleClass().add("disabled-chat");
+ }
+ }
+
+ /**
+ * Resets every component back to its inital state before a chat was selected.
+ *
+ * @since Envoy Client v0.3-beta
+ */
+ public void resetState() {
+ currentChat = null;
+ chatList.getSelectionModel().clearSelection();
+ messageList.getItems().clear();
+ messageTextArea.setDisable(true);
+ attachmentView.setImage(null);
+ topBarContactLabel.setVisible(false);
+ topBarStatusLabel.setVisible(false);
+ messageSearchButton.setVisible(false);
+ messageTextArea.clear();
+ messageTextArea.setDisable(true);
+ attachmentButton.setDisable(true);
+ voiceButton.setDisable(true);
+ remainingChars.setVisible(false);
+ pendingAttachment = null;
+ recipientProfilePic.setImage(null);
+ if (recorder.isRecording())
+ recorder.cancel();
+ }
@FXML
private void copyAndPostMessage() {
final var messageText = messageTextArea.getText();
- Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
+ Toolkit.getDefaultToolkit().getSystemClipboard()
+ .setContents(new StringSelection(messageText), null);
final var image = attachmentView.getImage();
final var messageAttachment = pendingAttachment;
postMessage();
@@ -747,7 +871,8 @@ public final class ChatScene implements EventListener, Restorable {
updateRemainingCharsLabel();
postButton.setDisable(messageText.isBlank());
attachmentView.setImage(image);
- if (attachmentView.getImage() != null) attachmentView.setVisible(true);
+ if (attachmentView.getImage() != null)
+ attachmentView.setVisible(true);
pendingAttachment = messageAttachment;
}
@@ -756,11 +881,14 @@ public final class ChatScene implements EventListener, Restorable {
*
* @since Envoy Client v0.3-beta
*/
- public void clearMessageSelection() { messageList.getSelectionModel().clearSelection(); }
+ public void clearMessageSelection() {
+ messageList.getSelectionModel().clearSelection();
+ }
@FXML
private void searchContacts() {
chats.setPredicate(contactSearch.getText().isBlank() ? c -> true
- : c -> c.getRecipient().getName().toLowerCase().contains(contactSearch.getText().toLowerCase()));
+ : c -> c.getRecipient().getName().toLowerCase()
+ .contains(contactSearch.getText().toLowerCase()));
}
}
diff --git a/client/src/main/java/envoy/client/ui/controller/ContactSearchTab.java b/client/src/main/java/envoy/client/ui/controller/ContactSearchTab.java
index b573345..3445329 100644
--- a/client/src/main/java/envoy/client/ui/controller/ContactSearchTab.java
+++ b/client/src/main/java/envoy/client/ui/controller/ContactSearchTab.java
@@ -7,28 +7,28 @@ import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
+import dev.kske.eventbus.*;
+
+import envoy.data.User;
+import envoy.event.ElementOperation;
+import envoy.event.contact.*;
+import envoy.util.EnvoyLog;
+
import envoy.client.data.Context;
import envoy.client.event.BackEvent;
import envoy.client.helper.AlertHelper;
import envoy.client.net.Client;
import envoy.client.ui.control.ContactControl;
import envoy.client.ui.listcell.ListCellFactory;
-import envoy.data.User;
-import envoy.event.ElementOperation;
-import envoy.event.contact.*;
-import envoy.util.EnvoyLog;
-
-import dev.kske.eventbus.*;
/**
- * Provides a search bar in which a user name (substring) can be entered. The
- * users with a matching name are then displayed inside a list view. A
- * {@link UserSearchRequest} is sent on every keystroke.
+ * Provides a search bar in which a user name (substring) can be entered. The users with a matching
+ * name are then displayed inside a list view. A {@link UserSearchRequest} is sent on every
+ * keystroke.
*