From 40447f3f42e799af08dee7b36b11a5bc8b15e436 Mon Sep 17 00:00:00 2001 From: kske Date: Sun, 20 Sep 2020 14:13:11 +0200 Subject: [PATCH 1/6] Change Event Bus version to 0.0.4, fix message event handler The message event handler ignored group messages, as event handlers do not include subtypes be default. This behavior has been implemented in Event Bus 0.0.4 and integrated into Envoy. --- client/src/main/java/envoy/client/ui/controller/ChatScene.java | 2 +- common/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 36845a3..912076a 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -210,7 +210,7 @@ public final class ChatScene implements EventListener, Restorable { @Event(eventType = BackEvent.class) private void onBackEvent() { tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); } - @Event + @Event(includeSubtypes = true) private void onMessage(Message message) { // The sender of the message is the recipient of the chat diff --git a/common/pom.xml b/common/pom.xml index b2165ef..eb8daea 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -23,7 +23,7 @@ dev.kske event-bus - 0.0.3 + 0.0.4 org.junit.jupiter From 16a0786d540d12481a2bbffeb0bf0811e4b9b44c Mon Sep 17 00:00:00 2001 From: delvh Date: Sat, 19 Sep 2020 17:27:54 +0200 Subject: [PATCH 2/6] Fixed bug adding line break in messages sent using "Enter" --- .../envoy/client/ui/controller/ChatScene.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) 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 912076a..42e9f87 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -542,16 +542,20 @@ public final class ChatScene implements EventListener, Restorable { */ @FXML private void checkKeyCombination(KeyEvent e) { + // Checks whether the text is too long messageTextUpdated(); + // Sending an IsTyping event if none has been sent for // IsTyping#millisecondsActive if (currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) { eventBus.dispatch(new SendEvent(new IsTyping(getChatID(), currentChat.getRecipient().getID()))); currentChat.lastWritingEventWasNow(); } - // Automatic sending of messages via (ctrl +) enter - checkPostConditions(e); + + // 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); } /** @@ -572,8 +576,16 @@ public final class ChatScene implements EventListener, Restorable { */ @FXML private void checkPostConditions(KeyEvent e) { - checkPostConditions(settings.isEnterToSend() && e.getCode() == KeyCode.ENTER - || !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown()); + final var messagePosted = settings.isEnterToSend() && e.getCode() == KeyCode.ENTER + || !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown(); + if (messagePosted) { + + // Removing an inserted line break if added by pressing enter + final var text = messageTextArea.getText(); + final var caretPosition = messageTextArea.getCaretPosition(); + if (text.charAt(caretPosition - 1) == '\n') messageTextArea.setText(new StringBuilder(text).deleteCharAt(caretPosition - 1).toString()); + } + checkPostConditions(messagePosted); } private void checkPostConditions(boolean postMessage) { From 4959bc963471d7cdf1012e59af392e32db93a79c Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 20 Sep 2020 15:27:23 +0200 Subject: [PATCH 3/6] Fixed bug not updating UI after click on context menu item fixes #11 Additionally, previous commit fixes #5 --- .../envoy/client/ui/controller/ChatScene.java | 11 +- .../ui/custom/TextInputContextMenu.java | 109 ++++++++++++++++++ .../main/java/envoy/data/MessageBuilder.java | 58 ++++------ 3 files changed, 140 insertions(+), 38 deletions(-) create mode 100644 client/src/main/java/envoy/client/ui/custom/TextInputContextMenu.java 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 From 7cc49288269452e3a17bcf38f554c4eb2b9147b8 Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 20 Sep 2020 16:06:13 +0200 Subject: [PATCH 4/6] Fixed bug removing \n and added ability to use "ctrl"+"enter" for LB Fixes #34 --- .../envoy/client/ui/controller/ChatScene.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) 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 c022ac7..fbec656 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -562,7 +562,7 @@ 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 && e != null) checkPostConditions(e); - + // This is needed for the messageTA context menu else if (e == null) checkPostConditions(false); } @@ -585,14 +585,23 @@ public final class ChatScene implements EventListener, Restorable { */ @FXML private void checkPostConditions(KeyEvent e) { - final var messagePosted = settings.isEnterToSend() && e.getCode() == KeyCode.ENTER - || !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown(); + final var enterPressed = e.getCode() == KeyCode.ENTER; + final var messagePosted = enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown() : false; if (messagePosted) { // Removing an inserted line break if added by pressing enter final var text = messageTextArea.getText(); - final var caretPosition = messageTextArea.getCaretPosition(); - if (text.charAt(caretPosition - 1) == '\n') messageTextArea.setText(new StringBuilder(text).deleteCharAt(caretPosition - 1).toString()); + final var textPosition = messageTextArea.getCaretPosition() - 1; + if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n') + messageTextArea.setText(new StringBuilder(text).deleteCharAt(textPosition).toString()); + } + + // if control is pressed, the enter press is originally invalidated. Here it'll + // be inserted again + else if (enterPressed && e.isControlDown()) { + var caretPosition = messageTextArea.getCaretPosition(); + messageTextArea.setText(new StringBuilder(messageTextArea.getText()).insert(caretPosition, '\n').toString()); + messageTextArea.positionCaret(++caretPosition); } checkPostConditions(messagePosted); } From d394c2d058786972e72aa994dc7d66e146af446f Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 20 Sep 2020 22:11:15 +0200 Subject: [PATCH 5/6] Added option to close Envoy Linux-like with "Control"+"Q" --- .../src/main/java/envoy/client/ui/SceneContext.java | 12 ++++++++++++ client/src/main/java/envoy/client/ui/Startup.java | 10 +++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/client/src/main/java/envoy/client/ui/SceneContext.java b/client/src/main/java/envoy/client/ui/SceneContext.java index 2d693af..083333a 100644 --- a/client/src/main/java/envoy/client/ui/SceneContext.java +++ b/client/src/main/java/envoy/client/ui/SceneContext.java @@ -7,6 +7,7 @@ import java.util.logging.Level; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.*; +import javafx.scene.input.*; import javafx.stage.Stage; import envoy.client.data.Settings; @@ -105,6 +106,17 @@ public final class SceneContext implements EventListener { sceneStack.push(scene); stage.setScene(scene); + + // Adding the option to exit Linux-like with "Control" + "Q" + scene.getAccelerators() + .put(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN), + () -> { + // Presumably no Settings are loaded in the login scene, hence Envoy is closed + // directly + if (sceneInfo != SceneInfo.LOGIN_SCENE && settings.isHideOnClose()) stage.setIconified(true); + else System.exit(0); + }); + // The LoginScene is the only scene not intended to be resized // As strange as it seems, this is needed as otherwise the LoginScene won't be // displayed on some OS (...Debian...) diff --git a/client/src/main/java/envoy/client/ui/Startup.java b/client/src/main/java/envoy/client/ui/Startup.java index 90e4499..f53d474 100644 --- a/client/src/main/java/envoy/client/ui/Startup.java +++ b/client/src/main/java/envoy/client/ui/Startup.java @@ -67,7 +67,7 @@ public final class Startup extends Application { // Initialize the local database try { - File localDBDir = new File(config.getHomeDirectory(), config.getLocalDB().getPath()); + final var localDBDir = new File(config.getHomeDirectory(), config.getLocalDB().getPath()); logger.info("Initializing LocalDB at " + localDBDir); localDB = new LocalDB(localDBDir); localDB.lock(); @@ -99,11 +99,9 @@ public final class Startup extends Application { if (!performHandshake( LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync()))) sceneContext.load(SceneInfo.LOGIN_SCENE); - } else { - + } else // Load login scene sceneContext.load(SceneInfo.LOGIN_SCENE); - } } /** @@ -126,9 +124,7 @@ public final class Startup extends Application { loadChatScene(); client.initReceiver(localDB, cacheMap); return true; - } else { - return false; - } + } else return false; } catch (IOException | InterruptedException | TimeoutException e) { logger.log(Level.INFO, "Could not connect to server. Entering offline mode..."); return attemptOfflineMode(credentials.getIdentifier()); From 3cd9d76d2c7c2305b307d4afb5db75794b8ab1ff Mon Sep 17 00:00:00 2001 From: delvh Date: Mon, 21 Sep 2020 18:45:01 +0200 Subject: [PATCH 6/6] Fixed sudden Eclipse annoyance --- client/.project | 1 + .../envoy/client/data/commands/SystemCommandsMap.java | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/.project b/client/.project index 0f558b5..698636a 100644 --- a/client/.project +++ b/client/.project @@ -1,4 +1,5 @@ + client diff --git a/client/src/main/java/envoy/client/data/commands/SystemCommandsMap.java b/client/src/main/java/envoy/client/data/commands/SystemCommandsMap.java index ed75f24..959e0dd 100644 --- a/client/src/main/java/envoy/client/data/commands/SystemCommandsMap.java +++ b/client/src/main/java/envoy/client/data/commands/SystemCommandsMap.java @@ -1,9 +1,8 @@ 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.function.Consumer; +import java.util.logging.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -198,13 +197,13 @@ public final class SystemCommandsMap { * are present * @since Envoy Client v0.2-beta */ - public void requestRecommendations(String input, Function, Void> action) { + public void requestRecommendations(String input, Consumer> action) { final var partialCommand = getCommand(input); // Get the expected commands final var recommendations = recommendCommands(partialCommand); if (recommendations.isEmpty()) return; // Execute the given action - else action.apply(recommendations); + else action.accept(recommendations); } /**