Files
harbour-calibreweb/src/pagedtextitem.cpp
T

247 lines
6.5 KiB
C++

#include "pagedtextitem.h"
#include <QPainter>
#include <QAbstractTextDocumentLayout>
#include <QTextBlock>
#include <QTextLayout>
#include <QFont>
#include <QPointF>
#include <QRectF>
PagedTextItem::PagedTextItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
, m_fontPixelSize(16)
, m_page(0)
, m_pendingPosition(-1)
, m_layoutPageHeight(1)
{
// nessun margine interno: i margini li impone la geometria dell'item in QML
m_doc.setDocumentMargin(0);
applyFont();
}
void PagedTextItem::applyFont()
{
QFont f = m_doc.defaultFont();
if (!m_fontFamily.isEmpty())
f.setFamily(m_fontFamily);
f.setPixelSize(qMax(4, m_fontPixelSize));
m_doc.setDefaultFont(f);
}
void PagedTextItem::setHtml(const QString &html)
{
if (m_html == html)
return;
m_html = html;
m_page = 0;
m_doc.setHtml(m_html);
if (!m_baseUrl.isEmpty())
m_doc.setBaseUrl(QUrl(m_baseUrl));
relayout(false);
emit htmlChanged();
}
void PagedTextItem::setBaseUrl(const QString &url)
{
if (m_baseUrl == url)
return;
m_baseUrl = url;
m_doc.setBaseUrl(QUrl(url));
emit baseUrlChanged();
update();
}
void PagedTextItem::setFontFamily(const QString &family)
{
if (m_fontFamily == family)
return;
// posizione letta PRIMA di toccare il font: il layout e' ancora coerente
const int keep = positionAtPage(m_page);
m_fontFamily = family;
applyFont();
relayoutKeeping(keep);
emit fontFamilyChanged();
}
void PagedTextItem::setFontPixelSize(int px)
{
px = qMax(4, px);
if (m_fontPixelSize == px)
return;
// posizione letta PRIMA del reflow (vedi setFontFamily)
const int keep = positionAtPage(m_page);
m_fontPixelSize = px;
applyFont();
// reflow: mantiene la posizione di lettura per carattere
relayoutKeeping(keep);
emit fontPixelSizeChanged();
}
void PagedTextItem::setTextColor(const QColor &color)
{
if (m_textColor == color)
return;
m_textColor = color;
emit textColorChanged();
update();
}
void PagedTextItem::setPage(int page)
{
const int last = pageCount() - 1;
page = qBound(0, page, qMax(0, last));
if (m_page == page)
return;
m_page = page;
emit pageChanged();
emit positionChanged();
update();
}
void PagedTextItem::nextPage()
{
setPage(m_page + 1);
}
void PagedTextItem::previousPage()
{
setPage(m_page - 1);
}
void PagedTextItem::goToPosition(int pos)
{
if (width() <= 0 || height() <= 0) {
// geometria non ancora risolta: applica al primo layout
m_pendingPosition = pos;
return;
}
setPage(pageForPosition(pos));
}
int PagedTextItem::pageCount() const
{
const int n = m_doc.pageCount();
return n > 0 ? n : 1;
}
int PagedTextItem::characterCount() const
{
return m_doc.characterCount();
}
int PagedTextItem::position() const
{
return positionAtPage(m_page);
}
qreal PagedTextItem::pageHeight() const
{
// altezza dell'ULTIMO layout: se la geometria e' appena cambiata ma il
// documento non e' ancora stato ri-impaginato, le letture restano coerenti
return m_layoutPageHeight > 0 ? m_layoutPageHeight : qMax(qreal(1), height());
}
int PagedTextItem::positionAtPage(int page) const
{
if (m_doc.isEmpty() || width() <= 0 || height() <= 0)
return 0;
QAbstractTextDocumentLayout *layout = m_doc.documentLayout();
if (!layout)
return 0;
const qreal ph = pageHeight();
// punto appena dentro la pagina: hitTest dà il carattere piu' vicino
const QPointF probe(qMin(qreal(4), width() / 2), page * ph + 1);
const int pos = layout->hitTest(probe, Qt::FuzzyHit);
if (pos < 0)
return 0;
return qBound(0, pos, qMax(0, m_doc.characterCount() - 1));
}
int PagedTextItem::pageForPosition(int pos) const
{
if (m_doc.isEmpty() || width() <= 0 || height() <= 0)
return 0;
const int len = m_doc.characterCount();
const int at = qBound(0, pos, qMax(0, len - 1));
// findBlock invece di QTextCursor: metodo const (niente cast sul documento)
const QTextBlock block = m_doc.findBlock(at);
QRectF rect;
if (QAbstractTextDocumentLayout *layout = m_doc.documentLayout())
rect = layout->blockBoundingRect(block);
qreal y = rect.top();
if (block.layout()) {
// y della riga che contiene il carattere, relativa al blocco: cosi'
// anche i paragrafi lunghi (spezzati su piu' pagine) sono precisi
const int inBlock = qBound(0, at - block.position(),
qMax(0, block.length() - 1));
const QTextLine line = block.layout()->lineForTextPosition(inBlock);
if (line.isValid())
y += line.y();
}
return qMax(0, int(y / pageHeight()));
}
void PagedTextItem::clampPage()
{
const int last = pageCount() - 1;
m_page = qBound(0, m_page, qMax(0, last));
}
void PagedTextItem::relayoutKeeping(int pos)
{
if (width() <= 0 || height() <= 0)
return;
m_doc.setPageSize(QSizeF(width(), height()));
m_layoutPageHeight = qMax(qreal(1), height());
if (m_pendingPosition >= 0) {
// posizione chiesta prima del layout (ripristino all'apertura)
m_page = pageForPosition(m_pendingPosition);
m_pendingPosition = -1;
} else if (pos >= 0) {
m_page = pageForPosition(pos);
}
clampPage();
emit layoutChanged();
emit positionChanged();
update();
}
void PagedTextItem::relayout(bool keepPosition)
{
relayoutKeeping(keepPosition ? positionAtPage(m_page) : -1);
}
void PagedTextItem::geometryChanged(const QRectF &newGeometry,
const QRectF &oldGeometry)
{
QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);
if (newGeometry.size() != oldGeometry.size())
relayout(true);
}
void PagedTextItem::renderPage(QPainter *painter) const
{
if (!painter || width() <= 0 || height() <= 0 || m_doc.isEmpty())
return;
const qreal ph = pageHeight();
painter->save();
painter->setRenderHint(QPainter::TextAntialiasing, true);
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setClipRect(QRectF(0, 0, width(), ph));
painter->translate(0, -m_page * ph);
QAbstractTextDocumentLayout::PaintContext ctx;
// il clip del contesto e' in coordinate documento
ctx.clip = QRectF(0, m_page * ph, width(), ph);
if (m_textColor.isValid())
ctx.palette.setColor(QPalette::Text, m_textColor);
m_doc.documentLayout()->draw(painter, ctx);
painter->restore();
}
void PagedTextItem::paint(QPainter *painter)
{
renderPage(painter);
}