Skip to content

Commit

Permalink
Improvements to text layouting in QML
Browse files Browse the repository at this point in the history
Allow more control over the text layouting process in QML.

Give access to every text line through a hook, this gives the
opportunity to position and resize a line as it is being laid out.
It is then possible to lay out the text in columns or around other
objects.

Task-number: QTBUG-21367
Change-Id: I56dc0c1c4b575dc06360c135098024d0324d3656
Reviewed-on: http://codereview.qt-project.org/5351
Reviewed-by: Yann Bodson <yann.bodson@nokia.com>
Sanity-Review: Yann Bodson <yann.bodson@nokia.com>
  • Loading branch information
Yann Bodson authored and Qt by Nokia committed Oct 11, 2011
1 parent 6a9d97f commit 6fcaca3
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 17 deletions.
7 changes: 5 additions & 2 deletions doc/src/declarative/whatsnew.qdoc
Expand Up @@ -99,8 +99,6 @@ Added topMargin, bottomMargin, leftMargin, rightMargin, xOrigin, yOrigin propert
Image has two new properties: horizontalAlignment and verticalAlignment. It also has a new value for
fillMode (Image.Pad) that does not transform the image.

Text will now automatically switch to StyledText instead of RichText if textFormat is set to AutoText.

Grid now has rowSpacing and columnSpacing properties.

Positioners now have attached properties that can be used to determine a subitem's location within a
Expand All @@ -112,6 +110,11 @@ Loader improvements:
- now only emits the \c sourceChanged signal when the source is changed and the
\c sourceComponentChanged signal when the sourceComponent is changed. It used to emit both signals when one of the properties was changed.

Text improvements:
- a \c onLineLaidOut handler is called for every line during the layout process. This gives the opportunity to position and resize a line as it is being laid out.
- a \c doLayout method was added to trigger the layout from Javascript.
- now automatically switch to StyledText instead of RichText if textFormat is set to AutoText.

PathView now has a \c currentItem property

ListView and GridView now have headerItem and footerItem properties (the instantiated
Expand Down
1 change: 1 addition & 0 deletions src/declarative/items/qsgitemsmodule.cpp
Expand Up @@ -159,6 +159,7 @@ static void qt_sgitems_defineModule(const char *uri, int major, int minor)
qmlRegisterType<QDeclarativePathElement>();
qmlRegisterType<QDeclarativeCurve>();
qmlRegisterType<QSGScaleGrid>();
qmlRegisterType<QSGTextLine>();
#ifndef QT_NO_VALIDATOR
qmlRegisterType<QValidator>();
#endif
Expand Down
199 changes: 185 additions & 14 deletions src/declarative/items/qsgtext.cpp
Expand Up @@ -106,11 +106,11 @@ QSGTextPrivate::QSGTextPrivate()
imageCacheDirty(false), updateOnComponentComplete(true),
richText(false), styledText(false), singleline(false), cacheAllTextAsImage(true), internalWidthUpdate(false),
requireImplicitWidth(false), truncated(false), hAlignImplicit(true), rightToLeftText(false),
layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), naturalWidth(0),
doc(0), nodeType(NodeIsNull)
layoutTextElided(false), richTextAsImage(false), textureImageCacheDirty(false), textHasChanged(true),
naturalWidth(0), doc(0), textLine(0), nodeType(NodeIsNull)

#if defined(Q_OS_MAC)
, layoutThread(0)
, layoutThread(0), paintingThread(0)
#endif

{
Expand Down Expand Up @@ -203,6 +203,7 @@ QSet<QUrl> QSGTextDocumentWithImageResources::errors;

QSGTextPrivate::~QSGTextPrivate()
{
delete textLine; textLine = 0;
}

qreal QSGTextPrivate::getImplicitWidth() const
Expand Down Expand Up @@ -248,7 +249,10 @@ void QSGTextPrivate::updateLayout()
layout.setText(tmp);
} else {
singleline = false;
QDeclarativeStyledText::parse(text, layout);
if (textHasChanged) {
QDeclarativeStyledText::parse(text, layout);
textHasChanged = false;
}
}
} else {
ensureDoc();
Expand Down Expand Up @@ -362,6 +366,157 @@ void QSGTextPrivate::updateSize()
q->update();
}

QSGTextLine::QSGTextLine()
: QObject(), m_line(0), m_height(0)
{
}

void QSGTextLine::setLine(QTextLine *line)
{
m_line = line;
}

int QSGTextLine::number() const
{
if (m_line)
return m_line->lineNumber();
return 0;
}

qreal QSGTextLine::width() const
{
if (m_line)
return m_line->width();
return 0;
}

void QSGTextLine::setWidth(qreal width)
{
if (m_line)
m_line->setLineWidth(width);
}

qreal QSGTextLine::height() const
{
if (m_height)
return m_height;
if (m_line)
return m_line->height();
return 0;
}

void QSGTextLine::setHeight(qreal height)
{
if (m_line)
m_line->setPosition(QPointF(m_line->x(), m_line->y() - m_line->height() + height));
m_height = height;
}

qreal QSGTextLine::x() const
{
if (m_line)
return m_line->x();
return 0;
}

void QSGTextLine::setX(qreal x)
{
if (m_line)
m_line->setPosition(QPointF(x, m_line->y()));
}

qreal QSGTextLine::y() const
{
if (m_line)
return m_line->y();
return 0;
}

void QSGTextLine::setY(qreal y)
{
if (m_line)
m_line->setPosition(QPointF(m_line->x(), y));
}

void QSGText::doLayout()
{
Q_D(QSGText);
d->updateSize();
}

/*!
\qmlsignal QtQuick2::Text::onLineLaidOut(line)
This handler is called for every line during the layout process.
This gives the opportunity to position and resize a line as it is being laid out.
It can for example be used to create columns or lay out text around objects.
The properties of a line are:
\list
\o number (read-only)
\o x
\o y
\o width
\o height
\endlist
For example, this will move the first 5 lines of a text element by 100 pixels to the right:
\code
onLineLaidOut: {
if (line.number < 5) {
line.x = line.x + 100
line.width = line.width - 100
}
}
\endcode
*/

bool QSGTextPrivate::isLineLaidOutConnected()
{
static int idx = this->signalIndex("lineLaidOut(QSGTextLine*)");
return this->isSignalConnected(idx);
}

void QSGTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, qreal elideWidth = 0)
{
Q_Q(QSGText);

#if defined(Q_OS_MAC)
if (QThread::currentThread() != paintingThread) {
#endif
if (!line.lineNumber())
linesRects.clear();

if (!textLine)
textLine = new QSGTextLine;
textLine->setLine(&line);
textLine->setY(height);
textLine->setHeight(0);

// use the text item's width by default if it has one and wrap is on
if (q->widthValid() && q->wrapMode() != QSGText::NoWrap)
textLine->setWidth(q->width() - elideWidth);
else
textLine->setWidth(INT_MAX);
if (lineHeight != 1.0)
textLine->setHeight((lineHeightMode == QSGText::FixedHeight) ? lineHeight : line.height() * lineHeight);

emit q->lineLaidOut(textLine);

linesRects << QRectF(textLine->x(), textLine->y(), textLine->width(), textLine->height());
height += textLine->height();

#if defined(Q_OS_MAC)
} else {
if (line.lineNumber() < linesRects.count()) {
QRectF r = linesRects.at(line.lineNumber());
line.setLineWidth(r.width());
line.setPosition(r.topLeft());
}
}
#endif
}

/*!
Lays out the QSGTextPrivate::layout QTextLayout in the constraints of the QSGText.
Expand Down Expand Up @@ -420,6 +575,9 @@ QRect QSGTextPrivate::setupTextLayout()
layout.setText(elidedText);
}

qreal height = 0;
bool customLayout = isLineLaidOutConnected();

if (maximumLineCountValid) {
layout.beginLayout();
if (!lineWidth)
Expand All @@ -432,17 +590,23 @@ QRect QSGTextPrivate::setupTextLayout()
break;

visibleCount++;
if (lineWidth)

if (customLayout)
setupCustomLineGeometry(line, height);
else if (lineWidth)
line.setLineWidth(lineWidth);
visibleTextLength += line.textLength();

if (--linesLeft == 0) {
if (visibleTextLength < text.length()) {
truncate = true;
if (elideMode==QSGText::ElideRight && q->widthValid()) {
if (elideMode == QSGText::ElideRight && q->widthValid()) {
qreal elideWidth = fm.width(elideChar);
// Need to correct for alignment
line.setLineWidth(lineWidth-elideWidth);
if (customLayout)
setupCustomLineGeometry(line, height, elideWidth);
else
line.setLineWidth(lineWidth - elideWidth);
if (layout.text().mid(line.textStart(), line.textLength()).isRightToLeft()) {
line.setPosition(QPointF(line.position().x() + elideWidth, line.position().y()));
elidePos.setX(line.naturalTextRect().left() - elideWidth);
Expand All @@ -468,26 +632,32 @@ QRect QSGTextPrivate::setupTextLayout()
if (!line.isValid())
break;
visibleCount++;
if (lineWidth)
line.setLineWidth(lineWidth);
if (customLayout)
setupCustomLineGeometry(line, height);
else {
if (lineWidth)
line.setLineWidth(lineWidth);
}
}
layout.endLayout();
}

qreal height = 0;
height = 0;
QRectF br;
for (int i = 0; i < layout.lineCount(); ++i) {
QTextLine line = layout.lineAt(i);
// set line spacing
line.setPosition(QPointF(line.position().x(), height));
if (!customLayout)
line.setPosition(QPointF(line.position().x(), height));
if (elideText && i == layout.lineCount()-1) {
elidePos.setY(height + fm.ascent());
br = br.united(QRectF(elidePos, QSizeF(fm.width(elideChar), fm.ascent())));
}
br = br.united(line.naturalTextRect());
height += (lineHeightMode == QSGText::FixedHeight) ? lineHeight : line.height() * lineHeight;
}
br.setHeight(height);
if (!customLayout)
br.setHeight(height);

if (!q->widthValid())
naturalWidth = br.width();
Expand Down Expand Up @@ -965,6 +1135,7 @@ void QSGText::setText(const QString &n)
}
d->determineHorizontalAlignment();
}
d->textHasChanged = true;
d->updateLayout();
emit textChanged(d->text);
}
Expand Down Expand Up @@ -1500,7 +1671,8 @@ QSGNode *QSGText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)

// We need to make sure the layout is done in the current thread
#if defined(Q_OS_MAC)
if (d->layoutThread != QThread::currentThread())
d->paintingThread = QThread::currentThread();
if (d->layoutThread != d->paintingThread)
d->updateLayout();
#endif

Expand Down Expand Up @@ -1555,7 +1727,6 @@ QSGNode *QSGText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
node->setMatrix(QMatrix4x4());

if (d->richText) {

d->ensureDoc();
node->addTextDocument(bounds.topLeft(), d->doc, QColor(), d->style, d->styleColor);

Expand Down
37 changes: 37 additions & 0 deletions src/declarative/items/qsgtext_p.h
Expand Up @@ -55,6 +55,7 @@ QT_BEGIN_NAMESPACE

QT_MODULE(Declarative)
class QSGTextPrivate;
class QSGTextLine;
class Q_DECLARATIVE_PRIVATE_EXPORT QSGText : public QSGImplicitSizeItem
{
Q_OBJECT
Expand Down Expand Up @@ -172,6 +173,7 @@ class Q_DECLARATIVE_PRIVATE_EXPORT QSGText : public QSGImplicitSizeItem
qreal paintedHeight() const;

QRectF boundingRect() const;
Q_INVOKABLE void doLayout();

Q_SIGNALS:
void textChanged(const QString &text);
Expand All @@ -192,6 +194,7 @@ class Q_DECLARATIVE_PRIVATE_EXPORT QSGText : public QSGImplicitSizeItem
void lineHeightChanged(qreal lineHeight);
void lineHeightModeChanged(LineHeightMode mode);
void effectiveHorizontalAlignmentChanged();
void lineLaidOut(QSGTextLine *line);

protected:
void mousePressEvent(QMouseEvent *event);
Expand All @@ -206,9 +209,43 @@ class Q_DECLARATIVE_PRIVATE_EXPORT QSGText : public QSGImplicitSizeItem
Q_DECLARE_PRIVATE(QSGText)
};

class QTextLine;
class Q_AUTOTEST_EXPORT QSGTextLine : public QObject
{
Q_OBJECT
Q_PROPERTY(int number READ number)
Q_PROPERTY(qreal width READ width WRITE setWidth)
Q_PROPERTY(qreal height READ height WRITE setHeight)
Q_PROPERTY(qreal x READ x WRITE setX)
Q_PROPERTY(qreal y READ y WRITE setY)

public:
QSGTextLine();

void setLine(QTextLine* line);
int number() const;

qreal width() const;
void setWidth(qreal width);

qreal height() const;
void setHeight(qreal height);

qreal x() const;
void setX(qreal x);

qreal y() const;
void setY(qreal y);

private:
QTextLine *m_line;
qreal m_height;
};

QT_END_NAMESPACE

QML_DECLARE_TYPE(QSGText)
QML_DECLARE_TYPE(QSGTextLine)

QT_END_HEADER

Expand Down

0 comments on commit 6fcaca3

Please sign in to comment.