GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/browserbox.cpp Lines: 223 471 47.3 %
Date: 2017-11-29 Branches: 125 415 30.1 %

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-2017  The ManaPlus Developers
6
 *  Copyright (C) 2009  Aethyra Development Team
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
#include "gui/widgets/browserbox.h"
25
26
#include "enums/gui/linkhighlightmode.h"
27
28
#include "gui/gui.h"
29
#include "gui/mouseoverlink.h"
30
#include "gui/skin.h"
31
32
#include "gui/fonts/font.h"
33
34
#include "gui/widgets/browserbox.inc"
35
#include "gui/widgets/linkhandler.h"
36
37
#include "render/graphics.h"
38
39
#include "resources/imageset.h"
40
41
#include "resources/image/image.h"
42
43
#include "resources/loaders/imageloader.h"
44
#include "resources/loaders/imagesetloader.h"
45
46
#include "utils/browserboxtools.h"
47
#include "utils/checkutils.h"
48
#include "utils/foreach.h"
49
#include "utils/stringutils.h"
50
#include "utils/timer.h"
51
#include "utils/translation/podict.h"
52
53
#include <algorithm>
54
55
#include "debug.h"
56
57
ImageSet *BrowserBox::mEmotes = nullptr;
58
int BrowserBox::mInstances = 0;
59
60
12
BrowserBox::BrowserBox(const Widget2 *const widget,
61
                       const Opaque opaque,
62
12
                       const std::string &skin) :
63
    Widget(widget),
64
    MouseListener(),
65
    WidgetListener(),
66
    mTextRows(),
67
    mTextRowLinksCount(),
68
    mLineParts(),
69
    mLinks(),
70
    mLinkHandler(nullptr),
71
    mSkin(nullptr),
72
    mHighlightMode(0),
73
    mSelectedLink(-1),
74
    mMaxRows(0),
75
    mHeight(0),
76
    mWidth(0),
77
    mYStart(0),
78
    mUpdateTime(-1),
79
    mPadding(0),
80
    mNewLinePadding(15U),
81
    mItemPadding(0),
82
    mDataWidth(0),
83
24
    mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT)),
84
24
    mHyperLinkColor(getThemeColor(ThemeColorId::HYPERLINK)),
85
    mOpaque(opaque),
86
    mUseLinksAndUserColors(true),
87
    mUseEmotes(true),
88
    mAlwaysUpdate(true),
89
    mProcessVars(false),
90
    mEnableImages(false),
91
    mEnableKeys(false),
92
108
    mEnableTabs(false)
93
{
94
12
    mAllowLogic = false;
95
96
12
    setFocusable(true);
97
12
    addMouseListener(this);
98
12
    addWidgetListener(this);
99
100
24
    mBackgroundColor = getThemeColor(ThemeColorId::BACKGROUND);
101
102
12
    if (theme != nullptr)
103

60
        mSkin = theme->load(skin, "browserbox.xml");
104
12
    if (mInstances == 0)
105
    {
106

40
        mEmotes = Loader::getImageSet(
107
            "graphics/sprites/chatemotes.png", 17, 18);
108
    }
109
12
    mInstances ++;
110
111
12
    if (mSkin != nullptr)
112
    {
113
24
        mPadding = mSkin->getPadding();
114
12
        mNewLinePadding = CAST_U32(
115

48
            mSkin->getOption("newLinePadding", 15));
116

48
        mItemPadding = mSkin->getOption("itemPadding");
117

48
        if (mSkin->getOption("highlightBackground") != 0)
118
12
            mHighlightMode |= LinkHighlightMode::BACKGROUND;
119

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

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

368
        font->getWidth(hyphen));
287
92
    unsigned int x = 0;
288
289
460
    FOR_EACH (TextRowCIter, i, mTextRows)
290
    {
291
6216
        std::string tempRow = *i;
292
16560
        for (uint32_t j = 0, sz = CAST_U32(tempRow.size());
293
16560
             j < sz;
294
             j++)
295
        {
296
28976
            const std::string character = tempRow.substr(j, 1);
297
14488
            x += CAST_U32(font->getWidth(character));
298
14488
            nextChar = j + 1;
299
300
            // Wraping between words (at blank spaces)
301

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

2
    if (mSelectedLink >= 0 &&
421
        mSelectedLink < CAST_S32(mLinks.size()))
422
    {
423
        if ((mHighlightMode & LinkHighlightMode::BACKGROUND) != 0u)
424
        {
425
            BrowserLink &link = mLinks[CAST_SIZE(mSelectedLink)];
426
            graphics->setColor(mHighlightColor);
427
            graphics->fillRectangle(Rect(
428
                link.x1,
429
                link.y1,
430
                link.x2 - link.x1,
431
                link.y2 - link.y1));
432
        }
433
434
        if ((mHighlightMode & LinkHighlightMode::UNDERLINE) != 0u)
435
        {
436
            BrowserLink &link = mLinks[CAST_SIZE(mSelectedLink)];
437
            graphics->setColor(mHyperLinkColor);
438
            graphics->drawLine(
439
                link.x1,
440
                link.y2,
441
                link.x2,
442
                link.y2);
443
        }
444
    }
445
446
2
    Font *const font = getFont();
447
448
12
    FOR_EACH (LinePartCIter, i, mLineParts)
449
    {
450
        const LinePart &part = *i;
451
        if (part.mY + 50 < mYStart)
452
            continue;
453
        if (part.mY > yEnd)
454
            break;
455
        if (part.mType == 0u)
456
        {
457
            if (part.mBold)
458
            {
459
                boldFont->drawString(graphics,
460
                    part.mColor,
461
                    part.mColor2,
462
                    part.mText,
463
                    part.mX, part.mY);
464
            }
465
            else
466
            {
467
                font->drawString(graphics,
468
                    part.mColor,
469
                    part.mColor2,
470
                    part.mText,
471
                    part.mX, part.mY);
472
            }
473
        }
474
        else if (part.mImage != nullptr)
475
        {
476
            graphics->drawImage(part.mImage, part.mX, part.mY);
477
        }
478
    }
479
480
    BLOCK_END("BrowserBox::draw")
481
2
}
482
483
void BrowserBox::safeDraw(Graphics *const graphics)
484
{
485
    BrowserBox::draw(graphics);
486
}
487
488
295
int BrowserBox::calcHeight()
489
{
490
295
    unsigned int y = CAST_U32(mPadding);
491
295
    int wrappedLines = 0;
492
295
    int moreHeight = 0;
493
295
    int maxWidth = mDimension.width - mPadding;
494
295
    int link = 0;
495
295
    bool bold = false;
496
295
    unsigned int wWidth = CAST_U32(maxWidth);
497
498
295
    if (maxWidth < 0)
499
        return 1;
500
501
13
    const Font *const font = getFont();
502
13
    const int fontHeight = font->getHeight() + 2 * mItemPadding;
503

52
    const int fontWidthMinus = font->getWidth("-");
504
13
    const char *const hyphen = "~";
505

52
    const int hyphenWidth = font->getWidth(hyphen);
506
507
13
    Color selColor[2] = {mForegroundColor, mForegroundColor2};
508
13
    const Color textColor[2] = {mForegroundColor, mForegroundColor2};
509
26
    mLineParts.clear();
510
511
65
    FOR_EACH (TextRowCIter, i, mTextRows)
512
    {
513
6
        unsigned int x = CAST_U32(mPadding);
514
18
        const std::string row = *(i);
515
6
        bool wrapped = false;
516
6
        int objects = 0;
517
518
        // Check for separator lines
519
6
        if (row.find("---", 0) == 0)
520
        {
521
            const int dashWidth = fontWidthMinus;
522
            for (x = CAST_U32(mPadding); x < wWidth; x ++)
523
            {
524
                mLineParts.push_back(LinePart(CAST_S32(x),
525
                    CAST_S32(y) + mItemPadding,
526
                    selColor[0], selColor[1], "-", false));
527
                x += CAST_U32(CAST_S32(
528
                    dashWidth) - 2);
529
            }
530
531
            y += CAST_U32(fontHeight);
532
            continue;
533
        }
534

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

6
            if (start == 0 || mUseLinksAndUserColors)
583
            {
584
                // Check for color change in format "##x", x = [L,P,0..9]
585

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


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

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