Merge pull request 'Replace the Internal Event Bus with Event Bus 0.0.3' (#20) from integrate-event-bus into develop

Reviewed-on: https://git.kske.dev/zdm/envoy/pulls/20
This commit is contained in:
Kai S. K. Engelbart 2020-09-09 18:30:31 +02:00
commit 8829f267ec
21 changed files with 253 additions and 299 deletions

View File

@ -10,16 +10,7 @@ import envoy.event.Event;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public final class ThemeChangeEvent extends Event<String> { public final class ThemeChangeEvent extends Event.Valueless {
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
/**
* Initializes a {@link ThemeChangeEvent} conveying information about the change
* of the theme currently in use.
*
* @param theme the name of the new theme
* @since Envoy Client v0.2-alpha
*/
public ThemeChangeEvent(String theme) { super(theme); }
} }

View File

@ -1,20 +1,19 @@
package envoy.client.net; package envoy.client.net;
import java.io.Closeable; import java.io.*;
import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Level; import java.util.logging.*;
import java.util.logging.Logger;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.SendEvent; import envoy.client.event.SendEvent;
import envoy.data.*; import envoy.data.*;
import envoy.event.*; import envoy.event.*;
import envoy.event.contact.ContactOperation; import envoy.event.Event;
import envoy.event.contact.UserSearchResult; import envoy.event.contact.*;
import envoy.util.EnvoyLog; import envoy.util.*;
import envoy.util.SerializationUtils;
import dev.kske.eventbus.*;
/** /**
* Establishes a connection to the server, performs a handshake and delivers * Establishes a connection to the server, performs a handshake and delivers
@ -29,7 +28,7 @@ import envoy.util.SerializationUtils;
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public final class Client implements Closeable { public final class Client implements EventListener, Closeable {
// Connection handling // Connection handling
private Socket socket; private Socket socket;
@ -45,6 +44,15 @@ public final class Client implements Closeable {
private static final Logger logger = EnvoyLog.getLogger(Client.class); private static final Logger logger = EnvoyLog.getLogger(Client.class);
private static final EventBus eventBus = EventBus.getInstance(); private static final EventBus eventBus = EventBus.getInstance();
/**
* Constructs a client and registers it as an event listener.
*
* @since Envoy Client v0.2-beta
*/
public Client() {
eventBus.registerListener(this);
}
/** /**
* Enters the online mode by acquiring a user ID from the server. As a * Enters the online mode by acquiring a user ID from the server. As a
* connection has to be established and a handshake has to be made, this method * connection has to be established and a handshake has to be made, this method
@ -164,22 +172,14 @@ public final class Client implements Closeable {
// Process ProfilePicChanges // Process ProfilePicChanges
receiver.registerProcessor(ProfilePicChange.class, eventBus::dispatch); receiver.registerProcessor(ProfilePicChange.class, eventBus::dispatch);
// Process requests to not send any more attachments as they will not be shown to // Process requests to not send any more attachments as they will not be shown
// to
// other users // other users
receiver.registerProcessor(NoAttachments.class, eventBus::dispatch); receiver.registerProcessor(NoAttachments.class, eventBus::dispatch);
// Process group creation results - they might have been disabled on the server // Process group creation results - they might have been disabled on the server
receiver.registerProcessor(GroupCreationResult.class, eventBus::dispatch); receiver.registerProcessor(GroupCreationResult.class, eventBus::dispatch);
// Send event
eventBus.register(SendEvent.class, evt -> {
try {
sendEvent(evt.get());
} catch (final IOException e) {
logger.log(Level.WARNING, "An error occurred when trying to send " + evt, e);
}
});
// Request a generator if none is present or the existing one is consumed // Request a generator if none is present or the existing one is consumed
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIdGenerator(); if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIdGenerator();
@ -219,6 +219,21 @@ public final class Client implements Closeable {
writeObject(new IDGeneratorRequest()); writeObject(new IDGeneratorRequest());
} }
/**
* Sends the value of a send event to the server.
*
* @param evt the send event to extract the value from
* @since Envoy Client v0.2-beta
*/
@dev.kske.eventbus.Event
private void onSendEvent(SendEvent evt) {
try {
sendEvent(evt.get());
} catch (final IOException e) {
logger.log(Level.WARNING, "An error occurred when trying to send " + evt, e);
}
}
@Override @Override
public void close() throws IOException { if (online) socket.close(); } public void close() throws IOException { if (online) socket.close(); }

View File

@ -4,10 +4,11 @@ import java.util.function.Consumer;
import java.util.logging.Logger; import java.util.logging.Logger;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.event.EventBus;
import envoy.event.GroupMessageStatusChange; import envoy.event.GroupMessageStatusChange;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>GroupMessageStatusChangePocessor.java</strong><br> * File: <strong>GroupMessageStatusChangePocessor.java</strong><br>
@ -25,5 +26,4 @@ public final class GroupMessageStatusChangeProcessor implements Consumer<GroupMe
if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.warning("Received invalid group message status change " + evt); if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.warning("Received invalid group message status change " + evt);
else EventBus.getInstance().dispatch(evt); else EventBus.getInstance().dispatch(evt);
} }
} }

View File

@ -4,10 +4,11 @@ import java.util.function.Consumer;
import java.util.logging.Logger; import java.util.logging.Logger;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.event.EventBus;
import envoy.event.MessageStatusChange; import envoy.event.MessageStatusChange;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>MessageStatusChangeProcessor.java</strong><br> * File: <strong>MessageStatusChangeProcessor.java</strong><br>

View File

@ -6,9 +6,10 @@ import java.util.logging.Logger;
import envoy.client.event.MessageCreationEvent; import envoy.client.event.MessageCreationEvent;
import envoy.data.GroupMessage; import envoy.data.GroupMessage;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.event.EventBus;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>ReceivedGroupMessageProcessor.java</strong><br> * File: <strong>ReceivedGroupMessageProcessor.java</strong><br>

View File

@ -5,7 +5,8 @@ import java.util.function.Consumer;
import envoy.client.event.MessageCreationEvent; import envoy.client.event.MessageCreationEvent;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.event.EventBus;
import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>

View File

@ -6,15 +6,15 @@ import java.util.logging.Level;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent; import javafx.scene.*;
import javafx.scene.Scene;
import javafx.stage.Stage; import javafx.stage.Stage;
import envoy.client.data.Settings; import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent; import envoy.client.event.ThemeChangeEvent;
import envoy.event.EventBus;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.*;
/** /**
* Manages a stack of scenes. The most recently added scene is displayed inside * Manages a stack of scenes. The most recently added scene is displayed inside
* a stage. When a scene is removed from the stack, its predecessor is * a stage. When a scene is removed from the stack, its predecessor is
@ -30,7 +30,7 @@ import envoy.util.EnvoyLog;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public final class SceneContext { public final class SceneContext implements EventListener {
/** /**
* Contains information about different scenes and their FXML resource files. * Contains information about different scenes and their FXML resource files.
@ -84,7 +84,7 @@ public final class SceneContext {
*/ */
public SceneContext(Stage stage) { public SceneContext(Stage stage) {
this.stage = stage; this.stage = stage;
EventBus.getInstance().register(ThemeChangeEvent.class, theme -> applyCSS()); EventBus.getInstance().registerListener(this);
} }
/** /**
@ -152,6 +152,9 @@ public final class SceneContext {
} }
} }
@Event(priority = 150, eventType = ThemeChangeEvent.class)
private void onThemeChange() { applyCSS(); }
/** /**
* @param <T> the type of the controller * @param <T> the type of the controller
* @return the controller used by the current scene * @return the controller used by the current scene

View File

@ -8,7 +8,9 @@ import javafx.stage.Stage;
import envoy.client.event.MessageCreationEvent; import envoy.client.event.MessageCreationEvent;
import envoy.data.Message; import envoy.data.Message;
import envoy.event.EventBus;
import dev.kske.eventbus.*;
import dev.kske.eventbus.Event;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -18,7 +20,7 @@ import envoy.event.EventBus;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public final class StatusTrayIcon { public final class StatusTrayIcon implements EventListener {
/** /**
* The {@link TrayIcon} provided by the System Tray API for controlling the * The {@link TrayIcon} provided by the System Tray API for controlling the
@ -67,14 +69,7 @@ public final class StatusTrayIcon {
trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); })); trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); }));
// Start processing message events // Start processing message events
EventBus.getInstance().register(MessageCreationEvent.class, evt -> { EventBus.getInstance().registerListener(this);
if (displayMessages) trayIcon
.displayMessage(
evt.get().hasAttachment() ? "New " + evt.get().getAttachment().getType().toString().toLowerCase() + " message received"
: "New message received",
evt.get().getText(),
MessageType.INFO);
});
} }
/** /**
@ -94,4 +89,11 @@ public final class StatusTrayIcon {
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public void hide() { SystemTray.getSystemTray().remove(trayIcon); } public void hide() { SystemTray.getSystemTray().remove(trayIcon); }
@Event
private void onMessageCreation(MessageCreationEvent evt) {
if (displayMessages) trayIcon
.displayMessage(evt.get().hasAttachment() ? "New " + evt.get().getAttachment().getType().toString().toLowerCase() + " message received"
: "New message received", evt.get().getText(), MessageType.INFO);
}
} }

View File

@ -1,7 +1,5 @@
package envoy.client.ui.controller; package envoy.client.ui.controller;
import static envoy.data.Message.MessageStatus.RECEIVED;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.io.*; import java.io.*;
@ -36,11 +34,15 @@ import envoy.client.ui.listcell.*;
import envoy.client.util.ReflectionUtil; import envoy.client.util.ReflectionUtil;
import envoy.data.*; import envoy.data.*;
import envoy.data.Attachment.AttachmentType; import envoy.data.Attachment.AttachmentType;
import envoy.data.Message.MessageStatus;
import envoy.event.*; import envoy.event.*;
import envoy.event.contact.ContactOperation; import envoy.event.contact.ContactOperation;
import envoy.exception.EnvoyException; import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.*;
import dev.kske.eventbus.Event;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>ChatSceneController.java</strong><br> * File: <strong>ChatSceneController.java</strong><br>
@ -49,7 +51,7 @@ import envoy.util.EnvoyLog;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public final class ChatScene implements Restorable { public final class ChatScene implements EventListener, Restorable {
@FXML @FXML
private GridPane scene; private GridPane scene;
@ -160,6 +162,8 @@ public final class ChatScene implements Restorable {
*/ */
@FXML @FXML
private void initialize() { private void initialize() {
eventBus.registerListener(this);
// Initialize message and user rendering // Initialize message and user rendering
messageList.setCellFactory(MessageListCell::new); messageList.setCellFactory(MessageListCell::new);
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new)); chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
@ -201,110 +205,119 @@ public final class ChatScene implements Restorable {
updateInfoLabel("You are offline", "info-label-warning"); updateInfoLabel("You are offline", "info-label-warning");
} }
}); });
}
// Listen to backEvents @Event(eventType = BackEvent.class)
eventBus.register(BackEvent.class, e -> tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal())); private void onBackEvent() { tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); }
// Listen to received messages @Event
eventBus.register(MessageCreationEvent.class, e -> { private void onMessageCreation(MessageCreationEvent evt) {
final var message = e.get(); final var message = evt.get();
// The sender of the message is the recipient of the chat // The sender of the message is the recipient of the chat
// Exceptions: this user is the sender (sync) or group message (group is // Exceptions: this user is the sender (sync) or group message (group is
// recipient) // recipient)
final var recipientID = message instanceof GroupMessage || message.getSenderID() == localDB.getUser().getID() ? message.getRecipientID() final var recipientID = message instanceof GroupMessage || message.getSenderID() == localDB.getUser().getID() ? message.getRecipientID()
: message.getSenderID(); : message.getSenderID();
localDB.getChat(recipientID).ifPresent(chat -> { localDB.getChat(recipientID).ifPresent(chat -> {
chat.insert(message); chat.insert(message);
if (chat.equals(currentChat)) { if (chat.equals(currentChat)) {
try { try {
currentChat.read(writeProxy); currentChat.read(writeProxy);
} catch (final IOException e1) { } catch (final IOException e1) {
logger.log(Level.WARNING, "Could not read current chat: ", e1); logger.log(Level.WARNING, "Could not read current chat: ", e1);
} }
Platform.runLater(() -> { ListViewRefresh.deepRefresh(messageList); scrollToMessageListEnd(); }); Platform.runLater(() -> { ListViewRefresh.deepRefresh(messageList); scrollToMessageListEnd(); });
// TODO: Increment unread counter for group messages with status < RECEIVED // TODO: Increment unread counter for group messages with status < RECEIVED
} else if (message.getSenderID() != localDB.getUser().getID() && message.getStatus() == RECEIVED) chat.incrementUnreadAmount(); } else
if (message.getSenderID() != localDB.getUser().getID() && message.getStatus() == MessageStatus.RECEIVED) chat.incrementUnreadAmount();
// Move chat with most recent unread messages to the top // Move chat with most recent unread messages to the top
Platform.runLater(() -> {
chats.getSource().remove(chat);
((ObservableList<Chat>) chats.getSource()).add(0, chat);
if (chat.equals(currentChat)) chatList.getSelectionModel().select(0);
});
});
});
// Listen to message status changes
eventBus.register(MessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(message -> {
message.setStatus(e.get());
// Update UI if in current chat and the current user was the sender of the
// message
if (currentChat != null && message.getSenderID() == client.getSender().getID()) Platform.runLater(messageList::refresh);
}));
eventBus.register(GroupMessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(groupMessage -> {
((GroupMessage) groupMessage).getMemberStatuses().replace(e.getMemberID(), e.get());
// Update UI if in current chat
if (currentChat != null && groupMessage.getRecipientID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
}));
// Listen to user status changes
eventBus.register(UserStatusChange.class,
e -> chats.getSource()
.stream()
.filter(c -> c.getRecipient().getID() == e.getID())
.findAny()
.map(Chat::getRecipient)
.ifPresent(u -> { ((User) u).setStatus(e.get()); Platform.runLater(() -> ListViewRefresh.deepRefresh(chatList)); }));
// Listen to contacts changes
eventBus.register(ContactOperation.class, e -> {
final var contact = e.get();
switch (e.getOperationType()) {
case ADD:
if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
final var chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
Platform.runLater(() -> ((ObservableList<Chat>) chats.getSource()).add(0, chat));
break;
case REMOVE:
Platform.runLater(() -> chats.getSource().removeIf(c -> c.getRecipient().equals(contact)));
break;
}
});
// Disable attachment button if server says attachments will be filtered out
eventBus.register(NoAttachments.class, e -> {
Platform.runLater(() -> { Platform.runLater(() -> {
attachmentButton.setDisable(true); chats.getSource().remove(chat);
voiceButton.setDisable(true); ((ObservableList<Chat>) chats.getSource()).add(0, chat);
final var alert = new Alert(AlertType.ERROR);
alert.setTitle("No attachments possible"); if (chat.equals(currentChat)) chatList.getSelectionModel().select(0);
alert.setHeaderText("Your current server does not support attachments.");
alert.setContentText("If this is unplanned, please contact your server administrator.");
alert.showAndWait();
}); });
}); });
}
eventBus.register(GroupCreationResult.class, e -> Platform.runLater(() -> { newGroupButton.setDisable(!e.get()); })); @Event
private void onMessageStatusChange(MessageStatusChange evt) {
eventBus.register(ThemeChangeEvent.class, e -> { localDB.getMessage(evt.getID()).ifPresent(message -> {
settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE))); message.setStatus(evt.get());
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE))); // Update UI if in current chat and the current user was the sender of the
attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE))); // message
DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20); if (currentChat != null && message.getSenderID() == client.getSender().getID()) Platform.runLater(messageList::refresh);
attachmentView.setImage(isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
messageList.setCellFactory(MessageListCell::new);
if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
}); });
} }
@Event
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
localDB.getMessage(evt.getID()).ifPresent(groupMessage -> {
((GroupMessage) groupMessage).getMemberStatuses().replace(evt.getMemberID(), evt.get());
// Update UI if in current chat
if (currentChat != null && groupMessage.getRecipientID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
});
}
@Event
private void onUserStatusChange(UserStatusChange evt) {
chats.getSource()
.stream()
.filter(c -> c.getRecipient().getID() == evt.getID())
.findAny()
.map(Chat::getRecipient)
.ifPresent(u -> { ((User) u).setStatus(evt.get()); Platform.runLater(() -> ListViewRefresh.deepRefresh(chatList)); });
}
@Event
private void onContactOperation(ContactOperation operation) {
final var contact = operation.get();
switch (operation.getOperationType()) {
case ADD:
if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
final var chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
Platform.runLater(() -> ((ObservableList<Chat>) chats.getSource()).add(0, chat));
break;
case REMOVE:
Platform.runLater(() -> chats.getSource().removeIf(c -> c.getRecipient().equals(contact)));
break;
}
}
@Event(eventType = NoAttachments.class)
private void onNoAttachments() {
Platform.runLater(() -> {
attachmentButton.setDisable(true);
voiceButton.setDisable(true);
final var alert = new Alert(AlertType.ERROR);
alert.setTitle("No attachments possible");
alert.setHeaderText("Your current server does not support attachments.");
alert.setContentText("If this is unplanned, please contact your server administrator.");
alert.showAndWait();
});
}
@Event
private void onGroupCreationResult(GroupCreationResult result) { Platform.runLater(() -> newGroupButton.setDisable(!result.get())); }
@Event(eventType = ThemeChangeEvent.class)
private void onThemeChange() {
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)));
DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
attachmentView.setImage(isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
messageList.setCellFactory(MessageListCell::new);
if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
}
/** /**
* Initializes all {@code SystemCommands} used in {@code ChatScene}. * Initializes all {@code SystemCommands} used in {@code ChatScene}.
* *

View File

@ -1,26 +1,21 @@
package envoy.client.ui.controller; package envoy.client.ui.controller;
import java.util.function.Consumer; import java.util.logging.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import envoy.client.event.BackEvent; import envoy.client.event.*;
import envoy.client.event.SendEvent; import envoy.client.ui.listcell.*;
import envoy.client.ui.listcell.ContactControl;
import envoy.client.ui.listcell.ListCellFactory;
import envoy.data.User; import envoy.data.User;
import envoy.event.ElementOperation; import envoy.event.ElementOperation;
import envoy.event.EventBus; import envoy.event.contact.*;
import envoy.event.contact.ContactOperation;
import envoy.event.contact.UserSearchRequest;
import envoy.event.contact.UserSearchResult;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import dev.kske.eventbus.*;
/** /**
* Provides a search bar in which a user name (substring) can be entered. The * Provides a search bar in which a user name (substring) can be entered. The
* users with a matching name are then displayed inside a list view. A * users with a matching name are then displayed inside a list view. A
@ -39,7 +34,7 @@ import envoy.util.EnvoyLog;
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public class ContactSearchTab { public class ContactSearchTab implements EventListener {
@FXML @FXML
private TextArea searchBar; private TextArea searchBar;
@ -48,26 +43,29 @@ public class ContactSearchTab {
private ListView<User> userList; private ListView<User> userList;
private Alert alert = new Alert(AlertType.CONFIRMATION); private Alert alert = new Alert(AlertType.CONFIRMATION);
private User currentlySelectedUser; private User currentlySelectedUser;
private final Consumer<ContactOperation> handler = e -> {
final var contact = e.get();
if (e.getOperationType() == ElementOperation.ADD) Platform.runLater(() -> {
userList.getItems().remove(contact);
if (currentlySelectedUser != null && currentlySelectedUser.equals(contact) && alert.isShowing()) alert.close();
});
};
private static final EventBus eventBus = EventBus.getInstance(); private static final EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ChatScene.class); private static final Logger logger = EnvoyLog.getLogger(ChatScene.class);
@FXML @FXML
private void initialize() { private void initialize() {
eventBus.registerListener(this);
userList.setCellFactory(new ListCellFactory<>(ContactControl::new)); userList.setCellFactory(new ListCellFactory<>(ContactControl::new));
eventBus.register(UserSearchResult.class, }
response -> Platform.runLater(() -> { userList.getItems().clear(); userList.getItems().addAll(response.get()); }));
eventBus.register(ContactOperation.class, handler); @Event
private void onUserSearchResult(UserSearchResult result) {
Platform.runLater(() -> { userList.getItems().clear(); userList.getItems().addAll(result.get()); });
}
@Event
private void onContactOperation(ContactOperation operation) {
final var contact = operation.get();
if (operation.getOperationType() == ElementOperation.ADD) Platform.runLater(() -> {
userList.getItems().remove(contact);
if (currentlySelectedUser != null && currentlySelectedUser.equals(contact) && alert.isShowing()) alert.close();
});
} }
/** /**

View File

@ -8,20 +8,15 @@ import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import envoy.client.data.Chat; import envoy.client.data.*;
import envoy.client.data.Context; import envoy.client.event.*;
import envoy.client.data.LocalDB; import envoy.client.ui.listcell.*;
import envoy.client.event.BackEvent; import envoy.data.*;
import envoy.client.event.SendEvent;
import envoy.client.ui.listcell.ContactControl;
import envoy.client.ui.listcell.ListCellFactory;
import envoy.data.Contact;
import envoy.data.Group;
import envoy.data.User;
import envoy.event.EventBus;
import envoy.event.GroupCreation; import envoy.event.GroupCreation;
import envoy.util.Bounds; import envoy.util.Bounds;
import dev.kske.eventbus.*;
/** /**
* Provides a group creation interface. A group name can be entered in the text * Provides a group creation interface. A group name can be entered in the text
* field at the top. Available users (local chat recipients) are displayed * field at the top. Available users (local chat recipients) are displayed
@ -38,7 +33,7 @@ import envoy.util.Bounds;
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public class GroupCreationTab { public class GroupCreationTab implements EventListener {
@FXML @FXML
private Button createButton; private Button createButton;

View File

@ -1,9 +1,7 @@
package envoy.client.ui.controller; package envoy.client.ui.controller;
import java.util.logging.Level; import java.util.logging.*;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -11,13 +9,12 @@ import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import envoy.client.data.ClientConfig; import envoy.client.data.ClientConfig;
import envoy.client.ui.IconUtil; import envoy.client.ui.*;
import envoy.client.ui.Startup;
import envoy.data.LoginCredentials; import envoy.data.LoginCredentials;
import envoy.event.EventBus;
import envoy.event.HandshakeRejection; import envoy.event.HandshakeRejection;
import envoy.util.Bounds; import envoy.util.*;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.*;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -28,7 +25,7 @@ import envoy.util.EnvoyLog;
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public final class LoginScene { public final class LoginScene implements EventListener {
@FXML @FXML
private TextField userTextField; private TextField userTextField;
@ -60,7 +57,6 @@ public final class LoginScene {
private boolean registration = false; private boolean registration = false;
private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); private static final Logger logger = EnvoyLog.getLogger(LoginScene.class);
private static final EventBus eventBus = EventBus.getInstance();
private static final ClientConfig config = ClientConfig.getInstance(); private static final ClientConfig config = ClientConfig.getInstance();
@FXML @FXML
@ -68,7 +64,7 @@ public final class LoginScene {
connectionLabel.setText("Server: " + config.getServer() + ":" + config.getPort()); connectionLabel.setText("Server: " + config.getServer() + ":" + config.getPort());
// Show an alert after an unsuccessful handshake // Show an alert after an unsuccessful handshake
eventBus.register(HandshakeRejection.class, e -> Platform.runLater(() -> { new Alert(AlertType.ERROR, e.get()).showAndWait(); })); EventBus.getInstance().registerListener(this);
logo.setImage(IconUtil.loadIcon("envoy_logo")); logo.setImage(IconUtil.loadIcon("envoy_logo"));
@ -119,4 +115,7 @@ public final class LoginScene {
logger.log(Level.INFO, "The login process has been cancelled. Exiting..."); logger.log(Level.INFO, "The login process has been cancelled. Exiting...");
System.exit(0); System.exit(0);
} }
@Event
private void onHandshakeRejection(HandshakeRejection evt) { new Alert(AlertType.ERROR, evt.get()).showAndWait(); }
} }

View File

@ -7,9 +7,10 @@ import javafx.scene.input.InputEvent;
import envoy.client.event.SendEvent; import envoy.client.event.SendEvent;
import envoy.client.util.IssueUtil; import envoy.client.util.IssueUtil;
import envoy.data.User; import envoy.data.User;
import envoy.event.EventBus;
import envoy.event.IssueProposal; import envoy.event.IssueProposal;
import dev.kske.eventbus.EventBus;
/** /**
* This class offers the option for users to submit a bug report. Only the title * This class offers the option for users to submit a bug report. Only the title
* of a bug is needed to be sent. * of a bug is needed to be sent.

View File

@ -1,12 +1,12 @@
package envoy.client.ui.settings; package envoy.client.ui.settings;
import javafx.scene.control.ComboBox; import javafx.scene.control.*;
import javafx.scene.control.Tooltip;
import envoy.client.data.SettingsItem; import envoy.client.data.SettingsItem;
import envoy.client.event.ThemeChangeEvent; import envoy.client.event.ThemeChangeEvent;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
import envoy.event.EventBus;
import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>
@ -44,7 +44,7 @@ public final class GeneralSettingsPane extends SettingsPane {
combobox.setTooltip(new Tooltip("Determines the current theme Envoy will be displayed in.")); combobox.setTooltip(new Tooltip("Determines the current theme Envoy will be displayed in."));
combobox.setValue(settings.getCurrentTheme()); combobox.setValue(settings.getCurrentTheme());
combobox.setOnAction( combobox.setOnAction(
e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent(combobox.getValue())); }); e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent()); });
getChildren().add(combobox); getChildren().add(combobox);
final var statusComboBox = new ComboBox<UserStatus>(); final var statusComboBox = new ComboBox<UserStatus>();

View File

@ -1,31 +1,27 @@
package envoy.client.ui.settings; package envoy.client.ui.settings;
import java.io.ByteArrayInputStream; import java.io.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.logging.Level; import java.util.logging.*;
import java.util.logging.Logger;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Cursor; import javafx.scene.Cursor;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image; import javafx.scene.image.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.InputEvent; import javafx.scene.input.InputEvent;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import envoy.client.event.SendEvent; import envoy.client.event.SendEvent;
import envoy.client.ui.IconUtil; import envoy.client.ui.*;
import envoy.client.ui.SceneContext;
import envoy.client.ui.custom.ProfilePicImageView; import envoy.client.ui.custom.ProfilePicImageView;
import envoy.data.User; import envoy.data.User;
import envoy.event.*; import envoy.event.*;
import envoy.util.Bounds; import envoy.util.*;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br> * Project: <strong>envoy-client</strong><br>

View File

@ -7,19 +7,20 @@
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
module envoy { module envoy.client {
requires transitive envoy.common; requires envoy.common;
requires transitive java.desktop; requires java.desktop;
requires transitive java.logging; requires java.logging;
requires transitive java.prefs; requires java.prefs;
requires javafx.controls; requires javafx.controls;
requires javafx.fxml; requires javafx.fxml;
requires javafx.base; requires javafx.base;
requires javafx.graphics; requires javafx.graphics;
opens envoy.client.ui to javafx.graphics, javafx.fxml; opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus;
opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util; opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus;
opens envoy.client.ui.custom to javafx.graphics, javafx.fxml; opens envoy.client.ui.custom to javafx.graphics, javafx.fxml;
opens envoy.client.ui.settings to envoy.client.util; opens envoy.client.ui.settings to envoy.client.util;
opens envoy.client.net to dev.kske.eventbus;
} }

View File

@ -12,11 +12,24 @@
<version>0.1-beta</version> <version>0.1-beta</version>
</parent> </parent>
<repositories>
<repository>
<id>kske-repo</id>
<url>https://kske.dev/maven-repo</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency>
<groupId>dev.kske</groupId>
<artifactId>event-bus</artifactId>
<version>0.0.3</version>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId> <artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version> <version>5.5.2</version>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -2,7 +2,13 @@ package envoy.event;
import java.io.Serializable; import java.io.Serializable;
import dev.kske.eventbus.IEvent;
/** /**
* This class serves as a convenience base class for all events. It implements
* the {@link IEvent} interface and provides a generic value. For events without
* a value there also is {@link envoy.event.Event.Valueless}.
* <p>
* Project: <strong>envoy-common</strong><br> * Project: <strong>envoy-common</strong><br>
* File: <strong>Event.java</strong><br> * File: <strong>Event.java</strong><br>
* Created: <strong>04.12.2019</strong><br> * Created: <strong>04.12.2019</strong><br>
@ -11,7 +17,7 @@ import java.io.Serializable;
* @param <T> the type of the Event * @param <T> the type of the Event
* @since Envoy v0.2-alpha * @since Envoy v0.2-alpha
*/ */
public abstract class Event<T> implements Serializable { public abstract class Event<T> implements IEvent, Serializable {
protected final T value; protected final T value;

View File

@ -1,82 +0,0 @@
package envoy.event;
import java.util.*;
import java.util.function.Consumer;
/**
* This class handles events by allowing event handlers 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.<br>
* <br>
* Project: <strong>envoy-common</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 final class EventBus {
/**
* Contains all event handler instances registered at this event bus as values
* mapped to by their supported event classes.
*/
private Map<Class<? extends Event<?>>, List<Consumer<Event<?>>>> handlers = new HashMap<>();
/**
* The singleton instance of this event bus 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 event bus
* @since Envoy v0.2-alpha
*/
public static EventBus getInstance() { return eventBus; }
/**
* Registers an event handler to be notified when an
* event of a certain type is dispatched.
*
* @param <T> the type of event values to notify the handler about
* @param eventClass the class which the event handler is subscribing to
* @param handler the event handler to register
* @since Envoy v0.2-alpha
*/
public <T extends Event<?>> void register(Class<T> eventClass, Consumer<T> handler) {
if (!handlers.containsKey(eventClass)) handlers.put(eventClass, new ArrayList<>());
handlers.get(eventClass).add((Consumer<Event<?>>) handler);
}
/**
* Dispatches an event to every event handler subscribed to it.
*
* @param event the {@link Event} to dispatch
* @since Envoy v0.2-alpha
*/
public void dispatch(Event<?> event) {
handlers.keySet()
.stream()
.filter(event.getClass()::equals)
.map(handlers::get)
.flatMap(List::stream)
.forEach(h -> h.accept(event));
}
/**
* @return a map of all event handler instances currently registered at this
* event bus with the event classes they are subscribed to as keys
* @since Envoy v0.2-alpha
*/
public Map<Class<? extends Event<?>>, List<Consumer<Event<?>>>> getHandlers() { return handlers; }
}

View File

@ -16,4 +16,5 @@ module envoy.common {
exports envoy.event.contact; exports envoy.event.contact;
requires transitive java.logging; requires transitive java.logging;
requires transitive dev.kske.eventbus;
} }

View File

@ -16,5 +16,4 @@ module envoy.server {
requires transitive java.persistence; requires transitive java.persistence;
requires transitive java.sql; requires transitive java.sql;
requires transitive org.hibernate.orm.core; requires transitive org.hibernate.orm.core;
} }