| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 | /* * CTextInput.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */#include "StdInc.h"#include "CTextInput.h"#include "Images.h"#include "TextControls.h"#include "../GameEngine.h"#include "../eventsSDL/InputHandler.h"#include "../gui/Shortcut.h"#include "../render/Graphics.h"#include "../render/IFont.h"#include "../render/IRenderHandler.h"#include "../../lib/texts/TextOperations.h"#include <boost/lexical_cast.hpp>std::list<CFocusable *> CFocusable::focusables;CFocusable * CFocusable::inputWithFocus;CTextInputWithConfirm::CTextInputWithConfirm(const Rect & Pos, EFonts font, ETextAlignment alignment, std::string text, bool limitToRect, std::function<void()> confirmCallback)	: CTextInput(Pos, font, alignment, false), confirmCb(confirmCallback), limitToRect(limitToRect), initialText(text){	setText(text);}bool CTextInputWithConfirm::captureThisKey(EShortcut key){	return hasFocus() && (key == EShortcut::GLOBAL_ACCEPT || key == EShortcut::GLOBAL_CANCEL || key == EShortcut::GLOBAL_BACKSPACE);}void CTextInputWithConfirm::keyPressed(EShortcut key){	if(!hasFocus())		return;	if(key == EShortcut::GLOBAL_ACCEPT)		confirm();	else if(key == EShortcut::GLOBAL_CANCEL)	{		setText(initialText);		removeFocus();	}		CTextInput::keyPressed(key);}bool CTextInputWithConfirm::receiveEvent(const Point & position, int eventType) const{	return eventType == AEventsReceiver::LCLICK; // capture all left clicks (not only within control)}void CTextInputWithConfirm::clickReleased(const Point & cursorPosition){	if(!pos.isInside(cursorPosition)) // clicked outside		confirm();}void CTextInputWithConfirm::clickPressed(const Point & cursorPosition){	if(pos.isInside(cursorPosition)) // clickPressed should respect control area (receiveEvent also affects this)		CTextInput::clickPressed(cursorPosition);}void CTextInputWithConfirm::onFocusGot(){	initialText = getText();	CTextInput::onFocusGot();}void CTextInputWithConfirm::textInputted(const std::string & enteredText){	if(!hasFocus())		return;	std::string visibleText = getVisibleText() + enteredText;	const auto & font = ENGINE->renderHandler().loadFont(label->font);	if(!limitToRect || (font->getStringWidth(visibleText) - CLabel::getDelimitersWidth(label->font, visibleText)) < pos.w)		CTextInput::textInputted(enteredText);}void CTextInputWithConfirm::deactivate(){	removeUsedEvents(LCLICK);	CTextInput::deactivate();}void CTextInputWithConfirm::confirm(){	if(getText().empty())		setText(initialText);	if(confirmCb && initialText != getText())		confirmCb();	removeFocus();}CTextInput::CTextInput(const Rect & Pos)	:originalAlignment(ETextAlignment::CENTERLEFT){	pos += Pos.topLeft();	pos.h = Pos.h;	pos.w = Pos.w;	addUsedEvents(LCLICK | SHOW_POPUP | KEYBOARD | TEXTINPUT);}void CTextInput::createLabel(bool giveFocusToInput){	OBJECT_CONSTRUCTION;	label = std::make_shared<CLabel>();	label->pos = pos;	label->alignment = originalAlignment;#if !defined(VCMI_MOBILE)	if(giveFocusToInput)		giveFocus();#endif}CTextInput::CTextInput(const Rect & Pos, EFonts font, ETextAlignment alignment, bool giveFocusToInput)	: CTextInput(Pos){	originalAlignment = alignment;	setRedrawParent(true);	createLabel(giveFocusToInput);	setFont(font);	setAlignment(alignment);}CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName)	: CTextInput(Pos){	OBJECT_CONSTRUCTION;	if (!bgName.empty())		background = std::make_shared<CPicture>(bgName, bgOffset.x, bgOffset.y);	else		setRedrawParent(true);	createLabel(true);}CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)	: CTextInput(Pos){	OBJECT_CONSTRUCTION;	background = std::make_shared<CPicture>(srf, Pos);	pos.w = background->pos.w;	pos.h = background->pos.h;	background->pos = pos;	createLabel(true);}void CTextInput::setFont(EFonts font){	label->font = font;}void CTextInput::setColor(const ColorRGBA & color){	label->color = color;}void CTextInput::setAlignment(ETextAlignment alignment){	originalAlignment = alignment;	label->alignment = alignment;}const std::string & CTextInput::getText() const{	return currentText;}void CTextInput::setCallback(const TextEditedCallback & cb){	assert(!onTextEdited);	onTextEdited = cb;}void CTextInput::setPopupCallback(const std::function<void()> & cb){	callbackPopup = cb;}void CTextInput::setFilterFilename(){	assert(!onTextFiltering);	onTextFiltering = std::bind(&CTextInput::filenameFilter, _1, _2);}void CTextInput::setFilterNumber(int minValue, int maxValue){	onTextFiltering = std::bind(&CTextInput::numberFilter, _1, _2, minValue, maxValue);}std::string CTextInput::getVisibleText() const{	return hasFocus() ? currentText + composedText + "_" : currentText;}void CTextInput::showPopupWindow(const Point & cursorPosition){	if(callbackPopup)		callbackPopup();}void CTextInput::clickPressed(const Point & cursorPosition){	// attempt to give focus unconditionally, even if we already have it	// this forces on-screen keyboard to show up again, even if player have closed it before	giveFocus();}void CTextInput::keyPressed(EShortcut key){	if(!hasFocus())		return;	if(key == EShortcut::GLOBAL_MOVE_FOCUS)	{		moveFocus();		return;	}	bool redrawNeeded = false;	switch(key)	{		case EShortcut::GLOBAL_BACKSPACE:			if(!composedText.empty())			{				TextOperations::trimRightUnicode(composedText);				redrawNeeded = true;			}			else if(!currentText.empty())			{				TextOperations::trimRightUnicode(currentText);				redrawNeeded = true;			}			break;		default:			break;	}	if(redrawNeeded)	{		updateLabel();		if(onTextEdited)			onTextEdited(currentText);	}}void CTextInput::setText(const std::string & nText){	currentText = nText;	updateLabel();}void CTextInput::updateLabel(){	std::string visibleText = getVisibleText();	label->alignment = originalAlignment;	const auto & font = ENGINE->renderHandler().loadFont(label->font);	while ((font->getStringWidth(visibleText) - CLabel::getDelimitersWidth(label->font, visibleText)) > pos.w)	{		label->alignment = ETextAlignment::CENTERRIGHT;		visibleText = visibleText.substr(TextOperations::getUnicodeCharacterSize(visibleText[0]));	}	label->setText(visibleText);}void CTextInput::textInputted(const std::string & enteredText){	if(!hasFocus())		return;	std::string oldText = currentText;	setText(getText() + enteredText);	if(onTextFiltering)		onTextFiltering(currentText, oldText);	if(currentText != oldText)	{		updateLabel();		if(onTextEdited)			onTextEdited(currentText);	}	composedText.clear();}void CTextInput::textEdited(const std::string & enteredText){	if(!hasFocus())		return;	composedText = enteredText;	updateLabel();}void CTextInput::filenameFilter(std::string & text, const std::string &oldText){	static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed	size_t pos;	while((pos = text.find_first_of(forbiddenChars)) != std::string::npos)		text.erase(pos, 1);}void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue){	assert(minValue < maxValue);	if(text.empty())		text = "0";	size_t pos = 0;	if(text[0] == '-') //allow '-' sign as first symbol only		pos++;	while(pos < text.size())	{		if(text[pos] < '0' || text[pos] > '9')		{			text = oldText;			return; //new text is not number.		}		pos++;	}	try	{		int value = boost::lexical_cast<int>(text);		if(value < minValue)			text = std::to_string(minValue);		else if(value > maxValue)			text = std::to_string(maxValue);	}	catch(boost::bad_lexical_cast &)	{		//Should never happen. Unless I missed some cases		logGlobal->warn("Warning: failed to convert %s to number!", text);		text = oldText;	}}void CTextInput::activate(){	CFocusable::activate();	if (hasFocus())	{#if defined(VCMI_MOBILE)		//giveFocus();#else		ENGINE->input().startTextInput(pos);#endif	}}void CTextInput::deactivate(){	CFocusable::deactivate();	if (hasFocus())	{#if defined(VCMI_MOBILE)		removeFocus();#else		ENGINE->input().stopTextInput();#endif	}}void CTextInput::onFocusGot(){	updateLabel();}void CTextInput::onFocusLost(){	updateLabel();}void CFocusable::focusGot(){	if (isActive())		ENGINE->input().startTextInput(pos);	onFocusGot();}void CFocusable::focusLost(){	if (isActive())		ENGINE->input().stopTextInput();	onFocusLost();}CFocusable::CFocusable(){	focusables.push_back(this);}CFocusable::~CFocusable(){	if(hasFocus())		inputWithFocus = nullptr;	focusables -= this;}bool CFocusable::hasFocus() const{	return inputWithFocus == this;}void CFocusable::giveFocus(){	auto previousInput = inputWithFocus;	inputWithFocus = this;	if(previousInput)		previousInput->focusLost();	focusGot();}void CFocusable::moveFocus(){	auto i = vstd::find(focusables, this);	auto ourIt = i;	for(i++; i != ourIt; i++)	{		if(i == focusables.end())			i = focusables.begin();		if(*i == this)			return;		if((*i)->isActive())		{			(*i)->giveFocus();			break;		}	}}void CFocusable::removeFocus(){	if(this == inputWithFocus)	{		inputWithFocus = nullptr;		focusLost();	}}
 |