Merge pull request #134 from informatik-ag-ngl/f/settings

Reimplemented the settings UI as SettingsScene
This commit is contained in:
Kai S. K. Engelbart 2020-04-20 08:50:49 +02:00 committed by GitHub
commit d8e006f051
19 changed files with 296 additions and 838 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
package envoy.client;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>DeveloperComments.java</strong><br>
* Created: <strong>19 Apr 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class DeveloperComments {
// "Schau, es hat sich behindert" - Kai, 2020
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
}

View File

@ -88,11 +88,11 @@ public class Settings {
/** /**
* Updates the preferences when the save button is clicked. * Updates the preferences when the save button is clicked.
* *
* @throws IOException if an error occurs while saving the themes to the theme * @throws IOException if an error occurs while saving the themes
* file
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void save() throws IOException { public void save() throws IOException {
// Save settings to settings file // Save settings to settings file
SerializationUtils.write(settingsFile, items); SerializationUtils.write(settingsFile, items);
@ -103,7 +103,7 @@ public class Settings {
private void supplementDefaults() { private void supplementDefaults() {
items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key.")); items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
items.putIfAbsent("onCloseMode", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed.")); items.putIfAbsent("onCloseMode", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed."));
items.putIfAbsent("currentTheme", new SettingsItem<>("dark", null)); items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
} }
/** /**

View File

@ -1,19 +1,15 @@
package envoy.client.data; package envoy.client.data;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.swing.JComponent; import javax.swing.JComponent;
import envoy.client.ui.primary.PrimaryToggleSwitch;
/** /**
* Encapsulates a persistent value that is directly or indirectly mutable by the * Encapsulates a persistent value that is directly or indirectly mutable by the
* user.<br> * user.<br>
* <br> * <br>
* Project: <strong>envoy-clientChess</strong><br> * Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsItem.java</strong><br> * File: <strong>SettingsItem.java</strong><br>
* Created: <strong>23.12.2019</strong><br> * Created: <strong>23.12.2019</strong><br>
* *
@ -24,18 +20,11 @@ import envoy.client.ui.primary.PrimaryToggleSwitch;
public class SettingsItem<T> implements Serializable { public class SettingsItem<T> implements Serializable {
private T value; private T value;
private Class<? extends JComponent> componentClass;
private String userFriendlyName, description; private String userFriendlyName, description;
transient private Consumer<T> changeHandler; private transient Consumer<T> changeHandler;
private static final Map<Class<?>, Class<? extends JComponent>> componentClasses = new HashMap<>(); private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 0L;
static {
componentClasses.put(Boolean.class, PrimaryToggleSwitch.class);
}
/** /**
* Initializes a {@link SettingsItem}. The default value's class will be mapped * Initializes a {@link SettingsItem}. The default value's class will be mapped
@ -48,39 +37,11 @@ public class SettingsItem<T> implements Serializable {
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public SettingsItem(T value, String userFriendlyName, String description) { public SettingsItem(T value, String userFriendlyName, String description) {
this(value, componentClasses.get(value.getClass())); this.value = value;
this.userFriendlyName = userFriendlyName; this.userFriendlyName = userFriendlyName;
this.description = description; this.description = description;
} }
/**
* Initializes a {@link SettingsItem}. The default value's class will be mapped
* to a specific {@link JComponent}. The mapping can also be disables if this
* parameter is {@code null}. In that case a {@link NullPointerException} will
* be thrown if the method {@link SettingsItem#getComponent()} is called.
*
* @param value the default value
* @param componentClass the class of the {@link JComponent} to represent this
* {@link SettingsItem} with
* @since Envoy Client v0.3-alpha
*/
public SettingsItem(T value, Class<? extends JComponent> componentClass) {
this.value = value;
this.componentClass = componentClass;
}
/**
* @return an instance of the {@link JComponent} that represents this
* {@link SettingsItem}
* @throws ReflectiveOperationException if the component initialization failed
* @throws SecurityException if the component initialization failed
* @since Envoy Client v0.3-alpha
*/
public JComponent getComponent() throws ReflectiveOperationException, SecurityException {
if (componentClass == null) throw new NullPointerException("Component class is null");
return componentClass.getConstructor(SettingsItem.class).newInstance(this);
}
/** /**
* @return the value * @return the value
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
@ -99,18 +60,6 @@ public class SettingsItem<T> implements Serializable {
this.value = value; this.value = value;
} }
/**
* @return the componentClass
* @since Envoy Client v0.3-alpha
*/
public Class<? extends JComponent> getComponentClass() { return componentClass; }
/**
* @param componentClass the componentClass to set
* @since Envoy Client v0.3-alpha
*/
public void setComponentClass(Class<? extends JComponent> componentClass) { this.componentClass = componentClass; }
/** /**
* @return the userFriendlyName * @return the userFriendlyName
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha

View File

@ -11,12 +11,15 @@ import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import envoy.client.data.Chat; import envoy.client.data.Chat;
import envoy.client.data.LocalDB; import envoy.client.data.LocalDB;
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.settings.SettingsSceneController;
import envoy.data.Contact; import envoy.data.Contact;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.MessageBuilder; import envoy.data.MessageBuilder;
@ -59,6 +62,9 @@ public final class ChatSceneController {
private Chat currentChat; private Chat currentChat;
private Startup startup;
private static final Settings settings = Settings.getInstance();
private static final EventBus eventBus = EventBus.getInstance(); private static final EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ChatSceneController.class); private static final Logger logger = EnvoyLog.getLogger(ChatSceneController.class);
@ -91,7 +97,8 @@ public final class ChatSceneController {
eventBus.register(UserStatusChangeEvent.class, e -> Platform.runLater(() -> userList.refresh())); eventBus.register(UserStatusChangeEvent.class, e -> Platform.runLater(() -> userList.refresh()));
} }
void initializeData(LocalDB localDB, Client client, WriteProxy writeProxy) { void initializeData(Startup startup, LocalDB localDB, Client client, WriteProxy writeProxy) {
this.startup = startup;
this.localDB = localDB; this.localDB = localDB;
this.client = client; this.client = client;
this.writeProxy = writeProxy; this.writeProxy = writeProxy;
@ -123,11 +130,14 @@ public final class ChatSceneController {
private void postButtonClicked() { postMessage(); } private void postButtonClicked() { postMessage(); }
@FXML @FXML
private void settingsButtonClicked() { logger.info("Settings Button clicked."); } private void settingsButtonClicked() {
startup.changeScene("/fxml/SettingsScene.fxml", new VBox(), true);
Platform.runLater(() -> ((SettingsSceneController) startup.getCurrentController()).initializeData(startup));
}
@FXML @FXML
private void messageTextUpdated(KeyEvent e) { private void messageTextUpdated(KeyEvent e) {
if (e.getCode() == KeyCode.ENTER) postMessage(); if (settings.isEnterToSend() && e.getCode() == KeyCode.ENTER || !settings.isEnterToSend() && e.getCode() == KeyCode.CONTROL) postMessage();
else postButton.setDisable(messageTextArea.getText().isBlank()); else postButton.setDisable(messageTextArea.getText().isBlank());
} }

View File

@ -8,12 +8,14 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage; import javafx.stage.Stage;
import envoy.client.data.*; import envoy.client.data.*;
@ -40,6 +42,11 @@ public final class Startup extends Application {
private WriteProxy writeProxy; private WriteProxy writeProxy;
private Cache<Message> cache; private Cache<Message> cache;
private FXMLLoader loader = new FXMLLoader();
private Stage stage;
private Scene previousScene;
private static final ClientConfig config = ClientConfig.getInstance(); private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Startup.class); private static final Logger logger = EnvoyLog.getLogger(Startup.class);
@ -48,6 +55,7 @@ public final class Startup extends Application {
*/ */
@Override @Override
public void start(Stage stage) throws Exception { public void start(Stage stage) throws Exception {
this.stage = stage;
try { try {
// Load the configuration from client.properties first // Load the configuration from client.properties first
Properties properties = new Properties(); Properties properties = new Properties();
@ -124,20 +132,67 @@ public final class Startup extends Application {
.forEach(u -> u.setStatus(UserStatus.OFFLINE)); .forEach(u -> u.setStatus(UserStatus.OFFLINE));
// Prepare stage and load ChatScene // Prepare stage and load ChatScene
var loader = new FXMLLoader(getClass().getResource("/fxml/ChatScene.fxml")); changeScene("/fxml/ChatScene.fxml", new GridPane(), false);
var anchorPane = loader.<GridPane>load(); Platform.runLater(() -> { ((ChatSceneController) loader.getController()).initializeData(this, localDB, client, writeProxy); });
var chatScene = new Scene(anchorPane);
stage.setTitle("Envoy"); stage.setTitle("Envoy");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/icons/envoy_logo.png"))); stage.getIcons().add(new Image(getClass().getResourceAsStream("/icons/envoy_logo.png")));
stage.setScene(chatScene);
loader.<ChatSceneController>getController().initializeData(localDB, client, writeProxy);
stage.show(); stage.show();
// Relay unread messages from cache // Relay unread messages from cache
if (cache != null && client.isOnline()) cache.relay(); if (cache != null && client.isOnline()) cache.relay();
} }
/**
* Changes the scene of the stage.
*
* @param <T> the type of the layout to use
* @param fxmlLocation the location of the fxml file
* @param layout the layout to use
* @param savePrevious if true, the previous stage will be stored in this
* instance of Startup, else the variable storing it will be
* set to null
* @since Envoy Client v0.1-beta
*/
public <T extends Pane> void changeScene(String fxmlLocation, T layout, boolean savePrevious) {
Platform.runLater(() -> {
try {
// Clearing the loader so that a new Scene can be initialised
loader = new FXMLLoader();
var rootNode = loader.<T>load(getClass().getResourceAsStream(fxmlLocation));
var chatScene = new Scene(rootNode);
previousScene = (savePrevious) ? stage.getScene() : null;
stage.setScene(chatScene);
stage.show();
// return loader.getController();
} catch (IOException e) {
new Alert(AlertType.ERROR, "The screen could not be updated due to reasons. (...bad programming...)");
e.printStackTrace();
logger.severe("Something happened (while loading the new scene from " + fxmlLocation + ")");
}
});
}
/**
* Changes the visual scene back to the saved value. The currently active scene
* can be saved.
*
* @param storeCurrent the old scene to store, if wanted. Can be null
* @since Envoy Client v0.1-beta
*/
public void restoreScene(boolean storeCurrent) {
Platform.runLater(() -> {
if (previousScene == null) throw new IllegalStateException("Someone tried restoring a null scene. (Something happened)");
else {
// switching previous and current
var temp = (storeCurrent) ? stage.getScene() : null;
stage.setScene(previousScene);
previousScene = temp;
stage.show();
}
});
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -159,4 +214,14 @@ public final class Startup extends Application {
@SuppressWarnings("javadoc") @SuppressWarnings("javadoc")
public static void main(String[] args) { launch(args); } public static void main(String[] args) { launch(args); }
/**
* @return the controller of the current scene or a {@link NullPointerException}
* if there is none
* @since Envoy Client v0.1-beta
*/
public Object getCurrentController() {
if (loader.getController() == null) throw new NullPointerException("Cannot deliver current controller as its undefined (duh!)");
else return loader.getController();
}
} }

View File

@ -1,8 +1,8 @@
/** /**
* This package contains classes defining the user interface. * This package contains classes defining the user interface.
* *
* @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */

View File

@ -0,0 +1,37 @@
package envoy.client.ui.settings;
import java.util.List;
import javafx.scene.layout.VBox;
import envoy.client.data.Settings;
import envoy.client.data.SettingsItem;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>GeneralSettingsPane.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class GeneralSettingsPane extends SettingsPane {
private static final Settings settings = Settings.getInstance();
/**
* @since Envoy Client v0.1-beta
*/
public GeneralSettingsPane() {
super("General");
var vbox = new VBox();
// TODO: Support other value types
List.of("onCloseMode", "enterToSend")
.stream()
.map(settings.getItems()::get)
.map(i -> new SettingsToggleButton((SettingsItem<Boolean>) i))
.forEach(vbox.getChildren()::add);
getChildren().add(vbox);
}
}

View File

@ -1,89 +0,0 @@
package envoy.client.ui.settings;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JTextPane;
import envoy.client.data.Settings;
import envoy.client.data.SettingsItem;
import envoy.client.ui.Theme;
import envoy.util.EnvoyLog;
/**
* Displays GUI components that allow general settings regarding the client.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>GeneralSettingsPanel.java</strong><br>
* Created: <strong>21 Dec 2019</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.3-alpha
*/
public class GeneralSettingsPanel extends SettingsPanel {
private Theme theme;
private static final String[] items = { "onCloseMode", "enterToSend" };
private static final Logger logger = EnvoyLog.getLogger(GeneralSettingsPanel.class);
private static final long serialVersionUID = 0L;
/**
* This is the constructor for the General class. Here the user can set general
* settings for the client.
*
* @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed
* @since Envoy Client v0.3-alpha
*/
public GeneralSettingsPanel(SettingsScreen parent) {
super(parent);
theme = Settings.getInstance().getCurrentTheme();
setBackground(theme.getCellColor());
GridBagLayout gbl_general = new GridBagLayout();
gbl_general.columnWidths = new int[] { 1, 1 };
gbl_general.rowHeights = new int[] { 1, 1, 1 };
gbl_general.columnWeights = new double[] { 1.0, 0.1 };
gbl_general.rowWeights = new double[] { 0.02, 0.02, 1.0 };
setLayout(gbl_general);
for (int i = 0; i < items.length; i++)
try {
createSettingElement(i, Settings.getInstance().getItems().get(items[i]));
} catch (SecurityException | ReflectiveOperationException e) {
logger.log(Level.WARNING, "Could not create settings item", e);
}
}
private void createSettingElement(int gridy, SettingsItem<?> settingsItem) throws SecurityException, ReflectiveOperationException {
JTextPane descriptionText = new JTextPane();
JComponent settingComponent = settingsItem.getComponent();
GridBagConstraints gbc_toggleSwitch = new GridBagConstraints();
gbc_toggleSwitch.gridx = 1;
gbc_toggleSwitch.gridy = gridy;
add(settingComponent, gbc_toggleSwitch);
descriptionText.setText(settingsItem.getDescription());
descriptionText.setBackground(theme.getBackgroundColor());
descriptionText.setForeground(theme.getBackgroundColor().invert());
descriptionText.setEditable(false);
GridBagConstraints gbc_descriptionText = new GridBagConstraints();
gbc_descriptionText.fill = GridBagConstraints.BOTH;
gbc_descriptionText.gridx = 0;
gbc_descriptionText.gridy = gridy;
gbc_descriptionText.insets = new Insets(5, 5, 5, 5);
add(descriptionText, gbc_descriptionText);
}
}

View File

@ -1,228 +0,0 @@
package envoy.client.ui.settings;
import java.awt.*;
import java.util.function.Consumer;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import envoy.client.data.Settings;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.client.ui.primary.PrimaryTextArea;
/**
* Displays window where you can choose a name for the new {@link Theme}.
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>NewThemeScreen.java</strong><br>
* Created: <strong>26 Dec 2019</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.3-alpha
*/
public class NewThemeScreen extends JDialog {
private final JPanel standardPanel = new JPanel();
private final JPanel secondaryPanel = new JPanel();
private JTextPane text = new JTextPane();
private PrimaryTextArea nameEnterTextArea = new PrimaryTextArea(4);
private PrimaryButton confirmButton = new PrimaryButton("Confirm");
private JTextPane errorText = new JTextPane();
private PrimaryButton otherName = new PrimaryButton("Other Name");
private PrimaryButton overwrite = new PrimaryButton("Overwrite");
private final Consumer<String> newThemeAction, modifyThemeAction;
private static final long serialVersionUID = 0L;
/**
* Creates a window, where you can choose a name for a new {@link Theme}. <br>
* There are two versions of this Window. The first one is responsible for
* choosing the name, the second one appears, if the name already exists.
*
* @param parent the dialog is launched with its location relative to
* this {@link SettingsScreen}
* @param newThemeAction is executed when a new theme name is entered
* @param modifyThemeAction is executed when an existing theme name is entered
* and confirmed
* @since Envoy Client v0.3-alpha
*/
public NewThemeScreen(SettingsScreen parent, Consumer<String> newThemeAction, Consumer<String> modifyThemeAction) {
this.newThemeAction = newThemeAction;
this.modifyThemeAction = modifyThemeAction;
setLocationRelativeTo(parent);
setTitle("New Theme");
setModal(true);
setDimensions(true);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
Theme theme = Settings.getInstance().getCurrentTheme();
getContentPane().setLayout(new BorderLayout());
standardPanel.setBackground(theme.getBackgroundColor());
secondaryPanel.setBackground(theme.getBackgroundColor());
loadStandardContent(theme);
}
private void setDimensions(boolean isStandard) {
Dimension size = isStandard ? new Dimension(300, 170) : new Dimension(300, 225);
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
}
private void loadStandardContent(Theme theme) {
getContentPane().removeAll();
// ContentPane
GridBagLayout gbl_contentPanel = new GridBagLayout();
gbl_contentPanel.columnWidths = new int[] { 1, 1 };
gbl_contentPanel.rowHeights = new int[] { 1, 1, 1 };
gbl_contentPanel.columnWeights = new double[] { 1, 1 };
gbl_contentPanel.rowWeights = new double[] { 1, 1, 1 };
getContentPane().add(standardPanel, BorderLayout.CENTER);
standardPanel.setLayout(gbl_contentPanel);
// text.setFont(new Font());
text.setText("Please enter a name for the new Theme");
text.setAlignmentX(CENTER_ALIGNMENT);
text.setBackground(theme.getCellColor());
text.setForeground(theme.getUserNameColor());
text.setEditable(false);
GridBagConstraints gbc_text = new GridBagConstraints();
gbc_text.fill = GridBagConstraints.HORIZONTAL;
gbc_text.gridx = 0;
gbc_text.gridy = 0;
gbc_text.gridwidth = 2;
gbc_text.insets = new Insets(5, 5, 5, 5);
standardPanel.add(text, gbc_text);
nameEnterTextArea.setBackground(theme.getCellColor());
nameEnterTextArea.setForeground(theme.getTypingMessageColor());
nameEnterTextArea.setText("");
nameEnterTextArea.setEditable(true);
GridBagConstraints gbc_input = new GridBagConstraints();
gbc_input.fill = GridBagConstraints.HORIZONTAL;
gbc_input.gridx = 0;
gbc_input.gridy = 1;
gbc_input.gridwidth = 2;
gbc_input.insets = new Insets(5, 5, 5, 5);
standardPanel.add(nameEnterTextArea, gbc_input);
confirmButton.setBackground(theme.getInteractableBackgroundColor());
confirmButton.setForeground(theme.getInteractableForegroundColor());
GridBagConstraints gbc_confirmButton = new GridBagConstraints();
gbc_confirmButton.gridx = 0;
gbc_confirmButton.gridy = 2;
gbc_confirmButton.gridwidth = 2;
gbc_confirmButton.insets = new Insets(5, 5, 5, 5);
standardPanel.add(confirmButton, gbc_confirmButton);
confirmButton.addActionListener((evt) -> {
if (!nameEnterTextArea.getText().isEmpty()) if (Settings.getInstance().getThemes().containsKey(nameEnterTextArea.getText())) {
// load other panel
setDimensions(false);
loadSecondaryPage(theme);
} else {
newThemeAction.accept(nameEnterTextArea.getText());
dispose();
}
});
}
private void loadSecondaryPage(Theme theme) {
// ContentPane
getContentPane().removeAll();
GridBagLayout gbl_secondaryPanel = new GridBagLayout();
gbl_secondaryPanel.columnWidths = new int[] { 1, 1 };
gbl_secondaryPanel.rowHeights = new int[] { 1, 1, 1, 1 };
gbl_secondaryPanel.columnWeights = new double[] { 1, 1 };
gbl_secondaryPanel.rowWeights = new double[] { 1, 1, 1, 1 };
getContentPane().add(secondaryPanel, BorderLayout.CENTER);
secondaryPanel.setLayout(gbl_secondaryPanel);
// text.setFont(new Font());
text.setText("Please enter a name for the new Theme");
text.setAlignmentX(CENTER_ALIGNMENT);
text.setBackground(theme.getCellColor());
text.setForeground(theme.getUserNameColor());
text.setEditable(false);
GridBagConstraints gbc_text = new GridBagConstraints();
gbc_text.fill = GridBagConstraints.HORIZONTAL;
gbc_text.gridx = 0;
gbc_text.gridy = 0;
gbc_text.gridwidth = 2;
gbc_text.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(text, gbc_text);
nameEnterTextArea.setBackground(theme.getCellColor());
nameEnterTextArea.setForeground(theme.getTypingMessageColor());
nameEnterTextArea.setEditable(false);
GridBagConstraints gbc_input = new GridBagConstraints();
gbc_input.fill = GridBagConstraints.HORIZONTAL;
gbc_input.gridx = 0;
gbc_input.gridy = 1;
gbc_input.gridwidth = 2;
gbc_input.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(nameEnterTextArea, gbc_input);
errorText.setText("The name does already exist. Choose another one or overwrite the old theme.");
errorText.setAlignmentX(CENTER_ALIGNMENT);
errorText.setBackground(theme.getCellColor());
errorText.setForeground(theme.getUserNameColor());
errorText.setEditable(false);
GridBagConstraints gbc_errorText = new GridBagConstraints();
gbc_errorText.fill = GridBagConstraints.HORIZONTAL;
gbc_errorText.gridx = 0;
gbc_errorText.gridy = 2;
gbc_errorText.gridwidth = 2;
gbc_errorText.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(errorText, gbc_errorText);
otherName.setBackground(theme.getInteractableBackgroundColor());
otherName.setForeground(theme.getInteractableForegroundColor());
GridBagConstraints gbc_otherName = new GridBagConstraints();
gbc_otherName.gridx = 0;
gbc_otherName.gridy = 3;
gbc_otherName.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(otherName, gbc_otherName);
overwrite.setBackground(theme.getInteractableBackgroundColor());
overwrite.setForeground(theme.getInteractableForegroundColor());
GridBagConstraints gbc_overwrite = new GridBagConstraints();
gbc_overwrite.gridx = 1;
gbc_overwrite.gridy = 3;
gbc_overwrite.insets = new Insets(5, 5, 5, 5);
secondaryPanel.add(overwrite, gbc_overwrite);
otherName.addActionListener((evt) -> { setDimensions(true); loadStandardContent(theme); });
overwrite.addActionListener((evt) -> { modifyThemeAction.accept(nameEnterTextArea.getText()); dispose(); });
}
}

View File

@ -0,0 +1,24 @@
package envoy.client.ui.settings;
import javafx.scene.layout.Pane;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsPane.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public abstract class SettingsPane extends Pane {
protected String title;
protected SettingsPane(String title) { this.title = title; }
/**
* @return the title of this settings pane
* @since Envoy Client v0.1-beta
*/
public String getTitle() { return title; }
}

View File

@ -1,30 +0,0 @@
package envoy.client.ui.settings;
import javax.swing.JPanel;
/**
* Serves as an interface between {@link SettingsScreen} and different
* {@link JPanel}s with actual settings that are defined as sub classes of this
* class.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsPanel.java</strong><br>
* Created: <strong>20 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha
*/
public abstract class SettingsPanel extends JPanel {
protected final SettingsScreen parent;
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link SettingsPanel}.
*
* @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed
*/
public SettingsPanel(SettingsScreen parent) { this.parent = parent; }
}

View File

@ -0,0 +1,58 @@
package envoy.client.ui.settings;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import envoy.client.ui.Startup;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsSceneController.java</strong><br>
* Created: <strong>10.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class SettingsSceneController {
private Startup startup;
@FXML
private ListView<SettingsPane> settingsList;
@FXML
private TitledPane titledPane;
/**
* initializes the object needed to reset the scene
*
* @param startup the instance of startup that stores the stage
* @since Envoy Client v0.1-beta
*/
public void initializeData(Startup startup) { this.startup = startup; }
@FXML
private void initialize() {
settingsList.setCellFactory(listView -> new ListCell<>() {
@Override
protected void updateItem(SettingsPane item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) setGraphic(new Label(item.getTitle()));
}
});
settingsList.getItems().add(new GeneralSettingsPane());
}
@FXML
private void settingsListClicked() {
final var pane = settingsList.getSelectionModel().getSelectedItem();
if (pane != null) {
titledPane.setText(pane.getTitle());
titledPane.setContent(pane);
}
}
@FXML
private void backButtonClicked() { startup.restoreScene(false); }
}

View File

@ -1,172 +0,0 @@
package envoy.client.ui.settings;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.event.EventBus;
import envoy.util.EnvoyLog;
/**
* This class provides the GUI to change the user specific settings.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsScreen.java</strong><br>
* Created: <strong>31 Oct 2019</strong><br>
*
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha
*/
public class SettingsScreen extends JDialog {
private static final long serialVersionUID = 0L;
private final JPanel contentPanel = new JPanel();
// Settings panel list
private final DefaultListModel<String> optionsListModel = new DefaultListModel<>();
private final JList<String> options = new JList<>(optionsListModel);
// OK and cancel buttons
private final JPanel buttonPane = new JPanel();
private final PrimaryButton cancelButton = new PrimaryButton("Cancel");
private final Insets insets = new Insets(5, 5, 5, 5);
private SettingsPanel settingsPanel;
private static final Logger logger = EnvoyLog.getLogger(SettingsScreen.class);
/**
* Initializes the settings screen.
*
* @since Envoy Client v0.1-alpha
*/
public SettingsScreen() {
// Initialize settings pages
Map<String, Class<? extends SettingsPanel>> panels = new HashMap<>();
panels.put("General", GeneralSettingsPanel.class);
panels.put("Color Themes", ThemeCustomizationPanel.class);
setBounds(10, 10, 450, 650);
getContentPane().setLayout(new BorderLayout());
{
// ContentPane
GridBagLayout gbl_contentPanel = new GridBagLayout();
gbl_contentPanel.columnWidths = new int[] { 1, 1 };
gbl_contentPanel.rowHeights = new int[] { 1 };
gbl_contentPanel.columnWeights = new double[] { 0.05, 1.0 };
gbl_contentPanel.rowWeights = new double[] { 1.0 };
getContentPane().add(contentPanel, BorderLayout.CENTER);
contentPanel.setLayout(gbl_contentPanel);
// Constraints for the settings panel
GridBagConstraints gbc_panel = new GridBagConstraints();
gbc_panel.fill = GridBagConstraints.BOTH;
gbc_panel.gridx = 1;
gbc_panel.gridy = 0;
gbc_panel.anchor = GridBagConstraints.PAGE_START;
gbc_panel.insets = insets;
options.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
options.addListSelectionListener((listSelectionEvent) -> {
if (!listSelectionEvent.getValueIsAdjusting()) {
// Get selected settings panel
final String option = options.getSelectedValue();
logger.log(Level.FINEST, "Selected settings panel: " + option);
// Remove previous settings panel
if (settingsPanel != null) contentPanel.remove(settingsPanel);
try {
settingsPanel = panels.get(option).getDeclaredConstructor(getClass()).newInstance(this);
// Add selected settings panel
contentPanel.add(settingsPanel, gbc_panel);
revalidate();
repaint();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
logger.log(Level.SEVERE, "Failed to invoke constructor of SettingsPanel " + option, e);
}
}
});
options.setFont(new Font("Arial", Font.PLAIN, 14));
GridBagConstraints gbc_optionsList = new GridBagConstraints();
gbc_optionsList.fill = GridBagConstraints.BOTH;
gbc_optionsList.gridx = 0;
gbc_optionsList.gridy = 0;
gbc_optionsList.anchor = GridBagConstraints.PAGE_START;
gbc_optionsList.insets = insets;
panels.keySet().forEach(name -> optionsListModel.addElement(name));
contentPanel.add(options, gbc_optionsList);
// ButtonPane
GridBagLayout gbl_buttonPane = new GridBagLayout();
gbl_buttonPane.columnWidths = new int[] { 1, 1 };
gbl_buttonPane.rowHeights = new int[] { 25 };
gbl_buttonPane.columnWeights = new double[] { 1.0, 1.0 };
gbl_buttonPane.rowWeights = new double[] { 0.0 };
getContentPane().add(buttonPane, BorderLayout.SOUTH);
buttonPane.setLayout(gbl_buttonPane);
{
cancelButton.setActionCommand("Cancel");
cancelButton.setBorderPainted(false);
GridBagConstraints gbc_cancelButton = new GridBagConstraints();
gbc_cancelButton.anchor = GridBagConstraints.NORTHWEST;
gbc_cancelButton.insets = insets;
gbc_cancelButton.gridx = 0;
gbc_cancelButton.gridy = 0;
buttonPane.add(cancelButton, gbc_cancelButton);
cancelButton.addActionListener((evt) -> { dispose(); });
}
}
// Apply current theme
applyTheme(Settings.getInstance().getCurrentTheme());
// Respond to theme changes
EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get()));
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setModal(true);
}
private void applyTheme(Theme theme) {
// JDialog
setBackground(theme.getBackgroundColor());
// contentPanel
contentPanel.setBackground(theme.getBackgroundColor());
// buttonPane
buttonPane.setBackground(theme.getCellColor());
// cancelButton
cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getInteractableForegroundColor());
// options
options.setSelectionForeground(theme.getUserNameColor());
options.setSelectionBackground(theme.getSelectionColor());
options.setForeground(theme.getUserNameColor());
options.setBackground(theme.getCellColor());
}
}

View File

@ -0,0 +1,32 @@
package envoy.client.ui.settings;
import javafx.event.ActionEvent;
import javafx.scene.control.ToggleButton;
import envoy.client.data.SettingsItem;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsToggleButton.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class SettingsToggleButton extends ToggleButton {
/**
* Creates an instance of {@link SettingsToggleButton}.
*
* @param settingsItem the {@link SettingsItem} whose values could be adapted
* @since Envoy Client v0.1-beta
*/
public SettingsToggleButton(SettingsItem<Boolean> settingsItem) {
super(settingsItem.getUserFriendlyName());
setSelected(settingsItem.get());
// "Schau, es hat sich behindert" - Kai, 2020
addEventHandler(ActionEvent.ACTION, e -> settingsItem.set(!settingsItem.get()));
}
}

View File

@ -1,246 +0,0 @@
package envoy.client.ui.settings;
import java.awt.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent;
import envoy.client.ui.Color;
import envoy.client.ui.Theme;
import envoy.client.ui.primary.PrimaryButton;
import envoy.event.EventBus;
import envoy.util.EnvoyLog;
/**
* Displays GUI components that allow changing the current {@Theme} and creating
* new ones.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ThemeCustomizationPanel.java</strong><br>
* Created: <strong>20 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha
*/
public class ThemeCustomizationPanel extends SettingsPanel {
private JPanel colorsPanel = new JPanel();
private DefaultComboBoxModel<String> themesModel;
private JComboBox<String> themes;
private Theme temporaryTheme;
private PrimaryButton createThemeButton = new PrimaryButton("Create Theme");
private boolean themeChanged;
private final Insets insets = new Insets(5, 5, 5, 5);
private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class);
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link ThemeCustomizationPanel} that enables the user to change
* the current {@link Theme} and create new themes as part of the
* {@link SettingsScreen}.
*
* @param parent the {@link SettingsScreen} as a part of which this
* {@link SettingsPanel} is displayed
* @since Envoy Client v0.2-alpha
*/
public ThemeCustomizationPanel(SettingsScreen parent) {
super(parent);
temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getCurrentTheme());
var themeNames = Settings.getInstance().getThemes().keySet().toArray(new String[0]);
String currentThemeName = Settings.getInstance().getCurrentThemeName();
for (int i = 0; i < themeNames.length; i++)
if (currentThemeName.equals(themeNames[i])) {
themeNames[i] = themeNames[0];
themeNames[0] = currentThemeName;
break;
}
themesModel = new DefaultComboBoxModel<>(themeNames);
themes = new JComboBox<>(themesModel);
GridBagLayout gbl_themeLayout = new GridBagLayout();
gbl_themeLayout.columnWidths = new int[] { 1, 1 };
gbl_themeLayout.rowHeights = new int[] { 1, 1, 1 };
gbl_themeLayout.columnWeights = new double[] { 1.0, 1.0 };
gbl_themeLayout.rowWeights = new double[] { 0.01, 1.0, 0.01 };
setLayout(gbl_themeLayout);
themes.setSelectedItem(Settings.getInstance().getCurrentTheme());
GridBagConstraints gbc_themes = new GridBagConstraints();
gbc_themes.fill = GridBagConstraints.HORIZONTAL;
gbc_themes.gridwidth = 2;
gbc_themes.gridx = 0;
gbc_themes.gridy = 0;
gbc_themes.anchor = GridBagConstraints.NORTHWEST;
gbc_themes.insets = new Insets(10, 10, 20, 10);
add(themes, gbc_themes);
GridBagLayout gbl_colorCustomizations = new GridBagLayout();
gbl_colorCustomizations.columnWidths = new int[] { 1, 1 };
gbl_colorCustomizations.rowHeights = new int[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 };
gbl_colorCustomizations.columnWeights = new double[] { 1, 1 };
gbl_colorCustomizations.rowWeights = new double[] { 1, 1, 1, 1, 1, 1, 1, 1 };
colorsPanel.setLayout(gbl_colorCustomizations);
Theme theme = Settings.getInstance().getCurrentTheme();
buildCustomizeElements(theme);
GridBagConstraints gbc_colorsPanel = new GridBagConstraints();
gbc_colorsPanel.fill = GridBagConstraints.HORIZONTAL;
gbc_colorsPanel.gridx = 0;
gbc_colorsPanel.gridy = 1;
gbc_colorsPanel.gridwidth = 2;
gbc_colorsPanel.anchor = GridBagConstraints.NORTHWEST;
gbc_colorsPanel.insets = insets;
add(colorsPanel, gbc_colorsPanel);
createThemeButton.addActionListener((evt) -> {
if (themeChanged) {
new NewThemeScreen(parent, name -> {
// Create new theme
logger.log(Level.FINEST, name);
Settings.getInstance().addNewThemeToMap(new Theme(name, temporaryTheme));
// Add new theme name to combo box
themesModel.addElement(name);
// Select new theme name
themes.setSelectedIndex(themesModel.getSize() - 1);
}, name -> {
// Modify theme
Settings.getInstance().getThemes().replace(name, new Theme(name, temporaryTheme));
if (themes.getSelectedItem().equals(name))
EventBus.getInstance().dispatch(new ThemeChangeEvent(Settings.getInstance().getTheme(name)));
else themes.setSelectedItem(name);
}).setVisible(true);
themeChanged = false;
}
});
GridBagConstraints gbc_createThemeButton = new GridBagConstraints();
gbc_createThemeButton.fill = GridBagConstraints.HORIZONTAL;
gbc_createThemeButton.gridx = 0;
gbc_createThemeButton.gridy = 2;
gbc_createThemeButton.anchor = GridBagConstraints.CENTER;
gbc_createThemeButton.insets = insets;
add(createThemeButton, gbc_createThemeButton);
colorsPanel.setBackground(theme.getCellColor());
// Apply theme upon selection
themes.addItemListener(e -> {
String selectedValue = (String) themes.getSelectedItem();
logger.log(Level.FINEST, "Selected theme: " + selectedValue);
final Theme currentTheme = Settings.getInstance().getTheme(selectedValue);
Settings.getInstance().setCurrentTheme(selectedValue);
EventBus.getInstance().dispatch(new ThemeChangeEvent(currentTheme));
});
// Apply current theme
applyTheme(theme);
// Respond to theme changes
EventBus.getInstance()
.register(ThemeChangeEvent.class,
evt -> {
final Theme currentTheme = evt.get();
temporaryTheme = new Theme("temporaryTheme", currentTheme);
applyTheme(currentTheme);
});
}
private void applyTheme(Theme theme) {
// themeContent
setForeground(theme.getUserNameColor());
setBackground(theme.getCellColor());
// createThemeButton
createThemeButton.setForeground(theme.getInteractableForegroundColor());
createThemeButton.setBackground(theme.getInteractableBackgroundColor());
// themes
themes.setBackground(theme.getInteractableBackgroundColor());
themes.setForeground(theme.getInteractableForegroundColor());
colorsPanel.setBackground(theme.getCellColor());
// Color panel
updateColorVariables(theme);
revalidate();
repaint();
}
private void updateColorVariables(Theme theme) {
colorsPanel.removeAll();
buildCustomizeElements(theme);
}
private void buildCustomizeElements(Theme theme) {
buildCustomizeElement(theme, theme.getBackgroundColor(), "Background", "backgroundColor", 1);
buildCustomizeElement(theme, theme.getCellColor(), "Cells", "cellColor", 2);
buildCustomizeElement(theme, theme.getInteractableForegroundColor(), "Interactable Foreground", "interactableForegroundColor", 3);
buildCustomizeElement(theme, theme.getInteractableBackgroundColor(), "Interactable Background", "interactableBackgroundColor", 4);
buildCustomizeElement(theme, theme.getTextColor(), "Text Color", "textColor", 5);
buildCustomizeElement(theme, theme.getDateColor(), "Date Chat", "dateColorChat", 6);
buildCustomizeElement(theme, theme.getSelectionColor(), "Selection", "selectionColor", 7);
buildCustomizeElement(theme, theme.getTypingMessageColor(), "Typing Message", "typingMessageColor", 8);
buildCustomizeElement(theme, theme.getUserNameColor(), "User Names", "userNameColor", 9);
}
private void buildCustomizeElement(Theme theme, Color color, String name, String colorName, int gridy) {
JButton button = new JButton();
JTextPane textPane = new JTextPane();
textPane.setFont(new Font("Arial", Font.PLAIN, 14));
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getBackgroundColor().invert());
textPane.setText(name);
textPane.setEditable(false);
button.setBackground(color);
button.setPreferredSize(new Dimension(25, 25));
button.addActionListener((evt) -> {
java.awt.Color c = JColorChooser.showDialog(null, "Choose a color", color);
if (c != null) {
Color newColor = new Color(c);
if (!color.equals(newColor)) {
logger.log(Level.FINEST, "New Color: " + newColor);
temporaryTheme.setColor(colorName, newColor);
themeChanged = true;
}
button.setBackground(newColor);
}
});
GridBagConstraints gbc_textPane = new GridBagConstraints();
gbc_textPane.fill = GridBagConstraints.BOTH;
gbc_textPane.gridx = 0;
gbc_textPane.gridy = gridy;
gbc_textPane.anchor = GridBagConstraints.CENTER;
gbc_textPane.insets = insets;
colorsPanel.add(textPane, gbc_textPane);
GridBagConstraints gbc_button = new GridBagConstraints();
gbc_button.fill = GridBagConstraints.BOTH;
gbc_button.gridx = 1;
gbc_button.gridy = gridy;
gbc_button.anchor = GridBagConstraints.CENTER;
gbc_button.insets = insets;
colorsPanel.add(button, gbc_button);
}
}

View File

@ -1,9 +1,14 @@
/** /**
* This package contains user interface classes related to the settings screen. * This package contains classes used for representing the settings
* visually.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>19 Apr 2020</strong><br>
* *
* @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.1-beta
*/ */
package envoy.client.ui.settings; package envoy.client.ui.settings;

View File

@ -16,6 +16,8 @@ module envoy {
requires javafx.controls; requires javafx.controls;
requires javafx.fxml; requires javafx.fxml;
requires javafx.base; requires javafx.base;
requires javafx.graphics;
opens envoy.client.ui.settings to javafx.graphics, javafx.fxml;
opens envoy.client.ui to javafx.graphics, javafx.fxml; opens envoy.client.ui to javafx.graphics, javafx.fxml;
} }

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.HBox?>
<?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.settings.SettingsSceneController">
<children>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<ListView fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0" />
<TitledPane fx:id="titledPane" collapsible="false" prefHeight="200.0" prefWidth="200.0" />
</children>
</HBox>
<Button defaultButton="true" mnemonicParsing="false" onMouseClicked="#backButtonClicked" text="Back">
<opaqueInsets>
<Insets />
</opaqueInsets>
</Button>
</children>
</VBox>