Merge branch 'develop' into f/enhanced-status-tray-icon
Conflicts: client/src/main/java/envoy/client/data/Chat.java client/src/main/java/envoy/client/ui/StatusTrayIcon.java
This commit is contained in:
		@@ -7,8 +7,8 @@ import envoy.client.ui.Startup;
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * Triggers application startup.
 | 
					 * Triggers application startup.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * To allow Maven shading, the main method has to be separated from the
 | 
					 * To allow Maven shading, the main method has to be separated from the {@link Startup} class which
 | 
				
			||||||
 * {@link Startup} class which extends {@link Application}.
 | 
					 * extends {@link Application}.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -25,8 +25,7 @@ public final class Main {
 | 
				
			|||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Starts the application.
 | 
						 * Starts the application.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param args the command line arguments are processed by the
 | 
						 * @param args the command line arguments are processed by the client configuration
 | 
				
			||||||
	 *             client configuration
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void main(String[] args) {
 | 
						public static void main(String[] args) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,9 @@ public final class Cache<T> implements Consumer<T>, Serializable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@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.
 | 
						 * Sets the processor to which cached elements are relayed.
 | 
				
			||||||
@@ -52,7 +54,8 @@ public final class Cache<T> implements Consumer<T>, Serializable {
 | 
				
			|||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void relay() {
 | 
						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.forEach(processor::accept);
 | 
				
			||||||
		elements.clear();
 | 
							elements.clear();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -62,5 +65,7 @@ public final class Cache<T> implements Consumer<T>, Serializable {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void clear() { elements.clear(); }
 | 
						public void clear() {
 | 
				
			||||||
 | 
							elements.clear();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,7 @@ import java.io.Serializable;
 | 
				
			|||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Stores a heterogeneous map of {@link Cache} objects with different type
 | 
					 * Stores a heterogeneous map of {@link Cache} objects with different type parameters.
 | 
				
			||||||
 * parameters.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -24,7 +23,9 @@ public final class CacheMap implements Serializable {
 | 
				
			|||||||
	 * @param cache the cache to store
 | 
						 * @param cache the cache to store
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public <T> void put(Class<T> key, Cache<T> cache) { map.put(key, cache); }
 | 
						public <T> void put(Class<T> key, Cache<T> cache) {
 | 
				
			||||||
 | 
							map.put(key, cache);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Returns a cache mapped by a class.
 | 
						 * Returns a cache mapped by a class.
 | 
				
			||||||
@@ -34,7 +35,9 @@ public final class CacheMap implements Serializable {
 | 
				
			|||||||
	 * @return the cache
 | 
						 * @return the cache
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public <T> Cache<T> get(Class<T> key) { return (Cache<T>) map.get(key); }
 | 
						public <T> Cache<T> get(Class<T> key) {
 | 
				
			||||||
 | 
							return (Cache<T>) map.get(key);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Returns a cache mapped by a class or any of its subclasses.
 | 
						 * 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
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void clear() { map.values().forEach(Cache::clear); }
 | 
						public void clear() {
 | 
				
			||||||
 | 
							map.values().forEach(Cache::clear);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,17 +22,19 @@ import envoy.client.net.WriteProxy;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class Chat implements Serializable {
 | 
					public class Chat implements Serializable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected final Contact recipient;
 | 
						protected boolean disabled;
 | 
				
			||||||
 | 
					 | 
				
			||||||
	protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Stores the last time an {@link envoy.event.IsTyping} event has been sent.
 | 
						 * Stores the last time an {@link envoy.event.IsTyping} event has been sent.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	protected transient long lastWritingEvent;
 | 
						protected transient long lastWritingEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected int						unreadAmount;
 | 
						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;
 | 
						private static final long serialVersionUID = 2L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,8 +63,12 @@ public class Chat implements Serializable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() {
 | 
						public String toString() {
 | 
				
			||||||
		return String.format("%s[recipient=%s,messages=%d]", getClass().getSimpleName(), recipient,
 | 
							return String.format(
 | 
				
			||||||
			messages.size());
 | 
								"%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() {
 | 
						public void lastWritingEventWasNow() {
 | 
				
			||||||
		lastWritingEvent = System.currentTimeMillis();
 | 
							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; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,8 @@ import static java.util.function.Function.identity;
 | 
				
			|||||||
import envoy.data.Config;
 | 
					import envoy.data.Config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Implements a configuration specific to the Envoy Client with default values
 | 
					 * Implements a configuration specific to the Envoy Client with default values and convenience
 | 
				
			||||||
 * and convenience methods.
 | 
					 * methods.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -20,7 +20,8 @@ public final class ClientConfig extends Config {
 | 
				
			|||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static ClientConfig getInstance() {
 | 
						public static ClientConfig getInstance() {
 | 
				
			||||||
		if (config == null) config = new ClientConfig();
 | 
							if (config == null)
 | 
				
			||||||
 | 
								config = new ClientConfig();
 | 
				
			||||||
		return config;
 | 
							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
 | 
						 * @return the amount of minutes after which the local database should be saved
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Integer getLocalDBSaveInterval() { return (Integer) items.get("localDBSaveInterval").get(); }
 | 
						public Integer getLocalDBSaveInterval() {
 | 
				
			||||||
 | 
							return (Integer) items.get("localDBSaveInterval").get();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,8 @@ public class Context {
 | 
				
			|||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void initWriteProxy() {
 | 
						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);
 | 
							writeProxy = new WriteProxy(client, localDB);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,14 +2,14 @@ package envoy.client.data;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.time.Instant;
 | 
					import java.time.Instant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.net.WriteProxy;
 | 
					 | 
				
			||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
import envoy.data.Message.MessageStatus;
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
import envoy.event.GroupMessageStatusChange;
 | 
					import envoy.event.GroupMessageStatusChange;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.net.WriteProxy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Represents a chat between a user and a group
 | 
					 * Represents a chat between a user and a group as a list of messages.
 | 
				
			||||||
 * as a list of messages.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @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
 | 
						 * @param recipient the group whose members receive the messages
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GroupChat(User sender, Contact recipient) {
 | 
						public GroupChat(User sender, Group recipient) {
 | 
				
			||||||
		super(recipient);
 | 
							super(recipient);
 | 
				
			||||||
		this.sender = sender;
 | 
							this.sender = sender;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -34,11 +34,14 @@ public final class GroupChat extends Chat {
 | 
				
			|||||||
	public void read(WriteProxy writeProxy) {
 | 
						public void read(WriteProxy writeProxy) {
 | 
				
			||||||
		for (int i = messages.size() - 1; i >= 0; --i) {
 | 
							for (int i = messages.size() - 1; i >= 0; --i) {
 | 
				
			||||||
			final GroupMessage gmsg = (GroupMessage) messages.get(i);
 | 
								final GroupMessage gmsg = (GroupMessage) messages.get(i);
 | 
				
			||||||
			if (gmsg.getSenderID() != sender.getID()) if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break;
 | 
								if (gmsg.getSenderID() != sender.getID())
 | 
				
			||||||
			else {
 | 
									if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ)
 | 
				
			||||||
				gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
 | 
										break;
 | 
				
			||||||
				writeProxy.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, Instant.now(), sender.getID()));
 | 
									else {
 | 
				
			||||||
			}
 | 
										gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
 | 
				
			||||||
 | 
										writeProxy.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(),
 | 
				
			||||||
 | 
											MessageStatus.READ, Instant.now(), sender.getID()));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		unreadAmount = 0;
 | 
							unreadAmount = 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,29 +1,34 @@
 | 
				
			|||||||
package envoy.client.data;
 | 
					package envoy.client.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static java.util.function.Predicate.not;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.*;
 | 
					import java.io.*;
 | 
				
			||||||
import java.nio.channels.*;
 | 
					import java.nio.channels.*;
 | 
				
			||||||
import java.nio.file.StandardOpenOption;
 | 
					import java.nio.file.StandardOpenOption;
 | 
				
			||||||
import java.time.Instant;
 | 
					import java.time.Instant;
 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.logging.*;
 | 
					import java.util.logging.*;
 | 
				
			||||||
 | 
					import java.util.stream.Stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javafx.application.Platform;
 | 
					import javafx.application.Platform;
 | 
				
			||||||
import javafx.collections.*;
 | 
					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.Event;
 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
import dev.kske.eventbus.EventListener;
 | 
					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.
 | 
					 * Stores information about the current {@link User} and their {@link Chat}s. For message ID
 | 
				
			||||||
 * For message ID generation a {@link IDGenerator} is stored as well.
 | 
					 * generation a {@link IDGenerator} is stored as well.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * The managed objects are stored inside a folder in the local file system.
 | 
					 * 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 IDGenerator				idGenerator;
 | 
				
			||||||
	private CacheMap				cacheMap	= new CacheMap();
 | 
						private CacheMap				cacheMap	= new CacheMap();
 | 
				
			||||||
	private String					authToken;
 | 
						private String					authToken;
 | 
				
			||||||
 | 
						private boolean					contactsChanged;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Auto save timer
 | 
						// Auto save timer
 | 
				
			||||||
	private Timer	autoSaver;
 | 
						private Timer	autoSaver;
 | 
				
			||||||
@@ -68,8 +74,11 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
		EventBus.getInstance().registerListener(this);
 | 
							EventBus.getInstance().registerListener(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Ensure that the database directory exists
 | 
							// Ensure that the database directory exists
 | 
				
			||||||
		if (!dbDir.exists()) dbDir.mkdirs();
 | 
							if (!dbDir.exists())
 | 
				
			||||||
		else if (!dbDir.isDirectory()) throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
 | 
								dbDir.mkdirs();
 | 
				
			||||||
 | 
							else if (!dbDir.isDirectory())
 | 
				
			||||||
 | 
								throw new IOException(
 | 
				
			||||||
 | 
									String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Lock the directory
 | 
							// Lock the directory
 | 
				
			||||||
		lock();
 | 
							lock();
 | 
				
			||||||
@@ -88,8 +97,7 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Ensured that only one Envoy instance is using this local database by creating
 | 
						 * Ensured that only one Envoy instance is using this local database by creating a lock file.
 | 
				
			||||||
	 * a lock file.
 | 
					 | 
				
			||||||
	 * The lock file is deleted on application exit.
 | 
						 * The lock file is deleted on application exit.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @throws EnvoyException if the lock cannot by acquired
 | 
						 * @throws EnvoyException if the lock cannot by acquired
 | 
				
			||||||
@@ -98,17 +106,19 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
	private synchronized void lock() throws EnvoyException {
 | 
						private synchronized void lock() throws EnvoyException {
 | 
				
			||||||
		final var file = new File(dbDir, "instance.lock");
 | 
							final var file = new File(dbDir, "instance.lock");
 | 
				
			||||||
		try {
 | 
							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();
 | 
								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) {
 | 
							} catch (final IOException e) {
 | 
				
			||||||
			throw new EnvoyException("Could not create lock file!", e);
 | 
								throw new EnvoyException("Could not create lock file!", e);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads the local user registry {@code users.db}, the id generator
 | 
						 * Loads the local user registry {@code users.db}, the id generator {@code id_gen.db} and last
 | 
				
			||||||
	 * {@code id_gen.db} and last login file {@code last_login.db}.
 | 
						 * login file {@code last_login.db}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -133,10 +143,45 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public synchronized void loadUserData() throws ClassNotFoundException, IOException {
 | 
						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");
 | 
							userFile = new File(dbDir, user.getID() + ".db");
 | 
				
			||||||
		try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
 | 
							try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
 | 
				
			||||||
			chats		= FXCollections.observableList((List<Chat>) in.readObject());
 | 
								chats = FXCollections.observableList((List<Chat>) 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();
 | 
								cacheMap	= (CacheMap) in.readObject();
 | 
				
			||||||
			lastSync	= (Instant) in.readObject();
 | 
								lastSync	= (Instant) in.readObject();
 | 
				
			||||||
		} finally {
 | 
							} finally {
 | 
				
			||||||
@@ -145,31 +190,34 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Synchronizes the contact list of the client user with the chat and user
 | 
						 * Synchronizes the contact list of the client user with the chat and user storage.
 | 
				
			||||||
	 * storage.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void synchronize() {
 | 
						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);
 | 
							users.put(user.getName(), user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Synchronize user status data
 | 
							// Synchronize user status data
 | 
				
			||||||
		for (final var contact : user.getContacts())
 | 
							for (final var contact : user.getContacts())
 | 
				
			||||||
			if (contact instanceof User)
 | 
								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
 | 
							// Create missing chats
 | 
				
			||||||
		user.getContacts()
 | 
							user.getContacts()
 | 
				
			||||||
			.stream()
 | 
								.stream()
 | 
				
			||||||
			.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
 | 
								.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);
 | 
								.forEach(chats::add);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a timer that automatically saves this local database after a
 | 
						 * Initializes a timer that automatically saves this local database after a period of time
 | 
				
			||||||
	 * period of time specified in the settings.
 | 
						 * specified in the settings.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -184,68 +232,112 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
		autoSaver.schedule(new TimerTask() {
 | 
							autoSaver.schedule(new TimerTask() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			@Override
 | 
								@Override
 | 
				
			||||||
			public void run() { save(); }
 | 
								public void run() {
 | 
				
			||||||
 | 
									save();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}, 2000, ClientConfig.getInstance().getLocalDBSaveInterval() * 60000);
 | 
							}, 2000, ClientConfig.getInstance().getLocalDBSaveInterval() * 60000);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Stores all users. If the client user is specified, their chats will be stored
 | 
						 * Stores all users. If the client user is specified, their chats will be stored as well. The
 | 
				
			||||||
	 * as well. The message id generator will also be saved if present.
 | 
						 * message id generator will also be saved if present.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @throws IOException if the saving process failed
 | 
						 * @throws IOException if the saving process failed
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Event(eventType = EnvoyCloseEvent.class, priority = 1000)
 | 
						@Event(eventType = EnvoyCloseEvent.class, priority = 500)
 | 
				
			||||||
	private synchronized void save() {
 | 
						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
 | 
							// Save users
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			SerializationUtils.write(usersFile, users);
 | 
								SerializationUtils.write(usersFile, users);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Save user data and last sync time stamp
 | 
								// Save user data and last sync time stamp
 | 
				
			||||||
			if (user != null) SerializationUtils
 | 
								if (user != null)
 | 
				
			||||||
				.write(userFile, new ArrayList<>(chats), cacheMap, Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
 | 
									SerializationUtils
 | 
				
			||||||
 | 
										.write(userFile, new ArrayList<>(chats), cacheMap,
 | 
				
			||||||
 | 
											Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Save last login information
 | 
								// Save last login information
 | 
				
			||||||
			if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken);
 | 
								if (authToken != null)
 | 
				
			||||||
 | 
									SerializationUtils.write(lastLoginFile, user, authToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Save ID generator
 | 
								// Save ID generator
 | 
				
			||||||
			if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
 | 
								if (hasIDGenerator())
 | 
				
			||||||
 | 
									SerializationUtils.write(idGeneratorFile, idGenerator);
 | 
				
			||||||
		} catch (final IOException e) {
 | 
							} 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)
 | 
						@Event(priority = 500)
 | 
				
			||||||
	private void onMessage(Message msg) { if (msg.getStatus() == MessageStatus.SENT) msg.nextStatus(); }
 | 
						private void onMessage(Message msg) {
 | 
				
			||||||
 | 
							if (msg.getStatus() == MessageStatus.SENT)
 | 
				
			||||||
 | 
								msg.nextStatus();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(priority = 150)
 | 
						@Event(priority = 500)
 | 
				
			||||||
	private void onGroupMessage(GroupMessage msg) {
 | 
						private void onGroupMessage(GroupMessage msg) {
 | 
				
			||||||
		// TODO: Cancel event once EventBus is updated
 | 
							// TODO: Cancel event once EventBus is updated
 | 
				
			||||||
		if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
 | 
							if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
 | 
				
			||||||
			logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
 | 
								logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(priority = 150)
 | 
						@Event(priority = 500)
 | 
				
			||||||
	private void onMessageStatusChange(MessageStatusChange evt) { getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get())); }
 | 
						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) {
 | 
						private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
 | 
				
			||||||
		this.<GroupMessage>getMessage(evt.getID()).ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
 | 
							this.<GroupMessage>getMessage(evt.getID())
 | 
				
			||||||
 | 
								.ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(priority = 150)
 | 
						@Event(priority = 500)
 | 
				
			||||||
	private void onUserStatusChange(UserStatusChange evt) {
 | 
						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)
 | 
						@Event(priority = 500)
 | 
				
			||||||
	private void onGroupResize(GroupResize evt) { getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast).ifPresent(evt::apply); }
 | 
						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) {
 | 
						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
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
	private void onNewAuthToken(NewAuthToken evt) { authToken = evt.get(); }
 | 
						private void onNewAuthToken(NewAuthToken evt) {
 | 
				
			||||||
 | 
							authToken = evt.get();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Deletes all associations to the current user.
 | 
						 * Deletes all associations to the current user.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Event(eventType = Logout.class, priority = 100)
 | 
						@Event(eventType = Logout.class, priority = 50)
 | 
				
			||||||
	private void onLogout() {
 | 
						private void onLogout() {
 | 
				
			||||||
		autoSaver.cancel();
 | 
							autoSaver.cancel();
 | 
				
			||||||
		autoSaveRestart = true;
 | 
							autoSaveRestart = true;
 | 
				
			||||||
@@ -289,16 +383,28 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
			// once a message was removed
 | 
								// once a message was removed
 | 
				
			||||||
			final var messageID = message.get();
 | 
								final var messageID = message.get();
 | 
				
			||||||
			for (final var chat : chats)
 | 
								for (final var chat : chats)
 | 
				
			||||||
				if (chat.remove(messageID)) break;
 | 
									if (chat.remove(messageID))
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(priority = 500)
 | 
						@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<String, User>} of all users stored locally with their
 | 
						 * @return a {@code Map<String, User>} of all users stored locally with their user names as keys
 | 
				
			||||||
	 *         user names as keys
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Map<String, User> getUsers() { return users; }
 | 
						public Map<String, User> getUsers() { return users; }
 | 
				
			||||||
@@ -311,7 +417,8 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public <T extends Message> Optional<T> getMessage(long id) {
 | 
						public <T extends Message> Optional<T> getMessage(long id) {
 | 
				
			||||||
		return (Optional<T>) chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
 | 
							return (Optional<T>) 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
 | 
						 * @return an optional containing the chat
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
 | 
						public Optional<Chat> 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
 | 
						 * @return all saved {@link Chat} objects that list the client user as the sender
 | 
				
			||||||
	 *         sender
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.1-alpha
 | 
						 * @since Envoy Client v0.1-alpha
 | 
				
			||||||
	 **/
 | 
						 **/
 | 
				
			||||||
	public ObservableList<Chat> getChats() { return chats; }
 | 
						public ObservableList<Chat> getChats() { return chats; }
 | 
				
			||||||
@@ -359,7 +467,9 @@ public final class LocalDB implements EventListener {
 | 
				
			|||||||
	 * @return {@code true} if an {@link IDGenerator} is present
 | 
						 * @return {@code true} if an {@link IDGenerator} is present
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @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
 | 
						 * @return the cache map for messages and message status changes
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,16 +5,16 @@ import java.util.*;
 | 
				
			|||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
import java.util.prefs.Preferences;
 | 
					import java.util.prefs.Preferences;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.event.EnvoyCloseEvent;
 | 
					 | 
				
			||||||
import envoy.util.*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
import dev.kske.eventbus.EventListener;
 | 
					import dev.kske.eventbus.EventListener;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.event.EnvoyCloseEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Manages all application settings, which are different objects that can be
 | 
					 * Manages all application settings, which are different objects that can be changed during runtime
 | 
				
			||||||
 * changed during runtime and serialized them by using either the file system or
 | 
					 * and serialized them by using either the file system or the {@link Preferences} API.
 | 
				
			||||||
 * the {@link Preferences} API.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
@@ -29,7 +29,8 @@ public final class Settings implements EventListener {
 | 
				
			|||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Settings are stored in this file.
 | 
						 * 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.
 | 
						 * Singleton instance of this class.
 | 
				
			||||||
@@ -37,8 +38,8 @@ public final class Settings implements EventListener {
 | 
				
			|||||||
	private static Settings settings = new Settings();
 | 
						private static Settings settings = new Settings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * The way to instantiate the settings. Is set to private to deny other
 | 
						 * The way to instantiate the settings. Is set to private to deny other instances of that
 | 
				
			||||||
	 * instances of that object.
 | 
						 * object.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @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
 | 
						 * @throws IOException if an error occurs while saving the themes
 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Event(eventType = EnvoyCloseEvent.class, priority = 900)
 | 
						@Event(eventType = EnvoyCloseEvent.class)
 | 
				
			||||||
	private void save() {
 | 
						private void save() {
 | 
				
			||||||
		EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");
 | 
							EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -76,20 +77,27 @@ public final class Settings implements EventListener {
 | 
				
			|||||||
		try {
 | 
							try {
 | 
				
			||||||
			SerializationUtils.write(settingsFile, items);
 | 
								SerializationUtils.write(settingsFile, items);
 | 
				
			||||||
		} catch (final IOException e) {
 | 
							} 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() {
 | 
						private void supplementDefaults() {
 | 
				
			||||||
		items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
 | 
							items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send",
 | 
				
			||||||
		items.putIfAbsent("hideOnClose", new SettingsItem<>(false, "Hide on close", "Hides the chat window when it is closed."));
 | 
								"Sends a message by pressing the enter key."));
 | 
				
			||||||
		items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
 | 
							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",
 | 
							items.putIfAbsent("downloadLocation",
 | 
				
			||||||
				new SettingsItem<>(new File(System.getProperty("user.home") + "/Downloads/"), "Download location",
 | 
								new SettingsItem<>(new File(System.getProperty("user.home") + "/Downloads/"),
 | 
				
			||||||
						"The location where files will be saved to"));
 | 
									"Download location",
 | 
				
			||||||
		items.putIfAbsent("autoSaveDownloads", new SettingsItem<>(false, "Save without asking?", "Should downloads be saved without asking?"));
 | 
									"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",
 | 
							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
 | 
						 * @param themeName the name to set
 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setCurrentTheme(String themeName) { ((SettingsItem<String>) items.get("currentTheme")).set(themeName); }
 | 
						public void setCurrentTheme(String themeName) {
 | 
				
			||||||
 | 
							((SettingsItem<String>) items.get("currentTheme")).set(themeName);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return true if the currently used theme is one of the default themes
 | 
						 * @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
 | 
						 * @return {@code true}, if pressing the {@code Enter} key suffices to send a message. Otherwise
 | 
				
			||||||
	 *         message. Otherwise it has to be pressed in conjunction with the
 | 
						 *         it has to be pressed in conjunction with the {@code Control} key.
 | 
				
			||||||
	 *         {@code Control} key.
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); }
 | 
						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.
 | 
						 * 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
 | 
						 * @param enterToSend If set to {@code true} a message can be sent by pressing the {@code Enter}
 | 
				
			||||||
	 *                    the {@code Enter} key. Otherwise it has to be pressed in
 | 
						 *                    key. Otherwise it has to be pressed in conjunction with the
 | 
				
			||||||
	 *                    conjunction with the {@code Control} key.
 | 
						 *                    {@code Control} key.
 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setEnterToSend(boolean enterToSend) { ((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend); }
 | 
						public void setEnterToSend(boolean enterToSend) {
 | 
				
			||||||
 | 
							((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return whether Envoy will prompt a dialogue before saving an
 | 
						 * @return whether Envoy will prompt a dialogue before saving an {@link envoy.data.Attachment}
 | 
				
			||||||
	 *         {@link envoy.data.Attachment}
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @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
 | 
						 * Sets whether Envoy will prompt a dialogue before saving an {@link envoy.data.Attachment}.
 | 
				
			||||||
	 * {@link envoy.data.Attachment}.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param autosaveDownload whether a download should be saved without asking
 | 
						 * @param autosaveDownload whether a download should be saved without asking before
 | 
				
			||||||
	 *                         before
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setDownloadSavedWithoutAsking(boolean autosaveDownload) {
 | 
						public void setDownloadSavedWithoutAsking(boolean autosaveDownload) {
 | 
				
			||||||
@@ -164,7 +174,9 @@ public final class Settings implements EventListener {
 | 
				
			|||||||
	 * @param downloadLocation the path to set
 | 
						 * @param downloadLocation the path to set
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setDownloadLocation(File downloadLocation) { ((SettingsItem<File>) items.get("downloadLocation")).set(downloadLocation); }
 | 
						public void setDownloadLocation(File downloadLocation) {
 | 
				
			||||||
 | 
							((SettingsItem<File>) items.get("downloadLocation")).set(downloadLocation);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the current on close mode.
 | 
						 * @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
 | 
						 * @param hideOnClose whether the application should be minimized on close
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setHideOnClose(boolean hideOnClose) { ((SettingsItem<Boolean>) items.get("hideOnClose")).set(hideOnClose); }
 | 
						public void setHideOnClose(boolean hideOnClose) {
 | 
				
			||||||
 | 
							((SettingsItem<Boolean>) items.get("hideOnClose")).set(hideOnClose);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return whether a confirmation dialog should be displayed before certain
 | 
						 * @return whether a confirmation dialog should be displayed before certain actions
 | 
				
			||||||
	 *         actions
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @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
 | 
						 * Changes the behavior of calling certain functionality by displaying a confirmation dialog
 | 
				
			||||||
	 * confirmation dialog before executing it.
 | 
						 * before executing it.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param askForConfirmation whether confirmation dialogs should be displayed
 | 
						 * @param askForConfirmation whether confirmation dialogs should be displayed before certain
 | 
				
			||||||
	 *                           before certain actions
 | 
						 *                           actions
 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setAskForConfirmation(boolean askForConfirmation) {
 | 
						public void setAskForConfirmation(boolean askForConfirmation) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,7 @@ import java.util.function.Consumer;
 | 
				
			|||||||
import javax.swing.JComponent;
 | 
					import javax.swing.JComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Encapsulates a persistent value that is directly or indirectly mutable by the
 | 
					 * Encapsulates a persistent value that is directly or indirectly mutable by the user.
 | 
				
			||||||
 * user.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @param <T> the type of this {@link SettingsItem}'s value
 | 
					 * @param <T> the type of this {@link SettingsItem}'s value
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
@@ -23,9 +22,8 @@ public final class SettingsItem<T> implements Serializable {
 | 
				
			|||||||
	private static final long serialVersionUID = 1L;
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a {@link SettingsItem}. The default value's class will be mapped
 | 
						 * Initializes a {@link SettingsItem}. The default value's class will be mapped to a
 | 
				
			||||||
	 * to a {@link JComponent} that can be used to display this {@link SettingsItem}
 | 
						 * {@link JComponent} that can be used to display this {@link SettingsItem} to the user.
 | 
				
			||||||
	 * to the user.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param value            the default value
 | 
						 * @param value            the default value
 | 
				
			||||||
	 * @param userFriendlyName the user friendly name (short)
 | 
						 * @param userFriendlyName the user friendly name (short)
 | 
				
			||||||
@@ -42,17 +40,20 @@ public final class SettingsItem<T> implements Serializable {
 | 
				
			|||||||
	 * @return the value
 | 
						 * @return the value
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @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
 | 
						 * Changes the value of this {@link SettingsItem}. If a {@code ChangeHandler} if defined, it
 | 
				
			||||||
	 * defined, it will be invoked with this value.
 | 
						 * will be invoked with this value.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param value the value to set
 | 
						 * @param value the value to set
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void set(T value) {
 | 
						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;
 | 
							this.value = value;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,7 +67,9 @@ public final class SettingsItem<T> implements Serializable {
 | 
				
			|||||||
	 * @param userFriendlyName the userFriendlyName to set
 | 
						 * @param userFriendlyName the userFriendlyName to set
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @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
 | 
						 * @return the description
 | 
				
			||||||
@@ -81,9 +84,8 @@ public final class SettingsItem<T> implements Serializable {
 | 
				
			|||||||
	public void setDescription(String description) { this.description = description; }
 | 
						public void setDescription(String description) { this.description = description; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sets a {@code ChangeHandler} for this {@link SettingsItem}. It will be
 | 
						 * Sets a {@code ChangeHandler} for this {@link SettingsItem}. It will be invoked with the
 | 
				
			||||||
	 * invoked with the current value once during the registration and every time
 | 
						 * current value once during the registration and every time when the value changes.
 | 
				
			||||||
	 * when the value changes.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param changeHandler the changeHandler to set
 | 
						 * @param changeHandler the changeHandler to set
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,9 @@ public final class AudioPlayer {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @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.
 | 
						 * Initializes the player with a given audio format.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,8 @@ public final class AudioRecorder {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @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.
 | 
						 * The format in which audio files will be saved.
 | 
				
			||||||
@@ -38,7 +39,9 @@ public final class AudioRecorder {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @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.
 | 
						 * Initializes the recorder with a given audio format.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,7 @@ package envoy.client.data.commands;
 | 
				
			|||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This interface defines an action that should be performed when a system
 | 
					 * This interface defines an action that should be performed when a system command gets called.
 | 
				
			||||||
 * command gets called.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
@@ -12,11 +11,9 @@ import java.util.List;
 | 
				
			|||||||
public interface Callable {
 | 
					public interface Callable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Performs the instance specific action when a {@link SystemCommand} has been
 | 
						 * Performs the instance specific action when a {@link SystemCommand} has been called.
 | 
				
			||||||
	 * called.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param arguments the arguments that should be passed to the
 | 
						 * @param arguments the arguments that should be passed to the {@link SystemCommand}
 | 
				
			||||||
	 *                  {@link SystemCommand}
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	void call(List<String> arguments);
 | 
						void call(List<String> arguments);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,15 +4,12 @@ import java.util.*;
 | 
				
			|||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This class is the base class of all {@code SystemCommands} and contains an
 | 
					 * This class is the base class of all {@code SystemCommands} and contains an action and a number of
 | 
				
			||||||
 * action and a number of arguments that should be used as input for this
 | 
					 * arguments that should be used as input for this function. No {@code SystemCommand} can return
 | 
				
			||||||
 * function.
 | 
					 * anything. Every {@code SystemCommand} must have as argument type {@code List<String>} so that the
 | 
				
			||||||
 * No {@code SystemCommand} can return anything.
 | 
					 * words following the indicator String can be used as input of the function. This approach has one
 | 
				
			||||||
 * Every {@code SystemCommand} must have as argument type {@code List<String>}
 | 
					 * limitation:<br>
 | 
				
			||||||
 * so that the words following the indicator String can be used as input of the
 | 
					 * <b>Order matters!</b> Changing the order of arguments will likely result in unexpected behavior.
 | 
				
			||||||
 * function. This approach has one limitation:<br>
 | 
					 | 
				
			||||||
 * <b>Order matters!</b> Changing the order of arguments will likely result in
 | 
					 | 
				
			||||||
 * unexpected behavior.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
@@ -28,8 +25,8 @@ public final class SystemCommand implements Callable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This function takes a {@code List<String>} as argument because automatically
 | 
						 * This function takes a {@code List<String>} as argument because automatically
 | 
				
			||||||
	 * {@code SystemCommand#numberOfArguments} words following the necessary command
 | 
						 * {@code SystemCommand#numberOfArguments} words following the necessary command will be put
 | 
				
			||||||
	 * will be put into this list.
 | 
						 * into this list.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @see String#split(String)
 | 
						 * @see String#split(String)
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -48,7 +45,8 @@ public final class SystemCommand implements Callable {
 | 
				
			|||||||
	 * @param description       the description of this {@code SystemCommand}
 | 
						 * @param description       the description of this {@code SystemCommand}
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public SystemCommand(Consumer<List<String>> action, int numberOfArguments, List<String> defaults, String description) {
 | 
						public SystemCommand(Consumer<List<String>> action, int numberOfArguments,
 | 
				
			||||||
 | 
							List<String> defaults, String description) {
 | 
				
			||||||
		this.numberOfArguments	= numberOfArguments;
 | 
							this.numberOfArguments	= numberOfArguments;
 | 
				
			||||||
		this.action				= action;
 | 
							this.action				= action;
 | 
				
			||||||
		this.defaults			= defaults == null ? new ArrayList<>() : defaults;
 | 
							this.defaults			= defaults == null ? new ArrayList<>() : defaults;
 | 
				
			||||||
@@ -92,20 +90,27 @@ public final class SystemCommand implements Callable {
 | 
				
			|||||||
	public List<String> getDefaults() { return defaults; }
 | 
						public List<String> getDefaults() { return defaults; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public int hashCode() { return Objects.hash(action); }
 | 
						public int hashCode() {
 | 
				
			||||||
 | 
							return Objects.hash(action);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean equals(Object obj) {
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
		if (this == obj) return true;
 | 
							if (this == obj)
 | 
				
			||||||
		if (obj == null) return false;
 | 
								return true;
 | 
				
			||||||
		if (getClass() != obj.getClass()) return false;
 | 
							if (obj == null)
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							if (getClass() != obj.getClass())
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
		final var other = (SystemCommand) obj;
 | 
							final var other = (SystemCommand) obj;
 | 
				
			||||||
		return Objects.equals(action, other.action);
 | 
							return Objects.equals(action, other.action);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() {
 | 
						public String toString() {
 | 
				
			||||||
		return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments + ", "
 | 
							return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments
 | 
				
			||||||
				+ (description != null ? "description=" + description + ", " : "") + (defaults != null ? "defaults=" + defaults : "") + "]";
 | 
								+ ", "
 | 
				
			||||||
 | 
								+ (description != null ? "description=" + description + ", " : "")
 | 
				
			||||||
 | 
								+ (defaults != null ? "defaults=" + defaults : "") + "]";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,18 +20,21 @@ public final class SystemCommandBuilder {
 | 
				
			|||||||
	private final SystemCommandMap commandsMap;
 | 
						private final SystemCommandMap commandsMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code SystemCommandsBuilder} without underlying
 | 
						 * Creates a new {@code SystemCommandsBuilder} without underlying {@link SystemCommandMap}.
 | 
				
			||||||
	 * {@link SystemCommandMap}.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public SystemCommandBuilder() { this(null); }
 | 
						public SystemCommandBuilder() {
 | 
				
			||||||
 | 
							this(null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param commandsMap the map to use when calling build (optional)
 | 
						 * @param commandsMap the map to use when calling build (optional)
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @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
 | 
						 * @param numberOfArguments the numberOfArguments to set
 | 
				
			||||||
@@ -104,12 +107,14 @@ public final class SystemCommandBuilder {
 | 
				
			|||||||
	 * @return the built {@code SystemCommand}
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @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.<br>
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
	 * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
 | 
						 * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the previous
 | 
				
			||||||
	 * previous value.<br>
 | 
						 * value.<br>
 | 
				
			||||||
	 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
						 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @return the built {@code SystemCommand}
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
@@ -122,8 +127,8 @@ public final class SystemCommandBuilder {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
	 * {@code SystemCommand#numberOfArguments} will be set to use the rest of the
 | 
						 * {@code SystemCommand#numberOfArguments} will be set to use the rest of the string as
 | 
				
			||||||
	 * string as argument, regardless of the previous value.<br>
 | 
						 * argument, regardless of the previous value.<br>
 | 
				
			||||||
	 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
						 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @return the built {@code SystemCommand}
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
@@ -136,27 +141,25 @@ public final class SystemCommandBuilder {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
	 * Automatically adds the built object to the given map.
 | 
						 * Automatically adds the built object to the given map. At the end, this
 | 
				
			||||||
	 * At the end, this {@code SystemCommandBuilder} <b>can</b> be reset but must
 | 
						 * {@code SystemCommandBuilder} <b>can</b> be reset but must not be.
 | 
				
			||||||
	 * not be.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param reset whether this {@code SystemCommandBuilder} should be reset
 | 
						 * @param reset whether this {@code SystemCommandBuilder} should be reset afterwards.<br>
 | 
				
			||||||
	 *              afterwards.<br>
 | 
						 *              This can be useful if another command wants to execute something similar
 | 
				
			||||||
	 *              This can be useful if another command wants to execute something
 | 
					 | 
				
			||||||
	 *              similar
 | 
					 | 
				
			||||||
	 * @return the built {@code SystemCommand}
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public SystemCommand build(boolean reset) {
 | 
						public SystemCommand build(boolean reset) {
 | 
				
			||||||
		final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
 | 
							final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
 | 
				
			||||||
		sc.setRelevance(relevance);
 | 
							sc.setRelevance(relevance);
 | 
				
			||||||
		if (reset) reset();
 | 
							if (reset)
 | 
				
			||||||
 | 
								reset();
 | 
				
			||||||
		return sc;
 | 
							return sc;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Builds a {@code SystemCommand} based upon the previously entered data.
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data. Automatically adds the
 | 
				
			||||||
	 * Automatically adds the built object to the given map.
 | 
						 * built object to the given map.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param command the command under which to store the SystemCommand in the
 | 
						 * @param command the command under which to store the SystemCommand in the
 | 
				
			||||||
	 *                {@link SystemCommandMap}
 | 
						 *                {@link SystemCommandMap}
 | 
				
			||||||
@@ -164,13 +167,14 @@ public final class SystemCommandBuilder {
 | 
				
			|||||||
	 * @throws NullPointerException if no map has been assigned to this builder
 | 
						 * @throws NullPointerException if no map has been assigned to this builder
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @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.<br>
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
	 * Automatically adds the built object to the given map.
 | 
						 * Automatically adds the built object to the given map. {@code SystemCommand#numberOfArguments}
 | 
				
			||||||
	 * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
 | 
						 * will be set to 0, regardless of the previous value.<br>
 | 
				
			||||||
	 * previous value.<br>
 | 
					 | 
				
			||||||
	 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
						 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param command the command under which to store the SystemCommand in the
 | 
						 * @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.<br>
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
	 * Automatically adds the built object to the given map.
 | 
						 * Automatically adds the built object to the given map. {@code SystemCommand#numberOfArguments}
 | 
				
			||||||
	 * {@code SystemCommand#numberOfArguments} will be set to use the rest of the
 | 
						 * will be set to use the rest of the string as argument, regardless of the previous value.<br>
 | 
				
			||||||
	 * string as argument, regardless of the previous value.<br>
 | 
					 | 
				
			||||||
	 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
						 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param command the command under which to store the SystemCommand in the
 | 
						 * @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.<br>
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
	 * Automatically adds the built object to the given map.
 | 
						 * Automatically adds the built object to the given map. At the end, this
 | 
				
			||||||
	 * At the end, this {@code SystemCommandBuilder} <b>can</b> be reset but must
 | 
						 * {@code SystemCommandBuilder} <b>can</b> be reset but must not be.
 | 
				
			||||||
	 * not be.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param command the command under which to store the SystemCommand in the
 | 
						 * @param command the command under which to store the SystemCommand in the
 | 
				
			||||||
	 *                {@link SystemCommandMap}
 | 
						 *                {@link SystemCommandMap}
 | 
				
			||||||
	 * @param reset   whether this {@code SystemCommandBuilder} should be reset
 | 
						 * @param reset   whether this {@code SystemCommandBuilder} should be reset afterwards.<br>
 | 
				
			||||||
	 *                afterwards.<br>
 | 
						 *                This can be useful if another command wants to execute something similar
 | 
				
			||||||
	 *                This can be useful if another command wants to execute
 | 
					 | 
				
			||||||
	 *                something
 | 
					 | 
				
			||||||
	 *                similar
 | 
					 | 
				
			||||||
	 * @return the built {@code SystemCommand}
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
	 * @throws NullPointerException if no map has been assigned to this builder
 | 
						 * @throws NullPointerException if no map has been assigned to this builder
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
@@ -222,9 +221,12 @@ public final class SystemCommandBuilder {
 | 
				
			|||||||
	public SystemCommand build(String command, boolean reset) {
 | 
						public SystemCommand build(String command, boolean reset) {
 | 
				
			||||||
		final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
 | 
							final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
 | 
				
			||||||
		sc.setRelevance(relevance);
 | 
							sc.setRelevance(relevance);
 | 
				
			||||||
		if (commandsMap != null) commandsMap.add(command, sc);
 | 
							if (commandsMap != null)
 | 
				
			||||||
		else throw new NullPointerException("No map in SystemCommandsBuilder present");
 | 
								commandsMap.add(command, sc);
 | 
				
			||||||
		if (reset) reset();
 | 
							else
 | 
				
			||||||
 | 
								throw new NullPointerException("No map in SystemCommandsBuilder present");
 | 
				
			||||||
 | 
							if (reset)
 | 
				
			||||||
 | 
								reset();
 | 
				
			||||||
		return sc;
 | 
							return sc;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,11 +14,9 @@ import javafx.scene.control.Alert.AlertType;
 | 
				
			|||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Stores all {@link SystemCommand}s used.
 | 
					 * Stores all {@link SystemCommand}s used. SystemCommands can be called using an activator char and
 | 
				
			||||||
 * SystemCommands can be called using an activator char and the text that needs
 | 
					 * the text that needs to be present behind the activator. Additionally offers the option to request
 | 
				
			||||||
 * to be present behind the activator.
 | 
					 * recommendations for a partial input String.
 | 
				
			||||||
 * Additionally offers the option to request recommendations for a partial input
 | 
					 | 
				
			||||||
 * String.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
@@ -27,96 +25,100 @@ public final class SystemCommandMap {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private final Character						activator;
 | 
						private final Character						activator;
 | 
				
			||||||
	private final Map<String, SystemCommand>	systemCommands	= new HashMap<>();
 | 
						private final Map<String, SystemCommand>	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);
 | 
						private static final Logger logger = EnvoyLog.getLogger(SystemCommandMap.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code SystemCommandMap} with the given char as activator.
 | 
						 * Creates a new {@code SystemCommandMap} with the given char as activator. If this Character is
 | 
				
			||||||
	 * If this Character is null, any text used as input will be treated as a system
 | 
						 * null, any text used as input will be treated as a system command.
 | 
				
			||||||
	 * command.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param activator the char to use as activator for commands
 | 
						 * @param activator the char to use as activator for commands
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @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.
 | 
						 * Creates a new {@code SystemCommandMap} with '/' as activator.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @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.
 | 
						 * Adds a new command to the map if the command name is valid.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param command       the input string to execute the
 | 
						 * @param command       the input string to execute the given action
 | 
				
			||||||
	 *                      given action
 | 
						 * @param systemCommand the command to add - can be built using {@link SystemCommandBuilder}
 | 
				
			||||||
	 * @param systemCommand the command to add - can be built using
 | 
					 | 
				
			||||||
	 *                      {@link SystemCommandBuilder}
 | 
					 | 
				
			||||||
	 * @see SystemCommandMap#isValidKey(String)
 | 
						 * @see SystemCommandMap#isValidKey(String)
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void add(String command, SystemCommand systemCommand) {
 | 
						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
 | 
						 * This method checks if the input String is a key in the map and returns the wrapped System
 | 
				
			||||||
	 * wrapped System command if present.
 | 
						 * command if present.
 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * Usage example:<br>
 | 
						 * Usage example:<br>
 | 
				
			||||||
	 * {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}<br>
 | 
						 * {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}<br>
 | 
				
			||||||
	 * {@code systemCommands.add("example", new SystemCommand(text -> {}, 1, null,
 | 
						 * {@code systemCommands.add("example", new SystemCommand(text -> {}, 1, null, ""));}<br>
 | 
				
			||||||
	 * ""));}<br>
 | 
					 | 
				
			||||||
	 * {@code ....}<br>
 | 
						 * {@code ....}<br>
 | 
				
			||||||
	 * user input: {@code "*example xyz ..."}<br>
 | 
						 * user input: {@code "*example xyz ..."}<br>
 | 
				
			||||||
	 * {@code systemCommands.get("example xyz ...")} or
 | 
						 * {@code systemCommands.get("example xyz ...")} or
 | 
				
			||||||
	 * {@code systemCommands.get("*example xyz ...")}
 | 
						 * {@code systemCommands.get("*example xyz ...")} result:
 | 
				
			||||||
	 * result: {@code Optional<SystemCommand>.get() != null}
 | 
						 * {@code Optional<SystemCommand>.get() != null}
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param input the input string given by the user
 | 
						 * @param input the input string given by the user
 | 
				
			||||||
	 * @return the wrapped system command, if present
 | 
						 * @return the wrapped system command, if present
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Optional<SystemCommand> get(String input) { return Optional.ofNullable(systemCommands.get(getCommand(input.toLowerCase()))); }
 | 
						public Optional<SystemCommand> get(String input) {
 | 
				
			||||||
 | 
							return Optional.ofNullable(systemCommands.get(getCommand(input.toLowerCase())));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This method ensures that the activator of a {@link SystemCommand} is
 | 
						 * This method ensures that the activator of a {@link SystemCommand} is stripped.<br>
 | 
				
			||||||
	 * stripped.<br>
 | 
						 * It only checks the word beginning from the first non-blank position in the input. It returns
 | 
				
			||||||
	 * It only checks the word beginning from the first non-blank position in the
 | 
						 * the command as (most likely) entered as key in the map for the first word of the text.<br>
 | 
				
			||||||
	 * input.
 | 
					 | 
				
			||||||
	 * It returns the command as (most likely) entered as key in the map for the
 | 
					 | 
				
			||||||
	 * first word of the text.<br>
 | 
					 | 
				
			||||||
	 * Activators in the middle of the word will be disregarded.
 | 
						 * Activators in the middle of the word will be disregarded.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param raw the input
 | 
						 * @param raw the input
 | 
				
			||||||
	 * @return the command as entered in the map
 | 
						 * @return the command as entered in the map
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 * @apiNote this method will (most likely) not return anything useful if
 | 
						 * @apiNote this method will (most likely) not return anything useful if whatever is entered
 | 
				
			||||||
	 *          whatever is entered after the activator is not a system command.
 | 
						 *          after the activator is not a system command. Only exception: for recommendation
 | 
				
			||||||
	 *          Only exception: for recommendation purposes.
 | 
						 *          purposes.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public String getCommand(String raw) {
 | 
						public String getCommand(String raw) {
 | 
				
			||||||
		final var trimmed = raw.stripLeading();
 | 
							final var trimmed = raw.stripLeading();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Entering only the activator should not throw an error
 | 
							// 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 {
 | 
							else {
 | 
				
			||||||
			final var index = trimmed.indexOf(' ');
 | 
								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
 | 
						 * Examines whether a key can be put in the map and logs it with {@code Level.WARNING} if that
 | 
				
			||||||
	 * {@code Level.WARNING} if that key violates API constrictions.<br>
 | 
						 * key violates API constrictions.<br>
 | 
				
			||||||
	 * (allowed chars are <b>a-zA-Z0-9_:!/()?.,;-</b>)
 | 
						 * (allowed chars are <b>a-zA-Z0-9_:!/()?.,;-</b>)
 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * The approach to not throw an exception was taken so that an ugly try-catch
 | 
						 * The approach to not throw an exception was taken so that an ugly try-catch block for every
 | 
				
			||||||
	 * block for every addition to the system commands map could be avoided, an
 | 
						 * addition to the system commands map could be avoided, an error that should only occur during
 | 
				
			||||||
	 * error that should only occur during implementation and not in production.
 | 
						 * implementation and not in production.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param command the key to examine
 | 
						 * @param command the key to examine
 | 
				
			||||||
	 * @return whether this key can be used in the map
 | 
						 * @return whether this key can be used in the map
 | 
				
			||||||
@@ -124,17 +126,18 @@ public final class SystemCommandMap {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean isValidKey(String command) {
 | 
						public boolean isValidKey(String command) {
 | 
				
			||||||
		final var valid = commandPattern.matcher(command).matches();
 | 
							final var valid = commandPattern.matcher(command).matches();
 | 
				
			||||||
		if (!valid) logger.log(Level.WARNING,
 | 
							if (!valid)
 | 
				
			||||||
 | 
								logger.log(Level.WARNING,
 | 
				
			||||||
				"The command \"" + command
 | 
									"The command \"" + command
 | 
				
			||||||
						+ "\" is not valid. As it might cause problems when executed, it will not be entered into the map. Only the characters "
 | 
										+ "\" is not valid. As it might cause problems when executed, it will not be entered into the map. Only the characters "
 | 
				
			||||||
						+ commandPattern + "are allowed");
 | 
										+ commandPattern + "are allowed");
 | 
				
			||||||
		return valid;
 | 
							return valid;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Takes a 'raw' string (the whole input) and checks if the activator is the
 | 
						 * Takes a 'raw' string (the whole input) and checks if the activator is the first visible
 | 
				
			||||||
	 * first visible character and then checks if a command is present after that
 | 
						 * character and then checks if a command is present after that activator. If that is the case,
 | 
				
			||||||
	 * activator. If that is the case, it will be executed.
 | 
						 * it will be executed.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param raw the raw input string
 | 
						 * @param raw the raw input string
 | 
				
			||||||
	 * @return whether a command could be found and successfully executed
 | 
						 * @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
 | 
							// possibly a command was detected and could be executed
 | 
				
			||||||
		final var	raw2			= raw.stripLeading();
 | 
							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
 | 
							// 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;
 | 
							return commandFound;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Retrieves the recommendations based on the current input entered.<br>
 | 
						 * Retrieves the recommendations based on the current input entered.<br>
 | 
				
			||||||
	 * The first word is used for the recommendations and
 | 
						 * The first word is used for the recommendations and it does not matter if the activator is at
 | 
				
			||||||
	 * it does not matter if the activator is at its beginning or not.<br>
 | 
						 * its beginning or not.<br>
 | 
				
			||||||
	 * If recommendations are present, the given function will be executed on the
 | 
						 * If recommendations are present, the given function will be executed on the
 | 
				
			||||||
	 * recommendations.<br>
 | 
						 * recommendations.<br>
 | 
				
			||||||
	 * Otherwise nothing will be done.<br>
 | 
						 * Otherwise nothing will be done.<br>
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param input  the input string
 | 
						 * @param input  the input string
 | 
				
			||||||
	 * @param action the action that should be taken for the recommendations, if any
 | 
						 * @param action the action that should be taken for the recommendations, if any are present
 | 
				
			||||||
	 *               are present
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void requestRecommendations(String input, Consumer<Set<String>> action) {
 | 
						public void requestRecommendations(String input, Consumer<Set<String>> action) {
 | 
				
			||||||
@@ -169,27 +174,28 @@ public final class SystemCommandMap {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Get the expected commands
 | 
							// Get the expected commands
 | 
				
			||||||
		final var recommendations = recommendCommands(partialCommand);
 | 
							final var recommendations = recommendCommands(partialCommand);
 | 
				
			||||||
		if (recommendations.isEmpty()) return;
 | 
							if (recommendations.isEmpty())
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Execute the given action
 | 
							// 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
 | 
						 * This method checks if the input String is a key in the map and executes the wrapped System
 | 
				
			||||||
	 * wrapped System command if present.
 | 
						 * command if present.
 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * Usage example:<br>
 | 
						 * Usage example:<br>
 | 
				
			||||||
	 * {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}<br>
 | 
						 * {@code SystemCommandMap systemCommands = new SystemCommandMap('*');}<br>
 | 
				
			||||||
	 * {@code Button button = new Button();}<br>
 | 
						 * {@code Button button = new Button();}<br>
 | 
				
			||||||
	 * {@code systemCommands.add("example", new SystemCommand(text ->
 | 
						 * {@code systemCommands.add("example", new SystemCommand(text -> {button.setText(text.get(0))},
 | 
				
			||||||
	 * {button.setText(text.get(0))}, 1, null,
 | 
						 * 1, null, ""));}<br>
 | 
				
			||||||
	 * ""));}<br>
 | 
					 | 
				
			||||||
	 * {@code ....}<br>
 | 
						 * {@code ....}<br>
 | 
				
			||||||
	 * user input: {@code "*example xyz ..."}<br>
 | 
						 * user input: {@code "*example xyz ..."}<br>
 | 
				
			||||||
	 * {@code systemCommands.executeIfPresent("example xyz ...")} or
 | 
						 * {@code systemCommands.executeIfPresent("example xyz ...")} or
 | 
				
			||||||
	 * {@code systemCommands.executeIfPresent("*example xyz ...")}
 | 
						 * {@code systemCommands.executeIfPresent("*example xyz ...")} result:
 | 
				
			||||||
	 * result: {@code button.getText()=="xyz"}
 | 
						 * {@code button.getText()=="xyz"}
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param input the input string given by the user
 | 
						 * @param input the input string given by the user
 | 
				
			||||||
	 * @return whether a command could be found and successfully executed
 | 
						 * @return whether a command could be found and successfully executed
 | 
				
			||||||
@@ -211,9 +217,9 @@ public final class SystemCommandMap {
 | 
				
			|||||||
				systemCommand.call(arguments);
 | 
									systemCommand.call(arguments);
 | 
				
			||||||
			} catch (final NumberFormatException e) {
 | 
								} catch (final NumberFormatException e) {
 | 
				
			||||||
				logger.log(Level.INFO,
 | 
									logger.log(Level.INFO,
 | 
				
			||||||
						String.format(
 | 
										String.format(
 | 
				
			||||||
								"System command %s could not be performed correctly because the user is a dumbass and could not write a parseable number.",
 | 
											"System command %s could not be performed correctly because the user is a dumbass and could not write a parseable number.",
 | 
				
			||||||
								command));
 | 
											command));
 | 
				
			||||||
				Platform.runLater(() -> {
 | 
									Platform.runLater(() -> {
 | 
				
			||||||
					final var alert = new Alert(AlertType.ERROR);
 | 
										final var alert = new Alert(AlertType.ERROR);
 | 
				
			||||||
					alert.setContentText("Please enter a readable number as argument.");
 | 
										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);
 | 
									logger.log(Level.WARNING, "System command " + command + " threw an exception: ", e);
 | 
				
			||||||
				Platform.runLater(() -> {
 | 
									Platform.runLater(() -> {
 | 
				
			||||||
					final var alert = new Alert(AlertType.ERROR);
 | 
										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();
 | 
										alert.showAndWait();
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
				commandExecuted.set(false);
 | 
									commandExecuted.set(false);
 | 
				
			||||||
@@ -245,7 +252,8 @@ public final class SystemCommandMap {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// no more arguments follow after the command (e.g. text = "/DABR")
 | 
							// no more arguments follow after the command (e.g. text = "/DABR")
 | 
				
			||||||
		final var indexOfSpace = input.indexOf(" ");
 | 
							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
 | 
							// the arguments behind a system command
 | 
				
			||||||
		final var	remainingString		= input.substring(indexOfSpace + 1);
 | 
							final var	remainingString		= input.substring(indexOfSpace + 1);
 | 
				
			||||||
@@ -253,15 +261,17 @@ public final class SystemCommandMap {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// splitting those arguments and supplying default values
 | 
							// splitting those arguments and supplying default values
 | 
				
			||||||
		final var	textArguments		= remainingString.split(" ", -1);
 | 
							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);
 | 
							final var	arguments			= supplementDefaults(originalArguments, systemCommand);
 | 
				
			||||||
		return arguments;
 | 
							return arguments;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Recommends commands based upon the currently entered input.<br>
 | 
						 * Recommends commands based upon the currently entered input.<br>
 | 
				
			||||||
	 * In the current implementation, all that gets checked is whether a key
 | 
						 * In the current implementation, all that gets checked is whether a key contains this input.
 | 
				
			||||||
	 * contains this input. This might be updated later on.
 | 
						 * This might be updated later on.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param partialCommand the partially entered command
 | 
						 * @param partialCommand the partially entered command
 | 
				
			||||||
	 * @return a set of all commands that match this input
 | 
						 * @return a set of all commands that match this input
 | 
				
			||||||
@@ -274,36 +284,41 @@ public final class SystemCommandMap {
 | 
				
			|||||||
		return systemCommands.keySet()
 | 
							return systemCommands.keySet()
 | 
				
			||||||
			.stream()
 | 
								.stream()
 | 
				
			||||||
			.filter(command -> command.contains(partialCommand))
 | 
								.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());
 | 
								.collect(Collectors.toSet());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Supplies the default values for arguments if none are present in the text for
 | 
						 * Supplies the default values for arguments if none are present in the text for any argument.
 | 
				
			||||||
	 * any argument. <br>
 | 
						 * <br>
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param textArguments the arguments that were parsed from the text
 | 
						 * @param textArguments the arguments that were parsed from the text
 | 
				
			||||||
	 * @param toEvaluate    the system command whose default values should be used
 | 
						 * @param toEvaluate    the system command whose default values should be used
 | 
				
			||||||
	 * @return the final argument list
 | 
						 * @return the final argument list
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 * @apiNote this method will insert an empty String if the size of the list
 | 
						 * @apiNote this method will insert an empty String if the size of the list given to the
 | 
				
			||||||
	 *          given to the {@code SystemCommand} is smaller than its argument
 | 
						 *          {@code SystemCommand} is smaller than its argument counter and no more text
 | 
				
			||||||
	 *          counter and no more text arguments could be found.
 | 
						 *          arguments could be found.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private List<String> supplementDefaults(String[] textArguments, SystemCommand toEvaluate) {
 | 
						private List<String> supplementDefaults(String[] textArguments, SystemCommand toEvaluate) {
 | 
				
			||||||
		final var			defaults			= toEvaluate.getDefaults();
 | 
							final var			defaults			= toEvaluate.getDefaults();
 | 
				
			||||||
		final var			numberOfArguments	= toEvaluate.getNumberOfArguments();
 | 
							final var			numberOfArguments	= toEvaluate.getNumberOfArguments();
 | 
				
			||||||
		final List<String>	result				= new ArrayList<>();
 | 
							final List<String>	result				= new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (toEvaluate.getNumberOfArguments() > 0) for (var index = 0; index < numberOfArguments; index++) {
 | 
							if (toEvaluate.getNumberOfArguments() > 0)
 | 
				
			||||||
			String textArg = null;
 | 
								for (var index = 0; index < numberOfArguments; index++) {
 | 
				
			||||||
			if (index < textArguments.length) textArg = textArguments[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
 | 
									// 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.
 | 
									// is present. Otherwise the default for that argument will be taken if present.
 | 
				
			||||||
			// In the worst case, an empty String will be used.
 | 
									// In the worst case, an empty String will be used.
 | 
				
			||||||
			result.add(!(textArg == null) && !textArg.isBlank() ? textArg : index < defaults.size() ? defaults.get(index) : "");
 | 
									result.add(!(textArg == null) && !textArg.isBlank() ? textArg
 | 
				
			||||||
		}
 | 
										: index < defaults.size() ? defaults.get(index) : "");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,8 @@ package envoy.client.data.shortcuts;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javafx.scene.input.*;
 | 
					import javafx.scene.input.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Context;
 | 
					import envoy.client.data.Context;
 | 
				
			||||||
import envoy.client.helper.ShutdownHelper;
 | 
					import envoy.client.helper.ShutdownHelper;
 | 
				
			||||||
import envoy.client.ui.SceneContext.SceneInfo;
 | 
					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
 | 
							// Add the option to exit with "Control" + "Q" or "Alt" + "F4" as offered by
 | 
				
			||||||
		// some desktop environments
 | 
							// 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
 | 
							// 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),
 | 
							instance.addForNotExcluded(
 | 
				
			||||||
				UserUtil::logout,
 | 
								new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN,
 | 
				
			||||||
				SceneInfo.LOGIN_SCENE);
 | 
									KeyCombination.SHIFT_DOWN),
 | 
				
			||||||
 | 
								UserUtil::logout,
 | 
				
			||||||
 | 
								SceneInfo.LOGIN_SCENE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Add option to open settings scene with "Control"+"S", if not in 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),
 | 
							instance.addForNotExcluded(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN),
 | 
				
			||||||
				() -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE),
 | 
								() -> Context.getInstance().getSceneContext().load(SceneInfo.SETTINGS_SCENE),
 | 
				
			||||||
				SceneInfo.SETTINGS_SCENE,
 | 
								SceneInfo.SETTINGS_SCENE,
 | 
				
			||||||
				SceneInfo.LOGIN_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);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,8 @@ import envoy.client.ui.SceneContext.SceneInfo;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public final class GlobalKeyShortcuts {
 | 
					public final class GlobalKeyShortcuts {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final EnumMap<SceneInfo, Map<KeyCombination, Runnable>> shortcuts = new EnumMap<>(SceneInfo.class);
 | 
						private final EnumMap<SceneInfo, Map<KeyCombination, Runnable>> shortcuts =
 | 
				
			||||||
 | 
							new EnumMap<>(SceneInfo.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static GlobalKeyShortcuts instance = new GlobalKeyShortcuts();
 | 
						private static GlobalKeyShortcuts instance = new GlobalKeyShortcuts();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,16 +37,16 @@ public final class GlobalKeyShortcuts {
 | 
				
			|||||||
	 * @param action the action to perform
 | 
						 * @param action the action to perform
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @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
 | 
						 * Adds the given keyboard shortcut and its action to all scenes that are not part of exclude.
 | 
				
			||||||
	 * part of exclude.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param keys    the keys to press to perform the given action
 | 
						 * @param keys    the keys to press to perform the given action
 | 
				
			||||||
	 * @param action  the action to perform
 | 
						 * @param action  the action to perform
 | 
				
			||||||
	 * @param exclude the scenes that should be excluded from receiving this
 | 
						 * @param exclude the scenes that should be excluded from receiving this keyboard shortcut
 | 
				
			||||||
	 *                keyboard shortcut
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void addForNotExcluded(KeyCombination keys, Runnable action, SceneInfo... exclude) {
 | 
						public void addForNotExcluded(KeyCombination keys, Runnable action, SceneInfo... exclude) {
 | 
				
			||||||
@@ -53,10 +54,10 @@ public final class GlobalKeyShortcuts {
 | 
				
			|||||||
		// Computing the remaining sceneInfos
 | 
							// Computing the remaining sceneInfos
 | 
				
			||||||
		final var	include	= new SceneInfo[SceneInfo.values().length - exclude.length];
 | 
							final var	include	= new SceneInfo[SceneInfo.values().length - exclude.length];
 | 
				
			||||||
		int			index	= 0;
 | 
							int			index	= 0;
 | 
				
			||||||
		outer:
 | 
							outer: for (final var sceneInfo : SceneInfo.values()) {
 | 
				
			||||||
		for (final var sceneInfo : SceneInfo.values()) {
 | 
					 | 
				
			||||||
			for (final var excluded : exclude)
 | 
								for (final var excluded : exclude)
 | 
				
			||||||
				if (sceneInfo.equals(excluded)) continue outer;
 | 
									if (sceneInfo.equals(excluded))
 | 
				
			||||||
 | 
										continue outer;
 | 
				
			||||||
			include[index++] = sceneInfo;
 | 
								include[index++] = sceneInfo;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,5 +73,7 @@ public final class GlobalKeyShortcuts {
 | 
				
			|||||||
	 * @return all stored keyboard shortcuts for this scene
 | 
						 * @return all stored keyboard shortcuts for this scene
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Map<KeyCombination, Runnable> getKeyboardShortcuts(SceneInfo sceneInfo) { return shortcuts.get(sceneInfo); }
 | 
						public Map<KeyCombination, Runnable> getKeyboardShortcuts(SceneInfo sceneInfo) {
 | 
				
			||||||
 | 
							return shortcuts.get(sceneInfo);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,10 +7,9 @@ import javafx.scene.input.KeyCombination;
 | 
				
			|||||||
import envoy.client.ui.SceneContext;
 | 
					import envoy.client.ui.SceneContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides methods to set the keyboard shortcuts for a specific scene.
 | 
					 * Provides methods to set the keyboard shortcuts for a specific scene. Should only be implemented
 | 
				
			||||||
 * Should only be implemented by controllers of scenes so that these methods can
 | 
					 * by controllers of scenes so that these methods can automatically be called inside
 | 
				
			||||||
 * automatically be called inside {@link SceneContext} as soon
 | 
					 * {@link SceneContext} as soon as the underlying FXML file has been loaded.
 | 
				
			||||||
 * as the underlying FXML file has been loaded.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.3-beta
 | 
					 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								client/src/main/java/envoy/client/event/ContactDisabled.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								client/src/main/java/envoy/client/event/ContactDisabled.java
									
									
									
									
									
										Normal file
									
								
							@@ -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<Contact> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,9 +3,8 @@ package envoy.client.event;
 | 
				
			|||||||
import envoy.event.Event.Valueless;
 | 
					import envoy.event.Event.Valueless;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This event notifies various Envoy components of the application being about
 | 
					 * This event notifies various Envoy components of the application being about to shut down. This
 | 
				
			||||||
 * to shut down. This allows the graceful closing of connections, persisting
 | 
					 * allows the graceful closing of connections, persisting local data etc.
 | 
				
			||||||
 * local data etc.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,5 +16,7 @@ public class MessageDeletion extends Event<Long> {
 | 
				
			|||||||
	 * @param messageID the ID of the deleted message
 | 
						 * @param messageID the ID of the deleted message
 | 
				
			||||||
	 * @since Envoy Common v0.3-beta
 | 
						 * @since Envoy Common v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public MessageDeletion(long messageID) { super(messageID); }
 | 
						public MessageDeletion(long messageID) {
 | 
				
			||||||
 | 
							super(messageID);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,5 +17,7 @@ public class OwnStatusChange extends Event<UserStatus> {
 | 
				
			|||||||
	 * @param value the new user status of the client user
 | 
						 * @param value the new user status of the client user
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public OwnStatusChange(UserStatus value) { super(value); }
 | 
						public OwnStatusChange(UserStatus value) {
 | 
				
			||||||
 | 
							super(value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,20 +15,19 @@ public final class AlertHelper {
 | 
				
			|||||||
	private AlertHelper() {}
 | 
						private AlertHelper() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Asks for a confirmation dialog if {@link Settings#isAskForConfirmation()}
 | 
						 * Asks for a confirmation dialog if {@link Settings#isAskForConfirmation()} returns
 | 
				
			||||||
	 * returns {@code true}.
 | 
						 * {@code true}. Immediately executes the action if no dialog was requested or the dialog was
 | 
				
			||||||
	 * 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.
 | 
				
			||||||
	 * exited with a confirmation.
 | 
					 | 
				
			||||||
	 * Does nothing if the dialog was closed without clicking on OK.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param alert  the (customized) alert to show. <strong>Should not be shown
 | 
						 * @param alert  the (customized) alert to show. <strong>Should not be shown already</strong>
 | 
				
			||||||
	 *               already</strong>
 | 
					 | 
				
			||||||
	 * @param action the action to perform in case of success
 | 
						 * @param action the action to perform in case of success
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void confirmAction(Alert alert, Runnable action) {
 | 
						public static void confirmAction(Alert alert, Runnable action) {
 | 
				
			||||||
		alert.setHeaderText("");
 | 
							alert.setHeaderText("");
 | 
				
			||||||
		if (Settings.getInstance().isAskForConfirmation()) alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
 | 
							if (Settings.getInstance().isAskForConfirmation())
 | 
				
			||||||
		else action.run();
 | 
								alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								action.run();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
package envoy.client.helper;
 | 
					package envoy.client.helper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.event.EnvoyCloseEvent;
 | 
					import envoy.client.event.EnvoyCloseEvent;
 | 
				
			||||||
import envoy.client.ui.StatusTrayIcon;
 | 
					import envoy.client.ui.StatusTrayIcon;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Simplifies shutdown actions.
 | 
					 * Simplifies shutdown actions.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -22,18 +22,21 @@ public final class ShutdownHelper {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @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},
 | 
						 * Exits Envoy immediately if {@code force = true}, else it can exit or minimize Envoy,
 | 
				
			||||||
	 * else it can exit or minimize Envoy, depending on the current state of
 | 
						 * depending on the current state of {@link Settings#isHideOnClose()} and
 | 
				
			||||||
	 * {@link Settings#isHideOnClose()} and {@link StatusTrayIcon#isSupported()}.
 | 
						 * {@link StatusTrayIcon#isSupported()}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param force whether to close in any case.
 | 
						 * @param force whether to close in any case.
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void exit(boolean force) {
 | 
						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 {
 | 
							else {
 | 
				
			||||||
			EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
								EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
				
			||||||
			System.exit(0);
 | 
								System.exit(0);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,18 +5,19 @@ import java.net.Socket;
 | 
				
			|||||||
import java.util.concurrent.TimeoutException;
 | 
					import java.util.concurrent.TimeoutException;
 | 
				
			||||||
import java.util.logging.*;
 | 
					import java.util.logging.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
import envoy.client.event.EnvoyCloseEvent;
 | 
					import dev.kske.eventbus.Event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
import envoy.event.*;
 | 
					import envoy.event.*;
 | 
				
			||||||
import envoy.util.*;
 | 
					import envoy.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import dev.kske.eventbus.Event;
 | 
					import envoy.client.event.EnvoyCloseEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Establishes a connection to the server, performs a handshake and delivers
 | 
					 * Establishes a connection to the server, performs a handshake and delivers certain objects to the
 | 
				
			||||||
 * certain objects to the server.
 | 
					 * server.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
@@ -44,26 +45,31 @@ public final class Client implements EventListener, Closeable {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @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
 | 
						 * Enters the online mode by acquiring a user ID from the server. As a connection has to be
 | 
				
			||||||
	 * connection has to be established and a handshake has to be made, this method
 | 
						 * established and a handshake has to be made, this method will block for up to 5 seconds. If
 | 
				
			||||||
	 * will block for up to 5 seconds. If the handshake does exceed this time limit,
 | 
						 * the handshake does exceed this time limit, an exception is thrown.
 | 
				
			||||||
	 * an exception is thrown.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param credentials the login credentials of the user
 | 
						 * @param credentials the login credentials of the user
 | 
				
			||||||
	 * @param cacheMap    the map of all caches needed
 | 
						 * @param cacheMap    the map of all caches needed
 | 
				
			||||||
	 * @throws TimeoutException     if the server could not be reached
 | 
						 * @throws TimeoutException     if the server could not be reached
 | 
				
			||||||
	 * @throws IOException          if the login credentials could not be written
 | 
						 * @throws IOException          if the login credentials could not be written
 | 
				
			||||||
	 * @throws InterruptedException if the current thread is interrupted while
 | 
						 * @throws InterruptedException if the current thread is interrupted while waiting for the
 | 
				
			||||||
	 *                              waiting for the handshake response
 | 
						 *                              handshake response
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void performHandshake(LoginCredentials credentials, CacheMap cacheMap) throws TimeoutException, IOException, InterruptedException {
 | 
						public void performHandshake(LoginCredentials credentials, CacheMap cacheMap)
 | 
				
			||||||
		if (online) throw new IllegalStateException("Handshake has already been performed successfully");
 | 
							throws TimeoutException, IOException, InterruptedException {
 | 
				
			||||||
 | 
							if (online)
 | 
				
			||||||
 | 
								throw new IllegalStateException("Handshake has already been performed successfully");
 | 
				
			||||||
 | 
							rejected = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Establish TCP connection
 | 
							// 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());
 | 
							socket = new Socket(config.getServer(), config.getPort());
 | 
				
			||||||
		logger.log(Level.FINE, "Successfully established TCP connection to server");
 | 
							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.registerProcessor(User.class, sender -> this.sender = sender);
 | 
				
			||||||
		receiver.registerProcessors(cacheMap.getMap());
 | 
							receiver.registerProcessors(cacheMap.getMap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		rejected = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Start receiver
 | 
							// Start receiver
 | 
				
			||||||
		receiver.start();
 | 
							receiver.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,7 +99,10 @@ public final class Client implements EventListener, Closeable {
 | 
				
			|||||||
				return;
 | 
									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);
 | 
								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
 | 
						 * Initializes the {@link Receiver} used to process data sent from the server to this client.
 | 
				
			||||||
	 * this client.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param localDB  the local database used to persist the current
 | 
						 * @param localDB  the local database used to persist the current {@link IDGenerator}
 | 
				
			||||||
	 *                 {@link IDGenerator}
 | 
					 | 
				
			||||||
	 * @param cacheMap the map of all caches needed
 | 
						 * @param cacheMap the map of all caches needed
 | 
				
			||||||
	 * @throws IOException if no {@link IDGenerator} is present and none could be
 | 
						 * @throws IOException if no {@link IDGenerator} is present and none could be requested from the
 | 
				
			||||||
	 *                     requested from the server
 | 
						 *                     server
 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void initReceiver(LocalDB localDB, CacheMap cacheMap) throws IOException {
 | 
						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);
 | 
							cacheMap.get(GroupMessageStatusChange.class).setProcessor(eventBus::dispatch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Request a generator if none is present or the existing one is consumed
 | 
							// 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
 | 
							// Relay caches
 | 
				
			||||||
		cacheMap.getMap().values().forEach(Cache::relay);
 | 
							cacheMap.getMap().values().forEach(Cache::relay);
 | 
				
			||||||
@@ -146,14 +152,14 @@ public final class Client implements EventListener, Closeable {
 | 
				
			|||||||
		logger.log(Level.FINE, "Sending " + obj);
 | 
							logger.log(Level.FINE, "Sending " + obj);
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
 | 
								SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
 | 
				
			||||||
		} catch (IOException e) {
 | 
							} catch (final IOException e) {
 | 
				
			||||||
			throw new RuntimeException(e);
 | 
								throw new RuntimeException(e);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sends a message to the server. The message's status will be incremented once
 | 
						 * Sends a message to the server. The message's status will be incremented once it was delivered
 | 
				
			||||||
	 * it was delivered successfully.
 | 
						 * successfully.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param message the message to send
 | 
						 * @param message the message to send
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
@@ -174,10 +180,12 @@ public final class Client implements EventListener, Closeable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(eventType = HandshakeRejection.class, priority = 1000)
 | 
						@Event(eventType = HandshakeRejection.class, priority = 1000)
 | 
				
			||||||
	private void onHandshakeRejection() { rejected = true; }
 | 
						private void onHandshakeRejection() {
 | 
				
			||||||
 | 
							rejected = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	@Event(eventType = EnvoyCloseEvent.class, priority = 800)
 | 
						@Event(eventType = EnvoyCloseEvent.class, priority = 50)
 | 
				
			||||||
	public void close() {
 | 
						public void close() {
 | 
				
			||||||
		if (online) {
 | 
							if (online) {
 | 
				
			||||||
			logger.log(Level.INFO, "Closing connection...");
 | 
								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
 | 
						 * @throws IllegalStateException if the client is not online
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @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
 | 
						 * @return the {@link User} as which this client is logged in
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,13 +6,12 @@ import java.util.*;
 | 
				
			|||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
import java.util.logging.*;
 | 
					import java.util.logging.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.util.*;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Receives objects from the server and passes them to processor objects based
 | 
					 * Receives objects from the server and passes them to processor objects based on their class.
 | 
				
			||||||
 * on their class.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.3-alpha
 | 
					 * @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
 | 
						 * Starts the receiver loop. When an object is read, it is passed to the appropriate processor.
 | 
				
			||||||
	 * appropriate processor.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @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
 | 
										// Server has stopped sending, i.e. because he went offline
 | 
				
			||||||
					if (bytesRead == -1) {
 | 
										if (bytesRead == -1) {
 | 
				
			||||||
						isAlive = false;
 | 
											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;
 | 
											continue;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					logger.log(Level.WARNING,
 | 
										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;
 | 
										continue;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
 | 
									try (ObjectInputStream oin =
 | 
				
			||||||
 | 
										new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
 | 
				
			||||||
					final Object obj = oin.readObject();
 | 
										final Object obj = oin.readObject();
 | 
				
			||||||
					logger.log(Level.FINE, "Received " + obj);
 | 
										logger.log(Level.FINE, "Received " + obj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,12 +85,17 @@ public final class Receiver extends Thread {
 | 
				
			|||||||
					final Consumer processor = processors.get(obj.getClass());
 | 
										final Consumer processor = processors.get(obj.getClass());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Dispatch to the processor if present
 | 
										// 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
 | 
										// 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
 | 
										// Notify if no processor could be located
 | 
				
			||||||
					else logger.log(Level.WARNING,
 | 
										else
 | 
				
			||||||
							String.format("The received object has the %s for which no processor is defined.", obj.getClass()));
 | 
											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) {
 | 
								} catch (final SocketException | EOFException e) {
 | 
				
			||||||
				// Connection probably closed by client.
 | 
									// 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
 | 
						 * Adds an object processor to this {@link Receiver}. It will be called once an object of the
 | 
				
			||||||
	 * object of the accepted class has been received.
 | 
						 * accepted class has been received.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param processorClass the object class accepted by the processor
 | 
						 * @param processorClass the object class accepted by the processor
 | 
				
			||||||
	 * @param processor      the object processor
 | 
						 * @param processor      the object processor
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public <T> void registerProcessor(Class<T> processorClass, Consumer<T> processor) { processors.put(processorClass, processor); }
 | 
						public <T> void registerProcessor(Class<T> processorClass, Consumer<T> processor) {
 | 
				
			||||||
 | 
							processors.put(processorClass, processor);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Adds a map of object processors to this {@link Receiver}.
 | 
						 * 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
 | 
						 * @param processors the processors to add the processors to add
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void registerProcessors(Map<Class<?>, ? extends Consumer<?>> processors) { this.processors.putAll(processors); }
 | 
						public void registerProcessors(Map<Class<?>, ? extends Consumer<?>> processors) {
 | 
				
			||||||
 | 
							this.processors.putAll(processors);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Removes all object processors registered at this {@link Receiver}.
 | 
						 * Removes all object processors registered at this {@link Receiver}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void removeAllProcessors() { processors.clear(); }
 | 
						public void removeAllProcessors() {
 | 
				
			||||||
 | 
							processors.clear();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,15 +2,15 @@ package envoy.client.net;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.logging.*;
 | 
					import java.util.logging.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
import envoy.event.MessageStatusChange;
 | 
					import envoy.event.MessageStatusChange;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.data.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Implements methods to send {@link Message}s and
 | 
					 * Implements methods to send {@link Message}s and {@link MessageStatusChange}s to the server or
 | 
				
			||||||
 * {@link MessageStatusChange}s to the server or cache them inside a
 | 
					 * cache them inside a {@link LocalDB} depending on the online status.
 | 
				
			||||||
 * {@link LocalDB} depending on the online status.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.3-alpha
 | 
					 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
@@ -23,12 +23,11 @@ public final class WriteProxy {
 | 
				
			|||||||
	private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class);
 | 
						private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a write proxy using a client and a local database. The
 | 
						 * Initializes a write proxy using a client and a local database. The corresponding cache
 | 
				
			||||||
	 * corresponding cache processors are injected into the caches.
 | 
						 * processors are injected into the caches.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param client  the client instance used to send messages and events if online
 | 
						 * @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
 | 
						 * @param localDB the local database used to cache messages and events if offline
 | 
				
			||||||
	 *                offline
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public WriteProxy(Client client, LocalDB localDB) {
 | 
						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
 | 
						 * Sends cached {@link Message}s and {@link MessageStatusChange}s to the server.
 | 
				
			||||||
	 * server.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @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
 | 
						 * Delivers a message to the server if online. Otherwise the message is cached inside the local
 | 
				
			||||||
	 * inside the local database.
 | 
						 * database.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param message the message to send
 | 
						 * @param message the message to send
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void writeMessage(Message message) {
 | 
						public void writeMessage(Message message) {
 | 
				
			||||||
		if (client.isOnline()) client.sendMessage(message);
 | 
							if (client.isOnline())
 | 
				
			||||||
		else localDB.getCacheMap().getApplicable(Message.class).accept(message);
 | 
								client.sendMessage(message);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								localDB.getCacheMap().getApplicable(Message.class).accept(message);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Delivers a message status change event to the server if online. Otherwise the
 | 
						 * Delivers a message status change event to the server if online. Otherwise the event is cached
 | 
				
			||||||
	 * event is cached inside the local database.
 | 
						 * inside the local database.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param evt the event to send
 | 
						 * @param evt the event to send
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void writeMessageStatusChange(MessageStatusChange evt) {
 | 
						public void writeMessageStatusChange(MessageStatusChange evt) {
 | 
				
			||||||
		if (client.isOnline()) client.send(evt);
 | 
							if (client.isOnline())
 | 
				
			||||||
		else localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
 | 
								client.send(evt);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
package envoy.client.ui;
 | 
					package envoy.client.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This interface defines an action that should be performed when a scene gets
 | 
					 * This interface defines an action that should be performed when a scene gets restored from the
 | 
				
			||||||
 * restored from the scene stack in {@link SceneContext}.
 | 
					 * scene stack in {@link SceneContext}.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -12,8 +12,7 @@ public interface Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This method is getting called when a scene gets restored.<br>
 | 
						 * This method is getting called when a scene gets restored.<br>
 | 
				
			||||||
	 * Hence, it can contain anything that should be done when the underlying scene
 | 
						 * Hence, it can contain anything that should be done when the underlying scene gets restored.
 | 
				
			||||||
	 * gets restored.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,20 +9,19 @@ import javafx.fxml.FXMLLoader;
 | 
				
			|||||||
import javafx.scene.*;
 | 
					import javafx.scene.*;
 | 
				
			||||||
import javafx.stage.Stage;
 | 
					import javafx.stage.Stage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					import envoy.client.data.Settings;
 | 
				
			||||||
import envoy.client.data.shortcuts.*;
 | 
					import envoy.client.data.shortcuts.*;
 | 
				
			||||||
import envoy.client.event.*;
 | 
					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
 | 
					 * Manages a stack of scenes. The most recently added scene is displayed inside a stage. When a
 | 
				
			||||||
 * a stage. When a scene is removed from the stack, its predecessor is
 | 
					 * scene is removed from the stack, its predecessor is displayed.
 | 
				
			||||||
 * displayed.
 | 
					 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * When a scene is loaded, the style sheet for the current theme is applied to
 | 
					 * When a scene is loaded, the style sheet for the current theme is applied to it.
 | 
				
			||||||
 * it.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -63,7 +62,9 @@ public final class SceneContext implements EventListener {
 | 
				
			|||||||
		 */
 | 
							 */
 | 
				
			||||||
		public final String path;
 | 
							public final String path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		SceneInfo(String path) { this.path = path; }
 | 
							SceneInfo(String path) {
 | 
				
			||||||
 | 
								this.path = path;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Stage			stage;
 | 
						private final Stage			stage;
 | 
				
			||||||
@@ -97,7 +98,8 @@ public final class SceneContext implements EventListener {
 | 
				
			|||||||
		loader.setController(null);
 | 
							loader.setController(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		try {
 | 
							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	scene		= new Scene(rootNode);
 | 
				
			||||||
			final var	controller	= loader.getController();
 | 
								final var	controller	= loader.getController();
 | 
				
			||||||
			controllerStack.push(controller);
 | 
								controllerStack.push(controller);
 | 
				
			||||||
@@ -106,10 +108,13 @@ public final class SceneContext implements EventListener {
 | 
				
			|||||||
			stage.setScene(scene);
 | 
								stage.setScene(scene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Supply the global custom keyboard shortcuts for that 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
 | 
								// 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
 | 
								// 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
 | 
								// 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();
 | 
								applyCSS();
 | 
				
			||||||
			stage.show();
 | 
								stage.show();
 | 
				
			||||||
		} catch (final IOException e) {
 | 
							} 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);
 | 
								throw new RuntimeException(e);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -144,7 +150,8 @@ public final class SceneContext implements EventListener {
 | 
				
			|||||||
			// If the controller implements the Restorable interface,
 | 
								// If the controller implements the Restorable interface,
 | 
				
			||||||
			// the actions to perform on restoration will be executed here
 | 
								// the actions to perform on restoration will be executed here
 | 
				
			||||||
			final var controller = controllerStack.peek();
 | 
								final var controller = controllerStack.peek();
 | 
				
			||||||
			if (controller instanceof Restorable) ((Restorable) controller).onRestore();
 | 
								if (controller instanceof Restorable)
 | 
				
			||||||
 | 
									((Restorable) controller).onRestore();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		stage.show();
 | 
							stage.show();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -154,7 +161,8 @@ public final class SceneContext implements EventListener {
 | 
				
			|||||||
			final var	styleSheets	= stage.getScene().getStylesheets();
 | 
								final var	styleSheets	= stage.getScene().getStylesheets();
 | 
				
			||||||
			final var	themeCSS	= "/css/" + settings.getCurrentTheme() + ".css";
 | 
								final var	themeCSS	= "/css/" + settings.getCurrentTheme() + ".css";
 | 
				
			||||||
			styleSheets.clear();
 | 
								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)
 | 
						@Event(priority = 150, eventType = ThemeChangeEvent.class)
 | 
				
			||||||
	private void onThemeChange() { applyCSS(); }
 | 
						private void onThemeChange() {
 | 
				
			||||||
 | 
							applyCSS();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param <T> the type of the controller
 | 
						 * @param <T> the type of the controller
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,12 @@ import javafx.scene.control.Alert;
 | 
				
			|||||||
import javafx.scene.control.Alert.AlertType;
 | 
					import javafx.scene.control.Alert.AlertType;
 | 
				
			||||||
import javafx.stage.Stage;
 | 
					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.*;
 | 
				
			||||||
import envoy.client.data.shortcuts.EnvoyShortcutConfig;
 | 
					import envoy.client.data.shortcuts.EnvoyShortcutConfig;
 | 
				
			||||||
import envoy.client.helper.ShutdownHelper;
 | 
					import envoy.client.helper.ShutdownHelper;
 | 
				
			||||||
@@ -17,11 +23,6 @@ import envoy.client.net.Client;
 | 
				
			|||||||
import envoy.client.ui.SceneContext.SceneInfo;
 | 
					import envoy.client.ui.SceneContext.SceneInfo;
 | 
				
			||||||
import envoy.client.ui.controller.LoginScene;
 | 
					import envoy.client.ui.controller.LoginScene;
 | 
				
			||||||
import envoy.client.util.IconUtil;
 | 
					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.
 | 
					 * Handles application startup.
 | 
				
			||||||
@@ -47,8 +48,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
	private static final Logger			logger	= EnvoyLog.getLogger(Startup.class);
 | 
						private static final Logger			logger	= EnvoyLog.getLogger(Startup.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads the configuration, initializes the client and the local database and
 | 
						 * Loads the configuration, initializes the client and the local database and delegates the rest
 | 
				
			||||||
	 * delegates the rest of the startup process to {@link LoginScene}.
 | 
						 * of the startup process to {@link LoginScene}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -57,7 +58,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Initialize config and logger
 | 
							// Initialize config and logger
 | 
				
			||||||
		try {
 | 
							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);
 | 
								EnvoyLog.initialize(config);
 | 
				
			||||||
		} catch (final IllegalStateException e) {
 | 
							} catch (final IllegalStateException e) {
 | 
				
			||||||
			new Alert(AlertType.ERROR, "Error loading configuration values:\n" + 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...");
 | 
								logger.info("Attempting authentication with token...");
 | 
				
			||||||
			localDB.loadUserData();
 | 
								localDB.loadUserData();
 | 
				
			||||||
			if (!performHandshake(
 | 
								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);
 | 
									sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
				
			||||||
		} else
 | 
							} else
 | 
				
			||||||
			// Load login scene
 | 
								// Load login scene
 | 
				
			||||||
@@ -117,7 +120,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
		cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
 | 
							cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
 | 
				
			||||||
		cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
 | 
							cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
 | 
				
			||||||
		cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
 | 
							cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
 | 
				
			||||||
		final var originalStatus = localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
 | 
							final var originalStatus =
 | 
				
			||||||
 | 
								localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			client.performHandshake(credentials, cacheMap);
 | 
								client.performHandshake(credentials, cacheMap);
 | 
				
			||||||
			if (client.isOnline()) {
 | 
								if (client.isOnline()) {
 | 
				
			||||||
@@ -127,7 +131,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
				loadChatScene();
 | 
									loadChatScene();
 | 
				
			||||||
				client.initReceiver(localDB, cacheMap);
 | 
									client.initReceiver(localDB, cacheMap);
 | 
				
			||||||
				return true;
 | 
									return true;
 | 
				
			||||||
			} else return false;
 | 
								} else
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
		} catch (IOException | InterruptedException | TimeoutException e) {
 | 
							} catch (IOException | InterruptedException | TimeoutException e) {
 | 
				
			||||||
			logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
 | 
								logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
 | 
				
			||||||
			return attemptOfflineMode(credentials.getIdentifier());
 | 
								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
 | 
						 * Attempts to load {@link envoy.client.ui.controller.ChatScene} in offline mode for a given
 | 
				
			||||||
	 * for a given user.
 | 
						 * user.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param identifier the identifier of the user - currently his username
 | 
						 * @param identifier the identifier of the user - currently his username
 | 
				
			||||||
	 * @return whether the offline mode could be entered
 | 
						 * @return whether the offline mode could be entered
 | 
				
			||||||
@@ -146,7 +151,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
		try {
 | 
							try {
 | 
				
			||||||
			// Try entering offline mode
 | 
								// Try entering offline mode
 | 
				
			||||||
			final User clientUser = localDB.getUsers().get(identifier);
 | 
								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);
 | 
								client.setSender(clientUser);
 | 
				
			||||||
			loadChatScene();
 | 
								loadChatScene();
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
@@ -187,7 +193,9 @@ public final class Startup extends Application {
 | 
				
			|||||||
		} catch (final FileNotFoundException e) {
 | 
							} catch (final FileNotFoundException e) {
 | 
				
			||||||
			// The local database file has not yet been created, probably first login
 | 
								// The local database file has not yet been created, probably first login
 | 
				
			||||||
		} catch (final Exception e) {
 | 
							} 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);
 | 
								logger.log(Level.WARNING, "Could not load local database: ", e);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -197,7 +205,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
			context.getWriteProxy().flushCache();
 | 
								context.getWriteProxy().flushCache();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Inform the server that this user has a different user status than expected
 | 
								// 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
 | 
							} else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Set all contacts to offline mode
 | 
								// Set all contacts to offline mode
 | 
				
			||||||
@@ -211,7 +220,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
		final var stage = context.getStage();
 | 
							final var stage = context.getStage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Pop LoginScene if present
 | 
							// Pop LoginScene if present
 | 
				
			||||||
		if (!context.getSceneContext().isEmpty()) context.getSceneContext().pop();
 | 
							if (!context.getSceneContext().isEmpty())
 | 
				
			||||||
 | 
								context.getSceneContext().pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Load ChatScene
 | 
							// Load ChatScene
 | 
				
			||||||
		stage.setMinHeight(400);
 | 
							stage.setMinHeight(400);
 | 
				
			||||||
@@ -221,15 +231,21 @@ public final class Startup extends Application {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Exit or minimize the stage when a close request occurs
 | 
							// Exit or minimize the stage when a close request occurs
 | 
				
			||||||
		stage.setOnCloseRequest(
 | 
							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()) {
 | 
							if (StatusTrayIcon.isSupported()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Initialize status tray icon
 | 
								// Initialize status tray icon
 | 
				
			||||||
			final var trayIcon = new StatusTrayIcon(stage);
 | 
								final var trayIcon = new StatusTrayIcon(stage);
 | 
				
			||||||
			Settings.getInstance().getItems().get("hideOnClose").setChangeHandler(c -> {
 | 
								Settings.getInstance().getItems().get("hideOnClose").setChangeHandler(c -> {
 | 
				
			||||||
				if ((Boolean) c) trayIcon.show();
 | 
									if ((Boolean) c)
 | 
				
			||||||
				else trayIcon.hide();
 | 
										trayIcon.show();
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
										trayIcon.hide();
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,19 +8,19 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.control.Alert.AlertType;
 | 
					import javafx.scene.control.Alert.AlertType;
 | 
				
			||||||
import javafx.scene.control.skin.VirtualFlow;
 | 
					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.Context;
 | 
				
			||||||
import envoy.client.data.commands.*;
 | 
					import envoy.client.data.commands.*;
 | 
				
			||||||
import envoy.client.helper.ShutdownHelper;
 | 
					import envoy.client.helper.ShutdownHelper;
 | 
				
			||||||
import envoy.client.ui.SceneContext.SceneInfo;
 | 
					import envoy.client.ui.SceneContext.SceneInfo;
 | 
				
			||||||
import envoy.client.ui.controller.ChatScene;
 | 
					import envoy.client.ui.controller.ChatScene;
 | 
				
			||||||
import envoy.client.util.*;
 | 
					import envoy.client.util.*;
 | 
				
			||||||
import envoy.data.Message;
 | 
					 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Contains all {@link SystemCommand}s used for
 | 
					 * Contains all {@link SystemCommand}s used for {@link envoy.client.ui.controller.ChatScene}.
 | 
				
			||||||
 * {@link envoy.client.ui.controller.ChatScene}.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.3-beta
 | 
					 * @since Envoy Client v0.3-beta
 | 
				
			||||||
@@ -29,12 +29,13 @@ public final class ChatSceneCommands {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private final ListView<Message>		messageList;
 | 
						private final ListView<Message>		messageList;
 | 
				
			||||||
	private final SystemCommandMap		messageTextAreaCommands	= new SystemCommandMap();
 | 
						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 messageList the message list to use for some commands
 | 
				
			||||||
	 * @param chatScene   the instance of {@code ChatScene} that uses this object
 | 
						 * @param chatScene   the instance of {@code ChatScene} that uses this object
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
@@ -43,25 +44,33 @@ public final class ChatSceneCommands {
 | 
				
			|||||||
		this.messageList = messageList;
 | 
							this.messageList = messageList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Error message initialization
 | 
							// 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
 | 
							// Do A Barrel roll initialization
 | 
				
			||||||
		final var random = new Random();
 | 
							final var random = new Random();
 | 
				
			||||||
		builder.setAction(text -> chatScene.doABarrelRoll(Integer.parseInt(text.get(0)), Double.parseDouble(text.get(1))))
 | 
							builder
 | 
				
			||||||
			.setDefaults(Integer.toString(random.nextInt(3) + 1), Double.toString(random.nextDouble() * 3 + 1))
 | 
								.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 :)")
 | 
								.setDescription("See for yourself :)")
 | 
				
			||||||
			.setNumberOfArguments(2)
 | 
								.setNumberOfArguments(2)
 | 
				
			||||||
			.build("dabr");
 | 
								.build("dabr");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Logout initialization
 | 
							// 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
 | 
							// 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");
 | 
							builder.build("q");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Open settings scene initialization
 | 
							// 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")
 | 
								.setDescription("Opens the settings screen")
 | 
				
			||||||
			.buildNoArg("settings");
 | 
								.buildNoArg("settings");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,81 +83,106 @@ public final class ChatSceneCommands {
 | 
				
			|||||||
				alert.setContentText("Please provide an existing status");
 | 
									alert.setContentText("Please provide an existing status");
 | 
				
			||||||
				alert.showAndWait();
 | 
									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
 | 
							// Selection of a new message initialization
 | 
				
			||||||
		messageDependantAction("s",
 | 
							messageDependantAction("s",
 | 
				
			||||||
				m -> { messageList.getSelectionModel().clearSelection(); messageList.getSelectionModel().select(m); },
 | 
								m -> {
 | 
				
			||||||
				m -> true,
 | 
									messageList.getSelectionModel().clearSelection();
 | 
				
			||||||
				"Selects");
 | 
									messageList.getSelectionModel().select(m);
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								m -> true,
 | 
				
			||||||
 | 
								"Selects");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Copy text of selection initialization
 | 
							// 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
 | 
							// Delete selection initialization
 | 
				
			||||||
		messageDependantAction("del", MessageUtil::deleteMessage, m -> true, "Deletes");
 | 
							messageDependantAction("del", MessageUtil::deleteMessage, m -> true, "Deletes");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Save attachment of selection initialization
 | 
							// 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<Message> action, Predicate<Message> additionalCheck, String description) {
 | 
						private void messageDependantAction(String command, Consumer<Message> action,
 | 
				
			||||||
 | 
							Predicate<Message> additionalCheck, String description) {
 | 
				
			||||||
		builder.setAction(text -> {
 | 
							builder.setAction(text -> {
 | 
				
			||||||
			final var positionalArgument = text.get(0).toLowerCase();
 | 
								final var positionalArgument = text.get(0).toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// the currently selected message was requested
 | 
								// the currently selected message was requested
 | 
				
			||||||
			if (positionalArgument.startsWith("s")) {
 | 
								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
 | 
									// Only s has been used as input
 | 
				
			||||||
				if (positionalArgument.length() == 1) {
 | 
									if (positionalArgument.length() == 1) {
 | 
				
			||||||
					final var selectedMessage = messageList.getSelectionModel().getSelectedItem();
 | 
										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;
 | 
										return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Either s++ or s-- has been requested
 | 
										// 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.
 | 
									// A message relative to the currently selected message should be used (i.e.
 | 
				
			||||||
				// s+4)
 | 
									// s+4)
 | 
				
			||||||
				else useRelativeMessage(command, action, additionalCheck, relativeString, true);
 | 
									else
 | 
				
			||||||
 | 
										useRelativeMessage(command, action, additionalCheck, relativeString, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Either ++s or --s has been requested
 | 
									// Either ++s or --s has been requested
 | 
				
			||||||
			} else if (positionalArgument.equals("--s") || positionalArgument.equals("++s"))
 | 
								} else if (positionalArgument.equals("--s") || positionalArgument.equals("++s"))
 | 
				
			||||||
				selectionNeighbor(action, additionalCheck, positionalArgument);
 | 
									selectionNeighbor(action, additionalCheck, positionalArgument);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Just a number is expected: ((+)4)
 | 
								// Just a number is expected: ((+)4)
 | 
				
			||||||
			else useRelativeMessage(command, action, additionalCheck, positionalArgument, false);
 | 
								else
 | 
				
			||||||
		}).setDefaults("s").setNumberOfArguments(1).setDescription(description.concat(messageDependantCommandDescription)).build(command);
 | 
									useRelativeMessage(command, action, additionalCheck, positionalArgument, false);
 | 
				
			||||||
 | 
							}).setDefaults("s").setNumberOfArguments(1)
 | 
				
			||||||
 | 
								.setDescription(description.concat(messageDependantCommandDescription)).build(command);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void selectionNeighbor(Consumer<Message> action, Predicate<Message> additionalCheck, final String positionalArgument) {
 | 
						private void selectionNeighbor(Consumer<Message> action, Predicate<Message> additionalCheck,
 | 
				
			||||||
		final var wantedIndex = messageList.getSelectionModel().getSelectedIndex() + (positionalArgument.contains("+") ? 1 : -1);
 | 
							final String positionalArgument) {
 | 
				
			||||||
 | 
							final var wantedIndex = messageList.getSelectionModel().getSelectedIndex()
 | 
				
			||||||
 | 
								+ (positionalArgument.contains("+") ? 1 : -1);
 | 
				
			||||||
		messageList.getSelectionModel().clearAndSelect(wantedIndex);
 | 
							messageList.getSelectionModel().clearAndSelect(wantedIndex);
 | 
				
			||||||
		final var selectedMessage = messageList.getItems().get(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<Message> action, Predicate<Message> additionalCheck, final String positionalArgument,
 | 
						private void useRelativeMessage(String command, Consumer<Message> action,
 | 
				
			||||||
			boolean useSelectedMessage) throws NumberFormatException {
 | 
							Predicate<Message> additionalCheck, final String positionalArgument,
 | 
				
			||||||
		final var	stripPlus	= positionalArgument.startsWith("+") ? positionalArgument.substring(1) : positionalArgument;
 | 
							boolean useSelectedMessage) throws NumberFormatException {
 | 
				
			||||||
 | 
							final var	stripPlus	=
 | 
				
			||||||
 | 
								positionalArgument.startsWith("+") ? positionalArgument.substring(1)
 | 
				
			||||||
 | 
									: positionalArgument;
 | 
				
			||||||
		final var	incDec		= Integer.valueOf(stripPlus);
 | 
							final var	incDec		= Integer.valueOf(stripPlus);
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// The currently selected message is the base message
 | 
								// The currently selected message is the base message
 | 
				
			||||||
			if (useSelectedMessage) {
 | 
								if (useSelectedMessage) {
 | 
				
			||||||
				final var messageToUse = messageList.getItems().get(messageList.getSelectionModel().getSelectedIndex() + incDec);
 | 
									final var messageToUse = messageList.getItems()
 | 
				
			||||||
				if (messageToUse != null && additionalCheck.test(messageToUse)) action.accept(messageToUse);
 | 
										.get(messageList.getSelectionModel().getSelectedIndex() + incDec);
 | 
				
			||||||
 | 
									if (messageToUse != null && additionalCheck.test(messageToUse))
 | 
				
			||||||
 | 
										action.accept(messageToUse);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// The currently upmost completely visible message is the base message
 | 
									// The currently upmost completely visible message is the base message
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				final var messageToUse = messageList.getItems()
 | 
									final var messageToUse = messageList.getItems()
 | 
				
			||||||
					.get(((VirtualFlow<?>) messageList.lookup(".virtual-flow")).getFirstVisibleCell().getIndex() + 1 + incDec);
 | 
										.get(((VirtualFlow<?>) messageList.lookup(".virtual-flow"))
 | 
				
			||||||
				if (messageToUse != null && additionalCheck.test(messageToUse)) action.accept(messageToUse);
 | 
											.getFirstVisibleCell().getIndex() + 1 + incDec);
 | 
				
			||||||
 | 
									if (messageToUse != null && additionalCheck.test(messageToUse))
 | 
				
			||||||
 | 
										action.accept(messageToUse);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} catch (final IndexOutOfBoundsException e) {
 | 
							} catch (final IndexOutOfBoundsException e) {
 | 
				
			||||||
			EnvoyLog.getLogger(ChatSceneCommands.class)
 | 
								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);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.input.Clipboard;
 | 
					import javafx.scene.input.Clipboard;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Displays a context menu that offers an additional option when one of
 | 
					 * Displays a context menu that offers an additional option when one of its menu items has been
 | 
				
			||||||
 * its menu items has been clicked.
 | 
					 * clicked.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * Current options are:
 | 
					 * Current options are:
 | 
				
			||||||
 * <ul>
 | 
					 * <ul>
 | 
				
			||||||
@@ -24,9 +24,8 @@ import javafx.scene.input.Clipboard;
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 * @apiNote please refrain from using
 | 
					 * @apiNote please refrain from using {@link ContextMenu#setOnShowing(EventHandler)} as this is
 | 
				
			||||||
 *          {@link ContextMenu#setOnShowing(EventHandler)} as this is already
 | 
					 *          already used by this component
 | 
				
			||||||
 *          used by this component
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class TextInputContextMenu extends ContextMenu {
 | 
					public class TextInputContextMenu extends ContextMenu {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -40,8 +39,8 @@ public class TextInputContextMenu extends ContextMenu {
 | 
				
			|||||||
	private final MenuItem	selectAllMI	= new MenuItem("Select all");
 | 
						private final MenuItem	selectAllMI	= new MenuItem("Select all");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code TextInputContextMenu} with an optional action when
 | 
						 * Creates a new {@code TextInputContextMenu} with an optional action when this menu was
 | 
				
			||||||
	 * this menu was clicked. Currently shows:
 | 
						 * clicked. Currently shows:
 | 
				
			||||||
	 * <ul>
 | 
						 * <ul>
 | 
				
			||||||
	 * <li>undo</li>
 | 
						 * <li>undo</li>
 | 
				
			||||||
	 * <li>redo</li>
 | 
						 * <li>redo</li>
 | 
				
			||||||
@@ -53,14 +52,12 @@ public class TextInputContextMenu extends ContextMenu {
 | 
				
			|||||||
	 * <li>Select all</li>
 | 
						 * <li>Select all</li>
 | 
				
			||||||
	 * </ul>
 | 
						 * </ul>
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param control         the text input component to display this
 | 
						 * @param control         the text input component to display this {@code ContextMenu}
 | 
				
			||||||
	 *                        {@code ContextMenu}
 | 
						 * @param menuItemClicked the second action to perform when a menu item of this context menu has
 | 
				
			||||||
	 * @param menuItemClicked the second action to perform when a menu item of this
 | 
						 *                        been clicked
 | 
				
			||||||
	 *                        context menu has been clicked
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 * @apiNote please refrain from using
 | 
						 * @apiNote please refrain from using {@link ContextMenu#setOnShowing(EventHandler)} as this is
 | 
				
			||||||
	 *          {@link ContextMenu#setOnShowing(EventHandler)} as this is already
 | 
						 *          already used by this component
 | 
				
			||||||
	 *          used by this component
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public TextInputContextMenu(TextInputControl control, Consumer<ActionEvent> menuItemClicked) {
 | 
						public TextInputContextMenu(TextInputControl control, Consumer<ActionEvent> menuItemClicked) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -100,7 +97,11 @@ public class TextInputContextMenu extends ContextMenu {
 | 
				
			|||||||
		getItems().add(selectAllMI);
 | 
							getItems().add(selectAllMI);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private EventHandler<ActionEvent> addAction(Consumer<ActionEvent> originalAction, Consumer<ActionEvent> additionalAction) {
 | 
						private EventHandler<ActionEvent> addAction(Consumer<ActionEvent> originalAction,
 | 
				
			||||||
		return e -> { originalAction.accept(e); additionalAction.accept(e); };
 | 
							Consumer<ActionEvent> additionalAction) {
 | 
				
			||||||
 | 
							return e -> {
 | 
				
			||||||
 | 
								originalAction.accept(e);
 | 
				
			||||||
 | 
								additionalAction.accept(e);
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,10 +6,11 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.control.Alert.AlertType;
 | 
					import javafx.scene.control.Alert.AlertType;
 | 
				
			||||||
import javafx.scene.layout.HBox;
 | 
					import javafx.scene.layout.HBox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.audio.AudioPlayer;
 | 
					 | 
				
			||||||
import envoy.exception.EnvoyException;
 | 
					import envoy.exception.EnvoyException;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.data.audio.AudioPlayer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Enables the play back of audio clips through a button.
 | 
					 * Enables the play back of audio clips through a button.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -18,7 +19,7 @@ import envoy.util.EnvoyLog;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public final class AudioControl extends HBox {
 | 
					public final class AudioControl extends HBox {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private AudioPlayer player = new AudioPlayer();
 | 
						private final AudioPlayer player = new AudioPlayer();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final Logger logger = EnvoyLog.getLogger(AudioControl.class);
 | 
						private static final Logger logger = EnvoyLog.getLogger(AudioControl.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,8 +9,8 @@ import envoy.client.data.*;
 | 
				
			|||||||
import envoy.client.util.IconUtil;
 | 
					import envoy.client.util.IconUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Displays a chat using a contact control for the recipient and a label for the
 | 
					 * Displays a chat using a contact control for the recipient and a label for the unread message
 | 
				
			||||||
 * unread message count.
 | 
					 * count.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @see ContactControl
 | 
					 * @see ContactControl
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
@@ -19,7 +19,7 @@ import envoy.client.util.IconUtil;
 | 
				
			|||||||
public final class ChatControl extends HBox {
 | 
					public final class ChatControl extends HBox {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
 | 
						private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
 | 
				
			||||||
			groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
 | 
							groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code ChatControl}.
 | 
						 * Creates a new {@code ChatControl}.
 | 
				
			||||||
@@ -32,7 +32,8 @@ public final class ChatControl extends HBox {
 | 
				
			|||||||
		setPadding(new Insets(0, 0, 3, 0));
 | 
							setPadding(new Insets(0, 0, 3, 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Profile picture
 | 
							// Profile picture
 | 
				
			||||||
		final var contactProfilePic = new ProfilePicImageView(chat instanceof GroupChat ? groupIcon : userIcon, 32);
 | 
							final var contactProfilePic =
 | 
				
			||||||
 | 
								new ProfilePicImageView(chat instanceof GroupChat ? groupIcon : userIcon, 32);
 | 
				
			||||||
		getChildren().add(contactProfilePic);
 | 
							getChildren().add(contactProfilePic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Spacing
 | 
							// Spacing
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,8 @@ import javafx.scene.layout.VBox;
 | 
				
			|||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Displays information about a contact in two rows. The first row contains the
 | 
					 * Displays information about a contact in two rows. The first row contains the name. The second row
 | 
				
			||||||
 * name. The second row contains the online status (user) or the member count
 | 
					 * contains the online status (user) or the member count (group).
 | 
				
			||||||
 * (group).
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
@@ -29,23 +28,24 @@ public final class ContactControl extends VBox {
 | 
				
			|||||||
		getChildren().add(nameLabel);
 | 
							getChildren().add(nameLabel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Online status (user) or member count (group)
 | 
							// Online status (user) or member count (group)
 | 
				
			||||||
		getChildren().add(contact instanceof User ? new UserStatusLabel((User) contact) : new GroupSizeLabel((Group) contact));
 | 
							getChildren().add(contact instanceof User ? new UserStatusLabel((User) contact)
 | 
				
			||||||
 | 
								: new GroupSizeLabel((Group) contact));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		getStyleClass().add("list-element");
 | 
							getStyleClass().add("list-element");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Replaces the info label of this {@code ContactControl} with an updated
 | 
						 * Replaces the info label of this {@code ContactControl} with an updated version.
 | 
				
			||||||
	 * version.
 | 
					 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * This method should be called when the status of the underlying user or the
 | 
						 * This method should be called when the status of the underlying user or the size of the
 | 
				
			||||||
	 * size of the underlying group has changed.
 | 
						 * underlying group has changed.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 * @apiNote will produce buggy results if contact control gets updated so that
 | 
						 * @apiNote will produce buggy results if contact control gets updated so that the info label is
 | 
				
			||||||
	 *          the info label is no longer on index 1.
 | 
						 *          no longer on index 1.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void replaceInfoLabel() {
 | 
						public void replaceInfoLabel() {
 | 
				
			||||||
		getChildren().set(1, contact instanceof User ? new UserStatusLabel((User) contact) : new GroupSizeLabel((Group) contact));
 | 
							getChildren().set(1, contact instanceof User ? new UserStatusLabel((User) contact)
 | 
				
			||||||
 | 
								: new GroupSizeLabel((Group) contact));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,5 +16,8 @@ public final class GroupSizeLabel extends Label {
 | 
				
			|||||||
	 * @param recipient the group whose members to show
 | 
						 * @param recipient the group whose members to show
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GroupSizeLabel(Group recipient) { super(recipient.getContacts().size() + " members"); }
 | 
						public GroupSizeLabel(Group recipient) {
 | 
				
			||||||
 | 
							super(recipient.getContacts().size() + " member"
 | 
				
			||||||
 | 
								+ (recipient.getContacts().size() != 1 ? "s" : ""));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,13 +11,14 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.image.*;
 | 
					import javafx.scene.image.*;
 | 
				
			||||||
import javafx.scene.layout.*;
 | 
					import javafx.scene.layout.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					 | 
				
			||||||
import envoy.client.net.Client;
 | 
					 | 
				
			||||||
import envoy.client.util.*;
 | 
					 | 
				
			||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
import envoy.data.Message.MessageStatus;
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.data.*;
 | 
				
			||||||
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
 | 
					import envoy.client.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This class transforms a single {@link Message} into a UI component.
 | 
					 * This class transforms a single {@link Message} into a UI component.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -33,13 +34,15 @@ public final class MessageControl extends Label {
 | 
				
			|||||||
	private final Client	client	= context.getClient();
 | 
						private final Client	client	= context.getClient();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final Context					context			= Context.getInstance();
 | 
						private static final Context					context			= Context.getInstance();
 | 
				
			||||||
	private static final DateTimeFormatter			dateFormat		= DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
 | 
						private static final DateTimeFormatter			dateFormat		=
 | 
				
			||||||
		.withZone(ZoneId.systemDefault());
 | 
							DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
 | 
				
			||||||
	private static final Map<MessageStatus, Image>	statusImages	= IconUtil.loadByEnum(MessageStatus.class, 16);
 | 
								.withZone(ZoneId.systemDefault());
 | 
				
			||||||
	private static final Logger						logger			= EnvoyLog.getLogger(MessageControl.class);
 | 
						private static final Map<MessageStatus, Image>	statusImages	=
 | 
				
			||||||
 | 
							IconUtil.loadByEnum(MessageStatus.class, 16);
 | 
				
			||||||
 | 
						private static final Logger						logger			=
 | 
				
			||||||
 | 
							EnvoyLog.getLogger(MessageControl.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 *
 | 
					 | 
				
			||||||
	 * @param message the message that should be formatted
 | 
						 * @param message the message that should be formatted
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -107,7 +110,9 @@ public final class MessageControl extends Label {
 | 
				
			|||||||
			switch (message.getAttachment().getType()) {
 | 
								switch (message.getAttachment().getType()) {
 | 
				
			||||||
				case PICTURE:
 | 
									case PICTURE:
 | 
				
			||||||
					vbox.getChildren()
 | 
										vbox.getChildren()
 | 
				
			||||||
						.add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true)));
 | 
											.add(new ImageView(
 | 
				
			||||||
 | 
												new Image(new ByteArrayInputStream(message.getAttachment().getData()),
 | 
				
			||||||
 | 
													256, 256, true, true)));
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				case VIDEO:
 | 
									case VIDEO:
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
@@ -138,7 +143,8 @@ public final class MessageControl extends Label {
 | 
				
			|||||||
			hBoxBottom.setAlignment(Pos.BOTTOM_RIGHT);
 | 
								hBoxBottom.setAlignment(Pos.BOTTOM_RIGHT);
 | 
				
			||||||
			getStyleClass().add("own-message");
 | 
								getStyleClass().add("own-message");
 | 
				
			||||||
			hbox.setAlignment(Pos.CENTER_RIGHT);
 | 
								hbox.setAlignment(Pos.CENTER_RIGHT);
 | 
				
			||||||
		} else getStyleClass().add("received-message");
 | 
							} else
 | 
				
			||||||
 | 
								getStyleClass().add("received-message");
 | 
				
			||||||
		vbox.getChildren().add(hBoxBottom);
 | 
							vbox.getChildren().add(hBoxBottom);
 | 
				
			||||||
		// Adjusting height and weight of the cell to the corresponding ListView
 | 
							// Adjusting height and weight of the cell to the corresponding ListView
 | 
				
			||||||
		paddingProperty().setValue(new Insets(5, 20, 5, 20));
 | 
							paddingProperty().setValue(new Insets(5, 20, 5, 20));
 | 
				
			||||||
@@ -146,11 +152,13 @@ public final class MessageControl extends Label {
 | 
				
			|||||||
		setGraphic(vbox);
 | 
							setGraphic(vbox);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); }
 | 
						private void loadMessageInfoScene(Message message) {
 | 
				
			||||||
 | 
							logger.log(Level.FINEST, "message info scene was requested for " + message);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return whether the message stored by this {@code MessageControl} has been
 | 
						 * @return whether the message stored by this {@code MessageControl} has been sent by this user
 | 
				
			||||||
	 *         sent by this user of Envoy
 | 
						 *         of Envoy
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean isOwnMessage() { return ownMessage; }
 | 
						public boolean isOwnMessage() { return ownMessage; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,9 @@ public final class ProfilePicImageView extends ImageView {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ProfilePicImageView() { this(null); }
 | 
						public ProfilePicImageView() {
 | 
				
			||||||
 | 
							this(null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code ProfilePicImageView}.
 | 
						 * Creates a new {@code ProfilePicImageView}.
 | 
				
			||||||
@@ -24,17 +26,20 @@ public final class ProfilePicImageView extends ImageView {
 | 
				
			|||||||
	 * @param image the image to display
 | 
						 * @param image the image to display
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ProfilePicImageView(Image image) { this(image, 40); }
 | 
						public ProfilePicImageView(Image image) {
 | 
				
			||||||
 | 
							this(image, 40);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code ProfilePicImageView}.
 | 
						 * Creates a new {@code ProfilePicImageView}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param image           the image to display
 | 
						 * @param image           the image to display
 | 
				
			||||||
	 * @param sizeAndRounding the size and rounding for a circular
 | 
						 * @param sizeAndRounding the size and rounding for a circular {@code ProfilePicImageView}
 | 
				
			||||||
	 *                        {@code ProfilePicImageView}
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ProfilePicImageView(Image image, double sizeAndRounding) { this(image, sizeAndRounding, sizeAndRounding); }
 | 
						public ProfilePicImageView(Image image, double sizeAndRounding) {
 | 
				
			||||||
 | 
							this(image, sizeAndRounding, sizeAndRounding);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code ProfilePicImageView}.
 | 
						 * Creates a new {@code ProfilePicImageView}.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,25 +8,26 @@ import javafx.scene.image.ImageView;
 | 
				
			|||||||
import javafx.scene.layout.*;
 | 
					import javafx.scene.layout.*;
 | 
				
			||||||
import javafx.scene.shape.Rectangle;
 | 
					import javafx.scene.shape.Rectangle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.util.IconUtil;
 | 
					import envoy.client.util.IconUtil;
 | 
				
			||||||
import envoy.data.*;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Displays an {@link User} as a quick select control which is used in the
 | 
					 * Displays an {@link User} as a quick select control which is used in the quick select list.
 | 
				
			||||||
 * quick select list.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 * @since Envoy Client v0.3-beta
 | 
					 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class QuickSelectControl extends VBox {
 | 
					public class QuickSelectControl extends VBox {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private User user;
 | 
						private final User user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of the {@code QuickSelectControl}.
 | 
						 * Creates an instance of the {@code QuickSelectControl}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param user the contact whose data is used to create this instance.
 | 
						 * @param user   the contact whose data is used to create this instance.
 | 
				
			||||||
	 * @param action the action to perform when a contact is removed with this control as a parameter
 | 
						 * @param action the action to perform when a contact is removed with this control as a
 | 
				
			||||||
 | 
						 *               parameter
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public QuickSelectControl(User user, Consumer<QuickSelectControl> action) {
 | 
						public QuickSelectControl(User user, Consumer<QuickSelectControl> action) {
 | 
				
			||||||
@@ -44,7 +45,8 @@ public class QuickSelectControl extends VBox {
 | 
				
			|||||||
		picHold.setPrefHeight(35);
 | 
							picHold.setPrefHeight(35);
 | 
				
			||||||
		picHold.setMaxHeight(35);
 | 
							picHold.setMaxHeight(35);
 | 
				
			||||||
		picHold.setMinHeight(35);
 | 
							picHold.setMinHeight(35);
 | 
				
			||||||
		var			contactProfilePic	= new ImageView(IconUtil.loadIconThemeSensitive("user_icon", 32));
 | 
							var			contactProfilePic	=
 | 
				
			||||||
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("user_icon", 32));
 | 
				
			||||||
		final var	clip				= new Rectangle();
 | 
							final var	clip				= new Rectangle();
 | 
				
			||||||
		clip.setWidth(32);
 | 
							clip.setWidth(32);
 | 
				
			||||||
		clip.setHeight(32);
 | 
							clip.setHeight(32);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,17 @@ import javafx.scene.shape.Rectangle;
 | 
				
			|||||||
import javafx.stage.FileChooser;
 | 
					import javafx.stage.FileChooser;
 | 
				
			||||||
import javafx.util.Duration;
 | 
					import javafx.util.Duration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
 | 
					import dev.kske.eventbus.Event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.*;
 | 
				
			||||||
 | 
					import envoy.data.Attachment.AttachmentType;
 | 
				
			||||||
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
 | 
					import envoy.event.*;
 | 
				
			||||||
 | 
					import envoy.event.contact.UserOperation;
 | 
				
			||||||
 | 
					import envoy.exception.EnvoyException;
 | 
				
			||||||
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.data.audio.AudioRecorder;
 | 
					import envoy.client.data.audio.AudioRecorder;
 | 
				
			||||||
import envoy.client.event.*;
 | 
					import envoy.client.event.*;
 | 
				
			||||||
@@ -33,16 +44,6 @@ import envoy.client.ui.chatscene.*;
 | 
				
			|||||||
import envoy.client.ui.control.*;
 | 
					import envoy.client.ui.control.*;
 | 
				
			||||||
import envoy.client.ui.listcell.*;
 | 
					import envoy.client.ui.listcell.*;
 | 
				
			||||||
import envoy.client.util.*;
 | 
					import envoy.client.util.*;
 | 
				
			||||||
import envoy.data.*;
 | 
					 | 
				
			||||||
import envoy.data.Attachment.AttachmentType;
 | 
					 | 
				
			||||||
import envoy.data.Message.MessageStatus;
 | 
					 | 
				
			||||||
import envoy.event.*;
 | 
					 | 
				
			||||||
import envoy.event.contact.ContactOperation;
 | 
					 | 
				
			||||||
import envoy.exception.EnvoyException;
 | 
					 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					 | 
				
			||||||
import dev.kske.eventbus.Event;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller for the chat scene.
 | 
					 * Controller for the chat scene.
 | 
				
			||||||
@@ -91,9 +92,6 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private Label topBarStatusLabel;
 | 
						private Label topBarStatusLabel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
					 | 
				
			||||||
	private MenuItem deleteContactMenuItem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private ImageView attachmentView;
 | 
						private ImageView attachmentView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -142,9 +140,11 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	private final WriteProxy	writeProxy			= context.getWriteProxy();
 | 
						private final WriteProxy	writeProxy			= context.getWriteProxy();
 | 
				
			||||||
	private final SceneContext	sceneContext		= context.getSceneContext();
 | 
						private final SceneContext	sceneContext		= context.getSceneContext();
 | 
				
			||||||
	private final AudioRecorder	recorder			= new AudioRecorder();
 | 
						private final AudioRecorder	recorder			= new AudioRecorder();
 | 
				
			||||||
	private final Tooltip		onlyIfOnlineTooltip	= new Tooltip("You need to be online to do this");
 | 
						private final Tooltip		onlyIfOnlineTooltip	=
 | 
				
			||||||
 | 
							new Tooltip("You need to be online to do this");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static Image DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
 | 
						private static Image DEFAULT_ATTACHMENT_VIEW_IMAGE =
 | 
				
			||||||
 | 
							IconUtil.loadIconThemeSensitive("attachment_present", 20);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final Settings	settings			= Settings.getInstance();
 | 
						private static final Settings	settings			= Settings.getInstance();
 | 
				
			||||||
	private static final EventBus	eventBus			= EventBus.getInstance();
 | 
						private static final EventBus	eventBus			= EventBus.getInstance();
 | 
				
			||||||
@@ -165,19 +165,24 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Initialize message and user rendering
 | 
							// Initialize message and user rendering
 | 
				
			||||||
		messageList.setCellFactory(MessageListCell::new);
 | 
							messageList.setCellFactory(MessageListCell::new);
 | 
				
			||||||
		chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
 | 
							chatList.setCellFactory(ChatListCell::new);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// JavaFX provides an internal way of populating the context menu of a text
 | 
							// JavaFX provides an internal way of populating the context menu of a text
 | 
				
			||||||
		// area.
 | 
							// area.
 | 
				
			||||||
		// We, however, need additional functionality.
 | 
							// We, however, need additional functionality.
 | 
				
			||||||
		messageTextArea.setContextMenu(new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
 | 
							messageTextArea.setContextMenu(
 | 
				
			||||||
 | 
								new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Set the icons of buttons and image views
 | 
							// Set the icons of buttons and image views
 | 
				
			||||||
		settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
 | 
							settingsButton.setGraphic(
 | 
				
			||||||
		voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
 | 
							voiceButton.setGraphic(
 | 
				
			||||||
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
 | 
							attachmentButton.setGraphic(
 | 
				
			||||||
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
							attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
				
			||||||
		messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
 | 
							messageSearchButton.setGraphic(
 | 
				
			||||||
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
							clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
				
			||||||
		onlyIfOnlineTooltip.setShowDelay(Duration.millis(250));
 | 
							onlyIfOnlineTooltip.setShowDelay(Duration.millis(250));
 | 
				
			||||||
		final var clip = new Rectangle();
 | 
							final var clip = new Rectangle();
 | 
				
			||||||
@@ -191,7 +196,6 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Set the design of the box in the upper-left corner
 | 
							// Set the design of the box in the upper-left corner
 | 
				
			||||||
		settingsButton.setAlignment(Pos.BOTTOM_RIGHT);
 | 
							settingsButton.setAlignment(Pos.BOTTOM_RIGHT);
 | 
				
			||||||
		HBox.setHgrow(spaceBetweenUserAndSettingsButton, Priority.ALWAYS);
 | 
					 | 
				
			||||||
		generateOwnStatusControl();
 | 
							generateOwnStatusControl();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Platform.runLater(() -> {
 | 
							Platform.runLater(() -> {
 | 
				
			||||||
@@ -199,15 +203,19 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			// no check will be performed in case it has already been disabled - a negative
 | 
								// no check will be performed in case it has already been disabled - a negative
 | 
				
			||||||
			// GroupCreationResult might have been returned
 | 
								// GroupCreationResult might have been returned
 | 
				
			||||||
			if (!newGroupButton.isDisabled()) newGroupButton.setDisable(!online);
 | 
								if (!newGroupButton.isDisabled())
 | 
				
			||||||
 | 
									newGroupButton.setDisable(!online);
 | 
				
			||||||
			newContactButton.setDisable(!online);
 | 
								newContactButton.setDisable(!online);
 | 
				
			||||||
			if (online) try {
 | 
								if (online)
 | 
				
			||||||
				Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
 | 
									try {
 | 
				
			||||||
				contactSearchTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
 | 
										Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
 | 
				
			||||||
				groupCreationTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
 | 
										contactSearchTab.setContent(new FXMLLoader()
 | 
				
			||||||
			} catch (final IOException e) {
 | 
											.load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
 | 
				
			||||||
				logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
 | 
										groupCreationTab.setContent(new FXMLLoader()
 | 
				
			||||||
			}
 | 
											.load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
 | 
				
			||||||
 | 
									} catch (final IOException e) {
 | 
				
			||||||
 | 
										logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			else {
 | 
								else {
 | 
				
			||||||
				Tooltip.install(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
 | 
									Tooltip.install(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
 | 
				
			||||||
				updateInfoLabel("You are offline", "info-label-warning");
 | 
									updateInfoLabel("You are offline", "info-label-warning");
 | 
				
			||||||
@@ -216,7 +224,9 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(eventType = BackEvent.class)
 | 
						@Event(eventType = BackEvent.class)
 | 
				
			||||||
	private void onBackEvent() { tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); }
 | 
						private void onBackEvent() {
 | 
				
			||||||
 | 
							tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(includeSubtypes = true)
 | 
						@Event(includeSubtypes = true)
 | 
				
			||||||
	private void onMessage(Message message) {
 | 
						private void onMessage(Message message) {
 | 
				
			||||||
@@ -225,7 +235,9 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		// Exceptions: this user is the sender (sync) or group message (group is
 | 
							// Exceptions: this user is the sender (sync) or group message (group is
 | 
				
			||||||
		// recipient)
 | 
							// recipient)
 | 
				
			||||||
		final var	ownMessage	= message.getSenderID() == localDB.getUser().getID();
 | 
							final var	ownMessage	= message.getSenderID() == localDB.getUser().getID();
 | 
				
			||||||
		final var	recipientID	= message instanceof GroupMessage || ownMessage ? message.getRecipientID() : message.getSenderID();
 | 
							final var	recipientID	=
 | 
				
			||||||
 | 
								message instanceof GroupMessage || ownMessage ? message.getRecipientID()
 | 
				
			||||||
 | 
									: message.getSenderID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		localDB.getChat(recipientID).ifPresent(chat -> {
 | 
							localDB.getChat(recipientID).ifPresent(chat -> {
 | 
				
			||||||
			Platform.runLater(() -> {
 | 
								Platform.runLater(() -> {
 | 
				
			||||||
@@ -235,13 +247,15 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
				if (chat.equals(currentChat)) {
 | 
									if (chat.equals(currentChat)) {
 | 
				
			||||||
					currentChat.read(writeProxy);
 | 
										currentChat.read(writeProxy);
 | 
				
			||||||
					scrollToMessageListEnd();
 | 
										scrollToMessageListEnd();
 | 
				
			||||||
				} else if (!ownMessage && message.getStatus() != MessageStatus.READ) chat.incrementUnreadAmount();
 | 
									} else if (!ownMessage && message.getStatus() != MessageStatus.READ)
 | 
				
			||||||
 | 
										chat.incrementUnreadAmount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Move chat with most recent unread messages to the top
 | 
									// Move chat with most recent unread messages to the top
 | 
				
			||||||
				chats.getSource().remove(chat);
 | 
									chats.getSource().remove(chat);
 | 
				
			||||||
				((ObservableList<Chat>) chats.getSource()).add(0, chat);
 | 
									((ObservableList<Chat>) chats.getSource()).add(0, chat);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (chat.equals(currentChat)) chatList.getSelectionModel().select(0);
 | 
									if (chat.equals(currentChat))
 | 
				
			||||||
 | 
										chatList.getSelectionModel().select(0);
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -251,9 +265,10 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Update UI if in current chat and the current user was the sender of the
 | 
							// Update UI if in current chat and the current user was the sender of the
 | 
				
			||||||
		// message
 | 
							// message
 | 
				
			||||||
		if (currentChat != null) localDB.getMessage(evt.getID())
 | 
							if (currentChat != null)
 | 
				
			||||||
			.filter(msg -> msg.getSenderID() == client.getSender().getID())
 | 
								localDB.getMessage(evt.getID())
 | 
				
			||||||
			.ifPresent(msg -> Platform.runLater(messageList::refresh));
 | 
									.filter(msg -> msg.getSenderID() == client.getSender().getID())
 | 
				
			||||||
 | 
									.ifPresent(msg -> Platform.runLater(messageList::refresh));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
@@ -271,18 +286,25 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
	private void onContactOperation(ContactOperation operation) {
 | 
						private void onUserOperation(UserOperation operation) {
 | 
				
			||||||
		final var contact = operation.get();
 | 
					
 | 
				
			||||||
		switch (operation.getOperationType()) {
 | 
							// All ADD dependent logic resides in LocalDB
 | 
				
			||||||
			case ADD:
 | 
							if (operation.getOperationType().equals(ElementOperation.REMOVE))
 | 
				
			||||||
				if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
 | 
								Platform.runLater(() -> disableChat(new ContactDisabled(operation.get())));
 | 
				
			||||||
				final var chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
 | 
						}
 | 
				
			||||||
				Platform.runLater(() -> ((ObservableList<Chat>) chats.getSource()).add(0, chat));
 | 
					
 | 
				
			||||||
				break;
 | 
						@Event
 | 
				
			||||||
			case REMOVE:
 | 
						private void onGroupResize(GroupResize resize) {
 | 
				
			||||||
				Platform.runLater(() -> chats.getSource().removeIf(c -> c.getRecipient().equals(contact)));
 | 
							final var chatFound = localDB.getChat(resize.getGroupID());
 | 
				
			||||||
				break;
 | 
							chatFound.ifPresent(chat -> Platform.runLater(() -> {
 | 
				
			||||||
		}
 | 
								chatList.refresh();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Update the top-bar status label if all conditions apply
 | 
				
			||||||
 | 
								if (currentChat != null && currentChat.getRecipient().equals(chat.getRecipient()))
 | 
				
			||||||
 | 
									topBarStatusLabel
 | 
				
			||||||
 | 
										.setText(chat.getRecipient().getContacts().size() + " member"
 | 
				
			||||||
 | 
											+ (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(eventType = NoAttachments.class)
 | 
						@Event(eventType = NoAttachments.class)
 | 
				
			||||||
@@ -298,31 +320,43 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event(priority = 150)
 | 
				
			||||||
	private void onGroupCreationResult(GroupCreationResult result) { Platform.runLater(() -> newGroupButton.setDisable(!result.get())); }
 | 
						private void onGroupCreationResult(GroupCreationResult result) {
 | 
				
			||||||
 | 
							Platform.runLater(() -> newGroupButton.setDisable(result.get() == null));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(eventType = ThemeChangeEvent.class)
 | 
						@Event(eventType = ThemeChangeEvent.class)
 | 
				
			||||||
	private void onThemeChange() {
 | 
						private void onThemeChange() {
 | 
				
			||||||
		settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
 | 
							settingsButton.setGraphic(
 | 
				
			||||||
		voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
 | 
							voiceButton.setGraphic(
 | 
				
			||||||
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
 | 
							attachmentButton.setGraphic(
 | 
				
			||||||
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
 | 
							DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
 | 
				
			||||||
		attachmentView.setImage(isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
							attachmentView.setImage(
 | 
				
			||||||
		messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
 | 
								isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
				
			||||||
 | 
							messageSearchButton.setGraphic(
 | 
				
			||||||
 | 
								new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
							clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
				
			||||||
		chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
 | 
							chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
 | 
				
			||||||
		messageList.setCellFactory(MessageListCell::new);
 | 
							messageList.setCellFactory(MessageListCell::new);
 | 
				
			||||||
		// TODO: cache image
 | 
					 | 
				
			||||||
		if (currentChat != null)
 | 
							if (currentChat != null)
 | 
				
			||||||
			if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
								if (currentChat.getRecipient() instanceof User)
 | 
				
			||||||
			else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
									recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(eventType = Logout.class, priority = 200)
 | 
						@Event(eventType = Logout.class, priority = 200)
 | 
				
			||||||
	private void onLogout() { eventBus.removeListener(this); }
 | 
						private void onLogout() {
 | 
				
			||||||
 | 
							eventBus.removeListener(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void onRestore() { updateRemainingCharsLabel(); }
 | 
						public void onRestore() {
 | 
				
			||||||
 | 
							updateRemainingCharsLabel();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Actions to perform when the list of contacts has been clicked.
 | 
						 * Actions to perform when the list of contacts has been clicked.
 | 
				
			||||||
@@ -331,9 +365,13 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void chatListClicked() {
 | 
						private void chatListClicked() {
 | 
				
			||||||
		if (chatList.getSelectionModel().isEmpty()) return;
 | 
							if (chatList.getSelectionModel().isEmpty())
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							final var chat = chatList.getSelectionModel().getSelectedItem();
 | 
				
			||||||
 | 
							if (chat == null)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final var user = chatList.getSelectionModel().getSelectedItem().getRecipient();
 | 
							final var user = chat.getRecipient();
 | 
				
			||||||
		if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
 | 
							if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
 | 
								// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
 | 
				
			||||||
@@ -345,7 +383,6 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
			final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount();
 | 
								final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount();
 | 
				
			||||||
			messageList.scrollTo(scrollIndex);
 | 
								messageList.scrollTo(scrollIndex);
 | 
				
			||||||
			logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
 | 
								logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
 | 
				
			||||||
			deleteContactMenuItem.setText("Delete " + user.getName());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Read the current chat
 | 
								// Read the current chat
 | 
				
			||||||
			currentChat.read(writeProxy);
 | 
								currentChat.read(writeProxy);
 | 
				
			||||||
@@ -353,7 +390,8 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
			// Discard the pending attachment
 | 
								// Discard the pending attachment
 | 
				
			||||||
			if (recorder.isRecording()) {
 | 
								if (recorder.isRecording()) {
 | 
				
			||||||
				recorder.cancel();
 | 
									recorder.cancel();
 | 
				
			||||||
				voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
									voiceButton.setGraphic(new ImageView(
 | 
				
			||||||
 | 
										IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
				voiceButton.setText(null);
 | 
									voiceButton.setText(null);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			pendingAttachment = null;
 | 
								pendingAttachment = null;
 | 
				
			||||||
@@ -361,22 +399,32 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			remainingChars.setVisible(true);
 | 
								remainingChars.setVisible(true);
 | 
				
			||||||
			remainingChars
 | 
								remainingChars
 | 
				
			||||||
				.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
 | 
									.setText(String.format("remaining chars: %d/%d",
 | 
				
			||||||
 | 
										MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
 | 
					
 | 
				
			||||||
		voiceButton.setDisable(!recorder.isSupported());
 | 
							// Enable or disable the necessary UI controls
 | 
				
			||||||
		attachmentButton.setDisable(false);
 | 
							final var chatEditable = currentChat == null || currentChat.isDisabled();
 | 
				
			||||||
 | 
							messageTextArea.setDisable(chatEditable || postingPermanentlyDisabled);
 | 
				
			||||||
 | 
							voiceButton.setDisable(!recorder.isSupported() || chatEditable);
 | 
				
			||||||
 | 
							attachmentButton.setDisable(chatEditable);
 | 
				
			||||||
		chatList.refresh();
 | 
							chatList.refresh();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Design the top bar
 | 
				
			||||||
		if (currentChat != null) {
 | 
							if (currentChat != null) {
 | 
				
			||||||
			topBarContactLabel.setText(currentChat.getRecipient().getName());
 | 
								topBarContactLabel.setText(currentChat.getRecipient().getName());
 | 
				
			||||||
 | 
								topBarContactLabel.setVisible(true);
 | 
				
			||||||
 | 
								topBarStatusLabel.setVisible(true);
 | 
				
			||||||
			if (currentChat.getRecipient() instanceof User) {
 | 
								if (currentChat.getRecipient() instanceof User) {
 | 
				
			||||||
				final var status = ((User) currentChat.getRecipient()).getStatus().toString();
 | 
									final var status = ((User) currentChat.getRecipient()).getStatus().toString();
 | 
				
			||||||
				topBarStatusLabel.setText(status);
 | 
									topBarStatusLabel.setText(status);
 | 
				
			||||||
 | 
									topBarStatusLabel.getStyleClass().clear();
 | 
				
			||||||
				topBarStatusLabel.getStyleClass().add(status.toLowerCase());
 | 
									topBarStatusLabel.getStyleClass().add(status.toLowerCase());
 | 
				
			||||||
				recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
									recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members");
 | 
									topBarStatusLabel
 | 
				
			||||||
 | 
										.setText(currentChat.getRecipient().getContacts().size() + " member"
 | 
				
			||||||
 | 
											+ (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
 | 
				
			||||||
				topBarStatusLabel.getStyleClass().clear();
 | 
									topBarStatusLabel.getStyleClass().clear();
 | 
				
			||||||
				recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
									recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -386,7 +434,6 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
			clip.setArcHeight(43);
 | 
								clip.setArcHeight(43);
 | 
				
			||||||
			clip.setArcWidth(43);
 | 
								clip.setArcWidth(43);
 | 
				
			||||||
			recipientProfilePic.setClip(clip);
 | 
								recipientProfilePic.setClip(clip);
 | 
				
			||||||
 | 
					 | 
				
			||||||
			messageSearchButton.setVisible(true);
 | 
								messageSearchButton.setVisible(true);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -397,7 +444,9 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void settingsButtonClicked() { sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); }
 | 
						private void settingsButtonClicked() {
 | 
				
			||||||
 | 
							sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Actions to perform when the "Add Contact" - Button has been clicked.
 | 
						 * Actions to perform when the "Add Contact" - Button has been clicked.
 | 
				
			||||||
@@ -405,10 +454,14 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void addContactButtonClicked() { tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal()); }
 | 
						private void addContactButtonClicked() {
 | 
				
			||||||
 | 
							tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void groupCreationButtonClicked() { tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal()); }
 | 
						private void groupCreationButtonClicked() {
 | 
				
			||||||
 | 
							tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void voiceButtonClicked() {
 | 
						private void voiceButtonClicked() {
 | 
				
			||||||
@@ -417,15 +470,19 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
				if (!recorder.isRecording()) {
 | 
									if (!recorder.isRecording()) {
 | 
				
			||||||
					Platform.runLater(() -> {
 | 
										Platform.runLater(() -> {
 | 
				
			||||||
						voiceButton.setText("Recording");
 | 
											voiceButton.setText("Recording");
 | 
				
			||||||
						voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
 | 
											voiceButton.setGraphic(new ImageView(
 | 
				
			||||||
 | 
												IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
					});
 | 
										});
 | 
				
			||||||
					recorder.start();
 | 
										recorder.start();
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					pendingAttachment = new Attachment(recorder.finish(), "Voice_recording_"
 | 
										pendingAttachment = new Attachment(recorder.finish(), "Voice_recording_"
 | 
				
			||||||
							+ DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss").format(LocalDateTime.now()) + "." + AudioRecorder.FILE_FORMAT,
 | 
											+ DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss")
 | 
				
			||||||
							AttachmentType.VOICE);
 | 
												.format(LocalDateTime.now())
 | 
				
			||||||
 | 
											+ "." + AudioRecorder.FILE_FORMAT,
 | 
				
			||||||
 | 
											AttachmentType.VOICE);
 | 
				
			||||||
					Platform.runLater(() -> {
 | 
										Platform.runLater(() -> {
 | 
				
			||||||
						voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
											voiceButton.setGraphic(new ImageView(
 | 
				
			||||||
 | 
												IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
						voiceButton.setText(null);
 | 
											voiceButton.setText(null);
 | 
				
			||||||
						checkPostConditions(false);
 | 
											checkPostConditions(false);
 | 
				
			||||||
						updateAttachmentView(true);
 | 
											updateAttachmentView(true);
 | 
				
			||||||
@@ -433,7 +490,8 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			} catch (final EnvoyException e) {
 | 
								} catch (final EnvoyException e) {
 | 
				
			||||||
				logger.log(Level.SEVERE, "Could not record audio: ", e);
 | 
									logger.log(Level.SEVERE, "Could not record audio: ", e);
 | 
				
			||||||
				Platform.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
 | 
									Platform
 | 
				
			||||||
 | 
										.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}).start();
 | 
							}).start();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -447,15 +505,16 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
 | 
							fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
 | 
				
			||||||
		fileChooser.getExtensionFilters()
 | 
							fileChooser.getExtensionFilters()
 | 
				
			||||||
			.addAll(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"),
 | 
								.addAll(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"),
 | 
				
			||||||
					new FileChooser.ExtensionFilter("Videos", "*.mp4"),
 | 
									new FileChooser.ExtensionFilter("Videos", "*.mp4"),
 | 
				
			||||||
					new FileChooser.ExtensionFilter("All Files", "*.*"));
 | 
									new FileChooser.ExtensionFilter("All Files", "*.*"));
 | 
				
			||||||
		final var file = fileChooser.showOpenDialog(sceneContext.getStage());
 | 
							final var file = fileChooser.showOpenDialog(sceneContext.getStage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (file != null) {
 | 
							if (file != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Check max file size
 | 
								// Check max file size
 | 
				
			||||||
			if (file.length() > 16E6) {
 | 
								if (file.length() > 16E6) {
 | 
				
			||||||
				new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!").showAndWait();
 | 
									new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!")
 | 
				
			||||||
 | 
										.showAndWait();
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -477,7 +536,8 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
				checkPostConditions(false);
 | 
									checkPostConditions(false);
 | 
				
			||||||
				// Setting the preview image as image of the attachmentView
 | 
									// Setting the preview image as image of the attachmentView
 | 
				
			||||||
				if (type == AttachmentType.PICTURE) {
 | 
									if (type == AttachmentType.PICTURE) {
 | 
				
			||||||
					attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes), DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
 | 
										attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes),
 | 
				
			||||||
 | 
											DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
 | 
				
			||||||
					isCustomAttachmentImage = true;
 | 
										isCustomAttachmentImage = true;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				attachmentView.setVisible(true);
 | 
									attachmentView.setVisible(true);
 | 
				
			||||||
@@ -488,8 +548,7 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Rotates every element in our application by {@code rotations}*360° in
 | 
						 * Rotates every element in our application by {@code rotations}*360° in {@code an}.
 | 
				
			||||||
	 * {@code an}.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param rotations     the amount of times the scene is rotated by 360°
 | 
						 * @param rotations     the amount of times the scene is rotated by 360°
 | 
				
			||||||
	 * @param animationTime the time in seconds that this animation lasts
 | 
						 * @param animationTime the time in seconds that this animation lasts
 | 
				
			||||||
@@ -506,7 +565,8 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		final var rotatableNodes = ReflectionUtil.getAllDeclaredNodeVariables(this);
 | 
							final var rotatableNodes = ReflectionUtil.getAllDeclaredNodeVariables(this);
 | 
				
			||||||
		for (final var node : rotatableNodes) {
 | 
							for (final var node : rotatableNodes) {
 | 
				
			||||||
			// Sets the animation duration to {animationTime}
 | 
								// Sets the animation duration to {animationTime}
 | 
				
			||||||
			final var rotateTransition = new RotateTransition(Duration.seconds(animationTime), node);
 | 
								final var rotateTransition =
 | 
				
			||||||
 | 
									new RotateTransition(Duration.seconds(animationTime), node);
 | 
				
			||||||
			// rotates every element {rotations} times
 | 
								// rotates every element {rotations} times
 | 
				
			||||||
			rotateTransition.setByAngle(rotations * 360);
 | 
								rotateTransition.setByAngle(rotations * 360);
 | 
				
			||||||
			rotateTransition.play();
 | 
								rotateTransition.play();
 | 
				
			||||||
@@ -517,9 +577,8 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Checks the text length of the {@code messageTextArea}, adjusts the
 | 
						 * Checks the text length of the {@code messageTextArea}, adjusts the {@code remainingChars}
 | 
				
			||||||
	 * {@code remainingChars} label and checks whether to send the message
 | 
						 * label and checks whether to send the message automatically.
 | 
				
			||||||
	 * automatically.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param e the key event that will be analyzed for a post request
 | 
						 * @param e the key event that will be analyzed for a post request
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -532,29 +591,33 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Sending an IsTyping event if none has been sent for
 | 
							// Sending an IsTyping event if none has been sent for
 | 
				
			||||||
		// IsTyping#millisecondsActive
 | 
							// IsTyping#millisecondsActive
 | 
				
			||||||
		if (client.isOnline() && currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
 | 
							if (client.isOnline() && currentChat.getLastWritingEvent()
 | 
				
			||||||
 | 
								+ IsTyping.millisecondsActive <= System.currentTimeMillis()) {
 | 
				
			||||||
			client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID()));
 | 
								client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID()));
 | 
				
			||||||
			currentChat.lastWritingEventWasNow();
 | 
								currentChat.lastWritingEventWasNow();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// KeyPressed will be called before the char has been added to the text, hence
 | 
							// KeyPressed will be called before the char has been added to the text, hence
 | 
				
			||||||
		// this is needed for the first char
 | 
							// this is needed for the first char
 | 
				
			||||||
		if (messageTextArea.getText().length() == 1 && e != null) checkPostConditions(e);
 | 
							if (messageTextArea.getText().length() == 1 && e != null)
 | 
				
			||||||
 | 
								checkPostConditions(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// This is needed for the messageTA context menu
 | 
							// This is needed for the messageTA context menu
 | 
				
			||||||
		else if (e == null) checkPostConditions(false);
 | 
							else if (e == null)
 | 
				
			||||||
 | 
								checkPostConditions(false);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Returns the id that should be used to send things to the server: the id of
 | 
						 * Returns the id that should be used to send things to the server: the id of 'our' {@link User}
 | 
				
			||||||
	 * 'our' {@link User} if the recipient of that object is another User, else the
 | 
						 * if the recipient of that object is another User, else the id of the {@link Group} 'our' user
 | 
				
			||||||
	 * id of the {@link Group} 'our' user is sending to.
 | 
						 * is sending to.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @return an id that can be sent to the server
 | 
						 * @return an id that can be sent to the server
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private long getChatID() {
 | 
						private long getChatID() {
 | 
				
			||||||
		return currentChat.getRecipient() instanceof User ? client.getSender().getID() : currentChat.getRecipient().getID();
 | 
							return currentChat.getRecipient() instanceof User ? client.getSender().getID()
 | 
				
			||||||
 | 
								: currentChat.getRecipient().getID();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -564,21 +627,25 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void checkPostConditions(KeyEvent e) {
 | 
						private void checkPostConditions(KeyEvent e) {
 | 
				
			||||||
		final var	enterPressed	= e.getCode() == KeyCode.ENTER;
 | 
							final var	enterPressed	= e.getCode() == KeyCode.ENTER;
 | 
				
			||||||
		final var	messagePosted	= enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown() : false;
 | 
							final var	messagePosted	=
 | 
				
			||||||
 | 
								enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown()
 | 
				
			||||||
 | 
									: false;
 | 
				
			||||||
		if (messagePosted) {
 | 
							if (messagePosted) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Removing an inserted line break if added by pressing enter
 | 
								// Removing an inserted line break if added by pressing enter
 | 
				
			||||||
			final var	text			= messageTextArea.getText();
 | 
								final var	text			= messageTextArea.getText();
 | 
				
			||||||
			final var	textPosition	= messageTextArea.getCaretPosition() - 1;
 | 
								final var	textPosition	= messageTextArea.getCaretPosition() - 1;
 | 
				
			||||||
			if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n')
 | 
								if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n')
 | 
				
			||||||
				messageTextArea.setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
 | 
									messageTextArea
 | 
				
			||||||
 | 
										.setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// if control is pressed, the enter press is originally invalidated. Here it'll
 | 
							// if control is pressed, the enter press is originally invalidated. Here it'll
 | 
				
			||||||
		// be inserted again
 | 
							// be inserted again
 | 
				
			||||||
		else if (enterPressed && e.isControlDown()) {
 | 
							else if (enterPressed && e.isControlDown()) {
 | 
				
			||||||
			var caretPosition = messageTextArea.getCaretPosition();
 | 
								var caretPosition = messageTextArea.getCaretPosition();
 | 
				
			||||||
			messageTextArea.setText(new StringBuilder(messageTextArea.getText()).insert(caretPosition, '\n').toString());
 | 
								messageTextArea.setText(new StringBuilder(messageTextArea.getText())
 | 
				
			||||||
 | 
									.insert(caretPosition, '\n').toString());
 | 
				
			||||||
			messageTextArea.positionCaret(++caretPosition);
 | 
								messageTextArea.positionCaret(++caretPosition);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		checkPostConditions(messagePosted);
 | 
							checkPostConditions(messagePosted);
 | 
				
			||||||
@@ -586,8 +653,10 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private void checkPostConditions(boolean postMessage) {
 | 
						private void checkPostConditions(boolean postMessage) {
 | 
				
			||||||
		if (!postingPermanentlyDisabled) {
 | 
							if (!postingPermanentlyDisabled) {
 | 
				
			||||||
			if (!postButton.isDisabled() && postMessage) postMessage();
 | 
								if (!postButton.isDisabled() && postMessage)
 | 
				
			||||||
			postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null || currentChat == null);
 | 
									postMessage();
 | 
				
			||||||
 | 
								postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null
 | 
				
			||||||
 | 
									|| currentChat == null);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			final var noMoreMessaging = "Go online to send messages";
 | 
								final var noMoreMessaging = "Go online to send messages";
 | 
				
			||||||
			if (!infoLabel.getText().equals(noMoreMessaging))
 | 
								if (!infoLabel.getText().equals(noMoreMessaging))
 | 
				
			||||||
@@ -621,13 +690,14 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	private void updateRemainingCharsLabel() {
 | 
						private void updateRemainingCharsLabel() {
 | 
				
			||||||
		final var	currentLength	= messageTextArea.getText().length();
 | 
							final var	currentLength	= messageTextArea.getText().length();
 | 
				
			||||||
		final var	remainingLength	= MAX_MESSAGE_LENGTH - currentLength;
 | 
							final var	remainingLength	= MAX_MESSAGE_LENGTH - currentLength;
 | 
				
			||||||
		remainingChars.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
 | 
							remainingChars
 | 
				
			||||||
 | 
								.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
 | 
				
			||||||
		remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
 | 
							remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sends a new {@link Message} or {@link GroupMessage} to the server based on
 | 
						 * Sends a new {@link Message} or {@link GroupMessage} to the server based on the text entered
 | 
				
			||||||
	 * the text entered in the {@code messageTextArea} and the given attachment.
 | 
						 * in the {@code messageTextArea} and the given attachment.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -644,8 +714,9 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		final var text = messageTextArea.getText().strip();
 | 
							final var text = messageTextArea.getText().strip();
 | 
				
			||||||
		if (!commands.getChatSceneCommands().executeIfPresent(text)) {
 | 
							if (!commands.getChatSceneCommands().executeIfPresent(text)) {
 | 
				
			||||||
			// Creating the message and its metadata
 | 
								// Creating the message and its metadata
 | 
				
			||||||
			final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
 | 
								final var builder = new MessageBuilder(localDB.getUser().getID(),
 | 
				
			||||||
				.setText(text);
 | 
									currentChat.getRecipient().getID(), localDB.getIDGenerator())
 | 
				
			||||||
 | 
										.setText(text);
 | 
				
			||||||
			// Setting an attachment, if present
 | 
								// Setting an attachment, if present
 | 
				
			||||||
			if (pendingAttachment != null) {
 | 
								if (pendingAttachment != null) {
 | 
				
			||||||
				builder.setAttachment(pendingAttachment);
 | 
									builder.setAttachment(pendingAttachment);
 | 
				
			||||||
@@ -653,8 +724,9 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
				updateAttachmentView(false);
 | 
									updateAttachmentView(false);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			// Building the final message
 | 
								// Building the final message
 | 
				
			||||||
			final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
 | 
								final var message = currentChat.getRecipient() instanceof Group
 | 
				
			||||||
					: builder.build();
 | 
									? builder.buildGroupMessage((Group) currentChat.getRecipient())
 | 
				
			||||||
 | 
									: builder.build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Send message
 | 
								// Send message
 | 
				
			||||||
			writeProxy.writeMessage(message);
 | 
								writeProxy.writeMessage(message);
 | 
				
			||||||
@@ -665,14 +737,15 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
			Platform.runLater(() -> {
 | 
								Platform.runLater(() -> {
 | 
				
			||||||
				chats.getSource().remove(currentChat);
 | 
									chats.getSource().remove(currentChat);
 | 
				
			||||||
				((ObservableList<Chat>) chats.getSource()).add(0, currentChat);
 | 
									((ObservableList<Chat>) chats.getSource()).add(0, currentChat);
 | 
				
			||||||
				chatList.getSelectionModel().select(0);
 | 
					 | 
				
			||||||
				localDB.getChats().remove(currentChat);
 | 
									localDB.getChats().remove(currentChat);
 | 
				
			||||||
				localDB.getChats().add(0, currentChat);
 | 
									localDB.getChats().add(0, currentChat);
 | 
				
			||||||
 | 
									chatList.getSelectionModel().select(0);
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			scrollToMessageListEnd();
 | 
								scrollToMessageListEnd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Request a new ID generator if all IDs were used
 | 
								// Request a new ID generator if all IDs were used
 | 
				
			||||||
			if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIDGenerator();
 | 
								if (!localDB.getIDGenerator().hasNext() && client.isOnline())
 | 
				
			||||||
 | 
									client.requestIDGenerator();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Clear text field and disable post button
 | 
							// Clear text field and disable post button
 | 
				
			||||||
@@ -687,14 +760,16 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void scrollToMessageListEnd() { messageList.scrollTo(messageList.getItems().size() - 1); }
 | 
						private void scrollToMessageListEnd() {
 | 
				
			||||||
 | 
							messageList.scrollTo(messageList.getItems().size() - 1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Updates the {@code infoLabel}.
 | 
						 * Updates the {@code infoLabel}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param text        the text to use
 | 
						 * @param text        the text to use
 | 
				
			||||||
	 * @param infoLabelID the id the the {@code infoLabel} should have so that it
 | 
						 * @param infoLabelID the id the the {@code infoLabel} should have so that it can be styled
 | 
				
			||||||
	 *                    can be styled accordingly in CSS
 | 
						 *                    accordingly in CSS
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void updateInfoLabel(String text, String infoLabelID) {
 | 
						private void updateInfoLabel(String text, String infoLabelID) {
 | 
				
			||||||
@@ -705,14 +780,16 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Updates the {@code attachmentView} in terms of visibility.<br>
 | 
						 * Updates the {@code attachmentView} in terms of visibility.<br>
 | 
				
			||||||
	 * Additionally resets the shown image to {@code DEFAULT_ATTACHMENT_VIEW_IMAGE}
 | 
						 * Additionally resets the shown image to {@code DEFAULT_ATTACHMENT_VIEW_IMAGE} if another image
 | 
				
			||||||
	 * if another image is currently present.
 | 
						 * is currently present.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param visible whether the {@code attachmentView} should be displayed
 | 
						 * @param visible whether the {@code attachmentView} should be displayed
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void updateAttachmentView(boolean visible) {
 | 
						private void updateAttachmentView(boolean visible) {
 | 
				
			||||||
		if (!attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)) attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
							if (!(attachmentView.getImage() == null
 | 
				
			||||||
 | 
								|| attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)))
 | 
				
			||||||
 | 
								attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
				
			||||||
		attachmentView.setVisible(visible);
 | 
							attachmentView.setVisible(visible);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -727,19 +804,66 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
			// Else prepend it to the HBox children
 | 
								// Else prepend it to the HBox children
 | 
				
			||||||
			final var ownUserControl = new ContactControl(localDB.getUser());
 | 
								final var ownUserControl = new ContactControl(localDB.getUser());
 | 
				
			||||||
			ownUserControl.setAlignment(Pos.CENTER_LEFT);
 | 
								ownUserControl.setAlignment(Pos.CENTER_LEFT);
 | 
				
			||||||
 | 
								HBox.setHgrow(ownUserControl, Priority.NEVER);
 | 
				
			||||||
			ownContactControl.getChildren().add(0, ownUserControl);
 | 
								ownContactControl.getChildren().add(0, ownUserControl);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Context menu actions
 | 
						/**
 | 
				
			||||||
 | 
						 * Redesigns the UI when the {@link Chat} of the given contact has been marked as disabled.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param event the contact whose chat got disabled
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Event
 | 
				
			||||||
 | 
						public void disableChat(ContactDisabled event) {
 | 
				
			||||||
 | 
							chatList.refresh();
 | 
				
			||||||
 | 
							final var recipient = event.get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
							// Decrement member count for groups
 | 
				
			||||||
	private void deleteContact() { try {} catch (final NullPointerException e) {} }
 | 
							if (recipient instanceof Group)
 | 
				
			||||||
 | 
								topBarStatusLabel.setText(recipient.getContacts().size() + " member"
 | 
				
			||||||
 | 
									+ (recipient.getContacts().size() != 1 ? "s" : ""));
 | 
				
			||||||
 | 
							if (currentChat != null && currentChat.getRecipient().equals(recipient)) {
 | 
				
			||||||
 | 
								messageTextArea.setDisable(true);
 | 
				
			||||||
 | 
								voiceButton.setDisable(true);
 | 
				
			||||||
 | 
								attachmentButton.setDisable(true);
 | 
				
			||||||
 | 
								pendingAttachment = null;
 | 
				
			||||||
 | 
								messageList.getStyleClass().clear();
 | 
				
			||||||
 | 
								messageList.getStyleClass().add("disabled-chat");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Resets every component back to its inital state before a chat was selected.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void resetState() {
 | 
				
			||||||
 | 
							currentChat = null;
 | 
				
			||||||
 | 
							chatList.getSelectionModel().clearSelection();
 | 
				
			||||||
 | 
							messageList.getItems().clear();
 | 
				
			||||||
 | 
							messageTextArea.setDisable(true);
 | 
				
			||||||
 | 
							attachmentView.setImage(null);
 | 
				
			||||||
 | 
							topBarContactLabel.setVisible(false);
 | 
				
			||||||
 | 
							topBarStatusLabel.setVisible(false);
 | 
				
			||||||
 | 
							messageSearchButton.setVisible(false);
 | 
				
			||||||
 | 
							messageTextArea.clear();
 | 
				
			||||||
 | 
							messageTextArea.setDisable(true);
 | 
				
			||||||
 | 
							attachmentButton.setDisable(true);
 | 
				
			||||||
 | 
							voiceButton.setDisable(true);
 | 
				
			||||||
 | 
							remainingChars.setVisible(false);
 | 
				
			||||||
 | 
							pendingAttachment = null;
 | 
				
			||||||
 | 
							recipientProfilePic.setImage(null);
 | 
				
			||||||
 | 
							if (recorder.isRecording())
 | 
				
			||||||
 | 
								recorder.cancel();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void copyAndPostMessage() {
 | 
						private void copyAndPostMessage() {
 | 
				
			||||||
		final var messageText = messageTextArea.getText();
 | 
							final var messageText = messageTextArea.getText();
 | 
				
			||||||
		Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
 | 
							Toolkit.getDefaultToolkit().getSystemClipboard()
 | 
				
			||||||
 | 
								.setContents(new StringSelection(messageText), null);
 | 
				
			||||||
		final var	image				= attachmentView.getImage();
 | 
							final var	image				= attachmentView.getImage();
 | 
				
			||||||
		final var	messageAttachment	= pendingAttachment;
 | 
							final var	messageAttachment	= pendingAttachment;
 | 
				
			||||||
		postMessage();
 | 
							postMessage();
 | 
				
			||||||
@@ -747,7 +871,8 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		updateRemainingCharsLabel();
 | 
							updateRemainingCharsLabel();
 | 
				
			||||||
		postButton.setDisable(messageText.isBlank());
 | 
							postButton.setDisable(messageText.isBlank());
 | 
				
			||||||
		attachmentView.setImage(image);
 | 
							attachmentView.setImage(image);
 | 
				
			||||||
		if (attachmentView.getImage() != null) attachmentView.setVisible(true);
 | 
							if (attachmentView.getImage() != null)
 | 
				
			||||||
 | 
								attachmentView.setVisible(true);
 | 
				
			||||||
		pendingAttachment = messageAttachment;
 | 
							pendingAttachment = messageAttachment;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -756,11 +881,14 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void clearMessageSelection() { messageList.getSelectionModel().clearSelection(); }
 | 
						public void clearMessageSelection() {
 | 
				
			||||||
 | 
							messageList.getSelectionModel().clearSelection();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void searchContacts() {
 | 
						private void searchContacts() {
 | 
				
			||||||
		chats.setPredicate(contactSearch.getText().isBlank() ? c -> true
 | 
							chats.setPredicate(contactSearch.getText().isBlank() ? c -> true
 | 
				
			||||||
				: c -> c.getRecipient().getName().toLowerCase().contains(contactSearch.getText().toLowerCase()));
 | 
								: c -> c.getRecipient().getName().toLowerCase()
 | 
				
			||||||
 | 
									.contains(contactSearch.getText().toLowerCase()));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,28 +7,28 @@ import javafx.fxml.FXML;
 | 
				
			|||||||
import javafx.scene.control.*;
 | 
					import javafx.scene.control.*;
 | 
				
			||||||
import javafx.scene.control.Alert.AlertType;
 | 
					import javafx.scene.control.Alert.AlertType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.User;
 | 
				
			||||||
 | 
					import envoy.event.ElementOperation;
 | 
				
			||||||
 | 
					import envoy.event.contact.*;
 | 
				
			||||||
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Context;
 | 
					import envoy.client.data.Context;
 | 
				
			||||||
import envoy.client.event.BackEvent;
 | 
					import envoy.client.event.BackEvent;
 | 
				
			||||||
import envoy.client.helper.AlertHelper;
 | 
					import envoy.client.helper.AlertHelper;
 | 
				
			||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
import envoy.client.ui.control.ContactControl;
 | 
					import envoy.client.ui.control.ContactControl;
 | 
				
			||||||
import envoy.client.ui.listcell.ListCellFactory;
 | 
					import envoy.client.ui.listcell.ListCellFactory;
 | 
				
			||||||
import envoy.data.User;
 | 
					 | 
				
			||||||
import envoy.event.ElementOperation;
 | 
					 | 
				
			||||||
import envoy.event.contact.*;
 | 
					 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides a search bar in which a user name (substring) can be entered. The
 | 
					 * Provides a search bar in which a user name (substring) can be entered. The users with a matching
 | 
				
			||||||
 * users with a matching name are then displayed inside a list view. A
 | 
					 * name are then displayed inside a list view. A {@link UserSearchRequest} is sent on every
 | 
				
			||||||
 * {@link UserSearchRequest} is sent on every keystroke.
 | 
					 * keystroke.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * <i>The actual search algorithm is implemented on the server.
 | 
					 * <i>The actual search algorithm is implemented on the server.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * To create a group, a button is available that loads the
 | 
					 * To create a group, a button is available that loads the {@link GroupCreationTab}.
 | 
				
			||||||
 * {@link GroupCreationTab}.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
@@ -59,16 +59,22 @@ public class ContactSearchTab implements EventListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
	private void onUserSearchResult(UserSearchResult result) {
 | 
						private void onUserSearchResult(UserSearchResult result) {
 | 
				
			||||||
		Platform.runLater(() -> { userList.getItems().clear(); userList.getItems().addAll(result.get()); });
 | 
							Platform.runLater(() -> {
 | 
				
			||||||
 | 
								userList.getItems().clear();
 | 
				
			||||||
 | 
								userList.getItems().addAll(result.get());
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
	private void onContactOperation(ContactOperation operation) {
 | 
						private void onUserOperation(UserOperation operation) {
 | 
				
			||||||
		final var contact = operation.get();
 | 
							final var contact = operation.get();
 | 
				
			||||||
		if (operation.getOperationType() == ElementOperation.ADD) Platform.runLater(() -> {
 | 
							if (operation.getOperationType() == ElementOperation.ADD)
 | 
				
			||||||
			userList.getItems().remove(contact);
 | 
								Platform.runLater(() -> {
 | 
				
			||||||
			if (currentlySelectedUser != null && currentlySelectedUser.equals(contact) && alert.isShowing()) alert.close();
 | 
									userList.getItems().remove(contact);
 | 
				
			||||||
		});
 | 
									if (currentlySelectedUser != null && currentlySelectedUser.equals(contact)
 | 
				
			||||||
 | 
										&& alert.isShowing())
 | 
				
			||||||
 | 
										alert.close();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -79,13 +85,15 @@ public class ContactSearchTab implements EventListener {
 | 
				
			|||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void sendRequest() {
 | 
						private void sendRequest() {
 | 
				
			||||||
		final var text = searchBar.getText().strip();
 | 
							final var text = searchBar.getText().strip();
 | 
				
			||||||
		if (!text.isBlank()) client.send(new UserSearchRequest(text));
 | 
							if (!text.isBlank())
 | 
				
			||||||
		else userList.getItems().clear();
 | 
								client.send(new UserSearchRequest(text));
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								userList.getItems().clear();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Clears the text in the search bar and the items shown in the list.
 | 
						 * Clears the text in the search bar and the items shown in the list. Additionally disables both
 | 
				
			||||||
	 * Additionally disables both clear and search button.
 | 
						 * clear and search button.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -96,8 +104,7 @@ public class ContactSearchTab implements EventListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sends an {@link ContactOperation} for the selected user to the
 | 
						 * Sends an {@link UserOperation} for the selected user to the server.
 | 
				
			||||||
	 * server.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -106,7 +113,8 @@ public class ContactSearchTab implements EventListener {
 | 
				
			|||||||
		final var user = userList.getSelectionModel().getSelectedItem();
 | 
							final var user = userList.getSelectionModel().getSelectedItem();
 | 
				
			||||||
		if (user != null) {
 | 
							if (user != null) {
 | 
				
			||||||
			currentlySelectedUser = user;
 | 
								currentlySelectedUser = user;
 | 
				
			||||||
			alert.setContentText("Add user " + currentlySelectedUser.getName() + " to your contacts?");
 | 
								alert.setContentText(
 | 
				
			||||||
 | 
									"Add user " + currentlySelectedUser.getName() + " to your contacts?");
 | 
				
			||||||
			AlertHelper.confirmAction(alert, this::addAsContact);
 | 
								AlertHelper.confirmAction(alert, this::addAsContact);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -114,7 +122,7 @@ public class ContactSearchTab implements EventListener {
 | 
				
			|||||||
	private void addAsContact() {
 | 
						private void addAsContact() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Sends the event to the server
 | 
							// Sends the event to the server
 | 
				
			||||||
		final var event = new ContactOperation(currentlySelectedUser, ElementOperation.ADD);
 | 
							final var event = new UserOperation(currentlySelectedUser, ElementOperation.ADD);
 | 
				
			||||||
		client.send(event);
 | 
							client.send(event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Removes the chosen user and updates the UI
 | 
							// Removes the chosen user and updates the UI
 | 
				
			||||||
@@ -124,5 +132,8 @@ public class ContactSearchTab implements EventListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void backButtonClicked() { eventBus.dispatch(new BackEvent()); }
 | 
						private void backButtonClicked() {
 | 
				
			||||||
 | 
							searchBar.setText("");
 | 
				
			||||||
 | 
							eventBus.dispatch(new BackEvent());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,25 +10,25 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.input.MouseEvent;
 | 
					import javafx.scene.input.MouseEvent;
 | 
				
			||||||
import javafx.scene.layout.HBox;
 | 
					import javafx.scene.layout.HBox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.*;
 | 
				
			||||||
 | 
					import envoy.event.GroupCreation;
 | 
				
			||||||
 | 
					import envoy.event.contact.UserOperation;
 | 
				
			||||||
 | 
					import envoy.util.Bounds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.event.BackEvent;
 | 
					import envoy.client.event.BackEvent;
 | 
				
			||||||
import envoy.client.ui.control.*;
 | 
					import envoy.client.ui.control.*;
 | 
				
			||||||
import envoy.client.ui.listcell.ListCellFactory;
 | 
					import envoy.client.ui.listcell.ListCellFactory;
 | 
				
			||||||
import envoy.data.*;
 | 
					 | 
				
			||||||
import envoy.event.GroupCreation;
 | 
					 | 
				
			||||||
import envoy.event.contact.ContactOperation;
 | 
					 | 
				
			||||||
import envoy.util.Bounds;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides a group creation interface. A group name can be entered in the text
 | 
					 * Provides a group creation interface. A group name can be entered in the text field at the top.
 | 
				
			||||||
 * field at the top. Available users (local chat recipients) are displayed
 | 
					 * Available users (local chat recipients) are displayed inside a list and can be selected (multiple
 | 
				
			||||||
 * inside a list and can be selected (multiple selection available).
 | 
					 * selection available).
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * When the group creation button is pressed, a {@link GroupCreation} is sent to
 | 
					 * When the group creation button is pressed, a {@link GroupCreation} is sent to the server. This
 | 
				
			||||||
 * the server. This controller enforces a valid group name and a non-empty
 | 
					 * controller enforces a valid group name and a non-empty member list (excluding the client user).
 | 
				
			||||||
 * member list (excluding the client user).
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -82,7 +82,7 @@ public class GroupCreationTab implements EventListener {
 | 
				
			|||||||
				.map(User.class::cast)
 | 
									.map(User.class::cast)
 | 
				
			||||||
				.collect(Collectors.toList()));
 | 
									.collect(Collectors.toList()));
 | 
				
			||||||
		resizeQuickSelectSpace(0);
 | 
							resizeQuickSelectSpace(0);
 | 
				
			||||||
		quickSelectList.addEventFilter(MouseEvent.MOUSE_PRESSED, evt -> evt.consume());
 | 
							quickSelectList.addEventFilter(MouseEvent.MOUSE_PRESSED, MouseEvent::consume);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -93,8 +93,10 @@ public class GroupCreationTab implements EventListener {
 | 
				
			|||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void userListClicked() {
 | 
						private void userListClicked() {
 | 
				
			||||||
		if (userList.getSelectionModel().getSelectedItem() != null) {
 | 
							if (userList.getSelectionModel().getSelectedItem() != null) {
 | 
				
			||||||
			quickSelectList.getItems().add(new QuickSelectControl(userList.getSelectionModel().getSelectedItem(), this::removeFromQuickSelection));
 | 
								quickSelectList.getItems().add(new QuickSelectControl(
 | 
				
			||||||
			createButton.setDisable(quickSelectList.getItems().isEmpty() || groupNameField.getText().isBlank());
 | 
									userList.getSelectionModel().getSelectedItem(), this::removeFromQuickSelection));
 | 
				
			||||||
 | 
								createButton.setDisable(
 | 
				
			||||||
 | 
									quickSelectList.getItems().isEmpty() || groupNameField.getText().isBlank());
 | 
				
			||||||
			resizeQuickSelectSpace(60);
 | 
								resizeQuickSelectSpace(60);
 | 
				
			||||||
			userList.getItems().remove(userList.getSelectionModel().getSelectedItem());
 | 
								userList.getItems().remove(userList.getSelectionModel().getSelectedItem());
 | 
				
			||||||
			userList.getSelectionModel().clearSelection();
 | 
								userList.getSelectionModel().clearSelection();
 | 
				
			||||||
@@ -102,13 +104,16 @@ public class GroupCreationTab implements EventListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Checks, whether the {@code createButton} can be enabled because text is
 | 
						 * Checks, whether the {@code createButton} can be enabled because text is present in the text
 | 
				
			||||||
	 * present in the text field.
 | 
						 * field.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void textUpdated() { createButton.setDisable(quickSelectList.getItems().isEmpty() || groupNameField.getText().isBlank()); }
 | 
						private void textUpdated() {
 | 
				
			||||||
 | 
							createButton
 | 
				
			||||||
 | 
								.setDisable(quickSelectList.getItems().isEmpty() || groupNameField.getText().isBlank());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sends a {@link GroupCreation} to the server and closes this scene.
 | 
						 * Sends a {@link GroupCreation} to the server and closes this scene.
 | 
				
			||||||
@@ -152,19 +157,20 @@ public class GroupCreationTab implements EventListener {
 | 
				
			|||||||
	private void createGroup(String name) {
 | 
						private void createGroup(String name) {
 | 
				
			||||||
		Context.getInstance()
 | 
							Context.getInstance()
 | 
				
			||||||
			.getClient()
 | 
								.getClient()
 | 
				
			||||||
			.send(new GroupCreation(name, quickSelectList.getItems().stream().map(q -> q.getUser().getID()).collect(Collectors.toSet())));
 | 
								.send(new GroupCreation(name, quickSelectList.getItems().stream()
 | 
				
			||||||
 | 
									.map(q -> q.getUser().getID()).collect(Collectors.toSet())));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Returns true if the proposed group name is already present in the users
 | 
						 * Returns true if the proposed group name is already present in the users {@code LocalDB}.
 | 
				
			||||||
	 * {@code LocalDB}.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param newName the chosen group name
 | 
						 * @param newName the chosen group name
 | 
				
			||||||
	 * @return true if this name is already present
 | 
						 * @return true if this name is already present
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean groupNameAlreadyPresent(String newName) {
 | 
						public boolean groupNameAlreadyPresent(String newName) {
 | 
				
			||||||
		return localDB.getChats().stream().map(Chat::getRecipient).filter(Group.class::isInstance).map(Contact::getName).anyMatch(newName::equals);
 | 
							return localDB.getChats().stream().map(Chat::getRecipient).filter(Group.class::isInstance)
 | 
				
			||||||
 | 
								.map(Contact::getName).anyMatch(newName::equals);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -234,11 +240,11 @@ public class GroupCreationTab implements EventListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
	private void onContactOperation(ContactOperation operation) {
 | 
						private void onUserOperation(UserOperation operation) {
 | 
				
			||||||
		if (operation.get() instanceof User) Platform.runLater(() -> {
 | 
							Platform.runLater(() -> {
 | 
				
			||||||
			switch (operation.getOperationType()) {
 | 
								switch (operation.getOperationType()) {
 | 
				
			||||||
				case ADD:
 | 
									case ADD:
 | 
				
			||||||
					userList.getItems().add((User) operation.get());
 | 
										userList.getItems().add(operation.get());
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				case REMOVE:
 | 
									case REMOVE:
 | 
				
			||||||
					userList.getItems().removeIf(operation.get()::equals);
 | 
										userList.getItems().removeIf(operation.get()::equals);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,14 +10,15 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.control.Alert.AlertType;
 | 
					import javafx.scene.control.Alert.AlertType;
 | 
				
			||||||
import javafx.scene.image.ImageView;
 | 
					import javafx.scene.image.ImageView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.ClientConfig;
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
import envoy.client.ui.*;
 | 
					
 | 
				
			||||||
import envoy.client.util.IconUtil;
 | 
					 | 
				
			||||||
import envoy.data.LoginCredentials;
 | 
					import envoy.data.LoginCredentials;
 | 
				
			||||||
import envoy.event.HandshakeRejection;
 | 
					import envoy.event.HandshakeRejection;
 | 
				
			||||||
import envoy.util.*;
 | 
					import envoy.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					import envoy.client.data.ClientConfig;
 | 
				
			||||||
 | 
					import envoy.client.ui.Startup;
 | 
				
			||||||
 | 
					import envoy.client.util.IconUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller for the login scene.
 | 
					 * Controller for the login scene.
 | 
				
			||||||
@@ -78,25 +79,32 @@ public final class LoginScene implements EventListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void loginButtonPressed() {
 | 
						private void loginButtonPressed() {
 | 
				
			||||||
		final String user = userTextField.getText(), pass = passwordField.getText(), repeatPass = repeatPasswordField.getText();
 | 
							final String	user			= userTextField.getText(), pass = passwordField.getText(),
 | 
				
			||||||
 | 
								repeatPass = repeatPasswordField.getText();
 | 
				
			||||||
		final boolean	requestToken	= cbStaySignedIn.isSelected();
 | 
							final boolean	requestToken	= cbStaySignedIn.isSelected();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Prevent registration with unequal passwords
 | 
							// Prevent registration with unequal passwords
 | 
				
			||||||
		if (registration && !pass.equals(repeatPass)) {
 | 
							if (registration && !pass.equals(repeatPass)) {
 | 
				
			||||||
			new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
 | 
								new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one")
 | 
				
			||||||
 | 
									.showAndWait();
 | 
				
			||||||
			repeatPasswordField.clear();
 | 
								repeatPasswordField.clear();
 | 
				
			||||||
		} else if (!Bounds.isValidContactName(user)) {
 | 
							} else if (!Bounds.isValidContactName(user)) {
 | 
				
			||||||
			new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
 | 
								new Alert(AlertType.ERROR,
 | 
				
			||||||
 | 
									"The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")")
 | 
				
			||||||
 | 
										.showAndWait();
 | 
				
			||||||
			userTextField.clear();
 | 
								userTextField.clear();
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			Instant lastSync = Startup.loadLastSync(userTextField.getText());
 | 
								Instant lastSync = Startup.loadLastSync(userTextField.getText());
 | 
				
			||||||
			Startup.performHandshake(registration ? LoginCredentials.registration(user, pass, requestToken, Startup.VERSION, lastSync)
 | 
								Startup.performHandshake(registration
 | 
				
			||||||
					: LoginCredentials.login(user, pass, requestToken, Startup.VERSION, lastSync));
 | 
									? LoginCredentials.registration(user, pass, requestToken, Startup.VERSION, lastSync)
 | 
				
			||||||
 | 
									: LoginCredentials.login(user, pass, requestToken, Startup.VERSION, lastSync));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void offlineModeButtonPressed() { Startup.attemptOfflineMode(userTextField.getText()); }
 | 
						private void offlineModeButtonPressed() {
 | 
				
			||||||
 | 
							Startup.attemptOfflineMode(userTextField.getText());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void registerSwitchPressed() {
 | 
						private void registerSwitchPressed() {
 | 
				
			||||||
@@ -127,5 +135,7 @@ public final class LoginScene implements EventListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
	private void onHandshakeRejection(HandshakeRejection evt) { Platform.runLater(() -> new Alert(AlertType.ERROR, evt.get()).showAndWait()); }
 | 
						private void onHandshakeRejection(HandshakeRejection evt) {
 | 
				
			||||||
 | 
							Platform.runLater(() -> new Alert(AlertType.ERROR, evt.get()).showAndWait());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,8 @@ public final class SettingsScene implements KeyboardMapping {
 | 
				
			|||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void initialize() {
 | 
						private void initialize() {
 | 
				
			||||||
		settingsList.setCellFactory(new ListCellFactory<>(pane -> new Label(pane.getTitle())));
 | 
							settingsList.setCellFactory(new ListCellFactory<>(pane -> new Label(pane.getTitle())));
 | 
				
			||||||
		settingsList.getItems().addAll(new GeneralSettingsPane(), new UserSettingsPane(), new DownloadSettingsPane(), new BugReportPane());
 | 
							settingsList.getItems().addAll(new GeneralSettingsPane(), new UserSettingsPane(),
 | 
				
			||||||
 | 
								new DownloadSettingsPane(), new BugReportPane());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
@@ -41,10 +42,13 @@ public final class SettingsScene implements KeyboardMapping {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void backButtonClicked() { Context.getInstance().getSceneContext().pop(); }
 | 
						private void backButtonClicked() {
 | 
				
			||||||
 | 
							Context.getInstance().getSceneContext().pop();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
 | 
						public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
 | 
				
			||||||
		return Map.of(new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN), this::backButtonClicked);
 | 
							return Map.of(new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN),
 | 
				
			||||||
 | 
								this::backButtonClicked);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,7 @@ public abstract class AbstractListCell<T, U extends Node> extends ListCell<T> {
 | 
				
			|||||||
			setGraphic(renderItem(item));
 | 
								setGraphic(renderItem(item));
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			setGraphic(null);
 | 
								setGraphic(null);
 | 
				
			||||||
 | 
								setCursor(Cursor.DEFAULT);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					package envoy.client.ui.listcell;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javafx.scene.control.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.data.*;
 | 
				
			||||||
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
 | 
					import envoy.client.ui.control.ChatControl;
 | 
				
			||||||
 | 
					import envoy.client.util.UserUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * A list cell containing chats represented as chat controls.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class ChatListCell extends AbstractListCell<Chat, ChatControl> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Client client = Context.getInstance().getClient();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param listView the list view inside of which the cell will be displayed
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public ChatListCell(ListView<? extends Chat> listView) {
 | 
				
			||||||
 | 
							super(listView);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						protected ChatControl renderItem(Chat chat) {
 | 
				
			||||||
 | 
							if (client.isOnline()) {
 | 
				
			||||||
 | 
								final var	menu		= new ContextMenu();
 | 
				
			||||||
 | 
								final var	removeMI	= new MenuItem();
 | 
				
			||||||
 | 
								removeMI.setText(
 | 
				
			||||||
 | 
									chat.isDisabled() ? "Delete "
 | 
				
			||||||
 | 
										: chat.getRecipient() instanceof User ? "Block "
 | 
				
			||||||
 | 
											: "Leave group " + chat.getRecipient().getName());
 | 
				
			||||||
 | 
								removeMI.setOnAction(
 | 
				
			||||||
 | 
									chat.isDisabled() ? e -> UserUtil.deleteContact(chat.getRecipient())
 | 
				
			||||||
 | 
										: e -> UserUtil.disableContact(chat.getRecipient()));
 | 
				
			||||||
 | 
								menu.getItems().add(removeMI);
 | 
				
			||||||
 | 
								setContextMenu(menu);
 | 
				
			||||||
 | 
							} else
 | 
				
			||||||
 | 
								setContextMenu(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: replace with icon in ChatControl
 | 
				
			||||||
 | 
							final var chatControl = new ChatControl(chat);
 | 
				
			||||||
 | 
							if (chat.isDisabled())
 | 
				
			||||||
 | 
								chatControl.getStyleClass().add("disabled-chat");
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								chatControl.getStyleClass().remove("disabled-chat");
 | 
				
			||||||
 | 
							return chatControl;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -28,5 +28,7 @@ public final class GenericListCell<T, U extends Node> extends AbstractListCell<T
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	protected U renderItem(T item) { return renderer.apply(item); }
 | 
						protected U renderItem(T item) {
 | 
				
			||||||
 | 
							return renderer.apply(item);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,15 +7,15 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.util.Callback;
 | 
					import javafx.util.Callback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides a creation mechanism for generic list cells given a list view and a
 | 
					 * Provides a creation mechanism for generic list cells given a list view and a conversion function.
 | 
				
			||||||
 * conversion function.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @param <T> the type of object to display
 | 
					 * @param <T> the type of object to display
 | 
				
			||||||
 * @param <U> the type of node displayed
 | 
					 * @param <U> the type of node displayed
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public final class ListCellFactory<T, U extends Node> implements Callback<ListView<T>, ListCell<T>> {
 | 
					public final class ListCellFactory<T, U extends Node>
 | 
				
			||||||
 | 
						implements Callback<ListView<T>, ListCell<T>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Function<? super T, U> renderer;
 | 
						private final Function<? super T, U> renderer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,8 +23,12 @@ public final class ListCellFactory<T, U extends Node> implements Callback<ListVi
 | 
				
			|||||||
	 * @param renderer a function converting the type to display into a node
 | 
						 * @param renderer a function converting the type to display into a node
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ListCellFactory(Function<? super T, U> renderer) { this.renderer = renderer; }
 | 
						public ListCellFactory(Function<? super T, U> renderer) {
 | 
				
			||||||
 | 
							this.renderer = renderer;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public ListCell<T> call(ListView<T> listView) { return new GenericListCell<>(listView, renderer); }
 | 
						public ListCell<T> call(ListView<T> listView) {
 | 
				
			||||||
 | 
							return new GenericListCell<>(listView, renderer);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,9 +3,10 @@ package envoy.client.ui.listcell;
 | 
				
			|||||||
import javafx.geometry.*;
 | 
					import javafx.geometry.*;
 | 
				
			||||||
import javafx.scene.control.ListView;
 | 
					import javafx.scene.control.ListView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.ui.control.MessageControl;
 | 
					 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.ui.control.MessageControl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * A list cell containing messages represented as message controls.
 | 
					 * A list cell containing messages represented as message controls.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -18,15 +19,20 @@ public final class MessageListCell extends AbstractListCell<Message, MessageCont
 | 
				
			|||||||
	 * @param listView the list view inside of which the cell will be displayed
 | 
						 * @param listView the list view inside of which the cell will be displayed
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public MessageListCell(ListView<? extends Message> listView) { super(listView); }
 | 
						public MessageListCell(ListView<? extends Message> listView) {
 | 
				
			||||||
 | 
							super(listView);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	protected MessageControl renderItem(Message message) {
 | 
						protected MessageControl renderItem(Message message) {
 | 
				
			||||||
		final var control = new MessageControl(message);
 | 
							final var control = new MessageControl(message);
 | 
				
			||||||
		listView.widthProperty().addListener((observable, oldValue, newValue) -> adjustPadding(newValue.intValue(), control.isOwnMessage()));
 | 
							listView.widthProperty().addListener((observable, oldValue,
 | 
				
			||||||
 | 
								newValue) -> adjustPadding(newValue.intValue(), control.isOwnMessage()));
 | 
				
			||||||
		adjustPadding((int) listView.getWidth(), control.isOwnMessage());
 | 
							adjustPadding((int) listView.getWidth(), control.isOwnMessage());
 | 
				
			||||||
		if (control.isOwnMessage()) setAlignment(Pos.CENTER_RIGHT);
 | 
							if (control.isOwnMessage())
 | 
				
			||||||
		else setAlignment(Pos.CENTER_LEFT);
 | 
								setAlignment(Pos.CENTER_RIGHT);
 | 
				
			||||||
 | 
							else
 | 
				
			||||||
 | 
								setAlignment(Pos.CENTER_LEFT);
 | 
				
			||||||
		return control;
 | 
							return control;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * This package contains custom list cells that are used to display certain
 | 
					 * This package contains custom list cells that are used to display certain things.
 | 
				
			||||||
 * things.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,8 @@ import javafx.scene.input.InputEvent;
 | 
				
			|||||||
import envoy.event.IssueProposal;
 | 
					import envoy.event.IssueProposal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This class offers the option for users to submit a bug report. Only the title
 | 
					 * This class offers the option for users to submit a bug report. Only the title of a bug is needed
 | 
				
			||||||
 * of a bug is needed to be sent.
 | 
					 * to be sent.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
@@ -17,12 +17,15 @@ public final class BugReportPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private final Label		titleLabel				= new Label("Suggest a title for the bug:");
 | 
						private final Label		titleLabel				= new Label("Suggest a title for the bug:");
 | 
				
			||||||
	private final TextField	titleTextField			= new TextField();
 | 
						private final TextField	titleTextField			= new TextField();
 | 
				
			||||||
	private final Label		pleaseExplainLabel		= new Label("Paste here the log of what went wrong and/ or explain what went wrong:");
 | 
						private final Label		pleaseExplainLabel		=
 | 
				
			||||||
 | 
							new Label("Paste here the log of what went wrong and/ or explain what went wrong:");
 | 
				
			||||||
	private final TextArea	errorDetailArea			= new TextArea();
 | 
						private final TextArea	errorDetailArea			= new TextArea();
 | 
				
			||||||
	private final CheckBox	showUsernameInBugReport	= new CheckBox("Show your username in the bug report?");
 | 
						private final CheckBox	showUsernameInBugReport	=
 | 
				
			||||||
 | 
							new CheckBox("Show your username in the bug report?");
 | 
				
			||||||
	private final Button	submitReportButton		= new Button("Submit report");
 | 
						private final Button	submitReportButton		= new Button("Submit report");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final EventHandler<? super InputEvent> inputEventHandler = e -> submitReportButton.setDisable(titleTextField.getText().isBlank());
 | 
						private final EventHandler<? super InputEvent> inputEventHandler =
 | 
				
			||||||
 | 
							e -> submitReportButton.setDisable(titleTextField.getText().isBlank());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code BugReportPane}.
 | 
						 * Creates a new {@code BugReportPane}.
 | 
				
			||||||
@@ -59,7 +62,9 @@ public final class BugReportPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
		submitReportButton.setDisable(true);
 | 
							submitReportButton.setDisable(true);
 | 
				
			||||||
		submitReportButton.setOnAction(e -> {
 | 
							submitReportButton.setOnAction(e -> {
 | 
				
			||||||
			String title = titleTextField.getText(), description = errorDetailArea.getText();
 | 
								String title = titleTextField.getText(), description = errorDetailArea.getText();
 | 
				
			||||||
			client.send(showUsernameInBugReport.isSelected() ? new IssueProposal(title, description, true) : new IssueProposal(title, description, client.getSender().getName(), true));
 | 
								client.send(
 | 
				
			||||||
 | 
									showUsernameInBugReport.isSelected() ? new IssueProposal(title, description, true)
 | 
				
			||||||
 | 
										: new IssueProposal(title, description, client.getSender().getName(), true));
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		getChildren().add(submitReportButton);
 | 
							getChildren().add(submitReportButton);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,18 +24,22 @@ public final class DownloadSettingsPane extends SettingsPane {
 | 
				
			|||||||
		setPadding(new Insets(15));
 | 
							setPadding(new Insets(15));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Checkbox to disable asking
 | 
							// Checkbox to disable asking
 | 
				
			||||||
		final var checkBox = new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName());
 | 
							final var checkBox =
 | 
				
			||||||
 | 
								new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName());
 | 
				
			||||||
		checkBox.setSelected(settings.isDownloadSavedWithoutAsking());
 | 
							checkBox.setSelected(settings.isDownloadSavedWithoutAsking());
 | 
				
			||||||
		checkBox.setTooltip(new Tooltip("Determines whether a \"Select save location\" - dialogue will be shown when saving attachments."));
 | 
							checkBox.setTooltip(new Tooltip(
 | 
				
			||||||
 | 
								"Determines whether a \"Select save location\" - dialogue will be shown when saving attachments."));
 | 
				
			||||||
		checkBox.setOnAction(e -> settings.setDownloadSavedWithoutAsking(checkBox.isSelected()));
 | 
							checkBox.setOnAction(e -> settings.setDownloadSavedWithoutAsking(checkBox.isSelected()));
 | 
				
			||||||
		getChildren().add(checkBox);
 | 
							getChildren().add(checkBox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Displaying the default path to save to
 | 
							// Displaying the default path to save to
 | 
				
			||||||
		final var pathLabel = new Label(settings.getItems().get("downloadLocation").getDescription() + ":");
 | 
							final var pathLabel =
 | 
				
			||||||
 | 
								new Label(settings.getItems().get("downloadLocation").getDescription() + ":");
 | 
				
			||||||
		pathLabel.setWrapText(true);
 | 
							pathLabel.setWrapText(true);
 | 
				
			||||||
		getChildren().add(pathLabel);
 | 
							getChildren().add(pathLabel);
 | 
				
			||||||
		final var hbox = new HBox(20);
 | 
							final var hbox = new HBox(20);
 | 
				
			||||||
		Tooltip.install(hbox, new Tooltip("Determines the location where attachments will be saved to."));
 | 
							Tooltip.install(hbox,
 | 
				
			||||||
 | 
								new Tooltip("Determines the location where attachments will be saved to."));
 | 
				
			||||||
		final var currentPath = new Label(settings.getDownloadLocation().getAbsolutePath());
 | 
							final var currentPath = new Label(settings.getDownloadLocation().getAbsolutePath());
 | 
				
			||||||
		hbox.getChildren().add(currentPath);
 | 
							hbox.getChildren().add(currentPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,7 +49,8 @@ public final class DownloadSettingsPane extends SettingsPane {
 | 
				
			|||||||
			final var directoryChooser = new DirectoryChooser();
 | 
								final var directoryChooser = new DirectoryChooser();
 | 
				
			||||||
			directoryChooser.setTitle("Select the directory where attachments should be saved to");
 | 
								directoryChooser.setTitle("Select the directory where attachments should be saved to");
 | 
				
			||||||
			directoryChooser.setInitialDirectory(settings.getDownloadLocation());
 | 
								directoryChooser.setInitialDirectory(settings.getDownloadLocation());
 | 
				
			||||||
			final var selectedDirectory = directoryChooser.showDialog(context.getSceneContext().getStage());
 | 
								final var selectedDirectory =
 | 
				
			||||||
 | 
									directoryChooser.showDialog(context.getSceneContext().getStage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (selectedDirectory != null) {
 | 
								if (selectedDirectory != null) {
 | 
				
			||||||
				currentPath.setText(selectedDirectory.getAbsolutePath());
 | 
									currentPath.setText(selectedDirectory.getAbsolutePath());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,13 +2,14 @@ package envoy.client.ui.settings;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javafx.scene.control.*;
 | 
					import javafx.scene.control.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.SettingsItem;
 | 
					import envoy.client.data.SettingsItem;
 | 
				
			||||||
import envoy.client.event.ThemeChangeEvent;
 | 
					import envoy.client.event.ThemeChangeEvent;
 | 
				
			||||||
import envoy.client.ui.StatusTrayIcon;
 | 
					import envoy.client.ui.StatusTrayIcon;
 | 
				
			||||||
import envoy.client.util.UserUtil;
 | 
					import envoy.client.util.UserUtil;
 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
@@ -27,22 +28,27 @@ public final class GeneralSettingsPane extends SettingsPane {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Add hide on close if supported
 | 
							// Add hide on close if supported
 | 
				
			||||||
		if (StatusTrayIcon.isSupported()) {
 | 
							if (StatusTrayIcon.isSupported()) {
 | 
				
			||||||
			final var	hideOnCloseCheckbox	= new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("hideOnClose"));
 | 
								final var	hideOnCloseCheckbox	=
 | 
				
			||||||
			final var	hideOnCloseTooltip	= new Tooltip("If selected, Envoy will still be present in the task bar when closed.");
 | 
									new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("hideOnClose"));
 | 
				
			||||||
 | 
								final var	hideOnCloseTooltip	= new Tooltip(
 | 
				
			||||||
 | 
									"If selected, Envoy will still be present in the task bar when closed.");
 | 
				
			||||||
			hideOnCloseTooltip.setWrapText(true);
 | 
								hideOnCloseTooltip.setWrapText(true);
 | 
				
			||||||
			hideOnCloseCheckbox.setTooltip(hideOnCloseTooltip);
 | 
								hideOnCloseCheckbox.setTooltip(hideOnCloseTooltip);
 | 
				
			||||||
			getChildren().add(hideOnCloseCheckbox);
 | 
								getChildren().add(hideOnCloseCheckbox);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final var	enterToSendCheckbox	= new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("enterToSend"));
 | 
							final var	enterToSendCheckbox	=
 | 
				
			||||||
 | 
								new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("enterToSend"));
 | 
				
			||||||
		final var	enterToSendTooltip	= new Tooltip(
 | 
							final var	enterToSendTooltip	= new Tooltip(
 | 
				
			||||||
				"When selected, messages can be sent pressing \"Enter\". A line break can be inserted by pressing \"Ctrl\" + \"Enter\". Else it will be the other way around.");
 | 
								"When selected, messages can be sent pressing \"Enter\". A line break can be inserted by pressing \"Ctrl\" + \"Enter\". Else it will be the other way around.");
 | 
				
			||||||
		enterToSendTooltip.setWrapText(true);
 | 
							enterToSendTooltip.setWrapText(true);
 | 
				
			||||||
		enterToSendCheckbox.setTooltip(enterToSendTooltip);
 | 
							enterToSendCheckbox.setTooltip(enterToSendTooltip);
 | 
				
			||||||
		getChildren().add(enterToSendCheckbox);
 | 
							getChildren().add(enterToSendCheckbox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final var	askForConfirmationCheckbox	= new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("askForConfirmation"));
 | 
							final var	askForConfirmationCheckbox	=
 | 
				
			||||||
		final var	askForConfirmationTooltip	= new Tooltip("When selected, nothing will prompt a confirmation dialog");
 | 
								new SettingsCheckbox((SettingsItem<Boolean>) settingsItems.get("askForConfirmation"));
 | 
				
			||||||
 | 
							final var	askForConfirmationTooltip	=
 | 
				
			||||||
 | 
								new Tooltip("When selected, nothing will prompt a confirmation dialog");
 | 
				
			||||||
		askForConfirmationTooltip.setWrapText(true);
 | 
							askForConfirmationTooltip.setWrapText(true);
 | 
				
			||||||
		askForConfirmationCheckbox.setTooltip(askForConfirmationTooltip);
 | 
							askForConfirmationCheckbox.setTooltip(askForConfirmationTooltip);
 | 
				
			||||||
		getChildren().add(askForConfirmationCheckbox);
 | 
							getChildren().add(askForConfirmationCheckbox);
 | 
				
			||||||
@@ -50,9 +56,13 @@ public final class GeneralSettingsPane extends SettingsPane {
 | 
				
			|||||||
		final var combobox = new ComboBox<String>();
 | 
							final var combobox = new ComboBox<String>();
 | 
				
			||||||
		combobox.getItems().add("dark");
 | 
							combobox.getItems().add("dark");
 | 
				
			||||||
		combobox.getItems().add("light");
 | 
							combobox.getItems().add("light");
 | 
				
			||||||
		combobox.setTooltip(new Tooltip("Determines the current theme Envoy will be displayed in."));
 | 
							combobox
 | 
				
			||||||
 | 
								.setTooltip(new Tooltip("Determines the current theme Envoy will be displayed in."));
 | 
				
			||||||
		combobox.setValue(settings.getCurrentTheme());
 | 
							combobox.setValue(settings.getCurrentTheme());
 | 
				
			||||||
		combobox.setOnAction(e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent()); });
 | 
							combobox.setOnAction(e -> {
 | 
				
			||||||
 | 
								settings.setCurrentTheme(combobox.getValue());
 | 
				
			||||||
 | 
								EventBus.getInstance().dispatch(new ThemeChangeEvent());
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
		getChildren().add(combobox);
 | 
							getChildren().add(combobox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final var statusComboBox = new ComboBox<UserStatus>();
 | 
							final var statusComboBox = new ComboBox<UserStatus>();
 | 
				
			||||||
@@ -64,7 +74,8 @@ public final class GeneralSettingsPane extends SettingsPane {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		final var logoutButton = new Button("Logout");
 | 
							final var logoutButton = new Button("Logout");
 | 
				
			||||||
		logoutButton.setOnAction(e -> UserUtil.logout());
 | 
							logoutButton.setOnAction(e -> UserUtil.logout());
 | 
				
			||||||
		final var logoutTooltip = new Tooltip("Brings you back to the login screen and removes \"remember me\" status from this account");
 | 
							final var logoutTooltip = new Tooltip(
 | 
				
			||||||
 | 
								"Brings you back to the login screen and removes \"remember me\" status from this account");
 | 
				
			||||||
		logoutTooltip.setWrapText(true);
 | 
							logoutTooltip.setWrapText(true);
 | 
				
			||||||
		logoutButton.setTooltip(logoutTooltip);
 | 
							logoutButton.setTooltip(logoutTooltip);
 | 
				
			||||||
		getChildren().add(logoutButton);
 | 
							getChildren().add(logoutButton);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,10 +8,10 @@ import javafx.scene.paint.Color;
 | 
				
			|||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Inheriting from this class signifies that options should only be available if
 | 
					 * Inheriting from this class signifies that options should only be available if the
 | 
				
			||||||
 * the {@link envoy.data.User} is currently online. If the user is currently
 | 
					 * {@link envoy.data.User} is currently online. If the user is currently offline, all
 | 
				
			||||||
 * offline, all {@link javafx.scene.Node} variables will be disabled and a
 | 
					 * {@link javafx.scene.Node} variables will be disabled and a {@link Tooltip} will be displayed for
 | 
				
			||||||
 * {@link Tooltip} will be displayed for the whole node.
 | 
					 * the whole node.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
@@ -21,7 +21,8 @@ public abstract class OnlineOnlySettingsPane extends SettingsPane {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	protected final Client client = context.getClient();
 | 
						protected final Client client = context.getClient();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account.");
 | 
						private final Tooltip beOnlineReminder =
 | 
				
			||||||
 | 
							new Tooltip("You need to be online to modify your account.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param title the title of this pane
 | 
						 * @param title the title of this pane
 | 
				
			||||||
@@ -33,14 +34,17 @@ public abstract class OnlineOnlySettingsPane extends SettingsPane {
 | 
				
			|||||||
		setDisable(!client.isOnline());
 | 
							setDisable(!client.isOnline());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!client.isOnline()) {
 | 
							if (!client.isOnline()) {
 | 
				
			||||||
			final var infoLabel = new Label("You shall not pass!\n(... Unless you would happen to be online)");
 | 
								final var infoLabel =
 | 
				
			||||||
 | 
									new Label("You shall not pass!\n(... Unless you would happen to be online)");
 | 
				
			||||||
			infoLabel.setId("info-label-warning");
 | 
								infoLabel.setId("info-label-warning");
 | 
				
			||||||
			infoLabel.setWrapText(true);
 | 
								infoLabel.setWrapText(true);
 | 
				
			||||||
			getChildren().add(infoLabel);
 | 
								getChildren().add(infoLabel);
 | 
				
			||||||
			setBackground(new Background(new BackgroundFill(Color.grayRgb(100, 0.3), CornerRadii.EMPTY, Insets.EMPTY)));
 | 
								setBackground(new Background(
 | 
				
			||||||
 | 
									new BackgroundFill(Color.grayRgb(100, 0.3), CornerRadii.EMPTY, Insets.EMPTY)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Tooltip.install(this, beOnlineReminder);
 | 
								Tooltip.install(this, beOnlineReminder);
 | 
				
			||||||
		} else Tooltip.uninstall(this, beOnlineReminder);
 | 
							} else
 | 
				
			||||||
 | 
								Tooltip.uninstall(this, beOnlineReminder);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -49,5 +53,7 @@ public abstract class OnlineOnlySettingsPane extends SettingsPane {
 | 
				
			|||||||
	 * @param text the text to display
 | 
						 * @param text the text to display
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	protected void setToolTipText(String text) { beOnlineReminder.setText(text); }
 | 
						protected void setToolTipText(String text) {
 | 
				
			||||||
 | 
							beOnlineReminder.setText(text);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,9 @@ public abstract class SettingsPane extends VBox {
 | 
				
			|||||||
	protected static final Settings	settings	= Settings.getInstance();
 | 
						protected static final Settings	settings	= Settings.getInstance();
 | 
				
			||||||
	protected static final Context	context		= Context.getInstance();
 | 
						protected static final Context	context		= Context.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected SettingsPane(String title) { this.title = title; }
 | 
						protected SettingsPane(String title) {
 | 
				
			||||||
 | 
							this.title = title;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the title of this settings pane
 | 
						 * @return the title of this settings pane
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,12 +14,13 @@ import javafx.scene.input.InputEvent;
 | 
				
			|||||||
import javafx.scene.layout.HBox;
 | 
					import javafx.scene.layout.HBox;
 | 
				
			||||||
import javafx.stage.FileChooser;
 | 
					import javafx.stage.FileChooser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.ui.control.ProfilePicImageView;
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
import envoy.client.util.IconUtil;
 | 
					
 | 
				
			||||||
import envoy.event.*;
 | 
					import envoy.event.*;
 | 
				
			||||||
import envoy.util.*;
 | 
					import envoy.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					import envoy.client.ui.control.ProfilePicImageView;
 | 
				
			||||||
 | 
					import envoy.client.util.IconUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
@@ -58,12 +59,14 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
		profilePic.setFitWidth(60);
 | 
							profilePic.setFitWidth(60);
 | 
				
			||||||
		profilePic.setFitHeight(60);
 | 
							profilePic.setFitHeight(60);
 | 
				
			||||||
		profilePic.setOnMouseClicked(e -> {
 | 
							profilePic.setOnMouseClicked(e -> {
 | 
				
			||||||
			if (!client.isOnline()) return;
 | 
								if (!client.isOnline())
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
			final var pictureChooser = new FileChooser();
 | 
								final var pictureChooser = new FileChooser();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			pictureChooser.setTitle("Select a new profile pic");
 | 
								pictureChooser.setTitle("Select a new profile pic");
 | 
				
			||||||
			pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
 | 
								pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
 | 
				
			||||||
			pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"));
 | 
								pictureChooser.getExtensionFilters().add(
 | 
				
			||||||
 | 
									new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			final var file = pictureChooser.showOpenDialog(context.getSceneContext().getStage());
 | 
								final var file = pictureChooser.showOpenDialog(context.getSceneContext().getStage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,7 +75,8 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
				// Check max file size
 | 
									// Check max file size
 | 
				
			||||||
				// TODO: Move to config
 | 
									// TODO: Move to config
 | 
				
			||||||
				if (file.length() > 5E6) {
 | 
									if (file.length() > 5E6) {
 | 
				
			||||||
					new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 5MB!").showAndWait();
 | 
										new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 5MB!")
 | 
				
			||||||
 | 
											.showAndWait();
 | 
				
			||||||
					return;
 | 
										return;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -92,8 +96,8 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
		newUsername = username;
 | 
							newUsername = username;
 | 
				
			||||||
		usernameTextField.setText(username);
 | 
							usernameTextField.setText(username);
 | 
				
			||||||
		final EventHandler<? super InputEvent> textChanged = e -> {
 | 
							final EventHandler<? super InputEvent> textChanged = e -> {
 | 
				
			||||||
			newUsername = usernameTextField.getText();
 | 
								newUsername		= usernameTextField.getText();
 | 
				
			||||||
			usernameChanged = newUsername != username;
 | 
								usernameChanged	= newUsername != username;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		usernameTextField.setOnInputMethodTextChanged(textChanged);
 | 
							usernameTextField.setOnInputMethodTextChanged(textChanged);
 | 
				
			||||||
		usernameTextField.setOnKeyTyped(textChanged);
 | 
							usernameTextField.setOnKeyTyped(textChanged);
 | 
				
			||||||
@@ -102,14 +106,21 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// "Displaying" the password change mechanism
 | 
							// "Displaying" the password change mechanism
 | 
				
			||||||
		final HBox[]	passwordHBoxes	= { new HBox(), new HBox(), new HBox() };
 | 
							final HBox[]	passwordHBoxes	= { new HBox(), new HBox(), new HBox() };
 | 
				
			||||||
		final Label[]	passwordLabels	= { new Label("Enter current password:"), new Label("Enter new password:"),
 | 
							final Label[]	passwordLabels	=
 | 
				
			||||||
 | 
								{	new Label("Enter current password:"), new Label("Enter new password:"),
 | 
				
			||||||
				new Label("Repeat new password:") };
 | 
									new Label("Repeat new password:") };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final PasswordField[]					passwordFields	= { currentPasswordField, newPasswordField, repeatNewPasswordField };
 | 
							final PasswordField[]					passwordFields	=
 | 
				
			||||||
 | 
								{ currentPasswordField, newPasswordField, repeatNewPasswordField };
 | 
				
			||||||
		final EventHandler<? super InputEvent>	passwordEntered	= e -> {
 | 
							final EventHandler<? super InputEvent>	passwordEntered	= e -> {
 | 
				
			||||||
																	newPassword		= newPasswordField.getText();
 | 
																						newPassword		=
 | 
				
			||||||
																	validPassword	= newPassword.equals(repeatNewPasswordField.getText())
 | 
																							newPasswordField.getText();
 | 
				
			||||||
																			&& !newPasswordField.getText().isBlank();
 | 
																						validPassword	= newPassword
 | 
				
			||||||
 | 
																							.equals(
 | 
				
			||||||
 | 
																								repeatNewPasswordField
 | 
				
			||||||
 | 
																									.getText())
 | 
				
			||||||
 | 
																							&& !newPasswordField
 | 
				
			||||||
 | 
																								.getText().isBlank();
 | 
				
			||||||
																};
 | 
																					};
 | 
				
			||||||
		newPasswordField.setOnInputMethodTextChanged(passwordEntered);
 | 
							newPasswordField.setOnInputMethodTextChanged(passwordEntered);
 | 
				
			||||||
		newPasswordField.setOnKeyTyped(passwordEntered);
 | 
							newPasswordField.setOnKeyTyped(passwordEntered);
 | 
				
			||||||
@@ -125,7 +136,8 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Displaying the save button
 | 
							// Displaying the save button
 | 
				
			||||||
		saveButton.setOnAction(e -> save(client.getSender().getID(), currentPasswordField.getText()));
 | 
							saveButton
 | 
				
			||||||
 | 
								.setOnAction(e -> save(client.getSender().getID(), currentPasswordField.getText()));
 | 
				
			||||||
		saveButton.setAlignment(Pos.BOTTOM_RIGHT);
 | 
							saveButton.setAlignment(Pos.BOTTOM_RIGHT);
 | 
				
			||||||
		getChildren().add(saveButton);
 | 
							getChildren().add(saveButton);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -156,7 +168,9 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
		} else if (!validContactName) {
 | 
							} else if (!validContactName) {
 | 
				
			||||||
			final var alert = new Alert(AlertType.ERROR);
 | 
								final var alert = new Alert(AlertType.ERROR);
 | 
				
			||||||
			alert.setTitle("Invalid username");
 | 
								alert.setTitle("Invalid username");
 | 
				
			||||||
			alert.setContentText("The entered username does not conform with the naming limitations: " + Bounds.CONTACT_NAME_PATTERN);
 | 
								alert.setContentText(
 | 
				
			||||||
 | 
									"The entered username does not conform with the naming limitations: "
 | 
				
			||||||
 | 
										+ Bounds.CONTACT_NAME_PATTERN);
 | 
				
			||||||
			alert.showAndWait();
 | 
								alert.showAndWait();
 | 
				
			||||||
			logger.log(Level.INFO, "An invalid username was requested.");
 | 
								logger.log(Level.INFO, "An invalid username was requested.");
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * This package contains classes used for representing the settings
 | 
					 * This package contains classes used for representing the settings visually.
 | 
				
			||||||
 * visually.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,12 +9,12 @@ import javax.imageio.ImageIO;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javafx.scene.image.Image;
 | 
					import javafx.scene.image.Image;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.data.Settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides static utility methods for loading icons from the resource
 | 
					 * Provides static utility methods for loading icons from the resource folder.
 | 
				
			||||||
 * folder.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.1-beta
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -34,7 +34,10 @@ public final class IconUtil {
 | 
				
			|||||||
	 * @return the loaded image
 | 
						 * @return the loaded image
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static Image load(String path) { return cache.computeIfAbsent(path, p -> new Image(IconUtil.class.getResource(p).toExternalForm())); }
 | 
						public static Image load(String path) {
 | 
				
			||||||
 | 
							return cache.computeIfAbsent(path,
 | 
				
			||||||
 | 
								p -> new Image(IconUtil.class.getResource(p).toExternalForm()));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads an image from the resource folder and scales it to the given size.
 | 
						 * Loads an image from the resource folder and scales it to the given size.
 | 
				
			||||||
@@ -45,12 +48,13 @@ public final class IconUtil {
 | 
				
			|||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static Image load(String path, int size) {
 | 
						public static Image load(String path, int size) {
 | 
				
			||||||
		return scaledCache.computeIfAbsent(path + size, p -> new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true));
 | 
							return scaledCache.computeIfAbsent(path + size,
 | 
				
			||||||
 | 
								p -> new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true,
 | 
				
			||||||
 | 
									true));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads a {@code .png} image from the sub-folder {@code /icons/} of the
 | 
						 * Loads a {@code .png} image from the sub-folder {@code /icons/} of the resource folder.
 | 
				
			||||||
	 * resource folder.
 | 
					 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * The suffix {@code .png} is automatically appended.
 | 
						 * The suffix {@code .png} is automatically appended.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
@@ -60,11 +64,13 @@ public final class IconUtil {
 | 
				
			|||||||
	 * @apiNote let's load a sample image {@code /icons/abc.png}.<br>
 | 
						 * @apiNote let's load a sample image {@code /icons/abc.png}.<br>
 | 
				
			||||||
	 *          To do that, we only have to call {@code IconUtil.loadIcon("abc")}
 | 
						 *          To do that, we only have to call {@code IconUtil.loadIcon("abc")}
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static Image loadIcon(String name) { return load("/icons/" + name + ".png"); }
 | 
						public static Image loadIcon(String name) {
 | 
				
			||||||
 | 
							return load("/icons/" + name + ".png");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads a {@code .png} image from the sub-folder {@code /icons/} of the
 | 
						 * Loads a {@code .png} image from the sub-folder {@code /icons/} of the resource folder and
 | 
				
			||||||
	 * resource folder and scales it to the given size.<br>
 | 
						 * scales it to the given size.<br>
 | 
				
			||||||
	 * The suffix {@code .png} is automatically appended.
 | 
						 * The suffix {@code .png} is automatically appended.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param name the image name without the .png suffix
 | 
						 * @param name the image name without the .png suffix
 | 
				
			||||||
@@ -72,20 +78,19 @@ public final class IconUtil {
 | 
				
			|||||||
	 * @return the loaded image
 | 
						 * @return the loaded image
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 * @apiNote let's load a sample image {@code /icons/abc.png} in size 16.<br>
 | 
						 * @apiNote let's load a sample image {@code /icons/abc.png} in size 16.<br>
 | 
				
			||||||
	 *          To do that, we only have to call
 | 
						 *          To do that, we only have to call {@code IconUtil.loadIcon("abc", 16)}
 | 
				
			||||||
	 *          {@code IconUtil.loadIcon("abc", 16)}
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static Image loadIcon(String name, int size) { return load("/icons/" + name + ".png", size); }
 | 
						public static Image loadIcon(String name, int size) {
 | 
				
			||||||
 | 
							return load("/icons/" + name + ".png", size);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads a {@code .png} image whose design depends on the currently active theme
 | 
						 * Loads a {@code .png} image whose design depends on the currently active theme from the
 | 
				
			||||||
	 * from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the
 | 
						 * sub-folder {@code /icons/dark/} or {@code /icons/light/} of the resource folder.
 | 
				
			||||||
	 * resource folder.
 | 
					 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * The suffix {@code .png} is automatically appended.
 | 
						 * The suffix {@code .png} is automatically appended.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param name the image name without the "black" or "white" suffix and without
 | 
						 * @param name the image name without the "black" or "white" suffix and without the .png suffix
 | 
				
			||||||
	 *             the .png suffix
 | 
					 | 
				
			||||||
	 * @return the loaded image
 | 
						 * @return the loaded image
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 * @apiNote let's take two sample images {@code /icons/dark/abc.png} and
 | 
						 * @apiNote let's take two sample images {@code /icons/dark/abc.png} and
 | 
				
			||||||
@@ -93,12 +98,14 @@ public final class IconUtil {
 | 
				
			|||||||
	 *          To do that theme sensitive, we only have to call
 | 
						 *          To do that theme sensitive, we only have to call
 | 
				
			||||||
	 *          {@code IconUtil.loadIconThemeSensitive("abc")}
 | 
						 *          {@code IconUtil.loadIconThemeSensitive("abc")}
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static Image loadIconThemeSensitive(String name) { return loadIcon(themeSpecificSubFolder() + name); }
 | 
						public static Image loadIconThemeSensitive(String name) {
 | 
				
			||||||
 | 
							return loadIcon(themeSpecificSubFolder() + name);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads a {@code .png} image whose design depends on the currently active theme
 | 
						 * Loads a {@code .png} image whose design depends on the currently active theme from the
 | 
				
			||||||
	 * from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the
 | 
						 * sub-folder {@code /icons/dark/} or {@code /icons/light/} of the resource folder and scales it
 | 
				
			||||||
	 * resource folder and scales it to the given size.
 | 
						 * to the given size.
 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * The suffix {@code .png} is automatically appended.
 | 
						 * The suffix {@code .png} is automatically appended.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
@@ -111,20 +118,19 @@ public final class IconUtil {
 | 
				
			|||||||
	 *          To do that theme sensitive, we only have to call
 | 
						 *          To do that theme sensitive, we only have to call
 | 
				
			||||||
	 *          {@code IconUtil.loadIconThemeSensitive("abc", 16)}
 | 
						 *          {@code IconUtil.loadIconThemeSensitive("abc", 16)}
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static Image loadIconThemeSensitive(String name, int size) { return loadIcon(themeSpecificSubFolder() + name, size); }
 | 
						public static Image loadIconThemeSensitive(String name, int size) {
 | 
				
			||||||
 | 
							return loadIcon(themeSpecificSubFolder() + name, size);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 *
 | 
						 * Loads images specified by an enum. The images have to be named like the lowercase enum
 | 
				
			||||||
	 * Loads images specified by an enum. The images have to be named like the
 | 
						 * constants with {@code .png} extension and be located inside a folder with the lowercase name
 | 
				
			||||||
	 * lowercase enum constants with {@code .png} extension and be located inside a
 | 
						 * of the enum, which must be contained inside the {@code /icons/} folder.
 | 
				
			||||||
	 * folder with the lowercase name of the enum, which must be contained inside
 | 
					 | 
				
			||||||
	 * the {@code /icons/} folder.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param <T>       the enum that specifies the images to load
 | 
						 * @param <T>       the enum that specifies the images to load
 | 
				
			||||||
	 * @param enumClass the class of the enum
 | 
						 * @param enumClass the class of the enum
 | 
				
			||||||
	 * @param size      the size to scale the images to
 | 
						 * @param size      the size to scale the images to
 | 
				
			||||||
	 * @return a map containing the loaded images with the corresponding enum
 | 
						 * @return a map containing the loaded images with the corresponding enum constants as keys
 | 
				
			||||||
	 *         constants as keys
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static <T extends Enum<T>> EnumMap<T, Image> loadByEnum(Class<T> enumClass, int size) {
 | 
						public static <T extends Enum<T>> EnumMap<T, Image> loadByEnum(Class<T> enumClass, int size) {
 | 
				
			||||||
@@ -147,22 +153,25 @@ public final class IconUtil {
 | 
				
			|||||||
			try {
 | 
								try {
 | 
				
			||||||
				return ImageIO.read(IconUtil.class.getResource(path));
 | 
									return ImageIO.read(IconUtil.class.getResource(path));
 | 
				
			||||||
			} catch (IOException e) {
 | 
								} catch (IOException e) {
 | 
				
			||||||
				EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
 | 
									EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING,
 | 
				
			||||||
 | 
										String.format("Could not load image at path %s: ", path), e);
 | 
				
			||||||
				return null;
 | 
									return null;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This method should be called if the display of an image depends upon the
 | 
						 * This method should be called if the display of an image depends upon the currently active
 | 
				
			||||||
	 * currently active theme.<br>
 | 
						 * theme.<br>
 | 
				
			||||||
	 * In case of a default theme, the string returned will be
 | 
						 * In case of a default theme, the string returned will be ({@code dark/} or {@code light/}),
 | 
				
			||||||
	 * ({@code dark/} or {@code light/}), otherwise it will be empty.
 | 
						 * otherwise it will be empty.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @return the theme specific folder
 | 
						 * @return the theme specific folder
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private static String themeSpecificSubFolder() {
 | 
						private static String themeSpecificSubFolder() {
 | 
				
			||||||
		return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : "";
 | 
							return Settings.getInstance().isUsingDefaultTheme()
 | 
				
			||||||
 | 
								? Settings.getInstance().getCurrentTheme() + "/"
 | 
				
			||||||
 | 
								: "";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,13 +7,14 @@ import java.util.logging.*;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javafx.stage.FileChooser;
 | 
					import javafx.stage.FileChooser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
import envoy.client.event.MessageDeletion;
 | 
					
 | 
				
			||||||
import envoy.client.ui.controller.ChatScene;
 | 
					 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
 | 
					import envoy.client.event.MessageDeletion;
 | 
				
			||||||
 | 
					import envoy.client.ui.controller.ChatScene;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Contains methods that are commonly used for {@link Message}s.
 | 
					 * Contains methods that are commonly used for {@link Message}s.
 | 
				
			||||||
@@ -34,8 +35,10 @@ public class MessageUtil {
 | 
				
			|||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void copyMessageText(Message message) {
 | 
						public static void copyMessageText(Message message) {
 | 
				
			||||||
		logger.log(Level.FINEST, "A copy of message text \"" + message.getText() + "\" was requested");
 | 
							logger.log(Level.FINEST,
 | 
				
			||||||
		Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(message.getText()), null);
 | 
								"A copy of message text \"" + message.getText() + "\" was requested");
 | 
				
			||||||
 | 
							Toolkit.getDefaultToolkit().getSystemClipboard()
 | 
				
			||||||
 | 
								.setContents(new StringSelection(message.getText()), null);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -46,8 +49,10 @@ public class MessageUtil {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void deleteMessage(Message message) {
 | 
						public static void deleteMessage(Message message) {
 | 
				
			||||||
		final var	messageDeletionEvent	= new MessageDeletion(message.getID());
 | 
							final var	messageDeletionEvent	= new MessageDeletion(message.getID());
 | 
				
			||||||
		final var	controller				= Context.getInstance().getSceneContext().getController();
 | 
							final var	controller				=
 | 
				
			||||||
		if (controller instanceof ChatScene) ((ChatScene) controller).clearMessageSelection();
 | 
								Context.getInstance().getSceneContext().getController();
 | 
				
			||||||
 | 
							if (controller instanceof ChatScene)
 | 
				
			||||||
 | 
								((ChatScene) controller).clearMessageSelection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Removing the message locally
 | 
							// Removing the message locally
 | 
				
			||||||
		EventBus.getInstance().dispatch(messageDeletionEvent);
 | 
							EventBus.getInstance().dispatch(messageDeletionEvent);
 | 
				
			||||||
@@ -56,22 +61,24 @@ public class MessageUtil {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Forwards the given message.
 | 
						 * Forwards the given message. Currently not implemented.
 | 
				
			||||||
	 * Currently not implemented.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param message the message to forward
 | 
						 * @param message the message to forward
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void forwardMessage(Message message) { logger.log(Level.FINEST, "Message forwarding was requested for " + message); }
 | 
						public static void forwardMessage(Message message) {
 | 
				
			||||||
 | 
							logger.log(Level.FINEST, "Message forwarding was requested for " + message);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Quotes the given message.
 | 
						 * Quotes the given message. Currently not implemented.
 | 
				
			||||||
	 * Currently not implemented.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param message the message to quote
 | 
						 * @param message the message to quote
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void quoteMessage(Message message) { logger.log(Level.FINEST, "Message quotation was requested for " + message); }
 | 
						public static void quoteMessage(Message message) {
 | 
				
			||||||
 | 
							logger.log(Level.FINEST, "Message quotation was requested for " + message);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Saves the attachment of a message, if present.
 | 
						 * Saves the attachment of a message, if present.
 | 
				
			||||||
@@ -81,7 +88,8 @@ public class MessageUtil {
 | 
				
			|||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void saveAttachment(Message message) {
 | 
						public static void saveAttachment(Message message) {
 | 
				
			||||||
		if (!message.hasAttachment()) throw new IllegalArgumentException("Cannot save a non-existing attachment");
 | 
							if (!message.hasAttachment())
 | 
				
			||||||
 | 
								throw new IllegalArgumentException("Cannot save a non-existing attachment");
 | 
				
			||||||
		File		file;
 | 
							File		file;
 | 
				
			||||||
		final var	fileName			= message.getAttachment().getName();
 | 
							final var	fileName			= message.getAttachment().getName();
 | 
				
			||||||
		final var	downloadLocation	= Settings.getInstance().getDownloadLocation();
 | 
							final var	downloadLocation	= Settings.getInstance().getDownloadLocation();
 | 
				
			||||||
@@ -92,14 +100,17 @@ public class MessageUtil {
 | 
				
			|||||||
			fileChooser.setInitialFileName(fileName);
 | 
								fileChooser.setInitialFileName(fileName);
 | 
				
			||||||
			fileChooser.setInitialDirectory(downloadLocation);
 | 
								fileChooser.setInitialDirectory(downloadLocation);
 | 
				
			||||||
			file = fileChooser.showSaveDialog(Context.getInstance().getSceneContext().getStage());
 | 
								file = fileChooser.showSaveDialog(Context.getInstance().getSceneContext().getStage());
 | 
				
			||||||
		} else file = new File(downloadLocation, fileName);
 | 
							} else
 | 
				
			||||||
 | 
								file = new File(downloadLocation, fileName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// A file was selected
 | 
							// A file was selected
 | 
				
			||||||
		if (file != null) try (var fos = new FileOutputStream(file)) {
 | 
							if (file != null)
 | 
				
			||||||
			fos.write(message.getAttachment().getData());
 | 
								try (var fos = new FileOutputStream(file)) {
 | 
				
			||||||
			logger.log(Level.FINE, "Attachment of message was saved at " + file.getAbsolutePath());
 | 
									fos.write(message.getAttachment().getData());
 | 
				
			||||||
		} catch (final IOException e) {
 | 
									logger.log(Level.FINE,
 | 
				
			||||||
			logger.log(Level.WARNING, "Could not save attachment of " + message + ": ", e);
 | 
										"Attachment of message was saved at " + file.getAbsolutePath());
 | 
				
			||||||
		}
 | 
								} catch (final IOException e) {
 | 
				
			||||||
 | 
									logger.log(Level.WARNING, "Could not save attachment of " + message + ": ", e);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,47 +14,43 @@ public final class ReflectionUtil {
 | 
				
			|||||||
	private ReflectionUtil() {}
 | 
						private ReflectionUtil() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Gets all declared variable values of the given instance that have the
 | 
						 * Gets all declared variable values of the given instance that have the specified class.
 | 
				
			||||||
	 * specified class.
 | 
					 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * (i.e. can get all {@code JComponents} (Swing) or {@code Nodes} (JavaFX) in a
 | 
						 * (i.e. can get all {@code JComponents} (Swing) or {@code Nodes} (JavaFX) in a GUI class).
 | 
				
			||||||
	 * GUI class).
 | 
					 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * <b>Important: If you are using a module, you first need to declare <br>
 | 
						 * <b>Important: If you are using a module, you first need to declare <br>
 | 
				
			||||||
	 * "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
 | 
						 * "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param <T>          the type of the object
 | 
						 * @param <T>          the type of the object
 | 
				
			||||||
	 * @param <R>          the type to return
 | 
						 * @param <R>          the type to return
 | 
				
			||||||
	 * @param instance     the instance of a given class whose values are to be
 | 
						 * @param instance     the instance of a given class whose values are to be evaluated
 | 
				
			||||||
	 *                     evaluated
 | 
					 | 
				
			||||||
	 * @param typeToReturn the type of variable to return
 | 
						 * @param typeToReturn the type of variable to return
 | 
				
			||||||
	 * @return all variables in the given instance that have the requested type
 | 
						 * @return all variables in the given instance that have the requested type
 | 
				
			||||||
	 * @throws RuntimeException if an exception occurs
 | 
						 * @throws RuntimeException if an exception occurs
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static <T, R> Stream<R> getAllDeclaredVariablesOfTypeAsStream(T instance, Class<R> typeToReturn) {
 | 
						public static <T, R> Stream<R> getAllDeclaredVariablesOfTypeAsStream(T instance,
 | 
				
			||||||
		return Arrays.stream(instance.getClass().getDeclaredFields()).filter(field -> typeToReturn.isAssignableFrom(field.getType())).map(field -> {
 | 
							Class<R> typeToReturn) {
 | 
				
			||||||
			try {
 | 
							return Arrays.stream(instance.getClass().getDeclaredFields())
 | 
				
			||||||
				field.setAccessible(true);
 | 
								.filter(field -> typeToReturn.isAssignableFrom(field.getType())).map(field -> {
 | 
				
			||||||
				return typeToReturn.cast(field.get(instance));
 | 
									try {
 | 
				
			||||||
			} catch (IllegalArgumentException | IllegalAccessException e) {
 | 
										field.setAccessible(true);
 | 
				
			||||||
				throw new RuntimeException(e);
 | 
										return typeToReturn.cast(field.get(instance));
 | 
				
			||||||
			}
 | 
									} catch (IllegalArgumentException | IllegalAccessException e) {
 | 
				
			||||||
		});
 | 
										throw new RuntimeException(e);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Gets all declared variables of the given instance that are children of
 | 
						 * Gets all declared variables of the given instance that are children of {@code Node}.
 | 
				
			||||||
	 * {@code Node}.
 | 
					 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * <b>Important: If you are using a module, you first need to declare <br>
 | 
						 * <b>Important: If you are using a module, you first need to declare <br>
 | 
				
			||||||
	 * "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
 | 
						 * "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param <T>      the type of the instance
 | 
						 * @param <T>      the type of the instance
 | 
				
			||||||
	 * @param instance the instance of a given class whose values are to be
 | 
						 * @param instance the instance of a given class whose values are to be evaluated
 | 
				
			||||||
	 *                 evaluated
 | 
						 * @return all variables of the given object that have the requested type as {@code Stream}
 | 
				
			||||||
	 * @return all variables of the given object that have the requested type as
 | 
					 | 
				
			||||||
	 *         {@code Stream}
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static <T> Stream<Node> getAllDeclaredNodeVariablesAsStream(T instance) {
 | 
						public static <T> Stream<Node> getAllDeclaredNodeVariablesAsStream(T instance) {
 | 
				
			||||||
@@ -62,15 +58,13 @@ public final class ReflectionUtil {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Gets all declared variables of the given instance that are children of
 | 
						 * Gets all declared variables of the given instance that are children of {@code Node}<br>
 | 
				
			||||||
	 * {@code Node}<br>
 | 
					 | 
				
			||||||
	 * <p>
 | 
						 * <p>
 | 
				
			||||||
	 * <b>Important: If you are using a module, you first need to declare <br>
 | 
						 * <b>Important: If you are using a module, you first need to declare <br>
 | 
				
			||||||
	 * "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
 | 
						 * "opens {your_package} to envoy.client.util;" in your module-info.java</b>.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param <T>      the type of the instance
 | 
						 * @param <T>      the type of the instance
 | 
				
			||||||
	 * @param instance the instance of a given class whose values are to be
 | 
						 * @param instance the instance of a given class whose values are to be evaluated
 | 
				
			||||||
	 *                 evaluated
 | 
					 | 
				
			||||||
	 * @return all variables of the given object that have the requested type
 | 
						 * @return all variables of the given object that have the requested type
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,23 @@
 | 
				
			|||||||
package envoy.client.util;
 | 
					package envoy.client.util;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javafx.scene.control.Alert;
 | 
					import javafx.scene.control.Alert;
 | 
				
			||||||
import javafx.scene.control.Alert.AlertType;
 | 
					import javafx.scene.control.Alert.AlertType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.*;
 | 
				
			||||||
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
 | 
					import envoy.event.*;
 | 
				
			||||||
 | 
					import envoy.event.contact.UserOperation;
 | 
				
			||||||
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Context;
 | 
					import envoy.client.data.Context;
 | 
				
			||||||
import envoy.client.event.*;
 | 
					import envoy.client.event.*;
 | 
				
			||||||
import envoy.client.helper.*;
 | 
					import envoy.client.helper.*;
 | 
				
			||||||
import envoy.client.ui.SceneContext.SceneInfo;
 | 
					import envoy.client.ui.SceneContext.SceneInfo;
 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					import envoy.client.ui.controller.ChatScene;
 | 
				
			||||||
import envoy.event.UserStatusChange;
 | 
					 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Contains methods that change something about the currently logged in user.
 | 
					 * Contains methods that change something about the currently logged in user.
 | 
				
			||||||
@@ -23,11 +27,13 @@ import dev.kske.eventbus.EventBus;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public final class UserUtil {
 | 
					public final class UserUtil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Context	context	= Context.getInstance();
 | 
				
			||||||
 | 
						private static final Logger		logger	= EnvoyLog.getLogger(UserUtil.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private UserUtil() {}
 | 
						private UserUtil() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Logs the current user out and reopens
 | 
						 * Logs the current user out and reopens {@link envoy.client.ui.controller.LoginScene}.
 | 
				
			||||||
	 * {@link envoy.client.ui.controller.LoginScene}.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -40,13 +46,13 @@ public final class UserUtil {
 | 
				
			|||||||
			EnvoyLog.getLogger(ShutdownHelper.class).log(Level.INFO, "A logout was requested");
 | 
								EnvoyLog.getLogger(ShutdownHelper.class).log(Level.INFO, "A logout was requested");
 | 
				
			||||||
			EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
								EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
				
			||||||
			EventBus.getInstance().dispatch(new Logout());
 | 
								EventBus.getInstance().dispatch(new Logout());
 | 
				
			||||||
			Context.getInstance().getSceneContext().load(SceneInfo.LOGIN_SCENE);
 | 
								context.getSceneContext().load(SceneInfo.LOGIN_SCENE);
 | 
				
			||||||
 | 
								logger.log(Level.INFO, "A logout occurred.");
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Notifies the application that the status of the currently logged in user has
 | 
						 * Notifies the application that the status of the currently logged in user has changed.
 | 
				
			||||||
	 * changed.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param newStatus the new status
 | 
						 * @param newStatus the new status
 | 
				
			||||||
	 * @since Envoy Client v0.3-beta
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
@@ -54,11 +60,65 @@ public final class UserUtil {
 | 
				
			|||||||
	public static void changeStatus(UserStatus newStatus) {
 | 
						public static void changeStatus(UserStatus newStatus) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Sending the already active status is a valid action
 | 
							// Sending the already active status is a valid action
 | 
				
			||||||
		if (newStatus.equals(Context.getInstance().getLocalDB().getUser().getStatus())) return;
 | 
							if (newStatus.equals(context.getLocalDB().getUser().getStatus()))
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
		else {
 | 
							else {
 | 
				
			||||||
			EventBus.getInstance().dispatch(new OwnStatusChange(newStatus));
 | 
								EventBus.getInstance().dispatch(new OwnStatusChange(newStatus));
 | 
				
			||||||
			if (Context.getInstance().getClient().isOnline())
 | 
								if (context.getClient().isOnline())
 | 
				
			||||||
				Context.getInstance().getClient().send(new UserStatusChange(Context.getInstance().getLocalDB().getUser().getID(), newStatus));
 | 
									context.getClient()
 | 
				
			||||||
 | 
										.send(new UserStatusChange(context.getLocalDB().getUser().getID(), newStatus));
 | 
				
			||||||
 | 
								logger.log(Level.INFO, "A manual status change occurred.");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Removes the given contact.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param block the contact that should be removed
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void disableContact(Contact block) {
 | 
				
			||||||
 | 
							if (!context.getClient().isOnline() || block == null)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								final var alert = new Alert(AlertType.CONFIRMATION);
 | 
				
			||||||
 | 
								alert.setContentText("Are you sure you want to "
 | 
				
			||||||
 | 
									+ (block instanceof User ? "block " : "leave group ") + block.getName() + "?");
 | 
				
			||||||
 | 
								AlertHelper.confirmAction(alert, () -> {
 | 
				
			||||||
 | 
									final var isUser = block instanceof User;
 | 
				
			||||||
 | 
									context.getClient()
 | 
				
			||||||
 | 
										.send(isUser ? new UserOperation((User) block, ElementOperation.REMOVE)
 | 
				
			||||||
 | 
											: new GroupResize(context.getLocalDB().getUser(), (Group) block,
 | 
				
			||||||
 | 
												ElementOperation.REMOVE));
 | 
				
			||||||
 | 
									if (!isUser)
 | 
				
			||||||
 | 
										block.getContacts().remove(context.getLocalDB().getUser());
 | 
				
			||||||
 | 
									EventBus.getInstance().dispatch(new ContactDisabled(block));
 | 
				
			||||||
 | 
									logger.log(Level.INFO, isUser ? "A user was blocked." : "The user left a group.");
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Deletes the given contact with all his messages entirely.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param delete the contact to delete
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.3-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void deleteContact(Contact delete) {
 | 
				
			||||||
 | 
							if (delete == null)
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							else {
 | 
				
			||||||
 | 
								final var alert = new Alert(AlertType.CONFIRMATION);
 | 
				
			||||||
 | 
								alert.setContentText("Are you sure you want to delete " + delete.getName()
 | 
				
			||||||
 | 
									+ " entirely? All messages with this contact will be deleted. This action cannot be undone.");
 | 
				
			||||||
 | 
								AlertHelper.confirmAction(alert, () -> {
 | 
				
			||||||
 | 
									context.getLocalDB().getUsers().remove(delete.getName());
 | 
				
			||||||
 | 
									context.getLocalDB().getChats()
 | 
				
			||||||
 | 
										.removeIf(chat -> chat.getRecipient().equals(delete));
 | 
				
			||||||
 | 
									if (context.getSceneContext().getController() instanceof ChatScene)
 | 
				
			||||||
 | 
										((ChatScene) context.getSceneContext().getController()).resetState();
 | 
				
			||||||
 | 
									logger.log(Level.INFO, "A contact with all his messages was deleted.");
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * This module contains all classes defining the client application of the Envoy
 | 
					 * This module contains all classes defining the client application of the Envoy project.
 | 
				
			||||||
 * project.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
 | 
					fileLevelBarrier=OFF
 | 
				
			||||||
 | 
					consoleLevelBarrier=FINER
 | 
				
			||||||
server=localhost
 | 
					server=localhost
 | 
				
			||||||
port=8080
 | 
					port=8080
 | 
				
			||||||
localDB=localDB
 | 
					 | 
				
			||||||
localDBSaveInterval=2
 | 
					localDBSaveInterval=2
 | 
				
			||||||
consoleLevelBarrier=FINER
 | 
					 | 
				
			||||||
fileLevelBarrier=OFF
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -140,19 +140,24 @@
 | 
				
			|||||||
.tab-pane {
 | 
					.tab-pane {
 | 
				
			||||||
    -fx-tab-max-height: 0.0 ;
 | 
					    -fx-tab-max-height: 0.0 ;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.tab-pane .tab-header-area {
 | 
					.tab-pane .tab-header-area {
 | 
				
			||||||
    visibility: hidden ;
 | 
					    visibility: hidden ;
 | 
				
			||||||
    -fx-padding: -20.0 0.0 0.0 0.0;
 | 
					    -fx-padding: -20.0 0.0 0.0 0.0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.disabled-chat {
 | 
				
			||||||
 | 
						-fx-background-color: #0000FF;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#quick-select-list .scroll-bar:horizontal{
 | 
					#quick-select-list .scroll-bar:horizontal{
 | 
				
			||||||
	-fx-pref-height: 0;
 | 
						-fx-pref-height: 0.0;
 | 
				
			||||||
	-fx-max-height: 0;
 | 
						-fx-max-height: 0.0;
 | 
				
			||||||
	-fx-min-height: 0; 
 | 
						-fx-min-height: 0.0; 
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#quick-select-list .scroll-bar:vertical{
 | 
					#quick-select-list .scroll-bar:vertical{
 | 
				
			||||||
	-fx-pref-width: 0;
 | 
						-fx-pref-width: 0.0;
 | 
				
			||||||
	-fx-max-width: 0;
 | 
						-fx-max-width: 0.0;
 | 
				
			||||||
	-fx-min-width: 0; 
 | 
						-fx-min-width: 0.0; 
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,15 +126,6 @@
 | 
				
			|||||||
										<ListView id="chat-list" fx:id="chatList"
 | 
															<ListView id="chat-list" fx:id="chatList"
 | 
				
			||||||
											focusTraversable="false" onMouseClicked="#chatListClicked"
 | 
																focusTraversable="false" onMouseClicked="#chatListClicked"
 | 
				
			||||||
											prefWidth="316.0" VBox.vgrow="ALWAYS">
 | 
																prefWidth="316.0" VBox.vgrow="ALWAYS">
 | 
				
			||||||
											<contextMenu>
 | 
					 | 
				
			||||||
												<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
 | 
					 | 
				
			||||||
													<items>
 | 
					 | 
				
			||||||
														<MenuItem fx:id="deleteContactMenuItem"
 | 
					 | 
				
			||||||
															mnemonicParsing="false" onAction="#deleteContact"
 | 
					 | 
				
			||||||
															text="Delete" />
 | 
					 | 
				
			||||||
													</items>
 | 
					 | 
				
			||||||
												</ContextMenu>
 | 
					 | 
				
			||||||
											</contextMenu>
 | 
					 | 
				
			||||||
											<padding>
 | 
																<padding>
 | 
				
			||||||
												<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
 | 
																	<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
 | 
				
			||||||
											</padding>
 | 
																</padding>
 | 
				
			||||||
@@ -167,7 +158,7 @@
 | 
				
			|||||||
				<HBox id="transparent-background" fx:id="ownContactControl">
 | 
									<HBox id="transparent-background" fx:id="ownContactControl">
 | 
				
			||||||
					<children>
 | 
										<children>
 | 
				
			||||||
						<Region id="transparent-background" prefWidth="120"
 | 
											<Region id="transparent-background" prefWidth="120"
 | 
				
			||||||
							fx:id="spaceBetweenUserAndSettingsButton" />
 | 
												fx:id="spaceBetweenUserAndSettingsButton" HBox.hgrow="ALWAYS" />
 | 
				
			||||||
						<Button fx:id="settingsButton" mnemonicParsing="false"
 | 
											<Button fx:id="settingsButton" mnemonicParsing="false"
 | 
				
			||||||
							onAction="#settingsButtonClicked" prefHeight="30.0"
 | 
												onAction="#settingsButtonClicked" prefHeight="30.0"
 | 
				
			||||||
							prefWidth="30.0" text="" alignment="CENTER">
 | 
												prefWidth="30.0" text="" alignment="CENTER">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,8 @@ package envoy.data;
 | 
				
			|||||||
import java.io.Serializable;
 | 
					import java.io.Serializable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This interface should be used for any type supposed to be a {@link Message}
 | 
					 * This interface should be used for any type supposed to be a {@link Message} attachment (i.e.
 | 
				
			||||||
 * attachment (i.e. images or sound).
 | 
					 * images or sound).
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,15 +9,13 @@ import java.util.stream.Collectors;
 | 
				
			|||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Manages all application settings that are set during application startup by
 | 
					 * Manages all application settings that are set during application startup by either loading them
 | 
				
			||||||
 * either loading them from the {@link Properties} file (default values)
 | 
					 * from the {@link Properties} file (default values) {@code client.properties} or parsing them from
 | 
				
			||||||
 * {@code client.properties} or parsing them from the command line arguments of
 | 
					 * the command line arguments of the application.
 | 
				
			||||||
 * the application.
 | 
					 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * All items inside the {@code Config} are supposed to either be supplied over
 | 
					 * All items inside the {@code Config} are supposed to either be supplied over default value or over
 | 
				
			||||||
 * default value or over command line argument. Developers that fail to provide
 | 
					 * command line argument. Developers that fail to provide default values will be greeted with an
 | 
				
			||||||
 * default values will be greeted with an error message the next time they try
 | 
					 * error message the next time they try to start Envoy...
 | 
				
			||||||
 * to start Envoy...
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Common v0.1-beta
 | 
					 * @since Envoy Common v0.1-beta
 | 
				
			||||||
@@ -44,15 +42,14 @@ public class Config {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void load(Properties properties) {
 | 
						private void load(Properties properties) {
 | 
				
			||||||
		items.entrySet().stream().filter(e -> properties.containsKey(e.getKey()))
 | 
							items.entrySet().stream().filter(e -> properties.containsKey(e.getKey()))
 | 
				
			||||||
				.forEach(e -> e.getValue().parse(properties.getProperty(e.getKey())));
 | 
								.forEach(e -> e.getValue().parse(properties.getProperty(e.getKey())));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Parses config items from an array of command line arguments.
 | 
						 * Parses config items from an array of command line arguments.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param args the command line arguments to parse
 | 
						 * @param args the command line arguments to parse
 | 
				
			||||||
	 * @throws IllegalStateException if a malformed command line argument has been
 | 
						 * @throws IllegalStateException if a malformed command line argument has been supplied
 | 
				
			||||||
	 *                               supplied
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void load(String[] args) {
 | 
						private void load(String[] args) {
 | 
				
			||||||
@@ -61,7 +58,7 @@ public class Config {
 | 
				
			|||||||
				if (args[i].startsWith("--")) {
 | 
									if (args[i].startsWith("--")) {
 | 
				
			||||||
					if (args[i].length() == 2)
 | 
										if (args[i].length() == 2)
 | 
				
			||||||
						throw new IllegalStateException(
 | 
											throw new IllegalStateException(
 | 
				
			||||||
								"Malformed command line argument at position " + i + ": " + args[i]);
 | 
												"Malformed command line argument at position " + i + ": " + args[i]);
 | 
				
			||||||
					final String commandLong = args[i].substring(2);
 | 
										final String commandLong = args[i].substring(2);
 | 
				
			||||||
					if (item.getCommandLong().equals(commandLong)) {
 | 
										if (item.getCommandLong().equals(commandLong)) {
 | 
				
			||||||
						item.parse(args[++i]);
 | 
											item.parse(args[++i]);
 | 
				
			||||||
@@ -70,7 +67,7 @@ public class Config {
 | 
				
			|||||||
				} else if (args[i].startsWith("-")) {
 | 
									} else if (args[i].startsWith("-")) {
 | 
				
			||||||
					if (args[i].length() == 1)
 | 
										if (args[i].length() == 1)
 | 
				
			||||||
						throw new IllegalStateException(
 | 
											throw new IllegalStateException(
 | 
				
			||||||
								"Malformed command line argument at position " + i + ": " + args[i]);
 | 
												"Malformed command line argument at position " + i + ": " + args[i]);
 | 
				
			||||||
					final String commandShort = args[i].substring(1);
 | 
										final String commandShort = args[i].substring(1);
 | 
				
			||||||
					if (item.getCommandShort().equals(commandShort)) {
 | 
										if (item.getCommandShort().equals(commandShort)) {
 | 
				
			||||||
						item.parse(args[++i]);
 | 
											item.parse(args[++i]);
 | 
				
			||||||
@@ -78,35 +75,36 @@ public class Config {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
				} else
 | 
									} else
 | 
				
			||||||
					throw new IllegalStateException(
 | 
										throw new IllegalStateException(
 | 
				
			||||||
							"Malformed command line argument at position " + i + ": " + args[i]);
 | 
											"Malformed command line argument at position " + i + ": " + args[i]);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Supplies default values from the given .properties file and parses the
 | 
						 * Supplies default values from the given .properties file and parses the configuration from an
 | 
				
			||||||
	 * configuration from an array of command line arguments.
 | 
						 * array of command line arguments.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param declaringClass     the class calling this method
 | 
						 * @param declaringClass     the class calling this method
 | 
				
			||||||
	 * @param propertiesFilePath the path to where the .properties file can be found
 | 
						 * @param propertiesFilePath the path to where the .properties file can be found - will be only
 | 
				
			||||||
	 *                           - will be only the file name if it is located
 | 
						 *                           the file name if it is located directly inside the
 | 
				
			||||||
	 *                           directly inside the {@code src/main/resources}
 | 
						 *                           {@code src/main/resources} folder
 | 
				
			||||||
	 *                           folder
 | 
					 | 
				
			||||||
	 * @param args               the command line arguments to parse
 | 
						 * @param args               the command line arguments to parse
 | 
				
			||||||
	 * @throws IllegalStateException if this method is getting called again or if a
 | 
						 * @throws IllegalStateException if this method is getting called again or if a malformed
 | 
				
			||||||
	 *                               malformed command line argument has been
 | 
						 *                               command line argument has been supplied
 | 
				
			||||||
	 *                               supplied
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void loadAll(Class<?> declaringClass, String propertiesFilePath, String[] args) {
 | 
						public void loadAll(Class<?> declaringClass, String propertiesFilePath, String[] args) {
 | 
				
			||||||
		if (modificationDisabled)
 | 
							if (modificationDisabled)
 | 
				
			||||||
			throw new IllegalStateException("Cannot change config after isInitialized has been called");
 | 
								throw new IllegalStateException(
 | 
				
			||||||
 | 
									"Cannot change config after isInitialized has been called");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Load the defaults from the given .properties file first
 | 
							// Load the defaults from the given .properties file first
 | 
				
			||||||
		final var properties = new Properties();
 | 
							final var properties = new Properties();
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			properties.load(declaringClass.getClassLoader().getResourceAsStream(propertiesFilePath));
 | 
								properties
 | 
				
			||||||
 | 
									.load(declaringClass.getClassLoader().getResourceAsStream(propertiesFilePath));
 | 
				
			||||||
		} catch (final IOException e) {
 | 
							} catch (final IOException e) {
 | 
				
			||||||
			EnvoyLog.getLogger(Config.class).log(Level.SEVERE, "An error occurred when reading in the configuration: ",
 | 
								EnvoyLog.getLogger(Config.class).log(Level.SEVERE,
 | 
				
			||||||
					e);
 | 
									"An error occurred when reading in the configuration: ",
 | 
				
			||||||
 | 
									e);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		load(properties);
 | 
							load(properties);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,13 +120,13 @@ public class Config {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @throws IllegalStateException if a {@link ConfigItem} has not been
 | 
						 * @throws IllegalStateException if a {@link ConfigItem} has not been initialized
 | 
				
			||||||
	 *                               initialized
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void isInitialized() {
 | 
						private void isInitialized() {
 | 
				
			||||||
		String uninitialized = items.values().stream().filter(c -> c.get() == null).map(ConfigItem::getCommandLong).collect(Collectors.joining(", "));
 | 
							String uninitialized = items.values().stream().filter(c -> c.get() == null)
 | 
				
			||||||
		if(!uninitialized.isEmpty())
 | 
								.map(ConfigItem::getCommandLong).collect(Collectors.joining(", "));
 | 
				
			||||||
 | 
							if (!uninitialized.isEmpty())
 | 
				
			||||||
			throw new IllegalStateException("Config items uninitialized: " + uninitialized);
 | 
								throw new IllegalStateException("Config items uninitialized: " + uninitialized);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -148,11 +146,11 @@ public class Config {
 | 
				
			|||||||
	 * @param <T>           the type of the {@link ConfigItem}
 | 
						 * @param <T>           the type of the {@link ConfigItem}
 | 
				
			||||||
	 * @param commandName   the key for this config item as well as its long name
 | 
						 * @param commandName   the key for this config item as well as its long name
 | 
				
			||||||
	 * @param commandShort  the abbreviation of this config item
 | 
						 * @param commandShort  the abbreviation of this config item
 | 
				
			||||||
	 * @param parseFunction the {@code Function<String, T>} that parses the value
 | 
						 * @param parseFunction the {@code Function<String, T>} that parses the value from a string
 | 
				
			||||||
	 *                      from a string
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	protected <T> void put(String commandName, String commandShort, Function<String, T> parseFunction) {
 | 
						protected <T> void put(String commandName, String commandShort,
 | 
				
			||||||
 | 
							Function<String, T> parseFunction) {
 | 
				
			||||||
		items.put(commandName, new ConfigItem<>(commandName, commandShort, parseFunction));
 | 
							items.put(commandName, new ConfigItem<>(commandName, commandShort, parseFunction));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -160,17 +158,13 @@ public class Config {
 | 
				
			|||||||
	 * @return the directory in which all local files are saves
 | 
						 * @return the directory in which all local files are saves
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public File getHomeDirectory() {
 | 
						public File getHomeDirectory() { return (File) items.get("homeDirectory").get(); }
 | 
				
			||||||
		return (File) items.get("homeDirectory").get();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the minimal {@link Level} to log inside the log file
 | 
						 * @return the minimal {@link Level} to log inside the log file
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Level getFileLevelBarrier() {
 | 
						public Level getFileLevelBarrier() { return (Level) items.get("fileLevelBarrier").get(); }
 | 
				
			||||||
		return (Level) items.get("fileLevelBarrier").get();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the minimal {@link Level} to log inside the console
 | 
						 * @return the minimal {@link Level} to log inside the console
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,8 @@ package envoy.data;
 | 
				
			|||||||
import java.util.function.Function;
 | 
					import java.util.function.Function;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Contains a single {@link Config} value as well as the corresponding command
 | 
					 * Contains a single {@link Config} value as well as the corresponding command line arguments and
 | 
				
			||||||
 * line arguments and its default value.
 | 
					 * its default value.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * All {@code ConfigItem}s are automatically mandatory.
 | 
					 * All {@code ConfigItem}s are automatically mandatory.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -24,8 +24,7 @@ public final class ConfigItem<T> {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param commandLong   the long command line argument to set this value
 | 
						 * @param commandLong   the long command line argument to set this value
 | 
				
			||||||
	 * @param commandShort  the short command line argument to set this value
 | 
						 * @param commandShort  the short command line argument to set this value
 | 
				
			||||||
	 * @param parseFunction the {@code Function<String, T>} that parses the value
 | 
						 * @param parseFunction the {@code Function<String, T>} that parses the value from a string
 | 
				
			||||||
	 *                      from a string
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ConfigItem(String commandLong, String commandShort, Function<String, T> parseFunction) {
 | 
						public ConfigItem(String commandLong, String commandShort, Function<String, T> parseFunction) {
 | 
				
			||||||
@@ -40,18 +39,18 @@ public final class ConfigItem<T> {
 | 
				
			|||||||
	 * @param input the string to parse from
 | 
						 * @param input the string to parse from
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void parse(String input) { value = parseFunction.apply(input); }
 | 
						public void parse(String input) {
 | 
				
			||||||
 | 
							value = parseFunction.apply(input);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return The long command line argument to set the value of this
 | 
						 * @return The long command line argument to set the value of this {@link ConfigItem}
 | 
				
			||||||
	 *         {@link ConfigItem}
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public String getCommandLong() { return commandLong; }
 | 
						public String getCommandLong() { return commandLong; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return The short command line argument to set the value of this
 | 
						 * @return The short command line argument to set the value of this {@link ConfigItem}
 | 
				
			||||||
	 *         {@link ConfigItem}
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public String getCommandShort() { return commandShort; }
 | 
						public String getCommandShort() { return commandShort; }
 | 
				
			||||||
@@ -60,7 +59,9 @@ public final class ConfigItem<T> {
 | 
				
			|||||||
	 * @return the value of this {@link ConfigItem}
 | 
						 * @return the value of this {@link ConfigItem}
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public T get() { return value; }
 | 
						public T get() {
 | 
				
			||||||
 | 
							return value;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param value the value to set
 | 
						 * @param value the value to set
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,19 +57,23 @@ public abstract class Contact implements Serializable {
 | 
				
			|||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public final int hashCode() { return Objects.hash(id); }
 | 
						public final int hashCode() {
 | 
				
			||||||
 | 
							return Objects.hash(id);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Tests equality to another object. If that object is a contact as well,
 | 
						 * Tests equality to another object. If that object is a contact as well, equality is determined
 | 
				
			||||||
	 * equality is determined by the ID.
 | 
						 * by the ID.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param obj the object to test for equality to this contact
 | 
						 * @param obj the object to test for equality to this contact
 | 
				
			||||||
	 * @return {code true} if both objects are contacts and have identical IDs
 | 
						 * @return {code true} if both objects are contacts and have identical IDs
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public final boolean equals(Object obj) {
 | 
						public final boolean equals(Object obj) {
 | 
				
			||||||
		if (this == obj) return true;
 | 
							if (this == obj)
 | 
				
			||||||
		if (!(obj instanceof Contact)) return false;
 | 
								return true;
 | 
				
			||||||
 | 
							if (!(obj instanceof Contact))
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
		return id == ((Contact) obj).id;
 | 
							return id == ((Contact) obj).id;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,9 @@ public final class Group extends Contact {
 | 
				
			|||||||
	 * @param name the name of this group
 | 
						 * @param name the name of this group
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Group(long id, String name) { this(id, name, new HashSet<User>()); }
 | 
						public Group(long id, String name) {
 | 
				
			||||||
 | 
							this(id, name, new HashSet<User>());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of a {@link Group}.
 | 
						 * Creates an instance of a {@link Group}.
 | 
				
			||||||
@@ -28,10 +30,14 @@ public final class Group extends Contact {
 | 
				
			|||||||
	 * @param members all members that should be preinitialized
 | 
						 * @param members all members that should be preinitialized
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Group(long id, String name, Set<User> members) { super(id, name, members); }
 | 
						public Group(long id, String name, Set<User> members) {
 | 
				
			||||||
 | 
							super(id, name, members);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("Group[id=%d,name=%s,%d member(s)]", id, name, contacts.size()); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("Group[id=%d,name=%s,%d member(s)]", id, name, contacts.size());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void readObject(ObjectInputStream inputStream) throws Exception {
 | 
						private void readObject(ObjectInputStream inputStream) throws Exception {
 | 
				
			||||||
		inputStream.defaultReadObject();
 | 
							inputStream.defaultReadObject();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,11 +14,9 @@ public final class GroupMessage extends Message {
 | 
				
			|||||||
	private static final long serialVersionUID = 1L;
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a {@link GroupMessage} with values for all of its properties. The
 | 
						 * Initializes a {@link GroupMessage} with values for all of its properties. The use of this
 | 
				
			||||||
	 * use
 | 
						 * constructor is only intended for the {@link MessageBuilder} class, as this class provides
 | 
				
			||||||
	 * of this constructor is only intended for the {@link MessageBuilder} class, as
 | 
						 * {@code null} checks and default values for all properties.
 | 
				
			||||||
	 * this class provides {@code null} checks and default values for all
 | 
					 | 
				
			||||||
	 * properties.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param id             unique ID
 | 
						 * @param id             unique ID
 | 
				
			||||||
	 * @param senderID       the ID of the user who sends the message
 | 
						 * @param senderID       the ID of the user who sends the message
 | 
				
			||||||
@@ -28,16 +26,18 @@ public final class GroupMessage extends Message {
 | 
				
			|||||||
	 * @param readDate       the read date of the message
 | 
						 * @param readDate       the read date of the message
 | 
				
			||||||
	 * @param text           the text content of the message
 | 
						 * @param text           the text content of the message
 | 
				
			||||||
	 * @param attachment     the attachment of the message, if present
 | 
						 * @param attachment     the attachment of the message, if present
 | 
				
			||||||
	 * @param status         the current {@link Message.MessageStatus} of the
 | 
						 * @param status         the current {@link Message.MessageStatus} of the message
 | 
				
			||||||
	 *                       message
 | 
					 | 
				
			||||||
	 * @param forwarded      whether this message was forwarded
 | 
						 * @param forwarded      whether this message was forwarded
 | 
				
			||||||
	 * @param memberStatuses a map of all members and their status according to this
 | 
						 * @param memberStatuses a map of all members and their status according to this
 | 
				
			||||||
	 *                       {@link GroupMessage}
 | 
						 *                       {@link GroupMessage}
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	GroupMessage(long id, long senderID, long groupID, Instant creationDate, Instant receivedDate, Instant readDate, String text,
 | 
						GroupMessage(long id, long senderID, long groupID, Instant creationDate, Instant receivedDate,
 | 
				
			||||||
			Attachment attachment, MessageStatus status, boolean forwarded, Map<Long, MessageStatus> memberStatuses) {
 | 
							Instant readDate, String text,
 | 
				
			||||||
		super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status, forwarded);
 | 
							Attachment attachment, MessageStatus status, boolean forwarded,
 | 
				
			||||||
 | 
							Map<Long, MessageStatus> memberStatuses) {
 | 
				
			||||||
 | 
							super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status,
 | 
				
			||||||
 | 
								forwarded);
 | 
				
			||||||
		this.memberStatuses = memberStatuses;
 | 
							this.memberStatuses = memberStatuses;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,20 +30,25 @@ public final class IDGenerator implements IEvent, Serializable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("IDGenerator[current=%d,end=%d]", current, end); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("IDGenerator[current=%d,end=%d]", current, end);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return {@code true} if there are unused IDs remaining
 | 
						 * @return {@code true} if there are unused IDs remaining
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean hasNext() { return current < end; }
 | 
						public boolean hasNext() {
 | 
				
			||||||
 | 
							return current < end;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the next ID
 | 
						 * @return the next ID
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public long next() {
 | 
						public long next() {
 | 
				
			||||||
		if (!hasNext()) throw new IllegalStateException("All IDs have been used");
 | 
							if (!hasNext())
 | 
				
			||||||
 | 
								throw new IllegalStateException("All IDs have been used");
 | 
				
			||||||
		return current++;
 | 
							return current++;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,11 +4,9 @@ import java.io.Serializable;
 | 
				
			|||||||
import java.time.Instant;
 | 
					import java.time.Instant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Contains a {@link User}'s login / registration information as well as the
 | 
					 * Contains a {@link User}'s login / registration information as well as the client version.
 | 
				
			||||||
 * client version.
 | 
					 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * If the authentication is performed with a token, the token is stored instead
 | 
					 * If the authentication is performed with a token, the token is stored instead of the password.
 | 
				
			||||||
 * of the password.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Common v0.2-alpha
 | 
					 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
@@ -21,8 +19,9 @@ public final class LoginCredentials implements Serializable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = 4;
 | 
						private static final long serialVersionUID = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private LoginCredentials(String identifier, String password, boolean registration, boolean token, boolean requestToken, String clientVersion,
 | 
						private LoginCredentials(String identifier, String password, boolean registration,
 | 
				
			||||||
			Instant lastSync) {
 | 
							boolean token, boolean requestToken, String clientVersion,
 | 
				
			||||||
 | 
							Instant lastSync) {
 | 
				
			||||||
		this.identifier		= identifier;
 | 
							this.identifier		= identifier;
 | 
				
			||||||
		this.password		= password;
 | 
							this.password		= password;
 | 
				
			||||||
		this.registration	= registration;
 | 
							this.registration	= registration;
 | 
				
			||||||
@@ -43,8 +42,10 @@ public final class LoginCredentials implements Serializable {
 | 
				
			|||||||
	 * @return the created login credentials
 | 
						 * @return the created login credentials
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static LoginCredentials login(String identifier, String password, boolean requestToken, String clientVersion, Instant lastSync) {
 | 
						public static LoginCredentials login(String identifier, String password, boolean requestToken,
 | 
				
			||||||
		return new LoginCredentials(identifier, password, false, false, requestToken, clientVersion, lastSync);
 | 
							String clientVersion, Instant lastSync) {
 | 
				
			||||||
 | 
							return new LoginCredentials(identifier, password, false, false, requestToken, clientVersion,
 | 
				
			||||||
 | 
								lastSync);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -57,7 +58,8 @@ public final class LoginCredentials implements Serializable {
 | 
				
			|||||||
	 * @return the created login credentials
 | 
						 * @return the created login credentials
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static LoginCredentials loginWithToken(String identifier, String token, String clientVersion, Instant lastSync) {
 | 
						public static LoginCredentials loginWithToken(String identifier, String token,
 | 
				
			||||||
 | 
							String clientVersion, Instant lastSync) {
 | 
				
			||||||
		return new LoginCredentials(identifier, token, false, true, false, clientVersion, lastSync);
 | 
							return new LoginCredentials(identifier, token, false, true, false, clientVersion, lastSync);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,19 +74,22 @@ public final class LoginCredentials implements Serializable {
 | 
				
			|||||||
	 * @return the created login credentials
 | 
						 * @return the created login credentials
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static LoginCredentials registration(String identifier, String password, boolean requestToken, String clientVersion, Instant lastSync) {
 | 
						public static LoginCredentials registration(String identifier, String password,
 | 
				
			||||||
		return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion, lastSync);
 | 
							boolean requestToken, String clientVersion, Instant lastSync) {
 | 
				
			||||||
 | 
							return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion,
 | 
				
			||||||
 | 
								lastSync);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() {
 | 
						public String toString() {
 | 
				
			||||||
		return String.format("LoginCredentials[identifier=%s,registration=%b,token=%b,requestToken=%b,clientVersion=%s,lastSync=%s]",
 | 
							return String.format(
 | 
				
			||||||
				identifier,
 | 
								"LoginCredentials[identifier=%s,registration=%b,token=%b,requestToken=%b,clientVersion=%s,lastSync=%s]",
 | 
				
			||||||
				registration,
 | 
								identifier,
 | 
				
			||||||
				token,
 | 
								registration,
 | 
				
			||||||
				requestToken,
 | 
								token,
 | 
				
			||||||
				clientVersion,
 | 
								requestToken,
 | 
				
			||||||
				lastSync);
 | 
								clientVersion,
 | 
				
			||||||
 | 
								lastSync);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -100,24 +105,27 @@ public final class LoginCredentials implements Serializable {
 | 
				
			|||||||
	public String getPassword() { return password; }
 | 
						public String getPassword() { return password; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return {@code true} if these credentials are used for user registration
 | 
						 * @return {@code true} if these credentials are used for user registration instead of user
 | 
				
			||||||
	 *         instead of user login
 | 
						 *         login
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean isRegistration() { return registration; }
 | 
						public boolean isRegistration() { return registration; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return {@code true} if these credentials use an authentication token instead
 | 
						 * @return {@code true} if these credentials use an authentication token instead of a password
 | 
				
			||||||
	 *         of a password
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean usesToken() { return token; }
 | 
						public boolean usesToken() {
 | 
				
			||||||
 | 
							return token;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return {@code true} if the server should generate a new authentication token
 | 
						 * @return {@code true} if the server should generate a new authentication token
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean requestToken() { return requestToken; }
 | 
						public boolean requestToken() {
 | 
				
			||||||
 | 
							return requestToken;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the version of the client sending these credentials
 | 
						 * @return the version of the client sending these credentials
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,8 @@ import java.time.Instant;
 | 
				
			|||||||
import dev.kske.eventbus.IEvent;
 | 
					import dev.kske.eventbus.IEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Represents a unique message with a unique, numeric ID. Further metadata
 | 
					 * Represents a unique message with a unique, numeric ID. Further metadata includes the sender and
 | 
				
			||||||
 * includes the sender and recipient {@link User}s, as well as the creation
 | 
					 * recipient {@link User}s, as well as the creation date and the current {@link MessageStatus}.<br>
 | 
				
			||||||
 * date and the current {@link MessageStatus}.<br>
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
@@ -56,10 +55,9 @@ public class Message implements Serializable, IEvent {
 | 
				
			|||||||
	private static final long serialVersionUID = 2L;
 | 
						private static final long serialVersionUID = 2L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a {@link Message} with values for all of its properties. The use
 | 
						 * Initializes a {@link Message} with values for all of its properties. The use of this
 | 
				
			||||||
	 * of this constructor is only intended for the {@link MessageBuilder} class, as
 | 
						 * constructor is only intended for the {@link MessageBuilder} class, as this class provides
 | 
				
			||||||
	 * this class provides {@code null} checks and default values for all
 | 
						 * {@code null} checks and default values for all properties.
 | 
				
			||||||
	 * properties.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param id           unique ID
 | 
						 * @param id           unique ID
 | 
				
			||||||
	 * @param senderID     the ID of the user who sends the message
 | 
						 * @param senderID     the ID of the user who sends the message
 | 
				
			||||||
@@ -73,8 +71,9 @@ public class Message implements Serializable, IEvent {
 | 
				
			|||||||
	 * @param forwarded    whether this message was forwarded
 | 
						 * @param forwarded    whether this message was forwarded
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	Message(long id, long senderID, long recipientID, Instant creationDate, Instant receivedDate, Instant readDate, String text,
 | 
						Message(long id, long senderID, long recipientID, Instant creationDate, Instant receivedDate,
 | 
				
			||||||
			Attachment attachment, MessageStatus status, boolean forwarded) {
 | 
							Instant readDate, String text,
 | 
				
			||||||
 | 
							Attachment attachment, MessageStatus status, boolean forwarded) {
 | 
				
			||||||
		this.id				= id;
 | 
							this.id				= id;
 | 
				
			||||||
		this.senderID		= senderID;
 | 
							this.senderID		= senderID;
 | 
				
			||||||
		this.recipientID	= recipientID;
 | 
							this.recipientID	= recipientID;
 | 
				
			||||||
@@ -101,21 +100,23 @@ public class Message implements Serializable, IEvent {
 | 
				
			|||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void nextStatus() {
 | 
						public void nextStatus() {
 | 
				
			||||||
		if (status == MessageStatus.READ) throw new IllegalStateException("Message status READ is already reached");
 | 
							if (status == MessageStatus.READ)
 | 
				
			||||||
 | 
								throw new IllegalStateException("Message status READ is already reached");
 | 
				
			||||||
		status = MessageStatus.values()[status.ordinal() + 1];
 | 
							status = MessageStatus.values()[status.ordinal() + 1];
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() {
 | 
						public String toString() {
 | 
				
			||||||
		return String.format("Message[id=%d,sender=%s,recipient=%s,date=%s,status=%s,text=%s,forwarded=%b,hasAttachment=%b]",
 | 
							return String.format(
 | 
				
			||||||
				id,
 | 
								"Message[id=%d,sender=%s,recipient=%s,date=%s,status=%s,text=%s,forwarded=%b,hasAttachment=%b]",
 | 
				
			||||||
				senderID,
 | 
								id,
 | 
				
			||||||
				recipientID,
 | 
								senderID,
 | 
				
			||||||
				creationDate,
 | 
								recipientID,
 | 
				
			||||||
				status,
 | 
								creationDate,
 | 
				
			||||||
				text,
 | 
								status,
 | 
				
			||||||
				forwarded,
 | 
								text,
 | 
				
			||||||
				attachment != null);
 | 
								forwarded,
 | 
				
			||||||
 | 
								attachment != null);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -149,8 +150,7 @@ public class Message implements Serializable, IEvent {
 | 
				
			|||||||
	public Instant getReceivedDate() { return receivedDate; }
 | 
						public Instant getReceivedDate() { return receivedDate; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param receivedDate the date at which the message has been received by the
 | 
						 * @param receivedDate the date at which the message has been received by the sender
 | 
				
			||||||
	 *                     sender
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setReceivedDate(Instant receivedDate) { this.receivedDate = receivedDate; }
 | 
						public void setReceivedDate(Instant receivedDate) { this.receivedDate = receivedDate; }
 | 
				
			||||||
@@ -183,7 +183,9 @@ public class Message implements Serializable, IEvent {
 | 
				
			|||||||
	 * @return {@code true} if an attachment is present
 | 
						 * @return {@code true} if an attachment is present
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean hasAttachment() { return attachment != null; }
 | 
						public boolean hasAttachment() {
 | 
				
			||||||
 | 
							return attachment != null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the current status of this message
 | 
						 * @return the current status of this message
 | 
				
			||||||
@@ -196,7 +198,8 @@ public class Message implements Serializable, IEvent {
 | 
				
			|||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setStatus(MessageStatus status) {
 | 
						public void setStatus(MessageStatus status) {
 | 
				
			||||||
		if (status.ordinal() < this.status.ordinal()) throw new IllegalStateException("This message is moving backwards in time");
 | 
							if (status.ordinal() < this.status.ordinal())
 | 
				
			||||||
 | 
								throw new IllegalStateException("This message is moving backwards in time");
 | 
				
			||||||
		this.status = status;
 | 
							this.status = status;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,20 +25,21 @@ public final class MessageBuilder {
 | 
				
			|||||||
	private boolean					forwarded;
 | 
						private boolean					forwarded;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of {@link MessageBuilder} with all mandatory values
 | 
						 * Creates an instance of {@link MessageBuilder} with all mandatory values without defaults for
 | 
				
			||||||
	 * without defaults for the {@link Message} class.
 | 
						 * the {@link Message} class.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param senderID    the ID of the user who sends the {@link Message}
 | 
						 * @param senderID    the ID of the user who sends the {@link Message}
 | 
				
			||||||
	 * @param recipientID the ID of the user who receives the {@link Message}
 | 
						 * @param recipientID the ID of the user who receives the {@link Message}
 | 
				
			||||||
	 * @param idGenerator the ID generator used to generate a unique {@link Message}
 | 
						 * @param idGenerator the ID generator used to generate a unique {@link Message} id
 | 
				
			||||||
	 *                    id
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public MessageBuilder(long senderID, long recipientID, IDGenerator idGenerator) { this(senderID, recipientID, idGenerator.next()); }
 | 
						public MessageBuilder(long senderID, long recipientID, IDGenerator idGenerator) {
 | 
				
			||||||
 | 
							this(senderID, recipientID, idGenerator.next());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of {@link MessageBuilder} with all mandatory values
 | 
						 * Creates an instance of {@link MessageBuilder} with all mandatory values without defaults for
 | 
				
			||||||
	 * without defaults for the {@link Message} class.
 | 
						 * the {@link Message} class.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param senderID    the ID of the user who sends the {@link Message}
 | 
						 * @param senderID    the ID of the user who sends the {@link Message}
 | 
				
			||||||
	 * @param recipientID the ID of the user who receives the {@link Message}
 | 
						 * @param recipientID the ID of the user who receives the {@link Message}
 | 
				
			||||||
@@ -52,14 +53,12 @@ public final class MessageBuilder {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This constructor transforms a given {@link Message} into a new message for a
 | 
						 * This constructor transforms a given {@link Message} into a new message for a new receiver.
 | 
				
			||||||
	 * new receiver.
 | 
					 | 
				
			||||||
	 * This makes it especially useful in the case of forwarding messages.
 | 
						 * This makes it especially useful in the case of forwarding messages.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param msg         the message to copy
 | 
						 * @param msg         the message to copy
 | 
				
			||||||
	 * @param recipientID the ID of the user who receives the {@link Message}
 | 
						 * @param recipientID the ID of the user who receives the {@link Message}
 | 
				
			||||||
	 * @param iDGenerator the ID generator used to generate a unique {@link Message}
 | 
						 * @param iDGenerator the ID generator used to generate a unique {@link Message} id
 | 
				
			||||||
	 *                    id
 | 
					 | 
				
			||||||
	 * @since Envoy v0.1-beta
 | 
						 * @since Envoy v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public MessageBuilder(Message msg, long recipientID, IDGenerator iDGenerator) {
 | 
						public MessageBuilder(Message msg, long recipientID, IDGenerator iDGenerator) {
 | 
				
			||||||
@@ -72,79 +71,69 @@ public final class MessageBuilder {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of {@link Message} with the previously supplied values.
 | 
						 * Creates an instance of {@link Message} with the previously supplied values. If a mandatory
 | 
				
			||||||
	 * If a mandatory value is not set, a default value will be used instead:<br>
 | 
						 * value is not set, a default value will be used instead:<br>
 | 
				
			||||||
	 * <br>
 | 
						 * <br>
 | 
				
			||||||
	 * {@code date}
 | 
						 * {@code date} {@code Instant.now()} and {@code null} for {@code receivedDate} and
 | 
				
			||||||
	 * {@code Instant.now()} and {@code null} for {@code receivedDate} and
 | 
						 * {@code readDate} <br>
 | 
				
			||||||
	 * {@code readDate}
 | 
						 * {@code text} {@code ""} <br>
 | 
				
			||||||
	 * <br>
 | 
						 * {@code status} {@code MessageStatus.WAITING}
 | 
				
			||||||
	 * {@code text}
 | 
					 | 
				
			||||||
	 * {@code ""}
 | 
					 | 
				
			||||||
	 * <br>
 | 
					 | 
				
			||||||
	 * {@code status}
 | 
					 | 
				
			||||||
	 * {@code MessageStatus.WAITING}
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @return a new instance of {@link Message}
 | 
						 * @return a new instance of {@link Message}
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Message build() {
 | 
						public Message build() {
 | 
				
			||||||
		supplyDefaults();
 | 
							supplyDefaults();
 | 
				
			||||||
		return new Message(id, senderID, recipientID, creationDate, receivedDate, readDate, text, attachment, status, forwarded);
 | 
							return new Message(id, senderID, recipientID, creationDate, receivedDate, readDate, text,
 | 
				
			||||||
 | 
								attachment, status, forwarded);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of {@link GroupMessage} with the previously supplied
 | 
						 * Creates an instance of {@link GroupMessage} with the previously supplied values. <br>
 | 
				
			||||||
	 * values. <br>
 | 
					 | 
				
			||||||
	 * <b> Sets all member statuses to {@link MessageStatus#WAITING}.</b><br>
 | 
						 * <b> Sets all member statuses to {@link MessageStatus#WAITING}.</b><br>
 | 
				
			||||||
	 * If a mandatory value is not set, a default value will be used
 | 
						 * If a mandatory value is not set, a default value will be used instead:<br>
 | 
				
			||||||
	 * instead:<br>
 | 
					 | 
				
			||||||
	 * <br>
 | 
					 | 
				
			||||||
	 * {@code time stamp}
 | 
					 | 
				
			||||||
	 * {@code Instant.now()}
 | 
					 | 
				
			||||||
	 * <br>
 | 
					 | 
				
			||||||
	 * {@code text}
 | 
					 | 
				
			||||||
	 * {@code ""}
 | 
					 | 
				
			||||||
	 * <br>
 | 
						 * <br>
 | 
				
			||||||
 | 
						 * {@code time stamp} {@code Instant.now()} <br>
 | 
				
			||||||
 | 
						 * {@code text} {@code ""} <br>
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param group the {@link Group} that is used to fill the map of member
 | 
						 * @param group the {@link Group} that is used to fill the map of member statuses
 | 
				
			||||||
	 *              statuses
 | 
					 | 
				
			||||||
	 * @return a new instance of {@link GroupMessage}
 | 
						 * @return a new instance of {@link GroupMessage}
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GroupMessage buildGroupMessage(Group group) {
 | 
						public GroupMessage buildGroupMessage(Group group) {
 | 
				
			||||||
		final var memberStatuses = new HashMap<Long, Message.MessageStatus>();
 | 
							final var memberStatuses = new HashMap<Long, Message.MessageStatus>();
 | 
				
			||||||
		group.getContacts().forEach(user -> memberStatuses.put(user.getID(), MessageStatus.WAITING));
 | 
							group.getContacts()
 | 
				
			||||||
 | 
								.forEach(user -> memberStatuses.put(user.getID(), MessageStatus.WAITING));
 | 
				
			||||||
		return buildGroupMessage(group, memberStatuses);
 | 
							return buildGroupMessage(group, memberStatuses);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of {@link GroupMessage} with the previously supplied
 | 
						 * Creates an instance of {@link GroupMessage} with the previously supplied values. If a
 | 
				
			||||||
	 * values. If a mandatory value is not set, a default value will be used
 | 
						 * mandatory value is not set, a default value will be used instead:<br>
 | 
				
			||||||
	 * instead:<br>
 | 
					 | 
				
			||||||
	 * <br>
 | 
						 * <br>
 | 
				
			||||||
	 * {@code time stamp}
 | 
						 * {@code time stamp} {@code Instant.now()} <br>
 | 
				
			||||||
	 * {@code Instant.now()}
 | 
						 * {@code text} {@code ""}
 | 
				
			||||||
	 * <br>
 | 
					 | 
				
			||||||
	 * {@code text}
 | 
					 | 
				
			||||||
	 * {@code ""}
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param group          the {@link Group} that is used to fill the map of
 | 
						 * @param group          the {@link Group} that is used to fill the map of member statuses
 | 
				
			||||||
	 *                       member statuses
 | 
					 | 
				
			||||||
	 * @param memberStatuses the map of all current statuses
 | 
						 * @param memberStatuses the map of all current statuses
 | 
				
			||||||
	 * @return a new instance of {@link GroupMessage}
 | 
						 * @return a new instance of {@link GroupMessage}
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GroupMessage buildGroupMessage(Group group, Map<Long, MessageStatus> memberStatuses) {
 | 
						public GroupMessage buildGroupMessage(Group group, Map<Long, MessageStatus> memberStatuses) {
 | 
				
			||||||
		if (group == null || memberStatuses == null) throw new NullPointerException();
 | 
							if (group == null || memberStatuses == null)
 | 
				
			||||||
 | 
								throw new NullPointerException();
 | 
				
			||||||
		supplyDefaults();
 | 
							supplyDefaults();
 | 
				
			||||||
		return new GroupMessage(id, senderID, recipientID, creationDate, receivedDate, readDate, text, attachment, status, forwarded, memberStatuses);
 | 
							return new GroupMessage(id, senderID, recipientID, creationDate, receivedDate, readDate,
 | 
				
			||||||
 | 
								text, attachment, status, forwarded, memberStatuses);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void supplyDefaults() {
 | 
						private void supplyDefaults() {
 | 
				
			||||||
		if (creationDate == null) creationDate = Instant.now();
 | 
							if (creationDate == null)
 | 
				
			||||||
		if (text == null) text = "";
 | 
								creationDate = Instant.now();
 | 
				
			||||||
		if (status == null) status = MessageStatus.WAITING;
 | 
							if (text == null)
 | 
				
			||||||
 | 
								text = "";
 | 
				
			||||||
 | 
							if (status == null)
 | 
				
			||||||
 | 
								status = MessageStatus.WAITING;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -188,8 +177,7 @@ public final class MessageBuilder {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param attachment the {@link Attachment} of the {@link Message} to
 | 
						 * @param attachment the {@link Attachment} of the {@link Message} to create
 | 
				
			||||||
	 *                   create
 | 
					 | 
				
			||||||
	 * @return this {@link MessageBuilder}
 | 
						 * @return this {@link MessageBuilder}
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,7 @@ import java.io.*;
 | 
				
			|||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Represents a unique user with a unique, numeric ID, a name and a current
 | 
					 * Represents a unique user with a unique, numeric ID, a name and a current {@link UserStatus}.<br>
 | 
				
			||||||
 * {@link UserStatus}.<br>
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Common v0.2-alpha
 | 
					 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
@@ -34,8 +33,7 @@ public final class User extends Contact {
 | 
				
			|||||||
		ONLINE,
 | 
							ONLINE,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/**
 | 
							/**
 | 
				
			||||||
		 * select this, if a user is online but unavailable at the moment (sudden
 | 
							 * select this, if a user is online but unavailable at the moment (sudden interruption)
 | 
				
			||||||
		 * interruption)
 | 
					 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		AWAY,
 | 
							AWAY,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,8 +50,7 @@ public final class User extends Contact {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a {@link User}. <br>
 | 
						 * Initializes a {@link User}. <br>
 | 
				
			||||||
	 * The {@link UserStatus} is set to {@link UserStatus#ONLINE}.
 | 
						 * The {@link UserStatus} is set to {@link UserStatus#ONLINE}. No contacts are initialized.
 | 
				
			||||||
	 * No contacts are initialized.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param id   unique ID
 | 
						 * @param id   unique ID
 | 
				
			||||||
	 * @param name user name
 | 
						 * @param name user name
 | 
				
			||||||
@@ -94,7 +91,8 @@ public final class User extends Contact {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() {
 | 
						public String toString() {
 | 
				
			||||||
		return String.format("User[id=%d,name=%s,status=%s", id, name, status) + (contacts.isEmpty() ? "]" : "," + contacts.size() + " contact(s)]");
 | 
							return String.format("User[id=%d,name=%s,status=%s", id, name, status)
 | 
				
			||||||
 | 
								+ (contacts.isEmpty() ? "]" : "," + contacts.size() + " contact(s)]");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -119,15 +117,18 @@ public final class User extends Contact {
 | 
				
			|||||||
	private void writeObject(ObjectOutputStream outputStream) throws Exception {
 | 
						private void writeObject(ObjectOutputStream outputStream) throws Exception {
 | 
				
			||||||
		outputStream.defaultWriteObject();
 | 
							outputStream.defaultWriteObject();
 | 
				
			||||||
		if (serializeContacts) {
 | 
							if (serializeContacts) {
 | 
				
			||||||
			getContacts().stream().filter(User.class::isInstance).map(User.class::cast).forEach(user -> user.serializeContacts = false);
 | 
								getContacts().stream().filter(User.class::isInstance).map(User.class::cast)
 | 
				
			||||||
 | 
									.forEach(user -> user.serializeContacts = false);
 | 
				
			||||||
			outputStream.writeObject(getContacts());
 | 
								outputStream.writeObject(getContacts());
 | 
				
			||||||
		} else outputStream.writeObject(new HashSet<>());
 | 
							} else
 | 
				
			||||||
 | 
								outputStream.writeObject(new HashSet<>());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param serializeContacts whether the contacts of this {@link User} should be
 | 
						 * @param serializeContacts whether the contacts of this {@link User} should be serialized
 | 
				
			||||||
	 *                          serialized
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void serializeContacts(boolean serializeContacts) { this.serializeContacts = serializeContacts; }
 | 
						public void serializeContacts(boolean serializeContacts) {
 | 
				
			||||||
 | 
							this.serializeContacts = serializeContacts;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * This package contains all data objects that are used both by Envoy Client and
 | 
					 * This package contains all data objects that are used both by Envoy Client and by Envoy Server.
 | 
				
			||||||
 * by Envoy Server.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,7 @@ package envoy.event;
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * This enum declares all modification possibilities for a given container.
 | 
					 * This enum declares all modification possibilities for a given container.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * These can be: {@link ElementOperation#ADD} or
 | 
					 * These can be: {@link ElementOperation#ADD} or {@link ElementOperation#REMOVE}.
 | 
				
			||||||
 * {@link ElementOperation#REMOVE}.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Common v0.1-beta
 | 
					 * @since Envoy Common v0.1-beta
 | 
				
			||||||
@@ -12,14 +11,12 @@ package envoy.event;
 | 
				
			|||||||
public enum ElementOperation {
 | 
					public enum ElementOperation {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Select this element, if the given element should be added to the given
 | 
						 * Select this element, if the given element should be added to the given container.
 | 
				
			||||||
	 * container.
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	ADD,
 | 
						ADD,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Select this element, if the given element should be removed from the given
 | 
						 * Select this element, if the given element should be removed from the given container.
 | 
				
			||||||
	 * container.
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	REMOVE
 | 
						REMOVE
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,9 +5,9 @@ import java.io.Serializable;
 | 
				
			|||||||
import dev.kske.eventbus.IEvent;
 | 
					import dev.kske.eventbus.IEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This class serves as a convenience base class for all events. It implements
 | 
					 * This class serves as a convenience base class for all events. It implements the {@link IEvent}
 | 
				
			||||||
 * the {@link IEvent} interface and provides a generic value. For events without
 | 
					 * interface and provides a generic value. For events without a value there also is
 | 
				
			||||||
 * a value there also is {@link envoy.event.Event.Valueless}.
 | 
					 * {@link envoy.event.Event.Valueless}.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @param <T> the type of the Event
 | 
					 * @param <T> the type of the Event
 | 
				
			||||||
@@ -19,15 +19,21 @@ public abstract class Event<T> implements IEvent, Serializable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = 0L;
 | 
						private static final long serialVersionUID = 0L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected Event(T value) { this.value = value; }
 | 
						protected Event(T value) {
 | 
				
			||||||
 | 
							this.value = value;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the data associated with this event
 | 
						 * @return the data associated with this event
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public T get() { return value; }
 | 
						public T get() {
 | 
				
			||||||
 | 
							return value;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("%s[value=%s]", this.getClass().getSimpleName(), value); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("%s[value=%s]", this.getClass().getSimpleName(), value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Serves as a super class for events that do not carry a value.
 | 
						 * Serves as a super class for events that do not carry a value.
 | 
				
			||||||
@@ -39,9 +45,13 @@ public abstract class Event<T> implements IEvent, Serializable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		private static final long serialVersionUID = 0L;
 | 
							private static final long serialVersionUID = 0L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		protected Valueless() { super(null); }
 | 
							protected Valueless() {
 | 
				
			||||||
 | 
								super(null);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@Override
 | 
							@Override
 | 
				
			||||||
		public String toString() { return this.getClass().getSimpleName(); }
 | 
							public String toString() {
 | 
				
			||||||
 | 
								return this.getClass().getSimpleName();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,20 +17,19 @@ public final class GroupCreation extends Event<String> {
 | 
				
			|||||||
	private static final long serialVersionUID = 0L;
 | 
						private static final long serialVersionUID = 0L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param value            the name of this group at creation time
 | 
						 * @param name             the name of this group at creation time
 | 
				
			||||||
	 * @param initialMemberIDs the IDs of all {@link User}s that should be group
 | 
						 * @param initialMemberIDs the IDs of all {@link User}s that should be group members from the
 | 
				
			||||||
	 *                         members from the beginning on (excluding the creator
 | 
						 *                         beginning on (excluding the creator of this group)
 | 
				
			||||||
	 *                         of this group)
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GroupCreation(String value, Set<Long> initialMemberIDs) {
 | 
						public GroupCreation(String name, Set<Long> initialMemberIDs) {
 | 
				
			||||||
		super(value);
 | 
							super(name);
 | 
				
			||||||
		this.initialMemberIDs = initialMemberIDs != null ? initialMemberIDs : new HashSet<>();
 | 
							this.initialMemberIDs = initialMemberIDs != null ? initialMemberIDs : new HashSet<>();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the IDs of all {@link User}s that are members from the beginning
 | 
						 * @return the IDs of all {@link User}s that are members from the beginning (excluding the
 | 
				
			||||||
	 *         (excluding the creator of this group)
 | 
						 *         creator of this group)
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Set<Long> getInitialMemberIDs() { return initialMemberIDs; }
 | 
						public Set<Long> getInitialMemberIDs() { return initialMemberIDs; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,21 +1,35 @@
 | 
				
			|||||||
package envoy.event;
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.Group;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Used to communicate with a client that his request to create a group might
 | 
					 * Used to communicate with a client that his request to create a group might have been rejected as
 | 
				
			||||||
 * have been rejected as it might be disabled on his current server.
 | 
					 * it might be disabled on his current server.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Common v0.2-beta
 | 
					 * @since Envoy Common v0.2-beta
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class GroupCreationResult extends Event<Boolean> {
 | 
					public class GroupCreationResult extends Event<Group> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = 1L;
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@code GroupCreationResult}.
 | 
						 * Creates a new {@code GroupCreationResult} that implies the failure of this
 | 
				
			||||||
 | 
						 * {@link GroupCreationResult}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param success whether the GroupCreation sent before was successful
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GroupCreationResult(boolean success) { super(success); }
 | 
						public GroupCreationResult() {
 | 
				
			||||||
 | 
							super(null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new {@code GroupCreationResult}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param resultGroup the group the server created
 | 
				
			||||||
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public GroupCreationResult(Group resultGroup) {
 | 
				
			||||||
 | 
							super(resultGroup);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,8 +20,7 @@ public final class GroupMessageStatusChange extends MessageStatusChange {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param id       the ID of the {@link GroupMessage} this event is related to
 | 
						 * @param id       the ID of the {@link GroupMessage} this event is related to
 | 
				
			||||||
	 * @param status   the status of this specific members {@link GroupMessage}
 | 
						 * @param status   the status of this specific members {@link GroupMessage}
 | 
				
			||||||
	 * @param date     the date at which the MessageStatus change occurred for
 | 
						 * @param date     the date at which the MessageStatus change occurred for this specific member
 | 
				
			||||||
	 *                 this specific member
 | 
					 | 
				
			||||||
	 * @param memberID the ID of the group member that caused the status change
 | 
						 * @param memberID the ID of the group member that caused the status change
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -37,5 +36,8 @@ public final class GroupMessageStatusChange extends MessageStatusChange {
 | 
				
			|||||||
	public long getMemberID() { return memberID; }
 | 
						public long getMemberID() { return memberID; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("GroupMessageStatusChange[meta=%s,memberID=%d]", super.toString(), memberID); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("GroupMessageStatusChange[meta=%s,memberID=%d]", super.toString(),
 | 
				
			||||||
 | 
								memberID);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,11 +5,9 @@ import static envoy.event.ElementOperation.*;
 | 
				
			|||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This event is used to communicate changes in the group size between client
 | 
					 * This event is used to communicate changes in the group size between client and server.
 | 
				
			||||||
 * and server.
 | 
					 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * Possible actions are adding or removing certain {@link User}s to or from a
 | 
					 * Possible actions are adding or removing certain {@link User}s to or from a certain {@link Group}.
 | 
				
			||||||
 * certain {@link Group}.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Common v0.1-beta
 | 
					 * @since Envoy Common v0.1-beta
 | 
				
			||||||
@@ -22,8 +20,7 @@ public final class GroupResize extends Event<User> {
 | 
				
			|||||||
	private static final long serialVersionUID = 0L;
 | 
						private static final long serialVersionUID = 0L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a {@link GroupResize} through a Contact where the name has
 | 
						 * Initializes a {@link GroupResize} through a Contact where the name has already been set.
 | 
				
			||||||
	 * already been set.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param user      the {@link User} who wants to join or leave a group
 | 
						 * @param user      the {@link User} who wants to join or leave a group
 | 
				
			||||||
	 * @param group     the {@link Group} he wants to join or leave
 | 
						 * @param group     the {@link Group} he wants to join or leave
 | 
				
			||||||
@@ -33,13 +30,14 @@ public final class GroupResize extends Event<User> {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GroupResize(User user, Group group, ElementOperation operation) {
 | 
						public GroupResize(User user, Group group, ElementOperation operation) {
 | 
				
			||||||
		super(user);
 | 
							super(user);
 | 
				
			||||||
		this.operation	= operation;
 | 
							this.operation = operation;
 | 
				
			||||||
		if (group.getContacts().contains(user)) {
 | 
							final var contained = group.getContacts().contains(user);
 | 
				
			||||||
			if (operation.equals(ADD))
 | 
							if (contained && operation.equals(ADD))
 | 
				
			||||||
				throw new IllegalArgumentException(String.format("Cannot add %s to %s!", user, group));
 | 
								throw new IllegalArgumentException(String.format("Cannot add %s to %s!", user, group));
 | 
				
			||||||
		} else if (operation.equals(REMOVE))
 | 
							else if (operation.equals(REMOVE) && !contained)
 | 
				
			||||||
			throw new IllegalArgumentException(String.format("Cannot remove %s from %s!", user, group));
 | 
								throw new IllegalArgumentException(
 | 
				
			||||||
		groupID	= group.getID();
 | 
									String.format("Cannot remove %s from %s!", user, group));
 | 
				
			||||||
 | 
							groupID = group.getID();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -72,5 +70,8 @@ public final class GroupResize extends Event<User> {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("GroupResize[userid=%d,groupid=%d,operation=%s]", get(), groupID, operation); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("GroupResize[user=%s,groupid=%d,operation=%s]", get(), groupID,
 | 
				
			||||||
 | 
								operation);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
package envoy.event;
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Signifies to the client that the handshake failed for the attached
 | 
					 * Signifies to the client that the handshake failed for the attached reason.
 | 
				
			||||||
 * reason.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Common v0.3-alpha
 | 
					 * @since Envoy Common v0.3-alpha
 | 
				
			||||||
@@ -24,8 +23,7 @@ public final class HandshakeRejection extends Event<String> {
 | 
				
			|||||||
	public static final String USERNAME_TAKEN = "Incorrect user name or password.";
 | 
						public static final String USERNAME_TAKEN = "Incorrect user name or password.";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Select this value if the version of the client is incompatible with the
 | 
						 * Select this value if the version of the client is incompatible with the server.
 | 
				
			||||||
	 * server.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -39,8 +37,7 @@ public final class HandshakeRejection extends Event<String> {
 | 
				
			|||||||
	public static final String INVALID_TOKEN = "Invalid authentication token";
 | 
						public static final String INVALID_TOKEN = "Invalid authentication token";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Select this value if the handshake could not be completed for some different
 | 
						 * Select this value if the handshake could not be completed for some different reason.
 | 
				
			||||||
	 * reason.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Common v0.3-alpha
 | 
						 * @since Envoy Common v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -54,7 +51,9 @@ public final class HandshakeRejection extends Event<String> {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Common v0.3-alpha
 | 
						 * @since Envoy Common v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public HandshakeRejection() { super(INTERNAL_ERROR); }
 | 
						public HandshakeRejection() {
 | 
				
			||||||
 | 
							super(INTERNAL_ERROR);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of {@link HandshakeRejection}.
 | 
						 * Creates an instance of {@link HandshakeRejection}.
 | 
				
			||||||
@@ -62,5 +61,7 @@ public final class HandshakeRejection extends Event<String> {
 | 
				
			|||||||
	 * @param reason the reason why the handshake was rejected
 | 
						 * @param reason the reason why the handshake was rejected
 | 
				
			||||||
	 * @since Envoy Common v0.3-alpha
 | 
						 * @since Envoy Common v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public HandshakeRejection(String reason) { super(reason); }
 | 
						public HandshakeRejection(String reason) {
 | 
				
			||||||
 | 
							super(reason);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
package envoy.event;
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Signifies to the server that the client needs a new
 | 
					 * Signifies to the server that the client needs a new {@link envoy.data.IDGenerator} instance.
 | 
				
			||||||
 * {@link envoy.data.IDGenerator} instance.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Common v0.3-alpha
 | 
					 * @since Envoy Common v0.3-alpha
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
package envoy.event;
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This event should be sent when a user is currently typing something in a
 | 
					 * This event should be sent when a user is currently typing something in a chat.
 | 
				
			||||||
 * chat.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Client v0.2-beta
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
package envoy.event;
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This class allows envoy users to send an issue proposal to the server who, if
 | 
					 * This class allows envoy users to send an issue proposal to the server who, if not disabled by its
 | 
				
			||||||
 * not disabled by its administrator, will forward it directly to Gitea.
 | 
					 * administrator, will forward it directly to Gitea.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Common v0.2-beta
 | 
					 * @since Envoy Common v0.2-beta
 | 
				
			||||||
@@ -17,9 +17,8 @@ public final class IssueProposal extends Event<String> {
 | 
				
			|||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param title       the title of the reported bug
 | 
						 * @param title       the title of the reported bug
 | 
				
			||||||
	 * @param description the description of this bug
 | 
						 * @param description the description of this bug
 | 
				
			||||||
	 * @param isBug       determines whether this {@code IssueProposal} is
 | 
						 * @param isBug       determines whether this {@code IssueProposal} is supposed to be a feature
 | 
				
			||||||
	 *                    supposed to be a
 | 
						 *                    or a bug (true = bug, false = feature)
 | 
				
			||||||
	 *                    feature or a bug (true = bug, false = feature)
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public IssueProposal(String title, String description, boolean isBug) {
 | 
						public IssueProposal(String title, String description, boolean isBug) {
 | 
				
			||||||
@@ -32,14 +31,14 @@ public final class IssueProposal extends Event<String> {
 | 
				
			|||||||
	 * @param title       the title of the reported bug
 | 
						 * @param title       the title of the reported bug
 | 
				
			||||||
	 * @param description the description of this bug
 | 
						 * @param description the description of this bug
 | 
				
			||||||
	 * @param user        the name of the user creating the issue
 | 
						 * @param user        the name of the user creating the issue
 | 
				
			||||||
	 * @param isBug       determines whether this {@code IssueProposal} is
 | 
						 * @param isBug       determines whether this {@code IssueProposal} is supposed to be a feature
 | 
				
			||||||
	 *                    supposed to be a
 | 
						 *                    or a bug (true = bug, false = feature)
 | 
				
			||||||
	 *                    feature or a bug (true = bug, false = feature)
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public IssueProposal(String title, String description, String user, boolean isBug) {
 | 
						public IssueProposal(String title, String description, String user, boolean isBug) {
 | 
				
			||||||
		super(escape(title));
 | 
							super(escape(title));
 | 
				
			||||||
		this.description	= sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user);
 | 
							this.description	=
 | 
				
			||||||
 | 
								sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user);
 | 
				
			||||||
		bug					= isBug;
 | 
							bug					= isBug;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,7 +60,9 @@ public final class IssueProposal extends Event<String> {
 | 
				
			|||||||
	 * @return the escaped string
 | 
						 * @return the escaped string
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private static String escape(String raw) { return raw.replace("\\", "\\\\").replace("\"", "\\\""); }
 | 
						private static String escape(String raw) {
 | 
				
			||||||
 | 
							return raw.replace("\\", "\\\\").replace("\"", "\\\"");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the description
 | 
						 * @return the description
 | 
				
			||||||
@@ -70,8 +71,7 @@ public final class IssueProposal extends Event<String> {
 | 
				
			|||||||
	public String getDescription() { return description; }
 | 
						public String getDescription() { return description; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return whether this issue is supposed to be a bug - otherwise it is intended
 | 
						 * @return whether this issue is supposed to be a bug - otherwise it is intended as a feature
 | 
				
			||||||
	 *         as a feature
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean isBug() { return bug; }
 | 
						public boolean isBug() { return bug; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,8 +19,7 @@ public class MessageStatusChange extends Event<Message.MessageStatus> {
 | 
				
			|||||||
	 * Initializes a {@link MessageStatusChange}.
 | 
						 * Initializes a {@link MessageStatusChange}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param id     the ID of the {@link Message} this event is related to
 | 
						 * @param id     the ID of the {@link Message} this event is related to
 | 
				
			||||||
	 * @param status the status of the {@link Message} this event is related
 | 
						 * @param status the status of the {@link Message} this event is related to
 | 
				
			||||||
	 *               to
 | 
					 | 
				
			||||||
	 * @param date   the date at which the MessageStatus change occurred
 | 
						 * @param date   the date at which the MessageStatus change occurred
 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -36,7 +35,9 @@ public class MessageStatusChange extends Event<Message.MessageStatus> {
 | 
				
			|||||||
	 * @param message the message from which to build the event
 | 
						 * @param message the message from which to build the event
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public MessageStatusChange(Message message) { this(message.getID(), message.getStatus(), Instant.now()); }
 | 
						public MessageStatusChange(Message message) {
 | 
				
			||||||
 | 
							this(message.getID(), message.getStatus(), Instant.now());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the ID of the {@link Message} this event is related to
 | 
						 * @return the ID of the {@link Message} this event is related to
 | 
				
			||||||
@@ -51,5 +52,7 @@ public class MessageStatusChange extends Event<Message.MessageStatus> {
 | 
				
			|||||||
	public Instant getDate() { return date; }
 | 
						public Instant getDate() { return date; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("MessageStatusChange[id=%d,status=%s,date=%s]", id, value, date); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("MessageStatusChange[id=%d,status=%s,date=%s]", id, value, date);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,7 @@ import envoy.data.Contact;
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * This event informs
 | 
					 * This event informs
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * a) the server of the name change of a user or a group.
 | 
					 * a) the server of the name change of a user or a group. b) another user of this users name change.
 | 
				
			||||||
 * b) another user of this users name change.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Common v0.1-beta
 | 
					 * @since Envoy Common v0.1-beta
 | 
				
			||||||
@@ -15,7 +14,7 @@ public final class NameChange extends Event<String> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private final long id;
 | 
						private final long id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final long	serialVersionUID	= 0L;
 | 
						private static final long serialVersionUID = 0L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@link NameChange} for a user or a group.
 | 
						 * Creates a new {@link NameChange} for a user or a group.
 | 
				
			||||||
@@ -30,13 +29,14 @@ public final class NameChange extends Event<String> {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a {@link NameChange} through a Contact where the name has
 | 
						 * Initializes a {@link NameChange} through a Contact where the name has already been set.
 | 
				
			||||||
	 * already been set.
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param contact the contact whose name was updated
 | 
						 * @param contact the contact whose name was updated
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public NameChange(Contact contact) { this(contact.getID(), contact.getName()); }
 | 
						public NameChange(Contact contact) {
 | 
				
			||||||
 | 
							this(contact.getID(), contact.getName());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the ID of the {@link Contact} this event is related to
 | 
						 * @return the ID of the {@link Contact} this event is related to
 | 
				
			||||||
@@ -45,5 +45,7 @@ public final class NameChange extends Event<String> {
 | 
				
			|||||||
	public long getID() { return id; }
 | 
						public long getID() { return id; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("NameChange[id=%d,name=%s]", id, value); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("NameChange[id=%d,name=%s]", id, value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,5 +21,7 @@ public class NewAuthToken extends Event<String> {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return "NewAuthToken"; }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return "NewAuthToken";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
package envoy.event;
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This event is used so that the server can tell the client that attachments
 | 
					 * This event is used so that the server can tell the client that attachments will be filtered out.
 | 
				
			||||||
 * will be filtered out.
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Common v0.2-beta
 | 
					 * @since Envoy Common v0.2-beta
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,5 +38,7 @@ public final class PasswordChangeRequest extends Event<String> {
 | 
				
			|||||||
	public String getOldPassword() { return oldPassword; }
 | 
						public String getOldPassword() { return oldPassword; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return "PasswordChangeRequest[id=" + id + "]"; }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return "PasswordChangeRequest[id=" + id + "]";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
package envoy.event;
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This class acts as a notice to the user whether his
 | 
					 * This class acts as a notice to the user whether his {@link envoy.event.PasswordChangeRequest} was
 | 
				
			||||||
 * {@link envoy.event.PasswordChangeRequest} was successful.
 | 
					 * successful.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @since Envoy Common v0.2-beta
 | 
					 * @since Envoy Common v0.2-beta
 | 
				
			||||||
@@ -14,9 +14,10 @@ public final class PasswordChangeResult extends Event<Boolean> {
 | 
				
			|||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates an instance of {@code PasswordChangeResult}.
 | 
						 * Creates an instance of {@code PasswordChangeResult}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param value whether the preceding {@link envoy.event.PasswordChangeRequest}
 | 
						 * @param value whether the preceding {@link envoy.event.PasswordChangeRequest} was successful.
 | 
				
			||||||
	 *              was successful.
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-beta
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public PasswordChangeResult(boolean value) { super(value); }
 | 
						public PasswordChangeResult(boolean value) {
 | 
				
			||||||
 | 
							super(value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,8 +17,7 @@ public final class UserStatusChange extends Event<UserStatus> {
 | 
				
			|||||||
	 * Initializes a {@link UserStatusChange}.
 | 
						 * Initializes a {@link UserStatusChange}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param id     the ID of the {@link User} this event is related to
 | 
						 * @param id     the ID of the {@link User} this event is related to
 | 
				
			||||||
	 * @param status the status of the {@link User} this event is related
 | 
						 * @param status the status of the {@link User} this event is related to
 | 
				
			||||||
	 *               to
 | 
					 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public UserStatusChange(long id, User.UserStatus status) {
 | 
						public UserStatusChange(long id, User.UserStatus status) {
 | 
				
			||||||
@@ -32,7 +31,9 @@ public final class UserStatusChange extends Event<UserStatus> {
 | 
				
			|||||||
	 * @param user the User from which to build the event
 | 
						 * @param user the User from which to build the event
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public UserStatusChange(User user) { this(user.getID(), user.getStatus()); }
 | 
						public UserStatusChange(User user) {
 | 
				
			||||||
 | 
							this(user.getID(), user.getStatus());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the ID of the {@link User} this event is related to
 | 
						 * @return the ID of the {@link User} this event is related to
 | 
				
			||||||
@@ -41,5 +42,7 @@ public final class UserStatusChange extends Event<UserStatus> {
 | 
				
			|||||||
	public long getID() { return id; }
 | 
						public long getID() { return id; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("UserStatusChange[id=%d,status=%s]", id, value); }
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("UserStatusChange[id=%d,status=%s]", id, value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					package envoy.event.contact;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.event.Event.Valueless;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Conveys that either a direct contact or a group member has been deleted while the user has been
 | 
				
			||||||
 | 
					 * offline.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Common v0.3-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class ContactsChangedSinceLastLogin extends Valueless {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,35 +1,41 @@
 | 
				
			|||||||
package envoy.event.contact;
 | 
					package envoy.event.contact;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.data.Contact;
 | 
					import envoy.data.User;
 | 
				
			||||||
import envoy.event.*;
 | 
					import envoy.event.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Signifies the modification of a contact list.
 | 
					 * Signifies the modification of a contact list.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 * @since Envoy Common v0.2-alpha
 | 
					 * @since Envoy Common v0.3-beta
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public final class ContactOperation extends Event<Contact> {
 | 
					public final class UserOperation extends Event<User> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final ElementOperation operationType;
 | 
						private final ElementOperation operationType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = 1L;
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes a {@link ContactOperation}.
 | 
						 * Initializes a {@link UserOperation}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param contact       the user on which the operation is performed
 | 
						 * @param contact       the user on which the operation is performed
 | 
				
			||||||
	 * @param operationType the type of operation to perform
 | 
						 * @param operationType the type of operation to perform
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ContactOperation(Contact contact, ElementOperation operationType) {
 | 
						public UserOperation(User contact, ElementOperation operationType) {
 | 
				
			||||||
		super(contact);
 | 
							super(contact);
 | 
				
			||||||
		this.operationType = operationType;
 | 
							this.operationType = operationType;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the type of operation to perform
 | 
						 * @return the type of operation to perform
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.3-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ElementOperation getOperationType() { return operationType; }
 | 
						public ElementOperation getOperationType() { return operationType; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("%s[contact=%s, operation=%s]", UserOperation.class.getSimpleName(),
 | 
				
			||||||
 | 
								value, operationType);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -18,5 +18,7 @@ public final class UserSearchRequest extends Event<String> {
 | 
				
			|||||||
	 * @param searchPhrase the search phrase to use in the user search
 | 
						 * @param searchPhrase the search phrase to use in the user search
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public UserSearchRequest(String searchPhrase) { super(searchPhrase); }
 | 
						public UserSearchRequest(String searchPhrase) {
 | 
				
			||||||
 | 
							super(searchPhrase);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,5 +21,7 @@ public final class UserSearchResult extends Event<List<User>> {
 | 
				
			|||||||
	 * @param users the users found during the search
 | 
						 * @param users the users found during the search
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public UserSearchResult(List<User> users) { super(users); }
 | 
						public UserSearchResult(List<User> users) {
 | 
				
			||||||
 | 
							super(users);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * This package contains all events that can be sent or received by Envoy Client
 | 
					 * This package contains all events that can be sent or received by Envoy Client or Envoy Server
 | 
				
			||||||
 * or Envoy Server Standalone.
 | 
					 * Standalone.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Leon Hofmeister
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 * @author Maximilian Käfer
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user