diff --git a/client/src/main/java/envoy/client/Main.java b/client/src/main/java/envoy/client/Main.java index d64d075..626d7d5 100644 --- a/client/src/main/java/envoy/client/Main.java +++ b/client/src/main/java/envoy/client/Main.java @@ -7,8 +7,8 @@ import envoy.client.ui.Startup; /** * Triggers application startup. *

- * 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: *