Improve auto-completition of nick names and add it to the in-game chat. Patch by trompetin17. Refs #1767.

This was SVN commit r16261.
This commit is contained in:
leper 2015-02-02 23:44:06 +00:00
parent 3ee30f125c
commit 53b335f5ae
6 changed files with 129 additions and 92 deletions

View File

@ -240,3 +240,49 @@ function sanitizePlayerName(name, stripUnicode, stripSpaces)
// Limit the length to 20 characters
return sanitizedName.substr(0,20);
}
function tryAutoComplete(text, autoCompleteList)
{
if (!text.length)
return text;
var wordSplit = text.split(/\s/g);
if (!wordSplit.length)
return text;
var lastWord = wordSplit.pop();
if (!lastWord.length)
return text;
for (var word of autoCompleteList)
{
if (word.toLowerCase().indexOf(lastWord.toLowerCase()) != 0)
continue;
text = wordSplit.join(" ")
if (text.length > 0)
text += " ";
text += word;
break;
}
return text;
}
function autoCompleteNick(guiName, playerList)
{
var input = Engine.GetGUIObjectByName(guiName);
var text = input.caption;
if (!text.length)
return;
var autoCompleteList = [];
for (var player of playerList)
autoCompleteList.push(player.name);
var bufferPosition = input.buffer_position;
var textTillBufferPosition = text.substring(0, bufferPosition);
var newText = tryAutoComplete(textTillBufferPosition, autoCompleteList);
input.caption = newText + text.substring(bufferPosition);
input.buffer_position = bufferPosition + (newText.length - textTillBufferPosition.length);
}

View File

@ -752,35 +752,6 @@ function submitChatInput()
}
}
function completeNick()
{
var input = Engine.GetGUIObjectByName("chatInput");
var text = escapeText(input.caption);
if (text.length)
{
var matched = false;
for each (var playerObj in Engine.GetPlayerList())
{
var player = playerObj.name;
var breaks = text.match(/(\s+)/g) || [];
text.split(/\s+/g).reduceRight(function (wordsSoFar, word, index)
{
if (matched)
return null;
var matchCandidate = word + (breaks[index - 1] || "") + wordsSoFar;
if (player.toUpperCase().indexOf(matchCandidate.toUpperCase().trim()) == 0)
{
input.caption = text.replace(matchCandidate.trim(), player);
matched = true;
}
return matchCandidate;
}, "");
if (matched)
break;
}
}
}
function isValidNick(nick)
{
var prohibitedNicks = ["system"];

View File

@ -231,7 +231,7 @@
<object name="chatText" size="0 0 100% 94%" type="text" style="ChatPanel" font="sans-13"/>
<object name="chatInput" size="0 94% 100% 100%" type="input" style="ModernInput" font="sans-13">
<action on="Press">submitChatInput();</action>
<action on="Tab">completeNick();</action>
<action on="Tab">autoCompleteNick("chatInput", Engine.GetPlayerList());</action>
</object>
</object>
</object>

View File

@ -197,6 +197,7 @@
<object name="chatDialogPanel" size="50%-180 50%-48 50%+180 50%+36" type="image" hidden="true" sprite="genericPanel">
<object name="chatInput" size="16 12 100%-16 36" type="input" style="ModernInput" max_length="80">
<action on="Press">submitChatInput();</action>
<action on="Tab">autoCompleteNick("chatInput", g_Players);</action>
</object>
<object size="16 100%-40 30%+16 100%-12" type="button" style="StoneButton">

View File

@ -50,6 +50,7 @@ CInput::CInput()
m_PrevTime(0.0), m_CursorVisState(true), m_CursorBlinkRate(0.5), m_ComposingText(false),
m_iComposedLength(0), m_iComposedPos(0), m_iInsertPos(0)
{
AddSetting(GUIST_int, "buffer_position");
AddSetting(GUIST_float, "buffer_zone");
AddSetting(GUIST_CStrW, "caption");
AddSetting(GUIST_int, "cell_id");
@ -79,11 +80,18 @@ CInput::~CInput()
{
}
void CInput::UpdateBufferPositionSetting()
{
int* bufferPos = (int*)m_Settings["buffer_position"].m_pSetting;
*bufferPos = m_iBufferPos;
}
void CInput::ClearComposedText()
{
CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
pCaption->erase(m_iInsertPos, m_iComposedLength);
m_iBufferPos = m_iInsertPos;
UpdateBufferPositionSetting();
m_iComposedLength = 0;
m_iComposedPos = 0;
}
@ -126,6 +134,7 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
m_iBufferPos += text.length();
UpdateBufferPositionSetting();
m_iBufferPos_Tail = -1;
UpdateAutoScroll();
@ -171,6 +180,7 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
m_iBufferPos_Tail = -1;
}
UpdateBufferPositionSetting();
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
UpdateAutoScroll();
@ -551,6 +561,7 @@ InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev)
break;
}
UpdateBufferPositionSetting();
return IN_HANDLED;
}
@ -585,6 +596,7 @@ InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
m_iBufferPos += (int)wcslen(text);
UpdateBufferPositionSetting();
sys_clipboard_free(text);
}
@ -660,6 +672,7 @@ InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
}
}
UpdateBufferPositionSetting();
DeleteCurSelection();
}
return IN_HANDLED;
@ -692,6 +705,7 @@ InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
m_iBufferPos++;
}
UpdateBufferPositionSetting();
DeleteCurSelection();
}
return IN_HANDLED;
@ -748,6 +762,7 @@ InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
m_iBufferPos_Tail = -1;
}
UpdateBufferPositionSetting();
UpdateAutoScroll();
return IN_HANDLED;
@ -796,6 +811,7 @@ InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
m_iBufferPos_Tail = -1;
}
UpdateBufferPositionSetting();
UpdateAutoScroll();
return IN_HANDLED;
@ -841,6 +857,12 @@ void CInput::HandleMessage(SGUIMessage &Message)
GetScrollBar(0).SetScrollBarStyle(scrollbar_style);
}
if (Message.value == CStr("buffer_position"))
{
GUI<int>::GetSetting(this, Message.value, m_iBufferPos);
m_iBufferPos_Tail = -1; // position change resets selection
}
if (Message.value == CStr("size") ||
Message.value == CStr("z") ||
Message.value == CStr("font") ||
@ -857,14 +879,10 @@ void CInput::HandleMessage(SGUIMessage &Message)
bool multiline;
GUI<bool>::GetSetting(this, "multiline", multiline);
if (multiline == false)
{
if (!multiline)
GetScrollBar(0).SetLength(0.f);
}
else
{
GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top );
}
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
UpdateText();
}
@ -893,13 +911,9 @@ void CInput::HandleMessage(SGUIMessage &Message)
// should of course be placed accordingly. Other
// special cases are handled like the input box norms.
if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT])
{
m_iBufferPos = GetMouseHoveringTextPosition();
}
else
{
m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
}
m_SelectingText = true;
@ -1098,6 +1112,7 @@ void CInput::HandleMessage(SGUIMessage &Message)
default:
break;
}
UpdateBufferPositionSetting();
}
void CInput::UpdateCachedSize()
@ -1552,6 +1567,7 @@ void CInput::UpdateText(int from, int to_before, int to_after)
// Ensure positions are valid after caption changes
m_iBufferPos = std::min(m_iBufferPos, (int)caption.size());
m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, (int)caption.size());
UpdateBufferPositionSetting();
if (font_name.empty())
{
@ -2053,6 +2069,7 @@ void CInput::DeleteCurSelection()
// Remove selection
m_iBufferPos_Tail = -1;
m_iBufferPos = virtualFrom;
UpdateBufferPositionSetting();
}
bool CInput::SelectingText() const

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2014 Wildfire Games.
/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -36,9 +36,6 @@ GUI Object - Input [box]
//--------------------------------------------------------
#include "GUI.h"
// TODO Gee: Remove
class IGUIScrollBar;
//--------------------------------------------------------
// Macros
//--------------------------------------------------------
@ -111,53 +108,54 @@ protected:
*/
virtual void Draw();
// Calculate m_CharacterPosition
// the main task for this function is to perfom word-wrapping
// You input from which character it has been changed, because
// if we add a character to the very last end, we don't want
// process everything all over again! Also notice you can
// specify a 'to' also, it will only be used though if a '\n'
// appears, because then the word-wrapping won't change after
// that.
/**
* Calculate m_CharacterPosition
* the main task for this function is to perfom word-wrapping
* You input from which character it has been changed, because
* if we add a character to the very last end, we don't want
* process everything all over again! Also notice you can
* specify a 'to' also, it will only be used though if a '\n'
* appears, because then the word-wrapping won't change after
* that.
*/
void UpdateText(int from=0, int to_before=-1, int to_after=-1);
// Delete the current selection. Also places the pointer at the
// crack between the two segments kept.
/**
* Delete the current selection. Also places the pointer at the
* crack between the two segments kept.
*/
void DeleteCurSelection();
// Is text selected? It can be denote two ways, m_iBufferPos_Tail
// being -1 or the same as m_iBufferPos. This makes for clearer
// code.
/**
* Is text selected? It can be denote two ways, m_iBufferPos_Tail
* being -1 or the same as m_iBufferPos. This makes for clearer
* code.
*/
bool SelectingText() const;
// Get area of where text can be drawn.
/// Get area of where text can be drawn.
float GetTextAreaWidth();
// Called every time the auto-scrolling should be checked.
/// Called every time the auto-scrolling should be checked.
void UpdateAutoScroll();
// Clear composed IME input when supported (SDL2 only).
/// Clear composed IME input when supported (SDL2 only).
void ClearComposedText();
/// Updates the buffer (cursor) position exposed to JS.
void UpdateBufferPositionSetting();
protected:
// Cursor position
// (the second one is for selection of larger areas, -1 if not used)
// A note on 'Tail', it was first called 'To', and the natural order
// of X and X_To was X then X_To. Now Tail is called so, because it
// can be placed both before and after, but the important things is
// that m_iBufferPos is ALWAYS where the edit pointer is. Yes, there
// is an edit pointer even though you select a larger area. For instance
// if you want to resize the selection with Shift+Left/Right, there
// are always two ways a selection can look. Check any OS GUI and you'll
// see.
int m_iBufferPos,
m_iBufferPos_Tail;
/// Cursor position
int m_iBufferPos;
/// Cursor position we started to select from. (-1 if not selecting)
/// (NB: Can be larger than m_iBufferPos if selecting from back to front.)
int m_iBufferPos_Tail;
// If we're composing text with an IME
/// If we're composing text with an IME
bool m_ComposingText;
// The length and position of the current IME composition
/// The length and position of the current IME composition
int m_iComposedLength, m_iComposedPos;
// The position to insert committed text
/// The position to insert committed text
int m_iInsertPos;
// the outer vector is lines, and the inner is X positions
@ -166,40 +164,44 @@ protected:
// pointer should be placed when the input control is pressed.
struct SRow
{
int m_ListStart; // Where does the Row starts
std::vector<float> m_ListOfX; // List of X values for each character.
int m_ListStart; /// Where does the Row starts
std::vector<float> m_ListOfX; /// List of X values for each character.
};
// List of rows, list because I use a lot of iterators, and change
// its size continuously, it's easier and safer if I know the
// iterators never gets invalidated.
// For one-liners, only one row is used.
/**
* List of rows to ease changing its size, so iterators stay valid.
* For one-liners only one row is used.
*/
std::list< SRow > m_CharacterPositions;
// *** Things for a multi-lined input control *** //
// This is when you change row with up/down, and the row you jump
// to doesn't have anything at that X position, then it will
// keep the WantedX position in mind when switching to the next
// row. It will keep on being used until it reach a row which meets
// the requirements.
// 0.0f means not in use.
/**
* When you change row with up/down, and the row you jump to does
* not have anything at that X position, then it will keep the
* m_WantedX position in mind when switching to the next row.
* It will keep on being used until it reach a row which meets the
* requirements.
* 0.0f means not in use.
*/
float m_WantedX;
// If we are in the process of selecting a larger selection of text
// using the mouse click (hold) and drag, this is true.
/**
* If we are in the process of selecting a larger selection of text
* using the mouse click (hold) and drag, this is true.
*/
bool m_SelectingText;
// *** Things for one-line input control *** //
float m_HorizontalScroll;
// Used to store the previous time for flashing the cursor.
/// Used to store the previous time for flashing the cursor.
double m_PrevTime;
// Cursor blink rate in seconds, if greater than 0.0.
/// Cursor blink rate in seconds, if greater than 0.0.
double m_CursorBlinkRate;
// If the cursor should be drawn or not.
/// If the cursor should be drawn or not.
bool m_CursorVisState;
};