Files
qt-creator/src/plugins/help/qlitehtml/container_qpainter.cpp
Eike Ziller 62d16e437a Help: Add context menu to litehtml backend
Change-Id: I051984e360b41d17d32cff80fd1d4017fc81dae6
Reviewed-by: hjk <hjk@qt.io>
2019-09-10 12:56:51 +00:00

1105 lines
40 KiB
C++

/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of QLiteHtml.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "container_qpainter.h"
#include <QCursor>
#include <QDebug>
#include <QDir>
#include <QFont>
#include <QFontDatabase>
#include <QFontMetrics>
#include <QGuiApplication>
#include <QLoggingCategory>
#include <QPainter>
#include <QPalette>
#include <QScreen>
#include <QTextLayout>
#include <QUrl>
#include <algorithm>
const int kDragDistance = 5;
using Font = QFont;
using Context = QPainter;
namespace {
Q_LOGGING_CATEGORY(log, "qlitehtml", QtCriticalMsg)
}
static QFont toQFont(litehtml::uint_ptr hFont)
{
return *reinterpret_cast<Font *>(hFont);
}
static QPainter *toQPainter(litehtml::uint_ptr hdc)
{
return reinterpret_cast<Context *>(hdc);
}
static QRect toQRect(litehtml::position position)
{
return {position.x, position.y, position.width, position.height};
}
static litehtml::elements_vector path(const litehtml::element::ptr &element)
{
litehtml::elements_vector result;
litehtml::element::ptr current = element;
while (current) {
result.push_back(current);
current = current->parent();
}
std::reverse(std::begin(result), std::end(result));
return result;
}
static std::pair<litehtml::element::ptr, size_t> getCommonParent(const litehtml::elements_vector &a,
const litehtml::elements_vector &b)
{
litehtml::element::ptr parent;
const size_t minSize = std::min(a.size(), b.size());
for (size_t i = 0; i < minSize; ++i) {
if (a.at(i) != b.at(i))
return {parent, i};
parent = a.at(i);
}
return {parent, minSize};
}
static std::pair<Selection::Element, Selection::Element> getStartAndEnd(const Selection::Element &a,
const Selection::Element &b)
{
if (a.element == b.element) {
if (a.index <= b.index)
return {a, b};
return {b, a};
}
const litehtml::elements_vector aPath = path(a.element);
const litehtml::elements_vector bPath = path(b.element);
litehtml::element::ptr commonParent;
size_t firstDifferentIndex;
std::tie(commonParent, firstDifferentIndex) = getCommonParent(aPath, bPath);
if (!commonParent) {
qWarning() << "internal error: litehtml elements do not have common parent";
return {a, b};
}
if (commonParent == a.element)
return {a, a}; // 'a' already contains 'b'
if (commonParent == b.element)
return {b, b};
// find out if a or b is first in the child sub-trees of commonParent
const litehtml::element::ptr aBranch = aPath.at(firstDifferentIndex);
const litehtml::element::ptr bBranch = bPath.at(firstDifferentIndex);
for (int i = 0; i < int(commonParent->get_children_count()); ++i) {
const litehtml::element::ptr child = commonParent->get_child(i);
if (child == aBranch)
return {a, b};
if (child == bBranch)
return {b, a};
}
qWarning() << "internal error: failed to find out order of litehtml elements";
return {a, b};
}
static int findChild(const litehtml::element::ptr &child, const litehtml::element::ptr &parent)
{
for (int i = 0; i < int(parent->get_children_count()); ++i)
if (parent->get_child(i) == child)
return i;
return -1;
}
static litehtml::element::ptr nextLeaf(const litehtml::element::ptr &element,
const litehtml::element::ptr &stop)
{
if (element == stop)
return element;
litehtml::element::ptr current = element;
if (current->have_parent()) {
// find next sibling
const litehtml::element::ptr parent = current->parent();
const int childIndex = findChild(current, parent);
if (childIndex < 0) {
qWarning() << "internal error: filed to find litehtml child element in parent";
return stop;
}
if (childIndex + 1 >= int(parent->get_children_count())) // no sibling, move up
return nextLeaf(parent, stop);
current = parent->get_child(childIndex + 1);
}
// move down to first leaf
while (current != stop && current->get_children_count() > 0)
current = current->get_child(0);
return current;
}
static Selection::Element selectionDetails(const litehtml::element::ptr &element,
const QString &text,
const QPoint &pos)
{
// shortcut, which _might_ not really be correct
if (element->get_children_count() > 0)
return {element, -1, -1}; // everything selected
const QFont &font = toQFont(element->get_font());
const QFontMetrics fm(font);
int previous = 0;
for (int i = 0; i < text.size(); ++i) {
const int width = fm.size(0, text.left(i + 1)).width();
if ((width + previous) / 2 >= pos.x())
return {element, i, previous};
previous = width;
}
return {element, text.size(), previous};
}
static Selection::Element deepest_child_at_point(const litehtml::document::ptr &document,
const QPoint &pos,
const QPoint &viewportPos,
Selection::Mode mode)
{
if (!document)
return {};
// the following does not find the "smallest" element, it often consists of children
// with individual words as text...
const litehtml::element::ptr element = document->root()->get_element_by_point(pos.x(),
pos.y(),
viewportPos.x(),
viewportPos.y());
// ...so try to find a better match
const std::function<Selection::Element(litehtml::element::ptr, QRect)> recursion =
[&recursion, pos, mode](const litehtml::element::ptr &element,
const QRect &placement) -> Selection::Element {
if (!element)
return {};
Selection::Element result;
for (int i = 0; i < int(element->get_children_count()); ++i) {
const litehtml::element::ptr child = element->get_child(i);
result = recursion(child,
toQRect(child->get_position()).translated(placement.topLeft()));
if (result.element)
return result;
}
if (placement.contains(pos)) {
litehtml::tstring text;
element->get_text(text);
if (!text.empty()) {
return mode == Selection::Mode::Free
? selectionDetails(element,
QString::fromStdString(text),
pos - placement.topLeft())
: Selection::Element({element, -1, -1});
}
}
return {};
};
return recursion(element, element ? toQRect(element->get_placement()) : QRect());
}
// CSS: 400 == normal, 700 == bold.
// Qt: 50 == normal, 75 == bold
static int cssWeightToQtWeight(int cssWeight)
{
if (cssWeight <= 400)
return cssWeight * 50 / 400;
if (cssWeight >= 700)
return 75 + (cssWeight - 700) * 25 / 300;
return 50 + (cssWeight - 400) * 25 / 300;
}
static QFont::Style toQFontStyle(litehtml::font_style style)
{
switch (style) {
case litehtml::fontStyleNormal:
return QFont::StyleNormal;
case litehtml::fontStyleItalic:
return QFont::StyleItalic;
}
// should not happen
qWarning(log) << "Unknown litehtml font style:" << style;
return QFont::StyleNormal;
}
static QColor toQColor(const litehtml::web_color &color)
{
return {color.red, color.green, color.blue, color.alpha};
}
static Qt::PenStyle borderPenStyle(litehtml::border_style style)
{
switch (style) {
case litehtml::border_style_dotted:
return Qt::DotLine;
case litehtml::border_style_dashed:
return Qt::DashLine;
case litehtml::border_style_solid:
return Qt::SolidLine;
default:
qWarning(log) << "Unsupported border style:" << style;
}
return Qt::SolidLine;
}
static QPen borderPen(const litehtml::border &border)
{
return {toQColor(border.color), qreal(border.width), borderPenStyle(border.style)};
}
static QCursor toQCursor(const QString &c)
{
if (c == "alias")
return {Qt::PointingHandCursor}; // ???
if (c == "all-scroll")
return {Qt::SizeAllCursor};
if (c == "auto")
return {Qt::ArrowCursor}; // ???
if (c == "cell")
return {Qt::UpArrowCursor};
if (c == "context-menu")
return {Qt::ArrowCursor}; // ???
if (c == "col-resize")
return {Qt::SplitHCursor};
if (c == "copy")
return {Qt::DragCopyCursor};
if (c == "crosshair")
return {Qt::CrossCursor};
if (c == "default")
return {Qt::ArrowCursor};
if (c == "e-resize")
return {Qt::SizeHorCursor}; // ???
if (c == "ew-resize")
return {Qt::SizeHorCursor};
if (c == "grab")
return {Qt::OpenHandCursor};
if (c == "grabbing")
return {Qt::ClosedHandCursor};
if (c == "help")
return {Qt::WhatsThisCursor};
if (c == "move")
return {Qt::SizeAllCursor};
if (c == "n-resize")
return {Qt::SizeVerCursor}; // ???
if (c == "ne-resize")
return {Qt::SizeBDiagCursor}; // ???
if (c == "nesw-resize")
return {Qt::SizeBDiagCursor};
if (c == "ns-resize")
return {Qt::SizeVerCursor};
if (c == "nw-resize")
return {Qt::SizeFDiagCursor}; // ???
if (c == "nwse-resize")
return {Qt::SizeFDiagCursor};
if (c == "no-drop")
return {Qt::ForbiddenCursor};
if (c == "none")
return {Qt::BlankCursor};
if (c == "not-allowed")
return {Qt::ForbiddenCursor};
if (c == "pointer")
return {Qt::PointingHandCursor};
if (c == "progress")
return {Qt::BusyCursor};
if (c == "row-resize")
return {Qt::SplitVCursor};
if (c == "s-resize")
return {Qt::SizeVerCursor}; // ???
if (c == "se-resize")
return {Qt::SizeFDiagCursor}; // ???
if (c == "sw-resize")
return {Qt::SizeBDiagCursor}; // ???
if (c == "text")
return {Qt::IBeamCursor};
if (c == "url")
return {Qt::ArrowCursor}; // ???
if (c == "w-resize")
return {Qt::SizeHorCursor}; // ???
if (c == "wait")
return {Qt::BusyCursor};
if (c == "zoom-in")
return {Qt::ArrowCursor}; // ???
qWarning(log) << QString("unknown cursor property \"%1\"").arg(c).toUtf8().constData();
return {Qt::ArrowCursor};
}
bool Selection::isValid() const
{
return !selection.isEmpty();
}
void Selection::update()
{
const auto addElement = [this](const Selection::Element &element,
const Selection::Element &end = {}) {
litehtml::tstring elemText;
element.element->get_text(elemText);
const QString textStr = QString::fromStdString(elemText);
if (!textStr.isEmpty()) {
QRect rect = toQRect(element.element->get_placement()).adjusted(-1, -1, 1, 1);
if (element.index < 0) { // fully selected
text += textStr;
} else if (end.element) { // select from element "to end"
if (element.element == end.element) {
// end.index is guaranteed to be >= element.index by caller, same for x
text += textStr.mid(element.index, end.index - element.index);
const int left = rect.left();
rect.setLeft(left + element.x);
rect.setRight(left + end.x);
} else {
text += textStr.mid(element.index);
rect.setLeft(rect.left() + element.x);
}
} else { // select from start of element
text += textStr.left(element.index);
rect.setRight(rect.left() + element.x);
}
selection.append(rect);
}
};
if (startElem.element && endElem.element) {
// Edge cases:
// start and end elements could be reversed or children of each other
Selection::Element start;
Selection::Element end;
std::tie(start, end) = getStartAndEnd(startElem, endElem);
selection.clear();
text.clear();
// Treats start element as a leaf even if it isn't, because it already contains all its
// children
addElement(start, end);
if (start.element != end.element) {
litehtml::element::ptr current = start.element;
do {
current = nextLeaf(current, end.element);
if (current == end.element)
addElement(end);
else
addElement({current, -1, -1});
} while (current != end.element);
}
} else {
selection = {};
text.clear();
}
}
QRect Selection::boundingRect() const
{
QRect rect;
for (const QRect &r : selection)
rect = rect.united(r);
return rect;
}
DocumentContainer::DocumentContainer() = default;
DocumentContainer::~DocumentContainer() = default;
litehtml::uint_ptr DocumentContainer::create_font(const litehtml::tchar_t *faceName,
int size,
int weight,
litehtml::font_style italic,
unsigned int decoration,
litehtml::font_metrics *fm)
{
const QStringList splitNames = QString::fromUtf8(faceName).split(',', QString::SkipEmptyParts);
QStringList familyNames;
std::transform(splitNames.cbegin(),
splitNames.cend(),
std::back_inserter(familyNames),
[this](const QString &s) {
// clean whitespace and quotes
QString name = s.trimmed();
if (name.startsWith('\"'))
name = name.mid(1);
if (name.endsWith('\"'))
name.chop(1);
const QString lowerName = name.toLower();
if (lowerName == "serif")
return serifFont();
if (lowerName == "sans-serif")
return sansSerifFont();
if (lowerName == "monospace")
return monospaceFont();
return name;
});
auto font = new QFont();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
font->setFamilies(familyNames);
#else
static const auto knownFamilies = QFontDatabase().families().toSet();
font->setFamily(familyNames.last());
for (const QString &name : qAsConst(familyNames))
if (knownFamilies.contains(name))
font->setFamily(name);
#endif
font->setPixelSize(size);
font->setWeight(cssWeightToQtWeight(weight));
font->setStyle(toQFontStyle(italic));
if (decoration == litehtml::font_decoration_underline)
font->setUnderline(true);
if (decoration == litehtml::font_decoration_overline)
font->setOverline(true);
if (decoration == litehtml::font_decoration_linethrough)
font->setStrikeOut(true);
if (fm) {
const QFontMetrics metrics(*font);
fm->height = metrics.height();
fm->ascent = metrics.ascent();
fm->descent = metrics.descent();
fm->x_height = metrics.xHeight();
fm->draw_spaces = true;
}
return reinterpret_cast<litehtml::uint_ptr>(font);
}
void DocumentContainer::delete_font(litehtml::uint_ptr hFont)
{
auto font = reinterpret_cast<Font *>(hFont);
delete font;
}
int DocumentContainer::text_width(const litehtml::tchar_t *text, litehtml::uint_ptr hFont)
{
const QFontMetrics fm(toQFont(hFont));
return fm.horizontalAdvance(QString::fromUtf8(text));
}
void DocumentContainer::draw_text(litehtml::uint_ptr hdc,
const litehtml::tchar_t *text,
litehtml::uint_ptr hFont,
litehtml::web_color color,
const litehtml::position &pos)
{
auto painter = toQPainter(hdc);
painter->setFont(toQFont(hFont));
painter->setPen(toQColor(color));
painter->drawText(toQRect(pos), 0, QString::fromUtf8(text));
}
int DocumentContainer::pt_to_px(int pt)
{
return pt;
}
int DocumentContainer::get_default_font_size() const
{
return m_defaultFont.pointSize();
}
const litehtml::tchar_t *DocumentContainer::get_default_font_name() const
{
return m_defaultFontFamilyName.constData();
}
void DocumentContainer::draw_list_marker(litehtml::uint_ptr hdc, const litehtml::list_marker &marker)
{
auto painter = toQPainter(hdc);
if (marker.image.empty()) {
if (marker.marker_type == litehtml::list_style_type_square) {
painter->setPen(Qt::NoPen);
painter->setBrush(toQColor(marker.color));
painter->drawRect(toQRect(marker.pos));
} else if (marker.marker_type == litehtml::list_style_type_disc) {
painter->setPen(Qt::NoPen);
painter->setBrush(toQColor(marker.color));
painter->drawEllipse(toQRect(marker.pos));
} else if (marker.marker_type == litehtml::list_style_type_circle) {
painter->setPen(toQColor(marker.color));
painter->setBrush(Qt::NoBrush);
painter->drawEllipse(toQRect(marker.pos));
} else {
// TODO we do not get information about index and font for e.g. decimal / roman
// at least draw a bullet
painter->setPen(Qt::NoPen);
painter->setBrush(toQColor(marker.color));
painter->drawEllipse(toQRect(marker.pos));
qWarning(log) << "list marker of type" << marker.marker_type << "not supported";
}
} else {
const QPixmap pixmap = getPixmap(QString::fromStdString(marker.image),
QString::fromStdString(marker.baseurl));
painter->drawPixmap(toQRect(marker.pos), pixmap);
}
}
void DocumentContainer::load_image(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
bool redraw_on_ready)
{
const auto qtSrc = QString::fromUtf8(src);
const auto qtBaseUrl = QString::fromUtf8(baseurl);
Q_UNUSED(redraw_on_ready)
qDebug(log) << "load_image:" << QString("src = \"%1\";").arg(qtSrc).toUtf8().constData()
<< QString("base = \"%1\"").arg(qtBaseUrl).toUtf8().constData();
const QUrl url = resolveUrl(qtSrc, qtBaseUrl);
if (m_pixmaps.contains(url))
return;
QPixmap pixmap;
pixmap.loadFromData(m_dataCallback(url));
m_pixmaps.insert(url, pixmap);
}
void DocumentContainer::get_image_size(const litehtml::tchar_t *src,
const litehtml::tchar_t *baseurl,
litehtml::size &sz)
{
const auto qtSrc = QString::fromUtf8(src);
const auto qtBaseUrl = QString::fromUtf8(baseurl);
if (qtSrc.isEmpty()) // for some reason that happens
return;
qDebug(log) << "get_image_size:" << QString("src = \"%1\";").arg(qtSrc).toUtf8().constData()
<< QString("base = \"%1\"").arg(qtBaseUrl).toUtf8().constData();
const QPixmap pm = getPixmap(qtSrc, qtBaseUrl);
sz.width = pm.width();
sz.height = pm.height();
}
void DocumentContainer::drawSelection(QPainter *painter, const QRect &clip) const
{
painter->save();
painter->setClipRect(clip, Qt::IntersectClip);
for (const QRect &r : m_selection.selection) {
const QRect clientRect = r.translated(-m_scrollPosition);
const QPalette palette = m_paletteCallback();
painter->fillRect(clientRect, palette.brush(QPalette::Highlight));
}
painter->restore();
}
void DocumentContainer::draw_background(litehtml::uint_ptr hdc, const litehtml::background_paint &bg)
{
// TODO
auto painter = toQPainter(hdc);
if (bg.is_root) {
// TODO ?
drawSelection(painter, toQRect(bg.border_box));
return;
}
painter->save();
painter->setClipRect(toQRect(bg.clip_box));
const QRegion horizontalMiddle(
QRect(bg.border_box.x,
bg.border_box.y + bg.border_radius.top_left_y,
bg.border_box.width,
bg.border_box.height - bg.border_radius.top_left_y - bg.border_radius.bottom_left_y));
const QRegion horizontalTop(
QRect(bg.border_box.x + bg.border_radius.top_left_x,
bg.border_box.y,
bg.border_box.width - bg.border_radius.top_left_x - bg.border_radius.top_right_x,
bg.border_radius.top_left_y));
const QRegion horizontalBottom(QRect(bg.border_box.x + bg.border_radius.bottom_left_x,
bg.border_box.bottom() - bg.border_radius.bottom_left_y,
bg.border_box.width - bg.border_radius.bottom_left_x
- bg.border_radius.bottom_right_x,
bg.border_radius.bottom_left_y));
const QRegion topLeft(QRect(bg.border_box.left(),
bg.border_box.top(),
2 * bg.border_radius.top_left_x,
2 * bg.border_radius.top_left_y),
QRegion::Ellipse);
const QRegion topRight(QRect(bg.border_box.right() - 2 * bg.border_radius.top_right_x,
bg.border_box.top(),
2 * bg.border_radius.top_right_x,
2 * bg.border_radius.top_right_y),
QRegion::Ellipse);
const QRegion bottomLeft(QRect(bg.border_box.left(),
bg.border_box.bottom() - 2 * bg.border_radius.bottom_left_y,
2 * bg.border_radius.bottom_left_x,
2 * bg.border_radius.bottom_left_y),
QRegion::Ellipse);
const QRegion bottomRight(QRect(bg.border_box.right() - 2 * bg.border_radius.bottom_right_x,
bg.border_box.bottom() - 2 * bg.border_radius.bottom_right_y,
2 * bg.border_radius.bottom_right_x,
2 * bg.border_radius.bottom_right_y),
QRegion::Ellipse);
const QRegion clipRegion = horizontalMiddle.united(horizontalTop)
.united(horizontalBottom)
.united(topLeft)
.united(topRight)
.united(bottomLeft)
.united(bottomRight);
painter->setClipRegion(clipRegion, Qt::IntersectClip);
painter->setPen(Qt::NoPen);
painter->setBrush(toQColor(bg.color));
painter->drawRect(bg.border_box.x, bg.border_box.y, bg.border_box.width, bg.border_box.height);
drawSelection(painter, toQRect(bg.border_box));
if (!bg.image.empty()) {
const QPixmap pixmap = getPixmap(QString::fromStdString(bg.image),
QString::fromStdString(bg.baseurl));
if (bg.repeat == litehtml::background_repeat_no_repeat) {
painter->drawPixmap(QRect(bg.position_x,
bg.position_y,
bg.image_size.width,
bg.image_size.height),
pixmap);
} else if (bg.repeat == litehtml::background_repeat_repeat_x) {
if (bg.image_size.width > 0) {
int x = bg.border_box.left();
while (x <= bg.border_box.right()) {
painter->drawPixmap(QRect(x,
bg.border_box.top(),
bg.image_size.width,
bg.image_size.height),
pixmap);
x += bg.image_size.width;
}
}
} else {
qWarning(log) << "unsupported background repeat" << bg.repeat;
}
}
painter->restore();
}
void DocumentContainer::draw_borders(litehtml::uint_ptr hdc,
const litehtml::borders &borders,
const litehtml::position &draw_pos,
bool root)
{
Q_UNUSED(root)
// TODO: special border styles
auto painter = toQPainter(hdc);
if (borders.top.style != litehtml::border_style_none
&& borders.top.style != litehtml::border_style_hidden) {
painter->setPen(borderPen(borders.top));
painter->drawLine(draw_pos.left() + borders.radius.top_left_x,
draw_pos.top(),
draw_pos.right() - borders.radius.top_right_x,
draw_pos.top());
painter->drawArc(draw_pos.left(),
draw_pos.top(),
2 * borders.radius.top_left_x,
2 * borders.radius.top_left_y,
90 * 16,
90 * 16);
painter->drawArc(draw_pos.right() - 2 * borders.radius.top_right_x,
draw_pos.top(),
2 * borders.radius.top_right_x,
2 * borders.radius.top_right_y,
0,
90 * 16);
}
if (borders.bottom.style != litehtml::border_style_none
&& borders.bottom.style != litehtml::border_style_hidden) {
painter->setPen(borderPen(borders.bottom));
painter->drawLine(draw_pos.left() + borders.radius.bottom_left_x,
draw_pos.bottom(),
draw_pos.right() - borders.radius.bottom_right_x,
draw_pos.bottom());
painter->drawArc(draw_pos.left(),
draw_pos.bottom() - 2 * borders.radius.bottom_left_y,
2 * borders.radius.bottom_left_x,
2 * borders.radius.bottom_left_y,
180 * 16,
90 * 16);
painter->drawArc(draw_pos.right() - 2 * borders.radius.bottom_right_x,
draw_pos.bottom() - 2 * borders.radius.bottom_right_y,
2 * borders.radius.bottom_right_x,
2 * borders.radius.bottom_right_y,
270 * 16,
90 * 16);
}
if (borders.left.style != litehtml::border_style_none
&& borders.left.style != litehtml::border_style_hidden) {
painter->setPen(borderPen(borders.left));
painter->drawLine(draw_pos.left(),
draw_pos.top() + borders.radius.top_left_y,
draw_pos.left(),
draw_pos.bottom() - borders.radius.bottom_left_y);
}
if (borders.right.style != litehtml::border_style_none
&& borders.right.style != litehtml::border_style_hidden) {
painter->setPen(borderPen(borders.right));
painter->drawLine(draw_pos.right(),
draw_pos.top() + borders.radius.top_right_y,
draw_pos.right(),
draw_pos.bottom() - borders.radius.bottom_right_y);
}
}
void DocumentContainer::set_caption(const litehtml::tchar_t *caption)
{
m_caption = QString::fromUtf8(caption);
}
void DocumentContainer::set_base_url(const litehtml::tchar_t *base_url)
{
m_baseUrl = QString::fromUtf8(base_url);
}
void DocumentContainer::link(const std::shared_ptr<litehtml::document> &doc,
const litehtml::element::ptr &el)
{
// TODO
qDebug(log) << "link";
Q_UNUSED(doc)
Q_UNUSED(el)
}
void DocumentContainer::on_anchor_click(const litehtml::tchar_t *url,
const litehtml::element::ptr &el)
{
Q_UNUSED(el)
if (!m_blockLinks)
m_linkCallback(resolveUrl(QString::fromUtf8(url), m_baseUrl));
}
void DocumentContainer::set_cursor(const litehtml::tchar_t *cursor)
{
m_cursorCallback(toQCursor(QString::fromUtf8(cursor)));
}
void DocumentContainer::transform_text(litehtml::tstring &text, litehtml::text_transform tt)
{
// TODO
qDebug(log) << "transform_text";
Q_UNUSED(text)
Q_UNUSED(tt)
}
void DocumentContainer::import_css(litehtml::tstring &text,
const litehtml::tstring &url,
litehtml::tstring &baseurl)
{
const QUrl actualUrl = resolveUrl(QString::fromStdString(url), QString::fromStdString(baseurl));
const QString urlString = actualUrl.toString(QUrl::None);
const int lastSlash = urlString.lastIndexOf('/');
baseurl = urlString.left(lastSlash).toStdString();
text = QString::fromUtf8(m_dataCallback(actualUrl)).toStdString();
}
void DocumentContainer::set_clip(const litehtml::position &pos,
const litehtml::border_radiuses &bdr_radius,
bool valid_x,
bool valid_y)
{
// TODO
qDebug(log) << "set_clip";
Q_UNUSED(pos)
Q_UNUSED(bdr_radius)
Q_UNUSED(valid_x)
Q_UNUSED(valid_y)
}
void DocumentContainer::del_clip()
{
// TODO
qDebug(log) << "del_clip";
}
void DocumentContainer::get_client_rect(litehtml::position &client) const
{
client = {m_clientRect.x(), m_clientRect.y(), m_clientRect.width(), m_clientRect.height()};
}
std::shared_ptr<litehtml::element> DocumentContainer::create_element(
const litehtml::tchar_t *tag_name,
const litehtml::string_map &attributes,
const std::shared_ptr<litehtml::document> &doc)
{
// TODO
qDebug(log) << "create_element" << QString::fromUtf8(tag_name);
Q_UNUSED(attributes)
Q_UNUSED(doc)
return {};
}
void DocumentContainer::get_media_features(litehtml::media_features &media) const
{
media.type = litehtml::media_type_screen;
// TODO
qDebug(log) << "get_media_features";
}
void DocumentContainer::get_language(litehtml::tstring &language, litehtml::tstring &culture) const
{
// TODO
qDebug(log) << "get_language";
Q_UNUSED(language)
Q_UNUSED(culture)
}
void DocumentContainer::setScrollPosition(const QPoint &pos)
{
m_scrollPosition = pos;
}
void DocumentContainer::setDocument(const QByteArray &data, litehtml::context *context)
{
m_pixmaps.clear();
m_selection = {};
m_document = litehtml::document::createFromUTF8(data.constData(), this, context);
}
litehtml::document::ptr DocumentContainer::document() const
{
return m_document;
}
void DocumentContainer::render(int width, int height)
{
m_clientRect = {0, 0, width, height};
if (!m_document)
return;
m_document->render(width);
m_selection.update();
}
QVector<QRect> DocumentContainer::mousePressEvent(const QPoint &documentPos,
const QPoint &viewportPos,
Qt::MouseButton button)
{
if (!m_document || button != Qt::LeftButton)
return {};
QVector<QRect> redrawRects;
// selection
if (m_selection.isValid())
redrawRects.append(m_selection.boundingRect());
m_selection = {};
m_selection.selectionStartDocumentPos = documentPos;
m_selection.startElem = deepest_child_at_point(m_document,
documentPos,
viewportPos,
m_selection.mode);
// post to litehtml
litehtml::position::vector redrawBoxes;
if (m_document->on_lbutton_down(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y(),
redrawBoxes)) {
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
}
return redrawRects;
}
QVector<QRect> DocumentContainer::mouseMoveEvent(const QPoint &documentPos,
const QPoint &viewportPos)
{
if (!m_document)
return {};
QVector<QRect> redrawRects;
// selection
if (m_selection.isSelecting
|| (!m_selection.selectionStartDocumentPos.isNull()
&& (m_selection.selectionStartDocumentPos - documentPos).manhattanLength()
>= kDragDistance
&& m_selection.startElem.element)) {
const Selection::Element element = deepest_child_at_point(m_document,
documentPos,
viewportPos,
m_selection.mode);
if (element.element) {
m_selection.endElem = element;
redrawRects.append(m_selection.boundingRect()); // redraw old selection area
m_selection.update();
redrawRects.append(m_selection.boundingRect());
}
m_selection.isSelecting = true;
}
litehtml::position::vector redrawBoxes;
if (m_document->on_mouse_over(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y(),
redrawBoxes)) {
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
}
return redrawRects;
}
QVector<QRect> DocumentContainer::mouseReleaseEvent(const QPoint &documentPos,
const QPoint &viewportPos,
Qt::MouseButton button)
{
if (!m_document || button != Qt::LeftButton)
return {};
QVector<QRect> redrawRects;
// selection
m_selection.isSelecting = false;
m_selection.selectionStartDocumentPos = {};
if (m_selection.isValid())
m_blockLinks = true;
else
m_selection = {};
litehtml::position::vector redrawBoxes;
if (m_document->on_lbutton_up(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y(),
redrawBoxes)) {
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
}
m_blockLinks = false;
return redrawRects;
}
QVector<QRect> DocumentContainer::mouseDoubleClickEvent(const QPoint &documentPos,
const QPoint &viewportPos,
Qt::MouseButton button)
{
if (!m_document || button != Qt::LeftButton)
return {};
QVector<QRect> redrawRects;
m_selection = {};
m_selection.mode = Selection::Mode::Word;
const Selection::Element element = deepest_child_at_point(m_document,
documentPos,
viewportPos,
m_selection.mode);
if (element.element) {
m_selection.startElem = element;
m_selection.endElem = m_selection.startElem;
m_selection.isSelecting = true;
m_selection.update();
if (m_selection.isValid())
redrawRects.append(m_selection.boundingRect());
} else {
if (m_selection.isValid())
redrawRects.append(m_selection.boundingRect());
m_selection = {};
}
return redrawRects;
}
QVector<QRect> DocumentContainer::leaveEvent()
{
if (!m_document)
return {};
litehtml::position::vector redrawBoxes;
if (m_document->on_mouse_leave(redrawBoxes)) {
QVector<QRect> redrawRects;
for (const litehtml::position &box : redrawBoxes)
redrawRects.append(toQRect(box));
return redrawRects;
}
return {};
}
QUrl DocumentContainer::linkAt(const QPoint &documentPos, const QPoint &viewportPos)
{
if (!m_document)
return {};
const litehtml::element::ptr element = m_document->root()->get_element_by_point(documentPos.x(),
documentPos.y(),
viewportPos.x(),
viewportPos.y());
const char *href = element->get_attr("href");
if (href)
return resolveUrl(QString::fromUtf8(href), m_baseUrl);
return {};
}
QString DocumentContainer::caption() const
{
return m_caption;
}
QString DocumentContainer::selectedText() const
{
return m_selection.text;
}
void DocumentContainer::setDefaultFont(const QFont &font)
{
m_defaultFont = font;
m_defaultFontFamilyName = m_defaultFont.family().toUtf8();
}
QFont DocumentContainer::defaultFont() const
{
return m_defaultFont;
}
void DocumentContainer::setDataCallback(const DocumentContainer::DataCallback &callback)
{
m_dataCallback = callback;
}
void DocumentContainer::setCursorCallback(const DocumentContainer::CursorCallback &callback)
{
m_cursorCallback = callback;
}
void DocumentContainer::setLinkCallback(const DocumentContainer::LinkCallback &callback)
{
m_linkCallback = callback;
}
void DocumentContainer::setPaletteCallback(const DocumentContainer::PaletteCallback &callback)
{
m_paletteCallback = callback;
}
QPixmap DocumentContainer::getPixmap(const QString &imageUrl, const QString &baseUrl)
{
const QString actualBaseurl = baseUrl.isEmpty() ? m_baseUrl : baseUrl;
const QUrl url = resolveUrl(imageUrl, baseUrl);
if (!m_pixmaps.contains(url)) {
qWarning(log) << "draw_background: pixmap not loaded for" << url;
return {};
}
return m_pixmaps.value(url);
}
QString DocumentContainer::serifFont() const
{
// TODO make configurable
return "Times New Roman";
}
QString DocumentContainer::sansSerifFont() const
{
// TODO make configurable
return "Arial";
}
QString DocumentContainer::monospaceFont() const
{
// TODO make configurable
return "Courier";
}
QUrl DocumentContainer::resolveUrl(const QString &url, const QString &baseUrl) const
{
const QUrl qurl(url);
if (qurl.isRelative() && !qurl.path(QUrl::FullyEncoded).isEmpty()) {
const QString actualBaseurl = baseUrl.isEmpty() ? m_baseUrl : baseUrl;
QUrl resolvedUrl(actualBaseurl + '/' + url);
resolvedUrl.setPath(QDir::cleanPath(resolvedUrl.path(QUrl::FullyEncoded)));
return resolvedUrl;
}
return qurl;
}