Merge branch 'develop' into f/themes

This commit is contained in:
delvh 2019-12-07 14:50:20 +01:00 committed by GitHub
commit 6679685b3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1388 additions and 878 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: CyB3RC0nN0R, delvh, DieGurke, derharry333
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: CyB3RC0nN0R, delvh, DieGurke
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -34,7 +34,5 @@
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.jboss.tools.jst.web.kb.kbnature</nature>
<nature>org.jboss.tools.cdi.core.cdinature</nature>
</natures>
</projectDescription>

View File

@ -1,8 +1,107 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

76
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at kske@outlook.de. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@ -1,5 +1,7 @@
package envoy.client;
import java.util.logging.Logger;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
@ -28,18 +30,20 @@ public class Client {
private Config config;
private User sender, recipient;
private static final Logger logger = Logger.getLogger(Client.class.getSimpleName());
public Client(Config config, String username) {
this.config = config;
sender = getUser(username);
System.out.println("ID: " + sender.getID());
logger.info("ID: " + sender.getID());
}
private <T, R> R post(String uri, T body, Class<R> responseBodyClass) {
javax.ws.rs.client.Client client = ClientBuilder.newClient();
WebTarget target = client.target(uri);
Response response = target.request().post(Entity.entity(body, "application/xml"));
R responseBody = response.readEntity(responseBodyClass);
javax.ws.rs.client.Client client = ClientBuilder.newClient();
WebTarget target = client.target(uri);
Response response = target.request().post(Entity.entity(body, "application/xml"));
R responseBody = response.readEntity(responseBodyClass);
response.close();
client.close();
@ -92,7 +96,7 @@ public class Client {
if (returnSenderSync.getUsers().size() == 1) {
returnSender = returnSenderSync.getUsers().get(0);
} else {
System.out.println("ERROR exiting...");
logger.warning("ERROR exiting...");
}
return returnSender;
@ -129,7 +133,7 @@ public class Client {
* Updating UserStatus of all users in LocalDB. (Server sends all users with
* their updated UserStatus to the client.) <br>
*
* @param userId the client who sends the sync
* @param userId the id of the {@link Client} who sends the {@link Sync}
* @param sync the sync object (yet to be converted from java class to sync.xml)
* @return a returnSync.xml file
* @since Envoy v0.1-alpha
@ -166,16 +170,16 @@ public class Client {
public User getRecipient() { return recipient; }
/**
* Sets the recipient.
* Sets the recipient.
*
* @param recipient - the recipient to set
* @since Envoy v0.1-alpha
*/
public void setRecipient(User recipient) { this.recipient = recipient; }
/**
/**
* @return true, if a recipient is selected
* @since Envoy v0.1-alpha
*/
public boolean hasRecipient() { return recipient != null; }
}

View File

@ -32,7 +32,7 @@ public class Config {
*
* @param properties a {@link Properties} object containing information about
* the server and port, as well as the path to the local
* database
* database and the synchronization timeout
* @since Envoy v0.1-alpha
*/
public void load(Properties properties) {
@ -65,8 +65,6 @@ public class Config {
localDB = new File(args[++i]);
break;
}
if (localDB == null) localDB = new File(".\\localDB");
if (syncTimeout == 0) syncTimeout = 1000;
}
/**
@ -116,7 +114,7 @@ public class Config {
/**
* Changes the default local database.
* Exclusively intended for development purposes.
*
*
* @param localDB the file containing the local database
* @since Envoy v0.1-alpha
**/

View File

@ -1,313 +1,295 @@
package envoy.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import envoy.exception.EnvoyException;
import envoy.schema.Message;
import envoy.schema.Message.Metadata.MessageState;
import envoy.schema.ObjectFactory;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>LocalDB.java</strong><br>
* Created: <strong>27.10.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class LocalDB {
private File localDB;
private User sender;
private List<Chat> chats = new ArrayList<>();
private ObjectFactory objectFactory = new ObjectFactory();
private DatatypeFactory datatypeFactory;
private Sync unreadMessagesSync = objectFactory.createSync();
private Sync sync = objectFactory.createSync();
private Sync readMessages = objectFactory.createSync();
/**
* Constructs an empty local database.
*
* @param sender the user that is logged in with this client
* @since Envoy v0.1-alpha
*/
public LocalDB(User sender) {
this.sender = sender;
try {
datatypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
e.printStackTrace();
}
}
/**
* Initializes the local database and fills it with values
* if the user has already sent or received messages.
*
* @param localDBDir the directory where we wish to save/load the database from.
* @throws EnvoyException if the directory selected is not an actual directory.
* @since Envoy v0.1-alpha
*/
package envoy.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import envoy.client.event.EventBus;
import envoy.client.event.MessageCreationEvent;
import envoy.exception.EnvoyException;
import envoy.schema.Message;
import envoy.schema.Message.Metadata.MessageState;
import envoy.schema.ObjectFactory;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>LocalDB.java</strong><br>
* Created: <strong>27.10.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class LocalDB {
private File localDB;
private User sender;
private List<Chat> chats = new ArrayList<>();
private ObjectFactory objectFactory = new ObjectFactory();
private DatatypeFactory datatypeFactory;
private Sync unreadMessagesSync = objectFactory.createSync();
private Sync sync = objectFactory.createSync();
private Sync readMessages = objectFactory.createSync();
private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName());
/**
* Constructs an empty local database.
*
* @param sender the user that is logged in with this client
* @since Envoy v0.1-alpha
*/
public LocalDB(User sender) {
this.sender = sender;
try {
datatypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
e.printStackTrace();
}
}
/**
* Initializes the local database and fills it with values
* if the user has already sent or received messages.
*
* @param localDBDir the directory where we wish to save/load the database from.
* @throws EnvoyException if the directory selected is not an actual directory.
* @since Envoy v0.1-alpha
*/
public void initializeDBFile(File localDBDir) throws EnvoyException {
if (localDBDir.exists() && !localDBDir.isDirectory()) throw new EnvoyException(
String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
if (localDBDir.exists() && !localDBDir.isDirectory())
throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
localDB = new File(localDBDir, sender.getID() + ".db");
if (localDB.exists()) loadFromLocalDB();
}
/**
* Saves the database into a file for future use.<br>
* It is theoretically possible to fail due to unknown causes.<br>
* In such a case, every message sent/ received during that session will be
* lost.
*
* @throws IOException gets thrown, if saving failed for some reason
* @since Envoy v0.1-alpha
*/
public void saveToLocalDB() throws IOException{
try {
localDB.getParentFile().mkdirs();
localDB.createNewFile();
} catch (IOException e) {
e.printStackTrace();
System.err.println("unable to save the messages");
}
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
out.writeObject(chats);
} catch (IOException ex) {
ex.printStackTrace();
System.err.println("unable to save the messages");
}
}
/**
* Loads all chats saved by Envoy for the client user.
*
* @throws EnvoyException if something fails while loading.
* @since Envoy v0.1-alpha
*/
@SuppressWarnings("unchecked")
private void loadFromLocalDB() throws EnvoyException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) {
Object obj = in.readObject();
if (obj instanceof ArrayList<?>) chats = (ArrayList<Chat>) obj;
} catch (ClassNotFoundException | IOException e) {
throw new EnvoyException(e);
}
}
/**
* Creates a {@link Message} object serializable to XML.
*
* @param textContent The content (text) of the message
* @param recipientID self explanatory
* @return prepared {@link Message} object
*/
public Message createMessage(String textContent, long recipientID) {
Message.Metadata metaData = objectFactory.createMessageMetadata();
metaData.setSender(sender.getID());
metaData.setRecipient(recipientID);
metaData.setState(MessageState.WAITING);
metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString()));
Message.Content content = objectFactory.createMessageContent();
content.setType("text");
content.setText(textContent);
Message message = objectFactory.createMessage();
message.setMetadata(metaData);
message.getContent().add(content);
return message;
}
public Sync fillSync(long userId) {
addWaitingMessagesToSync();
sync.getMessages().addAll(readMessages.getMessages());
readMessages.getMessages().clear();
System.out.println(sync.getMessages().size());
return sync;
}
public void applySync(Sync returnSync) {
for (int i = 0; i < returnSync.getMessages().size(); i++) {
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.SENT) {
// Update Local Messages with State WAITING (add Message ID and change State to
// SENT)
for (int j = 0; j < sync.getMessages().size(); j++) {
if (j == i) {
sync.getMessages().get(j).getMetadata().setMessageId(returnSync.getMessages().get(j).getMetadata().getMessageId());
sync.getMessages().get(j).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState());
}
}
}
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() != 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) {
// these are the unread Messages from the server
unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i));
}
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() == 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) {
// Update Messages in localDB to state RECEIVED
for (int j = 0; j < getChats().size(); j++) {
if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) {
if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId()) {
// Update Message in LocalDB
getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState());
}
}
}
}
}
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) {
// Update local Messages to state READ
System.out.println("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId()
+ "was initialized to be set to READ in localDB.");
for (int j = 0; j < getChats().size(); j++) {
if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
System.out.println("Chat with: " + getChats().get(j).getRecipient().getID() + "was selected.");
for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) {
if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId()) {
System.out.println(
"Message with ID: " + getChats().get(j).getModel().get(k).getMetadata().getMessageId() + "was selected.");
getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
System.out
.println("Message State is now: " + getChats().get(j).getModel().get(k).getMetadata().getState().toString());
}
}
}
}
}
}
// Updating UserStatus of all users in LocalDB
for (int j = 0; j < returnSync.getUsers().size(); j++) {
for (int k = 0; k < getChats().size(); k++) {
if (getChats().get(k).getRecipient().getID() == returnSync.getUsers().get(j).getID()) {
getChats().get(k).getRecipient().setStatus(returnSync.getUsers().get(j).getStatus());
System.out.println(getChats().get(k).getRecipient().getStatus().toString());
}
}
}
sync.getMessages().clear();
sync.getUsers().clear();
}
if (localDB.exists()) loadFromLocalDB();
}
/**
* Adds a message to the "sync" Sync object.
*
* @param message the message to send
* @since Envoy v0.1-alpha
*/
public void addMessageToSync(Message message) { sync.getMessages().add(message); }
* Saves the database into a file for future use.
*
* @throws IOException if something went wrong during saving
* @since Envoy v0.1-alpha
*/
public void saveToLocalDB() throws IOException {
localDB.getParentFile().mkdirs();
localDB.createNewFile();
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
out.writeObject(chats);
} catch (IOException ex) {
throw ex;
}
}
/**
* Adds the unread messages returned from the server in the latest sync to the
* right chats in the LocalDB.
*
* @since Envoy v0.1-alpha
*/
public void addUnreadMessagesToLocalDB() {
Sync unreadMessages = unreadMessagesSync;
for (int i = 0; i < unreadMessages.getMessages().size(); i++)
for (int j = 0; j < getChats().size(); j++)
if (getChats().get(j).getRecipient().getID() == unreadMessages.getMessages().get(i).getMetadata().getSender()) {
getChats().get(j).appendMessage(unreadMessages.getMessages().get(i));
}
}
* Loads all chats saved by Envoy for the client user.
*
* @throws EnvoyException if something fails while loading.
* @since Envoy v0.1-alpha
*/
@SuppressWarnings("unchecked")
private void loadFromLocalDB() throws EnvoyException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) {
Object obj = in.readObject();
if (obj instanceof ArrayList<?>) chats = (ArrayList<Chat>) obj;
} catch (ClassNotFoundException | IOException e) {
throw new EnvoyException(e);
}
}
/**
* Changes all messages with state {@code RECEIVED} of a specific chat to state
* {@code READ}.
* <br>
* Adds these messages to the {@code readMessages} {@link Sync} object.
*
* @param currentChat the chat that was just opened
* @since Envoy v0.1-alpha
*/
public void setMessagesToRead(Chat currentChat) {
for (int i = currentChat.getModel().size() - 1; i >= 0; --i)
if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID())
if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) {
currentChat.getModel().get(i).getMetadata().setState(MessageState.READ);
readMessages.getMessages().add(currentChat.getModel().get(i));
} else break;
}
/**
* Adds all messages with state {@code WAITING} from the {@link LocalDB} to the
* {@link Sync} object.
* Creates a {@link Message} object serializable to XML.
*
* @param textContent The content (text) of the message
* @param recipientID The recipient of the message
* @return prepared {@link Message} object
* @since Envoy v0.1-alpha
*/
public Message createMessage(String textContent, long recipientID) {
Message.Metadata metaData = objectFactory.createMessageMetadata();
metaData.setSender(sender.getID());
metaData.setRecipient(recipientID);
metaData.setState(MessageState.WAITING);
metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString()));
Message.Content content = objectFactory.createMessageContent();
content.setType("text");
content.setText(textContent);
Message message = objectFactory.createMessage();
message.setMetadata(metaData);
message.getContent().add(content);
return message;
}
/**
* Creates a {@link Sync} object filled with the changes that occurred to the
* local database since the last synchronization.
*
* @param message the message that is not yet received by the other user
* @param currentChat the chat that is currently open
* @since Envoy v0.1-alpha
* @param userId the ID of the user that is synchronized by this client
* @return {@link Sync} object filled with the current changes
* @since Envoy v0.1-alpha
*/
public void addWaitingMessageToLocalDB(Message message, Chat currentChat) { currentChat.appendMessage(message); }
/**
* Adds all messages with State WAITING from the {@link LocalDB} to the Sync.
*
* @since Envoy v0.1-alpha
public Sync fillSync(long userId) {
addWaitingMessagesToSync();
sync.getMessages().addAll(readMessages.getMessages());
readMessages.getMessages().clear();
logger.finest(String.format("Filled sync with %d messages.", sync.getMessages().size()));
return sync;
}
/**
* Applies the changes carried by a {@link Sync} object to the local database
*
* @param returnSync the {@link Sync} object to apply
* @since Envoy v0.1-alpha
*/
private void addWaitingMessagesToSync() {
for (Chat chat : getChats())
for (int i = 0; i < chat.getModel().size(); i++)
if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) {
System.out.println("Got Waiting Message");
sync.getMessages().add(chat.getModel().get(i));
}
}
/**
* Clears the {@code unreadMessagesSync} {@link Sync} object.
*
* @since Envoy v0.1-alpha
*/
public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); }
/**
* @return all saves {@link Chat} objects that list the client user as the
* client
* @since Envoy v0.1-alpha
**/
public List<Chat> getChats() { return chats; }
/**
* @return the {@link User} who initialized the local database
* @since Envoy v0.1-alpha
*/
public User getUser() { return sender; }
}
public void applySync(Sync returnSync) {
for (int i = 0; i < returnSync.getMessages().size(); i++) {
// The message has an ID
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0) {
// Messages are processes differently corresponding to their state
switch (returnSync.getMessages().get(i).getMetadata().getState()) {
case SENT:
// Update previously waiting and now sent messages that were assigned an ID by
// the server
sync.getMessages().get(i).getMetadata().setMessageId(returnSync.getMessages().get(i).getMetadata().getMessageId());
sync.getMessages().get(i).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
break;
case RECEIVED:
if (returnSync.getMessages().get(i).getMetadata().getSender() != 0) {
// these are the unread Messages from the server
unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i));
// Create and dispatch message creation event
EventBus.getInstance().dispatch(new MessageCreationEvent(returnSync.getMessages().get(i)));
} else {
// Update Messages in localDB to state RECEIVED
for (Chat chat : getChats())
if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient())
for (int j = 0; j < chat.getModel().getSize(); j++)
if (chat.getModel().get(j).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId())
chat.getModel().get(j).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
}
break;
case READ:
// Update local Messages to state READ
logger.info("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId()
+ "was initialized to be set to READ in localDB.");
for (Chat chat : getChats())
if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
logger.info("Chat with: " + chat.getRecipient().getID() + "was selected.");
for (int k = 0; k < chat.getModel().getSize(); k++)
if (chat.getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId()) {
logger.info("Message with ID: " + chat.getModel().get(k).getMetadata().getMessageId() + "was selected.");
chat.getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
logger.info("Message State is now: " + chat.getModel().get(k).getMetadata().getState());
}
}
break;
}
}
}
// Updating UserStatus of all users in LocalDB
for (User user : returnSync.getUsers())
for (Chat chat : getChats())
if (user.getID() == chat.getRecipient().getID()) {
chat.getRecipient().setStatus(user.getStatus());
logger.info(chat.getRecipient().getStatus().toString());
}
sync.getMessages().clear();
sync.getUsers().clear();
}
/**
* Adds the unread messages returned from the server in the latest sync to the
* right chats in the LocalDB.
*
* @since Envoy v0.1-alpha
*/
public void addUnreadMessagesToLocalDB() {
for (Message message : unreadMessagesSync.getMessages())
for (Chat chat : getChats())
if (message.getMetadata().getSender() == chat.getRecipient().getID()) {
chat.appendMessage(message);
break;
}
}
/**
* Changes all messages with state {@code RECEIVED} of a specific chat to state
* {@code READ}.
* <br>
* Adds these messages to the {@code readMessages} {@link Sync} object.
*
* @param currentChat the {@link Chat} that was just opened
* @since Envoy v0.1-alpha
*/
public void setMessagesToRead(Chat currentChat) {
for (int i = currentChat.getModel().size() - 1; i >= 0; --i)
if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID())
if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) {
currentChat.getModel().get(i).getMetadata().setState(MessageState.READ);
readMessages.getMessages().add(currentChat.getModel().get(i));
} else break;
}
/**
* Adds all messages with state {@code WAITING} from the {@link LocalDB} to the
* {@link Sync} object.
*
* @since Envoy v0.1-alpha
*/
private void addWaitingMessagesToSync() {
for (Chat chat : getChats())
for (int i = 0; i < chat.getModel().size(); i++)
if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) {
logger.info("Got Waiting Message");
sync.getMessages().add(chat.getModel().get(i));
}
}
/**
* Clears the {@code unreadMessagesSync} {@link Sync} object.
*
* @since Envoy v0.1-alpha
*/
public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); }
/**
* @return all saved {@link Chat} objects that list the client user as the
* sender
* @since Envoy v0.1-alpha
**/
public List<Chat> getChats() { return chats; }
/**
* @return the {@link User} who initialized the local database
* @since Envoy v0.1-alpha
*/
public User getUser() { return sender; }
}

View File

@ -0,0 +1,17 @@
package envoy.client.event;
/**
* Project: <strong>envoy-clientChess</strong><br>
* File: <strong>Event.javaEvent.java</strong><br>
* Created: <strong>04.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha
*/
public interface Event<T> {
/**
* @return the data associated with this event
*/
T get();
}

View File

@ -0,0 +1,71 @@
package envoy.client.event;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class handles events by allowing {@link EventHandler} object to register
* themselves and then be notified about certain events dispatched by the event
* bus.<br>
* <br>
* The event bus is a singleton and can be used across the entire application to
* guarantee the propagation of events.
* Project: <strong>envoy-client</strong><br>
* File: <strong>EventBus.java</strong><br>
* Created: <strong>04.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha
*/
public class EventBus {
/**
* Contains all {@link EventHandler} instances registered at this
* {@link EventBus}.
*/
private List<EventHandler> handlers = new ArrayList<>();
/**
* The singleton instance of this {@link EventBus} that is used across the
* entire application.
*/
private static EventBus eventBus = new EventBus();
/**
* This constructor is not accessible from outside this class because a
* singleton instance of it is provided by the {@link EventBus#getInstance()}
* method.
*/
private EventBus() {}
/**
* @return the singleton instance of the {@link EventBus}
* @since Envoy v0.2-alpha
*/
public static EventBus getInstance() { return eventBus; }
/**
* Registers a list of {@link EventHandler} objects to be notified when a
* {@link Event} is dispatched that they are subscribed to.
*
* @param handlers the {@link EventHandler} objects to register
* @since Envoy v0.2-alpha
*/
public void register(EventHandler... handlers) { this.handlers.addAll(Arrays.asList(handlers)); }
/**
* Dispatches a {@link Event} to every {@link EventHandler} subscribed to it.
*
* @param event the {@link Event} to dispatch
* @since Envoy v0.2-alpha
*/
public void dispatch(Event<?> event) { handlers.stream().filter(h -> h.supports().contains(event.getClass())).forEach(h -> h.handle(event)); }
/**
* @return a list of all {@link EventHandler} instances currently registered at
* this {@link EventBus}
* @since Envoy v0.2-alpha
*/
public List<EventHandler> getHandlers() { return handlers; }
}

View File

@ -0,0 +1,25 @@
package envoy.client.event;
import java.util.Set;
/**
* Project: <strong>envoy-clientChess</strong><br>
* File: <strong>EventHandler.javaEvent.java</strong><br>
* Created: <strong>04.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public interface EventHandler {
/**
* Consumes an event dispatched by the event bus.
*
* @param event The event dispatched by the event bus, only of supported type
*/
void handle(Event<?> event);
/**
* @return A set of classes this class is supposed to handle in events
*/
Set<Class<? extends Event<?>>> supports();
}

View File

@ -0,0 +1,18 @@
package envoy.client.event;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageCreationEvent.java</strong><br>
* Created: <strong>4 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public class MessageCreationEvent extends MessageEvent {
/**
* @param message the {@link Message} that has been created
*/
public MessageCreationEvent(Message message) { super(message); }
}

View File

@ -0,0 +1,20 @@
package envoy.client.event;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageCreationEvent.java</strong><br>
* Created: <strong>4 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public class MessageEvent implements Event<Message> {
protected final Message message;
public MessageEvent(Message message) { this.message = message; }
@Override
public Message get() { return message; }
}

View File

@ -0,0 +1,18 @@
package envoy.client.event;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageModificationEvent.java</strong><br>
* Created: <strong>4 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public class MessageModificationEvent extends MessageEvent {
/**
* @param message the {@link Message} that has been modified
*/
public MessageModificationEvent(Message message) { super(message); }
}

View File

@ -1,381 +1,373 @@
package envoy.client.ui;
import java.awt.ComponentOrientation;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import envoy.client.Chat;
import envoy.client.Client;
import envoy.client.Config;
import envoy.client.LocalDB;
import envoy.client.Settings;
import envoy.schema.Message;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatWindow.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy v0.1-alpha
*/
public class ChatWindow extends JFrame {
private static final long serialVersionUID = 6865098428255463649L;
// user specific objects
private Client client;
private LocalDB localDB;
// GUI components
private JPanel contentPane = new JPanel();
private JTextArea messageEnterTextArea = new JTextArea();
private JList<User> userList = new JList<>();
private Chat currentChat;
private JList<Message> messageList = new JList<>();
private JScrollPane scrollPane = new JScrollPane();
private JTextPane textPane = new JTextPane();
// private JCheckBox jCbChangeMode;
private JButton postButton = new JButton("Post");
private JButton settingsButton = new JButton("Settings");
private static int space = 4;
public ChatWindow(Client client, LocalDB localDB) {
this.client = client;
this.localDB = localDB;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 800);
setTitle("Envoy");
setLocationRelativeTo(null);
// Save chats when window closes
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
try {
localDB.saveToLocalDB();
Settings.getInstance().save();
} catch (IOException e1) {
e1.printStackTrace();
System.err.println("Could not save localDB");
}
}
});
contentPane.setBorder(new EmptyBorder(space, space, space, space));
setContentPane(contentPane);
GridBagLayout gbl_contentPane = new GridBagLayout();
gbl_contentPane.columnWidths = new int[] { 1, 1, 1 };
gbl_contentPane.rowHeights = new int[] { 1, 1, 1 };
gbl_contentPane.columnWeights = new double[] { 0.3, 1.0, 0.1 };
gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 };
contentPane.setLayout(gbl_contentPane);
messageList.setCellRenderer(new MessageListRenderer());
messageList.setFocusTraversalKeysEnabled(false);
messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
DefaultListModel<Message> messageListModel = new DefaultListModel<>();
messageList.setModel(messageListModel);
messageList.setFont(new Font("Arial", Font.PLAIN, 17));
messageList.setFixedCellHeight(60);
messageList.setBorder(new EmptyBorder(space, space, space, space));
scrollPane.setViewportView(messageList);
scrollPane.setBorder(null);
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridwidth = 2;
gbc_scrollPane.gridx = 1;
gbc_scrollPane.gridy = 1;
gbc_scrollPane.insets = new Insets(space, space, space, space);
contentPane.add(scrollPane, gbc_scrollPane);
// Message enter field
messageEnterTextArea.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
&& ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0)
|| (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList);
}
}
});
// Checks for changed Message
messageEnterTextArea.setWrapStyleWord(true);
messageEnterTextArea.setLineWrap(true);
messageEnterTextArea.setBorder(null);
messageEnterTextArea.setFont(new Font("Arial", Font.PLAIN, 17));
messageEnterTextArea.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints();
gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH;
gbc_messageEnterTextfield.gridx = 1;
gbc_messageEnterTextfield.gridy = 2;
gbc_messageEnterTextfield.insets = new Insets(space, space, space, space);
contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield);
// Post Button
postButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints();
gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionPostButton.gridx = 2;
gbc_moveSelectionPostButton.gridy = 2;
gbc_moveSelectionPostButton.insets = new Insets(space, space, space, space);
postButton.addActionListener((evt) -> { postMessage(messageList); });
contentPane.add(postButton, gbc_moveSelectionPostButton);
// Settings Button
settingsButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints();
gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionSettingsButton.gridx = 2;
gbc_moveSelectionSettingsButton.gridy = 0;
gbc_moveSelectionSettingsButton.insets = new Insets(space, space, space, space);
settingsButton.addActionListener((evt) -> {
try {
SettingsScreen.open();
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
} catch (Exception e) {
System.err.println("An error occured while opening the settings screen: " + e);
e.printStackTrace();
}
});
contentPane.add(settingsButton, gbc_moveSelectionSettingsButton);
// Partner name display
textPane.setFont(new Font("Arial", Font.PLAIN, 20));
textPane.setEditable(false);
GridBagConstraints gbc_partnerName = new GridBagConstraints();
gbc_partnerName.fill = GridBagConstraints.HORIZONTAL;
gbc_partnerName.gridx = 1;
gbc_partnerName.gridy = 0;
gbc_partnerName.insets = new Insets(space, space, space, space);
contentPane.add(textPane, gbc_partnerName);
userList.setCellRenderer(new UserListRenderer());
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
userList.addListSelectionListener((listSelectionEvent) -> {
if (!listSelectionEvent.getValueIsAdjusting()) {
@SuppressWarnings("unchecked")
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
final User user = selectedUserList.getSelectedValue();
client.setRecipient(user);
currentChat = localDB.getChats()
.stream()
.filter(chat -> chat.getRecipient().getID() == user.getID())
.findFirst()
.get();
// Set all unread messages in the chat to read
readCurrentChat();
client.setRecipient(user);
textPane.setText(currentChat.getRecipient().getName());
messageList.setModel(currentChat.getModel());
contentPane.revalidate();
}
});
userList.setFont(new Font("Arial", Font.PLAIN, 17));
userList.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_userList = new GridBagConstraints();
gbc_userList.fill = GridBagConstraints.VERTICAL;
gbc_userList.gridx = 0;
gbc_userList.gridy = 1;
gbc_userList.anchor = GridBagConstraints.PAGE_START;
gbc_userList.insets = new Insets(space, space, space, space);
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
contentPane.add(userList, gbc_userList);
contentPane.revalidate();
loadUsersAndChats();
startSyncThread(Config.getInstance().getSyncTimeout());
contentPane.revalidate();
}
/**
* Used to immediately reload the ChatWindow when settings were changed.
*
* @since Envoy v0.1-alpha
*/
public void changeChatWindowColors(String key) {
Theme theme = Settings.getInstance().getThemes().get(key);
// contentPane
contentPane.setBackground(theme.getBackgroundColor());
contentPane.setForeground(theme.getUserNameColor());
// messageList
messageList.setSelectionForeground(theme.getUserNameColor());
messageList.setSelectionBackground(theme.getSelectionColor());
messageList.setForeground(theme.getMessageColorChat());
messageList.setBackground(theme.getCellColor());
// scrollPane
scrollPane.setForeground(theme.getBackgroundColor());
scrollPane.setBackground(theme.getCellColor());
// messageEnterTextArea
messageEnterTextArea.setCaretColor(theme.getTypingMessageColor());
messageEnterTextArea.setForeground(theme.getTypingMessageColor());
messageEnterTextArea.setBackground(theme.getCellColor());
// postButton
postButton.setForeground(theme.getInteractableForegroundColor());
postButton.setBackground(theme.getInteractableBackgroundColor());
// settingsButton
settingsButton.setForeground(theme.getInteractableForegroundColor());
settingsButton.setBackground(theme.getInteractableBackgroundColor());
// textPane
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getUserNameColor());
// userList
userList.setSelectionForeground(theme.getUserNameColor());
userList.setSelectionBackground(theme.getSelectionColor());
userList.setForeground(theme.getUserNameColor());
userList.setBackground(theme.getCellColor());
}
private void postMessage(JList<Message> messageList) {
if (!client.hasRecipient()) {
JOptionPane.showMessageDialog(this,
"Please select a recipient!",
"Cannot send message",
JOptionPane.INFORMATION_MESSAGE);
}
if (!messageEnterTextArea.getText().isEmpty()) try {
// Create and send message object
final Message message = localDB.createMessage(messageEnterTextArea.getText(),
currentChat.getRecipient().getID());
localDB.addWaitingMessageToLocalDB(message, currentChat);
messageList.setModel(currentChat.getModel());
// Clear text field
messageEnterTextArea.setText("");
contentPane.revalidate();
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"An exception occured while sending a message. See the log for more details.",
"Exception occured",
JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
/**
* Initializes the elements of the user list by downloading them from the
* server.
*
* @since Envoy v0.1-alpha
*/
private void loadUsersAndChats() {
new Thread(() -> {
Sync users = client.getUsersListXml();
DefaultListModel<User> userListModel = new DefaultListModel<>();
users.getUsers().forEach(user -> {
userListModel.addElement(user);
// Check if user exists in local DB
if (localDB.getChats().stream().filter(c -> c.getRecipient().getID() == user.getID()).count() == 0)
localDB.getChats().add(new Chat(user));
});
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
}).start();
}
/**
* Updates the data model and the UI repeatedly after a certain amount of
* time.
*
* @param timeout the amount of time that passes between two requests sent to
* the server
* @since Envoy v0.1-alpha
*/
private void startSyncThread(int timeout) {
new Timer(timeout, (evt) -> {
new Thread(() -> {
// Synchronize
localDB.applySync(
client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages
localDB.addUnreadMessagesToLocalDB();
localDB.clearUnreadMessagesSync();
// Mark unread messages as read when they are in the current chat
readCurrentChat();
// Update UI
SwingUtilities
.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start();
}).start();
}
private void updateUserStates() {
for (int i = 0; i < userList.getModel().getSize(); i++)
for (int j = 0; j < localDB.getChats().size(); j++)
if (userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID())
userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus());
}
/**
* Marks messages in the current chat as {@code READ}.
*/
private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } }
}
package envoy.client.ui;
import java.awt.Color;
import java.awt.ComponentOrientation;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import envoy.client.Chat;
import envoy.client.Client;
import envoy.client.Config;
import envoy.client.LocalDB;
import envoy.client.Settings;
import envoy.schema.Message;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatWindow.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy v0.1-alpha
*/
public class ChatWindow extends JFrame {
private static final long serialVersionUID = 6865098428255463649L;
// user specific objects
private Client client;
private LocalDB localDB;
// GUI components
private JPanel contentPane = new JPanel();
private JTextArea messageEnterTextArea = new JTextArea();
private JList<User> userList = new JList<>();
private Chat currentChat;
private JList<Message> messageList = new JList<>();
private JScrollPane scrollPane = new JScrollPane();
private JTextPane textPane = new JTextPane();
// private JCheckBox jCbChangeMode;
private JButton postButton = new JButton("Post");
private JButton settingsButton = new JButton("Settings");
private static int space = 4;
private static final Logger logger = Logger.getLogger(ChatWindow.class.getSimpleName());
public ChatWindow(Client client, LocalDB localDB) {
this.client = client;
this.localDB = localDB;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 800);
setTitle("Envoy");
setLocationRelativeTo(null);
setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png")));
// Save chats when window closes
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent evt) {
try {
localDB.saveToLocalDB();
Settings.getInstance().save();
} catch (IOException e1) {
e1.printStackTrace();
logger.log(Level.WARNING, "Unable to save the messages", e1);
}
}
});
contentPane.setBorder(new EmptyBorder(space, space, space, space));
setContentPane(contentPane);
GridBagLayout gbl_contentPane = new GridBagLayout();
gbl_contentPane.columnWidths = new int[] { 1, 1, 1 };
gbl_contentPane.rowHeights = new int[] { 1, 1, 1 };
gbl_contentPane.columnWeights = new double[] { 0.3, 1.0, 0.1 };
gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 };
contentPane.setLayout(gbl_contentPane);
messageList.setCellRenderer(new MessageListRenderer());
messageList.setFocusTraversalKeysEnabled(false);
messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
DefaultListModel<Message> messageListModel = new DefaultListModel<>();
messageList.setModel(messageListModel);
messageList.setFont(new Font("Arial", Font.PLAIN, 17));
messageList.setFixedCellHeight(60);
messageList.setBorder(new EmptyBorder(space, space, space, space));
scrollPane.setViewportView(messageList);
scrollPane.setBorder(null);
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridwidth = 2;
gbc_scrollPane.gridx = 1;
gbc_scrollPane.gridy = 1;
gbc_scrollPane.insets = new Insets(space, space, space, space);
contentPane.add(scrollPane, gbc_scrollPane);
// Message enter field
messageEnterTextArea.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
&& ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0)
|| (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList);
}
});
// Checks for changed Message
messageEnterTextArea.setWrapStyleWord(true);
messageEnterTextArea.setLineWrap(true);
messageEnterTextArea.setBorder(null);
messageEnterTextArea.setFont(new Font("Arial", Font.PLAIN, 17));
messageEnterTextArea.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints();
gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH;
gbc_messageEnterTextfield.gridx = 1;
gbc_messageEnterTextfield.gridy = 2;
gbc_messageEnterTextfield.insets = new Insets(space, space, space, space);
contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield);
// Post Button
postButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints();
gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionPostButton.gridx = 2;
gbc_moveSelectionPostButton.gridy = 2;
gbc_moveSelectionPostButton.insets = new Insets(space, space, space, space);
postButton.addActionListener((evt) -> { postMessage(messageList); });
contentPane.add(postButton, gbc_moveSelectionPostButton);
// Settings Button
settingsButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints();
gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionSettingsButton.gridx = 2;
gbc_moveSelectionSettingsButton.gridy = 0;
gbc_moveSelectionSettingsButton.insets = new Insets(space, space, space, space);
settingsButton.addActionListener((evt) -> {
try {
SettingsScreen.open();
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
} catch (Exception e) {
SettingsScreen.open();
logger.log(Level.WARNING, "An error occured while opening the settings screen", e);
e.printStackTrace();
}
});
contentPane.add(settingsButton, gbc_moveSelectionSettingsButton);
// Partner name display
textPane.setFont(new Font("Arial", Font.PLAIN, 20));
textPane.setEditable(false);
GridBagConstraints gbc_partnerName = new GridBagConstraints();
gbc_partnerName.fill = GridBagConstraints.HORIZONTAL;
gbc_partnerName.gridx = 1;
gbc_partnerName.gridy = 0;
gbc_partnerName.insets = new Insets(space, space, space, space);
contentPane.add(textPane, gbc_partnerName);
userList.setCellRenderer(new UserListRenderer());
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
userList.addListSelectionListener((listSelectionEvent) -> {
if (!listSelectionEvent.getValueIsAdjusting()) {
@SuppressWarnings("unchecked")
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
final User user = selectedUserList.getSelectedValue();
client.setRecipient(user);
currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get();
// Set all unread messages in the chat to read
readCurrentChat();
client.setRecipient(user);
textPane.setText(currentChat.getRecipient().getName());
messageList.setModel(currentChat.getModel());
contentPane.revalidate();
}
});
userList.setFont(new Font("Arial", Font.PLAIN, 17));
userList.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_userList = new GridBagConstraints();
gbc_userList.fill = GridBagConstraints.VERTICAL;
gbc_userList.gridx = 0;
gbc_userList.gridy = 1;
gbc_userList.anchor = GridBagConstraints.PAGE_START;
gbc_userList.insets = new Insets(space, space, space, space);
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
contentPane.add(userList, gbc_userList);
contentPane.revalidate();
loadUsersAndChats();
startSyncThread(Config.getInstance().getSyncTimeout());
contentPane.revalidate();
}
/**
* Used to immediately reload the ChatWindow when settings were changed.
*
* @since Envoy v0.1-alpha
*/
public void changeChatWindowColors(String key) {
Theme theme = Settings.getInstance().getThemes().get(key);
// contentPane
contentPane.setBackground(theme.getBackgroundColor());
contentPane.setForeground(theme.getUserNameColor());
// messageList
messageList.setSelectionForeground(theme.getUserNameColor());
messageList.setSelectionBackground(theme.getSelectionColor());
messageList.setForeground(theme.getMessageColorChat());
messageList.setBackground(theme.getCellColor());
// scrollPane
scrollPane.setForeground(theme.getBackgroundColor());
scrollPane.setBackground(theme.getCellColor());
// messageEnterTextArea
messageEnterTextArea.setCaretColor(theme.getTypingMessageColor());
messageEnterTextArea.setForeground(theme.getTypingMessageColor());
messageEnterTextArea.setBackground(theme.getCellColor());
// postButton
postButton.setForeground(theme.getInteractableForegroundColor());
postButton.setBackground(theme.getInteractableBackgroundColor());
// settingsButton
settingsButton.setForeground(theme.getInteractableForegroundColor());
settingsButton.setBackground(theme.getInteractableBackgroundColor());
// textPane
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getUserNameColor());
// userList
userList.setSelectionForeground(theme.getUserNameColor());
userList.setSelectionBackground(theme.getSelectionColor());
userList.setForeground(theme.getUserNameColor());
userList.setBackground(theme.getCellColor());
}
private void postMessage(JList<Message> messageList) {
if (!client.hasRecipient()) {
JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
return;
}
if (!messageEnterTextArea.getText().isEmpty()) try {
// Create and send message object
final Message message = localDB.createMessage(messageEnterTextArea.getText(), currentChat.getRecipient());
currentChat.appendMessage(message);
messageList.setModel(currentChat.getModel());
// Clear text field
messageEnterTextArea.setText("");
contentPane.revalidate();
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"An exception occured while sending a message. See the log for more details.",
"Exception occured",
JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
/**
* Initializes the elements of the user list by downloading them from the
* server.
*
* @since Envoy v0.1-alpha
*/
private void loadUsersAndChats() {
new Thread(() -> {
Sync users = client.getUsersListXml();
DefaultListModel<User> userListModel = new DefaultListModel<>();
users.getUsers().forEach(user -> {
userListModel.addElement(user);
// Check if user exists in local DB
if (localDB.getChats().stream().filter(c -> c.getRecipient().getID() == user.getID()).count() == 0)
localDB.getChats().add(new Chat(user));
});
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
}).start();
}
/**
* Updates the data model and the UI repeatedly after a certain amount of
* time.
*
* @param timeout the amount of time that passes between two requests sent to
* the server
* @since Envoy v0.1-alpha
*/
private void startSyncThread(int timeout) {
new Timer(timeout, (evt) -> {
new Thread(() -> {
// Synchronize
localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages
localDB.addUnreadMessagesToLocalDB();
localDB.clearUnreadMessagesSync();
// Mark unread messages as read when they are in the current chat
readCurrentChat();
// Update UI
SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start();
}).start();
}
private void updateUserStates() {
for (int i = 0; i < userList.getModel().getSize(); i++)
for (int j = 0; j < localDB.getChats().size(); j++)
if (userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID())
userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus());
}
/**
* Marks messages in the current chat as {@code READ}.
*/
private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } }
}

View File

@ -1,78 +1,76 @@
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import java.text.SimpleDateFormat;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.Message;
/**
* Defines how a message is displayed.<br>
* <br>
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class MessageListRenderer extends JLabel implements ListCellRenderer<Message> {
private static final long serialVersionUID = 5164417379767181198L;
@Override
public Component getListCellRendererComponent(JList<? extends Message> list, Message value, int index,
boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setOpaque(true);
final String text = value.getContent().get(0).getText();
final String state = value.getMetadata().getState().toString();
final String date = value.getMetadata().getDate() == null ? ""
: new SimpleDateFormat("dd.MM.yyyy HH:mm ")
.format(value.getMetadata().getDate().toGregorianCalendar().getTime());
// Getting the MessageColor in the Chat of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageColorChat());
// Getting the DateColor in the Chat of the current theme
String dateColor = null;
dateColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getDateColorChat());
setText(String.format(
"<html><p style=\"color:%s\"><b><small>%s</b></small><br><p style=\"color:%s\">%s :%s</html>",
dateColor,
date,
textColor,
text,
state));
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import java.text.SimpleDateFormat;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.Message;
/**
* Defines how a message is displayed.<br>
* <br>
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class MessageListRenderer extends JLabel implements ListCellRenderer<Message> {
private static final long serialVersionUID = 5164417379767181198L;
@Override
public Component getListCellRendererComponent(JList<? extends Message> list, Message value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setOpaque(true);
final String text = value.getContent().get(0).getText();
final String state = value.getMetadata().getState().toString();
final String date = value.getMetadata().getDate() == null ? ""
: new SimpleDateFormat("dd.MM.yyyy HH:mm ")
.format(value.getMetadata().getDate().toGregorianCalendar().getTime());
// Getting the MessageColor in the Chat of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageColorChat());
// Getting the DateColor in the Chat of the current theme
String dateColor = null;
dateColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getDateColorChat());
setText(String.format(
"<html><p style=\"color:%s\"><b><small>%s</b></small><br><p style=\"color:%s\">%s :%s</html>",
dateColor,
date,
textColor,
text,
state));
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
}

View File

@ -509,4 +509,5 @@ public class SettingsScreen extends JDialog {
colorsPanel.add(panel);
}
}

View File

@ -3,6 +3,8 @@ package envoy.client.ui;
import java.awt.EventQueue;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
@ -26,29 +28,35 @@ import envoy.exception.EnvoyException;
*/
public class Startup {
private static final Logger logger = Logger.getLogger(Startup.class.getSimpleName());
public static void main(String[] args) {
logger.setLevel(Level.ALL);
Config config = Config.getInstance();
if (args.length > 0) {
config.load(args);
} else {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Properties configProperties = new Properties();
configProperties.load(loader.getResourceAsStream("client.properties"));
config.load(configProperties);
} catch (IOException e) {
e.printStackTrace();
}
// Load the configuration from client.properties first
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Properties configProperties = new Properties();
configProperties.load(loader.getResourceAsStream("client.properties"));
config.load(configProperties);
} catch (IOException e) {
e.printStackTrace();
}
// Override configuration values with command line arguments
if (args.length > 0) config.load(args);
if (!config.isInitialized()) {
System.err.println("Server or port are not defined. Exiting...");
logger.severe("Server or port are not defined. Exiting...");
JOptionPane.showMessageDialog(null, "Error loading configuration values.", "Configuration error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
String userName = JOptionPane.showInputDialog("Please enter your username");
if (userName == null || userName.isEmpty()) {
System.err.println("User name is not set or empty. Exiting...");
logger.severe("User name is not set or empty. Exiting...");
System.exit(1);
}
Client client = new Client(config, userName);
@ -66,8 +74,9 @@ public class Startup {
EventQueue.invokeLater(() -> {
try {
ChatWindow frame = new ChatWindow(client, localDB);
frame.setVisible(true);
ChatWindow chatWindow = new ChatWindow(client, localDB);
new StatusTrayIcon(chatWindow).show();
chatWindow.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -0,0 +1,133 @@
package envoy.client.ui;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashSet;
import java.util.Set;
import envoy.client.event.Event;
import envoy.client.event.EventBus;
import envoy.client.event.EventHandler;
import envoy.client.event.MessageCreationEvent;
import envoy.exception.EnvoyException;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>StatusTrayIcon.java</strong><br>
* Created: <strong>3 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha
*/
public class StatusTrayIcon implements EventHandler {
/**
* The {@link TrayIcon} provided by the System Tray API for controlling the
* system tray. This includes displaying the icon, but also creating
* notifications when new messages are received.
*/
private TrayIcon trayIcon;
/**
* A received {@link Message} is only displayed as a system tray notification if
* this variable is set to {@code true}.
*/
private boolean displayMessages = false;
/**
* Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
* menu.
*
* @param focusTarget the {@link Window} which focus determines if message
* notifications are displayed
* @throws EnvoyException if the currently used OS does not support the System
* Tray API
* @since Envoy v0.2-alpha
*/
public StatusTrayIcon(Window focusTarget) throws EnvoyException {
if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Image img = Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
trayIcon = new TrayIcon(img, "Envoy Client");
trayIcon.setImageAutoSize(true);
trayIcon.setToolTip("You are notified if you have unread messages.");
PopupMenu popup = new PopupMenu();
MenuItem exitMenuItem = new MenuItem("Exit");
exitMenuItem.addActionListener((evt) -> System.exit(0));
popup.add(exitMenuItem);
trayIcon.setPopupMenu(popup);
// Only display messages if the chat window is not focused
focusTarget.addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
displayMessages = false;
}
@Override
public void windowLostFocus(WindowEvent e) {
displayMessages = true;
}
});
// Start processing message events
EventBus.getInstance().register(this);
}
/**
* Makes this {@link StatusTrayIcon} appear in the system tray.
*
* @throws EnvoyException if the status icon could not be attaches to the system
* tray for system-internal reasons
* @since Envoy v0.2-alpha
*/
public void show() throws EnvoyException {
try {
SystemTray.getSystemTray().add(trayIcon);
} catch (AWTException e) {
throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e);
}
}
/**
* Notifies the user of a message by displaying a pop-up every time a new
* message is received.
*
* @since Envoy v0.2-alpha
*/
@Override
public void handle(Event<?> event) {
System.out.println("Message received. Displaying message: " + displayMessages);
if (displayMessages)
trayIcon.displayMessage("New message received", ((MessageCreationEvent) event).get().getContent().get(0).getText(), MessageType.INFO);
}
/**
* The {@link StatusTrayIcon} only reacts to {@link MessageCreationEvent}
* instances which signify newly received messages.
*
* @return A set with the single element {@code MessageCreationEvent.class}
* @since Envoy v0.2-alpha
*/
@Override
public Set<Class<? extends Event<?>>> supports() {
Set<Class<? extends Event<?>>> supportedEvents = new HashSet<>();
supportedEvents.add(MessageCreationEvent.class);
return supportedEvents;
}
}

View File

@ -1,84 +1,77 @@
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.User;
import envoy.schema.User.UserStatus;
/**
* Defines how the {@code UserList} is displayed.
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
private static final long serialVersionUID = 5164417379767181198L;
@SuppressWarnings("incomplete-switch")
@Override
public Component getListCellRendererComponent(JList<? extends User> list, User value, int index, boolean isSelected,
boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
// Enable background rendering
setOpaque(true);
final String name = value.getName();
final UserStatus status = value.getStatus();
// Getting the UserNameColor of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor());
switch (status) {
case ONLINE:
setText(String.format(
"<html><p style=\"color:#03fc20\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
case OFFLINE:
setText(String.format(
"<html><p style=\"color:#fc0303\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
}
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.User;
import envoy.schema.User.UserStatus;
/**
* Defines how the {@code UserList} is displayed.
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
private static final long serialVersionUID = 5164417379767181198L;
@SuppressWarnings("incomplete-switch")
@Override
public Component getListCellRendererComponent(JList<? extends User> list, User value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
// Enable background rendering
setOpaque(true);
final String name = value.getName();
final UserStatus status = value.getStatus();
// Getting the UserNameColor of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor());
switch (status) {
case ONLINE:
setText(String.format(
"<html><p style=\"color:#03fc20\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
case OFFLINE:
setText(String.format(
"<html><p style=\"color:#fc0303\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
}
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB