package games.strategy.engine.chat;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import javax.swing.Action;
import javax.swing.BoundedRangeModel;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import games.strategy.debug.ClientLogger;
import games.strategy.net.INode;
import games.strategy.net.ServerMessenger;
import games.strategy.sound.ClipPlayer;
import games.strategy.sound.SoundPath;
import games.strategy.ui.SwingAction;
/**
* A Chat window.
*
* <p>
* Mutiple chat panels can be connected to the same Chat.
* </p>
*
* <p>
* We can change the chat we are connected to using the setChat(...) method.
* </p>
*/
public class ChatMessagePanel extends JPanel implements IChatListener {
private static final long serialVersionUID = 118727200083595226L;
private final ChatFloodControl floodControl = new ChatFloodControl();
private static final int MAX_LINES = 5000;
private JTextPane text;
private JScrollPane scrollPane;
private JTextField nextMessage;
private JButton send;
private JButton setStatus;
private Chat chat;
private boolean showTime = false;
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("'('HH:mm:ss')'");
private final SimpleAttributeSet bold = new SimpleAttributeSet();
private final SimpleAttributeSet italic = new SimpleAttributeSet();
private final SimpleAttributeSet normal = new SimpleAttributeSet();
public static final String ME = "/me ";
private static boolean isThirdPerson(final String msg) {
return msg.toLowerCase().startsWith(ME);
}
public ChatMessagePanel(final Chat chat) {
init();
setChat(chat);
}
private void init() {
createComponents();
layoutComponents();
StyleConstants.setBold(bold, true);
StyleConstants.setItalic(italic, true);
setSize(300, 200);
}
public String getAllText() {
return text.getText();
}
public void shutDown() {
if (chat != null) {
chat.removeChatListener(this);
cleanupKeyMap();
}
chat = null;
this.setVisible(false);
this.removeAll();
}
public void setChat(final Chat chat) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingAction.invokeAndWait(() -> setChat(chat));
return;
}
if (chat != null) {
chat.removeChatListener(this);
cleanupKeyMap();
}
this.chat = chat;
if (chat != null) {
setupKeyMap();
chat.addChatListener(this);
send.setEnabled(true);
text.setEnabled(true);
synchronized (chat.getMutex()) {
text.setText("");
for (final ChatMessage message : chat.getChatHistory()) {
if (message.getFrom().equals(chat.getServerNode().getName())) {
if (message.getMessage().equals(ServerMessenger.YOU_HAVE_BEEN_MUTED_LOBBY)) {
addChatMessage("YOUR LOBBY CHATTING HAS BEEN TEMPORARILY 'MUTED' BY THE ADMINS, TRY AGAIN LATER",
"ADMIN_CHAT_CONTROL", false);
continue;
} else if (message.getMessage().equals(ServerMessenger.YOU_HAVE_BEEN_MUTED_GAME)) {
addChatMessage("YOUR CHATTING IN THIS GAME HAS BEEN 'MUTED' BY THE HOST", "HOST_CHAT_CONTROL", false);
continue;
}
}
addChatMessage(message.getMessage(), message.getFrom(), message.isMyMessage());
}
}
} else {
send.setEnabled(false);
text.setEnabled(false);
updatePlayerList(Collections.emptyList());
}
}
public Chat getChat() {
return chat;
}
public void setShowTime(final boolean showTime) {
this.showTime = showTime;
}
private void layoutComponents() {
final Container content = this;
content.setLayout(new BorderLayout());
scrollPane = new JScrollPane(text);
content.add(scrollPane, BorderLayout.CENTER);
final JPanel sendPanel = new JPanel();
sendPanel.setLayout(new BorderLayout());
sendPanel.add(nextMessage, BorderLayout.CENTER);
sendPanel.add(send, BorderLayout.WEST);
sendPanel.add(setStatus, BorderLayout.EAST);
content.add(sendPanel, BorderLayout.SOUTH);
}
@Override
public boolean requestFocusInWindow() {
return nextMessage.requestFocusInWindow();
}
private void createComponents() {
text = new JTextPane();
text.setEditable(false);
text.addMouseListener(new MouseListener() {
@Override
public void mouseReleased(final MouseEvent e) {
final String markedText = text.getSelectedText();
if (markedText == null || markedText.length() == 0) {
nextMessage.requestFocusInWindow();
}
}
@Override
public void mousePressed(final MouseEvent e) {}
@Override
public void mouseExited(final MouseEvent e) {}
@Override
public void mouseEntered(final MouseEvent e) {}
@Override
public void mouseClicked(final MouseEvent e) {}
});
nextMessage = new JTextField(10);
// when enter is pressed, send the message
setStatus = new JButton(setStatusAction);
setStatus.setFocusable(false);
final Insets inset = new Insets(3, 3, 3, 3);
send = new JButton(sendAction);
send.setMargin(inset);
send.setFocusable(false);
}
private void setupKeyMap() {
final InputMap nextMessageKeymap = nextMessage.getInputMap();
nextMessageKeymap.put(KeyStroke.getKeyStroke('\n'), sendAction);
nextMessageKeymap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_UP, 0, false), upAction);
nextMessageKeymap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DOWN, 0, false), downAction);
}
private void cleanupKeyMap() {
final InputMap nextMessageKeymap = nextMessage.getInputMap();
nextMessageKeymap.remove(KeyStroke.getKeyStroke('\n'));
nextMessageKeymap.remove(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_UP, 0, false));
nextMessageKeymap.remove(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DOWN, 0, false));
}
/** thread safe. */
@Override
public void addMessage(final String message, final String from, final boolean thirdperson) {
addMessageWithSound(message, from, thirdperson, SoundPath.CLIP_CHAT_MESSAGE);
}
/** thread safe. */
@Override
public void addMessageWithSound(final String message, final String from, final boolean thirdperson,
final String sound) {
final Runnable runner = () -> {
if (from == null || chat == null || chat.getServerNode() == null || chat.getServerNode().getName() == null) {
// someone likely disconnected from the game.
return;
}
if (from.equals(chat.getServerNode().getName())) {
if (message.equals(ServerMessenger.YOU_HAVE_BEEN_MUTED_LOBBY)) {
addChatMessage("YOUR LOBBY CHATTING HAS BEEN TEMPORARILY 'MUTED' BY THE ADMINS, TRY AGAIN LATER",
"ADMIN_CHAT_CONTROL", false);
return;
} else if (message.equals(ServerMessenger.YOU_HAVE_BEEN_MUTED_GAME)) {
addChatMessage("YOUR CHATTING IN THIS GAME HAS BEEN 'MUTED' BY THE HOST", "HOST_CHAT_CONTROL", false);
return;
}
}
if (!floodControl.allow(from, System.currentTimeMillis())) {
if (from.equals(chat.getLocalNode().getName())) {
addChatMessage("MESSAGE LIMIT EXCEEDED, TRY AGAIN LATER", "ADMIN_FLOOD_CONTROL", false);
}
return;
}
addChatMessage(message, from, thirdperson);
SwingUtilities.invokeLater(() -> {
final BoundedRangeModel scrollModel = scrollPane.getVerticalScrollBar().getModel();
scrollModel.setValue(scrollModel.getMaximum());
});
ClipPlayer.play(sound);
};
if (SwingUtilities.isEventDispatchThread()) {
runner.run();
} else {
SwingUtilities.invokeLater(runner);
}
}
private void addChatMessage(final String originalMessage, final String from, final boolean thirdperson) {
final String message = trimMessage(originalMessage);
final String time = simpleDateFormat.format(new Date());
final Document doc = text.getDocument();
try {
if (thirdperson) {
doc.insertString(doc.getLength(), (showTime ? "* " + time + " " + from : "* " + from), bold);
} else {
doc.insertString(doc.getLength(), (showTime ? time + " " + from + ": " : from + ": "), bold);
}
doc.insertString(doc.getLength(), " " + message + "\n", normal);
// don't let the chat get too big
trimLines(doc, MAX_LINES);
} catch (final BadLocationException e) {
ClientLogger.logError("There was an Error whilst trying to add the Chat Message \"" + message + "\" sent by "
+ from + " at " + time, e);
}
}
public void addServerMessage(final String message) {
try {
final Document doc = text.getDocument();
doc.insertString(doc.getLength(), message + "\n", normal);
} catch (final BadLocationException e) {
ClientLogger.logError("There was an Error whilst trying to add the Server Message \"" + message + "\"", e);
}
}
@Override
public void addStatusMessage(final String message) {
SwingUtilities.invokeLater(() -> {
try {
final Document doc = text.getDocument();
doc.insertString(doc.getLength(), message + "\n", italic);
// don't let the chat get too big
trimLines(doc, MAX_LINES);
} catch (final BadLocationException e) {
ClientLogger.logError("There was an Error whilst trying to add the Status Message \"" + message + "\"", e);
}
});
}
/**
* Show only the first n lines.
*/
public static void trimLines(final Document doc, final int lineCount) {
if (doc.getLength() < lineCount) {
return;
}
try {
final String text = doc.getText(0, doc.getLength());
int returnsFound = 0;
for (int i = text.length() - 1; i >= 0; i--) {
if (text.charAt(i) == '\n') {
returnsFound++;
}
if (returnsFound == lineCount) {
doc.remove(0, i);
return;
}
}
} catch (final BadLocationException e) {
ClientLogger.logError("There was an Error whilst trying trimming Chat", e);
}
}
private static String trimMessage(final String originalMessage) {
// don't allow messages that are too long
if (originalMessage.length() > 200) {
return originalMessage.substring(0, 199) + "...";
} else {
return originalMessage;
}
}
private final Action setStatusAction = SwingAction.of("Status...", e -> {
String status = JOptionPane.showInputDialog(JOptionPane.getFrameForComponent(ChatMessagePanel.this),
"Enter Status Text (leave blank for no status)", "");
if (status != null) {
if (status.trim().length() == 0) {
status = null;
}
chat.getStatusManager().setStatus(status);
}
});
private final Action sendAction = SwingAction.of("Send", e -> {
if (nextMessage.getText().trim().length() == 0) {
return;
}
if (isThirdPerson(nextMessage.getText())) {
chat.sendMessage(nextMessage.getText().substring(ME.length()), true);
} else {
chat.sendMessage(nextMessage.getText(), false);
}
nextMessage.setText("");
});
private final Action downAction = SwingAction.of(e -> {
if (chat == null) {
return;
}
chat.getSentMessagesHistory().next();
nextMessage.setText(chat.getSentMessagesHistory().current());
});
private final Action upAction = SwingAction.of(e -> {
if (chat == null) {
return;
}
chat.getSentMessagesHistory().prev();
nextMessage.setText(chat.getSentMessagesHistory().current());
});
@Override
public void updatePlayerList(final Collection<INode> players) {}
}