src/gui/widgets/qtextbrowser.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/widgets/qtextbrowser.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1275 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qtextbrowser.h"
+#include "qtextedit_p.h"
+
+#ifndef QT_NO_TEXTBROWSER
+
+#include <qstack.h>
+#include <qapplication.h>
+#include <qevent.h>
+#include <qdesktopwidget.h>
+#include <qdebug.h>
+#include <qabstracttextdocumentlayout.h>
+#include "private/qtextdocumentlayout_p.h"
+#include <qtextcodec.h>
+#include <qpainter.h>
+#include <qdir.h>
+#include <qwhatsthis.h>
+#include <qtextobject.h>
+#include <qdesktopservices.h>
+
+QT_BEGIN_NAMESPACE
+
+class QTextBrowserPrivate : public QTextEditPrivate
+{
+    Q_DECLARE_PUBLIC(QTextBrowser)
+public:
+    inline QTextBrowserPrivate()
+        : textOrSourceChanged(false), forceLoadOnSourceChange(false), openExternalLinks(false),
+          openLinks(true)
+#ifdef QT_KEYPAD_NAVIGATION
+        , lastKeypadScrollValue(-1)
+#endif
+    {}
+
+    void init();
+
+    struct HistoryEntry {
+        inline HistoryEntry()
+            : hpos(0), vpos(0), focusIndicatorPosition(-1),
+              focusIndicatorAnchor(-1) {}
+        QUrl url;
+        QString title;
+        int hpos;
+        int vpos;
+        int focusIndicatorPosition, focusIndicatorAnchor;
+    };
+
+    HistoryEntry history(int i) const
+    {
+        if (i <= 0)
+            if (-i < stack.count())
+                return stack[stack.count()+i-1];
+            else
+                return HistoryEntry();
+        else
+            if (i <= forwardStack.count())
+                return forwardStack[forwardStack.count()-i];
+            else
+                return HistoryEntry();
+    }
+
+
+    HistoryEntry createHistoryEntry() const;
+    void restoreHistoryEntry(const HistoryEntry entry);
+
+    QStack<HistoryEntry> stack;
+    QStack<HistoryEntry> forwardStack;
+    QUrl home;
+    QUrl currentURL;
+
+    QStringList searchPaths;
+
+    /*flag necessary to give the linkClicked() signal some meaningful
+      semantics when somebody connected to it calls setText() or
+      setSource() */
+    bool textOrSourceChanged;
+    bool forceLoadOnSourceChange;
+
+    bool openExternalLinks;
+    bool openLinks;
+
+#ifndef QT_NO_CURSOR
+    QCursor oldCursor;
+#endif
+
+    QString findFile(const QUrl &name) const;
+
+    inline void _q_documentModified()
+    {
+        textOrSourceChanged = true;
+        forceLoadOnSourceChange = !currentURL.path().isEmpty();
+    }
+
+    void _q_activateAnchor(const QString &href);
+    void _q_highlightLink(const QString &href);
+
+    void setSource(const QUrl &url);
+
+    // re-imlemented from QTextEditPrivate
+    virtual QUrl resolveUrl(const QUrl &url) const;
+    inline QUrl resolveUrl(const QString &url) const
+    { return resolveUrl(QUrl::fromEncoded(url.toUtf8())); }
+
+#ifdef QT_KEYPAD_NAVIGATION
+    void keypadMove(bool next);
+    QTextCursor prevFocus;
+    int lastKeypadScrollValue;
+#endif
+};
+
+QString QTextBrowserPrivate::findFile(const QUrl &name) const
+{
+    QString fileName;
+    if (name.scheme() == QLatin1String("qrc"))
+        fileName = QLatin1String(":/") + name.path();
+    else
+        fileName = name.toLocalFile();
+
+    if (QFileInfo(fileName).isAbsolute())
+        return fileName;
+
+    foreach (QString path, searchPaths) {
+        if (!path.endsWith(QLatin1Char('/')))
+            path.append(QLatin1Char('/'));
+        path.append(fileName);
+        if (QFileInfo(path).isReadable())
+            return path;
+    }
+
+    return fileName;
+}
+
+QUrl QTextBrowserPrivate::resolveUrl(const QUrl &url) const
+{
+    if (!url.isRelative())
+        return url;
+
+    // For the second case QUrl can merge "#someanchor" with "foo.html"
+    // correctly to "foo.html#someanchor"
+    if (!(currentURL.isRelative()
+          || (currentURL.scheme() == QLatin1String("file")
+              && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
+          || (url.hasFragment() && url.path().isEmpty())) {
+        return currentURL.resolved(url);
+    }
+
+    // this is our last resort when current url and new url are both relative
+    // we try to resolve against the current working directory in the local
+    // file system.
+    QFileInfo fi(currentURL.toLocalFile());
+    if (fi.exists()) {
+        return QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(url);
+    }
+
+    return url;
+}
+
+void QTextBrowserPrivate::_q_activateAnchor(const QString &href)
+{
+    if (href.isEmpty())
+        return;
+    Q_Q(QTextBrowser);
+
+#ifndef QT_NO_CURSOR
+    viewport->setCursor(oldCursor);
+#endif
+
+    const QUrl url = resolveUrl(href);
+
+    if (!openLinks) {
+        emit q->anchorClicked(url);
+        return;
+    }
+
+    textOrSourceChanged = false;
+
+#ifndef QT_NO_DESKTOPSERVICES
+    if ((openExternalLinks
+         && url.scheme() != QLatin1String("file")
+         && url.scheme() != QLatin1String("qrc")
+         && !url.isRelative())
+        || (url.isRelative() && !currentURL.isRelative()
+            && currentURL.scheme() != QLatin1String("file")
+            && currentURL.scheme() != QLatin1String("qrc"))) {
+        QDesktopServices::openUrl(url);
+        return;
+    }
+#endif
+
+    emit q->anchorClicked(url);
+
+    if (textOrSourceChanged)
+        return;
+
+    q->setSource(url);
+}
+
+void QTextBrowserPrivate::_q_highlightLink(const QString &anchor)
+{
+    Q_Q(QTextBrowser);
+    if (anchor.isEmpty()) {
+#ifndef QT_NO_CURSOR
+        if (viewport->cursor().shape() != Qt::PointingHandCursor)
+            oldCursor = viewport->cursor();
+        viewport->setCursor(oldCursor);
+#endif
+        emit q->highlighted(QUrl());
+        emit q->highlighted(QString());
+    } else {
+#ifndef QT_NO_CURSOR
+        viewport->setCursor(Qt::PointingHandCursor);
+#endif
+
+        const QUrl url = resolveUrl(anchor);
+        emit q->highlighted(url);
+        // convenience to ease connecting to QStatusBar::showMessage(const QString &)
+        emit q->highlighted(url.toString());
+    }
+}
+
+void QTextBrowserPrivate::setSource(const QUrl &url)
+{
+    Q_Q(QTextBrowser);
+#ifndef QT_NO_CURSOR
+    if (q->isVisible())
+        QApplication::setOverrideCursor(Qt::WaitCursor);
+#endif
+    textOrSourceChanged = true;
+
+    QString txt;
+
+    bool doSetText = false;
+
+    QUrl currentUrlWithoutFragment = currentURL;
+    currentUrlWithoutFragment.setFragment(QString());
+    QUrl newUrlWithoutFragment = currentURL.resolved(url);
+    newUrlWithoutFragment.setFragment(QString());
+
+    if (url.isValid()
+        && (newUrlWithoutFragment != currentUrlWithoutFragment || forceLoadOnSourceChange)) {
+        QVariant data = q->loadResource(QTextDocument::HtmlResource, resolveUrl(url));
+        if (data.type() == QVariant::String) {
+            txt = data.toString();
+        } else if (data.type() == QVariant::ByteArray) {
+#ifndef QT_NO_TEXTCODEC
+            QByteArray ba = data.toByteArray();
+            QTextCodec *codec = Qt::codecForHtml(ba);
+            txt = codec->toUnicode(ba);
+#else
+	    txt = data.toString();
+#endif
+        }
+        if (txt.isEmpty())
+            qWarning("QTextBrowser: No document for %s", url.toString().toLatin1().constData());
+
+        if (q->isVisible()) {
+            QString firstTag = txt.left(txt.indexOf(QLatin1Char('>')) + 1);
+            if (firstTag.startsWith(QLatin1String("<qt")) && firstTag.contains(QLatin1String("type")) && firstTag.contains(QLatin1String("detail"))) {
+#ifndef QT_NO_CURSOR
+                QApplication::restoreOverrideCursor();
+#endif
+#ifndef QT_NO_WHATSTHIS
+                QWhatsThis::showText(QCursor::pos(), txt, q);
+#endif
+                return;
+            }
+        }
+
+        currentURL = resolveUrl(url);
+        doSetText = true;
+    }
+
+    if (!home.isValid())
+        home = url;
+
+    if (doSetText) {
+#ifndef QT_NO_TEXTHTMLPARSER
+        q->QTextEdit::setHtml(txt);
+        q->document()->setMetaInformation(QTextDocument::DocumentUrl, currentURL.toString());
+#else
+        q->QTextEdit::setPlainText(txt);
+#endif
+
+#ifdef QT_KEYPAD_NAVIGATION
+        prevFocus.movePosition(QTextCursor::Start);
+#endif
+    }
+
+    forceLoadOnSourceChange = false;
+
+    if (!url.fragment().isEmpty()) {
+        q->scrollToAnchor(url.fragment());
+    } else {
+        hbar->setValue(0);
+        vbar->setValue(0);
+    }
+#ifdef QT_KEYPAD_NAVIGATION
+    lastKeypadScrollValue = vbar->value();
+    emit q->highlighted(QUrl());
+    emit q->highlighted(QString());
+#endif
+
+#ifndef QT_NO_CURSOR
+    if (q->isVisible())
+        QApplication::restoreOverrideCursor();
+#endif
+    emit q->sourceChanged(url);
+}
+
+#ifdef QT_KEYPAD_NAVIGATION
+void QTextBrowserPrivate::keypadMove(bool next)
+{
+    Q_Q(QTextBrowser);
+
+    const int height = viewport->height();
+    const int overlap = qBound(20, height / 5, 40); // XXX arbitrary, but a good balance
+    const int visibleLinkAmount = overlap; // consistent, but maybe not the best choice (?)
+    int yOffset = vbar->value();
+    int scrollYOffset = qBound(0, next ? yOffset + height - overlap : yOffset - height + overlap, vbar->maximum());
+
+    bool foundNextAnchor = false;
+    bool focusIt = false;
+    int focusedPos = -1;
+
+    QTextCursor anchorToFocus;
+
+    QRectF viewRect = QRectF(0, yOffset, control->size().width(), height);
+    QRectF newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
+    QRectF bothViewRects = viewRect.united(newViewRect);
+
+    // If we don't have a previous anchor, pretend that we had the first/last character
+    // on the screen selected.
+    if (prevFocus.isNull()) {
+        if (next)
+            prevFocus = control->cursorForPosition(QPointF(0, yOffset));
+        else
+            prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
+    }
+
+    // First, check to see if someone has moved the scroll bars independently
+    if (lastKeypadScrollValue != yOffset) {
+        // Someone (user or programmatically) has moved us, so we might
+        // need to start looking from the current position instead of prevFocus
+
+        bool findOnScreen = true;
+
+        // If prevFocus is on screen at all, we just use it.
+        if (prevFocus.hasSelection()) {
+            QRectF prevRect = control->selectionRect(prevFocus);
+            if (viewRect.intersects(prevRect))
+                findOnScreen = false;
+        }
+
+        // Otherwise, we find a new anchor that's on screen.
+        // Basically, create a cursor with the last/first character
+        // on screen
+        if (findOnScreen) {
+            if (next)
+                prevFocus = control->cursorForPosition(QPointF(0, yOffset));
+            else
+                prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
+        }
+        foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
+    } else if (prevFocus.hasSelection()) {
+        // Check the pathological case that the current anchor is higher
+        // than the screen, and just scroll through it in that case
+        QRectF prevRect = control->selectionRect(prevFocus);
+        if ((next && prevRect.bottom() > (yOffset + height)) ||
+                (!next && prevRect.top() < yOffset)) {
+            anchorToFocus = prevFocus;
+            focusedPos = scrollYOffset;
+            focusIt = true;
+        } else {
+            // This is the "normal" case - no scroll bar adjustments, no large anchors,
+            // and no wrapping.
+            foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
+        }
+    }
+
+    // If not found yet, see if we need to wrap
+    if (!focusIt && !foundNextAnchor) {
+        if (next) {
+            if (yOffset == vbar->maximum()) {
+                prevFocus.movePosition(QTextCursor::Start);
+                yOffset = scrollYOffset = 0;
+
+                // Refresh the rectangles
+                viewRect = QRectF(0, yOffset, control->size().width(), height);
+                newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
+                bothViewRects = viewRect.united(newViewRect);
+            }
+        } else {
+            if (yOffset == 0) {
+                prevFocus.movePosition(QTextCursor::End);
+                yOffset = scrollYOffset = vbar->maximum();
+
+                // Refresh the rectangles
+                viewRect = QRectF(0, yOffset, control->size().width(), height);
+                newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
+                bothViewRects = viewRect.united(newViewRect);
+            }
+        }
+
+        // Try looking now
+        foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
+    }
+
+    // If we did actually find an anchor to use...
+    if (foundNextAnchor) {
+        QRectF desiredRect = control->selectionRect(anchorToFocus);
+
+        // XXX This is an arbitrary heuristic
+        // Decide to focus an anchor if it will be at least be
+        // in the middle region of the screen after a scroll.
+        // This can result in partial anchors with focus, but
+        // insisting on links being completely visible before
+        // selecting them causes disparities between links that
+        // take up 90% of the screen height and those that take
+        // up e.g. 110%
+        // Obviously if a link is entirely visible, we still
+        // focus it.
+        if(bothViewRects.contains(desiredRect)
+                || bothViewRects.adjusted(0, visibleLinkAmount, 0, -visibleLinkAmount).intersects(desiredRect)) {
+            focusIt = true;
+
+            // We aim to put the new link in the middle of the screen,
+            // unless the link is larger than the screen (we just move to
+            // display the first page of the link)
+            if (desiredRect.height() > height) {
+                if (next)
+                    focusedPos = (int) desiredRect.top();
+                else
+                    focusedPos = (int) desiredRect.bottom() - height;
+            } else
+                focusedPos = (int) ((desiredRect.top() + desiredRect.bottom()) / 2 - (height / 2));
+
+            // and clamp it to make sure we don't skip content.
+            if (next)
+                focusedPos = qBound(yOffset, focusedPos, scrollYOffset);
+            else
+                focusedPos = qBound(scrollYOffset, focusedPos, yOffset);
+        }
+    }
+
+    // If we didn't get a new anchor, check if the old one is still on screen when we scroll
+    // Note that big (larger than screen height) anchors also have some handling at the
+    // start of this function.
+    if (!focusIt && prevFocus.hasSelection()) {
+        QRectF desiredRect = control->selectionRect(prevFocus);
+        // XXX this may be better off also using the visibleLinkAmount value
+        if(newViewRect.intersects(desiredRect)) {
+            focusedPos = scrollYOffset;
+            focusIt = true;
+            anchorToFocus = prevFocus;
+        }
+    }
+
+    // setTextCursor ensures that the cursor is visible. save & restore
+    // the scroll bar values therefore
+    const int savedXOffset = hbar->value();
+
+    // Now actually process our decision
+    if (focusIt && control->setFocusToAnchor(anchorToFocus)) {
+        // Save the focus for next time
+        prevFocus = control->textCursor();
+
+        // Scroll
+        vbar->setValue(focusedPos);
+        lastKeypadScrollValue = focusedPos;
+        hbar->setValue(savedXOffset);
+
+        // Ensure that the new selection is highlighted.
+        const QString href = control->anchorAtCursor();
+        QUrl url = resolveUrl(href);
+        emit q->highlighted(url);
+        emit q->highlighted(url.toString());
+    } else {
+        // Scroll
+        vbar->setValue(scrollYOffset);
+        lastKeypadScrollValue = scrollYOffset;
+
+        // now make sure we don't have a focused anchor
+        QTextCursor cursor = control->textCursor();
+        cursor.clearSelection();
+
+        control->setTextCursor(cursor);
+
+        hbar->setValue(savedXOffset);
+        vbar->setValue(scrollYOffset);
+
+        emit q->highlighted(QUrl());
+        emit q->highlighted(QString());
+    }
+}
+#endif
+
+QTextBrowserPrivate::HistoryEntry QTextBrowserPrivate::createHistoryEntry() const
+{
+    HistoryEntry entry;
+    entry.url = q_func()->source();
+    entry.title = q_func()->documentTitle();
+    entry.hpos = hbar->value();
+    entry.vpos = vbar->value();
+
+    const QTextCursor cursor = control->textCursor();
+    if (control->cursorIsFocusIndicator()
+        && cursor.hasSelection()) {
+
+        entry.focusIndicatorPosition = cursor.position();
+        entry.focusIndicatorAnchor = cursor.anchor();
+    }
+    return entry;
+}
+
+void QTextBrowserPrivate::restoreHistoryEntry(const HistoryEntry entry)
+{
+    setSource(entry.url);
+    hbar->setValue(entry.hpos);
+    vbar->setValue(entry.vpos);
+    if (entry.focusIndicatorAnchor != -1 && entry.focusIndicatorPosition != -1) {
+        QTextCursor cursor(control->document());
+        cursor.setPosition(entry.focusIndicatorAnchor);
+        cursor.setPosition(entry.focusIndicatorPosition, QTextCursor::KeepAnchor);
+        control->setTextCursor(cursor);
+        control->setCursorIsFocusIndicator(true);
+    }
+#ifdef QT_KEYPAD_NAVIGATION
+    lastKeypadScrollValue = vbar->value();
+    prevFocus = control->textCursor();
+
+    Q_Q(QTextBrowser);
+    const QString href = prevFocus.charFormat().anchorHref();
+    QUrl url = resolveUrl(href);
+    emit q->highlighted(url);
+    emit q->highlighted(url.toString());
+#endif
+}
+
+/*!
+    \class QTextBrowser
+    \brief The QTextBrowser class provides a rich text browser with hypertext navigation.
+
+    \ingroup richtext-processing
+
+    This class extends QTextEdit (in read-only mode), adding some navigation
+    functionality so that users can follow links in hypertext documents.
+
+    If you want to provide your users with an editable rich text editor,
+    use QTextEdit. If you want a text browser without hypertext navigation
+    use QTextEdit, and use QTextEdit::setReadOnly() to disable
+    editing. If you just need to display a small piece of rich text
+    use QLabel.
+
+    \section1 Document Source and Contents
+
+    The contents of QTextEdit are set with setHtml() or setPlainText(),
+    but QTextBrowser also implements the setSource() function, making it
+    possible to use a named document as the source text. The name is looked
+    up in a list of search paths and in the directory of the current document
+    factory.
+
+    If a document name ends with
+    an anchor (for example, "\c #anchor"), the text browser automatically
+    scrolls to that position (using scrollToAnchor()). When the user clicks
+    on a hyperlink, the browser will call setSource() itself with the link's
+    \c href value as argument. You can track the current source by connecting
+    to the sourceChanged() signal.
+
+    \section1 Navigation
+
+    QTextBrowser provides backward() and forward() slots which you can
+    use to implement Back and Forward buttons. The home() slot sets
+    the text to the very first document displayed. The anchorClicked()
+    signal is emitted when the user clicks an anchor. To override the
+    default navigation behavior of the browser, call the setSource()
+    function to supply new document text in a slot connected to this
+    signal.
+
+    If you want to load documents stored in the Qt resource system use
+    \c{qrc} as the scheme in the URL to load. For example, for the document
+    resource path \c{:/docs/index.html} use \c{qrc:/docs/index.html} as
+    the URL with setSource().
+
+    \sa QTextEdit, QTextDocument
+*/
+
+/*!
+    \property QTextBrowser::modified
+    \brief whether the contents of the text browser have been modified
+*/
+
+/*!
+    \property QTextBrowser::readOnly
+    \brief whether the text browser is read-only
+
+    By default, this property is true.
+*/
+
+/*!
+    \property QTextBrowser::undoRedoEnabled
+    \brief whether the text browser supports undo/redo operations
+
+    By default, this property is false.
+*/
+
+void QTextBrowserPrivate::init()
+{
+    Q_Q(QTextBrowser);
+    control->setTextInteractionFlags(Qt::TextBrowserInteraction);
+#ifndef QT_NO_CURSOR
+    viewport->setCursor(oldCursor);
+#endif
+    q->setUndoRedoEnabled(false);
+    viewport->setMouseTracking(true);
+    QObject::connect(q->document(), SIGNAL(contentsChanged()), q, SLOT(_q_documentModified()));
+    QObject::connect(control, SIGNAL(linkActivated(QString)),
+                     q, SLOT(_q_activateAnchor(QString)));
+    QObject::connect(control, SIGNAL(linkHovered(QString)),
+                     q, SLOT(_q_highlightLink(QString)));
+}
+
+/*!
+    Constructs an empty QTextBrowser with parent \a parent.
+*/
+QTextBrowser::QTextBrowser(QWidget *parent)
+    : QTextEdit(*new QTextBrowserPrivate, parent)
+{
+    Q_D(QTextBrowser);
+    d->init();
+}
+
+#ifdef QT3_SUPPORT
+/*!
+    Use one of the constructors that doesn't take the \a name
+    argument and then use setObjectName() instead.
+*/
+QTextBrowser::QTextBrowser(QWidget *parent, const char *name)
+    : QTextEdit(*new QTextBrowserPrivate, parent)
+{
+    setObjectName(QString::fromAscii(name));
+    Q_D(QTextBrowser);
+    d->init();
+}
+#endif
+
+/*!
+    \internal
+*/
+QTextBrowser::~QTextBrowser()
+{
+}
+
+/*!
+    \property QTextBrowser::source
+    \brief the name of the displayed document.
+
+    This is a an invalid url if no document is displayed or if the
+    source is unknown.
+
+    When setting this property QTextBrowser tries to find a document
+    with the specified name in the paths of the searchPaths property
+    and directory of the current source, unless the value is an absolute
+    file path. It also checks for optional anchors and scrolls the document
+    accordingly
+
+    If the first tag in the document is \c{<qt type=detail>}, the
+    document is displayed as a popup rather than as new document in
+    the browser window itself. Otherwise, the document is displayed
+    normally in the text browser with the text set to the contents of
+    the named document with setHtml().
+
+    By default, this property contains an empty URL.
+*/
+QUrl QTextBrowser::source() const
+{
+    Q_D(const QTextBrowser);
+    if (d->stack.isEmpty())
+        return QUrl();
+    else
+        return d->stack.top().url;
+}
+
+/*!
+    \property QTextBrowser::searchPaths
+    \brief the search paths used by the text browser to find supporting
+    content
+
+    QTextBrowser uses this list to locate images and documents.
+
+    By default, this property contains an empty string list.
+*/
+
+QStringList QTextBrowser::searchPaths() const
+{
+    Q_D(const QTextBrowser);
+    return d->searchPaths;
+}
+
+void QTextBrowser::setSearchPaths(const QStringList &paths)
+{
+    Q_D(QTextBrowser);
+    d->searchPaths = paths;
+}
+
+/*!
+    Reloads the current set source.
+*/
+void QTextBrowser::reload()
+{
+    Q_D(QTextBrowser);
+    QUrl s = d->currentURL;
+    d->currentURL = QUrl();
+    setSource(s);
+}
+
+void QTextBrowser::setSource(const QUrl &url)
+{
+    Q_D(QTextBrowser);
+
+    const QTextBrowserPrivate::HistoryEntry historyEntry = d->createHistoryEntry();
+
+    d->setSource(url);
+
+    if (!url.isValid())
+        return;
+
+    // the same url you are already watching?
+    if (!d->stack.isEmpty() && d->stack.top().url == url)
+        return;
+
+    if (!d->stack.isEmpty())
+        d->stack.top() = historyEntry;
+
+    QTextBrowserPrivate::HistoryEntry entry;
+    entry.url = url;
+    entry.title = documentTitle();
+    entry.hpos = 0;
+    entry.vpos = 0;
+    d->stack.push(entry);
+
+    emit backwardAvailable(d->stack.count() > 1);
+
+    if (!d->forwardStack.isEmpty() && d->forwardStack.top().url == url) {
+        d->forwardStack.pop();
+        emit forwardAvailable(d->forwardStack.count() > 0);
+    } else {
+        d->forwardStack.clear();
+        emit forwardAvailable(false);
+    }
+
+    emit historyChanged();
+}
+
+/*!
+    \fn void QTextBrowser::backwardAvailable(bool available)
+
+    This signal is emitted when the availability of backward()
+    changes. \a available is false when the user is at home();
+    otherwise it is true.
+*/
+
+/*!
+    \fn void QTextBrowser::forwardAvailable(bool available)
+
+    This signal is emitted when the availability of forward() changes.
+    \a available is true after the user navigates backward() and false
+    when the user navigates or goes forward().
+*/
+
+/*!
+    \fn void QTextBrowser::historyChanged()
+    \since 4.4
+
+    This signal is emitted when the history changes.
+
+    \sa historyTitle(), historyUrl()
+*/
+
+/*!
+    \fn void QTextBrowser::sourceChanged(const QUrl &src)
+
+    This signal is emitted when the source has changed, \a src
+    being the new source.
+
+    Source changes happen both programmatically when calling
+    setSource(), forward(), backword() or home() or when the user
+    clicks on links or presses the equivalent key sequences.
+*/
+
+/*!  \fn void QTextBrowser::highlighted(const QUrl &link)
+
+    This signal is emitted when the user has selected but not
+    activated an anchor in the document. The URL referred to by the
+    anchor is passed in \a link.
+*/
+
+/*!  \fn void QTextBrowser::highlighted(const QString &link)
+     \overload
+
+     Convenience signal that allows connecting to a slot
+     that takes just a QString, like for example QStatusBar's
+     message().
+*/
+
+
+/*!
+    \fn void QTextBrowser::anchorClicked(const QUrl &link)
+
+    This signal is emitted when the user clicks an anchor. The
+    URL referred to by the anchor is passed in \a link.
+
+    Note that the browser will automatically handle navigation to the
+    location specified by \a link unless the openLinks property
+    is set to false or you call setSource() in a slot connected.
+    This mechanism is used to override the default navigation features of the browser.
+*/
+
+/*!
+    Changes the document displayed to the previous document in the
+    list of documents built by navigating links. Does nothing if there
+    is no previous document.
+
+    \sa forward(), backwardAvailable()
+*/
+void QTextBrowser::backward()
+{
+    Q_D(QTextBrowser);
+    if (d->stack.count() <= 1)
+        return;
+
+    // Update the history entry
+    d->forwardStack.push(d->createHistoryEntry());
+    d->stack.pop(); // throw away the old version of the current entry
+    d->restoreHistoryEntry(d->stack.top()); // previous entry
+    emit backwardAvailable(d->stack.count() > 1);
+    emit forwardAvailable(true);
+    emit historyChanged();
+}
+
+/*!
+    Changes the document displayed to the next document in the list of
+    documents built by navigating links. Does nothing if there is no
+    next document.
+
+    \sa backward(), forwardAvailable()
+*/
+void QTextBrowser::forward()
+{
+    Q_D(QTextBrowser);
+    if (d->forwardStack.isEmpty())
+        return;
+    if (!d->stack.isEmpty()) {
+        // Update the history entry
+        d->stack.top() = d->createHistoryEntry();
+    }
+    d->stack.push(d->forwardStack.pop());
+    d->restoreHistoryEntry(d->stack.top());
+    emit backwardAvailable(true);
+    emit forwardAvailable(!d->forwardStack.isEmpty());
+    emit historyChanged();
+}
+
+/*!
+    Changes the document displayed to be the first document from
+    the history.
+*/
+void QTextBrowser::home()
+{
+    Q_D(QTextBrowser);
+    if (d->home.isValid())
+        setSource(d->home);
+}
+
+/*!
+    The event \a ev is used to provide the following keyboard shortcuts:
+    \table
+    \header \i Keypress            \i Action
+    \row \i Alt+Left Arrow  \i \l backward()
+    \row \i Alt+Right Arrow \i \l forward()
+    \row \i Alt+Up Arrow    \i \l home()
+    \endtable
+*/
+void QTextBrowser::keyPressEvent(QKeyEvent *ev)
+{
+#ifdef QT_KEYPAD_NAVIGATION
+    Q_D(QTextBrowser);
+    switch (ev->key()) {
+    case Qt::Key_Select:
+        if (QApplication::keypadNavigationEnabled()) {
+            if (!hasEditFocus()) {
+                setEditFocus(true);
+                return;
+            } else {
+                QTextCursor cursor = d->control->textCursor();
+                QTextCharFormat charFmt = cursor.charFormat();
+                if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) {
+                    ev->accept();
+                    return;
+                }
+            }
+        }
+        break;
+    case Qt::Key_Back:
+        if (QApplication::keypadNavigationEnabled()) {
+            if (hasEditFocus()) {
+                setEditFocus(false);
+                ev->accept();
+                return;
+            }
+        }
+        QTextEdit::keyPressEvent(ev);
+        return;
+    default:
+        if (QApplication::keypadNavigationEnabled() && !hasEditFocus()) {
+            ev->ignore();
+            return;
+        }
+    }
+#endif
+
+    if (ev->modifiers() & Qt::AltModifier) {
+        switch (ev->key()) {
+        case Qt::Key_Right:
+            forward();
+            ev->accept();
+            return;
+        case Qt::Key_Left:
+            backward();
+            ev->accept();
+            return;
+        case Qt::Key_Up:
+            home();
+            ev->accept();
+            return;
+        }
+    }
+#ifdef QT_KEYPAD_NAVIGATION
+    else {
+        if (ev->key() == Qt::Key_Up) {
+            d->keypadMove(false);
+            return;
+        } else if (ev->key() == Qt::Key_Down) {
+            d->keypadMove(true);
+            return;
+        }
+    }
+#endif
+    QTextEdit::keyPressEvent(ev);
+}
+
+/*!
+    \reimp
+*/
+void QTextBrowser::mouseMoveEvent(QMouseEvent *e)
+{
+    QTextEdit::mouseMoveEvent(e);
+}
+
+/*!
+    \reimp
+*/
+void QTextBrowser::mousePressEvent(QMouseEvent *e)
+{
+    QTextEdit::mousePressEvent(e);
+}
+
+/*!
+    \reimp
+*/
+void QTextBrowser::mouseReleaseEvent(QMouseEvent *e)
+{
+    QTextEdit::mouseReleaseEvent(e);
+}
+
+/*!
+    \reimp
+*/
+void QTextBrowser::focusOutEvent(QFocusEvent *ev)
+{
+#ifndef QT_NO_CURSOR
+    Q_D(QTextBrowser);
+    d->viewport->setCursor((!(d->control->textInteractionFlags() & Qt::TextEditable)) ? d->oldCursor : Qt::IBeamCursor);
+#endif
+    QTextEdit::focusOutEvent(ev);
+}
+
+/*!
+    \reimp
+*/
+bool QTextBrowser::focusNextPrevChild(bool next)
+{
+    Q_D(QTextBrowser);
+    if (d->control->setFocusToNextOrPreviousAnchor(next)) {
+#ifdef QT_KEYPAD_NAVIGATION
+        // Might need to synthesize a highlight event.
+        if (d->prevFocus != d->control->textCursor() && d->control->textCursor().hasSelection()) {
+            const QString href = d->control->anchorAtCursor();
+            QUrl url = d->resolveUrl(href);
+            emit highlighted(url);
+            emit highlighted(url.toString());
+        }
+        d->prevFocus = d->control->textCursor();
+#endif
+        return true;
+    } else {
+#ifdef QT_KEYPAD_NAVIGATION
+        // We assume we have no highlight now.
+        emit highlighted(QUrl());
+        emit highlighted(QString());
+#endif
+    }
+    return QTextEdit::focusNextPrevChild(next);
+}
+
+/*!
+  \reimp
+*/
+void QTextBrowser::paintEvent(QPaintEvent *e)
+{
+    Q_D(QTextBrowser);
+    QPainter p(d->viewport);
+    d->paint(&p, e);
+}
+
+/*!
+    This function is called when the document is loaded and for
+    each image in the document. The \a type indicates the type of resource
+    to be loaded. An invalid QVariant is returned if the resource cannot be
+    loaded.
+
+    The default implementation ignores \a type and tries to locate
+    the resources by interpreting \a name as a file name. If it is
+    not an absolute path it tries to find the file in the paths of
+    the \l searchPaths property and in the same directory as the
+    current source. On success, the result is a QVariant that stores
+    a QByteArray with the contents of the file.
+
+    If you reimplement this function, you can return other QVariant
+    types. The table below shows which variant types are supported
+    depending on the resource type:
+
+    \table
+    \header \i ResourceType  \i QVariant::Type
+    \row    \i QTextDocument::HtmlResource  \i QString or QByteArray
+    \row    \i QTextDocument::ImageResource \i QImage, QPixmap or QByteArray
+    \row    \i QTextDocument::StyleSheetResource \i QString or QByteArray
+    \endtable
+*/
+QVariant QTextBrowser::loadResource(int /*type*/, const QUrl &name)
+{
+    Q_D(QTextBrowser);
+
+    QByteArray data;
+    QString fileName = d->findFile(d->resolveUrl(name));
+    QFile f(fileName);
+    if (f.open(QFile::ReadOnly)) {
+        data = f.readAll();
+        f.close();
+    } else {
+        return QVariant();
+    }
+
+    return data;
+}
+
+/*!
+    \since 4.2
+
+    Returns true if the text browser can go backward in the document history
+    using backward().
+
+    \sa backwardAvailable(), backward()
+*/
+bool QTextBrowser::isBackwardAvailable() const
+{
+    Q_D(const QTextBrowser);
+    return d->stack.count() > 1;
+}
+
+/*!
+    \since 4.2
+
+    Returns true if the text browser can go forward in the document history
+    using forward().
+
+    \sa forwardAvailable(), forward()
+*/
+bool QTextBrowser::isForwardAvailable() const
+{
+    Q_D(const QTextBrowser);
+    return !d->forwardStack.isEmpty();
+}
+
+/*!
+    \since 4.2
+
+    Clears the history of visited documents and disables the forward and
+    backward navigation.
+
+    \sa backward(), forward()
+*/
+void QTextBrowser::clearHistory()
+{
+    Q_D(QTextBrowser);
+    d->forwardStack.clear();
+    if (!d->stack.isEmpty()) {
+        QTextBrowserPrivate::HistoryEntry historyEntry = d->stack.top();
+        d->stack.resize(0);
+        d->stack.push(historyEntry);
+        d->home = historyEntry.url;
+    }
+    emit forwardAvailable(false);
+    emit backwardAvailable(false);
+    emit historyChanged();
+}
+
+/*!
+   Returns the url of the HistoryItem.
+
+    \table
+    \header \i Input            \i Return
+    \row \i \a{i} < 0  \i \l backward() history
+    \row \i\a{i} == 0 \i current, see QTextBrowser::source()
+    \row \i \a{i} > 0  \i \l forward() history
+    \endtable
+
+    \since 4.4
+*/
+QUrl QTextBrowser::historyUrl(int i) const
+{
+    Q_D(const QTextBrowser);
+    return d->history(i).url;
+}
+
+/*!
+    Returns the documentTitle() of the HistoryItem.
+
+    \table
+    \header \i Input            \i Return
+    \row \i \a{i} < 0  \i \l backward() history
+    \row \i \a{i} == 0 \i current, see QTextBrowser::source()
+    \row \i \a{i} > 0  \i \l forward() history
+    \endtable
+
+    \snippet doc/src/snippets/code/src_gui_widgets_qtextbrowser.cpp 0
+
+    \since 4.4
+*/
+QString QTextBrowser::historyTitle(int i) const
+{
+    Q_D(const QTextBrowser);
+    return d->history(i).title;
+}
+
+
+/*!
+    Returns the number of locations forward in the history.
+
+    \since 4.4
+*/
+int QTextBrowser::forwardHistoryCount() const
+{
+    Q_D(const QTextBrowser);
+    return d->forwardStack.count();
+}
+
+/*!
+    Returns the number of locations backward in the history.
+
+    \since 4.4
+*/
+int QTextBrowser::backwardHistoryCount() const
+{
+    Q_D(const QTextBrowser);
+    return d->stack.count()-1;
+}
+
+/*!
+    \property QTextBrowser::openExternalLinks
+    \since 4.2
+
+    Specifies whether QTextBrowser should automatically open links to external
+    sources using QDesktopServices::openUrl() instead of emitting the
+    anchorClicked signal. Links are considered external if their scheme is
+    neither file or qrc.
+
+    The default value is false.
+*/
+bool QTextBrowser::openExternalLinks() const
+{
+    Q_D(const QTextBrowser);
+    return d->openExternalLinks;
+}
+
+void QTextBrowser::setOpenExternalLinks(bool open)
+{
+    Q_D(QTextBrowser);
+    d->openExternalLinks = open;
+}
+
+/*!
+   \property QTextBrowser::openLinks
+   \since 4.3
+
+   This property specifies whether QTextBrowser should automatically open links the user tries to
+   activate by mouse or keyboard.
+
+   Regardless of the value of this property the anchorClicked signal is always emitted.
+
+   The default value is true.
+*/
+
+bool QTextBrowser::openLinks() const
+{
+    Q_D(const QTextBrowser);
+    return d->openLinks;
+}
+
+void QTextBrowser::setOpenLinks(bool open)
+{
+    Q_D(QTextBrowser);
+    d->openLinks = open;
+}
+
+/*! \reimp */
+bool QTextBrowser::event(QEvent *e)
+{
+    return QTextEdit::event(e);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qtextbrowser.cpp"
+
+#endif // QT_NO_TEXTBROWSER