From 4c946861721bd6fcc6a5833c0be8a0094438e20c Mon Sep 17 00:00:00 2001 From: delvh Date: Sat, 14 Mar 2020 19:59:37 +0100 Subject: [PATCH 01/32] added newline at EOF for any file not having one at its end --- pom.xml | 2 +- src/main/java/envoy/client/data/Chat.java | 12 ++++----- .../envoy/client/data/PersistentLocalDb.java | 2 +- src/main/java/envoy/client/data/Settings.java | 2 +- src/main/java/envoy/client/net/Receiver.java | 2 +- .../client/ui/ContactsSearchRenderer.java | 2 +- .../java/envoy/client/ui/PrimaryButton.java | 2 +- .../envoy/client/ui/PrimaryToggleSwitch.java | 6 ++--- src/main/java/envoy/client/ui/Startup.java | 6 ++--- .../java/envoy/client/ui/StatusTrayIcon.java | 2 +- .../envoy/client/ui/UserListRenderer.java | 2 +- .../ui/settings/GeneralSettingsPanel.java | 2 +- .../client/ui/settings/NewThemeScreen.java | 12 +++++---- .../client/ui/settings/SettingsScreen.java | 2 +- .../ui/settings/ThemeCustomizationPanel.java | 26 +++++++------------ 15 files changed, 36 insertions(+), 46 deletions(-) diff --git a/pom.xml b/pom.xml index 572b5ed..1ffd41f 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ com.github.informatik-ag-ngl envoy-common - develop-SNAPSHOT + f~forwarding_messages-SNAPSHOT diff --git a/src/main/java/envoy/client/data/Chat.java b/src/main/java/envoy/client/data/Chat.java index 49083a7..30b6bc6 100644 --- a/src/main/java/envoy/client/data/Chat.java +++ b/src/main/java/envoy/client/data/Chat.java @@ -61,12 +61,10 @@ public class Chat implements Serializable { public void read(WriteProxy writeProxy) throws IOException { for (int i = model.size() - 1; i >= 0; --i) { final Message m = model.get(i); - if (m.getSenderId() == recipient.getId()) { - if (m.getStatus() == MessageStatus.READ) break; - else { - m.setStatus(MessageStatus.READ); - writeProxy.writeMessageStatusChangeEvent(new MessageStatusChangeEvent(m)); - } + if (m.getSenderId() == recipient.getId()) if (m.getStatus() == MessageStatus.READ) break; + else { + m.setStatus(MessageStatus.READ); + writeProxy.writeMessageStatusChangeEvent(new MessageStatusChangeEvent(m)); } } } @@ -89,4 +87,4 @@ public class Chat implements Serializable { * @since Envoy v0.1-alpha */ public User getRecipient() { return recipient; } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/data/PersistentLocalDb.java b/src/main/java/envoy/client/data/PersistentLocalDb.java index f59b0f2..52d5c63 100644 --- a/src/main/java/envoy/client/data/PersistentLocalDb.java +++ b/src/main/java/envoy/client/data/PersistentLocalDb.java @@ -100,4 +100,4 @@ public class PersistentLocalDb extends LocalDb { idGenerator = SerializationUtils.read(idGeneratorFile, IdGenerator.class); } catch (ClassNotFoundException | IOException e) {} } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/data/Settings.java b/src/main/java/envoy/client/data/Settings.java index c44e4f4..89dea67 100644 --- a/src/main/java/envoy/client/data/Settings.java +++ b/src/main/java/envoy/client/data/Settings.java @@ -190,4 +190,4 @@ public class Settings { * @since Envoy v0.3-alpha */ public Theme getTheme(String themeName) { return themes.get(themeName); } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/net/Receiver.java b/src/main/java/envoy/client/net/Receiver.java index 08ea28d..d49fd93 100644 --- a/src/main/java/envoy/client/net/Receiver.java +++ b/src/main/java/envoy/client/net/Receiver.java @@ -85,4 +85,4 @@ public class Receiver extends Thread { * Removes all object processors registered at this {@link Receiver}. */ public void removeAllProcessors() { processors.clear(); } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/ContactsSearchRenderer.java b/src/main/java/envoy/client/ui/ContactsSearchRenderer.java index 83fb81d..aa96b62 100644 --- a/src/main/java/envoy/client/ui/ContactsSearchRenderer.java +++ b/src/main/java/envoy/client/ui/ContactsSearchRenderer.java @@ -74,4 +74,4 @@ public class ContactsSearchRenderer implements ComponentListCellRenderer { return panel; } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/PrimaryButton.java b/src/main/java/envoy/client/ui/PrimaryButton.java index 65c52a0..3486acd 100644 --- a/src/main/java/envoy/client/ui/PrimaryButton.java +++ b/src/main/java/envoy/client/ui/PrimaryButton.java @@ -60,4 +60,4 @@ public class PrimaryButton extends JButton { * @since Envoy 0.2-alpha */ public void setArcSize(int arcSize) { this.arcSize = arcSize; } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/PrimaryToggleSwitch.java b/src/main/java/envoy/client/ui/PrimaryToggleSwitch.java index 3f58e2e..7b4f41f 100644 --- a/src/main/java/envoy/client/ui/PrimaryToggleSwitch.java +++ b/src/main/java/envoy/client/ui/PrimaryToggleSwitch.java @@ -15,7 +15,7 @@ import envoy.client.data.SettingsItem; * Project: envoy-client
* File: PrimaryToggleSwitch.java
* Created: 21 Dec 2019
- * + * * @author Maximilian Käfer * @author Kai S. K. Engelbart * @since Envoy v0.3-alpha @@ -28,7 +28,7 @@ public class PrimaryToggleSwitch extends JButton { /** * Initializes a {@link PrimaryToggleSwitch}. - * + * * @param settingsItem the {@link SettingsItem} that is controlled by this * {@link PrimaryToggleSwitch} * @since Envoy v0.3-alpha @@ -54,4 +54,4 @@ public class PrimaryToggleSwitch extends JButton { g.setColor(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableBackgroundColor()); g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25); } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 98a8ffc..8f44c2a 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -127,11 +127,9 @@ public class Startup { // Save all users to the local database and flush cache localDb.setUsers(client.getUsers()); writeProxy.flushCache(); - } else { - + } else // Set all contacts to offline mode localDb.getUsers().values().stream().filter(u -> u != localDb.getUser()).forEach(u -> u.setStatus(UserStatus.OFFLINE)); - } // Display ChatWindow and StatusTrayIcon EventQueue.invokeLater(() -> { @@ -173,4 +171,4 @@ public class Startup { } })); } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/StatusTrayIcon.java b/src/main/java/envoy/client/ui/StatusTrayIcon.java index 4513aea..02e5629 100644 --- a/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -94,4 +94,4 @@ public class StatusTrayIcon { throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e); } } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/UserListRenderer.java b/src/main/java/envoy/client/ui/UserListRenderer.java index c43eeb6..2bdefca 100644 --- a/src/main/java/envoy/client/ui/UserListRenderer.java +++ b/src/main/java/envoy/client/ui/UserListRenderer.java @@ -59,4 +59,4 @@ public class UserListRenderer extends JLabel implements ListCellRenderer { } return this; } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java index 13df564..c85c048 100644 --- a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java +++ b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java @@ -90,4 +90,4 @@ public class GeneralSettingsPanel extends SettingsPanel { @Override public ActionListener getOkButtonAction() { return evt -> {}; } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java index 02dd42a..e30adc9 100644 --- a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java +++ b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java @@ -42,10 +42,12 @@ public class NewThemeScreen extends JDialog { * Creates a window, where you can choose a name for a new {@link Theme}.
* 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 + * + * @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 v0.3-alpha */ public NewThemeScreen(SettingsScreen parent, Consumer newThemeAction, Consumer modifyThemeAction) { @@ -223,4 +225,4 @@ public class NewThemeScreen extends JDialog { overwrite.addActionListener((evt) -> { modifyThemeAction.accept(nameEnterTextArea.getText()); dispose(); }); } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/settings/SettingsScreen.java b/src/main/java/envoy/client/ui/settings/SettingsScreen.java index c04da96..56e8cf4 100644 --- a/src/main/java/envoy/client/ui/settings/SettingsScreen.java +++ b/src/main/java/envoy/client/ui/settings/SettingsScreen.java @@ -189,4 +189,4 @@ public class SettingsScreen extends JDialog { options.setForeground(theme.getUserNameColor()); options.setBackground(theme.getCellColor()); } -} \ No newline at end of file +} diff --git a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java index a2aca9a..8772064 100644 --- a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java +++ b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java @@ -2,8 +2,6 @@ package envoy.client.ui.settings; import java.awt.*; import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.util.logging.Level; import java.util.logging.Logger; @@ -100,17 +98,13 @@ public class ThemeCustomizationPanel extends SettingsPanel { colorsPanel.setBackground(theme.getCellColor()); // Apply theme upon selection - themes.addItemListener(new ItemListener() { + themes.addItemListener(e -> { + String selectedValue = (String) themes.getSelectedItem(); + logger.log(Level.FINEST, "Selected theme: " + selectedValue); - @Override - public void itemStateChanged(ItemEvent 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)); - } + final Theme currentTheme = Settings.getInstance().getTheme(selectedValue); + Settings.getInstance().setCurrentTheme(selectedValue); + EventBus.getInstance().dispatch(new ThemeChangeEvent(currentTheme)); }); // Apply current theme @@ -143,11 +137,9 @@ public class ThemeCustomizationPanel extends SettingsPanel { }, name -> { // Modify theme Settings.getInstance().getThemes().replace(name, new Theme(name, temporaryTheme)); - if (themes.getSelectedItem().equals(name)) { + if (themes.getSelectedItem().equals(name)) EventBus.getInstance().dispatch(new ThemeChangeEvent(Settings.getInstance().getTheme(name))); - } else { - themes.setSelectedItem(name); - } + else themes.setSelectedItem(name); }).setVisible(true); themeChanged = false; } @@ -232,4 +224,4 @@ public class ThemeCustomizationPanel extends SettingsPanel { colorsPanel.add(button, gbc_button); } -} \ No newline at end of file +} From 9eaa9dc9d217e5e5c7a67b188457f15a2d08b319 Mon Sep 17 00:00:00 2001 From: delvh Date: Sat, 14 Mar 2020 21:34:17 +0100 Subject: [PATCH 02/32] Created packages ui.renderer and ui.primary --- .../java/envoy/client/data/SettingsItem.java | 2 +- src/main/java/envoy/client/ui/ChatWindow.java | 14 + .../java/envoy/client/ui/LoginDialog.java | 1 + .../ui/{ => primary}/PrimaryButton.java | 126 ++--- .../ui/{ => primary}/PrimaryScrollBar.java | 3 +- .../ui/{ => primary}/PrimaryScrollPane.java | 4 +- .../ui/{ => primary}/PrimaryTextArea.java | 2 +- .../ui/{ => primary}/PrimaryToggleSwitch.java | 115 ++--- .../envoy/client/ui/primary/package-info.java | 17 + .../ContactsSearchRenderer.java | 4 +- .../{ => renderer}/MessageListRenderer.java | 39 +- .../ui/{ => renderer}/UserListRenderer.java | 2 +- .../client/ui/renderer/package-info.java | 14 + .../client/ui/settings/NewThemeScreen.java | 456 +++++++++--------- .../client/ui/settings/SettingsScreen.java | 2 +- 15 files changed, 440 insertions(+), 361 deletions(-) rename src/main/java/envoy/client/ui/{ => primary}/PrimaryButton.java (94%) rename src/main/java/envoy/client/ui/{ => primary}/PrimaryScrollBar.java (98%) rename src/main/java/envoy/client/ui/{ => primary}/PrimaryScrollPane.java (97%) rename src/main/java/envoy/client/ui/{ => primary}/PrimaryTextArea.java (98%) rename src/main/java/envoy/client/ui/{ => primary}/PrimaryToggleSwitch.java (95%) create mode 100644 src/main/java/envoy/client/ui/primary/package-info.java rename src/main/java/envoy/client/ui/{ => renderer}/ContactsSearchRenderer.java (95%) rename src/main/java/envoy/client/ui/{ => renderer}/MessageListRenderer.java (70%) rename src/main/java/envoy/client/ui/{ => renderer}/UserListRenderer.java (98%) create mode 100644 src/main/java/envoy/client/ui/renderer/package-info.java diff --git a/src/main/java/envoy/client/data/SettingsItem.java b/src/main/java/envoy/client/data/SettingsItem.java index 57ba600..cd85c71 100644 --- a/src/main/java/envoy/client/data/SettingsItem.java +++ b/src/main/java/envoy/client/data/SettingsItem.java @@ -7,7 +7,7 @@ import java.util.function.Consumer; import javax.swing.JComponent; -import envoy.client.ui.PrimaryToggleSwitch; +import envoy.client.ui.primary.PrimaryToggleSwitch; /** * Encapsulates a persistent value that is directly or indirectly mutable by the diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 6a5ae38..2db2d3d 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -20,6 +20,12 @@ import envoy.client.net.Client; import envoy.client.net.WriteProxy; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListModel; +import envoy.client.ui.primary.PrimaryButton; +import envoy.client.ui.primary.PrimaryScrollPane; +import envoy.client.ui.primary.PrimaryTextArea; +import envoy.client.ui.renderer.ContactsSearchRenderer; +import envoy.client.ui.renderer.MessageListRenderer; +import envoy.client.ui.renderer.UserListRenderer; import envoy.client.ui.settings.SettingsScreen; import envoy.data.Message; import envoy.data.Message.MessageStatus; @@ -110,7 +116,15 @@ public class ChatWindow extends JFrame { contentPane.setLayout(gbl_contentPane); messageList.setBorder(new EmptyBorder(space, space, space, space)); + messageList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.isPopupTrigger()) { + + } + } + }); scrollPane.setViewportView(messageList); scrollPane.addComponentListener(new ComponentAdapter() { diff --git a/src/main/java/envoy/client/ui/LoginDialog.java b/src/main/java/envoy/client/ui/LoginDialog.java index 193ae8d..9cd645d 100644 --- a/src/main/java/envoy/client/ui/LoginDialog.java +++ b/src/main/java/envoy/client/ui/LoginDialog.java @@ -16,6 +16,7 @@ import javax.swing.border.EmptyBorder; import envoy.client.data.*; import envoy.client.event.HandshakeSuccessfulEvent; import envoy.client.net.Client; +import envoy.client.ui.primary.PrimaryButton; import envoy.data.LoginCredentials; import envoy.data.Message; import envoy.data.User; diff --git a/src/main/java/envoy/client/ui/PrimaryButton.java b/src/main/java/envoy/client/ui/primary/PrimaryButton.java similarity index 94% rename from src/main/java/envoy/client/ui/PrimaryButton.java rename to src/main/java/envoy/client/ui/primary/PrimaryButton.java index 3486acd..efcfd9f 100644 --- a/src/main/java/envoy/client/ui/PrimaryButton.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryButton.java @@ -1,63 +1,63 @@ -package envoy.client.ui; - -import java.awt.Graphics; - -import javax.swing.JButton; - -/** - * Project: envoy-client
- * File: PrimaryButton.javaEvent.java
- * Created: 07.12.2019
- * - * @author Kai S. K. Engelbart - * @author Maximilian Käfer - * @since Envoy v0.2-alpha - */ -public class PrimaryButton extends JButton { - - private static final long serialVersionUID = 3662266120667728364L; - - private int arcSize; - - /** - * Creates a primary button - * - * @param title the title of the button - * @since Envoy 0.2-alpha - */ - public PrimaryButton(String title) { this(title, 6); } - - /** - * Creates a primary button - * - * @param title the title of the button - * @param arcSize the size of the arc used to draw the round button edges - * @since Envoy 0.2-alpha - */ - public PrimaryButton(String title, int arcSize) { - super(title); - setBorderPainted(false); - setFocusPainted(false); - setContentAreaFilled(false); - this.arcSize = arcSize; - } - - @Override - protected void paintComponent(Graphics g) { - g.setColor(getBackground()); - g.fillRoundRect(0, 0, getWidth(), getHeight(), arcSize, arcSize); - super.paintComponent(g); - } - - /** - * @return the arcSize - * @since Envoy 0.2-alpha - */ - public int getArcSize() { return arcSize; } - - /** - * @param arcSize the arcSize to set - * @since Envoy 0.2-alpha - */ - public void setArcSize(int arcSize) { this.arcSize = arcSize; } -} +package envoy.client.ui.primary; + +import java.awt.Graphics; + +import javax.swing.JButton; + +/** + * Project: envoy-client
+ * File: PrimaryButton.javaEvent.java
+ * Created: 07.12.2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.2-alpha + */ +public class PrimaryButton extends JButton { + + private static final long serialVersionUID = 3662266120667728364L; + + private int arcSize; + + /** + * Creates a primary button + * + * @param title the title of the button + * @since Envoy 0.2-alpha + */ + public PrimaryButton(String title) { this(title, 6); } + + /** + * Creates a primary button + * + * @param title the title of the button + * @param arcSize the size of the arc used to draw the round button edges + * @since Envoy 0.2-alpha + */ + public PrimaryButton(String title, int arcSize) { + super(title); + setBorderPainted(false); + setFocusPainted(false); + setContentAreaFilled(false); + this.arcSize = arcSize; + } + + @Override + protected void paintComponent(Graphics g) { + g.setColor(getBackground()); + g.fillRoundRect(0, 0, getWidth(), getHeight(), arcSize, arcSize); + super.paintComponent(g); + } + + /** + * @return the arcSize + * @since Envoy 0.2-alpha + */ + public int getArcSize() { return arcSize; } + + /** + * @param arcSize the arcSize to set + * @since Envoy 0.2-alpha + */ + public void setArcSize(int arcSize) { this.arcSize = arcSize; } +} diff --git a/src/main/java/envoy/client/ui/PrimaryScrollBar.java b/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java similarity index 98% rename from src/main/java/envoy/client/ui/PrimaryScrollBar.java rename to src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java index 8e8131c..7dd560e 100644 --- a/src/main/java/envoy/client/ui/PrimaryScrollBar.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.primary; import java.awt.Color; import java.awt.Dimension; @@ -13,6 +13,7 @@ import javax.swing.JScrollBar; import javax.swing.plaf.basic.BasicScrollBarUI; import envoy.client.data.Settings; +import envoy.client.ui.Theme; /** * Project: envoy-client
diff --git a/src/main/java/envoy/client/ui/PrimaryScrollPane.java b/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java similarity index 97% rename from src/main/java/envoy/client/ui/PrimaryScrollPane.java rename to src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java index f8e4dbc..47c2abb 100644 --- a/src/main/java/envoy/client/ui/PrimaryScrollPane.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java @@ -1,7 +1,9 @@ -package envoy.client.ui; +package envoy.client.ui.primary; import javax.swing.JScrollPane; +import envoy.client.ui.Theme; + /** * Project: envoy-client
* File: PrimaryScrollPane.java
diff --git a/src/main/java/envoy/client/ui/PrimaryTextArea.java b/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java similarity index 98% rename from src/main/java/envoy/client/ui/PrimaryTextArea.java rename to src/main/java/envoy/client/ui/primary/PrimaryTextArea.java index 29ed2f9..9f6a7ce 100644 --- a/src/main/java/envoy/client/ui/PrimaryTextArea.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.primary; import java.awt.Font; import java.awt.Graphics; diff --git a/src/main/java/envoy/client/ui/PrimaryToggleSwitch.java b/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java similarity index 95% rename from src/main/java/envoy/client/ui/PrimaryToggleSwitch.java rename to src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java index 7b4f41f..30ee098 100644 --- a/src/main/java/envoy/client/ui/PrimaryToggleSwitch.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java @@ -1,57 +1,58 @@ -package envoy.client.ui; - -import java.awt.Dimension; -import java.awt.Graphics; - -import javax.swing.JButton; - -import envoy.client.data.Settings; -import envoy.client.data.SettingsItem; - -/** - * This component can be used to toggle between two options. This will change - * the state of a {@code boolean} {@link SettingsItem}.
- *
- * Project: envoy-client
- * File: PrimaryToggleSwitch.java
- * Created: 21 Dec 2019
- * - * @author Maximilian Käfer - * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha - */ -public class PrimaryToggleSwitch extends JButton { - - private boolean state; - - private static final long serialVersionUID = -721155303106833184L; - - /** - * Initializes a {@link PrimaryToggleSwitch}. - * - * @param settingsItem the {@link SettingsItem} that is controlled by this - * {@link PrimaryToggleSwitch} - * @since Envoy v0.3-alpha - */ - public PrimaryToggleSwitch(SettingsItem settingsItem) { - setPreferredSize(new Dimension(50, 25)); - setMinimumSize(new Dimension(50, 25)); - setMaximumSize(new Dimension(50, 25)); - - setBorderPainted(false); - setFocusPainted(false); - setContentAreaFilled(false); - - state = settingsItem.get(); - addActionListener((evt) -> { state = !state; settingsItem.set(state); revalidate(); repaint(); }); - } - - @Override - public void paintComponent(Graphics g) { - g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY); - g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25); - - g.setColor(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableBackgroundColor()); - g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25); - } -} +package envoy.client.ui.primary; + +import java.awt.Dimension; +import java.awt.Graphics; + +import javax.swing.JButton; + +import envoy.client.data.Settings; +import envoy.client.data.SettingsItem; +import envoy.client.ui.Color; + +/** + * This component can be used to toggle between two options. This will change + * the state of a {@code boolean} {@link SettingsItem}.
+ *
+ * Project: envoy-client
+ * File: PrimaryToggleSwitch.java
+ * Created: 21 Dec 2019
+ * + * @author Maximilian Käfer + * @author Kai S. K. Engelbart + * @since Envoy v0.3-alpha + */ +public class PrimaryToggleSwitch extends JButton { + + private boolean state; + + private static final long serialVersionUID = -721155303106833184L; + + /** + * Initializes a {@link PrimaryToggleSwitch}. + * + * @param settingsItem the {@link SettingsItem} that is controlled by this + * {@link PrimaryToggleSwitch} + * @since Envoy v0.3-alpha + */ + public PrimaryToggleSwitch(SettingsItem settingsItem) { + setPreferredSize(new Dimension(50, 25)); + setMinimumSize(new Dimension(50, 25)); + setMaximumSize(new Dimension(50, 25)); + + setBorderPainted(false); + setFocusPainted(false); + setContentAreaFilled(false); + + state = settingsItem.get(); + addActionListener((evt) -> { state = !state; settingsItem.set(state); revalidate(); repaint(); }); + } + + @Override + public void paintComponent(Graphics g) { + g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY); + g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25); + + g.setColor(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableBackgroundColor()); + g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25); + } +} diff --git a/src/main/java/envoy/client/ui/primary/package-info.java b/src/main/java/envoy/client/ui/primary/package-info.java new file mode 100644 index 0000000..8ede25f --- /dev/null +++ b/src/main/java/envoy/client/ui/primary/package-info.java @@ -0,0 +1,17 @@ +/** + * This package defines all "primary" components that were defined specifically + * for the visual improvement of Envoy. However, they can still be used in + * general for other projects.
+ * Primary elements are supposed to provide the main functionality of a UI + * component.
+ *
+ * Project: envoy-client
+ * File: package-info.java
+ * Created: 14 Mar 2020
+ * + * @author Leon Hofmeister + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-beta + */ +package envoy.client.ui.primary; diff --git a/src/main/java/envoy/client/ui/ContactsSearchRenderer.java b/src/main/java/envoy/client/ui/renderer/ContactsSearchRenderer.java similarity index 95% rename from src/main/java/envoy/client/ui/ContactsSearchRenderer.java rename to src/main/java/envoy/client/ui/renderer/ContactsSearchRenderer.java index aa96b62..60a0ef2 100644 --- a/src/main/java/envoy/client/ui/ContactsSearchRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/ContactsSearchRenderer.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.renderer; import java.awt.Component; import java.awt.Dimension; @@ -8,8 +8,10 @@ import javax.swing.*; import envoy.client.data.Settings; import envoy.client.event.SendEvent; +import envoy.client.ui.Color; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListCellRenderer; +import envoy.client.ui.primary.PrimaryButton; import envoy.data.User; import envoy.event.ContactOperationEvent; import envoy.event.EventBus; diff --git a/src/main/java/envoy/client/ui/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java similarity index 70% rename from src/main/java/envoy/client/ui/MessageListRenderer.java rename to src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index 6b70e1b..047f12c 100644 --- a/src/main/java/envoy/client/ui/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -1,15 +1,21 @@ -package envoy.client.ui; +package envoy.client.ui.renderer; -import java.awt.BorderLayout; import java.awt.Font; +import java.awt.image.BufferedImage; +import java.io.IOException; import java.text.SimpleDateFormat; +import java.util.EnumMap; +import javax.imageio.ImageIO; import javax.swing.*; import envoy.client.data.Settings; +import envoy.client.ui.Color; +import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListCellRenderer; import envoy.data.Message; +import envoy.data.Message.MessageStatus; /** * Defines how a message is displayed.
@@ -25,12 +31,23 @@ import envoy.data.Message; */ public class MessageListRenderer implements ComponentListCellRenderer { + private static final EnumMap statusIcons = new EnumMap<>(MessageStatus.class); + + static { + for (MessageStatus ms : MessageStatus.values()) + try { + statusIcons.put(ms, ImageIO.read(MessageListRenderer.class.getResourceAsStream(ms.toString().toLowerCase() + "_icon.png"))); + } catch (IOException e) { + e.printStackTrace(); + } + } + private JTextArea messageTextArea; @Override public JPanel getListCellComponent(ComponentList list, Message value, boolean isSelected) { final JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); // Panel background @@ -47,7 +64,17 @@ public class MessageListRenderer implements ComponentListCellRenderer { // Set the date color to be the value of DateColorChat dateLabel.setForeground(theme.getDateColor()); - panel.add(dateLabel, BorderLayout.NORTH); + panel.add(dateLabel); + + if (value.isForwarded()) try { + var forwardLabel = new JLabel("Forwarded", new ImageIcon(ClassLoader.getSystemResourceAsStream(null).readAllBytes()), + SwingConstants.CENTER); + forwardLabel.setBackground(panel.getBackground()); + forwardLabel.setForeground(Color.lightGray); + panel.add(forwardLabel); + } catch (IOException e) { + e.printStackTrace(); + } // The JTextArea that displays the text content of a message and its status messageTextArea = new JTextArea(text + System.getProperty("line.separator")); @@ -58,7 +85,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { messageTextArea.setBackground(panel.getBackground()); messageTextArea.setEditable(false); - panel.add(messageTextArea, BorderLayout.CENTER); + panel.add(messageTextArea); JLabel statusLabel = new JLabel(state); statusLabel.setFont(new Font("Arial", Font.BOLD, 14)); @@ -83,7 +110,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { statusLabel.setForeground(statusColor); statusLabel.setBackground(panel.getBackground()); - panel.add(statusLabel, BorderLayout.SOUTH); + panel.add(statusLabel); // Define some space to the messages below panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(), BorderFactory.createEtchedBorder())); diff --git a/src/main/java/envoy/client/ui/UserListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserListRenderer.java similarity index 98% rename from src/main/java/envoy/client/ui/UserListRenderer.java rename to src/main/java/envoy/client/ui/renderer/UserListRenderer.java index 2bdefca..7f6bc0c 100644 --- a/src/main/java/envoy/client/ui/UserListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/UserListRenderer.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.renderer; import java.awt.Component; import java.awt.Dimension; diff --git a/src/main/java/envoy/client/ui/renderer/package-info.java b/src/main/java/envoy/client/ui/renderer/package-info.java new file mode 100644 index 0000000..68748ef --- /dev/null +++ b/src/main/java/envoy/client/ui/renderer/package-info.java @@ -0,0 +1,14 @@ +/** + * This package contains all Envoy-specific renderers for lists that store an + * arbitrary number of JComponents.
+ *
+ * Project: envoy-client
+ * File: package-info.java
+ * Created: 14 Mar 2020
+ * + * @author Leon Hofmeister + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-beta + */ +package envoy.client.ui.renderer; diff --git a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java index e30adc9..4fdab10 100644 --- a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java +++ b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java @@ -1,228 +1,228 @@ -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.PrimaryButton; -import envoy.client.ui.PrimaryTextArea; -import envoy.client.ui.Theme; - -/** - * Displays window where you can choose a name for the new {@link Theme}. - *
- * Project: envoy-client
- * File: NewThemeScreen.java
- * Created: 26 Dec 2019
- * - * @author Maximilian Käfer - * @since Envoy 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 newThemeAction, modifyThemeAction; - - private static final long serialVersionUID = 2369985550946300976L; - - /** - * Creates a window, where you can choose a name for a new {@link Theme}.
- * 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 v0.3-alpha - */ - public NewThemeScreen(SettingsScreen parent, Consumer newThemeAction, Consumer 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().getThemes().get(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(); }); - } -} +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}. + *
+ * Project: envoy-client
+ * File: NewThemeScreen.java
+ * Created: 26 Dec 2019
+ * + * @author Maximilian Käfer + * @since Envoy 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 newThemeAction, modifyThemeAction; + + private static final long serialVersionUID = 2369985550946300976L; + + /** + * Creates a window, where you can choose a name for a new {@link Theme}.
+ * 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 v0.3-alpha + */ + public NewThemeScreen(SettingsScreen parent, Consumer newThemeAction, Consumer 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().getThemes().get(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(); }); + } +} diff --git a/src/main/java/envoy/client/ui/settings/SettingsScreen.java b/src/main/java/envoy/client/ui/settings/SettingsScreen.java index 56e8cf4..e1ae290 100644 --- a/src/main/java/envoy/client/ui/settings/SettingsScreen.java +++ b/src/main/java/envoy/client/ui/settings/SettingsScreen.java @@ -11,8 +11,8 @@ import javax.swing.*; import envoy.client.data.Settings; import envoy.client.event.ThemeChangeEvent; -import envoy.client.ui.PrimaryButton; import envoy.client.ui.Theme; +import envoy.client.ui.primary.PrimaryButton; import envoy.event.EventBus; import envoy.util.EnvoyLog; From b4e8a67b934532c2fd21acf9aa90be7296167150 Mon Sep 17 00:00:00 2001 From: delvh Date: Sat, 14 Mar 2020 22:44:03 +0100 Subject: [PATCH 03/32] Implemented method to forward a message --- src/main/java/envoy/client/ui/ChatWindow.java | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 2db2d3d..e95de1c 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -501,28 +501,52 @@ public class ChatWindow extends JFrame { scrollForPossibleContacts.applyTheme(theme); } + /** + * Sends a new message to the server based on the text entered in the textArea. + * + * @since Envoy v0.1-beta + */ private void postMessage() { if (userList.isSelectionEmpty()) { JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); return; } - if (!messageEnterTextArea.getText().isEmpty()) try { - checkMessageTextLength(); - // Create message - final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator()) - .setText(messageEnterTextArea.getText()) - .build(); + if (!messageEnterTextArea.getText().isEmpty()) checkMessageTextLength(); + // Create message + final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator()) + .setText(messageEnterTextArea.getText()) + .build(); + sendMessage(message); + // Clear text field + messageEnterTextArea.setText(""); + } + /** + * Forwards a message. + * + * @param msg the message to forward + * @param recipient the new recipient of the message + * @since Envoy v0.1-beta + */ + private void forwardMessage(Message msg, User recipient) { + sendMessage(new MessageBuilder(msg, recipient.getId(), localDb.getIdGenerator()).build()); + } + + /** + * Sends a {@link Message} to the server. + * + * @param message the message to send + * @since Envoy v0.1-beta + */ + private void sendMessage(final Message message) { + try { // Send message writeProxy.writeMessage(message); // Add message to PersistentLocalDb and update UI currentChat.appendMessage(message); - // Clear text field - messageEnterTextArea.setText(""); - // Update UI revalidate(); repaint(); From 9ef9282432849c35738b91769dd4bc3a9129a858 Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 15 Mar 2020 18:55:07 +0100 Subject: [PATCH 04/32] Added multiple selection capability to the component list --- .../envoy/client/ui/list/ComponentList.java | 67 ++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 9b5b749..372f33b 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -3,6 +3,8 @@ package envoy.client.ui.list; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.List; import javax.swing.*; @@ -23,8 +25,9 @@ public class ComponentList extends JPanel { private ComponentListModel model; private ComponentListCellRenderer renderer; + private boolean multipleSelectionEnabled = false; - private int currentSelection = -1; + private List currentSelections = new ArrayList<>(); private static final long serialVersionUID = 1759644503942876737L; @@ -137,27 +140,38 @@ public class ComponentList extends JPanel { * @since Envoy v0.1-beta */ private void componentSelected(int index) { - if (index == currentSelection) { + // removing selection of element at index + if (currentSelections.contains(index)) { // Clear selection - update(currentSelection, false); - currentSelection = -1; + updateSelection(index, false); + currentSelections.remove(Integer.valueOf(index)); } else { - // Remove old selection - if (currentSelection >= 0) update(currentSelection, false); + // Remove old selection if multipleSelection is disabled + if (!multipleSelectionEnabled && currentSelections.size() > 0) clearSelections(); // Assign new selection - currentSelection = index; + currentSelections.add(index); // Update current selection - update(currentSelection, true); + updateSelection(index, true); } revalidate(); repaint(); } + /** + * Clears all currently active selections. + * + * @since Envoy v0.1-beta + */ + private void clearSelections() { + currentSelections.forEach(index2 -> updateSelection(index2, false)); + currentSelections.clear(); + } + /** * Replaces a list element with a newly rendered instance of its contents. * @@ -165,8 +179,43 @@ public class ComponentList extends JPanel { * @param isSelected the selection state passed to the {@link ListCellRenderer} * @since Envoy v0.1-beta */ - private void update(int index, boolean isSelected) { + private void updateSelection(int index, boolean isSelected) { remove(index); add(model.get(index), index, isSelected); } + + /** + * @return the selected elements or null if none is selected + * @since Envoy v0.1-beta + */ + public List getSelected() { + List selected = new ArrayList<>(); + currentSelections.forEach(index -> selected.add(model.get(index))); + return selected; + } + + /** + * @return the multipleSelectionEnabled + * @since Envoy v0.1-beta + */ + public boolean isMultipleSelectionEnabled() { return multipleSelectionEnabled; } + + /** + * Enables the selection of multiple elements. + * + * @see ComponentList#disableMultipleSelection + * @since Envoy v0.1-beta + */ + public void enableMultipleSelection() { this.multipleSelectionEnabled = true; } + + /** + * Allows only one element to be selected. True by default. + * + * @see ComponentList#enableMultipleSelection + * @since Envoy v0.1-beta + */ + public void disableMultipleSelection() { + this.multipleSelectionEnabled = false; + clearSelections(); + } } From d0fded868e77929dbd30dbe4caad7b34e97e2ef1 Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 15 Mar 2020 19:11:02 +0100 Subject: [PATCH 05/32] implemented UserComponentListRenderer --- src/main/java/envoy/client/ui/ChatWindow.java | 1 + .../renderer/UserComponentListRenderer.java | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index e95de1c..8e7dc55 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -70,6 +70,7 @@ public class ChatWindow extends JFrame { private JTextPane textPane = new JTextPane(); private PrimaryButton postButton = new PrimaryButton("Post"); private PrimaryButton settingsButton = new PrimaryButton("Settings"); + private JPopupMenu contextMenu; // Contacts Header private JPanel contactsHeader = new JPanel(); diff --git a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java new file mode 100644 index 0000000..f762eab --- /dev/null +++ b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java @@ -0,0 +1,78 @@ +package envoy.client.ui.renderer; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.io.Serializable; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import envoy.client.data.Settings; +import envoy.client.ui.Theme; +import envoy.client.ui.list.ComponentList; +import envoy.client.ui.list.ComponentListCellRenderer; +import envoy.data.User; +import envoy.data.User.UserStatus; + +/** + * Project: envoy-client
+ * File: UserComponentListRenderer.java
+ * Created: 15 Mar 2020
+ * + * @author Leon Hofmeister + * @since Envoy v0.1-beta + */ +public class UserComponentListRenderer implements ComponentListCellRenderer, Serializable { + + private static final long serialVersionUID = -2379244319112111284L; + + /** + * @since Envoy v0.1-beta + */ + public UserComponentListRenderer() {} + + @Override + public JComponent getListCellComponent(ComponentList list, User value, boolean isSelected) { + final JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + + // Panel background + panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); + panel.setOpaque(true); + + panel.setPreferredSize(new Dimension(100, 35)); + + // TODO add profile picture support in BorderLayout.West + + JLabel username = new JLabel(value.getName()); + username.setForeground(theme.getUserNameColor()); + panel.add(username, BorderLayout.CENTER); + + final UserStatus status = value.getStatus(); + JLabel statusLabel = new JLabel(status.toString()); + java.awt.Color foreground; + switch (status) { + case OFFLINE: + foreground = java.awt.Color.LIGHT_GRAY; + break; + case AWAY: + foreground = java.awt.Color.YELLOW; + break; + case BUSY: + foreground = java.awt.Color.BLUE; + break; + case ONLINE: + foreground = java.awt.Color.GREEN; + break; + default: + foreground = java.awt.Color.LIGHT_GRAY; + break; + } + statusLabel.setForeground(foreground); + panel.add(statusLabel, BorderLayout.NORTH); + return panel; + } + +} From 2127018b5810c5c6df440b909dffd055bace76f8 Mon Sep 17 00:00:00 2001 From: delvh Date: Mon, 16 Mar 2020 11:21:41 +0100 Subject: [PATCH 06/32] fixed minor inconveniences --- .../java/envoy/client/ui/list/ComponentList.java | 13 +++++++++++++ .../ui/renderer/UserComponentListRenderer.java | 5 ----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 372f33b..ed74b61 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -194,12 +194,25 @@ public class ComponentList extends JPanel { return selected; } + /** + * @return the model + * @since Envoy v0.1-beta + */ + public ComponentListModel getModel() { return model; } + /** * @return the multipleSelectionEnabled * @since Envoy v0.1-beta */ public boolean isMultipleSelectionEnabled() { return multipleSelectionEnabled; } + /** + * @param multipleSelectionEnabled if true, multiple elements can be selected in + * the component list + * @since Envoy v0.1-beta + */ + public void setMultipleSelectionEnabled(boolean multipleSelectionEnabled) { this.multipleSelectionEnabled = multipleSelectionEnabled; } + /** * Enables the selection of multiple elements. * diff --git a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java index f762eab..e86214b 100644 --- a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java @@ -27,11 +27,6 @@ public class UserComponentListRenderer implements ComponentListCellRenderer list, User value, boolean isSelected) { final JPanel panel = new JPanel(); From e93cd8761f489f9d00abebd9a4b7ec0f8cccec90 Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 16 Mar 2020 14:30:14 +0100 Subject: [PATCH 07/32] Added IconUtil utility class --- src/main/java/envoy/client/ui/IconUtil.java | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/envoy/client/ui/IconUtil.java diff --git a/src/main/java/envoy/client/ui/IconUtil.java b/src/main/java/envoy/client/ui/IconUtil.java new file mode 100644 index 0000000..457a977 --- /dev/null +++ b/src/main/java/envoy/client/ui/IconUtil.java @@ -0,0 +1,31 @@ +package envoy.client.ui; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.EnumMap; +import java.util.EnumSet; + +import javax.imageio.ImageIO; + +/** + * Project: envoy-client + * File: IconUtil.java + * Created: 16.03.2020 + * + * @author Kai S. K. Engelbart + * @since Envoy v0.1-beta + */ +public class IconUtil { + + private IconUtil() {} + + public BufferedImage load(String path) throws IOException { return ImageIO.read(IconUtil.class.getResourceAsStream(path)); } + + public > EnumMap loadByEnum(Class enumClass) throws IOException { + var icons = new EnumMap(enumClass); + var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/"; + for (var e : EnumSet.allOf(enumClass)) + icons.put(e, load(path + e.toString().toLowerCase() + ".png")); + return icons; + } +} From c9c2cd9f4db6f739cf8a9d889424919767660d5a Mon Sep 17 00:00:00 2001 From: DieGurke <55625494+DieGurke@users.noreply.github.com> Date: Mon, 16 Mar 2020 19:08:26 +0100 Subject: [PATCH 08/32] Message displaying Implemented message status icons and revised message displaying (unfinished) --- src/main/java/envoy/client/ui/IconUtil.java | 13 +- .../ui/renderer/MessageListRenderer.java | 140 ++++++++++-------- .../resources/icons/messagestatus/read.png | Bin 0 -> 30078 bytes .../icons/messagestatus/received.png | Bin 0 -> 30067 bytes .../resources/icons/messagestatus/sent.png | Bin 0 -> 26174 bytes .../resources/icons/messagestatus/waiting.png | Bin 0 -> 26922 bytes 6 files changed, 87 insertions(+), 66 deletions(-) create mode 100644 src/main/resources/icons/messagestatus/read.png create mode 100644 src/main/resources/icons/messagestatus/received.png create mode 100644 src/main/resources/icons/messagestatus/sent.png create mode 100644 src/main/resources/icons/messagestatus/waiting.png diff --git a/src/main/java/envoy/client/ui/IconUtil.java b/src/main/java/envoy/client/ui/IconUtil.java index 457a977..47588ab 100644 --- a/src/main/java/envoy/client/ui/IconUtil.java +++ b/src/main/java/envoy/client/ui/IconUtil.java @@ -1,11 +1,12 @@ package envoy.client.ui; -import java.awt.image.BufferedImage; +import java.awt.Image; import java.io.IOException; import java.util.EnumMap; import java.util.EnumSet; import javax.imageio.ImageIO; +import javax.swing.ImageIcon; /** * Project: envoy-client @@ -19,13 +20,15 @@ public class IconUtil { private IconUtil() {} - public BufferedImage load(String path) throws IOException { return ImageIO.read(IconUtil.class.getResourceAsStream(path)); } + public static ImageIcon load(String path, int size) throws IOException { + return new ImageIcon(ImageIO.read(IconUtil.class.getResourceAsStream(path)).getScaledInstance(size, size, Image.SCALE_SMOOTH)); + } - public > EnumMap loadByEnum(Class enumClass) throws IOException { - var icons = new EnumMap(enumClass); + public static > EnumMap loadByEnum(Class enumClass, int size) throws IOException { + var icons = new EnumMap(enumClass); var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/"; for (var e : EnumSet.allOf(enumClass)) - icons.put(e, load(path + e.toString().toLowerCase() + ".png")); + icons.put(e, load(path + e.toString().toLowerCase() + ".png", size)); return icons; } } diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index 047f12c..b537c56 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -1,16 +1,15 @@ package envoy.client.ui.renderer; -import java.awt.Font; -import java.awt.image.BufferedImage; +import java.awt.*; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.EnumMap; -import javax.imageio.ImageIO; import javax.swing.*; import envoy.client.data.Settings; import envoy.client.ui.Color; +import envoy.client.ui.IconUtil; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListCellRenderer; @@ -31,90 +30,109 @@ import envoy.data.Message.MessageStatus; */ public class MessageListRenderer implements ComponentListCellRenderer { - private static final EnumMap statusIcons = new EnumMap<>(MessageStatus.class); + private static EnumMap statusIcons; static { - for (MessageStatus ms : MessageStatus.values()) - try { - statusIcons.put(ms, ImageIO.read(MessageListRenderer.class.getResourceAsStream(ms.toString().toLowerCase() + "_icon.png"))); - } catch (IOException e) { - e.printStackTrace(); - } + try { + statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16); + } catch (IOException e) { + e.printStackTrace(); + } } - private JTextArea messageTextArea; @Override - public JPanel getListCellComponent(ComponentList list, Message value, boolean isSelected) { - final JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + public JPanel getListCellComponent(ComponentList list, Message message, boolean isSelected) { final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); - // Panel background + // panel + final JPanel panel = new JPanel(); + + GridBagLayout gbl_panel = new GridBagLayout(); + gbl_panel.columnWidths = new int[] { 1, 1 }; + gbl_panel.rowHeights = new int[] { 1, 1 }; + gbl_panel.columnWeights = new double[] { 1, 1 }; + gbl_panel.rowWeights = new double[] { 1, 1 }; + + panel.setLayout(gbl_panel); panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); // TODO: Handle message attachments - final String state = value.getStatus().toString(); - final String date = new SimpleDateFormat("dd.MM.yyyy HH:mm").format(value.getCreationDate()); - final String text = value.getText(); + // content variables + final String status = message.getStatus().toString(); + final String date = new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate()); + final String text = message.getText(); - // The Label that displays the creation date of a message - JLabel dateLabel = new JLabel(date); - // Set the date color to be the value of DateColorChat + // date Label - The Label that displays the creation date of a message + JLabel dateLabel = new JLabel(); + dateLabel.setText(date); dateLabel.setForeground(theme.getDateColor()); + dateLabel.setAlignmentX(1f); + dateLabel.setFont(new Font("Arial", Font.PLAIN, 12)); + dateLabel.setPreferredSize(dateLabel.getPreferredSize()); - panel.add(dateLabel); + GridBagConstraints gbc_dateLabel = new GridBagConstraints(); + gbc_dateLabel.fill = GridBagConstraints.BOTH; + gbc_dateLabel.gridx = 0; + gbc_dateLabel.gridy = 0; + panel.add(dateLabel, gbc_dateLabel); - if (value.isForwarded()) try { + + // Message area - The JTextArea that displays the text content of a message. + var messageTextArea = new JTextArea(text); + messageTextArea.setLineWrap(true); + messageTextArea.setWrapStyleWord(true); + messageTextArea.setForeground(theme.getMessageTextColor()); + messageTextArea.setAlignmentX(0.5f); + messageTextArea.setBackground(Color.red); + messageTextArea.setEditable(false); + messageTextArea.setFont(new Font("Arial", Font.PLAIN, 14)); + // messageTextArea.setPreferredSize(messageTextArea.getPreferredSize()); + + GridBagConstraints gbc_messageTextArea = new GridBagConstraints(); + gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL; + gbc_messageTextArea.gridx = 0; + gbc_messageTextArea.gridy = 1; + panel.add(messageTextArea, gbc_messageTextArea); + + + // status Label - displays the status of the message + JLabel statusLabel = new JLabel(statusIcons.get(message.getStatus())); + + GridBagConstraints gbc_statusLabel = new GridBagConstraints(); + gbc_statusLabel.fill = GridBagConstraints.BOTH; + gbc_statusLabel.gridx = 1; + gbc_statusLabel.gridy = 1; + panel.add(statusLabel, gbc_statusLabel); + + // Forwarding + if (message.isForwarded()) try { var forwardLabel = new JLabel("Forwarded", new ImageIcon(ClassLoader.getSystemResourceAsStream(null).readAllBytes()), SwingConstants.CENTER); forwardLabel.setBackground(panel.getBackground()); forwardLabel.setForeground(Color.lightGray); - panel.add(forwardLabel); + + GridBagConstraints gbc_forwardLabel = new GridBagConstraints(); + gbc_forwardLabel.fill = GridBagConstraints.BOTH; + gbc_forwardLabel.gridx = 1; + gbc_forwardLabel.gridy = 0; + panel.add(forwardLabel, gbc_forwardLabel); } catch (IOException e) { e.printStackTrace(); } - // The JTextArea that displays the text content of a message and its status - messageTextArea = new JTextArea(text + System.getProperty("line.separator")); - messageTextArea.setLineWrap(true); - messageTextArea.setWrapStyleWord(true); - messageTextArea.setAlignmentX(0.5f); - messageTextArea.setForeground(theme.getMessageTextColor()); - messageTextArea.setBackground(panel.getBackground()); - messageTextArea.setEditable(false); - - panel.add(messageTextArea); - - JLabel statusLabel = new JLabel(state); - statusLabel.setFont(new Font("Arial", Font.BOLD, 14)); - Color statusColor; - switch (value.getStatus()) { - case WAITING: - statusColor = Color.gray; - break; - case SENT: - statusColor = Color.blue; - break; - case RECEIVED: - statusColor = Color.yellow; - break; - case READ: - statusColor = Color.green; - break; - default: - statusColor = theme.getMessageTextColor(); - break; - } - statusLabel.setForeground(statusColor); - statusLabel.setBackground(panel.getBackground()); - - panel.add(statusLabel); - + int padding = (int) (list.getWidth() * 0.35); // Define some space to the messages below - panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(), BorderFactory.createEtchedBorder())); + panel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, 0, 0, padding), + BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(), BorderFactory.createEtchedBorder()))); + var size = new Dimension(list.getWidth(), (int) panel.getPreferredSize().getHeight()); + + // panel.setPreferredSize(panel.getPreferredSize()); + panel.setMaximumSize(size); + // System.out.println(panel.getMaximumSize()); return panel; } } diff --git a/src/main/resources/icons/messagestatus/read.png b/src/main/resources/icons/messagestatus/read.png new file mode 100644 index 0000000000000000000000000000000000000000..d81f23c4d382048139d443ccd8db4190b25703c8 GIT binary patch literal 30078 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/messagestatus/received.png b/src/main/resources/icons/messagestatus/received.png new file mode 100644 index 0000000000000000000000000000000000000000..3da92824bcd8fdd61186aceac12541fc4988f1f8 GIT binary patch literal 30067 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

3A|UQiZi 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/messagestatus/waiting.png b/src/main/resources/icons/messagestatus/waiting.png new file mode 100644 index 0000000000000000000000000000000000000000..e767b28c3aa70ec3d4e7825d3bdf809deab43490 GIT binary patch literal 26922 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 literal 0 HcmV?d00001 From 011e85c18e218e3d847206b713ab406a6410e894 Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 16 Mar 2020 21:49:02 +0100 Subject: [PATCH 09/32] Working on line wrapping and message height (borders currently broken) --- .../envoy/client/ui/list/ComponentList.java | 1 - .../ui/renderer/MessageListRenderer.java | 59 ++++++++----------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index ed74b61..0bb8acf 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -53,7 +53,6 @@ public class ComponentList extends JPanel { */ public ComponentList(ComponentListModel model, ComponentListCellRenderer renderer) { this(renderer); - this.model = model; setModel(model); } diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index b537c56..911a419 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -10,7 +10,6 @@ import javax.swing.*; import envoy.client.data.Settings; import envoy.client.ui.Color; import envoy.client.ui.IconUtil; -import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListCellRenderer; import envoy.data.Message; @@ -40,13 +39,14 @@ public class MessageListRenderer implements ComponentListCellRenderer { } } + // TODO: Handle message attachments @Override public JPanel getListCellComponent(ComponentList list, Message message, boolean isSelected) { - final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + final var theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); - // panel - final JPanel panel = new JPanel(); + // Panel + final var panel = new JPanel(); GridBagLayout gbl_panel = new GridBagLayout(); gbl_panel.columnWidths = new int[] { 1, 1 }; @@ -57,30 +57,21 @@ public class MessageListRenderer implements ComponentListCellRenderer { panel.setLayout(gbl_panel); panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); - // TODO: Handle message attachments - - // content variables - final String status = message.getStatus().toString(); - final String date = new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate()); - final String text = message.getText(); - - // date Label - The Label that displays the creation date of a message - JLabel dateLabel = new JLabel(); - dateLabel.setText(date); + // Date Label - The Label that displays the creation date of a message + var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate())); dateLabel.setForeground(theme.getDateColor()); dateLabel.setAlignmentX(1f); dateLabel.setFont(new Font("Arial", Font.PLAIN, 12)); dateLabel.setPreferredSize(dateLabel.getPreferredSize()); - GridBagConstraints gbc_dateLabel = new GridBagConstraints(); + var gbc_dateLabel = new GridBagConstraints(); gbc_dateLabel.fill = GridBagConstraints.BOTH; gbc_dateLabel.gridx = 0; gbc_dateLabel.gridy = 0; panel.add(dateLabel, gbc_dateLabel); - // Message area - The JTextArea that displays the text content of a message. - var messageTextArea = new JTextArea(text); + var messageTextArea = new JTextArea(message.getText()); messageTextArea.setLineWrap(true); messageTextArea.setWrapStyleWord(true); messageTextArea.setForeground(theme.getMessageTextColor()); @@ -88,51 +79,49 @@ public class MessageListRenderer implements ComponentListCellRenderer { messageTextArea.setBackground(Color.red); messageTextArea.setEditable(false); messageTextArea.setFont(new Font("Arial", Font.PLAIN, 14)); - // messageTextArea.setPreferredSize(messageTextArea.getPreferredSize()); + messageTextArea.setSize(list.getWidth() - 1, 200); - GridBagConstraints gbc_messageTextArea = new GridBagConstraints(); + var gbc_messageTextArea = new GridBagConstraints(); gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL; gbc_messageTextArea.gridx = 0; gbc_messageTextArea.gridy = 1; panel.add(messageTextArea, gbc_messageTextArea); + // Status Label - displays the status of the message + var statusLabel = new JLabel(statusIcons.get(message.getStatus())); - // status Label - displays the status of the message - JLabel statusLabel = new JLabel(statusIcons.get(message.getStatus())); - - GridBagConstraints gbc_statusLabel = new GridBagConstraints(); + var gbc_statusLabel = new GridBagConstraints(); gbc_statusLabel.fill = GridBagConstraints.BOTH; gbc_statusLabel.gridx = 1; gbc_statusLabel.gridy = 1; panel.add(statusLabel, gbc_statusLabel); // Forwarding - if (message.isForwarded()) try { - var forwardLabel = new JLabel("Forwarded", new ImageIcon(ClassLoader.getSystemResourceAsStream(null).readAllBytes()), - SwingConstants.CENTER); + if (message.isForwarded()) { + // TODO: icon + var forwardLabel = new JLabel("Forwarded", null, SwingConstants.CENTER); forwardLabel.setBackground(panel.getBackground()); forwardLabel.setForeground(Color.lightGray); - - GridBagConstraints gbc_forwardLabel = new GridBagConstraints(); + + var gbc_forwardLabel = new GridBagConstraints(); gbc_forwardLabel.fill = GridBagConstraints.BOTH; gbc_forwardLabel.gridx = 1; gbc_forwardLabel.gridy = 0; panel.add(forwardLabel, gbc_forwardLabel); - } catch (IOException e) { - e.printStackTrace(); } int padding = (int) (list.getWidth() * 0.35); + // Define some space to the messages below - panel.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createEmptyBorder(0, 0, 0, padding), + panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 0, padding), BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(), BorderFactory.createEtchedBorder()))); - var size = new Dimension(list.getWidth(), (int) panel.getPreferredSize().getHeight()); + var size = new Dimension(list.getWidth() - 50, panel.getPreferredSize().height); - // panel.setPreferredSize(panel.getPreferredSize()); + panel.setPreferredSize(size); + panel.setMinimumSize(size); panel.setMaximumSize(size); - // System.out.println(panel.getMaximumSize()); + return panel; } } From 461a39543978396c2698786f3e8ea53f6d071d3f Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 17 Mar 2020 09:37:19 +0100 Subject: [PATCH 10/32] Working on message scaling --- src/main/java/envoy/client/ui/ChatWindow.java | 8 ++++++- .../ui/renderer/MessageListRenderer.java | 24 +++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 8e7dc55..5e87a00 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -131,7 +131,13 @@ public class ChatWindow extends JFrame { // updates list elements when list is resized @Override - public void componentResized(ComponentEvent e) { messageList.synchronizeModel(); } + public void componentResized(ComponentEvent e) { + messageList.synchronizeModel(); + var prefSize = e.getComponent().getPreferredSize(); + messageList.setMinimumSize(new Dimension(prefSize.width, 0)); + messageList.setMaximumSize(new Dimension(prefSize.width, Integer.MAX_VALUE)); + messageList.setPreferredSize(new Dimension(prefSize.width, messageList.getPreferredSize().height)); + } }); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index 911a419..f6b6596 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -30,10 +30,12 @@ import envoy.data.Message.MessageStatus; public class MessageListRenderer implements ComponentListCellRenderer { private static EnumMap statusIcons; + private static ImageIcon forwardIcon; static { try { statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16); + // forwardIcon = IconUtil.load("icons/forward.png", 16); } catch (IOException e) { e.printStackTrace(); } @@ -46,7 +48,8 @@ public class MessageListRenderer implements ComponentListCellRenderer { final var theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); // Panel - final var panel = new JPanel(); + final var panel = new JPanel(); + final int padding = (int) (list.getWidth() * 0.35); GridBagLayout gbl_panel = new GridBagLayout(); gbl_panel.columnWidths = new int[] { 1, 1 }; @@ -76,10 +79,14 @@ public class MessageListRenderer implements ComponentListCellRenderer { messageTextArea.setWrapStyleWord(true); messageTextArea.setForeground(theme.getMessageTextColor()); messageTextArea.setAlignmentX(0.5f); - messageTextArea.setBackground(Color.red); + messageTextArea.setBackground(theme.getCellColor()); messageTextArea.setEditable(false); - messageTextArea.setFont(new Font("Arial", Font.PLAIN, 14)); - messageTextArea.setSize(list.getWidth() - 1, 200); + var font = new Font("Arial", Font.PLAIN, 14); + messageTextArea.setFont(font); + // var frc = new FontRenderContext(new AffineTransform(), true, true); + // messageTextArea.setSize(Math.min(list.getWidth() - padding, (int) + // font.getStringBounds(message.getText(), frc).getWidth()), 10); + messageTextArea.setSize(list.getWidth() - padding, 10); var gbc_messageTextArea = new GridBagConstraints(); gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL; @@ -110,13 +117,10 @@ public class MessageListRenderer implements ComponentListCellRenderer { panel.add(forwardLabel, gbc_forwardLabel); } - int padding = (int) (list.getWidth() * 0.35); + // Define an etched border and some space to the messages below + panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 20, padding), BorderFactory.createEtchedBorder())); - // Define some space to the messages below - panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 0, padding), - BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(), BorderFactory.createEtchedBorder()))); - - var size = new Dimension(list.getWidth() - 50, panel.getPreferredSize().height); + var size = new Dimension(list.getWidth(), panel.getPreferredSize().height); panel.setPreferredSize(size); panel.setMinimumSize(size); From 29d2eeaa7e2a3ebdb55a244f67f13cfb45010178 Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 17 Mar 2020 10:08:58 +0100 Subject: [PATCH 11/32] Fixed horizontal message placement This commit contains potentially unstable changes to ComponentList. If the ListCellRenderer is not set, the class might behave in an unexpected way. --- src/main/java/envoy/client/ui/ChatWindow.java | 4 +++- .../envoy/client/ui/list/ComponentList.java | 18 ++++++++++++++++-- .../ui/renderer/MessageListRenderer.java | 14 ++++++++++---- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 5e87a00..5be89f1 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -65,7 +65,7 @@ public class ChatWindow extends JFrame { private PrimaryTextArea messageEnterTextArea = new PrimaryTextArea(space); private JList userList = new JList<>(); private DefaultListModel userListModel = new DefaultListModel<>(); - private ComponentList messageList = new ComponentList<>(new MessageListRenderer()); + private ComponentList messageList = new ComponentList<>(); private PrimaryScrollPane scrollPane = new PrimaryScrollPane(); private JTextPane textPane = new JTextPane(); private PrimaryButton postButton = new PrimaryButton("Post"); @@ -614,6 +614,8 @@ public class ChatWindow extends JFrame { this.localDb = localDb; this.writeProxy = writeProxy; + messageList.setRenderer(new MessageListRenderer(client.getSender().getId())); + // Load users and chats new Thread(() -> { localDb.getUsers().values().forEach(user -> { diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 0bb8acf..3d90ea9 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -31,6 +31,8 @@ public class ComponentList extends JPanel { private static final long serialVersionUID = 1759644503942876737L; + public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); } + /** * Creates an instance of {@link ComponentList}. * @@ -39,8 +41,8 @@ public class ComponentList extends JPanel { * @since Envoy v0.3-alpha */ public ComponentList(ComponentListCellRenderer renderer) { - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - this.renderer = renderer; + this(); + setRenderer(renderer); } /** @@ -199,6 +201,18 @@ public class ComponentList extends JPanel { */ public ComponentListModel getModel() { return model; } + /** + * @return the renderer + * @since Envoy v0.1-beta + */ + public ComponentListCellRenderer getRenderer() { return renderer; } + + /** + * @param renderer the renderer to set + * @since Envoy v0.1-beta + */ + public void setRenderer(ComponentListCellRenderer renderer) { this.renderer = renderer; } + /** * @return the multipleSelectionEnabled * @since Envoy v0.1-beta diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index f6b6596..f3ef692 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -29,7 +29,7 @@ import envoy.data.Message.MessageStatus; */ public class MessageListRenderer implements ComponentListCellRenderer { - private static EnumMap statusIcons; + private static EnumMap statusIcons; private static ImageIcon forwardIcon; static { @@ -41,6 +41,10 @@ public class MessageListRenderer implements ComponentListCellRenderer { } } + private final long senderId; + + public MessageListRenderer(long senderId) { this.senderId = senderId; } + // TODO: Handle message attachments @Override @@ -86,7 +90,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { // var frc = new FontRenderContext(new AffineTransform(), true, true); // messageTextArea.setSize(Math.min(list.getWidth() - padding, (int) // font.getStringBounds(message.getText(), frc).getWidth()), 10); - messageTextArea.setSize(list.getWidth() - padding, 10); + messageTextArea.setSize(list.getWidth() - padding - 16, 10); var gbc_messageTextArea = new GridBagConstraints(); gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL; @@ -98,7 +102,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { var statusLabel = new JLabel(statusIcons.get(message.getStatus())); var gbc_statusLabel = new GridBagConstraints(); - gbc_statusLabel.fill = GridBagConstraints.BOTH; + // gbc_statusLabel.fill = GridBagConstraints.BOTH; gbc_statusLabel.gridx = 1; gbc_statusLabel.gridy = 1; panel.add(statusLabel, gbc_statusLabel); @@ -118,7 +122,9 @@ public class MessageListRenderer implements ComponentListCellRenderer { } // Define an etched border and some space to the messages below - panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 20, padding), BorderFactory.createEtchedBorder())); + var ours = senderId == message.getSenderId(); + panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, ours ? padding : 0, 10, ours ? 0 : padding), + BorderFactory.createEtchedBorder())); var size = new Dimension(list.getWidth(), panel.getPreferredSize().height); From a6865a5399c8de50fc55490a38209a3650cc2f1b Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 17 Mar 2020 11:08:58 +0100 Subject: [PATCH 12/32] Cleanup --- .../client/ui/renderer/MessageListRenderer.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index f3ef692..7b2b427 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -35,7 +35,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { static { try { statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16); - // forwardIcon = IconUtil.load("icons/forward.png", 16); + forwardIcon = IconUtil.load("icons/forward.png", 16); } catch (IOException e) { e.printStackTrace(); } @@ -43,6 +43,14 @@ public class MessageListRenderer implements ComponentListCellRenderer { private final long senderId; + /** + * Initializes a message list renderer. Messages with the given sender ID will + * be aligned on the right side, while all other messages will be aligned on + * the left side + * + * @param senderId the sender ID of the messages to align on the right side + * @since Envoy v0.1-beta + */ public MessageListRenderer(long senderId) { this.senderId = senderId; } // TODO: Handle message attachments @@ -87,9 +95,6 @@ public class MessageListRenderer implements ComponentListCellRenderer { messageTextArea.setEditable(false); var font = new Font("Arial", Font.PLAIN, 14); messageTextArea.setFont(font); - // var frc = new FontRenderContext(new AffineTransform(), true, true); - // messageTextArea.setSize(Math.min(list.getWidth() - padding, (int) - // font.getStringBounds(message.getText(), frc).getWidth()), 10); messageTextArea.setSize(list.getWidth() - padding - 16, 10); var gbc_messageTextArea = new GridBagConstraints(); @@ -102,15 +107,13 @@ public class MessageListRenderer implements ComponentListCellRenderer { var statusLabel = new JLabel(statusIcons.get(message.getStatus())); var gbc_statusLabel = new GridBagConstraints(); - // gbc_statusLabel.fill = GridBagConstraints.BOTH; gbc_statusLabel.gridx = 1; gbc_statusLabel.gridy = 1; panel.add(statusLabel, gbc_statusLabel); // Forwarding if (message.isForwarded()) { - // TODO: icon - var forwardLabel = new JLabel("Forwarded", null, SwingConstants.CENTER); + var forwardLabel = new JLabel("Forwarded", forwardIcon, SwingConstants.CENTER); forwardLabel.setBackground(panel.getBackground()); forwardLabel.setForeground(Color.lightGray); From bb1cb6658e97fc7a11941ad90bd7bd5b0afc7154 Mon Sep 17 00:00:00 2001 From: DieGurke <55625494+DieGurke@users.noreply.github.com> Date: Tue, 17 Mar 2020 11:20:45 +0100 Subject: [PATCH 13/32] Minimum size of application and added forward and settings icons --- src/main/java/envoy/client/ui/ChatWindow.java | 1 + .../client/ui/renderer/MessageListRenderer.java | 4 ++-- src/main/resources/icons/forward.png | Bin 0 -> 26014 bytes src/main/resources/icons/settings.png | Bin 0 -> 29000 bytes 4 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/icons/forward.png create mode 100644 src/main/resources/icons/settings.png diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 5be89f1..eda61e4 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -103,6 +103,7 @@ public class ChatWindow extends JFrame { public ChatWindow() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 600, 800); + setMinimumSize(new Dimension(400, 300)); setTitle("Envoy"); setLocationRelativeTo(null); setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png"))); diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index 7b2b427..10bece5 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -35,7 +35,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { static { try { statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16); - forwardIcon = IconUtil.load("icons/forward.png", 16); + forwardIcon = IconUtil.load("/icons/forward.png", 16); } catch (IOException e) { e.printStackTrace(); } @@ -91,7 +91,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { messageTextArea.setWrapStyleWord(true); messageTextArea.setForeground(theme.getMessageTextColor()); messageTextArea.setAlignmentX(0.5f); - messageTextArea.setBackground(theme.getCellColor()); + messageTextArea.setBackground(Color.RED); messageTextArea.setEditable(false); var font = new Font("Arial", Font.PLAIN, 14); messageTextArea.setFont(font); diff --git a/src/main/resources/icons/forward.png b/src/main/resources/icons/forward.png new file mode 100644 index 0000000000000000000000000000000000000000..9c758540eca22d5761372b724bc589c75d109221 GIT binary patch literal 26014 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/icons/settings.png b/src/main/resources/icons/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..f956615fa7133ced0a93b645706a9a9812a6a574 GIT binary patch literal 29000 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 b9!Tr_dUdaI))_(I)rbt9u6{1-oD!M Date: Tue, 17 Mar 2020 12:11:59 +0100 Subject: [PATCH 14/32] Changed bgc of testArea and played on pref size of list and scrollPane --- src/main/java/envoy/client/ui/ChatWindow.java | 5 +++-- .../java/envoy/client/ui/renderer/MessageListRenderer.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index eda61e4..c7f74f4 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -133,11 +133,12 @@ public class ChatWindow extends JFrame { // updates list elements when list is resized @Override public void componentResized(ComponentEvent e) { - messageList.synchronizeModel(); var prefSize = e.getComponent().getPreferredSize(); + e.getComponent().setPreferredSize(prefSize); + messageList.synchronizeModel(); messageList.setMinimumSize(new Dimension(prefSize.width, 0)); messageList.setMaximumSize(new Dimension(prefSize.width, Integer.MAX_VALUE)); - messageList.setPreferredSize(new Dimension(prefSize.width, messageList.getPreferredSize().height)); + messageList.setPreferredSize(new Dimension(prefSize.width, (int) messageList.getPreferredSize().getHeight())); } }); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index 10bece5..e6622b4 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -91,7 +91,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { messageTextArea.setWrapStyleWord(true); messageTextArea.setForeground(theme.getMessageTextColor()); messageTextArea.setAlignmentX(0.5f); - messageTextArea.setBackground(Color.RED); + messageTextArea.setBackground(theme.getCellColor()); messageTextArea.setEditable(false); var font = new Font("Arial", Font.PLAIN, 14); messageTextArea.setFont(font); From 1e11948e100442954b8aa4a5dbff94ad5b057d38 Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 17 Mar 2020 12:35:31 +0100 Subject: [PATCH 15/32] Fixed message list dimension --- src/main/java/envoy/client/ui/ChatWindow.java | 8 ++------ .../envoy/client/ui/renderer/MessageListRenderer.java | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index c7f74f4..182f824 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -130,15 +130,11 @@ public class ChatWindow extends JFrame { scrollPane.setViewportView(messageList); scrollPane.addComponentListener(new ComponentAdapter() { - // updates list elements when list is resized + // Update list elements when scroll pane (and thus list) is resized @Override public void componentResized(ComponentEvent e) { - var prefSize = e.getComponent().getPreferredSize(); - e.getComponent().setPreferredSize(prefSize); + messageList.setMaximumSize(new Dimension(scrollPane.getWidth(), Integer.MAX_VALUE)); messageList.synchronizeModel(); - messageList.setMinimumSize(new Dimension(prefSize.width, 0)); - messageList.setMaximumSize(new Dimension(prefSize.width, Integer.MAX_VALUE)); - messageList.setPreferredSize(new Dimension(prefSize.width, (int) messageList.getPreferredSize().getHeight())); } }); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index e6622b4..27a5a0d 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -95,7 +95,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { messageTextArea.setEditable(false); var font = new Font("Arial", Font.PLAIN, 14); messageTextArea.setFont(font); - messageTextArea.setSize(list.getWidth() - padding - 16, 10); + messageTextArea.setSize(list.getMaximumSize().width - padding - 16, 10); var gbc_messageTextArea = new GridBagConstraints(); gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL; @@ -129,7 +129,7 @@ public class MessageListRenderer implements ComponentListCellRenderer { panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, ours ? padding : 0, 10, ours ? 0 : padding), BorderFactory.createEtchedBorder())); - var size = new Dimension(list.getWidth(), panel.getPreferredSize().height); + var size = new Dimension(list.getMaximumSize().width - 50, panel.getPreferredSize().height); panel.setPreferredSize(size); panel.setMinimumSize(size); From 8bc8507feb9e5db46b3c187a144e8d3ee73cee48 Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 17 Mar 2020 12:45:50 +0100 Subject: [PATCH 16/32] Added Javadoc to IconUtil --- src/main/java/envoy/client/ui/IconUtil.java | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/java/envoy/client/ui/IconUtil.java b/src/main/java/envoy/client/ui/IconUtil.java index 47588ab..25d6513 100644 --- a/src/main/java/envoy/client/ui/IconUtil.java +++ b/src/main/java/envoy/client/ui/IconUtil.java @@ -9,6 +9,9 @@ import javax.imageio.ImageIO; import javax.swing.ImageIcon; /** + * Provides static utility methods for loading icons from the resource + * folder.
+ *
* Project: envoy-client * File: IconUtil.java * Created: 16.03.2020 @@ -20,10 +23,34 @@ public class IconUtil { private IconUtil() {} + /** + * Loads an icon from resource folder and scales it to a given size. + * + * @param path the path to the icon inside the resource folder + * @param size the size to scale the icon to + * @return the scaled icon + * @throws IOException if the loading process failed + * @since Envoy v0.1-beta + */ public static ImageIcon load(String path, int size) throws IOException { return new ImageIcon(ImageIO.read(IconUtil.class.getResourceAsStream(path)).getScaledInstance(size, size, Image.SCALE_SMOOTH)); } + /** + * + * Loads icons specified by an enum. The images have to be named like the + * lowercase enum constants with {@code .png} extension and be located inside a + * folder with the lowercase name of the enum, which must be contained inside + * the {@code /icons} folder. + * + * @param the enum that specifies the icons to load + * @param enumClass the class of the enum + * @param size the size to scale the icons to + * @return a map containing the loaded icons with the corresponding enum + * constants as keys + * @throws IOException if the loading process failed + * @since Envoy v0.1-beta + */ public static > EnumMap loadByEnum(Class enumClass, int size) throws IOException { var icons = new EnumMap(enumClass); var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/"; From 24ce40c24a2a72c42418ba196985229c1bec7f52 Mon Sep 17 00:00:00 2001 From: delvh Date: Wed, 18 Mar 2020 08:03:35 +0100 Subject: [PATCH 17/32] Merged current working status of f/icon_util into f/forward__messages * added ContextMenu * added ContactsChooserDialog --- .../java/envoy/client/ui/ContextMenu.java | 184 ++++++++++++++++++ src/main/java/envoy/client/ui/Startup.java | 2 + .../client/ui/{ => container}/ChatWindow.java | 50 ++++- .../ui/container/ContactsChooserDialog.java | 125 ++++++++++++ .../ui/{ => container}/LoginDialog.java | 3 +- .../client/ui/container/package-info.java | 13 ++ .../envoy/client/ui/list/ComponentList.java | 11 +- 7 files changed, 375 insertions(+), 13 deletions(-) create mode 100644 src/main/java/envoy/client/ui/ContextMenu.java rename src/main/java/envoy/client/ui/{ => container}/ChatWindow.java (92%) create mode 100644 src/main/java/envoy/client/ui/container/ContactsChooserDialog.java rename src/main/java/envoy/client/ui/{ => container}/LoginDialog.java (99%) create mode 100644 src/main/java/envoy/client/ui/container/package-info.java diff --git a/src/main/java/envoy/client/ui/ContextMenu.java b/src/main/java/envoy/client/ui/ContextMenu.java new file mode 100644 index 0000000..7328b0d --- /dev/null +++ b/src/main/java/envoy/client/ui/ContextMenu.java @@ -0,0 +1,184 @@ +package envoy.client.ui; + +import java.awt.Component; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.*; + +/** + * This class defines a menu that will be automatically called if + * {@link MouseEvent#isPopupTrigger()} returns true for the parent component. + * The user has the possibility to directly add actions to be performed when + * clicking on the element with the selected String. Additionally, for each + * element an {@link Icon} can be added, but it must not be. + * If the key(text) of an element starts with one of the predefined values, a + * special component will be called: either a {@link JRadioButtonMenuItem}, a + * {@link JCheckBoxMenuItem} or a {@link JMenu} will be created.
+ *
+ * Project: envoy-client
+ * File: ContextMenu.java
+ * Created: 17 Mar 2020
+ * + * @author Leon Hofmeister + * @since Envoy v0.1-beta + */ +public class ContextMenu extends JPopupMenu { + + private static final long serialVersionUID = 2177146471226992104L; + + /** + * If a key starts with this String, a {@link JCheckBoxMenuItem} will be created + */ + public static final String checkboxMenuItem = "ChBoMI"; + /** + * If a key starts with this String, a {@link JRadioButtonMenuItem} will be + * created + */ + public static final String radioButtonMenuItem = "RaBuMI"; + /** + * If a key starts with this String, a {@link JMenu} will be created + */ + public static final String subMenuItem = "SubMI"; + + private Map items = new HashMap<>(); + private Map icons = new HashMap<>(); + + private ButtonGroup radioButtonGroup = new ButtonGroup(); + private boolean built = false; + private boolean visible = false; + + /** + * @since Envoy v0.1-beta + */ + public ContextMenu() {} + + /** + * @param label the string that a UI may use to display as a title + * for the pop-up menu + * @param parent the component which will call this + * {@link ContextMenu} + * @param itemsWithActions a map of all strings to be displayed with according + * actions + * @param itemIcons the icons to be displayed before a name, if wanted. + * Only keys in here will have an Icon displayed. More + * precisely, all keys here not included in the first + * map will be thrown out. + * @since Envoy v0.1-beta + */ + public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons) { + super(label); + setInvoker(parent); + this.items = (itemsWithActions != null) ? itemsWithActions : items; + this.icons = (itemIcons != null) ? itemIcons : icons; + } + + /** + * Prepares the PopupMenu to be displayed. Should only be used once all map + * values have been set. + * + * @return this instance of {@link ContextMenu} to allow chaining behind the + * constructor + * @since Envoy v0.1-beta + */ + public ContextMenu build() { + items.forEach((text, action) -> { + // case radio button wanted + if (text.startsWith(radioButtonMenuItem)) { + var item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); + item.addActionListener(action); + radioButtonGroup.add(item); + add(item); + // case check box wanted + } else if (text.startsWith(checkboxMenuItem)) { + var item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); + item.addActionListener(action); + add(item); + // case sub-menu wanted + } else if (text.startsWith(subMenuItem)) { + var item = new JMenu(text.substring(subMenuItem.length())); + item.addActionListener(action); + add(item); + } else { + // normal JMenuItem wanted + var item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null); + item.addActionListener(action); + add(item); + } + }); + getInvoker().addMouseListener(getShowingListener()); + built = true; + return this; + } + + /** + * @param label the string that a UI may use to display as a title for the + * pop-up menu. + * @since Envoy v0.1-beta + */ + public ContextMenu(String label) { super(label); } + + private MouseAdapter getShowingListener() { + return new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { action(e); } + + @Override + public void mousePressed(MouseEvent e) { action(e); } + + @Override + public void mouseReleased(MouseEvent e) { action(e); } + + private void action(MouseEvent e) { + if (!built) build(); + if (e.isPopupTrigger()) { + show(e.getComponent(), e.getX(), e.getY()); + // hides the menu if already visible + visible = !visible; + setVisible(visible); + + } + } + }; + } + + /** + * Removes all subcomponents of this menu. + * + * @since Envoy v0.1-beta + */ + public void clear() { + removeAll(); + items = new HashMap<>(); + icons = new HashMap<>(); + } + + /** + * @return the items + * @since Envoy v0.1-beta + */ + public Map getItems() { return items; } + + /** + * @param items the items with the displayed text and the according action to + * take once called + * @since Envoy v0.1-beta + */ + public void setItems(Map items) { this.items = items; } + + /** + * @return the icons + * @since Envoy v0.1-beta + */ + public Map getIcons() { return icons; } + + /** + * @param icons the icons to set + * @since Envoy v0.1-beta + */ + public void setIcons(Map icons) { this.icons = icons; } +} diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 8f44c2a..5c03881 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -15,6 +15,8 @@ import javax.swing.SwingUtilities; import envoy.client.data.*; import envoy.client.net.Client; import envoy.client.net.WriteProxy; +import envoy.client.ui.container.ChatWindow; +import envoy.client.ui.container.LoginDialog; import envoy.data.Config; import envoy.data.Message; import envoy.data.User.UserStatus; diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java similarity index 92% rename from src/main/java/envoy/client/ui/ChatWindow.java rename to src/main/java/envoy/client/ui/container/ChatWindow.java index 182f824..fc43f89 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -1,8 +1,12 @@ -package envoy.client.ui; +package envoy.client.ui.container; import java.awt.*; +import java.awt.datatransfer.StringSelection; import java.awt.event.*; import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -18,13 +22,13 @@ import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; +import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryScrollPane; import envoy.client.ui.primary.PrimaryTextArea; import envoy.client.ui.renderer.ContactsSearchRenderer; -import envoy.client.ui.renderer.MessageListRenderer; import envoy.client.ui.renderer.UserListRenderer; import envoy.client.ui.settings.SettingsScreen; import envoy.data.Message; @@ -103,7 +107,6 @@ public class ChatWindow extends JFrame { public ChatWindow() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 600, 800); - setMinimumSize(new Dimension(400, 300)); setTitle("Envoy"); setLocationRelativeTo(null); setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png"))); @@ -123,19 +126,39 @@ public class ChatWindow extends JFrame { @Override public void mouseClicked(MouseEvent e) { if (e.isPopupTrigger()) { + contextMenu = new JPopupMenu("message options"); + Map commands = new HashMap<>() { + private static final long serialVersionUID = -2755235774946990126L; + + { + put("forward selected message", + evt -> forwardMessageToMultipleUsers(messageList.getSelected().get(0), + ContactsChooserDialog + .showForwardingDialog("Forward selected message to", messageList.getSelected().get(0), client))); + put("copy", evt -> { + // TODO should be enhanced to allow also copying of message attachments, + // especially pictures + StringSelection copy = new StringSelection(messageList.getSelected().get(0).getText()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); + }); + // TODO insert implementation to edit and delete messages + put("delete", evt -> {}); + put("edit", evt -> {}); + put("quote", evt -> {}); + } + }; + commands.forEach((name, action) -> { var item = new JMenuItem(name); item.addActionListener(action); contextMenu.add(item); }); + contextMenu.show(e.getComponent(), e.getX(), e.getY()); } } }); scrollPane.setViewportView(messageList); scrollPane.addComponentListener(new ComponentAdapter() { - // Update list elements when scroll pane (and thus list) is resized + // updates list elements when list is resized @Override - public void componentResized(ComponentEvent e) { - messageList.setMaximumSize(new Dimension(scrollPane.getWidth(), Integer.MAX_VALUE)); - messageList.synchronizeModel(); - } + public void componentResized(ComponentEvent e) { messageList.synchronizeModel(); } }); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); @@ -538,6 +561,15 @@ public class ChatWindow extends JFrame { sendMessage(new MessageBuilder(msg, recipient.getId(), localDb.getIdGenerator()).build()); } + private void forwardMessageToMultipleUsers(Message message, Collection recipients) { + recipients.forEach(recipient -> forwardMessage(message, recipient)); + } + + @SuppressWarnings("unused") + private void forwardMultipleMessagesToMultipleUsers(Collection messages, Collection recipients) { + messages.forEach(message -> forwardMessageToMultipleUsers(message, recipients)); + } + /** * Sends a {@link Message} to the server. * @@ -612,8 +644,6 @@ public class ChatWindow extends JFrame { this.localDb = localDb; this.writeProxy = writeProxy; - messageList.setRenderer(new MessageListRenderer(client.getSender().getId())); - // Load users and chats new Thread(() -> { localDb.getUsers().values().forEach(user -> { diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java new file mode 100644 index 0000000..b82707b --- /dev/null +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -0,0 +1,125 @@ +package envoy.client.ui.container; + +import java.awt.BorderLayout; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; + +import envoy.client.data.Settings; +import envoy.client.net.Client; +import envoy.client.ui.Theme; +import envoy.client.ui.list.ComponentList; +import envoy.client.ui.list.ComponentListModel; +import envoy.client.ui.renderer.MessageListRenderer; +import envoy.client.ui.renderer.UserComponentListRenderer; +import envoy.data.Message; +import envoy.data.User; + +/** + * This class defines a dialog to choose contacts from.
+ *
+ * Project: envoy-client
+ * File: ContactsChooserDialog.java
+ * Created: 15 Mar 2020
+ * + * @author Leon Hofmeister + * @since Envoy v0.1-beta + */ +public class ContactsChooserDialog extends JDialog { + + private static final long serialVersionUID = -5774558118579032256L; + + private ComponentList contactList = new ComponentList<>(new UserComponentListRenderer()); + private JButton okButton = new JButton("Ok"); + private JButton cancelButton = new JButton("Cancel"); + + private final Theme theme = Settings.getInstance().getTheme(Settings.getInstance().getCurrentTheme()); + + private final JPanel contentPanel = new JPanel(); + + /** + * Shows a modal contacts-chooser dialog and blocks until the + * dialog is hidden. If the user presses the "OK" button, then + * this method hides/disposes the dialog and returns the selected element (has + * yet + * to be casted back to its original type due to the limitations of Generics in + * Java). + * If the user presses the "Cancel" button or closes the dialog without + * pressing "OK", then this method disposes the dialog and returns an empty + * ArrayList. + * + * @param title the title of the dialog + * @param message the {@link Message} to display on top of the Dialog + * @param client the client whose contacts should be displayed + * @return the selected Element (yet has to be casted to the wanted type due to + * the Generics limitations in Java) + * @since Envoy v0.1-beta + */ + public static List showForwardingDialog(String title, Message message, Client client) { + ContactsChooserDialog dialog = new ContactsChooserDialog(); + dialog.setTitle(title); + dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE); + dialog.addCancelButtonActionListener(e -> dialog.dispose()); + dialog.getContentPanel() + .add(new MessageListRenderer(client.getSender().getId()).getListCellComponent(null, message, false), BorderLayout.NORTH); + List results = new ArrayList<>(); + dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelected()); dialog.dispose(); }); + ComponentListModel contactListModel = dialog.getContactList().getModel(); + client.getContacts().getContacts().forEach(user -> contactListModel.add(user)); + dialog.setVisible(true); + dialog.repaint(); + dialog.revalidate(); + return results.size() > 0 ? results : null; + } + + /** + * @since Envoy v0.1-beta + */ + private ContactsChooserDialog() { + contactList.enableMultipleSelection(); + // setBounds(100, 100, 450, 300); + setModal(true); + getContentPane().setLayout(new BorderLayout()); + setBackground(theme.getBackgroundColor()); + setForeground(theme.getMessageTextColor()); + contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + getContentPane().add(contentPanel, BorderLayout.CENTER); + contentPanel.setLayout(new BorderLayout(0, 0)); + contentPanel.add(contactList, BorderLayout.CENTER); + { + JPanel buttonPane = new JPanel(); + getContentPane().add(buttonPane, BorderLayout.SOUTH); + { + JButton okButton = new JButton("OK"); + okButton.setMnemonic(KeyEvent.VK_ENTER); + okButton.setActionCommand("OK"); + buttonPane.setLayout(new BorderLayout(0, 0)); + buttonPane.add(okButton, BorderLayout.EAST); + getRootPane().setDefaultButton(okButton); + } + { + JButton cancelButton = new JButton("Cancel"); + cancelButton.setActionCommand("Cancel"); + buttonPane.add(cancelButton, BorderLayout.WEST); + } + } + } + + /** + * @return the underlying {@link ComponentList} + * @since Envoy v0.1-beta + */ + private ComponentList getContactList() { return contactList; } + + private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); } + + private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); } + + private JPanel getContentPanel() { return contentPanel; } +} diff --git a/src/main/java/envoy/client/ui/LoginDialog.java b/src/main/java/envoy/client/ui/container/LoginDialog.java similarity index 99% rename from src/main/java/envoy/client/ui/LoginDialog.java rename to src/main/java/envoy/client/ui/container/LoginDialog.java index 9cd645d..51d9817 100644 --- a/src/main/java/envoy/client/ui/LoginDialog.java +++ b/src/main/java/envoy/client/ui/container/LoginDialog.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.container; import java.awt.*; import java.awt.event.ItemEvent; @@ -16,6 +16,7 @@ import javax.swing.border.EmptyBorder; import envoy.client.data.*; import envoy.client.event.HandshakeSuccessfulEvent; import envoy.client.net.Client; +import envoy.client.ui.Theme; import envoy.client.ui.primary.PrimaryButton; import envoy.data.LoginCredentials; import envoy.data.Message; diff --git a/src/main/java/envoy/client/ui/container/package-info.java b/src/main/java/envoy/client/ui/container/package-info.java new file mode 100644 index 0000000..27645f4 --- /dev/null +++ b/src/main/java/envoy/client/ui/container/package-info.java @@ -0,0 +1,13 @@ +/** + * This package contains all graphical Containers, like Dialogs and Frames.
+ *
+ * Project: envoy-client
+ * File: package-info.java
+ * Created: 16 Mar 2020
+ * + * @author Leon Hofmeister + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-beta + */ +package envoy.client.ui.container; diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 3d90ea9..b6962e3 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -108,6 +108,10 @@ public class ComponentList extends JPanel { * @since Envoy v0.1-beta */ private void add(E elem, int index, boolean isSelected) { + if (isSelected && !multipleSelectionEnabled) { + clearSelections(); + currentSelections.add(index); + } final JComponent component = renderer.getListCellComponent(this, elem, isSelected); component.addMouseListener(getSelectionListener(index)); add(component, index); @@ -169,7 +173,7 @@ public class ComponentList extends JPanel { * @since Envoy v0.1-beta */ private void clearSelections() { - currentSelections.forEach(index2 -> updateSelection(index2, false)); + currentSelections.forEach(index -> updateSelection(index, false)); currentSelections.clear(); } @@ -224,7 +228,10 @@ public class ComponentList extends JPanel { * the component list * @since Envoy v0.1-beta */ - public void setMultipleSelectionEnabled(boolean multipleSelectionEnabled) { this.multipleSelectionEnabled = multipleSelectionEnabled; } + public void setMultipleSelectionEnabled(boolean multipleSelectionEnabled) { + this.multipleSelectionEnabled = multipleSelectionEnabled; + if (!multipleSelectionEnabled) clearSelections(); + } /** * Enables the selection of multiple elements. From 480961370cd4e42e645ed7927f288ed78877ca3a Mon Sep 17 00:00:00 2001 From: delvh Date: Wed, 18 Mar 2020 22:13:58 +0100 Subject: [PATCH 18/32] added missing merge content,mnemonics support(ContextMenu)and Nullchecks --- .../java/envoy/client/ui/ContextMenu.java | 73 ++++++++++++------- .../envoy/client/ui/container/ChatWindow.java | 63 ++++++++-------- .../envoy/client/ui/list/ComponentList.java | 18 ++++- .../client/ui/list/ComponentListModel.java | 2 +- 4 files changed, 91 insertions(+), 65 deletions(-) diff --git a/src/main/java/envoy/client/ui/ContextMenu.java b/src/main/java/envoy/client/ui/ContextMenu.java index 7328b0d..1ba5d14 100644 --- a/src/main/java/envoy/client/ui/ContextMenu.java +++ b/src/main/java/envoy/client/ui/ContextMenu.java @@ -44,17 +44,20 @@ public class ContextMenu extends JPopupMenu { */ public static final String subMenuItem = "SubMI"; - private Map items = new HashMap<>(); - private Map icons = new HashMap<>(); + private Map items = new HashMap<>(); + private Map icons = new HashMap<>(); + private Map mnemonics = new HashMap<>(); private ButtonGroup radioButtonGroup = new ButtonGroup(); private boolean built = false; private boolean visible = false; /** + * @param parent the component which will call this + * {@link ContextMenu} * @since Envoy v0.1-beta */ - public ContextMenu() {} + public ContextMenu(Component parent) { setInvoker(parent); } /** * @param label the string that a UI may use to display as a title @@ -67,13 +70,18 @@ public class ContextMenu extends JPopupMenu { * Only keys in here will have an Icon displayed. More * precisely, all keys here not included in the first * map will be thrown out. + * @param itemMnemonics the keyboard shortcuts that need to be pressed to + * automatically execute the {@link JMenuItem} with the + * given text * @since Envoy v0.1-beta */ - public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons) { + public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons, + Map itemMnemonics) { super(label); setInvoker(parent); - this.items = (itemsWithActions != null) ? itemsWithActions : items; - this.icons = (itemIcons != null) ? itemIcons : icons; + this.items = (itemsWithActions != null) ? itemsWithActions : items; + this.icons = (itemIcons != null) ? itemIcons : icons; + this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics; } /** @@ -87,27 +95,20 @@ public class ContextMenu extends JPopupMenu { public ContextMenu build() { items.forEach((text, action) -> { // case radio button wanted + AbstractButton item; if (text.startsWith(radioButtonMenuItem)) { - var item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); - item.addActionListener(action); + item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); radioButtonGroup.add(item); - add(item); // case check box wanted - } else if (text.startsWith(checkboxMenuItem)) { - var item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); - item.addActionListener(action); - add(item); - // case sub-menu wanted - } else if (text.startsWith(subMenuItem)) { - var item = new JMenu(text.substring(subMenuItem.length())); - item.addActionListener(action); - add(item); - } else { - // normal JMenuItem wanted - var item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null); - item.addActionListener(action); - add(item); - } + } else if (text.startsWith(checkboxMenuItem)) + item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); + // case sub-menu wanted + else if (text.startsWith(subMenuItem)) item = new JMenu(text.substring(subMenuItem.length())); + else // normal JMenuItem wanted + item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null); + item.addActionListener(action); + if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text)); + add(item); }); getInvoker().addMouseListener(getShowingListener()); built = true; @@ -136,10 +137,10 @@ public class ContextMenu extends JPopupMenu { private void action(MouseEvent e) { if (!built) build(); if (e.isPopupTrigger()) { - show(e.getComponent(), e.getX(), e.getY()); // hides the menu if already visible visible = !visible; - setVisible(visible); + if (visible) show(e.getComponent(), e.getX(), e.getY()); + else setVisible(false); } } @@ -153,8 +154,9 @@ public class ContextMenu extends JPopupMenu { */ public void clear() { removeAll(); - items = new HashMap<>(); - icons = new HashMap<>(); + items = new HashMap<>(); + icons = new HashMap<>(); + mnemonics = new HashMap<>(); } /** @@ -181,4 +183,19 @@ public class ContextMenu extends JPopupMenu { * @since Envoy v0.1-beta */ public void setIcons(Map icons) { this.icons = icons; } + + /** + * @return the mnemonics (the keyboard shortcuts that automatically execute the + * command for a {@link JMenuItem} with corresponding text) + * @since Envoy v0.1-beta + */ + public Map getMnemonics() { return mnemonics; } + + /** + * @param mnemonics the keyboard shortcuts that need to be pressed to + * automatically execute the {@link JMenuItem} with the given + * text + * @since Envoy v0.1-beta + */ + public void setMnemonics(Map mnemonics) { this.mnemonics = mnemonics; } } diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index fc43f89..a21d99a 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -22,6 +22,7 @@ import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; +import envoy.client.ui.ContextMenu; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListModel; @@ -29,6 +30,7 @@ import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryScrollPane; import envoy.client.ui.primary.PrimaryTextArea; import envoy.client.ui.renderer.ContactsSearchRenderer; +import envoy.client.ui.renderer.MessageListRenderer; import envoy.client.ui.renderer.UserListRenderer; import envoy.client.ui.settings.SettingsScreen; import envoy.data.Message; @@ -74,6 +76,7 @@ public class ChatWindow extends JFrame { private JTextPane textPane = new JTextPane(); private PrimaryButton postButton = new PrimaryButton("Post"); private PrimaryButton settingsButton = new PrimaryButton("Settings"); + @SuppressWarnings("unused") private JPopupMenu contextMenu; // Contacts Header @@ -107,6 +110,7 @@ public class ChatWindow extends JFrame { public ChatWindow() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 600, 800); + setMinimumSize(new Dimension(400, 300)); setTitle("Envoy"); setLocationRelativeTo(null); setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png"))); @@ -121,44 +125,37 @@ public class ChatWindow extends JFrame { contentPane.setLayout(gbl_contentPane); messageList.setBorder(new EmptyBorder(space, space, space, space)); - messageList.addMouseListener(new MouseAdapter() { + Map commands = new HashMap<>() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.isPopupTrigger()) { - contextMenu = new JPopupMenu("message options"); - Map commands = new HashMap<>() { + private static final long serialVersionUID = -2755235774946990126L; - private static final long serialVersionUID = -2755235774946990126L; - - { - put("forward selected message", - evt -> forwardMessageToMultipleUsers(messageList.getSelected().get(0), - ContactsChooserDialog - .showForwardingDialog("Forward selected message to", messageList.getSelected().get(0), client))); - put("copy", evt -> { - // TODO should be enhanced to allow also copying of message attachments, - // especially pictures - StringSelection copy = new StringSelection(messageList.getSelected().get(0).getText()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); - }); - // TODO insert implementation to edit and delete messages - put("delete", evt -> {}); - put("edit", evt -> {}); - put("quote", evt -> {}); - } - }; - commands.forEach((name, action) -> { var item = new JMenuItem(name); item.addActionListener(action); contextMenu.add(item); }); - contextMenu.show(e.getComponent(), e.getX(), e.getY()); - } + { + put("forward selected message", + evt -> forwardMessageToMultipleUsers(messageList.getSelected().get(0), + ContactsChooserDialog.showForwardingDialog("Forward selected message to", messageList.getSelected().get(0), client))); + put("copy", evt -> { + // TODO should be enhanced to allow also copying of message attachments, + // especially pictures + StringSelection copy = new StringSelection(messageList.getSelected().get(0).getText()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); + }); + // TODO insert implementation to edit and delete messages + put("delete", evt -> {}); + put("edit", evt -> {}); + put("quote", evt -> {}); } - }); + }; + contextMenu = new ContextMenu(null, messageList, commands, null, null).build(); + scrollPane.setViewportView(messageList); scrollPane.addComponentListener(new ComponentAdapter() { - // updates list elements when list is resized + // Update list elements when scroll pane (and thus list) is resized @Override - public void componentResized(ComponentEvent e) { messageList.synchronizeModel(); } + public void componentResized(ComponentEvent e) { + messageList.setMaximumSize(new Dimension(scrollPane.getWidth(), Integer.MAX_VALUE)); + messageList.synchronizeModel(); + } }); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); @@ -600,7 +597,7 @@ public class ChatWindow extends JFrame { private void readCurrentChat() { try { currentChat.read(writeProxy); - messageList.synchronizeModel(); + if (messageList.getRenderer() != null) messageList.synchronizeModel(); } catch (IOException e) { e.printStackTrace(); logger.log(Level.WARNING, "Couldn't notify server about message status change", e); @@ -644,6 +641,8 @@ public class ChatWindow extends JFrame { this.localDb = localDb; this.writeProxy = writeProxy; + messageList.setRenderer(new MessageListRenderer(client.getSender().getId())); + // Load users and chats new Thread(() -> { localDb.getUsers().values().forEach(user -> { diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index b6962e3..8b7ec43 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -31,6 +31,12 @@ public class ComponentList extends JPanel { private static final long serialVersionUID = 1759644503942876737L; + /** + * Sets the layout of this {@link ComponentList} to be a vertically oriented + * BoxLayout. + * + * @since Envoy v0.1-beta + */ public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); } /** @@ -74,7 +80,7 @@ public class ComponentList extends JPanel { // Synchronize with new model this.model = model; if (model != null) this.model.setComponentList(this); - synchronizeModel(); + if (renderer != null) synchronizeModel(); } /** @@ -84,9 +90,11 @@ public class ComponentList extends JPanel { * @since Envoy v0.3-alpha */ public void synchronizeModel() { - removeAll(); - if (model != null) model.forEach(this::add); - revalidate(); + if (model != null && renderer != null) { + removeAll(); + model.forEach(this::add); + revalidate(); + } } /** @@ -112,9 +120,11 @@ public class ComponentList extends JPanel { clearSelections(); currentSelections.add(index); } + if (renderer != null) { final JComponent component = renderer.getListCellComponent(this, elem, isSelected); component.addMouseListener(getSelectionListener(index)); add(component, index); + } } /** diff --git a/src/main/java/envoy/client/ui/list/ComponentListModel.java b/src/main/java/envoy/client/ui/list/ComponentListModel.java index 95775c1..aad379e 100644 --- a/src/main/java/envoy/client/ui/list/ComponentListModel.java +++ b/src/main/java/envoy/client/ui/list/ComponentListModel.java @@ -115,6 +115,6 @@ public final class ComponentListModel implements Iterable, Serializable { */ void setComponentList(ComponentList componentList) { this.componentList = componentList; - if (componentList != null) componentList.synchronizeModel(); + if (componentList != null && componentList.getRenderer() != null) componentList.synchronizeModel(); } } From 0258e04612adf542ea0adab48d9da18167103f5c Mon Sep 17 00:00:00 2001 From: kske Date: Thu, 19 Mar 2020 21:52:06 +0100 Subject: [PATCH 19/32] Unrelated cleanup in UserComponentListRenderer --- .../renderer/UserComponentListRenderer.java | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java index e86214b..f19f17e 100644 --- a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java @@ -9,6 +9,7 @@ import javax.swing.JLabel; import javax.swing.JPanel; import envoy.client.data.Settings; +import envoy.client.ui.Color; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListCellRenderer; @@ -28,46 +29,42 @@ public class UserComponentListRenderer implements ComponentListCellRenderer list, User value, boolean isSelected) { + public JComponent getListCellComponent(ComponentList list, User user, boolean isSelected) { + final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + final JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); - final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); // Panel background panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); panel.setOpaque(true); - panel.setPreferredSize(new Dimension(100, 35)); // TODO add profile picture support in BorderLayout.West - JLabel username = new JLabel(value.getName()); + JLabel username = new JLabel(user.getName()); username.setForeground(theme.getUserNameColor()); panel.add(username, BorderLayout.CENTER); - final UserStatus status = value.getStatus(); + final UserStatus status = user.getStatus(); JLabel statusLabel = new JLabel(status.toString()); - java.awt.Color foreground; + Color foreground; switch (status) { - case OFFLINE: - foreground = java.awt.Color.LIGHT_GRAY; - break; case AWAY: - foreground = java.awt.Color.YELLOW; + foreground = Color.yellow; break; case BUSY: - foreground = java.awt.Color.BLUE; + foreground = Color.blue; break; case ONLINE: - foreground = java.awt.Color.GREEN; + foreground = Color.green; break; default: - foreground = java.awt.Color.LIGHT_GRAY; + foreground = Color.lightGray; break; } statusLabel.setForeground(foreground); panel.add(statusLabel, BorderLayout.NORTH); return panel; } - } From 4c70702d027422c56097e16974146ca73497748d Mon Sep 17 00:00:00 2001 From: kske Date: Thu, 19 Mar 2020 22:16:31 +0100 Subject: [PATCH 20/32] Added SelectionMode enum to ComponentList, prepared integration These changes are not ready to build yet, as ChatWindow and ContactsChooserDialog have to be adjusted first. --- .../envoy/client/ui/container/ChatWindow.java | 6 +- .../ui/container/ContactsChooserDialog.java | 2 +- .../envoy/client/ui/list/ComponentList.java | 198 ++++++++---------- .../client/ui/list/ComponentListModel.java | 2 +- 4 files changed, 89 insertions(+), 119 deletions(-) diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index a21d99a..a41a465 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -131,12 +131,12 @@ public class ChatWindow extends JFrame { { put("forward selected message", - evt -> forwardMessageToMultipleUsers(messageList.getSelected().get(0), - ContactsChooserDialog.showForwardingDialog("Forward selected message to", messageList.getSelected().get(0), client))); + evt -> forwardMessageToMultipleUsers(messageList.getSelection().get(0), + ContactsChooserDialog.showForwardingDialog("Forward selected message to", messageList.getSelection().get(0), client))); put("copy", evt -> { // TODO should be enhanced to allow also copying of message attachments, // especially pictures - StringSelection copy = new StringSelection(messageList.getSelected().get(0).getText()); + StringSelection copy = new StringSelection(messageList.getSelection().get(0).getText()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); }); // TODO insert implementation to edit and delete messages diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index b82707b..b85f9a8 100644 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -69,7 +69,7 @@ public class ContactsChooserDialog extends JDialog { dialog.getContentPanel() .add(new MessageListRenderer(client.getSender().getId()).getListCellComponent(null, message, false), BorderLayout.NORTH); List results = new ArrayList<>(); - dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelected()); dialog.dispose(); }); + dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelection()); dialog.dispose(); }); ComponentListModel contactListModel = dialog.getContactList().getModel(); client.getContacts().getContacts().forEach(user -> contactListModel.add(user)); dialog.setVisible(true); diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 8b7ec43..2cac513 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -3,8 +3,8 @@ package envoy.client.ui.list; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import javax.swing.*; @@ -25,15 +25,17 @@ public class ComponentList extends JPanel { private ComponentListModel model; private ComponentListCellRenderer renderer; - private boolean multipleSelectionEnabled = false; - - private List currentSelections = new ArrayList<>(); + private SelectionMode selectionMode = SelectionMode.NONE; + private Set selection = new HashSet<>(); private static final long serialVersionUID = 1759644503942876737L; + public static enum SelectionMode { + NONE, SINGLE, MULTIPLE + } + /** - * Sets the layout of this {@link ComponentList} to be a vertically oriented - * BoxLayout. + * Initializes a default {@link ComponentList} without a model or a renderer. * * @since Envoy v0.1-beta */ @@ -79,8 +81,8 @@ public class ComponentList extends JPanel { // Synchronize with new model this.model = model; - if (model != null) this.model.setComponentList(this); - if (renderer != null) synchronizeModel(); + if (model != null) model.setComponentList(this); + synchronizeModel(); } /** @@ -92,11 +94,53 @@ public class ComponentList extends JPanel { public void synchronizeModel() { if (model != null && renderer != null) { removeAll(); - model.forEach(this::add); + model.forEach(this::addElement); revalidate(); } } + /** + * Selects a list element by index. If the element is already selected, it is + * removed from the selection. + * + * @param index the index of the selected component + * @since Envoy v0.1-beta + */ + public void selectElement(int index) { + if (selection.contains(index)) { + + // Remove selection of element at index + updateElement(index, false); + selection.remove(index); + } else { + + // Remove old selection if single selection is enabled + if (selectionMode == SelectionMode.SINGLE) clearSelection(); + + if (selectionMode != SelectionMode.NONE) { + + // Assign new selection + selection.add(index); + + // Update element + updateElement(index, true); + } + } + + revalidate(); + repaint(); + } + + /** + * Removes the current selection. + * + * @since Envoy v0.1-alpha + */ + public void clearSelection() { + selection.forEach(i -> updateElement(i, false)); + selection.clear(); + } + /** * Adds an object to the list by rendering it with the current * {@link ComponentListCellRenderer}. @@ -104,89 +148,26 @@ public class ComponentList extends JPanel { * @param elem the element to add * @since Envoy v0.3-alpha */ - void add(E elem) { add(elem, getComponentCount(), false); } + void addElement(E elem) { addElement(elem, getComponentCount(), false); } /** * Adds an object to the list by rendering it with the current - * {@link ComponentListRenderer}. + * {@link ComponentListRenderer}. If the renderer is {@code null}, no action is + * performed. * * @param elem the element to add * @param index the index at which to add the element * @param isSelected the selection state of the element * @since Envoy v0.1-beta */ - private void add(E elem, int index, boolean isSelected) { - if (isSelected && !multipleSelectionEnabled) { - clearSelections(); - currentSelections.add(index); - } + private void addElement(E elem, int index, boolean isSelected) { if (renderer != null) { - final JComponent component = renderer.getListCellComponent(this, elem, isSelected); - component.addMouseListener(getSelectionListener(index)); - add(component, index); + final JComponent component = renderer.getListCellComponent(this, elem, isSelected); + component.addMouseListener(getSelectionListener(index)); + add(component, index); } } - /** - * @param componentIndex the index of the list component to which the mouse - * listener will be added - * @return a mouse listener calling the - * {@link ComponentList#componentSelected(int)} method with the - * component's index when a left click is performed by the user - * @since Envoy v0.1-beta - */ - private MouseListener getSelectionListener(int componentIndex) { - return new MouseAdapter() { - - @Override - public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) componentSelected(componentIndex); } - }; - } - - /** - * Gets called when a list component has been clicked on by the user. Any - * previous selections are then removed and the selected component gets - * redrawn.
- *
- * If the currently selected component gets selected again, the selection is - * removed. - * - * @param index the index of the selected component - * @since Envoy v0.1-beta - */ - private void componentSelected(int index) { - // removing selection of element at index - if (currentSelections.contains(index)) { - - // Clear selection - updateSelection(index, false); - currentSelections.remove(Integer.valueOf(index)); - } else { - - // Remove old selection if multipleSelection is disabled - if (!multipleSelectionEnabled && currentSelections.size() > 0) clearSelections(); - - // Assign new selection - currentSelections.add(index); - - // Update current selection - updateSelection(index, true); - } - - revalidate(); - repaint(); - } - - /** - * Clears all currently active selections. - * - * @since Envoy v0.1-beta - */ - private void clearSelections() { - currentSelections.forEach(index -> updateSelection(index, false)); - currentSelections.clear(); - } - /** * Replaces a list element with a newly rendered instance of its contents. * @@ -194,21 +175,33 @@ public class ComponentList extends JPanel { * @param isSelected the selection state passed to the {@link ListCellRenderer} * @since Envoy v0.1-beta */ - private void updateSelection(int index, boolean isSelected) { + private void updateElement(int index, boolean isSelected) { remove(index); - add(model.get(index), index, isSelected); + addElement(model.get(index), index, isSelected); } /** - * @return the selected elements or null if none is selected + * @param componentIndex the index of the list component to which the mouse + * listener will be added + * @return a mouse listener calling the + * {@link ComponentList#selectElement(int)} method with the + * component's index when a left click is performed by the user * @since Envoy v0.1-beta */ - public List getSelected() { - List selected = new ArrayList<>(); - currentSelections.forEach(index -> selected.add(model.get(index))); - return selected; + private MouseListener getSelectionListener(int componentIndex) { + return new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) selectElement(componentIndex); } + }; } + /** + * @return a set of all selected indices + * @since Envoy v0.1-beta + */ + public Set getSelection() { return selection; } + /** * @return the model * @since Envoy v0.1-beta @@ -228,37 +221,14 @@ public class ComponentList extends JPanel { public void setRenderer(ComponentListCellRenderer renderer) { this.renderer = renderer; } /** - * @return the multipleSelectionEnabled + * @return the selectionMode * @since Envoy v0.1-beta */ - public boolean isMultipleSelectionEnabled() { return multipleSelectionEnabled; } + public SelectionMode getSelectionMode() { return selectionMode; } /** - * @param multipleSelectionEnabled if true, multiple elements can be selected in - * the component list + * @param selectionMode the selectionMode to set * @since Envoy v0.1-beta */ - public void setMultipleSelectionEnabled(boolean multipleSelectionEnabled) { - this.multipleSelectionEnabled = multipleSelectionEnabled; - if (!multipleSelectionEnabled) clearSelections(); - } - - /** - * Enables the selection of multiple elements. - * - * @see ComponentList#disableMultipleSelection - * @since Envoy v0.1-beta - */ - public void enableMultipleSelection() { this.multipleSelectionEnabled = true; } - - /** - * Allows only one element to be selected. True by default. - * - * @see ComponentList#enableMultipleSelection - * @since Envoy v0.1-beta - */ - public void disableMultipleSelection() { - this.multipleSelectionEnabled = false; - clearSelections(); - } + public void setSelectionMode(SelectionMode selectionMode) { this.selectionMode = selectionMode; } } diff --git a/src/main/java/envoy/client/ui/list/ComponentListModel.java b/src/main/java/envoy/client/ui/list/ComponentListModel.java index aad379e..05ef7d4 100644 --- a/src/main/java/envoy/client/ui/list/ComponentListModel.java +++ b/src/main/java/envoy/client/ui/list/ComponentListModel.java @@ -34,7 +34,7 @@ public final class ComponentListModel implements Iterable, Serializable { */ public boolean add(E e) { if (componentList != null) { - componentList.add(e); + componentList.addElement(e); componentList.revalidate(); } return elements.add(e); From 10f498ca0339ddd7d61a7173f34eba4a62c1d362 Mon Sep 17 00:00:00 2001 From: kske Date: Fri, 20 Mar 2020 18:32:57 +0100 Subject: [PATCH 21/32] Adjusted ChatWindow and ContactsChooserDialog to new interface --- .../envoy/client/ui/container/ChatWindow.java | 10 ++-- .../ui/container/ContactsChooserDialog.java | 5 +- .../envoy/client/ui/list/ComponentList.java | 48 +++++++++++++++++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index a41a465..7d883c2 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -25,6 +25,7 @@ import envoy.client.net.WriteProxy; import envoy.client.ui.ContextMenu; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; +import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryScrollPane; @@ -125,18 +126,21 @@ public class ChatWindow extends JFrame { contentPane.setLayout(gbl_contentPane); messageList.setBorder(new EmptyBorder(space, space, space, space)); + messageList.setSelectionMode(SelectionMode.SINGLE); Map commands = new HashMap<>() { private static final long serialVersionUID = -2755235774946990126L; { put("forward selected message", - evt -> forwardMessageToMultipleUsers(messageList.getSelection().get(0), - ContactsChooserDialog.showForwardingDialog("Forward selected message to", messageList.getSelection().get(0), client))); + evt -> forwardMessageToMultipleUsers(messageList + .getSingleSelectedElement(), + ContactsChooserDialog + .showForwardingDialog("Forward selected message to", messageList.getSingleSelectedElement(), client))); put("copy", evt -> { // TODO should be enhanced to allow also copying of message attachments, // especially pictures - StringSelection copy = new StringSelection(messageList.getSelection().get(0).getText()); + StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); }); // TODO insert implementation to edit and delete messages diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index b85f9a8..467d43f 100644 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -15,6 +15,7 @@ import envoy.client.data.Settings; import envoy.client.net.Client; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; +import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.renderer.MessageListRenderer; import envoy.client.ui.renderer.UserComponentListRenderer; @@ -69,7 +70,7 @@ public class ContactsChooserDialog extends JDialog { dialog.getContentPanel() .add(new MessageListRenderer(client.getSender().getId()).getListCellComponent(null, message, false), BorderLayout.NORTH); List results = new ArrayList<>(); - dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelection()); dialog.dispose(); }); + dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); }); ComponentListModel contactListModel = dialog.getContactList().getModel(); client.getContacts().getContacts().forEach(user -> contactListModel.add(user)); dialog.setVisible(true); @@ -82,7 +83,7 @@ public class ContactsChooserDialog extends JDialog { * @since Envoy v0.1-beta */ private ContactsChooserDialog() { - contactList.enableMultipleSelection(); + contactList.setSelectionMode(SelectionMode.MULTIPLE); // setBounds(100, 100, 450, 300); setModal(true); getContentPane().setLayout(new BorderLayout()); diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 2cac513..199946d 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -30,8 +30,26 @@ public class ComponentList extends JPanel { private static final long serialVersionUID = 1759644503942876737L; + /** + * Defines the possible modes of selection that can be performed by the user + * + * @since Envoy v0.1-beta + */ public static enum SelectionMode { - NONE, SINGLE, MULTIPLE + /** + * Selection is completely ignored. + */ + NONE, + + /** + * Only a single element can be selected. + */ + SINGLE, + + /** + * Multiple elements can be selected regardless of their position. + */ + MULTIPLE } /** @@ -202,6 +220,30 @@ public class ComponentList extends JPanel { */ public Set getSelection() { return selection; } + /** + * @return a set of all selected elements + * @since Envoy v0.1-beta + */ + public Set getSelectedElements() { + var selectedElements = new HashSet(); + selection.forEach(i -> selectedElements.add(model.get(i))); + return selectedElements; + } + + /** + * @return the index of an arbitrary selected element + * @throws java.util.NoSuchElementException if no selection is present + * @since Envoy v0.1-beta + */ + public int getSingleSelection() { return selection.iterator().next(); } + + /** + * @return an arbitrary selected element + * @throws java.util.NoSuchElementException if no selection is present + * @since Envoy v0.1-beta + */ + public E getSingleSelectedElement() { return model.get(getSingleSelection()); } + /** * @return the model * @since Envoy v0.1-beta @@ -221,13 +263,13 @@ public class ComponentList extends JPanel { public void setRenderer(ComponentListCellRenderer renderer) { this.renderer = renderer; } /** - * @return the selectionMode + * @return the selection mode * @since Envoy v0.1-beta */ public SelectionMode getSelectionMode() { return selectionMode; } /** - * @param selectionMode the selectionMode to set + * @param selectionMode the selection mode to set * @since Envoy v0.1-beta */ public void setSelectionMode(SelectionMode selectionMode) { this.selectionMode = selectionMode; } From a5e43db8de431aba1d581a522433637e4117d4b1 Mon Sep 17 00:00:00 2001 From: kske Date: Sat, 21 Mar 2020 13:20:45 +0100 Subject: [PATCH 22/32] Extracted MessageComponent from MessageListRenderer --- .../java/envoy/client/ui/ContextMenu.java | 11 +- .../envoy/client/ui/MessageComponent.java | 120 ++++++++++++++++++ .../envoy/client/ui/container/ChatWindow.java | 11 +- .../ui/container/ContactsChooserDialog.java | 2 +- .../envoy/client/ui/list/ComponentList.java | 23 +++- .../ui/renderer/MessageListRenderer.java | 104 +-------------- 6 files changed, 155 insertions(+), 116 deletions(-) create mode 100644 src/main/java/envoy/client/ui/MessageComponent.java diff --git a/src/main/java/envoy/client/ui/ContextMenu.java b/src/main/java/envoy/client/ui/ContextMenu.java index 1ba5d14..ff19ac8 100644 --- a/src/main/java/envoy/client/ui/ContextMenu.java +++ b/src/main/java/envoy/client/ui/ContextMenu.java @@ -50,7 +50,6 @@ public class ContextMenu extends JPopupMenu { private ButtonGroup radioButtonGroup = new ButtonGroup(); private boolean built = false; - private boolean visible = false; /** * @param parent the component which will call this @@ -110,8 +109,10 @@ public class ContextMenu extends JPopupMenu { if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text)); add(item); }); - getInvoker().addMouseListener(getShowingListener()); - built = true; + if (getInvoker() != null) { + getInvoker().addMouseListener(getShowingListener()); + built = true; + } return this; } @@ -138,10 +139,8 @@ public class ContextMenu extends JPopupMenu { if (!built) build(); if (e.isPopupTrigger()) { // hides the menu if already visible - visible = !visible; - if (visible) show(e.getComponent(), e.getX(), e.getY()); + if (!isVisible()) show(e.getComponent(), e.getX(), e.getY()); else setVisible(false); - } } }; diff --git a/src/main/java/envoy/client/ui/MessageComponent.java b/src/main/java/envoy/client/ui/MessageComponent.java new file mode 100644 index 0000000..045e725 --- /dev/null +++ b/src/main/java/envoy/client/ui/MessageComponent.java @@ -0,0 +1,120 @@ +package envoy.client.ui; + +import java.awt.*; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.EnumMap; + +import javax.swing.*; + +import envoy.client.data.Settings; +import envoy.client.ui.list.ComponentList; +import envoy.data.Message; +import envoy.data.Message.MessageStatus; + +/** + * Project: envoy-client + * File: MessageComponent.java + * Created: 21.03.2020 + * + * @author Kai S. K. Engelbart + * @since Envoy v0.1-beta + */ +public class MessageComponent extends JPanel { + + private static final long serialVersionUID = 103920706139926996L; + + private static EnumMap statusIcons; + private static ImageIcon forwardIcon; + + static { + try { + statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16); + forwardIcon = IconUtil.load("/icons/forward.png", 16); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public MessageComponent(ComponentList list, Message message, boolean isSelected, long senderId) { + this(list.getMaximumSize().width, message, isSelected, senderId); + } + + public MessageComponent(int width, Message message, boolean isSelected, long senderId) { + final var theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + final int padding = (int) (width * 0.35); + + GridBagLayout gbl_panel = new GridBagLayout(); + gbl_panel.columnWidths = new int[] { 1, 1 }; + gbl_panel.rowHeights = new int[] { 1, 1 }; + gbl_panel.columnWeights = new double[] { 1, 1 }; + gbl_panel.rowWeights = new double[] { 1, 1 }; + + setLayout(gbl_panel); + setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); + + // Date Label - The Label that displays the creation date of a message + var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate())); + dateLabel.setForeground(theme.getDateColor()); + dateLabel.setAlignmentX(1f); + dateLabel.setFont(new Font("Arial", Font.PLAIN, 12)); + dateLabel.setPreferredSize(dateLabel.getPreferredSize()); + + var gbc_dateLabel = new GridBagConstraints(); + gbc_dateLabel.fill = GridBagConstraints.BOTH; + gbc_dateLabel.gridx = 0; + gbc_dateLabel.gridy = 0; + add(dateLabel, gbc_dateLabel); + + // Message area - The JTextArea that displays the text content of a message. + var messageTextArea = new JTextArea(message.getText()); + messageTextArea.setLineWrap(true); + messageTextArea.setWrapStyleWord(true); + messageTextArea.setForeground(theme.getMessageTextColor()); + messageTextArea.setAlignmentX(0.5f); + messageTextArea.setBackground(theme.getCellColor()); + messageTextArea.setEditable(false); + var font = new Font("Arial", Font.PLAIN, 14); + messageTextArea.setFont(font); + messageTextArea.setSize(width - padding - 16, 10); + + var gbc_messageTextArea = new GridBagConstraints(); + gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL; + gbc_messageTextArea.gridx = 0; + gbc_messageTextArea.gridy = 1; + add(messageTextArea, gbc_messageTextArea); + + // Status Label - displays the status of the message + var statusLabel = new JLabel(statusIcons.get(message.getStatus())); + + var gbc_statusLabel = new GridBagConstraints(); + gbc_statusLabel.gridx = 1; + gbc_statusLabel.gridy = 1; + add(statusLabel, gbc_statusLabel); + + // Forwarding + if (message.isForwarded()) { + var forwardLabel = new JLabel("Forwarded", forwardIcon, SwingConstants.CENTER); + forwardLabel.setBackground(getBackground()); + forwardLabel.setForeground(Color.lightGray); + + var gbc_forwardLabel = new GridBagConstraints(); + gbc_forwardLabel.fill = GridBagConstraints.BOTH; + gbc_forwardLabel.gridx = 1; + gbc_forwardLabel.gridy = 0; + add(forwardLabel, gbc_forwardLabel); + } + + // Define an etched border and some space to the messages below + var ours = senderId == message.getSenderId(); + setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(0, ours ? padding : 10, 10, ours ? 0 : padding), + BorderFactory.createEtchedBorder())); + + var size = new Dimension(width - 50, getPreferredSize().height); + + setPreferredSize(size); + setMinimumSize(size); + setMaximumSize(size); + } +} diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index 7d883c2..808a4f7 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -125,16 +125,14 @@ public class ChatWindow extends JFrame { gbl_contentPane.rowWeights = new double[] { 0.03, 0.001, 1.0, 0.005 }; contentPane.setLayout(gbl_contentPane); - messageList.setBorder(new EmptyBorder(space, space, space, space)); - messageList.setSelectionMode(SelectionMode.SINGLE); + // ContextMenu Map commands = new HashMap<>() { private static final long serialVersionUID = -2755235774946990126L; { put("forward selected message", - evt -> forwardMessageToMultipleUsers(messageList - .getSingleSelectedElement(), + evt -> forwardMessageToMultipleUsers(messageList.getSingleSelectedElement(), ContactsChooserDialog .showForwardingDialog("Forward selected message to", messageList.getSingleSelectedElement(), client))); put("copy", evt -> { @@ -151,6 +149,11 @@ public class ChatWindow extends JFrame { }; contextMenu = new ContextMenu(null, messageList, commands, null, null).build(); + messageList.setBorder(new EmptyBorder(space, space, space, space)); + messageList.setSelectionMode(SelectionMode.SINGLE); + messageList.setSelectionListener((list, comp) -> + contextMenu.show(comp, 0, 0)); + scrollPane.setViewportView(messageList); scrollPane.addComponentListener(new ComponentAdapter() { diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index 467d43f..3add91a 100644 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -76,7 +76,7 @@ public class ContactsChooserDialog extends JDialog { dialog.setVisible(true); dialog.repaint(); dialog.revalidate(); - return results.size() > 0 ? results : null; + return results; } /** diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 199946d..a4b4953 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -5,6 +5,7 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.HashSet; import java.util.Set; +import java.util.function.BiConsumer; import javax.swing.*; @@ -23,10 +24,11 @@ import javax.swing.*; */ public class ComponentList extends JPanel { - private ComponentListModel model; - private ComponentListCellRenderer renderer; - private SelectionMode selectionMode = SelectionMode.NONE; - private Set selection = new HashSet<>(); + private ComponentListModel model; + private ComponentListCellRenderer renderer; + private SelectionMode selectionMode = SelectionMode.NONE; + private Set selection = new HashSet<>(); + private BiConsumer, JComponent> selectionListener; private static final long serialVersionUID = 1759644503942876737L; @@ -142,6 +144,9 @@ public class ComponentList extends JPanel { // Update element updateElement(index, true); + + // Trigger selection listener + if (selectionListener != null) selectionListener.accept(this, (JComponent) getComponents()[index]); } } @@ -273,4 +278,14 @@ public class ComponentList extends JPanel { * @since Envoy v0.1-beta */ public void setSelectionMode(SelectionMode selectionMode) { this.selectionMode = selectionMode; } + + /** + * @return the selectionListener + */ + public BiConsumer, JComponent> getSelectionListener() { return selectionListener; } + + /** + * @param selectionListener the selectionListener to set + */ + public void setSelectionListener(BiConsumer, JComponent> selectionListener) { this.selectionListener = selectionListener; } } diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java index 27a5a0d..a3c20bd 100644 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java @@ -1,19 +1,11 @@ package envoy.client.ui.renderer; -import java.awt.*; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.EnumMap; +import javax.swing.JPanel; -import javax.swing.*; - -import envoy.client.data.Settings; -import envoy.client.ui.Color; -import envoy.client.ui.IconUtil; +import envoy.client.ui.MessageComponent; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListCellRenderer; import envoy.data.Message; -import envoy.data.Message.MessageStatus; /** * Defines how a message is displayed.
@@ -29,18 +21,6 @@ import envoy.data.Message.MessageStatus; */ public class MessageListRenderer implements ComponentListCellRenderer { - private static EnumMap statusIcons; - private static ImageIcon forwardIcon; - - static { - try { - statusIcons = IconUtil.loadByEnum(MessageStatus.class, 16); - forwardIcon = IconUtil.load("/icons/forward.png", 16); - } catch (IOException e) { - e.printStackTrace(); - } - } - private final long senderId; /** @@ -57,84 +37,6 @@ public class MessageListRenderer implements ComponentListCellRenderer { @Override public JPanel getListCellComponent(ComponentList list, Message message, boolean isSelected) { - final var theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); - - // Panel - final var panel = new JPanel(); - final int padding = (int) (list.getWidth() * 0.35); - - GridBagLayout gbl_panel = new GridBagLayout(); - gbl_panel.columnWidths = new int[] { 1, 1 }; - gbl_panel.rowHeights = new int[] { 1, 1 }; - gbl_panel.columnWeights = new double[] { 1, 1 }; - gbl_panel.rowWeights = new double[] { 1, 1 }; - - panel.setLayout(gbl_panel); - panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); - - // Date Label - The Label that displays the creation date of a message - var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate())); - dateLabel.setForeground(theme.getDateColor()); - dateLabel.setAlignmentX(1f); - dateLabel.setFont(new Font("Arial", Font.PLAIN, 12)); - dateLabel.setPreferredSize(dateLabel.getPreferredSize()); - - var gbc_dateLabel = new GridBagConstraints(); - gbc_dateLabel.fill = GridBagConstraints.BOTH; - gbc_dateLabel.gridx = 0; - gbc_dateLabel.gridy = 0; - panel.add(dateLabel, gbc_dateLabel); - - // Message area - The JTextArea that displays the text content of a message. - var messageTextArea = new JTextArea(message.getText()); - messageTextArea.setLineWrap(true); - messageTextArea.setWrapStyleWord(true); - messageTextArea.setForeground(theme.getMessageTextColor()); - messageTextArea.setAlignmentX(0.5f); - messageTextArea.setBackground(theme.getCellColor()); - messageTextArea.setEditable(false); - var font = new Font("Arial", Font.PLAIN, 14); - messageTextArea.setFont(font); - messageTextArea.setSize(list.getMaximumSize().width - padding - 16, 10); - - var gbc_messageTextArea = new GridBagConstraints(); - gbc_messageTextArea.fill = GridBagConstraints.HORIZONTAL; - gbc_messageTextArea.gridx = 0; - gbc_messageTextArea.gridy = 1; - panel.add(messageTextArea, gbc_messageTextArea); - - // Status Label - displays the status of the message - var statusLabel = new JLabel(statusIcons.get(message.getStatus())); - - var gbc_statusLabel = new GridBagConstraints(); - gbc_statusLabel.gridx = 1; - gbc_statusLabel.gridy = 1; - panel.add(statusLabel, gbc_statusLabel); - - // Forwarding - if (message.isForwarded()) { - var forwardLabel = new JLabel("Forwarded", forwardIcon, SwingConstants.CENTER); - forwardLabel.setBackground(panel.getBackground()); - forwardLabel.setForeground(Color.lightGray); - - var gbc_forwardLabel = new GridBagConstraints(); - gbc_forwardLabel.fill = GridBagConstraints.BOTH; - gbc_forwardLabel.gridx = 1; - gbc_forwardLabel.gridy = 0; - panel.add(forwardLabel, gbc_forwardLabel); - } - - // Define an etched border and some space to the messages below - var ours = senderId == message.getSenderId(); - panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, ours ? padding : 0, 10, ours ? 0 : padding), - BorderFactory.createEtchedBorder())); - - var size = new Dimension(list.getMaximumSize().width - 50, panel.getPreferredSize().height); - - panel.setPreferredSize(size); - panel.setMinimumSize(size); - panel.setMaximumSize(size); - - return panel; + return new MessageComponent(list, message, isSelected, senderId); } } From 0a812738604b5cce4bffa7b0430ec559433be797 Mon Sep 17 00:00:00 2001 From: kske Date: Sat, 21 Mar 2020 16:10:59 +0100 Subject: [PATCH 23/32] Revised the rendering and selection mechanism in ComponentList --- src/main/java/envoy/client/data/Chat.java | 6 +- src/main/java/envoy/client/data/Settings.java | 10 +- ...derer.java => ContactSearchComponent.java} | 51 +++--- .../envoy/client/ui/MessageComponent.java | 10 +- .../java/envoy/client/ui/UserComponent.java | 62 +++++++ .../envoy/client/ui/container/ChatWindow.java | 37 ++-- .../ui/container/ContactsChooserDialog.java | 22 +-- .../client/ui/container/LoginDialog.java | 2 +- .../envoy/client/ui/list/ComponentList.java | 165 +++++++----------- .../{ComponentListModel.java => Model.java} | 6 +- ...entListCellRenderer.java => Renderer.java} | 7 +- .../client/ui/list/SelectionHandler.java | 27 +++ .../client/ui/primary/PrimaryScrollBar.java | 11 +- .../ui/primary/PrimaryToggleSwitch.java | 2 +- .../ui/renderer/MessageListRenderer.java | 42 ----- .../renderer/UserComponentListRenderer.java | 70 -------- .../client/ui/renderer/UserListRenderer.java | 2 +- .../ui/settings/GeneralSettingsPanel.java | 2 +- .../client/ui/settings/NewThemeScreen.java | 2 +- .../client/ui/settings/SettingsScreen.java | 2 +- .../ui/settings/ThemeCustomizationPanel.java | 11 +- 21 files changed, 242 insertions(+), 307 deletions(-) rename src/main/java/envoy/client/ui/{renderer/ContactsSearchRenderer.java => ContactSearchComponent.java} (51%) create mode 100644 src/main/java/envoy/client/ui/UserComponent.java rename src/main/java/envoy/client/ui/list/{ComponentListModel.java => Model.java} (93%) rename src/main/java/envoy/client/ui/list/{ComponentListCellRenderer.java => Renderer.java} (86%) create mode 100644 src/main/java/envoy/client/ui/list/SelectionHandler.java delete mode 100644 src/main/java/envoy/client/ui/renderer/MessageListRenderer.java delete mode 100644 src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java diff --git a/src/main/java/envoy/client/data/Chat.java b/src/main/java/envoy/client/data/Chat.java index 30b6bc6..dadeaaa 100644 --- a/src/main/java/envoy/client/data/Chat.java +++ b/src/main/java/envoy/client/data/Chat.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.io.Serializable; import envoy.client.net.WriteProxy; -import envoy.client.ui.list.ComponentListModel; +import envoy.client.ui.list.Model; import envoy.data.Message; import envoy.data.Message.MessageStatus; import envoy.data.User; @@ -28,7 +28,7 @@ public class Chat implements Serializable { private static final long serialVersionUID = -7751248474547242056L; private final User recipient; - private final ComponentListModel model = new ComponentListModel<>(); + private final Model model = new Model<>(); /** * Provides the list of messages that the recipient receives.
@@ -80,7 +80,7 @@ public class Chat implements Serializable { * @return all messages in the current chat * @since Envoy v0.1-alpha */ - public ComponentListModel getModel() { return model; } + public Model getModel() { return model; } /** * @return the recipient of a message diff --git a/src/main/java/envoy/client/data/Settings.java b/src/main/java/envoy/client/data/Settings.java index 89dea67..fd0170c 100644 --- a/src/main/java/envoy/client/data/Settings.java +++ b/src/main/java/envoy/client/data/Settings.java @@ -112,13 +112,19 @@ public class Settings { * @param theme the {@link Theme} to add * @since Envoy v0.2-alpha */ - public void addNewThemeToMap(Theme theme) { settings.getThemes().put(theme.getThemeName(), theme); } + public void addNewThemeToMap(Theme theme) { getThemes().put(theme.getThemeName(), theme); } /** * @return the name of the currently active {@link Theme} * @since Envoy v0.2-alpha */ - public String getCurrentTheme() { return (String) items.get("currentTheme").get(); } + public String getCurrentThemeName() { return (String) items.get("currentTheme").get(); } + + /** + * @return the currently active {@link Theme} + * @since Envoy v0.1-beta + */ + public Theme getCurrentTheme() { return getTheme(getCurrentThemeName()); } /** * Sets the name of the current {@link Theme}. diff --git a/src/main/java/envoy/client/ui/renderer/ContactsSearchRenderer.java b/src/main/java/envoy/client/ui/ContactSearchComponent.java similarity index 51% rename from src/main/java/envoy/client/ui/renderer/ContactsSearchRenderer.java rename to src/main/java/envoy/client/ui/ContactSearchComponent.java index 60a0ef2..39ae50b 100644 --- a/src/main/java/envoy/client/ui/renderer/ContactsSearchRenderer.java +++ b/src/main/java/envoy/client/ui/ContactSearchComponent.java @@ -1,4 +1,4 @@ -package envoy.client.ui.renderer; +package envoy.client.ui; import java.awt.Component; import java.awt.Dimension; @@ -8,45 +8,36 @@ import javax.swing.*; import envoy.client.data.Settings; import envoy.client.event.SendEvent; -import envoy.client.ui.Color; import envoy.client.ui.list.ComponentList; -import envoy.client.ui.list.ComponentListCellRenderer; import envoy.client.ui.primary.PrimaryButton; import envoy.data.User; import envoy.event.ContactOperationEvent; import envoy.event.EventBus; /** - * Defines how a contact is displayed.
- *
- * Project: envoy-client
- * File: ContactsSearchRenderer.java
- * Created: 08.02.2020
+ * Project: envoy-client + * File: ContactSearchComponent.java + * Created: 21.03.2020 * - * @author Maximilian Käfer * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy v0.1-beta */ -public class ContactsSearchRenderer implements ComponentListCellRenderer { +public class ContactSearchComponent extends JComponent { - @Override - public JComponent getListCellComponent(ComponentList list, User user, boolean isSelected) { - final JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - if (isSelected) { - panel.setBackground(Color.DARK_GRAY); - panel.setForeground(Color.RED); - } else { - panel.setBackground(list.getBackground()); - panel.setForeground(list.getForeground()); - } + private static final long serialVersionUID = 3166795412575239455L; + + public ContactSearchComponent(ComponentList list, User user) { + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + + setBackground(list.getBackground()); + setForeground(list.getForeground()); JLabel display = new JLabel(user.getName()); - display.setForeground(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageTextColor()); + display.setForeground(Settings.getInstance().getCurrentTheme().getMessageTextColor()); display.setAlignmentX(Component.LEFT_ALIGNMENT); display.setAlignmentY(Component.CENTER_ALIGNMENT); display.setFont(new Font("Arial", Font.PLAIN, 16)); - panel.add(display); + add(display); PrimaryButton add = new PrimaryButton("+"); add.setFont(new Font("Arial", Font.PLAIN, 19)); @@ -63,17 +54,15 @@ public class ContactsSearchRenderer implements ComponentListCellRenderer { EventBus.getInstance().dispatch(new SendEvent(contactsOperationEvent)); }); - panel.add(add); + add(add); // Define some space to the messages below - panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder())); + setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder())); // Define a maximum height of 50px Dimension size = new Dimension(435, 50); - panel.setMaximumSize(size); - panel.setMinimumSize(size); - panel.setPreferredSize(size); - - return panel; + setMaximumSize(size); + setMinimumSize(size); + setPreferredSize(size); } } diff --git a/src/main/java/envoy/client/ui/MessageComponent.java b/src/main/java/envoy/client/ui/MessageComponent.java index 045e725..a4a83a0 100644 --- a/src/main/java/envoy/client/ui/MessageComponent.java +++ b/src/main/java/envoy/client/ui/MessageComponent.java @@ -36,12 +36,12 @@ public class MessageComponent extends JPanel { } } - public MessageComponent(ComponentList list, Message message, boolean isSelected, long senderId) { - this(list.getMaximumSize().width, message, isSelected, senderId); + public MessageComponent(ComponentList list, Message message, long senderId) { + this(list.getMaximumSize().width, message, senderId); } - public MessageComponent(int width, Message message, boolean isSelected, long senderId) { - final var theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + public MessageComponent(int width, Message message, long senderId) { + final var theme = Settings.getInstance().getCurrentTheme(); final int padding = (int) (width * 0.35); GridBagLayout gbl_panel = new GridBagLayout(); @@ -51,7 +51,7 @@ public class MessageComponent extends JPanel { gbl_panel.rowWeights = new double[] { 1, 1 }; setLayout(gbl_panel); - setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); + setBackground(theme.getCellColor()); // Date Label - The Label that displays the creation date of a message var dateLabel = new JLabel(new SimpleDateFormat("dd.MM.yyyy HH:mm").format(message.getCreationDate())); diff --git a/src/main/java/envoy/client/ui/UserComponent.java b/src/main/java/envoy/client/ui/UserComponent.java new file mode 100644 index 0000000..7fd4392 --- /dev/null +++ b/src/main/java/envoy/client/ui/UserComponent.java @@ -0,0 +1,62 @@ +package envoy.client.ui; + +import java.awt.BorderLayout; +import java.awt.Dimension; + +import javax.swing.JComponent; +import javax.swing.JLabel; + +import envoy.client.data.Settings; +import envoy.client.ui.list.ComponentList; +import envoy.data.User; +import envoy.data.User.UserStatus; + +/** + * Project: envoy-client + * File: UserComponent.java + * Created: 21.03.2020 + * + * @author Kai S. K. Engelbart + * @since Envoy v0.1-beta + */ +public class UserComponent extends JComponent { + + private static final long serialVersionUID = 8450602172939729585L; + + public UserComponent(ComponentList list, User user) { + final Theme theme = Settings.getInstance().getCurrentTheme(); + + setLayout(new BorderLayout()); + + // Panel background + setBackground(theme.getCellColor()); + setOpaque(true); + setPreferredSize(new Dimension(100, 35)); + + // TODO add profile picture support in BorderLayout.West + + JLabel username = new JLabel(user.getName()); + username.setForeground(theme.getUserNameColor()); + add(username, BorderLayout.CENTER); + + final UserStatus status = user.getStatus(); + JLabel statusLabel = new JLabel(status.toString()); + Color foreground; + switch (status) { + case AWAY: + foreground = Color.yellow; + break; + case BUSY: + foreground = Color.blue; + break; + case ONLINE: + foreground = Color.green; + break; + default: + foreground = Color.lightGray; + break; + } + statusLabel.setForeground(foreground); + add(statusLabel, BorderLayout.NORTH); + } +} diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index 808a4f7..41e9614 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -22,16 +22,13 @@ import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; -import envoy.client.ui.ContextMenu; -import envoy.client.ui.Theme; +import envoy.client.ui.*; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList.SelectionMode; -import envoy.client.ui.list.ComponentListModel; +import envoy.client.ui.list.Model; import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryScrollPane; import envoy.client.ui.primary.PrimaryTextArea; -import envoy.client.ui.renderer.ContactsSearchRenderer; -import envoy.client.ui.renderer.MessageListRenderer; import envoy.client.ui.renderer.UserListRenderer; import envoy.client.ui.settings.SettingsScreen; import envoy.data.Message; @@ -54,8 +51,7 @@ import envoy.util.EnvoyLog; public class ChatWindow extends JFrame { /** - * This int defines the maximum amount of chars allowed per message. Currently - * set at 200. + * This integer defines the maximum amount of chars allowed per message. * * @since Envoy 0.1-beta */ @@ -77,7 +73,6 @@ public class ChatWindow extends JFrame { private JTextPane textPane = new JTextPane(); private PrimaryButton postButton = new PrimaryButton("Post"); private PrimaryButton settingsButton = new PrimaryButton("Settings"); - @SuppressWarnings("unused") private JPopupMenu contextMenu; // Contacts Header @@ -86,13 +81,12 @@ public class ChatWindow extends JFrame { private PrimaryButton addContact = new PrimaryButton("+"); // Search Contacts - private final JPanel searchPane = new JPanel(); - private final PrimaryButton cancelButton = new PrimaryButton("x"); - private final PrimaryTextArea searchField = new PrimaryTextArea(space); - private final PrimaryScrollPane scrollForPossibleContacts = new PrimaryScrollPane(); - private final ContactsSearchRenderer contactRenderer = new ContactsSearchRenderer(); - private final ComponentListModel contactsModel = new ComponentListModel<>(); - private final ComponentList contactList = new ComponentList<>(contactRenderer); + private final JPanel searchPane = new JPanel(); + private final PrimaryButton cancelButton = new PrimaryButton("x"); + private final PrimaryTextArea searchField = new PrimaryTextArea(space); + private final PrimaryScrollPane scrollForPossibleContacts = new PrimaryScrollPane(); + private final Model contactsModel = new Model<>(); + private final ComponentList contactList = new ComponentList().setRenderer(ContactSearchComponent::new); private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class); @@ -151,8 +145,11 @@ public class ChatWindow extends JFrame { messageList.setBorder(new EmptyBorder(space, space, space, space)); messageList.setSelectionMode(SelectionMode.SINGLE); - messageList.setSelectionListener((list, comp) -> - contextMenu.show(comp, 0, 0)); + messageList.setSelectionHandler((message, comp, isSelected) -> { + final var theme = Settings.getInstance().getCurrentTheme(); + comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); + contextMenu.show(comp, 0, 0); + }); scrollPane.setViewportView(messageList); scrollPane.addComponentListener(new ComponentAdapter() { @@ -399,11 +396,11 @@ public class ChatWindow extends JFrame { gbc_addContact.gridy = 0; gbc_addContact.insets = insets; - addContact.addActionListener((evt) -> { drawContactSearch(gbc_searchPane); }); + addContact.addActionListener(evt -> drawContactSearch(gbc_searchPane)); contactsHeader.add(addContact, gbc_addContact); - applyTheme(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); + applyTheme(Settings.getInstance().getCurrentTheme()); contentPane.add(contactsHeader, gbc_contactsHeader); contentPane.revalidate(); @@ -648,7 +645,7 @@ public class ChatWindow extends JFrame { this.localDb = localDb; this.writeProxy = writeProxy; - messageList.setRenderer(new MessageListRenderer(client.getSender().getId())); + messageList.setRenderer((list, message) -> new MessageComponent(list, message, client.getSender().getId())); // Load users and chats new Thread(() -> { diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index 3add91a..8171b82 100644 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -14,11 +14,10 @@ import javax.swing.border.EmptyBorder; import envoy.client.data.Settings; import envoy.client.net.Client; import envoy.client.ui.Theme; +import envoy.client.ui.UserComponent; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList.SelectionMode; -import envoy.client.ui.list.ComponentListModel; -import envoy.client.ui.renderer.MessageListRenderer; -import envoy.client.ui.renderer.UserComponentListRenderer; +import envoy.client.ui.list.Model; import envoy.data.Message; import envoy.data.User; @@ -36,11 +35,11 @@ public class ContactsChooserDialog extends JDialog { private static final long serialVersionUID = -5774558118579032256L; - private ComponentList contactList = new ComponentList<>(new UserComponentListRenderer()); + private ComponentList contactList = new ComponentList().setRenderer(UserComponent::new); private JButton okButton = new JButton("Ok"); private JButton cancelButton = new JButton("Cancel"); - private final Theme theme = Settings.getInstance().getTheme(Settings.getInstance().getCurrentTheme()); + private final Theme theme = Settings.getInstance().getCurrentTheme(); private final JPanel contentPanel = new JPanel(); @@ -67,11 +66,13 @@ public class ContactsChooserDialog extends JDialog { dialog.setTitle(title); dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE); dialog.addCancelButtonActionListener(e -> dialog.dispose()); - dialog.getContentPanel() - .add(new MessageListRenderer(client.getSender().getId()).getListCellComponent(null, message, false), BorderLayout.NORTH); + // dialog.getContentPanel() + // .add(new + // MessageListRenderer(client.getSender().getId()).getListCellComponent(null, + // message, false), BorderLayout.NORTH); List results = new ArrayList<>(); dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); }); - ComponentListModel contactListModel = dialog.getContactList().getModel(); + Model contactListModel = dialog.getContactList().getModel(); client.getContacts().getContacts().forEach(user -> contactListModel.add(user)); dialog.setVisible(true); dialog.repaint(); @@ -84,7 +85,8 @@ public class ContactsChooserDialog extends JDialog { */ private ContactsChooserDialog() { contactList.setSelectionMode(SelectionMode.MULTIPLE); - // setBounds(100, 100, 450, 300); + contactList + .setSelectionHandler((user, comp, isSelected) -> comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor())); setModal(true); getContentPane().setLayout(new BorderLayout()); setBackground(theme.getBackgroundColor()); @@ -121,6 +123,4 @@ public class ContactsChooserDialog extends JDialog { private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); } private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); } - - private JPanel getContentPanel() { return contentPanel; } } diff --git a/src/main/java/envoy/client/ui/container/LoginDialog.java b/src/main/java/envoy/client/ui/container/LoginDialog.java index 51d9817..8780358 100644 --- a/src/main/java/envoy/client/ui/container/LoginDialog.java +++ b/src/main/java/envoy/client/ui/container/LoginDialog.java @@ -291,7 +291,7 @@ public class LoginDialog extends JDialog { } private void setTheme() { - Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + Theme theme = Settings.getInstance().getCurrentTheme(); // Panels contentPanel.setBackground(theme.getBackgroundColor()); diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index a4b4953..44d98de 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -5,13 +5,12 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.HashSet; import java.util.Set; -import java.util.function.BiConsumer; import javax.swing.*; /** * Provides a vertical list layout of components provided in a - * {@link ComponentListModel}. Similar to {@link javax.swing.JList} but capable + * {@link Model}. Similar to {@link javax.swing.JList} but capable * of rendering {@link JPanel}s.
*
* Project: envoy-client
@@ -24,11 +23,11 @@ import javax.swing.*; */ public class ComponentList extends JPanel { - private ComponentListModel model; - private ComponentListCellRenderer renderer; - private SelectionMode selectionMode = SelectionMode.NONE; - private Set selection = new HashSet<>(); - private BiConsumer, JComponent> selectionListener; + private Model model; + private Renderer renderer; + private SelectionHandler selectionHandler; + private SelectionMode selectionMode = SelectionMode.NONE; + private Set selection = new HashSet<>(); private static final long serialVersionUID = 1759644503942876737L; @@ -55,64 +54,20 @@ public class ComponentList extends JPanel { } /** - * Initializes a default {@link ComponentList} without a model or a renderer. + * Creates an instance of {@link ComponentList}. * - * @since Envoy v0.1-beta + * @since Envoy v0.3-alpha */ public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); } - /** - * Creates an instance of {@link ComponentList}. - * - * @param renderer the list cell renderer used to display elements provided by - * the {@link ComponentListModel} - * @since Envoy v0.3-alpha - */ - public ComponentList(ComponentListCellRenderer renderer) { - this(); - setRenderer(renderer); - } - - /** - * Creates an instance of {@link ComponentList}. - * - * @param model the list model providing the list elements to render - * @param renderer the list cell renderer used to display elements provided by - * the {@link ComponentListModel} - * @since Envoy v0.3-alpha - */ - public ComponentList(ComponentListModel model, ComponentListCellRenderer renderer) { - this(renderer); - setModel(model); - } - - /** - * Sets the list model providing the list elements to render. The rendered - * components will be synchronized with the contents of the new model or removed - * if the new model is {@code null}. - * - * @param model the list model to set - * @since Envoy v0.3-alpha - */ - public void setModel(ComponentListModel model) { - - // Remove old model - if (this.model != null) this.model.setComponentList(null); - - // Synchronize with new model - this.model = model; - if (model != null) model.setComponentList(this); - synchronizeModel(); - } - /** * Removes all child components and then adds all components representing the - * elements of the {@link ComponentListModel}. + * elements of the {@link Model}. * * @since Envoy v0.3-alpha */ public void synchronizeModel() { - if (model != null && renderer != null) { + if (model != null) { removeAll(); model.forEach(this::addElement); revalidate(); @@ -127,10 +82,12 @@ public class ComponentList extends JPanel { * @since Envoy v0.1-beta */ public void selectElement(int index) { + final JComponent element = getComponent(index); + if (selection.contains(index)) { // Remove selection of element at index - updateElement(index, false); + if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true); selection.remove(index); } else { @@ -143,10 +100,7 @@ public class ComponentList extends JPanel { selection.add(index); // Update element - updateElement(index, true); - - // Trigger selection listener - if (selectionListener != null) selectionListener.accept(this, (JComponent) getComponents()[index]); + if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true); } } @@ -160,49 +114,25 @@ public class ComponentList extends JPanel { * @since Envoy v0.1-alpha */ public void clearSelection() { - selection.forEach(i -> updateElement(i, false)); + if (selectionHandler != null) selection.forEach(i -> selectionHandler.selectionChanged(model.get(i), getComponent(i), false)); selection.clear(); } /** * Adds an object to the list by rendering it with the current - * {@link ComponentListCellRenderer}. + * {@link Renderer}. * * @param elem the element to add * @since Envoy v0.3-alpha */ - void addElement(E elem) { addElement(elem, getComponentCount(), false); } - - /** - * Adds an object to the list by rendering it with the current - * {@link ComponentListRenderer}. If the renderer is {@code null}, no action is - * performed. - * - * @param elem the element to add - * @param index the index at which to add the element - * @param isSelected the selection state of the element - * @since Envoy v0.1-beta - */ - private void addElement(E elem, int index, boolean isSelected) { + void addElement(E elem) { if (renderer != null) { - final JComponent component = renderer.getListCellComponent(this, elem, isSelected); - component.addMouseListener(getSelectionListener(index)); - add(component, index); + final JComponent component = renderer.getListCellComponent(this, elem); + component.addMouseListener(getSelectionListener(getComponentCount())); + add(component, getComponentCount()); } } - /** - * Replaces a list element with a newly rendered instance of its contents. - * - * @param index the index of the element to update - * @param isSelected the selection state passed to the {@link ListCellRenderer} - * @since Envoy v0.1-beta - */ - private void updateElement(int index, boolean isSelected) { - remove(index); - addElement(model.get(index), index, isSelected); - } - /** * @param componentIndex the index of the list component to which the mouse * listener will be added @@ -219,6 +149,9 @@ public class ComponentList extends JPanel { }; } + @Override + public JComponent getComponent(int n) { return (JComponent) super.getComponent(n); } + /** * @return a set of all selected indices * @since Envoy v0.1-beta @@ -253,19 +186,45 @@ public class ComponentList extends JPanel { * @return the model * @since Envoy v0.1-beta */ - public ComponentListModel getModel() { return model; } + public Model getModel() { return model; } + + /** + * Sets the list model providing the list elements to render. The rendered + * components will be synchronized with the contents of the new model or removed + * if the new model is {@code null}. + * + * @param model the list model to set + * @return this component list + * @since Envoy v0.3-alpha + */ + public ComponentList setModel(Model model) { + + // Remove old model + if (this.model != null) this.model.setComponentList(null); + + // Synchronize with new model + this.model = model; + if (model != null) model.setComponentList(this); + synchronizeModel(); + + return this; + } /** * @return the renderer * @since Envoy v0.1-beta */ - public ComponentListCellRenderer getRenderer() { return renderer; } + public Renderer getRenderer() { return renderer; } /** * @param renderer the renderer to set + * @return this component list * @since Envoy v0.1-beta */ - public void setRenderer(ComponentListCellRenderer renderer) { this.renderer = renderer; } + public ComponentList setRenderer(Renderer renderer) { + this.renderer = renderer; + return this; + } /** * @return the selection mode @@ -274,18 +233,28 @@ public class ComponentList extends JPanel { public SelectionMode getSelectionMode() { return selectionMode; } /** + * Sets a new selection mode. The current selection will be cleared during this + * action. + * * @param selectionMode the selection mode to set + * @return this component list * @since Envoy v0.1-beta */ - public void setSelectionMode(SelectionMode selectionMode) { this.selectionMode = selectionMode; } + public ComponentList setSelectionMode(SelectionMode selectionMode) { + this.selectionMode = selectionMode; + clearSelection(); + return this; + } /** - * @return the selectionListener + * @return the selection handler + * @since Envoy v0.1-beta */ - public BiConsumer, JComponent> getSelectionListener() { return selectionListener; } + public SelectionHandler getSelectionHandler() { return selectionHandler; } /** - * @param selectionListener the selectionListener to set + * @param selectionHandler the selection handler to set + * @since Envoy v0.1-beta */ - public void setSelectionListener(BiConsumer, JComponent> selectionListener) { this.selectionListener = selectionListener; } + public void setSelectionHandler(SelectionHandler selectionHandler) { this.selectionHandler = selectionHandler; } } diff --git a/src/main/java/envoy/client/ui/list/ComponentListModel.java b/src/main/java/envoy/client/ui/list/Model.java similarity index 93% rename from src/main/java/envoy/client/ui/list/ComponentListModel.java rename to src/main/java/envoy/client/ui/list/Model.java index 05ef7d4..6dca5eb 100644 --- a/src/main/java/envoy/client/ui/list/ComponentListModel.java +++ b/src/main/java/envoy/client/ui/list/Model.java @@ -9,19 +9,19 @@ import java.util.List; * Stores objects that will be displayed in a {@link ComponentList}.
*
* Project: envoy-client
- * File: ComponentListModel.java
+ * File: Model.java
* Created: 25.01.2020
* * @param the type of object displayed in this list * @author Kai S. K. Engelbart * @since Envoy v0.3-alpha */ -public final class ComponentListModel implements Iterable, Serializable { +public final class Model implements Iterable, Serializable { private List elements = new ArrayList<>(); transient private ComponentList componentList; - private static final long serialVersionUID = 4815005915255497331L; + private static final long serialVersionUID = 0L; /** * Adds an element to this model and notifies the associated diff --git a/src/main/java/envoy/client/ui/list/ComponentListCellRenderer.java b/src/main/java/envoy/client/ui/list/Renderer.java similarity index 86% rename from src/main/java/envoy/client/ui/list/ComponentListCellRenderer.java rename to src/main/java/envoy/client/ui/list/Renderer.java index a8bdda6..58e6ad9 100644 --- a/src/main/java/envoy/client/ui/list/ComponentListCellRenderer.java +++ b/src/main/java/envoy/client/ui/list/Renderer.java @@ -7,14 +7,15 @@ import javax.swing.JComponent; * that can be rendered.
*
* Project: envoy-client
- * File: ComponentListCellRenderer.java
+ * File: Renderer.java
* Created: 25.01.2020
* * @param the type of object displayed in this list * @author Kai S. K. Engelbart * @since Envoy v0.3-alpha */ -public interface ComponentListCellRenderer { +@FunctionalInterface +public interface Renderer { /** * Provides a Swing component representing a list element. @@ -26,5 +27,5 @@ public interface ComponentListCellRenderer { * @return the component representing the list element * @since Envoy v0.3-alpha */ - JComponent getListCellComponent(ComponentList list, E value, boolean isSelected); + JComponent getListCellComponent(ComponentList list, E value); } diff --git a/src/main/java/envoy/client/ui/list/SelectionHandler.java b/src/main/java/envoy/client/ui/list/SelectionHandler.java new file mode 100644 index 0000000..b6ed40a --- /dev/null +++ b/src/main/java/envoy/client/ui/list/SelectionHandler.java @@ -0,0 +1,27 @@ +package envoy.client.ui.list; + +import javax.swing.JComponent; + +/** + * Handles the selection of elements in a {@link ComponentList}.
+ *
+ * Project: envoy-client + * File: SelectionHandler.java + * Created: 21.03.2020 + * + * @author Kai S. K. Engelbart + * @since Envoy v0.1-beta + */ +@FunctionalInterface +public interface SelectionHandler { + + /** + * Notifies the handler about a selection. + * + * @param element the selected element + * @param component the selected component + * @param isSelected contains the selection state + * @since Envoy v0.1-beta + */ + void selectionChanged(E element, JComponent component, boolean isSelected); +} diff --git a/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java b/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java index 7dd560e..59846fa 100644 --- a/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java @@ -1,11 +1,6 @@ package envoy.client.ui.primary; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; +import java.awt.*; import javax.swing.JButton; import javax.swing.JComponent; @@ -98,11 +93,11 @@ public class PrimaryScrollBar extends BasicScrollBarUI { g2.setPaint(color); if (isVertical) { g2.fillRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize); - g2.setPaint(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getCellColor()); + g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor()); g2.drawRoundRect(r.x - 9, r.y, r.width, r.height, arcSize, arcSize); } else { g2.fillRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize); - g2.setPaint(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getCellColor()); + g2.setPaint(Settings.getInstance().getCurrentTheme().getCellColor()); g2.drawRoundRect(r.x, r.y + 9, r.width, r.height - 10, arcSize, arcSize); } g2.dispose(); diff --git a/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java b/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java index 30ee098..fea465f 100644 --- a/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java @@ -52,7 +52,7 @@ public class PrimaryToggleSwitch extends JButton { g.setColor(state ? Color.GREEN : Color.LIGHT_GRAY); g.fillRoundRect(0, 0, getWidth(), getHeight(), 25, 25); - g.setColor(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableBackgroundColor()); + g.setColor(Settings.getInstance().getCurrentTheme().getInteractableBackgroundColor()); g.fillRoundRect(state ? 25 : 0, 0, 25, 25, 25, 25); } } diff --git a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java b/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java deleted file mode 100644 index a3c20bd..0000000 --- a/src/main/java/envoy/client/ui/renderer/MessageListRenderer.java +++ /dev/null @@ -1,42 +0,0 @@ -package envoy.client.ui.renderer; - -import javax.swing.JPanel; - -import envoy.client.ui.MessageComponent; -import envoy.client.ui.list.ComponentList; -import envoy.client.ui.list.ComponentListCellRenderer; -import envoy.data.Message; - -/** - * Defines how a message is displayed.
- *
- * Project: envoy-client
- * File: MessageListRenderer.java
- * Created: 19 Oct 2019
- * - * @author Kai S. K. Engelbart - * @author Maximilian Käfer - * @author Leon Hofmeister - * @since Envoy v0.1-alpha - */ -public class MessageListRenderer implements ComponentListCellRenderer { - - private final long senderId; - - /** - * Initializes a message list renderer. Messages with the given sender ID will - * be aligned on the right side, while all other messages will be aligned on - * the left side - * - * @param senderId the sender ID of the messages to align on the right side - * @since Envoy v0.1-beta - */ - public MessageListRenderer(long senderId) { this.senderId = senderId; } - - // TODO: Handle message attachments - - @Override - public JPanel getListCellComponent(ComponentList list, Message message, boolean isSelected) { - return new MessageComponent(list, message, isSelected, senderId); - } -} diff --git a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java deleted file mode 100644 index f19f17e..0000000 --- a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java +++ /dev/null @@ -1,70 +0,0 @@ -package envoy.client.ui.renderer; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.io.Serializable; - -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import envoy.client.data.Settings; -import envoy.client.ui.Color; -import envoy.client.ui.Theme; -import envoy.client.ui.list.ComponentList; -import envoy.client.ui.list.ComponentListCellRenderer; -import envoy.data.User; -import envoy.data.User.UserStatus; - -/** - * Project: envoy-client
- * File: UserComponentListRenderer.java
- * Created: 15 Mar 2020
- * - * @author Leon Hofmeister - * @since Envoy v0.1-beta - */ -public class UserComponentListRenderer implements ComponentListCellRenderer, Serializable { - - private static final long serialVersionUID = -2379244319112111284L; - - @Override - public JComponent getListCellComponent(ComponentList list, User user, boolean isSelected) { - final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); - - final JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - - // Panel background - panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); - panel.setOpaque(true); - panel.setPreferredSize(new Dimension(100, 35)); - - // TODO add profile picture support in BorderLayout.West - - JLabel username = new JLabel(user.getName()); - username.setForeground(theme.getUserNameColor()); - panel.add(username, BorderLayout.CENTER); - - final UserStatus status = user.getStatus(); - JLabel statusLabel = new JLabel(status.toString()); - Color foreground; - switch (status) { - case AWAY: - foreground = Color.yellow; - break; - case BUSY: - foreground = Color.blue; - break; - case ONLINE: - foreground = Color.green; - break; - default: - foreground = Color.lightGray; - break; - } - statusLabel.setForeground(foreground); - panel.add(statusLabel, BorderLayout.NORTH); - return panel; - } -} diff --git a/src/main/java/envoy/client/ui/renderer/UserListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserListRenderer.java index 7f6bc0c..89435df 100644 --- a/src/main/java/envoy/client/ui/renderer/UserListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/UserListRenderer.java @@ -46,7 +46,7 @@ public class UserListRenderer extends JLabel implements ListCellRenderer { // Getting the UserNameColor of the current theme String textColor = null; - textColor = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor().toHex(); + textColor = Settings.getInstance().getCurrentTheme().getUserNameColor().toHex(); switch (status) { case ONLINE: setText(String diff --git a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java index c85c048..fbc3952 100644 --- a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java +++ b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java @@ -43,7 +43,7 @@ public class GeneralSettingsPanel extends SettingsPanel { */ public GeneralSettingsPanel(SettingsScreen parent) { super(parent); - theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + theme = Settings.getInstance().getCurrentTheme(); setBackground(theme.getCellColor()); diff --git a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java index 4fdab10..906bb91 100644 --- a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java +++ b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java @@ -61,7 +61,7 @@ public class NewThemeScreen extends JDialog { setDimensions(true); setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); - Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + Theme theme = Settings.getInstance().getCurrentTheme(); getContentPane().setLayout(new BorderLayout()); standardPanel.setBackground(theme.getBackgroundColor()); diff --git a/src/main/java/envoy/client/ui/settings/SettingsScreen.java b/src/main/java/envoy/client/ui/settings/SettingsScreen.java index e1ae290..22a24a4 100644 --- a/src/main/java/envoy/client/ui/settings/SettingsScreen.java +++ b/src/main/java/envoy/client/ui/settings/SettingsScreen.java @@ -156,7 +156,7 @@ public class SettingsScreen extends JDialog { } // Apply current theme - applyTheme(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); + applyTheme(Settings.getInstance().getCurrentTheme()); // Respond to theme changes EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get())); diff --git a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java index 8772064..5408a05 100644 --- a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java +++ b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java @@ -38,8 +38,9 @@ public class ThemeCustomizationPanel extends SettingsPanel { private final Insets insets = new Insets(5, 5, 5, 5); - private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class); - private static final long serialVersionUID = -8697897390666456624L; + private static final Settings settings = Settings.getInstance(); + private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class); + private static final long serialVersionUID = -8697897390666456624L; /** * Initializes a {@link ThemeCustomizationPanel} that enables the user to change @@ -52,7 +53,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { */ public ThemeCustomizationPanel(SettingsScreen parent) { super(parent); - temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); + temporaryTheme = new Theme("temporaryTheme", settings.getCurrentTheme()); GridBagLayout gbl_themeLayout = new GridBagLayout(); @@ -63,7 +64,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { setLayout(gbl_themeLayout); - themes.setSelectedItem(Settings.getInstance().getCurrentTheme()); + themes.setSelectedItem(settings.getCurrentTheme()); GridBagConstraints gbc_themes = new GridBagConstraints(); gbc_themes.fill = GridBagConstraints.HORIZONTAL; @@ -83,7 +84,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { colorsPanel.setLayout(gbl_colorCustomizations); - Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + Theme theme = settings.getCurrentTheme(); buildCustomizeElements(theme); GridBagConstraints gbc_colorsPanel = new GridBagConstraints(); From 08f6ee62caa1a2c884c49b821a338b05aeab5171 Mon Sep 17 00:00:00 2001 From: delvh Date: Sat, 21 Mar 2020 19:26:11 +0100 Subject: [PATCH 24/32] Fixed bug adding a line break when sending via enter * additionally merged full input of f/enhanced_component_list with the current working state of f/forward_messages: * implemented setting of colors (theming) for ContextMenu * renamed messageTextColor to textColor --- .../java/envoy/client/{ui => }/Startup.java | 5 +- src/main/java/envoy/client/ui/Theme.java | 9 +- .../envoy/client/ui/container/ChatWindow.java | 16 +- .../ui/container/ContactsChooserDialog.java | 10 +- .../client/ui/container/ContextMenu.java | 246 ++++++++++++++++++ .../client/ui/list/SelectionHandler.java | 7 +- .../ContactSearchComponent.java | 9 +- .../MessageComponent.java | 23 +- .../{ => list_component}/UserComponent.java | 13 +- .../ui/list_component/package-info.java | 14 + .../ui/settings/ThemeCustomizationPanel.java | 13 +- 11 files changed, 325 insertions(+), 40 deletions(-) rename src/main/java/envoy/client/{ui => }/Startup.java (98%) mode change 100644 => 100755 src/main/java/envoy/client/ui/Theme.java mode change 100644 => 100755 src/main/java/envoy/client/ui/container/ContactsChooserDialog.java create mode 100755 src/main/java/envoy/client/ui/container/ContextMenu.java rename src/main/java/envoy/client/ui/{ => list_component}/ContactSearchComponent.java (89%) rename src/main/java/envoy/client/ui/{ => list_component}/MessageComponent.java (84%) rename src/main/java/envoy/client/ui/{ => list_component}/UserComponent.java (83%) create mode 100644 src/main/java/envoy/client/ui/list_component/package-info.java mode change 100644 => 100755 src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/Startup.java similarity index 98% rename from src/main/java/envoy/client/ui/Startup.java rename to src/main/java/envoy/client/Startup.java index 5c03881..012b1e8 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/Startup.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client; import java.awt.EventQueue; import java.io.File; @@ -15,6 +15,7 @@ import javax.swing.SwingUtilities; import envoy.client.data.*; import envoy.client.net.Client; import envoy.client.net.WriteProxy; +import envoy.client.ui.StatusTrayIcon; import envoy.client.ui.container.ChatWindow; import envoy.client.ui.container.LoginDialog; import envoy.data.Config; @@ -129,7 +130,7 @@ public class Startup { // Save all users to the local database and flush cache localDb.setUsers(client.getUsers()); writeProxy.flushCache(); - } else + } else // Set all contacts to offline mode localDb.getUsers().values().stream().filter(u -> u != localDb.getUser()).forEach(u -> u.setStatus(UserStatus.OFFLINE)); diff --git a/src/main/java/envoy/client/ui/Theme.java b/src/main/java/envoy/client/ui/Theme.java old mode 100644 new mode 100755 index 2956882..9fb8726 --- a/src/main/java/envoy/client/ui/Theme.java +++ b/src/main/java/envoy/client/ui/Theme.java @@ -29,7 +29,8 @@ public class Theme implements Serializable { * elements * @param interactableBackgroundColor the color of interactable background UI * elements - * @param messageColorChat the color of chat messages + * @param textColor the color normal text should be displayed + * in * @param dateColorChat the color of chat message metadata * @param selectionColor the section color * @param typingMessageColor the color of currently typed messages @@ -37,7 +38,7 @@ public class Theme implements Serializable { * @since Envoy v0.2-alpha */ public Theme(String themeName, Color backgroundColor, Color cellColor, Color interactableForegroundColor, Color interactableBackgroundColor, - Color messageColorChat, Color dateColorChat, Color selectionColor, Color typingMessageColor, Color userNameColor) { + Color textColor, Color dateColorChat, Color selectionColor, Color typingMessageColor, Color userNameColor) { this.themeName = themeName; @@ -45,7 +46,7 @@ public class Theme implements Serializable { colors.put("cellColor", cellColor); colors.put("interactableForegroundColor", interactableForegroundColor); colors.put("interactableBackgroundColor", interactableBackgroundColor); - colors.put("messageColorChat", messageColorChat); + colors.put("textColor", textColor); colors.put("dateColorChat", dateColorChat); colors.put("selectionColor", selectionColor); colors.put("typingMessageColor", typingMessageColor); @@ -89,7 +90,7 @@ public class Theme implements Serializable { * displayed * @since Envoy v0.2-alpha */ - public Color getMessageTextColor() { return colors.get("messageColorChat"); } + public Color getTextColor() { return colors.get("textColor"); } /** * @return the {@link Color} in which the creation date of a message should be diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index 41e9614..6f16af4 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -22,10 +22,12 @@ import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; -import envoy.client.ui.*; +import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.Model; +import envoy.client.ui.list_component.ContactSearchComponent; +import envoy.client.ui.list_component.MessageComponent; import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryScrollPane; import envoy.client.ui.primary.PrimaryTextArea; @@ -490,7 +492,7 @@ public class ChatWindow extends JFrame { contentPane.setBackground(theme.getBackgroundColor()); contentPane.setForeground(theme.getUserNameColor()); // messageList - messageList.setForeground(theme.getMessageTextColor()); + messageList.setForeground(theme.getTextColor()); messageList.setBackground(theme.getCellColor()); // scrollPane scrollPane.applyTheme(theme); @@ -525,7 +527,7 @@ public class ChatWindow extends JFrame { searchField.setForeground(theme.getUserNameColor()); cancelButton.setBackground(theme.getInteractableBackgroundColor()); cancelButton.setForeground(theme.getInteractableForegroundColor()); - contactList.setForeground(theme.getMessageTextColor()); + contactList.setForeground(theme.getTextColor()); contactList.setBackground(theme.getCellColor()); scrollForPossibleContacts.applyTheme(theme); } @@ -540,11 +542,15 @@ public class ChatWindow extends JFrame { JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); return; } + String text = messageEnterTextArea.getText().trim(); + if (!text.isEmpty()) checkMessageTextLength(); + // delete final line break, if present (especially if "Enter" is used to send + // the message) + if (text.endsWith(System.getProperty("line.separator"))) text = text.substring(0, text.lastIndexOf(System.getProperty("line.separator"))); - if (!messageEnterTextArea.getText().isEmpty()) checkMessageTextLength(); // Create message final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator()) - .setText(messageEnterTextArea.getText()) + .setText(text) .build(); sendMessage(message); // Clear text field diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java old mode 100644 new mode 100755 index 8171b82..5677e7d --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -14,7 +14,6 @@ import javax.swing.border.EmptyBorder; import envoy.client.data.Settings; import envoy.client.net.Client; import envoy.client.ui.Theme; -import envoy.client.ui.UserComponent; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.Model; @@ -35,7 +34,7 @@ public class ContactsChooserDialog extends JDialog { private static final long serialVersionUID = -5774558118579032256L; - private ComponentList contactList = new ComponentList().setRenderer(UserComponent::new); + private ComponentList contactList = new ComponentList<>(); private JButton okButton = new JButton("Ok"); private JButton cancelButton = new JButton("Cancel"); @@ -77,7 +76,7 @@ public class ContactsChooserDialog extends JDialog { dialog.setVisible(true); dialog.repaint(); dialog.revalidate(); - return results; + return results.size() > 0 ? results : null; } /** @@ -85,12 +84,11 @@ public class ContactsChooserDialog extends JDialog { */ private ContactsChooserDialog() { contactList.setSelectionMode(SelectionMode.MULTIPLE); - contactList - .setSelectionHandler((user, comp, isSelected) -> comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor())); + // setBounds(100, 100, 450, 300); setModal(true); getContentPane().setLayout(new BorderLayout()); setBackground(theme.getBackgroundColor()); - setForeground(theme.getMessageTextColor()); + setForeground(theme.getTextColor()); contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); getContentPane().add(contentPanel, BorderLayout.CENTER); contentPanel.setLayout(new BorderLayout(0, 0)); diff --git a/src/main/java/envoy/client/ui/container/ContextMenu.java b/src/main/java/envoy/client/ui/container/ContextMenu.java new file mode 100755 index 0000000..facfe73 --- /dev/null +++ b/src/main/java/envoy/client/ui/container/ContextMenu.java @@ -0,0 +1,246 @@ +package envoy.client.ui.container; + +import java.awt.Color; +import java.awt.Component; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.*; + +import envoy.client.ui.Theme; + +/** + * This class defines a menu that will be automatically called if + * {@link MouseEvent#isPopupTrigger()} returns true for the parent component. + * The user has the possibility to directly add actions to be performed when + * clicking on the element with the selected String. Additionally, for each + * element an {@link Icon} can be added, but it must not be. + * If the key(text) of an element starts with one of the predefined values, a + * special component will be called: either a {@link JRadioButtonMenuItem}, a + * {@link JCheckBoxMenuItem} or a {@link JMenu} will be created.
+ *
+ * Project: envoy-client
+ * File: ContextMenu.java
+ * Created: 17 Mar 2020
+ * + * @author Leon Hofmeister + * @since Envoy v0.1-beta + */ +public class ContextMenu extends JPopupMenu { + + private static final long serialVersionUID = 2177146471226992104L; + + /** + * If a key starts with this String, a {@link JCheckBoxMenuItem} will be created + */ + public static final String checkboxMenuItem = "ChBoMI"; + /** + * If a key starts with this String, a {@link JRadioButtonMenuItem} will be + * created + */ + public static final String radioButtonMenuItem = "RaBuMI"; + /** + * If a key starts with this String, a {@link JMenu} will be created + */ + public static final String subMenuItem = "SubMI"; + + private Map items = new HashMap<>(); + private Map icons = new HashMap<>(); + private Map mnemonics = new HashMap<>(); + + private ButtonGroup radioButtonGroup = new ButtonGroup(); + private boolean built = false; + private boolean visible = false; + + /** + * @param parent the component which will call this + * {@link ContextMenu} + * @since Envoy v0.1-beta + */ + public ContextMenu(Component parent) { setInvoker(parent); } + + /** + * @param label the string that a UI may use to display as a title + * for the pop-up menu + * @param parent the component which will call this + * {@link ContextMenu} + * @param itemsWithActions a map of all strings to be displayed with according + * actions + * @param itemIcons the icons to be displayed before a name, if wanted. + * Only keys in here will have an Icon displayed. More + * precisely, all keys here not included in the first + * map will be thrown out. + * @param itemMnemonics the keyboard shortcuts that need to be pressed to + * automatically execute the {@link JMenuItem} with the + * given text + * @since Envoy v0.1-beta + */ + public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons, + Map itemMnemonics) { + super(label); + setInvoker(parent); + this.items = (itemsWithActions != null) ? itemsWithActions : items; + this.icons = (itemIcons != null) ? itemIcons : icons; + this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics; + } + + /** + * Prepares the PopupMenu to be displayed. Should only be used once all map + * values have been set. + * + * @return this instance of {@link ContextMenu} to allow chaining behind the + * constructor + * @since Envoy v0.1-beta + */ + public ContextMenu build() { + items.forEach((text, action) -> { + // case radio button wanted + AbstractButton item; + if (text.startsWith(radioButtonMenuItem)) { + item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); + radioButtonGroup.add(item); + // case check box wanted + } else if (text.startsWith(checkboxMenuItem)) + item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); + // case sub-menu wanted + else if (text.startsWith(subMenuItem)) item = new JMenu(text.substring(subMenuItem.length())); + else // normal JMenuItem wanted + item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null); + item.addActionListener(action); + if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text)); + add(item); + }); + getInvoker().addMouseListener(getShowingListener()); + built = true; + return this; + } + + /** + * @param label the string that a UI may use to display as a title for the + * pop-up menu. + * @since Envoy v0.1-beta + */ + public ContextMenu(String label) { super(label); } + + private MouseAdapter getShowingListener() { + return new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { action(e); } + + @Override + public void mousePressed(MouseEvent e) { action(e); } + + @Override + public void mouseReleased(MouseEvent e) { action(e); } + + private void action(MouseEvent e) { + if (!built) build(); + if (e.isPopupTrigger()) { + // hides the menu if already visible + visible = !visible; + if (visible) show(e.getComponent(), e.getX(), e.getY()); + else setVisible(false); + + } + } + }; + } + + /** + * Removes all subcomponents of this menu. + * + * @since Envoy v0.1-beta + */ + public void clear() { + removeAll(); + items = new HashMap<>(); + icons = new HashMap<>(); + mnemonics = new HashMap<>(); + } + + /** + * @return the items + * @since Envoy v0.1-beta + */ + public Map getItems() { return items; } + + /** + * @param items the items with the displayed text and the according action to + * take once called + * @since Envoy v0.1-beta + */ + public void setItems(Map items) { this.items = items; } + + /** + * @return the icons + * @since Envoy v0.1-beta + */ + public Map getIcons() { return icons; } + + /** + * @param icons the icons to set + * @since Envoy v0.1-beta + */ + public void setIcons(Map icons) { this.icons = icons; } + + /** + * @return the mnemonics (the keyboard shortcuts that automatically execute the + * command for a {@link JMenuItem} with corresponding text) + * @since Envoy v0.1-beta + */ + public Map getMnemonics() { return mnemonics; } + + /** + * @param mnemonics the keyboard shortcuts that need to be pressed to + * automatically execute the {@link JMenuItem} with the given + * text + * @since Envoy v0.1-beta + */ + public void setMnemonics(Map mnemonics) { this.mnemonics = mnemonics; } + + /** + * {@inheritDoc}
+ * Additionally sets the foreground of all subcomponents of this + * {@link ContextMenu}. + * + * @since Envoy v0.1-beta + */ + @Override + public void setForeground(Color color) { + super.setForeground(color); + for (MenuElement element : getSubElements()) + ((Component) element).setForeground(color); + } + + /** + * {@inheritDoc}
+ * Additionally sets the background of all subcomponents of this + * {@link ContextMenu}. + * + * @since Envoy v0.1-beta + */ + @Override + public void setBackground(Color color) { + super.setBackground(color); + for (MenuElement element : getSubElements()) + ((Component) element).setBackground(color); + } + + /** + * Sets the fore- and background of all elements contained in this + * {@link ContextMenu} + * This method is to be only used by Envoy as {@link Theme} is an + * Envoy-exclusive object. + * + * @param theme the theme to use + * @since Envoy v0.1-beta + */ + protected void applyTheme(Theme theme) { + setBackground(theme.getInteractableBackgroundColor()); + setForeground(theme.getTextColor()); + } +} diff --git a/src/main/java/envoy/client/ui/list/SelectionHandler.java b/src/main/java/envoy/client/ui/list/SelectionHandler.java index b6ed40a..998bee2 100644 --- a/src/main/java/envoy/client/ui/list/SelectionHandler.java +++ b/src/main/java/envoy/client/ui/list/SelectionHandler.java @@ -10,6 +10,7 @@ import javax.swing.JComponent; * Created: 21.03.2020 * * @author Kai S. K. Engelbart + * @param the type of the underlying {@link ComponentList} * @since Envoy v0.1-beta */ @FunctionalInterface @@ -17,9 +18,9 @@ public interface SelectionHandler { /** * Notifies the handler about a selection. - * - * @param element the selected element - * @param component the selected component + * + * @param element the selected element + * @param component the selected component * @param isSelected contains the selection state * @since Envoy v0.1-beta */ diff --git a/src/main/java/envoy/client/ui/ContactSearchComponent.java b/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java similarity index 89% rename from src/main/java/envoy/client/ui/ContactSearchComponent.java rename to src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java index 39ae50b..16acaa4 100644 --- a/src/main/java/envoy/client/ui/ContactSearchComponent.java +++ b/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.list_component; import java.awt.Component; import java.awt.Dimension; @@ -26,6 +26,11 @@ public class ContactSearchComponent extends JComponent { private static final long serialVersionUID = 3166795412575239455L; + /** + * @param list the {@link ComponentList} that is used to display search results + * @param user the {@link User} that appears as a search result + * @since Envoy v0.1-beta + */ public ContactSearchComponent(ComponentList list, User user) { setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); @@ -33,7 +38,7 @@ public class ContactSearchComponent extends JComponent { setForeground(list.getForeground()); JLabel display = new JLabel(user.getName()); - display.setForeground(Settings.getInstance().getCurrentTheme().getMessageTextColor()); + display.setForeground(Settings.getInstance().getCurrentTheme().getTextColor()); display.setAlignmentX(Component.LEFT_ALIGNMENT); display.setAlignmentY(Component.CENTER_ALIGNMENT); display.setFont(new Font("Arial", Font.PLAIN, 16)); diff --git a/src/main/java/envoy/client/ui/MessageComponent.java b/src/main/java/envoy/client/ui/list_component/MessageComponent.java similarity index 84% rename from src/main/java/envoy/client/ui/MessageComponent.java rename to src/main/java/envoy/client/ui/list_component/MessageComponent.java index a4a83a0..4b6ca83 100644 --- a/src/main/java/envoy/client/ui/MessageComponent.java +++ b/src/main/java/envoy/client/ui/list_component/MessageComponent.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.list_component; import java.awt.*; import java.io.IOException; @@ -7,10 +7,14 @@ import java.util.EnumMap; import javax.swing.*; +import envoy.client.data.Chat; import envoy.client.data.Settings; +import envoy.client.ui.Color; +import envoy.client.ui.IconUtil; import envoy.client.ui.list.ComponentList; import envoy.data.Message; import envoy.data.Message.MessageStatus; +import envoy.data.User; /** * Project: envoy-client @@ -36,11 +40,15 @@ public class MessageComponent extends JPanel { } } + /** + * @param list the {@link ComponentList} that displays this {@link Chat} + * @param message the {@link Message} to display + * @param senderId the id of the {@link User} who sends messages from this + * account + * @since Envoy v0.1-beta + */ public MessageComponent(ComponentList list, Message message, long senderId) { - this(list.getMaximumSize().width, message, senderId); - } - - public MessageComponent(int width, Message message, long senderId) { + var width = list.getMaximumSize().width; final var theme = Settings.getInstance().getCurrentTheme(); final int padding = (int) (width * 0.35); @@ -70,7 +78,7 @@ public class MessageComponent extends JPanel { var messageTextArea = new JTextArea(message.getText()); messageTextArea.setLineWrap(true); messageTextArea.setWrapStyleWord(true); - messageTextArea.setForeground(theme.getMessageTextColor()); + messageTextArea.setForeground(theme.getTextColor()); messageTextArea.setAlignmentX(0.5f); messageTextArea.setBackground(theme.getCellColor()); messageTextArea.setEditable(false); @@ -107,8 +115,7 @@ public class MessageComponent extends JPanel { // Define an etched border and some space to the messages below var ours = senderId == message.getSenderId(); - setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createEmptyBorder(0, ours ? padding : 10, 10, ours ? 0 : padding), + setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, ours ? padding : 10, 10, ours ? 0 : padding), BorderFactory.createEtchedBorder())); var size = new Dimension(width - 50, getPreferredSize().height); diff --git a/src/main/java/envoy/client/ui/UserComponent.java b/src/main/java/envoy/client/ui/list_component/UserComponent.java similarity index 83% rename from src/main/java/envoy/client/ui/UserComponent.java rename to src/main/java/envoy/client/ui/list_component/UserComponent.java index 7fd4392..4a9fbc7 100644 --- a/src/main/java/envoy/client/ui/UserComponent.java +++ b/src/main/java/envoy/client/ui/list_component/UserComponent.java @@ -1,4 +1,4 @@ -package envoy.client.ui; +package envoy.client.ui.list_component; import java.awt.BorderLayout; import java.awt.Dimension; @@ -7,11 +7,14 @@ import javax.swing.JComponent; import javax.swing.JLabel; import envoy.client.data.Settings; -import envoy.client.ui.list.ComponentList; +import envoy.client.ui.Color; +import envoy.client.ui.Theme; import envoy.data.User; import envoy.data.User.UserStatus; /** + * Displays a {@link User}.
+ *
* Project: envoy-client * File: UserComponent.java * Created: 21.03.2020 @@ -23,7 +26,11 @@ public class UserComponent extends JComponent { private static final long serialVersionUID = 8450602172939729585L; - public UserComponent(ComponentList list, User user) { + /** + * @param user the {@link User} whose information is displayed + * @since Envoy v0.1-beta + */ + public UserComponent(User user) { final Theme theme = Settings.getInstance().getCurrentTheme(); setLayout(new BorderLayout()); diff --git a/src/main/java/envoy/client/ui/list_component/package-info.java b/src/main/java/envoy/client/ui/list_component/package-info.java new file mode 100644 index 0000000..053d2bd --- /dev/null +++ b/src/main/java/envoy/client/ui/list_component/package-info.java @@ -0,0 +1,14 @@ +/** + * This package contains swing components that can be displayed by + * {@link envoy.client.ui.list.ComponentList}.
+ *
+ * Project: envoy-client
+ * File: package-info.java
+ * Created: 21 Mar 2020
+ * + * @author Leon Hofmeister + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-beta + */ +package envoy.client.ui.list_component; diff --git a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java old mode 100644 new mode 100755 index 5408a05..997dae5 --- a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java +++ b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java @@ -38,9 +38,8 @@ public class ThemeCustomizationPanel extends SettingsPanel { private final Insets insets = new Insets(5, 5, 5, 5); - private static final Settings settings = Settings.getInstance(); - private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class); - private static final long serialVersionUID = -8697897390666456624L; + private static final Logger logger = EnvoyLog.getLogger(ThemeCustomizationPanel.class); + private static final long serialVersionUID = -8697897390666456624L; /** * Initializes a {@link ThemeCustomizationPanel} that enables the user to change @@ -53,7 +52,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { */ public ThemeCustomizationPanel(SettingsScreen parent) { super(parent); - temporaryTheme = new Theme("temporaryTheme", settings.getCurrentTheme()); + temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getCurrentTheme()); GridBagLayout gbl_themeLayout = new GridBagLayout(); @@ -64,7 +63,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { setLayout(gbl_themeLayout); - themes.setSelectedItem(settings.getCurrentTheme()); + themes.setSelectedItem(Settings.getInstance().getCurrentTheme()); GridBagConstraints gbc_themes = new GridBagConstraints(); gbc_themes.fill = GridBagConstraints.HORIZONTAL; @@ -84,7 +83,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { colorsPanel.setLayout(gbl_colorCustomizations); - Theme theme = settings.getCurrentTheme(); + Theme theme = Settings.getInstance().getCurrentTheme(); buildCustomizeElements(theme); GridBagConstraints gbc_colorsPanel = new GridBagConstraints(); @@ -174,7 +173,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { 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.getMessageTextColor(), "Messages Chat", "messageColorChat", 5); + 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); From e4eaf7239c0ee3906103be8d803fe64d40af33ec Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 22 Mar 2020 16:51:44 +0100 Subject: [PATCH 25/32] ContactsChooserDialog is now centered around a parent component additionally: * fixed bug not updating date color when changing themes * improved style of forwardMessage-Methoden --- .../envoy/client/ui/container/ChatWindow.java | 23 ++++++++----------- .../ui/container/ContactsChooserDialog.java | 21 +++++++++++------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index 6f16af4..b762ceb 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -154,6 +154,7 @@ public class ChatWindow extends JFrame { }); scrollPane.setViewportView(messageList); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrollPane.addComponentListener(new ComponentAdapter() { // Update list elements when scroll pane (and thus list) is resized @@ -163,7 +164,6 @@ public class ChatWindow extends JFrame { messageList.synchronizeModel(); } }); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); GridBagConstraints gbc_scrollPane = new GridBagConstraints(); gbc_scrollPane.fill = GridBagConstraints.BOTH; @@ -213,7 +213,6 @@ public class ChatWindow extends JFrame { gbc_postButton.fill = GridBagConstraints.BOTH; gbc_postButton.gridx = 2; gbc_postButton.gridy = 3; - gbc_postButton.insets = insets; postButton.addActionListener((evt) -> { postMessage(); }); @@ -494,6 +493,7 @@ public class ChatWindow extends JFrame { // messageList messageList.setForeground(theme.getTextColor()); messageList.setBackground(theme.getCellColor()); + messageList.synchronizeModel(); // scrollPane scrollPane.applyTheme(theme); scrollPane.autoscroll(); @@ -544,9 +544,6 @@ public class ChatWindow extends JFrame { } String text = messageEnterTextArea.getText().trim(); if (!text.isEmpty()) checkMessageTextLength(); - // delete final line break, if present (especially if "Enter" is used to send - // the message) - if (text.endsWith(System.getProperty("line.separator"))) text = text.substring(0, text.lastIndexOf(System.getProperty("line.separator"))); // Create message final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator()) @@ -560,21 +557,21 @@ public class ChatWindow extends JFrame { /** * Forwards a message. * - * @param msg the message to forward + * @param message the message to forward * @param recipient the new recipient of the message * @since Envoy v0.1-beta */ - private void forwardMessage(Message msg, User recipient) { - sendMessage(new MessageBuilder(msg, recipient.getId(), localDb.getIdGenerator()).build()); - } + private void forwardMessage(Message message, User... recipients) { + Arrays.stream(recipients).forEach(recipient -> { + if (message != null && recipients != null) sendMessage(new MessageBuilder(message, recipient.getId(), localDb.getIdGenerator()).build()); + else throw new NullPointerException("No recipient or no message selected"); + }); - private void forwardMessageToMultipleUsers(Message message, Collection recipients) { - recipients.forEach(recipient -> forwardMessage(message, recipient)); } @SuppressWarnings("unused") - private void forwardMultipleMessagesToMultipleUsers(Collection messages, Collection recipients) { - messages.forEach(message -> forwardMessageToMultipleUsers(message, recipients)); + private void forwardMessages(Collection messages, User... recipients) { + messages.forEach(message -> { forwardMessage(message, recipients); }); } /** diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index 5677e7d..b507cbc 100755 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -1,9 +1,11 @@ package envoy.client.ui.container; import java.awt.BorderLayout; +import java.awt.Component; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import javax.swing.JButton; @@ -12,7 +14,6 @@ import javax.swing.JPanel; import javax.swing.border.EmptyBorder; import envoy.client.data.Settings; -import envoy.client.net.Client; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList.SelectionMode; @@ -34,7 +35,7 @@ public class ContactsChooserDialog extends JDialog { private static final long serialVersionUID = -5774558118579032256L; - private ComponentList contactList = new ComponentList<>(); + private ComponentList contactList = new ComponentList().setModel(new Model()); private JButton okButton = new JButton("Ok"); private JButton cancelButton = new JButton("Cancel"); @@ -54,14 +55,17 @@ public class ContactsChooserDialog extends JDialog { * ArrayList. * * @param title the title of the dialog + * @param parent this @{@link Component} will be parsed to + * {@link java.awt.Window#setLocationRelativeTo(Component)} in + * order to change the location of the dialog * @param message the {@link Message} to display on top of the Dialog - * @param client the client whose contacts should be displayed + * @param users the users that should be displayed * @return the selected Element (yet has to be casted to the wanted type due to * the Generics limitations in Java) * @since Envoy v0.1-beta */ - public static List showForwardingDialog(String title, Message message, Client client) { - ContactsChooserDialog dialog = new ContactsChooserDialog(); + public static List showForwardingDialog(String title, Component parent, Message message, Collection users) { + ContactsChooserDialog dialog = new ContactsChooserDialog(parent); dialog.setTitle(title); dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE); dialog.addCancelButtonActionListener(e -> dialog.dispose()); @@ -72,7 +76,7 @@ public class ContactsChooserDialog extends JDialog { List results = new ArrayList<>(); dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); }); Model contactListModel = dialog.getContactList().getModel(); - client.getContacts().getContacts().forEach(user -> contactListModel.add(user)); + users.forEach(contactListModel::add); dialog.setVisible(true); dialog.repaint(); dialog.revalidate(); @@ -80,12 +84,15 @@ public class ContactsChooserDialog extends JDialog { } /** + * @param parent this @{@link Component} will be parsed to + * {@link java.awt.Window#setLocationRelativeTo(Component)} * @since Envoy v0.1-beta */ - private ContactsChooserDialog() { + private ContactsChooserDialog(Component parent) { contactList.setSelectionMode(SelectionMode.MULTIPLE); // setBounds(100, 100, 450, 300); setModal(true); + setLocationRelativeTo(parent); getContentPane().setLayout(new BorderLayout()); setBackground(theme.getBackgroundColor()); setForeground(theme.getTextColor()); From f08a7a6f5ea975de332bda20f6d4c21f9e61a793 Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 22 Mar 2020 17:05:28 +0100 Subject: [PATCH 26/32] Fixes #121 (first displayed theme is current theme) Additionally removed okButton from SettingsScreen --- .../envoy/client/ui/list/ComponentList.java | 6 +- .../ui/settings/GeneralSettingsPanel.java | 182 +++++++++--------- .../client/ui/settings/SettingsPanel.java | 11 +- .../client/ui/settings/SettingsScreen.java | 20 -- .../ui/settings/ThemeCustomizationPanel.java | 87 +++++---- 5 files changed, 146 insertions(+), 160 deletions(-) diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 44d98de..68b92a2 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -110,7 +110,7 @@ public class ComponentList extends JPanel { /** * Removes the current selection. - * + * * @since Envoy v0.1-alpha */ public void clearSelection() { @@ -173,7 +173,7 @@ public class ComponentList extends JPanel { * @throws java.util.NoSuchElementException if no selection is present * @since Envoy v0.1-beta */ - public int getSingleSelection() { return selection.iterator().next(); } + public int getSingleSelection() { return selection.stream().findAny().get(); } /** * @return an arbitrary selected element @@ -235,7 +235,7 @@ public class ComponentList extends JPanel { /** * Sets a new selection mode. The current selection will be cleared during this * action. - * + * * @param selectionMode the selection mode to set * @return this component list * @since Envoy v0.1-beta diff --git a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java index fbc3952..ce40e91 100644 --- a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java +++ b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java @@ -1,93 +1,89 @@ -package envoy.client.ui.settings; - -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ActionListener; -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.
- *
- * Project: envoy-client
- * File: GeneralSettingsPanel.java
- * Created: 21 Dec 2019
- * - * @author Maximilian Käfer - * @since Envoy 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 = -7470848775130754239L; - - /** - * 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 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); - } - - @Override - public ActionListener getOkButtonAction() { return evt -> {}; } -} +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.
+ *
+ * Project: envoy-client
+ * File: GeneralSettingsPanel.java
+ * Created: 21 Dec 2019
+ * + * @author Maximilian Käfer + * @since Envoy 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 = -7470848775130754239L; + + /** + * 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 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); + } +} diff --git a/src/main/java/envoy/client/ui/settings/SettingsPanel.java b/src/main/java/envoy/client/ui/settings/SettingsPanel.java index 80469c5..421b774 100644 --- a/src/main/java/envoy/client/ui/settings/SettingsPanel.java +++ b/src/main/java/envoy/client/ui/settings/SettingsPanel.java @@ -1,7 +1,5 @@ package envoy.client.ui.settings; -import java.awt.event.ActionListener; - import javax.swing.JPanel; /** @@ -24,16 +22,9 @@ public abstract class SettingsPanel extends JPanel { /** * 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; } - - /** - * @return an {@link ActionListener} that should be invoked when the OK button - * is pressed in the {@link SettingsScreen} - * @since Envoy v0.2-alpha - */ - public abstract ActionListener getOkButtonAction(); } diff --git a/src/main/java/envoy/client/ui/settings/SettingsScreen.java b/src/main/java/envoy/client/ui/settings/SettingsScreen.java index 22a24a4..0c98084 100644 --- a/src/main/java/envoy/client/ui/settings/SettingsScreen.java +++ b/src/main/java/envoy/client/ui/settings/SettingsScreen.java @@ -40,7 +40,6 @@ public class SettingsScreen extends JDialog { // OK and cancel buttons private final JPanel buttonPane = new JPanel(); - private final PrimaryButton okButton = new PrimaryButton("Save"); private final PrimaryButton cancelButton = new PrimaryButton("Cancel"); private final Insets insets = new Insets(5, 5, 5, 5); @@ -138,21 +137,6 @@ public class SettingsScreen extends JDialog { cancelButton.addActionListener((evt) -> { dispose(); }); } - { - okButton.setActionCommand("OK"); - okButton.setBorderPainted(false); - GridBagConstraints gbc_okButton = new GridBagConstraints(); - gbc_okButton.anchor = GridBagConstraints.NORTHEAST; - gbc_okButton.fill = GridBagConstraints.EAST; - gbc_okButton.insets = insets; - gbc_okButton.gridx = 2; - gbc_okButton.gridy = 0; - buttonPane.add(okButton, gbc_okButton); - getRootPane().setDefaultButton(okButton); - - // Invoke settings panel action on button press - okButton.addActionListener((evt) -> { if (settingsPanel != null) settingsPanel.getOkButtonAction().actionPerformed(evt); }); - } } // Apply current theme @@ -179,10 +163,6 @@ public class SettingsScreen extends JDialog { cancelButton.setBackground(theme.getInteractableBackgroundColor()); cancelButton.setForeground(theme.getInteractableForegroundColor()); - // okButton - okButton.setBackground(theme.getInteractableBackgroundColor()); - okButton.setForeground(theme.getInteractableForegroundColor()); - // options options.setSelectionForeground(theme.getUserNameColor()); options.setSelectionBackground(theme.getSelectionColor()); diff --git a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java index 997dae5..a68a6a5 100755 --- a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java +++ b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java @@ -1,7 +1,6 @@ package envoy.client.ui.settings; import java.awt.*; -import java.awt.event.ActionListener; import java.util.logging.Level; import java.util.logging.Logger; @@ -11,6 +10,7 @@ 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; @@ -30,13 +30,13 @@ public class ThemeCustomizationPanel extends SettingsPanel { private JPanel colorsPanel = new JPanel(); - private DefaultComboBoxModel themesModel = new DefaultComboBoxModel<>( - Settings.getInstance().getThemes().keySet().toArray(new String[0])); - private JComboBox themes = new JComboBox<>(themesModel); + private DefaultComboBoxModel themesModel; + private JComboBox themes; private Theme temporaryTheme; - private boolean themeChanged; + private PrimaryButton createThemeButton = new PrimaryButton("Create Theme"); - private final Insets insets = new Insets(5, 5, 5, 5); + 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 = -8697897390666456624L; @@ -54,12 +54,22 @@ public class ThemeCustomizationPanel extends SettingsPanel { 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 }; + 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 }; + gbl_themeLayout.rowWeights = new double[] { 0.01, 1.0, 0.01 }; setLayout(gbl_themeLayout); @@ -95,6 +105,37 @@ public class ThemeCustomizationPanel extends SettingsPanel { 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 @@ -120,37 +161,15 @@ public class ThemeCustomizationPanel extends SettingsPanel { }); } - @Override - public ActionListener getOkButtonAction() { - return (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; - } - }; - } - 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()); From 376c026b2a496d5726d132a6cc4fe2f16154a145 Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 22 Mar 2020 17:20:05 +0100 Subject: [PATCH 27/32] Added disabling of postButton while String.isBlank() returns true --- .../envoy/client/ui/container/ChatWindow.java | 90 ++++++++++--------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index b762ceb..a2c2f9a 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -4,9 +4,7 @@ import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.event.*; import java.io.IOException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -118,39 +116,44 @@ public class ChatWindow extends JFrame { gbl_contentPane.columnWidths = new int[] { 1, 1, 1 }; gbl_contentPane.rowHeights = new int[] { 1, 1, 1, 1 }; gbl_contentPane.columnWeights = new double[] { 0.03, 1.0, 0.1 }; - gbl_contentPane.rowWeights = new double[] { 0.03, 0.001, 1.0, 0.005 }; + gbl_contentPane.rowWeights = new double[] { 0.03, 0.001, 1.0, 0.001 }; contentPane.setLayout(gbl_contentPane); - // ContextMenu - Map commands = new HashMap<>() { - - private static final long serialVersionUID = -2755235774946990126L; - - { - put("forward selected message", - evt -> forwardMessageToMultipleUsers(messageList.getSingleSelectedElement(), - ContactsChooserDialog - .showForwardingDialog("Forward selected message to", messageList.getSingleSelectedElement(), client))); - put("copy", evt -> { - // TODO should be enhanced to allow also copying of message attachments, - // especially pictures - StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); - }); - // TODO insert implementation to edit and delete messages - put("delete", evt -> {}); - put("edit", evt -> {}); - put("quote", evt -> {}); - } - }; - contextMenu = new ContextMenu(null, messageList, commands, null, null).build(); - messageList.setBorder(new EmptyBorder(space, space, space, space)); messageList.setSelectionMode(SelectionMode.SINGLE); messageList.setSelectionHandler((message, comp, isSelected) -> { final var theme = Settings.getInstance().getCurrentTheme(); comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); - contextMenu.show(comp, 0, 0); + + // ContextMenu + Map commands = new HashMap<>() { + + private static final long serialVersionUID = -2755235774946990126L; + + { + put("forward selected message", evt -> { + final Message selectedMessage = messageList.getSingleSelectedElement(); + forwardMessage(selectedMessage, + ContactsChooserDialog + .showForwardingDialog("Forward selected message to", null, selectedMessage, localDb.getUsers().values()) + .toArray(new User[0])); + }); + put("copy", evt -> { + // TODO should be enhanced to allow also copying of message attachments, + // especially pictures + StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); + }); + // TODO insert implementation to edit and delete messages + put("delete", evt -> {}); + put("edit", evt -> {}); + put("quote", evt -> {}); + } + }; + if (isSelected) { + contextMenu = new ContextMenu(null, comp, commands, null, null).build(); + contextMenu.show(comp, 0, 0); + } }); scrollPane.setViewportView(messageList); @@ -180,7 +183,10 @@ public class ChatWindow extends JFrame { messageEnterTextArea.addInputMethodListener(new InputMethodListener() { @Override - public void inputMethodTextChanged(InputMethodEvent event) { checkMessageTextLength(); } + public void inputMethodTextChanged(InputMethodEvent event) { + checkMessageTextLength(); + checkPostButton(messageEnterTextArea.getText()); + } @Override public void caretPositionChanged(InputMethodEvent event) {} @@ -191,31 +197,30 @@ public class ChatWindow extends JFrame { @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER - && (Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0 || e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK)) + && (Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0 || e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK) + && postButton.isEnabled()) postMessage(); // Checking if text is too long checkMessageTextLength(); + checkPostButton(messageEnterTextArea.getText()); } }); - GridBagConstraints gbc_scrollPaneForTextInput = new GridBagConstraints(); - gbc_scrollPaneForTextInput.fill = GridBagConstraints.BOTH; - gbc_scrollPaneForTextInput.gridx = 1; - gbc_scrollPaneForTextInput.gridy = 3; - - gbc_scrollPaneForTextInput.insets = insets; - - contentPane.add(messageEnterTextArea, gbc_scrollPaneForTextInput); + GridBagConstraints gbc_messageEnterTextArea = new GridBagConstraints(); + gbc_messageEnterTextArea.fill = GridBagConstraints.BOTH; + gbc_messageEnterTextArea.gridx = 1; + gbc_messageEnterTextArea.gridy = 3; + gbc_messageEnterTextArea.insets = insets; + contentPane.add(messageEnterTextArea, gbc_messageEnterTextArea); // Post Button GridBagConstraints gbc_postButton = new GridBagConstraints(); - gbc_postButton.fill = GridBagConstraints.BOTH; gbc_postButton.gridx = 2; gbc_postButton.gridy = 3; gbc_postButton.insets = insets; - postButton.addActionListener((evt) -> { postMessage(); }); + postButton.setEnabled(false); contentPane.add(postButton, gbc_postButton); // Settings Button @@ -552,6 +557,7 @@ public class ChatWindow extends JFrame { sendMessage(message); // Clear text field messageEnterTextArea.setText(""); + postButton.setEnabled(false); } /** @@ -684,4 +690,6 @@ public class ChatWindow extends JFrame { JOptionPane.WARNING_MESSAGE); } } + + private void checkPostButton(String text) { postButton.setEnabled(!text.trim().isBlank()); } } From 1dbc8180199e38aac8212ebc2291462f4c442ff3 Mon Sep 17 00:00:00 2001 From: delvh Date: Mon, 23 Mar 2020 21:04:32 +0100 Subject: [PATCH 28/32] Added renderer to ContactsChooserDialog --- .../envoy/client/ui/container/ChatWindow.java | 44 ++++++++----------- .../ui/container/ContactsChooserDialog.java | 22 +++++++++- .../envoy/client/ui/list/ComponentList.java | 5 ++- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index a2c2f9a..4d92a03 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -4,7 +4,10 @@ import java.awt.*; import java.awt.datatransfer.StringSelection; import java.awt.event.*; import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -126,30 +129,19 @@ public class ChatWindow extends JFrame { comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); // ContextMenu - Map commands = new HashMap<>() { + Map commands = Map.of("forward selected message", evt -> { + final Message selectedMessage = messageList.getSingleSelectedElement(); + List chosenContacts = ContactsChooserDialog + .showForwardingDialog("Forward selected message to", null, selectedMessage, localDb.getUsers().values()); + if (chosenContacts != null && chosenContacts.size() > 0) forwardMessage(selectedMessage, chosenContacts.toArray(new User[0])); + }, "copy", evt -> { + // TODO should be enhanced to allow also copying of message attachments, + // especially pictures + StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); + // TODO insert implementation to edit and delete messages + }, "delete", evt -> {}, "edit", evt -> {}, "quote", evt -> {}); - private static final long serialVersionUID = -2755235774946990126L; - - { - put("forward selected message", evt -> { - final Message selectedMessage = messageList.getSingleSelectedElement(); - forwardMessage(selectedMessage, - ContactsChooserDialog - .showForwardingDialog("Forward selected message to", null, selectedMessage, localDb.getUsers().values()) - .toArray(new User[0])); - }); - put("copy", evt -> { - // TODO should be enhanced to allow also copying of message attachments, - // especially pictures - StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); - }); - // TODO insert implementation to edit and delete messages - put("delete", evt -> {}); - put("edit", evt -> {}); - put("quote", evt -> {}); - } - }; if (isSelected) { contextMenu = new ContextMenu(null, comp, commands, null, null).build(); contextMenu.show(comp, 0, 0); @@ -210,7 +202,7 @@ public class ChatWindow extends JFrame { gbc_messageEnterTextArea.fill = GridBagConstraints.BOTH; gbc_messageEnterTextArea.gridx = 1; gbc_messageEnterTextArea.gridy = 3; - gbc_messageEnterTextArea.insets = insets; + gbc_messageEnterTextArea.insets = insets; contentPane.add(messageEnterTextArea, gbc_messageEnterTextArea); // Post Button @@ -218,7 +210,7 @@ public class ChatWindow extends JFrame { gbc_postButton.fill = GridBagConstraints.BOTH; gbc_postButton.gridx = 2; gbc_postButton.gridy = 3; - gbc_postButton.insets = insets; + gbc_postButton.insets = insets; postButton.addActionListener((evt) -> { postMessage(); }); postButton.setEnabled(false); contentPane.add(postButton, gbc_postButton); diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index b507cbc..5832e25 100755 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -18,6 +18,7 @@ import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.Model; +import envoy.client.ui.list_component.UserComponent; import envoy.data.Message; import envoy.data.User; @@ -35,7 +36,8 @@ public class ContactsChooserDialog extends JDialog { private static final long serialVersionUID = -5774558118579032256L; - private ComponentList contactList = new ComponentList().setModel(new Model()); + private ComponentList contactList = new ComponentList().setModel(new Model()) + .setRenderer((list, user) -> new UserComponent(user)); private JButton okButton = new JButton("Ok"); private JButton cancelButton = new JButton("Cancel"); @@ -77,6 +79,7 @@ public class ContactsChooserDialog extends JDialog { dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); }); Model contactListModel = dialog.getContactList().getModel(); users.forEach(contactListModel::add); + dialog.setModalityType(ModalityType.APPLICATION_MODAL); dialog.setVisible(true); dialog.repaint(); dialog.revalidate(); @@ -90,12 +93,16 @@ public class ContactsChooserDialog extends JDialog { */ private ContactsChooserDialog(Component parent) { contactList.setSelectionMode(SelectionMode.MULTIPLE); + contactList.setSelectionHandler((user, comp, isSelected) -> { + final var theme = Settings.getInstance().getCurrentTheme(); + comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); + }); // setBounds(100, 100, 450, 300); - setModal(true); setLocationRelativeTo(parent); getContentPane().setLayout(new BorderLayout()); setBackground(theme.getBackgroundColor()); setForeground(theme.getTextColor()); + setSize(400, 400); contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); getContentPane().add(contentPanel, BorderLayout.CENTER); contentPanel.setLayout(new BorderLayout(0, 0)); @@ -117,6 +124,17 @@ public class ContactsChooserDialog extends JDialog { buttonPane.add(cancelButton, BorderLayout.WEST); } } + applyTheme(Settings.getInstance().getCurrentTheme()); + } + + private void applyTheme(Theme theme) { + contentPanel.setBackground(theme.getBackgroundColor()); + contentPanel.setForeground(theme.getTextColor()); + contactList.setBackground(theme.getCellColor()); + okButton.setBackground(theme.getInteractableBackgroundColor()); + okButton.setForeground(theme.getTextColor()); + cancelButton.setBackground(theme.getInteractableBackgroundColor()); + cancelButton.setForeground(theme.getTextColor()); } /** diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 68b92a2..c8ff3c0 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -83,17 +83,18 @@ public class ComponentList extends JPanel { */ public void selectElement(int index) { final JComponent element = getComponent(index); - if (selection.contains(index)) { - // Remove selection of element at index + // Deselect if clicked again if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true); selection.remove(index); + } else { // Remove old selection if single selection is enabled if (selectionMode == SelectionMode.SINGLE) clearSelection(); + // Select item if (selectionMode != SelectionMode.NONE) { // Assign new selection From 31f9d5bcefc7b5715b60dd8245020ee1be135ec5 Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 23 Mar 2020 21:28:00 +0100 Subject: [PATCH 29/32] Finalized forwarding UI --- .../ui/container/ContactsChooserDialog.java | 21 +++++++++---------- .../ui/list_component/UserComponent.java | 4 ++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index 5832e25..3aa44cd 100755 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -71,19 +71,19 @@ public class ContactsChooserDialog extends JDialog { dialog.setTitle(title); dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE); dialog.addCancelButtonActionListener(e -> dialog.dispose()); - // dialog.getContentPanel() - // .add(new - // MessageListRenderer(client.getSender().getId()).getListCellComponent(null, - // message, false), BorderLayout.NORTH); + List results = new ArrayList<>(); - dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); }); + dialog.addOkButtonActionListener(e -> { + results.addAll(dialog.getContactList().getSelectedElements()); + dialog.dispose(); + }); Model contactListModel = dialog.getContactList().getModel(); users.forEach(contactListModel::add); + dialog.setModalityType(ModalityType.APPLICATION_MODAL); dialog.setVisible(true); - dialog.repaint(); - dialog.revalidate(); - return results.size() > 0 ? results : null; + + return results; } /** @@ -97,7 +97,6 @@ public class ContactsChooserDialog extends JDialog { final var theme = Settings.getInstance().getCurrentTheme(); comp.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); }); - // setBounds(100, 100, 450, 300); setLocationRelativeTo(parent); getContentPane().setLayout(new BorderLayout()); setBackground(theme.getBackgroundColor()); @@ -111,7 +110,7 @@ public class ContactsChooserDialog extends JDialog { JPanel buttonPane = new JPanel(); getContentPane().add(buttonPane, BorderLayout.SOUTH); { - JButton okButton = new JButton("OK"); + okButton = new JButton("OK"); okButton.setMnemonic(KeyEvent.VK_ENTER); okButton.setActionCommand("OK"); buttonPane.setLayout(new BorderLayout(0, 0)); @@ -119,7 +118,7 @@ public class ContactsChooserDialog extends JDialog { getRootPane().setDefaultButton(okButton); } { - JButton cancelButton = new JButton("Cancel"); + cancelButton = new JButton("Cancel"); cancelButton.setActionCommand("Cancel"); buttonPane.add(cancelButton, BorderLayout.WEST); } diff --git a/src/main/java/envoy/client/ui/list_component/UserComponent.java b/src/main/java/envoy/client/ui/list_component/UserComponent.java index 4a9fbc7..ff60222 100644 --- a/src/main/java/envoy/client/ui/list_component/UserComponent.java +++ b/src/main/java/envoy/client/ui/list_component/UserComponent.java @@ -3,8 +3,8 @@ package envoy.client.ui.list_component; import java.awt.BorderLayout; import java.awt.Dimension; -import javax.swing.JComponent; import javax.swing.JLabel; +import javax.swing.JPanel; import envoy.client.data.Settings; import envoy.client.ui.Color; @@ -22,7 +22,7 @@ import envoy.data.User.UserStatus; * @author Kai S. K. Engelbart * @since Envoy v0.1-beta */ -public class UserComponent extends JComponent { +public class UserComponent extends JPanel { private static final long serialVersionUID = 8450602172939729585L; From 3dd9884cd958e22fb803fc986166b0bf37aa3dde Mon Sep 17 00:00:00 2001 From: delvh Date: Mon, 23 Mar 2020 21:35:55 +0100 Subject: [PATCH 30/32] Added theme support for ContextMenu --- .../client/ui/container/ContextMenu.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/main/java/envoy/client/ui/container/ContextMenu.java b/src/main/java/envoy/client/ui/container/ContextMenu.java index facfe73..4624223 100755 --- a/src/main/java/envoy/client/ui/container/ContextMenu.java +++ b/src/main/java/envoy/client/ui/container/ContextMenu.java @@ -10,6 +10,7 @@ import java.util.Map; import javax.swing.*; +import envoy.client.data.Settings; import envoy.client.ui.Theme; /** @@ -60,7 +61,10 @@ public class ContextMenu extends JPopupMenu { * {@link ContextMenu} * @since Envoy v0.1-beta */ - public ContextMenu(Component parent) { setInvoker(parent); } + public ContextMenu(Component parent) { + setInvoker(parent); + setOpaque(true); + } /** * @param label the string that a UI may use to display as a title @@ -80,13 +84,23 @@ public class ContextMenu extends JPopupMenu { */ public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons, Map itemMnemonics) { - super(label); + this(label); setInvoker(parent); this.items = (itemsWithActions != null) ? itemsWithActions : items; this.icons = (itemIcons != null) ? itemIcons : icons; this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics; } + /** + * @param label the string that a UI may use to display as a title for the + * pop-up menu. + * @since Envoy v0.1-beta + */ + public ContextMenu(String label) { + super(label); + setOpaque(true); + } + /** * Prepares the PopupMenu to be displayed. Should only be used once all map * values have been set. @@ -110,21 +124,16 @@ public class ContextMenu extends JPopupMenu { else // normal JMenuItem wanted item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null); item.addActionListener(action); + item.setOpaque(true); if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text)); add(item); }); getInvoker().addMouseListener(getShowingListener()); + applyTheme(Settings.getInstance().getCurrentTheme()); built = true; return this; } - /** - * @param label the string that a UI may use to display as a title for the - * pop-up menu. - * @since Envoy v0.1-beta - */ - public ContextMenu(String label) { super(label); } - private MouseAdapter getShowingListener() { return new MouseAdapter() { @@ -240,7 +249,7 @@ public class ContextMenu extends JPopupMenu { * @since Envoy v0.1-beta */ protected void applyTheme(Theme theme) { - setBackground(theme.getInteractableBackgroundColor()); + setBackground(theme.getCellColor()); setForeground(theme.getTextColor()); } } From 86d437760de124ff236cefc10c3574638673a969 Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 23 Mar 2020 21:52:33 +0100 Subject: [PATCH 31/32] Normalized since tags to fit envoy-common and envoy-server Envoy vXXX -> Envoy Client vXXX --- src/main/java/envoy/client/Startup.java | 4 +- src/main/java/envoy/client/data/Cache.java | 8 ++-- src/main/java/envoy/client/data/Chat.java | 14 +++---- .../java/envoy/client/data/ClientConfig.java | 26 ++++++------ src/main/java/envoy/client/data/LocalDb.java | 36 ++++++++--------- .../envoy/client/data/PersistentLocalDb.java | 8 ++-- src/main/java/envoy/client/data/Settings.java | 30 +++++++------- .../java/envoy/client/data/SettingsItem.java | 26 ++++++------ .../envoy/client/data/TransientLocalDb.java | 2 +- .../java/envoy/client/data/package-info.java | 2 +- .../event/HandshakeSuccessfulEvent.java | 2 +- .../client/event/MessageCreationEvent.java | 2 +- .../event/MessageModificationEvent.java | 2 +- .../java/envoy/client/event/SendEvent.java | 2 +- .../envoy/client/event/ThemeChangeEvent.java | 4 +- .../java/envoy/client/event/package-info.java | 2 +- src/main/java/envoy/client/net/Client.java | 20 +++++----- .../MessageStatusChangeEventProcessor.java | 4 +- .../client/net/ReceivedMessageProcessor.java | 2 +- src/main/java/envoy/client/net/Receiver.java | 2 +- .../client/net/UserStatusChangeProcessor.java | 4 +- .../java/envoy/client/net/WriteProxy.java | 10 ++--- .../java/envoy/client/net/package-info.java | 2 +- src/main/java/envoy/client/ui/Color.java | 6 +-- .../java/envoy/client/ui/ContextMenu.java | 24 +++++------ src/main/java/envoy/client/ui/IconUtil.java | 6 +-- .../java/envoy/client/ui/StatusTrayIcon.java | 6 +-- src/main/java/envoy/client/ui/Theme.java | 28 ++++++------- .../envoy/client/ui/container/ChatWindow.java | 16 ++++---- .../ui/container/ContactsChooserDialog.java | 8 ++-- .../client/ui/container/ContextMenu.java | 30 +++++++------- .../client/ui/container/LoginDialog.java | 8 ++-- .../client/ui/container/package-info.java | 2 +- .../envoy/client/ui/list/ComponentList.java | 40 +++++++++---------- src/main/java/envoy/client/ui/list/Model.java | 16 ++++---- .../java/envoy/client/ui/list/Renderer.java | 4 +- .../client/ui/list/SelectionHandler.java | 4 +- .../envoy/client/ui/list/package-info.java | 2 +- .../ContactSearchComponent.java | 4 +- .../ui/list_component/MessageComponent.java | 4 +- .../ui/list_component/UserComponent.java | 4 +- .../ui/list_component/package-info.java | 2 +- .../java/envoy/client/ui/package-info.java | 2 +- .../client/ui/primary/PrimaryButton.java | 2 +- .../client/ui/primary/PrimaryScrollBar.java | 2 +- .../client/ui/primary/PrimaryScrollPane.java | 8 ++-- .../client/ui/primary/PrimaryTextArea.java | 2 +- .../ui/primary/PrimaryToggleSwitch.java | 4 +- .../envoy/client/ui/primary/package-info.java | 2 +- .../client/ui/renderer/UserListRenderer.java | 2 +- .../client/ui/renderer/package-info.java | 2 +- .../ui/settings/GeneralSettingsPanel.java | 4 +- .../client/ui/settings/NewThemeScreen.java | 4 +- .../client/ui/settings/SettingsPanel.java | 2 +- .../client/ui/settings/SettingsScreen.java | 4 +- .../ui/settings/ThemeCustomizationPanel.java | 4 +- .../client/ui/settings/package-info.java | 2 +- src/main/java/module-info.java | 2 +- 58 files changed, 238 insertions(+), 238 deletions(-) diff --git a/src/main/java/envoy/client/Startup.java b/src/main/java/envoy/client/Startup.java index 012b1e8..e4e4667 100644 --- a/src/main/java/envoy/client/Startup.java +++ b/src/main/java/envoy/client/Startup.java @@ -34,7 +34,7 @@ import envoy.util.EnvoyLog; * @author Leon Hofmeister * @author Maximilian Käfer * @author Kai S. K. Engelbart - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public class Startup { @@ -51,7 +51,7 @@ public class Startup { * * @param args the command line arguments may contain configuration parameters * and are parsed by the {@link Config} class - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public static void main(String[] args) { ClientConfig config = ClientConfig.getInstance(); diff --git a/src/main/java/envoy/client/data/Cache.java b/src/main/java/envoy/client/data/Cache.java index 2622322..38d8d24 100644 --- a/src/main/java/envoy/client/data/Cache.java +++ b/src/main/java/envoy/client/data/Cache.java @@ -17,7 +17,7 @@ import envoy.util.EnvoyLog; * * @param the type of cached elements * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class Cache implements Consumer, Serializable { @@ -31,7 +31,7 @@ public class Cache implements Consumer, Serializable { * Adds an element to the cache. * * @param element the element to add - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ @Override public void accept(T element) { @@ -43,7 +43,7 @@ public class Cache implements Consumer, Serializable { * Sets the processor to which cached elements are relayed. * * @param processor the processor to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setProcessor(Consumer processor) { this.processor = processor; } @@ -51,7 +51,7 @@ public class Cache implements Consumer, Serializable { * Relays all cached elements to the processor. * * @throws IllegalStateException if the processor is not initialized - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void relay() { if (processor == null) throw new IllegalStateException("Processor is not defined"); diff --git a/src/main/java/envoy/client/data/Chat.java b/src/main/java/envoy/client/data/Chat.java index dadeaaa..c41b7db 100644 --- a/src/main/java/envoy/client/data/Chat.java +++ b/src/main/java/envoy/client/data/Chat.java @@ -21,7 +21,7 @@ import envoy.event.MessageStatusChangeEvent; * @author Maximilian Käfer * @author Leon Hofmeister * @author Kai S. K. Engelbart - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public class Chat implements Serializable { @@ -35,7 +35,7 @@ public class Chat implements Serializable { * Saves the Messages in the corresponding chat at that Point. * * @param recipient the user who receives the messages - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public Chat(User recipient) { this.recipient = recipient; } @@ -43,7 +43,7 @@ public class Chat implements Serializable { * Appends a message to the bottom of this chat * * @param message the message to append - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public void appendMessage(Message message) { model.add(message); } @@ -56,7 +56,7 @@ public class Chat implements Serializable { * the message status changes * @throws IOException if a {@link MessageStatusChangeEvent} could not be * delivered to the server - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void read(WriteProxy writeProxy) throws IOException { for (int i = model.size() - 1; i >= 0; --i) { @@ -72,19 +72,19 @@ public class Chat implements Serializable { /** * @return {@code true} if the newest message received in the chat doesn't have * the status {@code READ} - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public boolean isUnread() { return !model.isEmpty() && model.get(model.size() - 1).getStatus() != MessageStatus.READ; } /** * @return all messages in the current chat - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public Model getModel() { return model; } /** * @return the recipient of a message - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public User getRecipient() { return recipient; } } diff --git a/src/main/java/envoy/client/data/ClientConfig.java b/src/main/java/envoy/client/data/ClientConfig.java index 349fa54..63381a0 100644 --- a/src/main/java/envoy/client/data/ClientConfig.java +++ b/src/main/java/envoy/client/data/ClientConfig.java @@ -18,7 +18,7 @@ import envoy.data.LoginCredentials; * Created: 01.03.2020
* * @author Kai S. K. Engelbart - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class ClientConfig extends Config { @@ -26,7 +26,7 @@ public class ClientConfig extends Config { /** * @return the singleton instance of the client config - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public static ClientConfig getInstance() { if (config == null) config = new ClientConfig(); @@ -47,68 +47,68 @@ public class ClientConfig extends Config { /** * @return the host name of the Envoy server - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public String getServer() { return (String) items.get("server").get(); } /** * @return the port at which the Envoy server is located on the host - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public Integer getPort() { return (Integer) items.get("port").get(); } /** * @return the local database specific to the client user - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public File getLocalDB() { return (File) items.get("localDB").get(); } /** * @return {@code true} if the local database is to be ignored - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Boolean isIgnoreLocalDB() { return (Boolean) items.get("ignoreLocalDB").get(); } /** * @return the directory in which all local files are saves - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public File getHomeDirectory() { return (File) items.get("homeDirectory").get(); } /** * @return the minimal {@link Level} to log inside the log file - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Level getFileLevelBarrier() { return (Level) items.get("fileLevelBarrier").get(); } /** * @return the minimal {@link Level} to log inside the console - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Level getConsoleLevelBarrier() { return (Level) items.get("consoleLevelBarrier").get(); } /** * @return the user name - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public String getUser() { return (String) items.get("user").get(); } /** * @return the password - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public char[] getPassword() { return (char[]) items.get("password").get(); } /** * @return {@code true} if user name and password are set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; } /** * @return login credentials for the specified user name and password, without * the registration option - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public LoginCredentials getLoginCredentials() { try { diff --git a/src/main/java/envoy/client/data/LocalDb.java b/src/main/java/envoy/client/data/LocalDb.java index dd1db3a..ace19f9 100644 --- a/src/main/java/envoy/client/data/LocalDb.java +++ b/src/main/java/envoy/client/data/LocalDb.java @@ -16,7 +16,7 @@ import envoy.event.MessageStatusChangeEvent; * Created: 3 Feb 2020
* * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public abstract class LocalDb { @@ -30,7 +30,7 @@ public abstract class LocalDb { /** * Initializes a storage space for a user-specific list of chats. * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void initializeUserStorage() {} @@ -39,7 +39,7 @@ public abstract class LocalDb { * as well. The message id generator will also be saved if present. * * @throws Exception if the saving process failed - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void save() throws Exception {} @@ -47,7 +47,7 @@ public abstract class LocalDb { * Loads all user data. * * @throws Exception if the loading process failed - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void loadUsers() throws Exception {} @@ -55,21 +55,21 @@ public abstract class LocalDb { * Loads all data of the client user. * * @throws Exception if the loading process failed - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void loadUserData() throws Exception {} /** * Loads the ID generator. Any exception thrown during this process is ignored. * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void loadIdGenerator() {} /** * @return a {@code Map} of all users stored locally with their * user names as keys - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Map getUsers() { return users; } @@ -81,7 +81,7 @@ public abstract class LocalDb { /** * @return all saved {@link Chat} objects that list the client user as the * sender - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha **/ public List getChats() { return chats; } @@ -92,55 +92,55 @@ public abstract class LocalDb { /** * @return the {@link User} who initialized the local database - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public User getUser() { return user; } /** * @param user the user to set - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void setUser(User user) { this.user = user; } /** * @return the message ID generator - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public IdGenerator getIdGenerator() { return idGenerator; } /** * @param idGenerator the message ID generator to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setIdGenerator(IdGenerator idGenerator) { this.idGenerator = idGenerator; } /** * @return {@code true} if an {@link IdGenerator} is present - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public boolean hasIdGenerator() { return idGenerator != null; } /** * @return the offline message cache - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Cache getMessageCache() { return messageCache; } /** * @param messageCache the offline message cache to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setMessageCache(Cache messageCache) { this.messageCache = messageCache; } /** * @return the offline status cache - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Cache getStatusCache() { return statusCache; } /** * @param statusCache the offline status cache to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setStatusCache(Cache statusCache) { this.statusCache = statusCache; } @@ -150,7 +150,7 @@ public abstract class LocalDb { * @param id the ID of the message to search for * @return the message with the corresponding ID, or {@code null} if no message * has been found - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Message getMessage(long id) { for (Chat c : chats) diff --git a/src/main/java/envoy/client/data/PersistentLocalDb.java b/src/main/java/envoy/client/data/PersistentLocalDb.java index 52d5c63..2bfb23e 100644 --- a/src/main/java/envoy/client/data/PersistentLocalDb.java +++ b/src/main/java/envoy/client/data/PersistentLocalDb.java @@ -19,7 +19,7 @@ import envoy.util.SerializationUtils; * * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public class PersistentLocalDb extends LocalDb { @@ -32,7 +32,7 @@ public class PersistentLocalDb extends LocalDb { * This constructor shall be used in conjunction with the {@code ignoreLocalDB} * {@link ConfigItem}. * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public PersistentLocalDb() {} @@ -42,7 +42,7 @@ public class PersistentLocalDb extends LocalDb { * * @param localDbDir the directory in which to store users and chats * @throws IOException if the PersistentLocalDb could not be initialized - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public PersistentLocalDb(File localDbDir) throws IOException { localDBDir = localDbDir; @@ -58,7 +58,7 @@ public class PersistentLocalDb extends LocalDb { * Creates a database file for a user-specific list of chats. * * @throws NullPointerException if the client user is not yet specified - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ @Override public void initializeUserStorage() { diff --git a/src/main/java/envoy/client/data/Settings.java b/src/main/java/envoy/client/data/Settings.java index fd0170c..5bff58c 100644 --- a/src/main/java/envoy/client/data/Settings.java +++ b/src/main/java/envoy/client/data/Settings.java @@ -22,7 +22,7 @@ import envoy.util.SerializationUtils; * @author Leon Hofmeister * @author Maximilian Käfer * @author Kai S. K. Engelbart - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class Settings { @@ -49,7 +49,7 @@ public class Settings { * The way to instantiate the settings. * Is set to private to deny other instances of that object. * - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ private Settings() { // Load settings from settings file @@ -81,7 +81,7 @@ public class Settings { * This method is used to ensure that there is only one instance of Settings. * * @return the instance of Settings - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public static Settings getInstance() { return settings; } @@ -90,7 +90,7 @@ public class Settings { * * @throws IOException if an error occurs while saving the themes to the theme * file - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void save() throws IOException { // Save settings to settings file @@ -110,19 +110,19 @@ public class Settings { * Adds new theme to the theme map. * * @param theme the {@link Theme} to add - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void addNewThemeToMap(Theme theme) { getThemes().put(theme.getThemeName(), theme); } /** * @return the name of the currently active {@link Theme} - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public String getCurrentThemeName() { return (String) items.get("currentTheme").get(); } /** * @return the currently active {@link Theme} - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Theme getCurrentTheme() { return getTheme(getCurrentThemeName()); } @@ -130,7 +130,7 @@ public class Settings { * Sets the name of the current {@link Theme}. * * @param themeName the name to set - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void setCurrentTheme(String themeName) { ((SettingsItem) items.get("currentTheme")).set(themeName); } @@ -138,7 +138,7 @@ public class Settings { * @return {@code true}, if pressing the {@code Enter} key suffices to send a * message. Otherwise it has to be pressed in conjunction with the * {@code Control} key. - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); } @@ -148,13 +148,13 @@ public class Settings { * @param enterToSend If set to {@code true} a message can be sent by pressing * the {@code Enter} key. Otherwise it has to be pressed in * conjunction with the {@code Control} key. - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void setEnterToSend(boolean enterToSend) { ((SettingsItem) items.get("enterToSend")).set(enterToSend); } /** * @return the current on close mode. - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); } @@ -162,7 +162,7 @@ public class Settings { * Sets the current on close mode. * * @param currentOnCloseMode the on close mode that should be set. - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem) items.get("onCloseMode")).set(currentOnCloseMode); } @@ -178,7 +178,7 @@ public class Settings { /** * @return a {@code Map} of all themes with their names as keys - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Map getThemes() { return themes; } @@ -186,14 +186,14 @@ public class Settings { * Sets the {@code Map} of all themes with their names as keys * * @param themes the theme map to set - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void setThemes(Map themes) { this.themes = themes; } /** * @param themeName the name of the {@link Theme} to get * @return the {@link Theme} with the specified name - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Theme getTheme(String themeName) { return themes.get(themeName); } } diff --git a/src/main/java/envoy/client/data/SettingsItem.java b/src/main/java/envoy/client/data/SettingsItem.java index cd85c71..7b6eea6 100644 --- a/src/main/java/envoy/client/data/SettingsItem.java +++ b/src/main/java/envoy/client/data/SettingsItem.java @@ -19,7 +19,7 @@ import envoy.client.ui.primary.PrimaryToggleSwitch; * * @param the type of this {@link SettingsItem}'s value * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class SettingsItem implements Serializable { @@ -45,7 +45,7 @@ public class SettingsItem implements Serializable { * @param value the default value * @param userFriendlyName the user friendly name (short) * @param description the description (long) - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public SettingsItem(T value, String userFriendlyName, String description) { this(value, componentClasses.get(value.getClass())); @@ -61,7 +61,7 @@ public class SettingsItem implements Serializable { * * @param value the default value * @param componentClass the class of the {@link JComponent} to represent this {@link SettingsItem} with - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public SettingsItem(T value, Class componentClass) { this.value = value; @@ -72,7 +72,7 @@ public class SettingsItem implements Serializable { * @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 v0.3-alpha + * @since Envoy Client v0.3-alpha */ public JComponent getComponent() throws ReflectiveOperationException, SecurityException { if (componentClass == null) throw new NullPointerException("Component class is null"); @@ -81,7 +81,7 @@ public class SettingsItem implements Serializable { /** * @return the value - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public T get() { return value; } @@ -90,7 +90,7 @@ public class SettingsItem implements Serializable { * defined, it will be invoked with this value. * * @param value the value to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void set(T value) { if (changeHandler != null && value != this.value) changeHandler.accept(value); @@ -99,37 +99,37 @@ public class SettingsItem implements Serializable { /** * @return the componentClass - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Class getComponentClass() { return componentClass; } /** * @param componentClass the componentClass to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setComponentClass(Class componentClass) { this.componentClass = componentClass; } /** * @return the userFriendlyName - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public String getUserFriendlyName() { return userFriendlyName; } /** * @param userFriendlyName the userFriendlyName to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setUserFriendlyName(String userFriendlyName) { this.userFriendlyName = userFriendlyName; } /** * @return the description - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public String getDescription() { return description; } /** * @param description the description to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setDescription(String description) { this.description = description; } @@ -139,7 +139,7 @@ public class SettingsItem implements Serializable { * when the value changes. * * @param changeHandler the changeHandler to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setChangeHandler(Consumer changeHandler) { this.changeHandler = changeHandler; diff --git a/src/main/java/envoy/client/data/TransientLocalDb.java b/src/main/java/envoy/client/data/TransientLocalDb.java index 433488a..961dfcc 100644 --- a/src/main/java/envoy/client/data/TransientLocalDb.java +++ b/src/main/java/envoy/client/data/TransientLocalDb.java @@ -9,7 +9,7 @@ package envoy.client.data; * Created: 3 Feb 2020
* * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class TransientLocalDb extends LocalDb { } diff --git a/src/main/java/envoy/client/data/package-info.java b/src/main/java/envoy/client/data/package-info.java index 3129027..3455f5d 100644 --- a/src/main/java/envoy/client/data/package-info.java +++ b/src/main/java/envoy/client/data/package-info.java @@ -4,6 +4,6 @@ * @author Kai S. K. Engelbart * @author Leon Hofmeister * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.data; diff --git a/src/main/java/envoy/client/event/HandshakeSuccessfulEvent.java b/src/main/java/envoy/client/event/HandshakeSuccessfulEvent.java index 9cafa48..5fdb307 100644 --- a/src/main/java/envoy/client/event/HandshakeSuccessfulEvent.java +++ b/src/main/java/envoy/client/event/HandshakeSuccessfulEvent.java @@ -10,7 +10,7 @@ import envoy.event.Event; * Created: 8 Feb 2020
* * @author Leon Hofmeister - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class HandshakeSuccessfulEvent extends Event.Valueless { diff --git a/src/main/java/envoy/client/event/MessageCreationEvent.java b/src/main/java/envoy/client/event/MessageCreationEvent.java index 4781943..14e95e0 100644 --- a/src/main/java/envoy/client/event/MessageCreationEvent.java +++ b/src/main/java/envoy/client/event/MessageCreationEvent.java @@ -9,7 +9,7 @@ import envoy.event.Event; * Created: 4 Dec 2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class MessageCreationEvent extends Event { diff --git a/src/main/java/envoy/client/event/MessageModificationEvent.java b/src/main/java/envoy/client/event/MessageModificationEvent.java index 8ddaaf0..0deb536 100644 --- a/src/main/java/envoy/client/event/MessageModificationEvent.java +++ b/src/main/java/envoy/client/event/MessageModificationEvent.java @@ -9,7 +9,7 @@ import envoy.event.Event; * Created: 4 Dec 2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class MessageModificationEvent extends Event { diff --git a/src/main/java/envoy/client/event/SendEvent.java b/src/main/java/envoy/client/event/SendEvent.java index 2d95fc8..5d58f3e 100644 --- a/src/main/java/envoy/client/event/SendEvent.java +++ b/src/main/java/envoy/client/event/SendEvent.java @@ -9,7 +9,7 @@ import envoy.event.Event; * * @author: Maximilian Käfer * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class SendEvent extends Event> { diff --git a/src/main/java/envoy/client/event/ThemeChangeEvent.java b/src/main/java/envoy/client/event/ThemeChangeEvent.java index c164f72..1010927 100644 --- a/src/main/java/envoy/client/event/ThemeChangeEvent.java +++ b/src/main/java/envoy/client/event/ThemeChangeEvent.java @@ -9,7 +9,7 @@ import envoy.event.Event; * Created: 15 Dec 2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class ThemeChangeEvent extends Event { @@ -20,7 +20,7 @@ public class ThemeChangeEvent extends Event { * of the {@link Theme} currently in use * * @param theme the new currently used {@link Theme} object - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public ThemeChangeEvent(Theme theme) { super(theme); } } diff --git a/src/main/java/envoy/client/event/package-info.java b/src/main/java/envoy/client/event/package-info.java index 58383e0..6886d5c 100644 --- a/src/main/java/envoy/client/event/package-info.java +++ b/src/main/java/envoy/client/event/package-info.java @@ -4,6 +4,6 @@ * @author Kai S. K. Engelbart * @author Leon Hofmeister * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.event; diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index acd16e5..a877edd 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -30,7 +30,7 @@ import envoy.util.SerializationUtils; * @author Kai S. K. Engelbart * @author Maximilian Käfer * @author Leon Hofmeister - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public class Client implements Closeable { @@ -124,7 +124,7 @@ public class Client implements Closeable { * initialization * @throws IOException if no {@link IdGenerator} is present and none could be * requested from the server - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void initReceiver(LocalDb localDb, Cache receivedMessageCache) throws IOException { checkOnline(); @@ -181,7 +181,7 @@ public class Client implements Closeable { * * @param message the message to send * @throws IOException if the message does not reach the server - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void sendMessage(Message message) throws IOException { writeObject(message); @@ -200,7 +200,7 @@ public class Client implements Closeable { * Requests a new {@link IdGenerator} from the server. * * @throws IOException if the request does not reach the server - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void requestIdGenerator() throws IOException { logger.info("Requesting new id generator..."); @@ -210,7 +210,7 @@ public class Client implements Closeable { /** * @return a {@code Map} of all users on the server with their * user names as keys - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Map getUsers() { checkOnline(); @@ -232,7 +232,7 @@ public class Client implements Closeable { /** * @return the sender object that represents this client. - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public User getSender() { return sender; } @@ -240,7 +240,7 @@ public class Client implements Closeable { * Sets the client user which is used to send messages. * * @param sender the client user to set - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void setSender(User sender) { this.sender = sender; } @@ -251,19 +251,19 @@ public class Client implements Closeable { /** * @return {@code true} if a connection to the server could be established - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public boolean isOnline() { return online; } /** * @return the contacts of this {@link Client} - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Contacts getContacts() { return contacts; } /** * @param contacts the contacts to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void setContacts(Contacts contacts) { this.contacts = contacts; } } diff --git a/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java b/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java index 834bcc8..ce53139 100644 --- a/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java +++ b/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java @@ -14,7 +14,7 @@ import envoy.util.EnvoyLog; * Created: 4 Feb 2020
* * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class MessageStatusChangeEventProcessor implements Consumer { @@ -25,7 +25,7 @@ public class MessageStatusChangeEventProcessor implements Consumer31.12.2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class ReceivedMessageProcessor implements Consumer { diff --git a/src/main/java/envoy/client/net/Receiver.java b/src/main/java/envoy/client/net/Receiver.java index d49fd93..bdaf853 100644 --- a/src/main/java/envoy/client/net/Receiver.java +++ b/src/main/java/envoy/client/net/Receiver.java @@ -19,7 +19,7 @@ import envoy.util.SerializationUtils; * Created: 30.12.2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class Receiver extends Thread { diff --git a/src/main/java/envoy/client/net/UserStatusChangeProcessor.java b/src/main/java/envoy/client/net/UserStatusChangeProcessor.java index 69a36d4..0fffec2 100644 --- a/src/main/java/envoy/client/net/UserStatusChangeProcessor.java +++ b/src/main/java/envoy/client/net/UserStatusChangeProcessor.java @@ -12,7 +12,7 @@ import envoy.event.UserStatusChangeEvent; * Created: 2 Feb 2020
* * @author Leon Hofmeister - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class UserStatusChangeProcessor implements Consumer { @@ -20,7 +20,7 @@ public class UserStatusChangeProcessor implements Consumer6 Feb 2020
* * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class WriteProxy { @@ -36,7 +36,7 @@ public class WriteProxy { * events * @param localDb the local database used to cache messages and message status * change events - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public WriteProxy(Client client, LocalDb localDb) { this.client = client; @@ -68,7 +68,7 @@ public class WriteProxy { * Sends cached {@link Message}s and {@link MessageStatusChangeEvent}s to the * server. * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void flushCache() { // Send messages @@ -84,7 +84,7 @@ public class WriteProxy { * * @param message the message to send * @throws IOException if the message could not be sent - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void writeMessage(Message message) throws IOException { if (client.isOnline()) client.sendMessage(message); @@ -97,7 +97,7 @@ public class WriteProxy { * * @param evt the event to send * @throws IOException if the event could not be sent - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void writeMessageStatusChangeEvent(MessageStatusChangeEvent evt) throws IOException { if (client.isOnline()) client.sendEvent(evt); diff --git a/src/main/java/envoy/client/net/package-info.java b/src/main/java/envoy/client/net/package-info.java index 5ee0e4e..9c2a79e 100644 --- a/src/main/java/envoy/client/net/package-info.java +++ b/src/main/java/envoy/client/net/package-info.java @@ -4,6 +4,6 @@ * @author Kai S. K. Engelbart * @author Leon Hofmeister * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.net; diff --git a/src/main/java/envoy/client/ui/Color.java b/src/main/java/envoy/client/ui/Color.java index eabdac5..bb89943 100644 --- a/src/main/java/envoy/client/ui/Color.java +++ b/src/main/java/envoy/client/ui/Color.java @@ -11,7 +11,7 @@ import java.awt.color.ColorSpace; * Created: 23.12.2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ @SuppressWarnings("javadoc") public class Color extends java.awt.Color { @@ -102,13 +102,13 @@ public class Color extends java.awt.Color { /** * @return the inversion of this {@link Color} by replacing the red, green and * blue values by subtracting them form 255 - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public Color invert() { return new Color(255 - getRed(), 255 - getGreen(), 255 - getBlue()); } /** * @return the hex value of this {@link Color} - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public String toHex() { return String.format("#%02x%02x%02x", getRed(), getGreen(), getBlue()); } } diff --git a/src/main/java/envoy/client/ui/ContextMenu.java b/src/main/java/envoy/client/ui/ContextMenu.java index ff19ac8..c184eb5 100644 --- a/src/main/java/envoy/client/ui/ContextMenu.java +++ b/src/main/java/envoy/client/ui/ContextMenu.java @@ -24,7 +24,7 @@ import javax.swing.*; * Created: 17 Mar 2020
* * @author Leon Hofmeister - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class ContextMenu extends JPopupMenu { @@ -54,7 +54,7 @@ public class ContextMenu extends JPopupMenu { /** * @param parent the component which will call this * {@link ContextMenu} - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu(Component parent) { setInvoker(parent); } @@ -72,7 +72,7 @@ public class ContextMenu extends JPopupMenu { * @param itemMnemonics the keyboard shortcuts that need to be pressed to * automatically execute the {@link JMenuItem} with the * given text - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons, Map itemMnemonics) { @@ -89,7 +89,7 @@ public class ContextMenu extends JPopupMenu { * * @return this instance of {@link ContextMenu} to allow chaining behind the * constructor - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu build() { items.forEach((text, action) -> { @@ -119,7 +119,7 @@ public class ContextMenu extends JPopupMenu { /** * @param label the string that a UI may use to display as a title for the * pop-up menu. - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu(String label) { super(label); } @@ -149,7 +149,7 @@ public class ContextMenu extends JPopupMenu { /** * Removes all subcomponents of this menu. * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void clear() { removeAll(); @@ -160,33 +160,33 @@ public class ContextMenu extends JPopupMenu { /** * @return the items - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Map getItems() { return items; } /** * @param items the items with the displayed text and the according action to * take once called - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void setItems(Map items) { this.items = items; } /** * @return the icons - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Map getIcons() { return icons; } /** * @param icons the icons to set - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void setIcons(Map icons) { this.icons = icons; } /** * @return the mnemonics (the keyboard shortcuts that automatically execute the * command for a {@link JMenuItem} with corresponding text) - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Map getMnemonics() { return mnemonics; } @@ -194,7 +194,7 @@ public class ContextMenu extends JPopupMenu { * @param mnemonics the keyboard shortcuts that need to be pressed to * automatically execute the {@link JMenuItem} with the given * text - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void setMnemonics(Map mnemonics) { this.mnemonics = mnemonics; } } diff --git a/src/main/java/envoy/client/ui/IconUtil.java b/src/main/java/envoy/client/ui/IconUtil.java index 25d6513..0092483 100644 --- a/src/main/java/envoy/client/ui/IconUtil.java +++ b/src/main/java/envoy/client/ui/IconUtil.java @@ -17,7 +17,7 @@ import javax.swing.ImageIcon; * Created: 16.03.2020 * * @author Kai S. K. Engelbart - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class IconUtil { @@ -30,7 +30,7 @@ public class IconUtil { * @param size the size to scale the icon to * @return the scaled icon * @throws IOException if the loading process failed - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public static ImageIcon load(String path, int size) throws IOException { return new ImageIcon(ImageIO.read(IconUtil.class.getResourceAsStream(path)).getScaledInstance(size, size, Image.SCALE_SMOOTH)); @@ -49,7 +49,7 @@ public class IconUtil { * @return a map containing the loaded icons with the corresponding enum * constants as keys * @throws IOException if the loading process failed - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public static > EnumMap loadByEnum(Class enumClass, int size) throws IOException { var icons = new EnumMap(enumClass); diff --git a/src/main/java/envoy/client/ui/StatusTrayIcon.java b/src/main/java/envoy/client/ui/StatusTrayIcon.java index 02e5629..621655f 100644 --- a/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -16,7 +16,7 @@ import envoy.exception.EnvoyException; * Created: 3 Dec 2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class StatusTrayIcon { @@ -41,7 +41,7 @@ public class StatusTrayIcon { * notifications are displayed * @throws EnvoyException if the currently used OS does not support the System * Tray API - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public StatusTrayIcon(Window focusTarget) throws EnvoyException { if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported."); @@ -85,7 +85,7 @@ public class StatusTrayIcon { * * @throws EnvoyException if the status icon could not be attaches to the system * tray for system-internal reasons - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void show() throws EnvoyException { try { diff --git a/src/main/java/envoy/client/ui/Theme.java b/src/main/java/envoy/client/ui/Theme.java index 9fb8726..96d8357 100755 --- a/src/main/java/envoy/client/ui/Theme.java +++ b/src/main/java/envoy/client/ui/Theme.java @@ -10,7 +10,7 @@ import java.util.Map; * Created: 23 Nov 2019
* * @author Maximilian Käfer - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class Theme implements Serializable { @@ -35,7 +35,7 @@ public class Theme implements Serializable { * @param selectionColor the section color * @param typingMessageColor the color of currently typed messages * @param userNameColor the color of user names - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Theme(String themeName, Color backgroundColor, Color cellColor, Color interactableForegroundColor, Color interactableBackgroundColor, Color textColor, Color dateColorChat, Color selectionColor, Color typingMessageColor, Color userNameColor) { @@ -59,7 +59,7 @@ public class Theme implements Serializable { * * @param name the name of the {@link Theme} * @param other the {@link Theme} to copy - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Theme(String name, Theme other) { themeName = name; @@ -69,69 +69,69 @@ public class Theme implements Serializable { /** * @return a {@code Map} of all colors defined for this theme * with their names as keys - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Map getColors() { return colors; } /** * @return name of the theme - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public String getThemeName() { return themeName; } /** * @return interactableForegroundColor - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getInteractableForegroundColor() { return colors.get("interactableForegroundColor"); } /** * @return the {@link Color} in which the text content of a message should be * displayed - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getTextColor() { return colors.get("textColor"); } /** * @return the {@link Color} in which the creation date of a message should be * displayed - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getDateColor() { return colors.get("dateColorChat"); } /** * @return selectionColor - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getSelectionColor() { return colors.get("selectionColor"); } /** * @return typingMessageColor - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getTypingMessageColor() { return colors.get("typingMessageColor"); } /** * @return backgroundColor - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getBackgroundColor() { return colors.get("backgroundColor"); } /** * @return cellColor - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getCellColor() { return colors.get("cellColor"); } /** * @return interactableBackgroundColor - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getInteractableBackgroundColor() { return colors.get("interactableBackgroundColor"); } /** * @return userNameColor - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public Color getUserNameColor() { return colors.get("userNameColor"); } diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index 4d92a03..81b155c 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -49,7 +49,7 @@ import envoy.util.EnvoyLog; * @author Kai S. K. Engelbart * @author Maximilian Käfer * @author Leon Hofmeister - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public class ChatWindow extends JFrame { @@ -103,7 +103,7 @@ public class ChatWindow extends JFrame { * Initializes a {@link JFrame} with UI elements used to send and read messages * to different users. * - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public ChatWindow() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -481,7 +481,7 @@ public class ChatWindow extends JFrame { * Used to immediately reload the {@link ChatWindow} when settings were changed. * * @param theme the theme to change colors into - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ private void applyTheme(Theme theme) { // contentPane @@ -532,7 +532,7 @@ public class ChatWindow extends JFrame { /** * Sends a new message to the server based on the text entered in the textArea. * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private void postMessage() { if (userList.isSelectionEmpty()) { @@ -557,7 +557,7 @@ public class ChatWindow extends JFrame { * * @param message the message to forward * @param recipient the new recipient of the message - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private void forwardMessage(Message message, User... recipients) { Arrays.stream(recipients).forEach(recipient -> { @@ -576,7 +576,7 @@ public class ChatWindow extends JFrame { * Sends a {@link Message} to the server. * * @param message the message to send - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private void sendMessage(final Message message) { try { @@ -639,7 +639,7 @@ public class ChatWindow extends JFrame { * @param writeProxy the write proxy used to send messages and status change * events to the server or cache them inside the local * database - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void initContent(Client client, LocalDb localDb, WriteProxy writeProxy) { this.client = client; @@ -668,7 +668,7 @@ public class ChatWindow extends JFrame { * {@link ChatWindow#MAX_MESSAGE_LENGTH} * and splits the text into the allowed part, if that is the case. * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private void checkMessageTextLength() { String input = messageEnterTextArea.getText(); diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index 3aa44cd..26a099e 100755 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -30,7 +30,7 @@ import envoy.data.User; * Created: 15 Mar 2020
* * @author Leon Hofmeister - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class ContactsChooserDialog extends JDialog { @@ -64,7 +64,7 @@ public class ContactsChooserDialog extends JDialog { * @param users the users that should be displayed * @return the selected Element (yet has to be casted to the wanted type due to * the Generics limitations in Java) - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public static List showForwardingDialog(String title, Component parent, Message message, Collection users) { ContactsChooserDialog dialog = new ContactsChooserDialog(parent); @@ -89,7 +89,7 @@ public class ContactsChooserDialog extends JDialog { /** * @param parent this @{@link Component} will be parsed to * {@link java.awt.Window#setLocationRelativeTo(Component)} - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private ContactsChooserDialog(Component parent) { contactList.setSelectionMode(SelectionMode.MULTIPLE); @@ -138,7 +138,7 @@ public class ContactsChooserDialog extends JDialog { /** * @return the underlying {@link ComponentList} - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private ComponentList getContactList() { return contactList; } diff --git a/src/main/java/envoy/client/ui/container/ContextMenu.java b/src/main/java/envoy/client/ui/container/ContextMenu.java index 4624223..c52c042 100755 --- a/src/main/java/envoy/client/ui/container/ContextMenu.java +++ b/src/main/java/envoy/client/ui/container/ContextMenu.java @@ -28,7 +28,7 @@ import envoy.client.ui.Theme; * Created: 17 Mar 2020
* * @author Leon Hofmeister - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class ContextMenu extends JPopupMenu { @@ -59,7 +59,7 @@ public class ContextMenu extends JPopupMenu { /** * @param parent the component which will call this * {@link ContextMenu} - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu(Component parent) { setInvoker(parent); @@ -80,7 +80,7 @@ public class ContextMenu extends JPopupMenu { * @param itemMnemonics the keyboard shortcuts that need to be pressed to * automatically execute the {@link JMenuItem} with the * given text - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons, Map itemMnemonics) { @@ -94,7 +94,7 @@ public class ContextMenu extends JPopupMenu { /** * @param label the string that a UI may use to display as a title for the * pop-up menu. - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu(String label) { super(label); @@ -107,7 +107,7 @@ public class ContextMenu extends JPopupMenu { * * @return this instance of {@link ContextMenu} to allow chaining behind the * constructor - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContextMenu build() { items.forEach((text, action) -> { @@ -162,7 +162,7 @@ public class ContextMenu extends JPopupMenu { /** * Removes all subcomponents of this menu. * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void clear() { removeAll(); @@ -173,33 +173,33 @@ public class ContextMenu extends JPopupMenu { /** * @return the items - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Map getItems() { return items; } /** * @param items the items with the displayed text and the according action to * take once called - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void setItems(Map items) { this.items = items; } /** * @return the icons - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Map getIcons() { return icons; } /** * @param icons the icons to set - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void setIcons(Map icons) { this.icons = icons; } /** * @return the mnemonics (the keyboard shortcuts that automatically execute the * command for a {@link JMenuItem} with corresponding text) - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Map getMnemonics() { return mnemonics; } @@ -207,7 +207,7 @@ public class ContextMenu extends JPopupMenu { * @param mnemonics the keyboard shortcuts that need to be pressed to * automatically execute the {@link JMenuItem} with the given * text - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void setMnemonics(Map mnemonics) { this.mnemonics = mnemonics; } @@ -216,7 +216,7 @@ public class ContextMenu extends JPopupMenu { * Additionally sets the foreground of all subcomponents of this * {@link ContextMenu}. * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ @Override public void setForeground(Color color) { @@ -230,7 +230,7 @@ public class ContextMenu extends JPopupMenu { * Additionally sets the background of all subcomponents of this * {@link ContextMenu}. * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ @Override public void setBackground(Color color) { @@ -246,7 +246,7 @@ public class ContextMenu extends JPopupMenu { * Envoy-exclusive object. * * @param theme the theme to use - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ protected void applyTheme(Theme theme) { setBackground(theme.getCellColor()); diff --git a/src/main/java/envoy/client/ui/container/LoginDialog.java b/src/main/java/envoy/client/ui/container/LoginDialog.java index 8780358..23c0113 100644 --- a/src/main/java/envoy/client/ui/container/LoginDialog.java +++ b/src/main/java/envoy/client/ui/container/LoginDialog.java @@ -33,7 +33,7 @@ import envoy.util.EnvoyLog; * * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class LoginDialog extends JDialog { @@ -74,7 +74,7 @@ public class LoginDialog extends JDialog { * @param localDb the local database in which data is persisted * @param receivedMessageCache the cache that stored messages received during * the handshake - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public LoginDialog(Client client, LocalDb localDb, Cache receivedMessageCache) { this.client = client; @@ -283,7 +283,7 @@ public class LoginDialog extends JDialog { /** * Resets the text stored in the password fields. * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ private void clearPasswordFields() { passwordField.setText(null); @@ -337,7 +337,7 @@ public class LoginDialog extends JDialog { /** * Shuts the system down properly if the login was aborted. * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private void abortLogin() { logger.info("The login process has been cancelled. Exiting..."); diff --git a/src/main/java/envoy/client/ui/container/package-info.java b/src/main/java/envoy/client/ui/container/package-info.java index 27645f4..aab40a5 100644 --- a/src/main/java/envoy/client/ui/container/package-info.java +++ b/src/main/java/envoy/client/ui/container/package-info.java @@ -8,6 +8,6 @@ * @author Leon Hofmeister * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.ui.container; diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index c8ff3c0..f2de2c3 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -19,7 +19,7 @@ import javax.swing.*; * * @param the type of object displayed in this list * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class ComponentList extends JPanel { @@ -34,7 +34,7 @@ public class ComponentList extends JPanel { /** * Defines the possible modes of selection that can be performed by the user * - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public static enum SelectionMode { /** @@ -56,7 +56,7 @@ public class ComponentList extends JPanel { /** * Creates an instance of {@link ComponentList}. * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); } @@ -64,7 +64,7 @@ public class ComponentList extends JPanel { * Removes all child components and then adds all components representing the * elements of the {@link Model}. * - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void synchronizeModel() { if (model != null) { @@ -79,7 +79,7 @@ public class ComponentList extends JPanel { * removed from the selection. * * @param index the index of the selected component - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void selectElement(int index) { final JComponent element = getComponent(index); @@ -112,7 +112,7 @@ public class ComponentList extends JPanel { /** * Removes the current selection. * - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public void clearSelection() { if (selectionHandler != null) selection.forEach(i -> selectionHandler.selectionChanged(model.get(i), getComponent(i), false)); @@ -124,7 +124,7 @@ public class ComponentList extends JPanel { * {@link Renderer}. * * @param elem the element to add - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ void addElement(E elem) { if (renderer != null) { @@ -140,7 +140,7 @@ public class ComponentList extends JPanel { * @return a mouse listener calling the * {@link ComponentList#selectElement(int)} method with the * component's index when a left click is performed by the user - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ private MouseListener getSelectionListener(int componentIndex) { return new MouseAdapter() { @@ -155,13 +155,13 @@ public class ComponentList extends JPanel { /** * @return a set of all selected indices - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Set getSelection() { return selection; } /** * @return a set of all selected elements - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Set getSelectedElements() { var selectedElements = new HashSet(); @@ -172,20 +172,20 @@ public class ComponentList extends JPanel { /** * @return the index of an arbitrary selected element * @throws java.util.NoSuchElementException if no selection is present - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public int getSingleSelection() { return selection.stream().findAny().get(); } /** * @return an arbitrary selected element * @throws java.util.NoSuchElementException if no selection is present - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public E getSingleSelectedElement() { return model.get(getSingleSelection()); } /** * @return the model - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Model getModel() { return model; } @@ -196,7 +196,7 @@ public class ComponentList extends JPanel { * * @param model the list model to set * @return this component list - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public ComponentList setModel(Model model) { @@ -213,14 +213,14 @@ public class ComponentList extends JPanel { /** * @return the renderer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public Renderer getRenderer() { return renderer; } /** * @param renderer the renderer to set * @return this component list - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ComponentList setRenderer(Renderer renderer) { this.renderer = renderer; @@ -229,7 +229,7 @@ public class ComponentList extends JPanel { /** * @return the selection mode - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public SelectionMode getSelectionMode() { return selectionMode; } @@ -239,7 +239,7 @@ public class ComponentList extends JPanel { * * @param selectionMode the selection mode to set * @return this component list - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ComponentList setSelectionMode(SelectionMode selectionMode) { this.selectionMode = selectionMode; @@ -249,13 +249,13 @@ public class ComponentList extends JPanel { /** * @return the selection handler - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public SelectionHandler getSelectionHandler() { return selectionHandler; } /** * @param selectionHandler the selection handler to set - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public void setSelectionHandler(SelectionHandler selectionHandler) { this.selectionHandler = selectionHandler; } } diff --git a/src/main/java/envoy/client/ui/list/Model.java b/src/main/java/envoy/client/ui/list/Model.java index 6dca5eb..4f13636 100644 --- a/src/main/java/envoy/client/ui/list/Model.java +++ b/src/main/java/envoy/client/ui/list/Model.java @@ -14,7 +14,7 @@ import java.util.List; * * @param the type of object displayed in this list * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public final class Model implements Iterable, Serializable { @@ -30,7 +30,7 @@ public final class Model implements Iterable, Serializable { * @param e the element to add * @return {@code true} * @see java.util.List#add(java.lang.Object) - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public boolean add(E e) { if (componentList != null) { @@ -45,7 +45,7 @@ public final class Model implements Iterable, Serializable { * {@link ComponentList}. * * @see java.util.List#clear() - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public void clear() { elements.clear(); @@ -56,7 +56,7 @@ public final class Model implements Iterable, Serializable { * @param index the index to retrieve the element from * @return the element located at the index * @see java.util.List#get(int) - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public E get(int index) { return elements.get(index); } @@ -67,7 +67,7 @@ public final class Model implements Iterable, Serializable { * @param index the index of the element to remove * @return the removed element * @see java.util.List#remove(int) - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public E remove(int index) { if (componentList != null) componentList.remove(index); @@ -77,7 +77,7 @@ public final class Model implements Iterable, Serializable { /** * @return the amount of elements in this list model * @see java.util.List#size() - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public int size() { return elements.size(); } @@ -90,7 +90,7 @@ public final class Model implements Iterable, Serializable { /** * @return an iterator over the elements of this list model * @see java.util.List#iterator() - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ @Override public Iterator iterator() { @@ -111,7 +111,7 @@ public final class Model implements Iterable, Serializable { * synchronization. * * @param componentList the component list to set - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ void setComponentList(ComponentList componentList) { this.componentList = componentList; diff --git a/src/main/java/envoy/client/ui/list/Renderer.java b/src/main/java/envoy/client/ui/list/Renderer.java index 58e6ad9..f2d3ad9 100644 --- a/src/main/java/envoy/client/ui/list/Renderer.java +++ b/src/main/java/envoy/client/ui/list/Renderer.java @@ -12,7 +12,7 @@ import javax.swing.JComponent; * * @param the type of object displayed in this list * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ @FunctionalInterface public interface Renderer { @@ -25,7 +25,7 @@ public interface Renderer { * @param isSelected {@code true} if the user has selected the list cell in * which the list element is rendered * @return the component representing the list element - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ JComponent getListCellComponent(ComponentList list, E value); } diff --git a/src/main/java/envoy/client/ui/list/SelectionHandler.java b/src/main/java/envoy/client/ui/list/SelectionHandler.java index 998bee2..d8c1984 100644 --- a/src/main/java/envoy/client/ui/list/SelectionHandler.java +++ b/src/main/java/envoy/client/ui/list/SelectionHandler.java @@ -11,7 +11,7 @@ import javax.swing.JComponent; * * @author Kai S. K. Engelbart * @param the type of the underlying {@link ComponentList} - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ @FunctionalInterface public interface SelectionHandler { @@ -22,7 +22,7 @@ public interface SelectionHandler { * @param element the selected element * @param component the selected component * @param isSelected contains the selection state - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ void selectionChanged(E element, JComponent component, boolean isSelected); } diff --git a/src/main/java/envoy/client/ui/list/package-info.java b/src/main/java/envoy/client/ui/list/package-info.java index 5f12ffd..50d553b 100644 --- a/src/main/java/envoy/client/ui/list/package-info.java +++ b/src/main/java/envoy/client/ui/list/package-info.java @@ -5,6 +5,6 @@ * @author Kai S. K. Engelbart * @author Leon Hofmeister * @author Maximilian Käfer - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ package envoy.client.ui.list; diff --git a/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java b/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java index 16acaa4..9f66506 100644 --- a/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java +++ b/src/main/java/envoy/client/ui/list_component/ContactSearchComponent.java @@ -20,7 +20,7 @@ import envoy.event.EventBus; * Created: 21.03.2020 * * @author Kai S. K. Engelbart - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class ContactSearchComponent extends JComponent { @@ -29,7 +29,7 @@ public class ContactSearchComponent extends JComponent { /** * @param list the {@link ComponentList} that is used to display search results * @param user the {@link User} that appears as a search result - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public ContactSearchComponent(ComponentList list, User user) { setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); diff --git a/src/main/java/envoy/client/ui/list_component/MessageComponent.java b/src/main/java/envoy/client/ui/list_component/MessageComponent.java index 4b6ca83..abe436c 100644 --- a/src/main/java/envoy/client/ui/list_component/MessageComponent.java +++ b/src/main/java/envoy/client/ui/list_component/MessageComponent.java @@ -22,7 +22,7 @@ import envoy.data.User; * Created: 21.03.2020 * * @author Kai S. K. Engelbart - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class MessageComponent extends JPanel { @@ -45,7 +45,7 @@ public class MessageComponent extends JPanel { * @param message the {@link Message} to display * @param senderId the id of the {@link User} who sends messages from this * account - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public MessageComponent(ComponentList list, Message message, long senderId) { var width = list.getMaximumSize().width; diff --git a/src/main/java/envoy/client/ui/list_component/UserComponent.java b/src/main/java/envoy/client/ui/list_component/UserComponent.java index ff60222..7eebfdf 100644 --- a/src/main/java/envoy/client/ui/list_component/UserComponent.java +++ b/src/main/java/envoy/client/ui/list_component/UserComponent.java @@ -20,7 +20,7 @@ import envoy.data.User.UserStatus; * Created: 21.03.2020 * * @author Kai S. K. Engelbart - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public class UserComponent extends JPanel { @@ -28,7 +28,7 @@ public class UserComponent extends JPanel { /** * @param user the {@link User} whose information is displayed - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ public UserComponent(User user) { final Theme theme = Settings.getInstance().getCurrentTheme(); diff --git a/src/main/java/envoy/client/ui/list_component/package-info.java b/src/main/java/envoy/client/ui/list_component/package-info.java index 053d2bd..89da45c 100644 --- a/src/main/java/envoy/client/ui/list_component/package-info.java +++ b/src/main/java/envoy/client/ui/list_component/package-info.java @@ -9,6 +9,6 @@ * @author Leon Hofmeister * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.ui.list_component; diff --git a/src/main/java/envoy/client/ui/package-info.java b/src/main/java/envoy/client/ui/package-info.java index 22238af..10bf4ee 100644 --- a/src/main/java/envoy/client/ui/package-info.java +++ b/src/main/java/envoy/client/ui/package-info.java @@ -4,6 +4,6 @@ * @author Kai S. K. Engelbart * @author Leon Hofmeister * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.ui; diff --git a/src/main/java/envoy/client/ui/primary/PrimaryButton.java b/src/main/java/envoy/client/ui/primary/PrimaryButton.java index efcfd9f..811939f 100644 --- a/src/main/java/envoy/client/ui/primary/PrimaryButton.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryButton.java @@ -11,7 +11,7 @@ import javax.swing.JButton; * * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class PrimaryButton extends JButton { diff --git a/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java b/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java index 59846fa..5630525 100644 --- a/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryScrollBar.java @@ -16,7 +16,7 @@ import envoy.client.ui.Theme; * Created: 14.12.2019
* * @author Maximilian Käfer - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class PrimaryScrollBar extends BasicScrollBarUI { diff --git a/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java b/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java index 47c2abb..a27b075 100644 --- a/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryScrollPane.java @@ -22,7 +22,7 @@ public class PrimaryScrollPane extends JScrollPane { /** * Initializes a {@link JScrollPane} with the primary Envoy design scheme * - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public PrimaryScrollPane() { setBorder(null); } @@ -30,7 +30,7 @@ public class PrimaryScrollPane extends JScrollPane { * Styles the vertical and horizontal scroll bars. * * @param theme the color set used to color the component - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void applyTheme(Theme theme) { setForeground(theme.getBackgroundColor()); @@ -54,7 +54,7 @@ public class PrimaryScrollPane extends JScrollPane { * When rereading messages, the chat doesn't scroll down if new messages
* are added. (Besides see first point) * - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void autoscroll() { // Automatic scrolling to the bottom @@ -79,7 +79,7 @@ public class PrimaryScrollPane extends JScrollPane { * triggering it to automatically scroll down. * * @param chatOpened indicates the chat opening status - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public void setChatOpened(boolean chatOpened) { this.chatOpened = chatOpened; } } diff --git a/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java b/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java index 9f6a7ce..280d8e9 100644 --- a/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryTextArea.java @@ -12,7 +12,7 @@ import javax.swing.border.EmptyBorder; * Created: 07.12.2019
* * @author Maximilian Käfer - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class PrimaryTextArea extends JTextArea { diff --git a/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java b/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java index fea465f..d6931f7 100644 --- a/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java +++ b/src/main/java/envoy/client/ui/primary/PrimaryToggleSwitch.java @@ -19,7 +19,7 @@ import envoy.client.ui.Color; * * @author Maximilian Käfer * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class PrimaryToggleSwitch extends JButton { @@ -32,7 +32,7 @@ public class PrimaryToggleSwitch extends JButton { * * @param settingsItem the {@link SettingsItem} that is controlled by this * {@link PrimaryToggleSwitch} - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public PrimaryToggleSwitch(SettingsItem settingsItem) { setPreferredSize(new Dimension(50, 25)); diff --git a/src/main/java/envoy/client/ui/primary/package-info.java b/src/main/java/envoy/client/ui/primary/package-info.java index 8ede25f..d4c54fa 100644 --- a/src/main/java/envoy/client/ui/primary/package-info.java +++ b/src/main/java/envoy/client/ui/primary/package-info.java @@ -12,6 +12,6 @@ * @author Leon Hofmeister * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.ui.primary; diff --git a/src/main/java/envoy/client/ui/renderer/UserListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserListRenderer.java index 89435df..685c61b 100644 --- a/src/main/java/envoy/client/ui/renderer/UserListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/UserListRenderer.java @@ -20,7 +20,7 @@ import envoy.data.User.UserStatus; * * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public class UserListRenderer extends JLabel implements ListCellRenderer { diff --git a/src/main/java/envoy/client/ui/renderer/package-info.java b/src/main/java/envoy/client/ui/renderer/package-info.java index 68748ef..6a6f58e 100644 --- a/src/main/java/envoy/client/ui/renderer/package-info.java +++ b/src/main/java/envoy/client/ui/renderer/package-info.java @@ -9,6 +9,6 @@ * @author Leon Hofmeister * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ package envoy.client.ui.renderer; diff --git a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java index ce40e91..b194e1c 100644 --- a/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java +++ b/src/main/java/envoy/client/ui/settings/GeneralSettingsPanel.java @@ -22,7 +22,7 @@ import envoy.util.EnvoyLog; * Created: 21 Dec 2019
* * @author Maximilian Käfer - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class GeneralSettingsPanel extends SettingsPanel { @@ -38,7 +38,7 @@ public class GeneralSettingsPanel extends SettingsPanel { * * @param parent the {@link SettingsScreen} as a part of which this * {@link SettingsPanel} is displayed - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public GeneralSettingsPanel(SettingsScreen parent) { super(parent); diff --git a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java index 906bb91..6c6c273 100644 --- a/src/main/java/envoy/client/ui/settings/NewThemeScreen.java +++ b/src/main/java/envoy/client/ui/settings/NewThemeScreen.java @@ -20,7 +20,7 @@ import envoy.client.ui.primary.PrimaryTextArea; * Created: 26 Dec 2019
* * @author Maximilian Käfer - * @since Envoy v0.3-alpha + * @since Envoy Client v0.3-alpha */ public class NewThemeScreen extends JDialog { @@ -48,7 +48,7 @@ public class NewThemeScreen extends JDialog { * @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 v0.3-alpha + * @since Envoy Client v0.3-alpha */ public NewThemeScreen(SettingsScreen parent, Consumer newThemeAction, Consumer modifyThemeAction) { this.newThemeAction = newThemeAction; diff --git a/src/main/java/envoy/client/ui/settings/SettingsPanel.java b/src/main/java/envoy/client/ui/settings/SettingsPanel.java index 421b774..96be74e 100644 --- a/src/main/java/envoy/client/ui/settings/SettingsPanel.java +++ b/src/main/java/envoy/client/ui/settings/SettingsPanel.java @@ -12,7 +12,7 @@ import javax.swing.JPanel; * Created: 20 Dec 2019
* * @author Kai S. K. Engelbart - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public abstract class SettingsPanel extends JPanel { diff --git a/src/main/java/envoy/client/ui/settings/SettingsScreen.java b/src/main/java/envoy/client/ui/settings/SettingsScreen.java index 0c98084..ba93270 100644 --- a/src/main/java/envoy/client/ui/settings/SettingsScreen.java +++ b/src/main/java/envoy/client/ui/settings/SettingsScreen.java @@ -26,7 +26,7 @@ import envoy.util.EnvoyLog; * @author Leon Hofmeister * @author Maximilian Käfer * @author Kai S. K. Engelbart - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class SettingsScreen extends JDialog { @@ -51,7 +51,7 @@ public class SettingsScreen extends JDialog { /** * Initializes the settings screen. * - * @since Envoy v0.1-alpha + * @since Envoy Client v0.1-alpha */ public SettingsScreen() { // Initialize settings pages diff --git a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java index a68a6a5..fce9754 100755 --- a/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java +++ b/src/main/java/envoy/client/ui/settings/ThemeCustomizationPanel.java @@ -24,7 +24,7 @@ import envoy.util.EnvoyLog; * * @author Kai S. K. Engelbart * @author Maximilian Käfer - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public class ThemeCustomizationPanel extends SettingsPanel { @@ -48,7 +48,7 @@ public class ThemeCustomizationPanel extends SettingsPanel { * * @param parent the {@link SettingsScreen} as a part of which this * {@link SettingsPanel} is displayed - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ public ThemeCustomizationPanel(SettingsScreen parent) { super(parent); diff --git a/src/main/java/envoy/client/ui/settings/package-info.java b/src/main/java/envoy/client/ui/settings/package-info.java index bde6540..5325a73 100644 --- a/src/main/java/envoy/client/ui/settings/package-info.java +++ b/src/main/java/envoy/client/ui/settings/package-info.java @@ -4,6 +4,6 @@ * @author Kai S. K. Engelbart * @author Leon Hofmeister * @author Maximilian Käfer - * @since Envoy v0.2-alpha + * @since Envoy Client v0.2-alpha */ package envoy.client.ui.settings; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4227885..d5c969a 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -5,7 +5,7 @@ * @author Kai S. K. Engelbart * @author Leon Hofmeister * @author Maximilian Käfer - * @since Envoy v0.1-beta + * @since Envoy Client v0.1-beta */ module envoy { From 5587c9f10c754b51636e59665fbc01dd81e7cd17 Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 23 Mar 2020 21:56:01 +0100 Subject: [PATCH 32/32] Adjusted envoy-common dependency back to develop-snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1ffd41f..572b5ed 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ com.github.informatik-ag-ngl envoy-common - f~forwarding_messages-SNAPSHOT + develop-SNAPSHOT