Allow limiting the length of text in gcn::TextField

Add gcn::TextField::setMaxLengthBytes and
gcn::TextField::getMaxLengthBytes.  Publish them to Lua.
Nothing calls these functions yet.
This commit is contained in:
kon 2011-10-02 08:05:21 +00:00
parent 9787d06786
commit 2374d6ea49
4 changed files with 285 additions and 33 deletions
guichan
include/guichan/widgets
widgets
tolua

View file

@ -97,6 +97,37 @@ namespace gcn
*/
virtual const std::string& getText() const;
/**
* Sets the maximum number of bytes that the user can type in
* the TextField. Because TextField uses the UTF-8 encoding,
* each character may require several bytes.
*
* If the text in the field is already too long, this function
* truncates it immediately.
*
* @param maxLengthBytes The new maximum length, in bytes.
* Must not be negative.
*
* In addition to this UTF-8 byte limit, we could also
* implement limits on the number of code points, the number
* of UTF-16 code units, the number of grapheme clusters, or
* the width of the string in a given font. However, there
* haven't been any use cases for those so far.
*
* @seealso #getMaxLengthBytes
*/
void setMaxLengthBytes(int maxLengthBytes);
/**
* Gets the maximum number of bytes that the user can type in
* the TextField.
*
* @return The current maximum length, in bytes.
*
* @seealso #setMaxLengthBytes
*/
int getMaxLengthBytes() const;
/**
* Draws the caret (the little marker in the text that shows where the
* letters you type will appear). Easily overloaded if you want to
@ -163,10 +194,49 @@ namespace gcn
*/
void fixScroll();
/**
* Truncates #mText to #mMaxLengthBytes, and #mCaretPosition
* and #mSelectStart to the length of #mText. Marks the
* widget dirty if it changes anything.
*/
void truncateToMaxLength();
/**
* Inserts a string at the caret, replacing the selection if any.
*
* This function also marks the widget dirty. However, it
* does not call #fixScroll because that is somewhat
* expensive and should not be called redundantly. Therefore,
* the caller must do that.
*
* @param text The text to insert.
*/
void insertAtCaret(const std::string& str);
/**
* Finds the last UTF-8 character boundary in the first
* @a maxBytes bytes of @a str.
*
* @param str UTF-8 string.
*
* @param maxBytes How many bytes to consider at the beginning
* of @a str. Must not be negative.
*
* @return The position of the last character boundary. Always
* <= @a maxBytes.
*/
static int UTF8LastCharacterBoundary(const std::string &str, int maxBytes);
std::string mText;
int mCaretPosition;
int mXScroll;
int mSelectStart;
/**
* The maximum length of #mText, in bytes. Might be zero but
* never negative.
*/
int mMaxLengthBytes;
};
}

View file

@ -56,6 +56,7 @@
* For comments regarding functions please see the header file.
*/
#include <limits>
#include "guichan/keyinput.h"
#include "guichan/mouseinput.h"
#include "guichan/widgets/textfield.h"
@ -69,6 +70,7 @@ namespace gcn
mCaretPosition = 0;
mXScroll = 0;
mSelectStart = 0;
mMaxLengthBytes = std::numeric_limits<int>::max();
setFocusable(true);
@ -83,8 +85,11 @@ namespace gcn
mCaretPosition = 0;
mXScroll = 0;
mSelectStart = 0;
mMaxLengthBytes = std::numeric_limits<int>::max();
mText = text;
// std::string::max_size() might exceed std::numeric_limits<int>::max().
truncateToMaxLength();
adjustSize();
setBorderSize(1);
@ -96,14 +101,10 @@ namespace gcn
void TextField::setText(const std::string& text)
{
if ((int)text.size() < mCaretPosition )
{
mCaretPosition = text.size();
}
mText = text;
truncateToMaxLength();
mSelectStart = mCaretPosition;
mText = text;
setDirty(true);
}
void TextField::draw(Graphics* graphics)
@ -191,9 +192,10 @@ namespace gcn
{
std::string str;
if (GetClipboard(str) >= 0) {
for (size_t i = 0; i < str.size(); ++i) {
keyPress(Key(str[i]));
}
// GetClipboard ensures that the string does not
// contain control characters.
insertAtCaret(str);
fixScroll();
}
}
}
@ -319,36 +321,20 @@ namespace gcn
{
std::string str;
if (selLen > 0) {
mText.erase(selFirst, selLen);
mCaretPosition = selFirst;
mSelectStart = selFirst;
}
if (GetClipboard(str) >= 0) {
for (size_t i = 0; i < str.size(); ++i) {
keyPress(Key(str[i]));
}
// GetClipboard ensures that the string does not
// contain control characters.
insertAtCaret(str);
}
// Even if GetClipboard failed, we did recognize the key
// and the caller should not treat it as a hot key.
ret = true;
}
else if (key.isCharacter())
{
if (selLen > 0) {
mText.erase(selFirst, selLen);
mCaretPosition = selFirst;
mSelectStart = selFirst;
}
mText.insert(mCaretPosition,key.toString());
int newpos = UTF8GetNext(mText, mCaretPosition);
if (newpos > (int)mText.size()) {
throw GCN_EXCEPTION("Invalid UTF8.");
}
mCaretPosition = newpos;
mSelectStart = newpos;
insertAtCaret(key.toString());
ret = true;
}
@ -434,4 +420,131 @@ namespace gcn
{
fixScroll();
}
int TextField::getMaxLengthBytes() const
{
return mMaxLengthBytes;
}
void TextField::setMaxLengthBytes(int maxLengthBytes)
{
if (maxLengthBytes < 0)
{
maxLengthBytes = 0;
}
mMaxLengthBytes = maxLengthBytes;
truncateToMaxLength();
}
void TextField::truncateToMaxLength()
{
bool changedSomething = false;
// Because we never let mMaxLengthBytes become negative,
// the following static_cast cannot wrap around.
if (mText.size() > static_cast<unsigned int>(mMaxLengthBytes))
{
int newLength = UTF8LastCharacterBoundary(mText, mMaxLengthBytes);
mText.resize(newLength);
changedSomething = true;
}
// Because of mMaxLengthBytes, the following static_casts
// cannot overflow.
if (mCaretPosition > static_cast<int>(mText.size()))
{
mCaretPosition = mText.size();
changedSomething = true;
}
if (mSelectStart > static_cast<int>(mText.size()))
{
mSelectStart = mText.size();
changedSomething = true;
}
if (changedSomething)
{
fixScroll();
setDirty(true);
}
}
int TextField::UTF8LastCharacterBoundary(const std::string &str,
int maxBytes)
{
if (maxBytes < 0)
{
// A bug in the caller.
maxBytes = 0;
}
// The following static_cast cannot wrap around because
// maxBytes cannot be negative at this point.
if (str.size() < static_cast<unsigned int>(maxBytes))
{
// The following static_cast cannot overflow because
// str.size() is less than int maxBytes.
maxBytes = static_cast<int>(str.size());
}
if (maxBytes == 0)
{
// There is no space for any character.
return 0;
}
// The following would be easy to implement as:
// return UTF8GetPrev(str, maxBytes + 1);
// but maxBytes + 1 might overflow, so don't do that.
// Find the previous character boundary.
int prev = UTF8GetPrev(str, maxBytes);
// Assert(prev < maxBytes);
// If maxBytes already is at a character
// boundary, then that character will fit.
int next = UTF8GetNext(str, prev);
if (next == maxBytes)
{
return maxBytes;
}
else
{
return prev;
}
}
void TextField::insertAtCaret(const std::string& str)
{
int bytesFree = mMaxLengthBytes - mText.size();
if (bytesFree < 0)
{
bytesFree = 0;
}
int bytesToCopy;
if (str.size() > static_cast<unsigned int>(bytesFree))
{
bytesToCopy = UTF8LastCharacterBoundary(str, bytesFree);
}
else
{
bytesToCopy = static_cast<int>(str.size());
}
unsigned int selFirst;
unsigned int selLen;
getTextSelectionPositions(&selFirst, &selLen);
if (selLen > 0) {
mText.erase(selFirst, selLen);
mCaretPosition = selFirst;
mSelectStart = selFirst;
}
mText.insert(mCaretPosition, str, 0, bytesToCopy);
mCaretPosition += bytesToCopy;
mSelectStart = mCaretPosition;
setDirty(true);
}
}

View file

@ -1,6 +1,6 @@
/*
** Lua binding: stratagus
** Generated automatically by tolua++-1.0.92 on Sat Jul 16 22:41:54 2011.
** Generated automatically by tolua++-1.0.93 on Sun Oct 2 03:06:13 2011.
*/
#ifndef __cplusplus
@ -9830,6 +9830,71 @@ static int tolua_stratagus_TextField_getText00(lua_State* tolua_S)
}
#endif //#ifndef TOLUA_DISABLE
/* method: setMaxLengthBytes of class TextField */
#ifndef TOLUA_DISABLE_tolua_stratagus_TextField_setMaxLengthBytes00
static int tolua_stratagus_TextField_setMaxLengthBytes00(lua_State* tolua_S)
{
#ifndef TOLUA_RELEASE
tolua_Error tolua_err;
if (
!tolua_isusertype(tolua_S,1,"TextField",0,&tolua_err) ||
!tolua_isnumber(tolua_S,2,0,&tolua_err) ||
!tolua_isnoobj(tolua_S,3,&tolua_err)
)
goto tolua_lerror;
else
#endif
{
TextField* self = (TextField*) tolua_tousertype(tolua_S,1,0);
int maxLengthBytes = ((int) tolua_tonumber(tolua_S,2,0));
#ifndef TOLUA_RELEASE
if (!self) tolua_error(tolua_S,"invalid 'self' in function 'setMaxLengthBytes'", NULL);
#endif
{
self->setMaxLengthBytes(maxLengthBytes);
}
}
return 0;
#ifndef TOLUA_RELEASE
tolua_lerror:
tolua_error(tolua_S,"#ferror in function 'setMaxLengthBytes'.",&tolua_err);
return 0;
#endif
}
#endif //#ifndef TOLUA_DISABLE
/* method: getMaxLengthBytes of class TextField */
#ifndef TOLUA_DISABLE_tolua_stratagus_TextField_getMaxLengthBytes00
static int tolua_stratagus_TextField_getMaxLengthBytes00(lua_State* tolua_S)
{
#ifndef TOLUA_RELEASE
tolua_Error tolua_err;
if (
!tolua_isusertype(tolua_S,1,"const TextField",0,&tolua_err) ||
!tolua_isnoobj(tolua_S,2,&tolua_err)
)
goto tolua_lerror;
else
#endif
{
const TextField* self = (const TextField*) tolua_tousertype(tolua_S,1,0);
#ifndef TOLUA_RELEASE
if (!self) tolua_error(tolua_S,"invalid 'self' in function 'getMaxLengthBytes'", NULL);
#endif
{
int tolua_ret = (int) self->getMaxLengthBytes();
tolua_pushnumber(tolua_S,(lua_Number)tolua_ret);
}
}
return 1;
#ifndef TOLUA_RELEASE
tolua_lerror:
tolua_error(tolua_S,"#ferror in function 'getMaxLengthBytes'.",&tolua_err);
return 0;
#endif
}
#endif //#ifndef TOLUA_DISABLE
/* method: new of class ListBoxWidget */
#ifndef TOLUA_DISABLE_tolua_stratagus_ListBoxWidget_new00
static int tolua_stratagus_ListBoxWidget_new00(lua_State* tolua_S)
@ -20179,6 +20244,8 @@ TOLUA_API int tolua_stratagus_open (lua_State* tolua_S)
tolua_function(tolua_S,".call",tolua_stratagus_TextField_new00_local);
tolua_function(tolua_S,"setText",tolua_stratagus_TextField_setText00);
tolua_function(tolua_S,"getText",tolua_stratagus_TextField_getText00);
tolua_function(tolua_S,"setMaxLengthBytes",tolua_stratagus_TextField_setMaxLengthBytes00);
tolua_function(tolua_S,"getMaxLengthBytes",tolua_stratagus_TextField_getMaxLengthBytes00);
tolua_endmodule(tolua_S);
tolua_cclass(tolua_S,"ListBox","ListBox","Widget",NULL);
tolua_beginmodule(tolua_S,"ListBox");

View file

@ -442,6 +442,8 @@ class TextField : public Widget
TextField(const std::string text);
virtual void setText(const std::string text);
virtual std::string &getText();
void setMaxLengthBytes(int maxLengthBytes);
int getMaxLengthBytes() const;
};
class ListBox : public Widget