GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/textfield.cpp Lines: 127 347 36.6 %
Date: 2021-03-17 Branches: 50 270 18.5 %

Line Branch Exec Source
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
109
93
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
93
                     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
186
    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
558
    mTextChanged(true)
134
{
135
93
    mAllowLogic = false;
136
93
    setFocusable(true);
137
93
    addMouseListener(this);
138
93
    addKeyListener(this);
139
140
186
    setFrameSize(2);
141
186
    mForegroundColor = getThemeColor(ThemeColorId::TEXTFIELD, 255U);
142
186
    mForegroundColor2 = getThemeColor(ThemeColorId::TEXTFIELD_OUTLINE, 255U);
143
144
93
    addFocusListener(this);
145
146
93
    if (instances == 0)
147
    {
148
21
        if (theme != nullptr)
149
        {
150

147
            mSkin = theme->loadSkinRect(skin,
151
                "textfield.xml",
152
                "textfield_background.xml",
153
                0,
154
                8);
155
        }
156
    }
157
158
93
    instances++;
159
160
93
    if (mSkin != nullptr)
161
    {
162
186
        mPadding = mSkin->getPadding();
163

372
        mFrameSize = mSkin->getOption("frameSize", 2);
164
    }
165
166
93
    adjustSize();
167
93
    if (!eventId.empty())
168
21
        setActionEventId(eventId);
169
170
93
    if (listener != nullptr)
171
21
        addActionListener(listener);
172
93
}
173
174
700
TextField::~TextField()
175
{
176
93
    if (mWindow != nullptr)
177
93
        mWindow->removeWidgetListener(this);
178
179
93
    if (gui != nullptr)
180
93
        gui->removeDragged(this);
181
182
93
    instances--;
183
93
    if (instances == 0)
184
    {
185
21
        if (theme != nullptr)
186
        {
187
21
            theme->unload(mSkin);
188
21
            Theme::unloadRect(skin, 0, 8);
189
        }
190
    }
191
93
    mTextChunk.deleteImage();
192
142
}
193
194
29
void TextField::updateAlpha()
195
{
196
    const float alpha = std::max(settings.guiAlpha,
197
87
        theme->getMinimumOpacity());
198
199
29
    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
29
}
209
210
29
void TextField::draw(Graphics *const graphics)
211
{
212
    BLOCK_START("TextField::draw")
213
29
    updateAlpha();
214
215
29
    Font *const font = getFont();
216
29
    if (isFocused())
217
    {
218
4
        drawCaret(graphics,
219

12
            font->getWidth(mText.substr(0, mCaretPosition)) - mXScroll);
220
    }
221
222
29
    if (mTextChanged)
223
    {
224
29
        mTextChunk.textFont = font;
225
29
        mTextChunk.deleteImage();
226
58
        mTextChunk.text = mText;
227
29
        mTextChunk.color = mForegroundColor;
228
29
        mTextChunk.color2 = mForegroundColor2;
229
29
        font->generate(mTextChunk);
230
29
        mTextChanged = false;
231
    }
232
233
29
    const Image *const image = mTextChunk.img;
234
29
    if (image != nullptr)
235
8
        graphics->drawImage(image, mPadding - mXScroll, mPadding);
236
237
    BLOCK_END("TextField::draw")
238
29
}
239
240
void TextField::safeDraw(Graphics *const graphics)
241
{
242
    TextField::draw(graphics);
243
}
244
245
29
void TextField::drawFrame(Graphics *const graphics)
246
{
247
    BLOCK_START("TextField::drawFrame")
248
29
    const int bs = 2 * mFrameSize;
249
58
    graphics->drawImageRect(0,
250
        0,
251
29
        mDimension.width + bs,
252
29
        mDimension.height + bs,
253
58
        skin);
254
    BLOCK_END("TextField::drawFrame")
255
29
}
256
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
269
9
void TextField::setNumeric(const bool numeric)
270
{
271
9
    mNumeric = numeric;
272
9
    if (!numeric)
273
        return;
274
275
18
    const char *const text = mText.c_str();
276
9
    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
286
8
int TextField::getValue() const
287
{
288
8
    if (!mNumeric)
289
        return 0;
290
291
24
    const int value = atoi(mText.c_str());
292
8
    if (value < mMinimum)
293
        return mMinimum;
294
295
6
    if (value > mMaximum)
296
        return mMaximum;
297
298
    return value;
299
}
300
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
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
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
595
void TextField::moveCaretBack()
596
{
597
    while (mCaretPosition > 0)
598
    {
599
        --mCaretPosition;
600
        if ((mText[mCaretPosition] & 192) != 128)
601
        break;
602
    }
603
}
604
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
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
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
641
void TextField::caretDeleteToStart()
642
{
643
    if (mCaretPosition > 0)
644
    {
645
        mText = mText.substr(mCaretPosition);
646
        mCaretPosition = 0;
647
    }
648
    mTextChanged = true;
649
}
650
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
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
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
701
void TextField::handleCopy() const
702
{
703
    std::string text = getText();
704
    sendBuffer(text);
705
}
706
707
4
void TextField::drawCaret(Graphics* graphics, int x)
708
{
709
4
    const ClipRect &clipArea = graphics->getTopClip();
710
711
4
    graphics->setColor(*mCaretColor);
712
4
    graphics->drawLine(x + mPadding, clipArea.height - mPadding,
713
8
        x + mPadding, mPadding);
714
4
}
715
716
94
void TextField::adjustSize()
717
{
718
94
    setWidth(getFont()->getWidth(mText) + 2 * mPadding + 1);
719
94
    adjustHeight();
720
721
94
    fixScroll();
722
94
}
723
724
94
void TextField::adjustHeight()
725
{
726
94
    setHeight(getFont()->getHeight() + 2 * mPadding);
727
94
}
728
729
100
void TextField::fixScroll()
730
{
731
100
    if (isFocused())
732
    {
733
1
        const int caretX = getFont()->getWidth(
734
3
            mText.substr(0, mCaretPosition));
735
736
1
        const int width = mDimension.width;
737
1
        const int pad = 2 * mPadding;
738
1
        if (caretX - mXScroll >= width - pad)
739
        {
740
            mXScroll = caretX - width + pad;
741
        }
742
1
        else if (caretX - mXScroll <= 0)
743
        {
744
1
            mXScroll = caretX - width / 2;
745
746
1
            if (mXScroll < 0)
747
1
                mXScroll = 0;
748
        }
749
    }
750
100
}
751
752
6
void TextField::setCaretPosition(unsigned int position)
753
{
754
6
    const unsigned int sz = CAST_U32(mText.size());
755

6
    if (position > sz)
756
5
        mCaretPosition = CAST_S32(sz);
757
    else
758
1
        mCaretPosition = position;
759
760
6
    fixScroll();
761
6
}
762
763
void TextField::fontChanged()
764
{
765
    fixScroll();
766
}
767
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
791
4
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
4
}
798
799
void TextField::focusLost(const Event &event A_UNUSED)
800
{
801
}
802
803
15
void TextField::setText(const std::string& text)
804
{
805
15
    const unsigned int sz = CAST_U32(text.size());
806

15
    if (sz < mCaretPosition)
807
        mCaretPosition = sz;
808
30
    mText = text;
809
15
    mTextChanged = true;
810
15
}
811
812
void TextField::mouseDragged(MouseEvent& event)
813
{
814
    event.consume();
815
}
816
817
1
void TextField::widgetHidden(const Event &event A_UNUSED)
818
{
819
1
    mTextChanged = true;
820
1
    mTextChunk.deleteImage();
821
1
}
822
823
91
void TextField::setParent(Widget *widget)
824
{
825
91
    if (mWindow != nullptr)
826
91
        mWindow->addWidgetListener(this);
827
182
    Widget::setParent(widget);
828
91
}
829
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
843
void TextField::signalEvent()
844
{
845
    distributeActionEvent();
846
2
}