source: trunk/src/gui/widgets/qtextbrowser.cpp@ 500

Last change on this file since 500 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 36.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qtextbrowser.h"
43#include "qtextedit_p.h"
44
45#ifndef QT_NO_TEXTBROWSER
46
47#include <qstack.h>
48#include <qapplication.h>
49#include <qevent.h>
50#include <qdesktopwidget.h>
51#include <qdebug.h>
52#include <qabstracttextdocumentlayout.h>
53#include "private/qtextdocumentlayout_p.h"
54#include <qtextcodec.h>
55#include <qpainter.h>
56#include <qdir.h>
57#include <qwhatsthis.h>
58#include <qtextobject.h>
59#include <qdesktopservices.h>
60
61QT_BEGIN_NAMESPACE
62
63class QTextBrowserPrivate : public QTextEditPrivate
64{
65 Q_DECLARE_PUBLIC(QTextBrowser)
66public:
67 inline QTextBrowserPrivate()
68 : textOrSourceChanged(false), forceLoadOnSourceChange(false), openExternalLinks(false),
69 openLinks(true)
70#ifdef QT_KEYPAD_NAVIGATION
71 , lastKeypadScrollValue(-1)
72#endif
73 {}
74
75 void init();
76
77 struct HistoryEntry {
78 inline HistoryEntry()
79 : hpos(0), vpos(0), focusIndicatorPosition(-1),
80 focusIndicatorAnchor(-1) {}
81 QUrl url;
82 QString title;
83 int hpos;
84 int vpos;
85 int focusIndicatorPosition, focusIndicatorAnchor;
86 };
87
88 HistoryEntry history(int i) const
89 {
90 if (i <= 0)
91 if (-i < stack.count())
92 return stack[stack.count()+i-1];
93 else
94 return HistoryEntry();
95 else
96 if (i <= forwardStack.count())
97 return forwardStack[forwardStack.count()-i];
98 else
99 return HistoryEntry();
100 }
101
102
103 HistoryEntry createHistoryEntry() const;
104 void restoreHistoryEntry(const HistoryEntry entry);
105
106 QStack<HistoryEntry> stack;
107 QStack<HistoryEntry> forwardStack;
108 QUrl home;
109 QUrl currentURL;
110
111 QStringList searchPaths;
112
113 /*flag necessary to give the linkClicked() signal some meaningful
114 semantics when somebody connected to it calls setText() or
115 setSource() */
116 bool textOrSourceChanged;
117 bool forceLoadOnSourceChange;
118
119 bool openExternalLinks;
120 bool openLinks;
121
122#ifndef QT_NO_CURSOR
123 QCursor oldCursor;
124#endif
125
126 QString findFile(const QUrl &name) const;
127
128 inline void _q_documentModified()
129 {
130 textOrSourceChanged = true;
131 forceLoadOnSourceChange = !currentURL.path().isEmpty();
132 }
133
134 void _q_activateAnchor(const QString &href);
135 void _q_highlightLink(const QString &href);
136
137 void setSource(const QUrl &url);
138
139 // re-imlemented from QTextEditPrivate
140 virtual QUrl resolveUrl(const QUrl &url) const;
141 inline QUrl resolveUrl(const QString &url) const
142 { return resolveUrl(QUrl::fromEncoded(url.toUtf8())); }
143
144#ifdef QT_KEYPAD_NAVIGATION
145 void keypadMove(bool next);
146 QTextCursor prevFocus;
147 int lastKeypadScrollValue;
148#endif
149};
150
151QString QTextBrowserPrivate::findFile(const QUrl &name) const
152{
153 QString fileName;
154 if (name.scheme() == QLatin1String("qrc"))
155 fileName = QLatin1String(":/") + name.path();
156 else
157 fileName = name.toLocalFile();
158
159 if (QFileInfo(fileName).isAbsolute())
160 return fileName;
161
162 foreach (QString path, searchPaths) {
163 if (!path.endsWith(QLatin1Char('/')))
164 path.append(QLatin1Char('/'));
165 path.append(fileName);
166 if (QFileInfo(path).isReadable())
167 return path;
168 }
169
170 return fileName;
171}
172
173QUrl QTextBrowserPrivate::resolveUrl(const QUrl &url) const
174{
175 if (!url.isRelative())
176 return url;
177
178 // For the second case QUrl can merge "#someanchor" with "foo.html"
179 // correctly to "foo.html#someanchor"
180 if (!(currentURL.isRelative()
181 || (currentURL.scheme() == QLatin1String("file")
182 && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
183 || (url.hasFragment() && url.path().isEmpty())) {
184 return currentURL.resolved(url);
185 }
186
187 // this is our last resort when current url and new url are both relative
188 // we try to resolve against the current working directory in the local
189 // file system.
190 QFileInfo fi(currentURL.toLocalFile());
191 if (fi.exists()) {
192 return QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(url);
193 }
194
195 return url;
196}
197
198void QTextBrowserPrivate::_q_activateAnchor(const QString &href)
199{
200 if (href.isEmpty())
201 return;
202 Q_Q(QTextBrowser);
203
204#ifndef QT_NO_CURSOR
205 viewport->setCursor(oldCursor);
206#endif
207
208 const QUrl url = resolveUrl(href);
209
210 if (!openLinks) {
211 emit q->anchorClicked(url);
212 return;
213 }
214
215 textOrSourceChanged = false;
216
217#ifndef QT_NO_DESKTOPSERVICES
218 if ((openExternalLinks
219 && url.scheme() != QLatin1String("file")
220 && url.scheme() != QLatin1String("qrc")
221 && !url.isRelative())
222 || (url.isRelative() && !currentURL.isRelative()
223 && currentURL.scheme() != QLatin1String("file")
224 && currentURL.scheme() != QLatin1String("qrc"))) {
225 QDesktopServices::openUrl(url);
226 return;
227 }
228#endif
229
230 emit q->anchorClicked(url);
231
232 if (textOrSourceChanged)
233 return;
234
235 q->setSource(url);
236}
237
238void QTextBrowserPrivate::_q_highlightLink(const QString &anchor)
239{
240 Q_Q(QTextBrowser);
241 if (anchor.isEmpty()) {
242#ifndef QT_NO_CURSOR
243 if (viewport->cursor().shape() != Qt::PointingHandCursor)
244 oldCursor = viewport->cursor();
245 viewport->setCursor(oldCursor);
246#endif
247 emit q->highlighted(QUrl());
248 emit q->highlighted(QString());
249 } else {
250#ifndef QT_NO_CURSOR
251 viewport->setCursor(Qt::PointingHandCursor);
252#endif
253
254 const QUrl url = resolveUrl(anchor);
255 emit q->highlighted(url);
256 // convenience to ease connecting to QStatusBar::showMessage(const QString &)
257 emit q->highlighted(url.toString());
258 }
259}
260
261void QTextBrowserPrivate::setSource(const QUrl &url)
262{
263 Q_Q(QTextBrowser);
264#ifndef QT_NO_CURSOR
265 if (q->isVisible())
266 qApp->setOverrideCursor(Qt::WaitCursor);
267#endif
268 textOrSourceChanged = true;
269
270 QString txt;
271
272 bool doSetText = false;
273
274 QUrl currentUrlWithoutFragment = currentURL;
275 currentUrlWithoutFragment.setFragment(QString());
276 QUrl newUrlWithoutFragment = currentURL.resolved(url);
277 newUrlWithoutFragment.setFragment(QString());
278
279 if (url.isValid()
280 && (newUrlWithoutFragment != currentUrlWithoutFragment || forceLoadOnSourceChange)) {
281 QVariant data = q->loadResource(QTextDocument::HtmlResource, resolveUrl(url));
282 if (data.type() == QVariant::String) {
283 txt = data.toString();
284 } else if (data.type() == QVariant::ByteArray) {
285#ifndef QT_NO_TEXTCODEC
286 QByteArray ba = data.toByteArray();
287 QTextCodec *codec = Qt::codecForHtml(ba);
288 txt = codec->toUnicode(ba);
289#else
290 txt = data.toString();
291#endif
292 }
293 if (txt.isEmpty())
294 qWarning("QTextBrowser: No document for %s", url.toString().toLatin1().constData());
295
296 if (q->isVisible()) {
297 QString firstTag = txt.left(txt.indexOf(QLatin1Char('>')) + 1);
298 if (firstTag.left(3) == QLatin1String("<qt") && firstTag.contains(QLatin1String("type")) && firstTag.contains(QLatin1String("detail"))) {
299#ifndef QT_NO_CURSOR
300 qApp->restoreOverrideCursor();
301#endif
302#ifndef QT_NO_WHATSTHIS
303 QWhatsThis::showText(QCursor::pos(), txt, q);
304#endif
305 return;
306 }
307 }
308
309 currentURL = resolveUrl(url);
310 doSetText = true;
311 }
312
313 if (!home.isValid())
314 home = url;
315
316 if (doSetText) {
317#ifndef QT_NO_TEXTHTMLPARSER
318 q->QTextEdit::setHtml(txt);
319 q->document()->setMetaInformation(QTextDocument::DocumentUrl, currentURL.toString());
320#else
321 q->QTextEdit::setPlainText(txt);
322#endif
323
324#ifdef QT_KEYPAD_NAVIGATION
325 prevFocus.movePosition(QTextCursor::Start);
326#endif
327 }
328
329 forceLoadOnSourceChange = false;
330
331 if (!url.fragment().isEmpty()) {
332 q->scrollToAnchor(url.fragment());
333 } else {
334 hbar->setValue(0);
335 vbar->setValue(0);
336 }
337#ifdef QT_KEYPAD_NAVIGATION
338 lastKeypadScrollValue = vbar->value();
339 emit q->highlighted(QUrl());
340 emit q->highlighted(QString());
341#endif
342
343#ifndef QT_NO_CURSOR
344 if (q->isVisible())
345 qApp->restoreOverrideCursor();
346#endif
347 emit q->sourceChanged(url);
348}
349
350#ifdef QT_KEYPAD_NAVIGATION
351void QTextBrowserPrivate::keypadMove(bool next)
352{
353 Q_Q(QTextBrowser);
354
355 const int height = viewport->height();
356 const int overlap = qBound(20, height / 5, 40); // XXX arbitrary, but a good balance
357 const int visibleLinkAmount = overlap; // consistent, but maybe not the best choice (?)
358 int yOffset = vbar->value();
359 int scrollYOffset = qBound(0, next ? yOffset + height - overlap : yOffset - height + overlap, vbar->maximum());
360
361 bool foundNextAnchor = false;
362 bool focusIt = false;
363 int focusedPos = -1;
364
365 QTextCursor anchorToFocus;
366
367 QRectF viewRect = QRectF(0, yOffset, control->size().width(), height);
368 QRectF newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
369 QRectF bothViewRects = viewRect.united(newViewRect);
370
371 // If we don't have a previous anchor, pretend that we had the first/last character
372 // on the screen selected.
373 if (prevFocus.isNull()) {
374 if (next)
375 prevFocus = control->cursorForPosition(QPointF(0, yOffset));
376 else
377 prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
378 }
379
380 // First, check to see if someone has moved the scroll bars independently
381 if (lastKeypadScrollValue != yOffset) {
382 // Someone (user or programmatically) has moved us, so we might
383 // need to start looking from the current position instead of prevFocus
384
385 bool findOnScreen = true;
386
387 // If prevFocus is on screen at all, we just use it.
388 if (prevFocus.hasSelection()) {
389 QRectF prevRect = control->selectionRect(prevFocus);
390 if (viewRect.intersects(prevRect))
391 findOnScreen = false;
392 }
393
394 // Otherwise, we find a new anchor that's on screen.
395 // Basically, create a cursor with the last/first character
396 // on screen
397 if (findOnScreen) {
398 if (next)
399 prevFocus = control->cursorForPosition(QPointF(0, yOffset));
400 else
401 prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
402 }
403 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
404 } else if (prevFocus.hasSelection()) {
405 // Check the pathological case that the current anchor is higher
406 // than the screen, and just scroll through it in that case
407 QRectF prevRect = control->selectionRect(prevFocus);
408 if ((next && prevRect.bottom() > (yOffset + height)) ||
409 (!next && prevRect.top() < yOffset)) {
410 anchorToFocus = prevFocus;
411 focusedPos = scrollYOffset;
412 focusIt = true;
413 } else {
414 // This is the "normal" case - no scroll bar adjustments, no large anchors,
415 // and no wrapping.
416 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
417 }
418 }
419
420 // If not found yet, see if we need to wrap
421 if (!focusIt && !foundNextAnchor) {
422 if (next) {
423 if (yOffset == vbar->maximum()) {
424 prevFocus.movePosition(QTextCursor::Start);
425 yOffset = scrollYOffset = 0;
426
427 // Refresh the rectangles
428 viewRect = QRectF(0, yOffset, control->size().width(), height);
429 newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
430 bothViewRects = viewRect.united(newViewRect);
431 }
432 } else {
433 if (yOffset == 0) {
434 prevFocus.movePosition(QTextCursor::End);
435 yOffset = scrollYOffset = vbar->maximum();
436
437 // Refresh the rectangles
438 viewRect = QRectF(0, yOffset, control->size().width(), height);
439 newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
440 bothViewRects = viewRect.united(newViewRect);
441 }
442 }
443
444 // Try looking now
445 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
446 }
447
448 // If we did actually find an anchor to use...
449 if (foundNextAnchor) {
450 QRectF desiredRect = control->selectionRect(anchorToFocus);
451
452 // XXX This is an arbitrary heuristic
453 // Decide to focus an anchor if it will be at least be
454 // in the middle region of the screen after a scroll.
455 // This can result in partial anchors with focus, but
456 // insisting on links being completely visible before
457 // selecting them causes disparities between links that
458 // take up 90% of the screen height and those that take
459 // up e.g. 110%
460 // Obviously if a link is entirely visible, we still
461 // focus it.
462 if(bothViewRects.contains(desiredRect)
463 || bothViewRects.adjusted(0, visibleLinkAmount, 0, -visibleLinkAmount).intersects(desiredRect)) {
464 focusIt = true;
465
466 // We aim to put the new link in the middle of the screen,
467 // unless the link is larger than the screen (we just move to
468 // display the first page of the link)
469 if (desiredRect.height() > height) {
470 if (next)
471 focusedPos = (int) desiredRect.top();
472 else
473 focusedPos = (int) desiredRect.bottom() - height;
474 } else
475 focusedPos = (int) ((desiredRect.top() + desiredRect.bottom()) / 2 - (height / 2));
476
477 // and clamp it to make sure we don't skip content.
478 if (next)
479 focusedPos = qBound(yOffset, focusedPos, scrollYOffset);
480 else
481 focusedPos = qBound(scrollYOffset, focusedPos, yOffset);
482 }
483 }
484
485 // If we didn't get a new anchor, check if the old one is still on screen when we scroll
486 // Note that big (larger than screen height) anchors also have some handling at the
487 // start of this function.
488 if (!focusIt && prevFocus.hasSelection()) {
489 QRectF desiredRect = control->selectionRect(prevFocus);
490 // XXX this may be better off also using the visibleLinkAmount value
491 if(newViewRect.intersects(desiredRect)) {
492 focusedPos = scrollYOffset;
493 focusIt = true;
494 anchorToFocus = prevFocus;
495 }
496 }
497
498 // setTextCursor ensures that the cursor is visible. save & restore
499 // the scroll bar values therefore
500 const int savedXOffset = hbar->value();
501
502 // Now actually process our decision
503 if (focusIt && control->setFocusToAnchor(anchorToFocus)) {
504 // Save the focus for next time
505 prevFocus = control->textCursor();
506
507 // Scroll
508 vbar->setValue(focusedPos);
509 lastKeypadScrollValue = focusedPos;
510 hbar->setValue(savedXOffset);
511
512 // Ensure that the new selection is highlighted.
513 const QString href = control->anchorAtCursor();
514 QUrl url = resolveUrl(href);
515 emit q->highlighted(url);
516 emit q->highlighted(url.toString());
517 } else {
518 // Scroll
519 vbar->setValue(scrollYOffset);
520 lastKeypadScrollValue = scrollYOffset;
521
522 // now make sure we don't have a focused anchor
523 QTextCursor cursor = control->textCursor();
524 cursor.clearSelection();
525
526 control->setTextCursor(cursor);
527
528 hbar->setValue(savedXOffset);
529 vbar->setValue(scrollYOffset);
530
531 emit q->highlighted(QUrl());
532 emit q->highlighted(QString());
533 }
534}
535#endif
536
537QTextBrowserPrivate::HistoryEntry QTextBrowserPrivate::createHistoryEntry() const
538{
539 HistoryEntry entry;
540 entry.url = q_func()->source();
541 entry.title = q_func()->documentTitle();
542 entry.hpos = hbar->value();
543 entry.vpos = vbar->value();
544
545 const QTextCursor cursor = control->textCursor();
546 if (control->cursorIsFocusIndicator()
547 && cursor.hasSelection()) {
548
549 entry.focusIndicatorPosition = cursor.position();
550 entry.focusIndicatorAnchor = cursor.anchor();
551 }
552 return entry;
553}
554
555void QTextBrowserPrivate::restoreHistoryEntry(const HistoryEntry entry)
556{
557 setSource(entry.url);
558 hbar->setValue(entry.hpos);
559 vbar->setValue(entry.vpos);
560 if (entry.focusIndicatorAnchor != -1 && entry.focusIndicatorPosition != -1) {
561 QTextCursor cursor(control->document());
562 cursor.setPosition(entry.focusIndicatorAnchor);
563 cursor.setPosition(entry.focusIndicatorPosition, QTextCursor::KeepAnchor);
564 control->setTextCursor(cursor);
565 control->setCursorIsFocusIndicator(true);
566 }
567#ifdef QT_KEYPAD_NAVIGATION
568 lastKeypadScrollValue = vbar->value();
569 prevFocus = control->textCursor();
570
571 Q_Q(QTextBrowser);
572 const QString href = prevFocus.charFormat().anchorHref();
573 QUrl url = resolveUrl(href);
574 emit q->highlighted(url);
575 emit q->highlighted(url.toString());
576#endif
577}
578
579/*!
580 \class QTextBrowser
581 \brief The QTextBrowser class provides a rich text browser with hypertext navigation.
582
583 \ingroup text
584
585 This class extends QTextEdit (in read-only mode), adding some navigation
586 functionality so that users can follow links in hypertext documents.
587
588 If you want to provide your users with an editable rich text editor,
589 use QTextEdit. If you want a text browser without hypertext navigation
590 use QTextEdit, and use QTextEdit::setReadOnly() to disable
591 editing. If you just need to display a small piece of rich text
592 use QLabel.
593
594 \section1 Document Source and Contents
595
596 The contents of QTextEdit are set with setHtml() or setPlainText(),
597 but QTextBrowser also implements the setSource() function, making it
598 possible to use a named document as the source text. The name is looked
599 up in a list of search paths and in the directory of the current document
600 factory.
601
602 If a document name ends with
603 an anchor (for example, "\c #anchor"), the text browser automatically
604 scrolls to that position (using scrollToAnchor()). When the user clicks
605 on a hyperlink, the browser will call setSource() itself with the link's
606 \c href value as argument. You can track the current source by connecting
607 to the sourceChanged() signal.
608
609 \section1 Navigation
610
611 QTextBrowser provides backward() and forward() slots which you can
612 use to implement Back and Forward buttons. The home() slot sets
613 the text to the very first document displayed. The anchorClicked()
614 signal is emitted when the user clicks an anchor. To override the
615 default navigation behavior of the browser, call the setSource()
616 function to supply new document text in a slot connected to this
617 signal.
618
619 If you want to load documents stored in the Qt resource system use
620 \c{qrc} as the scheme in the URL to load. For example, for the document
621 resource path \c{:/docs/index.html} use \c{qrc:/docs/index.html} as
622 the URL with setSource().
623
624 \sa QTextEdit, QTextDocument
625*/
626
627/*!
628 \property QTextBrowser::modified
629 \brief whether the contents of the text browser have been modified
630*/
631
632/*!
633 \property QTextBrowser::readOnly
634 \brief whether the text browser is read-only
635
636 By default, this property is true.
637*/
638
639/*!
640 \property QTextBrowser::undoRedoEnabled
641 \brief whether the text browser supports undo/redo operations
642
643 By default, this property is false.
644*/
645
646void QTextBrowserPrivate::init()
647{
648 Q_Q(QTextBrowser);
649 control->setTextInteractionFlags(Qt::TextBrowserInteraction);
650#ifndef QT_NO_CURSOR
651 viewport->setCursor(oldCursor);
652#endif
653 q->setUndoRedoEnabled(false);
654 viewport->setMouseTracking(true);
655 QObject::connect(q->document(), SIGNAL(contentsChanged()), q, SLOT(_q_documentModified()));
656 QObject::connect(control, SIGNAL(linkActivated(QString)),
657 q, SLOT(_q_activateAnchor(QString)));
658 QObject::connect(control, SIGNAL(linkHovered(QString)),
659 q, SLOT(_q_highlightLink(QString)));
660}
661
662/*!
663 Constructs an empty QTextBrowser with parent \a parent.
664*/
665QTextBrowser::QTextBrowser(QWidget *parent)
666 : QTextEdit(*new QTextBrowserPrivate, parent)
667{
668 Q_D(QTextBrowser);
669 d->init();
670}
671
672#ifdef QT3_SUPPORT
673/*!
674 Use one of the constructors that doesn't take the \a name
675 argument and then use setObjectName() instead.
676*/
677QTextBrowser::QTextBrowser(QWidget *parent, const char *name)
678 : QTextEdit(*new QTextBrowserPrivate, parent)
679{
680 setObjectName(QString::fromAscii(name));
681 Q_D(QTextBrowser);
682 d->init();
683}
684#endif
685
686/*!
687 \internal
688*/
689QTextBrowser::~QTextBrowser()
690{
691}
692
693/*!
694 \property QTextBrowser::source
695 \brief the name of the displayed document.
696
697 This is a an invalid url if no document is displayed or if the
698 source is unknown.
699
700 When setting this property QTextBrowser tries to find a document
701 with the specified name in the paths of the searchPaths property
702 and directory of the current source, unless the value is an absolute
703 file path. It also checks for optional anchors and scrolls the document
704 accordingly
705
706 If the first tag in the document is \c{<qt type=detail>}, the
707 document is displayed as a popup rather than as new document in
708 the browser window itself. Otherwise, the document is displayed
709 normally in the text browser with the text set to the contents of
710 the named document with setHtml().
711
712 By default, this property contains an empty URL.
713*/
714QUrl QTextBrowser::source() const
715{
716 Q_D(const QTextBrowser);
717 if (d->stack.isEmpty())
718 return QUrl();
719 else
720 return d->stack.top().url;
721}
722
723/*!
724 \property QTextBrowser::searchPaths
725 \brief the search paths used by the text browser to find supporting
726 content
727
728 QTextBrowser uses this list to locate images and documents.
729
730 By default, this property contains an empty string list.
731*/
732
733QStringList QTextBrowser::searchPaths() const
734{
735 Q_D(const QTextBrowser);
736 return d->searchPaths;
737}
738
739void QTextBrowser::setSearchPaths(const QStringList &paths)
740{
741 Q_D(QTextBrowser);
742 d->searchPaths = paths;
743}
744
745/*!
746 Reloads the current set source.
747*/
748void QTextBrowser::reload()
749{
750 Q_D(QTextBrowser);
751 QUrl s = d->currentURL;
752 d->currentURL = QUrl();
753 setSource(s);
754}
755
756void QTextBrowser::setSource(const QUrl &url)
757{
758 Q_D(QTextBrowser);
759
760 const QTextBrowserPrivate::HistoryEntry historyEntry = d->createHistoryEntry();
761
762 d->setSource(url);
763
764 if (!url.isValid())
765 return;
766
767 // the same url you are already watching?
768 if (!d->stack.isEmpty() && d->stack.top().url == url)
769 return;
770
771 if (!d->stack.isEmpty())
772 d->stack.top() = historyEntry;
773
774 QTextBrowserPrivate::HistoryEntry entry;
775 entry.url = url;
776 entry.title = documentTitle();
777 entry.hpos = 0;
778 entry.vpos = 0;
779 d->stack.push(entry);
780
781 emit backwardAvailable(d->stack.count() > 1);
782
783 if (!d->forwardStack.isEmpty() && d->forwardStack.top().url == url) {
784 d->forwardStack.pop();
785 emit forwardAvailable(d->forwardStack.count() > 0);
786 } else {
787 d->forwardStack.clear();
788 emit forwardAvailable(false);
789 }
790
791 emit historyChanged();
792}
793
794/*!
795 \fn void QTextBrowser::backwardAvailable(bool available)
796
797 This signal is emitted when the availability of backward()
798 changes. \a available is false when the user is at home();
799 otherwise it is true.
800*/
801
802/*!
803 \fn void QTextBrowser::forwardAvailable(bool available)
804
805 This signal is emitted when the availability of forward() changes.
806 \a available is true after the user navigates backward() and false
807 when the user navigates or goes forward().
808*/
809
810/*!
811 \fn void QTextBrowser::historyChanged()
812 \since 4.4
813
814 This signal is emitted when the history changes.
815
816 \sa historyTitle(), historyUrl()
817*/
818
819/*!
820 \fn void QTextBrowser::sourceChanged(const QUrl &src)
821
822 This signal is emitted when the source has changed, \a src
823 being the new source.
824
825 Source changes happen both programmatically when calling
826 setSource(), forward(), backword() or home() or when the user
827 clicks on links or presses the equivalent key sequences.
828*/
829
830/*! \fn void QTextBrowser::highlighted(const QUrl &link)
831
832 This signal is emitted when the user has selected but not
833 activated an anchor in the document. The URL referred to by the
834 anchor is passed in \a link.
835*/
836
837/*! \fn void QTextBrowser::highlighted(const QString &link)
838 \overload
839
840 Convenience signal that allows connecting to a slot
841 that takes just a QString, like for example QStatusBar's
842 message().
843*/
844
845
846/*!
847 \fn void QTextBrowser::anchorClicked(const QUrl &link)
848
849 This signal is emitted when the user clicks an anchor. The
850 URL referred to by the anchor is passed in \a link.
851
852 Note that the browser will automatically handle navigation to the
853 location specified by \a link unless the openLinks property
854 is set to false or you call setSource() in a slot connected.
855 This mechanism is used to override the default navigation features of the browser.
856*/
857
858/*!
859 Changes the document displayed to the previous document in the
860 list of documents built by navigating links. Does nothing if there
861 is no previous document.
862
863 \sa forward(), backwardAvailable()
864*/
865void QTextBrowser::backward()
866{
867 Q_D(QTextBrowser);
868 if (d->stack.count() <= 1)
869 return;
870
871 // Update the history entry
872 d->forwardStack.push(d->createHistoryEntry());
873 d->stack.pop(); // throw away the old version of the current entry
874 d->restoreHistoryEntry(d->stack.top()); // previous entry
875 emit backwardAvailable(d->stack.count() > 1);
876 emit forwardAvailable(true);
877 emit historyChanged();
878}
879
880/*!
881 Changes the document displayed to the next document in the list of
882 documents built by navigating links. Does nothing if there is no
883 next document.
884
885 \sa backward(), forwardAvailable()
886*/
887void QTextBrowser::forward()
888{
889 Q_D(QTextBrowser);
890 if (d->forwardStack.isEmpty())
891 return;
892 if (!d->stack.isEmpty()) {
893 // Update the history entry
894 d->stack.top() = d->createHistoryEntry();
895 }
896 d->stack.push(d->forwardStack.pop());
897 d->restoreHistoryEntry(d->stack.top());
898 emit backwardAvailable(true);
899 emit forwardAvailable(!d->forwardStack.isEmpty());
900 emit historyChanged();
901}
902
903/*!
904 Changes the document displayed to be the first document from
905 the history.
906*/
907void QTextBrowser::home()
908{
909 Q_D(QTextBrowser);
910 if (d->home.isValid())
911 setSource(d->home);
912}
913
914/*!
915 The event \a ev is used to provide the following keyboard shortcuts:
916 \table
917 \header \i Keypress \i Action
918 \row \i Alt+Left Arrow \i \l backward()
919 \row \i Alt+Right Arrow \i \l forward()
920 \row \i Alt+Up Arrow \i \l home()
921 \endtable
922*/
923void QTextBrowser::keyPressEvent(QKeyEvent *ev)
924{
925#ifdef QT_KEYPAD_NAVIGATION
926 Q_D(QTextBrowser);
927 switch (ev->key()) {
928 case Qt::Key_Select:
929 if (QApplication::keypadNavigationEnabled()) {
930 if (!hasEditFocus()) {
931 setEditFocus(true);
932 return;
933 } else {
934 QTextCursor cursor = d->control->textCursor();
935 QTextCharFormat charFmt = cursor.charFormat();
936 if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) {
937 ev->accept();
938 return;
939 }
940 }
941 }
942 break;
943 case Qt::Key_Back:
944 if (QApplication::keypadNavigationEnabled()) {
945 if (hasEditFocus()) {
946 setEditFocus(false);
947 ev->accept();
948 return;
949 }
950 }
951 QTextEdit::keyPressEvent(ev);
952 return;
953 default:
954 if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) {
955 ev->ignore();
956 return;
957 }
958 }
959#endif
960
961 if (ev->modifiers() & Qt::AltModifier) {
962 switch (ev->key()) {
963 case Qt::Key_Right:
964 forward();
965 ev->accept();
966 return;
967 case Qt::Key_Left:
968 backward();
969 ev->accept();
970 return;
971 case Qt::Key_Up:
972 home();
973 ev->accept();
974 return;
975 }
976 }
977#ifdef QT_KEYPAD_NAVIGATION
978 else {
979 if (ev->key() == Qt::Key_Up) {
980 d->keypadMove(false);
981 return;
982 } else if (ev->key() == Qt::Key_Down) {
983 d->keypadMove(true);
984 return;
985 }
986 }
987#endif
988 QTextEdit::keyPressEvent(ev);
989}
990
991/*!
992 \reimp
993*/
994void QTextBrowser::mouseMoveEvent(QMouseEvent *e)
995{
996 QTextEdit::mouseMoveEvent(e);
997}
998
999/*!
1000 \reimp
1001*/
1002void QTextBrowser::mousePressEvent(QMouseEvent *e)
1003{
1004 QTextEdit::mousePressEvent(e);
1005}
1006
1007/*!
1008 \reimp
1009*/
1010void QTextBrowser::mouseReleaseEvent(QMouseEvent *e)
1011{
1012 QTextEdit::mouseReleaseEvent(e);
1013}
1014
1015/*!
1016 \reimp
1017*/
1018void QTextBrowser::focusOutEvent(QFocusEvent *ev)
1019{
1020#ifndef QT_NO_CURSOR
1021 Q_D(QTextBrowser);
1022 d->viewport->setCursor((!(d->control->textInteractionFlags() & Qt::TextEditable)) ? d->oldCursor : Qt::IBeamCursor);
1023#endif
1024 QTextEdit::focusOutEvent(ev);
1025}
1026
1027/*!
1028 \reimp
1029*/
1030bool QTextBrowser::focusNextPrevChild(bool next)
1031{
1032 Q_D(QTextBrowser);
1033 if (d->control->setFocusToNextOrPreviousAnchor(next)) {
1034#ifdef QT_KEYPAD_NAVIGATION
1035 // Might need to synthesize a highlight event.
1036 if (d->prevFocus != d->control->textCursor() && d->control->textCursor().hasSelection()) {
1037 const QString href = d->control->anchorAtCursor();
1038 QUrl url = d->resolveUrl(href);
1039 emit highlighted(url);
1040 emit highlighted(url.toString());
1041 }
1042 d->prevFocus = d->control->textCursor();
1043#endif
1044 return true;
1045 } else {
1046#ifdef QT_KEYPAD_NAVIGATION
1047 // We assume we have no highlight now.
1048 emit highlighted(QUrl());
1049 emit highlighted(QString());
1050#endif
1051 }
1052 return QTextEdit::focusNextPrevChild(next);
1053}
1054
1055/*!
1056 \reimp
1057*/
1058void QTextBrowser::paintEvent(QPaintEvent *e)
1059{
1060 Q_D(QTextBrowser);
1061 QPainter p(d->viewport);
1062 d->paint(&p, e);
1063}
1064
1065/*!
1066 This function is called when the document is loaded and for
1067 each image in the document. The \a type indicates the type of resource
1068 to be loaded. An invalid QVariant is returned if the resource cannot be
1069 loaded.
1070
1071 The default implementation ignores \a type and tries to locate
1072 the resources by interpreting \a name as a file name. If it is
1073 not an absolute path it tries to find the file in the paths of
1074 the \l searchPaths property and in the same directory as the
1075 current source. On success, the result is a QVariant that stores
1076 a QByteArray with the contents of the file.
1077
1078 If you reimplement this function, you can return other QVariant
1079 types. The table below shows which variant types are supported
1080 depending on the resource type:
1081
1082 \table
1083 \header \i ResourceType \i QVariant::Type
1084 \row \i QTextDocument::HtmlResource \i QString or QByteArray
1085 \row \i QTextDocument::ImageResource \i QImage, QPixmap or QByteArray
1086 \row \i QTextDocument::StyleSheetResource \i QString or QByteArray
1087 \endtable
1088*/
1089QVariant QTextBrowser::loadResource(int /*type*/, const QUrl &name)
1090{
1091 Q_D(QTextBrowser);
1092
1093 QByteArray data;
1094 QString fileName = d->findFile(d->resolveUrl(name));
1095 QFile f(fileName);
1096 if (f.open(QFile::ReadOnly)) {
1097 data = f.readAll();
1098 f.close();
1099 } else {
1100 return QVariant();
1101 }
1102
1103 return data;
1104}
1105
1106/*!
1107 \since 4.2
1108
1109 Returns true if the text browser can go backward in the document history
1110 using backward().
1111
1112 \sa backwardAvailable(), backward()
1113*/
1114bool QTextBrowser::isBackwardAvailable() const
1115{
1116 Q_D(const QTextBrowser);
1117 return d->stack.count() > 1;
1118}
1119
1120/*!
1121 \since 4.2
1122
1123 Returns true if the text browser can go forward in the document history
1124 using forward().
1125
1126 \sa forwardAvailable(), forward()
1127*/
1128bool QTextBrowser::isForwardAvailable() const
1129{
1130 Q_D(const QTextBrowser);
1131 return !d->forwardStack.isEmpty();
1132}
1133
1134/*!
1135 \since 4.2
1136
1137 Clears the history of visited documents and disables the forward and
1138 backward navigation.
1139
1140 \sa backward(), forward()
1141*/
1142void QTextBrowser::clearHistory()
1143{
1144 Q_D(QTextBrowser);
1145 d->forwardStack.clear();
1146 if (!d->stack.isEmpty()) {
1147 QTextBrowserPrivate::HistoryEntry historyEntry = d->stack.top();
1148 d->stack.resize(0);
1149 d->stack.push(historyEntry);
1150 d->home = historyEntry.url;
1151 }
1152 emit forwardAvailable(false);
1153 emit backwardAvailable(false);
1154 emit historyChanged();
1155}
1156
1157/*!
1158 Returns the url of the HistoryItem.
1159
1160 \table
1161 \header \i Input \i Return
1162 \row \i \a{i} < 0 \i \l backward() history
1163 \row \i\a{i} == 0 \i current, see QTextBrowser::source()
1164 \row \i \a{i} > 0 \i \l forward() history
1165 \endtable
1166
1167 \since 4.4
1168*/
1169QUrl QTextBrowser::historyUrl(int i) const
1170{
1171 Q_D(const QTextBrowser);
1172 return d->history(i).url;
1173}
1174
1175/*!
1176 Returns the documentTitle() of the HistoryItem.
1177
1178 \table
1179 \header \i Input \i Return
1180 \row \i \a{i} < 0 \i \l backward() history
1181 \row \i \a{i} == 0 \i current, see QTextBrowser::source()
1182 \row \i \a{i} > 0 \i \l forward() history
1183 \endtable
1184
1185 \snippet doc/src/snippets/code/src_gui_widgets_qtextbrowser.cpp 0
1186
1187 \since 4.4
1188*/
1189QString QTextBrowser::historyTitle(int i) const
1190{
1191 Q_D(const QTextBrowser);
1192 return d->history(i).title;
1193}
1194
1195
1196/*!
1197 Returns the number of locations forward in the history.
1198
1199 \since 4.4
1200*/
1201int QTextBrowser::forwardHistoryCount() const
1202{
1203 Q_D(const QTextBrowser);
1204 return d->forwardStack.count();
1205}
1206
1207/*!
1208 Returns the number of locations backward in the history.
1209
1210 \since 4.4
1211*/
1212int QTextBrowser::backwardHistoryCount() const
1213{
1214 Q_D(const QTextBrowser);
1215 return d->stack.count()-1;
1216}
1217
1218/*!
1219 \property QTextBrowser::openExternalLinks
1220 \since 4.2
1221
1222 Specifies whether QTextBrowser should automatically open links to external
1223 sources using QDesktopServices::openUrl() instead of emitting the
1224 anchorClicked signal. Links are considered external if their scheme is
1225 neither file or qrc.
1226
1227 The default value is false.
1228*/
1229bool QTextBrowser::openExternalLinks() const
1230{
1231 Q_D(const QTextBrowser);
1232 return d->openExternalLinks;
1233}
1234
1235void QTextBrowser::setOpenExternalLinks(bool open)
1236{
1237 Q_D(QTextBrowser);
1238 d->openExternalLinks = open;
1239}
1240
1241/*!
1242 \property QTextBrowser::openLinks
1243 \since 4.3
1244
1245 This property specifies whether QTextBrowser should automatically open links the user tries to
1246 activate by mouse or keyboard.
1247
1248 Regardless of the value of this property the anchorClicked signal is always emitted.
1249
1250 The default value is true.
1251*/
1252
1253bool QTextBrowser::openLinks() const
1254{
1255 Q_D(const QTextBrowser);
1256 return d->openLinks;
1257}
1258
1259void QTextBrowser::setOpenLinks(bool open)
1260{
1261 Q_D(QTextBrowser);
1262 d->openLinks = open;
1263}
1264
1265/*! \reimp */
1266bool QTextBrowser::event(QEvent *e)
1267{
1268 return QTextEdit::event(e);
1269}
1270
1271QT_END_NAMESPACE
1272
1273#include "moc_qtextbrowser.cpp"
1274
1275#endif // QT_NO_TEXTBROWSER
Note: See TracBrowser for help on using the repository browser.