Merge pull request #93 from informatik-ag-ngl/f/component_list
Component List
This commit is contained in:
commit
bd61936fde
@ -28,11 +28,5 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
7
pom.xml
7
pom.xml
@ -28,11 +28,16 @@
|
||||
<dependency>
|
||||
<groupId>com.github.informatik-ag-ngl</groupId>
|
||||
<artifactId>envoy-common</artifactId>
|
||||
<version>e5c67b8</version>
|
||||
<version>develop-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>envoy-client</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
</project>
|
@ -2,9 +2,9 @@ package envoy.client;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.swing.DefaultListModel;
|
||||
|
||||
import envoy.client.ui.list.ComponentListModel;
|
||||
import envoy.data.Message;
|
||||
import envoy.data.Message.MessageStatus;
|
||||
import envoy.data.User;
|
||||
|
||||
/**
|
||||
@ -25,7 +25,7 @@ public class Chat implements Serializable {
|
||||
private static final long serialVersionUID = -7751248474547242056L;
|
||||
|
||||
private User recipient;
|
||||
private DefaultListModel<Message> model = new DefaultListModel<>();
|
||||
private ComponentListModel<Message> model = new ComponentListModel<>();
|
||||
|
||||
/**
|
||||
* Provides the list of messages that the recipient receives.<br>
|
||||
@ -37,22 +37,34 @@ public class Chat implements Serializable {
|
||||
public Chat(User recipient) { this.recipient = recipient; }
|
||||
|
||||
/**
|
||||
* @return the recipient of a message
|
||||
* Appends a message to the bottom of this chat
|
||||
*
|
||||
* @param message the message to append
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
public User getRecipient() { return recipient; }
|
||||
public void appendMessage(Message message) { model.add(message); }
|
||||
|
||||
/**
|
||||
* Adds the received message at the current Point in the current chat
|
||||
* Sets the status of all chat messages to {@code READ} starting from the bottom
|
||||
* and stopping once a read message is found.
|
||||
*
|
||||
* @param message the message to add in said chat
|
||||
* @since Envoy v0.1-alpha
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
public void appendMessage(Message message) { model.addElement(message); }
|
||||
public void read() {
|
||||
for (int i = model.size() - 1; i >= 0; --i)
|
||||
if (model.get(i).getStatus() == MessageStatus.READ) break;
|
||||
else model.get(i).setStatus(MessageStatus.READ);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all messages in the current chat
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
public DefaultListModel<Message> getModel() { return model; }
|
||||
public ComponentListModel<Message> getModel() { return model; }
|
||||
|
||||
/**
|
||||
* @return the recipient of a message
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
public User getRecipient() { return recipient; }
|
||||
}
|
@ -11,6 +11,7 @@ import javax.naming.TimeLimitExceededException;
|
||||
|
||||
import envoy.client.util.EnvoyLog;
|
||||
import envoy.data.*;
|
||||
import envoy.event.IdGeneratorRequest;
|
||||
import envoy.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
@ -45,11 +46,13 @@ public class Client implements Closeable {
|
||||
* an exception is thrown.
|
||||
*
|
||||
* @param credentials the login credentials of the user
|
||||
* @param localDB the local database used to persist the current
|
||||
* {@link IdGenerator}
|
||||
* @throws Exception if the online mode could not be entered or the request
|
||||
* failed for some other reason
|
||||
* @since Envoy v0.2-alpha
|
||||
*/
|
||||
public void onlineInit(LoginCredentials credentials) throws Exception {
|
||||
public void onlineInit(LoginCredentials credentials, LocalDB localDB) throws Exception {
|
||||
// Establish TCP connection
|
||||
logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
|
||||
socket = new Socket(config.getServer(), config.getPort());
|
||||
@ -84,6 +87,13 @@ public class Client implements Closeable {
|
||||
|
||||
// Register processors for message and status handling
|
||||
receiver.registerProcessor(Message.class, new ReceivedMessageProcessor());
|
||||
// TODO: Status handling
|
||||
|
||||
// Process message ID generation
|
||||
receiver.registerProcessor(IdGenerator.class, localDB::setIdGenerator);
|
||||
|
||||
// Request a generator if none is present
|
||||
if (!localDB.hasIdGenerator() || !localDB.getIdGenerator().hasNext()) requestIdGenerator();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,11 +104,21 @@ public class Client implements Closeable {
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
public void sendMessage(Message message) throws IOException {
|
||||
checkOnline();
|
||||
SerializationUtils.writeBytesWithLength(message, socket.getOutputStream());
|
||||
writeObject(message);
|
||||
message.nextStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a new {@link IdGenerator} from the server.
|
||||
*
|
||||
* @throws IOException if the request does not reach the server
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
public void requestIdGenerator() throws IOException {
|
||||
logger.info("Requesting new id generator...");
|
||||
writeObject(new IdGeneratorRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@code Map<String, User>} of all users on the server with their
|
||||
* user names as keys
|
||||
@ -114,6 +134,11 @@ public class Client implements Closeable {
|
||||
@Override
|
||||
public void close() throws IOException { if (online) socket.close(); }
|
||||
|
||||
private void writeObject(Object obj) throws IOException {
|
||||
checkOnline();
|
||||
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
|
||||
}
|
||||
|
||||
private void checkOnline() { if (!online) throw new IllegalStateException("Client is not online"); }
|
||||
|
||||
/**
|
||||
|
@ -31,7 +31,6 @@ public class Config {
|
||||
items.put("server", new ConfigItem<>("server", "s", (input) -> input, null));
|
||||
items.put("port", new ConfigItem<>("port", "p", (input) -> Integer.parseInt(input), null));
|
||||
items.put("localDB", new ConfigItem<>("localDB", "db", (input) -> new File(input), new File("localDB")));
|
||||
items.put("syncTimeout", new ConfigItem<>("syncTimeout", "st", (input) -> Integer.parseInt(input), 1000));
|
||||
items.put("homeDirectory",
|
||||
new ConfigItem<>("homeDirectory", "h", (input) -> new File(input), new File(System.getProperty("user.home"), ".envoy")));
|
||||
items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", (input) -> Level.parse(input), Level.CONFIG));
|
||||
@ -112,7 +111,7 @@ public class Config {
|
||||
* @return the port at which the Envoy server is located on the host
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
public int getPort() { return (int) items.get("port").get(); }
|
||||
public Integer getPort() { return (Integer) items.get("port").get(); }
|
||||
|
||||
/**
|
||||
* @return the local database specific to the client user
|
||||
@ -120,12 +119,6 @@ public class Config {
|
||||
*/
|
||||
public File getLocalDB() { return (File) items.get("localDB").get(); }
|
||||
|
||||
/**
|
||||
* @return the current time (milliseconds) that is waited between Syncs
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
public int getSyncTimeout() { return (int) items.get("syncTimeout").get(); }
|
||||
|
||||
/**
|
||||
* @return the directory in which all local files are saves
|
||||
* @since Envoy v0.2-alpha
|
||||
|
@ -4,10 +4,15 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import envoy.data.IdGenerator;
|
||||
import envoy.data.User;
|
||||
import envoy.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
* Stored information about the current {@link User} and their {@link Chat}s.
|
||||
* For message ID generation a {@link IdGenerator} is stored as well.
|
||||
* These object are persisted inside a folder of the local file system.<br>
|
||||
* <br>
|
||||
* Project: <strong>envoy-client</strong><br>
|
||||
* File: <strong>LocalDB.java</strong><br>
|
||||
* Created: <strong>27.10.2019</strong><br>
|
||||
@ -18,10 +23,11 @@ import envoy.util.SerializationUtils;
|
||||
*/
|
||||
public class LocalDB {
|
||||
|
||||
private File localDBDir, localDBFile, usersFile;
|
||||
private File localDBDir, localDBFile, usersFile, idGeneratorFile;
|
||||
private User user;
|
||||
private Map<String, User> users = new HashMap<>();
|
||||
private List<Chat> chats = new ArrayList<>();
|
||||
private IdGenerator idGenerator;
|
||||
|
||||
/**
|
||||
* Constructs an empty local database. To serialize any chats to the file
|
||||
@ -38,6 +44,7 @@ public class LocalDB {
|
||||
if (localDBDir.exists() && !localDBDir.isDirectory())
|
||||
throw new IOException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
|
||||
usersFile = new File(localDBDir, "users.db");
|
||||
idGeneratorFile = new File(localDBDir, "id_generator.db");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,7 +60,8 @@ public class LocalDB {
|
||||
|
||||
/**
|
||||
* Stores all users to the local database. If the client user is specified, the
|
||||
* chats related to this user are stored as well.
|
||||
* chats related to this user are stored as well. The message id generator will
|
||||
* also be saved if present.
|
||||
*
|
||||
* @throws IOException if something went wrong during saving
|
||||
* @since Envoy v0.1-alpha
|
||||
@ -63,7 +71,10 @@ public class LocalDB {
|
||||
SerializationUtils.write(usersFile, users);
|
||||
|
||||
// Save chats
|
||||
SerializationUtils.write(localDBFile, chats);
|
||||
if (user != null) SerializationUtils.write(localDBFile, chats);
|
||||
|
||||
// Save id generator
|
||||
if (hasIdGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,6 +95,18 @@ public class LocalDB {
|
||||
*/
|
||||
public void loadChats() throws ClassNotFoundException, IOException { chats = SerializationUtils.read(localDBFile, ArrayList.class); }
|
||||
|
||||
/**
|
||||
* Loads the message ID generator that is stored in the local database. If the
|
||||
* file is not found, the exception is ignored.
|
||||
*
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
public void loadIdGenerator() {
|
||||
try {
|
||||
idGenerator = SerializationUtils.read(idGeneratorFile, IdGenerator.class);
|
||||
} catch (ClassNotFoundException | IOException e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@code Map<String, User>} of all users stored locally with their
|
||||
* user names as keys
|
||||
@ -119,4 +142,22 @@ public class LocalDB {
|
||||
* @since Envoy v0.2-alpha
|
||||
*/
|
||||
public void setUser(User user) { this.user = user; }
|
||||
|
||||
/**
|
||||
* @return the message ID generator
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
public IdGenerator getIdGenerator() { return idGenerator; }
|
||||
|
||||
/**
|
||||
* @param idGenerator the message ID generator to set
|
||||
* @since Envoy 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
|
||||
*/
|
||||
public boolean hasIdGenerator() { return idGenerator != null; }
|
||||
}
|
@ -135,7 +135,7 @@ public class Settings {
|
||||
* {@code Control} key.
|
||||
* @since Envoy v0.2-alpha
|
||||
*/
|
||||
public boolean isEnterToSend() { return (boolean) items.get("enterToSend").get(); }
|
||||
public Boolean isEnterToSend() { return (Boolean) items.get("enterToSend").get(); }
|
||||
|
||||
/**
|
||||
* Changes the keystrokes performed by the user to send a message.
|
||||
@ -152,7 +152,7 @@ public class Settings {
|
||||
* @return the current on close mode.
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
public boolean getCurrentOnCloseMode() { return (boolean) items.get("onCloseMode").get(); }
|
||||
public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); }
|
||||
|
||||
/**
|
||||
* Sets the current on close mode.
|
||||
|
@ -10,7 +10,9 @@ import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import envoy.client.*;
|
||||
import envoy.client.event.MessageCreationEvent;
|
||||
import envoy.client.event.ThemeChangeEvent;
|
||||
import envoy.client.ui.list.ComponentList;
|
||||
import envoy.client.ui.settings.SettingsScreen;
|
||||
import envoy.client.util.EnvoyLog;
|
||||
import envoy.data.Message;
|
||||
@ -30,8 +32,6 @@ import envoy.event.EventBus;
|
||||
*/
|
||||
public class ChatWindow extends JFrame {
|
||||
|
||||
private static final long serialVersionUID = 6865098428255463649L;
|
||||
|
||||
// User specific objects
|
||||
private Client client;
|
||||
private LocalDB localDB;
|
||||
@ -41,16 +41,20 @@ public class ChatWindow extends JFrame {
|
||||
private PrimaryTextArea messageEnterTextArea = new PrimaryTextArea(space);
|
||||
private JList<User> userList = new JList<>();
|
||||
private Chat currentChat;
|
||||
private JList<Message> messageList = new JList<>();
|
||||
private ComponentList<Message> messageList = new ComponentList<>(new MessageListRenderer());
|
||||
private PrimaryScrollPane scrollPane = new PrimaryScrollPane();
|
||||
private JTextPane textPane = new JTextPane();
|
||||
private PrimaryButton postButton = new PrimaryButton("Post");
|
||||
private PrimaryButton settingsButton = new PrimaryButton("Settings");
|
||||
|
||||
private static int space = 4;
|
||||
|
||||
private static final Logger logger = EnvoyLog.getLogger(ChatWindow.class.getSimpleName());
|
||||
|
||||
// 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 = 6865098428255463649L;
|
||||
|
||||
/**
|
||||
* Initializes a {@link JFrame} with UI elements used to send and read messages
|
||||
* to different users.
|
||||
@ -73,14 +77,11 @@ public class ChatWindow extends JFrame {
|
||||
gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 };
|
||||
contentPane.setLayout(gbl_contentPane);
|
||||
|
||||
messageList.setCellRenderer(new MessageListRenderer());
|
||||
messageList.setFocusTraversalKeysEnabled(false);
|
||||
messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
|
||||
// TODO: messageList.setFocusTraversalKeysEnabled(false);
|
||||
// messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
|
||||
|
||||
DefaultListModel<Message> messageListModel = new DefaultListModel<>();
|
||||
messageList.setModel(messageListModel);
|
||||
messageList.setFont(new Font("Arial", Font.PLAIN, 17));
|
||||
messageList.setFixedCellHeight(60);
|
||||
// messageList.setFont(new Font("Arial", Font.PLAIN, 17));
|
||||
// messageList.setFixedCellHeight(60);
|
||||
messageList.setBorder(new EmptyBorder(space, space, space, space));
|
||||
|
||||
scrollPane.setViewportView(messageList);
|
||||
@ -91,7 +92,7 @@ public class ChatWindow extends JFrame {
|
||||
gbc_scrollPane.gridx = 1;
|
||||
gbc_scrollPane.gridy = 1;
|
||||
|
||||
gbc_scrollPane.insets = new Insets(space, space, space, space);
|
||||
gbc_scrollPane.insets = insets;
|
||||
contentPane.add(scrollPane, gbc_scrollPane);
|
||||
|
||||
// Message enter field
|
||||
@ -100,8 +101,8 @@ 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)))
|
||||
postMessage(messageList);
|
||||
&& (Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0 || e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))
|
||||
postMessage();
|
||||
}
|
||||
});
|
||||
|
||||
@ -110,7 +111,7 @@ public class ChatWindow extends JFrame {
|
||||
gbc_messageEnterTextfield.gridx = 1;
|
||||
gbc_messageEnterTextfield.gridy = 2;
|
||||
|
||||
gbc_messageEnterTextfield.insets = new Insets(space, space, space, space);
|
||||
gbc_messageEnterTextfield.insets = insets;
|
||||
|
||||
contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield);
|
||||
|
||||
@ -121,9 +122,9 @@ public class ChatWindow extends JFrame {
|
||||
gbc_moveSelectionPostButton.gridx = 2;
|
||||
gbc_moveSelectionPostButton.gridy = 2;
|
||||
|
||||
gbc_moveSelectionPostButton.insets = new Insets(space, space, space, space);
|
||||
gbc_moveSelectionPostButton.insets = insets;
|
||||
|
||||
postButton.addActionListener((evt) -> { postMessage(messageList); });
|
||||
postButton.addActionListener((evt) -> { postMessage(); });
|
||||
contentPane.add(postButton, gbc_moveSelectionPostButton);
|
||||
|
||||
// Settings Button
|
||||
@ -133,7 +134,7 @@ public class ChatWindow extends JFrame {
|
||||
gbc_moveSelectionSettingsButton.gridx = 2;
|
||||
gbc_moveSelectionSettingsButton.gridy = 0;
|
||||
|
||||
gbc_moveSelectionSettingsButton.insets = new Insets(space, space, space, space);
|
||||
gbc_moveSelectionSettingsButton.insets = insets;
|
||||
|
||||
settingsButton.addActionListener((evt) -> {
|
||||
try {
|
||||
@ -154,7 +155,7 @@ public class ChatWindow extends JFrame {
|
||||
gbc_partnerName.gridx = 1;
|
||||
gbc_partnerName.gridy = 0;
|
||||
|
||||
gbc_partnerName.insets = new Insets(space, space, space, space);
|
||||
gbc_partnerName.insets = insets;
|
||||
contentPane.add(textPane, gbc_partnerName);
|
||||
|
||||
userList.setCellRenderer(new UserListRenderer());
|
||||
@ -165,17 +166,22 @@ public class ChatWindow extends JFrame {
|
||||
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
|
||||
final User user = selectedUserList.getSelectedValue();
|
||||
|
||||
// Select current chat
|
||||
currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getId() == user.getId()).findFirst().get();
|
||||
|
||||
// Set all unread messages in the chat to read
|
||||
readCurrentChat();
|
||||
// Read current Chat
|
||||
currentChat.read();
|
||||
|
||||
// Set recipient in client and chat title
|
||||
client.setRecipient(user);
|
||||
textPane.setText(currentChat.getRecipient().getName());
|
||||
|
||||
// Update model and scroll down
|
||||
messageList.setModel(currentChat.getModel());
|
||||
scrollPane.setChatOpened(true);
|
||||
contentPane.revalidate();
|
||||
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
|
||||
@ -187,20 +193,29 @@ public class ChatWindow extends JFrame {
|
||||
gbc_userList.gridx = 0;
|
||||
gbc_userList.gridy = 1;
|
||||
gbc_userList.anchor = GridBagConstraints.PAGE_START;
|
||||
gbc_userList.insets = new Insets(space, space, space, space);
|
||||
gbc_userList.insets = insets;
|
||||
|
||||
applyTheme(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()));
|
||||
|
||||
contentPane.add(userList, gbc_userList);
|
||||
contentPane.revalidate();
|
||||
|
||||
// Listen to theme changes
|
||||
EventBus.getInstance().register(ThemeChangeEvent.class, (evt) -> applyTheme((Theme) evt.get()));
|
||||
|
||||
contentPane.revalidate();
|
||||
// Listen to received messages
|
||||
EventBus.getInstance().register(MessageCreationEvent.class, (evt) -> {
|
||||
Message message = ((MessageCreationEvent) evt).get();
|
||||
localDB.getChats().stream().filter(c -> c.getRecipient().getId() == message.getSenderId()).findFirst().get().appendMessage(message);
|
||||
revalidate();
|
||||
repaint();
|
||||
});
|
||||
|
||||
revalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to immediately reload the ChatWindow when settings were changed.
|
||||
* Used to immediately reload the {@link ChatWindow} when settings were changed.
|
||||
*
|
||||
* @param theme the theme to change colors into
|
||||
* @since Envoy v0.2-alpha
|
||||
@ -210,8 +225,8 @@ public class ChatWindow extends JFrame {
|
||||
contentPane.setBackground(theme.getBackgroundColor());
|
||||
contentPane.setForeground(theme.getUserNameColor());
|
||||
// messageList
|
||||
messageList.setSelectionForeground(theme.getUserNameColor());
|
||||
messageList.setSelectionBackground(theme.getSelectionColor());
|
||||
// messageList.setSelectionForeground(theme.getUserNameColor());
|
||||
// messageList.setSelectionBackground(theme.getSelectionColor());
|
||||
messageList.setForeground(theme.getMessageColorChat());
|
||||
messageList.setBackground(theme.getCellColor());
|
||||
// scrollPane
|
||||
@ -238,7 +253,7 @@ public class ChatWindow extends JFrame {
|
||||
userList.setBackground(theme.getCellColor());
|
||||
}
|
||||
|
||||
private void postMessage(JList<Message> messageList) {
|
||||
private void postMessage() {
|
||||
if (!client.hasRecipient()) {
|
||||
JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
|
||||
return;
|
||||
@ -247,24 +262,32 @@ public class ChatWindow extends JFrame {
|
||||
if (!messageEnterTextArea.getText().isEmpty()) try {
|
||||
|
||||
// Create message
|
||||
final Message message = new MessageBuilder(localDB.getUser().getId(), currentChat.getRecipient().getId())
|
||||
final Message message = new MessageBuilder(localDB.getUser().getId(), currentChat.getRecipient().getId(), localDB.getIdGenerator())
|
||||
.setText(messageEnterTextArea.getText())
|
||||
.build();
|
||||
|
||||
// Send message
|
||||
// TODO: Store offline messages
|
||||
client.sendMessage(message);
|
||||
|
||||
// Add message to LocalDB and update UI
|
||||
currentChat.appendMessage(message);
|
||||
messageList.setModel(currentChat.getModel());
|
||||
// messageList.setModel(currentChat.getModel());
|
||||
|
||||
// Clear text field
|
||||
messageEnterTextArea.setText("");
|
||||
contentPane.revalidate();
|
||||
|
||||
// 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,
|
||||
"An exception occured while sending a message. See the log for more details.",
|
||||
"Exception occured",
|
||||
"Error sending message:\n" + e.toString(),
|
||||
"Message sending error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -291,64 +314,13 @@ public class ChatWindow extends JFrame {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the data model and the UI repeatedly after a certain amount of
|
||||
* time.
|
||||
*
|
||||
* @param timeout the amount of time that passes between two requests sent to
|
||||
* the server
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
private void startSyncThread(int timeout) {
|
||||
new Timer(timeout, (evt) -> {
|
||||
new Thread(() -> {
|
||||
|
||||
// Synchronize
|
||||
try {
|
||||
// localDB.applySync(client.sendSync(client.getSender().getId(),
|
||||
// localDB.fillSync(client.getSender().getId())));
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Could not perform sync", e);
|
||||
}
|
||||
|
||||
// TODO: Process unread messages
|
||||
// localDB.addUnreadMessagesToLocalDB();
|
||||
// localDB.clearUnreadMessagesSync();
|
||||
|
||||
// Mark unread messages as read when they are in the current chat
|
||||
readCurrentChat();
|
||||
|
||||
// Update UI
|
||||
SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
|
||||
}).start();
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void updateUserStates() {
|
||||
for (int i = 0; i < userList.getModel().getSize(); i++)
|
||||
for (int j = 0; j < localDB.getChats().size(); j++)
|
||||
if (userList.getModel().getElementAt(i).getId() == localDB.getChats().get(j).getRecipient().getId())
|
||||
userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks messages in the current chat as {@code READ}.
|
||||
*/
|
||||
private void readCurrentChat() {
|
||||
if (currentChat != null) {
|
||||
// TODO: localDB.setMessagesToRead(currentChat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Client} used by this {@link ChatWindow}. If the client is
|
||||
* online, the sync thread is started.
|
||||
* Sets the {@link Client} used by this {@link ChatWindow}.
|
||||
*
|
||||
* @param client the {@link Client} used to send and receive messages
|
||||
* @since Envoy v0.2-alpha
|
||||
*/
|
||||
public void setClient(Client client) {
|
||||
this.client = client;
|
||||
if (client.isOnline() && localDB != null) startSyncThread(Config.getInstance().getSyncTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -361,6 +333,5 @@ public class ChatWindow extends JFrame {
|
||||
public void setLocalDB(LocalDB localDB) {
|
||||
this.localDB = localDB;
|
||||
loadUsersAndChats();
|
||||
if (client != null && client.isOnline()) startSyncThread(Config.getInstance().getSyncTimeout());
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package envoy.client.ui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JList;
|
||||
import javax.swing.ListCellRenderer;
|
||||
import javax.swing.*;
|
||||
|
||||
import envoy.client.Settings;
|
||||
import envoy.client.ui.list.ComponentList;
|
||||
import envoy.client.ui.list.ComponentListCellRenderer;
|
||||
import envoy.data.Message;
|
||||
|
||||
/**
|
||||
@ -21,22 +21,22 @@ import envoy.data.Message;
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy v0.1-alpha
|
||||
*/
|
||||
public class MessageListRenderer extends JLabel implements ListCellRenderer<Message> {
|
||||
|
||||
private static final long serialVersionUID = 5164417379767181198L;
|
||||
public class MessageListRenderer implements ComponentListCellRenderer<Message> {
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends Message> list, Message value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
public JComponent getListCellComponent(ComponentList<? extends Message> list, Message value, boolean isSelected) {
|
||||
final JPanel panel = new JPanel();
|
||||
if (isSelected) {
|
||||
setBackground(list.getSelectionBackground());
|
||||
setForeground(list.getSelectionForeground());
|
||||
panel.setBackground(Color.DARK_GRAY);
|
||||
panel.setForeground(Color.RED);
|
||||
// TODO: Selection
|
||||
// setBackground(list.getSelectionBackground());
|
||||
// setForeground(list.getSelectionForeground());
|
||||
} else {
|
||||
setBackground(list.getBackground());
|
||||
setForeground(list.getForeground());
|
||||
panel.setBackground(list.getBackground());
|
||||
panel.setForeground(list.getForeground());
|
||||
}
|
||||
|
||||
setOpaque(true);
|
||||
|
||||
// TODO: Handle message attachments
|
||||
|
||||
final String text = value.getText();
|
||||
@ -49,12 +49,22 @@ public class MessageListRenderer extends JLabel implements ListCellRenderer<Mess
|
||||
// Getting the DateColor in the Chat of the current theme
|
||||
String dateColor = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getDateColorChat().toHex();
|
||||
|
||||
setText(String.format("<html><p style=\"color:%s\"><b><small>%s</b></small><br><p style=\"color:%s\">%s :%s</html>",
|
||||
panel.add(new JLabel(String.format("<html><p style=\"color:%s\"><b><small>%s</b></small><br><p style=\"color:%s\">%s :%s</html>",
|
||||
dateColor,
|
||||
date,
|
||||
textColor,
|
||||
text,
|
||||
state));
|
||||
return this;
|
||||
state)));
|
||||
|
||||
// Define some space to the messages below
|
||||
panel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 15, 0), BorderFactory.createEtchedBorder()));
|
||||
|
||||
// Define a maximum height of 50px
|
||||
Dimension size = new Dimension(list.getWidth() - 25, 50);
|
||||
panel.setMaximumSize(size);
|
||||
panel.setMinimumSize(size);
|
||||
panel.setPreferredSize(size);
|
||||
|
||||
return panel;
|
||||
}
|
||||
}
|
@ -97,7 +97,8 @@ public class Startup {
|
||||
Client client = new Client();
|
||||
try {
|
||||
// Try entering online mode first
|
||||
client.onlineInit(credentials);
|
||||
localDB.loadIdGenerator();
|
||||
client.onlineInit(credentials, localDB);
|
||||
} catch (Exception e1) {
|
||||
logger.warning("Could not connect to server. Trying offline mode...");
|
||||
e1.printStackTrace();
|
||||
@ -152,7 +153,7 @@ public class Startup {
|
||||
.getItems()
|
||||
.get("onCloseMode")
|
||||
.setChangeHandler((onCloseMode) -> chatWindow
|
||||
.setDefaultCloseOperation((boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE : JFrame.EXIT_ON_CLOSE));
|
||||
.setDefaultCloseOperation((Boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE : JFrame.EXIT_ON_CLOSE));
|
||||
} catch (EnvoyException e) {
|
||||
logger.warning("The StatusTrayIcon is not supported on this platform!");
|
||||
}
|
||||
|
91
src/main/java/envoy/client/ui/list/ComponentList.java
Normal file
91
src/main/java/envoy/client/ui/list/ComponentList.java
Normal file
@ -0,0 +1,91 @@
|
||||
package envoy.client.ui.list;
|
||||
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* Provides a vertical list layout of components provided in a
|
||||
* {@link ComponentListModel}. 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 v0.3-alpha
|
||||
*/
|
||||
public class ComponentList<E> extends JPanel {
|
||||
|
||||
private ComponentListModel<E> model;
|
||||
private ComponentListCellRenderer<E> renderer;
|
||||
|
||||
private static final long serialVersionUID = 1759644503942876737L;
|
||||
|
||||
/**
|
||||
* 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<E> renderer) {
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
this.renderer = 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<E> model, ComponentListCellRenderer<E> renderer) {
|
||||
this(renderer);
|
||||
this.model = model;
|
||||
setModel(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list model providing the list elements to render
|
||||
*
|
||||
* @param model the list model to set
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
public void setModel(ComponentListModel<E> model) {
|
||||
// Remove old model
|
||||
if (this.model != null)
|
||||
this.model.setComponentList(null);
|
||||
|
||||
// Synchronize with new model
|
||||
this.model = model;
|
||||
this.model.setComponentList(this);
|
||||
synchronizeModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an object to the list by rendering it with the current
|
||||
* {@link ComponentListCellRenderer}.
|
||||
*
|
||||
* @param elem the element to add
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
void add(E elem) {
|
||||
add(renderer.getListCellComponent(this, elem, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all child components and then adds all components representing the
|
||||
* elements of the {@link ComponentListModel}.
|
||||
*
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
void synchronizeModel() {
|
||||
removeAll();
|
||||
if (model != null) for (E elem : model)
|
||||
add(elem);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
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>ComponentListCellRenderer.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 v0.3-alpha
|
||||
*/
|
||||
public interface ComponentListCellRenderer<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 v0.3-alpha
|
||||
*/
|
||||
JComponent getListCellComponent(ComponentList<? extends E> list, E value, boolean isSelected);
|
||||
}
|
111
src/main/java/envoy/client/ui/list/ComponentListModel.java
Normal file
111
src/main/java/envoy/client/ui/list/ComponentListModel.java
Normal file
@ -0,0 +1,111 @@
|
||||
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>ComponentListModel.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 v0.3-alpha
|
||||
*/
|
||||
public final class ComponentListModel<E> implements Iterable<E>, Serializable {
|
||||
|
||||
private List<E> elements = new ArrayList<>();
|
||||
transient private ComponentList<E> componentList;
|
||||
|
||||
private static final long serialVersionUID = 4815005915255497331L;
|
||||
|
||||
/**
|
||||
* 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 v0.3-alpha
|
||||
*/
|
||||
public boolean add(E e) {
|
||||
if (componentList != null) componentList.add(e);
|
||||
return elements.add(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from this model and clears the associated
|
||||
* {@link ComponentList}.
|
||||
*
|
||||
* @see java.util.List#clear()
|
||||
* @since Envoy 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 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 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 v0.3-alpha
|
||||
*/
|
||||
public int size() { return elements.size(); }
|
||||
|
||||
/**
|
||||
* @return an iterator over the elements of this list model
|
||||
* @see java.util.List#iterator()
|
||||
* @since Envoy v0.3-alpha
|
||||
*/
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return new Iterator<E>() {
|
||||
|
||||
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 v0.3-alpha
|
||||
*/
|
||||
void setComponentList(ComponentList<E> componentList) {
|
||||
this.componentList = componentList;
|
||||
if (componentList != null) componentList.synchronizeModel();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user