|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtDeclarative module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 |
|
42 #include "private/qdeclarativetext_p.h" |
|
43 #include "private/qdeclarativetext_p_p.h" |
|
44 #include <qdeclarativestyledtext_p.h> |
|
45 #include <qdeclarativeinfo.h> |
|
46 #include <qdeclarativepixmapcache_p.h> |
|
47 |
|
48 #include <QSet> |
|
49 #include <QTextLayout> |
|
50 #include <QTextLine> |
|
51 #include <QTextDocument> |
|
52 #include <QTextCursor> |
|
53 #include <QGraphicsSceneMouseEvent> |
|
54 #include <QPainter> |
|
55 #include <QAbstractTextDocumentLayout> |
|
56 #include <qmath.h> |
|
57 |
|
58 QT_BEGIN_NAMESPACE |
|
59 |
|
60 class QTextDocumentWithImageResources : public QTextDocument { |
|
61 Q_OBJECT |
|
62 |
|
63 public: |
|
64 QTextDocumentWithImageResources(QDeclarativeText *parent) : |
|
65 QTextDocument(parent), |
|
66 outstanding(0) |
|
67 { |
|
68 } |
|
69 |
|
70 int resourcesLoading() const { return outstanding; } |
|
71 |
|
72 protected: |
|
73 QVariant loadResource(int type, const QUrl &name) |
|
74 { |
|
75 QUrl url = qmlContext(parent())->resolvedUrl(name); |
|
76 |
|
77 if (type == QTextDocument::ImageResource) { |
|
78 QPixmap pm; |
|
79 QString errorString; |
|
80 QDeclarativePixmapReply::Status status = QDeclarativePixmapCache::get(url, &pm, &errorString, 0, false, 0, 0); |
|
81 if (status == QDeclarativePixmapReply::Ready) |
|
82 return pm; |
|
83 if (status == QDeclarativePixmapReply::Error) { |
|
84 if (!errors.contains(url)) { |
|
85 errors.insert(url); |
|
86 qmlInfo(parent()) << errorString; |
|
87 } |
|
88 } else { |
|
89 QDeclarativePixmapReply *reply = QDeclarativePixmapCache::request(qmlEngine(parent()), url); |
|
90 connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); |
|
91 outstanding++; |
|
92 } |
|
93 } |
|
94 |
|
95 return QTextDocument::loadResource(type,url); // The *resolved* URL |
|
96 } |
|
97 |
|
98 private slots: |
|
99 void requestFinished() |
|
100 { |
|
101 outstanding--; |
|
102 if (outstanding == 0) |
|
103 static_cast<QDeclarativeText*>(parent())->reloadWithResources(); |
|
104 } |
|
105 |
|
106 private: |
|
107 int outstanding; |
|
108 static QSet<QUrl> errors; |
|
109 }; |
|
110 |
|
111 QSet<QUrl> QTextDocumentWithImageResources::errors; |
|
112 |
|
113 /*! |
|
114 \qmlclass Text QDeclarativeText |
|
115 \since 4.7 |
|
116 \brief The Text item allows you to add formatted text to a scene. |
|
117 \inherits Item |
|
118 |
|
119 A Text item can display both plain and rich text. For example: |
|
120 |
|
121 \qml |
|
122 Text { text: "Hello World!"; font.family: "Helvetica"; font.pointSize: 24; color: "red" } |
|
123 Text { text: "<b>Hello</b> <i>World!</i>" } |
|
124 \endqml |
|
125 |
|
126 \image declarative-text.png |
|
127 |
|
128 If height and width are not explicitly set, Text will attempt to determine how |
|
129 much room is needed and set it accordingly. Unless \c wrapMode is set, it will always |
|
130 prefer width to height (all text will be placed on a single line). |
|
131 |
|
132 The \c elide property can alternatively be used to fit a single line of |
|
133 plain text to a set width. |
|
134 |
|
135 Note that the \l{Supported HTML Subset} is limited. Also, if the text contains |
|
136 HTML img tags that load remote images, the text is reloaded. |
|
137 |
|
138 Text provides read-only text. For editable text, see \l TextEdit. |
|
139 */ |
|
140 |
|
141 /*! |
|
142 \internal |
|
143 \class QDeclarativeText |
|
144 \qmlclass Text |
|
145 |
|
146 \brief The QDeclarativeText class provides a formatted text item that you can add to a QDeclarativeView. |
|
147 |
|
148 Text was designed for read-only text; it does not allow for any text editing. |
|
149 It can display both plain and rich text. For example: |
|
150 |
|
151 \qml |
|
152 Text { text: "Hello World!"; font.family: "Helvetica"; font.pointSize: 24; color: "red" } |
|
153 Text { text: "<b>Hello</b> <i>World!</i>" } |
|
154 \endqml |
|
155 |
|
156 \image text.png |
|
157 |
|
158 If height and width are not explicitly set, Text will attempt to determine how |
|
159 much room is needed and set it accordingly. Unless \c wrapMode is set, it will always |
|
160 prefer width to height (all text will be placed on a single line). |
|
161 |
|
162 The \c elide property can alternatively be used to fit a line of plain text to a set width. |
|
163 |
|
164 A QDeclarativeText object can be instantiated in Qml using the tag \c Text. |
|
165 */ |
|
166 QDeclarativeText::QDeclarativeText(QDeclarativeItem *parent) |
|
167 : QDeclarativeItem(*(new QDeclarativeTextPrivate), parent) |
|
168 { |
|
169 } |
|
170 |
|
171 QDeclarativeText::~QDeclarativeText() |
|
172 { |
|
173 } |
|
174 |
|
175 |
|
176 QDeclarativeTextPrivate::~QDeclarativeTextPrivate() |
|
177 { |
|
178 } |
|
179 |
|
180 /*! |
|
181 \qmlproperty string Text::font.family |
|
182 |
|
183 Sets the family name of the font. |
|
184 |
|
185 The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]". |
|
186 If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen. |
|
187 If the family isn't available a family will be set using the font matching algorithm. |
|
188 */ |
|
189 |
|
190 /*! |
|
191 \qmlproperty bool Text::font.bold |
|
192 |
|
193 Sets whether the font weight is bold. |
|
194 */ |
|
195 |
|
196 /*! |
|
197 \qmlproperty enumeration Text::font.weight |
|
198 |
|
199 Sets the font's weight. |
|
200 |
|
201 The weight can be one of: |
|
202 \list |
|
203 \o Font.Light |
|
204 \o Font.Normal - the default |
|
205 \o Font.DemiBold |
|
206 \o Font.Bold |
|
207 \o Font.Black |
|
208 \endlist |
|
209 |
|
210 \qml |
|
211 Text { text: "Hello"; font.weight: Font.DemiBold } |
|
212 \endqml |
|
213 */ |
|
214 |
|
215 /*! |
|
216 \qmlproperty bool Text::font.italic |
|
217 |
|
218 Sets whether the font has an italic style. |
|
219 */ |
|
220 |
|
221 /*! |
|
222 \qmlproperty bool Text::font.underline |
|
223 |
|
224 Sets whether the text is underlined. |
|
225 */ |
|
226 |
|
227 /*! |
|
228 \qmlproperty bool Text::font.outline |
|
229 |
|
230 Sets whether the font has an outline style. |
|
231 */ |
|
232 |
|
233 /*! |
|
234 \qmlproperty bool Text::font.strikeout |
|
235 |
|
236 Sets whether the font has a strikeout style. |
|
237 */ |
|
238 |
|
239 /*! |
|
240 \qmlproperty real Text::font.pointSize |
|
241 |
|
242 Sets the font size in points. The point size must be greater than zero. |
|
243 */ |
|
244 |
|
245 /*! |
|
246 \qmlproperty int Text::font.pixelSize |
|
247 |
|
248 Sets the font size in pixels. |
|
249 |
|
250 Using this function makes the font device dependent. |
|
251 Use \c pointSize to set the size of the font in a device independent manner. |
|
252 */ |
|
253 |
|
254 /*! |
|
255 \qmlproperty real Text::font.letterSpacing |
|
256 |
|
257 Sets the letter spacing for the font. |
|
258 |
|
259 Letter spacing changes the default spacing between individual letters in the font. |
|
260 A value of 100 will keep the spacing unchanged; a value of 200 will enlarge the spacing after a character by |
|
261 the width of the character itself. |
|
262 */ |
|
263 |
|
264 /*! |
|
265 \qmlproperty real Text::font.wordSpacing |
|
266 |
|
267 Sets the word spacing for the font. |
|
268 |
|
269 Word spacing changes the default spacing between individual words. |
|
270 A positive value increases the word spacing by a corresponding amount of pixels, |
|
271 while a negative value decreases the inter-word spacing accordingly. |
|
272 */ |
|
273 |
|
274 /*! |
|
275 \qmlproperty enumeration Text::font.capitalization |
|
276 |
|
277 Sets the capitalization for the text. |
|
278 |
|
279 \list |
|
280 \o Font.MixedCase - This is the normal text rendering option where no capitalization change is applied. |
|
281 \o Font.AllUppercase - This alters the text to be rendered in all uppercase type. |
|
282 \o Font.AllLowercase - This alters the text to be rendered in all lowercase type. |
|
283 \o Font.SmallCaps - This alters the text to be rendered in small-caps type. |
|
284 \o Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character. |
|
285 \endlist |
|
286 |
|
287 \qml |
|
288 Text { text: "Hello"; font.capitalization: Font.AllLowercase } |
|
289 \endqml |
|
290 */ |
|
291 |
|
292 QFont QDeclarativeText::font() const |
|
293 { |
|
294 Q_D(const QDeclarativeText); |
|
295 return d->font; |
|
296 } |
|
297 |
|
298 void QDeclarativeText::setFont(const QFont &font) |
|
299 { |
|
300 Q_D(QDeclarativeText); |
|
301 if (d->font == font) |
|
302 return; |
|
303 |
|
304 d->font = font; |
|
305 |
|
306 d->updateLayout(); |
|
307 d->markImgDirty(); |
|
308 emit fontChanged(d->font); |
|
309 } |
|
310 |
|
311 void QDeclarativeText::setText(const QString &n) |
|
312 { |
|
313 Q_D(QDeclarativeText); |
|
314 if (d->text == n) |
|
315 return; |
|
316 |
|
317 d->richText = d->format == RichText || (d->format == AutoText && Qt::mightBeRichText(n)); |
|
318 if (d->richText) { |
|
319 if (isComponentComplete()) { |
|
320 d->ensureDoc(); |
|
321 d->doc->setHtml(n); |
|
322 } |
|
323 } |
|
324 |
|
325 d->text = n; |
|
326 d->updateLayout(); |
|
327 d->markImgDirty(); |
|
328 emit textChanged(d->text); |
|
329 } |
|
330 |
|
331 /*! |
|
332 \qmlproperty string Text::text |
|
333 |
|
334 The text to display. Text supports both plain and rich text strings. |
|
335 |
|
336 The item will try to automatically determine whether the text should |
|
337 be treated as rich text. This determination is made using Qt::mightBeRichText(). |
|
338 */ |
|
339 QString QDeclarativeText::text() const |
|
340 { |
|
341 Q_D(const QDeclarativeText); |
|
342 return d->text; |
|
343 } |
|
344 |
|
345 void QDeclarativeText::setColor(const QColor &color) |
|
346 { |
|
347 Q_D(QDeclarativeText); |
|
348 if (d->color == color) |
|
349 return; |
|
350 |
|
351 d->color = color; |
|
352 d->markImgDirty(); |
|
353 emit colorChanged(d->color); |
|
354 } |
|
355 |
|
356 /*! |
|
357 \qmlproperty color Text::color |
|
358 |
|
359 The text color. |
|
360 |
|
361 \qml |
|
362 //green text using hexadecimal notation |
|
363 Text { color: "#00FF00"; ... } |
|
364 |
|
365 //steelblue text using SVG color name |
|
366 Text { color: "steelblue"; ... } |
|
367 \endqml |
|
368 */ |
|
369 |
|
370 QColor QDeclarativeText::color() const |
|
371 { |
|
372 Q_D(const QDeclarativeText); |
|
373 return d->color; |
|
374 } |
|
375 |
|
376 /*! |
|
377 \qmlproperty enumeration Text::style |
|
378 |
|
379 Set an additional text style. |
|
380 |
|
381 Supported text styles are: |
|
382 \list |
|
383 \o Text.Normal - the default |
|
384 \o Text.Outline |
|
385 \o Text.Raised |
|
386 \o Text.Sunken |
|
387 \endlist |
|
388 |
|
389 \qml |
|
390 Row { |
|
391 Text { font.pointSize: 24; text: "Normal" } |
|
392 Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" } |
|
393 Text { font.pointSize: 24; text: "Outline";style: Text.Outline; styleColor: "red" } |
|
394 Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" } |
|
395 } |
|
396 \endqml |
|
397 |
|
398 \image declarative-textstyle.png |
|
399 */ |
|
400 QDeclarativeText::TextStyle QDeclarativeText::style() const |
|
401 { |
|
402 Q_D(const QDeclarativeText); |
|
403 return d->style; |
|
404 } |
|
405 |
|
406 void QDeclarativeText::setStyle(QDeclarativeText::TextStyle style) |
|
407 { |
|
408 Q_D(QDeclarativeText); |
|
409 if (d->style == style) |
|
410 return; |
|
411 |
|
412 d->style = style; |
|
413 d->markImgDirty(); |
|
414 emit styleChanged(d->style); |
|
415 } |
|
416 |
|
417 void QDeclarativeText::setStyleColor(const QColor &color) |
|
418 { |
|
419 Q_D(QDeclarativeText); |
|
420 if (d->styleColor == color) |
|
421 return; |
|
422 |
|
423 d->styleColor = color; |
|
424 d->markImgDirty(); |
|
425 emit styleColorChanged(d->styleColor); |
|
426 } |
|
427 |
|
428 /*! |
|
429 \qmlproperty color Text::styleColor |
|
430 |
|
431 Defines the secondary color used by text styles. |
|
432 |
|
433 \c styleColor is used as the outline color for outlined text, and as the |
|
434 shadow color for raised or sunken text. If no style has been set, it is not |
|
435 used at all. |
|
436 |
|
437 \qml |
|
438 Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" } |
|
439 \endqml |
|
440 */ |
|
441 QColor QDeclarativeText::styleColor() const |
|
442 { |
|
443 Q_D(const QDeclarativeText); |
|
444 return d->styleColor; |
|
445 } |
|
446 |
|
447 /*! |
|
448 \qmlproperty enumeration Text::horizontalAlignment |
|
449 \qmlproperty enumeration Text::verticalAlignment |
|
450 |
|
451 Sets the horizontal and vertical alignment of the text within the Text items |
|
452 width and height. By default, the text is top-left aligned. |
|
453 |
|
454 The valid values for \c horizontalAlignment are \c Text.AlignLeft, \c Text.AlignRight and |
|
455 \c Text.AlignHCenter. The valid values for \c verticalAlignment are \c Text.AlignTop, \c Text.AlignBottom |
|
456 and \c Text.AlignVCenter. |
|
457 |
|
458 Note that for a single line of text, the size of the text is the area of the text. In this common case, |
|
459 all alignments are equivalent. If you want the text to be, say, centered in it parent, then you will |
|
460 need to either modify the Item::anchors, or set horizontalAlignment to Text.AlignHCenter and bind the width to |
|
461 that of the parent. |
|
462 */ |
|
463 QDeclarativeText::HAlignment QDeclarativeText::hAlign() const |
|
464 { |
|
465 Q_D(const QDeclarativeText); |
|
466 return d->hAlign; |
|
467 } |
|
468 |
|
469 void QDeclarativeText::setHAlign(HAlignment align) |
|
470 { |
|
471 Q_D(QDeclarativeText); |
|
472 if (d->hAlign == align) |
|
473 return; |
|
474 |
|
475 d->hAlign = align; |
|
476 emit horizontalAlignmentChanged(align); |
|
477 } |
|
478 |
|
479 QDeclarativeText::VAlignment QDeclarativeText::vAlign() const |
|
480 { |
|
481 Q_D(const QDeclarativeText); |
|
482 return d->vAlign; |
|
483 } |
|
484 |
|
485 void QDeclarativeText::setVAlign(VAlignment align) |
|
486 { |
|
487 Q_D(QDeclarativeText); |
|
488 if (d->vAlign == align) |
|
489 return; |
|
490 |
|
491 d->vAlign = align; |
|
492 emit verticalAlignmentChanged(align); |
|
493 } |
|
494 |
|
495 /*! |
|
496 \qmlproperty enumeration Text::wrapMode |
|
497 |
|
498 Set this property to wrap the text to the Text item's width. The text will only |
|
499 wrap if an explicit width has been set. wrapMode can be one of: |
|
500 |
|
501 \list |
|
502 \o Text.NoWrap - no wrapping will be performed. |
|
503 \o Text.WordWrap - wrapping is done on word boundaries. If the text cannot be |
|
504 word-wrapped to the specified width it will be partially drawn outside of the item's bounds. |
|
505 If this is undesirable then enable clipping on the item (Item::clip). |
|
506 \o Text.WrapAnywhere - Text can be wrapped at any point on a line, even if it occurs in the middle of a word. |
|
507 \o Text.WrapAtWordBoundaryOrAnywhere - If possible, wrapping occurs at a word boundary; otherwise it |
|
508 will occur at the appropriate point on the line, even in the middle of a word. |
|
509 \endlist |
|
510 |
|
511 The default is Text.NoWrap. |
|
512 */ |
|
513 QDeclarativeText::WrapMode QDeclarativeText::wrapMode() const |
|
514 { |
|
515 Q_D(const QDeclarativeText); |
|
516 return d->wrapMode; |
|
517 } |
|
518 |
|
519 void QDeclarativeText::setWrapMode(WrapMode mode) |
|
520 { |
|
521 Q_D(QDeclarativeText); |
|
522 if (mode == d->wrapMode) |
|
523 return; |
|
524 |
|
525 d->wrapMode = mode; |
|
526 |
|
527 d->updateLayout(); |
|
528 d->markImgDirty(); |
|
529 emit wrapModeChanged(); |
|
530 } |
|
531 |
|
532 |
|
533 /*! |
|
534 \qmlproperty enumeration Text::textFormat |
|
535 |
|
536 The way the text property should be displayed. |
|
537 |
|
538 Supported text formats are: |
|
539 |
|
540 \list |
|
541 \o Text.AutoText |
|
542 \o Text.PlainText |
|
543 \o Text.RichText |
|
544 \o Text.StyledText |
|
545 \endlist |
|
546 |
|
547 The default is Text.AutoText. If the text format is Text.AutoText the text element |
|
548 will automatically determine whether the text should be treated as |
|
549 rich text. This determination is made using Qt::mightBeRichText(). |
|
550 |
|
551 Text.StyledText is an optimized format supporting some basic text |
|
552 styling markup, in the style of html 3.2: |
|
553 |
|
554 \code |
|
555 <font size="4" color="#ff0000">font size and color</font> |
|
556 <b>bold</b> |
|
557 <i>italic</i> |
|
558 <br> |
|
559 > < & |
|
560 \endcode |
|
561 |
|
562 \c Text.StyledText parser is strict, requiring tags to be correctly nested. |
|
563 |
|
564 \table |
|
565 \row |
|
566 \o |
|
567 \qml |
|
568 Column { |
|
569 Text { |
|
570 font.pointSize: 24 |
|
571 text: "<b>Hello</b> <i>World!</i>" |
|
572 } |
|
573 Text { |
|
574 font.pointSize: 24 |
|
575 textFormat: Text.RichText |
|
576 text: "<b>Hello</b> <i>World!</i>" |
|
577 } |
|
578 Text { |
|
579 font.pointSize: 24 |
|
580 textFormat: Text.PlainText |
|
581 text: "<b>Hello</b> <i>World!</i>" |
|
582 } |
|
583 } |
|
584 \endqml |
|
585 \o \image declarative-textformat.png |
|
586 \endtable |
|
587 */ |
|
588 |
|
589 QDeclarativeText::TextFormat QDeclarativeText::textFormat() const |
|
590 { |
|
591 Q_D(const QDeclarativeText); |
|
592 return d->format; |
|
593 } |
|
594 |
|
595 void QDeclarativeText::setTextFormat(TextFormat format) |
|
596 { |
|
597 Q_D(QDeclarativeText); |
|
598 if (format == d->format) |
|
599 return; |
|
600 d->format = format; |
|
601 bool wasRich = d->richText; |
|
602 d->richText = format == RichText || (format == AutoText && Qt::mightBeRichText(d->text)); |
|
603 |
|
604 if (wasRich && !d->richText) { |
|
605 //### delete control? (and vice-versa below) |
|
606 d->updateLayout(); |
|
607 d->markImgDirty(); |
|
608 } else if (!wasRich && d->richText) { |
|
609 if (isComponentComplete()) { |
|
610 d->ensureDoc(); |
|
611 d->doc->setHtml(d->text); |
|
612 } |
|
613 d->updateLayout(); |
|
614 d->markImgDirty(); |
|
615 } |
|
616 |
|
617 emit textFormatChanged(d->format); |
|
618 } |
|
619 |
|
620 /*! |
|
621 \qmlproperty enumeration Text::elide |
|
622 |
|
623 Set this property to elide parts of the text fit to the Text item's width. |
|
624 The text will only elide if an explicit width has been set. |
|
625 |
|
626 This property cannot be used with wrapping enabled or with rich text. |
|
627 |
|
628 Eliding can be: |
|
629 \list |
|
630 \o Text.ElideNone - the default |
|
631 \o Text.ElideLeft |
|
632 \o Text.ElideMiddle |
|
633 \o Text.ElideRight |
|
634 \endlist |
|
635 |
|
636 If the text is a multi-length string, and the mode is not \c Text.ElideNone, |
|
637 the first string that fits will be used, otherwise the last will be elided. |
|
638 |
|
639 Multi-length strings are ordered from longest to shortest, separated by the |
|
640 Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}). |
|
641 */ |
|
642 QDeclarativeText::TextElideMode QDeclarativeText::elideMode() const |
|
643 { |
|
644 Q_D(const QDeclarativeText); |
|
645 return d->elideMode; |
|
646 } |
|
647 |
|
648 void QDeclarativeText::setElideMode(QDeclarativeText::TextElideMode mode) |
|
649 { |
|
650 Q_D(QDeclarativeText); |
|
651 if (mode == d->elideMode) |
|
652 return; |
|
653 |
|
654 d->elideMode = mode; |
|
655 |
|
656 d->updateLayout(); |
|
657 d->markImgDirty(); |
|
658 emit elideModeChanged(d->elideMode); |
|
659 } |
|
660 |
|
661 void QDeclarativeText::geometryChanged(const QRectF &newGeometry, |
|
662 const QRectF &oldGeometry) |
|
663 { |
|
664 Q_D(QDeclarativeText); |
|
665 if (newGeometry.width() != oldGeometry.width()) { |
|
666 if (d->wrapMode != QDeclarativeText::NoWrap || d->elideMode != QDeclarativeText::ElideNone) { |
|
667 //re-elide if needed |
|
668 if (d->singleline && d->elideMode != QDeclarativeText::ElideNone && |
|
669 isComponentComplete() && widthValid()) { |
|
670 |
|
671 QFontMetrics fm(d->font); |
|
672 QString tmp = fm.elidedText(d->text,(Qt::TextElideMode)d->elideMode,width()); // XXX still worth layout...? |
|
673 d->layout.setText(tmp); |
|
674 } |
|
675 |
|
676 d->imgDirty = true; |
|
677 d->updateSize(); |
|
678 } |
|
679 } |
|
680 QDeclarativeItem::geometryChanged(newGeometry, oldGeometry); |
|
681 } |
|
682 |
|
683 void QDeclarativeTextPrivate::updateLayout() |
|
684 { |
|
685 Q_Q(QDeclarativeText); |
|
686 if (q->isComponentComplete()) { |
|
687 //setup instance of QTextLayout for all cases other than richtext |
|
688 if (!richText) { |
|
689 layout.clearLayout(); |
|
690 layout.setFont(font); |
|
691 if (format != QDeclarativeText::StyledText) { |
|
692 QString tmp = text; |
|
693 tmp.replace(QLatin1Char('\n'), QChar::LineSeparator); |
|
694 singleline = !tmp.contains(QChar::LineSeparator); |
|
695 if (singleline && elideMode != QDeclarativeText::ElideNone && q->widthValid()) { |
|
696 QFontMetrics fm(font); |
|
697 tmp = fm.elidedText(tmp,(Qt::TextElideMode)elideMode,q->width()); // XXX still worth layout...? |
|
698 } |
|
699 layout.setText(tmp); |
|
700 } else { |
|
701 singleline = false; |
|
702 QDeclarativeStyledText::parse(text, layout); |
|
703 } |
|
704 } |
|
705 updateSize(); |
|
706 } else { |
|
707 dirty = true; |
|
708 } |
|
709 } |
|
710 |
|
711 void QDeclarativeTextPrivate::updateSize() |
|
712 { |
|
713 Q_Q(QDeclarativeText); |
|
714 if (q->isComponentComplete()) { |
|
715 QFontMetrics fm(font); |
|
716 if (text.isEmpty()) { |
|
717 q->setImplicitHeight(fm.height()); |
|
718 return; |
|
719 } |
|
720 |
|
721 int dy = q->height(); |
|
722 QSize size(0, 0); |
|
723 |
|
724 //setup instance of QTextLayout for all cases other than richtext |
|
725 if (!richText) { |
|
726 size = setupTextLayout(&layout); |
|
727 cachedLayoutSize = size; |
|
728 dy -= size.height(); |
|
729 } else { |
|
730 singleline = false; // richtext can't elide or be optimized for single-line case |
|
731 ensureDoc(); |
|
732 doc->setDefaultFont(font); |
|
733 QTextOption option((Qt::Alignment)int(hAlign | vAlign)); |
|
734 option.setWrapMode(QTextOption::WrapMode(wrapMode)); |
|
735 doc->setDefaultTextOption(option); |
|
736 if (wrapMode != QDeclarativeText::NoWrap && q->widthValid()) |
|
737 doc->setTextWidth(q->width()); |
|
738 else |
|
739 doc->setTextWidth(doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug) |
|
740 dy -= (int)doc->size().height(); |
|
741 cachedLayoutSize = doc->size().toSize(); |
|
742 } |
|
743 int yoff = 0; |
|
744 |
|
745 if (q->heightValid()) { |
|
746 if (vAlign == QDeclarativeText::AlignBottom) |
|
747 yoff = dy; |
|
748 else if (vAlign == QDeclarativeText::AlignVCenter) |
|
749 yoff = dy/2; |
|
750 } |
|
751 q->setBaselineOffset(fm.ascent() + yoff); |
|
752 |
|
753 //### need to comfirm cost of always setting these for richText |
|
754 q->setImplicitWidth(richText ? (int)doc->idealWidth() : size.width()); |
|
755 q->setImplicitHeight(richText ? (int)doc->size().height() : size.height()); |
|
756 } else { |
|
757 dirty = true; |
|
758 } |
|
759 } |
|
760 |
|
761 // ### text layout handling should be profiled and optimized as needed |
|
762 // what about QStackTextEngine engine(tmp, d->font.font()); QTextLayout textLayout(&engine); |
|
763 |
|
764 void QDeclarativeTextPrivate::drawOutline() |
|
765 { |
|
766 QPixmap img = QPixmap(imgStyleCache.width()+2,imgStyleCache.height()+2); |
|
767 img.fill(Qt::transparent); |
|
768 |
|
769 QPainter ppm(&img); |
|
770 |
|
771 QPoint pos(imgCache.rect().topLeft()); |
|
772 pos += QPoint(-1, 0); |
|
773 ppm.drawPixmap(pos, imgStyleCache); |
|
774 pos += QPoint(2, 0); |
|
775 ppm.drawPixmap(pos, imgStyleCache); |
|
776 pos += QPoint(-1, -1); |
|
777 ppm.drawPixmap(pos, imgStyleCache); |
|
778 pos += QPoint(0, 2); |
|
779 ppm.drawPixmap(pos, imgStyleCache); |
|
780 |
|
781 pos += QPoint(0, -1); |
|
782 ppm.drawPixmap(pos, imgCache); |
|
783 ppm.end(); |
|
784 |
|
785 imgCache = img; |
|
786 } |
|
787 |
|
788 void QDeclarativeTextPrivate::drawOutline(int yOffset) |
|
789 { |
|
790 QPixmap img = QPixmap(imgStyleCache.width()+2,imgStyleCache.height()+2); |
|
791 img.fill(Qt::transparent); |
|
792 |
|
793 QPainter ppm(&img); |
|
794 |
|
795 QPoint pos(imgCache.rect().topLeft()); |
|
796 pos += QPoint(0, yOffset); |
|
797 ppm.drawPixmap(pos, imgStyleCache); |
|
798 |
|
799 pos += QPoint(0, -yOffset); |
|
800 ppm.drawPixmap(pos, imgCache); |
|
801 ppm.end(); |
|
802 |
|
803 imgCache = img; |
|
804 } |
|
805 |
|
806 QSize QDeclarativeTextPrivate::setupTextLayout(QTextLayout *layout) |
|
807 { |
|
808 Q_Q(QDeclarativeText); |
|
809 layout->setCacheEnabled(true); |
|
810 |
|
811 int height = 0; |
|
812 qreal widthUsed = 0; |
|
813 qreal lineWidth = 0; |
|
814 |
|
815 //set manual width |
|
816 if ((wrapMode != QDeclarativeText::NoWrap || elideMode != QDeclarativeText::ElideNone) && q->widthValid()) |
|
817 lineWidth = q->width(); |
|
818 |
|
819 QTextOption textOption = layout->textOption(); |
|
820 textOption.setWrapMode(QTextOption::WrapMode(wrapMode)); |
|
821 layout->setTextOption(textOption); |
|
822 |
|
823 layout->beginLayout(); |
|
824 |
|
825 while (1) { |
|
826 QTextLine line = layout->createLine(); |
|
827 if (!line.isValid()) |
|
828 break; |
|
829 |
|
830 if ((wrapMode != QDeclarativeText::NoWrap || elideMode != QDeclarativeText::ElideNone) && q->widthValid()) |
|
831 line.setLineWidth(lineWidth); |
|
832 } |
|
833 layout->endLayout(); |
|
834 |
|
835 int x = 0; |
|
836 for (int i = 0; i < layout->lineCount(); ++i) { |
|
837 QTextLine line = layout->lineAt(i); |
|
838 widthUsed = qMax(widthUsed, line.naturalTextWidth()); |
|
839 line.setPosition(QPointF(0, height)); |
|
840 height += int(line.height()); |
|
841 |
|
842 if (!cache) { |
|
843 if (hAlign == QDeclarativeText::AlignLeft) { |
|
844 x = 0; |
|
845 } else if (hAlign == QDeclarativeText::AlignRight) { |
|
846 x = q->width() - (int)line.naturalTextWidth(); |
|
847 } else if (hAlign == QDeclarativeText::AlignHCenter) { |
|
848 x = (q->width() - (int)line.naturalTextWidth()) / 2; |
|
849 } |
|
850 line.setPosition(QPoint(x, (int)line.y())); |
|
851 } |
|
852 } |
|
853 |
|
854 return QSize(qCeil(widthUsed), height); |
|
855 } |
|
856 |
|
857 QPixmap QDeclarativeTextPrivate::wrappedTextImage(bool drawStyle) |
|
858 { |
|
859 //do layout |
|
860 QSize size = cachedLayoutSize; |
|
861 |
|
862 int x = 0; |
|
863 for (int i = 0; i < layout.lineCount(); ++i) { |
|
864 QTextLine line = layout.lineAt(i); |
|
865 if (hAlign == QDeclarativeText::AlignLeft) { |
|
866 x = 0; |
|
867 } else if (hAlign == QDeclarativeText::AlignRight) { |
|
868 x = size.width() - (int)line.naturalTextWidth(); |
|
869 } else if (hAlign == QDeclarativeText::AlignHCenter) { |
|
870 x = (size.width() - (int)line.naturalTextWidth()) / 2; |
|
871 } |
|
872 line.setPosition(QPoint(x, (int)line.y())); |
|
873 } |
|
874 |
|
875 //paint text |
|
876 QPixmap img(size); |
|
877 if (!size.isEmpty()) { |
|
878 img.fill(Qt::transparent); |
|
879 QPainter p(&img); |
|
880 drawWrappedText(&p, QPointF(0,0), drawStyle); |
|
881 } |
|
882 return img; |
|
883 } |
|
884 |
|
885 void QDeclarativeTextPrivate::drawWrappedText(QPainter *p, const QPointF &pos, bool drawStyle) |
|
886 { |
|
887 if (drawStyle) |
|
888 p->setPen(styleColor); |
|
889 else |
|
890 p->setPen(color); |
|
891 p->setFont(font); |
|
892 layout.draw(p, pos); |
|
893 } |
|
894 |
|
895 QPixmap QDeclarativeTextPrivate::richTextImage(bool drawStyle) |
|
896 { |
|
897 QSize size = doc->size().toSize(); |
|
898 |
|
899 //paint text |
|
900 QPixmap img(size); |
|
901 img.fill(Qt::transparent); |
|
902 QPainter p(&img); |
|
903 |
|
904 QAbstractTextDocumentLayout::PaintContext context; |
|
905 |
|
906 if (drawStyle) { |
|
907 context.palette.setColor(QPalette::Text, styleColor); |
|
908 // ### Do we really want this? |
|
909 QTextOption colorOption; |
|
910 colorOption.setFlags(QTextOption::SuppressColors); |
|
911 doc->setDefaultTextOption(colorOption); |
|
912 } else { |
|
913 context.palette.setColor(QPalette::Text, color); |
|
914 } |
|
915 doc->documentLayout()->draw(&p, context); |
|
916 if (drawStyle) |
|
917 doc->setDefaultTextOption(QTextOption()); |
|
918 return img; |
|
919 } |
|
920 |
|
921 void QDeclarativeTextPrivate::checkImgCache() |
|
922 { |
|
923 if (!imgDirty) |
|
924 return; |
|
925 |
|
926 bool empty = text.isEmpty(); |
|
927 if (empty) { |
|
928 imgCache = QPixmap(); |
|
929 imgStyleCache = QPixmap(); |
|
930 } else if (richText) { |
|
931 imgCache = richTextImage(false); |
|
932 if (style != QDeclarativeText::Normal) |
|
933 imgStyleCache = richTextImage(true); //### should use styleColor |
|
934 } else { |
|
935 imgCache = wrappedTextImage(false); |
|
936 if (style != QDeclarativeText::Normal) |
|
937 imgStyleCache = wrappedTextImage(true); //### should use styleColor |
|
938 } |
|
939 if (!empty) |
|
940 switch (style) { |
|
941 case QDeclarativeText::Outline: |
|
942 drawOutline(); |
|
943 break; |
|
944 case QDeclarativeText::Sunken: |
|
945 drawOutline(-1); |
|
946 break; |
|
947 case QDeclarativeText::Raised: |
|
948 drawOutline(1); |
|
949 break; |
|
950 default: |
|
951 break; |
|
952 } |
|
953 |
|
954 imgDirty = false; |
|
955 } |
|
956 |
|
957 void QDeclarativeTextPrivate::ensureDoc() |
|
958 { |
|
959 if (!doc) { |
|
960 Q_Q(QDeclarativeText); |
|
961 doc = new QTextDocumentWithImageResources(q); |
|
962 doc->setDocumentMargin(0); |
|
963 } |
|
964 } |
|
965 |
|
966 void QDeclarativeText::reloadWithResources() |
|
967 { |
|
968 Q_D(QDeclarativeText); |
|
969 if (!d->richText) |
|
970 return; |
|
971 d->doc->setHtml(d->text); |
|
972 d->updateLayout(); |
|
973 d->markImgDirty(); |
|
974 } |
|
975 |
|
976 /*! |
|
977 Returns the number of resources (images) that are being loaded asynchronously. |
|
978 */ |
|
979 int QDeclarativeText::resourcesLoading() const |
|
980 { |
|
981 Q_D(const QDeclarativeText); |
|
982 return d->doc ? d->doc->resourcesLoading() : 0; |
|
983 } |
|
984 |
|
985 void QDeclarativeText::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) |
|
986 { |
|
987 Q_D(QDeclarativeText); |
|
988 |
|
989 if (d->cache || d->style != Normal) { |
|
990 d->checkImgCache(); |
|
991 if (d->imgCache.isNull()) |
|
992 return; |
|
993 |
|
994 bool oldAA = p->testRenderHint(QPainter::Antialiasing); |
|
995 bool oldSmooth = p->testRenderHint(QPainter::SmoothPixmapTransform); |
|
996 if (d->smooth) |
|
997 p->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, d->smooth); |
|
998 |
|
999 int w = width(); |
|
1000 int h = height(); |
|
1001 |
|
1002 int x = 0; |
|
1003 int y = 0; |
|
1004 |
|
1005 switch (d->hAlign) { |
|
1006 case AlignLeft: |
|
1007 x = 0; |
|
1008 break; |
|
1009 case AlignRight: |
|
1010 x = w - d->imgCache.width(); |
|
1011 break; |
|
1012 case AlignHCenter: |
|
1013 x = (w - d->imgCache.width()) / 2; |
|
1014 break; |
|
1015 } |
|
1016 |
|
1017 switch (d->vAlign) { |
|
1018 case AlignTop: |
|
1019 y = 0; |
|
1020 break; |
|
1021 case AlignBottom: |
|
1022 y = h - d->imgCache.height(); |
|
1023 break; |
|
1024 case AlignVCenter: |
|
1025 y = (h - d->imgCache.height()) / 2; |
|
1026 break; |
|
1027 } |
|
1028 |
|
1029 bool needClip = clip() && (d->imgCache.width() > width() || |
|
1030 d->imgCache.height() > height()); |
|
1031 |
|
1032 if (needClip) { |
|
1033 p->save(); |
|
1034 p->setClipRect(boundingRect(), Qt::IntersectClip); |
|
1035 } |
|
1036 p->drawPixmap(x, y, d->imgCache); |
|
1037 if (needClip) |
|
1038 p->restore(); |
|
1039 |
|
1040 if (d->smooth) { |
|
1041 p->setRenderHint(QPainter::Antialiasing, oldAA); |
|
1042 p->setRenderHint(QPainter::SmoothPixmapTransform, oldSmooth); |
|
1043 } |
|
1044 } else { |
|
1045 int h = height(); |
|
1046 int y = 0; |
|
1047 |
|
1048 switch (d->vAlign) { |
|
1049 case AlignTop: |
|
1050 y = 0; |
|
1051 break; |
|
1052 case AlignBottom: |
|
1053 y = h - d->cachedLayoutSize.height(); |
|
1054 break; |
|
1055 case AlignVCenter: |
|
1056 y = (h - d->cachedLayoutSize.height()) / 2; |
|
1057 break; |
|
1058 } |
|
1059 bool needClip = !clip() && (d->cachedLayoutSize.width() > width() || |
|
1060 d->cachedLayoutSize.height() > height()); |
|
1061 |
|
1062 if (needClip) { |
|
1063 p->save(); |
|
1064 p->setClipRect(boundingRect(), Qt::IntersectClip); |
|
1065 } |
|
1066 if (d->richText) { |
|
1067 QAbstractTextDocumentLayout::PaintContext context; |
|
1068 context.palette.setColor(QPalette::Text, d->color); |
|
1069 p->translate(0, y); |
|
1070 d->doc->documentLayout()->draw(p, context); |
|
1071 p->translate(0, -y); |
|
1072 } else { |
|
1073 d->drawWrappedText(p, QPointF(0,y), false); |
|
1074 } |
|
1075 if (needClip) |
|
1076 p->restore(); |
|
1077 } |
|
1078 } |
|
1079 |
|
1080 /*! |
|
1081 \qmlproperty bool Text::smooth |
|
1082 |
|
1083 This property holds whether the text is smoothly scaled or transformed. |
|
1084 |
|
1085 Smooth filtering gives better visual quality, but is slower. If |
|
1086 the item is displayed at its natural size, this property has no visual or |
|
1087 performance effect. |
|
1088 |
|
1089 \note Generally scaling artifacts are only visible if the item is stationary on |
|
1090 the screen. A common pattern when animating an item is to disable smooth |
|
1091 filtering at the beginning of the animation and reenable it at the conclusion. |
|
1092 */ |
|
1093 |
|
1094 void QDeclarativeText::componentComplete() |
|
1095 { |
|
1096 Q_D(QDeclarativeText); |
|
1097 QDeclarativeItem::componentComplete(); |
|
1098 if (d->dirty) { |
|
1099 if (d->richText) { |
|
1100 d->ensureDoc(); |
|
1101 d->doc->setHtml(d->text); |
|
1102 } |
|
1103 d->updateLayout(); |
|
1104 d->dirty = false; |
|
1105 } |
|
1106 } |
|
1107 |
|
1108 /*! |
|
1109 \overload |
|
1110 Handles the given mouse \a event. |
|
1111 */ |
|
1112 void QDeclarativeText::mousePressEvent(QGraphicsSceneMouseEvent *event) |
|
1113 { |
|
1114 Q_D(QDeclarativeText); |
|
1115 |
|
1116 if (!d->richText || !d->doc || d->doc->documentLayout()->anchorAt(event->pos()).isEmpty()) { |
|
1117 event->setAccepted(false); |
|
1118 d->activeLink = QString(); |
|
1119 } else { |
|
1120 d->activeLink = d->doc->documentLayout()->anchorAt(event->pos()); |
|
1121 } |
|
1122 |
|
1123 // ### may malfunction if two of the same links are clicked & dragged onto each other) |
|
1124 |
|
1125 if (!event->isAccepted()) |
|
1126 QDeclarativeItem::mousePressEvent(event); |
|
1127 |
|
1128 } |
|
1129 |
|
1130 /*! |
|
1131 \qmlsignal Text::linkActivated(link) |
|
1132 |
|
1133 This handler is called when the user clicks on a link embedded in the text. |
|
1134 */ |
|
1135 |
|
1136 /*! |
|
1137 \overload |
|
1138 Handles the given mouse \a event. |
|
1139 */ |
|
1140 void QDeclarativeText::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) |
|
1141 { |
|
1142 Q_D(QDeclarativeText); |
|
1143 |
|
1144 // ### confirm the link, and send a signal out |
|
1145 if (d->richText && d->doc && d->activeLink == d->doc->documentLayout()->anchorAt(event->pos())) |
|
1146 emit linkActivated(d->activeLink); |
|
1147 else |
|
1148 event->setAccepted(false); |
|
1149 |
|
1150 if (!event->isAccepted()) |
|
1151 QDeclarativeItem::mouseReleaseEvent(event); |
|
1152 } |
|
1153 |
|
1154 QT_END_NAMESPACE |
|
1155 |
|
1156 #include "qdeclarativetext.moc" |