8#include <qapplication.h>
9#include <private/qapplication_p.h>
12#include <qabstracttextdocumentlayout.h>
13#include "private/qtextdocumentlayout_p.h"
16#if QT_CONFIG(whatsthis)
17#include <qwhatsthis.h>
19#include <qtextobject.h>
20#include <qdesktopservices.h>
21#include <qstringconverter.h>
25using namespace Qt::StringLiterals;
29#if defined (Q_OS_ANDROID)
30 return !texbrowser->isReadOnly() || (texbrowser->textInteractionFlags() & Qt::TextSelectableByMouse);
32 return !texbrowser->isReadOnly();
40 Q_DECLARE_PUBLIC(QTextBrowser)
45#ifdef QT_KEYPAD_NAVIGATION
72 if (-i < stack.size())
73 return stack[stack.size()+i-1];
77 if (i <= forwardStack.size())
78 return forwardStack[forwardStack.size()-i];
95
96
114 forceLoadOnSourceChange = !currentURL.path().isEmpty();
120 void setSource(
const QUrl &url, QTextDocument::ResourceType type);
127#ifdef QT_KEYPAD_NAVIGATION
135 emit q->highlighted(url);
144 if (name.scheme() ==
"qrc"_L1) {
145 fileName =
":/"_L1 + name.path();
146 }
else if (name.scheme().isEmpty()) {
147 fileName = name.path();
149#if defined(Q_OS_ANDROID)
150 if (name.scheme() ==
"assets"_L1)
151 fileName =
"assets:"_L1 + name.path();
154 fileName = name.toLocalFile();
157 if (fileName.isEmpty())
160 if (QFileInfo(fileName).isAbsolute())
163 for (QString path : std::as_const(searchPaths)) {
164 if (!path.endsWith(u'/'))
166 path.append(fileName);
167 if (QFileInfo(path).isReadable())
176 if (!url.isRelative())
181 if (!(currentURL.isRelative()
182 || (currentURL.scheme() ==
"file"_L1
183 && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
184 || (url.hasFragment() && url.path().isEmpty())) {
185 return currentURL.resolved(url);
191 QFileInfo fi(currentURL.toLocalFile());
193 return QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(url);
206 viewport->setCursor(oldCursor);
209 const QUrl url = resolveUrl(href);
212 emit q->anchorClicked(url);
218#ifndef QT_NO_DESKTOPSERVICES
220 url.scheme() ==
"file"_L1
221#if defined(Q_OS_ANDROID)
222 || url.scheme() ==
"assets"_L1
224 || url.scheme() ==
"qrc"_L1;
225 if ((openExternalLinks && !isFileScheme && !url.isRelative())
226 || (url.isRelative() && !currentURL.isRelative() && !isFileScheme)) {
227 QDesktopServices::openUrl(url);
232 emit q->anchorClicked(url);
242 if (anchor.isEmpty()) {
244 if (viewport->cursor().shape() != Qt::PointingHandCursor)
245 oldCursor = viewport->cursor();
246 viewport->setCursor(oldCursor);
251 viewport->setCursor(Qt::PointingHandCursor);
254 const QUrl url = resolveUrl(anchor);
264 QGuiApplication::setOverrideCursor(Qt::WaitCursor);
270 bool doSetText =
false;
272 QUrl currentUrlWithoutFragment = currentURL;
273 currentUrlWithoutFragment.setFragment(QString());
274 QUrl newUrlWithoutFragment = currentURL.resolved(url);
275 newUrlWithoutFragment.setFragment(QString());
276 QString fileName = url.fileName();
277 if (type == QTextDocument::UnknownResource) {
278#if QT_CONFIG(textmarkdownreader)
279 if (fileName.endsWith(
".md"_L1) ||
280 fileName.endsWith(
".mkd"_L1) ||
281 fileName.endsWith(
".markdown"_L1))
282 type = QTextDocument::MarkdownResource;
285 type = QTextDocument::HtmlResource;
291 QVariant data = q->loadResource(type, resolveUrl(url));
292 if (data.userType() == QMetaType::QString) {
293 txt = data.toString();
294 }
else if (data.userType() == QMetaType::QByteArray) {
295 QByteArray ba = data.toByteArray();
296 if (type == QTextDocument::HtmlResource) {
297 auto decoder = QStringDecoder::decoderForHtml(ba);
298 if (!decoder.isValid())
300 decoder = QStringDecoder(QStringDecoder::Utf8);
303 txt = QString::fromUtf8(ba);
306 if (Q_UNLIKELY(txt.isEmpty()))
307 qWarning(
"QTextBrowser: No document for %s", url.toString().toLatin1().constData());
309 if (q->isVisible()) {
310 const QStringView firstTag = QStringView{txt}.left(txt.indexOf(u'>') + 1);
311 if (firstTag.startsWith(
"<qt"_L1) && firstTag.contains(
"type"_L1) && firstTag.contains(
"detail"_L1)) {
313 QGuiApplication::restoreOverrideCursor();
315#if QT_CONFIG(whatsthis)
316 QWhatsThis::showText(QCursor::pos(), txt, q);
322 currentURL = resolveUrl(url);
333 QUrl baseUrl = currentURL.adjusted(QUrl::RemoveFilename);
334 if (!baseUrl.path().isEmpty())
335 q->document()->setBaseUrl(baseUrl);
336 q->document()->setMetaInformation(QTextDocument::DocumentUrl, currentURL.toString());
337 qCDebug(lcBrowser) <<
"loading" << currentURL <<
"base" << q->document()->baseUrl() <<
"type" << type << txt.size() <<
"chars";
338#if QT_CONFIG(textmarkdownreader)
339 if (type == QTextDocument::MarkdownResource)
340 q->QTextEdit::setMarkdown(txt);
343#ifndef QT_NO_TEXTHTMLPARSER
344 q->QTextEdit::setHtml(txt);
346 q->QTextEdit::setPlainText(txt);
349#ifdef QT_KEYPAD_NAVIGATION
350 prevFocus.movePosition(QTextCursor::Start);
356 if (!url.fragment().isEmpty()) {
357 q->scrollToAnchor(url.fragment());
362#ifdef QT_KEYPAD_NAVIGATION
363 lastKeypadScrollValue = vbar->value();
364 emitHighlighted(QUrl());
369 QGuiApplication::restoreOverrideCursor();
371 emit q->sourceChanged(url);
374#ifdef QT_KEYPAD_NAVIGATION
375void QTextBrowserPrivate::keypadMove(
bool next)
379 const int height = viewport->height();
380 const int overlap = qBound(20, height / 5, 40);
381 const int visibleLinkAmount = overlap;
382 int yOffset = vbar->value();
383 int scrollYOffset = qBound(0, next ? yOffset + height - overlap : yOffset - height + overlap, vbar->maximum());
385 bool foundNextAnchor =
false;
386 bool focusIt =
false;
389 QTextCursor anchorToFocus;
391 QRectF viewRect = QRectF(0, yOffset, control->size().width(), height);
392 QRectF newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
393 QRectF bothViewRects = viewRect.united(newViewRect);
397 if (prevFocus.isNull()) {
399 prevFocus = control->cursorForPosition(QPointF(0, yOffset));
401 prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
405 if (lastKeypadScrollValue != yOffset) {
409 bool findOnScreen =
true;
412 if (prevFocus.hasSelection()) {
413 QRectF prevRect = control->selectionRect(prevFocus);
414 if (viewRect.intersects(prevRect))
415 findOnScreen =
false;
423 prevFocus = control->cursorForPosition(QPointF(0, yOffset));
425 prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
427 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
428 }
else if (prevFocus.hasSelection()) {
431 QRectF prevRect = control->selectionRect(prevFocus);
432 if ((next && prevRect.bottom() > (yOffset + height)) ||
433 (!next && prevRect.top() < yOffset)) {
434 anchorToFocus = prevFocus;
435 focusedPos = scrollYOffset;
440 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
445 if (!focusIt && !foundNextAnchor) {
447 if (yOffset == vbar->maximum()) {
448 prevFocus.movePosition(QTextCursor::Start);
449 yOffset = scrollYOffset = 0;
452 viewRect = QRectF(0, yOffset, control->size().width(), height);
453 newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
454 bothViewRects = viewRect.united(newViewRect);
458 prevFocus.movePosition(QTextCursor::End);
459 yOffset = scrollYOffset = vbar->maximum();
462 viewRect = QRectF(0, yOffset, control->size().width(), height);
463 newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
464 bothViewRects = viewRect.united(newViewRect);
469 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
473 if (foundNextAnchor) {
474 QRectF desiredRect = control->selectionRect(anchorToFocus);
486 if (bothViewRects.contains(desiredRect)
487 || bothViewRects.adjusted(0, visibleLinkAmount, 0, -visibleLinkAmount).intersects(desiredRect)) {
493 if (desiredRect.height() > height) {
495 focusedPos = (
int) desiredRect.top();
497 focusedPos = (
int) desiredRect.bottom() - height;
499 focusedPos = (
int) ((desiredRect.top() + desiredRect.bottom()) / 2 - (height / 2));
503 focusedPos = qBound(yOffset, focusedPos, scrollYOffset);
505 focusedPos = qBound(scrollYOffset, focusedPos, yOffset);
512 if (!focusIt && prevFocus.hasSelection()) {
513 QRectF desiredRect = control->selectionRect(prevFocus);
515 if (newViewRect.intersects(desiredRect)) {
516 focusedPos = scrollYOffset;
518 anchorToFocus = prevFocus;
524 const int savedXOffset = hbar->value();
527 if (focusIt && control->setFocusToAnchor(anchorToFocus)) {
529 prevFocus = control->textCursor();
532 vbar->setValue(focusedPos);
533 lastKeypadScrollValue = focusedPos;
534 hbar->setValue(savedXOffset);
537 const QString href = control->anchorAtCursor();
538 QUrl url = resolveUrl(href);
539 emitHighlighted(url);
542 vbar->setValue(scrollYOffset);
543 lastKeypadScrollValue = scrollYOffset;
546 QTextCursor cursor = control->textCursor();
547 cursor.clearSelection();
549 control->setTextCursor(cursor);
551 hbar->setValue(savedXOffset);
552 vbar->setValue(scrollYOffset);
554 emitHighlighted(QUrl());
562 entry.url = q_func()->source();
563 entry.type = q_func()->sourceType();
564 entry.title = q_func()->documentTitle();
565 entry.hpos = hbar->value();
566 entry.vpos = vbar->value();
568 const QTextCursor cursor = control->textCursor();
569 if (control->cursorIsFocusIndicator()
570 && cursor.hasSelection()) {
580 setSource(entry.url, entry.type);
581 hbar->setValue(entry.hpos);
582 vbar->setValue(entry.vpos);
584 QTextCursor cursor(control->document());
586 cursor.setPosition(entry.focusIndicatorPosition, QTextCursor::KeepAnchor);
587 control->setTextCursor(cursor);
588 control->setCursorIsFocusIndicator(
true);
590#ifdef QT_KEYPAD_NAVIGATION
591 lastKeypadScrollValue = vbar->value();
592 prevFocus = control->textCursor();
595 const QString href = prevFocus.charFormat().anchorHref();
596 QUrl url = resolveUrl(href);
597 emitHighlighted(url);
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
651
652
653
656
657
658
659
660
663
664
665
666
667
672 control->setTextInteractionFlags(Qt::TextBrowserInteraction);
674 viewport->setCursor(oldCursor);
676 q->setAttribute(Qt::WA_InputMethodEnabled, shouldEnableInputMethod(q));
677 q->setUndoRedoEnabled(
false);
680 QObjectPrivate::connect(q->document(), &QTextDocument::contentsChanged,
681 this, &QTextBrowserPrivate::documentModified),
682 QObjectPrivate::connect(control, &QWidgetTextControl::linkActivated,
683 this, &QTextBrowserPrivate::activateAnchor),
684 QObjectPrivate::connect(control, &QWidgetTextControl::linkHovered,
685 this, &QTextBrowserPrivate::highlightLink),
690
691
692QTextBrowser::QTextBrowser(QWidget *parent)
693 : QTextEdit(*
new QTextBrowserPrivate, parent)
701
702
703QTextBrowser::~QTextBrowser()
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734QUrl QTextBrowser::source()
const
736 Q_D(
const QTextBrowser);
737 if (d->stack.isEmpty())
740 return d->stack.top().url;
744
745
746
747
748
749
750
751QTextDocument::ResourceType QTextBrowser::sourceType()
const
753 Q_D(
const QTextBrowser);
754 if (d->stack.isEmpty())
755 return QTextDocument::UnknownResource;
757 return d->stack.top().type;
761
762
763
764
765
766
767
768
770QStringList QTextBrowser::searchPaths()
const
772 Q_D(
const QTextBrowser);
773 return d->searchPaths;
776void QTextBrowser::setSearchPaths(
const QStringList &paths)
779 d->searchPaths = paths;
783
784
785void QTextBrowser::reload()
788 QUrl s = d->currentURL;
789 d->currentURL = QUrl();
790 setSource(s, d->currentType);
794
795
796
797
798
799
800
801
802
803void QTextBrowser::setSource(
const QUrl &url, QTextDocument::ResourceType type)
805 doSetSource(url, type);
809
810
811
812
813
814void QTextBrowser::doSetSource(
const QUrl &url, QTextDocument::ResourceType type)
818 const QTextBrowserPrivate::HistoryEntry historyEntry = d->createHistoryEntry();
820 d->setSource(url, type);
826 if (!d->stack.isEmpty() && d->stack.top().url == url)
829 if (!d->stack.isEmpty())
830 d->stack.top() = historyEntry;
832 QTextBrowserPrivate::HistoryEntry entry;
834 entry.type = d->currentType;
835 entry.title = documentTitle();
838 d->stack.push(entry);
840 emit backwardAvailable(d->stack.size() > 1);
842 if (!d->forwardStack.isEmpty() && d->forwardStack.top().url == url) {
843 d->forwardStack.pop();
844 emit forwardAvailable(d->forwardStack.size() > 0);
846 d->forwardStack.clear();
847 emit forwardAvailable(
false);
850 emit historyChanged();
854
855
856
857
858
859
862
863
864
865
866
867
870
871
872
873
874
875
876
879
880
881
882
883
884
885
886
887
890
891
892
893
894
897
898
899
900
901
902
903
904
905
906
909
910
911
912
913
914
915void QTextBrowser::backward()
918 if (d->stack.size() <= 1)
922 d->forwardStack.push(d->createHistoryEntry());
924 d->restoreHistoryEntry(d->stack.top());
925 emit backwardAvailable(d->stack.size() > 1);
926 emit forwardAvailable(
true);
927 emit historyChanged();
931
932
933
934
935
936
937void QTextBrowser::forward()
940 if (d->forwardStack.isEmpty())
942 if (!d->stack.isEmpty()) {
944 d->stack.top() = d->createHistoryEntry();
946 d->stack.push(d->forwardStack.pop());
947 d->restoreHistoryEntry(d->stack.top());
948 emit backwardAvailable(
true);
949 emit forwardAvailable(!d->forwardStack.isEmpty());
950 emit historyChanged();
954
955
956
957void QTextBrowser::home()
960 if (d->home.isValid())
965
966
967
968
969
970
971
972
973void QTextBrowser::keyPressEvent(QKeyEvent *ev)
975#ifdef QT_KEYPAD_NAVIGATION
979 if (QApplicationPrivate::keypadNavigationEnabled()) {
980 if (!hasEditFocus()) {
984 QTextCursor cursor = d->control->textCursor();
985 QTextCharFormat charFmt = cursor.charFormat();
986 if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) {
994 if (QApplicationPrivate::keypadNavigationEnabled()) {
995 if (hasEditFocus()) {
1001 QTextEdit::keyPressEvent(ev);
1004 if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus()) {
1011 if (ev->modifiers() & Qt::AltModifier) {
1012 switch (ev->key()) {
1027#ifdef QT_KEYPAD_NAVIGATION
1029 if (ev->key() == Qt::Key_Up) {
1030 d->keypadMove(
false);
1032 }
else if (ev->key() == Qt::Key_Down) {
1033 d->keypadMove(
true);
1038 QTextEdit::keyPressEvent(ev);
1042
1043
1044void QTextBrowser::mouseMoveEvent(QMouseEvent *e)
1046 QTextEdit::mouseMoveEvent(e);
1050
1051
1052void QTextBrowser::mousePressEvent(QMouseEvent *e)
1054 QTextEdit::mousePressEvent(e);
1058
1059
1060void QTextBrowser::mouseReleaseEvent(QMouseEvent *e)
1062 QTextEdit::mouseReleaseEvent(e);
1066
1067
1068void QTextBrowser::focusOutEvent(QFocusEvent *ev)
1072 d->viewport->setCursor((!(d->control->textInteractionFlags() & Qt::TextEditable)) ? d->oldCursor : Qt::IBeamCursor);
1074 QTextEdit::focusOutEvent(ev);
1078
1079
1080bool QTextBrowser::focusNextPrevChild(
bool next)
1083 if (d->control->setFocusToNextOrPreviousAnchor(next)) {
1084#ifdef QT_KEYPAD_NAVIGATION
1086 if (d->prevFocus != d->control->textCursor() && d->control->textCursor().hasSelection()) {
1087 const QString href = d->control->anchorAtCursor();
1088 QUrl url = d->resolveUrl(href);
1089 emitHighlighted(url);
1091 d->prevFocus = d->control->textCursor();
1095#ifdef QT_KEYPAD_NAVIGATION
1097 emitHighlighted(QUrl());
1100 return QTextEdit::focusNextPrevChild(next);
1104
1105
1106void QTextBrowser::paintEvent(QPaintEvent *e)
1109 QPainter p(d->viewport);
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138QVariant QTextBrowser::loadResource(
int ,
const QUrl &name)
1143 QString fileName = d->findFile(d->resolveUrl(name));
1144 if (fileName.isEmpty())
1147 if (f.open(QFile::ReadOnly)) {
1158
1159
1160
1161
1162
1163
1164
1165bool QTextBrowser::isBackwardAvailable()
const
1167 Q_D(
const QTextBrowser);
1168 return d->stack.size() > 1;
1172
1173
1174
1175
1176
1177
1178
1179bool QTextBrowser::isForwardAvailable()
const
1181 Q_D(
const QTextBrowser);
1182 return !d->forwardStack.isEmpty();
1186
1187
1188
1189
1190
1191
1192
1193void QTextBrowser::clearHistory()
1196 d->forwardStack.clear();
1197 if (!d->stack.isEmpty()) {
1198 QTextBrowserPrivate::HistoryEntry historyEntry = d->stack.top();
1200 d->stack.push(historyEntry);
1201 d->home = historyEntry.url;
1203 emit forwardAvailable(
false);
1204 emit backwardAvailable(
false);
1205 emit historyChanged();
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220QUrl QTextBrowser::historyUrl(
int i)
const
1222 Q_D(
const QTextBrowser);
1223 return d->history(i).url;
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240QString QTextBrowser::historyTitle(
int i)
const
1242 Q_D(
const QTextBrowser);
1243 return d->history(i).title;
1248
1249
1250
1251
1252int QTextBrowser::forwardHistoryCount()
const
1254 Q_D(
const QTextBrowser);
1255 return d->forwardStack.size();
1259
1260
1261
1262
1263int QTextBrowser::backwardHistoryCount()
const
1265 Q_D(
const QTextBrowser);
1266 return d->stack.size()-1;
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280bool QTextBrowser::openExternalLinks()
const
1282 Q_D(
const QTextBrowser);
1283 return d->openExternalLinks;
1286void QTextBrowser::setOpenExternalLinks(
bool open)
1289 d->openExternalLinks = open;
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1304bool QTextBrowser::openLinks()
const
1306 Q_D(
const QTextBrowser);
1307 return d->openLinks;
1310void QTextBrowser::setOpenLinks(
bool open)
1313 d->openLinks = open;
1317bool QTextBrowser::event(QEvent *e)
1319 return QTextEdit::event(e);
1324#include "moc_qtextbrowser.cpp"
QRect viewport() const
Returns the viewport rectangle.
std::array< QMetaObject::Connection, 3 > connections
bool forceLoadOnSourceChange
QUrl resolveUrl(const QString &url) const
QTextDocument::ResourceType currentType
void restoreHistoryEntry(const HistoryEntry &entry)
void activateAnchor(const QString &href)
void emitHighlighted(const QUrl &url)
void highlightLink(const QString &href)
HistoryEntry history(int i) const
HistoryEntry createHistoryEntry() const
QStack< HistoryEntry > forwardStack
QString findFile(const QUrl &name) const
QStack< HistoryEntry > stack
virtual QUrl resolveUrl(const QUrl &url) const override
void setSource(const QUrl &url, QTextDocument::ResourceType type)
Combined button and popup list for selecting options.
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
static bool shouldEnableInputMethod(QTextBrowser *texbrowser)
Q_DECLARE_TYPEINFO(QTextBrowserPrivate::HistoryEntry, Q_RELOCATABLE_TYPE)
int focusIndicatorPosition
QTextDocument::ResourceType type