src/declarative/graphicsitems/qdeclarativetext.cpp
changeset 33 3e2da88830cd
parent 30 5dc02b23752f
child 37 758a864f9613
--- a/src/declarative/graphicsitems/qdeclarativetext.cpp	Tue Jul 06 15:10:48 2010 +0300
+++ b/src/declarative/graphicsitems/qdeclarativetext.cpp	Wed Aug 18 10:37:55 2010 +0300
@@ -57,56 +57,106 @@
 
 QT_BEGIN_NAMESPACE
 
+extern Q_GUI_EXPORT bool qt_applefontsmoothing_enabled;
+
 class QTextDocumentWithImageResources : public QTextDocument {
     Q_OBJECT
 
 public:
-    QTextDocumentWithImageResources(QDeclarativeText *parent) :
-        QTextDocument(parent),
-        outstanding(0)
-    {
-    }
+    QTextDocumentWithImageResources(QDeclarativeText *parent);
+    virtual ~QTextDocumentWithImageResources();
 
+    void setText(const QString &);
     int resourcesLoading() const { return outstanding; }
 
 protected:
-    QVariant loadResource(int type, const QUrl &name)
-    {
-        QUrl url = qmlContext(parent())->resolvedUrl(name);
+    QVariant loadResource(int type, const QUrl &name);
+
+private slots:
+    void requestFinished();
+
+private:
+    QHash<QUrl, QDeclarativePixmap *> m_resources;
+
+    int outstanding;
+    static QSet<QUrl> errors;
+};
+
+QTextDocumentWithImageResources::QTextDocumentWithImageResources(QDeclarativeText *parent) 
+: QTextDocument(parent), outstanding(0)
+{
+}
 
-        if (type == QTextDocument::ImageResource) {
-            QPixmap pm;
-            QString errorString;
-            QDeclarativePixmapReply::Status status = QDeclarativePixmapCache::get(url, &pm, &errorString, 0, false, 0, 0);
-            if (status == QDeclarativePixmapReply::Ready)
-                return pm;
-            if (status == QDeclarativePixmapReply::Error) {
-                if (!errors.contains(url)) {
-                    errors.insert(url);
-                    qmlInfo(parent()) << errorString;
-                }
-            } else {
-                QDeclarativePixmapReply *reply = QDeclarativePixmapCache::request(qmlEngine(parent()), url);
-                connect(reply, SIGNAL(finished()), this, SLOT(requestFinished()));
+QTextDocumentWithImageResources::~QTextDocumentWithImageResources()
+{
+    if (!m_resources.isEmpty()) 
+        qDeleteAll(m_resources);
+}
+
+QVariant QTextDocumentWithImageResources::loadResource(int type, const QUrl &name)
+{
+    QDeclarativeContext *context = qmlContext(parent());
+    QUrl url = context->resolvedUrl(name);
+
+    if (type == QTextDocument::ImageResource) {
+        QHash<QUrl, QDeclarativePixmap *>::Iterator iter = m_resources.find(url);
+
+        if (iter == m_resources.end()) {
+            QDeclarativePixmap *p = new QDeclarativePixmap(context->engine(), url);
+            iter = m_resources.insert(name, p);
+
+            if (p->isLoading()) {
+                p->connectFinished(this, SLOT(requestFinished()));
                 outstanding++;
             }
         }
 
-        return QTextDocument::loadResource(type,url); // The *resolved* URL
+        QDeclarativePixmap *p = *iter;
+        if (p->isReady()) {
+            return p->pixmap();
+        } else if (p->isError()) {
+            if (!errors.contains(url)) {
+                errors.insert(url);
+                qmlInfo(parent()) << p->error();
+            }
+        }
     }
 
-private slots:
-    void requestFinished()
-    {
-        outstanding--;
-        if (outstanding == 0)
-            static_cast<QDeclarativeText*>(parent())->reloadWithResources();
+    return QTextDocument::loadResource(type,url); // The *resolved* URL
+}
+
+void QTextDocumentWithImageResources::requestFinished()
+{
+    outstanding--;
+    if (outstanding == 0) {
+        QDeclarativeText *textItem = static_cast<QDeclarativeText*>(parent());
+        QString text = textItem->text();
+#ifndef QT_NO_TEXTHTMLPARSER
+        setHtml(text);
+#else
+        setPlainText(text);
+#endif
+        QDeclarativeTextPrivate *d = QDeclarativeTextPrivate::get(textItem);
+        d->updateLayout();
+        d->markImgDirty();
+    }
+}
+
+void QTextDocumentWithImageResources::setText(const QString &text)
+{
+    if (!m_resources.isEmpty()) {
+        qWarning("CLEAR");
+        qDeleteAll(m_resources);
+        m_resources.clear();
+        outstanding = 0;
     }
 
-private:
-    int outstanding;
-    static QSet<QUrl> errors;
-};
+#ifndef QT_NO_TEXTHTMLPARSER
+    setHtml(text);
+#else
+    setPlainText(text);
+#endif
+}
 
 QSet<QUrl> QTextDocumentWithImageResources::errors;
 
@@ -126,16 +176,18 @@
     \image declarative-text.png
 
     If height and width are not explicitly set, Text will attempt to determine how
-    much room is needed and set it accordingly. Unless \c wrapMode is set, it will always
+    much room is needed and set it accordingly. Unless \l wrapMode is set, it will always
     prefer width to height (all text will be placed on a single line).
 
-    The \c elide property can alternatively be used to fit a single line of
+    The \l elide property can alternatively be used to fit a single line of
     plain text to a set width.
 
     Note that the \l{Supported HTML Subset} is limited. Also, if the text contains
     HTML img tags that load remote images, the text is reloaded.
 
     Text provides read-only text. For editable text, see \l TextEdit.
+
+    \sa {declarative/text/fonts}{Fonts example}
 */
 
 /*!
@@ -161,7 +213,7 @@
 
     The \c elide property can alternatively be used to fit a line of plain text to a set width.
 
-    A QDeclarativeText object can be instantiated in Qml using the tag \c Text.
+    A QDeclarativeText object can be instantiated in QML using the tag \c Text.
 */
 QDeclarativeText::QDeclarativeText(QDeclarativeItem *parent)
   : QDeclarativeItem(*(new QDeclarativeTextPrivate), parent)
@@ -225,12 +277,6 @@
 */
 
 /*!
-    \qmlproperty bool Text::font.outline
-
-    Sets whether the font has an outline style.
-*/
-
-/*!
     \qmlproperty bool Text::font.strikeout
 
     Sets whether the font has a strikeout style.
@@ -257,8 +303,7 @@
     Sets the letter spacing for the font.
 
     Letter spacing changes the default spacing between individual letters in the font.
-    A value of 100 will keep the spacing unchanged; a value of 200 will enlarge the spacing after a character by
-    the width of the character itself.
+    A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
 */
 
 /*!
@@ -318,7 +363,7 @@
     if (d->richText) {
         if (isComponentComplete()) {
             d->ensureDoc();
-            d->doc->setHtml(n);
+            d->doc->setText(n);
         }
     }
 
@@ -437,6 +482,8 @@
     \qml
     Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" }
     \endqml
+
+    \sa style
  */
 QColor QDeclarativeText::styleColor() const
 {
@@ -473,6 +520,7 @@
         return;
 
     d->hAlign = align;
+    update();
     emit horizontalAlignmentChanged(align);
 }
 
@@ -489,6 +537,7 @@
         return;
 
     d->vAlign = align;
+    update();
     emit verticalAlignmentChanged(align);
 }
 
@@ -499,16 +548,11 @@
     wrap if an explicit width has been set.  wrapMode can be one of:
 
     \list
-    \o Text.NoWrap - no wrapping will be performed.
-    \o Text.WordWrap - wrapping is done on word boundaries. If the text cannot be
-    word-wrapped to the specified width it will be partially drawn outside of the item's bounds.
-    If this is undesirable then enable clipping on the item (Item::clip).
-    \o Text.WrapAnywhere - Text can be wrapped at any point on a line, even if it occurs in the middle of a word.
-    \o Text.WrapAtWordBoundaryOrAnywhere - 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.
+    \o Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l paintedWidth will exceed a set width.
+    \o Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l paintedWidth will exceed a set width.
+    \o Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
+    \o 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.
     \endlist
-
-    The default is Text.NoWrap.
 */
 QDeclarativeText::WrapMode QDeclarativeText::wrapMode() const
 {
@@ -538,13 +582,13 @@
     Supported text formats are:
     
     \list
-    \o Text.AutoText
+    \o Text.AutoText (default)
     \o Text.PlainText
     \o Text.RichText
     \o Text.StyledText
     \endlist
 
-    The default is Text.AutoText.  If the text format is Text.AutoText the text element
+    If the text format is \c Text.AutoText the text element
     will automatically determine whether the text should be treated as
     rich text.  This determination is made using Qt::mightBeRichText().
 
@@ -608,7 +652,7 @@
     } else if (!wasRich && d->richText) {
         if (isComponentComplete()) {
             d->ensureDoc();
-            d->doc->setHtml(d->text);
+            d->doc->setText(d->text);
         }
         d->updateLayout();
         d->markImgDirty();
@@ -658,11 +702,54 @@
     emit elideModeChanged(d->elideMode);
 }
 
+QRectF QDeclarativeText::boundingRect() const
+{
+    Q_D(const QDeclarativeText);
+
+    int w = width();
+    int h = height();
+
+    int x = 0;
+    int y = 0;
+
+    QSize size = d->cachedLayoutSize;
+    if (d->style != Normal)
+        size += QSize(2,2);
+
+    // Could include font max left/right bearings to either side of rectangle.
+
+    switch (d->hAlign) {
+    case AlignLeft:
+        x = 0;
+        break;
+    case AlignRight:
+        x = w - size.width();
+        break;
+    case AlignHCenter:
+        x = (w - size.width()) / 2;
+        break;
+    }
+
+    switch (d->vAlign) {
+    case AlignTop:
+        y = 0;
+        break;
+    case AlignBottom:
+        y = h - size.height();
+        break;
+    case AlignVCenter:
+        y = (h - size.height()) / 2;
+        break;
+    }
+
+    return QRectF(x,y,size.width(),size.height());
+}
+
 void QDeclarativeText::geometryChanged(const QRectF &newGeometry,
                               const QRectF &oldGeometry)
 {
     Q_D(QDeclarativeText);
-    if (newGeometry.width() != oldGeometry.width()) {
+    if (!d->internalWidthUpdate && newGeometry.width() != oldGeometry.width()) {
         if (d->wrapMode != QDeclarativeText::NoWrap || d->elideMode != QDeclarativeText::ElideNone) {
             //re-elide if needed
             if (d->singleline && d->elideMode != QDeclarativeText::ElideNone &&
@@ -708,6 +795,7 @@
     }
 }
 
+
 void QDeclarativeTextPrivate::updateSize()
 {
     Q_Q(QDeclarativeText);
@@ -715,6 +803,7 @@
         QFontMetrics fm(font);
         if (text.isEmpty()) {
             q->setImplicitHeight(fm.height());
+            emit q->paintedSizeChanged();
             return;
         }
 
@@ -724,7 +813,10 @@
         //setup instance of QTextLayout for all cases other than richtext
         if (!richText) {
             size = setupTextLayout(&layout);
-            cachedLayoutSize = size;
+            if (cachedLayoutSize != size) {
+                q->prepareGeometryChange();
+                cachedLayoutSize = size;
+            }
             dy -= size.height();
         } else {
             singleline = false; // richtext can't elide or be optimized for single-line case
@@ -738,7 +830,13 @@
             else
                 doc->setTextWidth(doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug)
             dy -= (int)doc->size().height();
-            cachedLayoutSize = doc->size().toSize();
+                q->prepareGeometryChange();
+            QSize dsize = doc->size().toSize();
+            if (dsize != cachedLayoutSize) {
+                q->prepareGeometryChange();
+                cachedLayoutSize = dsize;
+            }
+            size = QSize(int(doc->idealWidth()),dsize.height());
         }
         int yoff = 0;
 
@@ -751,13 +849,40 @@
         q->setBaselineOffset(fm.ascent() + yoff);
 
         //### need to comfirm cost of always setting these for richText
-        q->setImplicitWidth(richText ? (int)doc->idealWidth() : size.width());
-        q->setImplicitHeight(richText ? (int)doc->size().height() : size.height());
+        internalWidthUpdate = true;
+        q->setImplicitWidth(size.width());
+        internalWidthUpdate = false;
+        q->setImplicitHeight(size.height());
+        emit q->paintedSizeChanged();
     } else {
         dirty = true;
     }
 }
 
+/*!
+    \qmlproperty real Text::paintedWidth
+
+    Returns the width of the text, including width past the width
+    which is covered due to insufficient wrapping if WrapMode is set.
+*/
+qreal QDeclarativeText::paintedWidth() const
+{
+    return implicitWidth();
+}
+
+/*!
+    \qmlproperty real Text::paintedHeight
+
+    Returns the height of the text, including height past the height
+    which is covered due to there being more text than fits in the set height.
+*/
+qreal QDeclarativeText::paintedHeight() const
+{
+    return implicitHeight();
+}
+
+
+
 // ### text layout handling should be profiled and optimized as needed
 // what about QStackTextEngine engine(tmp, d->font.font()); QTextLayout textLayout(&engine);
 
@@ -782,6 +907,8 @@
     ppm.drawPixmap(pos, imgCache);
     ppm.end();
 
+    if (imgCache.size() != img.size())
+        q_func()->prepareGeometryChange();
     imgCache = img;
 }
 
@@ -800,6 +927,8 @@
     ppm.drawPixmap(pos, imgCache);
     ppm.end();
 
+    if (imgCache.size() != img.size())
+        q_func()->prepareGeometryChange();
     imgCache = img;
 }
 
@@ -876,7 +1005,14 @@
     QPixmap img(size);
     if (!size.isEmpty()) {
         img.fill(Qt::transparent);
+#ifdef Q_WS_MAC
+        bool oldSmooth = qt_applefontsmoothing_enabled;
+        qt_applefontsmoothing_enabled = false;
+#endif
         QPainter p(&img);
+#ifdef Q_WS_MAC
+        qt_applefontsmoothing_enabled = oldSmooth;
+#endif
         drawWrappedText(&p, QPointF(0,0), drawStyle);
     }
     return img;
@@ -899,7 +1035,14 @@
     //paint text
     QPixmap img(size);
     img.fill(Qt::transparent);
+#ifdef Q_WS_MAC
+    bool oldSmooth = qt_applefontsmoothing_enabled;
+    qt_applefontsmoothing_enabled = false;
+#endif
     QPainter p(&img);
+#ifdef Q_WS_MAC
+    qt_applefontsmoothing_enabled = oldSmooth;
+#endif
 
     QAbstractTextDocumentLayout::PaintContext context;
 
@@ -924,18 +1067,21 @@
         return;
 
     bool empty = text.isEmpty();
+    QPixmap newImgCache;
     if (empty) {
-        imgCache = QPixmap();
         imgStyleCache = QPixmap();
     } else if (richText) {
-        imgCache = richTextImage(false);
+        newImgCache = richTextImage(false);
         if (style != QDeclarativeText::Normal)
             imgStyleCache = richTextImage(true); //### should use styleColor
     } else {
-        imgCache = wrappedTextImage(false);
+        newImgCache = wrappedTextImage(false);
         if (style != QDeclarativeText::Normal)
             imgStyleCache = wrappedTextImage(true); //### should use styleColor
     }
+    if (imgCache.size() != newImgCache.size())
+        q_func()->prepareGeometryChange();
+    imgCache = newImgCache;
     if (!empty)
         switch (style) {
         case QDeclarativeText::Outline:
@@ -963,16 +1109,6 @@
     }
 }
 
-void QDeclarativeText::reloadWithResources()
-{
-    Q_D(QDeclarativeText);
-    if (!d->richText)
-        return;
-    d->doc->setHtml(d->text);
-    d->updateLayout();
-    d->markImgDirty();
-}
-
 /*!
     Returns the number of resources (images) that are being loaded asynchronously.
 */
@@ -982,6 +1118,15 @@
     return d->doc ? d->doc->resourcesLoading() : 0;
 }
 
+/*!
+  \qmlproperty bool Text::clip
+  This property holds whether the text is clipped.
+
+  Note that if the text does not fit in the bounding rectangle it will be abruptly chopped.
+
+  If you want to display potentially long text in a limited space, you probably want to use \c elide instead.
+*/
+
 void QDeclarativeText::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
 {
     Q_D(QDeclarativeText);
@@ -996,72 +1141,29 @@
         if (d->smooth)
             p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth);
 
-        int w = width();
-        int h = height();
-
-        int x = 0;
-        int y = 0;
-
-        switch (d->hAlign) {
-        case AlignLeft:
-            x = 0;
-            break;
-        case AlignRight:
-            x = w - d->imgCache.width();
-            break;
-        case AlignHCenter:
-            x = (w - d->imgCache.width()) / 2;
-            break;
-        }
-
-        switch (d->vAlign) {
-        case AlignTop:
-            y = 0;
-            break;
-        case AlignBottom:
-            y = h - d->imgCache.height();
-            break;
-        case AlignVCenter:
-            y = (h - d->imgCache.height()) / 2;
-            break;
-        }
+        QRect br = boundingRect().toRect();
 
         bool needClip = clip() && (d->imgCache.width() > width() ||
                                    d->imgCache.height() > height());
 
-        if (needClip) {
-            p->save();
-            p->setClipRect(boundingRect(), Qt::IntersectClip);
-        }
-        p->drawPixmap(x, y, d->imgCache);
         if (needClip)
-            p->restore();
+            p->drawPixmap(0, 0, width(), height(), d->imgCache, -br.x(), -br.y(), width(), height());
+        else
+            p->drawPixmap(br.x(), br.y(), d->imgCache);
 
         if (d->smooth) {
             p->setRenderHint(QPainter::Antialiasing, oldAA);
             p->setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth);
         }
     } else {
-        int h = height();
-        int y = 0;
+        qreal y = boundingRect().y();
 
-        switch (d->vAlign) {
-        case AlignTop:
-            y = 0;
-            break;
-        case AlignBottom:
-            y = h - d->cachedLayoutSize.height();
-            break;
-        case AlignVCenter:
-            y = (h - d->cachedLayoutSize.height()) / 2;
-            break;
-        }
         bool needClip = !clip() && (d->cachedLayoutSize.width() > width() ||
                                     d->cachedLayoutSize.height() > height());
 
         if (needClip) {
             p->save();
-            p->setClipRect(boundingRect(), Qt::IntersectClip);
+            p->setClipRect(0, 0, width(), height(), Qt::IntersectClip);
         }
         if (d->richText) {
             QAbstractTextDocumentLayout::PaintContext context;
@@ -1098,7 +1200,7 @@
     if (d->dirty) {
         if (d->richText) {
             d->ensureDoc();
-            d->doc->setHtml(d->text);
+            d->doc->setText(d->text);
         }
         d->updateLayout();
         d->dirty = false;
@@ -1128,7 +1230,7 @@
 }
 
 /*!
-    \qmlsignal Text::linkActivated(link)
+    \qmlsignal Text::onLinkActivated(link)
 
     This handler is called when the user clicks on a link embedded in the text.
 */