LineEdit.cpp 18 KB

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