Big chat revamp to migrate various useful things from Yohoho like /command
handling, chat history support and chat mogrification into the base framework. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3690 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -21,9 +21,17 @@
|
|||||||
|
|
||||||
package com.threerings.crowd.chat.client;
|
package com.threerings.crowd.chat.client;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.samskivert.util.Collections;
|
||||||
import com.samskivert.util.HashIntMap;
|
import com.samskivert.util.HashIntMap;
|
||||||
import com.samskivert.util.ObserverList;
|
import com.samskivert.util.ObserverList;
|
||||||
import com.samskivert.util.ResultListener;
|
import com.samskivert.util.ResultListener;
|
||||||
@@ -85,11 +93,52 @@ public class ChatDirector extends BasicDirector
|
|||||||
public boolean isChatterValid (Name username);
|
public boolean isChatterValid (Name username);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to implement a slash command (e.g. <code>/who</code>).
|
||||||
|
*/
|
||||||
|
public static abstract class CommandHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handles the specified chat command.
|
||||||
|
*
|
||||||
|
* @param speakSvc an optional SpeakService object representing
|
||||||
|
* the object to send the chat message on.
|
||||||
|
* @param command the slash command that was used to invoke this
|
||||||
|
* handler (e.g. <code>/tell</code>).
|
||||||
|
* @param args the arguments provided along with the command (e.g.
|
||||||
|
* <code>Bob hello</code>) or <code>null</code> if no arguments
|
||||||
|
* were supplied.
|
||||||
|
* @param history an in/out parameter that allows the command to
|
||||||
|
* modify the text that will be appended to the chat history. If
|
||||||
|
* this is set to null, nothing will be appended.
|
||||||
|
*
|
||||||
|
* @return an untranslated string that will be reported to the
|
||||||
|
* chat box to convey an error response to the user, or {@link
|
||||||
|
* ChatCodes#SUCCESS}.
|
||||||
|
*/
|
||||||
|
public abstract String handleCommand (
|
||||||
|
SpeakService speakSvc, String command, String args,
|
||||||
|
String[] history);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this user should have access to this chat
|
||||||
|
* command.
|
||||||
|
*/
|
||||||
|
public boolean checkAccess (BodyObject user)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a chat director and initializes it with the supplied
|
* Creates a chat director and initializes it with the supplied
|
||||||
* context. The chat director will register itself as a location
|
* context. The chat director will register itself as a location
|
||||||
* observer so that it can automatically process place constrained
|
* observer so that it can automatically process place constrained
|
||||||
* chat.
|
* chat.
|
||||||
|
*
|
||||||
|
* @param msgmgr the message manager via which we do our translations.
|
||||||
|
* @param bundle the message bundle from which we obtain our
|
||||||
|
* chat-related translation strings.
|
||||||
*/
|
*/
|
||||||
public ChatDirector (CrowdContext ctx, MessageManager msgmgr, String bundle)
|
public ChatDirector (CrowdContext ctx, MessageManager msgmgr, String bundle)
|
||||||
{
|
{
|
||||||
@@ -102,6 +151,14 @@ public class ChatDirector extends BasicDirector
|
|||||||
|
|
||||||
// register ourselves as a location observer
|
// register ourselves as a location observer
|
||||||
_ctx.getLocationDirector().addLocationObserver(this);
|
_ctx.getLocationDirector().addLocationObserver(this);
|
||||||
|
|
||||||
|
// register our default chat handlers
|
||||||
|
MessageBundle msg = _msgmgr.getBundle(_bundle);
|
||||||
|
registerCommandHandler(msg, "help", new HelpHandler());
|
||||||
|
registerCommandHandler(msg, "clear", new ClearHandler());
|
||||||
|
registerCommandHandler(msg, "speak", new SpeakHandler());
|
||||||
|
registerCommandHandler(msg, "emote", new EmoteHandler());
|
||||||
|
registerCommandHandler(msg, "think", new ThinkHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -169,41 +226,54 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a chatter to our list of recent chatters.
|
* Registers a chat command handler.
|
||||||
|
*
|
||||||
|
* @param msg the message bundle via which the slash command will be
|
||||||
|
* translated (as <code>c.</code><i>command</i>). If no translation
|
||||||
|
* exists the command will be <code>/</code><i>command</i>.
|
||||||
|
* @param command the name of the command that will be used to invoke
|
||||||
|
* this handler (e.g. <code>tell</code> if the command will be invoked
|
||||||
|
* as <code>/tell</code>).
|
||||||
|
* @param handler the chat command handler itself.
|
||||||
*/
|
*/
|
||||||
protected void addChatter (Name name)
|
public void registerCommandHandler (
|
||||||
|
MessageBundle msg, String command, CommandHandler handler)
|
||||||
{
|
{
|
||||||
// check to see if the chatter validator approves..
|
String key = "c." + command;
|
||||||
if ((_chatterValidator != null) &&
|
if (msg.exists(key)) {
|
||||||
(!_chatterValidator.isChatterValid(name))) {
|
StringTokenizer st = new StringTokenizer(msg.get(key));
|
||||||
return;
|
while (st.hasMoreTokens()) {
|
||||||
}
|
_handlers.put(st.nextToken(), handler);
|
||||||
|
|
||||||
boolean wasthere = _chatters.remove(name);
|
|
||||||
_chatters.addFirst(name);
|
|
||||||
|
|
||||||
if (!wasthere) {
|
|
||||||
if (_chatters.size() > MAX_CHATTERS) {
|
|
||||||
_chatters.removeLast();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
notifyChatterObservers();
|
// fall back to just using the English command
|
||||||
|
_handlers.put(command, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies all registered {@link ChatterObserver}s that the list of
|
* Return the current size of the history.
|
||||||
* chatters has changed.
|
|
||||||
*/
|
*/
|
||||||
protected void notifyChatterObservers ()
|
public int getCommandHistorySize ()
|
||||||
{
|
{
|
||||||
_chatterObservers.apply(new ObserverList.ObserverOp() {
|
return _history.size();
|
||||||
public boolean apply (Object observer) {
|
}
|
||||||
((ChatterObserver)observer).chattersUpdated(
|
|
||||||
_chatters.listIterator());
|
/**
|
||||||
return true;
|
* Get the chat history entry at the specified index,
|
||||||
}
|
* with 0 being the oldest.
|
||||||
});
|
*/
|
||||||
|
public String getCommandHistory (int index)
|
||||||
|
{
|
||||||
|
return (String)_history.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the chat command history.
|
||||||
|
*/
|
||||||
|
public void clearCommandHistory ()
|
||||||
|
{
|
||||||
|
_history.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,23 +338,6 @@ public class ChatDirector extends BasicDirector
|
|||||||
bundle, message, SystemMessage.ATTENTION, PLACE_CHAT_TYPE);
|
bundle, message, SystemMessage.ATTENTION, PLACE_CHAT_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Display the specified system message as if it had come from the server.
|
|
||||||
*/
|
|
||||||
protected void displaySystem (
|
|
||||||
String bundle, String message, byte attLevel, String localtype)
|
|
||||||
{
|
|
||||||
// nothing should be untranslated, so pass the default bundle if need
|
|
||||||
// be.
|
|
||||||
if (bundle == null) {
|
|
||||||
bundle = _bundle;
|
|
||||||
}
|
|
||||||
SystemMessage msg = new SystemMessage();
|
|
||||||
msg.attentionLevel = attLevel;
|
|
||||||
msg.setClientInfo(xlate(bundle, message), localtype);
|
|
||||||
dispatchMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches the provided message to our chat displays.
|
* Dispatches the provided message to our chat displays.
|
||||||
*/
|
*/
|
||||||
@@ -295,27 +348,82 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a {@link #requestSpeak(SpeakService,String,byte)} on the
|
* Parses and delivers the supplied chat message. Slash command
|
||||||
* place object that we currently occupy.
|
* processing and mogrification are performed and the message is added
|
||||||
|
* to the chat history if appropriate.
|
||||||
|
*
|
||||||
|
* @param speakSvc the SpeakService representing the target dobj of
|
||||||
|
* the speak or null if we should speak in the "default" way.
|
||||||
|
* @param text the text to be parsed and sent.
|
||||||
|
* @param record if text is a command, should it be added to the history?
|
||||||
|
*
|
||||||
|
* @return <code>ChatCodes#SUCCESS</code> if the message was parsed
|
||||||
|
* and sent correctly, a translatable error string if there was some
|
||||||
|
* problem.
|
||||||
*/
|
*/
|
||||||
public void requestSpeak (String message)
|
public String requestChat (
|
||||||
|
SpeakService speakSvc, String text, boolean record)
|
||||||
{
|
{
|
||||||
requestSpeak(message, DEFAULT_MODE);
|
if (text.startsWith("/")) {
|
||||||
}
|
// split the text up into a command and arguments
|
||||||
|
String command = text.substring(1).toLowerCase();
|
||||||
|
String[] hist = new String[1];
|
||||||
|
String args = "";
|
||||||
|
int sidx = text.indexOf(" ");
|
||||||
|
if (sidx != -1) {
|
||||||
|
command = text.substring(1, sidx).toLowerCase();
|
||||||
|
args = text.substring(sidx+1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
HashMap possibleCommands = getCommandHandlers(command);
|
||||||
* Dispatches a {@link #requestSpeak(SpeakService,String,byte)} on the
|
switch (possibleCommands.size()) {
|
||||||
* place object that we currently occupy.
|
case 0:
|
||||||
*/
|
StringTokenizer tok = new StringTokenizer(text);
|
||||||
public void requestSpeak (String message, byte mode)
|
return MessageBundle.tcompose(
|
||||||
{
|
"m.unknown_command", tok.nextToken());
|
||||||
// make sure we're currently in a place
|
|
||||||
if (_place == null) {
|
case 1:
|
||||||
return;
|
Iterator itr = possibleCommands.entrySet().iterator();
|
||||||
|
Map.Entry entry = (Map.Entry) itr.next();
|
||||||
|
String cmdName = (String) entry.getKey();
|
||||||
|
CommandHandler cmd = (CommandHandler) entry.getValue();
|
||||||
|
|
||||||
|
String result = cmd.handleCommand(speakSvc, cmdName, args, hist);
|
||||||
|
if (!result.equals(ChatCodes.SUCCESS)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
// get the final history-ready command string
|
||||||
|
hist[0] = "/" + ((hist[0] == null) ? command : hist[0]);
|
||||||
|
|
||||||
|
// remove from history if it was present and
|
||||||
|
// add it to the end
|
||||||
|
addToHistory(hist[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
default:
|
||||||
|
String alternativeCommands = "";
|
||||||
|
itr = Collections.getSortedIterator(possibleCommands.keySet());
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
cmdName = (String)itr.next();
|
||||||
|
alternativeCommands += " /" + cmdName;
|
||||||
|
}
|
||||||
|
return MessageBundle.tcompose(
|
||||||
|
"m.unspecific_command", alternativeCommands);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatch a speak request on the active place object
|
// if not a command then just speak
|
||||||
requestSpeak(_place.speakService, message, mode);
|
String message = text.trim();
|
||||||
|
if (StringUtil.blank(message)) {
|
||||||
|
// report silent failure for now
|
||||||
|
return ChatCodes.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deliverChat(speakSvc, message, ChatCodes.DEFAULT_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -326,7 +434,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
* possibly vetoed) before being dispatched.
|
* possibly vetoed) before being dispatched.
|
||||||
*
|
*
|
||||||
* @param speakService the speak service to use when generating the
|
* @param speakService the speak service to use when generating the
|
||||||
* speak request.
|
* speak request or null if we should speak in the current "place".
|
||||||
* @param message the contents of the speak message.
|
* @param message the contents of the speak message.
|
||||||
* @param mode a speech mode that will be interpreted by the {@link
|
* @param mode a speech mode that will be interpreted by the {@link
|
||||||
* ChatDisplay} implementations that eventually display this speak
|
* ChatDisplay} implementations that eventually display this speak
|
||||||
@@ -335,6 +443,13 @@ public class ChatDirector extends BasicDirector
|
|||||||
public void requestSpeak (
|
public void requestSpeak (
|
||||||
SpeakService speakService, String message, byte mode)
|
SpeakService speakService, String message, byte mode)
|
||||||
{
|
{
|
||||||
|
if (speakService == null) {
|
||||||
|
if (_place == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
speakService = _place.speakService;
|
||||||
|
}
|
||||||
|
|
||||||
// make sure they can say what they want to say
|
// make sure they can say what they want to say
|
||||||
message = filter(message, null, true);
|
message = filter(message, null, true);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
@@ -483,11 +598,22 @@ public class ChatDirector extends BasicDirector
|
|||||||
_auxes.remove(source.getOid());
|
_auxes.remove(source.getOid());
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited from interface
|
/**
|
||||||
protected void fetchServices (Client client)
|
* Run a message through all the currently registered filters.
|
||||||
|
*/
|
||||||
|
public String filter (String msg, Name otherUser, boolean outgoing)
|
||||||
{
|
{
|
||||||
// get a handle on our chat service
|
_filterMessageOp.setMessage(msg, otherUser, outgoing);
|
||||||
_cservice = (ChatService)client.requireService(ChatService.class);
|
_filters.apply(_filterMessageOp);
|
||||||
|
return _filterMessageOp.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the supplied message through the various chat mogrifications.
|
||||||
|
*/
|
||||||
|
public String mogrifyChat (String text)
|
||||||
|
{
|
||||||
|
return mogrifyChat(text, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
@@ -565,42 +691,6 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Translates the specified message using the specified bundle.
|
|
||||||
*/
|
|
||||||
protected String xlate (String bundle, String message)
|
|
||||||
{
|
|
||||||
if (bundle != null && _msgmgr != null) {
|
|
||||||
MessageBundle msgb = _msgmgr.getBundle(bundle);
|
|
||||||
if (msgb == null) {
|
|
||||||
Log.warning(
|
|
||||||
"No message bundle available to translate message " +
|
|
||||||
"[bundle=" + bundle + ", message=" + message + "].");
|
|
||||||
} else {
|
|
||||||
message = msgb.xlate(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks up and returns the message type associated with the specified
|
|
||||||
* oid.
|
|
||||||
*/
|
|
||||||
protected String getLocalType (int oid)
|
|
||||||
{
|
|
||||||
String type = (String)_auxes.get(oid);
|
|
||||||
return (type == null) ? PLACE_CHAT_TYPE : type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to assign unique ids to all speak requests.
|
|
||||||
*/
|
|
||||||
protected synchronized int nextRequestId ()
|
|
||||||
{
|
|
||||||
return _requestId++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
public void clientDidLogon (Client client)
|
public void clientDidLogon (Client client)
|
||||||
{
|
{
|
||||||
@@ -649,13 +739,291 @@ public class ChatDirector extends BasicDirector
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a message through all the currently registered filters.
|
* Called to determine whether we are permitted to post the supplied
|
||||||
|
* chat message. Derived classes may wish to throttle chat or restrict
|
||||||
|
* certain types in certain circumstances for whatever reason.
|
||||||
|
*
|
||||||
|
* @return null if the chat is permitted, a translatable string
|
||||||
|
* indicating the reason for rejection if not.
|
||||||
*/
|
*/
|
||||||
public String filter (String msg, Name otherUser, boolean outgoing)
|
protected String checkCanChat (
|
||||||
|
SpeakService speakSvc, String message, byte mode)
|
||||||
{
|
{
|
||||||
_filterMessageOp.setMessage(msg, otherUser, outgoing);
|
return null;
|
||||||
_filters.apply(_filterMessageOp);
|
}
|
||||||
return _filterMessageOp.getMessage();
|
|
||||||
|
/**
|
||||||
|
* Delivers a plain chat message (not a slash command) on the
|
||||||
|
* specified speak service in the specified mode. The message will be
|
||||||
|
* mogrified and filtered prior to delivery.
|
||||||
|
*
|
||||||
|
* @return {@link ChatCodes#SUCCESS} if the message was delivered or a
|
||||||
|
* string indicating why it failed.
|
||||||
|
*/
|
||||||
|
protected String deliverChat (
|
||||||
|
SpeakService speakSvc, String message, byte mode)
|
||||||
|
{
|
||||||
|
// run the message through our mogrification process
|
||||||
|
message = mogrifyChat(message, true, mode != ChatCodes.EMOTE_MODE);
|
||||||
|
|
||||||
|
// mogrification may result in something being turned into a slash
|
||||||
|
// command, in which case we have to run everything through again
|
||||||
|
// from the start
|
||||||
|
if (message.startsWith("/")) {
|
||||||
|
return requestChat(speakSvc, message, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure this client is not restricted from performing this
|
||||||
|
// chat message for some reason or other
|
||||||
|
String errmsg = checkCanChat(speakSvc, message, mode);
|
||||||
|
if (errmsg != null) {
|
||||||
|
return errmsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// speak on the specified service
|
||||||
|
requestSpeak(speakSvc, message, mode);
|
||||||
|
|
||||||
|
return ChatCodes.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified command to the history.
|
||||||
|
*/
|
||||||
|
protected void addToHistory (String cmd)
|
||||||
|
{
|
||||||
|
// remove any previous instance of this command
|
||||||
|
_history.remove(cmd);
|
||||||
|
|
||||||
|
// append it to the end
|
||||||
|
_history.add(cmd);
|
||||||
|
|
||||||
|
// prune the history once it extends beyond max size
|
||||||
|
if (_history.size() > MAX_COMMAND_HISTORY) {
|
||||||
|
_history.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mogrify common literary crutches into more appealing chat or
|
||||||
|
* commands.
|
||||||
|
*
|
||||||
|
* @param transformsAllowed if true, the chat may transformed into a
|
||||||
|
* different mode. (lol -> /emote laughs)
|
||||||
|
* @param capFirst if true, the first letter of the text is
|
||||||
|
* capitalized. This is not desired if the chat is already an emote.
|
||||||
|
*/
|
||||||
|
protected String mogrifyChat (
|
||||||
|
String text, boolean transformsAllowed, boolean capFirst)
|
||||||
|
{
|
||||||
|
int tlen = text.length();
|
||||||
|
if (tlen == 0) {
|
||||||
|
return text;
|
||||||
|
|
||||||
|
// check to make sure there aren't too many caps
|
||||||
|
} else if (tlen > 7) {
|
||||||
|
// count caps
|
||||||
|
int caps = 0;
|
||||||
|
for (int ii=0; ii < tlen; ii++) {
|
||||||
|
if (Character.isUpperCase(text.charAt(ii))) {
|
||||||
|
caps++;
|
||||||
|
if (caps > (tlen / 2)) {
|
||||||
|
// lowercase the whole string if there are
|
||||||
|
text = text.toLowerCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer buf = new StringBuffer(text);
|
||||||
|
buf = mogrifyChat(buf, transformsAllowed, capFirst);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper function for {@link #mogrifyChat}. */
|
||||||
|
protected StringBuffer mogrifyChat (
|
||||||
|
StringBuffer buf, boolean transformsAllowed, boolean capFirst)
|
||||||
|
{
|
||||||
|
// do the generic mogrifications and translations
|
||||||
|
buf = translatedReplacements("x.mogrifies", buf);
|
||||||
|
|
||||||
|
// perform themed expansions and transformations
|
||||||
|
if (transformsAllowed) {
|
||||||
|
buf = translatedReplacements("x.transforms", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// capitalize the first letter
|
||||||
|
if (capFirst) {
|
||||||
|
buf.setCharAt(0, Character.toUpperCase(buf.charAt(0)));
|
||||||
|
}
|
||||||
|
// and capitalize any letters after a sentence-ending punctuation
|
||||||
|
Pattern p = Pattern.compile("([^\\.][\\.\\?\\!](\\s)+\\p{Ll})");
|
||||||
|
Matcher m = p.matcher(buf);
|
||||||
|
if (m.find()) {
|
||||||
|
buf = new StringBuffer();
|
||||||
|
m.appendReplacement(buf, m.group().toUpperCase());
|
||||||
|
while (m.find()) {
|
||||||
|
m.appendReplacement(buf, m.group().toUpperCase());
|
||||||
|
}
|
||||||
|
m.appendTail(buf);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do all the replacements (mogrifications) specified in the
|
||||||
|
* translation string specified by the key.
|
||||||
|
*/
|
||||||
|
protected StringBuffer translatedReplacements (String key, StringBuffer buf)
|
||||||
|
{
|
||||||
|
MessageBundle bundle = _msgmgr.getBundle(_bundle);
|
||||||
|
if (!bundle.exists(key)) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
StringTokenizer st = new StringTokenizer(bundle.get(key), "#");
|
||||||
|
// apply the replacements to each mogrification that matches
|
||||||
|
while (st.hasMoreTokens()) {
|
||||||
|
String pattern = st.nextToken();
|
||||||
|
String replace = st.nextToken();
|
||||||
|
Matcher m = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE).
|
||||||
|
matcher(buf);
|
||||||
|
if (m.find()) {
|
||||||
|
buf = new StringBuffer();
|
||||||
|
m.appendReplacement(buf, replace);
|
||||||
|
// they may match more than once
|
||||||
|
while (m.find()) {
|
||||||
|
m.appendReplacement(buf, replace);
|
||||||
|
}
|
||||||
|
m.appendTail(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a hashmap containing all command handlers that match the
|
||||||
|
* specified command (i.e. the specified command is a prefix of their
|
||||||
|
* registered command string).
|
||||||
|
*/
|
||||||
|
protected HashMap getCommandHandlers (String command)
|
||||||
|
{
|
||||||
|
HashMap matches = new HashMap();
|
||||||
|
BodyObject user = (BodyObject)_ctx.getClient().getClientObject();
|
||||||
|
Iterator itr = _handlers.entrySet().iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
Map.Entry entry = (Map.Entry) itr.next();
|
||||||
|
String cmd = (String) entry.getKey();
|
||||||
|
if (!cmd.startsWith(command)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CommandHandler handler = (CommandHandler)entry.getValue();
|
||||||
|
if (!handler.checkAccess(user)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
matches.put(cmd, handler);
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a chatter to our list of recent chatters.
|
||||||
|
*/
|
||||||
|
protected void addChatter (Name name)
|
||||||
|
{
|
||||||
|
// check to see if the chatter validator approves..
|
||||||
|
if ((_chatterValidator != null) &&
|
||||||
|
(!_chatterValidator.isChatterValid(name))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean wasthere = _chatters.remove(name);
|
||||||
|
_chatters.addFirst(name);
|
||||||
|
|
||||||
|
if (!wasthere) {
|
||||||
|
if (_chatters.size() > MAX_CHATTERS) {
|
||||||
|
_chatters.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyChatterObservers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies all registered {@link ChatterObserver}s that the list of
|
||||||
|
* chatters has changed.
|
||||||
|
*/
|
||||||
|
protected void notifyChatterObservers ()
|
||||||
|
{
|
||||||
|
_chatterObservers.apply(new ObserverList.ObserverOp() {
|
||||||
|
public boolean apply (Object observer) {
|
||||||
|
((ChatterObserver)observer).chattersUpdated(
|
||||||
|
_chatters.listIterator());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the specified message using the specified bundle.
|
||||||
|
*/
|
||||||
|
protected String xlate (String bundle, String message)
|
||||||
|
{
|
||||||
|
if (bundle != null && _msgmgr != null) {
|
||||||
|
MessageBundle msgb = _msgmgr.getBundle(bundle);
|
||||||
|
if (msgb == null) {
|
||||||
|
Log.warning(
|
||||||
|
"No message bundle available to translate message " +
|
||||||
|
"[bundle=" + bundle + ", message=" + message + "].");
|
||||||
|
} else {
|
||||||
|
message = msgb.xlate(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified system message as if it had come from the server.
|
||||||
|
*/
|
||||||
|
protected void displaySystem (
|
||||||
|
String bundle, String message, byte attLevel, String localtype)
|
||||||
|
{
|
||||||
|
// nothing should be untranslated, so pass the default bundle if need
|
||||||
|
// be.
|
||||||
|
if (bundle == null) {
|
||||||
|
bundle = _bundle;
|
||||||
|
}
|
||||||
|
SystemMessage msg = new SystemMessage();
|
||||||
|
msg.attentionLevel = attLevel;
|
||||||
|
msg.setClientInfo(xlate(bundle, message), localtype);
|
||||||
|
dispatchMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up and returns the message type associated with the specified
|
||||||
|
* oid.
|
||||||
|
*/
|
||||||
|
protected String getLocalType (int oid)
|
||||||
|
{
|
||||||
|
String type = (String)_auxes.get(oid);
|
||||||
|
return (type == null) ? PLACE_CHAT_TYPE : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to assign unique ids to all speak requests.
|
||||||
|
*/
|
||||||
|
protected synchronized int nextRequestId ()
|
||||||
|
{
|
||||||
|
return _requestId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
protected void fetchServices (Client client)
|
||||||
|
{
|
||||||
|
// get a handle on our chat service
|
||||||
|
_cservice = (ChatService)client.requireService(ChatService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -708,6 +1076,114 @@ public class ChatDirector extends BasicDirector
|
|||||||
protected ChatMessage _message;
|
protected ChatMessage _message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Implements <code>/help</code>. */
|
||||||
|
protected class HelpHandler extends CommandHandler
|
||||||
|
{
|
||||||
|
public String handleCommand (
|
||||||
|
SpeakService speakSvc, String command, String args, String[] history)
|
||||||
|
{
|
||||||
|
String hcmd = "";
|
||||||
|
|
||||||
|
// grab the command they want help on
|
||||||
|
if (!StringUtil.blank(args)) {
|
||||||
|
hcmd = args;
|
||||||
|
int sidx = args.indexOf(" ");
|
||||||
|
if (sidx != -1) {
|
||||||
|
hcmd = args.substring(0, sidx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let the user give commands with or with the /
|
||||||
|
if (hcmd.startsWith("/")) {
|
||||||
|
hcmd = hcmd.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle "/help help" and "/help someboguscommand"
|
||||||
|
HashMap possibleCommands = getCommandHandlers(hcmd);
|
||||||
|
if (hcmd.equals("help") || possibleCommands.isEmpty()) {
|
||||||
|
possibleCommands = getCommandHandlers("");
|
||||||
|
possibleCommands.remove("help"); // remove help from the list
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is only one possible command display its usage
|
||||||
|
switch (possibleCommands.size()) {
|
||||||
|
case 1:
|
||||||
|
Iterator itr = possibleCommands.keySet().iterator();
|
||||||
|
// this is a little funny, but we display the feeback
|
||||||
|
// message by hand and return SUCCESS so that the chat
|
||||||
|
// entry field doesn't think that we've failed and
|
||||||
|
// preserve our command text
|
||||||
|
displayFeedback(null, "m.usage_" + (String)itr.next());
|
||||||
|
return ChatCodes.SUCCESS;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Object[] commands = possibleCommands.keySet().toArray();
|
||||||
|
Arrays.sort(commands);
|
||||||
|
String commandList = "";
|
||||||
|
for (int ii = 0; ii < commands.length; ii++) {
|
||||||
|
commandList += " /" + commands[ii];
|
||||||
|
}
|
||||||
|
return MessageBundle.tcompose("m.usage_help", commandList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implements <code>/clear</code>. */
|
||||||
|
protected class ClearHandler extends CommandHandler
|
||||||
|
{
|
||||||
|
public String handleCommand (
|
||||||
|
SpeakService speakSvc, String command, String args, String[] history)
|
||||||
|
{
|
||||||
|
clearDisplays();
|
||||||
|
return ChatCodes.SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implements <code>/speak</code>. */
|
||||||
|
protected class SpeakHandler extends CommandHandler
|
||||||
|
{
|
||||||
|
public String handleCommand (
|
||||||
|
SpeakService speakSvc, String command, String args, String[] history)
|
||||||
|
{
|
||||||
|
if (StringUtil.blank(args)) {
|
||||||
|
return "m.usage_speak";
|
||||||
|
}
|
||||||
|
// note the command to be stored in the history
|
||||||
|
history[0] = command + " ";
|
||||||
|
return requestChat(null, args, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implements <code>/emote</code>. */
|
||||||
|
protected class EmoteHandler extends CommandHandler
|
||||||
|
{
|
||||||
|
public String handleCommand (
|
||||||
|
SpeakService speakSvc, String command, String args, String[] history)
|
||||||
|
{
|
||||||
|
if (StringUtil.blank(args)) {
|
||||||
|
return "m.usage_emote";
|
||||||
|
}
|
||||||
|
// note the command to be stored in the history
|
||||||
|
history[0] = command + " ";
|
||||||
|
return deliverChat(speakSvc, args, ChatCodes.EMOTE_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implements <code>/think</code>. */
|
||||||
|
protected class ThinkHandler extends CommandHandler
|
||||||
|
{
|
||||||
|
public String handleCommand (
|
||||||
|
SpeakService speakSvc, String command, String args, String[] history)
|
||||||
|
{
|
||||||
|
if (StringUtil.blank(args)) {
|
||||||
|
return "m.usage_think";
|
||||||
|
}
|
||||||
|
// note the command to be stored in the history
|
||||||
|
history[0] = command + " ";
|
||||||
|
return deliverChat(speakSvc, args, ChatCodes.THINK_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Our active chat context. */
|
/** Our active chat context. */
|
||||||
protected CrowdContext _ctx;
|
protected CrowdContext _ctx;
|
||||||
|
|
||||||
@@ -748,6 +1224,12 @@ public class ChatDirector extends BasicDirector
|
|||||||
protected ObserverList _chatterObservers =
|
protected ObserverList _chatterObservers =
|
||||||
new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
|
new ObserverList(ObserverList.FAST_UNSAFE_NOTIFY);
|
||||||
|
|
||||||
|
/** Registered chat command handlers. */
|
||||||
|
protected static HashMap _handlers = new HashMap();
|
||||||
|
|
||||||
|
/** A history of chat commands. */
|
||||||
|
protected static ArrayList _history = new ArrayList();
|
||||||
|
|
||||||
/** Used by {@link #nextRequestId}. */
|
/** Used by {@link #nextRequestId}. */
|
||||||
protected int _requestId;
|
protected int _requestId;
|
||||||
|
|
||||||
@@ -759,4 +1241,7 @@ public class ChatDirector extends BasicDirector
|
|||||||
|
|
||||||
/** The maximum number of chatter usernames to track. */
|
/** The maximum number of chatter usernames to track. */
|
||||||
protected static final int MAX_CHATTERS = 6;
|
protected static final int MAX_CHATTERS = 6;
|
||||||
|
|
||||||
|
/** The maximum number of commands to keep in the chat history. */
|
||||||
|
protected static final int MAX_COMMAND_HISTORY = 10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ public class ChatView extends BContainer
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// request to send this text as a chat message
|
// request to send this text as a chat message
|
||||||
_chatdtr.requestSpeak(text);
|
_chatdtr.requestSpeak(null, text, ChatCodes.DEFAULT_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ public class ChatPanel
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// request to send this text as a chat message
|
// request to send this text as a chat message
|
||||||
_chatdtr.requestSpeak(text);
|
_chatdtr.requestSpeak(null, text, ChatCodes.DEFAULT_MODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clear out the input because we sent a request
|
// clear out the input because we sent a request
|
||||||
|
|||||||
Reference in New Issue
Block a user