Merge pull request #123 from informatik-ag-ngl/f/forward_messages

Added message forwarding capability
This commit is contained in:
delvh 2020-03-23 22:07:43 +01:00 committed by GitHub
commit 2b501f0329
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 2021 additions and 1014 deletions

View File

@ -1,4 +1,4 @@
package envoy.client.ui; package envoy.client;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.io.File; import java.io.File;
@ -15,6 +15,9 @@ import javax.swing.SwingUtilities;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.StatusTrayIcon;
import envoy.client.ui.container.ChatWindow;
import envoy.client.ui.container.LoginDialog;
import envoy.data.Config; import envoy.data.Config;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
@ -31,7 +34,7 @@ import envoy.util.EnvoyLog;
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian Käfer * @author Maximilian Käfer
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public class Startup { public class Startup {
@ -48,7 +51,7 @@ public class Startup {
* *
* @param args the command line arguments may contain configuration parameters * @param args the command line arguments may contain configuration parameters
* and are parsed by the {@link Config} class * and are parsed by the {@link Config} class
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public static void main(String[] args) { public static void main(String[] args) {
ClientConfig config = ClientConfig.getInstance(); ClientConfig config = ClientConfig.getInstance();
@ -127,11 +130,9 @@ public class Startup {
// Save all users to the local database and flush cache // Save all users to the local database and flush cache
localDb.setUsers(client.getUsers()); localDb.setUsers(client.getUsers());
writeProxy.flushCache(); writeProxy.flushCache();
} else { } else
// Set all contacts to offline mode // Set all contacts to offline mode
localDb.getUsers().values().stream().filter(u -> u != localDb.getUser()).forEach(u -> u.setStatus(UserStatus.OFFLINE)); localDb.getUsers().values().stream().filter(u -> u != localDb.getUser()).forEach(u -> u.setStatus(UserStatus.OFFLINE));
}
// Display ChatWindow and StatusTrayIcon // Display ChatWindow and StatusTrayIcon
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {

View File

@ -17,7 +17,7 @@ import envoy.util.EnvoyLog;
* *
* @param <T> the type of cached elements * @param <T> the type of cached elements
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class Cache<T> implements Consumer<T>, Serializable { public class Cache<T> implements Consumer<T>, Serializable {
@ -31,7 +31,7 @@ public class Cache<T> implements Consumer<T>, Serializable {
* Adds an element to the cache. * Adds an element to the cache.
* *
* @param element the element to add * @param element the element to add
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
@Override @Override
public void accept(T element) { public void accept(T element) {
@ -43,7 +43,7 @@ public class Cache<T> implements Consumer<T>, Serializable {
* Sets the processor to which cached elements are relayed. * Sets the processor to which cached elements are relayed.
* *
* @param processor the processor to set * @param processor the processor to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setProcessor(Consumer<T> processor) { this.processor = processor; } public void setProcessor(Consumer<T> processor) { this.processor = processor; }
@ -51,7 +51,7 @@ public class Cache<T> implements Consumer<T>, Serializable {
* Relays all cached elements to the processor. * Relays all cached elements to the processor.
* *
* @throws IllegalStateException if the processor is not initialized * @throws IllegalStateException if the processor is not initialized
* @since Envoy 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");

View File

@ -4,7 +4,7 @@ import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.list.Model;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.data.User; import envoy.data.User;
@ -21,21 +21,21 @@ import envoy.event.MessageStatusChangeEvent;
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public class Chat implements Serializable { public class Chat implements Serializable {
private static final long serialVersionUID = -7751248474547242056L; private static final long serialVersionUID = -7751248474547242056L;
private final User recipient; private final User recipient;
private final ComponentListModel<Message> model = new ComponentListModel<>(); private final Model<Message> model = new Model<>();
/** /**
* Provides the list of messages that the recipient receives.<br> * Provides the list of messages that the recipient receives.<br>
* Saves the Messages in the corresponding chat at that Point. * Saves the Messages in the corresponding chat at that Point.
* *
* @param recipient the user who receives the messages * @param recipient the user who receives the messages
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public Chat(User recipient) { this.recipient = recipient; } public Chat(User recipient) { this.recipient = recipient; }
@ -43,7 +43,7 @@ public class Chat implements Serializable {
* Appends a message to the bottom of this chat * Appends a message to the bottom of this chat
* *
* @param message the message to append * @param message the message to append
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public void appendMessage(Message message) { model.add(message); } public void appendMessage(Message message) { model.add(message); }
@ -56,37 +56,35 @@ public class Chat implements Serializable {
* the message status changes * the message status changes
* @throws IOException if a {@link MessageStatusChangeEvent} could not be * @throws IOException if a {@link MessageStatusChangeEvent} could not be
* delivered to the server * delivered to the server
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void read(WriteProxy writeProxy) throws IOException { public void read(WriteProxy writeProxy) throws IOException {
for (int i = model.size() - 1; i >= 0; --i) { for (int i = model.size() - 1; i >= 0; --i) {
final Message m = model.get(i); final Message m = model.get(i);
if (m.getSenderId() == recipient.getId()) { if (m.getSenderId() == recipient.getId()) if (m.getStatus() == MessageStatus.READ) break;
if (m.getStatus() == MessageStatus.READ) break;
else { else {
m.setStatus(MessageStatus.READ); m.setStatus(MessageStatus.READ);
writeProxy.writeMessageStatusChangeEvent(new MessageStatusChangeEvent(m)); writeProxy.writeMessageStatusChangeEvent(new MessageStatusChangeEvent(m));
} }
} }
} }
}
/** /**
* @return {@code true} if the newest message received in the chat doesn't have * @return {@code true} if the newest message received in the chat doesn't have
* the status {@code READ} * the status {@code READ}
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public boolean isUnread() { return !model.isEmpty() && model.get(model.size() - 1).getStatus() != MessageStatus.READ; } public boolean isUnread() { return !model.isEmpty() && model.get(model.size() - 1).getStatus() != MessageStatus.READ; }
/** /**
* @return all messages in the current chat * @return all messages in the current chat
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public ComponentListModel<Message> getModel() { return model; } public Model<Message> getModel() { return model; }
/** /**
* @return the recipient of a message * @return the recipient of a message
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public User getRecipient() { return recipient; } public User getRecipient() { return recipient; }
} }

View File

@ -18,7 +18,7 @@ import envoy.data.LoginCredentials;
* Created: <strong>01.03.2020</strong><br> * Created: <strong>01.03.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public class ClientConfig extends Config { public class ClientConfig extends Config {
@ -26,7 +26,7 @@ public class ClientConfig extends Config {
/** /**
* @return the singleton instance of the client config * @return the singleton instance of the client config
* @since Envoy 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();
@ -47,68 +47,68 @@ public class ClientConfig extends Config {
/** /**
* @return the host name of the Envoy server * @return the host name of the Envoy server
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public String getServer() { return (String) items.get("server").get(); } public String getServer() { return (String) items.get("server").get(); }
/** /**
* @return the port at which the Envoy server is located on the host * @return the port at which the Envoy server is located on the host
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public Integer getPort() { return (Integer) items.get("port").get(); } public Integer getPort() { return (Integer) items.get("port").get(); }
/** /**
* @return the local database specific to the client user * @return the local database specific to the client user
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public File getLocalDB() { return (File) items.get("localDB").get(); } public File getLocalDB() { return (File) items.get("localDB").get(); }
/** /**
* @return {@code true} if the local database is to be ignored * @return {@code true} if the local database is to be ignored
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Boolean isIgnoreLocalDB() { return (Boolean) items.get("ignoreLocalDB").get(); } public Boolean isIgnoreLocalDB() { return (Boolean) items.get("ignoreLocalDB").get(); }
/** /**
* @return the directory in which all local files are saves * @return the directory in which all local files are saves
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public File getHomeDirectory() { return (File) items.get("homeDirectory").get(); } public File getHomeDirectory() { 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 v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Level getFileLevelBarrier() { return (Level) items.get("fileLevelBarrier").get(); } public Level getFileLevelBarrier() { 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
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Level getConsoleLevelBarrier() { return (Level) items.get("consoleLevelBarrier").get(); } public Level getConsoleLevelBarrier() { return (Level) items.get("consoleLevelBarrier").get(); }
/** /**
* @return the user name * @return the user name
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public String getUser() { return (String) items.get("user").get(); } public String getUser() { return (String) items.get("user").get(); }
/** /**
* @return the password * @return the password
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public char[] getPassword() { return (char[]) items.get("password").get(); } public char[] getPassword() { return (char[]) items.get("password").get(); }
/** /**
* @return {@code true} if user name and password are set * @return {@code true} if user name and password are set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; } public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; }
/** /**
* @return login credentials for the specified user name and password, without * @return login credentials for the specified user name and password, without
* the registration option * the registration option
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public LoginCredentials getLoginCredentials() { public LoginCredentials getLoginCredentials() {
try { try {

View File

@ -16,7 +16,7 @@ import envoy.event.MessageStatusChangeEvent;
* Created: <strong>3 Feb 2020</strong><br> * Created: <strong>3 Feb 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public abstract class LocalDb { public abstract class LocalDb {
@ -30,7 +30,7 @@ public abstract class LocalDb {
/** /**
* Initializes a storage space for a user-specific list of chats. * Initializes a storage space for a user-specific list of chats.
* *
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void initializeUserStorage() {} public void initializeUserStorage() {}
@ -39,7 +39,7 @@ public abstract class LocalDb {
* as well. The message id generator will also be saved if present. * as well. The message id generator will also be saved if present.
* *
* @throws Exception if the saving process failed * @throws Exception if the saving process failed
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void save() throws Exception {} public void save() throws Exception {}
@ -47,7 +47,7 @@ public abstract class LocalDb {
* Loads all user data. * Loads all user data.
* *
* @throws Exception if the loading process failed * @throws Exception if the loading process failed
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void loadUsers() throws Exception {} public void loadUsers() throws Exception {}
@ -55,21 +55,21 @@ public abstract class LocalDb {
* Loads all data of the client user. * Loads all data of the client user.
* *
* @throws Exception if the loading process failed * @throws Exception if the loading process failed
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void loadUserData() throws Exception {} public void loadUserData() throws Exception {}
/** /**
* Loads the ID generator. Any exception thrown during this process is ignored. * Loads the ID generator. Any exception thrown during this process is ignored.
* *
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void loadIdGenerator() {} public void loadIdGenerator() {}
/** /**
* @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 v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Map<String, User> getUsers() { return users; } public Map<String, User> getUsers() { return users; }
@ -81,7 +81,7 @@ public abstract class LocalDb {
/** /**
* @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 v0.1-alpha * @since Envoy Client v0.1-alpha
**/ **/
public List<Chat> getChats() { return chats; } public List<Chat> getChats() { return chats; }
@ -92,55 +92,55 @@ public abstract class LocalDb {
/** /**
* @return the {@link User} who initialized the local database * @return the {@link User} who initialized the local database
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public User getUser() { return user; } public User getUser() { return user; }
/** /**
* @param user the user to set * @param user the user to set
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void setUser(User user) { this.user = user; } public void setUser(User user) { this.user = user; }
/** /**
* @return the message ID generator * @return the message ID generator
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public IdGenerator getIdGenerator() { return idGenerator; } public IdGenerator getIdGenerator() { return idGenerator; }
/** /**
* @param idGenerator the message ID generator to set * @param idGenerator the message ID generator to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setIdGenerator(IdGenerator idGenerator) { this.idGenerator = idGenerator; } public void setIdGenerator(IdGenerator idGenerator) { this.idGenerator = idGenerator; }
/** /**
* @return {@code true} if an {@link IdGenerator} is present * @return {@code true} if an {@link IdGenerator} is present
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public boolean hasIdGenerator() { return idGenerator != null; } public boolean hasIdGenerator() { return idGenerator != null; }
/** /**
* @return the offline message cache * @return the offline message cache
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Cache<Message> getMessageCache() { return messageCache; } public Cache<Message> getMessageCache() { return messageCache; }
/** /**
* @param messageCache the offline message cache to set * @param messageCache the offline message cache to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setMessageCache(Cache<Message> messageCache) { this.messageCache = messageCache; } public void setMessageCache(Cache<Message> messageCache) { this.messageCache = messageCache; }
/** /**
* @return the offline status cache * @return the offline status cache
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Cache<MessageStatusChangeEvent> getStatusCache() { return statusCache; } public Cache<MessageStatusChangeEvent> getStatusCache() { return statusCache; }
/** /**
* @param statusCache the offline status cache to set * @param statusCache the offline status cache to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setStatusCache(Cache<MessageStatusChangeEvent> statusCache) { this.statusCache = statusCache; } public void setStatusCache(Cache<MessageStatusChangeEvent> statusCache) { this.statusCache = statusCache; }
@ -150,7 +150,7 @@ public abstract class LocalDb {
* @param id the ID of the message to search for * @param id the ID of the message to search for
* @return the message with the corresponding ID, or {@code null} if no message * @return the message with the corresponding ID, or {@code null} if no message
* has been found * has been found
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public Message getMessage(long id) { public Message getMessage(long id) {
for (Chat c : chats) for (Chat c : chats)

View File

@ -19,7 +19,7 @@ import envoy.util.SerializationUtils;
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public class PersistentLocalDb extends LocalDb { public class PersistentLocalDb extends LocalDb {
@ -32,7 +32,7 @@ public class PersistentLocalDb extends LocalDb {
* This constructor shall be used in conjunction with the {@code ignoreLocalDB} * This constructor shall be used in conjunction with the {@code ignoreLocalDB}
* {@link ConfigItem}. * {@link ConfigItem}.
* *
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public PersistentLocalDb() {} public PersistentLocalDb() {}
@ -42,7 +42,7 @@ public class PersistentLocalDb extends LocalDb {
* *
* @param localDbDir the directory in which to store users and chats * @param localDbDir the directory in which to store users and chats
* @throws IOException if the PersistentLocalDb could not be initialized * @throws IOException if the PersistentLocalDb could not be initialized
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public PersistentLocalDb(File localDbDir) throws IOException { public PersistentLocalDb(File localDbDir) throws IOException {
localDBDir = localDbDir; localDBDir = localDbDir;
@ -58,7 +58,7 @@ public class PersistentLocalDb extends LocalDb {
* Creates a database file for a user-specific list of chats. * Creates a database file for a user-specific list of chats.
* *
* @throws NullPointerException if the client user is not yet specified * @throws NullPointerException if the client user is not yet specified
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
@Override @Override
public void initializeUserStorage() { public void initializeUserStorage() {

View File

@ -22,7 +22,7 @@ import envoy.util.SerializationUtils;
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class Settings { public class Settings {
@ -49,7 +49,7 @@ public class Settings {
* The way to instantiate the settings. * The way to instantiate the settings.
* Is set to private to deny other instances of that object. * Is set to private to deny other instances of that object.
* *
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
private Settings() { private Settings() {
// Load settings from settings file // Load settings from settings file
@ -81,7 +81,7 @@ public class Settings {
* This method is used to ensure that there is only one instance of Settings. * This method is used to ensure that there is only one instance of Settings.
* *
* @return the instance of Settings * @return the instance of Settings
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public static Settings getInstance() { return settings; } public static Settings getInstance() { return settings; }
@ -90,7 +90,7 @@ public class Settings {
* *
* @throws IOException if an error occurs while saving the themes to the theme * @throws IOException if an error occurs while saving the themes to the theme
* file * file
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void save() throws IOException { public void save() throws IOException {
// Save settings to settings file // Save settings to settings file
@ -110,21 +110,27 @@ public class Settings {
* Adds new theme to the theme map. * Adds new theme to the theme map.
* *
* @param theme the {@link Theme} to add * @param theme the {@link Theme} to add
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void addNewThemeToMap(Theme theme) { settings.getThemes().put(theme.getThemeName(), theme); } public void addNewThemeToMap(Theme theme) { getThemes().put(theme.getThemeName(), theme); }
/** /**
* @return the name of the currently active {@link Theme} * @return the name of the currently active {@link Theme}
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public String getCurrentTheme() { return (String) items.get("currentTheme").get(); } public String getCurrentThemeName() { return (String) items.get("currentTheme").get(); }
/**
* @return the currently active {@link Theme}
* @since Envoy Client v0.1-beta
*/
public Theme getCurrentTheme() { return getTheme(getCurrentThemeName()); }
/** /**
* Sets the name of the current {@link Theme}. * Sets the name of the current {@link Theme}.
* *
* @param themeName the name to set * @param themeName the name to set
* @since Envoy 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); }
@ -132,7 +138,7 @@ public class Settings {
* @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 it has to be pressed in conjunction with the * message. Otherwise it has to be pressed in conjunction with the
* {@code Control} key. * {@code Control} key.
* @since Envoy 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(); }
@ -142,13 +148,13 @@ public class Settings {
* @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} key. Otherwise it has to be pressed in * the {@code Enter} key. Otherwise it has to be pressed in
* conjunction with the {@code Control} key. * conjunction with the {@code Control} key.
* @since Envoy 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 the current on close mode. * @return the current on close mode.
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); } public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); }
@ -156,7 +162,7 @@ public class Settings {
* Sets the current on close mode. * Sets the current on close mode.
* *
* @param currentOnCloseMode the on close mode that should be set. * @param currentOnCloseMode the on close mode that should be set.
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem<Boolean>) items.get("onCloseMode")).set(currentOnCloseMode); } public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem<Boolean>) items.get("onCloseMode")).set(currentOnCloseMode); }
@ -172,7 +178,7 @@ public class Settings {
/** /**
* @return a {@code Map<String, Theme>} of all themes with their names as keys * @return a {@code Map<String, Theme>} of all themes with their names as keys
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Map<String, Theme> getThemes() { return themes; } public Map<String, Theme> getThemes() { return themes; }
@ -180,14 +186,14 @@ public class Settings {
* Sets the {@code Map<String, Theme>} of all themes with their names as keys * Sets the {@code Map<String, Theme>} of all themes with their names as keys
* *
* @param themes the theme map to set * @param themes the theme map to set
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void setThemes(Map<String, Theme> themes) { this.themes = themes; } public void setThemes(Map<String, Theme> themes) { this.themes = themes; }
/** /**
* @param themeName the name of the {@link Theme} to get * @param themeName the name of the {@link Theme} to get
* @return the {@link Theme} with the specified name * @return the {@link Theme} with the specified name
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Theme getTheme(String themeName) { return themes.get(themeName); } public Theme getTheme(String themeName) { return themes.get(themeName); }
} }

View File

@ -7,7 +7,7 @@ import java.util.function.Consumer;
import javax.swing.JComponent; import javax.swing.JComponent;
import envoy.client.ui.PrimaryToggleSwitch; import envoy.client.ui.primary.PrimaryToggleSwitch;
/** /**
* Encapsulates a persistent value that is directly or indirectly mutable by the * Encapsulates a persistent value that is directly or indirectly mutable by the
@ -19,7 +19,7 @@ import envoy.client.ui.PrimaryToggleSwitch;
* *
* @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
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class SettingsItem<T> implements Serializable { public class SettingsItem<T> implements Serializable {
@ -45,7 +45,7 @@ public class SettingsItem<T> implements Serializable {
* @param value the default value * @param value the default value
* @param userFriendlyName the user friendly name (short) * @param userFriendlyName the user friendly name (short)
* @param description the description (long) * @param description the description (long)
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public SettingsItem(T value, String userFriendlyName, String description) { public SettingsItem(T value, String userFriendlyName, String description) {
this(value, componentClasses.get(value.getClass())); this(value, componentClasses.get(value.getClass()));
@ -61,7 +61,7 @@ public class SettingsItem<T> implements Serializable {
* *
* @param value the default value * @param value the default value
* @param componentClass the class of the {@link JComponent} to represent this {@link SettingsItem} with * @param componentClass the class of the {@link JComponent} to represent this {@link SettingsItem} with
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public SettingsItem(T value, Class<? extends JComponent> componentClass) { public SettingsItem(T value, Class<? extends JComponent> componentClass) {
this.value = value; this.value = value;
@ -72,7 +72,7 @@ public class SettingsItem<T> implements Serializable {
* @return an instance of the {@link JComponent} that represents this {@link SettingsItem} * @return an instance of the {@link JComponent} that represents this {@link SettingsItem}
* @throws ReflectiveOperationException if the component initialization failed * @throws ReflectiveOperationException if the component initialization failed
* @throws SecurityException if the component initialization failed * @throws SecurityException if the component initialization failed
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public JComponent getComponent() throws ReflectiveOperationException, SecurityException { public JComponent getComponent() throws ReflectiveOperationException, SecurityException {
if (componentClass == null) throw new NullPointerException("Component class is null"); if (componentClass == null) throw new NullPointerException("Component class is null");
@ -81,7 +81,7 @@ public class SettingsItem<T> implements Serializable {
/** /**
* @return the value * @return the value
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public T get() { return value; } public T get() { return value; }
@ -90,7 +90,7 @@ public class SettingsItem<T> implements Serializable {
* defined, it will be invoked with this value. * defined, it will be invoked with this value.
* *
* @param value the value to set * @param value the value to set
* @since Envoy 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);
@ -99,37 +99,37 @@ public class SettingsItem<T> implements Serializable {
/** /**
* @return the componentClass * @return the componentClass
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Class<? extends JComponent> getComponentClass() { return componentClass; } public Class<? extends JComponent> getComponentClass() { return componentClass; }
/** /**
* @param componentClass the componentClass to set * @param componentClass the componentClass to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setComponentClass(Class<? extends JComponent> componentClass) { this.componentClass = componentClass; } public void setComponentClass(Class<? extends JComponent> componentClass) { this.componentClass = componentClass; }
/** /**
* @return the userFriendlyName * @return the userFriendlyName
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public String getUserFriendlyName() { return userFriendlyName; } public String getUserFriendlyName() { return userFriendlyName; }
/** /**
* @param userFriendlyName the userFriendlyName to set * @param userFriendlyName the userFriendlyName to set
* @since Envoy 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
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public String getDescription() { return description; } public String getDescription() { return description; }
/** /**
* @param description the description to set * @param description the description to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setDescription(String description) { this.description = description; } public void setDescription(String description) { this.description = description; }
@ -139,7 +139,7 @@ public class SettingsItem<T> implements Serializable {
* when the value changes. * when the value changes.
* *
* @param changeHandler the changeHandler to set * @param changeHandler the changeHandler to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setChangeHandler(Consumer<T> changeHandler) { public void setChangeHandler(Consumer<T> changeHandler) {
this.changeHandler = changeHandler; this.changeHandler = changeHandler;

View File

@ -9,7 +9,7 @@ package envoy.client.data;
* Created: <strong>3 Feb 2020</strong><br> * Created: <strong>3 Feb 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class TransientLocalDb extends LocalDb { public class TransientLocalDb extends LocalDb {
} }

View File

@ -4,6 +4,6 @@
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
package envoy.client.data; package envoy.client.data;

View File

@ -10,7 +10,7 @@ import envoy.event.Event;
* Created: <strong>8 Feb 2020</strong><br> * Created: <strong>8 Feb 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class HandshakeSuccessfulEvent extends Event.Valueless { public class HandshakeSuccessfulEvent extends Event.Valueless {

View File

@ -9,7 +9,7 @@ import envoy.event.Event;
* Created: <strong>4 Dec 2019</strong><br> * Created: <strong>4 Dec 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class MessageCreationEvent extends Event<Message> { public class MessageCreationEvent extends Event<Message> {

View File

@ -9,7 +9,7 @@ import envoy.event.Event;
* Created: <strong>4 Dec 2019</strong><br> * Created: <strong>4 Dec 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class MessageModificationEvent extends Event<Message> { public class MessageModificationEvent extends Event<Message> {

View File

@ -9,7 +9,7 @@ import envoy.event.Event;
* *
* @author: Maximilian K&aumlfer * @author: Maximilian K&aumlfer
* *
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class SendEvent extends Event<Event<?>> { public class SendEvent extends Event<Event<?>> {

View File

@ -9,7 +9,7 @@ import envoy.event.Event;
* Created: <strong>15 Dec 2019</strong><br> * Created: <strong>15 Dec 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class ThemeChangeEvent extends Event<Theme> { public class ThemeChangeEvent extends Event<Theme> {
@ -20,7 +20,7 @@ public class ThemeChangeEvent extends Event<Theme> {
* of the {@link Theme} currently in use * of the {@link Theme} currently in use
* *
* @param theme the new currently used {@link Theme} object * @param theme the new currently used {@link Theme} object
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public ThemeChangeEvent(Theme theme) { super(theme); } public ThemeChangeEvent(Theme theme) { super(theme); }
} }

View File

@ -4,6 +4,6 @@
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
package envoy.client.event; package envoy.client.event;

View File

@ -30,7 +30,7 @@ import envoy.util.SerializationUtils;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public class Client implements Closeable { public class Client implements Closeable {
@ -124,7 +124,7 @@ public class Client implements Closeable {
* initialization * initialization
* @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 server * requested from the server
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void initReceiver(LocalDb localDb, Cache<Message> receivedMessageCache) throws IOException { public void initReceiver(LocalDb localDb, Cache<Message> receivedMessageCache) throws IOException {
checkOnline(); checkOnline();
@ -181,7 +181,7 @@ public class Client implements Closeable {
* *
* @param message the message to send * @param message the message to send
* @throws IOException if the message does not reach the server * @throws IOException if the message does not reach the server
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void sendMessage(Message message) throws IOException { public void sendMessage(Message message) throws IOException {
writeObject(message); writeObject(message);
@ -200,7 +200,7 @@ public class Client implements Closeable {
* Requests a new {@link IdGenerator} from the server. * Requests a new {@link IdGenerator} from the server.
* *
* @throws IOException if the request does not reach the server * @throws IOException if the request does not reach the server
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void requestIdGenerator() throws IOException { public void requestIdGenerator() throws IOException {
logger.info("Requesting new id generator..."); logger.info("Requesting new id generator...");
@ -210,7 +210,7 @@ public class Client implements Closeable {
/** /**
* @return a {@code Map<String, User>} of all users on the server with their * @return a {@code Map<String, User>} of all users on the server with their
* user names as keys * user names as keys
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Map<String, User> getUsers() { public Map<String, User> getUsers() {
checkOnline(); checkOnline();
@ -232,7 +232,7 @@ public class Client implements Closeable {
/** /**
* @return the sender object that represents this client. * @return the sender object that represents this client.
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public User getSender() { return sender; } public User getSender() { return sender; }
@ -240,7 +240,7 @@ public class Client implements Closeable {
* Sets the client user which is used to send messages. * Sets the client user which is used to send messages.
* *
* @param sender the client user to set * @param sender the client user to set
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void setSender(User sender) { this.sender = sender; } public void setSender(User sender) { this.sender = sender; }
@ -251,19 +251,19 @@ public class Client implements Closeable {
/** /**
* @return {@code true} if a connection to the server could be established * @return {@code true} if a connection to the server could be established
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public boolean isOnline() { return online; } public boolean isOnline() { return online; }
/** /**
* @return the contacts of this {@link Client} * @return the contacts of this {@link Client}
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Contacts getContacts() { return contacts; } public Contacts getContacts() { return contacts; }
/** /**
* @param contacts the contacts to set * @param contacts the contacts to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void setContacts(Contacts contacts) { this.contacts = contacts; } public void setContacts(Contacts contacts) { this.contacts = contacts; }
} }

View File

@ -14,7 +14,7 @@ import envoy.util.EnvoyLog;
* Created: <strong>4 Feb 2020</strong><br> * Created: <strong>4 Feb 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class MessageStatusChangeEventProcessor implements Consumer<MessageStatusChangeEvent> { public class MessageStatusChangeEventProcessor implements Consumer<MessageStatusChangeEvent> {
@ -25,7 +25,7 @@ public class MessageStatusChangeEventProcessor implements Consumer<MessageStatus
* {@code RECEIVED} or {@code READ}. * {@code RECEIVED} or {@code READ}.
* *
* @param evt the status change event * @param evt the status change event
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
@Override @Override
public void accept(MessageStatusChangeEvent evt) { public void accept(MessageStatusChangeEvent evt) {

View File

@ -15,7 +15,7 @@ import envoy.util.EnvoyLog;
* Created: <strong>31.12.2019</strong><br> * Created: <strong>31.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class ReceivedMessageProcessor implements Consumer<Message> { public class ReceivedMessageProcessor implements Consumer<Message> {

View File

@ -19,7 +19,7 @@ import envoy.util.SerializationUtils;
* Created: <strong>30.12.2019</strong><br> * Created: <strong>30.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class Receiver extends Thread { public class Receiver extends Thread {

View File

@ -12,7 +12,7 @@ import envoy.event.UserStatusChangeEvent;
* Created: <strong>2 Feb 2020</strong><br> * Created: <strong>2 Feb 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class UserStatusChangeProcessor implements Consumer<UserStatusChangeEvent> { public class UserStatusChangeProcessor implements Consumer<UserStatusChangeEvent> {
@ -20,7 +20,7 @@ public class UserStatusChangeProcessor implements Consumer<UserStatusChangeEvent
/** /**
* @param localDb the local database in which status updates will by applied * @param localDb the local database in which status updates will by applied
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public UserStatusChangeProcessor(LocalDb localDb) { this.localDb = localDb; } public UserStatusChangeProcessor(LocalDb localDb) { this.localDb = localDb; }

View File

@ -19,7 +19,7 @@ import envoy.util.EnvoyLog;
* Created: <strong>6 Feb 2020</strong><br> * Created: <strong>6 Feb 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class WriteProxy { public class WriteProxy {
@ -36,7 +36,7 @@ public class WriteProxy {
* events * events
* @param localDb the local database used to cache messages and message status * @param localDb the local database used to cache messages and message status
* change events * change events
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public WriteProxy(Client client, LocalDb localDb) { public WriteProxy(Client client, LocalDb localDb) {
this.client = client; this.client = client;
@ -68,7 +68,7 @@ public class WriteProxy {
* Sends cached {@link Message}s and {@link MessageStatusChangeEvent}s to the * Sends cached {@link Message}s and {@link MessageStatusChangeEvent}s to the
* server. * server.
* *
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void flushCache() { public void flushCache() {
// Send messages // Send messages
@ -84,7 +84,7 @@ public class WriteProxy {
* *
* @param message the message to send * @param message the message to send
* @throws IOException if the message could not be sent * @throws IOException if the message could not be sent
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void writeMessage(Message message) throws IOException { public void writeMessage(Message message) throws IOException {
if (client.isOnline()) client.sendMessage(message); if (client.isOnline()) client.sendMessage(message);
@ -97,7 +97,7 @@ public class WriteProxy {
* *
* @param evt the event to send * @param evt the event to send
* @throws IOException if the event could not be sent * @throws IOException if the event could not be sent
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void writeMessageStatusChangeEvent(MessageStatusChangeEvent evt) throws IOException { public void writeMessageStatusChangeEvent(MessageStatusChangeEvent evt) throws IOException {
if (client.isOnline()) client.sendEvent(evt); if (client.isOnline()) client.sendEvent(evt);

View File

@ -4,6 +4,6 @@
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
package envoy.client.net; package envoy.client.net;

View File

@ -11,7 +11,7 @@ import java.awt.color.ColorSpace;
* Created: <strong>23.12.2019</strong><br> * Created: <strong>23.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
@SuppressWarnings("javadoc") @SuppressWarnings("javadoc")
public class Color extends java.awt.Color { public class Color extends java.awt.Color {
@ -102,13 +102,13 @@ public class Color extends java.awt.Color {
/** /**
* @return the inversion of this {@link Color} by replacing the red, green and * @return the inversion of this {@link Color} by replacing the red, green and
* blue values by subtracting them form 255 * blue values by subtracting them form 255
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public Color invert() { return new Color(255 - getRed(), 255 - getGreen(), 255 - getBlue()); } public Color invert() { return new Color(255 - getRed(), 255 - getGreen(), 255 - getBlue()); }
/** /**
* @return the hex value of this {@link Color} * @return the hex value of this {@link Color}
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public String toHex() { return String.format("#%02x%02x%02x", getRed(), getGreen(), getBlue()); } public String toHex() { return String.format("#%02x%02x%02x", getRed(), getGreen(), getBlue()); }
} }

View File

@ -0,0 +1,200 @@
package envoy.client.ui;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
/**
* This class defines a menu that will be automatically called if
* {@link MouseEvent#isPopupTrigger()} returns true for the parent component.
* The user has the possibility to directly add actions to be performed when
* clicking on the element with the selected String. Additionally, for each
* element an {@link Icon} can be added, but it must not be.
* If the key(text) of an element starts with one of the predefined values, a
* special component will be called: either a {@link JRadioButtonMenuItem}, a
* {@link JCheckBoxMenuItem} or a {@link JMenu} will be created.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContextMenu.java</strong><br>
* Created: <strong>17 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContextMenu extends JPopupMenu {
private static final long serialVersionUID = 2177146471226992104L;
/**
* If a key starts with this String, a {@link JCheckBoxMenuItem} will be created
*/
public static final String checkboxMenuItem = "ChBoMI";
/**
* If a key starts with this String, a {@link JRadioButtonMenuItem} will be
* created
*/
public static final String radioButtonMenuItem = "RaBuMI";
/**
* If a key starts with this String, a {@link JMenu} will be created
*/
public static final String subMenuItem = "SubMI";
private Map<String, ActionListener> items = new HashMap<>();
private Map<String, Icon> icons = new HashMap<>();
private Map<String, Integer> mnemonics = new HashMap<>();
private ButtonGroup radioButtonGroup = new ButtonGroup();
private boolean built = false;
/**
* @param parent the component which will call this
* {@link ContextMenu}
* @since Envoy Client v0.1-beta
*/
public ContextMenu(Component parent) { setInvoker(parent); }
/**
* @param label the string that a UI may use to display as a title
* for the pop-up menu
* @param parent the component which will call this
* {@link ContextMenu}
* @param itemsWithActions a map of all strings to be displayed with according
* actions
* @param itemIcons the icons to be displayed before a name, if wanted.
* Only keys in here will have an Icon displayed. More
* precisely, all keys here not included in the first
* map will be thrown out.
* @param itemMnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the
* given text
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label, Component parent, Map<String, ActionListener> itemsWithActions, Map<String, Icon> itemIcons,
Map<String, Integer> itemMnemonics) {
super(label);
setInvoker(parent);
this.items = (itemsWithActions != null) ? itemsWithActions : items;
this.icons = (itemIcons != null) ? itemIcons : icons;
this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics;
}
/**
* Prepares the PopupMenu to be displayed. Should only be used once all map
* values have been set.
*
* @return this instance of {@link ContextMenu} to allow chaining behind the
* constructor
* @since Envoy Client v0.1-beta
*/
public ContextMenu build() {
items.forEach((text, action) -> {
// case radio button wanted
AbstractButton item;
if (text.startsWith(radioButtonMenuItem)) {
item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
radioButtonGroup.add(item);
// case check box wanted
} else if (text.startsWith(checkboxMenuItem))
item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
// case sub-menu wanted
else if (text.startsWith(subMenuItem)) item = new JMenu(text.substring(subMenuItem.length()));
else // normal JMenuItem wanted
item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null);
item.addActionListener(action);
if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text));
add(item);
});
if (getInvoker() != null) {
getInvoker().addMouseListener(getShowingListener());
built = true;
}
return this;
}
/**
* @param label the string that a UI may use to display as a title for the
* pop-up menu.
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label) { super(label); }
private MouseAdapter getShowingListener() {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { action(e); }
@Override
public void mousePressed(MouseEvent e) { action(e); }
@Override
public void mouseReleased(MouseEvent e) { action(e); }
private void action(MouseEvent e) {
if (!built) build();
if (e.isPopupTrigger()) {
// hides the menu if already visible
if (!isVisible()) show(e.getComponent(), e.getX(), e.getY());
else setVisible(false);
}
}
};
}
/**
* Removes all subcomponents of this menu.
*
* @since Envoy Client v0.1-beta
*/
public void clear() {
removeAll();
items = new HashMap<>();
icons = new HashMap<>();
mnemonics = new HashMap<>();
}
/**
* @return the items
* @since Envoy Client v0.1-beta
*/
public Map<String, ActionListener> getItems() { return items; }
/**
* @param items the items with the displayed text and the according action to
* take once called
* @since Envoy Client v0.1-beta
*/
public void setItems(Map<String, ActionListener> items) { this.items = items; }
/**
* @return the icons
* @since Envoy Client v0.1-beta
*/
public Map<String, Icon> getIcons() { return icons; }
/**
* @param icons the icons to set
* @since Envoy Client v0.1-beta
*/
public void setIcons(Map<String, Icon> icons) { this.icons = icons; }
/**
* @return the mnemonics (the keyboard shortcuts that automatically execute the
* command for a {@link JMenuItem} with corresponding text)
* @since Envoy Client v0.1-beta
*/
public Map<String, Integer> getMnemonics() { return mnemonics; }
/**
* @param mnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the given
* text
* @since Envoy Client v0.1-beta
*/
public void setMnemonics(Map<String, Integer> mnemonics) { this.mnemonics = mnemonics; }
}

View File

@ -0,0 +1,61 @@
package envoy.client.ui;
import java.awt.Image;
import java.io.IOException;
import java.util.EnumMap;
import java.util.EnumSet;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
/**
* Provides static utility methods for loading icons from the resource
* folder.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>IconUtil.java</strong>
* Created: <strong>16.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class IconUtil {
private IconUtil() {}
/**
* Loads an icon from resource folder and scales it to a given size.
*
* @param path the path to the icon inside the resource folder
* @param size the size to scale the icon to
* @return the scaled icon
* @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
public static ImageIcon load(String path, int size) throws IOException {
return new ImageIcon(ImageIO.read(IconUtil.class.getResourceAsStream(path)).getScaledInstance(size, size, Image.SCALE_SMOOTH));
}
/**
*
* Loads icons specified by an enum. The images have to be named like the
* lowercase enum constants with {@code .png} extension and be located inside a
* folder with the lowercase name of the enum, which must be contained inside
* the {@code /icons} folder.
*
* @param <T> the enum that specifies the icons to load
* @param enumClass the class of the enum
* @param size the size to scale the icons to
* @return a map containing the loaded icons with the corresponding enum
* constants as keys
* @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
public static <T extends Enum<T>> EnumMap<T, ImageIcon> loadByEnum(Class<T> enumClass, int size) throws IOException {
var icons = new EnumMap<T, ImageIcon>(enumClass);
var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/";
for (var e : EnumSet.allOf(enumClass))
icons.put(e, load(path + e.toString().toLowerCase() + ".png", size));
return icons;
}
}

View File

@ -1,93 +0,0 @@
package envoy.client.ui;
import java.awt.BorderLayout;
import java.awt.Font;
import java.text.SimpleDateFormat;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentListCellRenderer;
import envoy.data.Message;
/**
* Defines how a message is displayed.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListRenderer.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy v0.1-alpha
*/
public class MessageListRenderer implements ComponentListCellRenderer<Message> {
private JTextArea messageTextArea;
@Override
public JPanel getListCellComponent(ComponentList<? extends Message> list, Message value, boolean isSelected) {
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme());
// Panel background
panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor());
// TODO: Handle message attachments
final String state = value.getStatus().toString();
final String date = new SimpleDateFormat("dd.MM.yyyy HH:mm").format(value.getCreationDate());
final String text = value.getText();
// The Label that displays the creation date of a message
JLabel dateLabel = new JLabel(date);
// Set the date color to be the value of DateColorChat
dateLabel.setForeground(theme.getDateColor());
panel.add(dateLabel, BorderLayout.NORTH);
// The JTextArea that displays the text content of a message and its status
messageTextArea = new JTextArea(text + System.getProperty("line.separator"));
messageTextArea.setLineWrap(true);
messageTextArea.setWrapStyleWord(true);
messageTextArea.setAlignmentX(0.5f);
messageTextArea.setForeground(theme.getMessageTextColor());
messageTextArea.setBackground(panel.getBackground());
messageTextArea.setEditable(false);
panel.add(messageTextArea, BorderLayout.CENTER);
JLabel statusLabel = new JLabel(state);
statusLabel.setFont(new Font("Arial", Font.BOLD, 14));
Color statusColor;
switch (value.getStatus()) {
case WAITING:
statusColor = Color.gray;
break;
case SENT:
statusColor = Color.blue;
break;
case RECEIVED:
statusColor = Color.yellow;
break;
case READ:
statusColor = Color.green;
break;
default:
statusColor = theme.getMessageTextColor();
break;
}
statusLabel.setForeground(statusColor);
statusLabel.setBackground(panel.getBackground());
panel.add(statusLabel, BorderLayout.SOUTH);
// Define some space to the messages below
panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(), BorderFactory.createEtchedBorder()));
return panel;
}
}

View File

@ -16,7 +16,7 @@ import envoy.exception.EnvoyException;
* Created: <strong>3 Dec 2019</strong><br> * Created: <strong>3 Dec 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class StatusTrayIcon { public class StatusTrayIcon {
@ -41,7 +41,7 @@ public class StatusTrayIcon {
* notifications are displayed * notifications are displayed
* @throws EnvoyException if the currently used OS does not support the System * @throws EnvoyException if the currently used OS does not support the System
* Tray API * Tray API
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public StatusTrayIcon(Window focusTarget) throws EnvoyException { public StatusTrayIcon(Window focusTarget) throws EnvoyException {
if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported."); if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
@ -85,7 +85,7 @@ public class StatusTrayIcon {
* *
* @throws EnvoyException if the status icon could not be attaches to the system * @throws EnvoyException if the status icon could not be attaches to the system
* tray for system-internal reasons * tray for system-internal reasons
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void show() throws EnvoyException { public void show() throws EnvoyException {
try { try {

37
src/main/java/envoy/client/ui/Theme.java Normal file → Executable file
View File

@ -10,7 +10,7 @@ import java.util.Map;
* Created: <strong>23 Nov 2019</strong><br> * Created: <strong>23 Nov 2019</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class Theme implements Serializable { public class Theme implements Serializable {
@ -29,15 +29,16 @@ public class Theme implements Serializable {
* elements * elements
* @param interactableBackgroundColor the color of interactable background UI * @param interactableBackgroundColor the color of interactable background UI
* elements * elements
* @param messageColorChat the color of chat messages * @param textColor the color normal text should be displayed
* in
* @param dateColorChat the color of chat message metadata * @param dateColorChat the color of chat message metadata
* @param selectionColor the section color * @param selectionColor the section color
* @param typingMessageColor the color of currently typed messages * @param typingMessageColor the color of currently typed messages
* @param userNameColor the color of user names * @param userNameColor the color of user names
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Theme(String themeName, Color backgroundColor, Color cellColor, Color interactableForegroundColor, Color interactableBackgroundColor, public Theme(String themeName, Color backgroundColor, Color cellColor, Color interactableForegroundColor, Color interactableBackgroundColor,
Color messageColorChat, Color dateColorChat, Color selectionColor, Color typingMessageColor, Color userNameColor) { Color textColor, Color dateColorChat, Color selectionColor, Color typingMessageColor, Color userNameColor) {
this.themeName = themeName; this.themeName = themeName;
@ -45,7 +46,7 @@ public class Theme implements Serializable {
colors.put("cellColor", cellColor); colors.put("cellColor", cellColor);
colors.put("interactableForegroundColor", interactableForegroundColor); colors.put("interactableForegroundColor", interactableForegroundColor);
colors.put("interactableBackgroundColor", interactableBackgroundColor); colors.put("interactableBackgroundColor", interactableBackgroundColor);
colors.put("messageColorChat", messageColorChat); colors.put("textColor", textColor);
colors.put("dateColorChat", dateColorChat); colors.put("dateColorChat", dateColorChat);
colors.put("selectionColor", selectionColor); colors.put("selectionColor", selectionColor);
colors.put("typingMessageColor", typingMessageColor); colors.put("typingMessageColor", typingMessageColor);
@ -58,7 +59,7 @@ public class Theme implements Serializable {
* *
* @param name the name of the {@link Theme} * @param name the name of the {@link Theme}
* @param other the {@link Theme} to copy * @param other the {@link Theme} to copy
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Theme(String name, Theme other) { public Theme(String name, Theme other) {
themeName = name; themeName = name;
@ -68,69 +69,69 @@ public class Theme implements Serializable {
/** /**
* @return a {@code Map<String, Color>} of all colors defined for this theme * @return a {@code Map<String, Color>} of all colors defined for this theme
* with their names as keys * with their names as keys
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Map<String, Color> getColors() { return colors; } public Map<String, Color> getColors() { return colors; }
/** /**
* @return name of the theme * @return name of the theme
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public String getThemeName() { return themeName; } public String getThemeName() { return themeName; }
/** /**
* @return interactableForegroundColor * @return interactableForegroundColor
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getInteractableForegroundColor() { return colors.get("interactableForegroundColor"); } public Color getInteractableForegroundColor() { return colors.get("interactableForegroundColor"); }
/** /**
* @return the {@link Color} in which the text content of a message should be * @return the {@link Color} in which the text content of a message should be
* displayed * displayed
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getMessageTextColor() { return colors.get("messageColorChat"); } public Color getTextColor() { return colors.get("textColor"); }
/** /**
* @return the {@link Color} in which the creation date of a message should be * @return the {@link Color} in which the creation date of a message should be
* displayed * displayed
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getDateColor() { return colors.get("dateColorChat"); } public Color getDateColor() { return colors.get("dateColorChat"); }
/** /**
* @return selectionColor * @return selectionColor
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getSelectionColor() { return colors.get("selectionColor"); } public Color getSelectionColor() { return colors.get("selectionColor"); }
/** /**
* @return typingMessageColor * @return typingMessageColor
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getTypingMessageColor() { return colors.get("typingMessageColor"); } public Color getTypingMessageColor() { return colors.get("typingMessageColor"); }
/** /**
* @return backgroundColor * @return backgroundColor
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getBackgroundColor() { return colors.get("backgroundColor"); } public Color getBackgroundColor() { return colors.get("backgroundColor"); }
/** /**
* @return cellColor * @return cellColor
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getCellColor() { return colors.get("cellColor"); } public Color getCellColor() { return colors.get("cellColor"); }
/** /**
* @return interactableBackgroundColor * @return interactableBackgroundColor
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getInteractableBackgroundColor() { return colors.get("interactableBackgroundColor"); } public Color getInteractableBackgroundColor() { return colors.get("interactableBackgroundColor"); }
/** /**
* @return userNameColor * @return userNameColor
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public Color getUserNameColor() { return colors.get("userNameColor"); } public Color getUserNameColor() { return colors.get("userNameColor"); }

View File

@ -1,8 +1,13 @@
package envoy.client.ui; package envoy.client.ui.container;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*; import java.awt.event.*;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -18,8 +23,16 @@ import envoy.client.event.MessageCreationEvent;
import envoy.client.event.ThemeChangeEvent; import envoy.client.event.ThemeChangeEvent;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.Theme;
import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.list.ComponentList.SelectionMode;
import envoy.client.ui.list.Model;
import envoy.client.ui.list_component.ContactSearchComponent;
import envoy.client.ui.list_component.MessageComponent;
import envoy.client.ui.primary.PrimaryButton;
import envoy.client.ui.primary.PrimaryScrollPane;
import envoy.client.ui.primary.PrimaryTextArea;
import envoy.client.ui.renderer.UserListRenderer;
import envoy.client.ui.settings.SettingsScreen; import envoy.client.ui.settings.SettingsScreen;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
@ -36,13 +49,12 @@ import envoy.util.EnvoyLog;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public class ChatWindow extends JFrame { public class ChatWindow extends JFrame {
/** /**
* This int defines the maximum amount of chars allowed per message. Currently * This integer defines the maximum amount of chars allowed per message.
* set at 200.
* *
* @since Envoy 0.1-beta * @since Envoy 0.1-beta
*/ */
@ -59,11 +71,12 @@ public class ChatWindow extends JFrame {
private PrimaryTextArea messageEnterTextArea = new PrimaryTextArea(space); private PrimaryTextArea messageEnterTextArea = new PrimaryTextArea(space);
private JList<User> userList = new JList<>(); private JList<User> userList = new JList<>();
private DefaultListModel<User> userListModel = new DefaultListModel<>(); private DefaultListModel<User> userListModel = new DefaultListModel<>();
private ComponentList<Message> messageList = new ComponentList<>(new MessageListRenderer()); private ComponentList<Message> messageList = new ComponentList<>();
private PrimaryScrollPane scrollPane = new PrimaryScrollPane(); private PrimaryScrollPane scrollPane = new PrimaryScrollPane();
private JTextPane textPane = new JTextPane(); private JTextPane textPane = new JTextPane();
private PrimaryButton postButton = new PrimaryButton("Post"); private PrimaryButton postButton = new PrimaryButton("Post");
private PrimaryButton settingsButton = new PrimaryButton("Settings"); private PrimaryButton settingsButton = new PrimaryButton("Settings");
private JPopupMenu contextMenu;
// Contacts Header // Contacts Header
private JPanel contactsHeader = new JPanel(); private JPanel contactsHeader = new JPanel();
@ -75,9 +88,8 @@ public class ChatWindow extends JFrame {
private final PrimaryButton cancelButton = new PrimaryButton("x"); private final PrimaryButton cancelButton = new PrimaryButton("x");
private final PrimaryTextArea searchField = new PrimaryTextArea(space); private final PrimaryTextArea searchField = new PrimaryTextArea(space);
private final PrimaryScrollPane scrollForPossibleContacts = new PrimaryScrollPane(); private final PrimaryScrollPane scrollForPossibleContacts = new PrimaryScrollPane();
private final ContactsSearchRenderer contactRenderer = new ContactsSearchRenderer(); private final Model<User> contactsModel = new Model<>();
private final ComponentListModel<User> contactsModel = new ComponentListModel<>(); private final ComponentList<User> contactList = new ComponentList<User>().setRenderer(ContactSearchComponent::new);
private final ComponentList<User> contactList = new ComponentList<>(contactRenderer);
private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class); private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class);
@ -91,11 +103,12 @@ public class ChatWindow extends JFrame {
* Initializes a {@link JFrame} with UI elements used to send and read messages * Initializes a {@link JFrame} with UI elements used to send and read messages
* to different users. * to different users.
* *
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public ChatWindow() { public ChatWindow() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 800); setBounds(100, 100, 600, 800);
setMinimumSize(new Dimension(400, 300));
setTitle("Envoy"); setTitle("Envoy");
setLocationRelativeTo(null); setLocationRelativeTo(null);
setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png"))); setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png")));
@ -106,19 +119,46 @@ public class ChatWindow extends JFrame {
gbl_contentPane.columnWidths = new int[] { 1, 1, 1 }; gbl_contentPane.columnWidths = new int[] { 1, 1, 1 };
gbl_contentPane.rowHeights = new int[] { 1, 1, 1, 1 }; gbl_contentPane.rowHeights = new int[] { 1, 1, 1, 1 };
gbl_contentPane.columnWeights = new double[] { 0.03, 1.0, 0.1 }; gbl_contentPane.columnWeights = new double[] { 0.03, 1.0, 0.1 };
gbl_contentPane.rowWeights = new double[] { 0.03, 0.001, 1.0, 0.005 }; gbl_contentPane.rowWeights = new double[] { 0.03, 0.001, 1.0, 0.001 };
contentPane.setLayout(gbl_contentPane); contentPane.setLayout(gbl_contentPane);
messageList.setBorder(new EmptyBorder(space, space, space, space)); messageList.setBorder(new EmptyBorder(space, space, space, space));
messageList.setSelectionMode(SelectionMode.SINGLE);
messageList.setSelectionHandler((message, comp, isSelected) -> {
final var theme = Settings.getInstance().getCurrentTheme();
comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor());
// ContextMenu
Map<String, ActionListener> commands = Map.of("forward selected message", evt -> {
final Message selectedMessage = messageList.getSingleSelectedElement();
List<User> chosenContacts = ContactsChooserDialog
.showForwardingDialog("Forward selected message to", null, selectedMessage, localDb.getUsers().values());
if (chosenContacts != null && chosenContacts.size() > 0) forwardMessage(selectedMessage, chosenContacts.toArray(new User[0]));
}, "copy", evt -> {
// TODO should be enhanced to allow also copying of message attachments,
// especially pictures
StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText());
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy);
// TODO insert implementation to edit and delete messages
}, "delete", evt -> {}, "edit", evt -> {}, "quote", evt -> {});
if (isSelected) {
contextMenu = new ContextMenu(null, comp, commands, null, null).build();
contextMenu.show(comp, 0, 0);
}
});
scrollPane.setViewportView(messageList); scrollPane.setViewportView(messageList);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.addComponentListener(new ComponentAdapter() { scrollPane.addComponentListener(new ComponentAdapter() {
// updates list elements when list is resized // Update list elements when scroll pane (and thus list) is resized
@Override @Override
public void componentResized(ComponentEvent e) { messageList.synchronizeModel(); } public void componentResized(ComponentEvent e) {
messageList.setMaximumSize(new Dimension(scrollPane.getWidth(), Integer.MAX_VALUE));
messageList.synchronizeModel();
}
}); });
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
GridBagConstraints gbc_scrollPane = new GridBagConstraints(); GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH; gbc_scrollPane.fill = GridBagConstraints.BOTH;
@ -135,7 +175,10 @@ public class ChatWindow extends JFrame {
messageEnterTextArea.addInputMethodListener(new InputMethodListener() { messageEnterTextArea.addInputMethodListener(new InputMethodListener() {
@Override @Override
public void inputMethodTextChanged(InputMethodEvent event) { checkMessageTextLength(); } public void inputMethodTextChanged(InputMethodEvent event) {
checkMessageTextLength();
checkPostButton(messageEnterTextArea.getText());
}
@Override @Override
public void caretPositionChanged(InputMethodEvent event) {} public void caretPositionChanged(InputMethodEvent event) {}
@ -146,32 +189,30 @@ public class ChatWindow extends JFrame {
@Override @Override
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER if (e.getKeyCode() == KeyEvent.VK_ENTER
&& (Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0 || e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK)) && (Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0 || e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK)
&& postButton.isEnabled())
postMessage(); postMessage();
// Checking if text is too long // Checking if text is too long
checkMessageTextLength(); checkMessageTextLength();
checkPostButton(messageEnterTextArea.getText());
} }
}); });
GridBagConstraints gbc_scrollPaneForTextInput = new GridBagConstraints(); GridBagConstraints gbc_messageEnterTextArea = new GridBagConstraints();
gbc_scrollPaneForTextInput.fill = GridBagConstraints.BOTH; gbc_messageEnterTextArea.fill = GridBagConstraints.BOTH;
gbc_scrollPaneForTextInput.gridx = 1; gbc_messageEnterTextArea.gridx = 1;
gbc_scrollPaneForTextInput.gridy = 3; gbc_messageEnterTextArea.gridy = 3;
gbc_messageEnterTextArea.insets = insets;
gbc_scrollPaneForTextInput.insets = insets; contentPane.add(messageEnterTextArea, gbc_messageEnterTextArea);
contentPane.add(messageEnterTextArea, gbc_scrollPaneForTextInput);
// Post Button // Post Button
GridBagConstraints gbc_postButton = new GridBagConstraints(); GridBagConstraints gbc_postButton = new GridBagConstraints();
gbc_postButton.fill = GridBagConstraints.BOTH; gbc_postButton.fill = GridBagConstraints.BOTH;
gbc_postButton.gridx = 2; gbc_postButton.gridx = 2;
gbc_postButton.gridy = 3; gbc_postButton.gridy = 3;
gbc_postButton.insets = insets; gbc_postButton.insets = insets;
postButton.addActionListener((evt) -> { postMessage(); }); postButton.addActionListener((evt) -> { postMessage(); });
postButton.setEnabled(false);
contentPane.add(postButton, gbc_postButton); contentPane.add(postButton, gbc_postButton);
// Settings Button // Settings Button
@ -353,11 +394,11 @@ public class ChatWindow extends JFrame {
gbc_addContact.gridy = 0; gbc_addContact.gridy = 0;
gbc_addContact.insets = insets; gbc_addContact.insets = insets;
addContact.addActionListener((evt) -> { drawContactSearch(gbc_searchPane); }); addContact.addActionListener(evt -> drawContactSearch(gbc_searchPane));
contactsHeader.add(addContact, gbc_addContact); contactsHeader.add(addContact, gbc_addContact);
applyTheme(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); applyTheme(Settings.getInstance().getCurrentTheme());
contentPane.add(contactsHeader, gbc_contactsHeader); contentPane.add(contactsHeader, gbc_contactsHeader);
contentPane.revalidate(); contentPane.revalidate();
@ -440,15 +481,16 @@ public class ChatWindow extends JFrame {
* Used to immediately reload the {@link ChatWindow} when settings were changed. * Used to immediately reload the {@link ChatWindow} when settings were changed.
* *
* @param theme the theme to change colors into * @param theme the theme to change colors into
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
private void applyTheme(Theme theme) { private void applyTheme(Theme theme) {
// contentPane // contentPane
contentPane.setBackground(theme.getBackgroundColor()); contentPane.setBackground(theme.getBackgroundColor());
contentPane.setForeground(theme.getUserNameColor()); contentPane.setForeground(theme.getUserNameColor());
// messageList // messageList
messageList.setForeground(theme.getMessageTextColor()); messageList.setForeground(theme.getTextColor());
messageList.setBackground(theme.getCellColor()); messageList.setBackground(theme.getCellColor());
messageList.synchronizeModel();
// scrollPane // scrollPane
scrollPane.applyTheme(theme); scrollPane.applyTheme(theme);
scrollPane.autoscroll(); scrollPane.autoscroll();
@ -482,33 +524,68 @@ public class ChatWindow extends JFrame {
searchField.setForeground(theme.getUserNameColor()); searchField.setForeground(theme.getUserNameColor());
cancelButton.setBackground(theme.getInteractableBackgroundColor()); cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getInteractableForegroundColor()); cancelButton.setForeground(theme.getInteractableForegroundColor());
contactList.setForeground(theme.getMessageTextColor()); contactList.setForeground(theme.getTextColor());
contactList.setBackground(theme.getCellColor()); contactList.setBackground(theme.getCellColor());
scrollForPossibleContacts.applyTheme(theme); scrollForPossibleContacts.applyTheme(theme);
} }
/**
* Sends a new message to the server based on the text entered in the textArea.
*
* @since Envoy Client v0.1-beta
*/
private void postMessage() { private void postMessage() {
if (userList.isSelectionEmpty()) { if (userList.isSelectionEmpty()) {
JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
return; return;
} }
String text = messageEnterTextArea.getText().trim();
if (!text.isEmpty()) checkMessageTextLength();
if (!messageEnterTextArea.getText().isEmpty()) try {
checkMessageTextLength();
// Create message // Create message
final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator()) final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator())
.setText(messageEnterTextArea.getText()) .setText(text)
.build(); .build();
sendMessage(message);
// Clear text field
messageEnterTextArea.setText("");
postButton.setEnabled(false);
}
/**
* Forwards a message.
*
* @param message the message to forward
* @param recipient the new recipient of the message
* @since Envoy Client v0.1-beta
*/
private void forwardMessage(Message message, User... recipients) {
Arrays.stream(recipients).forEach(recipient -> {
if (message != null && recipients != null) sendMessage(new MessageBuilder(message, recipient.getId(), localDb.getIdGenerator()).build());
else throw new NullPointerException("No recipient or no message selected");
});
}
@SuppressWarnings("unused")
private void forwardMessages(Collection<Message> messages, User... recipients) {
messages.forEach(message -> { forwardMessage(message, recipients); });
}
/**
* Sends a {@link Message} to the server.
*
* @param message the message to send
* @since Envoy Client v0.1-beta
*/
private void sendMessage(final Message message) {
try {
// Send message // Send message
writeProxy.writeMessage(message); writeProxy.writeMessage(message);
// Add message to PersistentLocalDb and update UI // Add message to PersistentLocalDb and update UI
currentChat.appendMessage(message); currentChat.appendMessage(message);
// Clear text field
messageEnterTextArea.setText("");
// Update UI // Update UI
revalidate(); revalidate();
repaint(); repaint();
@ -525,7 +602,7 @@ public class ChatWindow extends JFrame {
private void readCurrentChat() { private void readCurrentChat() {
try { try {
currentChat.read(writeProxy); currentChat.read(writeProxy);
messageList.synchronizeModel(); if (messageList.getRenderer() != null) messageList.synchronizeModel();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
logger.log(Level.WARNING, "Couldn't notify server about message status change", e); logger.log(Level.WARNING, "Couldn't notify server about message status change", e);
@ -562,13 +639,15 @@ public class ChatWindow extends JFrame {
* @param writeProxy the write proxy used to send messages and status change * @param writeProxy the write proxy used to send messages and status change
* events to the server or cache them inside the local * events to the server or cache them inside the local
* database * database
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void initContent(Client client, LocalDb localDb, WriteProxy writeProxy) { public void initContent(Client client, LocalDb localDb, WriteProxy writeProxy) {
this.client = client; this.client = client;
this.localDb = localDb; this.localDb = localDb;
this.writeProxy = writeProxy; this.writeProxy = writeProxy;
messageList.setRenderer((list, message) -> new MessageComponent(list, message, client.getSender().getId()));
// Load users and chats // Load users and chats
new Thread(() -> { new Thread(() -> {
localDb.getUsers().values().forEach(user -> { localDb.getUsers().values().forEach(user -> {
@ -589,7 +668,7 @@ public class ChatWindow extends JFrame {
* {@link ChatWindow#MAX_MESSAGE_LENGTH} * {@link ChatWindow#MAX_MESSAGE_LENGTH}
* and splits the text into the allowed part, if that is the case. * and splits the text into the allowed part, if that is the case.
* *
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
private void checkMessageTextLength() { private void checkMessageTextLength() {
String input = messageEnterTextArea.getText(); String input = messageEnterTextArea.getText();
@ -603,4 +682,6 @@ public class ChatWindow extends JFrame {
JOptionPane.WARNING_MESSAGE); JOptionPane.WARNING_MESSAGE);
} }
} }
private void checkPostButton(String text) { postButton.setEnabled(!text.trim().isBlank()); }
} }

View File

@ -0,0 +1,148 @@
package envoy.client.ui.container;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import envoy.client.data.Settings;
import envoy.client.ui.Theme;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentList.SelectionMode;
import envoy.client.ui.list.Model;
import envoy.client.ui.list_component.UserComponent;
import envoy.data.Message;
import envoy.data.User;
/**
* This class defines a dialog to choose contacts from.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactsChooserDialog.java</strong><br>
* Created: <strong>15 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContactsChooserDialog extends JDialog {
private static final long serialVersionUID = -5774558118579032256L;
private ComponentList<User> contactList = new ComponentList<User>().setModel(new Model<User>())
.setRenderer((list, user) -> new UserComponent(user));
private JButton okButton = new JButton("Ok");
private JButton cancelButton = new JButton("Cancel");
private final Theme theme = Settings.getInstance().getCurrentTheme();
private final JPanel contentPanel = new JPanel();
/**
* Shows a modal contacts-chooser dialog and blocks until the
* dialog is hidden. If the user presses the "OK" button, then
* this method hides/disposes the dialog and returns the selected element (has
* yet
* to be casted back to its original type due to the limitations of Generics in
* Java).
* If the user presses the "Cancel" button or closes the dialog without
* pressing "OK", then this method disposes the dialog and returns an empty
* <code>ArrayList</code>.
*
* @param title the title of the dialog
* @param parent this @{@link Component} will be parsed to
* {@link java.awt.Window#setLocationRelativeTo(Component)} in
* order to change the location of the dialog
* @param message the {@link Message} to display on top of the Dialog
* @param users the users that should be displayed
* @return the selected Element (yet has to be casted to the wanted type due to
* the Generics limitations in Java)
* @since Envoy Client v0.1-beta
*/
public static List<User> showForwardingDialog(String title, Component parent, Message message, Collection<User> users) {
ContactsChooserDialog dialog = new ContactsChooserDialog(parent);
dialog.setTitle(title);
dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
dialog.addCancelButtonActionListener(e -> dialog.dispose());
List<User> results = new ArrayList<>();
dialog.addOkButtonActionListener(e -> {
results.addAll(dialog.getContactList().getSelectedElements());
dialog.dispose();
});
Model<User> contactListModel = dialog.getContactList().getModel();
users.forEach(contactListModel::add);
dialog.setModalityType(ModalityType.APPLICATION_MODAL);
dialog.setVisible(true);
return results;
}
/**
* @param parent this @{@link Component} will be parsed to
* {@link java.awt.Window#setLocationRelativeTo(Component)}
* @since Envoy Client v0.1-beta
*/
private ContactsChooserDialog(Component parent) {
contactList.setSelectionMode(SelectionMode.MULTIPLE);
contactList.setSelectionHandler((user, comp, isSelected) -> {
final var theme = Settings.getInstance().getCurrentTheme();
comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor());
});
setLocationRelativeTo(parent);
getContentPane().setLayout(new BorderLayout());
setBackground(theme.getBackgroundColor());
setForeground(theme.getTextColor());
setSize(400, 400);
contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
getContentPane().add(contentPanel, BorderLayout.CENTER);
contentPanel.setLayout(new BorderLayout(0, 0));
contentPanel.add(contactList, BorderLayout.CENTER);
{
JPanel buttonPane = new JPanel();
getContentPane().add(buttonPane, BorderLayout.SOUTH);
{
okButton = new JButton("OK");
okButton.setMnemonic(KeyEvent.VK_ENTER);
okButton.setActionCommand("OK");
buttonPane.setLayout(new BorderLayout(0, 0));
buttonPane.add(okButton, BorderLayout.EAST);
getRootPane().setDefaultButton(okButton);
}
{
cancelButton = new JButton("Cancel");
cancelButton.setActionCommand("Cancel");
buttonPane.add(cancelButton, BorderLayout.WEST);
}
}
applyTheme(Settings.getInstance().getCurrentTheme());
}
private void applyTheme(Theme theme) {
contentPanel.setBackground(theme.getBackgroundColor());
contentPanel.setForeground(theme.getTextColor());
contactList.setBackground(theme.getCellColor());
okButton.setBackground(theme.getInteractableBackgroundColor());
okButton.setForeground(theme.getTextColor());
cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getTextColor());
}
/**
* @return the underlying {@link ComponentList}
* @since Envoy Client v0.1-beta
*/
private ComponentList<User> getContactList() { return contactList; }
private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); }
private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); }
}

View File

@ -0,0 +1,255 @@
package envoy.client.ui.container;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.ui.Theme;
/**
* This class defines a menu that will be automatically called if
* {@link MouseEvent#isPopupTrigger()} returns true for the parent component.
* The user has the possibility to directly add actions to be performed when
* clicking on the element with the selected String. Additionally, for each
* element an {@link Icon} can be added, but it must not be.
* If the key(text) of an element starts with one of the predefined values, a
* special component will be called: either a {@link JRadioButtonMenuItem}, a
* {@link JCheckBoxMenuItem} or a {@link JMenu} will be created.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContextMenu.java</strong><br>
* Created: <strong>17 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContextMenu extends JPopupMenu {
private static final long serialVersionUID = 2177146471226992104L;
/**
* If a key starts with this String, a {@link JCheckBoxMenuItem} will be created
*/
public static final String checkboxMenuItem = "ChBoMI";
/**
* If a key starts with this String, a {@link JRadioButtonMenuItem} will be
* created
*/
public static final String radioButtonMenuItem = "RaBuMI";
/**
* If a key starts with this String, a {@link JMenu} will be created
*/
public static final String subMenuItem = "SubMI";
private Map<String, ActionListener> items = new HashMap<>();
private Map<String, Icon> icons = new HashMap<>();
private Map<String, Integer> mnemonics = new HashMap<>();
private ButtonGroup radioButtonGroup = new ButtonGroup();
private boolean built = false;
private boolean visible = false;
/**
* @param parent the component which will call this
* {@link ContextMenu}
* @since Envoy Client v0.1-beta
*/
public ContextMenu(Component parent) {
setInvoker(parent);
setOpaque(true);
}
/**
* @param label the string that a UI may use to display as a title
* for the pop-up menu
* @param parent the component which will call this
* {@link ContextMenu}
* @param itemsWithActions a map of all strings to be displayed with according
* actions
* @param itemIcons the icons to be displayed before a name, if wanted.
* Only keys in here will have an Icon displayed. More
* precisely, all keys here not included in the first
* map will be thrown out.
* @param itemMnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the
* given text
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label, Component parent, Map<String, ActionListener> itemsWithActions, Map<String, Icon> itemIcons,
Map<String, Integer> itemMnemonics) {
this(label);
setInvoker(parent);
this.items = (itemsWithActions != null) ? itemsWithActions : items;
this.icons = (itemIcons != null) ? itemIcons : icons;
this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics;
}
/**
* @param label the string that a UI may use to display as a title for the
* pop-up menu.
* @since Envoy Client v0.1-beta
*/
public ContextMenu(String label) {
super(label);
setOpaque(true);
}
/**
* Prepares the PopupMenu to be displayed. Should only be used once all map
* values have been set.
*
* @return this instance of {@link ContextMenu} to allow chaining behind the
* constructor
* @since Envoy Client v0.1-beta
*/
public ContextMenu build() {
items.forEach((text, action) -> {
// case radio button wanted
AbstractButton item;
if (text.startsWith(radioButtonMenuItem)) {
item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
radioButtonGroup.add(item);
// case check box wanted
} else if (text.startsWith(checkboxMenuItem))
item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null);
// case sub-menu wanted
else if (text.startsWith(subMenuItem)) item = new JMenu(text.substring(subMenuItem.length()));
else // normal JMenuItem wanted
item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null);
item.addActionListener(action);
item.setOpaque(true);
if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text));
add(item);
});
getInvoker().addMouseListener(getShowingListener());
applyTheme(Settings.getInstance().getCurrentTheme());
built = true;
return this;
}
private MouseAdapter getShowingListener() {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { action(e); }
@Override
public void mousePressed(MouseEvent e) { action(e); }
@Override
public void mouseReleased(MouseEvent e) { action(e); }
private void action(MouseEvent e) {
if (!built) build();
if (e.isPopupTrigger()) {
// hides the menu if already visible
visible = !visible;
if (visible) show(e.getComponent(), e.getX(), e.getY());
else setVisible(false);
}
}
};
}
/**
* Removes all subcomponents of this menu.
*
* @since Envoy Client v0.1-beta
*/
public void clear() {
removeAll();
items = new HashMap<>();
icons = new HashMap<>();
mnemonics = new HashMap<>();
}
/**
* @return the items
* @since Envoy Client v0.1-beta
*/
public Map<String, ActionListener> getItems() { return items; }
/**
* @param items the items with the displayed text and the according action to
* take once called
* @since Envoy Client v0.1-beta
*/
public void setItems(Map<String, ActionListener> items) { this.items = items; }
/**
* @return the icons
* @since Envoy Client v0.1-beta
*/
public Map<String, Icon> getIcons() { return icons; }
/**
* @param icons the icons to set
* @since Envoy Client v0.1-beta
*/
public void setIcons(Map<String, Icon> icons) { this.icons = icons; }
/**
* @return the mnemonics (the keyboard shortcuts that automatically execute the
* command for a {@link JMenuItem} with corresponding text)
* @since Envoy Client v0.1-beta
*/
public Map<String, Integer> getMnemonics() { return mnemonics; }
/**
* @param mnemonics the keyboard shortcuts that need to be pressed to
* automatically execute the {@link JMenuItem} with the given
* text
* @since Envoy Client v0.1-beta
*/
public void setMnemonics(Map<String, Integer> mnemonics) { this.mnemonics = mnemonics; }
/**
* {@inheritDoc}<br>
* Additionally sets the foreground of all subcomponents of this
* {@link ContextMenu}.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void setForeground(Color color) {
super.setForeground(color);
for (MenuElement element : getSubElements())
((Component) element).setForeground(color);
}
/**
* {@inheritDoc}<br>
* Additionally sets the background of all subcomponents of this
* {@link ContextMenu}.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void setBackground(Color color) {
super.setBackground(color);
for (MenuElement element : getSubElements())
((Component) element).setBackground(color);
}
/**
* Sets the fore- and background of all elements contained in this
* {@link ContextMenu}
* This method is to be only used by Envoy as {@link Theme} is an
* Envoy-exclusive object.
*
* @param theme the theme to use
* @since Envoy Client v0.1-beta
*/
protected void applyTheme(Theme theme) {
setBackground(theme.getCellColor());
setForeground(theme.getTextColor());
}
}

View File

@ -1,4 +1,4 @@
package envoy.client.ui; package envoy.client.ui.container;
import java.awt.*; import java.awt.*;
import java.awt.event.ItemEvent; import java.awt.event.ItemEvent;
@ -16,6 +16,8 @@ import javax.swing.border.EmptyBorder;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.HandshakeSuccessfulEvent; import envoy.client.event.HandshakeSuccessfulEvent;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.data.LoginCredentials; import envoy.data.LoginCredentials;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.User; import envoy.data.User;
@ -31,7 +33,7 @@ import envoy.util.EnvoyLog;
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class LoginDialog extends JDialog { public class LoginDialog extends JDialog {
@ -72,7 +74,7 @@ public class LoginDialog extends JDialog {
* @param localDb the local database in which data is persisted * @param localDb the local database in which data is persisted
* @param receivedMessageCache the cache that stored messages received during * @param receivedMessageCache the cache that stored messages received during
* the handshake * the handshake
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public LoginDialog(Client client, LocalDb localDb, Cache<Message> receivedMessageCache) { public LoginDialog(Client client, LocalDb localDb, Cache<Message> receivedMessageCache) {
this.client = client; this.client = client;
@ -281,7 +283,7 @@ public class LoginDialog extends JDialog {
/** /**
* Resets the text stored in the password fields. * Resets the text stored in the password fields.
* *
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
private void clearPasswordFields() { private void clearPasswordFields() {
passwordField.setText(null); passwordField.setText(null);
@ -289,7 +291,7 @@ public class LoginDialog extends JDialog {
} }
private void setTheme() { private void setTheme() {
Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); Theme theme = Settings.getInstance().getCurrentTheme();
// Panels // Panels
contentPanel.setBackground(theme.getBackgroundColor()); contentPanel.setBackground(theme.getBackgroundColor());
@ -335,7 +337,7 @@ public class LoginDialog extends JDialog {
/** /**
* Shuts the system down properly if the login was aborted. * Shuts the system down properly if the login was aborted.
* *
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
private void abortLogin() { private void abortLogin() {
logger.info("The login process has been cancelled. Exiting..."); logger.info("The login process has been cancelled. Exiting...");

View File

@ -0,0 +1,13 @@
/**
* This package contains all graphical Containers, like Dialogs and Frames.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>16 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.container;

View File

@ -3,12 +3,14 @@ package envoy.client.ui.list;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.util.HashSet;
import java.util.Set;
import javax.swing.*; import javax.swing.*;
/** /**
* Provides a vertical list layout of components provided in a * Provides a vertical list layout of components provided in a
* {@link ComponentListModel}. Similar to {@link javax.swing.JList} but capable * {@link Model}. Similar to {@link javax.swing.JList} but capable
* of rendering {@link JPanel}s.<br> * of rendering {@link JPanel}s.<br>
* <br> * <br>
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -17,141 +19,90 @@ import javax.swing.*;
* *
* @param <E> the type of object displayed in this list * @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class ComponentList<E> extends JPanel { public class ComponentList<E> extends JPanel {
private ComponentListModel<E> model; private Model<E> model;
private ComponentListCellRenderer<E> renderer; private Renderer<E> renderer;
private SelectionHandler<E> selectionHandler;
private int currentSelection = -1; private SelectionMode selectionMode = SelectionMode.NONE;
private Set<Integer> selection = new HashSet<>();
private static final long serialVersionUID = 1759644503942876737L; private static final long serialVersionUID = 1759644503942876737L;
/** /**
* Creates an instance of {@link ComponentList}. * Defines the possible modes of selection that can be performed by the user
* *
* @param renderer the list cell renderer used to display elements provided by * @since Envoy Client v0.1-beta
* the {@link ComponentListModel}
* @since Envoy v0.3-alpha
*/ */
public ComponentList(ComponentListCellRenderer<E> renderer) { public static enum SelectionMode {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); /**
this.renderer = renderer; * Selection is completely ignored.
*/
NONE,
/**
* Only a single element can be selected.
*/
SINGLE,
/**
* Multiple elements can be selected regardless of their position.
*/
MULTIPLE
} }
/** /**
* Creates an instance of {@link ComponentList}. * Creates an instance of {@link ComponentList}.
* *
* @param model the list model providing the list elements to render * @since Envoy Client v0.3-alpha
* @param renderer the list cell renderer used to display elements provided by
* the {@link ComponentListModel}
* @since Envoy v0.3-alpha
*/ */
public ComponentList(ComponentListModel<E> model, ComponentListCellRenderer<E> renderer) { public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); }
this(renderer);
this.model = model;
setModel(model);
}
/**
* Sets the list model providing the list elements to render. The rendered
* components will be synchronized with the contents of the new model or removed
* if the new model is {@code null}.
*
* @param model the list model to set
* @since Envoy v0.3-alpha
*/
public void setModel(ComponentListModel<E> model) {
// Remove old model
if (this.model != null) this.model.setComponentList(null);
// Synchronize with new model
this.model = model;
if (model != null) this.model.setComponentList(this);
synchronizeModel();
}
/** /**
* Removes all child components and then adds all components representing the * Removes all child components and then adds all components representing the
* elements of the {@link ComponentListModel}. * elements of the {@link Model}.
* *
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void synchronizeModel() { public void synchronizeModel() {
if (model != null) {
removeAll(); removeAll();
if (model != null) model.forEach(this::add); model.forEach(this::addElement);
revalidate(); revalidate();
} }
/**
* Adds an object to the list by rendering it with the current
* {@link ComponentListCellRenderer}.
*
* @param elem the element to add
* @since Envoy v0.3-alpha
*/
void add(E elem) { add(elem, getComponentCount(), false); }
/**
* Adds an object to the list by rendering it with the current
* {@link ComponentListRenderer}.
*
* @param elem the element to add
* @param index the index at which to add the element
* @param isSelected the selection state of the element
* @since Envoy v0.1-beta
*/
private void add(E elem, int index, boolean isSelected) {
final JComponent component = renderer.getListCellComponent(this, elem, isSelected);
component.addMouseListener(getSelectionListener(index));
add(component, index);
} }
/** /**
* @param componentIndex the index of the list component to which the mouse * Selects a list element by index. If the element is already selected, it is
* listener will be added * removed from the selection.
* @return a mouse listener calling the
* {@link ComponentList#componentSelected(int)} method with the
* component's index when a left click is performed by the user
* @since Envoy v0.1-beta
*/
private MouseListener getSelectionListener(int componentIndex) {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) componentSelected(componentIndex); }
};
}
/**
* Gets called when a list component has been clicked on by the user. Any
* previous selections are then removed and the selected component gets
* redrawn.<br>
* <br>
* If the currently selected component gets selected again, the selection is
* removed.
* *
* @param index the index of the selected component * @param index the index of the selected component
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
private void componentSelected(int index) { public void selectElement(int index) {
if (index == currentSelection) { final JComponent element = getComponent(index);
if (selection.contains(index)) {
// Deselect if clicked again
if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true);
selection.remove(index);
// Clear selection
update(currentSelection, false);
currentSelection = -1;
} else { } else {
// Remove old selection // Remove old selection if single selection is enabled
if (currentSelection >= 0) update(currentSelection, false); if (selectionMode == SelectionMode.SINGLE) clearSelection();
// Select item
if (selectionMode != SelectionMode.NONE) {
// Assign new selection // Assign new selection
currentSelection = index; selection.add(index);
// Update current selection // Update element
update(currentSelection, true); if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true);
}
} }
revalidate(); revalidate();
@ -159,14 +110,152 @@ public class ComponentList<E> extends JPanel {
} }
/** /**
* Replaces a list element with a newly rendered instance of its contents. * Removes the current selection.
* *
* @param index the index of the element to update * @since Envoy Client v0.1-alpha
* @param isSelected the selection state passed to the {@link ListCellRenderer}
* @since Envoy v0.1-beta
*/ */
private void update(int index, boolean isSelected) { public void clearSelection() {
remove(index); if (selectionHandler != null) selection.forEach(i -> selectionHandler.selectionChanged(model.get(i), getComponent(i), false));
add(model.get(index), index, isSelected); selection.clear();
} }
/**
* Adds an object to the list by rendering it with the current
* {@link Renderer}.
*
* @param elem the element to add
* @since Envoy Client v0.3-alpha
*/
void addElement(E elem) {
if (renderer != null) {
final JComponent component = renderer.getListCellComponent(this, elem);
component.addMouseListener(getSelectionListener(getComponentCount()));
add(component, getComponentCount());
}
}
/**
* @param componentIndex the index of the list component to which the mouse
* listener will be added
* @return a mouse listener calling the
* {@link ComponentList#selectElement(int)} method with the
* component's index when a left click is performed by the user
* @since Envoy Client v0.1-beta
*/
private MouseListener getSelectionListener(int componentIndex) {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) selectElement(componentIndex); }
};
}
@Override
public JComponent getComponent(int n) { return (JComponent) super.getComponent(n); }
/**
* @return a set of all selected indices
* @since Envoy Client v0.1-beta
*/
public Set<Integer> getSelection() { return selection; }
/**
* @return a set of all selected elements
* @since Envoy Client v0.1-beta
*/
public Set<E> getSelectedElements() {
var selectedElements = new HashSet<E>();
selection.forEach(i -> selectedElements.add(model.get(i)));
return selectedElements;
}
/**
* @return the index of an arbitrary selected element
* @throws java.util.NoSuchElementException if no selection is present
* @since Envoy Client v0.1-beta
*/
public int getSingleSelection() { return selection.stream().findAny().get(); }
/**
* @return an arbitrary selected element
* @throws java.util.NoSuchElementException if no selection is present
* @since Envoy Client v0.1-beta
*/
public E getSingleSelectedElement() { return model.get(getSingleSelection()); }
/**
* @return the model
* @since Envoy Client v0.1-beta
*/
public Model<E> getModel() { return model; }
/**
* Sets the list model providing the list elements to render. The rendered
* components will be synchronized with the contents of the new model or removed
* if the new model is {@code null}.
*
* @param model the list model to set
* @return this component list
* @since Envoy Client v0.3-alpha
*/
public ComponentList<E> setModel(Model<E> model) {
// Remove old model
if (this.model != null) this.model.setComponentList(null);
// Synchronize with new model
this.model = model;
if (model != null) model.setComponentList(this);
synchronizeModel();
return this;
}
/**
* @return the renderer
* @since Envoy Client v0.1-beta
*/
public Renderer<E> getRenderer() { return renderer; }
/**
* @param renderer the renderer to set
* @return this component list
* @since Envoy Client v0.1-beta
*/
public ComponentList<E> setRenderer(Renderer<E> renderer) {
this.renderer = renderer;
return this;
}
/**
* @return the selection mode
* @since Envoy Client v0.1-beta
*/
public SelectionMode getSelectionMode() { return selectionMode; }
/**
* Sets a new selection mode. The current selection will be cleared during this
* action.
*
* @param selectionMode the selection mode to set
* @return this component list
* @since Envoy Client v0.1-beta
*/
public ComponentList<E> setSelectionMode(SelectionMode selectionMode) {
this.selectionMode = selectionMode;
clearSelection();
return this;
}
/**
* @return the selection handler
* @since Envoy Client v0.1-beta
*/
public SelectionHandler<E> getSelectionHandler() { return selectionHandler; }
/**
* @param selectionHandler the selection handler to set
* @since Envoy Client v0.1-beta
*/
public void setSelectionHandler(SelectionHandler<E> selectionHandler) { this.selectionHandler = selectionHandler; }
} }

View File

@ -9,19 +9,19 @@ import java.util.List;
* Stores objects that will be displayed in a {@link ComponentList}.<br> * Stores objects that will be displayed in a {@link ComponentList}.<br>
* <br> * <br>
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>ComponentListModel.java</strong><br> * File: <strong>Model.java</strong><br>
* Created: <strong>25.01.2020</strong><br> * Created: <strong>25.01.2020</strong><br>
* *
* @param <E> the type of object displayed in this list * @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public final class ComponentListModel<E> implements Iterable<E>, Serializable { public final class Model<E> implements Iterable<E>, Serializable {
private List<E> elements = new ArrayList<>(); private List<E> elements = new ArrayList<>();
transient private ComponentList<E> componentList; transient private ComponentList<E> componentList;
private static final long serialVersionUID = 4815005915255497331L; private static final long serialVersionUID = 0L;
/** /**
* Adds an element to this model and notifies the associated * Adds an element to this model and notifies the associated
@ -30,11 +30,11 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
* @param e the element to add * @param e the element to add
* @return {@code true} * @return {@code true}
* @see java.util.List#add(java.lang.Object) * @see java.util.List#add(java.lang.Object)
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public boolean add(E e) { public boolean add(E e) {
if (componentList != null) { if (componentList != null) {
componentList.add(e); componentList.addElement(e);
componentList.revalidate(); componentList.revalidate();
} }
return elements.add(e); return elements.add(e);
@ -45,7 +45,7 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
* {@link ComponentList}. * {@link ComponentList}.
* *
* @see java.util.List#clear() * @see java.util.List#clear()
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void clear() { public void clear() {
elements.clear(); elements.clear();
@ -56,7 +56,7 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
* @param index the index to retrieve the element from * @param index the index to retrieve the element from
* @return the element located at the index * @return the element located at the index
* @see java.util.List#get(int) * @see java.util.List#get(int)
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public E get(int index) { return elements.get(index); } public E get(int index) { return elements.get(index); }
@ -67,7 +67,7 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
* @param index the index of the element to remove * @param index the index of the element to remove
* @return the removed element * @return the removed element
* @see java.util.List#remove(int) * @see java.util.List#remove(int)
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public E remove(int index) { public E remove(int index) {
if (componentList != null) componentList.remove(index); if (componentList != null) componentList.remove(index);
@ -77,7 +77,7 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
/** /**
* @return the amount of elements in this list model * @return the amount of elements in this list model
* @see java.util.List#size() * @see java.util.List#size()
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public int size() { return elements.size(); } public int size() { return elements.size(); }
@ -90,7 +90,7 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
/** /**
* @return an iterator over the elements of this list model * @return an iterator over the elements of this list model
* @see java.util.List#iterator() * @see java.util.List#iterator()
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
@Override @Override
public Iterator<E> iterator() { public Iterator<E> iterator() {
@ -111,10 +111,10 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
* synchronization. * synchronization.
* *
* @param componentList the component list to set * @param componentList the component list to set
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
void setComponentList(ComponentList<E> componentList) { void setComponentList(ComponentList<E> componentList) {
this.componentList = componentList; this.componentList = componentList;
if (componentList != null) componentList.synchronizeModel(); if (componentList != null && componentList.getRenderer() != null) componentList.synchronizeModel();
} }
} }

View File

@ -7,14 +7,15 @@ import javax.swing.JComponent;
* that can be rendered.<br> * that can be rendered.<br>
* <br> * <br>
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>ComponentListCellRenderer.java</strong><br> * File: <strong>Renderer.java</strong><br>
* Created: <strong>25.01.2020</strong><br> * Created: <strong>25.01.2020</strong><br>
* *
* @param <E> the type of object displayed in this list * @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public interface ComponentListCellRenderer<E> { @FunctionalInterface
public interface Renderer<E> {
/** /**
* Provides a Swing component representing a list element. * Provides a Swing component representing a list element.
@ -24,7 +25,7 @@ public interface ComponentListCellRenderer<E> {
* @param isSelected {@code true} if the user has selected the list cell in * @param isSelected {@code true} if the user has selected the list cell in
* which the list element is rendered * which the list element is rendered
* @return the component representing the list element * @return the component representing the list element
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
JComponent getListCellComponent(ComponentList<? extends E> list, E value, boolean isSelected); JComponent getListCellComponent(ComponentList<? extends E> list, E value);
} }

View File

@ -0,0 +1,28 @@
package envoy.client.ui.list;
import javax.swing.JComponent;
/**
* Handles the selection of elements in a {@link ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>SelectionHandler.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @param <E> the type of the underlying {@link ComponentList}
* @since Envoy Client v0.1-beta
*/
@FunctionalInterface
public interface SelectionHandler<E> {
/**
* Notifies the handler about a selection.
*
* @param element the selected element
* @param component the selected component
* @param isSelected contains the selection state
* @since Envoy Client v0.1-beta
*/
void selectionChanged(E element, JComponent component, boolean isSelected);
}

View File

@ -5,6 +5,6 @@
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
package envoy.client.ui.list; package envoy.client.ui.list;

View File

@ -1,4 +1,4 @@
package envoy.client.ui; package envoy.client.ui.list_component;
import java.awt.Component; import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
@ -9,42 +9,40 @@ import javax.swing.*;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.event.SendEvent; import envoy.client.event.SendEvent;
import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentListCellRenderer; import envoy.client.ui.primary.PrimaryButton;
import envoy.data.User; import envoy.data.User;
import envoy.event.ContactOperationEvent; import envoy.event.ContactOperationEvent;
import envoy.event.EventBus; import envoy.event.EventBus;
/** /**
* Defines how a contact is displayed.<br> * Project: <strong>envoy-client</strong>
* <br> * File: <strong>ContactSearchComponent.java</strong>
* Project: <strong>envoy-client</strong><br> * Created: <strong>21.03.2020</strong>
* File: <strong>ContactsSearchRenderer.java</strong><br>
* Created: <strong>08.02.2020</strong><br>
* *
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.1-beta
*/ */
public class ContactsSearchRenderer implements ComponentListCellRenderer<User> { public class ContactSearchComponent extends JComponent {
@Override private static final long serialVersionUID = 3166795412575239455L;
public JComponent getListCellComponent(ComponentList<? extends User> list, User user, boolean isSelected) {
final JPanel panel = new JPanel(); /**
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); * @param list the {@link ComponentList} that is used to display search results
if (isSelected) { * @param user the {@link User} that appears as a search result
panel.setBackground(Color.DARK_GRAY); * @since Envoy Client v0.1-beta
panel.setForeground(Color.RED); */
} else { public ContactSearchComponent(ComponentList<? extends User> list, User user) {
panel.setBackground(list.getBackground()); setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
panel.setForeground(list.getForeground());
} setBackground(list.getBackground());
setForeground(list.getForeground());
JLabel display = new JLabel(user.getName()); JLabel display = new JLabel(user.getName());
display.setForeground(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageTextColor()); display.setForeground(Settings.getInstance().getCurrentTheme().getTextColor());
display.setAlignmentX(Component.LEFT_ALIGNMENT); display.setAlignmentX(Component.LEFT_ALIGNMENT);
display.setAlignmentY(Component.CENTER_ALIGNMENT); display.setAlignmentY(Component.CENTER_ALIGNMENT);
display.setFont(new Font("Arial", Font.PLAIN, 16)); display.setFont(new Font("Arial", Font.PLAIN, 16));
panel.add(display); add(display);
PrimaryButton add = new PrimaryButton("+"); PrimaryButton add = new PrimaryButton("+");
add.setFont(new Font("Arial", Font.PLAIN, 19)); add.setFont(new Font("Arial", Font.PLAIN, 19));
@ -61,17 +59,15 @@ public class ContactsSearchRenderer implements ComponentListCellRenderer<User> {
EventBus.getInstance().dispatch(new SendEvent(contactsOperationEvent)); EventBus.getInstance().dispatch(new SendEvent(contactsOperationEvent));
}); });
panel.add(add); add(add);
// Define some space to the messages below // Define some space to the messages below
panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder())); setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder()));
// Define a maximum height of 50px // Define a maximum height of 50px
Dimension size = new Dimension(435, 50); Dimension size = new Dimension(435, 50);
panel.setMaximumSize(size); setMaximumSize(size);
panel.setMinimumSize(size); setMinimumSize(size);
panel.setPreferredSize(size); setPreferredSize(size);
return panel;
} }
} }

View File

@ -0,0 +1,127 @@
package envoy.client.ui.list_component;
import java.awt.*;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.EnumMap;
import javax.swing.*;
import envoy.client.data.Chat;
import envoy.client.data.Settings;
import envoy.client.ui.Color;
import envoy.client.ui.IconUtil;
import envoy.client.ui.list.ComponentList;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
/**
* Project: <strong>envoy-client</strong>
* File: <strong>MessageComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageComponent extends JPanel {
private static final long serialVersionUID = 103920706139926996L;
private static EnumMap<MessageStatus, ImageIcon> statusIcons;
private static ImageIcon forwardIcon;
static {
try {
statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16);
forwardIcon = IconUtil.load("/icons/forward.png", 16);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param list the {@link ComponentList} that displays this {@link Chat}
* @param message the {@link Message} to display
* @param senderId the id of the {@link User} who sends messages from this
* account
* @since Envoy Client v0.1-beta
*/
public MessageComponent(ComponentList<? extends Message> list, Message message, long senderId) {
var width = list.getMaximumSize().width;
final var theme = Settings.getInstance().getCurrentTheme();
final int padding = (int) (width * 0.35);
GridBagLayout gbl_panel = new GridBagLayout();
gbl_panel.columnWidths = new int[] { 1, 1 };
gbl_panel.rowHeights = new int[] { 1, 1 };
gbl_panel.columnWeights = new double[] { 1, 1 };
gbl_panel.rowWeights = new double[] { 1, 1 };
setLayout(gbl_panel);
setBackground(theme.getCellColor());
// Date Label - The Label that displays the creation date of a message
var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate()));
dateLabel.setForeground(theme.getDateColor());
dateLabel.setAlignmentX(1f);
dateLabel.setFont(new Font("Arial", Font.PLAIN, 12));
dateLabel.setPreferredSize(dateLabel.getPreferredSize());
var gbc_dateLabel = new GridBagConstraints();
gbc_dateLabel.fill = GridBagConstraints.BOTH;
gbc_dateLabel.gridx = 0;
gbc_dateLabel.gridy = 0;
add(dateLabel, gbc_dateLabel);
// Message area - The JTextArea that displays the text content of a message.
var messageTextArea = new JTextArea(message.getText());
messageTextArea.setLineWrap(true);
messageTextArea.setWrapStyleWord(true);
messageTextArea.setForeground(theme.getTextColor());
messageTextArea.setAlignmentX(0.5f);
messageTextArea.setBackground(theme.getCellColor());
messageTextArea.setEditable(false);
var font = new Font("Arial", Font.PLAIN, 14);
messageTextArea.setFont(font);
messageTextArea.setSize(width - padding - 16, 10);
var gbc_messageTextArea = new GridBagConstraints();
gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL;
gbc_messageTextArea.gridx = 0;
gbc_messageTextArea.gridy = 1;
add(messageTextArea, gbc_messageTextArea);
// Status Label - displays the status of the message
var statusLabel = new JLabel(statusIcons.get(message.getStatus()));
var gbc_statusLabel = new GridBagConstraints();
gbc_statusLabel.gridx = 1;
gbc_statusLabel.gridy = 1;
add(statusLabel, gbc_statusLabel);
// Forwarding
if (message.isForwarded()) {
var forwardLabel = new JLabel("Forwarded", forwardIcon, SwingConstants.CENTER);
forwardLabel.setBackground(getBackground());
forwardLabel.setForeground(Color.lightGray);
var gbc_forwardLabel = new GridBagConstraints();
gbc_forwardLabel.fill = GridBagConstraints.BOTH;
gbc_forwardLabel.gridx = 1;
gbc_forwardLabel.gridy = 0;
add(forwardLabel, gbc_forwardLabel);
}
// Define an etched border and some space to the messages below
var ours = senderId == message.getSenderId();
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, ours ? padding : 10, 10, ours ? 0 : padding),
BorderFactory.createEtchedBorder()));
var size = new Dimension(width - 50, getPreferredSize().height);
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
}
}

View File

@ -0,0 +1,69 @@
package envoy.client.ui.list_component;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JLabel;
import javax.swing.JPanel;
import envoy.client.data.Settings;
import envoy.client.ui.Color;
import envoy.client.ui.Theme;
import envoy.data.User;
import envoy.data.User.UserStatus;
/**
* Displays a {@link User}.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>UserComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class UserComponent extends JPanel {
private static final long serialVersionUID = 8450602172939729585L;
/**
* @param user the {@link User} whose information is displayed
* @since Envoy Client v0.1-beta
*/
public UserComponent(User user) {
final Theme theme = Settings.getInstance().getCurrentTheme();
setLayout(new BorderLayout());
// Panel background
setBackground(theme.getCellColor());
setOpaque(true);
setPreferredSize(new Dimension(100, 35));
// TODO add profile picture support in BorderLayout.West
JLabel username = new JLabel(user.getName());
username.setForeground(theme.getUserNameColor());
add(username, BorderLayout.CENTER);
final UserStatus status = user.getStatus();
JLabel statusLabel = new JLabel(status.toString());
Color foreground;
switch (status) {
case AWAY:
foreground = Color.yellow;
break;
case BUSY:
foreground = Color.blue;
break;
case ONLINE:
foreground = Color.green;
break;
default:
foreground = Color.lightGray;
break;
}
statusLabel.setForeground(foreground);
add(statusLabel, BorderLayout.NORTH);
}
}

View File

@ -0,0 +1,14 @@
/**
* This package contains swing components that can be displayed by
* {@link envoy.client.ui.list.ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>21 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.list_component;

View File

@ -4,6 +4,6 @@
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
package envoy.client.ui; package envoy.client.ui;

View File

@ -1,4 +1,4 @@
package envoy.client.ui; package envoy.client.ui.primary;
import java.awt.Graphics; import java.awt.Graphics;
@ -11,7 +11,7 @@ import javax.swing.JButton;
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class PrimaryButton extends JButton { public class PrimaryButton extends JButton {

View File

@ -1,11 +1,6 @@
package envoy.client.ui; package envoy.client.ui.primary;
import java.awt.Color; import java.awt.*;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComponent; import javax.swing.JComponent;
@ -13,6 +8,7 @@ import javax.swing.JScrollBar;
import javax.swing.plaf.basic.BasicScrollBarUI; import javax.swing.plaf.basic.BasicScrollBarUI;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.ui.Theme;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -20,7 +16,7 @@ import envoy.client.data.Settings;
* Created: <strong>14.12.2019</strong><br> * Created: <strong>14.12.2019</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class PrimaryScrollBar extends BasicScrollBarUI { public class PrimaryScrollBar extends BasicScrollBarUI {
@ -97,11 +93,11 @@ public class PrimaryScrollBar extends BasicScrollBarUI {
g2.setPaint(color); g2.setPaint(color);
if (isVertical) { if (isVertical) {
g2.fillRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize); g2.fillRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize);
g2.setPaint(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getCellColor()); g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor());
g2.drawRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize); g2.drawRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize);
} else { } else {
g2.fillRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize); g2.fillRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize);
g2.setPaint(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getCellColor()); g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor());
g2.drawRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize); g2.drawRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize);
} }
g2.dispose(); g2.dispose();

View File

@ -1,7 +1,9 @@
package envoy.client.ui; package envoy.client.ui.primary;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import envoy.client.ui.Theme;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>PrimaryScrollPane.java</strong><br> * File: <strong>PrimaryScrollPane.java</strong><br>
@ -20,7 +22,7 @@ public class PrimaryScrollPane extends JScrollPane {
/** /**
* Initializes a {@link JScrollPane} with the primary Envoy design scheme * Initializes a {@link JScrollPane} with the primary Envoy design scheme
* *
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public PrimaryScrollPane() { setBorder(null); } public PrimaryScrollPane() { setBorder(null); }
@ -28,7 +30,7 @@ public class PrimaryScrollPane extends JScrollPane {
* Styles the vertical and horizontal scroll bars. * Styles the vertical and horizontal scroll bars.
* *
* @param theme the color set used to color the component * @param theme the color set used to color the component
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void applyTheme(Theme theme) { public void applyTheme(Theme theme) {
setForeground(theme.getBackgroundColor()); setForeground(theme.getBackgroundColor());
@ -52,7 +54,7 @@ public class PrimaryScrollPane extends JScrollPane {
* When rereading messages, the chat doesn't scroll down if new messages </br> * When rereading messages, the chat doesn't scroll down if new messages </br>
* are added. (Besides see first point) * are added. (Besides see first point)
* *
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void autoscroll() { public void autoscroll() {
// Automatic scrolling to the bottom // Automatic scrolling to the bottom
@ -77,7 +79,7 @@ public class PrimaryScrollPane extends JScrollPane {
* triggering it to automatically scroll down. * triggering it to automatically scroll down.
* *
* @param chatOpened indicates the chat opening status * @param chatOpened indicates the chat opening status
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void setChatOpened(boolean chatOpened) { this.chatOpened = chatOpened; } public void setChatOpened(boolean chatOpened) { this.chatOpened = chatOpened; }
} }

View File

@ -1,4 +1,4 @@
package envoy.client.ui; package envoy.client.ui.primary;
import java.awt.Font; import java.awt.Font;
import java.awt.Graphics; import java.awt.Graphics;
@ -12,7 +12,7 @@ import javax.swing.border.EmptyBorder;
* Created: <strong>07.12.2019</strong><br> * Created: <strong>07.12.2019</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class PrimaryTextArea extends JTextArea { public class PrimaryTextArea extends JTextArea {

View File

@ -1,4 +1,4 @@
package envoy.client.ui; package envoy.client.ui.primary;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
@ -7,6 +7,7 @@ import javax.swing.JButton;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.data.SettingsItem; import envoy.client.data.SettingsItem;
import envoy.client.ui.Color;
/** /**
* This component can be used to toggle between two options. This will change * This component can be used to toggle between two options. This will change
@ -18,7 +19,7 @@ import envoy.client.data.SettingsItem;
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class PrimaryToggleSwitch extends JButton { public class PrimaryToggleSwitch extends JButton {
@ -31,7 +32,7 @@ public class PrimaryToggleSwitch extends JButton {
* *
* @param settingsItem the {@link SettingsItem} that is controlled by this * @param settingsItem the {@link SettingsItem} that is controlled by this
* {@link PrimaryToggleSwitch} * {@link PrimaryToggleSwitch}
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public PrimaryToggleSwitch(SettingsItem<Boolean> settingsItem) { public PrimaryToggleSwitch(SettingsItem<Boolean> settingsItem) {
setPreferredSize(new Dimension(50, 25)); setPreferredSize(new Dimension(50, 25));
@ -51,7 +52,7 @@ public class PrimaryToggleSwitch extends JButton {
g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY); g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY);
g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25); g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25);
g.setColor(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableBackgroundColor()); g.setColor(Settings.getInstance().getCurrentTheme().getInteractableBackgroundColor());
g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25); g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25);
} }
} }

View File

@ -0,0 +1,17 @@
/**
* This package defines all "primary" components that were defined specifically
* for the visual improvement of Envoy. However, they can still be used in
* general for other projects.<br>
* Primary elements are supposed to provide the main functionality of a UI
* component.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>14 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.primary;

View File

@ -1,4 +1,4 @@
package envoy.client.ui; package envoy.client.ui.renderer;
import java.awt.Component; import java.awt.Component;
import java.awt.Dimension; import java.awt.Dimension;
@ -20,7 +20,7 @@ import envoy.data.User.UserStatus;
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public class UserListRenderer extends JLabel implements ListCellRenderer<User> { public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
@ -46,7 +46,7 @@ public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
// Getting the UserNameColor of the current theme // Getting the UserNameColor of the current theme
String textColor = null; String textColor = null;
textColor = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor().toHex(); textColor = Settings.getInstance().getCurrentTheme().getUserNameColor().toHex();
switch (status) { switch (status) {
case ONLINE: case ONLINE:
setText(String setText(String

View File

@ -0,0 +1,14 @@
/**
* This package contains all Envoy-specific renderers for lists that store an
* arbitrary number of JComponents.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>14 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.renderer;

View File

@ -3,7 +3,6 @@ package envoy.client.ui.settings;
import java.awt.GridBagConstraints; import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.awt.Insets; import java.awt.Insets;
import java.awt.event.ActionListener;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -23,7 +22,7 @@ import envoy.util.EnvoyLog;
* Created: <strong>21 Dec 2019</strong><br> * Created: <strong>21 Dec 2019</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class GeneralSettingsPanel extends SettingsPanel { public class GeneralSettingsPanel extends SettingsPanel {
@ -39,11 +38,11 @@ public class GeneralSettingsPanel extends SettingsPanel {
* *
* @param parent the {@link SettingsScreen} as a part of which this * @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed * {@link SettingsPanel} is displayed
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public GeneralSettingsPanel(SettingsScreen parent) { public GeneralSettingsPanel(SettingsScreen parent) {
super(parent); super(parent);
theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); theme = Settings.getInstance().getCurrentTheme();
setBackground(theme.getCellColor()); setBackground(theme.getCellColor());
@ -87,7 +86,4 @@ public class GeneralSettingsPanel extends SettingsPanel {
add(descriptionText, gbc_descriptionText); add(descriptionText, gbc_descriptionText);
} }
@Override
public ActionListener getOkButtonAction() { return evt -> {}; }
} }

View File

@ -8,9 +8,9 @@ import javax.swing.JPanel;
import javax.swing.JTextPane; import javax.swing.JTextPane;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.ui.PrimaryButton;
import envoy.client.ui.PrimaryTextArea;
import envoy.client.ui.Theme; import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.client.ui.primary.PrimaryTextArea;
/** /**
* Displays window where you can choose a name for the new {@link Theme}. * Displays window where you can choose a name for the new {@link Theme}.
@ -20,7 +20,7 @@ import envoy.client.ui.Theme;
* Created: <strong>26 Dec 2019</strong><br> * Created: <strong>26 Dec 2019</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public class NewThemeScreen extends JDialog { public class NewThemeScreen extends JDialog {
@ -43,10 +43,12 @@ public class NewThemeScreen extends JDialog {
* There are two versions of this Window. The first one is responsible for * There are two versions of this Window. The first one is responsible for
* choosing the name, the second one appears, if the name already exists. * choosing the name, the second one appears, if the name already exists.
* *
* @param parent the dialog is launched with its location relative to this {@link SettingsScreen} * @param parent the dialog is launched with its location relative to
* this {@link SettingsScreen}
* @param newThemeAction is executed when a new theme name is entered * @param newThemeAction is executed when a new theme name is entered
* @param modifyThemeAction is executed when an existing theme name is entered and confirmed * @param modifyThemeAction is executed when an existing theme name is entered
* @since Envoy v0.3-alpha * and confirmed
* @since Envoy Client v0.3-alpha
*/ */
public NewThemeScreen(SettingsScreen parent, Consumer<String> newThemeAction, Consumer<String> modifyThemeAction) { public NewThemeScreen(SettingsScreen parent, Consumer<String> newThemeAction, Consumer<String> modifyThemeAction) {
this.newThemeAction = newThemeAction; this.newThemeAction = newThemeAction;
@ -59,7 +61,7 @@ public class NewThemeScreen extends JDialog {
setDimensions(true); setDimensions(true);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); Theme theme = Settings.getInstance().getCurrentTheme();
getContentPane().setLayout(new BorderLayout()); getContentPane().setLayout(new BorderLayout());
standardPanel.setBackground(theme.getBackgroundColor()); standardPanel.setBackground(theme.getBackgroundColor());

View File

@ -1,7 +1,5 @@
package envoy.client.ui.settings; package envoy.client.ui.settings;
import java.awt.event.ActionListener;
import javax.swing.JPanel; import javax.swing.JPanel;
/** /**
@ -14,7 +12,7 @@ import javax.swing.JPanel;
* Created: <strong>20 Dec 2019</strong><br> * Created: <strong>20 Dec 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public abstract class SettingsPanel extends JPanel { public abstract class SettingsPanel extends JPanel {
@ -29,11 +27,4 @@ public abstract class SettingsPanel extends JPanel {
* {@link SettingsPanel} is displayed * {@link SettingsPanel} is displayed
*/ */
public SettingsPanel(SettingsScreen parent) { this.parent = parent; } public SettingsPanel(SettingsScreen parent) { this.parent = parent; }
/**
* @return an {@link ActionListener} that should be invoked when the OK button
* is pressed in the {@link SettingsScreen}
* @since Envoy v0.2-alpha
*/
public abstract ActionListener getOkButtonAction();
} }

View File

@ -11,8 +11,8 @@ import javax.swing.*;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent; import envoy.client.event.ThemeChangeEvent;
import envoy.client.ui.PrimaryButton;
import envoy.client.ui.Theme; import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.event.EventBus; import envoy.event.EventBus;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
@ -26,7 +26,7 @@ import envoy.util.EnvoyLog;
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class SettingsScreen extends JDialog { public class SettingsScreen extends JDialog {
@ -40,7 +40,6 @@ public class SettingsScreen extends JDialog {
// OK and cancel buttons // OK and cancel buttons
private final JPanel buttonPane = new JPanel(); private final JPanel buttonPane = new JPanel();
private final PrimaryButton okButton = new PrimaryButton("Save");
private final PrimaryButton cancelButton = new PrimaryButton("Cancel"); private final PrimaryButton cancelButton = new PrimaryButton("Cancel");
private final Insets insets = new Insets(5, 5, 5, 5); private final Insets insets = new Insets(5, 5, 5, 5);
@ -52,7 +51,7 @@ public class SettingsScreen extends JDialog {
/** /**
* Initializes the settings screen. * Initializes the settings screen.
* *
* @since Envoy v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public SettingsScreen() { public SettingsScreen() {
// Initialize settings pages // Initialize settings pages
@ -138,25 +137,10 @@ public class SettingsScreen extends JDialog {
cancelButton.addActionListener((evt) -> { dispose(); }); cancelButton.addActionListener((evt) -> { dispose(); });
} }
{
okButton.setActionCommand("OK");
okButton.setBorderPainted(false);
GridBagConstraints gbc_okButton = new GridBagConstraints();
gbc_okButton.anchor = GridBagConstraints.NORTHEAST;
gbc_okButton.fill = GridBagConstraints.EAST;
gbc_okButton.insets = insets;
gbc_okButton.gridx = 2;
gbc_okButton.gridy = 0;
buttonPane.add(okButton, gbc_okButton);
getRootPane().setDefaultButton(okButton);
// Invoke settings panel action on button press
okButton.addActionListener((evt) -> { if (settingsPanel != null) settingsPanel.getOkButtonAction().actionPerformed(evt); });
}
} }
// Apply current theme // Apply current theme
applyTheme(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); applyTheme(Settings.getInstance().getCurrentTheme());
// Respond to theme changes // Respond to theme changes
EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get())); EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get()));
@ -179,10 +163,6 @@ public class SettingsScreen extends JDialog {
cancelButton.setBackground(theme.getInteractableBackgroundColor()); cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getInteractableForegroundColor()); cancelButton.setForeground(theme.getInteractableForegroundColor());
// okButton
okButton.setBackground(theme.getInteractableBackgroundColor());
okButton.setForeground(theme.getInteractableForegroundColor());
// options // options
options.setSelectionForeground(theme.getUserNameColor()); options.setSelectionForeground(theme.getUserNameColor());
options.setSelectionBackground(theme.getSelectionColor()); options.setSelectionBackground(theme.getSelectionColor());

View File

@ -1,9 +1,6 @@
package envoy.client.ui.settings; package envoy.client.ui.settings;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -13,6 +10,7 @@ import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent; import envoy.client.event.ThemeChangeEvent;
import envoy.client.ui.Color; import envoy.client.ui.Color;
import envoy.client.ui.Theme; import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.event.EventBus; import envoy.event.EventBus;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
@ -26,18 +24,18 @@ import envoy.util.EnvoyLog;
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public class ThemeCustomizationPanel extends SettingsPanel { public class ThemeCustomizationPanel extends SettingsPanel {
private JPanel colorsPanel = new JPanel(); private JPanel colorsPanel = new JPanel();
private DefaultComboBoxModel<String> themesModel = new DefaultComboBoxModel<>( private DefaultComboBoxModel<String> themesModel;
Settings.getInstance().getThemes().keySet().toArray(new String[0])); private JComboBox<String> themes;
private JComboBox<String> themes = new JComboBox<>(themesModel);
private Theme temporaryTheme; private Theme temporaryTheme;
private boolean themeChanged; private PrimaryButton createThemeButton = new PrimaryButton("Create Theme");
private boolean themeChanged;
private final Insets insets = new Insets(5, 5, 5, 5); private final Insets insets = new Insets(5, 5, 5, 5);
private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class); private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class);
@ -50,18 +48,28 @@ public class ThemeCustomizationPanel extends SettingsPanel {
* *
* @param parent the {@link SettingsScreen} as a part of which this * @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed * {@link SettingsPanel} is displayed
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public ThemeCustomizationPanel(SettingsScreen parent) { public ThemeCustomizationPanel(SettingsScreen parent) {
super(parent); super(parent);
temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getCurrentTheme());
var themeNames = Settings.getInstance().getThemes().keySet().toArray(new String[0]);
String currentThemeName = Settings.getInstance().getCurrentThemeName();
for (int i = 0; i < themeNames.length; i++)
if (currentThemeName.equals(themeNames[i])) {
themeNames[i] = themeNames[0];
themeNames[0] = currentThemeName;
break;
}
themesModel = new DefaultComboBoxModel<>(themeNames);
themes = new JComboBox<>(themesModel);
GridBagLayout gbl_themeLayout = new GridBagLayout(); GridBagLayout gbl_themeLayout = new GridBagLayout();
gbl_themeLayout.columnWidths = new int[] { 1, 1 }; gbl_themeLayout.columnWidths = new int[] { 1, 1 };
gbl_themeLayout.rowHeights = new int[] { 1, 1 }; gbl_themeLayout.rowHeights = new int[] { 1, 1, 1 };
gbl_themeLayout.columnWeights = new double[] { 1.0, 1.0 }; gbl_themeLayout.columnWeights = new double[] { 1.0, 1.0 };
gbl_themeLayout.rowWeights = new double[] { 0.01, 1.0 }; gbl_themeLayout.rowWeights = new double[] { 0.01, 1.0, 0.01 };
setLayout(gbl_themeLayout); setLayout(gbl_themeLayout);
@ -85,7 +93,7 @@ public class ThemeCustomizationPanel extends SettingsPanel {
colorsPanel.setLayout(gbl_colorCustomizations); colorsPanel.setLayout(gbl_colorCustomizations);
Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); Theme theme = Settings.getInstance().getCurrentTheme();
buildCustomizeElements(theme); buildCustomizeElements(theme);
GridBagConstraints gbc_colorsPanel = new GridBagConstraints(); GridBagConstraints gbc_colorsPanel = new GridBagConstraints();
@ -97,20 +105,47 @@ public class ThemeCustomizationPanel extends SettingsPanel {
gbc_colorsPanel.insets = insets; gbc_colorsPanel.insets = insets;
add(colorsPanel, gbc_colorsPanel); add(colorsPanel, gbc_colorsPanel);
createThemeButton.addActionListener((evt) -> {
if (themeChanged) {
new NewThemeScreen(parent, name -> {
// Create new theme
logger.log(Level.FINEST, name);
Settings.getInstance().addNewThemeToMap(new Theme(name, temporaryTheme));
// Add new theme name to combo box
themesModel.addElement(name);
// Select new theme name
themes.setSelectedIndex(themesModel.getSize() - 1);
}, name -> {
// Modify theme
Settings.getInstance().getThemes().replace(name, new Theme(name, temporaryTheme));
if (themes.getSelectedItem().equals(name))
EventBus.getInstance().dispatch(new ThemeChangeEvent(Settings.getInstance().getTheme(name)));
else themes.setSelectedItem(name);
}).setVisible(true);
themeChanged = false;
}
});
GridBagConstraints gbc_createThemeButton = new GridBagConstraints();
gbc_createThemeButton.fill = GridBagConstraints.HORIZONTAL;
gbc_createThemeButton.gridx = 0;
gbc_createThemeButton.gridy = 2;
gbc_createThemeButton.anchor = GridBagConstraints.CENTER;
gbc_createThemeButton.insets = insets;
add(createThemeButton, gbc_createThemeButton);
colorsPanel.setBackground(theme.getCellColor()); colorsPanel.setBackground(theme.getCellColor());
// Apply theme upon selection // Apply theme upon selection
themes.addItemListener(new ItemListener() { themes.addItemListener(e -> {
@Override
public void itemStateChanged(ItemEvent e) {
String selectedValue = (String) themes.getSelectedItem(); String selectedValue = (String) themes.getSelectedItem();
logger.log(Level.FINEST, "Selected theme: " + selectedValue); logger.log(Level.FINEST, "Selected theme: " + selectedValue);
final Theme currentTheme = Settings.getInstance().getTheme(selectedValue); final Theme currentTheme = Settings.getInstance().getTheme(selectedValue);
Settings.getInstance().setCurrentTheme(selectedValue); Settings.getInstance().setCurrentTheme(selectedValue);
EventBus.getInstance().dispatch(new ThemeChangeEvent(currentTheme)); EventBus.getInstance().dispatch(new ThemeChangeEvent(currentTheme));
}
}); });
// Apply current theme // Apply current theme
@ -126,39 +161,15 @@ public class ThemeCustomizationPanel extends SettingsPanel {
}); });
} }
@Override
public ActionListener getOkButtonAction() {
return (evt) -> {
if (themeChanged) {
new NewThemeScreen(parent, name -> {
// Create new theme
logger.log(Level.FINEST, name);
Settings.getInstance().addNewThemeToMap(new Theme(name, temporaryTheme));
// Add new theme name to combo box
themesModel.addElement(name);
// Select new theme name
themes.setSelectedIndex(themesModel.getSize() - 1);
}, name -> {
// Modify theme
Settings.getInstance().getThemes().replace(name, new Theme(name, temporaryTheme));
if (themes.getSelectedItem().equals(name)) {
EventBus.getInstance().dispatch(new ThemeChangeEvent(Settings.getInstance().getTheme(name)));
} else {
themes.setSelectedItem(name);
}
}).setVisible(true);
themeChanged = false;
}
};
}
private void applyTheme(Theme theme) { private void applyTheme(Theme theme) {
// themeContent // themeContent
setForeground(theme.getUserNameColor()); setForeground(theme.getUserNameColor());
setBackground(theme.getCellColor()); setBackground(theme.getCellColor());
// createThemeButton
createThemeButton.setForeground(theme.getInteractableForegroundColor());
createThemeButton.setBackground(theme.getInteractableBackgroundColor());
// themes // themes
themes.setBackground(theme.getInteractableBackgroundColor()); themes.setBackground(theme.getInteractableBackgroundColor());
themes.setForeground(theme.getInteractableForegroundColor()); themes.setForeground(theme.getInteractableForegroundColor());
@ -181,7 +192,7 @@ public class ThemeCustomizationPanel extends SettingsPanel {
buildCustomizeElement(theme, theme.getCellColor(), "Cells", "cellColor", 2); buildCustomizeElement(theme, theme.getCellColor(), "Cells", "cellColor", 2);
buildCustomizeElement(theme, theme.getInteractableForegroundColor(), "Interactable Foreground", "interactableForegroundColor", 3); buildCustomizeElement(theme, theme.getInteractableForegroundColor(), "Interactable Foreground", "interactableForegroundColor", 3);
buildCustomizeElement(theme, theme.getInteractableBackgroundColor(), "Interactable Background", "interactableBackgroundColor", 4); buildCustomizeElement(theme, theme.getInteractableBackgroundColor(), "Interactable Background", "interactableBackgroundColor", 4);
buildCustomizeElement(theme, theme.getMessageTextColor(), "Messages Chat", "messageColorChat", 5); buildCustomizeElement(theme, theme.getTextColor(), "Text Color", "textColor", 5);
buildCustomizeElement(theme, theme.getDateColor(), "Date Chat", "dateColorChat", 6); buildCustomizeElement(theme, theme.getDateColor(), "Date Chat", "dateColorChat", 6);
buildCustomizeElement(theme, theme.getSelectionColor(), "Selection", "selectionColor", 7); buildCustomizeElement(theme, theme.getSelectionColor(), "Selection", "selectionColor", 7);
buildCustomizeElement(theme, theme.getTypingMessageColor(), "Typing Message", "typingMessageColor", 8); buildCustomizeElement(theme, theme.getTypingMessageColor(), "Typing Message", "typingMessageColor", 8);

View File

@ -4,6 +4,6 @@
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
package envoy.client.ui.settings; package envoy.client.ui.settings;

View File

@ -5,7 +5,7 @@
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy v0.1-beta * @since Envoy Client v0.1-beta
*/ */
module envoy { module envoy {

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB