aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2020-01-31 01:00:06 +0100
committerQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2020-01-31 01:00:06 +0100
commitc6c3613af3229b818b37482bfe285bbdb2e09e85 (patch)
tree9ba2466de0f426b5e7c666c053e9a0aa6a32d3b8
parente898490b4e6d6f85ea826a4e864b602e361fb7e5 (diff)
parentf7647d5adaaed4c651cb2e65c7ce66d7ef6639f1 (diff)
Merge remote-tracking branch 'origin/5.15' into dev
-rw-r--r--src/qml/jsruntime/qv4dateobject.cpp6
-rw-r--r--src/qml/qml/v8/qqmlbuiltinfunctions.cpp190
-rw-r--r--src/quick/items/qquickanimatedimage.cpp2
-rw-r--r--src/quick/items/qquickimage.cpp54
-rw-r--r--src/quick/items/qquickimage_p.h1
-rw-r--r--src/quick/items/qquickimagebase.cpp24
-rw-r--r--src/quick/items/qquickimagebase_p.h5
-rw-r--r--src/quick/items/qquickimagebase_p_p.h1
-rw-r--r--src/quick/items/qquicktext.cpp2
-rw-r--r--src/quick/util/qquickpixmapcache.cpp127
-rw-r--r--src/quick/util/qquickpixmapcache_p.h13
-rw-r--r--src/quick/util/qquickstyledtext.cpp2
-rw-r--r--tests/auto/qml/qmlformat/data/Example1.formatted.nosort.qml8
-rw-r--r--tests/auto/qml/qmlformat/data/Example1.formatted.qml8
-rw-r--r--tests/auto/qml/qmlformat/data/readOnlyProps.formatted.qml29
-rw-r--r--tests/auto/qml/qmlformat/data/readOnlyProps.qml15
-rw-r--r--tests/auto/qml/qmlformat/tst_qmlformat.cpp11
-rw-r--r--tests/auto/qml/qqmlqt/data/formattingLocale.qml12
-rw-r--r--tests/auto/qml/qqmlqt/tst_qqmlqt.cpp52
-rw-r--r--tests/manual/tableview/imagetiling/imageTiling.qml90
-rw-r--r--tests/manual/tableview/imagetiling/map.pdfbin0 -> 31904 bytes
-rw-r--r--tests/manual/tableview/imagetiling/map.pngbin0 -> 21101 bytes
-rw-r--r--tests/manual/tableview/imagetiling/map.svgzbin0 -> 27956 bytes
-rw-r--r--tools/qmlformat/commentastvisitor.cpp6
-rw-r--r--tools/qmlformat/commentastvisitor.h1
-rw-r--r--tools/qmlformat/dumpastvisitor.cpp367
-rw-r--r--tools/qmlformat/dumpastvisitor.h31
-rw-r--r--tools/qmlformat/main.cpp26
28 files changed, 827 insertions, 256 deletions
diff --git a/src/qml/jsruntime/qv4dateobject.cpp b/src/qml/jsruntime/qv4dateobject.cpp
index c2f48ffeac..bebcbd7e44 100644
--- a/src/qml/jsruntime/qv4dateobject.cpp
+++ b/src/qml/jsruntime/qv4dateobject.cpp
@@ -682,17 +682,17 @@ static inline QString ToTimeString(double t)
static inline QString ToLocaleString(double t)
{
- return ToDateTime(t, Qt::LocalTime).toString(Qt::DefaultLocaleShortDate);
+ return QLocale().toString(ToDateTime(t, Qt::LocalTime), QLocale::ShortFormat);
}
static inline QString ToLocaleDateString(double t)
{
- return ToDateTime(t, Qt::LocalTime).date().toString(Qt::DefaultLocaleShortDate);
+ return QLocale().toString(ToDateTime(t, Qt::LocalTime).date(), QLocale::ShortFormat);
}
static inline QString ToLocaleTimeString(double t)
{
- return ToDateTime(t, Qt::LocalTime).time().toString(Qt::DefaultLocaleShortDate);
+ return QLocale().toString(ToDateTime(t, Qt::LocalTime).time(), QLocale::ShortFormat);
}
static double getLocalTZA()
diff --git a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp
index 02032142ee..d47c2604ae 100644
--- a/src/qml/qml/v8/qqmlbuiltinfunctions.cpp
+++ b/src/qml/qml/v8/qqmlbuiltinfunctions.cpp
@@ -722,70 +722,127 @@ ReturnedValue QtObject::method_tint(const FunctionObject *b, const Value *, cons
return scope.engine->fromVariant(QQml_colorProvider()->tint(v1, v2));
}
+namespace {
+template <typename T>
+QString formatDateTimeObjectUsingDateFormat(T formatThis, Qt::DateFormat format) {
+ switch (format) {
+ case Qt::TextDate:
+ case Qt::ISODate:
+ case Qt::RFC2822Date:
+ case Qt::ISODateWithMs:
+ return formatThis.toString(format);
+ // ### Qt 6: Remove all locale dependent cases
+ QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
+ case Qt::SystemLocaleDate:
+ // case Qt::LocalDate: covered by SystemLocaleDate
+ return QLocale::system().toString(formatThis);
+ case Qt::LocaleDate:
+ case Qt::DefaultLocaleShortDate:
+ return QLocale().toString(formatThis, QLocale::ShortFormat);
+ case Qt::SystemLocaleShortDate:
+ return QLocale::system().toString(formatThis, QLocale::ShortFormat);
+ case Qt::SystemLocaleLongDate:
+ return QLocale::system().toString(formatThis, QLocale::LongFormat);
+ case Qt::DefaultLocaleLongDate:
+ return QLocale().toString(formatThis, QLocale::LongFormat);
+ }
+ QT_WARNING_POP
+ Q_UNREACHABLE();
+ return QString();
+}
+
+template <typename T>
+ReturnedValue formatDateTimeObject(const T &formatThis, const QV4::Scope &scope, const QString &functionName, int argc, const Value *argv) {
+
+ QString formatted;
+ if (argc >= 2) {
+ QV4::ScopedString s(scope, argv[1]);
+ if (s) {
+ if (argc == 3)
+ scope.engine->throwError(QLatin1String("%1(): Stay argument, third argument can only be used if second argument is a locale").arg(functionName));
+ QString format = s->toQString();
+ formatted = formatThis.toString(format);
+ } else if (argv[1].isNumber()) {
+ if (argc == 3)
+ scope.engine->throwError(QLatin1String("%1(): Stay argument, third argument can only be used if second argument is a locale").arg(functionName));
+ quint32 intFormat = argv[1].asDouble();
+ Qt::DateFormat format = Qt::DateFormat(intFormat);
+ formatted = formatDateTimeObjectUsingDateFormat(formatThis, format);
+ } else {
+ QLocale::FormatType formatOptions = QLocale::ShortFormat;
+ if (argc == 3) {
+ if (argv[2].isNumber())
+ formatOptions = QLocale::FormatType(quint32(argv[2].asDouble()));
+ else
+ scope.engine->throwError(QLatin1String("%1(): Third argument must be a Locale format option").arg(functionName));
+ }
+ auto enginePriv = QQmlEnginePrivate::get(scope.engine->qmlEngine());
+ auto localeMetaTypeId = qMetaTypeId<QLocale>();
+ QVariant locale = enginePriv->v4engine()->toVariant(argv[1], localeMetaTypeId);
+ if (!locale.canConvert(localeMetaTypeId))
+ scope.engine->throwError(QLatin1String("%1(): Bad second argument (must be either string, number or locale)").arg(functionName));
+ formatted = locale.value<QLocale>().toString(formatThis, formatOptions);
+ }
+ } else {
+ formatted = QLocale().toString(formatThis, QLocale::ShortFormat);
+ }
+
+ return Encode(scope.engine->newString(formatted));
+}
+
+}
+
/*!
-\qmlmethod string Qt::formatDate(datetime date, variant format)
+\qmlmethod string Qt::formatDate(datetime date, variant format, variant localeFormatOption)
-Returns a string representation of \a date, optionally formatted according
-to \a format.
+Returns a string representation of \a date, optionally formatted using \a format.
The \a date parameter may be a JavaScript \c Date object, a \l{date}{date}
-property, a QDate, or QDateTime value. The \a format parameter may be any of
-the possible format values as described for
+property, a QDate, or QDateTime value. The \a format and \a localeFormatOption
+parameter may be any of the possible format values as described for
\l{QtQml::Qt::formatDateTime()}{Qt.formatDateTime()}.
If \a format is not specified, \a date is formatted using
-\l {Qt::DefaultLocaleShortDate}{Qt.DefaultLocaleShortDate}.
+\l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} using the
+default locale.
\sa Locale
*/
ReturnedValue QtObject::method_formatDate(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
QV4::Scope scope(b);
- if (argc < 1 || argc > 2)
- THROW_GENERIC_ERROR("Qt.formatDate(): Invalid arguments");
+ if (argc < 1)
+ THROW_GENERIC_ERROR("Qt.formatDate(): Missing argument");
+ if (argc > 3)
+ THROW_GENERIC_ERROR("Qt.formatDate(): Stray arguments; formatDate takes at most 3 arguments.");
- Qt::DateFormat enumFormat = Qt::DefaultLocaleShortDate;
QDate date = scope.engine->toVariant(argv[0], -1).toDateTime().date();
- QString formattedDate;
- if (argc == 2) {
- QV4::ScopedString s(scope, argv[1]);
- if (s) {
- QString format = s->toQString();
- formattedDate = date.toString(format);
- } else if (argv[1].isNumber()) {
- quint32 intFormat = argv[1].asDouble();
- Qt::DateFormat format = Qt::DateFormat(intFormat);
- formattedDate = date.toString(format);
- } else {
- THROW_GENERIC_ERROR("Qt.formatDate(): Invalid date format");
- }
- } else {
- formattedDate = date.toString(enumFormat);
- }
-
- return Encode(scope.engine->newString(formattedDate));
+ return formatDateTimeObject(date, scope, QLatin1String("Qt.formatDate"), argc, argv);
}
/*!
-\qmlmethod string Qt::formatTime(datetime time, variant format)
+\qmlmethod string Qt::formatTime(datetime time, variant format, variant localeFormatOption)
-Returns a string representation of \a time, optionally formatted according to
-\a format.
+Returns a string representation of \a time, optionally formatted using
+\a format, and, if provided, \a localeFormatOption.
The \a time parameter may be a JavaScript \c Date object, a QTime, or QDateTime
-value. The \a format parameter may be any of the possible format values as
-described for \l{QtQml::Qt::formatDateTime()}{Qt.formatDateTime()}.
+value. The \a format and \a localeFormatOption parameter may be any of the
+possible format values as described for
+\l{QtQml::Qt::formatDateTime()}{Qt.formatDateTime()}.
If \a format is not specified, \a time is formatted using
-\l {Qt::DefaultLocaleShortDate}{Qt.DefaultLocaleShortDate}.
+\l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} using the default locale.
\sa Locale
*/
ReturnedValue QtObject::method_formatTime(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
QV4::Scope scope(b);
- if (argc < 1 || argc > 2)
- THROW_GENERIC_ERROR("Qt.formatTime(): Invalid arguments");
+ if (argc < 1)
+ THROW_GENERIC_ERROR("Qt.formatTime(): Missing argument");
+ if (argc > 3)
+ THROW_GENERIC_ERROR("Qt.formatTime(): Stray arguments; formatTime takes at most 3 arguments.");
QVariant argVariant = scope.engine->toVariant(argv[0], -1);
QTime time;
@@ -793,47 +850,34 @@ ReturnedValue QtObject::method_formatTime(const FunctionObject *b, const Value *
time = argVariant.toDateTime().time();
else // if (argVariant.type() == QVariant::Time), or invalid.
time = argVariant.toTime();
-
- Qt::DateFormat enumFormat = Qt::DefaultLocaleShortDate;
- QString formattedTime;
- if (argc == 2) {
- QV4::ScopedString s(scope, argv[1]);
- if (s) {
- QString format = s->toQString();
- formattedTime = time.toString(format);
- } else if (argv[1].isNumber()) {
- quint32 intFormat = argv[1].asDouble();
- Qt::DateFormat format = Qt::DateFormat(intFormat);
- formattedTime = time.toString(format);
- } else {
- THROW_GENERIC_ERROR("Qt.formatTime(): Invalid time format");
- }
- } else {
- formattedTime = time.toString(enumFormat);
- }
-
- return Encode(scope.engine->newString(formattedTime));
+ return formatDateTimeObject(time, scope, QLatin1String("Qt.formatTime"), argc, argv);
}
/*!
-\qmlmethod string Qt::formatDateTime(datetime dateTime, variant format)
+\qmlmethod string Qt::formatDateTime(datetime dateTime, variant format, variant localeFormatOption)
-Returns a string representation of \a dateTime, optionally formatted according to
+Returns a string representation of \a dateTime, optionally formatted using
\a format.
The \a dateTime parameter may be a JavaScript \c Date object, a \l{date}{date}
property, a QDate, QTime, or QDateTime value.
If \a format is not provided, \a dateTime is formatted using
-\l {Qt::DefaultLocaleShortDate}{Qt.DefaultLocaleShortDate}. Otherwise,
-\a format should be either:
+\l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} using the
+default locale. Otherwise, \a format should be either:
\list
\li One of the Qt::DateFormat enumeration values, such as
- \c Qt.DefaultLocaleShortDate or \c Qt.ISODate
+ \c Qt.RFC2822Date or \c Qt.ISODate.
\li A string that specifies the format of the returned string, as detailed below.
+\li A \c locale object.
\endlist
+If \a format specifies a locale object, \dateTime is formatted
+with \li{QLocale::toString}. In this case, localeFormatType can hold a value
+of type \l {QLocale::FormatType} to further tune the formatting. If none is
+provided, \l {QLocale::FormatType::ShortFormat}{Locale.ShortFormat} is used.
+
If \a format specifies a format string, it should use the following expressions
to specify the date:
@@ -910,29 +954,13 @@ with the \a format values below to produce the following results:
ReturnedValue QtObject::method_formatDateTime(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
QV4::Scope scope(b);
- if (argc < 1 || argc > 2)
- THROW_GENERIC_ERROR("Qt.formatDateTime(): Invalid arguments");
+ if (argc < 1)
+ THROW_GENERIC_ERROR("Qt.formatDateTime(): Missing argument");
+ if (argc > 3)
+ THROW_GENERIC_ERROR("Qt.formatDateTime(): Stray arguments; formatDate takes at most 3 arguments.");
- Qt::DateFormat enumFormat = Qt::DefaultLocaleShortDate;
QDateTime dt = scope.engine->toVariant(argv[0], -1).toDateTime();
- QString formattedDt;
- if (argc == 2) {
- QV4::ScopedString s(scope, argv[1]);
- if (s) {
- QString format = s->toQString();
- formattedDt = dt.toString(format);
- } else if (argv[1].isNumber()) {
- quint32 intFormat = argv[1].asDouble();
- Qt::DateFormat format = Qt::DateFormat(intFormat);
- formattedDt = dt.toString(format);
- } else {
- THROW_GENERIC_ERROR("Qt.formatDateTime(): Invalid datetime format");
- }
- } else {
- formattedDt = dt.toString(enumFormat);
- }
-
- return Encode(scope.engine->newString(formattedDt));
+ return formatDateTimeObject(dt, scope, QLatin1String("Qt.formatDateTime"), argc, argv);
}
/*!
diff --git a/src/quick/items/qquickanimatedimage.cpp b/src/quick/items/qquickanimatedimage.cpp
index 18d492cd68..192ed289e2 100644
--- a/src/quick/items/qquickanimatedimage.cpp
+++ b/src/quick/items/qquickanimatedimage.cpp
@@ -67,7 +67,7 @@ QQuickPixmap* QQuickAnimatedImagePrivate::infoForCurrentFrame(QQmlEngine *engine
.arg(current));
}
if (!requestedUrl.isEmpty()) {
- if (QQuickPixmap::isCached(requestedUrl, QSize(), 0, QQuickImageProviderOptions()))
+ if (QQuickPixmap::isCached(requestedUrl, QRect(), QSize(), 0, QQuickImageProviderOptions()))
pixmap = new QQuickPixmap(engine, requestedUrl);
else
pixmap = new QQuickPixmap(requestedUrl, movie->currentImage());
diff --git a/src/quick/items/qquickimage.cpp b/src/quick/items/qquickimage.cpp
index fccacdcce8..1882ec8997 100644
--- a/src/quick/items/qquickimage.cpp
+++ b/src/quick/items/qquickimage.cpp
@@ -427,10 +427,10 @@ qreal QQuickImage::paintedHeight() const
/*!
\qmlproperty QSize QtQuick::Image::sourceSize
- This property holds the actual width and height of the loaded image.
+ This property holds the scaled width and height of the full-frame image.
Unlike the \l {Item::}{width} and \l {Item::}{height} properties, which scale
- the painting of the image, this property sets the actual number of pixels
+ the painting of the image, this property sets the maximum number of pixels
stored for the loaded image so that large images do not use more
memory than necessary. For example, this ensures the image in memory is no
larger than 1024x1024 pixels, regardless of the Image's \l {Item::}{width} and
@@ -455,7 +455,7 @@ qreal QQuickImage::paintedHeight() const
other dimension is set in proportion to preserve the source image's aspect ratio.
(The \l fillMode is independent of this.)
- If both the sourceSize.width and sourceSize.height are set the image will be scaled
+ If both the sourceSize.width and sourceSize.height are set, the image will be scaled
down to fit within the specified size (unless PreserveAspectCrop or PreserveAspectFit
are used, then it will be scaled to match the optimal size for cropping/fitting),
maintaining the image's aspect ratio. The actual
@@ -470,6 +470,9 @@ qreal QQuickImage::paintedHeight() const
be no greater than this property specifies. For some formats (currently only JPEG),
the whole image will never actually be loaded into memory.
+ If the \l sourceClipRect property is also set, \c sourceSize determines the scale,
+ but it will be clipped to the size of the clip rectangle.
+
sourceSize can be cleared to the natural size of the image
by setting sourceSize to \c undefined.
@@ -478,6 +481,51 @@ qreal QQuickImage::paintedHeight() const
*/
/*!
+ \qmlproperty rect QtQuick::Image::sourceClipRect
+ \since 5.15
+
+ This property, if set, holds the rectangular region of the source image to
+ be loaded.
+
+ The \c sourceClipRect works together with the \l sourceSize property to
+ conserve system resources when only a portion of an image needs to be
+ loaded.
+
+ \code
+ Rectangle {
+ width: ...
+ height: ...
+
+ Image {
+ anchors.fill: parent
+ source: "reallyBigImage.svg"
+ sourceSize.width: 1024
+ sourceSize.height: 1024
+ sourceClipRect: Qt.rect(100, 100, 512, 512)
+ }
+ }
+ \endcode
+
+ In the above example, we conceptually scale the SVG graphic to 1024x1024
+ first, and then cut out a region of interest that is 512x512 pixels from a
+ location 100 pixels from the top and left edges. Thus \c sourceSize
+ determines the scale, but the actual output image is 512x512 pixels.
+
+ Some image formats are able to conserve CPU time by rendering only the
+ specified region. Others will need to load the entire image first and then
+ clip it to the specified region.
+
+ This property can be cleared to reload the entire image by setting
+ \c sourceClipRect to \c undefined.
+
+ \note \e {Changing this property dynamically causes the image source to be reloaded,
+ potentially even from the network, if it is not in the disk cache.}
+
+ \note Sub-pixel clipping is not supported: the given rectangle will be
+ passed to \l QImageReader::setScaledClipRect().
+*/
+
+/*!
\qmlproperty url QtQuick::Image::source
Image can handle any image format supported by Qt, loaded from any URL scheme supported by Qt.
diff --git a/src/quick/items/qquickimage_p.h b/src/quick/items/qquickimage_p.h
index f7e652cdcc..e22a94c5af 100644
--- a/src/quick/items/qquickimage_p.h
+++ b/src/quick/items/qquickimage_p.h
@@ -68,6 +68,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickImage : public QQuickImageBase
Q_PROPERTY(VAlignment verticalAlignment READ verticalAlignment WRITE setVerticalAlignment NOTIFY verticalAlignmentChanged)
Q_PROPERTY(bool mipmap READ mipmap WRITE setMipmap NOTIFY mipmapChanged REVISION 3)
Q_PROPERTY(bool autoTransform READ autoTransform WRITE setAutoTransform NOTIFY autoTransformChanged REVISION 5)
+ Q_PROPERTY(QRectF sourceClipRect READ sourceClipRect WRITE setSourceClipRect RESET resetSourceClipRect NOTIFY sourceClipRectChanged REVISION 15)
QML_NAMED_ELEMENT(Image)
public:
diff --git a/src/quick/items/qquickimagebase.cpp b/src/quick/items/qquickimagebase.cpp
index 55b29d7a70..2118f67387 100644
--- a/src/quick/items/qquickimagebase.cpp
+++ b/src/quick/items/qquickimagebase.cpp
@@ -167,6 +167,29 @@ void QQuickImageBase::resetSourceSize()
setSourceSize(QSize());
}
+QRectF QQuickImageBase::sourceClipRect() const
+{
+ Q_D(const QQuickImageBase);
+ return d->sourceClipRect;
+}
+
+void QQuickImageBase::setSourceClipRect(const QRectF &r)
+{
+ Q_D(QQuickImageBase);
+ if (d->sourceClipRect == r)
+ return;
+
+ d->sourceClipRect = r;
+ emit sourceClipRectChanged();
+ if (isComponentComplete())
+ load();
+}
+
+void QQuickImageBase::resetSourceClipRect()
+{
+ setSourceClipRect(QRect());
+}
+
bool QQuickImageBase::cache() const
{
Q_D(const QQuickImageBase);
@@ -295,6 +318,7 @@ void QQuickImageBase::loadPixmap(const QUrl &url, LoadPixmapOptions loadOptions)
d->pix.load(qmlEngine(this),
loadUrl,
+ d->sourceClipRect.toRect(),
(loadOptions & HandleDPR) ? d->sourcesize * d->devicePixelRatio : QSize(),
options,
(loadOptions & UseProviderOptions) ? d->providerOptions : QQuickImageProviderOptions(),
diff --git a/src/quick/items/qquickimagebase_p.h b/src/quick/items/qquickimagebase_p.h
index 9308230bfa..238b31b2e5 100644
--- a/src/quick/items/qquickimagebase_p.h
+++ b/src/quick/items/qquickimagebase_p.h
@@ -107,6 +107,10 @@ public:
QSize sourceSize() const;
void resetSourceSize();
+ QRectF sourceClipRect() const;
+ void setSourceClipRect(const QRectF &r);
+ void resetSourceClipRect();
+
virtual void setMirror(bool mirror);
bool mirror() const;
@@ -134,6 +138,7 @@ Q_SIGNALS:
void mirrorChanged();
Q_REVISION(14) void currentFrameChanged();
Q_REVISION(14) void frameCountChanged();
+ Q_REVISION(15) void sourceClipRectChanged();
protected:
void loadEmptyUrl();
diff --git a/src/quick/items/qquickimagebase_p_p.h b/src/quick/items/qquickimagebase_p_p.h
index 88e18ba32e..00a9295ef0 100644
--- a/src/quick/items/qquickimagebase_p_p.h
+++ b/src/quick/items/qquickimagebase_p_p.h
@@ -86,6 +86,7 @@ public:
QSize sourcesize;
QSize oldSourceSize;
qreal devicePixelRatio;
+ QRectF sourceClipRect;
QQuickImageProviderOptions providerOptions;
int currentFrame;
int frameCount;
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp
index 2a6337cdb3..f89e7d970f 100644
--- a/src/quick/items/qquicktext.cpp
+++ b/src/quick/items/qquicktext.cpp
@@ -1212,7 +1212,7 @@ void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal
if (!image->pix) {
QUrl url = q->baseUrl().resolved(image->url);
- image->pix = new QQuickPixmap(qmlEngine(q), url, image->size);
+ image->pix = new QQuickPixmap(qmlEngine(q), url, QRect(), image->size);
if (image->pix->isLoading()) {
image->pix->connectFinished(q, SLOT(imageDownloadFinished()));
if (!extra.isAllocated() || !extra->nbActiveDownloads)
diff --git a/src/quick/util/qquickpixmapcache.cpp b/src/quick/util/qquickpixmapcache.cpp
index d47719389b..bdd1e2c514 100644
--- a/src/quick/util/qquickpixmapcache.cpp
+++ b/src/quick/util/qquickpixmapcache.cpp
@@ -86,6 +86,8 @@ QT_BEGIN_NAMESPACE
const QLatin1String QQuickPixmap::itemGrabberScheme = QLatin1String("itemgrabber");
+Q_LOGGING_CATEGORY(lcImg, "qt.quick.image")
+
#ifndef QT_NO_DEBUG
static const bool qsg_leak_check = !qEnvironmentVariableIsEmpty("QML_LEAK_CHECK");
#endif
@@ -137,6 +139,7 @@ public:
QQuickPixmapData *data;
QQmlEngine *engineForReader; // always access reader inside readerMutex
+ QRect requestRegion;
QSize requestSize;
QUrl url;
@@ -240,9 +243,10 @@ public:
class QQuickPixmapData
{
public:
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &s, const QQuickImageProviderOptions &po, const QString &e)
+ QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &rs,
+ const QQuickImageProviderOptions &po, const QString &e)
: refCount(1), frameCount(1), frame(0), inCache(false), pixmapStatus(QQuickPixmap::Error),
- url(u), errorString(e), requestSize(s),
+ url(u), errorString(e), requestRegion(r), requestSize(rs),
providerOptions(po), appliedTransform(QQuickImageProviderOptions::UsePluginDefaultTransform),
textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr),
prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
@@ -250,9 +254,10 @@ public:
declarativePixmaps.insert(pixmap);
}
- QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QSize &r, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
+ QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, const QRect &r, const QSize &s, const QQuickImageProviderOptions &po,
+ QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
: refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Loading),
- url(u), requestSize(r),
+ url(u), requestRegion(r), requestSize(s),
providerOptions(po), appliedTransform(aTransform),
textureFactory(nullptr), reply(nullptr), prevUnreferenced(nullptr), prevUnreferencedPtr(nullptr),
nextUnreferenced(nullptr)
@@ -261,9 +266,10 @@ public:
}
QQuickPixmapData(QQuickPixmap *pixmap, const QUrl &u, QQuickTextureFactory *texture,
- const QSize &s, const QSize &r, const QQuickImageProviderOptions &po, QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
+ const QSize &s, const QRect &r, const QSize &rs, const QQuickImageProviderOptions &po,
+ QQuickImageProviderOptions::AutoTransform aTransform, int frame=0, int frameCount=1)
: refCount(1), frameCount(frameCount), frame(frame), inCache(false), pixmapStatus(QQuickPixmap::Ready),
- url(u), implicitSize(s), requestSize(r),
+ url(u), implicitSize(s), requestRegion(r), requestSize(rs),
providerOptions(po), appliedTransform(aTransform),
textureFactory(texture), reply(nullptr), prevUnreferenced(nullptr),
prevUnreferencedPtr(nullptr), nextUnreferenced(nullptr)
@@ -308,6 +314,7 @@ public:
QUrl url;
QString errorString;
QSize implicitSize;
+ QRect requestRegion;
QSize requestSize;
QQuickImageProviderOptions providerOptions;
QQuickImageProviderOptions::AutoTransform appliedTransform;
@@ -399,7 +406,7 @@ static void maybeRemoveAlpha(QImage *image)
}
static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *errorString, QSize *impsize, int *frameCount,
- const QSize &requestSize, const QQuickImageProviderOptions &providerOptions,
+ const QRect &requestRegion, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions,
QQuickImageProviderOptions::AutoTransform *appliedTransform = nullptr, int frame = 0)
{
QImageReader imgio(dev);
@@ -417,6 +424,10 @@ static bool readImage(const QUrl& url, QIODevice *dev, QImage *image, QString *e
QSize scSize = QQuickImageProviderWithOptions::loadSize(imgio.size(), requestSize, imgio.format(), providerOptions);
if (scSize.isValid())
imgio.setScaledSize(scSize);
+ if (!requestRegion.isNull())
+ imgio.setScaledClipRect(requestRegion);
+ qCDebug(lcImg) << url << "frame" << frame << "of" << imgio.imageCount()
+ << "requestRegion" << requestRegion << "QImageReader size" << imgio.size() << "-> scSize" << scSize;
if (impsize)
*impsize = imgio.size();
@@ -568,7 +579,8 @@ void QQuickPixmapReader::networkRequestDone(QNetworkReply *reply)
buff.open(QIODevice::ReadOnly);
int frameCount;
int const frame = job->data ? job->data->frame : 0;
- if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, &frameCount, job->requestSize, job->providerOptions, nullptr, frame))
+ if (!readImage(reply->url(), &buff, &image, &errorString, &readSize, &frameCount,
+ job->requestRegion, job->requestSize, job->providerOptions, nullptr, frame))
error = QQuickPixmapReply::Decoding;
else if (job->data)
job->data->frameCount = frameCount;
@@ -884,7 +896,8 @@ void QQuickPixmapReader::processJob(QQuickPixmapReply *runningJob, const QUrl &u
} else {
int frameCount;
int const frame = runningJob->data ? runningJob->data->frame : 0;
- if ( !readImage(url, &f, &image, &errorStr, &readSize, &frameCount, runningJob->requestSize, runningJob->providerOptions, nullptr, frame)) {
+ if (!readImage(url, &f, &image, &errorStr, &readSize, &frameCount, runningJob->requestRegion, runningJob->requestSize,
+ runningJob->providerOptions, nullptr, frame)) {
errorCode = QQuickPixmapReply::Loading;
if (f.fileName() != localFile)
errorStr += QString::fromLatin1(" (%1)").arg(f.fileName());
@@ -997,6 +1010,7 @@ class QQuickPixmapKey
{
public:
const QUrl *url;
+ const QRect *region;
const QSize *size;
int frame;
QQuickImageProviderOptions options;
@@ -1004,12 +1018,17 @@ public:
inline bool operator==(const QQuickPixmapKey &lhs, const QQuickPixmapKey &rhs)
{
- return *lhs.size == *rhs.size && *lhs.url == *rhs.url && lhs.options == rhs.options && lhs.frame == rhs.frame;
+ return *lhs.region == *rhs.region && *lhs.size == *rhs.size && *lhs.url == *rhs.url &&
+ lhs.options == rhs.options && lhs.frame == rhs.frame;
}
inline uint qHash(const QQuickPixmapKey &key)
{
- return qHash(*key.url) ^ (key.size->width()*7) ^ (key.size->height()*17) ^ (key.frame*23) ^ (key.options.autoTransform() * 0x5c5c5c5c);
+ return qHash(*key.url) ^ (key.size->width()*7) ^ (key.size->height()*17) ^ (key.frame*23) ^
+ (key.region->x()*29) ^ (key.region->y()*31) ^ (key.options.autoTransform() * 0x5c5c5c5c);
+ // key.region.width() and height() are not included, because the hash function should be simple,
+ // and they are more likely to be held constant for some batches of images
+ // (e.g. tiles, or repeatedly cropping to the same viewport at different positions).
}
class QQuickPixmapStore : public QObject
@@ -1176,7 +1195,8 @@ void QQuickPixmap::purgeCache()
}
QQuickPixmapReply::QQuickPixmapReply(QQuickPixmapData *d)
-: data(d), engineForReader(nullptr), requestSize(d->requestSize), url(d->url), loading(false), providerOptions(d->providerOptions), redirectCount(0)
+ : data(d), engineForReader(nullptr), requestRegion(d->requestRegion), requestSize(d->requestSize),
+ url(d->url), loading(false), providerOptions(d->providerOptions), redirectCount(0)
{
if (finishedIndex == -1) {
finishedIndex = QMetaMethod::fromSignal(&QQuickPixmapReply::finished).methodIndex();
@@ -1271,7 +1291,7 @@ void QQuickPixmapData::release()
void QQuickPixmapData::addToCache()
{
if (!inCache) {
- QQuickPixmapKey key = { &url, &requestSize, frame, providerOptions };
+ QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
pixmapStore()->m_cache.insert(key, this);
inCache = true;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
@@ -1282,7 +1302,7 @@ void QQuickPixmapData::addToCache()
void QQuickPixmapData::removeFromCache()
{
if (inCache) {
- QQuickPixmapKey key = { &url, &requestSize, frame, providerOptions };
+ QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
pixmapStore()->m_cache.remove(key);
inCache = false;
PIXMAP_PROFILE(pixmapCountChanged<QQuickProfiler::PixmapCacheCountChanged>(
@@ -1290,7 +1310,9 @@ void QQuickPixmapData::removeFromCache()
}
}
-static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url, const QSize &requestSize, const QQuickImageProviderOptions &providerOptions, int frame, bool *ok)
+static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, QQmlEngine *engine, const QUrl &url,
+ const QRect &requestRegion, const QSize &requestSize,
+ const QQuickImageProviderOptions &providerOptions, int frame, bool *ok)
{
if (url.scheme() == QLatin1String("image")) {
QSize readSize;
@@ -1305,7 +1327,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
switch (imageType) {
case QQuickImageProvider::Invalid:
- return new QQuickPixmapData(declarativePixmap, url, requestSize, providerOptions,
+ return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
QQuickPixmap::tr("Invalid image provider: %1").arg(url.toString()));
case QQuickImageProvider::Texture:
{
@@ -1313,7 +1335,8 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestTexture(imageId(url), &readSize, requestSize);
if (texture) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
+ return new QQuickPixmapData(declarativePixmap, url, texture, readSize, requestRegion, requestSize,
+ providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
break;
}
@@ -1324,7 +1347,9 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestImage(imageId(url), &readSize, requestSize);
if (!image.isNull()) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
+ return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image),
+ readSize, requestRegion, requestSize, providerOptions,
+ QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
break;
}
@@ -1334,7 +1359,9 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
: provider->requestPixmap(imageId(url), &readSize, requestSize);
if (!pixmap.isNull()) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()), readSize, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
+ return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(pixmap.toImage()),
+ readSize, requestRegion, requestSize, providerOptions,
+ QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
}
break;
}
@@ -1346,7 +1373,7 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
}
// provider has bad image type, or provider returned null image
- return new QQuickPixmapData(declarativePixmap, url, requestSize, providerOptions,
+ return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions,
QQuickPixmap::tr("Failed to get image from provider: %1").arg(url.toString()));
}
@@ -1364,7 +1391,8 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
QQuickTextureFactory *factory = texReader.read();
if (factory) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
+ return new QQuickPixmapData(declarativePixmap, url, factory, factory->textureSize(), requestRegion, requestSize,
+ providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame);
} else {
errorString = QQuickPixmap::tr("Error decoding: %1").arg(url.toString());
if (f.fileName() != localFile)
@@ -1374,9 +1402,10 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
QImage image;
QQuickImageProviderOptions::AutoTransform appliedTransform = providerOptions.autoTransform();
int frameCount;
- if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestSize, providerOptions, &appliedTransform, frame)) {
+ if (readImage(url, &f, &image, &errorString, &readSize, &frameCount, requestRegion, requestSize, providerOptions, &appliedTransform, frame)) {
*ok = true;
- return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestSize, providerOptions, appliedTransform, frame, frameCount);
+ return new QQuickPixmapData(declarativePixmap, url, QQuickTextureFactory::textureFactoryForImage(image), readSize, requestRegion, requestSize,
+ providerOptions, appliedTransform, frame, frameCount);
} else if (f.fileName() != localFile) {
errorString += QString::fromLatin1(" (%1)").arg(f.fileName());
}
@@ -1384,12 +1413,13 @@ static QQuickPixmapData* createPixmapDataSync(QQuickPixmap *declarativePixmap, Q
} else {
errorString = QQuickPixmap::tr("Cannot open: %1").arg(url.toString());
}
- return new QQuickPixmapData(declarativePixmap, url, requestSize, providerOptions, errorString);
+ return new QQuickPixmapData(declarativePixmap, url, requestRegion, requestSize, providerOptions, errorString);
}
struct QQuickPixmapNull {
QUrl url;
+ QRect region;
QSize size;
};
Q_GLOBAL_STATIC(QQuickPixmapNull, nullPixmap);
@@ -1405,15 +1435,16 @@ QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url)
load(engine, url);
}
-QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QSize &size)
+QQuickPixmap::QQuickPixmap(QQmlEngine *engine, const QUrl &url, const QRect &region, const QSize &size)
: d(nullptr)
{
- load(engine, url, size);
+ load(engine, url, region, size);
}
QQuickPixmap::QQuickPixmap(const QUrl &url, const QImage &image)
{
- d = new QQuickPixmapData(this, url, new QQuickDefaultTextureFactory(image), image.size(), QSize(), QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
+ d = new QQuickPixmapData(this, url, new QQuickDefaultTextureFactory(image), image.size(), QRect(), QSize(),
+ QQuickImageProviderOptions(), QQuickImageProviderOptions::UsePluginDefaultTransform);
d->addToCache();
}
@@ -1486,6 +1517,14 @@ const QSize &QQuickPixmap::requestSize() const
return nullPixmap()->size;
}
+const QRect &QQuickPixmap::requestRegion() const
+{
+ if (d)
+ return d->requestRegion;
+ else
+ return nullPixmap()->region;
+}
+
QQuickImageProviderOptions::AutoTransform QQuickPixmap::autoTransform() const
{
if (d)
@@ -1561,25 +1600,26 @@ QRect QQuickPixmap::rect() const
void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url)
{
- load(engine, url, QSize(), QQuickPixmap::Cache);
+ load(engine, url, QRect(), QSize(), QQuickPixmap::Cache);
}
void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, QQuickPixmap::Options options)
{
- load(engine, url, QSize(), options);
+ load(engine, url, QRect(), QSize(), options);
}
-void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &size)
+void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize)
{
- load(engine, url, size, QQuickPixmap::Cache);
+ load(engine, url, requestRegion, requestSize, QQuickPixmap::Cache);
}
-void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options)
+void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options)
{
- load(engine, url, requestSize, options, QQuickImageProviderOptions());
+ load(engine, url, requestRegion, requestSize, options, QQuickImageProviderOptions());
}
-void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &requestSize, QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
+void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
+ QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame, int frameCount)
{
if (d) {
d->declarativePixmaps.remove(this);
@@ -1587,7 +1627,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques
d = nullptr;
}
- QQuickPixmapKey key = { &url, &requestSize, frame, providerOptions };
+ QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, providerOptions };
QQuickPixmapStore *store = pixmapStore();
QHash<QQuickPixmapKey, QQuickPixmapData *>::Iterator iter = store->m_cache.end();
@@ -1596,10 +1636,11 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques
// cached version. Unless it's an itemgrabber url, since the cache is used to pass
// the result between QQuickItemGrabResult and QQuickImage.
if (url.scheme() == itemGrabberScheme) {
- QSize dummy;
- if (requestSize != dummy)
+ QRect dummyRegion;
+ QSize dummySize;
+ if (requestSize != dummySize)
qWarning() << "Ignoring sourceSize request for image url that came from grabToImage. Use the targetSize parameter of the grabToImage() function instead.";
- const QQuickPixmapKey grabberKey = { &url, &dummy, 0, QQuickImageProviderOptions() };
+ const QQuickPixmapKey grabberKey = { &url, &dummyRegion, &dummySize, 0, QQuickImageProviderOptions() };
iter = store->m_cache.find(grabberKey);
} else if (options & QQuickPixmap::Cache)
iter = store->m_cache.find(key);
@@ -1621,7 +1662,7 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques
if (!(options & QQuickPixmap::Asynchronous)) {
bool ok = false;
PIXMAP_PROFILE(pixmapStateChanged<QQuickProfiler::PixmapLoadingStarted>(url));
- d = createPixmapDataSync(this, engine, url, requestSize, providerOptions, frame, &ok);
+ d = createPixmapDataSync(this, engine, url, requestRegion, requestSize, providerOptions, frame, &ok);
if (ok) {
PIXMAP_PROFILE(pixmapLoadingFinished(url, QSize(width(), height())));
if (options & QQuickPixmap::Cache)
@@ -1638,7 +1679,8 @@ void QQuickPixmap::load(QQmlEngine *engine, const QUrl &url, const QSize &reques
return;
- d = new QQuickPixmapData(this, url, requestSize, providerOptions, QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
+ d = new QQuickPixmapData(this, url, requestRegion, requestSize, providerOptions,
+ QQuickImageProviderOptions::UsePluginDefaultTransform, frame, frameCount);
if (options & QQuickPixmap::Cache)
d->addToCache();
@@ -1672,9 +1714,10 @@ void QQuickPixmap::clear(QObject *obj)
}
}
-bool QQuickPixmap::isCached(const QUrl &url, const QSize &requestSize, const int frame, const QQuickImageProviderOptions &options)
+bool QQuickPixmap::isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
+ const int frame, const QQuickImageProviderOptions &options)
{
- QQuickPixmapKey key = { &url, &requestSize, frame, options };
+ QQuickPixmapKey key = { &url, &requestRegion, &requestSize, frame, options };
QQuickPixmapStore *store = pixmapStore();
return store->m_cache.contains(key);
diff --git a/src/quick/util/qquickpixmapcache_p.h b/src/quick/util/qquickpixmapcache_p.h
index c4f4d1a101..65d102af71 100644
--- a/src/quick/util/qquickpixmapcache_p.h
+++ b/src/quick/util/qquickpixmapcache_p.h
@@ -127,7 +127,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickPixmap
public:
QQuickPixmap();
QQuickPixmap(QQmlEngine *, const QUrl &);
- QQuickPixmap(QQmlEngine *, const QUrl &, const QSize &);
+ QQuickPixmap(QQmlEngine *, const QUrl &, const QRect &region, const QSize &);
QQuickPixmap(const QUrl &, const QImage &image);
~QQuickPixmap();
@@ -148,6 +148,7 @@ public:
QString error() const;
const QUrl &url() const;
const QSize &implicitSize() const;
+ const QRect &requestRegion() const;
const QSize &requestSize() const;
QQuickImageProviderOptions::AutoTransform autoTransform() const;
int frameCount() const;
@@ -163,9 +164,10 @@ public:
void load(QQmlEngine *, const QUrl &);
void load(QQmlEngine *, const QUrl &, QQuickPixmap::Options options);
- void load(QQmlEngine *, const QUrl &, const QSize &);
- void load(QQmlEngine *, const QUrl &, const QSize &, QQuickPixmap::Options options);
- void load(QQmlEngine *, const QUrl &, const QSize &, QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame = 0, int frameCount = 1);
+ void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize);
+ void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize, QQuickPixmap::Options options);
+ void load(QQmlEngine *, const QUrl &, const QRect &requestRegion, const QSize &requestSize,
+ QQuickPixmap::Options options, const QQuickImageProviderOptions &providerOptions, int frame = 0, int frameCount = 1);
void clear();
void clear(QObject *);
@@ -176,7 +178,8 @@ public:
bool connectDownloadProgress(QObject *, int);
static void purgeCache();
- static bool isCached(const QUrl &url, const QSize &requestSize, const int frame, const QQuickImageProviderOptions &options);
+ static bool isCached(const QUrl &url, const QRect &requestRegion, const QSize &requestSize,
+ const int frame, const QQuickImageProviderOptions &options);
static const QLatin1String itemGrabberScheme;
diff --git a/src/quick/util/qquickstyledtext.cpp b/src/quick/util/qquickstyledtext.cpp
index 08d06c66ab..660852ba83 100644
--- a/src/quick/util/qquickstyledtext.cpp
+++ b/src/quick/util/qquickstyledtext.cpp
@@ -706,7 +706,7 @@ void QQuickStyledTextPrivate::parseImageAttributes(const QChar *&ch, const QStri
// to avoid a relayout later on.
QUrl url = baseUrl.resolved(image->url);
if (url.isLocalFile()) {
- image->pix = new QQuickPixmap(context->engine(), url, image->size);
+ image->pix = new QQuickPixmap(context->engine(), url, QRect(), image->size);
if (image->pix && image->pix->isReady()) {
image->size = image->pix->implicitSize();
} else {
diff --git a/tests/auto/qml/qmlformat/data/Example1.formatted.nosort.qml b/tests/auto/qml/qmlformat/data/Example1.formatted.nosort.qml
index 080cec438e..34d58cf571 100644
--- a/tests/auto/qml/qmlformat/data/Example1.formatted.nosort.qml
+++ b/tests/auto/qml/qmlformat/data/Example1.formatted.nosort.qml
@@ -91,11 +91,11 @@ Item {
x = 100;
break;
}
- if (x == 50)
+ if (x == 50)
console.log("true");
- else if (x == 50)
+ else if (x == 50)
console.log("other thing");
- else
+ else
console.log("false");
if (x == 50) {
@@ -132,6 +132,7 @@ Item {
Rectangle {
}
]
+
Text {
required property string batman
@@ -143,6 +144,7 @@ Item {
// This comment is related to the property animation
PropertyAnimation on x {
id: foo
+
x: 3
y: x + 3
}
diff --git a/tests/auto/qml/qmlformat/data/Example1.formatted.qml b/tests/auto/qml/qmlformat/data/Example1.formatted.qml
index 4b65b9add6..b06734eb0b 100644
--- a/tests/auto/qml/qmlformat/data/Example1.formatted.qml
+++ b/tests/auto/qml/qmlformat/data/Example1.formatted.qml
@@ -91,11 +91,11 @@ Item {
x = 100;
break;
}
- if (x == 50)
+ if (x == 50)
console.log("true");
- else if (x == 50)
+ else if (x == 50)
console.log("other thing");
- else
+ else
console.log("false");
if (x == 50) {
@@ -132,6 +132,7 @@ Item {
Rectangle {
}
]
+
Text {
required property string batman
@@ -143,6 +144,7 @@ Item {
// This comment is related to the property animation
PropertyAnimation on x {
id: foo
+
x: 3
y: x + 3
}
diff --git a/tests/auto/qml/qmlformat/data/readOnlyProps.formatted.qml b/tests/auto/qml/qmlformat/data/readOnlyProps.formatted.qml
new file mode 100644
index 0000000000..6e7cc31dcf
--- /dev/null
+++ b/tests/auto/qml/qmlformat/data/readOnlyProps.formatted.qml
@@ -0,0 +1,29 @@
+import QtQuick 2.0
+
+QtObject {
+ // Testing UiObjectBinding
+ readonly property Item
+ item: Item {
+ id: test
+
+ signal foo()
+ }
+ // End comment
+
+ // Testing UiArrayBinding
+ readonly property list<Item> array: [
+ Item {
+ id: test1
+
+ signal foo()
+ },
+ Item {
+ id: test2
+
+ signal bar()
+ }
+ ]
+ // Testing UiScriptBinding
+ readonly property int script: Math.sin(Math.PI)
+ property bool normalProperty: true
+}
diff --git a/tests/auto/qml/qmlformat/data/readOnlyProps.qml b/tests/auto/qml/qmlformat/data/readOnlyProps.qml
new file mode 100644
index 0000000000..8a32dd131e
--- /dev/null
+++ b/tests/auto/qml/qmlformat/data/readOnlyProps.qml
@@ -0,0 +1,15 @@
+import QtQuick 2.0
+
+QtObject {
+ // Testing UiObjectBinding
+ readonly property Item item: Item { id: test; signal foo() }
+ // End comment
+
+ // Testing UiArrayBinding
+ readonly property list<Item> array: [ Item { id: test1; signal foo() }, Item { id: test2; signal bar() } ]
+
+ // Testing UiScriptBinding
+ readonly property int script: Math.sin(Math.PI)
+
+ property bool normalProperty: true
+}
diff --git a/tests/auto/qml/qmlformat/tst_qmlformat.cpp b/tests/auto/qml/qmlformat/tst_qmlformat.cpp
index 95c8e88f21..3201fa5ace 100644
--- a/tests/auto/qml/qmlformat/tst_qmlformat.cpp
+++ b/tests/auto/qml/qmlformat/tst_qmlformat.cpp
@@ -42,6 +42,7 @@ private Q_SLOTS:
void testFormat();
void testFormatNoSort();
+ void testReadOnlyProps();
#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void testExample();
@@ -120,6 +121,11 @@ void TestQmlformat::initTestCase()
m_invalidFiles << "tests/auto/qml/qqmllanguage/data/nullishCoalescing_RHS_Or.qml";
m_invalidFiles << "tests/auto/qml/qqmllanguage/data/typeAnnotations.2.qml";
m_invalidFiles << "tests/auto/qml/qqmlparser/data/disallowedtypeannotations/qmlnestedfunction.qml";
+
+ // These files rely on exact formatting
+ m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon1.qml";
+ m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon_error1.qml";
+ m_invalidFiles << "tests/auto/qml/qqmlecmascript/data/incrDecrSemicolon2.qml";
}
QStringList TestQmlformat::findFiles(const QDir &d)
@@ -178,6 +184,11 @@ void TestQmlformat::testFormatNoSort()
QCOMPARE(runQmlformat(testFile("Example1.qml"), false, true), readTestFile("Example1.formatted.nosort.qml"));
}
+void TestQmlformat::testReadOnlyProps()
+{
+ QCOMPARE(runQmlformat(testFile("readOnlyProps.qml"), false, true), readTestFile("readOnlyProps.formatted.qml"));
+}
+
#if !defined(QTEST_CROSS_COMPILED) // sources not available when cross compiled
void TestQmlformat::testExample_data()
{
diff --git a/tests/auto/qml/qqmlqt/data/formattingLocale.qml b/tests/auto/qml/qqmlqt/data/formattingLocale.qml
new file mode 100644
index 0000000000..9da349b101
--- /dev/null
+++ b/tests/auto/qml/qqmlqt/data/formattingLocale.qml
@@ -0,0 +1,12 @@
+import QtQml 2.15
+
+QtObject {
+ required property var myDateTime
+ required property var myDate
+ property var myTime
+
+ property string dateTimeString: Qt.formatDateTime(myDateTime, Qt.locale("de_DE"), Locale.NarrowFormat)
+ property string dateString: Qt.formatDate(myDate, Qt.locale("de_DE"))
+
+ function invalidUsage() { Qt.formatTime(myTime, null, "hello") }
+}
diff --git a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
index 8fa18c9860..4a2e6841e7 100644
--- a/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
+++ b/tests/auto/qml/qqmlqt/tst_qqmlqt.cpp
@@ -91,6 +91,7 @@ private slots:
void dateTimeFormatting_data();
void dateTimeFormattingVariants();
void dateTimeFormattingVariants_data();
+ void dateTimeFormattingWithLocale();
void isQtObject();
void btoa();
void atob();
@@ -738,12 +739,12 @@ void tst_qqmlqt::dateTimeFormatting()
QQmlComponent component(&eng, testFileUrl("formatting.qml"));
QStringList warnings;
- warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Invalid date format"
- << component.url().toString() + ":36: Error: Qt.formatDate(): Invalid arguments"
- << component.url().toString() + ":40: Error: Qt.formatTime(): Invalid time format"
- << component.url().toString() + ":39: Error: Qt.formatTime(): Invalid arguments"
- << component.url().toString() + ":43: Error: Qt.formatDateTime(): Invalid datetime format"
- << component.url().toString() + ":42: Error: Qt.formatDateTime(): Invalid arguments";
+ warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Bad second argument (must be either string, number or locale)"
+ << component.url().toString() + ":36: Error: Qt.formatDate(): Missing argument"
+ << component.url().toString() + ":40: Error: Qt.formatTime(): Bad second argument (must be either string, number or locale)"
+ << component.url().toString() + ":39: Error: Qt.formatTime(): Missing argument"
+ << component.url().toString() + ":43: Error: Qt.formatDateTime(): Bad second argument (must be either string, number or locale)"
+ << component.url().toString() + ":42: Error: Qt.formatDateTime(): Missing argument";
foreach (const QString &warning, warnings)
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
@@ -773,6 +774,8 @@ void tst_qqmlqt::dateTimeFormatting()
void tst_qqmlqt::dateTimeFormatting_data()
{
+ QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
+ // Test intentionally uses deprecated enumerators from Qt::DateFormat
QTest::addColumn<QString>("method");
QTest::addColumn<QStringList>("inputProperties");
QTest::addColumn<QStringList>("expectedResults");
@@ -802,6 +805,7 @@ void tst_qqmlqt::dateTimeFormatting_data()
<< (QStringList() << dateTime.toString(Qt::DefaultLocaleShortDate)
<< dateTime.toString(Qt::DefaultLocaleLongDate)
<< dateTime.toString("M/d/yy H:m:s a"));
+ QT_WARNING_POP
}
void tst_qqmlqt::dateTimeFormattingVariants()
@@ -814,12 +818,12 @@ void tst_qqmlqt::dateTimeFormattingVariants()
QQmlComponent component(&eng, testFileUrl("formatting.qml"));
QStringList warnings;
- warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Invalid date format"
- << component.url().toString() + ":36: Error: Qt.formatDate(): Invalid arguments"
- << component.url().toString() + ":40: Error: Qt.formatTime(): Invalid time format"
- << component.url().toString() + ":39: Error: Qt.formatTime(): Invalid arguments"
- << component.url().toString() + ":43: Error: Qt.formatDateTime(): Invalid datetime format"
- << component.url().toString() + ":42: Error: Qt.formatDateTime(): Invalid arguments";
+ warnings << component.url().toString() + ":37: Error: Qt.formatDate(): Bad second argument (must be either string, number or locale)"
+ << component.url().toString() + ":36: Error: Qt.formatDate(): Missing argument"
+ << component.url().toString() + ":40: Error: Qt.formatTime(): Bad second argument (must be either string, number or locale)"
+ << component.url().toString() + ":39: Error: Qt.formatTime(): Missing argument"
+ << component.url().toString() + ":43: Error: Qt.formatDateTime(): Bad second argument (must be either string, number or locale)"
+ << component.url().toString() + ":42: Error: Qt.formatDateTime(): Missing argument";
foreach (const QString &warning, warnings)
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
@@ -840,6 +844,8 @@ void tst_qqmlqt::dateTimeFormattingVariants()
void tst_qqmlqt::dateTimeFormattingVariants_data()
{
+ QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED
+ // Test intentionally uses deprecated enumerators from Qt::DateFormat
QTest::addColumn<QString>("method");
QTest::addColumn<QVariant>("variant");
QTest::addColumn<QStringList>("expectedResults");
@@ -880,6 +886,28 @@ void tst_qqmlqt::dateTimeFormattingVariants_data()
QTest::newRow("formatDate, int") << "formatDate" << QVariant::fromValue(integer) << (QStringList() << temporary.date().toString(Qt::DefaultLocaleShortDate) << temporary.date().toString(Qt::DefaultLocaleLongDate) << temporary.date().toString("ddd MMMM d yy"));
QTest::newRow("formatDateTime, int") << "formatDateTime" << QVariant::fromValue(integer) << (QStringList() << temporary.toString(Qt::DefaultLocaleShortDate) << temporary.toString(Qt::DefaultLocaleLongDate) << temporary.toString("M/d/yy H:m:s a"));
QTest::newRow("formatTime, int") << "formatTime" << QVariant::fromValue(integer) << (QStringList() << temporary.time().toString(Qt::DefaultLocaleShortDate) << temporary.time().toString(Qt::DefaultLocaleLongDate) << temporary.time().toString("H:m:s a") << temporary.time().toString("hh:mm:ss.zzz"));
+ QT_WARNING_POP
+}
+
+void tst_qqmlqt::dateTimeFormattingWithLocale()
+{
+ QQmlEngine engine;
+ auto url = testFileUrl("formattingLocale.qml");
+ QQmlComponent comp(&engine, url);
+ QDateTime dateTime = QDateTime::fromString("M1d1y9800:01:02",
+ "'M'M'd'd'y'yyhh:mm:ss");
+ QDate date(1995, 5, 17);
+ QScopedPointer<QObject> o(comp.createWithInitialProperties({ {"myDateTime", dateTime}, {"myDate", date} }));
+ QVERIFY(!o.isNull());
+
+ auto dateTimeString = o->property("dateTimeString").toString();
+ QCOMPARE(dateTimeString, QLocale("de_DE").toString(dateTime, QLocale::NarrowFormat));
+ auto dateString = o->property("dateString").toString();
+ QCOMPARE(dateString, QLocale("de_DE").toString(date, QLocale::ShortFormat));
+
+ QString warningMsg = url.toString() + QLatin1String(":11: Error: Qt.formatTime(): Third argument must be a Locale format option");
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, warningMsg.toUtf8().constData());
+ QMetaObject::invokeMethod(o.get(), "invalidUsage");
}
void tst_qqmlqt::isQtObject()
diff --git a/tests/manual/tableview/imagetiling/imageTiling.qml b/tests/manual/tableview/imagetiling/imageTiling.qml
new file mode 100644
index 0000000000..5c9cdb9888
--- /dev/null
+++ b/tests/manual/tableview/imagetiling/imageTiling.qml
@@ -0,0 +1,90 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the manual tests of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+import QtQuick 2.15
+import Qt.labs.qmlmodels 1.0
+
+TableView {
+ id: root
+ width: 1024
+ height: 200
+ property int tileSize: 1024 / 8
+ rowSpacing: 1; columnSpacing: 1
+ model: TableModel {
+ TableModelColumn {}
+ TableModelColumn {}
+ TableModelColumn {}
+ TableModelColumn {}
+ TableModelColumn {}
+ TableModelColumn {}
+ TableModelColumn {}
+ TableModelColumn {}
+ columns: [0, 1, 2, 3, 4, 5, 6, 7]
+ rows: [0, 1, 2, 3]
+ }
+ delegate: Image {
+ id: image
+// source: "map.png" // unscaled because png doesn't support QImageIOHandler::ScaledClipRect
+// source: "map.svgz" // tiles offset incorrectly: see QTBUG-81044
+ source: "map.pdf" // ok if the qtpdf plugin is installed
+ fillMode: Image.Pad
+ sourceSize.width: 1024
+ sourceClipRect: Qt.rect(model.column * tileSize, model.row * tileSize, tileSize, tileSize)
+ cache: true
+ asynchronous: true
+ Text {
+ text: image.sourceClipRect.width + "x" + image.sourceClipRect.height +
+ " " + image.sourceClipRect.x + "," + image.sourceClipRect.y +
+ "\nfrom " + image.sourceSize.toString() + // inconsistency: if we don't set sourceSize, it ends up being sourceClipRect.size
+ "\nimplicit " + image.implicitWidth + "x" + image.implicitHeight
+ }
+ }
+ columnWidthProvider: function (c) { return tileSize } // workaround for QTBUG-81045
+ rowHeightProvider: function (r) { return tileSize }
+}
diff --git a/tests/manual/tableview/imagetiling/map.pdf b/tests/manual/tableview/imagetiling/map.pdf
new file mode 100644
index 0000000000..31e7b5225e
--- /dev/null
+++ b/tests/manual/tableview/imagetiling/map.pdf
Binary files differ
diff --git a/tests/manual/tableview/imagetiling/map.png b/tests/manual/tableview/imagetiling/map.png
new file mode 100644
index 0000000000..23a8342818
--- /dev/null
+++ b/tests/manual/tableview/imagetiling/map.png
Binary files differ
diff --git a/tests/manual/tableview/imagetiling/map.svgz b/tests/manual/tableview/imagetiling/map.svgz
new file mode 100644
index 0000000000..64d509c106
--- /dev/null
+++ b/tests/manual/tableview/imagetiling/map.svgz
Binary files differ
diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp
index 8264a6099e..4dd241ff93 100644
--- a/tools/qmlformat/commentastvisitor.cpp
+++ b/tools/qmlformat/commentastvisitor.cpp
@@ -268,3 +268,9 @@ bool CommentAstVisitor::visit(UiImport *node)
attachComment(node);
return true;
}
+
+bool CommentAstVisitor::visit(UiPragma *node)
+{
+ attachComment(node);
+ return true;
+}
diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h
index 369784a5ba..c756c4f820 100644
--- a/tools/qmlformat/commentastvisitor.h
+++ b/tools/qmlformat/commentastvisitor.h
@@ -108,6 +108,7 @@ public:
void endVisit(StatementList *node) override;
bool visit(UiImport *node) override;
+ bool visit(UiPragma *node) override;
bool visit(UiPublicMember *node) override;
bool visit(FunctionDeclaration *node) override;
private:
diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp
index 20ebef8927..75d9fa8a4f 100644
--- a/tools/qmlformat/dumpastvisitor.cpp
+++ b/tools/qmlformat/dumpastvisitor.cpp
@@ -35,10 +35,21 @@ DumpAstVisitor::DumpAstVisitor(Node *rootNode, CommentAstVisitor *comment): m_co
// Add all completely orphaned comments
m_result += getOrphanedComments(nullptr);
+ m_scope_properties.push(ScopeProperties {});
+
rootNode->accept(this);
// We need to get rid of one new-line so our output doesn't append an empty line
m_result.chop(1);
+
+ // Remove trailing whitespace
+ QStringList lines = m_result.split("\n");
+ for (QString& line : lines) {
+ while (line.endsWith(" "))
+ line.chop(1);
+ }
+
+ m_result = lines.join("\n");
}
static QString parseUiQualifiedId(UiQualifiedId *id)
@@ -185,7 +196,7 @@ QString DumpAstVisitor::parseUiParameterList(UiParameterList *list) {
QString result = "";
for (auto *item = list; item != nullptr; item = item->next)
- result += item->type->name + " " + item->name + (item->next != nullptr ? ", " : "");
+ result += parseUiQualifiedId(item->type) + " " + item->name + (item->next != nullptr ? ", " : "");
return result;
}
@@ -218,20 +229,28 @@ QString DumpAstVisitor::parsePatternElement(PatternElement *element, bool scope)
result += element->bindingIdentifier.toString();
+ if (element->typeAnnotation != nullptr)
+ result += ": " + parseType(element->typeAnnotation->type);
+
if (!expr.isEmpty())
result += " = "+expr;
return result;
}
default:
+ m_error = true;
return "pe_unknown";
}
}
QString escapeString(QString string)
{
- // Escape \r, \n and \t
- string = string.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t");
+ // Handle escape sequences
+ string = string.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t")
+ .replace("\b","\\b").replace("\v", "\\v").replace("\f", "\\f");
+
+ // Escape backslash
+ string = string.replace("\\", "\\\\");
// Escape "
string = string.replace("\"", "\\\"");
@@ -261,7 +280,14 @@ QString DumpAstVisitor::parseFormalParameterList(FormalParameterList *list)
QString DumpAstVisitor::parsePatternProperty(PatternProperty *property)
{
- return escapeString(property->name->asString())+": "+parsePatternElement(property, false);
+ switch (property->type) {
+ case PatternElement::Getter:
+ return "get "+parseFunctionExpression(cast<FunctionExpression *>(property->initializer), true);
+ case PatternElement::Setter:
+ return "set "+parseFunctionExpression(cast<FunctionExpression *>(property->initializer), true);
+ default:
+ return escapeString(property->name->asString())+": "+parsePatternElement(property, false);
+ }
}
QString DumpAstVisitor::parsePatternPropertyList(PatternPropertyList *list)
@@ -275,6 +301,61 @@ QString DumpAstVisitor::parsePatternPropertyList(PatternPropertyList *list)
return result;
}
+QString DumpAstVisitor::parseFunctionExpression(FunctionExpression *functExpr, bool omitFunction)
+{
+ m_indentLevel++;
+ QString result;
+
+ if (!functExpr->isArrowFunction) {
+ result += omitFunction ? "" : "function";
+
+ if (functExpr->isGenerator)
+ result += "*";
+
+ if (!functExpr->name.isEmpty())
+ result += (omitFunction ? "" : " ") + functExpr->name;
+
+ result += "("+parseFormalParameterList(functExpr->formals)+")";
+
+ if (functExpr->typeAnnotation != nullptr)
+ result += " : " + parseType(functExpr->typeAnnotation->type);
+
+ result += " {\n" + parseStatementList(functExpr->body);
+ } else {
+ result += "("+parseFormalParameterList(functExpr->formals)+")";
+
+ if (functExpr->typeAnnotation != nullptr)
+ result += " : " + parseType(functExpr->typeAnnotation->type);
+
+ result += " => {\n" + parseStatementList(functExpr->body);
+ }
+
+ m_indentLevel--;
+
+ result += formatLine("}", false);
+
+ return result;
+
+}
+
+QString DumpAstVisitor::parseType(Type *type) {
+ QString result = parseUiQualifiedId(type->typeId);
+
+ if (type->typeArguments != nullptr) {
+ TypeArgumentList *list = cast<TypeArgumentList *>(type->typeArguments);
+
+ result += "<";
+
+ for (auto *item = list; item != nullptr; item = item->next) {
+ result += parseType(item->typeId) + (item->next != nullptr ? ", " : "");
+ }
+
+ result += ">";
+ }
+
+ return result;
+}
+
QString DumpAstVisitor::parseExpression(ExpressionNode *expression)
{
if (expression == nullptr)
@@ -288,7 +369,15 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression)
return cast<IdentifierExpression*>(expression)->name.toString();
case Node::Kind_FieldMemberExpression: {
auto *fieldMemberExpr = cast<FieldMemberExpression *>(expression);
- return parseExpression(fieldMemberExpr->base) + "." + fieldMemberExpr->name.toString();
+ QString result = parseExpression(fieldMemberExpr->base);
+
+ // If we're operating on a numeric literal, always put it in braces
+ if (fieldMemberExpr->base->kind == Node::Kind_NumericLiteral)
+ result = "(" + result + ")";
+
+ result += "." + fieldMemberExpr->name.toString();
+
+ return result;
}
case Node::Kind_ArrayMemberExpression: {
auto *arrayMemberExpr = cast<ArrayMemberExpression *>(expression);
@@ -304,31 +393,7 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression)
case Node::Kind_FunctionExpression:
{
auto *functExpr = cast<FunctionExpression *>(expression);
-
- m_indentLevel++;
- QString result;
-
- if (!functExpr->isArrowFunction) {
- result += "function";
-
- if (functExpr->isGenerator)
- result += "*";
-
- if (!functExpr->name.isEmpty())
- result += " " + functExpr->name;
-
- result += "("+parseFormalParameterList(functExpr->formals)+") {\n"
- + parseStatementList(functExpr->body);
- } else {
- result += "("+parseFormalParameterList(functExpr->formals)+") => {\n";
- result += parseStatementList(functExpr->body);
- }
-
- m_indentLevel--;
-
- result += formatLine("}", false);
-
- return result;
+ return parseFunctionExpression(functExpr);
}
case Node::Kind_NullExpression:
return "null";
@@ -419,8 +484,7 @@ QString DumpAstVisitor::parseExpression(ExpressionNode *expression)
}
case Node::Kind_Type: {
auto* type = reinterpret_cast<Type*>(expression);
-
- return parseUiQualifiedId(type->typeId);
+ return parseType(type);
}
case Node::Kind_RegExpLiteral: {
auto* regexpLiteral = cast<RegExpLiteral*>(expression);
@@ -610,10 +674,14 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext,
result += "; ";
result += parseExpression(forStatement->condition) + "; ";
- result += parseExpression(forStatement->expression)+") ";
+ result += parseExpression(forStatement->expression)+")";
- result += parseStatement(forStatement->statement);
+ const QString statement = parseStatement(forStatement->statement);
+ if (!statement.isEmpty())
+ result += " "+statement;
+ else
+ result += ";";
return result;
}
@@ -639,9 +707,16 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext,
break;
}
- result += parseExpression(forEachStatement->expression) + ") ";
+ result += parseExpression(forEachStatement->expression) + ")";
+
+ const QString statement = parseStatement(forEachStatement->statement);
+
+ if (!statement.isEmpty())
+ result += " "+statement;
+ else
+ result += ";";
+
- result += parseStatement(forEachStatement->statement);
return result;
}
@@ -652,9 +727,14 @@ QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext,
auto statement = parseStatement(whileStatement->statement, false, true);
- return "while ("+parseExpression(whileStatement->expression) + ")"
- + (m_blockNeededBraces ? " " : "")
- + statement;
+ QString result = "while ("+parseExpression(whileStatement->expression) + ")";
+
+ if (!statement.isEmpty())
+ result += (m_blockNeededBraces ? " " : "") + statement;
+ else
+ result += ";";
+
+ return result;
}
case Node::Kind_DoWhileStatement: {
auto *doWhileStatement = cast<DoWhileStatement *>(statement);
@@ -762,31 +842,32 @@ bool DumpAstVisitor::visit(UiPublicMember *node) {
switch (node->type)
{
case UiPublicMember::Signal:
- if (m_firstSignal) {
- if (m_firstOfAll)
- m_firstOfAll = false;
+ if (scope().m_firstSignal) {
+ if (scope().m_firstOfAll)
+ scope().m_firstOfAll = false;
else
addNewLine();
- m_firstSignal = false;
+ scope().m_firstSignal = false;
}
addLine("signal "+node->name.toString()+"("+parseUiParameterList(node->parameters) + ")"
+ commentBackInline);
break;
case UiPublicMember::Property: {
- if (m_firstProperty) {
- if (m_firstOfAll)
- m_firstOfAll = false;
+ if (scope().m_firstProperty) {
+ if (scope().m_firstOfAll)
+ scope().m_firstOfAll = false;
else
addNewLine();
- m_firstProperty = false;
+ scope().m_firstProperty = false;
}
const bool is_required = node->requiredToken.isValid();
const bool is_default = node->defaultToken.isValid();
const bool is_readonly = node->readonlyToken.isValid();
+ const bool has_type_modifier = node->typeModifierToken.isValid();
QString prefix = "";
QString statement = parseStatement(node->statement);
@@ -803,8 +884,20 @@ bool DumpAstVisitor::visit(UiPublicMember *node) {
if (is_readonly)
prefix += "readonly ";
- addLine(prefix + "property " + node->memberType->name + " "
- + node->name+statement + commentBackInline);
+ QString member_type = parseUiQualifiedId(node->memberType);
+
+ if (has_type_modifier)
+ member_type = node->typeModifier + "<" + member_type + ">";
+
+ if (is_readonly && statement.isEmpty()
+ && scope().m_bindings.contains(node->name.toString())) {
+ m_result += formatLine(prefix + "property " + member_type + " ", false);
+
+ scope().m_pendingBinding = true;
+ } else {
+ addLine(prefix + "property " + member_type + " "
+ + node->name+statement + commentBackInline);
+ }
break;
}
}
@@ -845,26 +938,70 @@ void DumpAstVisitor::addLine(QString line) {
m_result += formatLine(line);
}
+QHash<QString, UiObjectMember*> findBindings(UiObjectMemberList *list) {
+ QHash<QString, UiObjectMember*> bindings;
+
+ // This relies on RestructureASTVisitor having run beforehand
+
+ for (auto *item = list; item != nullptr; item = item->next) {
+ switch (item->member->kind) {
+ case Node::Kind_UiPublicMember: {
+ UiPublicMember *member = cast<UiPublicMember *>(item->member);
+
+ if (member->type != UiPublicMember::Property)
+ continue;
+
+ bindings[member->name.toString()] = nullptr;
+
+ break;
+ }
+ case Node::Kind_UiObjectBinding: {
+ UiObjectBinding *binding = cast<UiObjectBinding *>(item->member);
+
+ const QString name = parseUiQualifiedId(binding->qualifiedId);
+
+ if (bindings.contains(name))
+ bindings[name] = binding;
+
+ break;
+ }
+ case Node::Kind_UiArrayBinding: {
+ UiArrayBinding *binding = cast<UiArrayBinding *>(item->member);
+
+ const QString name = parseUiQualifiedId(binding->qualifiedId);
+
+ if (bindings.contains(name))
+ bindings[name] = binding;
+
+ break;
+ }
+ case Node::Kind_UiScriptBinding:
+ // We can ignore UiScriptBindings since those are actually properly attached to the property
+ break;
+ }
+ }
+
+ return bindings;
+}
+
bool DumpAstVisitor::visit(UiObjectDefinition *node) {
- if (m_firstObject) {
- if (m_firstOfAll)
- m_firstOfAll = false;
+ if (scope().m_firstObject) {
+ if (scope().m_firstOfAll)
+ scope().m_firstOfAll = false;
else
addNewLine();
- m_firstObject = false;
+ scope().m_firstObject = false;
}
addLine(getComment(node, Comment::Location::Front));
- addLine(node->qualifiedTypeNameId->name+" {");
+ addLine(parseUiQualifiedId(node->qualifiedTypeNameId) + " {");
m_indentLevel++;
- m_firstProperty = true;
- m_firstSignal = true;
- m_firstBinding = true;
- m_firstObject = true;
- m_firstOfAll = true;
+ ScopeProperties props;
+ props.m_bindings = findBindings(node->initializer->members);
+ m_scope_properties.push(props);
m_result += getOrphanedComments(node);
@@ -873,9 +1010,14 @@ bool DumpAstVisitor::visit(UiObjectDefinition *node) {
void DumpAstVisitor::endVisit(UiObjectDefinition *node) {
m_indentLevel--;
- addLine(m_inArrayBinding && m_lastInArrayBinding != node ? "}," : "}");
+
+ m_scope_properties.pop();
+
+ bool need_comma = scope().m_inArrayBinding && scope().m_lastInArrayBinding != node;
+
+ addLine(need_comma ? "}," : "}");
addLine(getComment(node, Comment::Location::Back));
- if (!m_inArrayBinding)
+ if (!scope().m_inArrayBinding)
addNewLine();
}
@@ -920,49 +1062,64 @@ bool DumpAstVisitor::visit(UiEnumMemberList *node) {
}
bool DumpAstVisitor::visit(UiScriptBinding *node) {
- if (m_firstBinding) {
- if (m_firstOfAll)
- m_firstOfAll = false;
+ if (scope().m_firstBinding) {
+ if (scope().m_firstOfAll)
+ scope().m_firstOfAll = false;
else
addNewLine();
if (parseUiQualifiedId(node->qualifiedId) != "id")
- m_firstBinding = false;
+ scope().m_firstBinding = false;
}
addLine(getComment(node, Comment::Location::Front));
- addLine(parseUiQualifiedId(node->qualifiedId)+ ": " + parseStatement(node->statement)
- + getComment(node, Comment::Location::Back_Inline));
+
+ QString statement = parseStatement(node->statement);
+
+ QString result = parseUiQualifiedId(node->qualifiedId) + ":";
+
+ if (!statement.isEmpty())
+ result += " "+statement;
+ else
+ result += ";";
+
+ result += getComment(node, Comment::Location::Back_Inline);
+
+ addLine(result);
+
return true;
}
bool DumpAstVisitor::visit(UiArrayBinding *node) {
- if (m_firstBinding) {
- if (m_firstOfAll)
- m_firstOfAll = false;
+ if (!scope().m_pendingBinding && scope().m_firstBinding) {
+ if (scope().m_firstOfAll)
+ scope().m_firstOfAll = false;
else
addNewLine();
- m_firstBinding = false;
+ scope().m_firstBinding = false;
}
- addLine(getComment(node, Comment::Location::Front));
- addLine(parseUiQualifiedId(node->qualifiedId)+ ": [");
+ if (scope().m_pendingBinding) {
+ m_result += parseUiQualifiedId(node->qualifiedId)+ ": [\n";
+ scope().m_pendingBinding = false;
+ } else {
+ addLine(getComment(node, Comment::Location::Front));
+ addLine(parseUiQualifiedId(node->qualifiedId)+ ": [");
+ }
m_indentLevel++;
- m_inArrayBinding = true;
- m_firstOfAll = true;
- m_firstObject = true;
- m_firstSignal = true;
- m_firstBinding = true;
- m_firstProperty = true;
+ ScopeProperties props;
+ props.m_inArrayBinding = true;
for (auto *item = node->members; item != nullptr; item = item->next) {
if (item->next == nullptr)
- m_lastInArrayBinding = item->member;
+ props.m_lastInArrayBinding = item->member;
}
+ m_scope_properties.push(props);
+
m_result += getOrphanedComments(node);
return true;
@@ -970,8 +1127,7 @@ bool DumpAstVisitor::visit(UiArrayBinding *node) {
void DumpAstVisitor::endVisit(UiArrayBinding *) {
m_indentLevel--;
- m_inArrayBinding = false;
- m_lastInArrayBinding = nullptr;
+ m_scope_properties.pop();
addLine("]");
}
@@ -986,7 +1142,12 @@ bool DumpAstVisitor::visit(FunctionDeclaration *node) {
if (node->isGenerator)
head += "*";
- head += " "+node->name+"("+parseFormalParameterList(node->formals)+") {";
+ head += " "+node->name+"("+parseFormalParameterList(node->formals)+")";
+
+ if (node->typeAnnotation != nullptr)
+ head += " : " + parseType(node->typeAnnotation->type);
+
+ head += " {";
addLine(head);
m_indentLevel++;
@@ -1000,27 +1161,37 @@ bool DumpAstVisitor::visit(FunctionDeclaration *node) {
}
bool DumpAstVisitor::visit(UiObjectBinding *node) {
- if (m_firstObject) {
- if (m_firstOfAll)
- m_firstOfAll = false;
+ if (!scope().m_pendingBinding && scope().m_firstObject) {
+ if (scope().m_firstOfAll)
+ scope().m_firstOfAll = false;
else
addNewLine();
- m_firstObject = false;
+ scope().m_firstObject = false;
}
QString name = parseUiQualifiedId(node->qualifiedTypeNameId);
QString result = name;
+ ScopeProperties props;
+ props.m_bindings = findBindings(node->initializer->members);
+ m_scope_properties.push(props);
+
if (node->hasOnToken)
result += " on "+parseUiQualifiedId(node->qualifiedId);
else
result.prepend(parseUiQualifiedId(node->qualifiedId) + ": ");
- addNewLine();
- addLine(getComment(node, Comment::Location::Front));
- addLine(result+" {");
+ if (scope().m_pendingBinding) {
+ m_result += result + " {\n";
+
+ scope().m_pendingBinding = false;
+ } else {
+ addNewLine();
+ addLine(getComment(node, Comment::Location::Front));
+ addLine(result + " {");
+ }
m_indentLevel++;
@@ -1029,6 +1200,8 @@ bool DumpAstVisitor::visit(UiObjectBinding *node) {
void DumpAstVisitor::endVisit(UiObjectBinding *node) {
m_indentLevel--;
+ m_scope_properties.pop();
+
addLine("}");
addLine(getComment(node, Comment::Location::Back));
@@ -1036,6 +1209,8 @@ void DumpAstVisitor::endVisit(UiObjectBinding *node) {
}
bool DumpAstVisitor::visit(UiImport *node) {
+ scope().m_firstOfAll = false;
+
addLine(getComment(node, Comment::Location::Front));
QString result = "import ";
@@ -1060,3 +1235,15 @@ bool DumpAstVisitor::visit(UiImport *node) {
return true;
}
+
+bool DumpAstVisitor::visit(UiPragma *node) {
+ scope().m_firstOfAll = false;
+
+ addLine(getComment(node, Comment::Location::Front));
+ QString result = "pragma "+ node->name;
+ result += getComment(node, Comment::Location::Back_Inline);
+
+ addLine(result);
+
+ return true;
+}
diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h
index 2001f4366e..8c34e01960 100644
--- a/tools/qmlformat/dumpastvisitor.h
+++ b/tools/qmlformat/dumpastvisitor.h
@@ -32,6 +32,9 @@
#include <QtQml/private/qqmljsastvisitor_p.h>
#include <QtQml/private/qqmljsast_p.h>
+#include <QHash>
+#include <QStack>
+
#include "commentastvisitor.h"
using namespace QQmlJS::AST;
@@ -62,11 +65,25 @@ public:
bool visit(UiEnumMemberList *node) override;
bool visit(UiPublicMember *node) override;
bool visit(UiImport *node) override;
+ bool visit(UiPragma *node) override;
void throwRecursionDepthError() override {}
bool error() const { return m_error; }
private:
+ struct ScopeProperties {
+ bool m_firstOfAll = true;
+ bool m_firstSignal = true;
+ bool m_firstProperty = true;
+ bool m_firstBinding = true;
+ bool m_firstObject = true;
+ bool m_inArrayBinding = false;
+ bool m_pendingBinding = false;
+
+ UiObjectMember* m_lastInArrayBinding = nullptr;
+ QHash<QString, UiObjectMember*> m_bindings;
+ };
+
QString generateIndent() const;
QString formatLine(QString line, bool newline = true) const;
@@ -106,19 +123,19 @@ private:
QString parseFormalParameterList(FormalParameterList *list);
+ QString parseType(Type *type);
+
+ QString parseFunctionExpression(FunctionExpression *expression, bool omitFunction = false);
+
+ ScopeProperties& scope() { return m_scope_properties.top(); }
+
int m_indentLevel = 0;
bool m_error = false;
bool m_blockNeededBraces = false;
- bool m_inArrayBinding = false;
- bool m_firstOfAll = false;
- bool m_firstSignal = false;
- bool m_firstProperty = false;
- bool m_firstBinding = false;
- bool m_firstObject = true;
+ QStack<ScopeProperties> m_scope_properties;
- UiObjectMember* m_lastInArrayBinding = nullptr;
QString m_result = "";
CommentAstVisitor *m_comment;
};
diff --git a/tools/qmlformat/main.cpp b/tools/qmlformat/main.cpp
index 036fbe9748..915389e3d2 100644
--- a/tools/qmlformat/main.cpp
+++ b/tools/qmlformat/main.cpp
@@ -101,11 +101,29 @@ bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImp
DumpAstVisitor dump(parser.rootNode(), &comment);
- if (dump.error()) {
+ QString dumpCode = dump.toString();
+
+ lexer.setCode(dumpCode, 1, true);
+
+ bool dumpSuccess = parser.parse();
+
+ if (!dumpSuccess) {
+ if (verbose) {
+ const auto diagnosticMessages = parser.diagnosticMessages();
+ for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) {
+ qWarning().noquote() << QString::fromLatin1("<formatted>:%2 : %3")
+ .arg(m.line).arg(m.message);
+ }
+ }
+
+ qWarning().noquote() << "Failed to parse formatted code.";
+ }
+
+ if (dump.error() || !dumpSuccess) {
if (force) {
qWarning().noquote() << "An error has occurred. The output may not be reliable.";
} else {
- qWarning().noquote() << "Am error has occurred. Aborting.";
+ qWarning().noquote() << "An error has occurred. Aborting.";
return false;
}
}
@@ -120,10 +138,10 @@ bool parseFile(const QString& filename, bool inplace, bool verbose, bool sortImp
return false;
}
- file.write(dump.toString().toUtf8());
+ file.write(dumpCode.toUtf8());
file.close();
} else {
- QTextStream(stdout) << dump.toString();
+ QTextStream(stdout) << dumpCode;
}
return true;