Replace the internal event bus with Event Bus 0.0.3

The Event class has been retrofitted to implement IEvent, so that no
event implementations had to be changed.
This commit is contained in:
Kai S. K. Engelbart 2020-09-08 20:41:01 +02:00
parent 69ea737361
commit 465ed20efa
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
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;
} }