Merge branch 'develop' into f/groupMessages

This commit is contained in:
Kai S. K. Engelbart 2020-07-05 11:15:00 +02:00
commit 483ab9e344
28 changed files with 908 additions and 481 deletions

View File

@ -5,6 +5,7 @@ labels: bug
assignees: CyB3RC0nN0R, delvh, DieGurke assignees: CyB3RC0nN0R, delvh, DieGurke
reviewers: CyB3RC0nN0R, delvh reviewers: CyB3RC0nN0R, delvh
projects: Envoy projects: Envoy
milestone: Envoy v0.3-alpha milestone: Envoy v0.1-beta
--- ---
Fixes #{issue} Fixes #{issue}

View File

@ -1,9 +1,10 @@
--- ---
name: Feature integration name: Feature integration
title: Added feature title: Added feature
labels: enhancement labels: feature
assignees: CyB3RC0nN0R, delvh, DieGurke assignees: CyB3RC0nN0R, delvh, DieGurke
reviewers: CyB3RC0nN0R, delvh reviewers: CyB3RC0nN0R, delvh
projects: Envoy projects: Envoy
milestone: Envoy v0.3-alpha milestone: Envoy v0.1-beta
--- ---

View File

@ -5,5 +5,6 @@ labels: documentation
assignees: CyB3RC0nN0R, delvh assignees: CyB3RC0nN0R, delvh
reviewers: CyB3RC0nN0R, delvh reviewers: CyB3RC0nN0R, delvh
projects: Envoy projects: Envoy
milestone: Envoy v0.3-alpha milestone: Envoy v0.1-beta
--- ---

View File

@ -4,14 +4,25 @@ on: [push]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: Set up JDK 11 - name: Set up JDK 11
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 11 java-version: 11
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven - name: Build with Maven
run: mvn -B package --file pom.xml run: mvn -B package --file pom.xml
- name: Stage build artifacts
run: mkdir staging && cp target/*.jar staging
- uses: actions/upload-artifact@v1
with:
name: envoy-client-artifacts
path: staging

View File

@ -114,6 +114,21 @@ public final class Chat implements Serializable {
*/ */
public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; } public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; }
/**
* Inserts a message at the correct place according to its creation date.
*
* @param message the message to insert
* @since Envoy Client v0.1-beta
*/
public void insert(Message message) {
for (int i = messages.size() - 1; i >= 0; --i)
if (message.getCreationDate().isAfter(messages.get(i).getCreationDate())) {
messages.add(i + 1, message);
return;
}
messages.add(0, message);
}
/** /**
* @return all messages in the current chat * @return all messages in the current chat
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -0,0 +1,174 @@
package envoy.client.ui;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import envoy.client.data.Settings;
/**
* This class offers a text field that is automatically equipped with a clear
* button.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ClearableTextField.java</strong><br>
* Created: <strong>25.06.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ClearableTextField extends GridPane {
private final TextField textField;
private final Button clearButton;
/**
* Constructs a new {@code ClearableTextField} with no initial text and icon
* size 16.
*
* @since Envoy Client v0.1-beta
*/
public ClearableTextField() { this("", 16); }
/**
* Constructs a new {@code ClearableTextField} with initial text and a
* predetermined icon size.
*
* @param text the text that should be displayed by default
* @param size the size of the icon
* @since Envoy Client v0.1-beta
*/
public ClearableTextField(String text, int size) {
// initializing the textField and the button
textField = new TextField(text);
clearButton = new Button("",
new ImageView(IconUtil.load(
Settings.getInstance().getCurrentTheme().equals("dark") ? "/icons/clear_button_white.png" : "/icons/clear_button_black.png",
size)));
clearButton.setOnAction(e -> textField.clear());
clearButton.setFocusTraversable(false);
clearButton.getStyleClass().clear();
clearButton.setBackground(Background.EMPTY);
// Adding the two elements to the GridPane
add(textField, 0, 0, 2, 1);
add(clearButton, 1, 0, 1, 1);
// Setting the percent - widths of the two columns.
// Used to locate the button on the right.
final var columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(90);
getColumnConstraints().add(columnConstraints);
final var columnConstraints2 = new ColumnConstraints();
columnConstraints2.setPercentWidth(10);
getColumnConstraints().add(columnConstraints2);
}
/**
* @return the underlying {@code textField}
* @since Envoy Client v0.1-beta
*/
public TextField getTextField() { return textField; }
/**
* This method offers the freedom to perform custom actions when the
* {@code clearButton} has been pressed.
* <p>
* The default is
* <b><code> e -> {clearableTextField.getTextField().clear();}</code></b>
*
* @param onClearButtonAction the action that should be performed
* @since Envoy Client v0.1-beta
*/
public void setClearButtonListener(EventHandler<ActionEvent> onClearButtonAction) { clearButton.setOnAction(onClearButtonAction); }
/**
* @return the current property of the prompt text
* @see javafx.scene.control.TextInputControl#promptTextProperty()
* @since Envoy Client v0.1-beta
*/
public final StringProperty promptTextProperty() { return textField.promptTextProperty(); }
/**
* @return the current prompt text
* @see javafx.scene.control.TextInputControl#getPromptText()
* @since Envoy Client v0.1-beta
*/
public final String getPromptText() { return textField.getPromptText(); }
/**
* @param value the prompt text to display
* @see javafx.scene.control.TextInputControl#setPromptText(java.lang.String)
* @since Envoy Client v0.1-beta
*/
public final void setPromptText(String value) { textField.setPromptText(value); }
/**
* @return the current property of the tooltip
* @see javafx.scene.control.Control#tooltipProperty()
* @since Envoy Client v0.1-beta
*/
public final ObjectProperty<Tooltip> tooltipProperty() { return textField.tooltipProperty(); }
/**
* @param value the new tooltip
* @see javafx.scene.control.Control#setTooltip(javafx.scene.control.Tooltip)
* @since Envoy Client v0.1-beta
*/
public final void setTooltip(Tooltip value) { textField.setTooltip(value); }
/**
* @return the current tooltip
* @see javafx.scene.control.Control#getTooltip()
* @since Envoy Client v0.1-beta
*/
public final Tooltip getTooltip() { return textField.getTooltip(); }
/**
* @return the current property of the context menu
* @see javafx.scene.control.Control#contextMenuProperty()
* @since Envoy Client v0.1-beta
*/
public final ObjectProperty<ContextMenu> contextMenuProperty() { return textField.contextMenuProperty(); }
/**
* @param value the new context menu
* @see javafx.scene.control.Control#setContextMenu(javafx.scene.control.ContextMenu)
* @since Envoy Client v0.1-beta
*/
public final void setContextMenu(ContextMenu value) { textField.setContextMenu(value); }
/**
* @return the current context menu
* @see javafx.scene.control.Control#getContextMenu()
* @since Envoy Client v0.1-beta
*/
public final ContextMenu getContextMenu() { return textField.getContextMenu(); }
/**
* @param value whether this ClearableTextField should be editable
* @see javafx.scene.control.TextInputControl#setEditable(boolean)
* @since Envoy Client v0.1-beta
*/
public final void setEditable(boolean value) { textField.setEditable(value); }
/**
* @return the current property whether this ClearableTextField is editable
* @see javafx.scene.control.TextInputControl#editableProperty()
* @since Envoy Client v0.1-beta
*/
public final BooleanProperty editableProperty() { return textField.editableProperty(); }
/**
* @return whether this {@code ClearableTextField} is editable
* @see javafx.scene.control.TextInputControl#isEditable()
* @since Envoy Client v0.1-beta
*/
public final boolean isEditable() { return textField.isEditable(); }
}

View File

@ -1,49 +0,0 @@
package envoy.client.ui;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.VBox;
import envoy.data.Contact;
import envoy.data.Group;
import envoy.data.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ContactListCell extends ListCell<Contact> {
/**
* Displays the name of a contact. If the contact is a user, their online status
* is displayed as well.
*
* @since Envoy Client v0.1-beta
*/
@Override
protected void updateItem(Contact contact, boolean empty) {
super.updateItem(contact, empty);
if (empty || contact == null) {
setText(null);
setGraphic(null);
} else {
// Container with contact name
final var vbox = new VBox(new Label(contact.getName()));
if (contact instanceof User) {
// Online status
final var user = (User) contact;
final var statusLabel = new Label(user.getStatus().toString());
statusLabel.getStyleClass().add(user.getStatus().toString().toLowerCase());
vbox.getChildren().add(statusLabel);
} else {
// Member count
vbox.getChildren().add(new Label(((Group) contact).getContacts().size() + " members"));
}
setGraphic(vbox);
}
}
}

View File

@ -1,60 +0,0 @@
package envoy.client.ui;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
/**
* Displays a single message inside the message list.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageListCell extends ListCell<Message> {
private static User client;
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
private static final Map<MessageStatus, Image> statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
/**
* Displays the text, the data of creation and the status of a message.
*
* @since Envoy v0.1-beta
*/
@Override
protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty);
if (empty || message == null) {
setText(null);
setGraphic(null);
} else {
final var cell = new VBox(new Label(dateFormat.format(message.getCreationDate())), new Label(message.getText()));
if (message.getRecipientID() == client.getID()) {
cell.getChildren().add(new Label("", new ImageView(statusImages.get(message.getStatus()))));
cell.getStyleClass().add("own-message");
} else cell.getStyleClass().add("received-message");
cell.paddingProperty().setValue(new Insets(5, 20, 5, 20));
setGraphic(cell);
}
}
/**
* @param client the user who chats with another person
* @since Envoy Client v0.1-beta
*/
public static void setUser(User client) { MessageListCell.client = client; }
}

View File

@ -0,0 +1,25 @@
package envoy.client.ui;
/**
* This interface defines an action that should be performed when a scene gets
* restored from the scene stack in {@link SceneContext}.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Restorable.java</strong><br>
* Created: <strong>03.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
@FunctionalInterface
public interface Restorable {
/**
* This method is getting called when a scene gets restored.<br>
* Hence, it can contain anything that should be done when the underlying scene
* gets restored.
*
* @since Envoy Client v0.1-beta
*/
void onRestore();
}

View File

@ -40,21 +40,21 @@ public final class SceneContext {
public enum SceneInfo { public enum SceneInfo {
/** /**
* The main scene in which chats are displayed. * The main scene in which the chat screen is displayed.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
CHAT_SCENE("/fxml/ChatScene.fxml"), CHAT_SCENE("/fxml/ChatScene.fxml"),
/** /**
* The scene in which settings are displayed. * The scene in which the settings screen is displayed.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
SETTINGS_SCENE("/fxml/SettingsScene.fxml"), SETTINGS_SCENE("/fxml/SettingsScene.fxml"),
/** /**
* The scene in which the contact search is displayed. * The scene in which the contact search screen is displayed.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
@ -72,7 +72,14 @@ public final class SceneContext {
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
LOGIN_SCENE("/fxml/LoginScene.fxml"); LOGIN_SCENE("/fxml/LoginScene.fxml"),
/**
* The scene in which the info screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
MESSAGE_INFO_SCENE("/fxml/MessageInfoScene.fxml");
/** /**
* The path to the FXML resource. * The path to the FXML resource.
@ -83,8 +90,9 @@ public final class SceneContext {
} }
private final Stage stage; private final Stage stage;
private final FXMLLoader loader = new FXMLLoader(); private final FXMLLoader loader = new FXMLLoader();
private final Stack<Scene> sceneStack = new Stack<>(); private final Stack<Scene> sceneStack = new Stack<>();
private final Stack<Object> controllerStack = new Stack<>();
private static final Settings settings = Settings.getInstance(); private static final Settings settings = Settings.getInstance();
@ -113,6 +121,7 @@ public final class SceneContext {
try { try {
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path)); final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
final var scene = new Scene(rootNode); final var scene = new Scene(rootNode);
controllerStack.push(loader.getController());
sceneStack.push(scene); sceneStack.push(scene);
stage.setScene(scene); stage.setScene(scene);
@ -132,10 +141,16 @@ public final class SceneContext {
*/ */
public void pop() { public void pop() {
sceneStack.pop(); sceneStack.pop();
controllerStack.pop();
if (!sceneStack.isEmpty()) { if (!sceneStack.isEmpty()) {
stage.setScene(sceneStack.peek()); final var newScene = sceneStack.peek();
stage.setScene(newScene);
applyCSS(); applyCSS();
stage.sizeToScene(); stage.sizeToScene();
// If the controller implements the Restorable interface,
// the actions to perform on restoration will be executed here
final var controller = controllerStack.peek();
if (controller instanceof Restorable) ((Restorable) controller).onRestore();
} }
stage.show(); stage.show();
} }
@ -154,7 +169,7 @@ public final class SceneContext {
* @return the controller used by the current scene * @return the controller used by the current scene
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public <T> T getController() { return loader.getController(); } public <T> T getController() { return (T) controllerStack.peek(); }
/** /**
* @return the stage in which the scenes are displayed * @return the stage in which the scenes are displayed

View File

@ -16,7 +16,6 @@ import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import envoy.client.data.Chat; import envoy.client.data.Chat;
import envoy.client.data.LocalDB; import envoy.client.data.LocalDB;
@ -24,7 +23,12 @@ import envoy.client.data.Settings;
import envoy.client.event.MessageCreationEvent; import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.*; import envoy.client.ui.IconUtil;
import envoy.client.ui.Restorable;
import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.ContactListCellFactory;
import envoy.client.ui.listcell.MessageControl;
import envoy.client.ui.listcell.MessageListCellFactory;
import envoy.data.*; import envoy.data.*;
import envoy.event.*; import envoy.event.*;
import envoy.event.contact.ContactOperation; import envoy.event.contact.ContactOperation;
@ -38,7 +42,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 { public final class ChatScene implements Restorable {
@FXML @FXML
private Label contactLabel; private Label contactLabel;
@ -90,26 +94,16 @@ public final class ChatScene {
private void initialize() { private void initialize() {
// Initialize message and user rendering // Initialize message and user rendering
messageList.setCellFactory(listView -> new MessageListCell()); messageList.setCellFactory(MessageListCellFactory::new);
userList.setCellFactory(listView -> new ContactListCell()); userList.setCellFactory(ContactListCellFactory::new);
settingsButton.setGraphic(new ImageView(IconUtil.load("/icons/settings.png", 16))); settingsButton.setGraphic(new ImageView(IconUtil.load("/icons/settings.png", 16)));
// Listen to received messages // Listen to received messages
eventBus.register(MessageCreationEvent.class, e -> { eventBus.register(MessageCreationEvent.class, e -> {
final var message = e.get(); final var message = e.get();
if (message.getClass().equals(Message.class)) { localDB.getChat(message instanceof GroupMessage ? message.getSenderID() : message.getRecipientID()).ifPresent(chat -> {
localDB.getChat(message.getSenderID()).ifPresent(chat -> { chat.insert(message);
chat.getMessages().add(message);
// Update UI if in current chat
if (chat == currentChat)
Platform.runLater(messageList::refresh);
});
} else {
localDB.getChat(message.getRecipientID()).ifPresent(chat -> {
chat.getMessages().add(message);
if (chat.equals(currentChat)) { if (chat.equals(currentChat)) {
try { try {
currentChat.read(writeProxy); currentChat.read(writeProxy);
@ -118,8 +112,7 @@ public final class ChatScene {
} }
Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); }); Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); });
} }
}); });
}
}); });
// Listen to message status changes // Listen to message status changes
@ -128,7 +121,7 @@ public final class ChatScene {
// Update UI if in current chat // Update UI if in current chat
if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh); if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
})); }));
eventBus.register(GroupMessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(groupMessage -> { eventBus.register(GroupMessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(groupMessage -> {
((GroupMessage) groupMessage).getMemberStatuses().replace(e.getMemberID(), e.get()); ((GroupMessage) groupMessage).getMemberStatuses().replace(e.getMemberID(), e.get());
@ -180,10 +173,13 @@ public final class ChatScene {
userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList()))); userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList())));
contactLabel.setText(localDB.getUser().getName()); contactLabel.setText(localDB.getUser().getName());
MessageListCell.setUser(localDB.getUser()); MessageControl.setUser(localDB.getUser());
if (!client.isOnline()) updateInfoLabel("You are offline", Color.YELLOW); if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
} }
@Override
public void onRestore() { updateRemainingCharsLabel(); }
/** /**
* Actions to perform when the list of contacts has been clicked. * Actions to perform when the list of contacts has been clicked.
* *
@ -193,7 +189,6 @@ public final class ChatScene {
private void userListClicked() { private void userListClicked() {
final Contact user = userList.getSelectionModel().getSelectedItem(); final Contact user = userList.getSelectionModel().getSelectedItem();
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) { if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
logger.log(Level.FINEST, "Loading chat with " + user);
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes // LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
@ -201,6 +196,9 @@ public final class ChatScene {
currentChat = localDB.getChat(user.getID()).get(); currentChat = localDB.getChat(user.getID()).get();
messageList.setItems(FXCollections.observableList(currentChat.getMessages())); messageList.setItems(FXCollections.observableList(currentChat.getMessages()));
final var scrollIndex = messageList.getItems().size() - 1;
messageList.scrollTo(scrollIndex);
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
deleteContactMenuItem.setText("Delete " + user.getName()); deleteContactMenuItem.setText("Delete " + user.getName());
// Read the current chat // Read the current chat
@ -271,7 +269,7 @@ public final class ChatScene {
if (!infoLabel.getText().equals(noMoreMessaging)) if (!infoLabel.getText().equals(noMoreMessaging))
// Informing the user that he is a f*cking moron and should use Envoy online // Informing the user that he is a f*cking moron and should use Envoy online
// because he ran out of messageIDs to use // because he ran out of messageIDs to use
updateInfoLabel(noMoreMessaging, Color.RED); updateInfoLabel(noMoreMessaging, "infoLabel-error");
} }
} }
@ -317,35 +315,24 @@ public final class ChatScene {
postButton.setDisable(true); postButton.setDisable(true);
messageTextArea.setDisable(true); messageTextArea.setDisable(true);
messageTextArea.clear(); messageTextArea.clear();
updateInfoLabel("You need to go online to send more messages", Color.RED); updateInfoLabel("You need to go online to send more messages", "infoLabel-error");
return; return;
} }
final var text = messageTextArea.getText().strip(); final var text = messageTextArea.getText().strip();
if (text.isBlank()) throw new IllegalArgumentException("A message without visible text can not be sent."); if (text.isBlank()) throw new IllegalArgumentException("A message without visible text can not be sent.");
try { try {
if (currentChat.getRecipient().getClass().equals(Group.class)) { // Create and send message
// Create and send groupMessage final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
final var groupMessage = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator()) .setText(text);
.setText(messageTextArea.getText().strip()) final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
.buildGroupMessage((Group) currentChat.getRecipient()); : builder.build();
// Send groupMessage // Send message
writeProxy.writeMessage(groupMessage); writeProxy.writeMessage(message);
// Add message to LocalDB and update UI // Add message to LocalDB and update UI
messageList.getItems().add(groupMessage); currentChat.insert(message);
} else { messageList.refresh();
// Create and send message
final var message = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(messageTextArea.getText().strip())
.build();
// Send message
writeProxy.writeMessage(message);
// Add message to LocalDB and update UI
messageList.getItems().add(message);
}
scrollToMessageListEnd(); scrollToMessageListEnd();
// Request a new ID generator if all IDs were used // Request a new ID generator if all IDs were used
@ -372,13 +359,14 @@ public final class ChatScene {
/** /**
* Updates the {@code infoLabel}. * Updates the {@code infoLabel}.
* *
* @param text the text to use * @param text the text to use
* @param textfill the color in which to display information * @param infoLabelID the id the the {@code infoLabel} should have so that it
* can be styled accordingly in CSS
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
private void updateInfoLabel(String text, Paint textfill) { private void updateInfoLabel(String text, String infoLabelID) {
infoLabel.setText(text); infoLabel.setText(text);
infoLabel.setTextFill(textfill); infoLabel.setId(infoLabelID);
infoLabel.setVisible(true); infoLabel.setVisible(true);
} }
@ -414,4 +402,7 @@ public final class ChatScene {
updateRemainingCharsLabel(); updateRemainingCharsLabel();
postButton.setDisable(messageText.isBlank()); postButton.setDisable(messageText.isBlank());
} }
@FXML
private void loadMessageInfoScene() { try {} catch (final NullPointerException e) {} }
} }

View File

@ -5,13 +5,16 @@ 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.Alert;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import envoy.client.data.LocalDB; import envoy.client.data.LocalDB;
import envoy.client.event.SendEvent; import envoy.client.event.SendEvent;
import envoy.client.ui.ContactListCell; import envoy.client.ui.ClearableTextField;
import envoy.client.ui.SceneContext; import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.ContactListCellFactory;
import envoy.data.Contact; import envoy.data.Contact;
import envoy.event.ElementOperation; import envoy.event.ElementOperation;
import envoy.event.EventBus; import envoy.event.EventBus;
@ -31,19 +34,7 @@ import envoy.util.EnvoyLog;
public class ContactSearchScene { public class ContactSearchScene {
@FXML @FXML
private Button backButton; private ClearableTextField searchBar;
@FXML
private Button clearButton;
@FXML
private Button searchButton;
@FXML
private Button newGroupButton;
@FXML
private TextField searchBar;
@FXML @FXML
private ListView<Contact> contactList; private ListView<Contact> contactList;
@ -67,7 +58,8 @@ public class ContactSearchScene {
@FXML @FXML
private void initialize() { private void initialize() {
contactList.setCellFactory(e -> new ContactListCell()); contactList.setCellFactory(ContactListCellFactory::new);
searchBar.setClearButtonListener(e -> { searchBar.getTextField().clear(); contactList.getItems().clear(); });
eventBus.register(ContactSearchResult.class, eventBus.register(ContactSearchResult.class,
response -> Platform.runLater(() -> { contactList.getItems().clear(); contactList.getItems().addAll(response.get()); })); response -> Platform.runLater(() -> { contactList.getItems().clear(); contactList.getItems().addAll(response.get()); }));
} }
@ -78,20 +70,12 @@ public class ContactSearchScene {
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
@FXML @FXML
private void checkClearButton() { private void sendRequest() {
final var containsContent = searchBar.getText().strip().isEmpty(); final var text = searchBar.getTextField().getText().strip();
clearButton.setDisable(containsContent); if (!text.isBlank()) eventBus.dispatch(new SendEvent(new ContactSearchRequest(text)));
searchButton.setDisable(containsContent); else contactList.getItems().clear();
} }
/**
* Sends a {@link ContactSearchRequest} to the server.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void suggestContacts() { eventBus.dispatch(new SendEvent(new ContactSearchRequest(searchBar.getText()))); }
/** /**
* Clears the text in the search bar and the items shown in the list. * Clears the text in the search bar and the items shown in the list.
* Additionally disables both clear and search button. * Additionally disables both clear and search button.
@ -100,10 +84,8 @@ public class ContactSearchScene {
*/ */
@FXML @FXML
private void clear() { private void clear() {
searchBar.setText(null); searchBar.getTextField().setText(null);
contactList.getItems().clear(); contactList.getItems().clear();
clearButton.setDisable(true);
searchButton.setDisable(true);
} }
/** /**
@ -119,14 +101,17 @@ public class ContactSearchScene {
final var alert = new Alert(AlertType.CONFIRMATION); final var alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Add Contact to Contact List"); alert.setTitle("Add Contact to Contact List");
alert.setHeaderText("Add the user " + contact.getName() + " to your contact list?"); alert.setHeaderText("Add the user " + contact.getName() + " to your contact list?");
alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> { // Normally, this would be total BS (we are already on the FX Thread), however
// it could be proven that the creation of this dialog wrapped in
// Platform.runLater is less error-prone than without it
Platform.runLater(() -> alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
final var event = new ContactOperation(contact, ElementOperation.ADD); final var event = new ContactOperation(contact, ElementOperation.ADD);
// Sends the event to the server // Sends the event to the server
eventBus.dispatch(new SendEvent(event)); eventBus.dispatch(new SendEvent(event));
// Updates the UI // Updates the UI
eventBus.dispatch(event); eventBus.dispatch(event);
logger.log(Level.INFO, "Added contact " + contact); logger.log(Level.INFO, "Added contact " + contact);
}); }));
} }
} }

View File

@ -9,8 +9,9 @@ import javafx.scene.control.Alert.AlertType;
import envoy.client.data.LocalDB; import envoy.client.data.LocalDB;
import envoy.client.event.SendEvent; import envoy.client.event.SendEvent;
import envoy.client.ui.ContactListCell; import envoy.client.ui.ClearableTextField;
import envoy.client.ui.SceneContext; import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.ContactListCellFactory;
import envoy.data.Contact; import envoy.data.Contact;
import envoy.event.EventBus; import envoy.event.EventBus;
import envoy.event.GroupCreation; import envoy.event.GroupCreation;
@ -30,7 +31,7 @@ public class GroupCreationScene {
private Button createButton; private Button createButton;
@FXML @FXML
private TextField groupNameField; private ClearableTextField groupNameField;
@FXML @FXML
private ListView<Contact> contactList; private ListView<Contact> contactList;
@ -41,8 +42,9 @@ public class GroupCreationScene {
@FXML @FXML
private void initialize() { private void initialize() {
contactList.setCellFactory(e -> new ContactListCell()); contactList.setCellFactory(ContactListCellFactory::new);
contactList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); contactList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
groupNameField.setClearButtonListener(e -> { groupNameField.getTextField().clear(); createButton.setDisable(true); });
} }
/** /**
@ -59,11 +61,22 @@ public class GroupCreationScene {
/** /**
* Enables the {@code createButton} if at least one contact is selected. * Enables the {@code createButton} if at least one contact is selected.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
@FXML @FXML
private void contactListClicked() { createButton.setDisable(contactList.getSelectionModel().isEmpty()); } private void contactListClicked() {
createButton.setDisable(contactList.getSelectionModel().isEmpty() || groupNameField.getTextField().getText().isBlank());
}
/**
* Checks, whether the {@code createButton} can be enabled because text is
* present in the textfield.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void textUpdated() { createButton.setDisable(groupNameField.getTextField().getText().isBlank()); }
/** /**
* Sends a {@link GroupCreation} to the server and closes this scene. * Sends a {@link GroupCreation} to the server and closes this scene.
@ -74,10 +87,10 @@ public class GroupCreationScene {
*/ */
@FXML @FXML
private void createButtonClicked() { private void createButtonClicked() {
final var name = groupNameField.getText(); final var name = groupNameField.getTextField().getText();
if (!Bounds.isValidContactName(name)) { if (!Bounds.isValidContactName(name)) {
new Alert(AlertType.ERROR, "The entered group name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait(); new Alert(AlertType.ERROR, "The entered group name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
groupNameField.clear(); groupNameField.getTextField().clear();
} else { } else {
eventBus.dispatch(new SendEvent(new GroupCreation(name, eventBus.dispatch(new SendEvent(new GroupCreation(name,
contactList.getSelectionModel().getSelectedItems().stream().map(Contact::getID).collect(Collectors.toSet())))); contactList.getSelectionModel().getSelectedItems().stream().map(Contact::getID).collect(Collectors.toSet()))));

View File

@ -13,6 +13,7 @@ import javafx.scene.control.Alert.AlertType;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.ui.ClearableTextField;
import envoy.client.ui.SceneContext; import envoy.client.ui.SceneContext;
import envoy.client.ui.Startup; import envoy.client.ui.Startup;
import envoy.data.*; import envoy.data.*;
@ -34,7 +35,7 @@ import envoy.util.EnvoyLog;
public final class LoginScene { public final class LoginScene {
@FXML @FXML
private TextField userTextField; private ClearableTextField userTextField;
@FXML @FXML
private PasswordField passwordField; private PasswordField passwordField;
@ -119,17 +120,17 @@ public final class LoginScene {
if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) { if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) {
new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait(); new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
repeatPasswordField.clear(); repeatPasswordField.clear();
} else if (!Bounds.isValidContactName(userTextField.getText())) { } else if (!Bounds.isValidContactName(userTextField.getTextField().getText())) {
new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait(); new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
userTextField.clear(); userTextField.getTextField().clear();
} else } else performHandshake(new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText().toCharArray(),
performHandshake( registerCheckBox.isSelected(), Startup.VERSION));
new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected(), Startup.VERSION));
} }
@FXML @FXML
private void offlineModeButtonPressed() { private void offlineModeButtonPressed() {
attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), false, Startup.VERSION)); attemptOfflineMode(
new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText().toCharArray(), false, Startup.VERSION));
} }
@FXML @FXML
@ -162,8 +163,7 @@ public final class LoginScene {
loadChatScene(); loadChatScene();
} }
} catch (IOException | InterruptedException | TimeoutException e) { } catch (IOException | InterruptedException | TimeoutException e) {
logger.log(Level.WARNING, "Could not connect to server: ", e); logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
logger.log(Level.FINER, "Attempting offline mode...");
attemptOfflineMode(credentials); attemptOfflineMode(credentials);
} }
} }

View File

@ -0,0 +1,40 @@
package envoy.client.ui.listcell;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import envoy.data.Contact;
import envoy.data.Group;
import envoy.data.User;
/**
* This class formats a single {@link Contact} into a UI component.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactControl.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContactControl extends VBox {
/**
* @param contact the contact that should be formatted
* @since Envoy Client v0.1-beta
*/
public ContactControl(Contact contact) {
// Container with contact name
final var nameLabel = new Label(contact.getName());
nameLabel.setWrapText(true);
getChildren().add(nameLabel);
if (contact instanceof User) {
// Online status
final var user = (User) contact;
final var statusLabel = new Label(user.getStatus().toString());
statusLabel.getStyleClass().add(user.getStatus().toString().toLowerCase());
getChildren().add(statusLabel);
} else // Member count
getChildren().add(new Label(((Group) contact).getContacts().size() + " members"));
}
}

View File

@ -0,0 +1,44 @@
package envoy.client.ui.listcell;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import envoy.data.Contact;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ContactListCellFactory extends ListCell<Contact> {
private final ListView<Contact> listView;
/**
* @param listView the list view inside which this cell is contained
* @since Envoy Client v0.1-beta
*/
public ContactListCellFactory(ListView<Contact> listView) { this.listView = listView; }
/**
* Displays the name of a contact. If the contact is a user, their online status
* is displayed as well.
*
* @since Envoy Client v0.1-beta
*/
@Override
protected void updateItem(Contact contact, boolean empty) {
super.updateItem(contact, empty);
if (empty || contact == null) {
setText(null);
setGraphic(null);
} else {
final var control = new ContactControl(contact);
prefWidthProperty().bind(listView.widthProperty().subtract(40));
setGraphic(control);
}
}
}

View File

@ -0,0 +1,60 @@
package envoy.client.ui.listcell;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import envoy.client.ui.IconUtil;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
/**
* This class formats a single {@link Message} into a UI component.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageControl.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class MessageControl extends VBox {
private static User client;
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
private static final Map<MessageStatus, Image> statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
/**
*
* @param message the message that should be formatted
* @since Envoy Client v0.1-beta
*/
public MessageControl(Message message) {
// Creating the underlying VBox, the dateLabel and the textLabel
super(new Label(dateFormat.format(message.getCreationDate())));
final var textLabel = new Label(message.getText());
textLabel.setWrapText(true);
getChildren().add(textLabel);
// Setting the message status icon and background color
if (message.getRecipientID() != client.getID()) {
final var statusIcon = new ImageView(statusImages.get(message.getStatus()));
statusIcon.setPreserveRatio(true);
getChildren().add(statusIcon);
getStyleClass().add("own-message");
} else getStyleClass().add("received-message");
// Adjusting height and weight of the cell to the corresponding ListView
paddingProperty().setValue(new Insets(5, 20, 5, 20));
}
/**
* @param client the user who has logged in
* @since Envoy Client v0.1-beta
*/
public static void setUser(User client) { MessageControl.client = client; }
}

View File

@ -0,0 +1,52 @@
package envoy.client.ui.listcell;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Tooltip;
import javafx.stage.PopupWindow.AnchorLocation;
import envoy.data.Message;
/**
* Displays a single message inside the message list.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListCellFactory.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageListCellFactory extends ListCell<Message> {
private final ListView<Message> listView;
/**
* @param listView the list view inside which this cell is contained
* @since Envoy Client v0.1-beta
*/
public MessageListCellFactory(ListView<Message> listView) { this.listView = listView; }
/**
* Displays the text, the data of creation and the status of a message.
*
* @since Envoy v0.1-beta
*/
@Override
protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty);
if (empty || message == null) {
setText(null);
setGraphic(null);
} else {
final var control = new MessageControl(message);
control.prefWidthProperty().bind(listView.widthProperty().subtract(40));
// Creating the Tooltip to deselect a message
final var tooltip = new Tooltip("You can select a message by clicking on it \nand deselect it by pressing \"ctrl\" and clicking on it");
tooltip.setWrapText(true);
tooltip.setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
setTooltip(tooltip);
setGraphic(control);
}
}
}

View File

@ -0,0 +1,12 @@
/**
* This package contains custom list cells that are used to display certain
* things.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>30.06.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.listcell;

Binary file not shown.

View File

@ -3,7 +3,7 @@
} }
.context-menu, .context-menu > * { .context-menu, .context-menu > * {
-fx-background-radius: 15px; -fx-background-radius: 15.0px;
/*TODO: solution below does not work */ /*TODO: solution below does not work */
-fx-background-color: transparent; -fx-background-color: transparent;
} }
@ -58,3 +58,19 @@
-fx-text-fill: #00FF00; -fx-text-fill: #00FF00;
-fx-background-color: transparent; -fx-background-color: transparent;
} }
#infoLabel-success {
-fx-text-fill: #00FF00;
}
#infoLabel-info {
-fx-text-fill: yellow;
}
#infoLabel-warning {
-fx-text-fill: orange;
}
#infoLabel-error {
-fx-text-fill: red;
}

View File

@ -12,9 +12,9 @@
<?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.RowConstraints?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" <GridPane hgap="5.0" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="400.0" minWidth="350.0" prefHeight="400.0" prefWidth="600.0" minHeight="400.0" minWidth="350.0" prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/11.0.1" vgap="2.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="envoy.client.ui.controller.ChatScene"> fx:controller="envoy.client.ui.controller.ChatScene">
<columnConstraints> <columnConstraints>
@ -47,7 +47,7 @@
prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1" prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1"
GridPane.rowSpan="2147483647"> GridPane.rowSpan="2147483647">
<GridPane.margin> <GridPane.margin>
<Insets bottom="10.0" left="10.0" right="5.0" top="5.0" /> <Insets bottom="10.0" left="10.0" />
</GridPane.margin> </GridPane.margin>
<padding> <padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -64,7 +64,7 @@
<Label fx:id="contactLabel" prefHeight="16.0" prefWidth="250.0" <Label fx:id="contactLabel" prefHeight="16.0" prefWidth="250.0"
text="Select a contact to chat with" GridPane.columnSpan="2"> text="Select a contact to chat with" GridPane.columnSpan="2">
<GridPane.margin> <GridPane.margin>
<Insets left="15.0" right="5.0" top="5.0" /> <Insets left="10.0" />
</GridPane.margin> </GridPane.margin>
<padding> <padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -75,17 +75,18 @@
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.columnIndex="1" GridPane.columnSpan="2"
GridPane.halignment="RIGHT" GridPane.valignment="CENTER"> GridPane.halignment="RIGHT" GridPane.valignment="CENTER">
<GridPane.margin> <GridPane.margin>
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" /> <Insets bottom="10.0" right="10.0" top="10.0" />
</GridPane.margin> </GridPane.margin>
<padding> <padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding> </padding>
</Button> </Button>
<ListView fx:id="messageList" prefHeight="257.0" <ListView fx:id="messageList" prefHeight="257.0"
prefWidth="465.0" GridPane.columnIndex="1" GridPane.columnSpan="2" prefWidth="465.0" GridPane.columnIndex="1"
GridPane.rowIndex="1" GridPane.rowSpan="2"> GridPane.columnSpan="2147483647" GridPane.rowIndex="1"
GridPane.rowSpan="2">
<GridPane.margin> <GridPane.margin>
<Insets bottom="5.0" left="5.0" right="10.0" top="5.0" /> <Insets left="5.0" right="10.0" />
</GridPane.margin> </GridPane.margin>
<padding> <padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -101,6 +102,8 @@
onAction="#forwardMessage" text="Forward" /> onAction="#forwardMessage" text="Forward" />
<MenuItem mnemonicParsing="false" <MenuItem mnemonicParsing="false"
onAction="#quoteMessage" text="Quote" /> onAction="#quoteMessage" text="Quote" />
<MenuItem mnemonicParsing="false"
onAction="#loadMessageInfoScene" text="Info" />
</items> </items>
</ContextMenu> </ContextMenu>
</contextMenu> </contextMenu>
@ -111,11 +114,8 @@
GridPane.halignment="CENTER" GridPane.rowIndex="4" GridPane.halignment="CENTER" GridPane.rowIndex="4"
GridPane.valignment="BOTTOM"> GridPane.valignment="BOTTOM">
<GridPane.margin> <GridPane.margin>
<Insets bottom="10.0" left="5.0" right="10.0" /> <Insets bottom="10.0" right="10.0" />
</GridPane.margin> </GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip> <tooltip>
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true" <Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true"
maxWidth="350.0" maxWidth="350.0"
@ -130,6 +130,9 @@
</items> </items>
</ContextMenu> </ContextMenu>
</contextMenu> </contextMenu>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Button> </Button>
<TextArea fx:id="messageTextArea" disable="true" <TextArea fx:id="messageTextArea" disable="true"
onInputMethodTextChanged="#messageTextUpdated" onInputMethodTextChanged="#messageTextUpdated"
@ -137,7 +140,7 @@
prefHeight="200.0" prefWidth="200.0" wrapText="true" prefHeight="200.0" prefWidth="200.0" wrapText="true"
GridPane.columnIndex="1" GridPane.rowIndex="4"> GridPane.columnIndex="1" GridPane.rowIndex="4">
<GridPane.margin> <GridPane.margin>
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="10.0" left="5.0" top="3.0" />
</GridPane.margin> </GridPane.margin>
<opaqueInsets> <opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -163,7 +166,7 @@
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin> </GridPane.margin>
<padding> <padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" top="5.0" />
</padding> </padding>
<opaqueInsets> <opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -175,8 +178,8 @@
</tooltip> </tooltip>
</Label> </Label>
<Label fx:id="infoLabel" text="Something happened" <Label fx:id="infoLabel" text="Something happened"
textFill="#faa007" visible="false" GridPane.columnIndex="1" wrapText="true" textFill="#faa007" visible="false"
GridPane.rowIndex="1"> GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin> <GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin> </GridPane.margin>

View File

@ -1,77 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import envoy.client.ui.ClearableTextField?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?> <?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Tooltip?> <?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.ContactSearchScene"> <VBox maxHeight="-Infinity" maxWidth="-Infinity"
<children> minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0"> prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1"
<children> xmlns:fx="http://javafx.com/fxml/1"
<TextField fx:id="searchBar" onInputMethodTextChanged="#checkClearButton" onKeyTyped="#checkClearButton" prefColumnCount="22" promptText="Enter username to search for"> fx:controller="envoy.client.ui.controller.ContactSearchScene">
<HBox.margin> <children>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
</HBox.margin> <children>
<padding> <ClearableTextField fx:id="searchBar"
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> prefWidth="310.0">
</padding> <textField onInputMethodTextChanged="#sendRequest"
<tooltip> onKeyTyped="#sendRequest" prefColumnCount="22"
<Tooltip text="Enter a name. If an account by that name exists, it will be displayed below." wrapText="true" /> promptText="Enter username to search for">
</tooltip> </textField>
</TextField> <HBox.margin>
<Button fx:id="clearButton" disable="true" mnemonicParsing="true" onAction="#clear" prefHeight="26.0" prefWidth="110.0" text="Clea_r"> <Insets bottom="5.0" left="5.0" right="5.0" top="15.0" />
<tooltip> </HBox.margin>
<Tooltip autoHide="true" text="Clears the text to the left and the elements below" wrapText="true" /> <padding>
</tooltip> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<HBox.margin> </padding>
<Insets bottom="5.0" left="5.0" right="10.0" top="5.0" /> <tooltip>
</HBox.margin> <Tooltip
<padding> text="Enter a name. If an account by that name exists, it will be displayed below."
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> wrapText="true" />
</padding> </tooltip>
</Button> </ClearableTextField>
<Button fx:id="searchButton" defaultButton="true" disable="true" mnemonicParsing="true" onAction="#suggestContacts" prefHeight="26.0" prefWidth="124.0" text="_Search" textOverrun="LEADING_WORD_ELLIPSIS"> <Button mnemonicParsing="false"
<padding> onAction="#newGroupButtonClicked" prefHeight="26.0"
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> prefWidth="139.0" text="New Group">
</padding> <HBox.margin>
<HBox.margin> <Insets bottom="5.0" left="30.0" right="5.0" top="5.0" />
<Insets bottom="5.0" right="20.0" top="5.0" /> </HBox.margin>
</HBox.margin> <padding>
<tooltip> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<Tooltip autoHide="true" text="Search for accounts with the name entered to the left" wrapText="true" /> </padding>
</tooltip> </Button>
</Button> </children>
<Button mnemonicParsing="false" onAction="#newGroupButtonClicked" prefHeight="26.0" prefWidth="139.0" text="New Group"> </HBox>
<HBox.margin> <ListView fx:id="contactList"
<Insets bottom="5.0" left="20.0" right="5.0" top="5.0" /> onMouseClicked="#contactListClicked" prefHeight="314.0"
</HBox.margin> prefWidth="600.0">
<padding> <padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding> </padding>
</Button> <VBox.margin>
</children> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</HBox> </VBox.margin>
<ListView fx:id="contactList" onMouseClicked="#contactListClicked" prefHeight="314.0" prefWidth="600.0"> </ListView>
<padding> <Button cancelButton="true" mnemonicParsing="true"
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> onAction="#backButtonClicked" text="_Back">
</padding> <VBox.margin>
<VBox.margin> <Insets bottom="10.0" left="10.0" />
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> </VBox.margin>
</VBox.margin></ListView> <padding>
<Button fx:id="backButton" cancelButton="true" mnemonicParsing="true" onAction="#backButtonClicked" text="_Back"> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<VBox.margin> </padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <tooltip>
</VBox.margin> <Tooltip autoHide="true"
<padding> text="Takes you back to the screen where you can chat with others"
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> wrapText="true" />
</padding> </tooltip>
<tooltip> </Button>
<Tooltip autoHide="true" text="Takes you back to the screen where you can chat with others" wrapText="true" /> </children>
</tooltip>
</Button>
</children>
</VBox> </VBox>

View File

@ -1,67 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import envoy.client.ui.ClearableTextField?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?> <?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Tooltip?> <?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.GroupCreationScene"> <VBox maxHeight="-Infinity" maxWidth="-Infinity"
<children> minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0"
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0"> prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1"
<children> xmlns:fx="http://javafx.com/fxml/1"
<TextField fx:id="groupNameField" prefColumnCount="22" promptText="Enter Group Name"> fx:controller="envoy.client.ui.controller.GroupCreationScene">
<HBox.margin> <children>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
</HBox.margin> <children>
<padding> <ClearableTextField fx:id="groupNameField">
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <textField prefColumnCount="22"
</padding> promptText="Enter Group Name"
<tooltip> onInputMethodTextChanged="#textUpdated" onKeyTyped="#textUpdated" />
<Tooltip text="Enter a name. If an account by that name exists, it will be displayed below." wrapText="true" /> <HBox.margin>
</tooltip> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</TextField> </HBox.margin>
</children> <padding>
</HBox> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<Label text="Choose Members:"> </padding>
<font> <tooltip>
<Font size="16.0" /> <Tooltip
</font> text="Enter something. A group with this name will be created."
<VBox.margin> wrapText="true" />
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </tooltip>
</VBox.margin> </ClearableTextField>
<padding> </children>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </HBox>
</padding> <Label text="Choose Members:">
</Label> <font>
<ListView fx:id="contactList" onMouseClicked="#contactListClicked" prefHeight="314.0" prefWidth="600.0"> <Font size="16.0" />
<VBox.margin> </font>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /> <padding>
</VBox.margin> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<padding> </padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </Label>
</padding></ListView> <ListView fx:id="contactList"
<Button fx:id="createButton" defaultButton="true" disable="true" mnemonicParsing="false" onAction="#createButtonClicked" text="Create"> onMouseClicked="#contactListClicked" prefHeight="314.0"
<VBox.margin> prefWidth="600.0">
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <VBox.margin>
</VBox.margin> <Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
<padding> </VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <padding>
</padding></Button> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<Button cancelButton="true" mnemonicParsing="true" onAction="#backButtonClicked" text="_Back"> </padding>
<VBox.margin> </ListView>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <BorderPane prefHeight="50.0">
</VBox.margin> <left>
<padding> <Button cancelButton="true" mnemonicParsing="true"
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> onAction="#backButtonClicked" text="_Back"
</padding> BorderPane.alignment="CENTER">
<tooltip> <padding>
<Tooltip autoHide="true" text="Takes you back to the screen where you can chat with others" wrapText="true" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</tooltip> </padding>
</Button> <tooltip>
</children> <Tooltip autoHide="true"
text="Takes you back to the screen where you can chat with others"
wrapText="true" />
</tooltip>
<BorderPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</BorderPane.margin>
</Button>
</left>
<right>
<Button fx:id="createButton" alignment="CENTER_RIGHT"
defaultButton="true" disable="true" mnemonicParsing="false"
onAction="#createButtonClicked" text="Create"
BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<BorderPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</BorderPane.margin>
</Button>
</right>
</BorderPane>
</children>
</VBox> </VBox>

View File

@ -1,104 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import envoy.client.ui.ClearableTextField?>
<?import javafx.geometry.Insets?> <?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?> <?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?> <?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?> <?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?> <?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?> <?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?> <?import javafx.scene.text.Font?>
<VBox prefHeight="206.0" prefWidth="440.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.LoginScene"> <VBox prefHeight="206.0" prefWidth="440.0"
<children> xmlns="http://javafx.com/javafx/11.0.1"
<Label text="User Login"> xmlns:fx="http://javafx.com/fxml/1"
<font> fx:controller="envoy.client.ui.controller.LoginScene">
<Font size="26.0" /> <children>
</font> <Label text="User Login">
<VBox.margin> <font>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Font size="26.0" />
</VBox.margin> </font>
<padding> <VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding> </VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Label>
<GridPane hgap="5.0" vgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES"
minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES"
minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0"
vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0"
vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0"
vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="User Name:">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</Label> </Label>
<GridPane> <Label text="Password:" GridPane.rowIndex="1">
<columnConstraints> <padding>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="40.0" prefWidth="100.0" /> <Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> </padding>
</columnConstraints> <GridPane.margin>
<rowConstraints> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> </GridPane.margin>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> </Label>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <Label fx:id="repeatPasswordLabel" text="Repeat Password:"
</rowConstraints> visible="false" GridPane.rowIndex="2">
<children> <GridPane.margin>
<Label text="User Name:"> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<GridPane.margin> </GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <padding>
</GridPane.margin> <Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
<padding> </padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" /> </Label>
</padding></Label> <ClearableTextField fx:id="userTextField"
<Label text="Password" GridPane.rowIndex="1"> GridPane.columnIndex="1">
<padding> <GridPane.margin>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" /> <Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
</padding> </GridPane.margin>
<GridPane.margin> </ClearableTextField>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <PasswordField fx:id="passwordField"
</GridPane.margin></Label> GridPane.columnIndex="1" GridPane.rowIndex="1">
<Label fx:id="repeatPasswordLabel" text="Repeat Password:" visible="false" GridPane.rowIndex="2"> <GridPane.margin>
<GridPane.margin> <Insets bottom="10.0" left="5.0" right="5.0" top="10.0" />
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </GridPane.margin>
</GridPane.margin> <padding>
<padding> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" /> </padding>
</padding></Label> </PasswordField>
<TextField fx:id="userTextField" GridPane.columnIndex="1"> <PasswordField fx:id="repeatPasswordField"
<GridPane.margin> visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2">
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" /> <GridPane.margin>
</GridPane.margin> <Insets bottom="5.0" left="5.0" right="5.0" top="10.0" />
<padding> </GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <padding>
</padding></TextField> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="1"> </padding>
<GridPane.margin> </PasswordField>
<Insets bottom="10.0" left="5.0" right="5.0" top="10.0" /> </children>
</GridPane.margin> <VBox.margin>
<padding> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </VBox.margin>
</padding></PasswordField> </GridPane>
<PasswordField fx:id="repeatPasswordField" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2"> <CheckBox fx:id="registerCheckBox" mnemonicParsing="true"
<GridPane.margin> onAction="#registerCheckboxChanged" prefHeight="17.0"
<Insets bottom="5.0" left="5.0" right="5.0" top="10.0" /> prefWidth="181.0" text="_Register">
</GridPane.margin> <VBox.margin>
<padding> <Insets left="5.0" right="3.0" />
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </VBox.margin>
</padding></PasswordField> </CheckBox>
</children> <Label fx:id="connectionLabel">
</GridPane> <VBox.margin>
<CheckBox fx:id="registerCheckBox" mnemonicParsing="true" onAction="#registerCheckboxChanged" prefHeight="17.0" prefWidth="181.0" text="_Register"> <Insets left="5.0" />
<padding> </VBox.margin>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" /> </Label>
</padding> <BorderPane prefWidth="200.0">
<VBox.margin> <left>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" /> <Button cancelButton="true" mnemonicParsing="false"
</VBox.margin></CheckBox> onAction="#abortLogin" text="Close" BorderPane.alignment="CENTER">
<Label fx:id="connectionLabel" /> <padding>
<ButtonBar prefHeight="40.0" prefWidth="200.0"> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<buttons> </padding>
<Button mnemonicParsing="false" onAction="#abortLogin" text="Close"> <BorderPane.margin>
<padding> <Insets />
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </BorderPane.margin>
</padding> </Button>
</Button> </left>
<Button mnemonicParsing="false" onAction="#offlineModeButtonPressed" text="Offline mode" /> <center>
<Button defaultButton="true" mnemonicParsing="false" onAction="#loginButtonPressed" text="Login" /> <Button mnemonicParsing="false"
</buttons> onAction="#offlineModeButtonPressed" text="Offline mode"
</ButtonBar> BorderPane.alignment="CENTER">
</children> <BorderPane.margin>
<Insets />
</BorderPane.margin>
</Button>
</center>
<right>
<Button defaultButton="true" mnemonicParsing="false"
onAction="#loginButtonPressed" text="Login"
BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets />
</BorderPane.margin>
</Button>
</right>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" />
</VBox.margin>
</BorderPane>
</children>
</VBox> </VBox>

View File

@ -7,39 +7,50 @@
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.SettingsScene"> <VBox alignment="TOP_RIGHT" maxHeight="-Infinity"
maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="envoy.client.ui.controller.SettingsScene">
<children> <children>
<HBox prefHeight="389.0" prefWidth="600.0"> <HBox prefHeight="389.0" prefWidth="600.0">
<children> <children>
<ListView fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0"> <ListView fx:id="settingsList"
<opaqueInsets> onMouseClicked="#settingsListClicked" prefHeight="200.0"
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> prefWidth="200.0">
</opaqueInsets> <opaqueInsets>
<HBox.margin> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<Insets bottom="10.0" left="10.0" right="5.0" top="10.0" /> </opaqueInsets>
</HBox.margin> <HBox.margin>
<padding> <Insets bottom="10.0" left="10.0" right="5.0" top="10.0" />
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> </HBox.margin>
</padding></ListView> <padding>
<TitledPane fx:id="titledPane" collapsible="false" prefHeight="325.0" prefWidth="300.0"> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<HBox.margin> </padding>
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" /> </ListView>
</HBox.margin> <TitledPane fx:id="titledPane" collapsible="false"
<padding> prefHeight="325.0" prefWidth="300.0">
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <HBox.margin>
</padding></TitledPane> <Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
</children> </HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</TitledPane>
</children>
</HBox> </HBox>
<Button defaultButton="true" mnemonicParsing="true" onMouseClicked="#backButtonClicked" text="_Back"> <Button defaultButton="true" mnemonicParsing="true"
<opaqueInsets> onMouseClicked="#backButtonClicked" text="_Back">
<Insets /> <opaqueInsets>
</opaqueInsets> <Insets />
<padding> </opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <padding>
</padding> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
<VBox.margin> </padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <VBox.margin>
</VBox.margin> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</Button> </VBox.margin>
</Button>
</children> </children>
</VBox> </VBox>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB