1 /* This file is part of the KDE libraries
2 Copyright (C) 2001 David Faure <faure@kde.org>
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
19 #include "kwordwrap.h"
22 #include <QtGui/QPainter>
23 #include <QtCore/QMutableVectorIterator>
25 class KWordWrapPrivate
{
27 QRect m_constrainingRect
;
28 QVector
<int> m_breakPositions
;
29 QVector
<int> m_lineWidths
;
34 KWordWrap::KWordWrap(const QRect
& r
)
35 : d(new KWordWrapPrivate
)
37 d
->m_constrainingRect
= r
;
40 KWordWrap
* KWordWrap::formatText( QFontMetrics
&fm
, const QRect
& r
, int /*flags*/, const QString
& str
, int len
)
42 KWordWrap
* kw
= new KWordWrap( r
);
43 // The wordwrap algorithm
44 // The variable names and the global shape of the algorithm are inspired
45 // from QTextFormatterBreakWords::format().
46 //kDebug() << "KWordWrap::formatText " << str << " r=" << r.x() << "," << r.y() << " " << r.width() << "x" << r.height();
47 int height
= fm
.height();
51 kw
->d
->m_text
= str
.left( len
);
60 bool isBreakable
= false;
61 bool wasBreakable
= false; // value of isBreakable for last char (i-1)
62 bool isParens
= false; // true if one of ({[
63 bool wasParens
= false; // value of isParens for last char (i-1)
64 QString inputString
= str
;
66 for ( int i
= 0 ; i
< len
; ++i
)
68 const QChar c
= inputString
.at(i
);
69 const int ww
= fm
.charWidth(inputString
, i
);
71 isParens
= ( c
== QLatin1Char('(') || c
== QLatin1Char('[')
72 || c
== QLatin1Char('{') );
73 // isBreakable is true when we can break _after_ this character.
74 isBreakable
= ( c
.isSpace() || c
.isPunct() || c
.isSymbol() ) & !isParens
;
76 // Special case for '(', '[' and '{': we want to break before them
77 if ( !isBreakable
&& i
< len
-1 ) {
78 const QChar nextc
= inputString
.at(i
+ 1); // look at next char
79 isBreakable
= ( nextc
== QLatin1Char('(')
80 || nextc
== QLatin1Char('[')
81 || nextc
== QLatin1Char('{') );
83 // Special case for '/': after normal chars it's breakable (e.g. inside a path),
84 // but after another breakable char it's not (e.g. "mounted at /foo")
85 // Same thing after a parenthesis (e.g. "dfaure [/fool]")
86 if ( c
== QLatin1Char('/') && (wasBreakable
|| wasParens
) )
89 /*kDebug() << "c='" << QString(c) << "' i=" << i << "/" << len
90 << " x=" << x << " ww=" << ww << " w=" << w
91 << " lastBreak=" << lastBreak << " isBreakable=" << isBreakable << endl;*/
93 if ( x
+ ww
> w
&& lastBreak
!= -1 ) // time to break and we know where
95 if ( x
+ ww
> w
- 4 && lastBreak
== -1 ) // time to break but found nowhere [-> break here]
97 if (i
== len
- 2 && x
+ ww
+ fm
.charWidth(inputString
, i
+1) > w
) // don't leave the last char alone
98 breakAt
= lastBreak
== -1 ? i
- 1 : lastBreak
;
99 if ( c
== QLatin1Char('\n') ) // Forced break here
101 if ( breakAt
== -1 && lastBreak
!= -1) // only break if not already breaking
106 // remove the line feed from the string
107 kw
->d
->m_text
.remove(i
, 1);
108 inputString
.remove(i
, 1);
113 //kDebug() << "KWordWrap::formatText breaking after " << breakAt;
114 kw
->d
->m_breakPositions
.append( breakAt
);
115 int thisLineWidth
= lastBreak
== -1 ? x
+ ww
: lineWidth
;
116 kw
->d
->m_lineWidths
.append( thisLineWidth
);
117 textwidth
= qMax( textwidth
, thisLineWidth
);
122 if ( lastBreak
!= -1 )
124 // Breakable char was found, restart from there
129 } else if ( isBreakable
)
135 wasBreakable
= isBreakable
;
136 wasParens
= isParens
;
138 textwidth
= qMax( textwidth
, x
);
139 kw
->d
->m_lineWidths
.append( x
);
141 //kDebug() << "KWordWrap::formatText boundingRect:" << r.x() << "," << r.y() << " " << textwidth << "x" << y;
142 if ( r
.height() >= 0 && y
> r
.height() )
143 textwidth
= r
.width();
145 if ( r
.height() >= 0 )
147 while ( realY
> r
.height() )
149 realY
= qMax( realY
, 0 );
151 kw
->d
->m_boundingRect
.setRect( 0, 0, textwidth
, realY
);
155 KWordWrap::~KWordWrap() {
159 QString
KWordWrap::wrappedString() const
161 // We use the calculated break positions to insert '\n' into the string
164 for (int i
= 0; i
< d
->m_breakPositions
.count(); ++i
) {
165 int end
= d
->m_breakPositions
.at(i
);
166 ws
+= d
->m_text
.mid( start
, end
- start
+ 1 );
167 ws
+= QLatin1Char('\n');
170 ws
+= d
->m_text
.mid( start
);
174 QString
KWordWrap::truncatedString( bool dots
) const
176 if ( d
->m_breakPositions
.isEmpty() )
179 QString ts
= d
->m_text
.left( d
->m_breakPositions
.first() + 1 );
181 ts
+= QLatin1String("...");
185 static QColor
mixColors(double p1
, QColor c1
, QColor c2
) {
186 return QColor(int(c1
.red() * p1
+ c2
.red() * (1.0-p1
)),
187 int(c1
.green() * p1
+ c2
.green() * (1.0-p1
)),
188 int(c1
.blue() * p1
+ c2
.blue() * (1.0-p1
)));
191 void KWordWrap::drawFadeoutText(QPainter
*p
, int x
, int y
, int maxW
,
193 QFontMetrics fm
= p
->fontMetrics();
194 QColor bgColor
= p
->background().color();
195 QColor textColor
= p
->pen().color();
197 if ( ( fm
.boundingRect( t
).width() > maxW
) && ( t
.length() > 1 ) ) {
200 while ( tl
< t
.length() ) {
201 w
+= fm
.charWidth( t
, tl
);
207 int n
= qMin( tl
, 3);
208 if ( t
.isRightToLeft() ) {
209 x
+= maxW
; // start from the right side for RTL string
211 x
-= fm
.width( t
.left( tl
- 3 ) );
212 p
->drawText( x
, y
, t
.left( tl
- 3 ) );
214 for (int i
= 0; i
< n
; i
++) {
215 p
->setPen( mixColors( 0.70 - i
* 0.25, textColor
, bgColor
) );
216 QString
s( t
.at( tl
- n
+ i
) );
218 p
->drawText( x
, y
, s
);
223 p
->drawText( x
, y
, t
.left( tl
- 3 ) );
224 x
+= fm
.width( t
.left( tl
- 3 ) );
226 for (int i
= 0; i
< n
; i
++) {
227 p
->setPen( mixColors( 0.70 - i
* 0.25, textColor
, bgColor
) );
228 QString
s( t
.at( tl
- n
+ i
) );
229 p
->drawText( x
, y
, s
);
235 p
->drawText( x
, y
, t
);
238 void KWordWrap::drawTruncateText(QPainter
*p
, int x
, int y
, int maxW
,
240 QString tmpText
= p
->fontMetrics().elidedText(t
, Qt::ElideRight
, maxW
);
241 p
->drawText( x
, y
, tmpText
);
244 void KWordWrap::drawText( QPainter
*painter
, int textX
, int textY
, int flags
) const
246 //kDebug() << "KWordWrap::drawText text=" << wrappedString() << " x=" << textX << " y=" << textY;
247 // We use the calculated break positions to draw the text line by line using QPainter
250 QFontMetrics fm
= painter
->fontMetrics();
251 int height
= fm
.height(); // line height
252 int ascent
= fm
.ascent();
253 int maxwidth
= d
->m_boundingRect
.width();
257 for (i
= 0; i
< d
->m_breakPositions
.count() ; ++i
)
259 // if this is the last line, leave the loop
260 if ( (d
->m_constrainingRect
.height() >= 0) &&
261 ((y
+ 2 * height
) > d
->m_constrainingRect
.height()) )
263 end
= d
->m_breakPositions
.at(i
);
264 lwidth
= d
->m_lineWidths
.at(i
);
266 if ( flags
& Qt::AlignHCenter
)
267 x
+= ( maxwidth
- lwidth
) / 2;
268 else if ( flags
& Qt::AlignRight
)
269 x
+= maxwidth
- lwidth
;
270 painter
->drawText( x
, textY
+ y
+ ascent
, d
->m_text
.mid( start
, end
- start
+ 1 ) );
275 // Draw the last line
276 lwidth
= d
->m_lineWidths
.last();
278 if ( flags
& Qt::AlignHCenter
)
279 x
+= ( maxwidth
- lwidth
) / 2;
280 else if ( flags
& Qt::AlignRight
)
281 x
+= maxwidth
- lwidth
;
282 if ( (d
->m_constrainingRect
.height() < 0) ||
283 ((y
+ height
) <= d
->m_constrainingRect
.height()) ) {
284 if ( i
== d
->m_breakPositions
.count() )
285 painter
->drawText( x
, textY
+ y
+ ascent
, d
->m_text
.mid( start
) );
286 else if (flags
& FadeOut
)
287 drawFadeoutText( painter
, textX
, textY
+ y
+ ascent
,
288 d
->m_constrainingRect
.width(),
289 d
->m_text
.mid( start
) );
290 else if (flags
& Truncate
)
291 drawTruncateText( painter
, textX
, textY
+ y
+ ascent
,
292 d
->m_constrainingRect
.width(),
293 d
->m_text
.mid( start
) );
295 painter
->drawText( x
, textY
+ y
+ ascent
,
296 d
->m_text
.mid( start
) );
300 QRect
KWordWrap::boundingRect() const
302 return d
->m_boundingRect
;