package games.strategy.triplea.ui;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Insets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Action;
import javax.swing.BoundedRangeModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JButton;
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.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.tree.TreeNode;
import games.strategy.debug.ClientLogger;
import games.strategy.engine.data.GameData;
import games.strategy.engine.data.PlayerID;
import games.strategy.engine.history.Event;
import games.strategy.engine.history.HistoryNode;
import games.strategy.engine.history.Round;
import games.strategy.engine.history.Step;
import games.strategy.triplea.delegate.remote.IEditDelegate;
import games.strategy.ui.SwingAction;
public class CommentPanel extends JPanel {
private static final long serialVersionUID = -9122162393288045888L;
private JTextPane m_text;
private JScrollPane m_scrollPane;
private JTextField m_nextMessage;
private JButton m_save;
private final GameData m_data;
private final TripleAFrame m_frame;
private Map<PlayerID, Icon> m_iconMap;
private final SimpleAttributeSet bold = new SimpleAttributeSet();
private final SimpleAttributeSet italic = new SimpleAttributeSet();
private final SimpleAttributeSet normal = new SimpleAttributeSet();
public CommentPanel(final TripleAFrame frame, final GameData data) {
m_frame = frame;
m_data = data;
init();
}
private void init() {
createComponents();
layoutComponents();
setupKeyMap();
StyleConstants.setBold(bold, true);
StyleConstants.setItalic(italic, true);
setSize(300, 200);
loadHistory();
setupListeners();
}
private void layoutComponents() {
final Container content = this;
content.setLayout(new BorderLayout());
m_scrollPane = new JScrollPane(m_text);
content.add(m_scrollPane, BorderLayout.CENTER);
content.add(m_scrollPane, BorderLayout.CENTER);
final JPanel savePanel = new JPanel();
savePanel.setLayout(new BorderLayout());
savePanel.add(m_nextMessage, BorderLayout.CENTER);
savePanel.add(m_save, BorderLayout.WEST);
content.add(savePanel, BorderLayout.SOUTH);
}
private void createComponents() {
m_text = new JTextPane();
m_text.setEditable(false);
m_text.setFocusable(false);
m_nextMessage = new JTextField(10);
// when enter is pressed, send the message
final Insets inset = new Insets(3, 3, 3, 3);
m_save = new JButton(m_saveAction);
m_save.setMargin(inset);
m_save.setFocusable(false);
// create icon map
m_iconMap = new HashMap<>();
for (final PlayerID playerId : m_data.getPlayerList().getPlayers()) {
m_iconMap.put(playerId, new ImageIcon(m_frame.getUIContext().getFlagImageFactory().getSmallFlag(playerId)));
}
}
private void setupListeners() {
m_data.getHistory().addTreeModelListener(new TreeModelListener() {
@Override
public void treeNodesChanged(final TreeModelEvent e) {}
@Override
public void treeNodesInserted(final TreeModelEvent e) {
readHistoryTreeEvent(e);
}
@Override
public void treeNodesRemoved(final TreeModelEvent e) {}
@Override
public void treeStructureChanged(final TreeModelEvent e) {
readHistoryTreeEvent(e);
}
});
}
private void readHistoryTreeEvent(final TreeModelEvent e) {
final TreeModelEvent tme = e;
final Runnable runner = () -> {
m_data.acquireReadLock();
try {
final Document doc = m_text.getDocument();
final HistoryNode node = (HistoryNode) (tme.getTreePath().getLastPathComponent());
final TreeNode child = node == null ? null : (node.getChildCount() > 0 ? node.getLastChild() : null);
final String title =
child != null ? (child instanceof Event ? ((Event) child).getDescription() : child.toString())
: (node != null ? node.getTitle() : "");
final Pattern p = Pattern.compile("^COMMENT: (.*)");
final Matcher m = p.matcher(title);
if (m.matches()) {
final PlayerID playerId = m_data.getSequence().getStep().getPlayerID();
final int round = m_data.getSequence().getRound();
final String player = playerId.getName();
final Icon icon = m_iconMap.get(playerId);
try {
// insert into ui document
final String prefix = " " + player + "(" + round + ") : ";
m_text.insertIcon(icon);
doc.insertString(doc.getLength(), prefix, bold);
doc.insertString(doc.getLength(), m.group(1) + "\n", normal);
} catch (final BadLocationException e1) {
ClientLogger.logQuietly(e1);
}
}
} finally {
m_data.releaseReadLock();
}
};
// invoke in the swing event thread
if (SwingUtilities.isEventDispatchThread()) {
runner.run();
} else {
SwingUtilities.invokeLater(runner);
}
}
private void setupKeyMap() {
final InputMap nextMessageKeymap = m_nextMessage.getInputMap();
nextMessageKeymap.put(KeyStroke.getKeyStroke('\n'), m_saveAction);
}
private void loadHistory() {
final Document doc = m_text.getDocument();
final HistoryNode rootNode = (HistoryNode) m_data.getHistory().getRoot();
@SuppressWarnings("unchecked")
final Enumeration<HistoryNode> nodeEnum = rootNode.preorderEnumeration();
final Pattern p = Pattern.compile("^COMMENT: (.*)");
String player = "";
int round = 0;
Icon icon = null;
while (nodeEnum.hasMoreElements()) {
final HistoryNode node = nodeEnum.nextElement();
if (node instanceof Round) {
round++;
} else if (node instanceof Step) {
final PlayerID playerId = ((Step) node).getPlayerID();
if (playerId != null) {
player = playerId.getName();
icon = m_iconMap.get(playerId);
}
} else {
final String title = node.getTitle();
final Matcher m = p.matcher(title);
if (m.matches()) {
try {
// insert into ui document
final String prefix = " " + player + "(" + round + ") : ";
m_text.insertIcon(icon);
doc.insertString(doc.getLength(), prefix, bold);
doc.insertString(doc.getLength(), m.group(1) + "\n", normal);
} catch (final BadLocationException e) {
ClientLogger.logQuietly(e);
}
}
}
}
}
/** thread safe. */
public void addMessage(final String message) {
final Runnable runner = () -> {
try {
final Document doc = m_text.getDocument();
// save history entry
final IEditDelegate delegate = m_frame.getEditDelegate();
String error;
if (delegate == null) {
error = "You can only add comments during your turn";
} else {
error = delegate.addComment(message);
}
if (error != null) {
doc.insertString(doc.getLength(), error + "\n", italic);
}
} catch (final BadLocationException e) {
ClientLogger.logQuietly(e);
}
final BoundedRangeModel scrollModel = m_scrollPane.getVerticalScrollBar().getModel();
scrollModel.setValue(scrollModel.getMaximum());
};
// invoke in the swing event thread
if (SwingUtilities.isEventDispatchThread()) {
runner.run();
} else {
SwingUtilities.invokeLater(runner);
}
}
/**
* 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.logQuietly(e);
}
}
private final Action m_saveAction = SwingAction.of("Add Comment", e -> {
if (m_nextMessage.getText().trim().length() == 0) {
return;
}
addMessage(m_nextMessage.getText());
m_nextMessage.setText("");
});
}