CTextInput.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*
  2. * CTextInput.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CTextInput.h"
  12. #include "Images.h"
  13. #include "TextControls.h"
  14. #include "../GameEngine.h"
  15. #include "../eventsSDL/InputHandler.h"
  16. #include "../gui/Shortcut.h"
  17. #include "../render/Graphics.h"
  18. #include "../render/IFont.h"
  19. #include "../render/IRenderHandler.h"
  20. #include "../../lib/texts/TextOperations.h"
  21. #include <boost/lexical_cast.hpp>
  22. std::list<CFocusable *> CFocusable::focusables;
  23. CFocusable * CFocusable::inputWithFocus;
  24. CTextInputWithConfirm::CTextInputWithConfirm(const Rect & Pos, EFonts font, ETextAlignment alignment, std::string text, bool limitToRect, std::function<void()> confirmCallback)
  25. : CTextInput(Pos, font, alignment, false), confirmCb(confirmCallback), limitToRect(limitToRect), initialText(text)
  26. {
  27. setText(text);
  28. }
  29. bool CTextInputWithConfirm::captureThisKey(EShortcut key)
  30. {
  31. return hasFocus() && (key == EShortcut::GLOBAL_ACCEPT || key == EShortcut::GLOBAL_CANCEL || key == EShortcut::GLOBAL_BACKSPACE);
  32. }
  33. void CTextInputWithConfirm::keyPressed(EShortcut key)
  34. {
  35. if(!hasFocus())
  36. return;
  37. if(key == EShortcut::GLOBAL_ACCEPT)
  38. confirm();
  39. else if(key == EShortcut::GLOBAL_CANCEL)
  40. {
  41. setText(initialText);
  42. removeFocus();
  43. }
  44. CTextInput::keyPressed(key);
  45. }
  46. bool CTextInputWithConfirm::receiveEvent(const Point & position, int eventType) const
  47. {
  48. return eventType == AEventsReceiver::LCLICK; // capture all left clicks (not only within control)
  49. }
  50. void CTextInputWithConfirm::clickReleased(const Point & cursorPosition)
  51. {
  52. if(!pos.isInside(cursorPosition)) // clicked outside
  53. confirm();
  54. }
  55. void CTextInputWithConfirm::clickPressed(const Point & cursorPosition)
  56. {
  57. if(pos.isInside(cursorPosition)) // clickPressed should respect control area (receiveEvent also affects this)
  58. CTextInput::clickPressed(cursorPosition);
  59. }
  60. void CTextInputWithConfirm::onFocusGot()
  61. {
  62. initialText = getText();
  63. CTextInput::onFocusGot();
  64. }
  65. void CTextInputWithConfirm::textInputted(const std::string & enteredText)
  66. {
  67. if(!hasFocus())
  68. return;
  69. std::string visibleText = getVisibleText() + enteredText;
  70. const auto & font = ENGINE->renderHandler().loadFont(label->font);
  71. if(!limitToRect || (font->getStringWidth(visibleText) - CLabel::getDelimitersWidth(label->font, visibleText)) < pos.w)
  72. CTextInput::textInputted(enteredText);
  73. }
  74. void CTextInputWithConfirm::deactivate()
  75. {
  76. removeUsedEvents(LCLICK);
  77. CTextInput::deactivate();
  78. }
  79. void CTextInputWithConfirm::confirm()
  80. {
  81. if(getText().empty())
  82. setText(initialText);
  83. if(confirmCb && initialText != getText())
  84. confirmCb();
  85. removeFocus();
  86. }
  87. CTextInput::CTextInput(const Rect & Pos)
  88. :originalAlignment(ETextAlignment::CENTERLEFT)
  89. {
  90. pos += Pos.topLeft();
  91. pos.h = Pos.h;
  92. pos.w = Pos.w;
  93. addUsedEvents(LCLICK | SHOW_POPUP | KEYBOARD | TEXTINPUT);
  94. }
  95. void CTextInput::createLabel(bool giveFocusToInput)
  96. {
  97. OBJECT_CONSTRUCTION;
  98. label = std::make_shared<CLabel>();
  99. label->pos = pos;
  100. label->alignment = originalAlignment;
  101. #if !defined(VCMI_MOBILE)
  102. if(giveFocusToInput)
  103. giveFocus();
  104. #endif
  105. }
  106. CTextInput::CTextInput(const Rect & Pos, EFonts font, ETextAlignment alignment, bool giveFocusToInput)
  107. : CTextInput(Pos)
  108. {
  109. originalAlignment = alignment;
  110. setRedrawParent(true);
  111. createLabel(giveFocusToInput);
  112. setFont(font);
  113. setAlignment(alignment);
  114. }
  115. CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const ImagePath & bgName)
  116. : CTextInput(Pos)
  117. {
  118. OBJECT_CONSTRUCTION;
  119. if (!bgName.empty())
  120. background = std::make_shared<CPicture>(bgName, bgOffset.x, bgOffset.y);
  121. else
  122. setRedrawParent(true);
  123. createLabel(true);
  124. }
  125. CTextInput::CTextInput(const Rect & Pos, std::shared_ptr<IImage> srf)
  126. : CTextInput(Pos)
  127. {
  128. OBJECT_CONSTRUCTION;
  129. background = std::make_shared<CPicture>(srf, Pos);
  130. pos.w = background->pos.w;
  131. pos.h = background->pos.h;
  132. background->pos = pos;
  133. createLabel(true);
  134. }
  135. void CTextInput::setFont(EFonts font)
  136. {
  137. label->font = font;
  138. }
  139. void CTextInput::setColor(const ColorRGBA & color)
  140. {
  141. label->color = color;
  142. }
  143. void CTextInput::setAlignment(ETextAlignment alignment)
  144. {
  145. originalAlignment = alignment;
  146. label->alignment = alignment;
  147. }
  148. const std::string & CTextInput::getText() const
  149. {
  150. return currentText;
  151. }
  152. void CTextInput::setCallback(const TextEditedCallback & cb)
  153. {
  154. assert(!onTextEdited);
  155. onTextEdited = cb;
  156. }
  157. void CTextInput::setPopupCallback(const std::function<void()> & cb)
  158. {
  159. callbackPopup = cb;
  160. }
  161. void CTextInput::setFilterFilename()
  162. {
  163. assert(!onTextFiltering);
  164. onTextFiltering = std::bind(&CTextInput::filenameFilter, _1, _2);
  165. }
  166. void CTextInput::setFilterNumber(int minValue, int maxValue)
  167. {
  168. onTextFiltering = std::bind(&CTextInput::numberFilter, _1, _2, minValue, maxValue);
  169. }
  170. std::string CTextInput::getVisibleText() const
  171. {
  172. return hasFocus() ? currentText + composedText + "_" : currentText;
  173. }
  174. void CTextInput::showPopupWindow(const Point & cursorPosition)
  175. {
  176. if(callbackPopup)
  177. callbackPopup();
  178. }
  179. void CTextInput::clickPressed(const Point & cursorPosition)
  180. {
  181. // attempt to give focus unconditionally, even if we already have it
  182. // this forces on-screen keyboard to show up again, even if player have closed it before
  183. giveFocus();
  184. }
  185. void CTextInput::keyPressed(EShortcut key)
  186. {
  187. if(!hasFocus())
  188. return;
  189. if(key == EShortcut::GLOBAL_MOVE_FOCUS)
  190. {
  191. moveFocus();
  192. return;
  193. }
  194. bool redrawNeeded = false;
  195. switch(key)
  196. {
  197. case EShortcut::GLOBAL_BACKSPACE:
  198. if(!composedText.empty())
  199. {
  200. TextOperations::trimRightUnicode(composedText);
  201. redrawNeeded = true;
  202. }
  203. else if(!currentText.empty())
  204. {
  205. TextOperations::trimRightUnicode(currentText);
  206. redrawNeeded = true;
  207. }
  208. break;
  209. default:
  210. break;
  211. }
  212. if(redrawNeeded)
  213. {
  214. updateLabel();
  215. if(onTextEdited)
  216. onTextEdited(currentText);
  217. }
  218. }
  219. void CTextInput::setText(const std::string & nText)
  220. {
  221. currentText = nText;
  222. updateLabel();
  223. }
  224. void CTextInput::updateLabel()
  225. {
  226. std::string visibleText = getVisibleText();
  227. label->alignment = originalAlignment;
  228. const auto & font = ENGINE->renderHandler().loadFont(label->font);
  229. while ((font->getStringWidth(visibleText) - CLabel::getDelimitersWidth(label->font, visibleText)) > pos.w)
  230. {
  231. label->alignment = ETextAlignment::CENTERRIGHT;
  232. visibleText = visibleText.substr(TextOperations::getUnicodeCharacterSize(visibleText[0]));
  233. }
  234. label->setText(visibleText);
  235. }
  236. void CTextInput::textInputted(const std::string & enteredText)
  237. {
  238. if(!hasFocus())
  239. return;
  240. std::string oldText = currentText;
  241. setText(getText() + enteredText);
  242. if(onTextFiltering)
  243. onTextFiltering(currentText, oldText);
  244. if(currentText != oldText)
  245. {
  246. updateLabel();
  247. if(onTextEdited)
  248. onTextEdited(currentText);
  249. }
  250. composedText.clear();
  251. }
  252. void CTextInput::textEdited(const std::string & enteredText)
  253. {
  254. if(!hasFocus())
  255. return;
  256. composedText = enteredText;
  257. updateLabel();
  258. }
  259. void CTextInput::filenameFilter(std::string & text, const std::string &oldText)
  260. {
  261. static const std::string forbiddenChars = "<>:\"/\\|?*\r\n"; //if we are entering a filename, some special characters won't be allowed
  262. size_t pos;
  263. while((pos = text.find_first_of(forbiddenChars)) != std::string::npos)
  264. text.erase(pos, 1);
  265. }
  266. void CTextInput::numberFilter(std::string & text, const std::string & oldText, int minValue, int maxValue)
  267. {
  268. assert(minValue < maxValue);
  269. if(text.empty())
  270. text = "0";
  271. size_t pos = 0;
  272. if(text[0] == '-') //allow '-' sign as first symbol only
  273. pos++;
  274. while(pos < text.size())
  275. {
  276. if(text[pos] < '0' || text[pos] > '9')
  277. {
  278. text = oldText;
  279. return; //new text is not number.
  280. }
  281. pos++;
  282. }
  283. try
  284. {
  285. int value = boost::lexical_cast<int>(text);
  286. if(value < minValue)
  287. text = std::to_string(minValue);
  288. else if(value > maxValue)
  289. text = std::to_string(maxValue);
  290. }
  291. catch(boost::bad_lexical_cast &)
  292. {
  293. //Should never happen. Unless I missed some cases
  294. logGlobal->warn("Warning: failed to convert %s to number!", text);
  295. text = oldText;
  296. }
  297. }
  298. void CTextInput::activate()
  299. {
  300. CFocusable::activate();
  301. if (hasFocus())
  302. {
  303. #if defined(VCMI_MOBILE)
  304. //giveFocus();
  305. #else
  306. ENGINE->input().startTextInput(pos);
  307. #endif
  308. }
  309. }
  310. void CTextInput::deactivate()
  311. {
  312. CFocusable::deactivate();
  313. if (hasFocus())
  314. {
  315. #if defined(VCMI_MOBILE)
  316. removeFocus();
  317. #else
  318. ENGINE->input().stopTextInput();
  319. #endif
  320. }
  321. }
  322. void CTextInput::onFocusGot()
  323. {
  324. updateLabel();
  325. }
  326. void CTextInput::onFocusLost()
  327. {
  328. updateLabel();
  329. }
  330. void CFocusable::focusGot()
  331. {
  332. if (isActive())
  333. ENGINE->input().startTextInput(pos);
  334. onFocusGot();
  335. }
  336. void CFocusable::focusLost()
  337. {
  338. if (isActive())
  339. ENGINE->input().stopTextInput();
  340. onFocusLost();
  341. }
  342. CFocusable::CFocusable()
  343. {
  344. focusables.push_back(this);
  345. }
  346. CFocusable::~CFocusable()
  347. {
  348. if(hasFocus())
  349. inputWithFocus = nullptr;
  350. focusables -= this;
  351. }
  352. bool CFocusable::hasFocus() const
  353. {
  354. return inputWithFocus == this;
  355. }
  356. void CFocusable::giveFocus()
  357. {
  358. auto previousInput = inputWithFocus;
  359. inputWithFocus = this;
  360. if(previousInput)
  361. previousInput->focusLost();
  362. focusGot();
  363. }
  364. void CFocusable::moveFocus()
  365. {
  366. auto i = vstd::find(focusables, this);
  367. auto ourIt = i;
  368. for(i++; i != ourIt; i++)
  369. {
  370. if(i == focusables.end())
  371. i = focusables.begin();
  372. if(*i == this)
  373. return;
  374. if((*i)->isActive())
  375. {
  376. (*i)->giveFocus();
  377. break;
  378. }
  379. }
  380. }
  381. void CFocusable::removeFocus()
  382. {
  383. if(this == inputWithFocus)
  384. {
  385. inputWithFocus = nullptr;
  386. focusLost();
  387. }
  388. }