package games.strategy.engine.chat; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import javax.swing.SwingUtilities; import javax.swing.UIManager; import games.strategy.net.INode; import games.strategy.ui.SwingAction; public class ChatPlayerPanel extends JPanel implements IChatListener { private static final long serialVersionUID = -3153022965393962945L; private static final Icon ignoreIcon; static { final URL ignore = ChatPlayerPanel.class.getResource("ignore.png"); if (ignore == null) { throw new IllegalStateException("Could not find ignore icon"); } Image img; try { img = ImageIO.read(ignore); } catch (final IOException e) { throw new IllegalStateException(e); } ignoreIcon = new ImageIcon(img); } private JList<INode> players; private DefaultListModel<INode> listModel; private Chat chat; private final Set<String> hiddenPlayers = new HashSet<>(); private final IStatusListener statusListener; // if our renderer is overridden // we do not set this directly on the JList, // instead we feed it the node name and staus as a string private ListCellRenderer<Object> setCellRenderer = new DefaultListCellRenderer(); private final List<IPlayerActionFactory> actionFactories = new ArrayList<>(); public ChatPlayerPanel(final Chat chat) { createComponents(); layoutComponents(); setupListeners(); setWidgetActivation(); statusListener = (node, newStatus) -> repaint(); setChat(chat); } public void addHiddenPlayerName(final String name) { hiddenPlayers.add(name); } public void shutDown() { if (chat != null) { chat.removeChatListener(this); chat.getStatusManager().removeStatusListener(statusListener); } chat = null; this.setVisible(false); this.removeAll(); } public void setChat(final Chat chat) { if (this.chat != null) { this.chat.removeChatListener(this); this.chat.getStatusManager().removeStatusListener(statusListener); } this.chat = chat; if (chat != null) { chat.addChatListener(this); this.chat.getStatusManager().addStatusListener(statusListener); } else { // empty our player list updatePlayerList(Collections.emptyList()); } repaint(); } /** * set minimum size based on players (number and max name length) and distribution to playerIDs. */ private void setDynamicPreferredSize() { final List<INode> onlinePlayers = chat.getOnlinePlayers(); int maxNameLength = 0; final FontMetrics fontMetrics = this.getFontMetrics(UIManager.getFont("TextField.font")); for (final INode iNode : onlinePlayers) { maxNameLength = Math.max(maxNameLength, fontMetrics.stringWidth(iNode.getName())); } int iconCounter = 0; if (setCellRenderer instanceof PlayerChatRenderer) { iconCounter = ((PlayerChatRenderer) setCellRenderer).getMaxIconCounter(); } setPreferredSize(new Dimension(maxNameLength + 40 + iconCounter * 14, 80)); } private void createComponents() { listModel = new DefaultListModel<>(); players = new JList<>(listModel); players.setFocusable(false); players.setCellRenderer((list, node, index, isSelected, cellHasFocus) -> { if (setCellRenderer == null) { return new JLabel(); } final DefaultListCellRenderer renderer; if (setCellRenderer instanceof PlayerChatRenderer) { renderer = (DefaultListCellRenderer) setCellRenderer.getListCellRendererComponent(list, node, index, isSelected, cellHasFocus); } else { renderer = (DefaultListCellRenderer) setCellRenderer.getListCellRendererComponent(list, getDisplayString(node), index, isSelected, cellHasFocus); } if (chat.isIgnored(node)) { renderer.setIcon(ignoreIcon); } return renderer; }); } private void layoutComponents() { setLayout(new BorderLayout()); add(new JScrollPane(players), BorderLayout.CENTER); } private void setupListeners() { players.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(final MouseEvent e) { mouseOnPlayersList(e); } @Override public void mousePressed(final MouseEvent e) { mouseOnPlayersList(e); } @Override public void mouseReleased(final MouseEvent e) { mouseOnPlayersList(e); } }); actionFactories.add(clickedOn -> { // you can't slap or ignore yourself if (clickedOn.equals(chat.getLocalNode())) { return Collections.emptyList(); } final boolean isIgnored = chat.isIgnored(clickedOn); final Action ignore = SwingAction.of(isIgnored ? "Stop Ignoring" : "Ignore", e -> { chat.setIgnored(clickedOn, !isIgnored); repaint(); }); final Action slap = new AbstractAction("Slap " + clickedOn.getName()) { private static final long serialVersionUID = -5514772068903406263L; @Override public void actionPerformed(final ActionEvent event) { chat.sendSlap(clickedOn.getName()); } }; return Arrays.asList(slap, ignore); }); } private void setWidgetActivation() {} /** * The renderer will be passed in a string. */ public void setPlayerRenderer(final ListCellRenderer<Object> renderer) { setCellRenderer = renderer; setDynamicPreferredSize(); } private void mouseOnPlayersList(final MouseEvent e) { if (!e.isPopupTrigger()) { return; } final int index = players.locationToIndex(e.getPoint()); if (index == -1) { return; } final INode player = listModel.get(index); final JPopupMenu menu = new JPopupMenu(); boolean hasActions = false; for (final IPlayerActionFactory factory : actionFactories) { final List<Action> actions = factory.mouseOnPlayer(player); if (actions != null && !actions.isEmpty()) { if (hasActions) { menu.addSeparator(); } hasActions = true; for (final Action a : actions) { menu.add(a); } } } if (hasActions) { menu.show(players, e.getX(), e.getY()); } } /** * @param players * - a collection of Strings representing player names. */ @Override public synchronized void updatePlayerList(final Collection<INode> players) { final Runnable runner = () -> { listModel.clear(); for (final INode name : players) { if (!hiddenPlayers.contains(name.getName())) { listModel.addElement(name); } } }; // invoke in the swing event thread if (SwingUtilities.isEventDispatchThread()) { runner.run(); } else { SwingUtilities.invokeLater(runner); } } @Override public void addMessageWithSound(final String message, final String from, final boolean thirdperson, final String sound) {} @Override public void addMessage(final String message, final String from, final boolean thirdperson) {} private String getDisplayString(final INode node) { if (chat == null) { return ""; } String extra = ""; final String notes = chat.getNotesForNode(node); if (notes != null && notes.length() > 0) { extra = extra + notes; } String status = chat.getStatusManager().getStatus(node); final StringBuilder statusSB = new StringBuilder(""); if (status != null && status.length() > 0) { if (status.length() > 25) { status = status.substring(0, 25); } for (int i = 0; i < status.length(); i++) { final char c = status.charAt(i); if (c >= '\u0300' && c <= '\u036F') { // skip combining characters continue; } statusSB.append(c); } extra = extra + " (" + statusSB.toString() + ")"; } if (extra.length() == 0) { return node.getName(); } return node.getName() + extra; } @Override public void addStatusMessage(final String message) {} /** * Add an action factory that will be used to populate the pop up meny when * right clicking on a player in the chat panel. */ public void addActionFactory(final IPlayerActionFactory actionFactory) { actionFactories.add(actionFactory); } public void remove(final IPlayerActionFactory actionFactory) { actionFactories.remove(actionFactory); } }