Urho3D/Engine/UI/LineEdit.cpp
2011-02-08 11:06:12 +00:00

499 lines
15 KiB
C++

//
// Urho3D Engine
// Copyright (c) 2008-2011 Lasse Öörni
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#include "Precompiled.h"
#include "Input.h"
#include "LineEdit.h"
#include "Log.h"
#include "Text.h"
#include "UIEvents.h"
#include "DebugNew.h"
LineEdit::LineEdit(const std::string& name, const std::string& text) :
BorderImage(name),
mLastFont(0),
mLastFontSize(0),
mCursorPosition(0),
mDragStartPosition(M_MAX_UNSIGNED),
mCursorBlinkRate(1.0f),
mCursorBlinkTimer(0.0f),
mMaxLength(0),
mEchoCharacter(0),
mDefocusable(true),
mCursorMovable(true),
mTextSelectable(true),
mTextCopyable(true),
mDefocus(false)
{
mClipChildren = true;
mEnabled = true;
mFocusable = true;
mText = new Text();
mCursor = new BorderImage();
addChild(mText);
addChild(mCursor);
// Show cursor on top of text
mCursor->setPriority(1);
setText(text);
}
LineEdit::~LineEdit()
{
}
void LineEdit::setStyle(const XMLElement& element, ResourceCache* cache)
{
BorderImage::setStyle(element, cache);
if (element.hasChildElement("maxlength"))
setMaxLength(element.getChildElement("maxlength").getInt("value"));
if (element.hasChildElement("defocusable"))
setDefocusable(element.getChildElement("defocusable").getBool("enable"));
if (element.hasChildElement("cursormovable"))
setCursorMovable(element.getChildElement("cursormovable").getBool("enable"));
if (element.hasChildElement("textselectable"))
setTextSelectable(element.getChildElement("textselectable").getBool("enable"));
if (element.hasChildElement("textcopyable"))
setTextCopyable(element.getChildElement("textcopyable").getBool("enable"));
XMLElement textElem = element.getChildElement("text");
if (textElem)
{
if (textElem.hasAttribute("value"))
setText(textElem.getString("value"));
mText->setStyle(textElem, cache);
}
XMLElement cursorElem = element.getChildElement("cursor");
if (cursorElem)
mCursor->setStyle(cursorElem, cache);
if (element.hasChildElement("cursorposition"))
setCursorPosition(element.getChildElement("cursorposition").getInt("value"));
if (element.hasChildElement("cursorblinkrate"))
setCursorBlinkRate(element.getChildElement("cursorblinkrate").getFloat("value"));
if (element.hasChildElement("echocharacter"))
{
std::string text = element.getChildElement("echocharacter").getString("value");
if (text.length())
setEchoCharacter(text[0]);
}
}
void LineEdit::update(float timeStep)
{
if (mCursorBlinkRate > 0.0f)
mCursorBlinkTimer = fmodf(mCursorBlinkTimer + mCursorBlinkRate * timeStep, 1.0f);
else
mCursorBlinkTimer = 0.0f;
// Update cursor position if font has changed
if ((mText->getFont() != mLastFont) || (mText->getFontSize() != mLastFontSize))
{
mLastFont = mText->getFont();
mLastFontSize = mText->getFontSize();
updateCursor();
}
bool cursorVisible = false;
if (mFocus)
cursorVisible = mCursorBlinkTimer < 0.5f;
mCursor->setVisible(cursorVisible);
if (mDefocus)
{
setFocus(false);
mDefocus = false;
}
}
void LineEdit::onClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
{
if ((buttons & MOUSEB_LEFT) && (mCursorMovable))
{
unsigned pos = getCharIndex(position);
if (pos != M_MAX_UNSIGNED)
{
setCursorPosition(pos);
mText->setSelection(0, 0);
}
}
}
void LineEdit::onDragStart(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
{
mDragStartPosition = getCharIndex(position);
}
void LineEdit::onDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers)
{
if ((mCursorMovable) && (mTextSelectable))
{
unsigned start = mDragStartPosition;
unsigned current = getCharIndex(position);
if ((start != M_MAX_UNSIGNED) && (current != M_MAX_UNSIGNED))
{
if (start < current)
mText->setSelection(start, current - start);
else
mText->setSelection(current, start - current);
setCursorPosition(current);
}
}
}
void LineEdit::onKey(int key, int buttons, int qualifiers)
{
bool changed = false;
bool cursorMoved = false;
switch (key)
{
case 'X':
case 'C':
if ((mTextCopyable) && (qualifiers & QUAL_CTRL))
{
unsigned start = mText->getSelectionStart();
unsigned length = mText->getSelectionLength();
if (mText->getSelectionLength())
sClipBoard = mLine.substr(start, length);
if (key == 'X')
{
if (start + length < mLine.length())
mLine = mLine.substr(0, start) + mLine.substr(start + length);
else
mLine = mLine.substr(0, start);
mText->setSelection(0, 0);
mCursorPosition = start;
changed = true;
}
}
break;
case 'V':
if ((mTextCopyable) && (qualifiers & QUAL_CTRL))
{
if (sClipBoard.length())
{
if (mCursorPosition < mLine.length())
mLine = mLine.substr(0, mCursorPosition) + sClipBoard + mLine.substr(mCursorPosition);
else
mLine += sClipBoard;
mCursorPosition += sClipBoard.length();
changed = true;
}
}
break;
case KEY_LEFT:
if (!(qualifiers & QUAL_SHIFT))
mText->setSelection(0, 0);
if ((mCursorMovable) && (mCursorPosition > 0))
{
if ((mTextSelectable) && (qualifiers & QUAL_SHIFT) && (!mText->getSelectionLength()))
mDragStartPosition = mCursorPosition;
if (qualifiers & QUAL_CTRL)
mCursorPosition = 0;
else
--mCursorPosition;
cursorMoved = true;
if ((mTextSelectable) && (qualifiers & QUAL_SHIFT))
{
unsigned start = mDragStartPosition;
unsigned current = mCursorPosition;
if (start < current)
mText->setSelection(start, current - start);
else
mText->setSelection(current, start - current);
}
}
break;
case KEY_RIGHT:
if (!(qualifiers & QUAL_SHIFT))
mText->setSelection(0, 0);
if ((mCursorMovable) && (mCursorPosition < mLine.length()))
{
if ((mTextSelectable) && (qualifiers & QUAL_SHIFT) && (!mText->getSelectionLength()))
mDragStartPosition = mCursorPosition;
if (qualifiers & QUAL_CTRL)
mCursorPosition = mLine.length();
else
++mCursorPosition;
cursorMoved = true;
if ((mTextSelectable) && (qualifiers & QUAL_SHIFT))
{
unsigned start = mDragStartPosition;
unsigned current = mCursorPosition;
if (start < current)
mText->setSelection(start, current - start);
else
mText->setSelection(current, start - current);
}
}
break;
case KEY_DELETE:
if (!mText->getSelectionLength())
{
if (mCursorPosition < mLine.length())
{
mLine = mLine.substr(0, mCursorPosition) + mLine.substr(mCursorPosition + 1);
changed = true;
}
}
else
{
// If a selection exists, erase it
unsigned start = mText->getSelectionStart();
unsigned length = mText->getSelectionLength();
if (start + length < mLine.length())
mLine = mLine.substr(0, start) + mLine.substr(start + length);
else
mLine = mLine.substr(0, start);
mText->setSelection(0, 0);
changed = true;
}
break;
}
if (changed)
{
updateText();
updateCursor();
using namespace TextChanged;
VariantMap eventData;
eventData[P_ELEMENT] = (void*)this;
eventData[P_TEXT] = mLine;
sendEvent(EVENT_TEXTCHANGED, eventData);
}
else if (cursorMoved)
updateCursor();
}
void LineEdit::onChar(unsigned char c, int buttons, int qualifiers)
{
bool changed = false;
if (qualifiers & QUAL_CTRL)
return;
if (c == '\b')
{
if ((mLine.length()) && (mCursorPosition))
{
if (mCursorPosition < mLine.length())
mLine = mLine.substr(0, mCursorPosition - 1) + mLine.substr(mCursorPosition);
else
mLine = mLine.substr(0, mCursorPosition - 1);
--mCursorPosition;
changed = true;
}
}
else if (c == '\r')
{
using namespace TextFinished;
VariantMap eventData;
eventData[P_ELEMENT] = (void*)this;
sendEvent(EVENT_TEXTFINISHED, eventData);
mText->setSelection(0, 0);
}
else if (c == 27)
{
if (mDefocusable)
{
mText->setSelection(0, 0);
mDefocus = true;
}
}
else if ((c >= 0x20) && ((!mMaxLength) || (mLine.length() < mMaxLength)))
{
static std::string charStr;
charStr.resize(1);
charStr[0] = c;
if (!mText->getSelectionLength())
{
if (mCursorPosition == mLine.length())
mLine += charStr;
else
mLine = mLine.substr(0, mCursorPosition) + charStr + mLine.substr(mCursorPosition);
++mCursorPosition;
}
else
{
// If a selection exists, erase it first
unsigned start = mText->getSelectionStart();
unsigned length = mText->getSelectionLength();
if (start + length < mLine.length())
mLine = mLine.substr(0, start) + charStr + mLine.substr(start + length);
else
mLine = mLine.substr(0, start) + charStr;
mCursorPosition = start + 1;
}
changed = true;
}
if (changed)
{
mText->setSelection(0, 0);
updateText();
updateCursor();
using namespace TextChanged;
VariantMap eventData;
eventData[P_ELEMENT] = (void*)this;
eventData[P_TEXT] = mLine;
sendEvent(EVENT_TEXTCHANGED, eventData);
}
}
void LineEdit::setText(const std::string& text)
{
mLine = text;
mText->setText(text);
// If cursor is not movable, make sure it's at the text end
if (!mCursorMovable)
setCursorPosition(mLine.length());
updateText();
}
void LineEdit::setCursorPosition(unsigned position)
{
if ((position > mLine.length()) || (!mCursorMovable))
position = mLine.length();
if (position != mCursorPosition)
{
mCursorPosition = position;
updateCursor();
}
}
void LineEdit::setCursorBlinkRate(float rate)
{
mCursorBlinkRate = max(rate, 0.0f);
}
void LineEdit::setMaxLength(unsigned length)
{
mMaxLength = length;
}
void LineEdit::setEchoCharacter(char c)
{
mEchoCharacter = c;
updateText();
}
void LineEdit::setDefocusable(bool enable)
{
mDefocusable = enable;
}
void LineEdit::setCursorMovable(bool enable)
{
mCursorMovable = enable;
}
void LineEdit::setTextSelectable(bool enable)
{
mTextSelectable = enable;
}
void LineEdit::setTextCopyable(bool enable)
{
mTextCopyable = enable;
}
void LineEdit::updateText()
{
if (!mEchoCharacter)
mText->setText(mLine);
else
{
std::string echoText;
echoText.resize(mLine.length());
for (unsigned i = 0; i < mLine.length(); ++i)
echoText[i] = mEchoCharacter;
mText->setText(echoText);
}
if (mCursorPosition > mLine.length())
{
mCursorPosition = mLine.length();
updateCursor();
}
}
void LineEdit::updateCursor()
{
int x = 0;
const std::vector<IntVector2>& charPositions = mText->getCharPositions();
if (charPositions.size())
x = mCursorPosition < charPositions.size() ? charPositions[mCursorPosition].mX : charPositions.back().mX;
mCursor->setPosition(mText->getPosition() + IntVector2(x, 0));
mCursor->setSize(mCursor->getWidth(), mText->getRowHeight());
// Scroll if necessary
int sx = -getChildOffset().mX;
int left = mClipBorder.mLeft;
int right = getWidth() - mClipBorder.mLeft - mClipBorder.mRight - mCursor->getWidth();
if (x - sx > right)
sx = x - right;
if (x - sx < left)
sx = x - left;
setChildOffset(IntVector2(-sx, 0));
// Restart blinking
mCursorBlinkTimer = 0.0f;
}
unsigned LineEdit::getCharIndex(const IntVector2& position)
{
IntVector2 screenPosition = elementToScreen(position);
IntVector2 textPosition = mText->screenToElement(screenPosition);
const std::vector<IntVector2>& charPositions = mText->getCharPositions();
for (unsigned i = charPositions.size() - 1; i < charPositions.size(); --i)
{
if (textPosition.mX >= charPositions[i].mX)
return i;
}
return M_MAX_UNSIGNED;
}