KDEUI
kwordwrap.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2001 David Faure <faure@kde.org> 00003 00004 This library is free software; you can redistribute it and/or 00005 modify it under the terms of the GNU Library General Public 00006 License version 2 as published by the Free Software Foundation. 00007 00008 This library is distributed in the hope that it will be useful, 00009 but WITHOUT ANY WARRANTY; without even the implied warranty of 00010 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00011 Library General Public License for more details. 00012 00013 You should have received a copy of the GNU Library General Public License 00014 along with this library; see the file COPYING.LIB. If not, write to 00015 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00016 Boston, MA 02110-1301, USA. 00017 */ 00018 00019 #include "kwordwrap.h" 00020 #include <kdebug.h> 00021 00022 #include <QtGui/QPainter> 00023 #include <QtCore/QMutableVectorIterator> 00024 00025 class KWordWrapPrivate { 00026 public: 00027 QRect m_constrainingRect; 00028 QVector<int> m_breakPositions; 00029 QVector<int> m_lineWidths; 00030 QRect m_boundingRect; 00031 QString m_text; 00032 }; 00033 00034 KWordWrap::KWordWrap(const QRect & r) 00035 : d(new KWordWrapPrivate) 00036 { 00037 d->m_constrainingRect = r; 00038 } 00039 00040 KWordWrap* KWordWrap::formatText( QFontMetrics &fm, const QRect & r, int /*flags*/, const QString & str, int len ) 00041 { 00042 KWordWrap* kw = new KWordWrap( r ); 00043 // The wordwrap algorithm 00044 // The variable names and the global shape of the algorithm are inspired 00045 // from QTextFormatterBreakWords::format(). 00046 //kDebug() << "KWordWrap::formatText " << str << " r=" << r.x() << "," << r.y() << " " << r.width() << "x" << r.height(); 00047 int height = fm.height(); 00048 if ( len == -1 ) 00049 kw->d->m_text = str; 00050 else 00051 kw->d->m_text = str.left( len ); 00052 if ( len == -1 ) 00053 len = str.length(); 00054 int lastBreak = -1; 00055 int lineWidth = 0; 00056 int x = 0; 00057 int y = 0; 00058 int w = r.width(); 00059 int textwidth = 0; 00060 bool isBreakable = false; 00061 bool wasBreakable = false; // value of isBreakable for last char (i-1) 00062 bool isParens = false; // true if one of ({[ 00063 bool wasParens = false; // value of isParens for last char (i-1) 00064 QString inputString = str; 00065 00066 for ( int i = 0 ; i < len; ++i ) 00067 { 00068 const QChar c = inputString.at(i); 00069 const int ww = fm.charWidth(inputString, i); 00070 00071 isParens = ( c == QLatin1Char('(') || c == QLatin1Char('[') 00072 || c == QLatin1Char('{') ); 00073 // isBreakable is true when we can break _after_ this character. 00074 isBreakable = ( c.isSpace() || c.isPunct() || c.isSymbol() ) & !isParens; 00075 00076 // Special case for '(', '[' and '{': we want to break before them 00077 if ( !isBreakable && i < len-1 ) { 00078 const QChar nextc = inputString.at(i + 1); // look at next char 00079 isBreakable = ( nextc == QLatin1Char('(') 00080 || nextc == QLatin1Char('[') 00081 || nextc == QLatin1Char('{') ); 00082 } 00083 // Special case for '/': after normal chars it's breakable (e.g. inside a path), 00084 // but after another breakable char it's not (e.g. "mounted at /foo") 00085 // Same thing after a parenthesis (e.g. "dfaure [/fool]") 00086 if ( c == QLatin1Char('/') && (wasBreakable || wasParens) ) 00087 isBreakable = false; 00088 00089 /*kDebug() << "c='" << QString(c) << "' i=" << i << "/" << len 00090 << " x=" << x << " ww=" << ww << " w=" << w 00091 << " lastBreak=" << lastBreak << " isBreakable=" << isBreakable << endl;*/ 00092 int breakAt = -1; 00093 if ( x + ww > w && lastBreak != -1 ) // time to break and we know where 00094 breakAt = lastBreak; 00095 if ( x + ww > w - 4 && lastBreak == -1 ) // time to break but found nowhere [-> break here] 00096 breakAt = i; 00097 if (i == len - 2 && x + ww + fm.charWidth(inputString, i+1) > w) // don't leave the last char alone 00098 breakAt = lastBreak == -1 ? i - 1 : lastBreak; 00099 if ( c == QLatin1Char('\n') ) // Forced break here 00100 { 00101 if ( breakAt == -1 && lastBreak != -1) // only break if not already breaking 00102 { 00103 breakAt = i - 1; 00104 lastBreak = -1; 00105 } 00106 // remove the line feed from the string 00107 kw->d->m_text.remove(i, 1); 00108 inputString.remove(i, 1); 00109 len--; 00110 } 00111 if ( breakAt != -1 ) 00112 { 00113 //kDebug() << "KWordWrap::formatText breaking after " << breakAt; 00114 kw->d->m_breakPositions.append( breakAt ); 00115 int thisLineWidth = lastBreak == -1 ? x + ww : lineWidth; 00116 kw->d->m_lineWidths.append( thisLineWidth ); 00117 textwidth = qMax( textwidth, thisLineWidth ); 00118 x = 0; 00119 y += height; 00120 wasBreakable = true; 00121 wasParens = false; 00122 if ( lastBreak != -1 ) 00123 { 00124 // Breakable char was found, restart from there 00125 i = lastBreak; 00126 lastBreak = -1; 00127 continue; 00128 } 00129 } else if ( isBreakable ) 00130 { 00131 lastBreak = i; 00132 lineWidth = x + ww; 00133 } 00134 x += ww; 00135 wasBreakable = isBreakable; 00136 wasParens = isParens; 00137 } 00138 textwidth = qMax( textwidth, x ); 00139 kw->d->m_lineWidths.append( x ); 00140 y += height; 00141 //kDebug() << "KWordWrap::formatText boundingRect:" << r.x() << "," << r.y() << " " << textwidth << "x" << y; 00142 if ( r.height() >= 0 && y > r.height() ) 00143 textwidth = r.width(); 00144 int realY = y; 00145 if ( r.height() >= 0 ) 00146 { 00147 while ( realY > r.height() ) 00148 realY -= height; 00149 realY = qMax( realY, 0 ); 00150 } 00151 kw->d->m_boundingRect.setRect( 0, 0, textwidth, realY ); 00152 return kw; 00153 } 00154 00155 KWordWrap::~KWordWrap() { 00156 delete d; 00157 } 00158 00159 QString KWordWrap::wrappedString() const 00160 { 00161 // We use the calculated break positions to insert '\n' into the string 00162 QString ws; 00163 int start = 0; 00164 for (int i = 0; i < d->m_breakPositions.count(); ++i) { 00165 int end = d->m_breakPositions.at(i); 00166 ws += d->m_text.mid( start, end - start + 1 ); 00167 ws += QLatin1Char('\n'); 00168 start = end + 1; 00169 } 00170 ws += d->m_text.mid( start ); 00171 return ws; 00172 } 00173 00174 QString KWordWrap::truncatedString( bool dots ) const 00175 { 00176 if ( d->m_breakPositions.isEmpty() ) 00177 return d->m_text; 00178 00179 QString ts = d->m_text.left( d->m_breakPositions.first() + 1 ); 00180 if ( dots ) 00181 ts += QLatin1String("..."); 00182 return ts; 00183 } 00184 00185 static QColor mixColors(double p1, QColor c1, QColor c2) { 00186 return QColor(int(c1.red() * p1 + c2.red() * (1.0-p1)), 00187 int(c1.green() * p1 + c2.green() * (1.0-p1)), 00188 int(c1.blue() * p1 + c2.blue() * (1.0-p1))); 00189 } 00190 00191 void KWordWrap::drawFadeoutText(QPainter *p, int x, int y, int maxW, 00192 const QString &t) { 00193 QFontMetrics fm = p->fontMetrics(); 00194 QColor bgColor = p->background().color(); 00195 QColor textColor = p->pen().color(); 00196 00197 if ( ( fm.boundingRect( t ).width() > maxW ) && ( t.length() > 1 ) ) { 00198 int tl = 0; 00199 int w = 0; 00200 while ( tl < t.length() ) { 00201 w += fm.charWidth( t, tl ); 00202 if ( w >= maxW ) 00203 break; 00204 tl++; 00205 } 00206 00207 int n = qMin( tl, 3); 00208 if ( t.isRightToLeft() ) { 00209 x += maxW; // start from the right side for RTL string 00210 if (tl > 3) { 00211 x -= fm.width( t.left( tl - 3 ) ); 00212 p->drawText( x, y, t.left( tl - 3 ) ); 00213 } 00214 for (int i = 0; i < n; i++) { 00215 p->setPen( mixColors( 0.70 - i * 0.25, textColor, bgColor ) ); 00216 QString s( t.at( tl - n + i ) ); 00217 x -= fm.width( s ); 00218 p->drawText( x, y, s ); 00219 } 00220 } 00221 else { 00222 if (tl > 3) { 00223 p->drawText( x, y, t.left( tl - 3 ) ); 00224 x += fm.width( t.left( tl - 3 ) ); 00225 } 00226 for (int i = 0; i < n; i++) { 00227 p->setPen( mixColors( 0.70 - i * 0.25, textColor, bgColor ) ); 00228 QString s( t.at( tl - n + i ) ); 00229 p->drawText( x, y, s ); 00230 x += fm.width( s ); 00231 } 00232 } 00233 } 00234 else 00235 p->drawText( x, y, t ); 00236 } 00237 00238 void KWordWrap::drawTruncateText(QPainter *p, int x, int y, int maxW, 00239 const QString &t) { 00240 QString tmpText = p->fontMetrics().elidedText(t, Qt::ElideRight, maxW); 00241 p->drawText( x, y, tmpText ); 00242 } 00243 00244 void KWordWrap::drawText( QPainter *painter, int textX, int textY, int flags ) const 00245 { 00246 //kDebug() << "KWordWrap::drawText text=" << wrappedString() << " x=" << textX << " y=" << textY; 00247 // We use the calculated break positions to draw the text line by line using QPainter 00248 int start = 0; 00249 int y = 0; 00250 QFontMetrics fm = painter->fontMetrics(); 00251 int height = fm.height(); // line height 00252 int ascent = fm.ascent(); 00253 int maxwidth = d->m_boundingRect.width(); 00254 int i; 00255 int lwidth = 0; 00256 int end = 0; 00257 for (i = 0; i < d->m_breakPositions.count() ; ++i ) 00258 { 00259 // if this is the last line, leave the loop 00260 if ( (d->m_constrainingRect.height() >= 0) && 00261 ((y + 2 * height) > d->m_constrainingRect.height()) ) 00262 break; 00263 end = d->m_breakPositions.at(i); 00264 lwidth = d->m_lineWidths.at(i); 00265 int x = textX; 00266 if ( flags & Qt::AlignHCenter ) 00267 x += ( maxwidth - lwidth ) / 2; 00268 else if ( flags & Qt::AlignRight ) 00269 x += maxwidth - lwidth; 00270 painter->drawText( x, textY + y + ascent, d->m_text.mid( start, end - start + 1 ) ); 00271 y += height; 00272 start = end + 1; 00273 } 00274 00275 // Draw the last line 00276 lwidth = d->m_lineWidths.last(); 00277 int x = textX; 00278 if ( flags & Qt::AlignHCenter ) 00279 x += ( maxwidth - lwidth ) / 2; 00280 else if ( flags & Qt::AlignRight ) 00281 x += maxwidth - lwidth; 00282 if ( (d->m_constrainingRect.height() < 0) || 00283 ((y + height) <= d->m_constrainingRect.height()) ) { 00284 if ( i == d->m_breakPositions.count() ) 00285 painter->drawText( x, textY + y + ascent, d->m_text.mid( start ) ); 00286 else if (flags & FadeOut) 00287 drawFadeoutText( painter, textX, textY + y + ascent, 00288 d->m_constrainingRect.width(), 00289 d->m_text.mid( start ) ); 00290 else if (flags & Truncate) 00291 drawTruncateText( painter, textX, textY + y + ascent, 00292 d->m_constrainingRect.width(), 00293 d->m_text.mid( start ) ); 00294 else 00295 painter->drawText( x, textY + y + ascent, 00296 d->m_text.mid( start ) ); 00297 } 00298 } 00299 00300 QRect KWordWrap::boundingRect() const 00301 { 00302 return d->m_boundingRect; 00303 } 00304
KDE 4.6 API Reference