src/hbcore/primitives/hbrichtextitem.cpp
changeset 34 ed14f46c0e55
parent 1 f7ac710697a9
--- a/src/hbcore/primitives/hbrichtextitem.cpp	Mon Oct 04 17:49:30 2010 +0300
+++ b/src/hbcore/primitives/hbrichtextitem.cpp	Mon Oct 18 18:23:13 2010 +0300
@@ -37,17 +37,16 @@
 #include <QPainter>
 #include <QAbstractTextDocumentLayout>
 #include <QApplication>
+#include <QWidget>
+#include <QStyleOptionGraphicsItem>
 
-const int KMinimumLetersToShow = 4;
+const qreal KMinimumRichTextWidth = 6.0;
 
 static const QString KDefaultColorThemeName = "qtc_view_normal";
 
 HbRichTextItemPrivate::HbRichTextItemPrivate() :
-    mAlignment(Qt::AlignLeft|Qt::AlignVCenter),
-    mTextOption(mAlignment),
-    mDontPrint(false),
-    mDontClip(false),
-    mRtf(0)
+        mTextOption(Qt::AlignLeft|Qt::AlignVCenter),
+        mRtf(0)
 {
 }
 
@@ -62,7 +61,7 @@
 {
     Q_Q(HbRichTextItem);
 
-    q->setFlag(QGraphicsItem::ItemClipsToShape, !mDontClip);
+    q->setFlag(QGraphicsItem::ItemClipsToShape, true);
     q->setFlag(QGraphicsItem::ItemIsSelectable, false);
     q->setFlag(QGraphicsItem::ItemIsFocusable,  false);
 
@@ -77,54 +76,13 @@
 
 void HbRichTextItemPrivate::clear()
 {
-    delete mRtf;
 }
 
-int HbRichTextItemPrivate::textFlagsFromTextOption() const
+void HbRichTextItemPrivate::setDocumentWidth(qreal newWidth)
 {
-    int flags = (int)mAlignment;
-
-    switch(mTextOption.wrapMode()) {
-    case QTextOption::NoWrap:
-        flags |= Qt::TextSingleLine;
-        break;
-    case QTextOption::WordWrap:
-        flags |=Qt::TextWordWrap;
-        break;
-    case QTextOption::ManualWrap:
-        break;
-    case QTextOption::WrapAnywhere:
-        flags |=Qt::TextWrapAnywhere;
-        break;
-    case QTextOption::WrapAtWordBoundaryOrAnywhere:
-        flags |=Qt::TextWordWrap | Qt::TextWrapAnywhere;
-        break;
-    }
-
-    if(mDontClip)  flags |= Qt::TextDontClip;
-    if(mDontPrint) flags |= Qt::TextDontPrint;
-
-    return flags;
-}
-
-bool HbRichTextItemPrivate::setLayoutDirection(Qt::LayoutDirection newDirection)
-{
-    Qt::Alignment oldAlign = mTextOption.alignment();
-    Qt::Alignment alignment = QStyle::visualAlignment(newDirection, mAlignment);
-    mTextOption.setAlignment(alignment);
-    mTextOption.setTextDirection(newDirection);
-    mRtf->setDefaultTextOption(mTextOption);
-
-    return alignment!=oldAlign;
-}
-
-void HbRichTextItemPrivate::setSize(const QSizeF &newSize)
-{
-    if(mRtf->size()!=newSize) {
-        Q_Q(HbRichTextItem);
-        mRtf->setTextWidth(newSize.width());
+    if (!qFuzzyCompare(mRtf->textWidth(), newWidth)) {
+        mRtf->setTextWidth(newWidth);
         calculateOffset();
-        q->update();
     }
 }
 
@@ -134,11 +92,12 @@
     Q_Q(HbRichTextItem);
 
     qreal diff;
-    if(mAlignment.testFlag(Qt::AlignTop)) {
+    Qt::Alignment align = mTextOption.alignment();
+    if (align.testFlag(Qt::AlignTop)) {
         diff = 0.0;
     } else {
         diff = q->geometry().height() - mRtf->size().height();
-        if(!mAlignment.testFlag(Qt::AlignBottom)) {
+        if (!align.testFlag(Qt::AlignBottom)) {
             // default align Qt::AlignVCenter if no flags are set
             diff*=0.5;
         }
@@ -147,12 +106,166 @@
     diff = qMax(diff, (qreal)0.0);
 #endif
 
-    if(diff!=mOffset.y()) {
+    if (diff!=mOffset.y()) {
         mOffset.setY(diff);
         q->prepareGeometryChange();
     }
 }
 
+QSizeF HbRichTextItemPrivate::minimumSizeHint(const QSizeF &/*constraint*/) const
+{
+    QSizeF result(KMinimumRichTextWidth, 0);
+
+    QTextBlock textBlock = mRtf->begin();
+    if (textBlock.isValid() && textBlock.layout()->lineCount() > 0) {
+        QTextLine line = textBlock.layout()->lineAt(0);
+        result.setHeight(line.height());
+    } else {
+        QFontMetricsF metrics(mRtf->defaultFont());
+        result.setHeight(metrics.height());
+    }
+
+    qreal doubleDocMargin = mRtf->documentMargin() * (qreal)2.0;
+    result.rheight() += doubleDocMargin;
+    result.rwidth() += doubleDocMargin;
+
+    return result;
+}
+
+void HbRichTextItemPrivate::clearPrefSizeCache()
+{
+    mPrefSize.setWidth(-1);
+    mMinWidthForAdjust = QWIDGETSIZE_MAX;
+    mMaxWidthForAdjust = -1;
+}
+
+QSizeF HbRichTextItemPrivate::preferredSizeHint(const QSizeF &constraint) const
+{
+    if (mPrefSizeConstraint==constraint && mPrefSize.isValid()) {
+        return mPrefSize;
+    }
+    mPrefSizeConstraint=constraint;
+
+    QSizeF result;
+
+    if (constraint.width()<=0) {
+        mRtf->setTextWidth(QWIDGETSIZE_MAX);
+    } else {
+        QTextOption::WrapMode wrapMode = mTextOption.wrapMode();
+        // optimization when there is no automatic wrap there is no reason
+        // to setTextWidth with width constraint (width measure is not needed)
+        if (wrapMode!=QTextOption::NoWrap
+            && wrapMode!=QTextOption::ManualWrap) {
+            mRtf->setTextWidth(constraint.width());
+        }
+    }
+    result = mRtf->size();
+    mMaxWidthForAdjust = result.width();
+    result.setWidth(mRtf->idealWidth());
+    mMinWidthForAdjust = result.width();
+    mDefaultPrefHeight = result.height();
+    mPrefSize = result;
+
+    return result;
+}
+
+bool HbRichTextItemPrivate::isAdjustHeightNeeded(qreal newWidth,
+                                                 qreal prefHeight,
+                                                 qreal minHeight,
+                                                 qreal maxHeight)
+{
+    // first check if wrapping of text is not active
+    QTextOption::WrapMode wrapMode = mTextOption.wrapMode();
+    if (wrapMode==QTextOption::NoWrap
+        || wrapMode==QTextOption::ManualWrap) {
+        return false;
+    }
+
+    // preferred height was set from outside of this class so there is mo reason to adjust it
+    if (mPrefSizeConstraint.height()>0) {
+        return false;
+    }
+
+    // check if adjusted size has been already calculated
+    // new width is bigger than last estimated range of same height
+    if ((mMaxWidthForAdjust>=newWidth
+         // new width is smaller than last estimated range of same height
+         && newWidth>=mMinWidthForAdjust)) {
+        return false;
+    }
+
+    if (!mPrefSize.isValid()) {
+        // this means that preferred size is set outside of class by setPreferredSize
+        // so sizeHint(Qt::Preferredsize) was not called and size adjustment is useless
+        return false;
+    }
+
+    // new text width was set in setGeometry here it is not needed
+    Q_ASSERT_X(qFuzzyCompare(mRtf->textWidth(), newWidth),
+               "HbRichTextItemPrivate::isAdjustHeightNeeded",
+               QString("mRtf->textWidth()=%1, newWidth=%2")
+                    .arg(mRtf->textWidth())
+                    .arg(newWidth).toAscii().data());
+
+    // if preconditions are met test if current height is enough
+    QSizeF newAdjust = mRtf->size();
+
+    if (qFuzzyCompare(newAdjust.height(), mPrefSize.height())) {
+        // height is same as last time update range of same height
+        mMaxWidthForAdjust = qMax(mMaxWidthForAdjust, newWidth);
+        mMinWidthForAdjust = qMin(mMinWidthForAdjust, newWidth);
+        return false;
+    }
+
+    // new height was calculated create new range for which
+    // current mPrefSize.height is valid
+    mMaxWidthForAdjust = newWidth;
+    mMinWidthForAdjust = mRtf->idealWidth();
+
+    // store new height, don't change width
+    mPrefSize.setHeight(newAdjust.height());
+
+    Q_ASSERT_X(mPrefSizeConstraint.width()>0 || mPrefSize.width()>=mMinWidthForAdjust,
+               "HbRichTextItemPrivate::isAdjustHeightNeeded",
+               QString("Fail for (%1<%2) string: \"%3\"")
+               .arg(mPrefSize.width())
+               .arg(mMinWidthForAdjust)
+               .arg(mText).toAscii().data());
+
+    if (qFuzzyCompare(qBound(minHeight, mPrefSize.height(), maxHeight),
+                      prefHeight)) {
+        // updateGeometry has no effect
+        return false;
+    }
+
+    // all conditions for calling updateGeometry are meet
+    return true;
+}
+
+/*
+    returns true if updateGeometry is needed
+ */
+bool HbRichTextItemPrivate::restoreDefaultHeightHint()
+{
+    if (mPrefSizeConstraint.height()>0) {
+        return false;
+    }
+
+    if (mPrefSizeConstraint.width()>0) {
+        clearPrefSizeCache();
+        return true;
+    }
+
+    if (mPrefSize.height()==mDefaultPrefHeight) {
+        return true;
+    }
+
+    mPrefSize.setHeight(mDefaultPrefHeight);
+    mMinWidthForAdjust = mPrefSize.width();
+    mMaxWidthForAdjust = QWIDGETSIZE_MAX;
+    return true;
+}
+
 /*!
   @proto
   @hbcore
@@ -170,7 +283,7 @@
  */
 
 HbRichTextItem::HbRichTextItem(QGraphicsItem *parent) :
-    HbWidgetBase(*new HbRichTextItemPrivate, parent)
+        HbWidgetBase(*new HbRichTextItemPrivate, parent)
 {
     Q_D(HbRichTextItem);
     d->init();
@@ -180,7 +293,7 @@
     Constructor which set content using \a html format.
  */
 HbRichTextItem::HbRichTextItem(const QString &html, QGraphicsItem *parent) :
-    HbWidgetBase(*new HbRichTextItemPrivate, parent)
+        HbWidgetBase(*new HbRichTextItemPrivate, parent)
 {
     Q_D(HbRichTextItem);
     d->init();
@@ -191,7 +304,7 @@
     Constructor for internal use only
  */
 HbRichTextItem::HbRichTextItem(HbRichTextItemPrivate &dd, QGraphicsItem *parent) :
-    HbWidgetBase(dd, parent)
+        HbWidgetBase(dd, parent)
 {
     Q_D(HbRichTextItem);
     d->init();
@@ -217,6 +330,8 @@
     if (d->mText != text) {
         d->mText = text;
         d->mRtf->setHtml(text);
+        d->clearPrefSizeCache();
+        update();
         updateGeometry();
     }
 }
@@ -229,7 +344,7 @@
 
 QString HbRichTextItem::text() const
 {
-    Q_D( const HbRichTextItem );
+    Q_D(const HbRichTextItem);
     return d->mText;
 }
 
@@ -240,16 +355,15 @@
  */
 void HbRichTextItem::setAlignment(Qt::Alignment alignment)
 {
-    Q_D( HbRichTextItem );
-	d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextAlign, true);
-    alignment &= Qt::AlignVertical_Mask | Qt::AlignHorizontal_Mask;
-    if( d->mAlignment!=alignment ) {
+    Q_D(HbRichTextItem);
+    d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextAlign, true);
+    alignment = d->combineAlignment(alignment, d->mTextOption.alignment());
+    if (d->mTextOption.alignment()!=alignment) {
         prepareGeometryChange();
-        d->mAlignment = alignment;
+        d->mTextOption.setAlignment(alignment);
+        d->mRtf->setDefaultTextOption(d->mTextOption);
         d->calculateOffset();
-        if(d->setLayoutDirection(layoutDirection())) {
-            update();
-        }
+        update();
     }
 }
 
@@ -260,39 +374,39 @@
  */
 Qt::Alignment HbRichTextItem::alignment() const
 {
-    Q_D( const HbRichTextItem );
-    return d->mAlignment;
+    Q_D(const HbRichTextItem);
+    return d->mTextOption.alignment();
 }
 
 /*!
     \reimp
  */
 void HbRichTextItem::paint(QPainter *painter, 
-                            const QStyleOptionGraphicsItem *option, 
-                            QWidget *widget)
+                           const QStyleOptionGraphicsItem *option,
+                           QWidget *widget)
 {
-    Q_UNUSED(option);
     Q_UNUSED(widget);
 
     Q_D(HbRichTextItem);
 
-    // Save painter's state
-    QRegion oldClipRegion = painter->clipRegion();
-    QTransform oldTransform = painter->transform();
-
-    if(!d->mDontPrint) {
-        if(!d->mDontClip) {
-            painter->setClipRect(contentsRect(), Qt::IntersectClip);
-        }
-        painter->translate(d->mOffset);
-        QAbstractTextDocumentLayout::PaintContext context;
-        context.palette.setColor(QPalette::Text, textDefaultColor());
-        d->mRtf->documentLayout()->draw(painter, context);
+    if (option->exposedRect.isEmpty()) {
+        // nothing to paint
+        return;
     }
 
-    // Restore painter's state
-    painter->setClipRegion(oldClipRegion);
-    painter->setTransform(oldTransform);
+    painter->translate(d->mOffset);
+
+    QAbstractTextDocumentLayout::PaintContext context;
+    context.clip = option->exposedRect;
+    // painter was translated so it should be compensated
+    context.clip.translate(-d->mOffset);
+
+    context.palette.setColor(QPalette::Text, textDefaultColor());
+
+    d->mRtf->documentLayout()->draw(painter, context);
+
+    // restore painter
+    painter->translate(-d->mOffset);
 }
 
 /*!
@@ -306,8 +420,21 @@
 
     HbWidgetBase::setGeometry(rect);
 
-    if(rect.isValid()) {
-        d->setSize(rect.size());
+    // this call is needed since there is possible scenario
+    // when size was not changed after updateGeometry and sizeHint calls
+    d->setDocumentWidth(size().width());
+
+    if (parentLayoutItem() && parentLayoutItem()->isLayout()) {
+        // rect.size can't be used here since size can be limited inside of
+        // called method HbWidgetBase::setGeometry(rect) so size is used which
+        // holds current size
+        if (d->isAdjustHeightNeeded(size().width(),
+                                    preferredHeight(),
+                                    minimumHeight(),
+                                    maximumHeight())) {
+            updateGeometry();
+            return;
+        }
     }
 }
 
@@ -318,12 +445,12 @@
 {
     Q_D(const HbRichTextItem);
 
-    QRectF result(d->mOffset, d->mRtf->size());
+    QRectF result(d->mRtf->documentLayout()->frameBoundingRect(d->mRtf->rootFrame()));
+    result.translate(d->mOffset);
 
-    if(!d->mDontClip) {
+    if (flags().testFlag(QGraphicsItem::ItemClipsToShape)) {
         // clip
-        result = result.intersect(QRectF(QPointF(),
-                                          size()));
+        result = result.intersect(contentsRect());
     }
     return result;
 }
@@ -332,16 +459,17 @@
     \reimp
     Relayouts text according to new size.
  */
-void HbRichTextItem::resizeEvent(QGraphicsSceneResizeEvent *event)
+void HbRichTextItem::resizeEvent(QGraphicsSceneResizeEvent * /*event*/)
 {
     Q_D(HbRichTextItem);
 
-    d->setSize(event->newSize());
+    d->setDocumentWidth(size().width());
+    d->calculateOffset();
 }
 
 /*!
     \reimp
-    This impelementation detects layout direction changes, font changes and theme changes.
+    This implementation detects layout direction changes, font changes and theme changes.
  */
 void HbRichTextItem::changeEvent(QEvent *event)
 {
@@ -350,13 +478,15 @@
     switch(event->type()) {
     case QEvent::LayoutDirectionChange: {
             prepareGeometryChange();
-            d->setLayoutDirection(layoutDirection());
+            d->mTextOption.setTextDirection(layoutDirection());
+            d->mRtf->setDefaultTextOption(d->mTextOption);
             update();
         }
         break;
 
     case QEvent::FontChange: {
             d->mRtf->setDefaultFont(font());
+            d->clearPrefSizeCache();
             updateGeometry();
         }
         break;
@@ -367,7 +497,7 @@
         if (event->type() == HbEvent::ThemeChanged) {
             Q_D(HbRichTextItem);
             d->mDefaultColor = QColor();
-            if(!d->mColor.isValid()) { 
+            if (!d->mColor.isValid()) {
                 update();
             }
         }
@@ -387,29 +517,12 @@
     QSizeF result;
     switch(which) {
     case Qt::MinimumSize: {
-            QTextBlock textBlock = d->mRtf->begin();
-            if(textBlock.isValid() && textBlock.layout()->lineCount() > 0) {
-                QTextLine line = textBlock.layout()->lineAt(0);
-                result.setHeight(line.height());
-                int cursorPos(KMinimumLetersToShow);
-                result.setWidth( line.cursorToX(&cursorPos) );
-
-                qreal doubleDocMargin = d->mRtf->documentMargin() * (qreal)2.0;
-                result.rheight() += doubleDocMargin;
-                result.rwidth() += doubleDocMargin;
-            } else {
-                result = HbWidgetBase::sizeHint(which, constraint);
-            }
+            result = d->minimumSizeHint(constraint);
         }
         break;
 
     case Qt::PreferredSize: {
-            if(constraint.width()<=0) {
-                d->mRtf->adjustSize();
-            } else {
-                d->mRtf->setTextWidth(constraint.width());
-            }
-            result = d->mRtf->size();
+            result = d->preferredSizeHint(constraint);
         }
         break;
 
@@ -429,14 +542,17 @@
 void HbRichTextItem::setTextWrapping(Hb::TextWrapping mode)
 {
     Q_D(HbRichTextItem);
-	d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextWrapMode, true);
+    d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextWrapMode, true);
     QTextOption::WrapMode textWrapMode = static_cast<QTextOption::WrapMode>(mode);
 
-    if(d->mTextOption.wrapMode()!=textWrapMode) {
+    if (d->mTextOption.wrapMode()!=textWrapMode) {
         prepareGeometryChange();
         d->mTextOption.setWrapMode(textWrapMode);
         d->mRtf->setDefaultTextOption(d->mTextOption);
-        updateGeometry();
+        d->calculateOffset();
+        if (d->restoreDefaultHeightHint()) {
+            updateGeometry();
+        }
     }
 }
 
@@ -464,7 +580,7 @@
  */
 QColor HbRichTextItem::textDefaultColor() const
 {
-    Q_D( const HbRichTextItem );
+    Q_D(const HbRichTextItem);
 
     if (d->mColor.isValid()) { // Means user has set text color
         return d->mColor;
@@ -513,11 +629,7 @@
  */
 void HbRichTextItem::setTextVisible(bool isVisible)
 {
-    Q_D(HbRichTextItem);
-    if( d->mDontPrint == isVisible ) {
-        d->mDontPrint = !isVisible;
-        update();
-    }
+    setVisible(isVisible);
 }
 
 /*!
@@ -525,22 +637,15 @@
  */
 bool HbRichTextItem::isTextVisible() const
 {
-    Q_D(const HbRichTextItem);
-    return !d->mDontPrint;
+    return isVisible();
 }
 
 /*!
  * Enables (default) or disables text clipping when item geometry is too small.
  */
-void HbRichTextItem::setTextClip(bool cliping)
+void HbRichTextItem::setTextClip(bool clipping)
 {
-    Q_D(HbRichTextItem);
-    if( d->mDontClip == cliping ) {
-        prepareGeometryChange();
-        d->mDontClip = !cliping;
-        setFlag(QGraphicsItem::ItemClipsToShape, cliping);
-        update();
-    }
+    setFlag(QGraphicsItem::ItemClipsToShape, clipping);
 }
 
 /*!
@@ -548,8 +653,7 @@
  */
 bool HbRichTextItem::isTextClip() const
 {
-    Q_D(const HbRichTextItem);
-    return !d->mDontClip;
+    return flags().testFlag(ItemClipsToShape);
 }
 
 // end of file