summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrei Karas <akaras@inbox.ru>2017-06-19 18:18:51 +0300
committerAndrei Karas <akaras@inbox.ru>2017-06-19 18:50:35 +0300
commit59210410f604eb79f5cde445b788cf7f38641460 (patch)
treec7315ead3d18e098ffa181835f1b50e42ced209f
parentd9d99a3ea8dc905487c2ff2c32c507bbd2df9f86 (diff)
downloadplus-59210410f604eb79f5cde445b788cf7f38641460.tar.gz
plus-59210410f604eb79f5cde445b788cf7f38641460.tar.bz2
plus-59210410f604eb79f5cde445b788cf7f38641460.tar.xz
plus-59210410f604eb79f5cde445b788cf7f38641460.zip
Add staticbrowserbox based on browserbox but with some limits.
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/Makefile.am2
-rw-r--r--src/gui/widgets/staticbrowserbox.cpp800
-rw-r--r--src/gui/widgets/staticbrowserbox.h202
4 files changed, 1006 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0f1952173..e03348ed3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -197,6 +197,8 @@ SET(SRCS
gui/widgets/itemshortcutcontainer.h
gui/widgets/spellshortcutcontainer.cpp
gui/widgets/spellshortcutcontainer.h
+ gui/widgets/staticbrowserbox.cpp
+ gui/widgets/staticbrowserbox.h
gui/widgets/statspage.cpp
gui/widgets/statspage.h
gui/widgets/statspagebasic.cpp
diff --git a/src/Makefile.am b/src/Makefile.am
index 0bbe07a2e..aeb1e2624 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1106,6 +1106,8 @@ SRC = ${BASE_SRC} \
gui/widgets/itemshortcutcontainer.h \
gui/widgets/spellshortcutcontainer.cpp \
gui/widgets/spellshortcutcontainer.h \
+ gui/widgets/staticbrowserbox.cpp \
+ gui/widgets/staticbrowserbox.h \
gui/widgets/statspage.cpp \
gui/widgets/statspage.h \
gui/widgets/statspagebasic.cpp \
diff --git a/src/gui/widgets/staticbrowserbox.cpp b/src/gui/widgets/staticbrowserbox.cpp
new file mode 100644
index 000000000..36685ca8c
--- /dev/null
+++ b/src/gui/widgets/staticbrowserbox.cpp
@@ -0,0 +1,800 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2017 The ManaPlus Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gui/widgets/staticbrowserbox.h"
+
+#include "enums/gui/linkhighlightmode.h"
+
+#include "gui/gui.h"
+#include "gui/mouseoverlink.h"
+#include "gui/skin.h"
+
+#include "gui/fonts/font.h"
+
+#include "gui/widgets/linkhandler.h"
+
+#include "render/graphics.h"
+
+#include "resources/imageset.h"
+
+#include "resources/image/image.h"
+
+#include "resources/loaders/imageloader.h"
+#include "resources/loaders/imagesetloader.h"
+
+#include "utils/browserboxtools.h"
+#include "utils/checkutils.h"
+#include "utils/stringutils.h"
+#include "utils/timer.h"
+#include "utils/translation/podict.h"
+
+#include <algorithm>
+
+#include "debug.h"
+
+ImageSet *StaticBrowserBox::mEmotes = nullptr;
+int StaticBrowserBox::mInstances = 0;
+
+#define readColor(color) \
+ mColors[0][ColorName::color] = getThemeColor(ThemeColorId::color); \
+ mColors[1][ColorName::color] = getThemeColor(ThemeColorId::color##_OUTLINE)
+
+StaticBrowserBox::StaticBrowserBox(const Widget2 *const widget,
+ const Opaque opaque,
+ const std::string &skin) :
+ Widget(widget),
+ MouseListener(),
+ WidgetListener(),
+ mTextRows(),
+ mTextRowLinksCount(),
+ mLineParts(),
+ mLinks(),
+ mLinkHandler(nullptr),
+ mSkin(nullptr),
+ mHighlightMode(0),
+ mSelectedLink(-1),
+ mHeight(0),
+ mWidth(0),
+ mYStart(0),
+ mPadding(0),
+ mNewLinePadding(15U),
+ mItemPadding(0),
+ mDataWidth(0),
+ mHighlightColor(getThemeColor(ThemeColorId::HIGHLIGHT)),
+ mHyperLinkColor(getThemeColor(ThemeColorId::HYPERLINK)),
+ mOpaque(opaque),
+ mUseLinksAndUserColors(true),
+ mUseEmotes(true),
+ mProcessVars(false),
+ mEnableImages(false),
+ mEnableKeys(false),
+ mEnableTabs(false)
+{
+ mAllowLogic = false;
+
+ setFocusable(true);
+ addMouseListener(this);
+ addWidgetListener(this);
+
+ mBackgroundColor = getThemeColor(ThemeColorId::BACKGROUND);
+
+ if (theme != nullptr)
+ mSkin = theme->load(skin, "browserbox.xml");
+ if (mInstances == 0)
+ {
+ mEmotes = Loader::getImageSet(
+ "graphics/sprites/chatemotes.png", 17, 18);
+ }
+ mInstances ++;
+
+ if (mSkin != nullptr)
+ {
+ mPadding = mSkin->getPadding();
+ mNewLinePadding = CAST_U32(
+ mSkin->getOption("newLinePadding", 15));
+ mItemPadding = mSkin->getOption("itemPadding");
+ if (mSkin->getOption("highlightBackground") != 0)
+ mHighlightMode |= LinkHighlightMode::BACKGROUND;
+ if (mSkin->getOption("highlightUnderline") != 0)
+ mHighlightMode |= LinkHighlightMode::UNDERLINE;
+ }
+
+ readColor(BLACK);
+ readColor(RED);
+ readColor(GREEN);
+ readColor(BLUE);
+ readColor(ORANGE);
+ readColor(YELLOW);
+ readColor(PINK);
+ readColor(PURPLE);
+ readColor(GRAY);
+ readColor(BROWN);
+
+ mForegroundColor = getThemeColor(ThemeColorId::BROWSERBOX);
+ mForegroundColor2 = getThemeColor(ThemeColorId::BROWSERBOX_OUTLINE);
+}
+
+StaticBrowserBox::~StaticBrowserBox()
+{
+ if (gui != nullptr)
+ gui->removeDragged(this);
+
+ if (theme != nullptr)
+ {
+ theme->unload(mSkin);
+ mSkin = nullptr;
+ }
+
+ mInstances --;
+ if (mInstances == 0)
+ {
+ if (mEmotes != nullptr)
+ {
+ mEmotes->decRef();
+ mEmotes = nullptr;
+ }
+ }
+}
+
+void StaticBrowserBox::setLinkHandler(LinkHandler* linkHandler)
+{
+ mLinkHandler = linkHandler;
+}
+
+void StaticBrowserBox::addRow(const std::string &row,
+ const bool atTop)
+{
+ std::string tmp = row;
+ std::string newRow;
+ size_t idx1;
+ const Font *const font = getFont();
+ int linksCount = 0;
+
+ if (getWidth() < 0)
+ return;
+
+ if (mProcessVars)
+ {
+ BrowserBoxTools::replaceVars(tmp);
+ }
+
+ // Use links and user defined colors
+ if (mUseLinksAndUserColors)
+ {
+ BrowserLink bLink;
+
+ // Check for links in format "@@link|Caption@@"
+ const uint32_t sz = CAST_U32(mTextRows.size());
+
+ if (mEnableKeys)
+ {
+ BrowserBoxTools::replaceKeys(tmp);
+ }
+
+ idx1 = tmp.find("@@");
+ while (idx1 != std::string::npos)
+ {
+ const size_t idx2 = tmp.find('|', idx1);
+ const size_t idx3 = tmp.find("@@", idx2);
+
+ if (idx2 == std::string::npos || idx3 == std::string::npos)
+ break;
+ bLink.link = tmp.substr(idx1 + 2, idx2 - (idx1 + 2));
+ bLink.caption = tmp.substr(idx2 + 1, idx3 - (idx2 + 1));
+ bLink.y1 = CAST_S32(sz) * font->getHeight();
+ bLink.y2 = bLink.y1 + font->getHeight();
+ if (bLink.caption.empty())
+ {
+ bLink.caption = BrowserBoxTools::replaceLinkCommands(
+ bLink.link);
+ if (translator != nullptr)
+ bLink.caption = translator->getStr(bLink.caption);
+ }
+
+ newRow.append(tmp.substr(0, idx1));
+
+ std::string tmp2 = newRow;
+ idx1 = tmp2.find("##");
+ while (idx1 != std::string::npos)
+ {
+ tmp2.erase(idx1, 3);
+ idx1 = tmp2.find("##");
+ }
+ bLink.x1 = font->getWidth(tmp2) - 1;
+ bLink.x2 = bLink.x1 + font->getWidth(bLink.caption) + 1;
+
+ if (atTop)
+ mLinks.insert(mLinks.begin(), bLink);
+ else
+ mLinks.push_back(bLink);
+ linksCount ++;
+
+ newRow.append("##<").append(bLink.caption);
+
+ tmp.erase(0, idx3 + 2);
+ if (!tmp.empty())
+ newRow.append("##>");
+
+ idx1 = tmp.find("@@");
+ }
+
+ newRow.append(tmp);
+ }
+ // Don't use links and user defined colors
+ else
+ {
+ newRow = row;
+ }
+
+ if (mEnableTabs)
+ {
+ BrowserBoxTools::replaceTabs(newRow);
+ }
+
+ if (atTop)
+ {
+ mTextRows.push_front(newRow);
+ mTextRowLinksCount.push_front(linksCount);
+ }
+ else
+ {
+ mTextRows.push_back(newRow);
+ mTextRowLinksCount.push_back(linksCount);
+ }
+
+ std::string plain = newRow;
+ // workaround if used only one string started from bold
+ // width for this string can be calculated wrong
+ // this workaround fix width if string start from bold sign
+ const bool startBold = (plain.find("##B") == 0);
+ for (idx1 = plain.find("##");
+ idx1 != std::string::npos;
+ idx1 = plain.find("##"))
+ {
+ plain.erase(idx1, 3);
+ }
+
+ // Adjust the StaticBrowserBox size
+ const int w = startBold ?
+ boldFont->getWidth(plain) : font->getWidth(plain) + 2 * mPadding;
+ if (w > getWidth())
+ setWidth(w);
+}
+
+void StaticBrowserBox::addRow(const std::string &cmd,
+ const char *const text)
+{
+ addRow(strprintf("@@%s|%s@@", encodeLinkText(cmd).c_str(),
+ encodeLinkText(text).c_str()));
+}
+
+void StaticBrowserBox::addImage(const std::string &path)
+{
+ if (!mEnableImages)
+ return;
+
+ mTextRows.push_back("~~~" + path);
+ mTextRowLinksCount.push_back(0);
+}
+
+void StaticBrowserBox::clearRows()
+{
+ mTextRows.clear();
+ mTextRowLinksCount.clear();
+ mLinks.clear();
+ setWidth(0);
+ setHeight(0);
+ mSelectedLink = -1;
+ mDataWidth = 0;
+}
+
+void StaticBrowserBox::mousePressed(MouseEvent &event)
+{
+ if (mLinkHandler == nullptr)
+ return;
+
+ const LinkIterator i = std::find_if(mLinks.begin(), mLinks.end(),
+ MouseOverLink(event.getX(), event.getY()));
+
+ if (i != mLinks.end())
+ {
+ mLinkHandler->handleLink(i->link, &event);
+ event.consume();
+ }
+}
+
+void StaticBrowserBox::mouseMoved(MouseEvent &event)
+{
+ const LinkIterator i = std::find_if(mLinks.begin(), mLinks.end(),
+ MouseOverLink(event.getX(), event.getY()));
+
+ mSelectedLink = (i != mLinks.end())
+ ? CAST_S32(i - mLinks.begin()) : -1;
+}
+
+void StaticBrowserBox::mouseExited(MouseEvent &event A_UNUSED)
+{
+ mSelectedLink = -1;
+}
+
+void StaticBrowserBox::draw(Graphics *const graphics)
+{
+ BLOCK_START("StaticBrowserBox::draw")
+ const ClipRect &cr = graphics->getTopClip();
+ mYStart = cr.y - cr.yOffset;
+ const int yEnd = mYStart + cr.height;
+ if (mYStart < 0)
+ mYStart = 0;
+
+ if (mDimension.width != mWidth)
+ {
+ updateHeight();
+ reportAlways("browserbox resize in draw");
+ }
+
+ if (mOpaque == Opaque_true)
+ {
+ graphics->setColor(mBackgroundColor);
+ graphics->fillRectangle(Rect(0, 0,
+ mDimension.width, mDimension.height));
+ }
+
+ if (mSelectedLink >= 0 &&
+ mSelectedLink < CAST_S32(mLinks.size()))
+ {
+ if ((mHighlightMode & LinkHighlightMode::BACKGROUND) != 0u)
+ {
+ BrowserLink &link = mLinks[CAST_SIZE(mSelectedLink)];
+ graphics->setColor(mHighlightColor);
+ graphics->fillRectangle(Rect(
+ link.x1,
+ link.y1,
+ link.x2 - link.x1,
+ link.y2 - link.y1));
+ }
+
+ if ((mHighlightMode & LinkHighlightMode::UNDERLINE) != 0u)
+ {
+ BrowserLink &link = mLinks[CAST_SIZE(mSelectedLink)];
+ graphics->setColor(mHyperLinkColor);
+ graphics->drawLine(
+ link.x1,
+ link.y2,
+ link.x2,
+ link.y2);
+ }
+ }
+
+ Font *const font = getFont();
+
+ FOR_EACH (LinePartCIter, i, mLineParts)
+ {
+ const LinePart &part = *i;
+ if (part.mY + 50 < mYStart)
+ continue;
+ if (part.mY > yEnd)
+ break;
+ if (part.mType == 0u)
+ {
+ if (part.mBold)
+ {
+ boldFont->drawString(graphics,
+ part.mColor,
+ part.mColor2,
+ part.mText,
+ part.mX, part.mY);
+ }
+ else
+ {
+ font->drawString(graphics,
+ part.mColor,
+ part.mColor2,
+ part.mText,
+ part.mX, part.mY);
+ }
+ }
+ else if (part.mImage != nullptr)
+ {
+ graphics->drawImage(part.mImage, part.mX, part.mY);
+ }
+ }
+
+ BLOCK_END("StaticBrowserBox::draw")
+}
+
+void StaticBrowserBox::safeDraw(Graphics *const graphics)
+{
+ StaticBrowserBox::draw(graphics);
+}
+
+int StaticBrowserBox::calcHeight()
+{
+ unsigned int y = CAST_U32(mPadding);
+ int wrappedLines = 0;
+ int moreHeight = 0;
+ int maxWidth = mDimension.width - mPadding;
+ int link = 0;
+ bool bold = false;
+ unsigned int wWidth = CAST_U32(maxWidth);
+
+ if (maxWidth < 0)
+ return 1;
+
+ const Font *const font = getFont();
+ const int fontHeight = font->getHeight() + 2 * mItemPadding;
+ const int fontWidthMinus = font->getWidth("-");
+
+ Color selColor[2] = {mForegroundColor, mForegroundColor2};
+ const Color textColor[2] = {mForegroundColor, mForegroundColor2};
+ mLineParts.clear();
+
+ FOR_EACH (TextRowCIter, i, mTextRows)
+ {
+ unsigned int x = CAST_U32(mPadding);
+ const std::string row = *(i);
+ bool wrapped = false;
+ int objects = 0;
+
+ // Check for separator lines
+ if (row.find("---", 0) == 0)
+ {
+ const int dashWidth = fontWidthMinus;
+ for (x = CAST_U32(mPadding); x < wWidth; x ++)
+ {
+ mLineParts.push_back(LinePart(CAST_S32(x),
+ CAST_S32(y) + mItemPadding,
+ selColor[0], selColor[1], "-", false));
+ x += CAST_U32(CAST_S32(
+ dashWidth) - 2);
+ }
+
+ y += CAST_U32(fontHeight);
+ continue;
+ }
+ else if (mEnableImages && row.find("~~~", 0) == 0)
+ {
+ std::string str = row.substr(3);
+ const size_t sz = str.size();
+ if (sz > 2 && str.substr(sz - 1) == "~")
+ str = str.substr(0, sz - 1);
+ Image *const img = Loader::getImage(str);
+ if (img != nullptr)
+ {
+ img->incRef();
+ mLineParts.push_back(LinePart(CAST_S32(x),
+ CAST_S32(y) + mItemPadding,
+ selColor[0], selColor[1], img));
+ y += CAST_U32(img->getHeight() + 2);
+ moreHeight += img->getHeight();
+ if (img->getWidth() > maxWidth)
+ maxWidth = img->getWidth() + 2;
+ }
+ continue;
+ }
+
+ Color prevColor[2];
+ prevColor[0] = selColor[0];
+ prevColor[1] = selColor[1];
+ bold = false;
+
+ const int xPadding = CAST_S32(mNewLinePadding) + mPadding;
+
+ for (size_t start = 0, end = std::string::npos;
+ start != std::string::npos;
+ start = end, end = std::string::npos)
+ {
+ // Wrapped line continuation shall be indented
+ if (wrapped)
+ {
+ y += CAST_U32(fontHeight);
+ x = CAST_U32(xPadding);
+ wrapped = false;
+ }
+
+ size_t idx1 = end;
+ size_t idx2 = end;
+
+ // "Tokenize" the string at control sequences
+ if (mUseLinksAndUserColors)
+ idx1 = row.find("##", start + 1);
+ if (start == 0 || mUseLinksAndUserColors)
+ {
+ // Check for color change in format "##x", x = [L,P,0..9]
+ if (row.find("##", start) == start && row.size() > start + 2)
+ {
+ const signed char c = row.at(start + 2);
+
+ bool valid(false);
+ const Color col[2] =
+ {
+ getThemeCharColor(c, valid),
+ getThemeCharColor(CAST_S8(
+ c | 0x80), valid)
+ };
+
+ if (c == '>')
+ {
+ selColor[0] = prevColor[0];
+ selColor[1] = prevColor[1];
+ }
+ else if (c == '<')
+ {
+ prevColor[0] = selColor[0];
+ prevColor[1] = selColor[1];
+ selColor[0] = col[0];
+ selColor[1] = col[1];
+ }
+ else if (c == 'B')
+ {
+ bold = true;
+ }
+ else if (c == 'b')
+ {
+ bold = false;
+ }
+ else if (valid)
+ {
+ selColor[0] = col[0];
+ selColor[1] = col[1];
+ }
+ else
+ {
+ switch (c)
+ {
+ case '0':
+ selColor[0] = mColors[0][ColorName::BLACK];
+ selColor[1] = mColors[1][ColorName::BLACK];
+ break;
+ case '1':
+ selColor[0] = mColors[0][ColorName::RED];
+ selColor[1] = mColors[1][ColorName::RED];
+ break;
+ case '2':
+ selColor[0] = mColors[0][ColorName::GREEN];
+ selColor[1] = mColors[1][ColorName::GREEN];
+ break;
+ case '3':
+ selColor[0] = mColors[0][ColorName::BLUE];
+ selColor[1] = mColors[1][ColorName::BLUE];
+ break;
+ case '4':
+ selColor[0] = mColors[0][ColorName::ORANGE];
+ selColor[1] = mColors[1][ColorName::ORANGE];
+ break;
+ case '5':
+ selColor[0] = mColors[0][ColorName::YELLOW];
+ selColor[1] = mColors[1][ColorName::YELLOW];
+ break;
+ case '6':
+ selColor[0] = mColors[0][ColorName::PINK];
+ selColor[1] = mColors[1][ColorName::PINK];
+ break;
+ case '7':
+ selColor[0] = mColors[0][ColorName::PURPLE];
+ selColor[1] = mColors[1][ColorName::PURPLE];
+ break;
+ case '8':
+ selColor[0] = mColors[0][ColorName::GRAY];
+ selColor[1] = mColors[1][ColorName::GRAY];
+ break;
+ case '9':
+ selColor[0] = mColors[0][ColorName::BROWN];
+ selColor[1] = mColors[1][ColorName::BROWN];
+ break;
+ default:
+ selColor[0] = textColor[0];
+ selColor[1] = textColor[1];
+ break;
+ }
+ }
+
+ if (c == '<' && link < CAST_S32(mLinks.size()))
+ {
+ int size;
+ if (bold)
+ {
+ size = boldFont->getWidth(
+ mLinks[CAST_SIZE(link)].caption) + 1;
+ }
+ else
+ {
+ size = font->getWidth(
+ mLinks[CAST_SIZE(link)].caption) + 1;
+ }
+
+ BrowserLink &linkRef = mLinks[CAST_SIZE(
+ link)];
+ linkRef.x1 = CAST_S32(x);
+ linkRef.y1 = CAST_S32(y);
+ linkRef.x2 = linkRef.x1 + size;
+ linkRef.y2 = CAST_S32(y) + fontHeight - 1;
+ link++;
+ }
+
+ start += 3;
+ if (start == row.size())
+ break;
+ }
+ }
+ if (mUseEmotes)
+ idx2 = row.find("%%", start + 1);
+ if (idx1 < idx2)
+ end = idx1;
+ else
+ end = idx2;
+ if (mUseEmotes)
+ {
+ // check for emote icons
+ if (row.size() > start + 2 && row.substr(start, 2) == "%%")
+ {
+ if (objects < 5)
+ {
+ const int cid = row.at(start + 2) - '0';
+ if (cid >= 0)
+ {
+ if (mEmotes != nullptr)
+ {
+ const size_t sz = mEmotes->size();
+ if (CAST_SIZE(cid) < sz)
+ {
+ Image *const img = mEmotes->get(
+ CAST_SIZE(cid));
+ if (img != nullptr)
+ {
+ mLineParts.push_back(LinePart(
+ CAST_S32(x),
+ CAST_S32(y) + mItemPadding,
+ selColor[0], selColor[1], img));
+ x += 18;
+ }
+ }
+ }
+ }
+ objects ++;
+ }
+
+ start += 3;
+ if (start == row.size())
+ {
+ if (x > mDataWidth)
+ mDataWidth = x;
+ break;
+ }
+ }
+ }
+ const size_t len = (end == std::string::npos) ? end : end - start;
+
+ if (start >= row.length())
+ break;
+
+ std::string part = row.substr(start, len);
+ int width = 0;
+ if (bold)
+ width = boldFont->getWidth(part);
+ else
+ width = font->getWidth(part);
+
+ mLineParts.push_back(LinePart(CAST_S32(x),
+ CAST_S32(y) + mItemPadding,
+ selColor[0], selColor[1], part.c_str(), bold));
+
+ if (bold)
+ width = boldFont->getWidth(part);
+ else
+ width = font->getWidth(part);
+
+ x += CAST_U32(width);
+ if (x > mDataWidth)
+ mDataWidth = x;
+ }
+ y += CAST_U32(fontHeight);
+ }
+ if (CAST_S32(wWidth) != maxWidth)
+ setWidth(maxWidth);
+
+ return (CAST_S32(mTextRows.size()) + wrappedLines)
+ * fontHeight + moreHeight + 2 * mPadding;
+}
+
+void StaticBrowserBox::updateHeight()
+{
+ mWidth = mDimension.width;
+ mHeight = calcHeight();
+ setHeight(mHeight);
+}
+
+std::string StaticBrowserBox::getTextAtPos(const int x,
+ const int y) const
+{
+ int textX = 0;
+ int textY = 0;
+
+ getAbsolutePosition(textX, textY);
+ if (x < textX || y < textY)
+ return std::string();
+
+ textY = y - textY;
+ std::string str;
+ int lastY = 0;
+
+ FOR_EACH (LinePartCIter, i, mLineParts)
+ {
+ const LinePart &part = *i;
+ if (part.mY + 50 < mYStart)
+ continue;
+ if (part.mY > textY)
+ break;
+
+ if (part.mY > lastY)
+ {
+ str = part.mText;
+ lastY = part.mY;
+ }
+ else
+ {
+ str.append(part.mText);
+ }
+ }
+
+ return str;
+}
+
+void StaticBrowserBox::setForegroundColorAll(const Color &color1,
+ const Color &color2)
+{
+ mForegroundColor = color1;
+ mForegroundColor2 = color2;
+}
+
+void StaticBrowserBox::moveSelectionUp()
+{
+ if (mSelectedLink <= 0)
+ mSelectedLink = CAST_S32(mLinks.size()) - 1;
+ else
+ mSelectedLink --;
+}
+
+void StaticBrowserBox::moveSelectionDown()
+{
+ mSelectedLink ++;
+ if (mSelectedLink >= static_cast<signed int>(mLinks.size()))
+ mSelectedLink = 0;
+}
+
+void StaticBrowserBox::selectSelection()
+{
+ if ((mLinkHandler == nullptr) ||
+ mSelectedLink < 0 ||
+ mSelectedLink >= static_cast<signed int>(mLinks.size()))
+ {
+ return;
+ }
+
+ mLinkHandler->handleLink(mLinks[CAST_SIZE(mSelectedLink)].link,
+ nullptr);
+}
+
+void StaticBrowserBox::widgetResized(const Event &event A_UNUSED)
+{
+ updateHeight();
+}
diff --git a/src/gui/widgets/staticbrowserbox.h b/src/gui/widgets/staticbrowserbox.h
new file mode 100644
index 000000000..643f0a96f
--- /dev/null
+++ b/src/gui/widgets/staticbrowserbox.h
@@ -0,0 +1,202 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2017 The ManaPlus Developers
+ * Copyright (C) 2009 Aethyra Development Team
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GUI_WIDGETS_STATICBROWSERBOX_H
+#define GUI_WIDGETS_STATICBROWSERBOX_H
+
+#include "enums/simpletypes/opaque.h"
+
+#include "enums/gui/browserboxmode.h"
+#include "enums/gui/colorname.h"
+
+#include "gui/browserlink.h"
+
+#include "gui/widgets/linepart.h"
+#include "gui/widgets/widget.h"
+
+#include "listeners/mouselistener.h"
+#include "listeners/widgetlistener.h"
+
+#include "localconsts.h"
+
+class LinkHandler;
+
+/**
+ * A simple browser box able to handle links and forward events to the
+ * parent conteiner.
+ */
+class StaticBrowserBox final : public Widget,
+ public MouseListener,
+ public WidgetListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ StaticBrowserBox(const Widget2 *const widget,
+ const Opaque opaque,
+ const std::string &skin);
+
+ A_DELETE_COPY(StaticBrowserBox)
+
+ /**
+ * Destructor.
+ */
+ ~StaticBrowserBox();
+
+ /**
+ * Sets the handler for links.
+ */
+ void setLinkHandler(LinkHandler *linkHandler);
+
+ /**
+ * Sets the StaticBrowserBox opacity.
+ */
+ void setOpaque(Opaque opaque)
+ { mOpaque = opaque; }
+
+ /**
+ * Adds a text row to the browser.
+ */
+ void addRow(const std::string &row,
+ const bool atTop = false);
+
+ /**
+ * Adds a menu line to the browser.
+ */
+ void addRow(const std::string &cmd,
+ const char *const text);
+
+ void addImage(const std::string &path);
+
+ /**
+ * Remove all rows.
+ */
+ void clearRows();
+
+ /**
+ * Handles mouse actions.
+ */
+ void mousePressed(MouseEvent &event) override final;
+
+ void mouseMoved(MouseEvent &event) override final;
+
+ void mouseExited(MouseEvent& event) override final;
+
+ /**
+ * Draws the browser box.
+ */
+ void draw(Graphics *const graphics) override final A_NONNULL(2);
+
+ void safeDraw(Graphics *const graphics) override final A_NONNULL(2);
+
+ void updateHeight();
+
+ void updateSize();
+
+ typedef std::list<std::string> TextRows;
+
+ TextRows &getRows() noexcept2 A_WARN_UNUSED
+ { return mTextRows; }
+
+ bool hasRows() const noexcept2 A_WARN_UNUSED
+ { return !mTextRows.empty(); }
+
+ void setProcessVars(const bool n) noexcept2
+ { mProcessVars = n; }
+
+ void setEnableImages(const bool n) noexcept2
+ { mEnableImages = n; }
+
+ void setEnableKeys(const bool n) noexcept2
+ { mEnableKeys = n; }
+
+ void setEnableTabs(const bool n) noexcept2
+ { mEnableTabs = n; }
+
+ std::string getTextAtPos(const int x,
+ const int y) const A_WARN_UNUSED;
+
+ int getPadding() const noexcept2 A_WARN_UNUSED
+ { return mPadding; }
+
+ void setForegroundColorAll(const Color &color1,
+ const Color &color2);
+
+ unsigned int getDataWidth() const noexcept2 A_WARN_UNUSED
+ { return mDataWidth; }
+
+ void moveSelectionUp();
+
+ void moveSelectionDown();
+
+ void selectSelection();
+
+ void widgetResized(const Event &event) override final;
+
+ private:
+ int calcHeight() A_WARN_UNUSED;
+
+ typedef TextRows::iterator TextRowIterator;
+ typedef TextRows::const_iterator TextRowCIter;
+ TextRows mTextRows;
+ std::list<int> mTextRowLinksCount;
+
+ typedef std::vector<LinePart> LinePartList;
+ typedef LinePartList::iterator LinePartIterator;
+ typedef LinePartList::const_iterator LinePartCIter;
+ LinePartList mLineParts;
+
+ typedef std::vector<BrowserLink> Links;
+ typedef Links::iterator LinkIterator;
+ Links mLinks;
+
+ LinkHandler *mLinkHandler;
+ Skin *mSkin;
+ unsigned int mHighlightMode;
+ int mSelectedLink;
+ int mHeight;
+ int mWidth;
+ int mYStart;
+ int mPadding;
+ unsigned int mNewLinePadding;
+ int mItemPadding;
+ unsigned int mDataWidth;
+
+ Color mHighlightColor;
+ Color mHyperLinkColor;
+ Color mColors[2][ColorName::COLORS_MAX];
+
+ Opaque mOpaque;
+ bool mUseLinksAndUserColors;
+ bool mUseEmotes;
+ bool mProcessVars;
+ bool mEnableImages;
+ bool mEnableKeys;
+ bool mEnableTabs;
+
+ static ImageSet *mEmotes;
+ static int mInstances;
+};
+
+#endif // GUI_WIDGETS_STATICBROWSERBOX_H