GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/browserbox.cpp Lines: 223 466 47.9 %
Date: 2021-03-17 Branches: 129 431 29.9 %

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
 *  Copyright (C) 2009  Aethyra Development Team
8
 *
9
 *  This file is part of The ManaPlus Client.
10
 *
11
 *  This program is free software; you can redistribute it and/or modify
12
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 2 of the License, or
14
 *  any later version.
15
 *
16
 *  This program is distributed in the hope that it will be useful,
17
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 *  GNU General Public License for more details.
20
 *
21
 *  You should have received a copy of the GNU General Public License
22
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
#include "gui/widgets/browserbox.h"
26
27
#include "enums/gui/linkhighlightmode.h"
28
29
#include "gui/gui.h"
30
#include "gui/mouseoverlink.h"
31
#include "gui/skin.h"
32
33
#include "gui/fonts/font.h"
34
35
#include "gui/widgets/browserbox.inc"
36
#include "gui/widgets/linkhandler.h"
37
38
#include "render/graphics.h"
39
40
#include "resources/imageset.h"
41
42
#include "resources/image/image.h"
43
44
#include "resources/loaders/imageloader.h"
45
#include "resources/loaders/imagesetloader.h"
46
47
#include "utils/browserboxtools.h"
48
#include "utils/checkutils.h"
49
#include "utils/foreach.h"
50
#include "utils/stringutils.h"
51
#include "utils/timer.h"
52
#include "utils/translation/podict.h"
53
54
#include <algorithm>
55
56
#include "debug.h"
57
58
ImageSet *BrowserBox::mEmotes = nullptr;
59
int BrowserBox::mInstances = 0;
60
61
6
BrowserBox::BrowserBox(const Widget2 *const widget,
62
                       const Opaque opaque,
63
6
                       const std::string &skin) :
64
    Widget(widget),
65
    MouseListener(),
66
    WidgetListener(),
67
    mTextRows(),
68
    mTextRowLinksCount(),
69
    mLineParts(),
70
    mLinks(),
71
    mLinkHandler(nullptr),
72
    mSkin(nullptr),
73
    mHighlightMode(0),
74
    mSelectedLink(-1),
75
    mMaxRows(0),
76
    mHeight(0),
77
    mWidth(0),
78
    mYStart(0),
79
    mUpdateTime(-1),
80
    mPadding(0),
81
    mNewLinePadding(15U),
82
    mItemPadding(0),
83
    mDataWidth(0),
84
12
    mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT, 255U)),
85
12
    mHyperLinkColor(getThemeColor(ThemeColorId::HYPERLINK, 255U)),
86
    mOpaque(opaque),
87
    mUseLinksAndUserColors(true),
88
    mUseEmotes(true),
89
    mAlwaysUpdate(true),
90
    mProcessVars(false),
91
    mEnableImages(false),
92
    mEnableKeys(false),
93
54
    mEnableTabs(false)
94
{
95
6
    mAllowLogic = false;
96
97
6
    setFocusable(true);
98
6
    addMouseListener(this);
99
6
    addWidgetListener(this);
100
101
12
    mBackgroundColor = getThemeColor(ThemeColorId::BACKGROUND, 255U);
102
103
6
    if (theme != nullptr)
104
    {
105

24
        mSkin = theme->load(skin,
106
            "browserbox.xml",
107
            true,
108
6
            Theme::getThemePath());
109
    }
110
6
    if (mInstances == 0)
111
    {
112

20
        mEmotes = Loader::getImageSet(
113
            "graphics/sprites/chatemotes.png", 17, 18);
114
    }
115
6
    mInstances ++;
116
117
6
    if (mSkin != nullptr)
118
    {
119
12
        mPadding = mSkin->getPadding();
120
6
        mNewLinePadding = CAST_U32(
121

24
            mSkin->getOption("newLinePadding", 15));
122

24
        mItemPadding = mSkin->getOption("itemPadding");
123

24
        if (mSkin->getOption("highlightBackground") != 0)
124
6
            mHighlightMode |= LinkHighlightMode::BACKGROUND;
125

24
        if (mSkin->getOption("highlightUnderline") != 0)
126
6
            mHighlightMode |= LinkHighlightMode::UNDERLINE;
127
    }
128
129
18
    readColor(BLACK);
130
18
    readColor(RED);
131
18
    readColor(GREEN);
132
18
    readColor(BLUE);
133
18
    readColor(ORANGE);
134
18
    readColor(YELLOW);
135
18
    readColor(PINK);
136
18
    readColor(PURPLE);
137
18
    readColor(GRAY);
138
18
    readColor(BROWN);
139
140
12
    mForegroundColor = getThemeColor(ThemeColorId::BROWSERBOX, 255U);
141
12
    mForegroundColor2 = getThemeColor(ThemeColorId::BROWSERBOX_OUTLINE, 255U);
142
6
}
143
144
42
BrowserBox::~BrowserBox()
145
{
146
6
    if (gui != nullptr)
147
6
        gui->removeDragged(this);
148
149
6
    if (theme != nullptr)
150
    {
151
6
        theme->unload(mSkin);
152
6
        mSkin = nullptr;
153
    }
154
155
6
    mInstances --;
156
6
    if (mInstances == 0)
157
    {
158
5
        if (mEmotes != nullptr)
159
        {
160
5
            mEmotes->decRef();
161
5
            mEmotes = nullptr;
162
        }
163
    }
164
12
}
165
166
5
void BrowserBox::setLinkHandler(LinkHandler* linkHandler)
167
{
168
5
    mLinkHandler = linkHandler;
169
5
}
170
171
46
void BrowserBox::addRow(const std::string &row, const bool atTop)
172
{
173
92
    std::string tmp = row;
174
92
    std::string newRow;
175
46
    const Font *const font = getFont();
176
46
    int linksCount = 0;
177
178
92
    if (getWidth() < 0)
179
        return;
180
181
46
    if (mProcessVars)
182
    {
183
        BrowserBoxTools::replaceVars(tmp);
184
    }
185
186
    // Use links and user defined colors
187
46
    if (mUseLinksAndUserColors)
188
    {
189
92
        BrowserLink bLink;
190
191
        // Check for links in format "@@link|Caption@@"
192
92
        const uint32_t sz = CAST_U32(mTextRows.size());
193
194
46
        if (mEnableKeys)
195
        {
196
            BrowserBoxTools::replaceKeys(tmp);
197
        }
198
199
        size_t idx1 = tmp.find("@@");
200
86
        while (idx1 != std::string::npos)
201
        {
202
30
            const size_t idx2 = tmp.find('|', idx1);
203
30
            const size_t idx3 = tmp.find("@@", idx2);
204
205
30
            if (idx2 == std::string::npos || idx3 == std::string::npos)
206
                break;
207
40
            bLink.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2));
208
40
            bLink.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1));
209
20
            bLink.y1 = CAST_S32(sz) * font->getHeight();
210
20
            bLink.y2 = bLink.y1 + font->getHeight();
211
20
            if (bLink.caption.empty())
212
            {
213
2
                bLink.caption = BrowserBoxTools::replaceLinkCommands(
214
1
                    bLink.link);
215
1
                if (translator != nullptr)
216
                    bLink.caption = translator->getStr(bLink.caption);
217
            }
218
219
60
            newRow.append(tmp.substr(0, idx1));
220
221
40
            std::string tmp2 = newRow;
222
            idx1 = tmp2.find("##");
223
23
            while (idx1 != std::string::npos)
224
            {
225
3
                tmp2.erase(idx1, 3);
226
                idx1 = tmp2.find("##");
227
            }
228
20
            bLink.x1 = font->getWidth(tmp2) - 1;
229
20
            bLink.x2 = bLink.x1 + font->getWidth(bLink.caption) + 1;
230
231
20
            if (atTop)
232
                mLinks.insert(mLinks.begin(), bLink);
233
            else
234
20
                mLinks.push_back(bLink);
235
20
            linksCount ++;
236
237
40
            newRow.append("##<").append(bLink.caption);
238
239
20
            tmp.erase(0, idx3 + 2);
240
20
            if (!tmp.empty())
241
3
                newRow.append("##>");
242
243
20
            idx1 = tmp.find("@@");
244
        }
245
246
46
        newRow.append(tmp);
247
    }
248
    // Don't use links and user defined colors
249
    else
250
    {
251
        newRow = row;
252
    }
253
254
46
    if (mEnableTabs)
255
    {
256
        BrowserBoxTools::replaceTabs(newRow);
257
    }
258
259
46
    if (atTop)
260
    {
261
        mTextRows.push_front(newRow);
262
        mTextRowLinksCount.push_front(linksCount);
263
    }
264
    else
265
    {
266
46
        mTextRows.push_back(newRow);
267
46
        mTextRowLinksCount.push_back(linksCount);
268
    }
269
270
    // discard older rows when a row limit has been set
271

46
    if (mMaxRows > 0 && !mTextRows.empty())
272
    {
273
        while (mTextRows.size() > CAST_SIZE(mMaxRows))
274
        {
275
            mTextRows.pop_front();
276
            int cnt = mTextRowLinksCount.front();
277
            mTextRowLinksCount.pop_front();
278
279
            while ((cnt != 0) && !mLinks.empty())
280
            {
281
                mLinks.erase(mLinks.begin());
282
                cnt --;
283
            }
284
        }
285
    }
286
287
46
    const int fontHeight = font->getHeight();
288
46
    unsigned int y = 0;
289
    unsigned int nextChar;
290
46
    const char *const hyphen = "~";
291
    const unsigned int hyphenWidth = CAST_U32(
292

184
        font->getWidth(hyphen));
293
46
    unsigned int x = 0;
294
295
230
    FOR_EACH (TextRowCIter, i, mTextRows)
296
    {
297
3108
        std::string tempRow = *i;
298
8280
        for (uint32_t j = 0, sz = CAST_U32(tempRow.size());
299
8280
             j < sz;
300
             j++)
301
        {
302
14488
            const std::string character = tempRow.substr(j, 1);
303
7244
            x += CAST_U32(font->getWidth(character));
304
7244
            nextChar = j + 1;
305
306
            // Wraping between words (at blank spaces)
307

13452
            if (nextChar < sz && tempRow.at(nextChar) == ' ')
308
            {
309
                int nextSpacePos = CAST_U32(
310
                    tempRow.find(' ', (nextChar + 1)));
311
                if (nextSpacePos <= 0)
312
                    nextSpacePos = CAST_U32(sz) - 1U;
313
314
                const unsigned int nextWordWidth =
315
                    CAST_U32(font->getWidth(
316
                    tempRow.substr(nextChar,
317
                    (CAST_U32(nextSpacePos) - nextChar))));
318
319
                if ((x + nextWordWidth + 10)
320
                    > CAST_U32(getWidth()))
321
                {
322
                    x = mNewLinePadding;  // Ident in new line
323
                    y += 1;
324
                    j ++;
325
                }
326
            }
327
            // Wrapping looong lines (brutal force)
328
14488
            else if ((x + 2 * hyphenWidth)
329
14488
                     > CAST_U32(getWidth()))
330
            {
331
7240
                x = mNewLinePadding;  // Ident in new line
332
7240
                y += 1;
333
            }
334
        }
335
    }
336
337
46
    setHeight(fontHeight * (CAST_S32(
338
138
        CAST_U32(mTextRows.size()) + y)));
339
46
    mUpdateTime = 0;
340
46
    updateHeight();
341
}
342
343
void BrowserBox::addRow(const std::string &cmd, const char *const text)
344
{
345
    addRow(strprintf("@@%s|%s@@", encodeLinkText(cmd).c_str(),
346
        encodeLinkText(text).c_str()),
347
        false);
348
}
349
350
void BrowserBox::addImage(const std::string &path)
351
{
352
    if (!mEnableImages)
353
        return;
354
355
    mTextRows.push_back("~~~" + path);
356
    mTextRowLinksCount.push_back(0);
357
}
358
359
1
void BrowserBox::clearRows()
360
{
361
2
    mTextRows.clear();
362
2
    mTextRowLinksCount.clear();
363
2
    mLinks.clear();
364
1
    setWidth(0);
365
1
    setHeight(0);
366
1
    mSelectedLink = -1;
367
1
    mUpdateTime = 0;
368
1
    mDataWidth = 0;
369
1
    updateHeight();
370
1
}
371
372
void BrowserBox::mousePressed(MouseEvent &event)
373
{
374
    if (mLinkHandler == nullptr)
375
        return;
376
377
    const LinkIterator i = std::find_if(mLinks.begin(), mLinks.end(),
378
        MouseOverLink(event.getX(), event.getY()));
379
380
    if (i != mLinks.end())
381
    {
382
        mLinkHandler->handleLink(i->link, &event);
383
        event.consume();
384
    }
385
}
386
387
void BrowserBox::mouseMoved(MouseEvent &event)
388
{
389
    const LinkIterator i = std::find_if(mLinks.begin(), mLinks.end(),
390
        MouseOverLink(event.getX(), event.getY()));
391
392
    mSelectedLink = (i != mLinks.end())
393
        ? CAST_S32(i - mLinks.begin()) : -1;
394
}
395
396
void BrowserBox::mouseExited(MouseEvent &event A_UNUSED)
397
{
398
    mSelectedLink = -1;
399
}
400
401
1
void BrowserBox::draw(Graphics *const graphics)
402
{
403
    BLOCK_START("BrowserBox::draw")
404
1
    const ClipRect &cr = graphics->getTopClip();
405
1
    mYStart = cr.y - cr.yOffset;
406
1
    const int yEnd = mYStart + cr.height;
407
1
    if (mYStart < 0)
408
        mYStart = 0;
409
410
1
    if (mDimension.width != mWidth)
411
    {
412
        mWidth = mDimension.width;
413
        mHeight = calcHeight();
414
        setHeight(mHeight);
415
        mUpdateTime = cur_time;
416
        if (mDimension.width != mWidth)
417
            reportAlways("browserbox resize in draw")
418
    }
419
420
1
    if (mOpaque == Opaque_true)
421
    {
422
        graphics->setColor(mBackgroundColor);
423
        graphics->fillRectangle(Rect(0, 0,
424
            mDimension.width, mDimension.height));
425
    }
426
427

1
    if (mSelectedLink >= 0 &&
428
        mSelectedLink < CAST_S32(mLinks.size()))
429
    {
430
        if ((mHighlightMode & LinkHighlightMode::BACKGROUND) != 0U)
431
        {
432
            BrowserLink &link = mLinks[CAST_SIZE(mSelectedLink)];
433
            graphics->setColor(mHighlightColor);
434
            graphics->fillRectangle(Rect(
435
                link.x1,
436
                link.y1,
437
                link.x2 - link.x1,
438
                link.y2 - link.y1));
439
        }
440
441
        if ((mHighlightMode & LinkHighlightMode::UNDERLINE) != 0U)
442
        {
443
            BrowserLink &link = mLinks[CAST_SIZE(mSelectedLink)];
444
            graphics->setColor(mHyperLinkColor);
445
            graphics->drawLine(
446
                link.x1,
447
                link.y2,
448
                link.x2,
449
                link.y2);
450
        }
451
    }
452
453
1
    Font *const font = getFont();
454
455
6
    FOR_EACH (LinePartCIter, i, mLineParts)
456
    {
457
        const LinePart &part = *i;
458
        if (part.mY + 50 < mYStart)
459
            continue;
460
        if (part.mY > yEnd)
461
            break;
462
        if (part.mType == 0U)
463
        {
464
            if (part.mBold)
465
            {
466
                boldFont->drawString(graphics,
467
                    part.mColor,
468
                    part.mColor2,
469
                    part.mText,
470
                    part.mX, part.mY);
471
            }
472
            else
473
            {
474
                font->drawString(graphics,
475
                    part.mColor,
476
                    part.mColor2,
477
                    part.mText,
478
                    part.mX, part.mY);
479
            }
480
        }
481
        else if (part.mImage != nullptr)
482
        {
483
            graphics->drawImage(part.mImage, part.mX, part.mY);
484
        }
485
    }
486
487
    BLOCK_END("BrowserBox::draw")
488
1
}
489
490
void BrowserBox::safeDraw(Graphics *const graphics)
491
{
492
    BrowserBox::draw(graphics);
493
}
494
495
148
int BrowserBox::calcHeight()
496
{
497
148
    unsigned int y = CAST_U32(mPadding);
498
148
    int wrappedLines = 0;
499
148
    int moreHeight = 0;
500
148
    int maxWidth = mDimension.width - mPadding;
501
148
    int link = 0;
502
148
    bool bold = false;
503
148
    unsigned int wWidth = CAST_U32(maxWidth);
504
505
148
    if (maxWidth < 0)
506
        return 1;
507
508
8
    const Font *const font = getFont();
509
8
    const int fontHeight = font->getHeight() + 2 * mItemPadding;
510
32
    const int fontWidthMinus = font->getWidth("-");
511
8
    const char *const hyphen = "~";
512
32
    const int hyphenWidth = font->getWidth(hyphen);
513
514
8
    Color selColor[2] = {mForegroundColor, mForegroundColor2};
515
8
    const Color textColor[2] = {mForegroundColor, mForegroundColor2};
516
16
    mLineParts.clear();
517
518
40
    FOR_EACH (TextRowCIter, i, mTextRows)
519
    {
520
3
        unsigned int x = CAST_U32(mPadding);
521
9
        const std::string row = *(i);
522
3
        bool wrapped = false;
523
3
        int objects = 0;
524
525
        // Check for separator lines
526
3
        if (row.find("---", 0) == 0)
527
        {
528
            const int dashWidth = fontWidthMinus;
529
            for (x = CAST_U32(mPadding); x < wWidth; x ++)
530
            {
531
                mLineParts.push_back(LinePart(CAST_S32(x),
532
                    CAST_S32(y) + mItemPadding,
533
                    selColor[0], selColor[1], "-", false));
534
                x += CAST_U32(CAST_S32(
535
                    dashWidth) - 2);
536
            }
537
538
            y += CAST_U32(fontHeight);
539
            continue;
540
        }
541

3
        else if (mEnableImages && row.find("~~~", 0) == 0)
542
        {
543
            std::string str = row.substr(3);
544
            const size_t sz = str.size();
545
            if (sz > 2 && str.substr(sz - 1) == "~")
546
                str = str.substr(0, sz - 1);
547
            Image *const img = Loader::getImage(str);
548
            if (img != nullptr)
549
            {
550
                img->incRef();
551
                mLineParts.push_back(LinePart(CAST_S32(x),
552
                    CAST_S32(y) + mItemPadding,
553
                    selColor[0], selColor[1], img));
554
                y += CAST_U32(img->getHeight() + 2);
555
                moreHeight += img->getHeight();
556
                if (img->getWidth() > maxWidth)
557
                    maxWidth = img->getWidth() + 2;
558
            }
559
            continue;
560
        }
561
562
3
        Color prevColor[2];
563
3
        prevColor[0] = selColor[0];
564
3
        prevColor[1] = selColor[1];
565
3
        bold = false;
566
567
3
        const int xPadding = CAST_S32(mNewLinePadding) + mPadding;
568
569
6
        for (size_t start = 0, end = std::string::npos;
570
6
             start != std::string::npos;
571
3
             start = end, end = std::string::npos)
572
        {
573
3
            bool processed(false);
574
575
            // Wrapped line continuation shall be indented
576
3
            if (wrapped)
577
            {
578
                y += CAST_U32(fontHeight);
579
                x = CAST_U32(xPadding);
580
                wrapped = false;
581
            }
582
583
3
            size_t idx1 = end;
584
3
            size_t idx2 = end;
585
586
            // "Tokenize" the string at control sequences
587
3
            if (mUseLinksAndUserColors)
588
3
                idx1 = row.find("##", start + 1);
589

3
            if (start == 0 || mUseLinksAndUserColors)
590
            {
591
                // Check for color change in format "##x", x = [L,P,0..9]
592

3
                if (row.find("##", start) == start && row.size() > start + 2)
593
                {
594
                    const signed char c = row.at(start + 2);
595
596
                    bool valid(false);
597
                    const Color col[2] =
598
                    {
599
                        getThemeCharColor(c, valid),
600
                        getThemeCharColor(CAST_S8(
601
                            c | 0x80), valid)
602
                    };
603
604
                    if (c == '>')
605
                    {
606
                        selColor[0] = prevColor[0];
607
                        selColor[1] = prevColor[1];
608
                    }
609
                    else if (c == '<')
610
                    {
611
                        prevColor[0] = selColor[0];
612
                        prevColor[1] = selColor[1];
613
                        selColor[0] = col[0];
614
                        selColor[1] = col[1];
615
                    }
616
                    else if (c == 'B')
617
                    {
618
                        bold = true;
619
                    }
620
                    else if (c == 'b')
621
                    {
622
                        bold = false;
623
                    }
624
                    else if (valid)
625
                    {
626
                        selColor[0] = col[0];
627
                        selColor[1] = col[1];
628
                    }
629
                    else
630
                    {
631
                        switch (c)
632
                        {
633
                            case '0':
634
                                selColor[0] = mColors[0][ColorName::BLACK];
635
                                selColor[1] = mColors[1][ColorName::BLACK];
636
                                break;
637
                            case '1':
638
                                selColor[0] = mColors[0][ColorName::RED];
639
                                selColor[1] = mColors[1][ColorName::RED];
640
                                break;
641
                            case '2':
642
                                selColor[0] = mColors[0][ColorName::GREEN];
643
                                selColor[1] = mColors[1][ColorName::GREEN];
644
                                break;
645
                            case '3':
646
                                selColor[0] = mColors[0][ColorName::BLUE];
647
                                selColor[1] = mColors[1][ColorName::BLUE];
648
                                break;
649
                            case '4':
650
                                selColor[0] = mColors[0][ColorName::ORANGE];
651
                                selColor[1] = mColors[1][ColorName::ORANGE];
652
                                break;
653
                            case '5':
654
                                selColor[0] = mColors[0][ColorName::YELLOW];
655
                                selColor[1] = mColors[1][ColorName::YELLOW];
656
                                break;
657
                            case '6':
658
                                selColor[0] = mColors[0][ColorName::PINK];
659
                                selColor[1] = mColors[1][ColorName::PINK];
660
                                break;
661
                            case '7':
662
                                selColor[0] = mColors[0][ColorName::PURPLE];
663
                                selColor[1] = mColors[1][ColorName::PURPLE];
664
                                break;
665
                            case '8':
666
                                selColor[0] = mColors[0][ColorName::GRAY];
667
                                selColor[1] = mColors[1][ColorName::GRAY];
668
                                break;
669
                            case '9':
670
                                selColor[0] = mColors[0][ColorName::BROWN];
671
                                selColor[1] = mColors[1][ColorName::BROWN];
672
                                break;
673
                            default:
674
                                selColor[0] = textColor[0];
675
                                selColor[1] = textColor[1];
676
                                break;
677
                        }
678
                    }
679
680
                    if (c == '<' && link < CAST_S32(mLinks.size()))
681
                    {
682
                        int size;
683
                        if (bold)
684
                        {
685
                            size = boldFont->getWidth(
686
                                mLinks[CAST_SIZE(link)].caption) + 1;
687
                        }
688
                        else
689
                        {
690
                            size = font->getWidth(
691
                                mLinks[CAST_SIZE(link)].caption) + 1;
692
                        }
693
694
                        BrowserLink &linkRef = mLinks[CAST_SIZE(
695
                            link)];
696
                        linkRef.x1 = CAST_S32(x);
697
                        linkRef.y1 = CAST_S32(y);
698
                        linkRef.x2 = linkRef.x1 + size;
699
                        linkRef.y2 = CAST_S32(y) + fontHeight - 1;
700
                        link++;
701
                    }
702
703
                    processed = true;
704
                    start += 3;
705
                    if (start == row.size())
706
                        break;
707
                }
708
            }
709
3
            if (mUseEmotes)
710
3
                idx2 = row.find("%%", start + 1);
711
3
            if (idx1 < idx2)
712
                end = idx1;
713
            else
714
3
                end = idx2;
715
3
            if (mUseEmotes)
716
            {
717
                // check for emote icons
718



9
                if (row.size() > start + 2 && row.substr(start, 2) == "%%")
719
                {
720
                    if (objects < 5)
721
                    {
722
                        const int cid = row.at(start + 2) - '0';
723
                        if (cid >= 0)
724
                        {
725
                            if (mEmotes != nullptr)
726
                            {
727
                                const size_t sz = mEmotes->size();
728
                                if (CAST_SIZE(cid) < sz)
729
                                {
730
                                    Image *const img = mEmotes->get(
731
                                        CAST_SIZE(cid));
732
                                    if (img != nullptr)
733
                                    {
734
                                        mLineParts.push_back(LinePart(
735
                                            CAST_S32(x),
736
                                            CAST_S32(y) + mItemPadding,
737
                                            selColor[0], selColor[1], img));
738
                                        x += 18;
739
                                    }
740
                                }
741
                            }
742
                        }
743
                        objects ++;
744
                        processed = true;
745
                    }
746
747
                    start += 3;
748
                    if (start == row.size())
749
                    {
750
                        if (x > mDataWidth)
751
                            mDataWidth = x;
752
                        break;
753
                    }
754
                }
755
            }
756
3
            const size_t len = (end == std::string::npos) ? end : end - start;
757
758
3
            if (start >= row.length())
759
                break;
760
761
6
            std::string part = row.substr(start, len);
762
3
            int width = 0;
763
3
            if (bold)
764
                width = boldFont->getWidth(part);
765
            else
766
3
                width = font->getWidth(part);
767
768
            // Auto wrap mode
769
6
            if (wWidth > 0 &&
770
6
                width > 0 &&
771
3
                (x + CAST_U32(width) + 10) > wWidth)
772
            {
773
                bool forced = false;
774
775
                /* FIXME: This code layout makes it easy to crash remote
776
                   clients by talking garbage. Forged long utf-8 characters
777
                   will cause either a buffer underflow in substr or an
778
                   infinite loop in the main loop. */
779
                do
780
                {
781
                    if (!forced)
782
                        end = row.rfind(' ', end);
783
784
                    // Check if we have to (stupidly) force-wrap
785
                    if (end == std::string::npos || end <= start)
786
                    {
787
                        forced = true;
788
                        end = row.size();
789
                        x += CAST_U32(hyphenWidth);
790
                        continue;
791
                    }
792
793
                    // Skip to the start of the current character
794
                    while ((row[end] & 192) == 128)
795
                        end--;
796
                    end--;  // And then to the last byte of the previous one
797
798
                    part = row.substr(start, end - start + 1);
799
                    if (bold)
800
                        width = boldFont->getWidth(part);
801
                    else
802
                        width = font->getWidth(part);
803
                }
804
                while (end > start &&
805
                       width > 0 &&
806
                       (x + CAST_U32(width) + 10) > wWidth);
807
808
                if (forced)
809
                {
810
                    x -= CAST_U32(hyphenWidth);
811
                    mLineParts.push_back(LinePart(
812
                        CAST_S32(wWidth) - hyphenWidth,
813
                        CAST_S32(y) + mItemPadding,
814
                        selColor[0], selColor[1], hyphen, bold));
815
                    end++;  // Skip to the next character
816
                }
817
                else
818
                {
819
                    end += 2;  // Skip to after the space
820
                }
821
822
                wrapped = true;
823
                wrappedLines++;
824
            }
825
826
24
            mLineParts.push_back(LinePart(CAST_S32(x),
827
3
                CAST_S32(y) + mItemPadding,
828
3
                selColor[0], selColor[1], part.c_str(), bold));
829
830
3
            if (bold)
831
                width = boldFont->getWidth(part);
832
            else
833
3
                width = font->getWidth(part);
834
835
3
            if (width == 0 && !processed)
836
                break;
837
838
3
            x += CAST_U32(width);
839
3
            if (x > mDataWidth)
840
1
                mDataWidth = x;
841
        }
842
3
        y += CAST_U32(fontHeight);
843
    }
844
8
    if (CAST_S32(wWidth) != maxWidth)
845
        setWidth(maxWidth);
846
847
16
    return (CAST_S32(mTextRows.size()) + wrappedLines)
848
8
        * fontHeight + moreHeight + 2 * mPadding;
849
}
850
851
148
void BrowserBox::updateHeight()
852
{
853

299
    if (mAlwaysUpdate || mUpdateTime != cur_time
854

150
        || mTextRows.size() < 3 || (mUpdateTime == 0))
855
    {
856
148
        mWidth = mDimension.width;
857
148
        mHeight = calcHeight();
858
148
        setHeight(mHeight);
859
148
        mUpdateTime = cur_time;
860
    }
861
148
}
862
863
1
void BrowserBox::updateSize(const bool always)
864
{
865
1
    if (always)
866
1
        mUpdateTime = 0;
867
1
    updateHeight();
868
1
}
869
870
std::string BrowserBox::getTextAtPos(const int x, const int y) const
871
{
872
    int textX = 0;
873
    int textY = 0;
874
875
    getAbsolutePosition(textX, textY);
876
    if (x < textX || y < textY)
877
        return std::string();
878
879
    textY = y - textY;
880
    std::string str;
881
    int lastY = 0;
882
883
    FOR_EACH (LinePartCIter, i, mLineParts)
884
    {
885
        const LinePart &part = *i;
886
        if (part.mY + 50 < mYStart)
887
            continue;
888
        if (part.mY > textY)
889
            break;
890
891
        if (part.mY > lastY)
892
        {
893
            str = part.mText;
894
            lastY = part.mY;
895
        }
896
        else
897
        {
898
            str.append(part.mText);
899
        }
900
    }
901
902
    return str;
903
}
904
905
void BrowserBox::setForegroundColorAll(const Color &color1,
906
                                       const Color &color2)
907
{
908
    mForegroundColor = color1;
909
    mForegroundColor2 = color2;
910
}
911
912
void BrowserBox::moveSelectionUp()
913
{
914
    if (mSelectedLink <= 0)
915
        mSelectedLink = CAST_S32(mLinks.size()) - 1;
916
    else
917
        mSelectedLink --;
918
}
919
920
void BrowserBox::moveSelectionDown()
921
{
922
    mSelectedLink ++;
923
    if (mSelectedLink >= static_cast<signed int>(mLinks.size()))
924
        mSelectedLink = 0;
925
}
926
927
void BrowserBox::selectSelection()
928
{
929
    if ((mLinkHandler == nullptr) ||
930
        mSelectedLink < 0 ||
931
        mSelectedLink >= static_cast<signed int>(mLinks.size()))
932
    {
933
        return;
934
    }
935
936
    mLinkHandler->handleLink(mLinks[CAST_SIZE(mSelectedLink)].link,
937
        nullptr);
938
}
939
940
100
void BrowserBox::widgetResized(const Event &event A_UNUSED)
941
{
942
100
    updateHeight();
943
100
}