diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index a21d99a..7d883c2 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -25,6 +25,7 @@ import envoy.client.net.WriteProxy; import envoy.client.ui.ContextMenu; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; +import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryScrollPane; @@ -125,18 +126,21 @@ public class ChatWindow extends JFrame { contentPane.setLayout(gbl_contentPane); messageList.setBorder(new EmptyBorder(space, space, space, space)); + messageList.setSelectionMode(SelectionMode.SINGLE); Map commands = new HashMap<>() { private static final long serialVersionUID = -2755235774946990126L; { put("forward selected message", - evt -> forwardMessageToMultipleUsers(messageList.getSelected().get(0), - ContactsChooserDialog.showForwardingDialog("Forward selected message to", messageList.getSelected().get(0), client))); + evt -> forwardMessageToMultipleUsers(messageList + .getSingleSelectedElement(), + ContactsChooserDialog + .showForwardingDialog("Forward selected message to", messageList.getSingleSelectedElement(), client))); put("copy", evt -> { // TODO should be enhanced to allow also copying of message attachments, // especially pictures - StringSelection copy = new StringSelection(messageList.getSelected().get(0).getText()); + StringSelection copy = new StringSelection(messageList.getSingleSelectedElement().getText()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); }); // TODO insert implementation to edit and delete messages diff --git a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java index b82707b..467d43f 100644 --- a/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java +++ b/src/main/java/envoy/client/ui/container/ContactsChooserDialog.java @@ -15,6 +15,7 @@ import envoy.client.data.Settings; import envoy.client.net.Client; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; +import envoy.client.ui.list.ComponentList.SelectionMode; import envoy.client.ui.list.ComponentListModel; import envoy.client.ui.renderer.MessageListRenderer; import envoy.client.ui.renderer.UserComponentListRenderer; @@ -69,7 +70,7 @@ public class ContactsChooserDialog extends JDialog { dialog.getContentPanel() .add(new MessageListRenderer(client.getSender().getId()).getListCellComponent(null, message, false), BorderLayout.NORTH); List results = new ArrayList<>(); - dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelected()); dialog.dispose(); }); + dialog.addOkButtonActionListener(e -> { results.addAll(dialog.getContactList().getSelectedElements()); dialog.dispose(); }); ComponentListModel contactListModel = dialog.getContactList().getModel(); client.getContacts().getContacts().forEach(user -> contactListModel.add(user)); dialog.setVisible(true); @@ -82,7 +83,7 @@ public class ContactsChooserDialog extends JDialog { * @since Envoy v0.1-beta */ private ContactsChooserDialog() { - contactList.enableMultipleSelection(); + contactList.setSelectionMode(SelectionMode.MULTIPLE); // setBounds(100, 100, 450, 300); setModal(true); getContentPane().setLayout(new BorderLayout()); diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 8b7ec43..199946d 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -3,8 +3,8 @@ package envoy.client.ui.list; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import javax.swing.*; @@ -25,15 +25,35 @@ public class ComponentList extends JPanel { private ComponentListModel model; private ComponentListCellRenderer renderer; - private boolean multipleSelectionEnabled = false; - - private List currentSelections = new ArrayList<>(); + private SelectionMode selectionMode = SelectionMode.NONE; + private Set selection = new HashSet<>(); private static final long serialVersionUID = 1759644503942876737L; /** - * Sets the layout of this {@link ComponentList} to be a vertically oriented - * BoxLayout. + * Defines the possible modes of selection that can be performed by the user + * + * @since Envoy 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 + } + + /** + * Initializes a default {@link ComponentList} without a model or a renderer. * * @since Envoy v0.1-beta */ @@ -79,8 +99,8 @@ public class ComponentList extends JPanel { // Synchronize with new model this.model = model; - if (model != null) this.model.setComponentList(this); - if (renderer != null) synchronizeModel(); + if (model != null) model.setComponentList(this); + synchronizeModel(); } /** @@ -92,11 +112,53 @@ public class ComponentList extends JPanel { public void synchronizeModel() { if (model != null && renderer != null) { removeAll(); - model.forEach(this::add); + model.forEach(this::addElement); revalidate(); } } + /** + * Selects a list element by index. If the element is already selected, it is + * removed from the selection. + * + * @param index the index of the selected component + * @since Envoy v0.1-beta + */ + public void selectElement(int index) { + if (selection.contains(index)) { + + // Remove selection of element at index + updateElement(index, false); + selection.remove(index); + } else { + + // Remove old selection if single selection is enabled + if (selectionMode == SelectionMode.SINGLE) clearSelection(); + + if (selectionMode != SelectionMode.NONE) { + + // Assign new selection + selection.add(index); + + // Update element + updateElement(index, true); + } + } + + revalidate(); + repaint(); + } + + /** + * Removes the current selection. + * + * @since Envoy v0.1-alpha + */ + public void clearSelection() { + selection.forEach(i -> updateElement(i, false)); + selection.clear(); + } + /** * Adds an object to the list by rendering it with the current * {@link ComponentListCellRenderer}. @@ -104,89 +166,26 @@ public class ComponentList extends JPanel { * @param elem the element to add * @since Envoy v0.3-alpha */ - void add(E elem) { add(elem, getComponentCount(), false); } + void addElement(E elem) { addElement(elem, getComponentCount(), false); } /** * Adds an object to the list by rendering it with the current - * {@link ComponentListRenderer}. + * {@link ComponentListRenderer}. If the renderer is {@code null}, no action is + * performed. * * @param elem the element to add * @param index the index at which to add the element * @param isSelected the selection state of the element * @since Envoy v0.1-beta */ - private void add(E elem, int index, boolean isSelected) { - if (isSelected && !multipleSelectionEnabled) { - clearSelections(); - currentSelections.add(index); - } + private void addElement(E elem, int index, boolean isSelected) { if (renderer != null) { - final JComponent component = renderer.getListCellComponent(this, elem, isSelected); - component.addMouseListener(getSelectionListener(index)); - add(component, index); + final JComponent component = renderer.getListCellComponent(this, elem, isSelected); + component.addMouseListener(getSelectionListener(index)); + add(component, index); } } - /** - * @param componentIndex the index of the list component to which the mouse - * listener will be added - * @return a mouse listener calling the - * {@link ComponentList#componentSelected(int)} method with the - * component's index when a left click is performed by the user - * @since Envoy v0.1-beta - */ - private MouseListener getSelectionListener(int componentIndex) { - return new MouseAdapter() { - - @Override - public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) componentSelected(componentIndex); } - }; - } - - /** - * Gets called when a list component has been clicked on by the user. Any - * previous selections are then removed and the selected component gets - * redrawn.
- *
- * If the currently selected component gets selected again, the selection is - * removed. - * - * @param index the index of the selected component - * @since Envoy v0.1-beta - */ - private void componentSelected(int index) { - // removing selection of element at index - if (currentSelections.contains(index)) { - - // Clear selection - updateSelection(index, false); - currentSelections.remove(Integer.valueOf(index)); - } else { - - // Remove old selection if multipleSelection is disabled - if (!multipleSelectionEnabled && currentSelections.size() > 0) clearSelections(); - - // Assign new selection - currentSelections.add(index); - - // Update current selection - updateSelection(index, true); - } - - revalidate(); - repaint(); - } - - /** - * Clears all currently active selections. - * - * @since Envoy v0.1-beta - */ - private void clearSelections() { - currentSelections.forEach(index -> updateSelection(index, false)); - currentSelections.clear(); - } - /** * Replaces a list element with a newly rendered instance of its contents. * @@ -194,21 +193,57 @@ public class ComponentList extends JPanel { * @param isSelected the selection state passed to the {@link ListCellRenderer} * @since Envoy v0.1-beta */ - private void updateSelection(int index, boolean isSelected) { + private void updateElement(int index, boolean isSelected) { remove(index); - add(model.get(index), index, isSelected); + addElement(model.get(index), index, isSelected); } /** - * @return the selected elements or null if none is selected + * @param componentIndex the index of the list component to which the mouse + * listener will be added + * @return a mouse listener calling the + * {@link ComponentList#selectElement(int)} method with the + * component's index when a left click is performed by the user * @since Envoy v0.1-beta */ - public List getSelected() { - List selected = new ArrayList<>(); - currentSelections.forEach(index -> selected.add(model.get(index))); - return selected; + private MouseListener getSelectionListener(int componentIndex) { + return new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { if (SwingUtilities.isLeftMouseButton(e)) selectElement(componentIndex); } + }; } + /** + * @return a set of all selected indices + * @since Envoy v0.1-beta + */ + public Set getSelection() { return selection; } + + /** + * @return a set of all selected elements + * @since Envoy v0.1-beta + */ + public Set getSelectedElements() { + var selectedElements = new HashSet(); + selection.forEach(i -> selectedElements.add(model.get(i))); + return selectedElements; + } + + /** + * @return the index of an arbitrary selected element + * @throws java.util.NoSuchElementException if no selection is present + * @since Envoy v0.1-beta + */ + public int getSingleSelection() { return selection.iterator().next(); } + + /** + * @return an arbitrary selected element + * @throws java.util.NoSuchElementException if no selection is present + * @since Envoy v0.1-beta + */ + public E getSingleSelectedElement() { return model.get(getSingleSelection()); } + /** * @return the model * @since Envoy v0.1-beta @@ -228,37 +263,14 @@ public class ComponentList extends JPanel { public void setRenderer(ComponentListCellRenderer renderer) { this.renderer = renderer; } /** - * @return the multipleSelectionEnabled + * @return the selection mode * @since Envoy v0.1-beta */ - public boolean isMultipleSelectionEnabled() { return multipleSelectionEnabled; } + public SelectionMode getSelectionMode() { return selectionMode; } /** - * @param multipleSelectionEnabled if true, multiple elements can be selected in - * the component list + * @param selectionMode the selection mode to set * @since Envoy v0.1-beta */ - public void setMultipleSelectionEnabled(boolean multipleSelectionEnabled) { - this.multipleSelectionEnabled = multipleSelectionEnabled; - if (!multipleSelectionEnabled) clearSelections(); - } - - /** - * Enables the selection of multiple elements. - * - * @see ComponentList#disableMultipleSelection - * @since Envoy v0.1-beta - */ - public void enableMultipleSelection() { this.multipleSelectionEnabled = true; } - - /** - * Allows only one element to be selected. True by default. - * - * @see ComponentList#enableMultipleSelection - * @since Envoy v0.1-beta - */ - public void disableMultipleSelection() { - this.multipleSelectionEnabled = false; - clearSelections(); - } + public void setSelectionMode(SelectionMode selectionMode) { this.selectionMode = selectionMode; } } diff --git a/src/main/java/envoy/client/ui/list/ComponentListModel.java b/src/main/java/envoy/client/ui/list/ComponentListModel.java index aad379e..05ef7d4 100644 --- a/src/main/java/envoy/client/ui/list/ComponentListModel.java +++ b/src/main/java/envoy/client/ui/list/ComponentListModel.java @@ -34,7 +34,7 @@ public final class ComponentListModel implements Iterable, Serializable { */ public boolean add(E e) { if (componentList != null) { - componentList.add(e); + componentList.addElement(e); componentList.revalidate(); } return elements.add(e); diff --git a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java index e86214b..f19f17e 100644 --- a/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java +++ b/src/main/java/envoy/client/ui/renderer/UserComponentListRenderer.java @@ -9,6 +9,7 @@ import javax.swing.JLabel; import javax.swing.JPanel; import envoy.client.data.Settings; +import envoy.client.ui.Color; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListCellRenderer; @@ -28,46 +29,42 @@ public class UserComponentListRenderer implements ComponentListCellRenderer list, User value, boolean isSelected) { + public JComponent getListCellComponent(ComponentList list, User user, boolean isSelected) { + final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); + final JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); - final Theme theme = Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()); // Panel background panel.setBackground(isSelected ? theme.getSelectionColor() : theme.getCellColor()); panel.setOpaque(true); - panel.setPreferredSize(new Dimension(100, 35)); // TODO add profile picture support in BorderLayout.West - JLabel username = new JLabel(value.getName()); + JLabel username = new JLabel(user.getName()); username.setForeground(theme.getUserNameColor()); panel.add(username, BorderLayout.CENTER); - final UserStatus status = value.getStatus(); + final UserStatus status = user.getStatus(); JLabel statusLabel = new JLabel(status.toString()); - java.awt.Color foreground; + Color foreground; switch (status) { - case OFFLINE: - foreground = java.awt.Color.LIGHT_GRAY; - break; case AWAY: - foreground = java.awt.Color.YELLOW; + foreground = Color.yellow; break; case BUSY: - foreground = java.awt.Color.BLUE; + foreground = Color.blue; break; case ONLINE: - foreground = java.awt.Color.GREEN; + foreground = Color.green; break; default: - foreground = java.awt.Color.LIGHT_GRAY; + foreground = Color.lightGray; break; } statusLabel.setForeground(foreground); panel.add(statusLabel, BorderLayout.NORTH); return panel; } - }