GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
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 |
} |
Generated by: GCOVR (Version 3.3) |