diff options
-rw-r--r-- | examples/quick/window/Splash.qml | 2 | ||||
-rw-r--r-- | qtdeclarative.pro | 5 | ||||
-rw-r--r-- | src/imports/dialogs/qquickabstractfiledialog.cpp | 9 | ||||
-rw-r--r-- | src/imports/testlib/TestCase.qml | 11 | ||||
-rw-r--r-- | src/imports/testlib/testcase.qdoc | 7 | ||||
-rw-r--r-- | src/imports/widgets/qquickqfiledialog.cpp | 132 | ||||
-rw-r--r-- | src/imports/widgets/qquickqfiledialog_p.h | 29 | ||||
-rw-r--r-- | src/particles/qquickimageparticle.cpp | 10 | ||||
-rw-r--r-- | src/qml/doc/src/qtqml-cpp.qdoc | 2 | ||||
-rw-r--r-- | src/qml/qml/qqmlapplicationengine.cpp | 7 | ||||
-rw-r--r-- | src/qml/qml/qqmlapplicationengine_p.h | 1 | ||||
-rw-r--r-- | src/quick/doc/src/qtquick-cpp.qdoc | 1 | ||||
-rw-r--r-- | src/quick/items/context2d/qquickcontext2d.cpp | 237 | ||||
-rw-r--r-- | tests/auto/quick/qquickcanvasitem/data/tst_context.qml | 77 |
14 files changed, 359 insertions, 171 deletions
diff --git a/examples/quick/window/Splash.qml b/examples/quick/window/Splash.qml index 995e264f8b..629a52b6e4 100644 --- a/examples/quick/window/Splash.qml +++ b/examples/quick/window/Splash.qml @@ -60,7 +60,7 @@ Window { Image { id: splashImage - source: "../../shared/images/qt-logo.png" + source: "../shared/images/qt-logo.png" MouseArea { anchors.fill: parent onClicked: Qt.quit() diff --git a/qtdeclarative.pro b/qtdeclarative.pro index 8bc15a5662..02ba4a951d 100644 --- a/qtdeclarative.pro +++ b/qtdeclarative.pro @@ -1,2 +1,7 @@ CONFIG += tests_need_tools load(qt_parts) + +ios { + log("The qtdeclarative module was disabled from the build because the dependency qtjsbackend/V8 is not ported to iOS.") + SUBDIRS= +} diff --git a/src/imports/dialogs/qquickabstractfiledialog.cpp b/src/imports/dialogs/qquickabstractfiledialog.cpp index e415ebc7e4..3a0d5baa83 100644 --- a/src/imports/dialogs/qquickabstractfiledialog.cpp +++ b/src/imports/dialogs/qquickabstractfiledialog.cpp @@ -115,10 +115,9 @@ QUrl QQuickAbstractFileDialog::folder() void QQuickAbstractFileDialog::setFolder(const QUrl &f) { - QString dir = f.path(); if (m_dlgHelper) - m_dlgHelper->setDirectory(dir); - m_options->setInitialDirectory(dir); + m_dlgHelper->setDirectory(f); + m_options->setInitialDirectory(f); emit folderChanged(); } @@ -159,7 +158,9 @@ QUrl QQuickAbstractFileDialog::fileUrl() QList<QUrl> QQuickAbstractFileDialog::fileUrls() { - return m_dlgHelper->selectedFiles(); + if (m_dlgHelper) + return m_dlgHelper->selectedFiles(); + return QList<QUrl>(); } void QQuickAbstractFileDialog::updateModes() diff --git a/src/imports/testlib/TestCase.qml b/src/imports/testlib/TestCase.qml index 0bec4cddf2..6e2c8e73ab 100644 --- a/src/imports/testlib/TestCase.qml +++ b/src/imports/testlib/TestCase.qml @@ -290,14 +290,21 @@ Item { return qtest_results.grabImage(item); } - function tryCompare(obj, prop, value, timeout) { + function tryCompare(obj, prop, value, timeout, msg) { if (arguments.length == 2) { qtest_results.fail("A value is required for tryCompare", util.callerFile(), util.callerLine()) throw new Error("QtQuickTest::fail") } + if (timeout !== undefined && typeof(timeout) != "number") { + qtest_results.fail("timeout should be a number", + util.callerFile(), util.callerLine()) + throw new Error("QtQuickTest::fail") + } if (!timeout) timeout = 5000 + if (msg === undefined) + msg = "property " + prop if (!qtest_compareInternal(obj[prop], value)) wait(0) var i = 0 @@ -309,7 +316,7 @@ Item { var act = qtest_results.stringify(actual) var exp = qtest_results.stringify(value) var success = qtest_compareInternal(actual, value) - if (!qtest_results.compare(success, "property " + prop, act, exp, util.callerFile(), util.callerLine())) + if (!qtest_results.compare(success, msg, act, exp, util.callerFile(), util.callerLine())) throw new Error("QtQuickTest::fail") } diff --git a/src/imports/testlib/testcase.qdoc b/src/imports/testlib/testcase.qdoc index 56fcb24beb..dd1d9e5ad3 100644 --- a/src/imports/testlib/testcase.qdoc +++ b/src/imports/testlib/testcase.qdoc @@ -367,11 +367,12 @@ */ /*! - \qmlmethod TestCase::tryCompare(obj, property, expected, timeout = 5000) + \qmlmethod TestCase::tryCompare(obj, property, expected, timeout = 5000, message = "") Fails the current test case if the specified \a property on \a obj - is not the same as \a expected. The test will be retried multiple - times until the \a timeout (in milliseconds) is reached. + is not the same as \a expected, and displays the optional \a message. + The test will be retried multiple times until the + \a timeout (in milliseconds) is reached. This function is intended for testing applications where a property changes value based on asynchronous events. Use compare() for testing diff --git a/src/imports/widgets/qquickqfiledialog.cpp b/src/imports/widgets/qquickqfiledialog.cpp index 7446887eb8..c3991b4f3c 100644 --- a/src/imports/widgets/qquickqfiledialog.cpp +++ b/src/imports/widgets/qquickqfiledialog.cpp @@ -50,68 +50,6 @@ QT_BEGIN_NAMESPACE -class QFileDialogHelper : public QPlatformFileDialogHelper -{ -public: - QFileDialogHelper() : - QPlatformFileDialogHelper() - { - connect(&m_dialog, SIGNAL(currentChanged(const QString&)), this, SIGNAL(currentChanged(const QString&))); - connect(&m_dialog, SIGNAL(directoryEntered(const QString&)), this, SIGNAL(directoryEntered(const QString&))); - connect(&m_dialog, SIGNAL(fileSelected(const QString&)), this, SIGNAL(fileSelected(const QString&))); - connect(&m_dialog, SIGNAL(filesSelected(const QStringList&)), this, SIGNAL(filesSelected(const QStringList&))); - connect(&m_dialog, SIGNAL(filterSelected(const QString&)), this, SIGNAL(filterSelected(const QString&))); - connect(&m_dialog, SIGNAL(accepted()), this, SIGNAL(accept())); - connect(&m_dialog, SIGNAL(rejected()), this, SIGNAL(reject())); - } - - virtual bool defaultNameFilterDisables() const { return true; } - virtual void setDirectory(const QUrl &dir) { m_dialog.setDirectoryUrl(dir); } - virtual QUrl directory() const { return m_dialog.directoryUrl(); } - virtual void selectFile(const QUrl &f) { m_dialog.selectUrl(f); } - virtual QList<QUrl> selectedFiles() const; - - virtual void setFilter() { - m_dialog.setWindowTitle(QPlatformFileDialogHelper::options()->windowTitle()); - if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::LookIn)) - m_dialog.setLabelText(m_dialog.LookIn, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::LookIn)); - if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::FileName)) - m_dialog.setLabelText(m_dialog.FileName, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::FileName)); - if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::FileType)) - m_dialog.setLabelText(m_dialog.FileType, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::FileType)); - if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) - m_dialog.setLabelText(m_dialog.Accept, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::Accept)); - if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::Reject)) - m_dialog.setLabelText(m_dialog.Reject, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::Reject)); - m_dialog.setFilter(QPlatformFileDialogHelper::options()->filter()); - m_dialog.setNameFilters(QPlatformFileDialogHelper::options()->nameFilters()); - m_dialog.selectNameFilter(QPlatformFileDialogHelper::options()->initiallySelectedNameFilter()); - m_dialog.setFileMode(QFileDialog::FileMode(QPlatformFileDialogHelper::options()->fileMode())); - m_dialog.setOptions((QFileDialog::Options)((int)(QPlatformFileDialogHelper::options()->options()))); - m_dialog.setAcceptMode(QFileDialog::AcceptMode(QPlatformFileDialogHelper::options()->acceptMode())); - } - - virtual void selectNameFilter(const QString &f) { m_dialog.selectNameFilter(f); } - virtual QString selectedNameFilter() const { return m_dialog.selectedNameFilter(); } - virtual void exec() { m_dialog.exec(); } - - virtual bool show(Qt::WindowFlags f, Qt::WindowModality m, QWindow *parent) { - m_dialog.winId(); - QWindow *window = m_dialog.windowHandle(); - Q_ASSERT(window); - window->setTransientParent(parent); - window->setFlags(f); - m_dialog.setWindowModality(m); - m_dialog.show(); - return m_dialog.isVisible(); - } - - virtual void hide() { m_dialog.hide(); } - -private: - QFileDialog m_dialog; -}; - /*! \qmltype QtFileDialog \instantiates QQuickQFileDialog @@ -190,8 +128,8 @@ QPlatformFileDialogHelper *QQuickQFileDialog::helper() if (!m_dlgHelper) { m_dlgHelper = new QFileDialogHelper(); - connect(m_dlgHelper, SIGNAL(directoryEntered(QString)), this, SIGNAL(folderChanged())); - connect(m_dlgHelper, SIGNAL(filterSelected(QString)), this, SIGNAL(filterSelected())); + connect(m_dlgHelper, SIGNAL(directoryEntered(const QUrl &)), this, SIGNAL(folderChanged())); + connect(m_dlgHelper, SIGNAL(filterSelected(const QString &)), this, SIGNAL(filterSelected())); connect(m_dlgHelper, SIGNAL(accept()), this, SLOT(accept())); connect(m_dlgHelper, SIGNAL(reject()), this, SLOT(reject())); } @@ -199,9 +137,75 @@ QPlatformFileDialogHelper *QQuickQFileDialog::helper() return m_dlgHelper; } +QFileDialogHelper::QFileDialogHelper() : + QPlatformFileDialogHelper() +{ + connect(&m_dialog, SIGNAL(currentChanged(const QString&)), this, SLOT(currentChanged(const QString&))); + connect(&m_dialog, SIGNAL(directoryEntered(const QString&)), this, SLOT(directoryEntered(const QString&))); + connect(&m_dialog, SIGNAL(fileSelected(const QString&)), this, SLOT(fileSelected(const QString&))); + connect(&m_dialog, SIGNAL(filesSelected(const QStringList&)), this, SLOT(filesSelected(const QStringList&))); + connect(&m_dialog, SIGNAL(filterSelected(const QString&)), this, SIGNAL(filterSelected(const QString&))); + connect(&m_dialog, SIGNAL(accepted()), this, SIGNAL(accept())); + connect(&m_dialog, SIGNAL(rejected()), this, SIGNAL(reject())); +} + QList<QUrl> QFileDialogHelper::selectedFiles() const { return m_dialog.selectedUrls(); } +void QFileDialogHelper::setFilter() { + m_dialog.setWindowTitle(QPlatformFileDialogHelper::options()->windowTitle()); + if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::LookIn)) + m_dialog.setLabelText(m_dialog.LookIn, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::LookIn)); + if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::FileName)) + m_dialog.setLabelText(m_dialog.FileName, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::FileName)); + if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::FileType)) + m_dialog.setLabelText(m_dialog.FileType, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::FileType)); + if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) + m_dialog.setLabelText(m_dialog.Accept, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::Accept)); + if (QPlatformFileDialogHelper::options()->isLabelExplicitlySet(QFileDialogOptions::Reject)) + m_dialog.setLabelText(m_dialog.Reject, QPlatformFileDialogHelper::options()->labelText(QFileDialogOptions::Reject)); + m_dialog.setFilter(QPlatformFileDialogHelper::options()->filter()); + m_dialog.setNameFilters(QPlatformFileDialogHelper::options()->nameFilters()); + m_dialog.selectNameFilter(QPlatformFileDialogHelper::options()->initiallySelectedNameFilter()); + m_dialog.setFileMode(QFileDialog::FileMode(QPlatformFileDialogHelper::options()->fileMode())); + m_dialog.setOptions((QFileDialog::Options)((int)(QPlatformFileDialogHelper::options()->options()))); + m_dialog.setAcceptMode(QFileDialog::AcceptMode(QPlatformFileDialogHelper::options()->acceptMode())); +} + +bool QFileDialogHelper::show(Qt::WindowFlags f, Qt::WindowModality m, QWindow *parent) { + m_dialog.winId(); + QWindow *window = m_dialog.windowHandle(); + Q_ASSERT(window); + window->setTransientParent(parent); + window->setFlags(f); + m_dialog.setWindowModality(m); + m_dialog.show(); + return m_dialog.isVisible(); +} + +void QFileDialogHelper::currentChanged(const QString& path) +{ + emit QPlatformFileDialogHelper::currentChanged(QUrl::fromLocalFile(path)); +} + +void QFileDialogHelper::directoryEntered(const QString& path) +{ + emit QPlatformFileDialogHelper::directoryEntered(QUrl::fromLocalFile(path)); +} + +void QFileDialogHelper::fileSelected(const QString& path) +{ + emit QPlatformFileDialogHelper::fileSelected(QUrl::fromLocalFile(path)); +} + +void QFileDialogHelper::filesSelected(const QStringList& paths) +{ + QList<QUrl> pathUrls; + foreach (const QString &path, paths) + pathUrls << QUrl::fromLocalFile(path); + emit QPlatformFileDialogHelper::filesSelected(pathUrls); +} + QT_END_NAMESPACE diff --git a/src/imports/widgets/qquickqfiledialog_p.h b/src/imports/widgets/qquickqfiledialog_p.h index 73067f796c..8bf7c73882 100644 --- a/src/imports/widgets/qquickqfiledialog_p.h +++ b/src/imports/widgets/qquickqfiledialog_p.h @@ -53,6 +53,7 @@ // We mean it. // +#include <QFileDialog> #include "../dialogs/qquickabstractfiledialog_p.h" QT_BEGIN_NAMESPACE @@ -71,6 +72,34 @@ protected: Q_DISABLE_COPY(QQuickQFileDialog) }; +class QFileDialogHelper : public QPlatformFileDialogHelper +{ + Q_OBJECT +public: + QFileDialogHelper(); + + bool defaultNameFilterDisables() const Q_DECL_OVERRIDE { return true; } + void setDirectory(const QUrl &dir) Q_DECL_OVERRIDE { m_dialog.setDirectoryUrl(dir); } + QUrl directory() const Q_DECL_OVERRIDE { return m_dialog.directoryUrl(); } + void selectFile(const QUrl &f) Q_DECL_OVERRIDE { m_dialog.selectUrl(f); } + QList<QUrl> selectedFiles() const Q_DECL_OVERRIDE; + void setFilter() Q_DECL_OVERRIDE; + void selectNameFilter(const QString &f) Q_DECL_OVERRIDE { m_dialog.selectNameFilter(f); } + QString selectedNameFilter() const Q_DECL_OVERRIDE { return m_dialog.selectedNameFilter(); } + void exec() Q_DECL_OVERRIDE { m_dialog.exec(); } + bool show(Qt::WindowFlags f, Qt::WindowModality m, QWindow *parent) Q_DECL_OVERRIDE; + void hide() Q_DECL_OVERRIDE { m_dialog.hide(); } + +private slots: + void currentChanged(const QString& path); + void directoryEntered(const QString& path); + void fileSelected(const QString& path); + void filesSelected(const QStringList& paths); + +private: + QFileDialog m_dialog; +}; + QT_END_NAMESPACE QML_DECLARE_TYPE(QQuickQFileDialog *) diff --git a/src/particles/qquickimageparticle.cpp b/src/particles/qquickimageparticle.cpp index 424352d8e8..c3e13f162f 100644 --- a/src/particles/qquickimageparticle.cpp +++ b/src/particles/qquickimageparticle.cpp @@ -365,7 +365,6 @@ public: } void updateState(const DeformableMaterialData* d, const DeformableMaterialData*) { - glFuncs->glActiveTexture(GL_TEXTURE0); d->texture->bind(); program()->setUniformValue(m_timestamp_id, (float) d->timestamp); @@ -405,7 +404,7 @@ public: QList<QByteArray> attributes() const { return QList<QByteArray>() << "vPosTex" << "vData" << "vVec" << "vColor" << "vDeformVec" << "vRotation" << "vAnimData" << "vAnimPos"; - }; + } void initialize() { QSGSimpleMaterialShader<SpriteMaterialData>::initialize(); @@ -496,7 +495,6 @@ public: } void updateState(const ColoredMaterialData* d, const ColoredMaterialData*) { - glFuncs->glActiveTexture(GL_TEXTURE0); d->texture->bind(); program()->setUniformValue(m_timestamp_id, (float) d->timestamp); @@ -561,7 +559,6 @@ public: } void updateState(const SimpleMaterialData* d, const SimpleMaterialData*) { - glFuncs->glActiveTexture(GL_TEXTURE0); d->texture->bind(); program()->setUniformValue(m_timestamp_id, (float) d->timestamp); @@ -1677,7 +1674,6 @@ void QQuickImageParticle::spritesUpdate(qreal time) if (frameAt < (datum->frameCount-1)) x2 += w; - node->setFlag(QSGNode::OwnsGeometry, false); SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData(); spriteVertices += datum->index*4; for (int i=0; i<4; i++) { @@ -1689,7 +1685,6 @@ void QQuickImageParticle::spritesUpdate(qreal time) spriteVertices[i].animH = h; spriteVertices[i].animProgress = progress; } - node->setFlag(QSGNode::OwnsGeometry, true); } } } @@ -1853,7 +1848,6 @@ void QQuickImageParticle::commit(int gIdx, int pIdx) if (!node) return; QQuickParticleData* datum = m_system->groupData[gIdx]->data[pIdx]; - node->setFlag(QSGNode::OwnsGeometry, false); SpriteVertex *spriteVertices = (SpriteVertex *) node->geometry()->vertexData(); DeformableVertex *deformableVertices = (DeformableVertex *) node->geometry()->vertexData(); ColoredVertex *coloredVertices = (ColoredVertex *) node->geometry()->vertexData(); @@ -2004,8 +1998,6 @@ void QQuickImageParticle::commit(int gIdx, int pIdx) default: break; } - - node->setFlag(QSGNode::OwnsGeometry, true); } diff --git a/src/qml/doc/src/qtqml-cpp.qdoc b/src/qml/doc/src/qtqml-cpp.qdoc index 74e59d2540..07aca29f7d 100644 --- a/src/qml/doc/src/qtqml-cpp.qdoc +++ b/src/qml/doc/src/qtqml-cpp.qdoc @@ -27,6 +27,8 @@ /*! \module QtQml \title Qt QML C++ Classes +\ingroup modules +\qtvariable qml \brief The C++ API provided by the Qt QML module To include the definitions of the module's classes, use the diff --git a/src/qml/qml/qqmlapplicationengine.cpp b/src/qml/qml/qqmlapplicationengine.cpp index 9181dad519..85aeaf5786 100644 --- a/src/qml/qml/qqmlapplicationengine.cpp +++ b/src/qml/qml/qqmlapplicationengine.cpp @@ -54,6 +54,10 @@ QQmlApplicationEnginePrivate::QQmlApplicationEnginePrivate(QQmlEngine *e) QQmlApplicationEnginePrivate::~QQmlApplicationEnginePrivate() { +} + +void QQmlApplicationEnginePrivate::cleanUp() +{ qDeleteAll(objects); #ifndef QT_NO_TRANSLATIONS qDeleteAll(translators); @@ -229,7 +233,8 @@ QQmlApplicationEngine::QQmlApplicationEngine(const QString &filePath, QObject *p */ QQmlApplicationEngine::~QQmlApplicationEngine() { - //Instantiated root objects cleaned up in private class + Q_D(QQmlApplicationEngine); + d->cleanUp();//Instantiated root objects must be deleted before the engine } /*! diff --git a/src/qml/qml/qqmlapplicationengine_p.h b/src/qml/qml/qqmlapplicationengine_p.h index db144af504..cc38c62c02 100644 --- a/src/qml/qml/qqmlapplicationengine_p.h +++ b/src/qml/qml/qqmlapplicationengine_p.h @@ -70,6 +70,7 @@ public: QQmlApplicationEnginePrivate(QQmlEngine *e); ~QQmlApplicationEnginePrivate(); void init(); + void cleanUp(); void startLoad(const QUrl &url, const QByteArray &data = QByteArray(), bool dataFlag = false); void loadTranslations(const QUrl &rootFile); diff --git a/src/quick/doc/src/qtquick-cpp.qdoc b/src/quick/doc/src/qtquick-cpp.qdoc index 71234f142b..6ff52bfd4d 100644 --- a/src/quick/doc/src/qtquick-cpp.qdoc +++ b/src/quick/doc/src/qtquick-cpp.qdoc @@ -28,6 +28,7 @@ \module QtQuick \title Qt Quick C++ Classes \ingroup modules + \qtvariable quick \brief The Qt Quick module provides classes for embedding Qt Quick in Qt/C++ applications. diff --git a/src/quick/items/context2d/qquickcontext2d.cpp b/src/quick/items/context2d/qquickcontext2d.cpp index d59e65dfd4..d2014ded8c 100644 --- a/src/quick/items/context2d/qquickcontext2d.cpp +++ b/src/quick/items/context2d/qquickcontext2d.cpp @@ -198,57 +198,141 @@ QColor qt_color_from_string(const QV4::Value &name) return QColor(); } +static int qParseFontSizeFromToken(const QString &fontSizeToken, bool &ok) +{ + ok = false; + float size = fontSizeToken.trimmed().toFloat(&ok); + if (ok) { + return int(size); + } + qWarning().nospace() << "Context2D: A font size of " << fontSizeToken << " is invalid."; + return 0; +} + +/* + Attempts to set the font size of \a font to \a fontSizeToken, returning + \c true if successful. If the font size is invalid, \c false is returned + and a warning is printed. +*/ static bool qSetFontSizeFromToken(QFont &font, const QString &fontSizeToken) { const QString trimmedToken = fontSizeToken.trimmed(); - QString unit = trimmedToken.right(2); - QString value = trimmedToken.left(fontSizeToken.size() - 2); + const QString unitStr = trimmedToken.right(2); + const QString value = trimmedToken.left(trimmedToken.size() - 2); bool ok = false; - float size = value.trimmed().toFloat(&ok); - if (ok) { - int intSize = int(size); - if (unit.compare(QLatin1String("px")) == 0) { - font.setPixelSize(intSize); + int size = 0; + if (unitStr == QStringLiteral("px")) { + size = qParseFontSizeFromToken(value, ok); + if (ok) { + font.setPixelSize(size); return true; - } else if (unit.compare(QLatin1String("pt")) == 0) { - font.setPointSize(intSize); + } + } else if (unitStr == QStringLiteral("pt")) { + size = qParseFontSizeFromToken(value, ok); + if (ok) { + font.setPointSize(size); return true; } + } else { + qWarning().nospace() << "Context2D: Invalid font size unit in font string."; } - qWarning().nospace() << "Context2D: A font size of " << fontSizeToken << " is invalid."; return false; } -static bool qSetFontFamilyFromToken(QFont &font, const QString &fontFamilyToken) -{ - const QString trimmedToken = fontFamilyToken.trimmed(); - QFontDatabase fontDatabase; - if (fontDatabase.hasFamily(trimmedToken)) { - font.setFamily(trimmedToken); - return true; - } else { - // Can't find a family matching this name; if it's a generic family, - // try searching for the default family for it by using style hints. - QFont tmp; - int styleHint = -1; - if (fontFamilyToken.compare(QLatin1String("serif")) == 0) { - styleHint = QFont::Serif; - } else if (fontFamilyToken.compare(QLatin1String("sans-serif")) == 0) { - styleHint = QFont::SansSerif; - } else if (fontFamilyToken.compare(QLatin1String("cursive")) == 0) { - styleHint = QFont::Cursive; - } else if (fontFamilyToken.compare(QLatin1String("monospace")) == 0) { - styleHint = QFont::Monospace; - } else if (fontFamilyToken.compare(QLatin1String("fantasy")) == 0) { - styleHint = QFont::Fantasy; +/* + Returns a list of all of the families in \a fontFamiliesString, where + each family is separated by spaces. Families with spaces in their name + must be quoted. +*/ +static QStringList qExtractFontFamiliesFromString(const QString &fontFamiliesString) +{ + QStringList extractedFamilies; + int quoteIndex = -1; + QString currentFamily; + for (int index = 0; index < fontFamiliesString.size(); ++index) { + const QChar ch = fontFamiliesString.at(index); + if (ch == '"' || ch == '\'') { + if (quoteIndex == -1) { + quoteIndex = index; + } else { + if (ch == fontFamiliesString.at(quoteIndex)) { + // Found the matching quote. +1/-1 because we don't want the quote as part of the name. + const QString family = fontFamiliesString.mid(quoteIndex + 1, index - quoteIndex - 1); + extractedFamilies.push_back(family); + currentFamily.clear(); + quoteIndex = -1; + } else { + qWarning().nospace() << "Context2D: Mismatched quote in font string."; + return QStringList(); + } + } + } else if (ch == ' ' && quoteIndex == -1) { + // This is a space that's not within quotes... + if (!currentFamily.isEmpty()) { + // and there is a current family; consider it the end of the current family. + extractedFamilies.push_back(currentFamily); + currentFamily.clear(); + } // else: ignore the space + } else { + currentFamily.push_back(ch); + } + } + if (!currentFamily.isEmpty()) { + if (quoteIndex == -1) { + // This is the end of the string, so add this family to our list. + extractedFamilies.push_back(currentFamily); + } else { + qWarning().nospace() << "Context2D: Unclosed quote in font string."; + return QStringList(); } - if (styleHint != -1) { - tmp.setStyleHint(static_cast<QFont::StyleHint>(styleHint)); - font.setFamily(tmp.defaultFamily()); + } + if (extractedFamilies.isEmpty()) { + qWarning().nospace() << "Context2D: Missing or misplaced font family in font string" + << " (it must come after the font size)."; + } + return extractedFamilies; +} + +/*! + Tries to set a family on \a font using the families provided in \a fontFamilyTokens. + + The list is ordered by preference, with the first family having the highest preference. + If the first family is invalid, the next family in the list is evaluated. + This process is repeated until a valid font is found (at which point the function + will return \c true and the family set on \a font) or there are no more + families left, at which point a warning is printed and \c false is returned. +*/ +static bool qSetFontFamilyFromTokens(QFont &font, const QStringList &fontFamilyTokens) +{ + foreach (QString fontFamilyToken, fontFamilyTokens) { + QFontDatabase fontDatabase; + if (fontDatabase.hasFamily(fontFamilyToken)) { + font.setFamily(fontFamilyToken); return true; + } else { + // Can't find a family matching this name; if it's a generic family, + // try searching for the default family for it by using style hints. + int styleHint = -1; + if (fontFamilyToken.compare(QLatin1String("serif")) == 0) { + styleHint = QFont::Serif; + } else if (fontFamilyToken.compare(QLatin1String("sans-serif")) == 0) { + styleHint = QFont::SansSerif; + } else if (fontFamilyToken.compare(QLatin1String("cursive")) == 0) { + styleHint = QFont::Cursive; + } else if (fontFamilyToken.compare(QLatin1String("monospace")) == 0) { + styleHint = QFont::Monospace; + } else if (fontFamilyToken.compare(QLatin1String("fantasy")) == 0) { + styleHint = QFont::Fantasy; + } + if (styleHint != -1) { + QFont tmp; + tmp.setStyleHint(static_cast<QFont::StyleHint>(styleHint)); + font.setFamily(tmp.defaultFamily()); + return true; + } } } - qWarning().nospace() << "Context2D: The font family " << fontFamilyToken << " is invalid."; + qWarning("Context2D: The font families specified are invalid: %s", qPrintable(fontFamilyTokens.join(QString()).trimmed())); return false; } @@ -275,18 +359,63 @@ if (!(usedTokens & token)) { \ See: http://www.w3.org/TR/css3-fonts/#font-prop */ static QFont qt_font_from_string(const QString& fontString, const QFont ¤tFont) { - const QStringList tokens = fontString.split(QLatin1Char(' ')); - if (tokens.size() < 2) { - qWarning().nospace() << "Context2D: Insufficent amount of tokens in font string."; + if (fontString.isEmpty()) { + qWarning().nospace() << "Context2D: Font string is empty."; + return currentFont; + } + + // We know that font-size must be specified and it must be before font-family + // (which could potentially have "px" or "pt" in its name), so extract it now. + int fontSizeEnd = fontString.indexOf(QStringLiteral("px")); + if (fontSizeEnd == -1) + fontSizeEnd = fontString.indexOf(QStringLiteral("pt")); + if (fontSizeEnd == -1) { + qWarning().nospace() << "Context2D: Invalid font size unit in font string."; return currentFont; } + int fontSizeStart = fontString.lastIndexOf(' ', fontSizeEnd); + if (fontSizeStart == -1) { + // The font size might be the first token in the font string, which is OK. + // Regardless, we'll find out if the font is invalid with qSetFontSizeFromToken(). + fontSizeStart = 0; + } else { + // Don't want to take the leading space. + ++fontSizeStart; + } + + // + 2 for the unit, +1 for the space that we require. + fontSizeEnd += 3; + QFont newFont; - QStringList remainingTokens = tokens; + if (!qSetFontSizeFromToken(newFont, fontString.mid(fontSizeStart, fontSizeEnd - fontSizeStart))) + return currentFont; + + // We don't want to parse the size twice, so remove it now. + QString remainingFontString = fontString; + remainingFontString.remove(fontSizeStart, fontSizeEnd - fontSizeStart); + + // Next, we have to take any font families out, as QString::split() will ruin quoted family names. + const QString fontFamiliesString = remainingFontString.mid(fontSizeStart); + remainingFontString.chop(remainingFontString.length() - fontSizeStart); + QStringList fontFamilies = qExtractFontFamiliesFromString(fontFamiliesString); + if (fontFamilies.isEmpty()) { + return currentFont; + } + if (!qSetFontFamilyFromTokens(newFont, fontFamilies)) + return currentFont; + + // Now that we've removed the messy parts, we can split the font string on spaces. + const QString trimmedTokensStr = remainingFontString.trimmed(); + if (trimmedTokensStr.isEmpty()) { + // No optional properties. + return newFont; + } + const QStringList tokens = trimmedTokensStr.split(QLatin1Char(' ')); + int usedTokens = NoTokens; // Optional properties can be in any order, but font-size and font-family must be last. - while (remainingTokens.size() > 2) { - const QString token = remainingTokens.takeFirst(); + foreach (const QString token, tokens) { if (token.compare(QLatin1String("normal")) == 0) { if (!(usedTokens & FontStyle) || !(usedTokens & FontVariant) || !(usedTokens & FontWeight)) { // Could be font-style, font-variant or font-weight. @@ -330,19 +459,6 @@ static QFont qt_font_from_string(const QString& fontString, const QFont ¤t return currentFont; } } - if (remainingTokens.size() == 2) { - // Order must be: font-size font-family. - if (!qSetFontSizeFromToken(newFont, remainingTokens.first())) { - return currentFont; - } - if (!qSetFontFamilyFromToken(newFont, remainingTokens.last())) { - return currentFont; - } - return newFont; - } else { - qWarning().nospace() << "Context2D: Missing font-size and/or font-family tokens in font string."; - return currentFont; - } return newFont; } @@ -2399,8 +2515,9 @@ QV4::Value QQuickJSContext2DPrototype::method_caretBlinkRate(QV4::SimpleCallCont \li font-family: See \l {http://www.w3.org/TR/CSS2/fonts.html#propdef-font-family} \endlist - Note that font-size and font-family are mandatory and must be in the order - they are shown in above. + \note The font-size and font-family properties are mandatory and must be in + the order they are shown in above. In addition, a font family with spaces in + its name must be quoted. The default font value is "10px sans-serif". */ @@ -3648,10 +3765,12 @@ int baseLineOffset(QQuickContext2D::TextBaseLineType value, const QFontMetrics & int offset = 0; switch (value) { case QQuickContext2D::Top: + case QQuickContext2D::Hanging: break; - case QQuickContext2D::Alphabetic: case QQuickContext2D::Middle: - case QQuickContext2D::Hanging: + offset = (metrics.ascent() >> 1) + metrics.height() - metrics.ascent(); + break; + case QQuickContext2D::Alphabetic: offset = metrics.ascent(); break; case QQuickContext2D::Bottom: diff --git a/tests/auto/quick/qquickcanvasitem/data/tst_context.qml b/tests/auto/quick/qquickcanvasitem/data/tst_context.qml index ab351f0de8..b18250291e 100644 --- a/tests/auto/quick/qquickcanvasitem/data/tst_context.qml +++ b/tests/auto/quick/qquickcanvasitem/data/tst_context.qml @@ -111,6 +111,14 @@ Canvas { { string: "bold 12px sans-serif", expected: "sans-serif,-1,12,5,75,0,0,0,0,0" }, { string: "0 12px sans-serif", expected: "sans-serif,-1,12,5,0,0,0,0,0,0" }, { string: "small-caps 12px sans-serif", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px \"sans-serif\"", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px 'sans-serif'", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + // sans-serif will always be chosen, but this still tests that multiple families can be read. + { string: "12px 'sans-serif' 'cursive'", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px sans-serif 'cursive' monospace", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: "12px sans-serif 'cursive' monospace ", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: " 12px sans-serif 'cursive' monospace ", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" }, + { string: " 12px sans-serif 'cursive' monospace ", expected: "sans-serif,-1,12,5,50,0,0,0,0,0" } ]; for (var i = 0; i < validFonts.length; ++i) { ctx.font = validFonts[i].string; @@ -125,38 +133,51 @@ Canvas { var ctx = canvas.getContext("2d"); var originalFont = ctx.font; - var i = 0; - var insufficientQtyTokens = ["", "12px", "sans-serif"]; - for (i = 0; i < insufficientQtyTokens.length; ++i) { - ignoreWarning("Context2D: Insufficent amount of tokens in font string."); - ctx.font = insufficientQtyTokens[i]; - compare(ctx.font, originalFont); - } + var fontStrings = [ + "", + "12px", + "sans-serif", + "z12px sans-serif", + "1z2px sans-serif", + "12zpx sans-serif", + "12pxz sans-serif", + "sans-serif 12px", + "12px !@weeeeeeee!@!@Don'tNameYourFontThis", + "12px )(&*^^^%#$@*!!@#$JSPOR)", + "normal normal normal normal 12px sans-serif", + "normal normal bold bold 12px sans-serif", + "bold bold 12px sans-serif", + "12px 'cursive\"", + "12px 'cursive\" sans-serif", + "12px 'cursive" + ]; - var invalidFontSizes = ["z12px sans-serif", "1z2px sans-serif", "12zpx sans-serif", - "12pzx sans-serif", "12pxz sans-serif", "sans-serif 12px"]; - for (i = 0; i < invalidFontSizes.length; ++i) { - ignoreWarning("Context2D: A font size of \"" + invalidFontSizes[i].split(" ")[0] + "\" is invalid."); - ctx.font = invalidFontSizes[i]; - compare(ctx.font, originalFont); - } + var ignoredWarnings = [ + "Context2D: Font string is empty.", + "Context2D: Missing or misplaced font family in font string (it must come after the font size).", + "Context2D: Invalid font size unit in font string.", + "Context2D: A font size of \"z12\" is invalid.", + "Context2D: A font size of \"1z2\" is invalid.", + "Context2D: A font size of \"12z\" is invalid.", + "Context2D: Invalid font size unit in font string.", + "Context2D: Missing or misplaced font family in font string (it must come after the font size).", + "Context2D: Unclosed quote in font string.", + "Context2D: The font families specified are invalid: )(&*^^^%#$@*!!@#$JSPOR)", + "Context2D: Duplicate token \"normal\" found in font string.", + "Context2D: Duplicate token \"bold\" found in font string.", + "Context2D: Duplicate token \"bold\" found in font string.", + "Context2D: Mismatched quote in font string.", + "Context2D: Mismatched quote in font string.", + "Context2D: Unclosed quote in font string." + ]; - var invalidFontFamilies = ["12px !@weeeeeeee!@!@Don'tNameYourFontThis", "12px )(&*^^^%#$@*!!@#$JSPOR)"]; - for (i = 0; i < invalidFontFamilies.length; ++i) { - ignoreWarning("Context2D: The font family \"" + invalidFontFamilies[i].split(" ")[1] + "\" is invalid."); - ctx.font = invalidFontFamilies[i]; - compare(ctx.font, originalFont); - } + // Sanity check... + compare(ignoredWarnings.length, fontStrings.length); - var duplicates = [ - { duplicate: "normal", string: "normal normal normal normal 12px sans-serif" }, - { duplicate: "bold", string: "normal normal bold bold 12px sans-serif" }, - { duplicate: "bold", string: "bold bold 12px sans-serif" } - ]; - for (i = 0; i < duplicates.length; ++i) { - ignoreWarning("Context2D: Duplicate token \"" + duplicates[i].duplicate + "\" found in font string."); - ctx.font = duplicates[i].string; + for (var i = 0; i < fontStrings.length; ++i) { + ignoreWarning(ignoredWarnings[i]); + ctx.font = fontStrings[i]; compare(ctx.font, originalFont); } } |