Overload QTextBrowser::setSource() to add optional type argument

Now that it's trying to guess whether the type is markdown based on
the file extension, there needs to be a way to override it. For example
it might be arranged that directory listings will be generated in
markdown format instead of HTML; then when loading a source URL that
is a directory, the application may override the type. The type for
the single-argument setSource(url) is UnknownResource to preserve
the existing behavior, but the user can override the guessing by
setting a specific type.

Change-Id: Id111efd24de7d8fd18c47b16a2d58f5b09d77891
Reviewed-by: Gatis Paeglis <gatis.paeglis@qt.io>
This commit is contained in:
Shawn Rutledge 2019-05-25 12:09:53 +02:00
parent 84536ae61e
commit b149f5d77a
7 changed files with 127 additions and 21 deletions

View File

@ -225,6 +225,7 @@ public:
void print(QPagedPaintDevice *printer) const;
enum ResourceType {
UnknownResource = 0,
HtmlResource = 1,
ImageResource = 2,
StyleSheetResource = 3,
@ -232,6 +233,7 @@ public:
UserResource = 100
};
Q_ENUM(ResourceType)
QVariant resource(int type, const QUrl &name) const;
void addResource(int type, const QUrl &name, const QVariant &resource);

View File

@ -86,6 +86,7 @@ public:
int hpos;
int vpos;
int focusIndicatorPosition, focusIndicatorAnchor;
QTextDocument::ResourceType type = QTextDocument::UnknownResource;
};
HistoryEntry history(int i) const
@ -122,6 +123,8 @@ public:
bool openExternalLinks;
bool openLinks;
QTextDocument::ResourceType currentType;
#ifndef QT_NO_CURSOR
QCursor oldCursor;
#endif
@ -137,7 +140,7 @@ public:
void _q_activateAnchor(const QString &href);
void _q_highlightLink(const QString &href);
void setSource(const QUrl &url);
void setSource(const QUrl &url, QTextDocument::ResourceType type);
// re-imlemented from QTextEditPrivate
virtual QUrl resolveUrl(const QUrl &url) const override;
@ -274,7 +277,7 @@ void QTextBrowserPrivate::_q_highlightLink(const QString &anchor)
}
}
void QTextBrowserPrivate::setSource(const QUrl &url)
void QTextBrowserPrivate::setSource(const QUrl &url, QTextDocument::ResourceType type)
{
Q_Q(QTextBrowser);
#ifndef QT_NO_CURSOR
@ -291,14 +294,18 @@ void QTextBrowserPrivate::setSource(const QUrl &url)
currentUrlWithoutFragment.setFragment(QString());
QUrl newUrlWithoutFragment = currentURL.resolved(url);
newUrlWithoutFragment.setFragment(QString());
QTextDocument::ResourceType type = QTextDocument::HtmlResource;
QString fileName = url.fileName();
if (type == QTextDocument::UnknownResource) {
#if QT_CONFIG(textmarkdownreader)
if (fileName.endsWith(QLatin1String(".md")) ||
fileName.endsWith(QLatin1String(".mkd")) ||
fileName.endsWith(QLatin1String(".markdown")))
type = QTextDocument::MarkdownResource;
if (fileName.endsWith(QLatin1String(".md")) ||
fileName.endsWith(QLatin1String(".mkd")) ||
fileName.endsWith(QLatin1String(".markdown")))
type = QTextDocument::MarkdownResource;
else
#endif
type = QTextDocument::HtmlResource;
}
currentType = type;
if (url.isValid()
&& (newUrlWithoutFragment != currentUrlWithoutFragment || forceLoadOnSourceChange)) {
@ -574,6 +581,7 @@ QTextBrowserPrivate::HistoryEntry QTextBrowserPrivate::createHistoryEntry() cons
{
HistoryEntry entry;
entry.url = q_func()->source();
entry.type = q_func()->sourceType();
entry.title = q_func()->documentTitle();
entry.hpos = hbar->value();
entry.vpos = vbar->value();
@ -590,7 +598,7 @@ QTextBrowserPrivate::HistoryEntry QTextBrowserPrivate::createHistoryEntry() cons
void QTextBrowserPrivate::restoreHistoryEntry(const HistoryEntry &entry)
{
setSource(entry.url);
setSource(entry.url, entry.type);
hbar->setValue(entry.hpos);
vbar->setValue(entry.vpos);
if (entry.focusIndicatorAnchor != -1 && entry.focusIndicatorPosition != -1) {
@ -732,7 +740,13 @@ QTextBrowser::~QTextBrowser()
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().
the named document with \l QTextDocument::setHtml() or
\l QTextDocument::setMarkdown(), depending on whether the filename ends
with any of the known Markdown file extensions.
If you would like to avoid automatic type detection
and specify the type explicitly, call setSource() rather than
setting this property.
By default, this property contains an empty URL.
*/
@ -745,6 +759,23 @@ QUrl QTextBrowser::source() const
return d->stack.top().url;
}
/*!
\property QTextBrowser::sourceType
\brief the type of the displayed document
This is QTextDocument::UnknownResource if no document is displayed or if
the type of the source is unknown. Otherwise it holds the type that was
detected, or the type that was specified when setSource() was called.
*/
QTextDocument::ResourceType QTextBrowser::sourceType() const
{
Q_D(const QTextBrowser);
if (d->stack.isEmpty())
return QTextDocument::UnknownResource;
else
return d->stack.top().type;
}
/*!
\property QTextBrowser::searchPaths
\brief the search paths used by the text browser to find supporting
@ -775,16 +806,46 @@ void QTextBrowser::reload()
Q_D(QTextBrowser);
QUrl s = d->currentURL;
d->currentURL = QUrl();
setSource(s);
setSource(s, d->currentType);
}
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
void QTextBrowser::setSource(const QUrl &url)
{
setSource(url, QTextDocument::UnknownResource);
}
#endif
/*!
Attempts to load the document at the given \a url with the specified \a type.
If \a type is \l {QTextDocument::ResourceType::UnknownResource}{UnknownResource}
(the default), the document type will be detected: that is, if the url ends
with an extension of \c{.md}, \c{.mkd} or \c{.markdown}, the document will be
loaded via \l QTextDocument::setMarkdown(); otherwise it will be loaded via
\l QTextDocument::setHtml(). This detection can be bypassed by specifying
the \a type explicitly.
*/
void QTextBrowser::setSource(const QUrl &url, QTextDocument::ResourceType type)
{
doSetSource(url, type);
}
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
/*!
Attempts to load the document at the given \a url with the specified \a type.
setSource() calls doSetSource. In Qt 5, setSource(const QUrl &url) was virtual.
In Qt 6, doSetSource() is virtual instead, so that it can be overridden in subclasses.
*/
#endif
void QTextBrowser::doSetSource(const QUrl &url, QTextDocument::ResourceType type)
{
Q_D(QTextBrowser);
const QTextBrowserPrivate::HistoryEntry historyEntry = d->createHistoryEntry();
d->setSource(url);
d->setSource(url, type);
if (!url.isValid())
return;
@ -798,6 +859,7 @@ void QTextBrowser::setSource(const QUrl &url)
QTextBrowserPrivate::HistoryEntry entry;
entry.url = url;
entry.type = d->currentType;
entry.title = documentTitle();
entry.hpos = 0;
entry.vpos = 0;

View File

@ -55,6 +55,7 @@ class Q_WIDGETS_EXPORT QTextBrowser : public QTextEdit
Q_OBJECT
Q_PROPERTY(QUrl source READ source WRITE setSource)
Q_PROPERTY(QTextDocument::ResourceType sourceType READ sourceType)
Q_OVERRIDE(bool modified SCRIPTABLE false)
Q_OVERRIDE(bool readOnly DESIGNABLE false SCRIPTABLE false)
Q_OVERRIDE(bool undoRedoEnabled DESIGNABLE false SCRIPTABLE false)
@ -67,6 +68,7 @@ public:
virtual ~QTextBrowser();
QUrl source() const;
QTextDocument::ResourceType sourceType() const;
QStringList searchPaths() const;
void setSearchPaths(const QStringList &paths);
@ -88,7 +90,12 @@ public:
void setOpenLinks(bool open);
public Q_SLOTS:
#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
virtual void setSource(const QUrl &name);
void setSource(const QUrl &name, QTextDocument::ResourceType type);
#else
void setSource(const QUrl &name, QTextDocument::ResourceType type = QTextDocument::UnknownResource);
#endif
virtual void backward();
virtual void forward();
virtual void home();
@ -112,6 +119,10 @@ protected:
virtual void focusOutEvent(QFocusEvent *ev) override;
virtual bool focusNextPrevChild(bool next) override;
virtual void paintEvent(QPaintEvent *e) override;
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
virtual
#endif
void doSetSource(const QUrl &name, QTextDocument::ResourceType type = QTextDocument::UnknownResource);
private:
Q_DISABLE_COPY(QTextBrowser)

View File

@ -0,0 +1,2 @@
<h3>this is a heading</h3>
<p>this is a paragraph</p>

View File

@ -0,0 +1,2 @@
### this is a heading
this is a paragraph

View File

@ -4,6 +4,6 @@ SOURCES += tst_qtextbrowser.cpp
QT += widgets testlib
TESTDATA += *.html *.md subdir/*
TESTDATA += *.html *.md markdown.really subdir/*
builtin_testdata: DEFINES += BUILTIN_TESTDATA

View File

@ -92,7 +92,8 @@ private slots:
void focusIndicator();
void focusHistory();
void urlEncoding();
void markdown();
void sourceType_data();
void sourceType();
private:
TestBrowser *browser;
@ -679,19 +680,45 @@ void tst_QTextBrowser::urlEncoding()
delete browser;
}
void tst_QTextBrowser::markdown()
void tst_QTextBrowser::sourceType_data()
{
browser->setSource(QUrl::fromLocalFile(QFINDTESTDATA("markdown.md")));
QTest::addColumn<QString>("sourceFile");
QTest::addColumn<QTextDocument::ResourceType>("sourceType");
QTest::addColumn<int>("expectedMaxHeadingLevel");
QTest::addColumn<QTextDocument::ResourceType>("expectedSourceType");
#if QT_CONFIG(textmarkdownreader)
const int maxMdHeadingLevel = 3;
const QTextDocument::ResourceType mdExpectedType = QTextDocument::MarkdownResource;
#else
// If Qt doesn't support markdown, and we read a MD document anyway, it won't have any H3's.
const int maxMdHeadingLevel = 0;
const QTextDocument::ResourceType mdExpectedType = QTextDocument::HtmlResource;
#endif
QTest::newRow("markdown detected") << "markdown.md" << QTextDocument::UnknownResource << maxMdHeadingLevel << mdExpectedType;
QTest::newRow("markdown specified") << "markdown.really" << QTextDocument::MarkdownResource << maxMdHeadingLevel << mdExpectedType;
QTest::newRow("markdown not identified") << "markdown.really" << QTextDocument::UnknownResource << 0 << QTextDocument::HtmlResource;
QTest::newRow("html detected") << "heading.html" << QTextDocument::UnknownResource << 3 << QTextDocument::HtmlResource;
QTest::newRow("html specified") << "heading.html" << QTextDocument::HtmlResource << 3 << QTextDocument::HtmlResource;
}
void tst_QTextBrowser::sourceType()
{
QFETCH(QString, sourceFile);
QFETCH(QTextDocument::ResourceType, sourceType);
QFETCH(int, expectedMaxHeadingLevel);
QFETCH(QTextDocument::ResourceType, expectedSourceType);
if (sourceType == QTextDocument::UnknownResource)
// verify that the property setter works, with its default parameter for sourceType
browser->setProperty("source", QUrl::fromLocalFile(QFINDTESTDATA(sourceFile)));
else
browser->setSource(QUrl::fromLocalFile(QFINDTESTDATA(sourceFile)), sourceType);
QCOMPARE(browser->sourceType(), expectedSourceType);
QTextFrame::iterator iterator = browser->document()->rootFrame()->begin();
int maxHeadingLevel = -1;
while (!iterator.atEnd())
maxHeadingLevel = qMax(iterator++.currentBlock().blockFormat().intProperty(QTextFormat::HeadingLevel), maxHeadingLevel);
#if QT_CONFIG(textmarkdownreader)
QCOMPARE(maxHeadingLevel, 3);
#else
// If Qt doesn't support markdown, this document will be misidentified as HTML, so it won't have any H3's.
QCOMPARE(maxHeadingLevel, 0);
#endif
QCOMPARE(maxHeadingLevel, expectedMaxHeadingLevel);
}
QTEST_MAIN(tst_QTextBrowser)