diff options
Diffstat (limited to 'src/gui/widgets/staticbrowserbox.cpp')
-rw-r--r-- | src/gui/widgets/staticbrowserbox.cpp | 800 |
1 files changed, 800 insertions, 0 deletions
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(); +} |