/*
* $Id$
*
* Copyright (c) 2000-2003 by Rodney Kinney
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.build.module;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainView;
import javax.swing.text.Segment;
import javax.swing.text.TabExpander;
import javax.swing.text.Utilities;
import javax.swing.text.View;
import javax.swing.text.WrappedPlainView;
import VASSAL.build.Buildable;
import VASSAL.build.GameModule;
import VASSAL.command.Command;
import VASSAL.command.CommandEncoder;
import VASSAL.configure.ColorConfigurer;
import VASSAL.configure.FontConfigurer;
import VASSAL.i18n.Resources;
import VASSAL.preferences.Prefs;
import VASSAL.tools.ErrorDialog;
import VASSAL.tools.KeyStrokeSource;
import VASSAL.tools.ScrollPane;
/**
* The chat window component. Displays text messages and
* accepts input. Also acts as a {@link CommandEncoder},
* encoding/decoding commands that display message in the text area
*/
public class Chatter extends JPanel implements CommandEncoder, Buildable {
private static final long serialVersionUID = 1L;
protected JTextArea conversation;
protected JTextField input;
protected JScrollPane scroll = new ScrollPane(
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
protected static final String MY_CHAT_COLOR = "myChatColor"; //$NON-NLS-1$
protected static final String OTHER_CHAT_COLOR = "otherChatColor"; //$NON-NLS-1$
protected static final String GAME_MSG_COLOR = "gameMessageColor"; //$NON-NLS-1$
protected static final String SYS_MSG_COLOR = "systemMessageColor"; //$NON-NLS-1$
protected Color gameMsg;
protected Color systemMsg;
protected Color myChat;
protected Color otherChat;
public static final String getAnonymousUserName() {
return Resources.getString("Chat.anonymous"); //$NON-NLS-1$
}
public Chatter() {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
conversation = new JTextArea(15, 60);
for (int i = 0; i < 15; ++i) {
conversation.append("\n"); //$NON-NLS-1$
}
conversation.setEditable(false);
conversation.setLineWrap(true);
conversation.setWrapStyleWord(true);
conversation.setUI(new UI());
conversation.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
scroll.getVerticalScrollBar().setValue(scroll.getVerticalScrollBar().getMaximum());
}
});
input = new JTextField(60);
input.setFocusTraversalKeysEnabled(false);
input.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
send(formatChat(e.getActionCommand()));
input.setText(""); //$NON-NLS-1$
}
});
input.setMaximumSize(new Dimension(input.getMaximumSize().width,
input.getPreferredSize().height));
scroll.setViewportView(conversation);
add(scroll);
add(input);
}
private String formatChat(String text) {
final String id = GlobalOptions.getInstance().getPlayerId();
return "<" + (id.length() == 0 ? "("+getAnonymousUserName()+")" : id) + "> - " + text; //$NON-NLS-1$ //$NON-NLS-2$
}
public JTextField getInputField() {
return input;
}
/**
* Display a message in the text area
*/
public void show(String s) {
conversation.append("\n" + s); //$NON-NLS-1$
}
/** @deprecated use GlobalOptions.getPlayerId() */
@Deprecated public void setHandle(String s) {
}
/** @deprecated use GlobalOptions.getPlayerId() */
@Deprecated public String getHandle() {
return GlobalOptions.getInstance().getPlayerId();
}
/**
* Set the Font used by the text area
*/
public void setFont(Font f) {
if (input != null) {
if (input.getText().length() == 0) {
input.setText("XXX"); //$NON-NLS-1$
input.setFont(f);
input.setText(""); //$NON-NLS-1$
}
else
input.setFont(f);
}
if (conversation != null) {
conversation.setFont(f);
}
}
public void build(org.w3c.dom.Element e) {
}
public org.w3c.dom.Element getBuildElement(org.w3c.dom.Document doc) {
return doc.createElement(getClass().getName());
}
/**
* Expects to be added to a GameModule. Adds itself to the
* controls window and registers itself as a
* {@link CommandEncoder} */
public void addTo(Buildable b) {
GameModule mod = (GameModule) b;
mod.setChatter(this);
mod.addCommandEncoder(this);
mod.addKeyStrokeSource(new KeyStrokeSource(this, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT));
final FontConfigurer chatFont = new FontConfigurer(
"ChatFont", Resources.getString("Chatter.chat_font_preference")
);
chatFont.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
setFont((Font) evt.getNewValue());
}
});
mod.getControlPanel().add(this, BorderLayout.CENTER);
chatFont.fireUpdate();
mod.getPrefs().addOption(Resources.getString("Chatter.chat_window"), chatFont); //$NON-NLS-1$
//Bug 10179 - Do not re-read Chat colors each time the Chat Window is repainted.
final Prefs globalPrefs = Prefs.getGlobalPrefs();
//
// game message color
//
final ColorConfigurer gameMsgColor = new ColorConfigurer(
GAME_MSG_COLOR,
Resources.getString("Chatter.game_messages_preference"),
Color.magenta
);
gameMsgColor.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
gameMsg = (Color) e.getNewValue();
}
});
globalPrefs.addOption(
Resources.getString("Chatter.chat_window"), gameMsgColor
);
gameMsg = (Color) globalPrefs.getValue(GAME_MSG_COLOR);
//
// system message color
//
final ColorConfigurer systemMsgColor = new ColorConfigurer(
SYS_MSG_COLOR,
Resources.getString("Chatter.system_message_preference"),
new Color(160, 160, 160)
);
systemMsgColor.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
systemMsg = (Color) e.getNewValue();
}
});
globalPrefs.addOption(
Resources.getString("Chatter.chat_window"), systemMsgColor
);
systemMsg = (Color) globalPrefs.getValue(SYS_MSG_COLOR);
//
// my message color
//
final ColorConfigurer myChatColor = new ColorConfigurer(
MY_CHAT_COLOR,
Resources.getString("Chatter.my_text_preference"),
Color.gray
);
myChatColor.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
myChat = (Color) e.getNewValue();
}
});
globalPrefs.addOption(
Resources.getString("Chatter.chat_window"), myChatColor
);
myChat = (Color) globalPrefs.getValue(MY_CHAT_COLOR);
//
// other message color
//
final ColorConfigurer otherChatColor = new ColorConfigurer(
OTHER_CHAT_COLOR,
Resources.getString("Chatter.other_text_preference"),
Color.black
);
otherChatColor.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
otherChat = (Color) e.getNewValue();
}
});
globalPrefs.addOption(
Resources.getString("Chatter.chat_window"), otherChatColor
);
otherChat = (Color) globalPrefs.getValue(OTHER_CHAT_COLOR);
}
public void add(Buildable b) {
}
public Command decode(String s) {
if (s.startsWith("CHAT")) { //$NON-NLS-1$
return new DisplayText(this, s.substring(4));
}
else {
return null;
}
}
public String encode(Command c) {
if (c instanceof DisplayText) {
return "CHAT" + ((DisplayText) c).msg; //$NON-NLS-1$
}
else {
return null;
}
}
/**
* Displays the message, Also logs and sends to the server
* a {@link Command} that displays this message
*/
public void send(String msg) {
if (msg != null
&& msg.length() > 0) {
show(msg);
GameModule.getGameModule().sendAndLog(new DisplayText(this, msg));
}
}
/**
* Classes other than the Chatter itself may forward KeyEvents
* to the Chatter by using this method
*/
public void keyCommand(KeyStroke e) {
if ((e.getKeyCode() == 0 || e.getKeyCode() == KeyEvent.CHAR_UNDEFINED)
&& !Character.isISOControl(e.getKeyChar())) {
input.setText(input.getText() + e.getKeyChar());
}
else if (e.isOnKeyRelease()) {
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
if (input.getText().length() > 0)
send(formatChat(input.getText()));
input.setText(""); //$NON-NLS-1$
break;
case KeyEvent.VK_BACK_SPACE:
case KeyEvent.VK_DELETE:
String s = input.getText();
if (s.length() > 0)
input.setText(s.substring(0, s.length() - 1));
break;
}
}
}
private class UI extends BasicTextAreaUI {
public View create(javax.swing.text.Element elem) {
JTextComponent c = getComponent();
if (c instanceof JTextArea) {
JTextArea area = (JTextArea) c;
View v;
if (area.getLineWrap()) {
v = new WrappedView(elem, area.getWrapStyleWord());
}
else {
v = new PView(elem);
}
return v;
}
return null;
}
}
private int drawColoredText(Graphics g, int x, int y, TabExpander ex, Document doc,
int p0, int p1, Element elem) throws BadLocationException {
final Segment s = new Segment();
doc.getText(p0, p1 - p0, s);
g.setColor(getColor(elem));
final Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
return Utilities.drawTabbedText(s, x, y, g, ex, p0);
}
private class WrappedView extends WrappedPlainView {
private WrappedView(Element el, boolean wrap) {
super(el, wrap);
}
protected int drawUnselectedText(Graphics g, int x, int y,
int p0, int p1) throws BadLocationException {
final Element root = getElement();
return drawColoredText(g, x, y, this, getDocument(), p0, p1, root.getElement(root.getElementIndex(p0)));
}
}
private class PView extends PlainView {
private PView(Element el) {
super(el);
}
protected int drawUnselectedText(Graphics g, int x, int y,
int p0, int p1) throws BadLocationException {
Element root = getElement();
return drawColoredText(g, x, y, this, getDocument(), p0, p1, root.getElement(root.getElementIndex(p0)));
}
}
/**
* Determines the color with which to draw a given line of text
* @return the Color to draw
*/
protected Color getColor(Element elem) {
Color col = null;
try {
final String s = elem.getDocument().getText(
elem.getStartOffset(), elem.getEndOffset() - elem.getStartOffset()
).trim();
if (s.length() > 0) {
switch (s.charAt(0)) {
case '*':
col = gameMsg;
break;
case '-':
col = systemMsg;
break;
default:
if (s.startsWith(formatChat(""))) { //$NON-NLS-1$
col = myChat;
}
else {
col = otherChat;
}
break;
}
}
}
catch (BadLocationException e) {
ErrorDialog.bug(e);
}
return col == null ? Color.black : col;
}
/**
* This is a {@link Command} object that, when executed, displays
* a text message in the Chatter's text area */
public static class DisplayText extends Command {
private String msg;
private Chatter c;
public DisplayText(Chatter c, String s) {
this.c = c;
msg = s;
if (msg.startsWith("<>")) {
msg = "<(" + Chatter.getAnonymousUserName() + ")>" + s.substring(2);
}
else {
msg = s;
}
}
public void executeCommand() {
c.show(msg);
}
public Command myUndoCommand() {
return new DisplayText(c, Resources.getString("Chatter.undo_message", msg)); //$NON-NLS-1$
}
public String getMessage() {
return msg;
}
public String getDetails() {
return msg;
}
}
public static void main(String[] args) {
Chatter chat = new Chatter();
JFrame f = new JFrame();
f.add(chat);
f.pack();
f.setVisible(true);
}
}