diff -r 627c4a0fd0e7 -r c3690ec91ef8 src/hbcore/primitives/hbtextitem.cpp --- a/src/hbcore/primitives/hbtextitem.cpp Fri Jun 11 13:58:22 2010 +0300 +++ b/src/hbcore/primitives/hbtextitem.cpp Wed Jun 23 18:33:25 2010 +0300 @@ -38,15 +38,20 @@ #ifdef HB_TEXT_MEASUREMENT_UTILITY #include "hbtextmeasurementutility_p.h" -#include "hbfeaturemanager_p.h" +#include "hbfeaturemanager_r.h" #endif +// #define HB_TEXT_ITEM_LOGS #define EPSILON 0.01 +#ifdef HB_TEXT_ITEM_LOGS +# include +#endif // HB_TEXT_ITEM_LOGS + bool HbTextItemPrivate::outlinesEnabled = false; static const QString KDefaultColorThemeName = "qtc_view_normal"; -const int MinimumWidth = 5; // minimum width if there is some text. +const qreal MinimumWidth = 5.0; // minimum width if there is some text. const int KLayoutCacheLimit = 64; const qreal KFadeTolerance = 1.0; @@ -58,11 +63,13 @@ mPaintFaded(false), mFadeLengthX(30), mFadeLengthY(15), - mPrefHeight(0), - mMinLines(0), + mMinLines(1), mMaxLines(0), - mNeedToAdjustSizeHint(false), - mUpdateColor(true) + mMinWidthForAdjust(-1), + mMaxWidthForAdjust(-1), + mDefaultHeight(-1), + mUpdateColor(true), + mEventPosted(false) { } @@ -79,6 +86,7 @@ textOption.setWrapMode(QTextOption::WordWrap); mTextLayout.setTextOption(textOption); mTextLayout.setCacheEnabled(true); + mTextLayout.setFont(q->font()); } void HbTextItemPrivate::clear() @@ -86,57 +94,36 @@ // no implementation needed } -bool HbTextItemPrivate::doLayout(const QString& text, const qreal lineWidth, qreal leading) +bool HbTextItemPrivate::doLayout(const QString& text, const qreal lineWidth, qreal lineSpacing) { bool textTruncated = false; - mInvalidateShownText = false; mTextLayout.setText(text); - mTextLayout.setFont( q_func()->font() ); - qreal height = 0; + qreal yLinePos = 0; mTextLayout.beginLayout(); while (1) { QTextLine line = mTextLayout.createLine(); if (!line.isValid()) break; - if( ( mMaxLines > 0 ) && ( mTextLayout.lineCount() > mMaxLines ) ) { - textTruncated = true; + + line.setLineWidth(lineWidth); + line.setPosition(QPointF(0, yLinePos)); + + if( ( mMaxLines > 0 ) && ( mTextLayout.lineCount() >= mMaxLines ) ) { + textTruncated = (line.textStart()+line.textLength() < text.length()); break; } - - line.setLineWidth(lineWidth); - height += leading; - line.setPosition(QPointF(0, height)); - height += line.height(); + yLinePos += lineSpacing; } mTextLayout.endLayout(); - if( textTruncated ) { - mTextLayout.setText(text); - mTextLayout.setFont( q_func()->font() ); - - qreal height = 0; - mTextLayout.beginLayout(); - while ( mTextLayout.lineCount() < mMaxLines ) { - QTextLine line = mTextLayout.createLine(); - line.setLineWidth(lineWidth); - height += leading; - line.setPosition(QPointF(0, height)); - height += line.height(); - } - mTextLayout.endLayout(); - } - return textTruncated; } -void HbTextItemPrivate::setSize(const QSizeF &newSize) +void HbTextItemPrivate::rebuildTextLayout(const QSizeF &newSize) { - Q_Q(HbTextItem); - - QFont usedFont = q->font(); - QFontMetricsF fontMetrics(usedFont); + QFontMetricsF fontMetrics(mTextLayout.font()); const qreal lineWidth = qRound( newSize.width() + 0.5 ); // round up to integer @@ -148,11 +135,10 @@ tempText.replace('\n', QChar::LineSeparator); } - // function does the layout only when needed - mTextLayout.setFont(usedFont); // Need to call elidedText explicitly to enable multiple length translations. tempText = fontMetrics.elidedText(tempText, Qt::ElideNone, lineWidth); - bool textTruncated = doLayout(tempText, lineWidth, fontMetrics.leading()); + bool textTruncated = doLayout(tempText, lineWidth, fontMetrics.lineSpacing()); + if(mElideMode!=Qt::ElideNone && !tempText.isEmpty()) { if( ( mTextLayout.boundingRect().height() - newSize.height() > EPSILON ) || ( mTextLayout.boundingRect().width() - lineWidth > EPSILON ) || @@ -160,12 +146,15 @@ // TODO: Multiple length translations with multiline text doLayout(elideLayoutedText(newSize, fontMetrics), lineWidth, - fontMetrics.leading()); + fontMetrics.lineSpacing()); } } + calculateVerticalOffset(); calculateFadeRects(); - q->update(); + + // build of text layout is completed + mInvalidateShownText = false; } /* @@ -194,6 +183,25 @@ return i; } +QSizeF HbTextItemPrivate::respectSizeLimits(QSizeF size) const +{ + QFontMetricsF metrics(mTextLayout.font()); + + Q_ASSERT(mMinLines>0); + qreal minHeight = metrics.lineSpacing()*mMinLines - metrics.leading(); + if (size.height()0) { + qreal maxHeight = metrics.lineSpacing()*mMaxLines - metrics.leading(); + if (size.height()>maxHeight) { + size.setHeight(maxHeight); + } + } + return size; +} + QString HbTextItemPrivate::elideLayoutedText(const QSizeF& size, const QFontMetricsF& metrics) const { int lastVisibleLine =findIndexOfLastLineBeforeY(size.height()); @@ -280,38 +288,134 @@ return flags; } -bool HbTextItemPrivate::adjustSizeHint() -{ - Q_Q( HbTextItem ); +/*! + This method check spetial case of calculating sizeHint. + By default prefferedSize returns size of text which has lots of free space. + But there can be situaltion that text has less avaible free width ther requred. + In such cases prefered hight of HbTextItem should be recaculated because wrapping + of the line may requre more space in vertical direction. - mNeedToAdjustSizeHint = false; - - if ( !(q->sizePolicy().verticalPolicy()&QSizePolicy::IgnoreFlag) ) { - // only calculated if the vertical sizeHint is taken into account - - const QFontMetricsF metrics(q->font()); + In such case bigger hight should be cached for sizeHint calculations. + */ +bool HbTextItemPrivate::isAdjustHightNeeded(const QSizeF& newSize, + const QSizeF& prefSize) +{ +#ifdef HB_TEXT_ITEM_LOGS + qDebug() << "isAdjustHightNeeded for: " << q_ptr->objectName() + << " text=" << mText.left(20) + << " adjusted=" << mAdjustedSize + << " pref=" << prefSize + << mTextLayout.font(); +#endif // HB_TEXT_ITEM_LOGS - if ( mMinLines > 0 && (mMinLines == mMaxLines) ) { - // if the number of lines if fixed: optimize - const qreal newPrefHeight = ( metrics.height() + metrics.leading() ) * mMinLines - metrics.leading(); - if( qAbs( mPrefHeight - newPrefHeight ) > EPSILON ) { - mPrefHeight = newPrefHeight; - return true; - } - return false; - } + // first check if wrapping of text is not active + QTextOption::WrapMode wrapMode = mTextLayout.textOption().wrapMode(); + if (wrapMode==QTextOption::NoWrap + || wrapMode==QTextOption::ManualWrap) { + return false; + } + + // check if line count is fixed + if (mMaxLines == mMinLines) { + return false; + } - QSizeF currSize = q->size(); - // do the heavy calculation - QRectF desiredRect = metrics.boundingRect( QRectF( 0, 0 , currSize.width(), QWIDGETSIZE_MAX ), textFlagsFromTextOption(), mText ); + // check if preffered height is defined by user + // so check if preff height is same as value returned by sizeHint + if (!qFuzzyCompare(prefSize.height(), mAdjustedSize.height())) { + return false; + } - if( qAbs( desiredRect.height() - mPrefHeight ) > EPSILON ) { - mPrefHeight = desiredRect.height(); - return true; - } + // check if adjusted size has been already calculated + // new width is bigger then last estimated range of same height + if ((mMaxWidthForAdjust>=newSize.width() + // new width is smaller then last estimated range of same height + && newSize.width()>=mMinWidthForAdjust)) { + return false; + } + + if (!mAdjustedSize.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; } - return false; + // if preconditions are met test if current hight is enough + const QFontMetricsF metrics(mTextLayout.font()); + + // heavy calculation: check if text fits in current size and cache result + QSizeF newAdjust = metrics.boundingRect(QRectF(0, 0, newSize.width(), QWIDGETSIZE_MAX), + textFlagsFromTextOption(), + mText).size(); + + if (qFuzzyCompare(newAdjust.height(), mAdjustedSize.height())) { + // height is same as last time update range of same height + mMaxWidthForAdjust = qMax(mMaxWidthForAdjust, newSize.width()); + mMinWidthForAdjust = qMin(mMinWidthForAdjust, newSize.width()); + + // and don't update geometry + return false; + } + + // new height was calculated create new range for which + // current mAdjustedSize.height is valid + mMaxWidthForAdjust = newSize.width(); + mMinWidthForAdjust = newAdjust.width(); + + // store new hieght use don't change width + mAdjustedSize.setHeight(newAdjust.height()); + Q_ASSERT_X(mAdjustedSize.width()>=newAdjust.width(), + "HbTextItemPrivate::isAdjustHightNeeded", + QString("Fail for string: \"%1\"").arg(mText).toAscii().data()); + + if (respectSizeLimits(mAdjustedSize)==prefSize) { + // updateGeometry has no effect + return false; + } + + // all conditions for calling updateGeometry are meet + return true; +} + +void HbTextItemPrivate::clearAdjustedSizeCache() +{ + // clear cache of size + mMinWidthForAdjust = -1; + mMaxWidthForAdjust = -1; + mAdjustedSize.setHeight(-1); +} + +QSizeF HbTextItemPrivate::calculatePrefferedSize(const QSizeF& constraint) const +{ + const QFontMetricsF metrics(mTextLayout.font()); + + if (mAdjustedSize.isValid() && + (constraint.height()<0 || qFuzzyCompare(constraint.height(), mAdjustedSize.height())) && + (constraint.width()<0 || qFuzzyCompare(constraint.width(), mAdjustedSize.width()))) { + // return cached value, see more in: + // - HbTextItemPrivate::isAdjustHightNeeded + // - HbTextItem::resizeEvent + return mAdjustedSize; + } + + QSizeF maxSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + if(constraint.width()>0) { + maxSize.setWidth(constraint.width()); + } + if(constraint.height()>0) { + maxSize.setHeight(constraint.height()); + } + + QSizeF size = metrics.boundingRect(QRectF(QPointF(),maxSize), + textFlagsFromTextOption(), + mText).size(); + + mAdjustedSize = size; + mDefaultHeight = size.height(); + mMinWidthForAdjust = size.width(); + mMaxWidthForAdjust = QWIDGETSIZE_MAX; + + return size; } bool HbTextItemPrivate::fadeNeeded(const QRectF& contentRect) const @@ -532,7 +636,6 @@ // #define SEE_FADE_RECTANGLES #ifdef SEE_FADE_RECTANGLES - painter->setClipRect(mFadeToRect); painter->setBrush(QBrush(QColor(215, 0, 0, 30))); painter->drawRect(mFadeToRect); painter->setBrush(QBrush(QColor(0, 0, 200, 30))); @@ -743,6 +846,11 @@ return result; } +void HbTextItemPrivate::scheduleTextBuild() +{ + mInvalidateShownText = true; +} + /*! @alpha @hbcore @@ -757,7 +865,6 @@ /*! Constructor for the class with no content. */ - HbTextItem::HbTextItem (QGraphicsItem *parent) : HbWidgetBase(*new HbTextItemPrivate, parent) { @@ -872,21 +979,30 @@ #endif //HB_TEXT_MEASUREMENT_UTILITY if (d->mText != txt) { - d->mInvalidateShownText = true; + d->scheduleTextBuild(); prepareGeometryChange(); d->mText = txt; d->mTextLayout.setCacheEnabled(KLayoutCacheLimit >= d->mText.length()); - bool onlyHorizontalSizeHintChanged = false; - if ( d->mMinLines > 0 && (d->mMinLines == d->mMaxLines) ) { - onlyHorizontalSizeHintChanged = true; + d->clearAdjustedSizeCache(); + update(); + + // check if call of updateGeometry can be ignored + // don't call it when minimum and maximum lines are equal (height is fixed) or ... + if ((d->mMinLines == d->mMaxLines) + // or when preferred height is ignored + || (sizePolicy().verticalPolicy()&QSizePolicy::IgnoreFlag)) { + + // and when preferred width is ignored or ... + if (sizePolicy().horizontalPolicy()&QSizePolicy::IgnoreFlag + // or when preferred width is defined from outside + || !qFuzzyCompare(preferredWidth(), d->mAdjustedSize.width())) { + // TODO: looking for better solution since preferredWidth() can cause call of sizeHint + + // in those cases skip updateGeometry + return; + } } - if ( (sizePolicy().horizontalPolicy()&QSizePolicy::IgnoreFlag) && onlyHorizontalSizeHintChanged ) { - // suppress updateGeometry() and use the same geometry - d->setSize( size() ); - } else { - updateGeometry(); - } - update(); + updateGeometry(); } } @@ -952,8 +1068,9 @@ { Q_D(HbTextItem); if (elideMode != d->mElideMode) { - d->mInvalidateShownText = true; d->mElideMode = elideMode; + d->scheduleTextBuild(); + prepareGeometryChange(); update(); } } @@ -986,6 +1103,10 @@ painter->drawRect(rect); } + if (d->mInvalidateShownText) { + d->rebuildTextLayout(size()); + } + painter->setPen(textColor()); @@ -1011,15 +1132,16 @@ */ void HbTextItem::setGeometry(const QRectF & rect) { - Q_D(HbTextItem); - HbWidgetBase::setGeometry(rect); - // needed when there was no size change and some things - // need to relayout text - if(d->mInvalidateShownText) { - prepareGeometryChange(); - d->setSize(rect.size()); + 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 + Q_D(HbTextItem); + if (d->isAdjustHightNeeded(size(), preferredSize())) { + updateGeometry(); + } } } @@ -1032,6 +1154,9 @@ { Q_D(const HbTextItem); + if (d->mInvalidateShownText) { + const_cast(d)->rebuildTextLayout(size()); + } return d->boundingRect(contentsRect()); } // boundingRect() @@ -1044,6 +1169,21 @@ QSizeF size(0,0); + // TODO: Temporary work-around - font change event are not always received + // so updating font here (this is needed because of sizeHint adjustments). + if (d->mTextLayout.font()!=font()) { +#ifdef HB_TEXT_ITEM_LOGS + qWarning() << "Font change was not recieved on time: work-around is active" + << objectName() + << " test: " << d->mText.left(20) + << " oldFont:" << d->mTextLayout.font() + << " newFont:" << font(); +#endif // HB_TEXT_ITEM_LOGS + + const_cast(d)->mTextLayout.setFont(font()); + const_cast(d)->clearAdjustedSizeCache(); + } + Qt::Orientations effectiveOrientations(0); if ( !(sizePolicy().horizontalPolicy()&QSizePolicy::IgnoreFlag) ) { effectiveOrientations |= Qt::Horizontal; @@ -1058,71 +1198,32 @@ return HbWidgetBase::sizeHint( which, constraint ); } - const QFontMetricsF metrics(font()); - QSizeF maxSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); - - if(constraint.width()>0) { - maxSize.setWidth(constraint.width()); - } - if(constraint.height()>0) { - maxSize.setHeight(constraint.height()); - } - switch(which) { case Qt::MinimumSize: { if ( !d->mText.isEmpty() ) { - size.setWidth( MinimumWidth ); // just to show something -- should not matter in read use-case - - if( d->mMinLines > 1 ) { - size.setHeight( ( metrics.height() + metrics.leading() ) * d->mMinLines - metrics.leading() ); - } else { - size.setHeight( metrics.height() ); - } + size.setWidth(MinimumWidth); // just to show something -- should not matter in read use-case + size = d->respectSizeLimits(size); } - break; } case Qt::PreferredSize: { - if ( !(effectiveOrientations&Qt::Horizontal) && d->mMinLines > 0 && (d->mMinLines == d->mMaxLines) ) { + if ( !effectiveOrientations.testFlag(Qt::Horizontal) + && (d->mMinLines == d->mMaxLines) ) { //optimize single line if the horizontal sizeHint is ignored - size.setHeight( ( metrics.height() + metrics.leading() ) * d->mMinLines - metrics.leading() ); + size = d->respectSizeLimits(size); break; } - // do the heavy calculation - size = metrics.boundingRect(QRectF(QPointF(),maxSize), - d->textFlagsFromTextOption(), - d->mText).size(); - - - if( ( constraint.width() < 0 ) && ( constraint.height() < 0 ) ) { - - if( ( d->mNeedToAdjustSizeHint ) || ( d->oldSize != size ) ) { - const_cast(d)->adjustSizeHint(); - } - - qreal pref = d->mPrefHeight; - - if( d->mMaxLines > 0 ) { - qreal maxLimit = ( metrics.height() + metrics.leading() ) * d->mMaxLines - metrics.leading(); - if( maxLimit < pref ) { - pref = maxLimit; - } - - } - - const_cast(d)->oldSize = size; - size.setHeight( pref ); - } - + size = d->calculatePrefferedSize(constraint); + size = d->respectSizeLimits(size); break; } default: - size = HbWidgetBase::sizeHint( which, constraint ); + size = HbWidgetBase::sizeHint(which, constraint); } return size; @@ -1140,16 +1241,30 @@ switch(event->type()) { case QEvent::LayoutDirectionChange: { Q_D(HbTextItem); - d->mInvalidateShownText = true; - updateGeometry(); + d->scheduleTextBuild(); } break; case QEvent::FontChange: { Q_D(HbTextItem); - d->mInvalidateShownText = true; - prepareGeometryChange(); - updateGeometry(); + + if (!d->mTextLayout.font().isCopyOf(font())) { + +#ifdef HB_TEXT_ITEM_LOGS + qDebug() << "fontChangeEvent: " << objectName() + << " text: " << text().left(20) + << " font: " << font(); +#endif // HB_TEXT_ITEM_LOGS + + d->mTextLayout.setFont(font()); + d->clearAdjustedSizeCache(); + d->scheduleTextBuild(); + prepareGeometryChange(); + updateGeometry(); + } else { + // ignoring event since it has no effect + return; + } } break; @@ -1170,20 +1285,13 @@ /*! \reimp */ -void HbTextItem::resizeEvent ( QGraphicsSceneResizeEvent * event ) +void HbTextItem::resizeEvent (QGraphicsSceneResizeEvent *event) { Q_D(HbTextItem); HbWidgetBase::resizeEvent(event); - d->setSize(event->newSize()); - - if( ( qAbs(event->oldSize().width() - event->newSize().width()) > EPSILON ) && - ( ( event->oldSize().width() < preferredWidth() ) || ( event->newSize().width() < preferredWidth() ) ) ){ - if( d->adjustSizeHint() ) { - updateGeometry(); - } - } + d->scheduleTextBuild(); } /*! @@ -1205,9 +1313,23 @@ textOption.setWrapMode(textWrapMode); d->mTextLayout.setTextOption(textOption); if(!d->mText.isEmpty()) { - d->mInvalidateShownText = true; - d->mNeedToAdjustSizeHint = true; - updateGeometry(); + d->scheduleTextBuild(); + prepareGeometryChange(); + update(); + } + + // is size hint adjustable? + if (parentLayoutItem() && parentLayoutItem()->isLayout()) { + if (d->mAdjustedSize.isValid() && + d->mAdjustedSize.width() > size().width()) { + // restore default size hint + d->mAdjustedSize = d->respectSizeLimits( + QSizeF(d->mAdjustedSize.width(), d->mDefaultHeight)); + d->mMinWidthForAdjust = d->mAdjustedSize.width(); + d->mMaxWidthForAdjust = QWIDGETSIZE_MAX; + + updateGeometry(); + } } } } @@ -1261,7 +1383,8 @@ } /*! - Returns true if text is cliped when item geometry is to small. + Returns true if text is clipped when item geometry is too small. + \sa HbTextItem::setTextClip(bool) Equvalent of QGraphicsItem::flags().testFlag(QGraphicsItem::ItemClipsToShape) @@ -1287,14 +1410,17 @@ void HbTextItem::setMinimumLines( int minLines ) { Q_D( HbTextItem ); + minLines = qMax(minLines, 1); // zero or nagative values are meanless and are restoring 1 + d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextLinesMin, true); if( minLines != d->mMinLines ) { if( ( d->mMaxLines > 0 ) && ( minLines > d->mMaxLines ) ) { d->mMaxLines = minLines; } + d->mMinLines = minLines; - d->mMinLines = minLines; + // not needed?: d->clearAdjustedSizeCache(); // some condition? updateGeometry(); } } @@ -1317,12 +1443,18 @@ Q_D( HbTextItem ); d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextLinesMax, true); + maxLines = qMax(maxLines, 0); + if( maxLines != d->mMaxLines ) { - if( ( d->mMinLines > 0 ) && ( maxLines > 0 ) && ( maxLines < d->mMinLines ) ){ + if ((maxLines > 0) && (maxLines < d->mMinLines)){ d->mMinLines = maxLines; } + d->mMaxLines = maxLines; - d->mMaxLines = maxLines; + d->scheduleTextBuild(); + prepareGeometryChange(); + update(); + updateGeometry(); #ifdef HB_TEXT_MEASUREMENT_UTILITY if ( HbFeatureManager::instance()->featureStatus( HbFeatureManager::TextMeasurement ) ) { @@ -1336,7 +1468,7 @@ \sa HbTextItem::setMinimumLines() \sa HbTextItem::setMaximumLines() \sa HbTextItem::maximumLines() - \return "minimum lines" parameter + \return "minimum lines" parameter (zero value means unset) */ int HbTextItem::minimumLines() const {