Removed most Swing components

This commit is contained in:
Kai S. K. Engelbart 2020-03-31 21:54:56 +02:00
parent 94dbf0481b
commit 5314a12ff3
15 changed files with 0 additions and 1963 deletions

View File

@ -1,64 +0,0 @@
package envoy.client;
import java.util.logging.Logger;
import envoy.client.ui.container.ChatWindow;
import envoy.data.Config;
import envoy.util.EnvoyLog;
/**
* Starts the Envoy client and prompts the user to enter their name.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Startup.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-alpha
*/
public class Startup {
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
// TODO: Update Javadoc
/**
* Loads the application by first loading the configuration, then acquiring a
* user name and connecting to the server. If the server cannot be reached,
* offline mode is entered if possible. After that, a {@link ChatWindow}
* instance is initialized and then displayed to the user. Upon application
* exit, settings and the local database are saved.
*
* @param args the command line arguments may contain configuration parameters
* and are parsed by the {@link Config} class
* @since Envoy Client v0.1-alpha
*/
public static void main(String[] args) {
// Display ChatWindow and StatusTrayIcon
// EventQueue.invokeLater(() -> {
// try {
// chatWindow.initContent(client, localDB, writeProxy);
//
//
// try {
// new StatusTrayIcon(chatWindow).show();
//
// // If the tray icon is supported and corresponding settings is set, hide the
// // chat window on close
// Settings.getInstance()
// .getItems()
// .get("onCloseMode")
// .setChangeHandler((onCloseMode) -> chatWindow
// .setDefaultCloseOperation((Boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE :
// JFrame.EXIT_ON_CLOSE));
// } catch (EnvoyException e) {
// logger.warning("The StatusTrayIcon is not supported on this platform!");
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// });
}
}

View File

@ -1,687 +0,0 @@
package envoy.client.ui.container;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.IOException;
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;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import envoy.client.data.Chat;
import envoy.client.data.LocalDB;
import envoy.client.data.Settings;
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.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;
import envoy.client.ui.renderer.UserListRenderer;
import envoy.client.ui.settings.SettingsScreen;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.MessageBuilder;
import envoy.data.User;
import envoy.event.*;
import envoy.util.EnvoyLog;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatWindow.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy Client v0.1-alpha
*/
public class ChatWindow extends JFrame {
/**
* This integer defines the maximum amount of chars allowed per message.
*
* @since Envoy 0.1-beta
*/
public static final int MAX_MESSAGE_LENGTH = 200;
// User specific objects
private Client client;
private WriteProxy writeProxy;
private LocalDB localDB;
private Chat currentChat;
// GUI components
private JPanel contentPane = new JPanel();
private PrimaryTextArea messageEnterTextArea = new PrimaryTextArea(space);
private JList<User> userList = new JList<>();
private DefaultListModel<User> userListModel = new DefaultListModel<>();
private ComponentList<Message> messageList = new ComponentList<>();
private PrimaryScrollPane scrollPane = new PrimaryScrollPane();
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();
private JTextPane contactsDisplay = new JTextPane();
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 Model<User> contactsModel = new Model<>();
private final ComponentList<User> contactList = new ComponentList<User>().setRenderer(ContactSearchComponent::new);
private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class);
// GUI component spacing
private final static int space = 4;
private static final Insets insets = new Insets(space, space, space, space);
private static final long serialVersionUID = 0L;
/**
* Initializes a {@link JFrame} with UI elements used to send and read messages
* to different users.
*
* @since Envoy Client v0.1-alpha
*/
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")));
contentPane.setBorder(new EmptyBorder(space, space, space, space));
setContentPane(contentPane);
GridBagLayout gbl_contentPane = new GridBagLayout();
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.001 };
contentPane.setLayout(gbl_contentPane);
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
Map<String, ActionListener> commands = Map.of("forward selected message", evt -> {
final Message selectedMessage = messageList.getSingleSelectedElement();
List<User> 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 -> {});
if (isSelected) {
contextMenu = new ContextMenu(null, comp, commands, null, null).build();
contextMenu.show(comp, 0, 0);
}
});
scrollPane.setViewportView(messageList);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.addComponentListener(new ComponentAdapter() {
// Update list elements when scroll pane (and thus list) is resized
@Override
public void componentResized(ComponentEvent e) {
messageList.setMaximumSize(new Dimension(scrollPane.getWidth(), Integer.MAX_VALUE));
messageList.synchronizeModel();
}
});
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridwidth = 2;
gbc_scrollPane.gridheight = 2;
gbc_scrollPane.gridx = 1;
gbc_scrollPane.gridy = 1;
gbc_scrollPane.insets = insets;
drawChatBox(gbc_scrollPane);
// MessageEnterTextArea
messageEnterTextArea.addInputMethodListener(new InputMethodListener() {
@Override
public void inputMethodTextChanged(InputMethodEvent event) {
checkMessageTextLength();
checkPostButton(messageEnterTextArea.getText());
}
@Override
public void caretPositionChanged(InputMethodEvent event) {}
});
messageEnterTextArea.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
&& (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_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
GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints();
gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionSettingsButton.gridx = 2;
gbc_moveSelectionSettingsButton.gridy = 0;
gbc_moveSelectionSettingsButton.insets = insets;
settingsButton.addActionListener(evt -> new SettingsScreen().setVisible(true));
contentPane.add(settingsButton, gbc_moveSelectionSettingsButton);
// Partner name display
textPane.setFont(new Font("Arial", Font.PLAIN, 20));
textPane.setEditable(false);
GridBagConstraints gbc_partnerName = new GridBagConstraints();
gbc_partnerName.fill = GridBagConstraints.HORIZONTAL;
gbc_partnerName.gridx = 1;
gbc_partnerName.gridy = 0;
gbc_partnerName.insets = insets;
contentPane.add(textPane, gbc_partnerName);
userList.setCellRenderer(new UserListRenderer());
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
userList.addListSelectionListener((listSelectionEvent) -> {
if (client != null && localDB != null && !listSelectionEvent.getValueIsAdjusting()) {
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
final User user = selectedUserList.getSelectedValue();
for (int i = 0; i < contentPane.getComponents().length; i++)
if (contentPane.getComponent(i).equals(searchPane)) drawChatBox(gbc_scrollPane);
if (user != null) {
// Select current chat
currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get();
// Read current chat
readCurrentChat();
// Set chat title
textPane.setText(currentChat.getRecipient().getName());
// Update model and scroll down
// messageList.setModel(currentChat.getModel());
scrollPane.setChatOpened(true);
messageList.synchronizeModel();
revalidate();
repaint();
}
}
});
userList.setFont(new Font("Arial", Font.PLAIN, 17));
userList.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_userList = new GridBagConstraints();
gbc_userList.fill = GridBagConstraints.VERTICAL;
gbc_userList.gridx = 0;
gbc_userList.gridy = 2;
gbc_userList.gridheight = 2;
gbc_userList.anchor = GridBagConstraints.PAGE_START;
gbc_userList.insets = insets;
contentPane.add(userList, gbc_userList);
contentPane.revalidate();
// Contacts Search
GridBagConstraints gbc_searchPane = new GridBagConstraints();
gbc_searchPane.fill = GridBagConstraints.BOTH;
gbc_searchPane.gridwidth = 2;
gbc_searchPane.gridheight = 2;
gbc_searchPane.gridx = 1;
gbc_searchPane.gridy = 1;
gbc_searchPane.insets = insets;
GridBagLayout gbl_contactsSearch = new GridBagLayout();
gbl_contactsSearch.columnWidths = new int[] { 1, 1 };
gbl_contactsSearch.rowHeights = new int[] { 1, 1 };
gbl_contactsSearch.columnWeights = new double[] { 1, 0.1 };
gbl_contactsSearch.rowWeights = new double[] { 0.001, 1 };
searchPane.setLayout(gbl_contactsSearch);
GridBagConstraints gbc_searchField = new GridBagConstraints();
gbc_searchField.fill = GridBagConstraints.BOTH;
gbc_searchField.gridx = 0;
gbc_searchField.gridy = 0;
gbc_searchField.insets = new Insets(7, 4, 4, 4);
searchPane.add(searchField, gbc_searchField);
// Sends event to server, if input has changed
searchField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent evt) {
if (client.isOnline()) if (searchField.getText().isEmpty()) {
contactsModel.clear();
revalidate();
repaint();
} else try {
client.sendEvent(new ContactSearchRequest(searchField.getText()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void insertUpdate(DocumentEvent evt) {
if (client.isOnline()) try {
client.sendEvent(new ContactSearchRequest(searchField.getText()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void changedUpdate(DocumentEvent evt) {}
});
GridBagConstraints gbc_cancelButton = new GridBagConstraints();
gbc_cancelButton.fill = GridBagConstraints.BOTH;
gbc_cancelButton.gridx = 1;
gbc_cancelButton.gridy = 0;
gbc_cancelButton.insets = new Insets(7, 4, 4, 4);
cancelButton.addActionListener((evt) -> { drawChatBox(gbc_scrollPane); });
searchPane.add(cancelButton, gbc_cancelButton);
contactList.setModel(contactsModel);
scrollForPossibleContacts.setBorder(new EmptyBorder(space, space, space, space));
scrollForPossibleContacts.setViewportView(contactList);
GridBagConstraints gbc_possibleContacts = new GridBagConstraints();
gbc_possibleContacts.fill = GridBagConstraints.BOTH;
gbc_possibleContacts.gridwidth = 2;
gbc_possibleContacts.gridx = 0;
gbc_possibleContacts.gridy = 1;
gbc_possibleContacts.insets = insets;
searchPane.add(scrollForPossibleContacts, gbc_possibleContacts);
// Contacts Header
GridBagConstraints gbc_contactsHeader = new GridBagConstraints();
gbc_contactsHeader.fill = GridBagConstraints.BOTH;
gbc_contactsHeader.gridx = 0;
gbc_contactsHeader.gridy = 1;
gbc_contactsHeader.insets = insets;
GridBagLayout gbl_contactHeader = new GridBagLayout();
gbl_contactHeader.columnWidths = new int[] { 1, 1 };
gbl_contactHeader.rowHeights = new int[] { 1 };
gbl_contactHeader.columnWeights = new double[] { 1, 1 };
gbl_contactHeader.rowWeights = new double[] { 1 };
contactsHeader.setLayout(gbl_contactHeader);
contactsDisplay.setEditable(false);
contactsDisplay.setFont(new Font("Arial", Font.PLAIN, 12));
contactsDisplay.setText("Contacts");
GridBagConstraints gbc_contactsDisplay = new GridBagConstraints();
gbc_contactsDisplay.fill = GridBagConstraints.BOTH;
gbc_contactsDisplay.gridx = 0;
gbc_contactsDisplay.gridy = 0;
contactsHeader.add(contactsDisplay, gbc_contactsDisplay);
addContact.setFont(new Font("Arial", Font.PLAIN, 15));
GridBagConstraints gbc_addContact = new GridBagConstraints();
gbc_addContact.fill = GridBagConstraints.BOTH;
gbc_addContact.gridx = 1;
gbc_addContact.gridy = 0;
gbc_addContact.insets = insets;
addContact.addActionListener(evt -> drawContactSearch(gbc_searchPane));
contactsHeader.add(addContact, gbc_addContact);
applyTheme(Settings.getInstance().getCurrentTheme());
contentPane.add(contactsHeader, gbc_contactsHeader);
contentPane.revalidate();
// Listen to theme changes
EventBus.getInstance().register(ThemeChangeEvent.class, evt -> applyTheme(evt.get()));
// Listen to user status changes
EventBus.getInstance().register(UserStatusChangeEvent.class, evt -> { userList.revalidate(); userList.repaint(); });
// Listen to received messages
EventBus.getInstance().register(MessageCreationEvent.class, evt -> {
Message message = evt.get();
Chat chat = localDB.getChats().stream().filter(c -> c.getRecipient().getID() == message.getSenderID()).findFirst().get();
// chat.appendMessage(message);
// Read message and update UI if in current chat
if (chat == currentChat) readCurrentChat();
revalidate();
repaint();
});
// Listen to message status changes
EventBus.getInstance().register(MessageStatusChangeEvent.class, evt -> {
final long id = evt.getID();
final MessageStatus status = evt.get();
for (Chat c : localDB.getChats())
// for (Message m : c.getModel())
// if (m.getID() == id) {
//
// // Update message status
// m.setStatus(status);
//
// // Update model and scroll down if current chat
// if (c == currentChat) {
// messageList.setModel(currentChat.getModel());
// scrollPane.setChatOpened(true);
// } else messageList.synchronizeModel();
// }
revalidate();
repaint();
});
// Listen to contact search results
EventBus.getInstance()
.register(ContactSearchResult.class,
evt -> {
contactsModel.clear();
final java.util.List<User> contacts = evt.get();
contacts.forEach(contactsModel::add);
revalidate();
repaint();
});
// Add new contacts to the contact list
EventBus.getInstance().register(ContactOperationEvent.class, evt -> {
User contact = evt.get();
// Clearing the search field and the searchResultList
searchField.setText("");
contactsModel.clear();
// Update LocalDB
userListModel.addElement(contact);
localDB.getUsers().put(contact.getName(), contact);
localDB.getChats().add(new Chat(contact));
revalidate();
repaint();
});
revalidate();
repaint();
}
/**
* Used to immediately reload the {@link ChatWindow} when settings were changed.
*
* @param theme the theme to change colors into
* @since Envoy Client v0.2-alpha
*/
private void applyTheme(Theme theme) {
// contentPane
contentPane.setBackground(theme.getBackgroundColor());
contentPane.setForeground(theme.getUserNameColor());
// messageList
messageList.setForeground(theme.getTextColor());
messageList.setBackground(theme.getCellColor());
messageList.synchronizeModel();
// scrollPane
scrollPane.applyTheme(theme);
scrollPane.autoscroll();
// messageEnterTextArea
messageEnterTextArea.setCaretColor(theme.getTypingMessageColor());
messageEnterTextArea.setForeground(theme.getTypingMessageColor());
messageEnterTextArea.setBackground(theme.getCellColor());
// postButton
postButton.setForeground(theme.getInteractableForegroundColor());
postButton.setBackground(theme.getInteractableBackgroundColor());
// settingsButton
settingsButton.setForeground(theme.getInteractableForegroundColor());
settingsButton.setBackground(theme.getInteractableBackgroundColor());
// textPane
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getUserNameColor());
// userList
userList.setSelectionForeground(theme.getUserNameColor());
userList.setSelectionBackground(theme.getSelectionColor());
userList.setForeground(theme.getUserNameColor());
userList.setBackground(theme.getCellColor());
// contacts header
contactsHeader.setBackground(theme.getCellColor());
contactsDisplay.setBackground(theme.getCellColor());
contactsDisplay.setForeground(theme.getUserNameColor());
addContact.setBackground(theme.getInteractableBackgroundColor());
addContact.setForeground(theme.getInteractableForegroundColor());
// SearchPane
searchPane.setBackground(theme.getCellColor());
searchField.setBackground(theme.getBackgroundColor());
searchField.setForeground(theme.getUserNameColor());
cancelButton.setBackground(theme.getInteractableBackgroundColor());
cancelButton.setForeground(theme.getInteractableForegroundColor());
contactList.setForeground(theme.getTextColor());
contactList.setBackground(theme.getCellColor());
scrollForPossibleContacts.applyTheme(theme);
}
/**
* Sends a new message to the server based on the text entered in the textArea.
*
* @since Envoy Client v0.1-beta
*/
private void postMessage() {
if (userList.isSelectionEmpty()) {
JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
return;
}
String text = messageEnterTextArea.getText().trim();
if (!text.isEmpty()) checkMessageTextLength();
// Create message
final Message message = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(text)
.build();
sendMessage(message);
// Clear text field
messageEnterTextArea.setText("");
postButton.setEnabled(false);
}
/**
* Forwards a message.
*
* @param message the message to forward
* @param recipient the new recipient of the message
* @since Envoy Client v0.1-beta
*/
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");
});
}
@SuppressWarnings("unused")
private void forwardMessages(Collection<Message> messages, User... recipients) {
messages.forEach(message -> { forwardMessage(message, recipients); });
}
/**
* Sends a {@link Message} to the server.
*
* @param message the message to send
* @since Envoy Client 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);
// Update UI
revalidate();
repaint();
// Request a new id generator if all IDs were used
if (!localDB.getIDGenerator().hasNext()) client.requestIdGenerator();
} catch (Exception e) {
JOptionPane.showMessageDialog(this, "Error sending message:\n" + e.toString(), "Message sending error", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
private void readCurrentChat() {
try {
currentChat.read(writeProxy);
if (messageList.getRenderer() != null) messageList.synchronizeModel();
} catch (IOException e) {
e.printStackTrace();
logger.log(Level.WARNING, "Couldn't notify server about message status change", e);
}
}
private void drawChatBox(GridBagConstraints gbc_scrollPane) {
contentPane.remove(searchPane);
contentPane.add(scrollPane, gbc_scrollPane);
contentPane.revalidate();
contentPane.repaint();
}
private void drawContactSearch(GridBagConstraints gbc_searchPane) {
currentChat = null;
userList.removeSelectionInterval(0, userList.getModel().getSize() - 1);
messageList.setModel(null);
textPane.setText("");
contentPane.remove(scrollPane);
contentPane.add(searchPane, gbc_searchPane);
contentPane.revalidate();
contentPane.repaint();
}
/**
* Initializes the components responsible server communication and
* persistence.<br>
* <br>
* This will trigger the display of the contact list.
*
* @param client the client used to send and receive messages
* @param localDB the local database used to manage stored messages
* and users
* @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 Client v0.3-alpha
*/
public void initContent(Client client, LocalDB localDB, WriteProxy writeProxy) {
this.client = client;
this.localDB = localDB;
this.writeProxy = writeProxy;
messageList.setRenderer((list, message) -> new MessageComponent(list, message, client.getSender().getID()));
// Load users and chats
new Thread(() -> {
localDB.getUsers().values().forEach(user -> {
userListModel.addElement(user);
// Check if user exists in local DB
if (localDB.getChats().stream().noneMatch(c -> c.getRecipient().getID() == user.getID())) localDB.getChats().add(new Chat(user));
});
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
revalidate();
repaint();
}).start();
}
/**
* Checks whether the length of the text inside messageEnterTextArea >=
* {@link ChatWindow#MAX_MESSAGE_LENGTH}
* and splits the text into the allowed part, if that is the case.
*
* @since Envoy Client v0.1-beta
*/
private void checkMessageTextLength() {
String input = messageEnterTextArea.getText();
if (input.length() >= MAX_MESSAGE_LENGTH) {
messageEnterTextArea.setText(input.substring(0, MAX_MESSAGE_LENGTH - 1));
// TODO: current notification is like being hit with a hammer, maybe it should
// be replaced with a more subtle notification
JOptionPane.showMessageDialog(messageEnterTextArea,
"the maximum length for a message has been reached",
"maximum message length reached",
JOptionPane.WARNING_MESSAGE);
}
}
private void checkPostButton(String text) { postButton.setEnabled(!text.trim().isBlank()); }
}

View File

@ -1,145 +0,0 @@
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;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import envoy.client.data.Settings;
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;
/**
* This class defines a dialog to choose contacts from.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactsChooserDialog.java</strong><br>
* Created: <strong>15 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContactsChooserDialog extends JDialog {
private static final long serialVersionUID = 0L;
private ComponentList<User> contactList = new ComponentList<User>().setModel(new Model<User>())
.setRenderer((list, user) -> new UserComponent(user));
private JButton okButton = new JButton("Ok");
private JButton cancelButton = new JButton("Cancel");
private final Theme theme = 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
* <code>ArrayList</code>.
*
* @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 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 Client v0.1-beta
*/
public static List<User> showForwardingDialog(String title, Component parent, Message message, Collection<User> users) {
ContactsChooserDialog dialog = new ContactsChooserDialog(parent);
dialog.setTitle(title);
dialog.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
dialog.addCancelButtonActionListener(e -> dialog.dispose());
List<User> results = new ArrayList<>();
dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); });
Model<User> contactListModel = dialog.getContactList().getModel();
users.forEach(contactListModel::add);
dialog.setModalityType(ModalityType.APPLICATION_MODAL);
dialog.setVisible(true);
return results;
}
/**
* @param parent this @{@link Component} will be parsed to
* {@link java.awt.Window#setLocationRelativeTo(Component)}
* @since Envoy Client v0.1-beta
*/
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());
});
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));
contentPanel.add(contactList, BorderLayout.CENTER);
{
JPanel buttonPane = new JPanel();
getContentPane().add(buttonPane, BorderLayout.SOUTH);
{
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);
}
{
cancelButton = new JButton("Cancel");
cancelButton.setActionCommand("Cancel");
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());
}
/**
* @return the underlying {@link ComponentList}
* @since Envoy Client v0.1-beta
*/
private ComponentList<User> getContactList() { return contactList; }
private void addOkButtonActionListener(ActionListener l) { okButton.addActionListener(l); }
private void addCancelButtonActionListener(ActionListener l) { cancelButton.addActionListener(l); }
}

View File

@ -1,255 +0,0 @@
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.data.Settings;
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.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContextMenu.java</strong><br>
* Created: <strong>17 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContextMenu extends JPopupMenu {
private static final long serialVersionUID = 0L;
/**
* 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<String, ActionListener> items = new HashMap<>();
private Map<String, Icon> icons = new HashMap<>();
private Map<String, Integer> 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 Client v0.1-beta
*/
public ContextMenu(Component parent) {
setInvoker(parent);
setOpaque(true);
}
/**
* @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 Client v0.1-beta
*/
public ContextMenu(String label, Component parent, Map<String, ActionListener> itemsWithActions, Map<String, Icon> itemIcons,
Map<String, Integer> itemMnemonics) {
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 Client 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.
*
* @return this instance of {@link ContextMenu} to allow chaining behind the
* constructor
* @since Envoy Client 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);
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;
}
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 Client v0.1-beta
*/
public void clear() {
removeAll();
items = new HashMap<>();
icons = new HashMap<>();
mnemonics = new HashMap<>();
}
/**
* @return the items
* @since Envoy Client v0.1-beta
*/
public Map<String, ActionListener> getItems() { return items; }
/**
* @param items the items with the displayed text and the according action to
* take once called
* @since Envoy Client v0.1-beta
*/
public void setItems(Map<String, ActionListener> items) { this.items = items; }
/**
* @return the icons
* @since Envoy Client v0.1-beta
*/
public Map<String, Icon> getIcons() { return icons; }
/**
* @param icons the icons to set
* @since Envoy Client v0.1-beta
*/
public void setIcons(Map<String, Icon> icons) { this.icons = icons; }
/**
* @return the mnemonics (the keyboard shortcuts that automatically execute the
* command for a {@link JMenuItem} with corresponding text)
* @since Envoy Client v0.1-beta
*/
public Map<String, Integer> 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 Client v0.1-beta
*/
public void setMnemonics(Map<String, Integer> mnemonics) { this.mnemonics = mnemonics; }
/**
* {@inheritDoc}<br>
* Additionally sets the foreground of all subcomponents of this
* {@link ContextMenu}.
*
* @since Envoy Client v0.1-beta
*/
@Override
public void setForeground(Color color) {
super.setForeground(color);
for (MenuElement element : getSubElements())
((Component) element).setForeground(color);
}
/**
* {@inheritDoc}<br>
* Additionally sets the background of all subcomponents of this
* {@link ContextMenu}.
*
* @since Envoy Client 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 Client v0.1-beta
*/
protected void applyTheme(Theme theme) {
setBackground(theme.getCellColor());
setForeground(theme.getTextColor());
}
}

View File

@ -1,261 +0,0 @@
package envoy.client.ui.list;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.HashSet;
import java.util.Set;
import javax.swing.*;
/**
* Provides a vertical list layout of components provided in a
* {@link Model}. Similar to {@link javax.swing.JList} but capable
* of rendering {@link JPanel}s.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ComponentList.java</strong><br>
* Created: <strong>25.01.2020</strong><br>
*
* @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public class ComponentList<E> extends JPanel {
private Model<E> model;
private Renderer<E> renderer;
private SelectionHandler<E> selectionHandler;
private SelectionMode selectionMode = SelectionMode.NONE;
private Set<Integer> selection = new HashSet<>();
private static final long serialVersionUID = 0L;
/**
* Defines the possible modes of selection that can be performed by the user
*
* @since Envoy Client v0.1-beta
*/
public static enum SelectionMode {
/**
* Selection is completely ignored.
*/
NONE,
/**
* Only a single element can be selected.
*/
SINGLE,
/**
* Multiple elements can be selected regardless of their position.
*/
MULTIPLE
}
/**
* Creates an instance of {@link ComponentList}.
*
* @since Envoy Client v0.3-alpha
*/
public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); }
/**
* Removes all child components and then adds all components representing the
* elements of the {@link Model}.
*
* @since Envoy Client v0.3-alpha
*/
public void synchronizeModel() {
if (model != null) {
removeAll();
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 Client v0.1-beta
*/
public void selectElement(int index) {
final JComponent element = getComponent(index);
if (selection.contains(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
selection.add(index);
// Update element
if (selectionHandler != null) selectionHandler.selectionChanged(model.get(index), element, true);
}
}
revalidate();
repaint();
}
/**
* Removes the current selection.
*
* @since Envoy Client v0.1-alpha
*/
public void clearSelection() {
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 Renderer}.
*
* @param elem the element to add
* @since Envoy Client v0.3-alpha
*/
void addElement(E elem) {
if (renderer != null) {
final JComponent component = renderer.getListCellComponent(this, elem);
component.addMouseListener(getSelectionListener(getComponentCount()));
add(component, getComponentCount());
}
}
/**
* @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 Client v0.1-beta
*/
private MouseListener getSelectionListener(int componentIndex) {
return new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) selectElement(componentIndex); }
};
}
@Override
public JComponent getComponent(int n) { return (JComponent) super.getComponent(n); }
/**
* @return a set of all selected indices
* @since Envoy Client v0.1-beta
*/
public Set<Integer> getSelection() { return selection; }
/**
* @return a set of all selected elements
* @since Envoy Client v0.1-beta
*/
public Set<E> getSelectedElements() {
var selectedElements = new HashSet<E>();
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 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 Client v0.1-beta
*/
public E getSingleSelectedElement() { return model.get(getSingleSelection()); }
/**
* @return the model
* @since Envoy Client v0.1-beta
*/
public Model<E> 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 Client v0.3-alpha
*/
public ComponentList<E> setModel(Model<E> 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 Client v0.1-beta
*/
public Renderer<E> getRenderer() { return renderer; }
/**
* @param renderer the renderer to set
* @return this component list
* @since Envoy Client v0.1-beta
*/
public ComponentList<E> setRenderer(Renderer<E> renderer) {
this.renderer = renderer;
return this;
}
/**
* @return the selection mode
* @since Envoy Client v0.1-beta
*/
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 Client v0.1-beta
*/
public ComponentList<E> setSelectionMode(SelectionMode selectionMode) {
this.selectionMode = selectionMode;
clearSelection();
return this;
}
/**
* @return the selection handler
* @since Envoy Client v0.1-beta
*/
public SelectionHandler<E> getSelectionHandler() { return selectionHandler; }
/**
* @param selectionHandler the selection handler to set
* @since Envoy Client v0.1-beta
*/
public void setSelectionHandler(SelectionHandler<E> selectionHandler) { this.selectionHandler = selectionHandler; }
}

View File

@ -1,120 +0,0 @@
package envoy.client.ui.list;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Stores objects that will be displayed in a {@link ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Model.java</strong><br>
* Created: <strong>25.01.2020</strong><br>
*
* @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public final class Model<E> implements Iterable<E>, Serializable {
private List<E> elements = new ArrayList<>();
transient private ComponentList<E> componentList;
private static final long serialVersionUID = 0L;
/**
* Adds an element to this model and notifies the associated
* {@link ComponentList} to add the corresponding component.
*
* @param e the element to add
* @return {@code true}
* @see java.util.List#add(java.lang.Object)
* @since Envoy Client v0.3-alpha
*/
public boolean add(E e) {
if (componentList != null) {
componentList.addElement(e);
componentList.revalidate();
}
return elements.add(e);
}
/**
* Removes all elements from this model and clears the associated
* {@link ComponentList}.
*
* @see java.util.List#clear()
* @since Envoy Client v0.3-alpha
*/
public void clear() {
elements.clear();
if (componentList != null) componentList.removeAll();
}
/**
* @param index the index to retrieve the element from
* @return the element located at the index
* @see java.util.List#get(int)
* @since Envoy Client v0.3-alpha
*/
public E get(int index) { return elements.get(index); }
/**
* Removes the element at a specific index from this model and the corresponding
* component from the {@link ComponentList}.
*
* @param index the index of the element to remove
* @return the removed element
* @see java.util.List#remove(int)
* @since Envoy Client v0.3-alpha
*/
public E remove(int index) {
if (componentList != null) componentList.remove(index);
return elements.remove(index);
}
/**
* @return the amount of elements in this list model
* @see java.util.List#size()
* @since Envoy Client v0.3-alpha
*/
public int size() { return elements.size(); }
/**
* @return {@code true} if this model contains no elements
* @see java.util.List#isEmpty()
*/
public boolean isEmpty() { return elements.isEmpty(); }
/**
* @return an iterator over the elements of this list model
* @see java.util.List#iterator()
* @since Envoy Client v0.3-alpha
*/
@Override
public Iterator<E> iterator() {
return new Iterator<>() {
Iterator<E> iter = elements.iterator();
@Override
public boolean hasNext() { return iter.hasNext(); }
@Override
public E next() { return iter.next(); }
};
}
/**
* Sets the component list displaying the elements of this model and triggers a
* synchronization.
*
* @param componentList the component list to set
* @since Envoy Client v0.3-alpha
*/
void setComponentList(ComponentList<E> componentList) {
this.componentList = componentList;
if (componentList != null && componentList.getRenderer() != null) componentList.synchronizeModel();
}
}

View File

@ -1,31 +0,0 @@
package envoy.client.ui.list;
import javax.swing.JComponent;
/**
* Allows a {@link ComponentList} convert its elements into Swing components
* that can be rendered.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Renderer.java</strong><br>
* Created: <strong>25.01.2020</strong><br>
*
* @param <E> the type of object displayed in this list
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
@FunctionalInterface
public interface Renderer<E> {
/**
* Provides a Swing component representing a list element.
*
* @param list the list in which the component will be displayed
* @param value the list element that will be converted
* @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 Client v0.3-alpha
*/
JComponent getListCellComponent(ComponentList<? extends E> list, E value);
}

View File

@ -1,28 +0,0 @@
package envoy.client.ui.list;
import javax.swing.JComponent;
/**
* Handles the selection of elements in a {@link ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>SelectionHandler.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @param <E> the type of the underlying {@link ComponentList}
* @since Envoy Client v0.1-beta
*/
@FunctionalInterface
public interface SelectionHandler<E> {
/**
* Notifies the handler about a selection.
*
* @param element the selected element
* @param component the selected component
* @param isSelected contains the selection state
* @since Envoy Client v0.1-beta
*/
void selectionChanged(E element, JComponent component, boolean isSelected);
}

View File

@ -1,10 +0,0 @@
/**
* This package defines a Swing component that can be used to display lists of
* other components to the user.
*
* @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @since Envoy Client v0.3-alpha
*/
package envoy.client.ui.list;

View File

@ -1,73 +0,0 @@
package envoy.client.ui.list_component;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import javax.swing.*;
import envoy.client.data.Settings;
import envoy.client.event.SendEvent;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.primary.PrimaryButton;
import envoy.data.User;
import envoy.event.ContactOperationEvent;
import envoy.event.EventBus;
/**
* Project: <strong>envoy-client</strong>
* File: <strong>ContactSearchComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ContactSearchComponent extends JComponent {
private static final long serialVersionUID = 0L;
/**
* @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 Client v0.1-beta
*/
public ContactSearchComponent(ComponentList<? extends User> 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().getCurrentTheme().getTextColor());
display.setAlignmentX(Component.LEFT_ALIGNMENT);
display.setAlignmentY(Component.CENTER_ALIGNMENT);
display.setFont(new Font("Arial", Font.PLAIN, 16));
add(display);
PrimaryButton add = new PrimaryButton("+");
add.setFont(new Font("Arial", Font.PLAIN, 19));
add.setPreferredSize(new Dimension(45, 45));
add.setMinimumSize(new Dimension(45, 45));
add.setMaximumSize(new Dimension(45, 45));
add.setBackground(list.getBackground());
add.setForeground(list.getForeground());
add.addActionListener(evt -> {
ContactOperationEvent contactsOperationEvent = new ContactOperationEvent(user, ContactOperationEvent.Operation.ADD);
EventBus.getInstance().dispatch(contactsOperationEvent);
EventBus.getInstance().dispatch(new SendEvent(contactsOperationEvent));
});
add(add);
// Define some space to the messages below
setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder()));
// Define a maximum height of 50px
Dimension size = new Dimension(435, 50);
setMaximumSize(size);
setMinimumSize(size);
setPreferredSize(size);
}
}

View File

@ -1,127 +0,0 @@
package envoy.client.ui.list_component;
import java.awt.*;
import java.io.IOException;
import java.text.SimpleDateFormat;
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: <strong>envoy-client</strong>
* File: <strong>MessageComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageComponent extends JPanel {
private static final long serialVersionUID = 0L;
private static EnumMap<MessageStatus, ImageIcon> 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();
}
}
/**
* @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 Client v0.1-beta
*/
public MessageComponent(ComponentList<? extends Message> list, Message message, long senderId) {
var width = list.getMaximumSize().width;
final var theme = 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(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.getTextColor());
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);
}
}

View File

@ -1,69 +0,0 @@
package envoy.client.ui.list_component;
import java.awt.BorderLayout;
import java.awt.Dimension;
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.data.User;
import envoy.data.User.UserStatus;
/**
* Displays a {@link User}.<br>
* <br>
* Project: <strong>envoy-client</strong>
* File: <strong>UserComponent.java</strong>
* Created: <strong>21.03.2020</strong>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class UserComponent extends JPanel {
private static final long serialVersionUID = 0L;
/**
* @param user the {@link User} whose information is displayed
* @since Envoy Client v0.1-beta
*/
public UserComponent(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);
}
}

View File

@ -1,14 +0,0 @@
/**
* This package contains swing components that can be displayed by
* {@link envoy.client.ui.list.ComponentList}.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>21 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.list_component;

View File

@ -1,65 +0,0 @@
package envoy.client.ui.renderer;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.data.Settings;
import envoy.data.User;
import envoy.data.User.UserStatus;
/**
* Defines how the {@code UserList} is displayed.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-alpha
*/
public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
private static final long serialVersionUID = 5164417379767181198L;
/**
* {@inheritDoc}
*/
@Override
public Component getListCellRendererComponent(JList<? extends User> list, User value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
// Enable background rendering
setOpaque(true);
final String name = value.getName();
final UserStatus status = value.getStatus();
this.setPreferredSize(new Dimension(100, 35));
// Getting the UserNameColor of the current theme
String textColor = null;
textColor = Settings.getInstance().getCurrentTheme().getUserNameColor().toHex();
switch (status) {
case ONLINE:
setText(String
.format("<html><p style=\"color:#03fc20\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>", status, textColor, name));
break;
case OFFLINE:
setText(String
.format("<html><p style=\"color:#fc0303\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>", status, textColor, name));
break;
}
return this;
}
}

View File

@ -1,14 +0,0 @@
/**
* This package contains all Envoy-specific renderers for lists that store an
* arbitrary number of JComponents.<br>
* <br>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>14 Mar 2020</strong><br>
*
* @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.renderer;