1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the widgets module of the Qt Toolkit.
7
** This file may be distributed under the terms of the Q Public License
8
** as defined by Trolltech AS of Norway and appearing in the file
9
** LICENSE.QPL included in the packaging of this file.
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 as published by the Free Software
13
** Foundation and appearing in the file LICENSE.GPL included in the
14
** packaging of this file.
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
17
** information about Qt Commercial License Agreements.
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
21
** Contact info@trolltech.com if any conditions of this licensing are
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27
****************************************************************************/
29
#include "qtextbrowser.h"
30
#include "qtextedit_p.h"
33
#include <qapplication.h>
35
#include <qdesktopwidget.h>
37
#include <qabstracttextdocumentlayout.h>
38
#include "private/qtextdocumentlayout_p.h"
39
#include <qtextcodec.h>
42
#include <qwhatsthis.h>
43
#include <qtextobject.h>
45
class QTextBrowserPrivate : public QTextEditPrivate
47
Q_DECLARE_PUBLIC(QTextBrowser)
50
: textOrSourceChanged(false), forceLoadOnSourceChange(false),
51
hadSelectionOnMousePress(false) {}
61
QStack<HistoryEntry> stack;
62
QStack<HistoryEntry> forwardStack;
66
QStringList searchPaths;
68
/*flag necessary to give the linkClicked() signal some meaningful
69
semantics when somebody connected to it calls setText() or
71
bool textOrSourceChanged;
73
bool forceLoadOnSourceChange;
75
QString findFile(const QUrl &name) const;
77
inline void documentModified()
79
textOrSourceChanged = true;
80
forceLoadOnSourceChange = true;
83
void activateAnchor(const QString &href);
85
void setSource(const QUrl &url);
87
QString anchorOnMousePress;
88
bool hadSelectionOnMousePress;
91
static bool isAbsoluteFileName(const QString &name)
93
return !name.isEmpty()
96
|| (name[0].isLetter() && name[1] == QLatin1Char(':')) || name.startsWith("\\\\")
98
|| (name[0] == QLatin1Char(':') && name[1] == QLatin1Char('/'))
103
QString QTextBrowserPrivate::findFile(const QUrl &name) const
106
if (name.scheme() == QLatin1String("qrc"))
107
fileName = QLatin1String(":/") + name.path();
109
fileName = name.toLocalFile();
111
if (isAbsoluteFileName(fileName))
116
foreach (QString path, searchPaths) {
117
if (!path.endsWith(slash))
119
path.append(fileName);
120
if (QFileInfo(path).isReadable())
127
QFileInfo path(QFileInfo(currentURL.toLocalFile()).absolutePath(), fileName);
128
return path.absoluteFilePath();
131
void QTextBrowserPrivate::activateAnchor(const QString &href)
137
textOrSourceChanged = false;
139
const QUrl url = currentURL.resolved(href);
140
emit q->anchorClicked(url);
142
if (!textOrSourceChanged)
146
void QTextBrowserPrivate::setSource(const QUrl &url)
150
qApp->setOverrideCursor(Qt::WaitCursor);
152
textOrSourceChanged = true;
156
bool doSetText = false;
158
QUrl currentUrlWithoutFragment = currentURL;
159
currentUrlWithoutFragment.setFragment(QString());
160
QUrl urlWithoutFragment = url;
161
urlWithoutFragment.setFragment(QString());
164
&& (urlWithoutFragment != currentUrlWithoutFragment || forceLoadOnSourceChange)) {
165
QVariant data = q->loadResource(QTextDocument::HtmlResource, url);
166
if (data.type() == QVariant::String) {
167
txt = data.toString();
168
} else if (data.type() == QVariant::ByteArray) {
169
QByteArray ba = data.toByteArray();
170
QTextCodec *codec = Qt::codecForHtml(ba);
171
txt = codec->toUnicode(ba);
174
qWarning("QTextBrowser: no document for %s", url.toString().toLatin1().constData());
176
if (q->isVisible()) {
177
QString firstTag = txt.left(txt.indexOf('>') + 1);
178
if (firstTag.left(3) == "<qt" && firstTag.contains("type") && firstTag.contains("detail")) {
179
qApp->restoreOverrideCursor();
180
QWhatsThis::showText(QCursor::pos(), txt, q);
193
q->QTextEdit::setHtml(txt);
195
forceLoadOnSourceChange = false;
197
if (!url.fragment().isEmpty()) {
198
q->scrollToAnchor(url.fragment());
205
qApp->restoreOverrideCursor();
207
emit q->sourceChanged(url);
211
\class QTextBrowser qtextbrowser.h
212
\brief The QTextBrowser class provides a rich text browser with hypertext navigation.
216
This class extends QTextEdit (in read-only mode), adding some
217
navigation functionality so that users can follow links in
218
hypertext documents. The contents of QTextEdit are set with
219
setHtml() or setPlainText(), but QTextBrowser also implements the
220
setSource() function, making it possible to set the text to a named
221
document. The name is looked up in a list of search paths and in the
222
directory of the current document factory. If a document name ends with
223
an anchor (for example, "\c #anchor"), the text browser automatically
224
scrolls to that position (using scrollToAnchor()). When the user clicks
225
on a hyperlink, the browser will call setSource() itself with the link's
226
\c href value as argument. You can track the current source by connecting
227
to the sourceChanged() signal.
229
QTextBrowser provides backward() and forward() slots which you can
230
use to implement Back and Forward buttons. The home() slot sets
231
the text to the very first document displayed. The anchorClicked()
232
signal is emitted when the user clicks an anchor.
234
If you want to provide your users with editable rich text use
235
QTextEdit. If you want a text browser without hypertext navigation
236
use QTextEdit, and use QTextEdit::setReadOnly() to disable
237
editing. If you just need to display a small piece of rich text
242
\property QTextBrowser::modified
243
\brief whether the contents of the text browser have been modified
247
\property QTextBrowser::readOnly
248
\brief whether the text browser is read-only
252
\property QTextBrowser::undoRedoEnabled
253
\brief whether the text browser supports undo/redo operations
256
void QTextBrowserPrivate::init()
259
q->setReadOnly(true);
260
q->setUndoRedoEnabled(false);
261
viewport->setMouseTracking(true);
262
QObject::connect(q->document(), SIGNAL(contentsChanged()), q, SLOT(documentModified()));
266
Constructs an empty QTextBrowser with parent \a parent.
268
QTextBrowser::QTextBrowser(QWidget *parent)
269
: QTextEdit(*new QTextBrowserPrivate, parent)
277
Use one of the constructors that doesn't take the \a name
278
argument and then use setObjectName() instead.
280
QTextBrowser::QTextBrowser(QWidget *parent, const char *name)
281
: QTextEdit(*new QTextBrowserPrivate, parent)
292
QTextBrowser::~QTextBrowser()
297
\property QTextBrowser::source
298
\brief the name of the displayed document.
300
This is a an invalid url if no document is displayed or if the
303
When setting this property QTextBrowser tries to find a document
304
with the specified name in the paths of the searchPaths property
305
and directory of the current source, unless the value is an absolute
306
file path. It also checks for optional anchors and scrolls the document
309
If the first tag in the document is \c{<qt type=detail>}, the
310
document is displayed as a popup rather than as new document in
311
the browser window itself. Otherwise, the document is displayed
312
normally in the text browser with the text set to the contents of
313
the named document with setHtml().
315
QUrl QTextBrowser::source() const
317
Q_D(const QTextBrowser);
318
if (d->stack.isEmpty())
321
return d->stack.top().url;
325
\property QTextBrowser::searchPaths
326
\brief the search paths used by the text browser to find supporting
329
QTextBrowser uses this list to locate images and documents.
332
QStringList QTextBrowser::searchPaths() const
334
Q_D(const QTextBrowser);
335
return d->searchPaths;
338
void QTextBrowser::setSearchPaths(const QStringList &paths)
341
d->searchPaths = paths;
345
Reloads the current set source.
347
void QTextBrowser::reload()
350
QUrl s = d->currentURL;
351
d->currentURL = QUrl();
355
void QTextBrowser::setSource(const QUrl &url)
359
int hpos = d->hbar->value();
360
int vpos = d->vbar->value();
364
QUrl currentUrlWithoutFragment = d->currentURL;
365
currentUrlWithoutFragment.setFragment(QString());
366
QUrl urlWithoutFragment = url;
367
urlWithoutFragment.setFragment(QString());
370
&& (urlWithoutFragment == currentUrlWithoutFragment)) {
371
if (!d->stack.isEmpty() && d->stack.top().url == url) {
372
// the same url you are already watching
374
if (!d->stack.isEmpty()) {
375
d->stack.top().hpos = hpos;
376
d->stack.top().vpos = vpos;
378
QTextBrowserPrivate::HistoryEntry entry;
382
d->stack.push(entry);
384
emit backwardAvailable(d->stack.count() > 1);
386
if (!d->forwardStack.isEmpty() && d->forwardStack.top().url == url) {
387
d->forwardStack.pop();
388
emit forwardAvailable(d->forwardStack.count() > 0);
390
d->forwardStack.clear();
391
emit forwardAvailable(false);
398
\fn void QTextBrowser::backwardAvailable(bool available)
400
This signal is emitted when the availability of backward()
401
changes. \a available is false when the user is at home();
402
otherwise it is true.
406
\fn void QTextBrowser::forwardAvailable(bool available)
408
This signal is emitted when the availability of forward() changes.
409
\a available is true after the user navigates backward() and false
410
when the user navigates or goes forward().
414
\fn void QTextBrowser::sourceChanged(const QUrl &src)
416
This signal is emitted when the source has changed, \a src
417
being the new source.
419
Source changes happen both programmatically when calling
420
setSource(), forward(), backword() or home() or when the user
421
clicks on links or presses the equivalent key sequences.
424
/*! \fn void QTextBrowser::highlighted(const QUrl &link)
426
This signal is emitted when the user has selected but not
427
activated an anchor in the document. The URL referred to by the
428
anchor is passed in \a link.
431
/*! \fn void QTextBrowser::highlighted(const QString &link)
434
Convenience signal that allows connecting to a slot
435
that takes just a QString, like for example QStatusBar's
441
\fn void QTextBrowser::anchorClicked(const QUrl &link)
443
This signal is emitted when the user clicks an anchor. The
444
URL referred to by the anchor is passed in \a link.
448
Changes the document displayed to the previous document in the
449
list of documents built by navigating links. Does nothing if there
450
is no previous document.
452
\sa forward(), backwardAvailable()
454
void QTextBrowser::backward()
457
if (d->stack.count() <= 1)
459
d->forwardStack.push(d->stack.pop());
460
d->forwardStack.top().hpos = d->hbar->value();
461
d->forwardStack.top().vpos = d->vbar->value();
462
d->setSource(d->stack.top().url);
463
d->hbar->setValue(d->stack.top().hpos);
464
d->vbar->setValue(d->stack.top().vpos);
465
emit backwardAvailable(d->stack.count() > 1);
466
emit forwardAvailable(true);
470
Changes the document displayed to the next document in the list of
471
documents built by navigating links. Does nothing if there is no
474
\sa backward(), forwardAvailable()
476
void QTextBrowser::forward()
479
if (d->forwardStack.isEmpty())
481
if (!d->stack.isEmpty()) {
482
d->stack.top().hpos = d->hbar->value();
483
d->stack.top().vpos = d->vbar->value();
485
d->stack.push(d->forwardStack.pop());
486
setSource(d->stack.top().url);
487
d->hbar->setValue(d->stack.top().hpos);
488
d->vbar->setValue(d->stack.top().vpos);
489
emit backwardAvailable(true);
490
emit forwardAvailable(!d->forwardStack.isEmpty());
494
Changes the document displayed to be the first document the
497
void QTextBrowser::home()
500
if (d->home.isValid())
505
The event \a ev is used to provide the following keyboard shortcuts:
507
\header \i Keypress \i Action
508
\row \i Alt+Left Arrow \i \l backward()
509
\row \i Alt+Right Arrow \i \l forward()
510
\row \i Alt+Up Arrow \i \l home()
513
void QTextBrowser::keyPressEvent(QKeyEvent *ev)
517
if (ev->modifiers() & Qt::AltModifier) {
532
} else if ((ev->key() == Qt::Key_Return
533
|| ev->key() == Qt::Key_Enter)
534
&& d->focusIndicator.hasSelection()) {
536
QTextCursor cursor = d->focusIndicator;
537
if (cursor.selectionStart() != cursor.position())
538
cursor.setPosition(cursor.selectionStart());
539
cursor.movePosition(QTextCursor::NextCharacter);
543
const QString href = cursor.charFormat().anchorHref();
544
d->activateAnchor(href);
547
QTextEdit::keyPressEvent(ev);
553
void QTextBrowser::mouseMoveEvent(QMouseEvent *e)
556
QTextEdit::mouseMoveEvent(e);
558
QString anchor = anchorAt(e->pos());
559
if (anchor.isEmpty()) {
560
d->viewport->setCursor(Qt::ArrowCursor);
561
emit highlighted(QUrl());
562
emit highlighted(QString());
564
d->viewport->setCursor(Qt::PointingHandCursor);
566
QUrl url = QUrl(d->currentURL).resolved(anchor);
567
emit highlighted(url);
568
// convenience to ease connecting to QStatusBar::showMessage(const QString &)
569
emit highlighted(url.toString());
577
void QTextBrowser::mousePressEvent(QMouseEvent *e)
579
QTextEdit::mousePressEvent(e);
582
d->anchorOnMousePress = anchorAt(e->pos());
583
d->hadSelectionOnMousePress = d->cursor.hasSelection();
589
void QTextBrowser::mouseReleaseEvent(QMouseEvent *e)
592
QTextEdit::mouseReleaseEvent(e);
594
if (!(e->button() & Qt::LeftButton))
597
const QString anchor = anchorAt(e->pos());
599
if (anchor.isEmpty())
602
if (!d->cursor.hasSelection()
603
|| (anchor == d->anchorOnMousePress && d->hadSelectionOnMousePress))
604
d->activateAnchor(anchor);
610
void QTextBrowser::focusOutEvent(QFocusEvent *ev)
613
if (ev->reason() != Qt::ActiveWindowFocusReason
614
&& ev->reason() != Qt::PopupFocusReason) {
615
d->focusIndicator.clearSelection();
616
d->viewport->update();
618
QTextEdit::focusOutEvent(ev);
624
bool QTextBrowser::focusNextPrevChild(bool next)
631
if (!d->focusIndicator.hasSelection()) {
632
d->focusIndicator = QTextCursor(d->doc);
634
d->focusIndicator.movePosition(QTextCursor::Start);
636
d->focusIndicator.movePosition(QTextCursor::End);
639
Q_ASSERT(!d->focusIndicator.isNull());
641
int anchorStart = -1;
645
const int startPos = d->focusIndicator.selectionEnd();
647
QTextBlock block = d->doc->findBlock(startPos);
648
QTextBlock::Iterator it = block.begin();
650
while (!it.atEnd() && it.fragment().position() < startPos)
653
while (block.isValid()) {
657
for (; !it.atEnd(); ++it) {
658
const QTextFragment fragment = it.fragment();
659
const QTextCharFormat fmt = fragment.charFormat();
661
if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) {
662
anchorStart = fragment.position();
667
if (anchorStart != -1) {
670
// find next non-anchor fragment
671
for (; !it.atEnd(); ++it) {
672
const QTextFragment fragment = it.fragment();
673
const QTextCharFormat fmt = fragment.charFormat();
675
if (!fmt.isAnchor()) {
676
anchorEnd = fragment.position();
682
anchorEnd = block.position() + block.length() - 1;
684
// make found selection
688
block = block.next();
692
int startPos = d->focusIndicator.selectionStart();
696
QTextBlock block = d->doc->findBlock(startPos);
697
QTextBlock::Iterator blockStart = block.begin();
698
QTextBlock::Iterator it = block.end();
700
if (startPos == block.position()) {
704
if (it == blockStart) {
705
it = QTextBlock::Iterator();
706
block = QTextBlock();
710
} while (!it.atEnd() && it.fragment().position() + it.fragment().length() - 1 > startPos);
713
while (block.isValid()) {
718
const QTextFragment fragment = it.fragment();
719
const QTextCharFormat fmt = fragment.charFormat();
721
if (fmt.isAnchor() && fmt.hasProperty(QTextFormat::AnchorHref)) {
722
anchorStart = fragment.position() + fragment.length();
726
if (it == blockStart)
727
it = QTextBlock::Iterator();
730
} while (!it.atEnd());
733
if (anchorStart != -1 && !it.atEnd()) {
737
const QTextFragment fragment = it.fragment();
738
const QTextCharFormat fmt = fragment.charFormat();
740
if (!fmt.isAnchor()) {
741
anchorEnd = fragment.position() + fragment.length();
745
if (it == blockStart)
746
it = QTextBlock::Iterator();
749
} while (!it.atEnd());
752
anchorEnd = qMax(0, block.position());
757
block = block.previous();
759
if (it != block.begin())
761
blockStart = block.begin();
766
if (anchorStart != -1 && anchorEnd != -1) {
767
d->focusIndicator.setPosition(anchorStart);
768
d->focusIndicator.setPosition(anchorEnd, QTextCursor::KeepAnchor);
770
d->focusIndicator.clearSelection();
773
if (d->focusIndicator.hasSelection()) {
774
qSwap(d->focusIndicator, d->cursor);
775
ensureCursorVisible();
776
qSwap(d->focusIndicator, d->cursor);
777
d->viewport->update();
780
d->viewport->update();
788
void QTextBrowser::paintEvent(QPaintEvent *e)
791
QPainter p(d->viewport);
796
This function is called when the document is loaded. The \a type
797
indicates the type of resource to be loaded. For each image in
798
the document, this function is called once.
800
The default implementation ignores \a type and tries to locate
801
the resources by interpreting \a name as a file name. If it is
802
not an absolute path it tries to find the file in the paths of
803
the \l searchPaths property and in the same directory as the
804
current source. On success, the result is a QVariant that stores
805
a QByteArray with the contents of the file.
807
If you reimplement this function, you can return other QVariant
808
types. The table below shows which variant types are supported
809
depending on the resource type:
812
\header \i ResourceType \i QVariant::Type
813
\row \i QTextDocument::HtmlResource \i QString or QByteArray
814
\row \i QTextDocument::ImageResource \i QImage, QPixmap or QByteArray
817
QVariant QTextBrowser::loadResource(int /*type*/, const QUrl &name)
822
QUrl resolved = name;
823
if (!isAbsoluteFileName(name.toLocalFile()))
824
resolved = source().resolved(name);
825
QString fileName = d->findFile(resolved);
827
if (f.open(QFile::ReadOnly)) {
831
qWarning("QTextBrowser: cannot open '%s' for reading", fileName.toLocal8Bit().data());
837
#include "moc_qtextbrowser.cpp"