9#include <qapplication.h>
10#include <private/qapplication_p.h>
13#include <qabstracttextdocumentlayout.h>
14#include "private/qtextdocumentlayout_p.h"
17#if QT_CONFIG(whatsthis)
18#include <qwhatsthis.h>
20#include <qtextobject.h>
21#include <qdesktopservices.h>
22#include <qstringconverter.h>
26using namespace Qt::StringLiterals;
30#if defined (Q_OS_ANDROID)
31 return !texbrowser->isReadOnly() || (texbrowser->textInteractionFlags() & Qt::TextSelectableByMouse);
33 return !texbrowser->isReadOnly();
41 Q_DECLARE_PUBLIC(QTextBrowser)
46#ifdef QT_KEYPAD_NAVIGATION
73 if (-i < stack.size())
74 return stack[stack.size()+i-1];
78 if (i <= forwardStack.size())
79 return forwardStack[forwardStack.size()-i];
96
97
115 forceLoadOnSourceChange = !currentURL.path().isEmpty();
121 void setSource(
const QUrl &url, QTextDocument::ResourceType type);
128#ifdef QT_KEYPAD_NAVIGATION
136 emit q->highlighted(url);
145 if (name.scheme() ==
"qrc"_L1) {
146 fileName =
":/"_L1 + name.path();
147 }
else if (name.scheme().isEmpty()) {
148 fileName = name.path();
150#if defined(Q_OS_ANDROID)
151 if (name.scheme() ==
"assets"_L1)
152 fileName =
"assets:"_L1 + name.path();
155 fileName = name.toLocalFile();
158 if (fileName.isEmpty())
161 if (QFileInfo(fileName).isAbsolute())
164 for (QString path : std::as_const(searchPaths)) {
165 if (!path.endsWith(u'/'))
167 path.append(fileName);
168 if (QFileInfo(path).isReadable())
177 if (!url.isRelative())
182 if (!(currentURL.isRelative()
183 || (currentURL.scheme() ==
"file"_L1
184 && !QFileInfo(currentURL.toLocalFile()).isAbsolute()))
185 || (url.hasFragment() && url.path().isEmpty())) {
186 return currentURL.resolved(url);
192 QFileInfo fi(currentURL.toLocalFile());
194 return QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(url);
207 viewport->setCursor(oldCursor);
210 const QUrl url = resolveUrl(href);
213 emit q->anchorClicked(url);
219#ifndef QT_NO_DESKTOPSERVICES
221 url.scheme() ==
"file"_L1
222#if defined(Q_OS_ANDROID)
223 || url.scheme() ==
"assets"_L1
225 || url.scheme() ==
"qrc"_L1;
226 if ((openExternalLinks && !isFileScheme && !url.isRelative())
227 || (url.isRelative() && !currentURL.isRelative() && !isFileScheme)) {
228 QDesktopServices::openUrl(url);
233 emit q->anchorClicked(url);
243 if (anchor.isEmpty()) {
245 if (viewport->cursor().shape() != Qt::PointingHandCursor)
246 oldCursor = viewport->cursor();
247 viewport->setCursor(oldCursor);
252 viewport->setCursor(Qt::PointingHandCursor);
255 const QUrl url = resolveUrl(anchor);
265 QGuiApplication::setOverrideCursor(Qt::WaitCursor);
271 bool doSetText =
false;
273 QUrl currentUrlWithoutFragment = currentURL;
274 currentUrlWithoutFragment.setFragment(QString());
275 QUrl newUrlWithoutFragment = currentURL.resolved(url);
276 newUrlWithoutFragment.setFragment(QString());
277 QString fileName = url.fileName();
278 if (type == QTextDocument::UnknownResource) {
279#if QT_CONFIG(textmarkdownreader)
280 if (fileName.endsWith(
".md"_L1) ||
281 fileName.endsWith(
".mkd"_L1) ||
282 fileName.endsWith(
".markdown"_L1))
283 type = QTextDocument::MarkdownResource;
286 type = QTextDocument::HtmlResource;
292 QVariant data = q->loadResource(type, resolveUrl(url));
293 if (data.userType() == QMetaType::QString) {
294 txt = data.toString();
295 }
else if (data.userType() == QMetaType::QByteArray) {
296 QByteArray ba = data.toByteArray();
297 if (type == QTextDocument::HtmlResource) {
298 auto decoder = QStringDecoder::decoderForHtml(ba);
299 if (!decoder.isValid())
301 decoder = QStringDecoder(QStringDecoder::Utf8);
304 txt = QString::fromUtf8(ba);
307 if (Q_UNLIKELY(txt.isEmpty()))
308 qWarning(
"QTextBrowser: No document for %s", url.toString().toLatin1().constData());
310 if (q->isVisible()) {
311 const QStringView firstTag = QStringView{txt}.left(txt.indexOf(u'>') + 1);
312 if (firstTag.startsWith(
"<qt"_L1) && firstTag.contains(
"type"_L1) && firstTag.contains(
"detail"_L1)) {
314 QGuiApplication::restoreOverrideCursor();
316#if QT_CONFIG(whatsthis)
317 QWhatsThis::showText(QCursor::pos(), txt, q);
323 currentURL = resolveUrl(url);
334 QUrl baseUrl = currentURL.adjusted(QUrl::RemoveFilename);
335 if (!baseUrl.path().isEmpty())
336 q->document()->setBaseUrl(baseUrl);
337 q->document()->setMetaInformation(QTextDocument::DocumentUrl, currentURL.toString());
338 qCDebug(lcBrowser) <<
"loading" << currentURL <<
"base" << q->document()->baseUrl() <<
"type" << type << txt.size() <<
"chars";
339#if QT_CONFIG(textmarkdownreader)
340 if (type == QTextDocument::MarkdownResource)
341 q->QTextEdit::setMarkdown(txt);
344#ifndef QT_NO_TEXTHTMLPARSER
345 q->QTextEdit::setHtml(txt);
347 q->QTextEdit::setPlainText(txt);
350#ifdef QT_KEYPAD_NAVIGATION
351 prevFocus.movePosition(QTextCursor::Start);
357 if (!url.fragment().isEmpty()) {
358 q->scrollToAnchor(url.fragment());
363#ifdef QT_KEYPAD_NAVIGATION
364 lastKeypadScrollValue = vbar->value();
365 emitHighlighted(QUrl());
370 QGuiApplication::restoreOverrideCursor();
372 emit q->sourceChanged(url);
375#ifdef QT_KEYPAD_NAVIGATION
376void QTextBrowserPrivate::keypadMove(
bool next)
380 const int height = viewport->height();
381 const int overlap = qBound(20, height / 5, 40);
382 const int visibleLinkAmount = overlap;
383 int yOffset = vbar->value();
384 int scrollYOffset = qBound(0, next ? yOffset + height - overlap : yOffset - height + overlap, vbar->maximum());
386 bool foundNextAnchor =
false;
387 bool focusIt =
false;
390 QTextCursor anchorToFocus;
392 QRectF viewRect = QRectF(0, yOffset, control->size().width(), height);
393 QRectF newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
394 QRectF bothViewRects = viewRect.united(newViewRect);
398 if (prevFocus.isNull()) {
400 prevFocus = control->cursorForPosition(QPointF(0, yOffset));
402 prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
406 if (lastKeypadScrollValue != yOffset) {
410 bool findOnScreen =
true;
413 if (prevFocus.hasSelection()) {
414 QRectF prevRect = control->selectionRect(prevFocus);
415 if (viewRect.intersects(prevRect))
416 findOnScreen =
false;
424 prevFocus = control->cursorForPosition(QPointF(0, yOffset));
426 prevFocus = control->cursorForPosition(QPointF(control->size().width(), yOffset + height));
428 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
429 }
else if (prevFocus.hasSelection()) {
432 QRectF prevRect = control->selectionRect(prevFocus);
433 if ((next && prevRect.bottom() > (yOffset + height)) ||
434 (!next && prevRect.top() < yOffset)) {
435 anchorToFocus = prevFocus;
436 focusedPos = scrollYOffset;
441 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
446 if (!focusIt && !foundNextAnchor) {
448 if (yOffset == vbar->maximum()) {
449 prevFocus.movePosition(QTextCursor::Start);
450 yOffset = scrollYOffset = 0;
453 viewRect = QRectF(0, yOffset, control->size().width(), height);
454 newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
455 bothViewRects = viewRect.united(newViewRect);
459 prevFocus.movePosition(QTextCursor::End);
460 yOffset = scrollYOffset = vbar->maximum();
463 viewRect = QRectF(0, yOffset, control->size().width(), height);
464 newViewRect = QRectF(0, scrollYOffset, control->size().width(), height);
465 bothViewRects = viewRect.united(newViewRect);
470 foundNextAnchor = control->findNextPrevAnchor(prevFocus, next, anchorToFocus);
474 if (foundNextAnchor) {
475 QRectF desiredRect = control->selectionRect(anchorToFocus);
487 if (bothViewRects.contains(desiredRect)
488 || bothViewRects.adjusted(0, visibleLinkAmount, 0, -visibleLinkAmount).intersects(desiredRect)) {
494 if (desiredRect.height() > height) {
496 focusedPos = (
int) desiredRect.top();
498 focusedPos = (
int) desiredRect.bottom() - height;
500 focusedPos = (
int) ((desiredRect.top() + desiredRect.bottom()) / 2 - (height / 2));
504 focusedPos = qBound(yOffset, focusedPos, scrollYOffset);
506 focusedPos = qBound(scrollYOffset, focusedPos, yOffset);
513 if (!focusIt && prevFocus.hasSelection()) {
514 QRectF desiredRect = control->selectionRect(prevFocus);
516 if (newViewRect.intersects(desiredRect)) {
517 focusedPos = scrollYOffset;
519 anchorToFocus = prevFocus;
525 const int savedXOffset = hbar->value();
528 if (focusIt && control->setFocusToAnchor(anchorToFocus)) {
530 prevFocus = control->textCursor();
533 vbar->setValue(focusedPos);
534 lastKeypadScrollValue = focusedPos;
535 hbar->setValue(savedXOffset);
538 const QString href = control->anchorAtCursor();
539 QUrl url = resolveUrl(href);
540 emitHighlighted(url);
543 vbar->setValue(scrollYOffset);
544 lastKeypadScrollValue = scrollYOffset;
547 QTextCursor cursor = control->textCursor();
548 cursor.clearSelection();
550 control->setTextCursor(cursor);
552 hbar->setValue(savedXOffset);
553 vbar->setValue(scrollYOffset);
555 emitHighlighted(QUrl());
563 entry.url = q_func()->source();
564 entry.type = q_func()->sourceType();
565 entry.title = q_func()->documentTitle();
566 entry.hpos = hbar->value();
567 entry.vpos = vbar->value();
569 const QTextCursor cursor = control->textCursor();
570 if (control->cursorIsFocusIndicator()
571 && cursor.hasSelection()) {
581 setSource(entry.url, entry.type);
582 hbar->setValue(entry.hpos);
583 vbar->setValue(entry.vpos);
585 QTextCursor cursor(control->document());
587 cursor.setPosition(entry.focusIndicatorPosition, QTextCursor::KeepAnchor);
588 control->setTextCursor(cursor);
589 control->setCursorIsFocusIndicator(
true);
591#ifdef QT_KEYPAD_NAVIGATION
592 lastKeypadScrollValue = vbar->value();
593 prevFocus = control->textCursor();
596 const QString href = prevFocus.charFormat().anchorHref();
597 QUrl url = resolveUrl(href);
598 emitHighlighted(url);
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
649
652
653
654
657
658
659
660
661
664
665
666
667
668
673 control->setTextInteractionFlags(Qt::TextBrowserInteraction);
675 viewport->setCursor(oldCursor);
677 q->setAttribute(Qt::WA_InputMethodEnabled, shouldEnableInputMethod(q));
678 q->setUndoRedoEnabled(
false);
681 QObjectPrivate::connect(q->document(), &QTextDocument::contentsChanged,
682 this, &QTextBrowserPrivate::documentModified),
683 QObjectPrivate::connect(control, &QWidgetTextControl::linkActivated,
684 this, &QTextBrowserPrivate::activateAnchor),
685 QObjectPrivate::connect(control, &QWidgetTextControl::linkHovered,
686 this, &QTextBrowserPrivate::highlightLink),
691
692
693QTextBrowser::QTextBrowser(QWidget *parent)
694 : QTextEdit(*
new QTextBrowserPrivate, parent)
702
703
704QTextBrowser::~QTextBrowser()
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
734
735QUrl QTextBrowser::source()
const
737 Q_D(
const QTextBrowser);
738 if (d->stack.isEmpty())
741 return d->stack.top().url;
745
746
747
748
749
750
751
752QTextDocument::ResourceType QTextBrowser::sourceType()
const
754 Q_D(
const QTextBrowser);
755 if (d->stack.isEmpty())
756 return QTextDocument::UnknownResource;
758 return d->stack.top().type;
762
763
764
765
766
767
768
769
771QStringList QTextBrowser::searchPaths()
const
773 Q_D(
const QTextBrowser);
774 return d->searchPaths;
777void QTextBrowser::setSearchPaths(
const QStringList &paths)
780 d->searchPaths = paths;
784
785
786void QTextBrowser::reload()
789 QUrl s = d->currentURL;
790 d->currentURL = QUrl();
791 setSource(s, d->currentType);
795
796
797
798
799
800
801
802
803
804void QTextBrowser::setSource(
const QUrl &url, QTextDocument::ResourceType type)
806 doSetSource(url, type);
810
811
812
813
814
815void QTextBrowser::doSetSource(
const QUrl &url, QTextDocument::ResourceType type)
819 const QTextBrowserPrivate::HistoryEntry historyEntry = d->createHistoryEntry();
821 d->setSource(url, type);
827 if (!d->stack.isEmpty() && d->stack.top().url == url)
830 if (!d->stack.isEmpty())
831 d->stack.top() = historyEntry;
833 QTextBrowserPrivate::HistoryEntry entry;
835 entry.type = d->currentType;
836 entry.title = documentTitle();
839 d->stack.push(entry);
841 emit backwardAvailable(d->stack.size() > 1);
843 if (!d->forwardStack.isEmpty() && d->forwardStack.top().url == url) {
844 d->forwardStack.pop();
845 emit forwardAvailable(d->forwardStack.size() > 0);
847 d->forwardStack.clear();
848 emit forwardAvailable(
false);
851 emit historyChanged();
855
856
857
858
859
860
863
864
865
866
867
868
871
872
873
874
875
876
877
880
881
882
883
884
885
886
887
888
891
892
893
894
895
898
899
900
901
902
903
904
905
906
907
910
911
912
913
914
915
916void QTextBrowser::backward()
919 if (d->stack.size() <= 1)
923 d->forwardStack.push(d->createHistoryEntry());
925 d->restoreHistoryEntry(d->stack.top());
926 emit backwardAvailable(d->stack.size() > 1);
927 emit forwardAvailable(
true);
928 emit historyChanged();
932
933
934
935
936
937
938void QTextBrowser::forward()
941 if (d->forwardStack.isEmpty())
943 if (!d->stack.isEmpty()) {
945 d->stack.top() = d->createHistoryEntry();
947 d->stack.push(d->forwardStack.pop());
948 d->restoreHistoryEntry(d->stack.top());
949 emit backwardAvailable(
true);
950 emit forwardAvailable(!d->forwardStack.isEmpty());
951 emit historyChanged();
955
956
957
958void QTextBrowser::home()
961 if (d->home.isValid())
966
967
968
969
970
971
972
973
974void QTextBrowser::keyPressEvent(QKeyEvent *ev)
976#ifdef QT_KEYPAD_NAVIGATION
980 if (QApplicationPrivate::keypadNavigationEnabled()) {
981 if (!hasEditFocus()) {
985 QTextCursor cursor = d->control->textCursor();
986 QTextCharFormat charFmt = cursor.charFormat();
987 if (!cursor.hasSelection() || charFmt.anchorHref().isEmpty()) {
995 if (QApplicationPrivate::keypadNavigationEnabled()) {
996 if (hasEditFocus()) {
1002 QTextEdit::keyPressEvent(ev);
1005 if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus()) {
1012 if (ev->modifiers() & Qt::AltModifier) {
1013 switch (ev->key()) {
1028#ifdef QT_KEYPAD_NAVIGATION
1030 if (ev->key() == Qt::Key_Up) {
1031 d->keypadMove(
false);
1033 }
else if (ev->key() == Qt::Key_Down) {
1034 d->keypadMove(
true);
1039 QTextEdit::keyPressEvent(ev);
1043
1044
1045void QTextBrowser::mouseMoveEvent(QMouseEvent *e)
1047 QTextEdit::mouseMoveEvent(e);
1051
1052
1053void QTextBrowser::mousePressEvent(QMouseEvent *e)
1055 QTextEdit::mousePressEvent(e);
1059
1060
1061void QTextBrowser::mouseReleaseEvent(QMouseEvent *e)
1063 QTextEdit::mouseReleaseEvent(e);
1067
1068
1069void QTextBrowser::focusOutEvent(QFocusEvent *ev)
1073 d->viewport->setCursor((!(d->control->textInteractionFlags() & Qt::TextEditable)) ? d->oldCursor : Qt::IBeamCursor);
1075 QTextEdit::focusOutEvent(ev);
1079
1080
1081bool QTextBrowser::focusNextPrevChild(
bool next)
1084 if (d->control->setFocusToNextOrPreviousAnchor(next)) {
1085#ifdef QT_KEYPAD_NAVIGATION
1087 if (d->prevFocus != d->control->textCursor() && d->control->textCursor().hasSelection()) {
1088 const QString href = d->control->anchorAtCursor();
1089 QUrl url = d->resolveUrl(href);
1090 emitHighlighted(url);
1092 d->prevFocus = d->control->textCursor();
1096#ifdef QT_KEYPAD_NAVIGATION
1098 emitHighlighted(QUrl());
1101 return QTextEdit::focusNextPrevChild(next);
1105
1106
1107void QTextBrowser::paintEvent(QPaintEvent *e)
1110 QPainter p(d->viewport);
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139QVariant QTextBrowser::loadResource(
int ,
const QUrl &name)
1144 QString fileName = d->findFile(d->resolveUrl(name));
1145 if (fileName.isEmpty())
1148 if (f.open(QFile::ReadOnly)) {
1159
1160
1161
1162
1163
1164
1165
1166bool QTextBrowser::isBackwardAvailable()
const
1168 Q_D(
const QTextBrowser);
1169 return d->stack.size() > 1;
1173
1174
1175
1176
1177
1178
1179
1180bool QTextBrowser::isForwardAvailable()
const
1182 Q_D(
const QTextBrowser);
1183 return !d->forwardStack.isEmpty();
1187
1188
1189
1190
1191
1192
1193
1194void QTextBrowser::clearHistory()
1197 d->forwardStack.clear();
1198 if (!d->stack.isEmpty()) {
1199 QTextBrowserPrivate::HistoryEntry historyEntry = d->stack.top();
1201 d->stack.push(historyEntry);
1202 d->home = historyEntry.url;
1204 emit forwardAvailable(
false);
1205 emit backwardAvailable(
false);
1206 emit historyChanged();
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221QUrl QTextBrowser::historyUrl(
int i)
const
1223 Q_D(
const QTextBrowser);
1224 return d->history(i).url;
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241QString QTextBrowser::historyTitle(
int i)
const
1243 Q_D(
const QTextBrowser);
1244 return d->history(i).title;
1249
1250
1251
1252
1253int QTextBrowser::forwardHistoryCount()
const
1255 Q_D(
const QTextBrowser);
1256 return d->forwardStack.size();
1260
1261
1262
1263
1264int QTextBrowser::backwardHistoryCount()
const
1266 Q_D(
const QTextBrowser);
1267 return d->stack.size()-1;
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281bool QTextBrowser::openExternalLinks()
const
1283 Q_D(
const QTextBrowser);
1284 return d->openExternalLinks;
1287void QTextBrowser::setOpenExternalLinks(
bool open)
1290 d->openExternalLinks = open;
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1305bool QTextBrowser::openLinks()
const
1307 Q_D(
const QTextBrowser);
1308 return d->openLinks;
1311void QTextBrowser::setOpenLinks(
bool open)
1314 d->openLinks = open;
1318bool QTextBrowser::event(QEvent *e)
1320 return QTextEdit::event(e);
1325#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)
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