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 42e9f87..c022ac7 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,7 @@ import envoy.client.data.commands.*;
import envoy.client.event.*;
import envoy.client.net.*;
import envoy.client.ui.*;
+import envoy.client.ui.custom.TextInputContextMenu;
import envoy.client.ui.listcell.*;
import envoy.client.util.ReflectionUtil;
import envoy.data.*;
@@ -168,6 +169,11 @@ public final class ChatScene implements EventListener, Restorable {
messageList.setCellFactory(MessageListCell::new);
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
+ // JavaFX provides an internal way of populating the context menu of a textarea.
+ // We, however, need additional functionality.
+ messageTextArea.setContextMenu(new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
+
+ // Set the icons of buttons and image views
settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
@@ -555,7 +561,10 @@ public final class ChatScene implements EventListener, Restorable {
// KeyPressed will be called before the char has been added to the text, hence
// this is needed for the first char
- if (messageTextArea.getText().length() == 1) checkPostConditions(e);
+ if (messageTextArea.getText().length() == 1 && e != null) checkPostConditions(e);
+
+ // This is needed for the messageTA context menu
+ else if (e == null) checkPostConditions(false);
}
/**
diff --git a/client/src/main/java/envoy/client/ui/custom/TextInputContextMenu.java b/client/src/main/java/envoy/client/ui/custom/TextInputContextMenu.java
new file mode 100644
index 0000000..02b1889
--- /dev/null
+++ b/client/src/main/java/envoy/client/ui/custom/TextInputContextMenu.java
@@ -0,0 +1,109 @@
+package envoy.client.ui.custom;
+
+import java.util.function.Consumer;
+
+import javafx.event.*;
+import javafx.scene.control.*;
+import javafx.scene.input.Clipboard;
+
+/**
+ * Displays a context menu that offers an additional option when one of
+ * its menu items has been clicked.
+ *
+ * Current options are:
+ *
+ * - undo
+ * - redo
+ * - cut
+ * - copy
+ * - paste
+ * - delete
+ * - clear
+ * - Select all
+ *
+ *
+ * Project: client
+ * File: TextInputContextMenu.java
+ * Created: 20.09.2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.2-beta
+ * @apiNote please refrain from using
+ * {@link ContextMenu#setOnShowing(EventHandler)} as this is already
+ * used by this component
+ */
+public class TextInputContextMenu extends ContextMenu {
+
+ private final MenuItem undoMI = new MenuItem("Undo");
+ private final MenuItem redoMI = new MenuItem("Redo");
+ private final MenuItem cutMI = new MenuItem("Cut");
+ private final MenuItem copyMI = new MenuItem("Copy");
+ private final MenuItem pasteMI = new MenuItem("Paste");
+ private final MenuItem deleteMI = new MenuItem("Delete selection");
+ private final MenuItem clearMI = new MenuItem("Clear");
+ private final MenuItem selectAllMI = new MenuItem("Select all");
+ private final MenuItem separatorMI = new SeparatorMenuItem();
+
+ /**
+ * Creates a new {@code TextInputContextMenu} with an optional action when
+ * this menu was clicked. Currently shows:
+ *
+ * - undo
+ * - redo
+ * - cut
+ * - copy
+ * - paste
+ * - delete
+ * - clear
+ * - Select all
+ *
+ *
+ * @param control the text input component to display this
+ * {@code ContextMenu}
+ * @param menuItemClicked the second action to perform when a menu item of this
+ * context menu has been clicked
+ * @since Envoy Client v0.2-beta
+ * @apiNote please refrain from using
+ * {@link ContextMenu#setOnShowing(EventHandler)} as this is already
+ * used by this component
+ */
+ public TextInputContextMenu(TextInputControl control, Consumer menuItemClicked) {
+
+ // Define the actions when clicked
+ undoMI.setOnAction(addAction(e -> control.undo(), menuItemClicked));
+ redoMI.setOnAction(addAction(e -> control.redo(), menuItemClicked));
+ cutMI.setOnAction(addAction(e -> control.cut(), menuItemClicked));
+ copyMI.setOnAction(addAction(e -> control.copy(), menuItemClicked));
+ pasteMI.setOnAction(addAction(e -> control.paste(), menuItemClicked));
+ deleteMI.setOnAction(addAction(e -> control.replaceSelection(""), menuItemClicked));
+ clearMI.setOnAction(addAction(e -> control.setText(""), menuItemClicked));
+ selectAllMI.setOnAction(addAction(e -> control.selectAll(), menuItemClicked));
+
+ // Define the times it will be disabled
+ undoMI.disableProperty().bind(control.undoableProperty().not());
+ redoMI.disableProperty().bind(control.redoableProperty().not());
+ cutMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
+ copyMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
+ deleteMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
+ clearMI.disableProperty().bind(control.textProperty().isEmpty());
+ setOnShowing(e -> pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()));
+
+ selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE);
+
+ // Add all items to the ContextMenu
+ getItems().add(undoMI);
+ getItems().add(redoMI);
+ getItems().add(cutMI);
+ getItems().add(copyMI);
+ getItems().add(pasteMI);
+ getItems().add(separatorMI);
+ getItems().add(deleteMI);
+ getItems().add(clearMI);
+ getItems().add(separatorMI);
+ getItems().add(selectAllMI);
+ }
+
+ private EventHandler addAction(Consumer originalAction, Consumer additionalAction) {
+ return e -> { originalAction.accept(e); additionalAction.accept(e); };
+ }
+}
diff --git a/common/src/main/java/envoy/data/MessageBuilder.java b/common/src/main/java/envoy/data/MessageBuilder.java
index 2cddc6a..b1a3f74 100644
--- a/common/src/main/java/envoy/data/MessageBuilder.java
+++ b/common/src/main/java/envoy/data/MessageBuilder.java
@@ -1,8 +1,7 @@
package envoy.data;
import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
import envoy.data.Message.MessageStatus;
@@ -80,21 +79,15 @@ public final class MessageBuilder {
* Creates an instance of {@link Message} with the previously supplied values.
* If a mandatory value is not set, a default value will be used instead:
*
- *
- *
- * {@code date} |
- * {@code Instant.now()} and {@code null} for {@code receivedDate} and
- * {@code readDate} |
- *
- *
- * {@code text} |
- * {@code ""} |
- *
- *
- * {@code status} |
- * {@code MessageStatus.WAITING} |
- *
- *
+ * {@code date}
+ * {@code Instant.now()} and {@code null} for {@code receivedDate} and
+ * {@code readDate}
+ *
+ * {@code text}
+ * {@code ""}
+ *
+ * {@code status}
+ * {@code MessageStatus.WAITING}
*
* @return a new instance of {@link Message}
* @since Envoy Common v0.2-alpha
@@ -111,16 +104,12 @@ public final class MessageBuilder {
* If a mandatory value is not set, a default value will be used
* instead:
*
- *
- *
- * {@code time stamp} |
- * {@code Instant.now()} |
- *
- *
- * {@code text} |
- * {@code ""} |
- *
- *
+ * {@code time stamp}
+ * {@code Instant.now()}
+ *
+ * {@code text}
+ * {@code ""}
+ *
*
* @param group the {@link Group} that is used to fill the map of member
* statuses
@@ -138,16 +127,11 @@ public final class MessageBuilder {
* values. If a mandatory value is not set, a default value will be used
* instead:
*
- *
- *
- * {@code time stamp} |
- * {@code Instant.now()} |
- *
- *
- * {@code text} |
- * {@code ""} |
- *
- *
+ * {@code time stamp}
+ * {@code Instant.now()}
+ *
+ * {@code text}
+ * {@code ""}
*
* @param group the {@link Group} that is used to fill the map of
* member statuses