Removed most Swing components
This commit is contained in:
		| @@ -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ä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(); | ||||
| 		// } | ||||
| 		// }); | ||||
|  | ||||
| 	} | ||||
| } | ||||
| @@ -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ä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()); } | ||||
| } | ||||
| @@ -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); } | ||||
| } | ||||
| @@ -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()); | ||||
| 	} | ||||
| } | ||||
| @@ -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; } | ||||
| } | ||||
| @@ -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(); | ||||
| 	} | ||||
| } | ||||
| @@ -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); | ||||
| } | ||||
| @@ -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); | ||||
| } | ||||
| @@ -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äfer | ||||
|  * @since Envoy Client v0.3-alpha | ||||
|  */ | ||||
| package envoy.client.ui.list; | ||||
| @@ -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); | ||||
| 	} | ||||
| } | ||||
| @@ -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); | ||||
| 	} | ||||
| } | ||||
| @@ -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); | ||||
| 	} | ||||
| } | ||||
| @@ -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äfer | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| package envoy.client.ui.list_component; | ||||
| @@ -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ä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; | ||||
| 	} | ||||
| } | ||||
| @@ -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äfer | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| package envoy.client.ui.renderer; | ||||
		Reference in New Issue
	
	Block a user