Ensure correct horizontal alignment when onLineLaidOut is used.
[qt:qtdeclarative.git] / src / quick / items / qquicktext.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquicktext_p.h"
43 #include "qquicktext_p_p.h"
44
45 #include <QtQuick/private/qsgcontext_p.h>
46 #include <private/qqmlglobal_p.h>
47 #include <private/qsgadaptationlayer_p.h>
48 #include "qquicktextnode_p.h"
49 #include "qquickimage_p_p.h"
50 #include "qquicktextutil_p.h"
51
52 #include <QtQuick/private/qsgtexture_p.h>
53
54 #include <QtQml/qqmlinfo.h>
55 #include <QtGui/qevent.h>
56 #include <QtGui/qabstracttextdocumentlayout.h>
57 #include <QtGui/qpainter.h>
58 #include <QtGui/qtextdocument.h>
59 #include <QtGui/qtextobject.h>
60 #include <QtGui/qtextcursor.h>
61 #include <QtGui/qguiapplication.h>
62 #include <QtGui/qinputmethod.h>
63
64 #include <private/qtextengine_p.h>
65 #include <private/qquickstyledtext_p.h>
66 #include <QtQuick/private/qquickpixmapcache_p.h>
67
68 #include <qmath.h>
69 #include <limits.h>
70
71 QT_BEGIN_NAMESPACE
72
73
74 const QChar QQuickTextPrivate::elideChar = QChar(0x2026);
75
76 QQuickTextPrivate::QQuickTextPrivate()
77     : elideLayout(0), textLine(0), lineWidth(0)
78     , color(0xFF000000), linkColor(0xFF0000FF), styleColor(0xFF000000)
79     , lineCount(1), multilengthEos(-1)
80     , elideMode(QQuickText::ElideNone), hAlign(QQuickText::AlignLeft), vAlign(QQuickText::AlignTop)
81     , format(QQuickText::AutoText), wrapMode(QQuickText::NoWrap)
82     , style(QQuickText::Normal)
83     , renderType(QQuickText::QtRendering)
84     , updateType(UpdatePaintNode)
85     , maximumLineCountValid(false), updateOnComponentComplete(true), richText(false)
86     , styledText(false), widthExceeded(false), heightExceeded(false), internalWidthUpdate(false)
87     , requireImplicitSize(false), implicitWidthValid(false), implicitHeightValid(false)
88     , truncated(false), hAlignImplicit(true), rightToLeftText(false)
89     , layoutTextElided(false), textHasChanged(true), needToUpdateLayout(false), formatModifiesFontSize(false)
90 {
91 }
92
93 QQuickTextPrivate::ExtraData::ExtraData()
94     : lineHeight(1.0)
95     , doc(0)
96     , minimumPixelSize(12)
97     , minimumPointSize(12)
98     , nbActiveDownloads(0)
99     , maximumLineCount(INT_MAX)
100     , lineHeightMode(QQuickText::ProportionalHeight)
101     , fontSizeMode(QQuickText::FixedSize)
102 {
103 }
104
105 void QQuickTextPrivate::init()
106 {
107     Q_Q(QQuickText);
108     q->setAcceptedMouseButtons(Qt::LeftButton);
109     q->setFlag(QQuickItem::ItemHasContents);
110 }
111
112 QQuickTextDocumentWithImageResources::QQuickTextDocumentWithImageResources(QQuickItem *parent)
113 : QTextDocument(parent), outstanding(0)
114 {
115     setUndoRedoEnabled(false);
116     documentLayout()->registerHandler(QTextFormat::ImageObject, this);
117 }
118
119 QQuickTextDocumentWithImageResources::~QQuickTextDocumentWithImageResources()
120 {
121     if (!m_resources.isEmpty())
122         qDeleteAll(m_resources);
123 }
124
125 QVariant QQuickTextDocumentWithImageResources::loadResource(int type, const QUrl &name)
126 {
127     QQmlContext *context = qmlContext(parent());
128     QUrl url = m_baseUrl.resolved(name);
129
130     if (type == QTextDocument::ImageResource) {
131         QQuickPixmap *p = loadPixmap(context, url);
132         return p->image();
133     }
134
135     return QTextDocument::loadResource(type,url); // The *resolved* URL
136 }
137
138 void QQuickTextDocumentWithImageResources::requestFinished()
139 {
140     outstanding--;
141     if (outstanding == 0) {
142         markContentsDirty(0, characterCount());
143         emit imagesLoaded();
144     }
145 }
146
147 void QQuickTextDocumentWithImageResources::clear()
148 {
149     clearResources();
150
151     QTextDocument::clear();
152 }
153
154
155 QSizeF QQuickTextDocumentWithImageResources::intrinsicSize(
156         QTextDocument *, int, const QTextFormat &format)
157 {
158     if (format.isImageFormat()) {
159         QTextImageFormat imageFormat = format.toImageFormat();
160
161         const bool hasWidth = imageFormat.hasProperty(QTextFormat::ImageWidth);
162         const int width = qRound(imageFormat.width());
163         const bool hasHeight = imageFormat.hasProperty(QTextFormat::ImageHeight);
164         const int height = qRound(imageFormat.height());
165
166         QSizeF size(width, height);
167         if (!hasWidth || !hasHeight) {
168             QQmlContext *context = qmlContext(parent());
169             QUrl url = m_baseUrl.resolved(QUrl(imageFormat.name()));
170
171             QQuickPixmap *p = loadPixmap(context, url);
172             if (!p->isReady()) {
173                 if (!hasWidth)
174                     size.setWidth(16);
175                 if (!hasHeight)
176                     size.setHeight(16);
177                 return size;
178             }
179             QSize implicitSize = p->implicitSize();
180
181             if (!hasWidth) {
182                 if (!hasHeight)
183                     size.setWidth(implicitSize.width());
184                 else
185                     size.setWidth(qRound(height * (implicitSize.width() / (qreal) implicitSize.height())));
186             }
187             if (!hasHeight) {
188                 if (!hasWidth)
189                     size.setHeight(implicitSize.height());
190                 else
191                     size.setHeight(qRound(width * (implicitSize.height() / (qreal) implicitSize.width())));
192             }
193         }
194         return size;
195     }
196     return QSizeF();
197 }
198
199 void QQuickTextDocumentWithImageResources::drawObject(
200         QPainter *, const QRectF &, QTextDocument *, int, const QTextFormat &)
201 {
202 }
203
204 QImage QQuickTextDocumentWithImageResources::image(const QTextImageFormat &format)
205 {
206     QQmlContext *context = qmlContext(parent());
207     QUrl url = m_baseUrl.resolved(QUrl(format.name()));
208
209     QQuickPixmap *p = loadPixmap(context, url);
210     return p->image();
211 }
212
213 void QQuickTextDocumentWithImageResources::setBaseUrl(const QUrl &url, bool clear)
214 {
215     m_baseUrl = url;
216     if (clear) {
217         clearResources();
218         markContentsDirty(0, characterCount());
219     }
220 }
221
222 QQuickPixmap *QQuickTextDocumentWithImageResources::loadPixmap(
223         QQmlContext *context, const QUrl &url)
224 {
225
226     QHash<QUrl, QQuickPixmap *>::Iterator iter = m_resources.find(url);
227
228     if (iter == m_resources.end()) {
229         QQuickPixmap *p = new QQuickPixmap(context->engine(), url);
230         iter = m_resources.insert(url, p);
231
232         if (p->isLoading()) {
233             p->connectFinished(this, SLOT(requestFinished()));
234             outstanding++;
235         }
236     }
237
238     QQuickPixmap *p = *iter;
239     if (p->isError()) {
240         if (!errors.contains(url)) {
241             errors.insert(url);
242             qmlInfo(parent()) << p->error();
243         }
244     }
245     return p;
246 }
247
248 void QQuickTextDocumentWithImageResources::clearResources()
249 {
250     foreach (QQuickPixmap *pixmap, m_resources)
251         pixmap->clear(this);
252     qDeleteAll(m_resources);
253     m_resources.clear();
254     outstanding = 0;
255 }
256
257 void QQuickTextDocumentWithImageResources::setText(const QString &text)
258 {
259     clearResources();
260
261 #ifndef QT_NO_TEXTHTMLPARSER
262     setHtml(text);
263 #else
264     setPlainText(text);
265 #endif
266 }
267
268 QSet<QUrl> QQuickTextDocumentWithImageResources::errors;
269
270 QQuickTextPrivate::~QQuickTextPrivate()
271 {
272     delete elideLayout;
273     delete textLine; textLine = 0;
274     qDeleteAll(imgTags);
275     imgTags.clear();
276 }
277
278 qreal QQuickTextPrivate::getImplicitWidth() const
279 {
280     if (!requireImplicitSize) {
281         // We don't calculate implicitWidth unless it is required.
282         // We need to force a size update now to ensure implicitWidth is calculated
283         QQuickTextPrivate *me = const_cast<QQuickTextPrivate*>(this);
284         me->requireImplicitSize = true;
285         me->updateSize();
286     }
287     return implicitWidth;
288 }
289
290 qreal QQuickTextPrivate::getImplicitHeight() const
291 {
292     if (!requireImplicitSize) {
293         QQuickTextPrivate *me = const_cast<QQuickTextPrivate*>(this);
294         me->requireImplicitSize = true;
295         me->updateSize();
296     }
297     return implicitHeight;
298 }
299
300 /*!
301     \qmlproperty enumeration QtQuick::Text::renderType
302
303     Override the default rendering type for this component.
304
305     Supported render types are:
306     \list
307     \li Text.QtRendering - the default
308     \li Text.NativeRendering
309     \endlist
310
311     Select Text.NativeRendering if you prefer text to look native on the target platform and do
312     not require advanced features such as transformation of the text. Using such features in
313     combination with the NativeRendering render type will lend poor and sometimes pixelated
314     results.
315
316     On HighDpi "retina" displays and mobile and embedded platforms, this property is ignored
317     and QtRendering is always used.
318 */
319 QQuickText::RenderType QQuickText::renderType() const
320 {
321     Q_D(const QQuickText);
322     return d->renderType;
323 }
324
325 void QQuickText::setRenderType(QQuickText::RenderType renderType)
326 {
327     Q_D(QQuickText);
328     if (d->renderType == renderType)
329         return;
330
331     d->renderType = renderType;
332     emit renderTypeChanged();
333
334     if (isComponentComplete())
335         d->updateLayout();
336 }
337
338 void QQuickText::q_imagesLoaded()
339 {
340     Q_D(QQuickText);
341     d->updateLayout();
342 }
343
344 void QQuickTextPrivate::updateLayout()
345 {
346     Q_Q(QQuickText);
347     if (!q->isComponentComplete()) {
348         updateOnComponentComplete = true;
349         return;
350     }
351     updateOnComponentComplete = false;
352     layoutTextElided = false;
353
354     if (!visibleImgTags.isEmpty())
355         visibleImgTags.clear();
356     needToUpdateLayout = false;
357
358     // Setup instance of QTextLayout for all cases other than richtext
359     if (!richText) {
360         if (textHasChanged) {
361             if (styledText && !text.isEmpty()) {
362                 layout.setFont(font);
363                 // needs temporary bool because formatModifiesFontSize is in a bit-field
364                 bool fontSizeModified = false;
365                 QQuickStyledText::parse(text, layout, imgTags, q->baseUrl(), qmlContext(q), !maximumLineCountValid, &fontSizeModified);
366                 formatModifiesFontSize = fontSizeModified;
367                 multilengthEos = -1;
368             } else {
369                 layout.clearAdditionalFormats();
370                 if (elideLayout)
371                     elideLayout->clearAdditionalFormats();
372                 QString tmp = text;
373                 multilengthEos = tmp.indexOf(QLatin1Char('\x9c'));
374                 if (multilengthEos != -1) {
375                     tmp = tmp.mid(0, multilengthEos);
376                     tmp.replace(QLatin1Char('\n'), QChar::LineSeparator);
377                 } else if (tmp.contains(QLatin1Char('\n'))) {
378                     // Replace always does a detach.  Checking for the new line character first
379                     // means iterating over those items again if found but prevents a realloc
380                     // otherwise.
381                     tmp.replace(QLatin1Char('\n'), QChar::LineSeparator);
382                 }
383                 layout.setText(tmp);
384             }
385             textHasChanged = false;
386         }
387     } else {
388         ensureDoc();
389         QTextBlockFormat::LineHeightTypes type;
390         type = lineHeightMode() == QQuickText::FixedHeight ? QTextBlockFormat::FixedHeight : QTextBlockFormat::ProportionalHeight;
391         QTextBlockFormat blockFormat;
392         blockFormat.setLineHeight((lineHeightMode() == QQuickText::FixedHeight ? lineHeight() : lineHeight() * 100), type);
393         for (QTextBlock it = extra->doc->begin(); it != extra->doc->end(); it = it.next()) {
394             QTextCursor cursor(it);
395             cursor.mergeBlockFormat(blockFormat);
396         }
397     }
398
399     updateSize();
400
401     if (needToUpdateLayout) {
402         needToUpdateLayout = false;
403         textHasChanged = true;
404         updateLayout();
405     }
406 }
407
408 void QQuickText::imageDownloadFinished()
409 {
410     Q_D(QQuickText);
411
412     (d->extra->nbActiveDownloads)--;
413
414     // when all the remote images have been downloaded,
415     // if one of the sizes was not specified at parsing time
416     // we use the implicit size from pixmapcache and re-layout.
417
418     if (d->extra.isAllocated() && d->extra->nbActiveDownloads == 0) {
419         bool needToUpdateLayout = false;
420         foreach (QQuickStyledTextImgTag *img, d->visibleImgTags) {
421             if (!img->size.isValid()) {
422                 img->size = img->pix->implicitSize();
423                 needToUpdateLayout = true;
424             }
425         }
426
427         if (needToUpdateLayout) {
428             d->textHasChanged = true;
429             d->updateLayout();
430         } else {
431             d->updateType = QQuickTextPrivate::UpdatePaintNode;
432             update();
433         }
434     }
435 }
436
437 void QQuickTextPrivate::updateBaseline(qreal baseline, qreal dy)
438 {
439     Q_Q(QQuickText);
440
441     qreal yoff = 0;
442
443     if (q->heightValid()) {
444         if (vAlign == QQuickText::AlignBottom)
445             yoff = dy;
446         else if (vAlign == QQuickText::AlignVCenter)
447             yoff = dy/2;
448     }
449
450     q->setBaselineOffset(baseline + yoff);
451 }
452
453 void QQuickTextPrivate::updateSize()
454 {
455     Q_Q(QQuickText);
456
457     if (!q->isComponentComplete()) {
458         updateOnComponentComplete = true;
459         return;
460     }
461
462     if (!requireImplicitSize) {
463         emit q->implicitWidthChanged();
464         emit q->implicitHeightChanged();
465         // if the implicitWidth is used, then updateSize() has already been called (recursively)
466         if (requireImplicitSize)
467             return;
468     }
469
470     if (text.isEmpty() && !isLineLaidOutConnected() && fontSizeMode() == QQuickText::FixedSize) {
471         // How much more expensive is it to just do a full layout on an empty string here?
472         // There may be subtle differences in the height and baseline calculations between
473         // QTextLayout and QFontMetrics and the number of variables that can affect the size
474         // and position of a line is increasing.
475         QFontMetricsF fm(font);
476         qreal fontHeight = qCeil(fm.height());  // QScriptLine and therefore QTextLine rounds up
477         if (!richText) {                        // line height, so we will as well.
478             fontHeight = lineHeightMode() == QQuickText::FixedHeight
479                     ? lineHeight()
480                     : fontHeight * lineHeight();
481         }
482         updateBaseline(fm.ascent(), q->height() - fontHeight);
483         q->setImplicitSize(0, fontHeight);
484         layedOutTextRect = QRectF(0, 0, 0, fontHeight);
485         emit q->contentSizeChanged();
486         updateType = UpdatePaintNode;
487         q->update();
488         return;
489     }
490
491     QSizeF size(0, 0);
492     QSizeF previousSize = layedOutTextRect.size();
493
494     //setup instance of QTextLayout for all cases other than richtext
495     if (!richText) {
496         qreal baseline = 0;
497         QRectF textRect = setupTextLayout(&baseline);
498
499         if (internalWidthUpdate)    // probably the result of a binding loop, but by letting it
500             return;      // get this far we'll get a warning to that effect if it is.
501
502         layedOutTextRect = textRect;
503         size = textRect.size();
504         updateBaseline(baseline, q->height() - size.height());
505     } else {
506         widthExceeded = true; // always relayout rich text on width changes..
507         heightExceeded = false; // rich text layout isn't affected by height changes.
508         ensureDoc();
509         extra->doc->setDefaultFont(font);
510         QQuickText::HAlignment horizontalAlignment = q->effectiveHAlign();
511         if (rightToLeftText) {
512             if (horizontalAlignment == QQuickText::AlignLeft)
513                 horizontalAlignment = QQuickText::AlignRight;
514             else if (horizontalAlignment == QQuickText::AlignRight)
515                 horizontalAlignment = QQuickText::AlignLeft;
516         }
517         QTextOption option;
518         option.setAlignment((Qt::Alignment)int(horizontalAlignment | vAlign));
519         option.setWrapMode(QTextOption::WrapMode(wrapMode));
520         option.setUseDesignMetrics(renderType != QQuickText::NativeRendering);
521         extra->doc->setDefaultTextOption(option);
522         qreal naturalWidth = 0;
523         if (requireImplicitSize && q->widthValid()) {
524             extra->doc->setTextWidth(-1);
525             naturalWidth = extra->doc->idealWidth();
526             const bool wasInLayout = internalWidthUpdate;
527             internalWidthUpdate = true;
528             q->setImplicitWidth(naturalWidth);
529             internalWidthUpdate = wasInLayout;
530         }
531         if (internalWidthUpdate)
532             return;
533
534         extra->doc->setPageSize(QSizeF());
535         if (q->widthValid() && (wrapMode != QQuickText::NoWrap || extra->doc->idealWidth() < q->width()))
536             extra->doc->setTextWidth(q->width());
537         else
538             extra->doc->setTextWidth(extra->doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug)
539
540         widthExceeded = extra->doc->textWidth() < extra->doc->idealWidth();
541         QSizeF dsize = extra->doc->size();
542         layedOutTextRect = QRectF(QPointF(0,0), dsize);
543         size = QSizeF(extra->doc->idealWidth(),dsize.height());
544
545         QFontMetricsF fm(font);
546         updateBaseline(fm.ascent(), q->height() - size.height());
547
548         //### need to confirm cost of always setting these for richText
549         internalWidthUpdate = true;
550         qreal iWidth = -1;
551         if (!q->widthValid())
552             iWidth = size.width();
553         if (iWidth > -1)
554             q->setImplicitSize(iWidth, size.height());
555         internalWidthUpdate = false;
556
557         if (iWidth == -1)
558             q->setImplicitHeight(size.height());
559     }
560
561     if (layedOutTextRect.size() != previousSize)
562         emit q->contentSizeChanged();
563     updateType = UpdatePaintNode;
564     q->update();
565 }
566
567 QQuickTextLine::QQuickTextLine()
568     : QObject(), m_line(0), m_height(0)
569 {
570 }
571
572 void QQuickTextLine::setLine(QTextLine *line)
573 {
574     m_line = line;
575 }
576
577 void QQuickTextLine::setLineOffset(int offset)
578 {
579     m_lineOffset = offset;
580 }
581
582 int QQuickTextLine::number() const
583 {
584     if (m_line)
585         return m_line->lineNumber() + m_lineOffset;
586     return 0;
587 }
588
589 qreal QQuickTextLine::width() const
590 {
591     if (m_line)
592         return m_line->width();
593     return 0;
594 }
595
596 void QQuickTextLine::setWidth(qreal width)
597 {
598     if (m_line)
599         m_line->setLineWidth(width);
600 }
601
602 qreal QQuickTextLine::height() const
603 {
604     if (m_height)
605         return m_height;
606     if (m_line)
607         return m_line->height();
608     return 0;
609 }
610
611 void QQuickTextLine::setHeight(qreal height)
612 {
613     if (m_line)
614         m_line->setPosition(QPointF(m_line->x(), m_line->y() - m_line->height() + height));
615     m_height = height;
616 }
617
618 qreal QQuickTextLine::x() const
619 {
620     if (m_line)
621         return m_line->x();
622     return 0;
623 }
624
625 void QQuickTextLine::setX(qreal x)
626 {
627     if (m_line)
628         m_line->setPosition(QPointF(x, m_line->y()));
629 }
630
631 qreal QQuickTextLine::y() const
632 {
633     if (m_line)
634         return m_line->y();
635     return 0;
636 }
637
638 void QQuickTextLine::setY(qreal y)
639 {
640     if (m_line)
641         m_line->setPosition(QPointF(m_line->x(), y));
642 }
643
644 /*!
645     \qmlmethod QtQuick::Text::doLayout()
646
647     Triggers a re-layout of the displayed text.
648 */
649 void QQuickText::doLayout()
650 {
651     Q_D(QQuickText);
652     d->updateSize();
653 }
654
655 bool QQuickTextPrivate::isLineLaidOutConnected()
656 {
657     Q_Q(QQuickText);
658     IS_SIGNAL_CONNECTED(q, QQuickText, lineLaidOut, (QQuickTextLine *));
659 }
660
661 void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, int lineOffset)
662 {
663     Q_Q(QQuickText);
664
665     if (!textLine)
666         textLine = new QQuickTextLine;
667     textLine->setLine(&line);
668     textLine->setY(height);
669     textLine->setHeight(0);
670     textLine->setLineOffset(lineOffset);
671
672     // use the text item's width by default if it has one and wrap is on or text must be aligned
673     if (q->widthValid() && (q->wrapMode() != QQuickText::NoWrap ||
674                             q->effectiveHAlign() != QQuickText::AlignLeft))
675         textLine->setWidth(q->width());
676     else
677         textLine->setWidth(INT_MAX);
678     if (lineHeight() != 1.0)
679         textLine->setHeight((lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : line.height() * lineHeight());
680
681     emit q->lineLaidOut(textLine);
682
683     height += textLine->height();
684 }
685
686 void QQuickTextPrivate::elideFormats(
687         const int start, const int length, int offset, QList<QTextLayout::FormatRange> *elidedFormats)
688 {
689     const int end = start + length;
690     QList<QTextLayout::FormatRange> formats = layout.additionalFormats();
691     for (int i = 0; i < formats.count(); ++i) {
692         QTextLayout::FormatRange format = formats.at(i);
693         const int formatLength = qMin(format.start + format.length, end) - qMax(format.start, start);
694         if (formatLength > 0) {
695             format.start = qMax(offset, format.start - start + offset);
696             format.length = formatLength;
697             elidedFormats->append(format);
698         }
699     }
700 }
701
702 QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, QTextLine *nextLine) const
703 {
704     if (nextLine) {
705         return layout.engine()->elidedText(
706                 Qt::TextElideMode(elideMode),
707                 QFixed::fromReal(lineWidth),
708                 0,
709                 line.textStart(),
710                 line.textLength() + nextLine->textLength());
711     } else {
712         QString elideText = layout.text().mid(line.textStart(), line.textLength());
713         if (!styledText) {
714             // QFontMetrics won't help eliding styled text.
715             elideText[elideText.length() - 1] = elideChar;
716             // Appending the elide character may push the line over the maximum width
717             // in which case the elided text will need to be elided.
718             QFontMetricsF metrics(layout.font());
719             if (metrics.width(elideChar) + line.naturalTextWidth() >= lineWidth)
720                 elideText = metrics.elidedText(elideText, Qt::TextElideMode(elideMode), lineWidth);
721         }
722         return elideText;
723     }
724 }
725
726 /*!
727     Lays out the QQuickTextPrivate::layout QTextLayout in the constraints of the QQuickText.
728
729     Returns the size of the final text.  This can be used to position the text vertically (the text is
730     already absolutely positioned horizontally).
731 */
732
733 QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
734 {
735     Q_Q(QQuickText);
736
737     bool singlelineElide = elideMode != QQuickText::ElideNone && q->widthValid();
738     bool multilineElide = elideMode == QQuickText::ElideRight
739             && q->widthValid()
740             && (q->heightValid() || maximumLineCountValid);
741
742     if ((!requireImplicitSize || (implicitWidthValid && implicitHeightValid))
743             && ((singlelineElide && q->width() <= 0.) || (multilineElide && q->heightValid() && q->height() <= 0.))) {
744         // we are elided and we have a zero width or height
745         widthExceeded = q->widthValid() && q->width() <= 0.;
746         heightExceeded = q->heightValid() && q->height() <= 0.;
747
748         if (!truncated) {
749             truncated = true;
750             emit q->truncatedChanged();
751         }
752         if (lineCount) {
753             lineCount = 0;
754             emit q->lineCountChanged();
755         }
756
757         QFontMetricsF fm(font);
758         qreal height = (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : qCeil(fm.height()) * lineHeight();
759         *baseline = fm.ascent();
760         return QRectF(0, 0, 0, height);
761     }
762
763     bool shouldUseDesignMetrics = renderType != QQuickText::NativeRendering;
764
765     layout.setCacheEnabled(true);
766     QTextOption textOption = layout.textOption();
767     if (textOption.alignment() != q->effectiveHAlign()
768             || textOption.wrapMode() != QTextOption::WrapMode(wrapMode)
769             || textOption.useDesignMetrics() != shouldUseDesignMetrics) {
770         textOption.setAlignment(Qt::Alignment(q->effectiveHAlign()));
771         textOption.setWrapMode(QTextOption::WrapMode(wrapMode));
772         textOption.setUseDesignMetrics(shouldUseDesignMetrics);
773         layout.setTextOption(textOption);
774     }
775     if (layout.font() != font)
776         layout.setFont(font);
777
778     lineWidth = (q->widthValid() || implicitWidthValid) && q->width() > 0
779             ? q->width()
780             : FLT_MAX;
781     qreal maxHeight = q->heightValid() ? q->height() : FLT_MAX;
782
783     const bool customLayout = isLineLaidOutConnected();
784     const bool wasTruncated = truncated;
785
786     bool canWrap = wrapMode != QQuickText::NoWrap && q->widthValid();
787
788     bool horizontalFit = fontSizeMode() & QQuickText::HorizontalFit && q->widthValid();
789     bool verticalFit = fontSizeMode() & QQuickText::VerticalFit
790             && (q->heightValid() || (maximumLineCountValid && canWrap));
791
792     const bool pixelSize = font.pixelSize() != -1;
793     QString layoutText = layout.text();
794
795     int largeFont = pixelSize ? font.pixelSize() : font.pointSize();
796     int smallFont = fontSizeMode() != QQuickText::FixedSize
797             ? qMin(pixelSize ? minimumPixelSize() : minimumPointSize(), largeFont)
798             : largeFont;
799     int scaledFontSize = largeFont;
800
801     widthExceeded = q->width() <= 0 && (singlelineElide || canWrap || horizontalFit);
802     heightExceeded = q->height() <= 0 && (multilineElide || verticalFit);
803
804     QRectF br;
805
806     QFont scaledFont = font;
807
808     int visibleCount = 0;
809     bool elide;
810     qreal height = 0;
811     QString elideText;
812     bool once = true;
813     int elideStart = 0;
814     int elideEnd = 0;
815
816     int eos = multilengthEos;
817
818     // Repeated layouts with reduced font sizes or abbreviated strings may be required if the text
819     // doesn't fit within the item dimensions,  or a binding to implicitWidth/Height changes
820     // the item dimensions.
821     for (;;) {
822         if (!once) {
823             if (pixelSize)
824                 scaledFont.setPixelSize(scaledFontSize);
825             else
826                 scaledFont.setPointSize(scaledFontSize);
827             if (layout.font() != scaledFont)
828                 layout.setFont(scaledFont);
829         }
830
831         layout.beginLayout();
832
833         bool wrapped = false;
834         bool truncateHeight = false;
835         truncated = false;
836         elide = false;
837         int unwrappedLineCount = 1;
838         int maxLineCount = maximumLineCount();
839         height = 0;
840         qreal naturalHeight = 0;
841         qreal previousHeight = 0;
842         br = QRectF();
843
844         QRectF unelidedRect;
845         QTextLine line = layout.createLine();
846         for (visibleCount = 1; ; ++visibleCount) {
847             if (customLayout) {
848                 setupCustomLineGeometry(line, naturalHeight);
849             } else {
850                 setLineGeometry(line, lineWidth, naturalHeight);
851             }
852
853             unelidedRect = br.united(line.naturalTextRect());
854
855             // Elide the previous line if the accumulated height of the text exceeds the height
856             // of the element.
857             if (multilineElide && naturalHeight > maxHeight && visibleCount > 1) {
858                 elide = true;
859                 heightExceeded = true;
860                 if (eos != -1)  // There's an abbreviated string available, skip the rest as it's
861                     break;      // all going to be discarded.
862
863                 truncated = true;
864                 truncateHeight = true;
865
866                 visibleCount -= 1;
867
868                 QTextLine previousLine = layout.lineAt(visibleCount - 1);
869                 elideText = layoutText.at(line.textStart() - 1) != QChar::LineSeparator
870                         ? elidedText(lineWidth, previousLine, &line)
871                         : elidedText(lineWidth, previousLine);
872                 elideStart = previousLine.textStart();
873                 // elideEnd isn't required for right eliding.
874
875                 height = previousHeight;
876                 break;
877             }
878
879             const QTextLine previousLine = line;
880             line = layout.createLine();
881             if (!line.isValid()) {
882                 if (singlelineElide && visibleCount == 1 && previousLine.naturalTextWidth() > lineWidth) {
883                     // Elide a single previousLine of  text if its width exceeds the element width.
884                     elide = true;
885                     widthExceeded = true;
886                     if (eos != -1) // There's an abbreviated string available.
887                         break;
888
889                     truncated = true;
890                     elideText = layout.engine()->elidedText(
891                             Qt::TextElideMode(elideMode),
892                             QFixed::fromReal(lineWidth),
893                             0,
894                             previousLine.textStart(),
895                             previousLine.textLength());
896                     elideStart = previousLine.textStart();
897                     elideEnd = elideStart + previousLine.textLength();
898                 } else {
899                     br = unelidedRect;
900                     height = naturalHeight;
901                 }
902                 break;
903             } else {
904                 const bool wrappedLine = layoutText.at(line.textStart() - 1) != QChar::LineSeparator;
905                 wrapped |= wrappedLine;
906
907                 if (!wrappedLine)
908                     ++unwrappedLineCount;
909
910                 // Stop if the maximum number of lines has been reached and elide the last line
911                 // if enabled.
912                 if (visibleCount == maxLineCount) {
913                     truncated = true;
914                     heightExceeded |= wrapped;
915
916                     if (multilineElide) {
917                         elide = true;
918                         if (eos != -1)  // There's an abbreviated string available
919                             break;
920                         elideText = wrappedLine
921                                 ? elidedText(lineWidth, previousLine, &line)
922                                 : elidedText(lineWidth, previousLine);
923                         elideStart = previousLine.textStart();
924                         // elideEnd isn't required for right eliding.
925                     } else {
926                         br = unelidedRect;
927                         height = naturalHeight;
928                     }
929                     break;
930                 }
931             }
932             br = unelidedRect;
933             previousHeight = height;
934             height = naturalHeight;
935         }
936         widthExceeded |= wrapped;
937
938         // Save the implicit size of the text on the first layout only.
939         if (once) {
940             once = false;
941
942             // If implicit sizes are required layout any additional lines up to the maximum line
943             // count.
944             if ((requireImplicitSize) && line.isValid() && unwrappedLineCount < maxLineCount) {
945                 // Layout the remainder of the wrapped lines up to maxLineCount to get the implicit
946                 // height.
947                 for (int lineCount = layout.lineCount(); lineCount < maxLineCount; ++lineCount) {
948                     line = layout.createLine();
949                     if (!line.isValid())
950                         break;
951                     if (layoutText.at(line.textStart() - 1) == QChar::LineSeparator)
952                         ++unwrappedLineCount;
953                     setLineGeometry(line, lineWidth, naturalHeight);
954                 }
955
956                 // Create the remainder of the unwrapped lines up to maxLineCount to get the
957                 // implicit width.
958                 const int eol = line.isValid()
959                         ? line.textStart() + line.textLength()
960                         : layoutText.length();
961                 if (eol < layoutText.length() && layoutText.at(eol) != QChar::LineSeparator)
962                     line = layout.createLine();
963                 for (; line.isValid() && unwrappedLineCount <= maxLineCount; ++unwrappedLineCount)
964                     line = layout.createLine();
965             }
966             layout.endLayout();
967
968             const qreal naturalWidth = layout.maximumWidth();
969
970             bool wasInLayout = internalWidthUpdate;
971             internalWidthUpdate = true;
972             q->setImplicitSize(naturalWidth, naturalHeight);
973             internalWidthUpdate = wasInLayout;
974
975             // Update any variables that are dependent on the validity of the width or height.
976             singlelineElide = elideMode != QQuickText::ElideNone && q->widthValid();
977             multilineElide = elideMode == QQuickText::ElideRight
978                     && q->widthValid()
979                     && (q->heightValid() || maximumLineCountValid);
980             canWrap = wrapMode != QQuickText::NoWrap && q->widthValid();
981
982             horizontalFit = fontSizeMode() & QQuickText::HorizontalFit && q->widthValid();
983             verticalFit = fontSizeMode() & QQuickText::VerticalFit
984                     && (q->heightValid() || (maximumLineCountValid && canWrap));
985
986             const qreal oldWidth = lineWidth;
987             const qreal oldHeight = maxHeight;
988
989             lineWidth = q->widthValid() && q->width() > 0 ? q->width() : naturalWidth;
990             maxHeight = q->heightValid() ? q->height() : FLT_MAX;
991
992             // If the width of the item has changed and it's possible the result of wrapping,
993             // eliding, or scaling has changed do another layout.
994             if ((lineWidth < qMin(oldWidth, naturalWidth) || (widthExceeded && lineWidth > oldWidth))
995                     && (singlelineElide || multilineElide || canWrap || horizontalFit)) {
996                 widthExceeded = false;
997                 heightExceeded = false;
998                 continue;
999             }
1000
1001             // If the height of the item has changed and it's possible the result of eliding,
1002             // line count truncation or scaling has changed, do another layout.
1003             if ((maxHeight < qMin(oldHeight, naturalHeight) || (heightExceeded && maxHeight > oldHeight))
1004                     && (multilineElide || (canWrap && maximumLineCountValid))) {
1005                 widthExceeded = false;
1006                 heightExceeded = false;
1007                 continue;
1008             }
1009
1010             // If the horizontal alignment is not left and the width was not valid we need to relayout
1011             // now that we know the maximum line width.
1012             if (!implicitWidthValid && lineCount > 1 && q->effectiveHAlign() != QQuickText::AlignLeft) {
1013                 widthExceeded = false;
1014                 heightExceeded = false;
1015                 continue;
1016             }
1017         } else {
1018             layout.endLayout();
1019         }
1020
1021         // If the next needs to be elided and there's an abbreviated string available
1022         // go back and do another layout with the abbreviated string.
1023         if (eos != -1 && elide) {
1024             int start = eos + 1;
1025             eos = text.indexOf(QLatin1Char('\x9c'),  start);
1026             layoutText = text.mid(start, eos != -1 ? eos - start : -1);
1027             layoutText.replace(QLatin1Char('\n'), QChar::LineSeparator);
1028             layout.setText(layoutText);
1029             textHasChanged = true;
1030             continue;
1031         }
1032
1033         br.moveTop(0);
1034
1035         if (!horizontalFit && !verticalFit)
1036             break;
1037
1038         // Try and find a font size that better fits the dimensions of the element.
1039         if (horizontalFit) {
1040             if (unelidedRect.width() > lineWidth || (!verticalFit && wrapped)) {
1041                 widthExceeded = true;
1042                 largeFont = scaledFontSize - 1;
1043                 if (smallFont > largeFont)
1044                     break;
1045                 scaledFontSize = (smallFont + largeFont) / 2;
1046                 if (pixelSize)
1047                     scaledFont.setPixelSize(scaledFontSize);
1048                 else
1049                     scaledFont.setPointSize(scaledFontSize);
1050                 continue;
1051             } else if (!verticalFit) {
1052                 smallFont = scaledFontSize;
1053                 if (smallFont == largeFont)
1054                     break;
1055                 scaledFontSize = (smallFont + largeFont + 1) / 2;
1056             }
1057         }
1058
1059         if (verticalFit) {
1060             if (truncateHeight || unelidedRect.height() > maxHeight) {
1061                 heightExceeded = true;
1062                 largeFont = scaledFontSize - 1;
1063                 if (smallFont > largeFont)
1064                     break;
1065                 scaledFontSize = (smallFont + largeFont) / 2;
1066
1067             } else {
1068                 smallFont = scaledFontSize;
1069                 if (smallFont == largeFont)
1070                     break;
1071                 scaledFontSize = (smallFont + largeFont + 1) / 2;
1072             }
1073         }
1074     }
1075
1076     implicitWidthValid = true;
1077     implicitHeightValid = true;
1078
1079     if (eos != multilengthEos)
1080         truncated = true;
1081
1082     if (elide) {
1083         if (!elideLayout) {
1084             elideLayout = new QTextLayout;
1085             elideLayout->setCacheEnabled(true);
1086         }
1087         if (styledText) {
1088             QList<QTextLayout::FormatRange> formats;
1089             switch (elideMode) {
1090             case QQuickText::ElideRight:
1091                 elideFormats(elideStart, elideText.length() - 1, 0, &formats);
1092                 break;
1093             case QQuickText::ElideLeft:
1094                 elideFormats(elideEnd - elideText.length() + 1, elideText.length() - 1, 1, &formats);
1095                 break;
1096             case QQuickText::ElideMiddle: {
1097                 const int index = elideText.indexOf(elideChar);
1098                 if (index != -1) {
1099                     elideFormats(elideStart, index, 0, &formats);
1100                     elideFormats(
1101                             elideEnd - elideText.length() + index + 1,
1102                             elideText.length() - index - 1,
1103                             index + 1,
1104                             &formats);
1105                 }
1106                 break;
1107             }
1108             default:
1109                 break;
1110             }
1111             elideLayout->setAdditionalFormats(formats);
1112         }
1113
1114         elideLayout->setFont(layout.font());
1115         elideLayout->setTextOption(layout.textOption());
1116         elideLayout->setText(elideText);
1117         elideLayout->beginLayout();
1118
1119         QTextLine elidedLine = elideLayout->createLine();
1120         elidedLine.setPosition(QPointF(0, height));
1121         if (customLayout) {
1122             setupCustomLineGeometry(elidedLine, height, visibleCount - 1);
1123         } else {
1124             setLineGeometry(elidedLine, lineWidth, height);
1125         }
1126         elideLayout->endLayout();
1127
1128         br = br.united(elidedLine.naturalTextRect());
1129
1130         if (visibleCount == 1)
1131             layout.clearLayout();
1132     } else {
1133         delete elideLayout;
1134         elideLayout = 0;
1135     }
1136
1137     QTextLine firstLine = visibleCount == 1 && elideLayout
1138             ? elideLayout->lineAt(0)
1139             : layout.lineAt(0);
1140     Q_ASSERT(firstLine.isValid());
1141     *baseline = firstLine.y() + firstLine.ascent();
1142
1143     if (!customLayout)
1144         br.setHeight(height);
1145
1146     //Update the number of visible lines
1147     if (lineCount != visibleCount) {
1148         lineCount = visibleCount;
1149         emit q->lineCountChanged();
1150     }
1151
1152     if (truncated != wasTruncated)
1153         emit q->truncatedChanged();
1154
1155     return br;
1156 }
1157
1158 void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height)
1159 {
1160     Q_Q(QQuickText);
1161     line.setLineWidth(lineWidth);
1162
1163     if (imgTags.isEmpty()) {
1164         line.setPosition(QPointF(line.position().x(), height));
1165         height += (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : line.height() * lineHeight();
1166         return;
1167     }
1168
1169     qreal textTop = 0;
1170     qreal textHeight = line.height();
1171     qreal totalLineHeight = textHeight;
1172
1173     QList<QQuickStyledTextImgTag *> imagesInLine;
1174
1175     foreach (QQuickStyledTextImgTag *image, imgTags) {
1176         if (image->position >= line.textStart() &&
1177             image->position < line.textStart() + line.textLength()) {
1178
1179             if (!image->pix) {
1180                 QUrl url = q->baseUrl().resolved(image->url);
1181                 image->pix = new QQuickPixmap(qmlEngine(q), url, image->size);
1182                 if (image->pix->isLoading()) {
1183                     image->pix->connectFinished(q, SLOT(imageDownloadFinished()));
1184                     if (!extra.isAllocated() || !extra->nbActiveDownloads)
1185                         extra.value().nbActiveDownloads = 0;
1186                     extra->nbActiveDownloads++;
1187                 } else if (image->pix->isReady()) {
1188                     if (!image->size.isValid()) {
1189                         image->size = image->pix->implicitSize();
1190                         // if the size of the image was not explicitly set, we need to
1191                         // call updateLayout() once again.
1192                         needToUpdateLayout = true;
1193                     }
1194                 } else if (image->pix->isError()) {
1195                     qmlInfo(q) << image->pix->error();
1196                 }
1197             }
1198
1199             qreal ih = qreal(image->size.height());
1200             if (image->align == QQuickStyledTextImgTag::Top)
1201                 image->pos.setY(0);
1202             else if (image->align == QQuickStyledTextImgTag::Middle)
1203                 image->pos.setY((textHeight / 2.0) - (ih / 2.0));
1204             else
1205                 image->pos.setY(textHeight - ih);
1206             imagesInLine << image;
1207             textTop = qMax(textTop, qAbs(image->pos.y()));
1208         }
1209     }
1210
1211     foreach (QQuickStyledTextImgTag *image, imagesInLine) {
1212         totalLineHeight = qMax(totalLineHeight, textTop + image->pos.y() + image->size.height());
1213         image->pos.setX(line.cursorToX(image->position));
1214         image->pos.setY(image->pos.y() + height + textTop);
1215         visibleImgTags << image;
1216     }
1217
1218     line.setPosition(QPointF(line.position().x(), height + textTop));
1219     height += (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : totalLineHeight * lineHeight();
1220 }
1221
1222 /*!
1223     Ensures the QQuickTextPrivate::doc variable is set to a valid text document
1224 */
1225 void QQuickTextPrivate::ensureDoc()
1226 {
1227     if (!extra.isAllocated() || !extra->doc) {
1228         Q_Q(QQuickText);
1229         extra.value().doc = new QQuickTextDocumentWithImageResources(q);
1230         extra->doc->setPageSize(QSizeF(0, 0));
1231         extra->doc->setDocumentMargin(0);
1232         extra->doc->setBaseUrl(q->baseUrl());
1233         qmlobject_connect(extra->doc, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()),
1234                           q, QQuickText, SLOT(q_imagesLoaded()));
1235     }
1236 }
1237
1238 /*!
1239     \qmltype Text
1240     \instantiates QQuickText
1241     \inqmlmodule QtQuick
1242     \ingroup qtquick-visual
1243     \inherits Item
1244     \brief Specifies how to add formatted text to a scene
1245
1246     Text items can display both plain and rich text. For example, red text with
1247     a specific font and size can be defined like this:
1248
1249     \qml
1250     Text {
1251         text: "Hello World!"
1252         font.family: "Helvetica"
1253         font.pointSize: 24
1254         color: "red"
1255     }
1256     \endqml
1257
1258     Rich text is defined using HTML-style markup:
1259
1260     \qml
1261     Text {
1262         text: "<b>Hello</b> <i>World!</i>"
1263     }
1264     \endqml
1265
1266     \image declarative-text.png
1267
1268     If height and width are not explicitly set, Text will attempt to determine how
1269     much room is needed and set it accordingly. Unless \l wrapMode is set, it will always
1270     prefer width to height (all text will be placed on a single line).
1271
1272     The \l elide property can alternatively be used to fit a single line of
1273     plain text to a set width.
1274
1275     Note that the \l{Supported HTML Subset} is limited. Also, if the text contains
1276     HTML img tags that load remote images, the text is reloaded.
1277
1278     Text provides read-only text. For editable text, see \l TextEdit.
1279
1280     \sa {declarative/text/fonts}{Fonts example}
1281 */
1282 QQuickText::QQuickText(QQuickItem *parent)
1283 : QQuickImplicitSizeItem(*(new QQuickTextPrivate), parent)
1284 {
1285     Q_D(QQuickText);
1286     d->init();
1287 }
1288
1289 QQuickText::~QQuickText()
1290 {
1291 }
1292
1293 /*!
1294   \qmlproperty bool QtQuick::Text::clip
1295   This property holds whether the text is clipped.
1296
1297   Note that if the text does not fit in the bounding rectangle it will be abruptly chopped.
1298
1299   If you want to display potentially long text in a limited space, you probably want to use \c elide instead.
1300 */
1301
1302 /*!
1303     \qmlsignal QtQuick::Text::onLineLaidOut(object line)
1304
1305     This handler is called for each line of text that is laid out during the layout
1306     process. The specified \a line object provides more details about the line that
1307     is currently being laid out.
1308
1309     This gives the opportunity to position and resize a line as it is being laid out.
1310     It can for example be used to create columns or lay out text around objects.
1311
1312     The properties of the specified \a line object are:
1313     \list
1314     \li number (read-only)
1315     \li x
1316     \li y
1317     \li width
1318     \li height
1319     \endlist
1320
1321     For example, this will move the first 5 lines of a Text item by 100 pixels to the right:
1322     \code
1323     onLineLaidOut: {
1324         if (line.number < 5) {
1325             line.x = line.x + 100
1326             line.width = line.width - 100
1327         }
1328     }
1329     \endcode
1330 */
1331
1332 /*!
1333     \qmlsignal QtQuick::Text::onLinkActivated(string link)
1334
1335     This handler is called when the user clicks on a link embedded in the text.
1336     The link must be in rich text or HTML format and the
1337     \a link string provides access to the particular link.
1338
1339     \snippet qml/text/onLinkActivated.qml 0
1340
1341     The example code will display the text
1342     "See the \l{http://qt-project.org}{Qt Project website}."
1343
1344     Clicking on the highlighted link will output
1345     \tt{http://qt-project.org link activated} to the console.
1346 */
1347
1348 /*!
1349     \qmlproperty string QtQuick::Text::font.family
1350
1351     Sets the family name of the font.
1352
1353     The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
1354     If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
1355     If the family isn't available a family will be set using the font matching algorithm.
1356 */
1357
1358 /*!
1359     \qmlproperty bool QtQuick::Text::font.bold
1360
1361     Sets whether the font weight is bold.
1362 */
1363
1364 /*!
1365     \qmlproperty enumeration QtQuick::Text::font.weight
1366
1367     Sets the font's weight.
1368
1369     The weight can be one of:
1370     \list
1371     \li Font.Light
1372     \li Font.Normal - the default
1373     \li Font.DemiBold
1374     \li Font.Bold
1375     \li Font.Black
1376     \endlist
1377
1378     \qml
1379     Text { text: "Hello"; font.weight: Font.DemiBold }
1380     \endqml
1381 */
1382
1383 /*!
1384     \qmlproperty bool QtQuick::Text::font.italic
1385
1386     Sets whether the font has an italic style.
1387 */
1388
1389 /*!
1390     \qmlproperty bool QtQuick::Text::font.underline
1391
1392     Sets whether the text is underlined.
1393 */
1394
1395 /*!
1396     \qmlproperty bool QtQuick::Text::font.strikeout
1397
1398     Sets whether the font has a strikeout style.
1399 */
1400
1401 /*!
1402     \qmlproperty real QtQuick::Text::font.pointSize
1403
1404     Sets the font size in points. The point size must be greater than zero.
1405 */
1406
1407 /*!
1408     \qmlproperty int QtQuick::Text::font.pixelSize
1409
1410     Sets the font size in pixels.
1411
1412     Using this function makes the font device dependent.
1413     Use \c pointSize to set the size of the font in a device independent manner.
1414 */
1415
1416 /*!
1417     \qmlproperty real QtQuick::Text::font.letterSpacing
1418
1419     Sets the letter spacing for the font.
1420
1421     Letter spacing changes the default spacing between individual letters in the font.
1422     A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
1423 */
1424
1425 /*!
1426     \qmlproperty real QtQuick::Text::font.wordSpacing
1427
1428     Sets the word spacing for the font.
1429
1430     Word spacing changes the default spacing between individual words.
1431     A positive value increases the word spacing by a corresponding amount of pixels,
1432     while a negative value decreases the inter-word spacing accordingly.
1433 */
1434
1435 /*!
1436     \qmlproperty enumeration QtQuick::Text::font.capitalization
1437
1438     Sets the capitalization for the text.
1439
1440     \list
1441     \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
1442     \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
1443     \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
1444     \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
1445     \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
1446     \endlist
1447
1448     \qml
1449     Text { text: "Hello"; font.capitalization: Font.AllLowercase }
1450     \endqml
1451 */
1452 QFont QQuickText::font() const
1453 {
1454     Q_D(const QQuickText);
1455     return d->sourceFont;
1456 }
1457
1458 void QQuickText::setFont(const QFont &font)
1459 {
1460     Q_D(QQuickText);
1461     if (d->sourceFont == font)
1462         return;
1463
1464     d->sourceFont = font;
1465     QFont oldFont = d->font;
1466     d->font = font;
1467
1468     if (d->font.pointSizeF() != -1) {
1469         // 0.5pt resolution
1470         qreal size = qRound(d->font.pointSizeF()*2.0);
1471         d->font.setPointSizeF(size/2.0);
1472     }
1473
1474     if (oldFont != d->font) {
1475         // if the format changes the size of the text
1476         // with headings or <font> tag, we need to re-parse
1477         if (d->formatModifiesFontSize)
1478             d->textHasChanged = true;
1479         d->implicitWidthValid = false;
1480         d->implicitHeightValid = false;
1481         d->updateLayout();
1482     }
1483
1484     emit fontChanged(d->sourceFont);
1485 }
1486
1487 /*!
1488     \qmlproperty string QtQuick::Text::text
1489
1490     The text to display. Text supports both plain and rich text strings.
1491
1492     The item will try to automatically determine whether the text should
1493     be treated as styled text. This determination is made using Qt::mightBeRichText().
1494 */
1495 QString QQuickText::text() const
1496 {
1497     Q_D(const QQuickText);
1498     return d->text;
1499 }
1500
1501 void QQuickText::setText(const QString &n)
1502 {
1503     Q_D(QQuickText);
1504     if (d->text == n)
1505         return;
1506
1507     d->richText = d->format == RichText;
1508     d->styledText = d->format == StyledText || (d->format == AutoText && Qt::mightBeRichText(n));
1509     d->text = n;
1510     if (isComponentComplete()) {
1511         if (d->richText) {
1512             d->ensureDoc();
1513             d->extra->doc->setText(n);
1514             d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
1515         } else {
1516             d->rightToLeftText = d->text.isRightToLeft();
1517         }
1518         d->determineHorizontalAlignment();
1519     }
1520     d->textHasChanged = true;
1521     d->implicitWidthValid = false;
1522     d->implicitHeightValid = false;
1523     qDeleteAll(d->imgTags);
1524     d->imgTags.clear();
1525     d->updateLayout();
1526     setAcceptHoverEvents(d->richText || d->styledText);
1527     emit textChanged(d->text);
1528 }
1529
1530 /*!
1531     \qmlproperty color QtQuick::Text::color
1532
1533     The text color.
1534
1535     An example of green text defined using hexadecimal notation:
1536     \qml
1537     Text {
1538         color: "#00FF00"
1539         text: "green text"
1540     }
1541     \endqml
1542
1543     An example of steel blue text defined using an SVG color name:
1544     \qml
1545     Text {
1546         color: "steelblue"
1547         text: "blue text"
1548     }
1549     \endqml
1550 */
1551 QColor QQuickText::color() const
1552 {
1553     Q_D(const QQuickText);
1554     return QColor::fromRgba(d->color);
1555 }
1556
1557 void QQuickText::setColor(const QColor &color)
1558 {
1559     Q_D(QQuickText);
1560     QRgb rgb = color.rgba();
1561     if (d->color == rgb)
1562         return;
1563
1564     d->color = rgb;
1565     if (isComponentComplete())  {
1566         d->updateType = QQuickTextPrivate::UpdatePaintNode;
1567         update();
1568     }
1569     emit colorChanged();
1570 }
1571
1572 /*!
1573     \qmlproperty color QtQuick::Text::linkColor
1574
1575     The color of links in the text.
1576
1577     This property works with the StyledText \l textFormat, but not with RichText.
1578     Link color in RichText can be specified by including CSS style tags in the
1579     text.
1580 */
1581
1582 QColor QQuickText::linkColor() const
1583 {
1584     Q_D(const QQuickText);
1585     return QColor::fromRgba(d->linkColor);
1586 }
1587
1588 void QQuickText::setLinkColor(const QColor &color)
1589 {
1590     Q_D(QQuickText);
1591     QRgb rgb = color.rgba();
1592     if (d->linkColor == rgb)
1593         return;
1594
1595     d->linkColor = rgb;
1596     update();
1597     emit linkColorChanged();
1598 }
1599
1600 /*!
1601     \qmlproperty enumeration QtQuick::Text::style
1602
1603     Set an additional text style.
1604
1605     Supported text styles are:
1606     \list
1607     \li Text.Normal - the default
1608     \li Text.Outline
1609     \li Text.Raised
1610     \li Text.Sunken
1611     \endlist
1612
1613     \qml
1614     Row {
1615         Text { font.pointSize: 24; text: "Normal" }
1616         Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" }
1617         Text { font.pointSize: 24; text: "Outline";style: Text.Outline; styleColor: "red" }
1618         Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" }
1619     }
1620     \endqml
1621
1622     \image declarative-textstyle.png
1623 */
1624 QQuickText::TextStyle QQuickText::style() const
1625 {
1626     Q_D(const QQuickText);
1627     return d->style;
1628 }
1629
1630 void QQuickText::setStyle(QQuickText::TextStyle style)
1631 {
1632     Q_D(QQuickText);
1633     if (d->style == style)
1634         return;
1635
1636     d->style = style;
1637     if (isComponentComplete()) {
1638         d->updateType = QQuickTextPrivate::UpdatePaintNode;
1639         update();
1640     }
1641     emit styleChanged(d->style);
1642 }
1643
1644 /*!
1645     \qmlproperty color QtQuick::Text::styleColor
1646
1647     Defines the secondary color used by text styles.
1648
1649     \c styleColor is used as the outline color for outlined text, and as the
1650     shadow color for raised or sunken text. If no style has been set, it is not
1651     used at all.
1652
1653     \qml
1654     Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" }
1655     \endqml
1656
1657     \sa style
1658  */
1659 QColor QQuickText::styleColor() const
1660 {
1661     Q_D(const QQuickText);
1662     return QColor::fromRgba(d->styleColor);
1663 }
1664
1665 void QQuickText::setStyleColor(const QColor &color)
1666 {
1667     Q_D(QQuickText);
1668     QRgb rgb = color.rgba();
1669     if (d->styleColor == rgb)
1670         return;
1671
1672     d->styleColor = rgb;
1673     if (isComponentComplete()) {
1674         d->updateType = QQuickTextPrivate::UpdatePaintNode;
1675         update();
1676     }
1677     emit styleColorChanged();
1678 }
1679
1680 /*!
1681     \qmlproperty enumeration QtQuick::Text::horizontalAlignment
1682     \qmlproperty enumeration QtQuick::Text::verticalAlignment
1683     \qmlproperty enumeration QtQuick::Text::effectiveHorizontalAlignment
1684
1685     Sets the horizontal and vertical alignment of the text within the Text items
1686     width and height. By default, the text is vertically aligned to the top. Horizontal
1687     alignment follows the natural alignment of the text, for example text that is read
1688     from left to right will be aligned to the left.
1689
1690     The valid values for \c horizontalAlignment are \c Text.AlignLeft, \c Text.AlignRight, \c Text.AlignHCenter and
1691     \c Text.AlignJustify.  The valid values for \c verticalAlignment are \c Text.AlignTop, \c Text.AlignBottom
1692     and \c Text.AlignVCenter.
1693
1694     Note that for a single line of text, the size of the text is the area of the text. In this common case,
1695     all alignments are equivalent. If you want the text to be, say, centered in its parent, then you will
1696     need to either modify the Item::anchors, or set horizontalAlignment to Text.AlignHCenter and bind the width to
1697     that of the parent.
1698
1699     When using the attached property LayoutMirroring::enabled to mirror application
1700     layouts, the horizontal alignment of text will also be mirrored. However, the property
1701     \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
1702     of Text, use the read-only property \c effectiveHorizontalAlignment.
1703 */
1704 QQuickText::HAlignment QQuickText::hAlign() const
1705 {
1706     Q_D(const QQuickText);
1707     return d->hAlign;
1708 }
1709
1710 void QQuickText::setHAlign(HAlignment align)
1711 {
1712     Q_D(QQuickText);
1713     bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
1714     d->hAlignImplicit = false;
1715     if (d->setHAlign(align, forceAlign) && isComponentComplete())
1716         d->updateLayout();
1717 }
1718
1719 void QQuickText::resetHAlign()
1720 {
1721     Q_D(QQuickText);
1722     d->hAlignImplicit = true;
1723     if (isComponentComplete() && d->determineHorizontalAlignment())
1724         d->updateLayout();
1725 }
1726
1727 QQuickText::HAlignment QQuickText::effectiveHAlign() const
1728 {
1729     Q_D(const QQuickText);
1730     QQuickText::HAlignment effectiveAlignment = d->hAlign;
1731     if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
1732         switch (d->hAlign) {
1733         case QQuickText::AlignLeft:
1734             effectiveAlignment = QQuickText::AlignRight;
1735             break;
1736         case QQuickText::AlignRight:
1737             effectiveAlignment = QQuickText::AlignLeft;
1738             break;
1739         default:
1740             break;
1741         }
1742     }
1743     return effectiveAlignment;
1744 }
1745
1746 bool QQuickTextPrivate::setHAlign(QQuickText::HAlignment alignment, bool forceAlign)
1747 {
1748     Q_Q(QQuickText);
1749     if (hAlign != alignment || forceAlign) {
1750         QQuickText::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
1751         hAlign = alignment;
1752
1753         emit q->horizontalAlignmentChanged(hAlign);
1754         if (oldEffectiveHAlign != q->effectiveHAlign())
1755             emit q->effectiveHorizontalAlignmentChanged();
1756         return true;
1757     }
1758     return false;
1759 }
1760
1761 bool QQuickTextPrivate::determineHorizontalAlignment()
1762 {
1763     if (hAlignImplicit) {
1764 #ifndef QT_NO_IM
1765         bool alignToRight = text.isEmpty() ? qApp->inputMethod()->inputDirection() == Qt::RightToLeft : rightToLeftText;
1766 #else
1767         bool alignToRight = rightToLeftText;
1768 #endif
1769         return setHAlign(alignToRight ? QQuickText::AlignRight : QQuickText::AlignLeft);
1770     }
1771     return false;
1772 }
1773
1774 void QQuickTextPrivate::mirrorChange()
1775 {
1776     Q_Q(QQuickText);
1777     if (q->isComponentComplete()) {
1778         if (!hAlignImplicit && (hAlign == QQuickText::AlignRight || hAlign == QQuickText::AlignLeft)) {
1779             updateLayout();
1780             emit q->effectiveHorizontalAlignmentChanged();
1781         }
1782     }
1783 }
1784
1785 QQuickText::VAlignment QQuickText::vAlign() const
1786 {
1787     Q_D(const QQuickText);
1788     return d->vAlign;
1789 }
1790
1791 void QQuickText::setVAlign(VAlignment align)
1792 {
1793     Q_D(QQuickText);
1794     if (d->vAlign == align)
1795         return;
1796
1797     d->vAlign = align;
1798
1799     if (isComponentComplete())
1800         d->updateLayout();
1801
1802     emit verticalAlignmentChanged(align);
1803 }
1804
1805 /*!
1806     \qmlproperty enumeration QtQuick::Text::wrapMode
1807
1808     Set this property to wrap the text to the Text item's width.  The text will only
1809     wrap if an explicit width has been set.  wrapMode can be one of:
1810
1811     \list
1812     \li Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l contentWidth will exceed a set width.
1813     \li Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l contentWidth will exceed a set width.
1814     \li Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
1815     \li Text.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
1816     \endlist
1817 */
1818 QQuickText::WrapMode QQuickText::wrapMode() const
1819 {
1820     Q_D(const QQuickText);
1821     return d->wrapMode;
1822 }
1823
1824 void QQuickText::setWrapMode(WrapMode mode)
1825 {
1826     Q_D(QQuickText);
1827     if (mode == d->wrapMode)
1828         return;
1829
1830     d->wrapMode = mode;
1831     d->updateLayout();
1832
1833     emit wrapModeChanged();
1834 }
1835
1836 /*!
1837     \qmlproperty int QtQuick::Text::lineCount
1838
1839     Returns the number of lines visible in the text item.
1840
1841     This property is not supported for rich text.
1842
1843     \sa maximumLineCount
1844 */
1845 int QQuickText::lineCount() const
1846 {
1847     Q_D(const QQuickText);
1848     return d->lineCount;
1849 }
1850
1851 /*!
1852     \qmlproperty bool QtQuick::Text::truncated
1853
1854     Returns true if the text has been truncated due to \l maximumLineCount
1855     or \l elide.
1856
1857     This property is not supported for rich text.
1858
1859     \sa maximumLineCount, elide
1860 */
1861 bool QQuickText::truncated() const
1862 {
1863     Q_D(const QQuickText);
1864     return d->truncated;
1865 }
1866
1867 /*!
1868     \qmlproperty int QtQuick::Text::maximumLineCount
1869
1870     Set this property to limit the number of lines that the text item will show.
1871     If elide is set to Text.ElideRight, the text will be elided appropriately.
1872     By default, this is the value of the largest possible integer.
1873
1874     This property is not supported for rich text.
1875
1876     \sa lineCount, elide
1877 */
1878 int QQuickText::maximumLineCount() const
1879 {
1880     Q_D(const QQuickText);
1881     return d->maximumLineCount();
1882 }
1883
1884 void QQuickText::setMaximumLineCount(int lines)
1885 {
1886     Q_D(QQuickText);
1887
1888     d->maximumLineCountValid = lines==INT_MAX ? false : true;
1889     if (d->maximumLineCount() != lines) {
1890         d->extra.value().maximumLineCount = lines;
1891         d->implicitHeightValid = false;
1892         d->updateLayout();
1893         emit maximumLineCountChanged();
1894     }
1895 }
1896
1897 void QQuickText::resetMaximumLineCount()
1898 {
1899     Q_D(QQuickText);
1900     setMaximumLineCount(INT_MAX);
1901     if (d->truncated != false) {
1902         d->truncated = false;
1903         emit truncatedChanged();
1904     }
1905 }
1906
1907 /*!
1908     \qmlproperty enumeration QtQuick::Text::textFormat
1909
1910     The way the text property should be displayed.
1911
1912     Supported text formats are:
1913
1914     \list
1915     \li Text.AutoText (default)
1916     \li Text.PlainText
1917     \li Text.StyledText
1918     \li Text.RichText
1919     \endlist
1920
1921     If the text format is \c Text.AutoText the Text item
1922     will automatically determine whether the text should be treated as
1923     styled text.  This determination is made using Qt::mightBeRichText()
1924     which uses a fast and therefore simple heuristic. It mainly checks
1925     whether there is something that looks like a tag before the first
1926     line break. Although the result may be correct for common cases,
1927     there is no guarantee.
1928
1929     Text.StyledText is an optimized format supporting some basic text
1930     styling markup, in the style of HTML 3.2:
1931
1932     \code
1933     <b></b> - bold
1934     <strong></strong> - bold
1935     <i></i> - italic
1936     <br> - new line
1937     <p> - paragraph
1938     <u> - underlined text
1939     <font color="color_name" size="1-7"></font>
1940     <h1> to <h6> - headers
1941     <a href=""> - anchor
1942     <img src="" align="top,middle,bottom" width="" height=""> - inline images
1943     <ol type="">, <ul type=""> and <li> - ordered and unordered lists
1944     <pre></pre> - preformatted
1945     &gt; &lt; &amp;
1946     \endcode
1947
1948     \c Text.StyledText parser is strict, requiring tags to be correctly nested.
1949
1950     \table
1951     \row
1952     \li
1953     \qml
1954 Column {
1955     Text {
1956         font.pointSize: 24
1957         text: "<b>Hello</b> <i>World!</i>"
1958     }
1959     Text {
1960         font.pointSize: 24
1961         textFormat: Text.RichText
1962         text: "<b>Hello</b> <i>World!</i>"
1963     }
1964     Text {
1965         font.pointSize: 24
1966         textFormat: Text.PlainText
1967         text: "<b>Hello</b> <i>World!</i>"
1968     }
1969 }
1970     \endqml
1971     \li \image declarative-textformat.png
1972     \endtable
1973
1974     Text.RichText supports a larger subset of HTML 4, as described on the
1975     \l {Supported HTML Subset} page. You should prefer using Text.PlainText
1976     or Text.StyledText instead, as they offer better performance.
1977 */
1978 QQuickText::TextFormat QQuickText::textFormat() const
1979 {
1980     Q_D(const QQuickText);
1981     return d->format;
1982 }
1983
1984 void QQuickText::setTextFormat(TextFormat format)
1985 {
1986     Q_D(QQuickText);
1987     if (format == d->format)
1988         return;
1989     d->format = format;
1990     bool wasRich = d->richText;
1991     d->richText = format == RichText;
1992     d->styledText = format == StyledText || (format == AutoText && Qt::mightBeRichText(d->text));
1993
1994     if (isComponentComplete()) {
1995         if (!wasRich && d->richText) {
1996             d->ensureDoc();
1997             d->extra->doc->setText(d->text);
1998             d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
1999         } else {
2000             d->rightToLeftText = d->text.isRightToLeft();
2001             d->textHasChanged = true;
2002         }
2003         d->determineHorizontalAlignment();
2004     }
2005     d->updateLayout();
2006     setAcceptHoverEvents(d->richText || d->styledText);
2007
2008     emit textFormatChanged(d->format);
2009 }
2010
2011 /*!
2012     \qmlproperty enumeration QtQuick::Text::elide
2013
2014     Set this property to elide parts of the text fit to the Text item's width.
2015     The text will only elide if an explicit width has been set.
2016
2017     This property cannot be used with rich text.
2018
2019     Eliding can be:
2020     \list
2021     \li Text.ElideNone  - the default
2022     \li Text.ElideLeft
2023     \li Text.ElideMiddle
2024     \li Text.ElideRight
2025     \endlist
2026
2027     If this property is set to Text.ElideRight, it can be used with \l {wrapMode}{wrapped}
2028     text. The text will only elide if \c maximumLineCount, or \c height has been set.
2029     If both \c maximumLineCount and \c height are set, \c maximumLineCount will
2030     apply unless the lines do not fit in the height allowed.
2031
2032     If the text is a multi-length string, and the mode is not \c Text.ElideNone,
2033     the first string that fits will be used, otherwise the last will be elided.
2034
2035     Multi-length strings are ordered from longest to shortest, separated by the
2036     Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}).
2037 */
2038 QQuickText::TextElideMode QQuickText::elideMode() const
2039 {
2040     Q_D(const QQuickText);
2041     return d->elideMode;
2042 }
2043
2044 void QQuickText::setElideMode(QQuickText::TextElideMode mode)
2045 {
2046     Q_D(QQuickText);
2047     if (mode == d->elideMode)
2048         return;
2049
2050     d->elideMode = mode;
2051     d->updateLayout();
2052
2053     emit elideModeChanged(mode);
2054 }
2055
2056 /*!
2057     \qmlproperty url QtQuick::Text::baseUrl
2058
2059     This property specifies a base URL which is used to resolve relative URLs
2060     within the text.
2061
2062     Urls are resolved to be within the same directory as the target of the base
2063     URL meaning any portion of the path after the last '/' will be ignored.
2064
2065     \table
2066     \header \li Base URL \li Relative URL \li Resolved URL
2067     \row \li http://qt-project.org/ \li images/logo.png \li http://qt-project.org/images/logo.png
2068     \row \li http://qt-project.org/index.html \li images/logo.png \li http://qt-project.org/images/logo.png
2069     \row \li http://qt-project.org/content \li images/logo.png \li http://qt-project.org/content/images/logo.png
2070     \row \li http://qt-project.org/content/ \li images/logo.png \li http://qt-project.org/content/images/logo.png
2071     \row \li http://qt-project.org/content/index.html \li images/logo.png \li http://qt-project.org/content/images/logo.png
2072     \row \li http://qt-project.org/content/index.html \li ../images/logo.png \li http://qt-project.org/images/logo.png
2073     \row \li http://qt-project.org/content/index.html \li /images/logo.png \li http://qt-project.org/images/logo.png
2074     \endtable
2075
2076     The default value is the url of the QML file instantiating the Text item.
2077 */
2078
2079 QUrl QQuickText::baseUrl() const
2080 {
2081     Q_D(const QQuickText);
2082     if (d->baseUrl.isEmpty()) {
2083         if (QQmlContext *context = qmlContext(this))
2084             const_cast<QQuickTextPrivate *>(d)->baseUrl = context->baseUrl();
2085     }
2086     return d->baseUrl;
2087 }
2088
2089 void QQuickText::setBaseUrl(const QUrl &url)
2090 {
2091     Q_D(QQuickText);
2092     if (baseUrl() != url) {
2093         d->baseUrl = url;
2094
2095         if (d->richText) {
2096             d->ensureDoc();
2097             d->extra->doc->setBaseUrl(url);
2098         }
2099         if (d->styledText) {
2100             d->textHasChanged = true;
2101             qDeleteAll(d->imgTags);
2102             d->imgTags.clear();
2103             d->updateLayout();
2104         }
2105         emit baseUrlChanged();
2106     }
2107 }
2108
2109 void QQuickText::resetBaseUrl()
2110 {
2111     if (QQmlContext *context = qmlContext(this))
2112         setBaseUrl(context->baseUrl());
2113     else
2114         setBaseUrl(QUrl());
2115 }
2116
2117 /*! \internal */
2118 QRectF QQuickText::boundingRect() const
2119 {
2120     Q_D(const QQuickText);
2121
2122     QRectF rect = d->layedOutTextRect;
2123     rect.moveLeft(QQuickTextUtil::alignedX(rect.width(), width(), d->hAlign));
2124     rect.moveTop(QQuickTextUtil::alignedY(rect.height(), height(), d->vAlign));
2125
2126     if (d->style != Normal)
2127         rect.adjust(-1, 0, 1, 2);
2128     // Could include font max left/right bearings to either side of rectangle.
2129
2130     return rect;
2131 }
2132
2133 QRectF QQuickText::clipRect() const
2134 {
2135     Q_D(const QQuickText);
2136
2137     QRectF rect = QQuickImplicitSizeItem::clipRect();
2138     if (d->style != Normal)
2139         rect.adjust(-1, 0, 1, 2);
2140     return rect;
2141 }
2142
2143 /*! \internal */
2144 void QQuickText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
2145 {
2146     Q_D(QQuickText);
2147     if (d->text.isEmpty()) {
2148         QQuickItem::geometryChanged(newGeometry, oldGeometry);
2149         return;
2150     }
2151
2152     bool widthChanged = newGeometry.width() != oldGeometry.width();
2153     bool heightChanged = newGeometry.height() != oldGeometry.height();
2154     bool wrapped = d->wrapMode != QQuickText::NoWrap;
2155     bool elide = d->elideMode != QQuickText::ElideNone;
2156     bool scaleFont = d->fontSizeMode() != QQuickText::FixedSize && (widthValid() || heightValid());
2157     bool verticalScale = (d->fontSizeMode() & QQuickText::VerticalFit) && heightValid();
2158
2159     bool widthMaximum = newGeometry.width() >= oldGeometry.width() && !d->widthExceeded;
2160     bool heightMaximum = newGeometry.height() >= oldGeometry.height() && !d->heightExceeded;
2161
2162     if ((!widthChanged && !heightChanged) || d->internalWidthUpdate)
2163         goto geomChangeDone;
2164
2165     if ((effectiveHAlign() != QQuickText::AlignLeft && widthChanged)
2166             || (vAlign() != QQuickText::AlignTop && heightChanged)) {
2167         // If the width has changed and we're not left aligned do an update so the text is
2168         // repositioned even if a full layout isn't required. And the same for vertical.
2169         d->updateType = QQuickTextPrivate::UpdatePaintNode;
2170         update();
2171     }
2172
2173     if (!wrapped && !elide && !scaleFont)
2174         goto geomChangeDone; // left aligned unwrapped text without eliding never needs relayout
2175
2176     if (elide // eliding and dimensions were and remain invalid;
2177             && ((widthValid() && oldGeometry.width() <= 0 && newGeometry.width() <= 0)
2178             || (heightValid() && oldGeometry.height() <= 0 && newGeometry.height() <= 0))) {
2179         goto geomChangeDone;
2180     }
2181
2182     if (widthMaximum && heightMaximum && !d->isLineLaidOutConnected())  // Size is sufficient and growing.
2183         goto geomChangeDone;
2184
2185     if (!(widthChanged || widthMaximum) && !d->isLineLaidOutConnected()) { // only height has changed
2186         if (newGeometry.height() > oldGeometry.height()) {
2187             if (!d->heightExceeded) // Height is adequate and growing.
2188                 goto geomChangeDone;
2189             if (d->lineCount == d->maximumLineCount())  // Reached maximum line and height is growing.
2190                 goto geomChangeDone;
2191         } else if (newGeometry.height() < oldGeometry.height()) {
2192             if (d->lineCount < 2 && !verticalScale && newGeometry.height() > 0)  // A single line won't be truncated until the text is 0 height.
2193                 goto geomChangeDone;
2194
2195             if (!verticalScale // no scaling, no eliding, and either unwrapped, or no maximum line count.
2196                     && d->elideMode != QQuickText::ElideRight
2197                     && !(d->maximumLineCountValid && d->widthExceeded)) {
2198                 goto geomChangeDone;
2199             }
2200         }
2201     } else if (!heightChanged && widthMaximum) {
2202         goto geomChangeDone;
2203     }
2204
2205     if (d->updateOnComponentComplete || d->textHasChanged) {
2206         // We need to re-elide
2207         d->updateLayout();
2208     } else {
2209         // We just need to re-layout
2210         d->updateSize();
2211     }
2212
2213 geomChangeDone:
2214     QQuickItem::geometryChanged(newGeometry, oldGeometry);
2215 }
2216
2217 void QQuickText::triggerPreprocess()
2218 {
2219     Q_D(QQuickText);
2220     if (d->updateType == QQuickTextPrivate::UpdateNone)
2221         d->updateType = QQuickTextPrivate::UpdatePreprocess;
2222     update();
2223 }
2224
2225 QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
2226 {
2227     Q_UNUSED(data);
2228     Q_D(QQuickText);
2229
2230     if (d->text.isEmpty()) {
2231         delete oldNode;
2232         return 0;
2233     }
2234
2235     if (d->updateType != QQuickTextPrivate::UpdatePaintNode && oldNode != 0) {
2236         // Update done in preprocess() in the nodes
2237         d->updateType = QQuickTextPrivate::UpdateNone;
2238         return oldNode;
2239     }
2240
2241     d->updateType = QQuickTextPrivate::UpdateNone;
2242
2243     const qreal dy = QQuickTextUtil::alignedY(d->layedOutTextRect.height(), height(), d->vAlign);
2244
2245     QQuickTextNode *node = 0;
2246     if (!oldNode)
2247         node = new QQuickTextNode(this);
2248     else
2249         node = static_cast<QQuickTextNode *>(oldNode);
2250
2251     node->setUseNativeRenderer(d->renderType == NativeRendering && d->window->devicePixelRatio() <= 1);
2252     node->deleteContent();
2253     node->setMatrix(QMatrix4x4());
2254
2255     const QColor color = QColor::fromRgba(d->color);
2256     const QColor styleColor = QColor::fromRgba(d->styleColor);
2257     const QColor linkColor = QColor::fromRgba(d->linkColor);
2258
2259     if (d->richText) {
2260         const qreal dx = QQuickTextUtil::alignedX(d->layedOutTextRect.width(), width(), d->hAlign);
2261         d->ensureDoc();
2262         node->addTextDocument(QPointF(dx, dy), d->extra->doc, color, d->style, styleColor, linkColor);
2263     } else if (d->layedOutTextRect.width() > 0) {
2264         const qreal dx = QQuickTextUtil::alignedX(d->lineWidth, width(), d->hAlign);
2265         int unelidedLineCount = d->lineCount;
2266         if (d->elideLayout)
2267             unelidedLineCount -= 1;
2268         if (unelidedLineCount > 0) {
2269             node->addTextLayout(
2270                         QPointF(dx, dy),
2271                         &d->layout,
2272                         color, d->style, styleColor, linkColor,
2273                         QColor(), QColor(), -1, -1,
2274                         0, unelidedLineCount);
2275         }
2276         if (d->elideLayout)
2277             node->addTextLayout(QPointF(dx, dy), d->elideLayout, color, d->style, styleColor, linkColor);
2278
2279         foreach (QQuickStyledTextImgTag *img, d->visibleImgTags) {
2280             QQuickPixmap *pix = img->pix;
2281             if (pix && pix->isReady())
2282                 node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, pix->width(), pix->height()), pix->image());
2283         }
2284     }
2285     return node;
2286 }
2287
2288 void QQuickText::updatePolish()
2289 {
2290     Q_D(QQuickText);
2291     d->updateSize();
2292 }
2293
2294 /*!
2295     \qmlproperty real QtQuick::Text::contentWidth
2296
2297     Returns the width of the text, including width past the width
2298     which is covered due to insufficient wrapping if WrapMode is set.
2299 */
2300 qreal QQuickText::contentWidth() const
2301 {
2302     Q_D(const QQuickText);
2303     return d->layedOutTextRect.width();
2304 }
2305
2306 /*!
2307     \qmlproperty real QtQuick::Text::contentHeight
2308
2309     Returns the height of the text, including height past the height
2310     which is covered due to there being more text than fits in the set height.
2311 */
2312 qreal QQuickText::contentHeight() const
2313 {
2314     Q_D(const QQuickText);
2315     return d->layedOutTextRect.height();
2316 }
2317
2318 /*!
2319     \qmlproperty real QtQuick::Text::lineHeight
2320
2321     Sets the line height for the text.
2322     The value can be in pixels or a multiplier depending on lineHeightMode.
2323
2324     The default value is a multiplier of 1.0.
2325     The line height must be a positive value.
2326 */
2327 qreal QQuickText::lineHeight() const
2328 {
2329     Q_D(const QQuickText);
2330     return d->lineHeight();
2331 }
2332
2333 void QQuickText::setLineHeight(qreal lineHeight)
2334 {
2335     Q_D(QQuickText);
2336
2337     if ((d->lineHeight() == lineHeight) || (lineHeight < 0.0))
2338         return;
2339
2340     d->extra.value().lineHeight = lineHeight;
2341     d->implicitHeightValid = false;
2342     d->updateLayout();
2343     emit lineHeightChanged(lineHeight);
2344 }
2345
2346 /*!
2347     \qmlproperty enumeration QtQuick::Text::lineHeightMode
2348
2349     This property determines how the line height is specified.
2350     The possible values are:
2351
2352     \list
2353     \li Text.ProportionalHeight (default) - this sets the spacing proportional to the
2354        line (as a multiplier). For example, set to 2 for double spacing.
2355     \li Text.FixedHeight - this sets the line height to a fixed line height (in pixels).
2356     \endlist
2357 */
2358 QQuickText::LineHeightMode QQuickText::lineHeightMode() const
2359 {
2360     Q_D(const QQuickText);
2361     return d->lineHeightMode();
2362 }
2363
2364 void QQuickText::setLineHeightMode(LineHeightMode mode)
2365 {
2366     Q_D(QQuickText);
2367     if (mode == d->lineHeightMode())
2368         return;
2369
2370     d->implicitHeightValid = false;
2371     d->extra.value().lineHeightMode = mode;
2372     d->updateLayout();
2373
2374     emit lineHeightModeChanged(mode);
2375 }
2376
2377 /*!
2378     \qmlproperty enumeration QtQuick::Text::fontSizeMode
2379
2380     This property specifies how the font size of the displayed text is determined.
2381     The possible values are:
2382
2383     \list
2384     \li Text.FixedSize (default) - The size specified by \l font.pixelSize
2385     or \l font.pointSize is used.
2386     \li Text.HorizontalFit - The largest size up to the size specified that fits
2387     within the width of the item without wrapping is used.
2388     \li Text.VerticalFit - The largest size up to the size specified that fits
2389     the height of the item is used.
2390     \li Text.Fit - The largest size up to the size specified the fits within the
2391     width and height of the item is used.
2392     \endlist
2393
2394     The font size of fitted text has a minimum bound specified by the
2395     minimumPointSize or minimumPixelSize property and maximum bound specified
2396     by either the \l font.pointSize or \l font.pixelSize properties.
2397
2398     If the text does not fit within the item bounds with the minimum font size
2399     the text will be elided as per the \l elide property.
2400 */
2401
2402 QQuickText::FontSizeMode QQuickText::fontSizeMode() const
2403 {
2404     Q_D(const QQuickText);
2405     return d->fontSizeMode();
2406 }
2407
2408 void QQuickText::setFontSizeMode(FontSizeMode mode)
2409 {
2410     Q_D(QQuickText);
2411     if (d->fontSizeMode() == mode)
2412         return;
2413
2414     polish();
2415
2416     d->extra.value().fontSizeMode = mode;
2417     emit fontSizeModeChanged();
2418 }
2419
2420 /*!
2421     \qmlproperty int QtQuick::Text::minimumPixelSize
2422
2423     This property specifies the minimum font pixel size of text scaled by the
2424     fontSizeMode property.
2425
2426     If the fontSizeMode is Text.FixedSize or the \l font.pixelSize is -1 this
2427     property is ignored.
2428 */
2429
2430 int QQuickText::minimumPixelSize() const
2431 {
2432     Q_D(const QQuickText);
2433     return d->minimumPixelSize();
2434 }
2435
2436 void QQuickText::setMinimumPixelSize(int size)
2437 {
2438     Q_D(QQuickText);
2439     if (d->minimumPixelSize() == size)
2440         return;
2441
2442     if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid()))
2443         polish();
2444     d->extra.value().minimumPixelSize = size;
2445     emit minimumPixelSizeChanged();
2446 }
2447
2448 /*!
2449     \qmlproperty int QtQuick::Text::minimumPointSize
2450
2451     This property specifies the minimum font point \l size of text scaled by
2452     the fontSizeMode property.
2453
2454     If the fontSizeMode is Text.FixedSize or the \l font.pointSize is -1 this
2455     property is ignored.
2456 */
2457
2458 int QQuickText::minimumPointSize() const
2459 {
2460     Q_D(const QQuickText);
2461     return d->minimumPointSize();
2462 }
2463
2464 void QQuickText::setMinimumPointSize(int size)
2465 {
2466     Q_D(QQuickText);
2467     if (d->minimumPointSize() == size)
2468         return;
2469
2470     if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid()))
2471         polish();
2472     d->extra.value().minimumPointSize = size;
2473     emit minimumPointSizeChanged();
2474 }
2475
2476 /*!
2477     Returns the number of resources (images) that are being loaded asynchronously.
2478 */
2479 int QQuickText::resourcesLoading() const
2480 {
2481     Q_D(const QQuickText);
2482     if (d->richText && d->extra.isAllocated() && d->extra->doc)
2483         return d->extra->doc->resourcesLoading();
2484     return 0;
2485 }
2486
2487 /*! \internal */
2488 void QQuickText::componentComplete()
2489 {
2490     Q_D(QQuickText);
2491     if (d->updateOnComponentComplete) {
2492         if (d->richText) {
2493             d->ensureDoc();
2494             d->extra->doc->setText(d->text);
2495             d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
2496         } else {
2497             d->rightToLeftText = d->text.isRightToLeft();
2498         }
2499         d->determineHorizontalAlignment();
2500     }
2501     QQuickItem::componentComplete();
2502     if (d->updateOnComponentComplete)
2503         d->updateLayout();
2504 }
2505
2506 QString QQuickTextPrivate::anchorAt(const QTextLayout *layout, const QPointF &mousePos)
2507 {
2508     for (int i = 0; i < layout->lineCount(); ++i) {
2509         QTextLine line = layout->lineAt(i);
2510         if (line.naturalTextRect().contains(mousePos)) {
2511             int charPos = line.xToCursor(mousePos.x(), QTextLine::CursorOnCharacter);
2512             foreach (const QTextLayout::FormatRange &formatRange, layout->additionalFormats()) {
2513                 if (formatRange.format.isAnchor()
2514                         && charPos >= formatRange.start
2515                         && charPos < formatRange.start + formatRange.length) {
2516                     return formatRange.format.anchorHref();
2517                 }
2518             }
2519             break;
2520         }
2521     }
2522     return QString();
2523 }
2524
2525 QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const
2526 {
2527     Q_Q(const QQuickText);
2528     QPointF translatedMousePos = mousePos;
2529     translatedMousePos.ry() -= QQuickTextUtil::alignedY(layedOutTextRect.height(), q->height(), vAlign);
2530     if (styledText) {
2531         QString link = anchorAt(&layout, translatedMousePos);
2532         if (link.isEmpty() && elideLayout)
2533             link = anchorAt(elideLayout, translatedMousePos);
2534         return link;
2535     } else if (richText && extra.isAllocated() && extra->doc) {
2536         translatedMousePos.rx() -= QQuickTextUtil::alignedX(layedOutTextRect.width(), q->width(), hAlign);
2537         return extra->doc->documentLayout()->anchorAt(translatedMousePos);
2538     }
2539     return QString();
2540 }
2541
2542 bool QQuickTextPrivate::isLinkActivatedConnected()
2543 {
2544     Q_Q(QQuickText);
2545     IS_SIGNAL_CONNECTED(q, QQuickText, linkActivated, (const QString &));
2546 }
2547
2548 /*!  \internal */
2549 void QQuickText::mousePressEvent(QMouseEvent *event)
2550 {
2551     Q_D(QQuickText);
2552
2553     QString link;
2554     if (d->isLinkActivatedConnected())
2555         link = d->anchorAt(event->localPos());
2556
2557     if (link.isEmpty()) {
2558         event->setAccepted(false);
2559     } else {
2560         d->extra.value().activeLink = link;
2561     }
2562
2563     // ### may malfunction if two of the same links are clicked & dragged onto each other)
2564
2565     if (!event->isAccepted())
2566         QQuickItem::mousePressEvent(event);
2567 }
2568
2569
2570 /*! \internal */
2571 void QQuickText::mouseReleaseEvent(QMouseEvent *event)
2572 {
2573     Q_D(QQuickText);
2574
2575     // ### confirm the link, and send a signal out
2576
2577     QString link;
2578     if (d->isLinkActivatedConnected())
2579         link = d->anchorAt(event->localPos());
2580
2581     if (!link.isEmpty() && d->extra.isAllocated() && d->extra->activeLink == link)
2582         emit linkActivated(d->extra->activeLink);
2583     else
2584         event->setAccepted(false);
2585
2586     if (!event->isAccepted())
2587         QQuickItem::mouseReleaseEvent(event);
2588 }
2589
2590 bool QQuickTextPrivate::isLinkHoveredConnected()
2591 {
2592     Q_Q(QQuickText);
2593     IS_SIGNAL_CONNECTED(q, QQuickText, linkHovered, (const QString &));
2594 }
2595
2596 /*!
2597     \qmlsignal QtQuick::Text::onLinkHovered(string link)
2598     \since 5.2
2599
2600     This handler is called when the user hovers a link embedded in the
2601     text. The link must be in rich text or HTML format and the \a link
2602     string provides access to the particular link.
2603
2604     \sa hoveredLink
2605 */
2606
2607 /*!
2608     \qmlproperty string QtQuick::Text::hoveredLink
2609     \since 5.2
2610
2611     This property contains the link string when user hovers a link
2612     embedded in the text. The link must be in rich text or HTML format
2613     and the \a hoveredLink string provides access to the particular link.
2614
2615     \sa onLinkHovered
2616 */
2617
2618 QString QQuickText::hoveredLink() const
2619 {
2620     Q_D(const QQuickText);
2621     if (const_cast<QQuickTextPrivate *>(d)->isLinkHoveredConnected()) {
2622         if (d->extra.isAllocated())
2623             return d->extra->hoveredLink;
2624     } else {
2625 #ifndef QT_NO_CURSOR
2626         if (QQuickWindow *wnd = window()) {
2627             QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0));
2628             return d->anchorAt(pos);
2629         }
2630 #endif // QT_NO_CURSOR
2631     }
2632     return QString();
2633 }
2634
2635 void QQuickTextPrivate::processHoverEvent(QHoverEvent *event)
2636 {
2637     Q_Q(QQuickText);
2638     QString link;
2639     if (isLinkHoveredConnected()) {
2640         if (event->type() != QEvent::HoverLeave)
2641             link = anchorAt(event->posF());
2642
2643         if ((!extra.isAllocated() && !link.isEmpty()) || (extra.isAllocated() && extra->hoveredLink != link)) {
2644             extra.value().hoveredLink = link;
2645             emit q->linkHovered(extra->hoveredLink);
2646         }
2647     }
2648     event->setAccepted(!link.isEmpty());
2649 }
2650
2651 void QQuickText::hoverEnterEvent(QHoverEvent *event)
2652 {
2653     Q_D(QQuickText);
2654     d->processHoverEvent(event);
2655 }
2656
2657 void QQuickText::hoverMoveEvent(QHoverEvent *event)
2658 {
2659     Q_D(QQuickText);
2660     d->processHoverEvent(event);
2661 }
2662
2663 void QQuickText::hoverLeaveEvent(QHoverEvent *event)
2664 {
2665     Q_D(QQuickText);
2666     d->processHoverEvent(event);
2667 }
2668
2669 QT_END_NAMESPACE