GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/textbox.cpp Lines: 80 286 28.0 %
Date: 2021-03-17 Branches: 32 209 15.3 %

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/textbox.h"
68
69
#include "gui/gui.h"
70
71
#include "gui/fonts/font.h"
72
73
#include "render/graphics.h"
74
75
#include <sstream>
76
77
#include "debug.h"
78
79
22
TextBox::TextBox(const Widget2 *const widget) :
80
    Widget(widget),
81
    MouseListener(),
82
    KeyListener(),
83
    mTextRows(),
84
    mCaretColumn(0),
85
    mCaretRow(0),
86
44
    mMinWidth(getWidth()),
87
    mEditable(true),
88
110
    mOpaque(Opaque_true)
89
{
90
22
    mAllowLogic = false;
91

88
    setText("");
92
22
    setFocusable(true);
93
94
22
    addMouseListener(this);
95
22
    addKeyListener(this);
96
22
    adjustSize();
97
98
44
    mForegroundColor = getThemeColor(ThemeColorId::TEXTBOX, 255U);
99
44
    setOpaque(Opaque_false);
100
44
    setFrameSize(0);
101
22
}
102
103
110
TextBox::~TextBox()
104
{
105
22
    if (gui != nullptr)
106
22
        gui->removeDragged(this);
107
44
}
108
109
2
void TextBox::setTextWrapped(const std::string &text, const int minDimension)
110
{
111
    // Make sure parent scroll area sets width of this widget
112
4
    if (getParent() != nullptr)
113
        getParent()->logic();
114
115
    // Take the supplied minimum dimension as a starting
116
    // point and try to beat it
117
2
    mMinWidth = minDimension;
118
119
2
    const size_t textSize = text.size();
120
2
    size_t spacePos = text.rfind(' ', textSize);
121
122
2
    if (spacePos != std::string::npos)
123
    {
124
        const std::string word = text.substr(spacePos + 1);
125
        const int length = getFont()->getWidth(word);
126
127
        if (length > mMinWidth)
128
            mMinWidth = length;
129
    }
130
131
4
    std::stringstream wrappedStream;
132
    size_t newlinePos;
133
    size_t lastNewlinePos = 0;
134
    int minWidth = 0;
135
    int xpos;
136
137
    do
138
    {
139
        // Determine next piece of string to wrap
140
2
        newlinePos = text.find('\n', lastNewlinePos);
141
142
2
        if (newlinePos == std::string::npos)
143
2
            newlinePos = textSize;
144
145
        std::string line =
146
4
            text.substr(lastNewlinePos, newlinePos - lastNewlinePos);
147
2
        size_t lastSpacePos = 0;
148
2
        xpos = 0;
149
2
        const Font *const font = getFont();
150

8
        const int spaceWidth = font->getWidth(" ");
151
2
        size_t sz = line.size();
152
153
        do
154
        {
155
2
            spacePos = line.find(' ', lastSpacePos);
156
157
2
            if (spacePos == std::string::npos)
158
2
                spacePos = sz;
159
160
            const std::string word =
161
4
                line.substr(lastSpacePos, spacePos - lastSpacePos);
162
163
2
            const int width = font->getWidth(word);
164
165

2
            if (xpos == 0 && width > mMinWidth)
166
            {
167
                mMinWidth = width;
168
                xpos = width;
169
                wrappedStream << word;
170
            }
171

2
            else if (xpos != 0 && xpos + spaceWidth + width <=
172
                     mMinWidth)
173
            {
174
                xpos += spaceWidth + width;
175
                wrappedStream << " " << word;
176
            }
177
2
            else if (lastSpacePos == 0)
178
            {
179
2
                xpos += width;
180
                wrappedStream << word;
181
            }
182
            else
183
            {
184
                if (xpos > minWidth)
185
                    minWidth = xpos;
186
187
                // The window wasn't big enough. Resize it and try again.
188
                if (minWidth > mMinWidth)
189
                {
190
                    mMinWidth = minWidth;
191
                    wrappedStream.clear();
192
                    wrappedStream.str("");
193
                    lastNewlinePos = 0;
194
                    newlinePos = text.find('\n', lastNewlinePos);
195
                    if (newlinePos == std::string::npos)
196
                        newlinePos = textSize;
197
                    line = text.substr(lastNewlinePos, newlinePos -
198
                                       lastNewlinePos);
199
                    sz = line.size();
200
                    break;
201
                }
202
                else
203
                {
204
                    wrappedStream << "\n" << word;
205
                }
206
                xpos = width;
207
            }
208
2
            lastSpacePos = spacePos + 1;
209
        }
210
2
        while (spacePos != sz);
211
212
2
        if (text.find('\n', lastNewlinePos) != std::string::npos)
213
            wrappedStream << "\n";
214
215
2
        lastNewlinePos = newlinePos + 1;
216
    }
217
2
    while (newlinePos != textSize);
218
219
2
    if (xpos > minWidth)
220
        minWidth = xpos;
221
222
2
    mMinWidth = minWidth;
223
224
4
    setText(wrappedStream.str());
225
2
}
226
227
24
void TextBox::setText(const std::string& text)
228
{
229
24
    mCaretColumn = 0;
230
24
    mCaretRow = 0;
231
232
24
    mTextRows.clear();
233
24
    if (text.empty())
234
    {
235
24
        adjustSize();
236
24
        return;
237
    }
238
239
    size_t pos;
240
    size_t lastPos = 0;
241
    int length;
242
    do
243
    {
244
        pos = text.find('\n', lastPos);
245
246
        if (pos != std::string::npos)
247
            length = CAST_S32(pos - lastPos);
248
        else
249
            length = CAST_S32(text.size() - lastPos);
250
        std::string sub = text.substr(lastPos, length);
251
        mTextRows.push_back(sub);
252
        lastPos = pos + 1;
253
    } while (pos != std::string::npos);
254
255
    adjustSize();
256
}
257
258
void TextBox::keyPressed(KeyEvent& event)
259
{
260
    const Key &key = event.getKey();
261
    const InputActionT action = event.getActionId();
262
263
    PRAGMA45(GCC diagnostic push)
264
    PRAGMA45(GCC diagnostic ignored "-Wswitch-enum")
265
    switch (action)
266
    {
267
        case InputAction::GUI_LEFT:
268
        {
269
            --mCaretColumn;
270
            if (mCaretColumn < 0)
271
            {
272
                --mCaretRow;
273
274
                if (mCaretRow < 0)
275
                {
276
                    mCaretRow = 0;
277
                    mCaretColumn = 0;
278
                }
279
                else
280
                {
281
                    mCaretColumn = CAST_S32(
282
                        mTextRows[mCaretRow].size());
283
                }
284
            }
285
            break;
286
        }
287
288
        case InputAction::GUI_RIGHT:
289
        {
290
            ++mCaretColumn;
291
            if (mCaretColumn > CAST_S32(mTextRows[mCaretRow].size()))
292
            {
293
                ++ mCaretRow;
294
295
                const int sz = CAST_S32(mTextRows.size());
296
                if (mCaretRow >= sz)
297
                {
298
                    mCaretRow = sz - 1;
299
                    if (mCaretRow < 0)
300
                        mCaretRow = 0;
301
302
                    mCaretColumn = CAST_S32(
303
                        mTextRows[mCaretRow].size());
304
                }
305
                else
306
                {
307
                    mCaretColumn = 0;
308
                }
309
            }
310
            break;
311
        }
312
313
        case InputAction::GUI_DOWN:
314
        {
315
            setCaretRow(mCaretRow + 1);
316
            break;
317
        }
318
        case InputAction::GUI_UP:
319
        {
320
            setCaretRow(mCaretRow - 1);
321
            break;
322
        }
323
        case InputAction::GUI_HOME:
324
        {
325
            mCaretColumn = 0;
326
            break;
327
        }
328
        case InputAction::GUI_END:
329
        {
330
            mCaretColumn = CAST_S32(mTextRows[mCaretRow].size());
331
            break;
332
        }
333
334
        case InputAction::GUI_SELECT2:
335
        {
336
            if (mEditable)
337
            {
338
                mTextRows.insert(mTextRows.begin() + mCaretRow + 1,
339
                    mTextRows[mCaretRow].substr(mCaretColumn,
340
                    mTextRows[mCaretRow].size() - mCaretColumn));
341
                mTextRows[mCaretRow].resize(mCaretColumn);
342
                ++mCaretRow;
343
                mCaretColumn = 0;
344
            }
345
            break;
346
        }
347
348
        case InputAction::GUI_BACKSPACE:
349
        {
350
            if (mCaretColumn != 0 && mEditable)
351
            {
352
                mTextRows[mCaretRow].erase(mCaretColumn - 1, 1);
353
                --mCaretColumn;
354
            }
355
            else if (mCaretColumn == 0 && mCaretRow != 0 && mEditable)
356
            {
357
                mCaretColumn = CAST_S32(
358
                    mTextRows[mCaretRow - 1].size());
359
                mTextRows[mCaretRow - 1] += mTextRows[mCaretRow];
360
                mTextRows.erase(mTextRows.begin() + mCaretRow);
361
                --mCaretRow;
362
            }
363
            break;
364
        }
365
366
        case InputAction::GUI_DELETE:
367
        {
368
            if (mCaretColumn < CAST_S32(
369
                mTextRows[mCaretRow].size()) && mEditable)
370
            {
371
                mTextRows[mCaretRow].erase(mCaretColumn, 1);
372
            }
373
            else if (mCaretColumn == CAST_S32(
374
                     mTextRows[mCaretRow].size()) &&
375
                     mCaretRow < (CAST_S32(mTextRows.size()) - 1) &&
376
                     mEditable)
377
            {
378
                mTextRows[mCaretRow] += mTextRows[mCaretRow + 1];
379
                mTextRows.erase(mTextRows.begin() + mCaretRow + 1);
380
            }
381
            break;
382
        }
383
384
        case InputAction::GUI_PAGE_UP:
385
        {
386
            Widget *const par = getParent();
387
388
            if (par != nullptr)
389
            {
390
                const int rowsPerPage = par->getChildrenArea().height
391
                    / getFont()->getHeight();
392
                mCaretRow -= rowsPerPage;
393
394
                if (mCaretRow < 0)
395
                    mCaretRow = 0;
396
            }
397
            break;
398
        }
399
400
        case InputAction::GUI_PAGE_DOWN:
401
        {
402
            Widget *const par = getParent();
403
404
            if (par != nullptr)
405
            {
406
                const int rowsPerPage = par->getChildrenArea().height
407
                    / getFont()->getHeight();
408
                mCaretRow += rowsPerPage;
409
410
                const int sz = CAST_S32(mTextRows.size());
411
                if (mCaretRow >= sz)
412
                    mCaretRow = sz - 1;
413
            }
414
            break;
415
        }
416
417
        case InputAction::GUI_TAB:
418
        {
419
            if (mEditable)
420
            {
421
                mTextRows[mCaretRow].insert(mCaretColumn, std::string("    "));
422
                mCaretColumn += 4;
423
            }
424
            break;
425
        }
426
427
        default:
428
        {
429
            if (key.isCharacter() && mEditable)
430
            {
431
                mTextRows[mCaretRow].insert(mCaretColumn,
432
                    std::string(1, CAST_S8(key.getValue())));
433
                ++ mCaretColumn;
434
            }
435
            break;
436
        }
437
    }
438
    PRAGMA45(GCC diagnostic pop)
439
440
    adjustSize();
441
    scrollToCaret();
442
443
    event.consume();
444
}
445
446
2
void TextBox::draw(Graphics *const graphics)
447
{
448
    BLOCK_START("TextBox::draw")
449
2
    if (mOpaque == Opaque_true)
450
    {
451
        graphics->setColor(mBackgroundColor);
452
        graphics->fillRectangle(Rect(0, 0, getWidth(), getHeight()));
453
    }
454
455
2
    Font *const font = getFont();
456

2
    if (isFocused() && isEditable())
457
    {
458
        drawCaret(graphics, font->getWidth(
459
            mTextRows[mCaretRow].substr(0, mCaretColumn)),
460
            mCaretRow * font->getHeight());
461
    }
462
463
2
    const int fontHeight = font->getHeight();
464
465
4
    for (size_t i = 0, sz = mTextRows.size(); i < sz; i++)
466
    {
467
        // Move the text one pixel so we can have a caret before a letter.
468
        font->drawString(graphics,
469
            mForegroundColor,
470
            mForegroundColor2,
471
            mTextRows[i], 1,
472
            CAST_S32(i * CAST_SIZE(fontHeight)));
473
    }
474
    BLOCK_END("TextBox::draw")
475
2
}
476
477
void TextBox::safeDraw(Graphics *const graphics)
478
{
479
    TextBox::draw(graphics);
480
}
481
482
void TextBox::setForegroundColor(const Color &color)
483
{
484
    mForegroundColor = color;
485
    mForegroundColor2 = color;
486
}
487
488
20
void TextBox::setForegroundColorAll(const Color &color1,
489
                                    const Color &color2)
490
{
491
20
    mForegroundColor = color1;
492
20
    mForegroundColor2 = color2;
493
20
}
494
495
std::string TextBox::getText() const
496
{
497
    if (mTextRows.empty())
498
        return std::string();
499
500
    int i;
501
    std::string text;
502
503
    const int sz = CAST_S32(mTextRows.size());
504
    for (i = 0; i < sz - 1; ++ i)
505
        text.append(mTextRows[i]).append("\n");
506
    text.append(mTextRows[i]);
507
508
    return text;
509
}
510
511
void TextBox::setTextRow(const int row, const std::string& text)
512
{
513
    mTextRows[row] = text;
514
515
    if (mCaretRow == row)
516
        setCaretColumn(mCaretColumn);
517
518
    adjustSize();
519
}
520
521
void TextBox::setCaretPosition(unsigned int position)
522
{
523
    for (int row = 0, fsz = CAST_S32(mTextRows.size());
524
         row < fsz;
525
         row ++)
526
    {
527
        if (position <= mTextRows[row].size())
528
        {
529
            mCaretRow = row;
530
            mCaretColumn = position;
531
            return;  // we are done
532
        }
533
534
        position--;
535
    }
536
537
    // position beyond end of text
538
    mCaretRow = CAST_S32(mTextRows.size() - 1);
539
    mCaretColumn = CAST_S32(mTextRows[mCaretRow].size());
540
}
541
542
void TextBox::setCaretRow(const int row)
543
{
544
    mCaretRow = row;
545
546
    const int sz = CAST_S32(mTextRows.size());
547
    if (mCaretRow >= sz)
548
        mCaretRow = sz - 1;
549
550
    if (mCaretRow < 0)
551
        mCaretRow = 0;
552
553
    setCaretColumn(mCaretColumn);
554
}
555
556
unsigned int TextBox::getCaretPosition() const
557
{
558
    int pos = 0;
559
560
    for (int row = 0; row < mCaretRow; row++)
561
        pos += CAST_S32(mTextRows[row].size());
562
563
    return pos + mCaretColumn;
564
}
565
566
void TextBox::setCaretColumn(const int column)
567
{
568
    mCaretColumn = column;
569
570
    const int sz = CAST_S32(mTextRows[mCaretRow].size());
571
    if (mCaretColumn > sz)
572
        mCaretColumn = sz;
573
574
    if (mCaretColumn < 0)
575
        mCaretColumn = 0;
576
}
577
578
void TextBox::setCaretRowColumn(const int row, const int column)
579
{
580
    setCaretRow(row);
581
    setCaretColumn(column);
582
}
583
584
void TextBox::scrollToCaret()
585
{
586
    const Font *const font = getFont();
587
    Rect scroll;
588
    scroll.x = font->getWidth(mTextRows[mCaretRow].substr(0, mCaretColumn));
589
    scroll.y = font->getHeight() * mCaretRow;
590
    scroll.width = font->getWidth(" ");
591
    // add 2 for some extra space
592
    scroll.height = font->getHeight() + 2;
593
    showPart(scroll);
594
}
595
596
void TextBox::addRow(const std::string &row)
597
{
598
    mTextRows.push_back(row);
599
    adjustSize();
600
}
601
602
void TextBox::mousePressed(MouseEvent& event)
603
{
604
    if (event.getButton() == MouseButton::LEFT)
605
    {
606
        const int height = getFont()->getHeight();
607
        if (height == 0)
608
            return;
609
610
        event.consume();
611
        mCaretRow = event.getY() / height;
612
613
        const int sz = CAST_S32(mTextRows.size());
614
        if (mCaretRow >= sz)
615
            mCaretRow = sz - 1;
616
617
        mCaretColumn = getFont()->getStringIndexAt(
618
            mTextRows[mCaretRow], event.getX());
619
    }
620
}
621
622
void TextBox::mouseDragged(MouseEvent& event)
623
{
624
    event.consume();
625
}
626
627
void TextBox::drawCaret(Graphics *const graphics,
628
                        const int x,
629
                        const int y) const
630
{
631
    graphics->setColor(mForegroundColor);
632
    graphics->drawLine(x, getFont()->getHeight() + y, x, y);
633
}
634
635
46
void TextBox::adjustSize()
636
{
637
46
    int width = 0;
638
46
    const Font *const font = getFont();
639
92
    for (size_t i = 0, sz = mTextRows.size(); i < sz; ++i)
640
    {
641
        const int w = font->getWidth(mTextRows[i]);
642
        if (width < w)
643
            width = w;
644
    }
645
646
46
    setWidth(width + 1);
647
92
    setHeight(font->getHeight() * CAST_S32(mTextRows.size()));
648
46
}
649
650
22
void TextBox::setEditable(const bool editable)
651
{
652
22
    mEditable = editable;
653
22
    mSelectable = editable;
654
22
}