LineEdit.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  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. extern const char* UI_CATEGORY;
  33. OBJECTTYPESTATIC(LineEdit);
  34. LineEdit::LineEdit(Context* context) :
  35. BorderImage(context),
  36. lastFont_(0),
  37. lastFontSize_(0),
  38. cursorPosition_(0),
  39. dragBeginCursor_(M_MAX_UNSIGNED),
  40. cursorBlinkRate_(1.0f),
  41. cursorBlinkTimer_(0.0f),
  42. maxLength_(0),
  43. echoCharacter_(0),
  44. cursorMovable_(true),
  45. textSelectable_(true),
  46. textCopyable_(true),
  47. doubleClickInterval_(500)
  48. {
  49. clipChildren_ = true;
  50. enabled_ = true;
  51. focusMode_ = FM_FOCUSABLE_DEFOCUSABLE;
  52. text_ = CreateChild<Text>("LE_Text");
  53. text_->SetInternal(true);
  54. cursor_ = CreateChild<BorderImage>("LE_Cursor");
  55. cursor_->SetInternal(true);
  56. cursor_->SetPriority(1); // Show over text
  57. SubscribeToEvent(this, E_FOCUSED, HANDLER(LineEdit, HandleFocused));
  58. SubscribeToEvent(this, E_DEFOCUSED, HANDLER(LineEdit, HandleDefocused));
  59. SubscribeToEvent(this, E_LAYOUTUPDATED, HANDLER(LineEdit, HandleFocused));
  60. }
  61. LineEdit::~LineEdit()
  62. {
  63. }
  64. void LineEdit::RegisterObject(Context* context)
  65. {
  66. context->RegisterFactory<LineEdit>(UI_CATEGORY);
  67. COPY_BASE_ATTRIBUTES(LineEdit, BorderImage);
  68. UPDATE_ATTRIBUTE_DEFAULT_VALUE(LineEdit, "Clip Children", true);
  69. UPDATE_ATTRIBUTE_DEFAULT_VALUE(LineEdit, "Is Enabled", true);
  70. UPDATE_ATTRIBUTE_DEFAULT_VALUE(LineEdit, "Focus Mode", FM_FOCUSABLE_DEFOCUSABLE);
  71. ACCESSOR_ATTRIBUTE(LineEdit, VAR_INT, "Max Length", GetMaxLength, SetMaxLength, unsigned, 0, AM_FILE);
  72. ACCESSOR_ATTRIBUTE(LineEdit, VAR_BOOL, "Is Cursor Movable", IsCursorMovable, SetCursorMovable, bool, true, AM_FILE);
  73. ACCESSOR_ATTRIBUTE(LineEdit, VAR_BOOL, "Is Text Selectable", IsTextSelectable, SetTextSelectable, bool, true, AM_FILE);
  74. ACCESSOR_ATTRIBUTE(LineEdit, VAR_BOOL, "Is Text Copyable", IsTextCopyable, SetTextCopyable, bool, true, AM_FILE);
  75. ACCESSOR_ATTRIBUTE(LineEdit, VAR_FLOAT, "Cursor Blink Rate", GetCursorBlinkRate, SetCursorBlinkRate, float, 1.0f, AM_FILE);
  76. ATTRIBUTE(LineEdit, VAR_INT, "Echo Character", echoCharacter_, 0, AM_FILE);
  77. ACCESSOR_ATTRIBUTE(LineEdit, VAR_FLOAT, "Double Click Interval", GetDoubleClickInterval, SetDoubleClickInterval, float, 0.5f, AM_FILE);
  78. }
  79. void LineEdit::ApplyAttributes()
  80. {
  81. BorderImage::ApplyAttributes();
  82. // Set the text's position to match clipping and indent width, so that text left edge is not left partially hidden
  83. text_->SetPosition(GetIndentWidth() + clipBorder_.left_, clipBorder_.top_);
  84. // Sync the text line
  85. line_ = text_->GetText();
  86. }
  87. void LineEdit::Update(float timeStep)
  88. {
  89. if (cursorBlinkRate_ > 0.0f)
  90. cursorBlinkTimer_ = fmodf(cursorBlinkTimer_ + cursorBlinkRate_ * timeStep, 1.0f);
  91. // Update cursor position if font has changed
  92. if (text_->GetFont() != lastFont_ || text_->GetFontSize() != lastFontSize_)
  93. {
  94. lastFont_ = text_->GetFont();
  95. lastFontSize_ = text_->GetFontSize();
  96. UpdateCursor();
  97. }
  98. bool cursorVisible = HasFocus() ? cursorBlinkTimer_ < 0.5f : false;
  99. cursor_->SetVisible(cursorVisible);
  100. }
  101. void LineEdit::OnClick(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
  102. {
  103. if (buttons & MOUSEB_LEFT && cursorMovable_)
  104. {
  105. if (doubleClickTimer_.GetMSec(true) < doubleClickInterval_)
  106. text_->SetSelection(0);
  107. else
  108. {
  109. unsigned pos = GetCharIndex(position);
  110. if (pos != M_MAX_UNSIGNED)
  111. {
  112. SetCursorPosition(pos);
  113. text_->ClearSelection();
  114. }
  115. }
  116. }
  117. }
  118. void LineEdit::OnDragBegin(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
  119. {
  120. dragBeginCursor_ = GetCharIndex(position);
  121. }
  122. void LineEdit::OnDragMove(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
  123. {
  124. if (cursorMovable_ && textSelectable_)
  125. {
  126. unsigned start = dragBeginCursor_;
  127. unsigned current = GetCharIndex(position);
  128. if (start != M_MAX_UNSIGNED && current != M_MAX_UNSIGNED)
  129. {
  130. if (start < current)
  131. text_->SetSelection(start, current - start);
  132. else
  133. text_->SetSelection(current, start - current);
  134. SetCursorPosition(current);
  135. }
  136. }
  137. }
  138. bool LineEdit::OnDragDropTest(UIElement* source)
  139. {
  140. if (source)
  141. {
  142. ShortStringHash sourceType = source->GetType();
  143. return sourceType == LineEdit::GetTypeStatic() || sourceType == Text::GetTypeStatic();
  144. }
  145. return false;
  146. }
  147. bool LineEdit::OnDragDropFinish(UIElement* source)
  148. {
  149. if (source)
  150. {
  151. ShortStringHash sourceType = source->GetType();
  152. if (sourceType == LineEdit::GetTypeStatic())
  153. {
  154. LineEdit* sourceLineEdit = static_cast<LineEdit*>(source);
  155. SetText(sourceLineEdit->GetText());
  156. return true;
  157. }
  158. else if (sourceType == Text::GetTypeStatic())
  159. {
  160. Text* sourceText = static_cast<Text*>(source);
  161. SetText(sourceText->GetText());
  162. return true;
  163. }
  164. }
  165. return false;
  166. }
  167. void LineEdit::OnKey(int key, int buttons, int qualifiers)
  168. {
  169. bool changed = false;
  170. bool cursorMoved = false;
  171. switch (key)
  172. {
  173. case 'X':
  174. case 'C':
  175. if (textCopyable_ && qualifiers & QUAL_CTRL)
  176. {
  177. unsigned start = text_->GetSelectionStart();
  178. unsigned length = text_->GetSelectionLength();
  179. if (text_->GetSelectionLength())
  180. GetSubsystem<UI>()->SetClipBoardText(line_.SubstringUTF8(start, length));
  181. if (key == 'X')
  182. {
  183. if (start + length < line_.LengthUTF8())
  184. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  185. else
  186. line_ = line_.SubstringUTF8(0, start);
  187. text_->ClearSelection();
  188. cursorPosition_ = start;
  189. changed = true;
  190. }
  191. }
  192. break;
  193. case 'V':
  194. if (textCopyable_ && qualifiers & QUAL_CTRL)
  195. {
  196. const String& clipBoard = GetSubsystem<UI>()->GetClipBoardText();
  197. if (!clipBoard.Empty())
  198. {
  199. // Remove selected text first
  200. if(text_->GetSelectionLength() > 0)
  201. {
  202. unsigned start = text_->GetSelectionStart();
  203. unsigned length = text_->GetSelectionLength();
  204. if (start + length < line_.LengthUTF8())
  205. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  206. else
  207. line_ = line_.SubstringUTF8(0, start);
  208. text_->ClearSelection();
  209. cursorPosition_ = start;
  210. }
  211. if (cursorPosition_ < line_.LengthUTF8())
  212. line_ = line_.SubstringUTF8(0, cursorPosition_) + clipBoard + line_.SubstringUTF8(cursorPosition_);
  213. else
  214. line_ += clipBoard;
  215. cursorPosition_ += clipBoard.LengthUTF8();
  216. changed = true;
  217. }
  218. }
  219. break;
  220. case KEY_HOME:
  221. qualifiers |= QUAL_CTRL;
  222. // Fallthru
  223. case KEY_LEFT:
  224. if (!(qualifiers & QUAL_SHIFT))
  225. text_->ClearSelection();
  226. if (cursorMovable_ && cursorPosition_ > 0)
  227. {
  228. if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
  229. dragBeginCursor_ = cursorPosition_;
  230. if (qualifiers & QUAL_CTRL)
  231. cursorPosition_ = 0;
  232. else
  233. --cursorPosition_;
  234. cursorMoved = true;
  235. if (textSelectable_ && qualifiers & QUAL_SHIFT)
  236. {
  237. unsigned start = dragBeginCursor_;
  238. unsigned current = cursorPosition_;
  239. if (start < current)
  240. text_->SetSelection(start, current - start);
  241. else
  242. text_->SetSelection(current, start - current);
  243. }
  244. }
  245. break;
  246. case KEY_END:
  247. qualifiers |= QUAL_CTRL;
  248. // Fallthru
  249. case KEY_RIGHT:
  250. if (!(qualifiers & QUAL_SHIFT))
  251. text_->ClearSelection();
  252. if (cursorMovable_ && cursorPosition_ < line_.LengthUTF8())
  253. {
  254. if (textSelectable_ && qualifiers & QUAL_SHIFT && !text_->GetSelectionLength())
  255. dragBeginCursor_ = cursorPosition_;
  256. if (qualifiers & QUAL_CTRL)
  257. cursorPosition_ = line_.LengthUTF8();
  258. else
  259. ++cursorPosition_;
  260. cursorMoved = true;
  261. if (textSelectable_ && qualifiers & QUAL_SHIFT)
  262. {
  263. unsigned start = dragBeginCursor_;
  264. unsigned current = cursorPosition_;
  265. if (start < current)
  266. text_->SetSelection(start, current - start);
  267. else
  268. text_->SetSelection(current, start - current);
  269. }
  270. }
  271. break;
  272. case KEY_DELETE:
  273. if (!text_->GetSelectionLength())
  274. {
  275. if (cursorPosition_ < line_.LengthUTF8())
  276. {
  277. line_ = line_.SubstringUTF8(0, cursorPosition_) + line_.SubstringUTF8(cursorPosition_ + 1);
  278. changed = true;
  279. }
  280. }
  281. else
  282. {
  283. // If a selection exists, erase it
  284. unsigned start = text_->GetSelectionStart();
  285. unsigned length = text_->GetSelectionLength();
  286. if (start + length < line_.LengthUTF8())
  287. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  288. else
  289. line_ = line_.SubstringUTF8(0, start);
  290. text_->ClearSelection();
  291. cursorPosition_ = start;
  292. changed = true;
  293. }
  294. break;
  295. case KEY_UP:
  296. case KEY_DOWN:
  297. case KEY_PAGEUP:
  298. case KEY_PAGEDOWN:
  299. {
  300. using namespace UnhandledKey;
  301. VariantMap eventData;
  302. eventData[P_ELEMENT] = (void*)this;
  303. eventData[P_KEY] = key;
  304. eventData[P_BUTTONS] = buttons;
  305. eventData[P_QUALIFIERS] = qualifiers;
  306. SendEvent(E_UNHANDLEDKEY, eventData);
  307. }
  308. return;
  309. case KEY_BACKSPACE:
  310. if (!text_->GetSelectionLength())
  311. {
  312. if (line_.LengthUTF8() && cursorPosition_)
  313. {
  314. if (cursorPosition_ < line_.LengthUTF8())
  315. line_ = line_.SubstringUTF8(0, cursorPosition_ - 1) + line_.SubstringUTF8(cursorPosition_);
  316. else
  317. line_ = line_.SubstringUTF8(0, cursorPosition_ - 1);
  318. --cursorPosition_;
  319. changed = true;
  320. }
  321. }
  322. else
  323. {
  324. // If a selection exists, erase it
  325. unsigned start = text_->GetSelectionStart();
  326. unsigned length = text_->GetSelectionLength();
  327. if (start + length < line_.LengthUTF8())
  328. line_ = line_.SubstringUTF8(0, start) + line_.SubstringUTF8(start + length);
  329. else
  330. line_ = line_.SubstringUTF8(0, start);
  331. text_->ClearSelection();
  332. cursorPosition_ = start;
  333. changed = true;
  334. }
  335. break;
  336. case KEY_RETURN:
  337. case KEY_RETURN2:
  338. case KEY_KP_ENTER:
  339. {
  340. using namespace TextFinished;
  341. VariantMap eventData;
  342. eventData[P_ELEMENT] = (void*)this;
  343. eventData[P_TEXT] = line_;
  344. SendEvent(E_TEXTFINISHED, eventData);
  345. return;
  346. }
  347. break;
  348. }
  349. if (changed)
  350. {
  351. UpdateText();
  352. UpdateCursor();
  353. }
  354. else if (cursorMoved)
  355. UpdateCursor();
  356. }
  357. void LineEdit::OnChar(unsigned c, int buttons, int qualifiers)
  358. {
  359. bool changed = false;
  360. // If only CTRL is held down, do not edit
  361. if ((qualifiers & (QUAL_CTRL | QUAL_ALT)) == QUAL_CTRL)
  362. return;
  363. if (c >= 0x20 && (!maxLength_ || line_.LengthUTF8() < maxLength_))
  364. {
  365. String charStr;
  366. charStr.AppendUTF8(c);
  367. if (!text_->GetSelectionLength())
  368. {
  369. if (cursorPosition_ == line_.LengthUTF8())
  370. line_ += charStr;
  371. else
  372. line_ = line_.SubstringUTF8(0, cursorPosition_) + charStr + line_.SubstringUTF8(cursorPosition_);
  373. ++cursorPosition_;
  374. }
  375. else
  376. {
  377. // If a selection exists, erase it first
  378. unsigned start = text_->GetSelectionStart();
  379. unsigned length = text_->GetSelectionLength();
  380. if (start + length < line_.LengthUTF8())
  381. line_ = line_.SubstringUTF8(0, start) + charStr + line_.SubstringUTF8(start + length);
  382. else
  383. line_ = line_.SubstringUTF8(0, start) + charStr;
  384. cursorPosition_ = start + 1;
  385. }
  386. changed = true;
  387. }
  388. if (changed)
  389. {
  390. text_->ClearSelection();
  391. UpdateText();
  392. UpdateCursor();
  393. }
  394. }
  395. void LineEdit::SetText(const String& text)
  396. {
  397. if (text != line_)
  398. {
  399. line_ = text;
  400. cursorPosition_ = line_.LengthUTF8();
  401. UpdateText();
  402. UpdateCursor();
  403. }
  404. }
  405. void LineEdit::SetCursorPosition(unsigned position)
  406. {
  407. if (position > line_.LengthUTF8() || !cursorMovable_)
  408. position = line_.LengthUTF8();
  409. if (position != cursorPosition_)
  410. {
  411. cursorPosition_ = position;
  412. UpdateCursor();
  413. }
  414. }
  415. void LineEdit::SetCursorBlinkRate(float rate)
  416. {
  417. cursorBlinkRate_ = Max(rate, 0.0f);
  418. if (cursorBlinkRate_ == 0.0f)
  419. cursorBlinkTimer_ = 0.0f; // Cursor does not blink, i.e. always visible
  420. }
  421. void LineEdit::SetMaxLength(unsigned length)
  422. {
  423. maxLength_ = length;
  424. }
  425. void LineEdit::SetEchoCharacter(unsigned c)
  426. {
  427. echoCharacter_ = c;
  428. UpdateText();
  429. }
  430. void LineEdit::SetCursorMovable(bool enable)
  431. {
  432. cursorMovable_ = enable;
  433. }
  434. void LineEdit::SetTextSelectable(bool enable)
  435. {
  436. textSelectable_ = enable;
  437. }
  438. void LineEdit::SetTextCopyable(bool enable)
  439. {
  440. textCopyable_ = enable;
  441. }
  442. void LineEdit::SetDoubleClickInterval(float interval)
  443. {
  444. doubleClickInterval_ = Max((int)(interval * 1000.0f), 0);
  445. }
  446. float LineEdit::GetDoubleClickInterval() const
  447. {
  448. return (float)doubleClickInterval_ / 1000.0f;
  449. }
  450. bool LineEdit::FilterImplicitAttributes(XMLElement& dest) const
  451. {
  452. if (!BorderImage::FilterImplicitAttributes(dest))
  453. return false;
  454. XMLElement childElem = dest.GetChild("element");
  455. if (!childElem)
  456. return false;
  457. if (!RemoveChildXML(childElem, "Name", "LE_Text"))
  458. return false;
  459. if (!RemoveChildXML(childElem, "Position"))
  460. return false;
  461. childElem = childElem.GetNext("element");
  462. if (!childElem)
  463. return false;
  464. if (!RemoveChildXML(childElem, "Name", "LE_Cursor"))
  465. return false;
  466. if (!RemoveChildXML(childElem, "Priority", "1"))
  467. return false;
  468. if (!RemoveChildXML(childElem, "Position"))
  469. return false;
  470. if (!RemoveChildXML(childElem, "Is Visible"))
  471. return false;
  472. return true;
  473. }
  474. void LineEdit::UpdateText()
  475. {
  476. unsigned utf8Length = line_.LengthUTF8();
  477. if (!echoCharacter_)
  478. text_->SetText(line_);
  479. else
  480. {
  481. String echoText;
  482. for (unsigned i = 0; i < utf8Length; ++i)
  483. echoText.AppendUTF8(echoCharacter_);
  484. text_->SetText(echoText);
  485. }
  486. if (cursorPosition_ > utf8Length)
  487. {
  488. cursorPosition_ = utf8Length;
  489. UpdateCursor();
  490. }
  491. using namespace TextChanged;
  492. VariantMap eventData;
  493. eventData[P_ELEMENT] = (void*)this;
  494. eventData[P_TEXT] = line_;
  495. SendEvent(E_TEXTCHANGED, eventData);
  496. }
  497. void LineEdit::UpdateCursor()
  498. {
  499. int x = 0;
  500. const PODVector<IntVector2>& charPositions = text_->GetCharPositions();
  501. if (charPositions.Size())
  502. x = cursorPosition_ < charPositions.Size() ? charPositions[cursorPosition_].x_ : charPositions.Back().x_;
  503. text_->SetPosition(GetIndentWidth() + clipBorder_.left_, clipBorder_.top_);
  504. cursor_->SetPosition(text_->GetPosition() + IntVector2(x, 0));
  505. cursor_->SetSize(cursor_->GetWidth(), text_->GetRowHeight());
  506. // Scroll if necessary
  507. int sx = -GetChildOffset().x_;
  508. int left = clipBorder_.left_;
  509. int right = GetWidth() - clipBorder_.left_ - clipBorder_.right_ - cursor_->GetWidth();
  510. if (x - sx > right)
  511. sx = x - right;
  512. if (x - sx < left)
  513. sx = x - left;
  514. if (sx < 0)
  515. sx = 0;
  516. SetChildOffset(IntVector2(-sx, 0));
  517. // Restart blinking
  518. cursorBlinkTimer_ = 0.0f;
  519. }
  520. unsigned LineEdit::GetCharIndex(const IntVector2& position)
  521. {
  522. IntVector2 screenPosition = ElementToScreen(position);
  523. IntVector2 textPosition = text_->ScreenToElement(screenPosition);
  524. const PODVector<IntVector2>& charPositions = text_->GetCharPositions();
  525. if (textPosition.x_ < 0)
  526. return 0;
  527. for (unsigned i = charPositions.Size() - 1; i < charPositions.Size(); --i)
  528. {
  529. if (textPosition.x_ >= charPositions[i].x_)
  530. return i;
  531. }
  532. return M_MAX_UNSIGNED;
  533. }
  534. void LineEdit::HandleFocused(StringHash eventType, VariantMap& eventData)
  535. {
  536. UpdateCursor();
  537. }
  538. void LineEdit::HandleDefocused(StringHash eventType, VariantMap& eventData)
  539. {
  540. text_->ClearSelection();
  541. }
  542. }