diff --git a/src/main/java/envoy/client/ui/ContextMenu.java b/src/main/java/envoy/client/ui/ContextMenu.java index 7328b0d..1ba5d14 100644 --- a/src/main/java/envoy/client/ui/ContextMenu.java +++ b/src/main/java/envoy/client/ui/ContextMenu.java @@ -44,17 +44,20 @@ public class ContextMenu extends JPopupMenu { */ public static final String subMenuItem = "SubMI"; - private Map items = new HashMap<>(); - private Map icons = new HashMap<>(); + private Map items = new HashMap<>(); + private Map icons = new HashMap<>(); + private Map mnemonics = new HashMap<>(); private ButtonGroup radioButtonGroup = new ButtonGroup(); private boolean built = false; private boolean visible = false; /** + * @param parent the component which will call this + * {@link ContextMenu} * @since Envoy v0.1-beta */ - public ContextMenu() {} + public ContextMenu(Component parent) { setInvoker(parent); } /** * @param label the string that a UI may use to display as a title @@ -67,13 +70,18 @@ public class ContextMenu extends JPopupMenu { * Only keys in here will have an Icon displayed. More * precisely, all keys here not included in the first * map will be thrown out. + * @param itemMnemonics the keyboard shortcuts that need to be pressed to + * automatically execute the {@link JMenuItem} with the + * given text * @since Envoy v0.1-beta */ - public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons) { + public ContextMenu(String label, Component parent, Map itemsWithActions, Map itemIcons, + Map itemMnemonics) { super(label); setInvoker(parent); - this.items = (itemsWithActions != null) ? itemsWithActions : items; - this.icons = (itemIcons != null) ? itemIcons : icons; + this.items = (itemsWithActions != null) ? itemsWithActions : items; + this.icons = (itemIcons != null) ? itemIcons : icons; + this.mnemonics = (itemMnemonics != null) ? itemMnemonics : mnemonics; } /** @@ -87,27 +95,20 @@ public class ContextMenu extends JPopupMenu { public ContextMenu build() { items.forEach((text, action) -> { // case radio button wanted + AbstractButton item; if (text.startsWith(radioButtonMenuItem)) { - var item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); - item.addActionListener(action); + item = new JRadioButtonMenuItem(text.substring(radioButtonMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); radioButtonGroup.add(item); - add(item); // case check box wanted - } else if (text.startsWith(checkboxMenuItem)) { - var item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); - item.addActionListener(action); - add(item); - // case sub-menu wanted - } else if (text.startsWith(subMenuItem)) { - var item = new JMenu(text.substring(subMenuItem.length())); - item.addActionListener(action); - add(item); - } else { - // normal JMenuItem wanted - var item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null); - item.addActionListener(action); - add(item); - } + } else if (text.startsWith(checkboxMenuItem)) + item = new JCheckBoxMenuItem(text.substring(checkboxMenuItem.length()), icons.containsKey(text) ? icons.get(text) : null); + // case sub-menu wanted + else if (text.startsWith(subMenuItem)) item = new JMenu(text.substring(subMenuItem.length())); + else // normal JMenuItem wanted + item = new JMenuItem(text, icons.containsKey(text) ? icons.get(text) : null); + item.addActionListener(action); + if (mnemonics.containsKey(text)) item.setMnemonic(mnemonics.get(text)); + add(item); }); getInvoker().addMouseListener(getShowingListener()); built = true; @@ -136,10 +137,10 @@ public class ContextMenu extends JPopupMenu { private void action(MouseEvent e) { if (!built) build(); if (e.isPopupTrigger()) { - show(e.getComponent(), e.getX(), e.getY()); // hides the menu if already visible visible = !visible; - setVisible(visible); + if (visible) show(e.getComponent(), e.getX(), e.getY()); + else setVisible(false); } } @@ -153,8 +154,9 @@ public class ContextMenu extends JPopupMenu { */ public void clear() { removeAll(); - items = new HashMap<>(); - icons = new HashMap<>(); + items = new HashMap<>(); + icons = new HashMap<>(); + mnemonics = new HashMap<>(); } /** @@ -181,4 +183,19 @@ public class ContextMenu extends JPopupMenu { * @since Envoy v0.1-beta */ public void setIcons(Map icons) { this.icons = icons; } + + /** + * @return the mnemonics (the keyboard shortcuts that automatically execute the + * command for a {@link JMenuItem} with corresponding text) + * @since Envoy v0.1-beta + */ + public Map getMnemonics() { return mnemonics; } + + /** + * @param mnemonics the keyboard shortcuts that need to be pressed to + * automatically execute the {@link JMenuItem} with the given + * text + * @since Envoy v0.1-beta + */ + public void setMnemonics(Map mnemonics) { this.mnemonics = mnemonics; } } diff --git a/src/main/java/envoy/client/ui/container/ChatWindow.java b/src/main/java/envoy/client/ui/container/ChatWindow.java index fc43f89..a21d99a 100644 --- a/src/main/java/envoy/client/ui/container/ChatWindow.java +++ b/src/main/java/envoy/client/ui/container/ChatWindow.java @@ -22,6 +22,7 @@ import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; +import envoy.client.ui.ContextMenu; import envoy.client.ui.Theme; import envoy.client.ui.list.ComponentList; import envoy.client.ui.list.ComponentListModel; @@ -29,6 +30,7 @@ import envoy.client.ui.primary.PrimaryButton; import envoy.client.ui.primary.PrimaryScrollPane; import envoy.client.ui.primary.PrimaryTextArea; import envoy.client.ui.renderer.ContactsSearchRenderer; +import envoy.client.ui.renderer.MessageListRenderer; import envoy.client.ui.renderer.UserListRenderer; import envoy.client.ui.settings.SettingsScreen; import envoy.data.Message; @@ -74,6 +76,7 @@ public class ChatWindow extends JFrame { private JTextPane textPane = new JTextPane(); private PrimaryButton postButton = new PrimaryButton("Post"); private PrimaryButton settingsButton = new PrimaryButton("Settings"); + @SuppressWarnings("unused") private JPopupMenu contextMenu; // Contacts Header @@ -107,6 +110,7 @@ public class ChatWindow extends JFrame { public ChatWindow() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 600, 800); + setMinimumSize(new Dimension(400, 300)); setTitle("Envoy"); setLocationRelativeTo(null); setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png"))); @@ -121,44 +125,37 @@ public class ChatWindow extends JFrame { contentPane.setLayout(gbl_contentPane); messageList.setBorder(new EmptyBorder(space, space, space, space)); - messageList.addMouseListener(new MouseAdapter() { + Map commands = new HashMap<>() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.isPopupTrigger()) { - contextMenu = new JPopupMenu("message options"); - Map commands = new HashMap<>() { + private static final long serialVersionUID = -2755235774946990126L; - private static final long serialVersionUID = -2755235774946990126L; - - { - put("forward selected message", - evt -> forwardMessageToMultipleUsers(messageList.getSelected().get(0), - ContactsChooserDialog - .showForwardingDialog("Forward selected message to", messageList.getSelected().get(0), client))); - put("copy", evt -> { - // TODO should be enhanced to allow also copying of message attachments, - // especially pictures - StringSelection copy = new StringSelection(messageList.getSelected().get(0).getText()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); - }); - // TODO insert implementation to edit and delete messages - put("delete", evt -> {}); - put("edit", evt -> {}); - put("quote", evt -> {}); - } - }; - commands.forEach((name, action) -> { var item = new JMenuItem(name); item.addActionListener(action); contextMenu.add(item); }); - contextMenu.show(e.getComponent(), e.getX(), e.getY()); - } + { + put("forward selected message", + evt -> forwardMessageToMultipleUsers(messageList.getSelected().get(0), + ContactsChooserDialog.showForwardingDialog("Forward selected message to", messageList.getSelected().get(0), client))); + put("copy", evt -> { + // TODO should be enhanced to allow also copying of message attachments, + // especially pictures + StringSelection copy = new StringSelection(messageList.getSelected().get(0).getText()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(copy, copy); + }); + // TODO insert implementation to edit and delete messages + put("delete", evt -> {}); + put("edit", evt -> {}); + put("quote", evt -> {}); } - }); + }; + contextMenu = new ContextMenu(null, messageList, commands, null, null).build(); + scrollPane.setViewportView(messageList); scrollPane.addComponentListener(new ComponentAdapter() { - // updates list elements when list is resized + // Update list elements when scroll pane (and thus list) is resized @Override - public void componentResized(ComponentEvent e) { messageList.synchronizeModel(); } + public void componentResized(ComponentEvent e) { + messageList.setMaximumSize(new Dimension(scrollPane.getWidth(), Integer.MAX_VALUE)); + messageList.synchronizeModel(); + } }); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); @@ -600,7 +597,7 @@ public class ChatWindow extends JFrame { private void readCurrentChat() { try { currentChat.read(writeProxy); - messageList.synchronizeModel(); + if (messageList.getRenderer() != null) messageList.synchronizeModel(); } catch (IOException e) { e.printStackTrace(); logger.log(Level.WARNING, "Couldn't notify server about message status change", e); @@ -644,6 +641,8 @@ public class ChatWindow extends JFrame { this.localDb = localDb; this.writeProxy = writeProxy; + messageList.setRenderer(new MessageListRenderer(client.getSender().getId())); + // Load users and chats new Thread(() -> { localDb.getUsers().values().forEach(user -> { diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index b6962e3..8b7ec43 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -31,6 +31,12 @@ public class ComponentList extends JPanel { private static final long serialVersionUID = 1759644503942876737L; + /** + * Sets the layout of this {@link ComponentList} to be a vertically oriented + * BoxLayout. + * + * @since Envoy v0.1-beta + */ public ComponentList() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); } /** @@ -74,7 +80,7 @@ public class ComponentList extends JPanel { // Synchronize with new model this.model = model; if (model != null) this.model.setComponentList(this); - synchronizeModel(); + if (renderer != null) synchronizeModel(); } /** @@ -84,9 +90,11 @@ public class ComponentList extends JPanel { * @since Envoy v0.3-alpha */ public void synchronizeModel() { - removeAll(); - if (model != null) model.forEach(this::add); - revalidate(); + if (model != null && renderer != null) { + removeAll(); + model.forEach(this::add); + revalidate(); + } } /** @@ -112,9 +120,11 @@ public class ComponentList extends JPanel { clearSelections(); currentSelections.add(index); } + if (renderer != null) { final JComponent component = renderer.getListCellComponent(this, elem, isSelected); component.addMouseListener(getSelectionListener(index)); add(component, index); + } } /** diff --git a/src/main/java/envoy/client/ui/list/ComponentListModel.java b/src/main/java/envoy/client/ui/list/ComponentListModel.java index 95775c1..aad379e 100644 --- a/src/main/java/envoy/client/ui/list/ComponentListModel.java +++ b/src/main/java/envoy/client/ui/list/ComponentListModel.java @@ -115,6 +115,6 @@ public final class ComponentListModel implements Iterable, Serializable { */ void setComponentList(ComponentList componentList) { this.componentList = componentList; - if (componentList != null) componentList.synchronizeModel(); + if (componentList != null && componentList.getRenderer() != null) componentList.synchronizeModel(); } }