diff --git a/client/.settings/org.eclipse.jdt.ui.prefs b/client/.settings/org.eclipse.jdt.ui.prefs
index 1d718a1..359aaeb 100644
--- a/client/.settings/org.eclipse.jdt.ui.prefs
+++ b/client/.settings/org.eclipse.jdt.ui.prefs
@@ -6,4 +6,4 @@ org.eclipse.jdt.ui.importorder=java;javax;javafx;org;com;envoy;
org.eclipse.jdt.ui.javadoc=true
org.eclipse.jdt.ui.ondemandthreshold=4
org.eclipse.jdt.ui.staticondemandthreshold=2
-org.eclipse.jdt.ui.text.custom_code_templates=
+ * Project: envoy-client
+ * Project: envoy-client
+ * Project: envoy-client
+ * Project: envoy-client
+ * Usage example:
+ * The approach to not throw an exception was taken so that an ugly try-catch
+ * block for every addition to the system commands map could be avoided, an
+ * error that should only occur during implementation and not in production.
+ *
+ * @param command the key to examine
+ * @return whether this key can be used in the map
+ * @since Envoy Client v0.2-beta
+ */
+ public boolean isValidKey(String command) {
+ final boolean valid = commandPattern.matcher(command).matches();
+ if (!valid) logger.log(Level.WARNING,
+ "The command \"" + command
+ + "\" is not valid. As it will cause problems in execution, it will not be entered into the map. Only the characters "
+ + commandPattern + "are allowed");
+ return valid;
+ }
+
+ /**
+ * Takes a 'raw' string (the whole input) and checks if "/" is the first visible
+ * character and then checks if a command is present after that "/". If that is
+ * the case, it will be executed.
+ *
+ *
+ * @param raw the raw input string
+ * @return whether a command could be found
+ * @since Envoy Client v0.2-beta
+ */
+ public boolean executeIfAnyPresent(String raw) {
+ // possibly a command was detected and could be executed
+ final var raw2 = raw.stripLeading();
+ final var commandFound = raw2.startsWith("/") ? executeIfPresent(raw2) : false;
+ // the command was executed successfully - no further checking needed
+ if (commandFound) logger.log(Level.FINE, "executed system command " + getCommand(raw2));
+ return commandFound;
+ }
+
+ /**
+ * This method checks if the input String is a key in the map and executes the
+ * wrapped System command if present.
+ * Its intended usage is after a "/" has been detected in the input String.
+ * It will do nothing if the value after the slash is not a key in
+ * the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
+ * map).
+ *
+ * Usage example:
+ * Project: envoy-client
+ * File: OnCall.java
+ * Created: 23.07.2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.2-beta
+ */
+public interface OnCall {
+
+ /**
+ * Performs class specific actions when a {@link SystemCommand} has been called.
+ *
+ * @since Envoy Client v0.2-beta
+ */
+ void onCall();
+
+ /**
+ * Performs actions that can only be performed by classes that are not
+ * {@link SystemCommand}s when a SystemCommand has been called.
+ *
+ * @param consumer the action to perform when this {@link SystemCommand} has
+ * been called
+ * @since Envoy Client v0.2-beta
+ */
+ void onCall(Supplier
+ * Order matters! Changing the order of arguments will likely result in
+ * unexpected behavior.
+ *
+ * File: SystemCommand.java
+ * Created: 16.07.2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.2-beta
+ */
+public class SystemCommand implements OnCall {
+
+ protected int relevance;
+
+ /**
+ * The argument count of the command.
+ */
+ protected final int numberOfArguments;
+
+ /**
+ * This function takes a {@code List> action;
+
+ protected final String description;
+
+ protected final List
> action, int numberOfArguments, List
> getAction() { return action; }
+
+ /**
+ * @return the argument count of the command
+ * @since Envoy Client v0.2-beta
+ */
+ public int getNumberOfArguments() { return numberOfArguments; }
+
+ /**
+ * @return the description
+ * @since Envoy Client v0.2-beta
+ */
+ public String getDescription() { return description; }
+
+ /**
+ * @return the relevance
+ * @since Envoy Client v0.2-beta
+ */
+ public int getRelevance() { return relevance; }
+
+ /**
+ * @param relevance the relevance to set
+ * @since Envoy Client v0.2-beta
+ */
+ public void setRelevance(int relevance) { this.relevance = relevance; }
+
+ /**
+ * Increments the relevance of this {@code SystemCommand}.
+ */
+ @Override
+ public void onCall() { relevance++; }
+
+ /**
+ * Increments the relevance of this {@code SystemCommand} and executes the
+ * supplier.
+ */
+ @Override
+ public void onCall(Supplier
+ * File: SystemCommandBuilder.java
+ * Created: 23.07.2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.2-beta
+ */
+public class SystemCommandBuilder {
+
+ private int numberOfArguments;
+ private Consumer> action;
+ private List
> action) {
+ this.action = action;
+ return this;
+ }
+
+ /**
+ * @param description the description to set
+ * @return this {@code SystemCommandBuilder}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommandBuilder setDescription(String description) {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * @param relevance the relevance to set
+ * @return this {@code SystemCommandBuilder}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommandBuilder setRelevance(int relevance) {
+ this.relevance = relevance;
+ return this;
+ }
+
+ /**
+ * @param defaults the defaults to set
+ * @return this {@code SystemCommandBuilder}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommandBuilder setDefaults(String... defaults) {
+ this.defaults = List.of(defaults);
+ return this;
+ }
+
+ /**
+ * Resets all values stored.
+ *
+ * @return this {@code SystemCommandBuilder}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommandBuilder reset() {
+ numberOfArguments = 0;
+ action = null;
+ defaults = new ArrayList<>();
+ description = "";
+ relevance = 0;
+ return this;
+ }
+
+ /**
+ * Builds a {@code SystemCommand} based upon the previously entered data.
+ *
+ * @return the built {@code SystemCommand}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommand build() { return build(true); }
+
+ /**
+ * Builds a {@code SystemCommand} based upon the previously entered data.
+ * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
+ * previous value.
+ * At the end, this {@code SystemCommandBuilder} will be reset.
+ *
+ * @return the built {@code SystemCommand}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommand buildNoArg() {
+ numberOfArguments = 0;
+ return build(true);
+ }
+
+ /**
+ * Builds a {@code SystemCommand} based upon the previously entered data.
+ * {@code SystemCommand#numberOfArguments} will be set to use the rest of the
+ * string as argument, regardless of the previous value.
+ * At the end, this {@code SystemCommandBuilder} will be reset.
+ *
+ * @return the built {@code SystemCommand}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommand buildRemainingArg() {
+ numberOfArguments = -1;
+ return build(true);
+ }
+
+ /**
+ * Builds a {@code SystemCommand} based upon the previously entered data.
+ * At the end, this {@code SystemCommandBuilder} can be reset but must
+ * not be.
+ *
+ * @param reset whether this {@code SystemCommandBuilder} should be reset
+ * afterwards.
+ * This can be useful if another command wants to execute something
+ * similar
+ * @return the built {@code SystemCommand}
+ * @since Envoy Client v0.2-beta
+ */
+ public SystemCommand build(boolean reset) {
+ final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
+ sc.setRelevance(relevance);
+ if (reset) reset();
+ return sc;
+ }
+}
diff --git a/client/src/main/java/envoy/client/data/commands/SystemCommandsMap.java b/client/src/main/java/envoy/client/data/commands/SystemCommandsMap.java
new file mode 100644
index 0000000..cfcb0a4
--- /dev/null
+++ b/client/src/main/java/envoy/client/data/commands/SystemCommandsMap.java
@@ -0,0 +1,263 @@
+package envoy.client.data.commands;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import envoy.util.EnvoyLog;
+
+/**
+ * This class stores all {@link SystemCommand}s used.
+ *
+ * File: SystemCommandsMap.java
+ * Created: 17.07.2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.2-beta
+ */
+public final class SystemCommandsMap {
+
+ private final Map
+ * {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}
+ * {@code Button button = new Button();}
+ * {@code systemCommands.add("example", text -> button.setText(text.get(0), 1);}
+ * {@code ....}
+ * user input: {@code "/example xyz ..."}
+ * {@code systemCommands.get("example xyz ...")} or
+ * {@code systemCommands.get("/example xyz ...")}
+ * result: {@code Optional
+ * It returns the command as (most likely) entered as key in the map for the
+ * first word of the text.
+ * It should only be called on strings that contain a "/" at position 0/-1.
+ *
+ * @param raw the input
+ * @return the command as entered in the map
+ * @since Envoy Client v0.2-beta
+ * @apiNote this method will (most likely) not return anything useful if
+ * whatever is entered after the slash is not a system command. Only
+ * exception: for recommendation purposes.
+ */
+ public String getCommand(String raw) {
+ final var trimmed = raw.stripLeading();
+ final var index = trimmed.indexOf(' ');
+ return trimmed.substring(trimmed.charAt(0) == '/' ? 1 : 0, index < 1 ? trimmed.length() : index);
+ }
+
+ /**
+ * Examines whether a key can be put in the map and logs it with
+ * {@code Level.WARNING} if that key violates API constrictions.
+ * (allowed chars are a-zA-Z0-9_:!()?.,;-)
+ *
+ * {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}
+ * {@code Button button = new Button();}
+ * {@code systemCommands.add("example", (words)-> button.setText(words.get(0), 1);}
+ * {@code ....}
+ * user input: {@code "/example xyz ..."}
+ * {@code systemCommands.executeIfPresent("example xyz ...")}
+ * result: {@code button.getText()=="xyz"}
+ *
+ * @param input the input string given by the user
+ * @return whether a command could be found
+ * @since Envoy Client v0.2-beta
+ */
+ public boolean executeIfPresent(String input) {
+ final var command = getCommand(input);
+ final var value = get(command);
+ value.ifPresent(systemCommand -> {
+ // Splitting the String so that the leading command including the first " " is
+ // removed and only as many following words as allowed by the system command
+ // persist
+ final var arguments = extractArguments(input, systemCommand);
+ // Executing the function
+ try {
+ systemCommand.getAction().accept(arguments);
+ systemCommand.onCall();
+ } catch (final Exception e) {
+ logger.log(Level.WARNING, "The system command " + command + " threw an exception: ", e);
+ }
+ });
+ return value.isPresent();
+ }
+
+ /**
+ * Supplies missing values with default values.
+ *
+ * @param input the input String
+ * @param systemCommand the command that is expected
+ * @return the list of arguments that can be used to parse the systemCommand
+ * @since Envoy Client v0.2-beta
+ */
+ private List
+ * The first word is used for the recommendations and
+ * it does not matter if the "/" is at its beginning or not.
+ * If none are present, nothing will be done.
+ * Otherwise the given function will be executed on the recommendations.
+ *
+ * @param input the input string
+ * @param action the action that should be taken for the recommendations, if any
+ * are present
+ * @since Envoy Client v0.2-beta
+ */
+ public void requestRecommendations(String input, Function
+ * In the current implementation, all we check is whether a key contains this
+ * input. This might be updated later on.
+ *
+ * @param partialCommand the partially entered command
+ * @return a set of all commands that match this input
+ * @since Envoy Client v0.2-beta
+ */
+ private Set
+ * Will only work for {@code SystemCommand}s whose argument counter is bigger
+ * than 1.
+ *
+ * @param textArguments the arguments that were parsed from the text
+ * @param toEvaluate the system command whose default values should be used
+ * @return the final argument list
+ * @since Envoy Client v0.2-beta
+ * @apiNote this method will insert an empty String if the size of the list
+ * given to the {@code SystemCommand} is smaller than its argument
+ * counter and no more text arguments could be found.
+ */
+ private List
+ * Every system command can be called using a specific syntax:"/<command>"
+ *
+ * File: package-info.java
+ * Created: 16.07.2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.2-beta
+ */
+package envoy.client.data.commands;
diff --git a/client/src/main/java/envoy/client/ui/controller/ChatScene.java b/client/src/main/java/envoy/client/ui/controller/ChatScene.java
index f32ea08..a05150c 100644
--- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java
+++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java
@@ -30,6 +30,8 @@ import javafx.util.Duration;
import envoy.client.data.*;
import envoy.client.data.audio.AudioRecorder;
+import envoy.client.data.commands.SystemCommandBuilder;
+import envoy.client.data.commands.SystemCommandsMap;
import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
@@ -107,6 +109,8 @@ public final class ChatScene implements Restorable {
private Attachment pendingAttachment;
private boolean postingPermanentlyDisabled;
+ private final SystemCommandsMap messageTextAreaCommands = new SystemCommandsMap();
+
private static final Settings settings = Settings.getInstance();
private static final EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ChatScene.class);
@@ -193,7 +197,7 @@ public final class ChatScene implements Restorable {
switch (e.getOperationType()) {
case ADD:
if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
- Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
+ final Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
Platform.runLater(() -> chatList.getItems().add(chat));
break;
case REMOVE:
@@ -203,6 +207,22 @@ public final class ChatScene implements Restorable {
});
}
+ /**
+ * Initializes all {@code SystemCommands} used in {@code ChatScene}.
+ *
+ * @since Envoy Client v0.2-beta
+ */
+ private void initializeSystemCommandsMap() {
+ final var builder = new SystemCommandBuilder();
+ // Do A Barrel roll initialization
+ final var random = new Random();
+ builder.setAction(text -> doABarrelRoll(Integer.parseInt(text.get(0)), Double.parseDouble(text.get(1))))
+ .setDefaults(Integer.toString(random.nextInt(3) + 1), Double.toString(random.nextDouble() * 3 + 1))
+ .setDescription("See for yourself :)")
+ .setNumberOfArguments(2);
+ messageTextAreaCommands.add("DABR", builder.build());
+ }
+
/**
* Initializes all necessary data via dependency injection-
*
@@ -225,6 +245,7 @@ public final class ChatScene implements Restorable {
if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
recorder = new AudioRecorder();
+ initializeSystemCommandsMap();
}
@Override
@@ -376,20 +397,34 @@ public final class ChatScene implements Restorable {
}
/**
- * Rotates every element in our application by 360° in at most 2.75s.
+ * Rotates every element in our application by (at most 4 *) 360° in at most
+ * 2.75s.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void doABarrelRoll() {
+ final var random = new Random();
+ doABarrelRoll(random.nextInt(3) + 1, random.nextDouble() * 3 + 1);
+ }
+
+ /**
+ * Rotates every element in our application by {@code rotations}*360° in
+ * {@code an}.
+ *
+ * @param rotations the amount of times the scene is rotated by 360°
+ * @param animationTime the time in seconds that this animation lasts
+ * @since Envoy Client v0.1-beta
+ */
+ private void doABarrelRoll(int rotations, double animationTime) {
// contains all Node objects in ChatScene in alphabetical order
- final var rotatableNodes = new Node[] { attachmentButton, attachmentView, contactLabel, infoLabel, messageList, messageTextArea,
- postButton, remainingChars, rotateButton, scene, settingsButton, chatList, voiceButton };
- final var random = new Random();
+ final var rotatableNodes = new Node[] { attachmentButton, attachmentView, contactLabel, infoLabel, messageList, messageTextArea, postButton,
+ remainingChars, rotateButton, scene, settingsButton, chatList, voiceButton };
for (final var node : rotatableNodes) {
- // Defines at most four whole rotation in at most 4s
- final var rotateTransition = new RotateTransition(Duration.seconds(random.nextDouble() * 3 + 1), node);
- rotateTransition.setByAngle((random.nextInt(3) + 1) * 360);
+ // Sets the animation duration to {animationTime}
+ final var rotateTransition = new RotateTransition(Duration.seconds(animationTime), node);
+ // rotates every element {rotations} times
+ rotateTransition.setByAngle(rotations * 360);
rotateTransition.play();
// This is needed as for some strange reason objects could stop before being
// rotated back to 0°
@@ -480,8 +515,8 @@ public final class ChatScene implements Restorable {
updateInfoLabel("You need to go online to send more messages", "infoLabel-error");
return;
}
- final var text = messageTextArea.getText().strip();
- try {
+ final var text = messageTextArea.getText().strip();
+ if (!messageTextAreaCommands.executeIfAnyPresent(text)) try {
// Creating the message and its metadata
final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(text);
@@ -569,9 +604,14 @@ public final class ChatScene implements Restorable {
private void copyAndPostMessage() {
final var messageText = messageTextArea.getText();
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
+ final var image = attachmentView.getImage();
+ final var messageAttachment = pendingAttachment;
postMessage();
messageTextArea.setText(messageText);
updateRemainingCharsLabel();
postButton.setDisable(messageText.isBlank());
+ attachmentView.setImage(image);
+ if (attachmentView.getImage() != null) attachmentView.setVisible(true);
+ pendingAttachment = messageAttachment;
}
}
diff --git a/common/.settings/org.eclipse.jdt.ui.prefs b/common/.settings/org.eclipse.jdt.ui.prefs
index 1d718a1..3110cf7 100644
--- a/common/.settings/org.eclipse.jdt.ui.prefs
+++ b/common/.settings/org.eclipse.jdt.ui.prefs
@@ -6,4 +6,4 @@ org.eclipse.jdt.ui.importorder=java;javax;javafx;org;com;envoy;
org.eclipse.jdt.ui.javadoc=true
org.eclipse.jdt.ui.ondemandthreshold=4
org.eclipse.jdt.ui.staticondemandthreshold=2
-org.eclipse.jdt.ui.text.custom_code_templates=