LineEdit.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. //
  2. // Copyright (c) 2008-2013 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "Precompiled.h"
  23. #include "Context.h"
  24. #include "Input.h"
  25. #include "LineEdit.h"
  26. #include "Text.h"
  27. #include "UI.h"
  28. #include "UIEvents.h"
  29. #include "DebugNew.h"
  30. namespace Urho3D
  31. {
  32. OBJECTTYPESTATIC(LineEdit);
  33. LineEdit::LineEdit(Context* context) :
  34. BorderImage(context),
  35. lastFont_(0),
  36. lastFontSize_(0),
  37. cursorPosition_(0),
  38. dragBeginCursor_(M_MAX_UNSIGNED),
  39. cursorBlinkRate_(1.0f),
  40. cursorBlinkTimer_(0.0f),
  41. maxLength_(0),
  42. echoCharacter_(0),
  43. cursorMovable_(true),
  44. textSelectable_(true),
  45. textCopyable_(true)
  46. {
  47. clipChildren_ = true;
  48. active_ = true;
  49. focusMode_ = FM_FOCUSABLE_DEFOCUSABLE;
  50. text_ = CreateChild<Text>();
  51. text_->SetInternal(true);
  52. cursor_ = CreateChild<BorderImage>();
  53. cursor_->SetInternal(true);
  54. cursor_->SetPriority(1); // Show over text
  55. SubscribeToEvent(this, E_FOCUSED, HANDLER(LineEdit, HandleFocused));
  56. SubscribeToEvent(this, E_DEFOCUSED, HANDLER(LineEdit, HandleDefocused));
  57. }
  58. LineEdit::~LineEdit()
  59. {
  60. }
  61. void LineEdit::RegisterObject(Context* context)
  62. {
  63. context->RegisterFactory<LineEdit>();
  64. ACCESSOR_ATTRIBUTE(LineEdit, VAR_INT, "Max Length", GetMaxLength, SetMaxLength, unsigned, 0, AM_FILE);
  65. ACCESSOR_ATTRIBUTE(LineEdit, VAR_BOOL, "Is Cursor Movable", IsCursorMovable, SetCursorMovable, bool, true, AM_FILE);
  66. ACCESSOR_ATTRIBUTE(LineEdit, VAR_BOOL, "Is Text Selectable", IsTextSelectable, SetTextSelectable, bool, true, AM_FILE);
  67. ACCESSOR_ATTRIBUTE(LineEdit, VAR_BOOL, "Is Text Copyable", IsTextCopyable, SetTextCopyable, bool, true, AM_FILE);
  68. ACCESSOR_ATTRIBUTE(LineEdit, VAR_FLOAT, "Cursor Blink Rate", GetCursorBlinkRate, SetCursorBlinkRate, float, 1.0f, AM_FILE);
  69. ATTRIBUTE(LineEdit, VAR_INT, "Echo Character", echoCharacter_, 0, AM_FILE);
  70. COPY_BASE_ATTRIBUTES(LineEdit, BorderImage);
  71. }
  72. void LineEdit::ApplyAttributes()
  73. {
  74. BorderImage::ApplyAttributes();
  75. // Set the text's position to match clipping, so that text left edge is not left partially hidden
  76. text_->SetPosition(clipBorder_.left_, clipBorder_.top_);
  77. }
  78. void LineEdit::Update(float timeStep)
  79. {
  80. if (cursorBlinkRate_ > 0.0f)
  81. cursorBlinkTimer_ = fmodf(cursorBlinkTimer_ + cursorBlinkRate_ * timeStep, 1.0f);
  82. // Update cursor position if font has changed
  83. if (text_->GetFont() != lastFont_ || text_->GetFontSize() != lastFontSize_)
  84. {
  85. lastFont_ = text_->GetFont();
  86. lastFontSize_ = text_->GetFontSize();
  87. UpdateCursor();
  88. }
  89. bool cursorVisible = HasFocus() ? cursorBlinkTimer_ < 0.5f : false;
  90. cursor_->SetVisible(cursorVisible);
  91. }
  92. void LineEdit::OnClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
  93. {
  94. if (buttons & MOUSEB_LEFT && cursorMovable_)
  95. {
  96. unsigned pos = GetCharIndex(position);
  97. if (pos != M_MAX_UNSIGNED)
  98. {
  99. SetCursorPosition(pos);
  100. text_->ClearSelection();
  101. }
  102. }
  103. }
  104. void LineEdit::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
  105. {
  106. dragBeginCursor_ = GetCharIndex(position);
  107. }
  108. void LineEdit::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
  109. {
  110. if (cursorMovable_ && textSelectable_)
  111. {
  112. unsigned start = dragBeginCursor_;
  113. unsigned current = GetCharIndex(position);
  114. if (start != M_MAX_UNSIGNED && current != M_MAX_UNSIGNED)
  115. {
  116. if (start < current)
  117. text_->SetSelection(start, current - start);
  118. else
  119. text_->SetSelection(current, start - current);
  120. SetCursorPosition(current);
  121. }
  122. }
  123. }
  124. bool LineEdit::OnDragDropTest(UIElement* source)
  125. {
  126. if (source)
  127. {
  128. ShortStringHash sourceType = source->GetType();
  129. return sourceType == LineEdit::GetTypeStatic() || sourceType == Text::GetTypeStatic();
  130. }
  131. return false;
  132. }
  133. bool LineEdit::OnDragDropFinish(UIElement* source)
  134. {
  135. if (source)
  136. {
  137. ShortStringHash sourceType = source->GetType();
  138. if (sourceType == LineEdit::GetTypeStatic())
  139. {
  140. LineEdit* sourceLineEdit = static_cast<LineEdit*>(source);
  141. SetText(sourceLineEdit->GetText());
  142. return true;
  143. }
  144. else if (sourceType == Text::GetTypeStatic())
  145. {
  146. Text* sourceText = static_cast<Text*>(source);
  147. SetText(sourceText->GetText());
  148. return true;
  149. }
  150. }
  151. return false;
  152. }
  153. void LineEdit::OnKey(int key, int buttons, int qualifiers)
  154. {
  155. bool changed = false;
  156. bool cursorMoved = false;
  157. switch (key)
  158. {
  159. case 'X':
  160. case 'C':
  161. if (textCopyable_ && qualifiers & QUAL_CTRL)
  162. {
  163. unsigned start = text_->GetSelectionStart();
  164. unsigned length = text_->GetSelectionLength();
  165. if (text_->GetSelectionLength())
  166. GetSubsystem<UI>()->SetClipBoardText(line_.SubstringUTF8(start, length));
  167. if (key == 'X')
  168. {
  169. if (start + length < line_.LengthUTF8())
  170. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  171. else
  172. line_ = line_.SubstringUTF8(0, start);
  173. text_->ClearSelection();
  174. cursorPosition_ = start;
  175. changed = true;
  176. }
  177. }
  178. break;
  179. case 'V':
  180. if (textCopyable_ && qualifiers & QUAL_CTRL)
  181. {
  182. const String& clipBoard = GetSubsystem<UI>()->GetClipBoardText();
  183. if (!clipBoard.Empty())
  184. {
  185. // Remove selected text first
  186. if(text_->GetSelectionLength() > 0)
  187. {
  188. unsigned start = text_->GetSelectionStart();
  189. unsigned length = text_->GetSelectionLength();
  190. if (start + length < line_.LengthUTF8())
  191. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  192. else
  193. line_ = line_.SubstringUTF8(0, start);
  194. text_->ClearSelection();
  195. cursorPosition_ = start;
  196. }
  197. if (cursorPosition_ < line_.LengthUTF8())
  198. line_ = line_.SubstringUTF8(0, cursorPosition_) + clipBoard + line_.SubstringUTF8(cursorPosition_);
  199. else
  200. line_ += clipBoard;
  201. cursorPosition_ += clipBoard.LengthUTF8();
  202. changed = true;
  203. }
  204. }
  205. break;
  206. case KEY_HOME:
  207. qualifiers |= QUAL_CTRL;
  208. // Fallthru
  209. case KEY_LEFT:
  210. if (!(qualifiers & QUAL_SHIFT))
  211. text_->ClearSelection();
  212. if (cursorMovable_ && cursorPosition_ > 0)
  213. {
  214. if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
  215. dragBeginCursor_ = cursorPosition_;
  216. if (qualifiers & QUAL_CTRL)
  217. cursorPosition_ = 0;
  218. else
  219. --cursorPosition_;
  220. cursorMoved = true;
  221. if (textSelectable_ && qualifiers & QUAL_SHIFT)
  222. {
  223. unsigned start = dragBeginCursor_;
  224. unsigned current = cursorPosition_;
  225. if (start < current)
  226. text_->SetSelection(start, current - start);
  227. else
  228. text_->SetSelection(current, start - current);
  229. }
  230. }
  231. break;
  232. case KEY_END:
  233. qualifiers |= QUAL_CTRL;
  234. // Fallthru
  235. case KEY_RIGHT:
  236. if (!(qualifiers & QUAL_SHIFT))
  237. text_->ClearSelection();
  238. if (cursorMovable_ && cursorPosition_ < line_.LengthUTF8())
  239. {
  240. if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
  241. dragBeginCursor_ = cursorPosition_;
  242. if (qualifiers & QUAL_CTRL)
  243. cursorPosition_ = line_.LengthUTF8();
  244. else
  245. ++cursorPosition_;
  246. cursorMoved = true;
  247. if (textSelectable_ && qualifiers & QUAL_SHIFT)
  248. {
  249. unsigned start = dragBeginCursor_;
  250. unsigned current = cursorPosition_;
  251. if (start < current)
  252. text_->SetSelection(start, current - start);
  253. else
  254. text_->SetSelection(current, start - current);
  255. }
  256. }
  257. break;
  258. case KEY_DELETE:
  259. if (!text_->GetSelectionLength())
  260. {
  261. if (cursorPosition_ < line_.LengthUTF8())
  262. {
  263. line_ = line_.SubstringUTF8(0, cursorPosition_) + line_.SubstringUTF8(cursorPosition_ + 1);
  264. changed = true;
  265. }
  266. }
  267. else
  268. {
  269. // If a selection exists, erase it
  270. unsigned start = text_->GetSelectionStart();
  271. unsigned length = text_->GetSelectionLength();
  272. if (start + length < line_.LengthUTF8())
  273. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  274. else
  275. line_ = line_.SubstringUTF8(0, start);
  276. text_->ClearSelection();
  277. cursorPosition_ = start;
  278. changed = true;
  279. }
  280. break;
  281. case KEY_UP:
  282. case KEY_DOWN:
  283. case KEY_PAGEUP:
  284. case KEY_PAGEDOWN:
  285. {
  286. using namespace UnhandledKey;
  287. VariantMap eventData;
  288. eventData[P_ELEMENT] = (void*)this;
  289. eventData[P_KEY] = key;
  290. eventData[P_BUTTONS] = buttons;
  291. eventData[P_QUALIFIERS] = qualifiers;
  292. SendEvent(E_UNHANDLEDKEY, eventData);
  293. }
  294. return;
  295. case KEY_BACKSPACE:
  296. if (!text_->GetSelectionLength())
  297. {
  298. if (line_.LengthUTF8() && cursorPosition_)
  299. {
  300. if (cursorPosition_ < line_.LengthUTF8())
  301. line_ = line_.SubstringUTF8(0, cursorPosition_ - 1) + line_.SubstringUTF8(cursorPosition_);
  302. else
  303. line_ = line_.SubstringUTF8(0, cursorPosition_ - 1);
  304. --cursorPosition_;
  305. changed = true;
  306. }
  307. }
  308. else
  309. {
  310. // If a selection exists, erase it
  311. unsigned start = text_->GetSelectionStart();
  312. unsigned length = text_->GetSelectionLength();
  313. if (start + length < line_.LengthUTF8())
  314. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  315. else
  316. line_ = line_.SubstringUTF8(0, start);
  317. text_->ClearSelection();
  318. cursorPosition_ = start;
  319. changed = true;
  320. }
  321. break;
  322. case KEY_RETURN:
  323. {
  324. using namespace TextFinished;
  325. VariantMap eventData;
  326. eventData[P_ELEMENT] = (void*)this;
  327. eventData[P_TEXT] = line_;
  328. SendEvent(E_TEXTFINISHED, eventData);
  329. return;
  330. }
  331. break;
  332. }
  333. if (changed)
  334. {
  335. UpdateText();
  336. UpdateCursor();
  337. }
  338. else if (cursorMoved)
  339. UpdateCursor();
  340. }
  341. void LineEdit::OnChar(unsigned c, int buttons, int qualifiers)
  342. {
  343. bool changed = false;
  344. // If only CTRL is held down, do not edit
  345. if ((qualifiers & (QUAL_CTRL | QUAL_ALT)) == QUAL_CTRL)
  346. return;
  347. if (c >= 0x20 && (!maxLength_ || line_.LengthUTF8() < maxLength_))
  348. {
  349. String charStr;
  350. charStr.AppendUTF8(c);
  351. if (!text_->GetSelectionLength())
  352. {
  353. if (cursorPosition_ == line_.LengthUTF8())
  354. line_ += charStr;
  355. else
  356. line_ = line_.SubstringUTF8(0, cursorPosition_) + charStr + line_.SubstringUTF8(cursorPosition_);
  357. ++cursorPosition_;
  358. }
  359. else
  360. {
  361. // If a selection exists, erase it first
  362. unsigned start = text_->GetSelectionStart();
  363. unsigned length = text_->GetSelectionLength();
  364. if (start + length < line_.LengthUTF8())
  365. line_ = line_.SubstringUTF8(0, start) + charStr + line_.SubstringUTF8(start + length);
  366. else
  367. line_ = line_.SubstringUTF8(0, start) + charStr;
  368. cursorPosition_ = start + 1;
  369. }
  370. changed = true;
  371. }
  372. if (changed)
  373. {
  374. text_->ClearSelection();
  375. UpdateText();
  376. UpdateCursor();
  377. }
  378. }
  379. void LineEdit::SetText(const String& text)
  380. {
  381. if (text != line_)
  382. {
  383. line_ = text;
  384. cursorPosition_ = line_.LengthUTF8();
  385. UpdateText();
  386. UpdateCursor();
  387. }
  388. }
  389. void LineEdit::SetCursorPosition(unsigned position)
  390. {
  391. if (position > line_.LengthUTF8() || !cursorMovable_)
  392. position = line_.LengthUTF8();
  393. if (position != cursorPosition_)
  394. {
  395. cursorPosition_ = position;
  396. UpdateCursor();
  397. }
  398. }
  399. void LineEdit::SetCursorBlinkRate(float rate)
  400. {
  401. cursorBlinkRate_ = Max(rate, 0.0f);
  402. if (cursorBlinkRate_ == 0.0f)
  403. cursorBlinkTimer_ = 0.0f; // Cursor does not blink, i.e. always visible
  404. }
  405. void LineEdit::SetMaxLength(unsigned length)
  406. {
  407. maxLength_ = length;
  408. }
  409. void LineEdit::SetEchoCharacter(unsigned c)
  410. {
  411. echoCharacter_ = c;
  412. UpdateText();
  413. }
  414. void LineEdit::SetCursorMovable(bool enable)
  415. {
  416. cursorMovable_ = enable;
  417. }
  418. void LineEdit::SetTextSelectable(bool enable)
  419. {
  420. textSelectable_ = enable;
  421. }
  422. void LineEdit::SetTextCopyable(bool enable)
  423. {
  424. textCopyable_ = enable;
  425. }
  426. void LineEdit::UpdateText()
  427. {
  428. unsigned utf8Length = line_.LengthUTF8();
  429. if (!echoCharacter_)
  430. text_->SetText(line_);
  431. else
  432. {
  433. String echoText;
  434. for (unsigned i = 0; i < utf8Length; ++i)
  435. echoText.AppendUTF8(echoCharacter_);
  436. text_->SetText(echoText);
  437. }
  438. if (cursorPosition_ > utf8Length)
  439. {
  440. cursorPosition_ = utf8Length;
  441. UpdateCursor();
  442. }
  443. using namespace TextChanged;
  444. VariantMap eventData;
  445. eventData[P_ELEMENT] = (void*)this;
  446. eventData[P_TEXT] = line_;
  447. SendEvent(E_TEXTCHANGED, eventData);
  448. }
  449. void LineEdit::UpdateCursor()
  450. {
  451. int x = 0;
  452. const PODVector<IntVector2>& charPositions = text_->GetCharPositions();
  453. if (charPositions.Size())
  454. x = cursorPosition_ < charPositions.Size() ? charPositions[cursorPosition_].x_ : charPositions.Back().x_;
  455. text_->SetPosition(clipBorder_.left_, clipBorder_.top_);
  456. cursor_->SetPosition(text_->GetPosition() + IntVector2(x, 0));
  457. cursor_->SetSize(cursor_->GetWidth(), text_->GetRowHeight());
  458. // Scroll if necessary
  459. int sx = -GetChildOffset().x_;
  460. int left = clipBorder_.left_;
  461. int right = GetWidth() - clipBorder_.left_ - clipBorder_.right_ - cursor_->GetWidth();
  462. if (x - sx > right)
  463. sx = x - right;
  464. if (x - sx < left)
  465. sx = x - left;
  466. if (sx < 0)
  467. sx = 0;
  468. SetChildOffset(IntVector2(-sx, 0));
  469. // Restart blinking
  470. cursorBlinkTimer_ = 0.0f;
  471. }
  472. unsigned LineEdit::GetCharIndex(const IntVector2& position)
  473. {
  474. IntVector2 screenPosition = ElementToScreen(position);
  475. IntVector2 textPosition = text_->ScreenToElement(screenPosition);
  476. const PODVector<IntVector2>& charPositions = text_->GetCharPositions();
  477. if (textPosition.x_ < 0)
  478. return 0;
  479. for (unsigned i = charPositions.Size() - 1; i < charPositions.Size(); --i)
  480. {
  481. if (textPosition.x_ >= charPositions[i].x_)
  482. return i;
  483. }
  484. return M_MAX_UNSIGNED;
  485. }
  486. void LineEdit::HandleFocused(StringHash eventType, VariantMap& eventData)
  487. {
  488. UpdateCursor();
  489. }
  490. void LineEdit::HandleDefocused(StringHash eventType, VariantMap& eventData)
  491. {
  492. text_->ClearSelection();
  493. }
  494. }