1 /*
2  *  The ManaPlus Client
3  *  Copyright (C) 2004-2009  The Mana World Development Team
4  *  Copyright (C) 2009-2010  The Mana Developers
5  *  Copyright (C) 2011-2019  The ManaPlus Developers
6  *  Copyright (C) 2019-2021  Andrei Karas
7  *
8  *  This file is part of The ManaPlus Client.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /*      _______   __   __   __   ______   __   __   _______   __   __
25  *     / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___  /\ /  |\/ /\
26  *    / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
27  *   / / /__   / / // / // / // / /    / ___  / // ___  / // /| ' / /
28  *  / /_// /\ / /_// / // / // /_/_   / / // / // /\_/ / // / |  / /
29  * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
30  * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
31  *
32  * Copyright (c) 2004 - 2008 Olof Naessén and Per Larsson
33  *
34  *
35  * Per Larsson a.k.a finalman
36  * Olof Naessén a.k.a jansem/yakslem
37  *
38  * Visit: http://guichan.sourceforge.net
39  *
40  * License: (BSD)
41  * Redistribution and use in source and binary forms, with or without
42  * modification, are permitted provided that the following conditions
43  * are met:
44  * 1. Redistributions of source code must retain the above copyright
45  *    notice, this list of conditions and the following disclaimer.
46  * 2. Redistributions in binary form must reproduce the above copyright
47  *    notice, this list of conditions and the following disclaimer in
48  *    the documentation and/or other materials provided with the
49  *    distribution.
50  * 3. Neither the name of Guichan nor the names of its contributors may
51  *    be used to endorse or promote products derived from this software
52  *    without specific prior written permission.
53  *
54  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
60  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
61  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
62  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
63  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65  */
66 
67 #include "gui/widgets/textfield.h"
68 
69 #include "settings.h"
70 
71 #ifdef USE_SDL2
72 #include "enums/input/keyvalue.h"
73 #endif  // USE_SDL2
74 
75 #include "gui/gui.h"
76 #include "gui/skin.h"
77 #if defined(ANDROID) || defined(__SWITCH__)
78 #include "gui/windowmanager.h"
79 #endif  // ANDROID
80 
81 #include "gui/fonts/font.h"
82 
83 #include "gui/popups/popupmenu.h"
84 
85 #include "input/inputmanager.h"
86 
87 #include "utils/copynpaste.h"
88 #include "utils/stringutils.h"
89 
90 #ifndef USE_SDL2
91 #include "utils/timer.h"
92 #endif  // USE_SDL2
93 
94 #include "render/graphics.h"
95 
96 #include "resources/imagerect.h"
97 
98 #include "resources/image/image.h"
99 
100 #undef DELETE  // Win32 compatibility hack
101 
102 #include "debug.h"
103 
104 Skin *TextField::mSkin;
105 int TextField::instances = 0;
106 float TextField::mAlpha = 1.0;
107 ImageRect TextField::skin;
108 
TextField(const Widget2 * restrict const widget,const std::string & restrict text,const LoseFocusOnTab loseFocusOnTab,ActionListener * restrict const listener,const std::string & restrict eventId,const bool sendAlwaysEvents)109 TextField::TextField(const Widget2 *restrict const widget,
110                      const std::string &restrict text,
111                      const LoseFocusOnTab loseFocusOnTab,
112                      ActionListener *restrict const listener,
113                      const std::string &restrict eventId,
114                      const bool sendAlwaysEvents) :
115     Widget(widget),
116     FocusListener(),
117     KeyListener(),
118     MouseListener(),
119     WidgetListener(),
120     mText(text),
121     mTextChunk(),
122     mCaretPosition(0),
123     mXScroll(0),
124     mCaretColor(&getThemeColor(ThemeColorId::CARET, 255U)),
125     mMinimum(0),
126     mMaximum(0),
127     mLastEventPaste(0),
128     mPadding(1),
129     mNumeric(false),
130     mLoseFocusOnTab(loseFocusOnTab),
131     mAllowSpecialActions(true),
132     mSendAlwaysEvents(sendAlwaysEvents),
133     mTextChanged(true)
134 {
135     mAllowLogic = false;
136     setFocusable(true);
137     addMouseListener(this);
138     addKeyListener(this);
139 
140     setFrameSize(2);
141     mForegroundColor = getThemeColor(ThemeColorId::TEXTFIELD, 255U);
142     mForegroundColor2 = getThemeColor(ThemeColorId::TEXTFIELD_OUTLINE, 255U);
143 
144     addFocusListener(this);
145 
146     if (instances == 0)
147     {
148         if (theme != nullptr)
149         {
150             mSkin = theme->loadSkinRect(skin,
151                 "textfield.xml",
152                 "textfield_background.xml",
153                 0,
154                 8);
155         }
156     }
157 
158     instances++;
159 
160     if (mSkin != nullptr)
161     {
162         mPadding = mSkin->getPadding();
163         mFrameSize = mSkin->getOption("frameSize", 2);
164     }
165 
166     adjustSize();
167     if (!eventId.empty())
168         setActionEventId(eventId);
169 
170     if (listener != nullptr)
171         addActionListener(listener);
172 }
173 
~TextField()174 TextField::~TextField()
175 {
176     if (mWindow != nullptr)
177         mWindow->removeWidgetListener(this);
178 
179     if (gui != nullptr)
180         gui->removeDragged(this);
181 
182     instances--;
183     if (instances == 0)
184     {
185         if (theme != nullptr)
186         {
187             theme->unload(mSkin);
188             Theme::unloadRect(skin, 0, 8);
189         }
190     }
191     mTextChunk.deleteImage();
192 }
193 
updateAlpha()194 void TextField::updateAlpha()
195 {
196     const float alpha = std::max(settings.guiAlpha,
197         theme->getMinimumOpacity());
198 
199     if (alpha != mAlpha)
200     {
201         mAlpha = alpha;
202         for (int a = 0; a < 9; a++)
203         {
204             if (skin.grid[a] != nullptr)
205                 skin.grid[a]->setAlpha(mAlpha);
206         }
207     }
208 }
209 
draw(Graphics * const graphics)210 void TextField::draw(Graphics *const graphics)
211 {
212     BLOCK_START("TextField::draw")
213     updateAlpha();
214 
215     Font *const font = getFont();
216     if (isFocused())
217     {
218         drawCaret(graphics,
219             font->getWidth(mText.substr(0, mCaretPosition)) - mXScroll);
220     }
221 
222     if (mTextChanged)
223     {
224         mTextChunk.textFont = font;
225         mTextChunk.deleteImage();
226         mTextChunk.text = mText;
227         mTextChunk.color = mForegroundColor;
228         mTextChunk.color2 = mForegroundColor2;
229         font->generate(mTextChunk);
230         mTextChanged = false;
231     }
232 
233     const Image *const image = mTextChunk.img;
234     if (image != nullptr)
235         graphics->drawImage(image, mPadding - mXScroll, mPadding);
236 
237     BLOCK_END("TextField::draw")
238 }
239 
safeDraw(Graphics * const graphics)240 void TextField::safeDraw(Graphics *const graphics)
241 {
242     TextField::draw(graphics);
243 }
244 
drawFrame(Graphics * const graphics)245 void TextField::drawFrame(Graphics *const graphics)
246 {
247     BLOCK_START("TextField::drawFrame")
248     const int bs = 2 * mFrameSize;
249     graphics->drawImageRect(0,
250         0,
251         mDimension.width + bs,
252         mDimension.height + bs,
253         skin);
254     BLOCK_END("TextField::drawFrame")
255 }
256 
safeDrawFrame(Graphics * const graphics)257 void TextField::safeDrawFrame(Graphics *const graphics)
258 {
259     BLOCK_START("TextField::drawFrame")
260     const int bs = 2 * mFrameSize;
261     graphics->drawImageRect(0,
262         0,
263         mDimension.width + bs,
264         mDimension.height + bs,
265         skin);
266     BLOCK_END("TextField::drawFrame")
267 }
268 
setNumeric(const bool numeric)269 void TextField::setNumeric(const bool numeric)
270 {
271     mNumeric = numeric;
272     if (!numeric)
273         return;
274 
275     const char *const text = mText.c_str();
276     for (const char *textPtr = text; *textPtr != 0; ++textPtr)
277     {
278         if (*textPtr < '0' || *textPtr > '9')
279         {
280             setText(mText.substr(0, textPtr - text));
281             return;
282         }
283     }
284 }
285 
getValue() const286 int TextField::getValue() const
287 {
288     if (!mNumeric)
289         return 0;
290 
291     const int value = atoi(mText.c_str());
292     if (value < mMinimum)
293         return mMinimum;
294 
295     if (value > mMaximum)
296         return mMaximum;
297 
298     return value;
299 }
300 
keyPressed(KeyEvent & event)301 void TextField::keyPressed(KeyEvent &event)
302 {
303     const int val = event.getKey().getValue();
304 
305 #ifdef USE_SDL2
306     if (val == KeyValue::TEXTINPUT)
307     {
308         std::string str = event.getText();
309         mText.insert(mCaretPosition, str);
310         mTextChanged = true;
311         mCaretPosition += CAST_U32(str.size());
312         event.consume();
313         fixScroll();
314         if (mSendAlwaysEvents)
315             distributeActionEvent();
316         return;
317     }
318     bool consumed(false);
319 #else  // USE_SDL2
320 
321     if (val >= 32)
322     {
323         if (mNumeric)
324         {
325             if ((val >= '0' && val <= '9') ||
326                 (val == '-' && mCaretPosition == 0U))
327             {
328                 char buf[2];
329                 buf[0] = CAST_8(val);
330                 buf[1] = 0;
331                 mText.insert(mCaretPosition, std::string(buf));
332                 mTextChanged = true;
333                 mCaretPosition += 1;
334                 event.consume();
335                 fixScroll();
336                 if (mSendAlwaysEvents)
337                     distributeActionEvent();
338                 return;
339             }
340         }
341         else if ((mMaximum == 0) ||
342                  CAST_S32(mText.size()) < mMaximum)
343         {
344             int len;
345             if (val < 128)
346                 len = 1;               // 0xxxxxxx
347             else if (val < 0x800)
348                 len = 2;               // 110xxxxx 10xxxxxx
349             else if (val < 0x10000)
350                 len = 3;               // 1110xxxx 10xxxxxx 10xxxxxx
351             else
352                 len = 4;               // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
353 
354             char buf[4];
355             for (int i = 0; i < len; ++ i)
356             {
357                 buf[i] = CAST_8(val >> (6 * (len - i - 1)));
358                 if (i > 0)
359                     buf[i] = CAST_8((buf[i] & 63) | 128);
360             }
361 
362             if (len > 1)
363                 buf[0] |= CAST_8(255U << (8 - len));
364 
365             mText.insert(mCaretPosition, std::string(buf, buf + len));
366             mCaretPosition += len;
367             mTextChanged = true;
368             event.consume();
369             fixScroll();
370             if (mSendAlwaysEvents)
371                 distributeActionEvent();
372             return;
373         }
374     }
375 
376     /* In UTF-8, 10xxxxxx is only used for inner parts of characters. So skip
377        them when processing key presses. */
378 
379     // unblock past key
380     if (val != 22)
381         mLastEventPaste = 0;
382 
383     bool consumed(false);
384 #endif  // USE_SDL2
385 
386     const InputActionT action = event.getActionId();
387     if (!inputManager.isActionActive(InputAction::GUI_CTRL))
388     {
389         if (!handleNormalKeys(action, consumed))
390         {
391             if (consumed)
392                 event.consume();
393             return;
394         }
395     }
396     else
397     {
398         handleCtrlKeys(action, consumed);
399     }
400 
401     if (mSendAlwaysEvents)
402         distributeActionEvent();
403 
404     if (consumed)
405         event.consume();
406     fixScroll();
407 }
408 
handleNormalKeys(const InputActionT action,bool & consumed)409 bool TextField::handleNormalKeys(const InputActionT action, bool &consumed)
410 {
411     PRAGMA45(GCC diagnostic push)
412     PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
413     switch (action)
414     {
415         case InputAction::GUI_LEFT:
416         {
417             consumed = true;
418             while (mCaretPosition > 0)
419             {
420                 --mCaretPosition;
421                 if ((mText[mCaretPosition] & 192) != 128)
422                     break;
423             }
424             break;
425         }
426 
427         case InputAction::GUI_RIGHT:
428         {
429             consumed = true;
430             const unsigned sz = CAST_U32(mText.size());
431             while (mCaretPosition < sz)
432             {
433                 ++mCaretPosition;
434                 if (mCaretPosition == sz ||
435                     (mText[mCaretPosition] & 192) != 128)
436                 {
437                     break;
438                 }
439             }
440             break;
441         }
442 
443         case InputAction::GUI_DELETE:
444         {
445             consumed = true;
446             unsigned sz = CAST_U32(mText.size());
447             while (mCaretPosition < sz)
448             {
449                 --sz;
450                 mText.erase(mCaretPosition, 1);
451                 mTextChanged = true;
452                 if (mCaretPosition == sz ||
453                     (mText[mCaretPosition] & 192) != 128)
454                 {
455                     break;
456                 }
457             }
458             break;
459         }
460 
461         case InputAction::GUI_BACKSPACE:
462             consumed = true;
463             deleteCharLeft(mText, &mCaretPosition);
464             mTextChanged = true;
465             break;
466 
467         case InputAction::GUI_SELECT2:
468             distributeActionEvent();
469             consumed = true;
470             fixScroll();
471             return false;
472 
473         case InputAction::GUI_HOME:
474             mCaretPosition = 0;
475             consumed = true;
476             break;
477 
478         case InputAction::GUI_END:
479             mCaretPosition = CAST_U32(mText.size());
480             consumed = true;
481             break;
482 
483         case InputAction::GUI_TAB:
484             if (mLoseFocusOnTab == LoseFocusOnTab_true)
485                 return false;
486             consumed = true;
487             break;
488 
489         default:
490             break;
491     }
492     PRAGMA45(GCC diagnostic pop)
493     return true;
494 }
495 
handleCtrlKeys(const InputActionT action,bool & consumed)496 void TextField::handleCtrlKeys(const InputActionT action, bool &consumed)
497 {
498     PRAGMA45(GCC diagnostic push)
499     PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
500     switch (action)
501     {
502         case InputAction::GUI_LEFT:
503         {
504             moveCaretWordBack();
505             consumed = true;
506             break;
507         }
508         case InputAction::GUI_RIGHT:
509         {
510             moveCaretWordForward();
511             consumed = true;
512             break;
513         }
514         case InputAction::GUI_B:
515         {
516             if (mAllowSpecialActions)
517             {
518                 moveCaretBack();
519                 consumed = true;
520             }
521             break;
522         }
523         case InputAction::GUI_F:
524         {
525             moveCaretForward();
526             consumed = true;
527             break;
528         }
529         case InputAction::GUI_D:
530         {
531             caretDelete();
532             consumed = true;
533             break;
534         }
535         case InputAction::GUI_E:
536         {
537             mCaretPosition = CAST_S32(mText.size());
538             consumed = true;
539             break;
540         }
541         case InputAction::GUI_H:
542         {
543             deleteCharLeft(mText, &mCaretPosition);
544             mTextChanged = true;
545             consumed = true;
546             break;
547         }
548         case InputAction::GUI_K:
549         {
550             mText = mText.substr(0, mCaretPosition);
551             mTextChanged = true;
552             consumed = true;
553             break;
554         }
555         case InputAction::GUI_U:
556         {
557             caretDeleteToStart();
558             consumed = true;
559             break;
560         }
561         case InputAction::GUI_C:
562         {
563             handleCopy();
564             consumed = true;
565             break;
566         }
567         case InputAction::GUI_V:
568         {
569 #ifdef USE_SDL2
570             handlePaste();
571 #else  // USE_SDL2
572 
573             // hack to prevent paste key sticking
574             if ((mLastEventPaste != 0) && mLastEventPaste > cur_time)
575                 break;
576             handlePaste();
577             mLastEventPaste = cur_time + 2;
578 #endif  // USE_SDL2
579 
580             consumed = true;
581             break;
582         }
583         case InputAction::GUI_W:
584         {
585             caretDeleteWord();
586             consumed = true;
587             break;
588         }
589         default:
590             break;
591     }
592     PRAGMA45(GCC diagnostic pop)
593 }
594 
moveCaretBack()595 void TextField::moveCaretBack()
596 {
597     while (mCaretPosition > 0)
598     {
599         --mCaretPosition;
600         if ((mText[mCaretPosition] & 192) != 128)
601         break;
602     }
603 }
604 
moveCaretForward()605 void TextField::moveCaretForward()
606 {
607     const unsigned sz = CAST_U32(mText.size());
608     while (mCaretPosition < sz)
609     {
610         ++mCaretPosition;
611         if (mCaretPosition == sz || (mText[mCaretPosition] & 192) != 128)
612             break;
613     }
614 }
615 
caretDelete()616 void TextField::caretDelete()
617 {
618     unsigned sz = CAST_U32(mText.size());
619     while (mCaretPosition < sz)
620     {
621         --sz;
622         mText.erase(mCaretPosition, 1);
623         if (mCaretPosition == sz || (mText[mCaretPosition] & 192) != 128)
624             break;
625     }
626     mTextChanged = true;
627 }
628 
handlePaste()629 void TextField::handlePaste()
630 {
631     std::string text = getText();
632     size_t caretPos = CAST_SIZE(getCaretPosition());
633 
634     if (retrieveBuffer(text, caretPos))
635     {
636         setText(text);
637         setCaretPosition(CAST_U32(caretPos));
638     }
639 }
640 
caretDeleteToStart()641 void TextField::caretDeleteToStart()
642 {
643     if (mCaretPosition > 0)
644     {
645         mText = mText.substr(mCaretPosition);
646         mCaretPosition = 0;
647     }
648     mTextChanged = true;
649 }
650 
moveCaretWordBack()651 void TextField::moveCaretWordBack()
652 {
653     const unsigned int oldCaret = mCaretPosition;
654     while (mCaretPosition > 0)
655     {
656         if (!isWordSeparator(mText[mCaretPosition - 1]))
657             break;
658         mCaretPosition --;
659     }
660     if (oldCaret != mCaretPosition)
661         return;
662     while (mCaretPosition > 0)
663     {
664         if (isWordSeparator(mText[mCaretPosition - 1]))
665             break;
666         mCaretPosition --;
667     }
668 }
669 
moveCaretWordForward()670 void TextField::moveCaretWordForward()
671 {
672     const unsigned sz = CAST_U32(mText.size());
673     const unsigned int oldCaret = mCaretPosition;
674     while (mCaretPosition < sz)
675     {
676         if (!isWordSeparator(mText[mCaretPosition]))
677             break;
678         mCaretPosition ++;
679     }
680     if (oldCaret != mCaretPosition)
681         return;
682     while (mCaretPosition < sz)
683     {
684         if (isWordSeparator(mText[mCaretPosition]))
685             break;
686         mCaretPosition ++;
687     }
688 }
689 
caretDeleteWord()690 void TextField::caretDeleteWord()
691 {
692     while (mCaretPosition > 0)
693     {
694         deleteCharLeft(mText, &mCaretPosition);
695         if (mCaretPosition > 0 && isWordSeparator(mText[mCaretPosition - 1]))
696             break;
697     }
698     mTextChanged = true;
699 }
700 
handleCopy() const701 void TextField::handleCopy() const
702 {
703     std::string text = getText();
704     sendBuffer(text);
705 }
706 
drawCaret(Graphics * graphics,int x)707 void TextField::drawCaret(Graphics* graphics, int x)
708 {
709     const ClipRect &clipArea = graphics->getTopClip();
710 
711     graphics->setColor(*mCaretColor);
712     graphics->drawLine(x + mPadding, clipArea.height - mPadding,
713         x + mPadding, mPadding);
714 }
715 
adjustSize()716 void TextField::adjustSize()
717 {
718     setWidth(getFont()->getWidth(mText) + 2 * mPadding + 1);
719     adjustHeight();
720 
721     fixScroll();
722 }
723 
adjustHeight()724 void TextField::adjustHeight()
725 {
726     setHeight(getFont()->getHeight() + 2 * mPadding);
727 }
728 
fixScroll()729 void TextField::fixScroll()
730 {
731     if (isFocused())
732     {
733         const int caretX = getFont()->getWidth(
734             mText.substr(0, mCaretPosition));
735 
736         const int width = mDimension.width;
737         const int pad = 2 * mPadding;
738         if (caretX - mXScroll >= width - pad)
739         {
740             mXScroll = caretX - width + pad;
741         }
742         else if (caretX - mXScroll <= 0)
743         {
744             mXScroll = caretX - width / 2;
745 
746             if (mXScroll < 0)
747                 mXScroll = 0;
748         }
749     }
750 }
751 
setCaretPosition(unsigned int position)752 void TextField::setCaretPosition(unsigned int position)
753 {
754     const unsigned int sz = CAST_U32(mText.size());
755     if (position > sz)
756         mCaretPosition = CAST_S32(sz);
757     else
758         mCaretPosition = position;
759 
760     fixScroll();
761 }
762 
fontChanged()763 void TextField::fontChanged()
764 {
765     fixScroll();
766 }
767 
mousePressed(MouseEvent & event)768 void TextField::mousePressed(MouseEvent &event)
769 {
770 #if defined(ANDROID) || defined(__SWITCH__)
771     if (!WindowManager::isKeyboardVisible())
772         inputManager.executeAction(InputAction::SHOW_KEYBOARD);
773 #endif  // ANDROID
774 
775     event.consume();
776     if (event.getButton() == MouseButton::RIGHT)
777     {
778 #ifndef DYECMD
779         if (popupMenu != nullptr)
780             popupMenu->showTextFieldPopup(this);
781 #endif  // DYECMD
782     }
783     else if (event.getButton() == MouseButton::LEFT)
784     {
785         mCaretPosition = getFont()->getStringIndexAt(
786             mText, event.getX() + mXScroll);
787         fixScroll();
788     }
789 }
790 
focusGained(const Event & event A_UNUSED)791 void TextField::focusGained(const Event &event A_UNUSED)
792 {
793 #if defined(ANDROID) || defined(__SWITCH__)
794     if (!WindowManager::isKeyboardVisible())
795         inputManager.executeAction(InputAction::SHOW_KEYBOARD);
796 #endif  // ANDROID
797 }
798 
focusLost(const Event & event A_UNUSED)799 void TextField::focusLost(const Event &event A_UNUSED)
800 {
801 }
802 
setText(const std::string & text)803 void TextField::setText(const std::string& text)
804 {
805     const unsigned int sz = CAST_U32(text.size());
806     if (sz < mCaretPosition)
807         mCaretPosition = sz;
808     mText = text;
809     mTextChanged = true;
810 }
811 
mouseDragged(MouseEvent & event)812 void TextField::mouseDragged(MouseEvent& event)
813 {
814     event.consume();
815 }
816 
widgetHidden(const Event & event A_UNUSED)817 void TextField::widgetHidden(const Event &event A_UNUSED)
818 {
819     mTextChanged = true;
820     mTextChunk.deleteImage();
821 }
822 
setParent(Widget * widget)823 void TextField::setParent(Widget *widget)
824 {
825     if (mWindow != nullptr)
826         mWindow->addWidgetListener(this);
827     Widget::setParent(widget);
828 }
829 
setWindow(Widget * const widget)830 void TextField::setWindow(Widget *const widget)
831 {
832     if ((widget == nullptr) && (mWindow != nullptr))
833     {
834         mWindow->removeWidgetListener(this);
835         mWindow = nullptr;
836     }
837     else
838     {
839         Widget2::setWindow(widget);
840     }
841 }
842 
signalEvent()843 void TextField::signalEvent()
844 {
845     distributeActionEvent();
846 }
847