diff options
Diffstat (limited to 'src/gui/widgets/browserbox.cpp')
-rw-r--r-- | src/gui/widgets/browserbox.cpp | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp new file mode 100644 index 000000000..acb182c3c --- /dev/null +++ b/src/gui/widgets/browserbox.cpp @@ -0,0 +1,534 @@ +/* + * The Mana Client + * Copyright (C) 2004-2009 The Mana World Development Team + * Copyright (C) 2009-2010 The Mana Developers + * Copyright (C) 2009 Aethyra Development Team + * + * This file is part of The Mana 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/browserbox.h" + +#include "client.h" +#include "log.h" + +#include "utils/stringutils.h" + +#include "gui/palette.h" +#include "gui/theme.h" + +#include "gui/widgets/linkhandler.h" + +#include <guichan/graphics.hpp> +#include <guichan/font.hpp> +#include <guichan/cliprectangle.hpp> + +#include <algorithm> + +BrowserBox::BrowserBox(unsigned int mode, bool opaque): + gcn::Widget(), + mMode(mode), mHighMode(UNDERLINE | BACKGROUND), + mOpaque(opaque), + mUseLinksAndUserColors(true), + mSelectedLink(-1), + mMaxRows(0), + mHeight(0), + mWidth(0), + mYStart(0), + mUpdateTime(-1), + mAlwaysUpdate(true) +{ + setFocusable(true); + addMouseListener(this); +} + +BrowserBox::~BrowserBox() +{ +} + +void BrowserBox::setLinkHandler(LinkHandler* linkHandler) +{ + mLinkHandler = linkHandler; +} + +void BrowserBox::setOpaque(bool opaque) +{ + mOpaque = opaque; +} + +void BrowserBox::setHighlightMode(unsigned int highMode) +{ + mHighMode = highMode; +} + +void BrowserBox::addRow(const std::string &row, bool atTop) +{ + std::string tmp = row; + std::string newRow; + std::string::size_type idx1, idx2, idx3; + gcn::Font *font = getFont(); + + // Use links and user defined colors + if (mUseLinksAndUserColors) + { + BROWSER_LINK bLink; + + // Check for links in format "@@link|Caption@@" + idx1 = tmp.find("@@"); + while (idx1 != std::string::npos) + { + idx2 = tmp.find("|", idx1); + 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 = static_cast<int>(mTextRows.size()) * font->getHeight(); + bLink.y2 = bLink.y1 + font->getHeight(); + + newRow += 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; + + mLinks.push_back(bLink); + + newRow += "##<" + bLink.caption; + + tmp.erase(0, idx3 + 2); + if (!tmp.empty()) + newRow += "##>"; + + idx1 = tmp.find("@@"); + } + + newRow += tmp; + } + // Don't use links and user defined colors + else + { + newRow = row; + } + + if (atTop) + mTextRows.push_front(newRow); + else + mTextRows.push_back(newRow); + + //discard older rows when a row limit has been set + if (mMaxRows > 0) + { + while (mTextRows.size() > mMaxRows) + { + mTextRows.pop_front(); + for (unsigned int i = 0; i < mLinks.size(); i++) + { + mLinks[i].y1 -= font->getHeight(); + mLinks[i].y2 -= font->getHeight(); + + if (mLinks[i].y1 < 0) + mLinks.erase(mLinks.begin() + i); + } + } + } + + // Auto size mode + if (mMode == AUTO_SIZE) + { + std::string plain = newRow; + for (idx1 = plain.find("##"); + idx1 != std::string::npos; + idx1 = plain.find("##")) + { + plain.erase(idx1, 3); + } + + // Adjust the BrowserBox size + int w = font->getWidth(plain); + if (w > getWidth()) + setWidth(w); + } + + if (mMode == AUTO_WRAP) + { + unsigned int y = 0; + unsigned int nextChar; + const char *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + int x = 0; + + for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + { + std::string row = *i; + for (unsigned int j = 0; j < row.size(); j++) + { + std::string character = row.substr(j, 1); + x += font->getWidth(character); + nextChar = j + 1; + + // Wraping between words (at blank spaces) + if ((nextChar < row.size()) && (row.at(nextChar) == ' ')) + { + int nextSpacePos = static_cast<int>( + row.find(" ", (nextChar + 1))); + if (nextSpacePos <= 0) + nextSpacePos = static_cast<int>(row.size()) - 1; + + int nextWordWidth = font->getWidth( + row.substr(nextChar, + (nextSpacePos - nextChar))); + + if ((x + nextWordWidth + 10) > getWidth()) + { + x = 15; // Ident in new line + y += 1; + j++; + } + } + // Wrapping looong lines (brutal force) + else if ((x + 2 * hyphenWidth) > getWidth()) + { + x = 15; // Ident in new line + y += 1; + } + } + } + + setHeight(font->getHeight() * (static_cast<int>( + mTextRows.size()) + y)); + } + else + { + setHeight(font->getHeight() * static_cast<int>(mTextRows.size())); + } + mUpdateTime = 0; + updateHeight(); +} + +void BrowserBox::clearRows() +{ + mTextRows.clear(); + mLinks.clear(); + setWidth(0); + setHeight(0); + mSelectedLink = -1; + mUpdateTime = 0; + updateHeight(); +} + +struct MouseOverLink +{ + MouseOverLink(int x, int y) : mX(x), mY(y) + { } + + bool operator() (BROWSER_LINK &link) + { + return (mX >= link.x1 && mX < link.x2 && + mY >= link.y1 && mY < link.y2); + } + int mX, mY; +}; + +void BrowserBox::mousePressed(gcn::MouseEvent &event) +{ + if (!mLinkHandler) + return; + + LinkIterator i = find_if(mLinks.begin(), mLinks.end(), + MouseOverLink(event.getX(), event.getY())); + + if (i != mLinks.end()) + mLinkHandler->handleLink(i->link, &event); +} + +void BrowserBox::mouseMoved(gcn::MouseEvent &event) +{ + LinkIterator i = find_if(mLinks.begin(), mLinks.end(), + MouseOverLink(event.getX(), event.getY())); + + mSelectedLink = (i != mLinks.end()) + ? static_cast<int>(i - mLinks.begin()) : -1; +} + +void BrowserBox::draw(gcn::Graphics *graphics) +{ + gcn::ClipRectangle cr = graphics->getCurrentClipArea(); + mYStart = cr.y - cr.yOffset; + int yEnd = mYStart + cr.height; + if (mYStart < 0) + mYStart = 0; + + if (getWidth() != mWidth) + updateHeight(); + + if (mOpaque) + { + graphics->setColor(Theme::getThemeColor(Theme::BACKGROUND)); + graphics->fillRectangle(gcn::Rectangle(0, 0, getWidth(), getHeight())); + } + + if (mSelectedLink >= 0 && mSelectedLink < (signed)mLinks.size()) + { + if ((mHighMode & BACKGROUND)) + { + graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT)); + graphics->fillRectangle(gcn::Rectangle( + mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y1, + mLinks[mSelectedLink].x2 - mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y2 - mLinks[mSelectedLink].y1 + )); + } + + if ((mHighMode & UNDERLINE)) + { + graphics->setColor(Theme::getThemeColor(Theme::HYPERLINK)); + graphics->drawLine( + mLinks[mSelectedLink].x1, + mLinks[mSelectedLink].y2, + mLinks[mSelectedLink].x2, + mLinks[mSelectedLink].y2); + } + } + + gcn::Font *font = getFont(); + + for (LinePartIterator i = mLineParts.begin(); + i != mLineParts.end(); + i ++) + { + const LinePart &part = *i; + if (part.mY + 50 < mYStart) + continue; + if (part.mY > yEnd) + break; + graphics->setColor(part.mColor); + font->drawString(graphics, part.mText, part.mX, part.mY); + } + + return; +} + +int BrowserBox::calcHeight() +{ + int x = 0, y = 0; + int wrappedLines = 0; + int link = 0; + gcn::Font *font = getFont(); + + int fontHeight = font->getHeight(); + int fontWidthMinus = font->getWidth("-"); + char const *hyphen = "~"; + int hyphenWidth = font->getWidth(hyphen); + + gcn::Color selColor = Theme::getThemeColor(Theme::TEXT); + const gcn::Color textColor = Theme::getThemeColor(Theme::TEXT); + + mLineParts.clear(); + + for (TextRowIterator i = mTextRows.begin(); i != mTextRows.end(); i++) + { + const std::string row = *(i); + bool wrapped = false; + x = 0; + + // Check for separator lines + if (row.find("---", 0) == 0) + { + const int dashWidth = fontWidthMinus; + for (x = 0; x < getWidth(); x++) + { + mLineParts.push_back(LinePart(x, y, selColor, "-")); + x += dashWidth - 2; + } + + y += fontHeight; + continue; + } + + gcn::Color prevColor = selColor; + + // TODO: Check if we must take texture size limits into account here + // TODO: Check if some of the O(n) calls can be removed + for (std::string::size_type 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 += fontHeight; + x = 15; + wrapped = false; + } + + // "Tokenize" the string at control sequences + if (mUseLinksAndUserColors) + end = row.find("##", start + 1); + + if (mUseLinksAndUserColors || + (!mUseLinksAndUserColors && (start == 0))) + { + // Check for color change in format "##x", x = [L,P,0..9] + if (row.find("##", start) == start && row.size() > start + 2) + { + const char c = row.at(start + 2); + + bool valid; + const gcn::Color col = Theme::getThemeColor(c, valid); + + if (c == '>') + { + selColor = prevColor; + } + else if (c == '<') + { +// link++; + prevColor = selColor; + selColor = col; + } + else if (valid) + { + selColor = col; + } + else + { + + switch (c) + { + case '1': selColor = RED; break; + case '2': selColor = GREEN; break; + case '3': selColor = BLUE; break; + case '4': selColor = ORANGE; break; + case '5': selColor = YELLOW; break; + case '6': selColor = PINK; break; + case '7': selColor = PURPLE; break; + case '8': selColor = GRAY; break; + case '9': selColor = BROWN; break; + case '0': + default: + selColor = textColor; + } + } + + if (c == '<' && link < (signed)mLinks.size()) + { + const int size = + font->getWidth(mLinks[link].caption) + 1; + + mLinks[link].x1 = x; + mLinks[link].y1 = y; + mLinks[link].x2 = mLinks[link].x1 + size; + mLinks[link].y2 = y + fontHeight - 1; + link++; + } + start += 3; + + if (start == row.size()) + break; + } + } + + std::string::size_type len = + end == std::string::npos ? end : end - start; + + if (start >= row.length()) + break; + + std::string part = row.substr(start, len); + + // Auto wrap mode + if (mMode == AUTO_WRAP && getWidth() > 0 + && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()) + { + bool forced = false; + + /* FIXME: This code layout makes it easy to crash remote + clients by talking garbage. Forged long utf-8 characters + will cause either a buffer underflow in substr or an + infinite loop in the main loop. */ + do + { + if (!forced) + end = row.rfind(' ', end); + + // Check if we have to (stupidly) force-wrap + if (end == std::string::npos || end <= start) + { + forced = true; + end = row.size(); + x += hyphenWidth; // Account for the wrap-notifier + continue; + } + + // Skip to the start of the current character + while ((row[end] & 192) == 128) + end--; + end--; // And then to the last byte of the previous one + + part = row.substr(start, end - start + 1); + } + while (end > start && font->getWidth(part) > 0 + && (x + font->getWidth(part) + 10) > getWidth()); + + if (forced) + { + x -= hyphenWidth; // Remove the wrap-notifier accounting + mLineParts.push_back(LinePart(getWidth() - hyphenWidth, + y, selColor, hyphen)); + end++; // Skip to the next character + } + else + { + end += 2; // Skip to after the space + } + + wrapped = true; + wrappedLines++; + } + + mLineParts.push_back(LinePart(x, y, selColor, part.c_str())); + + if (mMode == AUTO_WRAP && font->getWidth(part) == 0) + break; + + x += font->getWidth(part); + } + y += fontHeight; + } + return (static_cast<int>(mTextRows.size()) + wrappedLines) * fontHeight; +} + +void BrowserBox::updateHeight() +{ + if (mAlwaysUpdate || mUpdateTime != cur_time + || mTextRows.size() < 3 || !mUpdateTime) + { + mWidth = getWidth(); + mHeight = calcHeight(); + setHeight(mHeight); + mUpdateTime = cur_time; + } +} |