summaryrefslogtreecommitdiffstats
path: root/src/gui/widgets/qtextbrowser.cpp
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2009-03-23 09:34:13 (GMT)
committerSimon Hausmann <simon.hausmann@nokia.com>2009-03-23 09:34:13 (GMT)
commit67ad0519fd165acee4a4d2a94fa502e9e4847bd0 (patch)
tree1dbf50b3dff8d5ca7e9344733968c72704eb15ff /src/gui/widgets/qtextbrowser.cpp
downloadQt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.zip
Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.gz
Qt-67ad0519fd165acee4a4d2a94fa502e9e4847bd0.tar.bz2
Long live Qt!
Diffstat (limited to 'src/gui/widgets/qtextbrowser.cpp')
-rw-r--r--src/gui/widgets/qtextbrowser.cpp1275
1 files changed, 1275 insertions, 0 deletions
diff --git a/src/gui/widgets/qtextbrowser.cpp b/src/gui/widgets/qtextbrowser.cpp
new file mode 100644
index 0000000..a1f4d34
--- /dev/null
+++ b/src/gui/widgets/qtextbrowser.cpp
@@ -0,0 +1,1275 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (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 either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** 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.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@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())
+ qApp->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.left(3) == QLatin1String("<qt") && firstTag.contains(QLatin1String("type")) && firstTag.contains(QLatin1String("detail"))) {
+#ifndef QT_NO_CURSOR
+ qApp->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())
+ qApp->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 text
+
+ 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