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:
parent
9787d06786
commit
2374d6ea49
4 changed files with 285 additions and 33 deletions
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue