GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/widgets/browserbox.cpp Lines: 223 466 47.9 %
Date: 2018-11-12 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-2018  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
6
BrowserBox::BrowserBox(const Widget2 *const widget,
61
                       const Opaque opaque,
62
6
                       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
12
    mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT, 255U)),
84
12
    mHyperLinkColor(getThemeColor(ThemeColorId::HYPERLINK, 255U)),
85
    mOpaque(opaque),
86
    mUseLinksAndUserColors(true),
87
    mUseEmotes(true),
88
    mAlwaysUpdate(true),
89
    mProcessVars(false),
90
    mEnableImages(false),
91
    mEnableKeys(false),
92
54
    mEnableTabs(false)
93
{
94
6
    mAllowLogic = false;
95
96
6
    setFocusable(true);
97
6
    addMouseListener(this);
98
6
    addWidgetListener(this);
99
100
12
    mBackgroundColor = getThemeColor(ThemeColorId::BACKGROUND, 255U);
101
102
6
    if (theme != nullptr)
103
    {
104

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

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

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

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

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

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

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

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

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

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

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

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

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



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

299
    if (mAlwaysUpdate || mUpdateTime != cur_time
853

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