diff --git a/doc/dev/doc-1.solved.mingw32.bug.txt b/doc/dev/doc-1.solved.mingw32.bug.txt index 55949106..ad0e03ea 100644 --- a/doc/dev/doc-1.solved.mingw32.bug.txt +++ b/doc/dev/doc-1.solved.mingw32.bug.txt @@ -1,21 +1,21 @@ Problem: On Windows Findutils find (version 4.4.2) provided with mingw32 is buggy. It won't properly change working directory within `execdir` command, when searching for `doc\Doxyfile` and `external\Doxyfile` files. Because of that generated documentation will be missing those directories. Investigation: Newer versions of find work as expected. Solution: One can replace mingw32 find with more recent version if one needs these directories to be generated on Windows. Path to find program can be overriden with `Makefile.user` file. -Anyways mingw32 is becomming obsolete and switching to mingw64 is desired. +Anyways mingw32 is becoming obsolete and switching to mingw64 is desired. diff --git a/extensions/CuteHMI.2/src/cutehmi/internal/QMLPlugin.cpp b/extensions/CuteHMI.2/src/cutehmi/internal/QMLPlugin.cpp index 982439f6..2e59ad3f 100644 --- a/extensions/CuteHMI.2/src/cutehmi/internal/QMLPlugin.cpp +++ b/extensions/CuteHMI.2/src/cutehmi/internal/QMLPlugin.cpp @@ -1,83 +1,83 @@ #include "QMLPlugin.hpp" #include #include #include #include #include #include // #ifdef DOXYGEN_WORKAROUND namespace CuteHMI { /** * Exposes cutehmi::Message to QML. */ class Message: public cutehmi::Message {}; /** * Exposes cutehmi::Notification to QML. */ -class Notificatoin: public cutehmi::Notificatoin {}; +class Notification: public cutehmi::Notification {}; /** * Exposes cutehmi::Messenger to QML. */ class Messenger: public cutehmi::Messenger {}; /** * Exposes cutehmi::Notifier to QML. */ class Notifier: public cutehmi::Notifier {}; } #endif // namespace cutehmi { namespace internal { /** * Register QML types. * @param uri URI. */ void QMLPlugin::registerTypes(const char * uri) { Q_ASSERT(uri == QLatin1String("CuteHMI")); qmlRegisterType(uri, CUTEHMI_MAJOR, 0, "Message"); qmlRegisterType(uri, CUTEHMI_MAJOR, 0, "Notification"); qmlRegisterSingletonType(uri, CUTEHMI_MAJOR, 0, "Messenger", MessengerProvider); qmlRegisterSingletonType(uri, CUTEHMI_MAJOR, 0, "Notifier", NotifierProvider); } QObject * QMLPlugin::MessengerProvider(QQmlEngine * engine, QJSEngine * scriptEngine) { Q_UNUSED(scriptEngine) cutehmi::Messenger * messenger = & cutehmi::Messenger::Instance(); engine->setObjectOwnership(messenger, QQmlEngine::CppOwnership); return messenger; } QObject * QMLPlugin::NotifierProvider(QQmlEngine * engine, QJSEngine * scriptEngine) { Q_UNUSED(scriptEngine) cutehmi::Notifier * notifier = & cutehmi::Notifier::Instance(); engine->setObjectOwnership(notifier, QQmlEngine::CppOwnership); return notifier; } } } //(c)C: Copyright © 2018-2020, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI.2/src/cutehmi/internal/singleton.cpp b/extensions/CuteHMI.2/src/cutehmi/internal/singleton.cpp index f9fafe2b..b297c2bd 100644 --- a/extensions/CuteHMI.2/src/cutehmi/internal/singleton.cpp +++ b/extensions/CuteHMI.2/src/cutehmi/internal/singleton.cpp @@ -1,89 +1,89 @@ #include "../../../include/cutehmi/internal/singleton.hpp" #include namespace { class SingletonDestroyWrapper { public: SingletonDestroyWrapper(cutehmi::internal::singletonDestroyCallback callback): m_callback(callback) { } [[gnu::unused]] bool operator ==(const SingletonDestroyWrapper & other) const { return m_callback == other.m_callback; } void call() { m_callback(); } operator uintptr_t() const { return reinterpret_cast(m_callback); } private: cutehmi::internal::singletonDestroyCallback m_callback; }; [[gnu::unused]] uint qHash(const SingletonDestroyWrapper & key) { return ::qHash(static_cast(key)); } // // Container should prserve order in which elements were added, so that singletons can be destroyed in reverse order as they // were added. This disqualifies QSet. typedef QList SingletonDestroyFunctionsContainer; - // Elements are prpeneded to this list (see storeSingletonDestroyCallback()). + // Elements are prepended to this list (see storeSingletonDestroyCallback()). SingletonDestroyFunctionsContainer singletonDestroyFunctions; // } namespace cutehmi { namespace internal { void destroySingletonInstances() { // SingletonDestroyFunctionsContainer copy = singletonDestroyFunctions; // // If Singleton called Destroy() it would invalidate copy and its iterators. for (auto it = copy.begin(); it != copy.end(); ++it) it->call(); // Call Singleton::Destroy(), which will remove callback from original singletonDestroyFunctions. // // } void storeSingletonDestroyCallback(singletonDestroyCallback callback) { // // Callbacks should be removed in reverse order as they were added. With prepending removeSingletonDestroyCallback() should be // able to remove callbacks pretty fast, if accessed through destroySingletonInstances(), because QList::removeOne() will find // each removed callback at the beginning of the list. singletonDestroyFunctions.prepend(callback); // } void removeSingletonDestroyCallback(singletonDestroyCallback callback) { singletonDestroyFunctions.removeOne(callback); } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI.2/tests/test_functions.cpp b/extensions/CuteHMI.2/tests/test_functions.cpp index 5f10cb6f..b3cb959d 100644 --- a/extensions/CuteHMI.2/tests/test_functions.cpp +++ b/extensions/CuteHMI.2/tests/test_functions.cpp @@ -1,101 +1,101 @@ #include #include #include namespace cutehmi { class test_functions: public QObject { Q_OBJECT private slots: void testApe(); void testAle(); void testClt(); void testCgt(); }; void test_functions::testApe() { QCOMPARE(ape(0.0, 1.0e-100, 0.25), true); QCOMPARE(ape(0.0, 0.24, 0.25), true); QCOMPARE(ape(0.0, 0.25, 0.25), true); QCOMPARE(ape(0.0, 0.26, 0.25), false); QCOMPARE(ape(1.0, 1.49, 0.25), true); QCOMPARE(ape(1.0, 1.51, 0.25), false); QCOMPARE(ape(1.0, 1.99, 0.25), false); - QCOMPARE(ape(1.0, 2.0, 0.25), true); // Weirdness comming from the fact that <= is used in comparison. + QCOMPARE(ape(1.0, 2.0, 0.25), true); // Weirdness coming from the fact that <= is used in comparison. QCOMPARE(ape(1.0, 2.01, 0.25), false); QCOMPARE(ape(1.01, 1.5, 0.25), true); QCOMPARE(ape(1.01, 1.52, 0.25), false); QCOMPARE(ape(1.01, 1.99, 0.25), false); QCOMPARE(ape(1.01, 2.0, 0.25), true); QCOMPARE(ape(1.01, 2.01, 0.25), true); QCOMPARE(ape(0.0, 0.0), true); QCOMPARE(ape(0.5, 0.5), true); QCOMPARE(ape(0.5e16, 0.5e16), true); QCOMPARE(ape(0.0, 0.0625), false); QCOMPARE(ape(0.5, 0.5 + 0.0625), false); qreal r = test::randExp(0, std::numeric_limits::max_exponent); QCOMPARE(ape(r, r + std::numeric_limits::epsilon() * 0.5), true); QCOMPARE(ape(r, 1.0625 * r), false); } void test_functions::testAle() { QCOMPARE(ale(0.0, 1.0e-100, 0.25), false); // Problematic behavior of ale() function. QCOMPARE(ale(1.0, 1.125, 0.25), true); QCOMPARE(ale(1.0, 1.99, 0.25), false); QCOMPARE(ale(1.0, 2.0, 0.25), false); QCOMPARE(ale(1.0, 2.01, 0.25), false); QCOMPARE(ale(2.0, 2.01, 0.25), true); } void test_functions::testClt() { QCOMPARE(clt(0.0, 0.0, 0.25), false); QCOMPARE(clt(-0.125, 0.0, 0.25), false); QCOMPARE(clt(-0.5, 0.0, 0.25), true); QCOMPARE(clt(-1000, -100, 0.25), true); QCOMPARE(clt(-1000, -999, 0.25), false); QCOMPARE(clt(1000, 2000, 0.25), true); QCOMPARE(clt(1000, 1001, 0.25), false); } void test_functions::testCgt() { QCOMPARE(cgt(0.0, 0.0, 0.25), false); QCOMPARE(cgt(0.0, -0.125, 0.25), false); QCOMPARE(cgt(0.0, -0.5, 0.25), true); QCOMPARE(cgt(-100, -1000, 0.25), true); QCOMPARE(cgt(-999, -1000, 0.25), false); QCOMPARE(cgt(2000, 1000, 0.25), true); QCOMPARE(cgt(1001, 1000, 0.25), false); } } QTEST_MAIN(cutehmi::test_functions) #include "test_functions.moc" //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/BitcoinCash.0/src/cutehmi/bitcoincash/Address.cpp b/extensions/CuteHMI/BitcoinCash.0/src/cutehmi/bitcoincash/Address.cpp index e9b6139e..1724b55d 100644 --- a/extensions/CuteHMI/BitcoinCash.0/src/cutehmi/bitcoincash/Address.cpp +++ b/extensions/CuteHMI/BitcoinCash.0/src/cutehmi/bitcoincash/Address.cpp @@ -1,250 +1,250 @@ #include "../../../include/cutehmi/bitcoincash/Address.hpp" #include #include #include #include namespace cutehmi { namespace bitcoincash { const QString Address::REQUEST_DETAILS_URL = "https://rest.bitcoin.com/v1/address/details/"; Address::Address(QObject * parent): QObject(parent), m(new Members) { connect(& m->networkAccessManager, & QNetworkAccessManager::finished, this, & Address::onNetworkAccessManagerFinished); connect(this, & Address::addressChanged, this, & Address::update); } QString Address::address() const { return m->address; } void Address::setAddress(const QString & address) { if (m->address != address) { m->address = address; emit addressChanged(); } } bool Address::updated() const { return m->updated; } double Address::balance() const { return m->balance; } double Address::totalReceived() const { return m->totalReceived; } double Address::totalSent() const { return m->totalSent; } double Address::unconfirmedBalance() const { return m->unconfirmedBalance; } int Address::unconfirmedTxApperances() const { return m->unconfirmedTxAppearances; } int Address::txAppearances() const { return m->txAppearances; } QStringList Address::transactions() const { return m->transactions; } QString Address::legacyAddress() const { return m->legacyAddress; } QString Address::cashAddress() const { return m->cashAddress; } double Address::zeroConfReceived() const { return m->zeroConfReceived; } void Address::update() { m->networkAccessManager.get(QNetworkRequest(QUrl(REQUEST_DETAILS_URL + address()))); } void Address::setUpdated(bool updated) { if (m->updated != updated) { m->updated = updated; emit updatedChanged(); } } void Address::setBalance(double balance) { if (m->balance != balance) { m->balance = balance; emit balanceChanged(); } } void Address::setTotalReceived(double totalReceived) { if (m->totalReceived != totalReceived) { m->totalReceived = totalReceived; emit totalReceivedChanged(); } } void Address::setTotalSent(double totalSent) { if (m->totalSent != totalSent) { m->totalSent = totalSent; emit totalSentChanged(); } } void Address::setUnconfirmedBalance(double unconfirmedBalance) { if (m->unconfirmedBalance != unconfirmedBalance) { m->unconfirmedBalance = unconfirmedBalance; emit unconfirmedBalanceChanged(); } } void Address::setUnconfirmedTxAppearances(int unconfirmedTxAppearances) { if (m->unconfirmedTxAppearances != unconfirmedTxAppearances) { m->unconfirmedTxAppearances = unconfirmedTxAppearances; emit unconfirmedTxAppearancesChanged(); } } void Address::setTxAppearances(int txAppearances) { if (m->txAppearances != txAppearances) { m->txAppearances = txAppearances; emit txAppearancesChanged(); } } void Address::setTransactions(const QStringList & transactions) { if (m->transactions != transactions) { m->transactions = transactions; emit transactionsChanged(); } } void Address::setLegacyAddress(const QString & legacyAddess) { if (m->legacyAddress != legacyAddess) { m->legacyAddress = legacyAddess; emit legacyAddressChanged(); } } void Address::setCashAddress(const QString & cashAddress) { if (m->cashAddress != cashAddress) { m->cashAddress = cashAddress; emit cashAddressChanged(); } } void Address::setZeroConfReceived(double zeroConfReceived) { if (m->zeroConfReceived != zeroConfReceived) { m->zeroConfReceived = zeroConfReceived; emit zeroConfReceivedChanged(); } } void Address::onNetworkAccessManagerFinished(QNetworkReply * reply) { if (reply->error() == QNetworkReply::NoError) { QJsonDocument jsonReply = QJsonDocument::fromJson(reply->readAll()); if (!jsonReply.isNull() || !jsonReply.isObject()) { try { typedef QList KeysContainer; QJsonObject json = jsonReply.object(); // Check if keys exist. KeysContainer keys = { "balance", "totalReceived", "totalSent", "unconfirmedBalance", "unconfirmedTxApperances", // AppErances (sic!). "txApperances", // AppErances (sic!). "transactions", "legacyAddress", "cashAddress" }; for (KeysContainer::const_iterator it = keys.begin(); it != keys.end(); ++it) if (!json.contains(*it)) throw std::runtime_error(*it); setBalance(json["balance"].toDouble()); setTotalReceived(json["totalReceived"].toDouble()); setTotalSent(json["totalSent"].toDouble()); setUnconfirmedBalance(json["unconfirmedBalance"].toDouble()); setUnconfirmedTxAppearances(json["unconfirmedTxApperances"].toInt()); // AppErances (sic!). setTxAppearances(json["txApperances"].toInt()); // AppErances (sic!). QJsonArray transactions = json["transactions"].toArray(); QStringList transactionsList; for (QJsonArray::const_iterator it = transactions.begin(); it != transactions.end(); ++it) transactionsList.append(it->toString()); setTransactions(transactionsList); setLegacyAddress(json["legacyAddress"].toString()); setCashAddress(json["cashAddress"].toString()); setZeroConfReceived(totalReceived() + (unconfirmedBalance() > 0.0 ? unconfirmedBalance() : 0.0)); setUpdated(true); } catch (const std::runtime_error & e) { CUTEHMI_CRITICAL("Could not find '" << e.what() << "' key in JSON reply."); setUpdated(false); } } else { - CUTEHMI_WARNING("Could not parse nework reply in JSON format."); + CUTEHMI_WARNING("Could not parse network reply in JSON format."); setUpdated(false); } } else { CUTEHMI_WARNING("Netowrk request for URL '" << reply->request().url().toString() << "' has failed. " << reply->errorString() << "."); setUpdated(false); } reply->deleteLater(); emit updateFinished(); } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Examples/Modbus/Requests.0/DiagnosticsControl.qml b/extensions/CuteHMI/Examples/Modbus/Requests.0/DiagnosticsControl.qml index 39c5ad30..ab4983c1 100644 --- a/extensions/CuteHMI/Examples/Modbus/Requests.0/DiagnosticsControl.qml +++ b/extensions/CuteHMI/Examples/Modbus/Requests.0/DiagnosticsControl.qml @@ -1,83 +1,83 @@ import QtQuick 2.0 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.0 import CuteHMI.Modbus 2.0 RowLayout { property AbstractDevice device property int diagnosticsData Button { text: "Diagnostics" onClicked: device.requestDiagnostics(subfunctionBox.value, parent.diagnosticsData) } Label { text: qsTr("Subfunction:") } ComboBox { id: subfunctionBox implicitWidth: 250 textRole: "key" model: ListModel { ListElement { key: qsTr("Return query data"); value: AbstractDevice.DIAGNOSTICS_RETURN_QUERY_DATA } ListElement { key: qsTr("Restart communications option"); value: AbstractDevice.DIAGNOSTICS_RESTART_COMM_OPTION } ListElement { key: qsTr("Return diagnostics register"); value: AbstractDevice.DIAGNOSTICS_RETURN_DIAGNOSTICS_REGISTER } - ListElement { key: qsTr("Change ASCII input delimeter"); value: AbstractDevice.DIAGNOSTICS_CHANGE_ASCII_INPUT_DELIMITER } + ListElement { key: qsTr("Change ASCII input delimiter"); value: AbstractDevice.DIAGNOSTICS_CHANGE_ASCII_INPUT_DELIMITER } ListElement { key: qsTr("Force listen only mode"); value: AbstractDevice.DIAGNOSTICS_FORCE_LISTEN_ONLY_MODE } ListElement { key: qsTr("Clear counters and diagnostic register"); value: AbstractDevice.DIAGNOSTICS_CLEAR_COUNTERS_AND_DIAGNOSTICS_REGISTER } ListElement { key: qsTr("Return bus message count"); value: AbstractDevice.DIAGNOSTICS_RETURN_BUS_MESSAGE_COUNT } ListElement { key: qsTr("Return bus communication error count"); value: AbstractDevice.DIAGNOSTICS_RETURN_BUS_COMM_ERROR_COUNT } ListElement { key: qsTr("Return bus exception error count"); value: AbstractDevice.DIAGNOSTICS_RETURN_BUS_EXCEPTION_ERROR_COUNT } ListElement { key: qsTr("Return slave message count"); value: AbstractDevice.DIAGNOSTICS_RETURN_SLAVE_MESSAGE_COUNT } ListElement { key: qsTr("Return slave no response count"); value: AbstractDevice.DIAGNOSTICS_RETURN_SLAVE_NO_RESPONSE_COUNT } ListElement { key: qsTr("Return slave NAK count"); value: AbstractDevice.DIAGNOSTICS_RETURN_SLAVE_NAK_COUNT } ListElement { key: qsTr("Return slave busy count"); value: AbstractDevice.DIAGNOSTICS_RETURN_SLAVE_BUSY_COUNT } ListElement { key: qsTr("Return bus character overrun count"); value: AbstractDevice.DIAGNOSTICS_RETURN_BUS_CHARACTER_OVERRUN_COUNT } ListElement { key: qsTr("Return IOP overrun count"); value: AbstractDevice.DIAGNOSTICS_RETURN_IOP_OVERRUN_COUNT } ListElement { key: qsTr("Clear overrun counter and flag"); value: AbstractDevice.DIAGNOSTICS_CLEAR_OVERRUN_COUNTER_AND_FLAG } ListElement { key: qsTr("Get/Clear modbus plus statistics"); value: AbstractDevice.DIAGNOSTICS_GET_CLEAR_MODBUS_PLUS_STATISTICS } } property int value: model.get(currentIndex).value onActivated: value = model.get(index).value } Label { text: qsTr("Data:") } TextField { text: parent.diagnosticsData validator: IntValidator { bottom: 0 top: 65535 } onAccepted: parent.diagnosticsData = Number.fromLocaleString(locale, text) } Label { text: qsTr("Data (hex):") } TextField { text: "0x" + parent.diagnosticsData.toString(16) validator: RegExpValidator { regExp: /0x[0-9A-Fa-f]{1,4}/ } onAccepted: parent.diagnosticsData = parseInt(text) } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Examples/Symbols/HVAC/Gallery.0/Main.qml b/extensions/CuteHMI/Examples/Symbols/HVAC/Gallery.0/Main.qml index e21de493..faa9e8f9 100644 --- a/extensions/CuteHMI/Examples/Symbols/HVAC/Gallery.0/Main.qml +++ b/extensions/CuteHMI/Examples/Symbols/HVAC/Gallery.0/Main.qml @@ -1,280 +1,280 @@ import QtQml 2.2 import QtQuick 2.11 import QtQuick.Controls 2.4 import QtQuick.Layouts 1.3 import CuteHMI.GUI 0.0 import CuteHMI.Symbols.HVAC 0.0 /** %Main component. */ Rectangle { anchors.fill: parent color: CuteApplication.theme.palette.background ColumnLayout { anchors.fill: parent anchors.margins: 10 spacing: 20 RowLayout { Label { text: qsTr("Size:") } Slider { from: 10 to: 240 value: CuteApplication.theme.units.quadrat onValueChanged: CuteApplication.theme.units.quadrat = value } } Flickable { Layout.fillHeight: true Layout.fillWidth: true ScrollBar.vertical: ScrollBar {} ScrollBar.horizontal: ScrollBar {} contentWidth: row.width contentHeight: row.height Row { id: row GridLayout { rows: 3 flow: GridLayout.TopToBottom Label { Layout.alignment: Qt.AlignCenter text: qsTr("Air filter") } AirFilter { id: filter Layout.alignment: Qt.AlignCenter } AirFilterSettings { Layout.alignment: Qt.AlignTop filter: filter } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Basic cooler") } BasicCooler { id: basicCooler Layout.alignment: Qt.AlignCenter } ElementSettings { Layout.alignment: Qt.AlignTop element: basicCooler } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Basic discrete instrument") } BasicDiscreteInstrument { id: basicDiscreteInstrument Layout.alignment: Qt.AlignCenter } BasicDiscreteInstrumentSettings { Layout.alignment: Qt.AlignTop instrument: basicDiscreteInstrument } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Basic heater") } BasicHeater { id: basicHeater Layout.alignment: Qt.AlignCenter } ElementSettings { Layout.alignment: Qt.AlignTop element: basicHeater } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Blade damper") } BladeDamper { id: bladeDamper Layout.alignment: Qt.AlignCenter } BladeDamperSettings { Layout.alignment: Qt.AlignTop damper: bladeDamper } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Centrifugal fan") } CentrifugalFan { id: centrifugalFan Layout.alignment: Qt.AlignCenter } CentrifugalFanSettings { Layout.alignment: Qt.AlignTop fan: centrifugalFan } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Cooler") } Cooler { id: cooler Layout.alignment: Qt.AlignCenter } ElementSettings { Layout.alignment: Qt.AlignTop element: cooler } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Heater") } Heater { id: heater Layout.alignment: Qt.AlignCenter } ElementSettings { Layout.alignment: Qt.AlignTop element: heater } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Heat exchanger") } HeatExchanger { id: heatExchanger Layout.alignment: Qt.AlignCenter } ElementSettings { Layout.alignment: Qt.AlignTop element: heatExchanger } Label { Layout.alignment: Qt.AlignCenter - text: qsTr("Heat revovery wheel") + text: qsTr("Heat recovery wheel") } HeatRecoveryWheel { id: wheel Layout.alignment: Qt.AlignCenter } HeatRecoveryWheelSettings { Layout.alignment: Qt.AlignTop wheel: wheel } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Motor actuator") } MotorActuator { id: motorActuator Layout.alignment: Qt.AlignCenter } MotorActuatorSettings { Layout.alignment: Qt.AlignTop actuator: motorActuator } Label { Layout.alignment: Qt.AlignCenter text: qsTr("Pump") } Pump { id: pump Layout.alignment: Qt.AlignCenter } PumpSettings { Layout.alignment: Qt.AlignTop pump: pump } } } } } } //(c)C: Copyright © 2020, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/Line.cpp b/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/Line.cpp index 6915af7c..bfd02fee 100644 --- a/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/Line.cpp +++ b/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/Line.cpp @@ -1,238 +1,238 @@ #include namespace cutehmi { namespace gpio { Line::Line(gpiod_line * line, QObject * parent): QObject(parent), m(new Members(line)) { readLineInfo(); connect(& m->monitorThread, & internal::LineEventMonitorThread::eventDetected, this, & Line::handleLineEvent); } Line::~Line() { releaseLine(); } int Line::value() const { return m->value; } void Line::setValue(int value) { if (m->value != value) { m->value = value; emit valueChanged(); } } QString Line::name() const { return m->name; } LineConfig * Line::config() const { return m->config; } void Line::setConfig(LineConfig * config) { if (m->config != config) { m->config = config; if (m->config) requestLine(); else releaseLine(); emit configChanged(); } } QString Line::consumer() const { CUTEHMI_WARNING("line consumer " << m->consumer); return m->consumer; } void Line::setConsumer(const QString & consumer) { if (m->consumer != consumer) { m->consumer = consumer.toUtf8(); if (m->config) requestLine(); emit consumerChanged(); } } bool Line::used() const { return m->used; } void Line::requestLine() { CUTEHMI_ASSERT(m->config != nullptr, "config must not be nullptr"); // Release line (if it was acquired). releaseLine(); // Set consumer. // Overcome weird behavior of libgpiod, which sets consumer to "?", if empty string is provided in the request. if (m->consumer.isEmpty()) m->consumer = "Unnamed Consumer"; m->requestConfig.consumer = m->consumer.data(); - // Configure reuest type. + // Configure request type. switch (m->config->direction()) { case LineConfig::DIRECTION_OUTPUT: m->requestConfig.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT; // Set Qt::DirectConnection explicitly, because it is important to deliver all valueChanged() signals. connect(this, & Line::valueChanged, this, & Line::requestValue, Qt::DirectConnection); break; case LineConfig::DIRECTION_INPUT: m->requestConfig.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES; break; default: CUTEHMI_CRITICAL("Unrecognized line direction code (" << m->config->direction() << ")."); } // Configure flags. m->requestConfig.flags = 0; if (m->config->openDrain()) m->requestConfig.flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN; if (m->config->openSource()) m->requestConfig.flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE; switch (m->config->activeState()) { case LineConfig::ACTIVE_STATE_LOW: m->requestConfig.flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW; break; case LineConfig::ACTIVE_STATE_HIGH: // High is default (i.e. flags = 0). break; default: CUTEHMI_CRITICAL("Unrecognized active state code (" << m->config->activeState() << ")."); } // Request line. gpiod_line_request(m->line, & m->requestConfig, 0); // Verify if config settings have been successfully set. ///@todo Turn CUTEHMI_DIE macros to exceptions. switch (m->config->direction()) { case LineConfig::DIRECTION_OUTPUT: if (gpiod_line_direction(m->line) != GPIOD_LINE_DIRECTION_OUTPUT) CUTEHMI_DIE("Requested direction and the one that line has been set to do not match."); break; case LineConfig::DIRECTION_INPUT: if (gpiod_line_direction(m->line) != GPIOD_LINE_DIRECTION_INPUT) CUTEHMI_DIE("Requested direction and the one that line has been set to do not match."); break; default: CUTEHMI_CRITICAL("Unrecognized line direction code (" << m->config->direction() << ")."); } switch (m->config->activeState()) { case LineConfig::ACTIVE_STATE_LOW: if (gpiod_line_active_state(m->line) != GPIOD_LINE_ACTIVE_STATE_LOW) CUTEHMI_DIE("Requested active state and the one that line has been set to do not match."); break; case LineConfig::ACTIVE_STATE_HIGH: if (gpiod_line_active_state(m->line) != GPIOD_LINE_ACTIVE_STATE_HIGH) CUTEHMI_DIE("Requested active state and the one that line has been set to do not match."); break; default: CUTEHMI_CRITICAL("Unrecognized active state code (" << m->config->activeState() << ")."); } if (m->config->openDrain() && (!gpiod_line_is_open_drain(m->line))) CUTEHMI_DIE("Could not set requested open drain configuration."); if (m->config->openSource() && (!gpiod_line_is_open_source(m->line))) CUTEHMI_DIE("Could not set requested open source configuration."); if (m->consumer != gpiod_line_consumer(m->line)) CUTEHMI_DIE("Requested consumer name ('%s') and the one that has been assigned to the line ('%s') do not match", m->consumer.data(), gpiod_line_consumer(m->line)); // Update line info. readLineInfo(); // Start monitoring thread for input direction. if (m->config->direction() == LineConfig::DIRECTION_INPUT) { m->monitorThread.setLine(m->line); m->monitorThread.start(); } } void Line::releaseLine() { if (gpiod_line_is_requested(m->line)) { m->monitorThread.requestInterruption(); m->monitorThread.wait(); disconnect(this, & Line::valueChanged, this, & Line::requestValue); gpiod_line_release(m->line); readLineInfo(); } } void Line::requestValue() { gpiod_line_set_value(m->line, m->value); } void Line::handleLineEvent(gpiod_line_event event) { switch (event.event_type) { case GPIOD_LINE_EVENT_RISING_EDGE: setValue(1); break; case GPIOD_LINE_EVENT_FALLING_EDGE: setValue(0); break; default: CUTEHMI_WARNING("Unrecognized line event type (" << event.event_type << ")."); } } void Line::readLineInfo() { if (gpiod_line_needs_update(m->line)) gpiod_line_update(m->line); m->name = gpiod_line_name(m->line); m->consumer = gpiod_line_consumer(m->line); m->used = gpiod_line_is_used(m->line); } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/internal/LineEventMonitorThread.cpp b/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/internal/LineEventMonitorThread.cpp index f7609798..bd4ee5ed 100644 --- a/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/internal/LineEventMonitorThread.cpp +++ b/extensions/CuteHMI/GPIO.0/src/cutehmi/gpio/internal/LineEventMonitorThread.cpp @@ -1,50 +1,50 @@ #include namespace cutehmi { namespace gpio { namespace internal { LineEventMonitorThread::LineEventMonitorThread(QObject * parent): QThread(parent), m(new Members) { Register_gpiod_line_event(); } void LineEventMonitorThread::setLine(gpiod_line * line) { m->line = line; } void LineEventMonitorThread::run() { while (1) { if (QThread::currentThread()->isInterruptionRequested()) break; int waitResult = gpiod_line_event_wait(m->line, & m->timeout); if (waitResult == 1) { gpiod_line_event event; if (gpiod_line_event_read(m->line, & event) == 0) emit eventDetected(event); else - CUTEHMI_WARNING("An error occured while reading line event."); + CUTEHMI_WARNING("An error occurred while reading line event."); } else if (waitResult == -1) - CUTEHMI_WARNING("An error occured while waiting for line event."); + CUTEHMI_WARNING("An error occurred while waiting for line event."); } } int LineEventMonitorThread::Register_gpiod_line_event() { static int id = qRegisterMetaType(); return id; } } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/AbstractRegisterController.hpp b/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/AbstractRegisterController.hpp index 80c9fbfc..54599d61 100644 --- a/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/AbstractRegisterController.hpp +++ b/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/AbstractRegisterController.hpp @@ -1,135 +1,135 @@ #ifndef H_EXTENSIONS_CUTEHMI_MODBUS_2_INCLUDE_CUTEHMI_MODBUS_ABSTRACTREGISTERCONTROLLER_HPP #define H_EXTENSIONS_CUTEHMI_MODBUS_2_INCLUDE_CUTEHMI_MODBUS_ABSTRACTREGISTERCONTROLLER_HPP #include "internal/common.hpp" #include "AbstractDevice.hpp" #include #include namespace cutehmi { namespace modbus { class CUTEHMI_MODBUS_API AbstractRegisterController: public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) public: enum WriteMode { WRITE_DELAYED, WRITE_POSTPONED, WRITE_IMMEDIATE, WRITE_EXPLICIT, }; Q_ENUM(WriteMode) static constexpr unsigned int INITIAL_ADDRESS = 0; static constexpr WriteMode INITIAL_WRITE_MODE = WRITE_DELAYED; static constexpr int INITIAL_WRITE_DELAY = 500; static constexpr bool INITIAL_BUSY = true; static constexpr bool INITIAL_READ_ON_WRITE = true; Q_PROPERTY(AbstractDevice * device READ device WRITE setDevice NOTIFY deviceChanged) - // Note: unsigned int is guranteed to be at least 16 bits wide by the standard. Using unsigned int instead of quint16 (aka + // Note: unsigned int is guaranteed to be at least 16 bits wide by the standard. Using unsigned int instead of quint16 (aka // ushort), because when using quint16 QML throws an error (unsupported type "ushort") for an alias to quint16 property. Q_PROPERTY(unsigned int address READ address WRITE setAddress NOTIFY addressChanged) Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) Q_PROPERTY(bool readOnWrite READ readOnWrite WRITE setReadOnWrite NOTIFY readOnWriteChanged) Q_PROPERTY(WriteMode writeMode READ writeMode WRITE setWriteMode NOTIFY writeModeChanged) Q_PROPERTY(int writeDelay READ writeDelay WRITE setWriteDelay NOTIFY writeDelayChanged) AbstractRegisterController(QObject * parent = nullptr); AbstractDevice * device() const; void setDevice(AbstractDevice * device); unsigned int address() const; void setAddress(unsigned int address); bool busy() const; bool readOnWrite() const; void setReadOnWrite(bool readOnWrite); WriteMode writeMode() const; void setWriteMode(WriteMode writeMode); int writeDelay() const; void setWriteDelay(int writeDelay); signals: void deviceChanged(); void addressChanged(); void busyChanged(); void readOnWriteChanged(); void writeModeChanged(); void writeDelayChanged(); protected: virtual void requestReadRegisters(quint16 address, quint16 amount, QUuid * requestId) const = 0; virtual quint16 bytes() const = 0; virtual void onDeviceDestroyed() = 0; void classBegin() override; void componentComplete() override; void setBusy(bool busy); protected slots: virtual void onRequestCompleted(QJsonObject request, QJsonObject reply) = 0; private: bool deviceReady() const; struct Members { AbstractDevice * device; unsigned int address; bool busy; bool readOnWrite; WriteMode writeMode; int writeDelay; bool postponedWritePending; bool deferRequestRead; Members(): device(nullptr), address(INITIAL_ADDRESS), busy(INITIAL_BUSY), readOnWrite(INITIAL_READ_ON_WRITE), writeMode(INITIAL_WRITE_MODE), writeDelay(INITIAL_WRITE_DELAY), deferRequestRead(false) { } }; MPtr m; }; } } #endif //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/internal/functions.hpp b/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/internal/functions.hpp index 394cdfc9..b830b8e7 100644 --- a/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/internal/functions.hpp +++ b/extensions/CuteHMI/Modbus.2/include/cutehmi/modbus/internal/functions.hpp @@ -1,91 +1,91 @@ #ifndef H_EXTENSIONS_CUTEHMI_MODBUS_2_INCLUDE_CUTEHMI_MODBUS_INTERNAL_FUNCTIONS_HPP #define H_EXTENSIONS_CUTEHMI_MODBUS_2_INCLUDE_CUTEHMI_MODBUS_INTERNAL_FUNCTIONS_HPP #include "common.hpp" #include namespace cutehmi { namespace modbus { namespace internal { template T toBigEndian(T src) { // Will use Qt endian functions, but need to assert that uchar is 8 bit wide. Qt implementation seems to be unaware that // sizeof(char) == 1 byte == not necessarily 8 bits. static_assert(std::numeric_limits::digits == 8, "uchar must be 8 bit wide"); return qToBigEndian(src); } template T fromBigEndian(T src) { // Will use Qt endian functions, but need to assert that uchar is 8 bit wide. Qt implementation seems to be unaware that // sizeof(char) == 1 byte == not necessarily 8 bits. static_assert(std::numeric_limits::digits == 8, "uchar must be 8 bit wide"); return qFromBigEndian(src); } template T toLittleEndian(T src) { // Will use Qt endian functions, but need to assert that uchar is 8 bit wide. Qt implementation seems to be unaware that // sizeof(char) == 1 byte == not necessarily 8 bits. static_assert(std::numeric_limits::digits == 8, "uchar must be 8 bit wide"); return qToLittleEndian(src); } template T fromLittleEndian(T src) { // Will use Qt endian functions, but need to assert that uchar is 8 bit wide. Qt implementation seems to be unaware that // sizeof(char) == 1 byte == not necessarily 8 bits. static_assert(std::numeric_limits::digits == 8, "uchar must be 8 bit wide"); return qFromLittleEndian(src); } /** * Store integer as 16 bit unsigned integer. This is convenient function that casts @p int to 16 bit integer and then stores it as * 16 bit unsigned integer. - * @param value value to be stored. Must be within 16 bit interger range (@p int is guranteed to be at least 16 bits wide by the + * @param value value to be stored. Must be within 16 bit interger range (@p int is guaranteed to be at least 16 bits wide by the * standard). * @return value converted to 16 bit unsigned integer. * * @remark It is assumed that @a value uses two's complement convention (one's complement machines are probably non-existent). */ quint16 CUTEHMI_MODBUS_PRIVATE intToUint16(int value); /** * Store 16 bit integer as 16 bit unsigned integer. * @param value value to be stored. * @return value converted to 16 bit unsigned integer. * * @remark It is assumed that @a value uses two's complement convention (one's complement machines are probably non-existent). */ quint16 CUTEHMI_MODBUS_PRIVATE int16ToUint16(qint16 value); /** * Restore 16 bit integer from 16 bit unsigned integer. * @param value value to be restored. * @return value converted to 16 bit integer. * * @remark It is assumed that @a value uses two's complement convention (one's complement machines are probably non-existent). */ qint16 CUTEHMI_MODBUS_PRIVATE int16FromUint16(quint16 value); } } } #endif //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/Register16Controller.cpp b/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/Register16Controller.cpp index 02921c63..585c9c11 100644 --- a/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/Register16Controller.cpp +++ b/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/Register16Controller.cpp @@ -1,205 +1,205 @@ #include #include namespace cutehmi { namespace modbus { constexpr qreal Register16Controller::INITIAL_VALUE; constexpr qreal Register16Controller::INITIAL_VALUE_SCALE; constexpr Register16Controller::Encoding Register16Controller::INITIAL_ENCODING; Register16Controller::Register16Controller(QObject * parent): AbstractRegisterController(parent), m(new Members) { connect(this, & AbstractRegisterController::deviceChanged, this, & Register16Controller::resetRegister); connect(this, & AbstractRegisterController::addressChanged, this, & Register16Controller::resetRegister); } Register16Controller::~Register16Controller() { setDevice(nullptr); } qreal Register16Controller::value() const { return m->value; } void Register16Controller::setValue(qreal value) { Mixin::setValue(value); } qreal Register16Controller::valueScale() const { return m->valueScale; } void Register16Controller::setValueScale(qreal valueScale) { if (m->valueScale != valueScale) { m->valueScale = valueScale; emit valueScaleChanged(); updateValue(); } } Register16Controller::Encoding Register16Controller::encoding() const { return m->encoding; } void Register16Controller::setEncoding(Encoding encoding) { if (m->encoding != encoding) { m->encoding = encoding; emit encodingChanged(); updateValue(); } } void Register16Controller::writeValue() { Mixin::writeValue(); } void Register16Controller::timerEvent(QTimerEvent * event) { Mixin::timerEvent(event); } quint16 Register16Controller::bytes() const { return 1; } void Register16Controller::onDeviceDestroyed() { // References to coils/registers become invalid *before* device object emits destroyed() signal. m->register16 = nullptr; } void Register16Controller::updateValue() { if (m->register16 == nullptr) return; updateValue(m->register16->value()); } void Register16Controller::updateValue(quint16 value) { - // Do not update value if user is adjusting it, beacause it could distract user. + // Do not update value if user is adjusting it, because it could distract user. if (m->adjustingValue) return; qreal newValue = m->valueScale * Decode(value, encoding()); if (m->value != newValue) { m->value = newValue; emit valueChanged(); } else if (m->value != m->requestedValue) emit valueChanged(); // Trigger slots also in case of failed writes. emit valueUpdated(); } void Register16Controller::updateValue(const QJsonValue & value) { updateValue(static_cast(value.toDouble())); } void Register16Controller::onRequestCompleted(QJsonObject request, QJsonObject reply) { Mixin::onRequestCompleted(request, reply); } void Register16Controller::resetRegister() { m->requestId = nullptr; // Setting up new register invalidates previous requests. m->postponedWritePending = false; m->adjustingValue = false; if (m->register16) m->register16->rest(); if (device()) { setBusy(true); m->register16 = registerAt(static_cast(address())); m->register16->awake(); updateValue(); } else m->register16 = nullptr; } qreal Register16Controller::Decode(quint16 value, Encoding encoding) { CUTEHMI_ASSERT(encoding == INT16 || encoding == UINT16, QString("unrecognized encoding ('%1')").arg(encoding).toLocal8Bit().constData()); switch (encoding) { case UINT16: return value; case INT16: return internal::int16FromUint16(value); } return std::numeric_limits::quiet_NaN(); } quint16 Register16Controller::Encode(qreal value, Encoding encoding) { CUTEHMI_ASSERT(encoding == INT16 || encoding == UINT16, QString("unrecognized encoding ('%1')").arg(encoding).toLocal8Bit().constData()); switch (encoding) { case UINT16: return static_cast(qRound64(value)); case INT16: - return internal::int16ToUint16(static_cast(qRound(value))); // Note: int (as returned by qRound()) is guranteed to be at least 16 bits wide by a standard. + return internal::int16ToUint16(static_cast(qRound(value))); // Note: int (as returned by qRound()) is guaranteed to be at least 16 bits wide by a standard. } return 0; } bool Register16Controller::ValidateEncoding(qreal value, Encoding encoding) { CUTEHMI_ASSERT(encoding == INT16 || encoding == UINT16, QString("unrecognized encoding ('%1')").arg(encoding).toLocal8Bit().constData()); switch (encoding) { case UINT16: return qRound64(value) <= std::numeric_limits::max() && qRound64(value) >= std::numeric_limits::min(); case INT16: - // Note: int (as returned by qRound()) is guranteed to be at least 16 bits wide by a standard. + // Note: int (as returned by qRound()) is guaranteed to be at least 16 bits wide by a standard. return qRound(value) <= std::numeric_limits::max() && qRound(value) >= std::numeric_limits::min(); } return false; } void Register16Controller::requestWrite(qreal value) { qreal scaledValue = value / m->valueScale; if (ValidateEncoding(scaledValue, encoding())) { setBusy(true); requestWriteRegister(static_cast(address()), Encode(scaledValue, encoding()), & m->requestId); } else { CUTEHMI_CRITICAL("Can not represent requested value '" << scaledValue << "' using selected encoding '" << encoding() << "'."); emit valueFailed(); } } bool Register16Controller::verifyRegisterValue() const { CUTEHMI_ASSERT(m->register16 != nullptr, "m->register16 can not be nullptr when calling this function"); return Decode(m->register16->value(), encoding()) == m->value; } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/DummyClientBackend.cpp b/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/DummyClientBackend.cpp index b56cafd5..3e149063 100644 --- a/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/DummyClientBackend.cpp +++ b/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/DummyClientBackend.cpp @@ -1,184 +1,184 @@ #include #include #include #include namespace cutehmi { namespace modbus { namespace internal { DummyClientBackend::DummyClientBackend(DummyClientConfig * config, QObject * parent): AbstractClientBackend(parent), m(new Members{config, AbstractDevice::CLOSED, {}, {}, {}, {}}) { } DummyClientBackend::~DummyClientBackend() { m->coils.free(); m->discreteInputs.free(); m->holdingRegisters.free(); m->inputRegisters.free(); } void DummyClientBackend::ensureClosed() { if (m->state != AbstractDevice::CLOSED && m->state != AbstractDevice::CLOSING) disconnect(); } bool DummyClientBackend::proceedRequest(QUuid requestId) { - // Normaly client would forward the request to Modbus server and emit reply with received data. Dummy client has no remote side + // Normally client would forward the request to Modbus server and emit reply with received data. Dummy client has no remote side // to ask, but it emulates the latency here. QThread::msleep(static_cast(m->config->latency())); // Fake processing. if (m->state != AbstractDevice::OPENED) { QJsonObject reply; reply.insert("success", false); reply.insert("error", "Client not connected."); emit replied(requestId, reply); return false; } return true; } void DummyClientBackend::readCoils(QUuid requestId, quint16 startAddress, quint16 endAddress) { QJsonObject reply; QJsonArray values; for (quint16 address = startAddress; address <= endAddress; address++) values.append(m->coils.value(address)->value()); reply.insert("values", values); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::writeCoil(QUuid requestId, quint16 address, bool value) { QJsonObject reply; m->coils.value(address)->setValue(value); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::writeMultipleCoils(QUuid requestId, quint16 startAddress, const QVector & values) { QJsonObject reply; - quint16 endAddress = startAddress + static_cast(values.size()) - 1; // Size of @a values vector is limitted by @ref cutehmi-modbus-AbstractDevice-query_limits. + quint16 endAddress = startAddress + static_cast(values.size()) - 1; // Size of @a values vector is limited by @ref cutehmi-modbus-AbstractDevice-query_limits. for (quint16 address = startAddress; address <= endAddress; address++) m->coils.value(address)->setValue(values.at(address)); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::readDiscreteInputs(QUuid requestId, quint16 startAddress, quint16 endAddress) { QJsonObject reply; QJsonArray values; for (quint16 address = startAddress; address <= endAddress; address++) values.append(m->discreteInputs.value(address)->value()); reply.insert("values", values); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::readHoldingRegisters(QUuid requestId, quint16 startAddress, quint16 endAddress) { QJsonObject reply; QJsonArray values; for (quint16 address = startAddress; address <= endAddress; address++) values.append(static_cast(m->holdingRegisters.value(address)->value())); reply.insert("values", values); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::writeHoldingRegister(QUuid requestId, quint16 address, quint16 value) { QJsonObject reply; m->holdingRegisters.value(address)->setValue(value); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::writeMultipleHoldingRegisters(QUuid requestId, quint16 startAddress, const QVector & values) { QJsonObject reply; - quint16 endAddress = startAddress + static_cast(values.size()) - 1; // Size of @a values vector is limitted by @ref cutehmi-modbus-AbstractDevice-query_limits. + quint16 endAddress = startAddress + static_cast(values.size()) - 1; // Size of @a values vector is limited by @ref cutehmi-modbus-AbstractDevice-query_limits. for (quint16 address = startAddress; address <= endAddress; address++) m->holdingRegisters.value(address)->setValue(values.at(address)); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::readInputRegisters(QUuid requestId, quint16 startAddress, quint16 endAddress) { QJsonObject reply; QJsonArray values; for (quint16 address = startAddress; address <= endAddress; address++) values.append(static_cast(m->inputRegisters.value(address)->value())); reply.insert("values", values); reply.insert("success", true); emit replied(requestId, reply); } void DummyClientBackend::open() { setState(AbstractClient::OPENING); QThread::msleep(static_cast(m->config->openLatency())); setState(AbstractClient::OPENED); emit opened(); CUTEHMI_DEBUG("Imaginary connection established."); } void DummyClientBackend::close() { setState(AbstractClient::CLOSING); QThread::msleep(static_cast(m->config->closeLatency())); setState(AbstractClient::CLOSED); emit closed(); CUTEHMI_DEBUG("Imaginary connection closed."); } void DummyClientBackend::setState(AbstractClient::State state) { if (m->state != state) { m->state = state; emit stateChanged(state); } } } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/functions.cpp b/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/functions.cpp index a7b99375..9d87b446 100644 --- a/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/functions.cpp +++ b/extensions/CuteHMI/Modbus.2/src/cutehmi/modbus/internal/functions.cpp @@ -1,65 +1,65 @@ #include #include #include namespace cutehmi { namespace modbus { namespace internal { quint16 intToUint16(int value) { - // Note: int is guranteed to be at least 16 bits wide by a standard. + // Note: int is guaranteed to be at least 16 bits wide by a standard. // Check if value can be stored in 16 bits. Q_ASSERT_X(value <= std::numeric_limits::max(), __func__, "value is below a limit of 16 bit storage"); Q_ASSERT_X(value >= std::numeric_limits::min(), __func__, "value is over a limit of 16 bit storage"); return int16ToUint16(static_cast(value)); } quint16 int16ToUint16(qint16 value) { // A bit paranoic, but int can be either one's or two's complement (http://en.cppreference.com/w/cpp/language/types). // Since one's complement machines are probably non-existent, just assert that machine is two's complement. // On one's complement machines ~0 == -0, on two's complement machines ~0 == -1. Q_ASSERT_X(-1 == ~0, __func__, "only two's complement machines are supported by this function"); // Conversion from signed to unsigned type is well defined by a standard. // "If the destination type is unsigned, the resulting value is the smallest unsigned value equal to the source value modulo 2^n // where n is the number of bits used to represent the destination type. That is, depending on whether the destination type is // wider or narrower, signed integers are sign-extended[footnote 1] or truncated and unsigned integers are zero-extended or // truncated respectively." -- http://en.cppreference.com/w/cpp/language/implicit_cast return static_cast(value); } qint16 int16FromUint16(quint16 value) { // A bit paranoic, but int can be either one's or two's complement (http://en.cppreference.com/w/cpp/language/types). // Since one's complement machines are probably non-existent, just assert that machine is two's complement. // On one's complement machines ~0 == -0, on two's complement machines ~0 == -1. Q_ASSERT_X(-1 == ~0, __func__, "only two's complement machines are supported by this function"); // Conversion from unsigned to signed type is well-defined if value can be represented in destination int. // "If the destination type is signed, the value does not change if the source integer can be represented in the destination type. // Otherwise the result is implementation-defined. (Note that this is different from signed integer arithmetic overflow, which // is undefined)." -- http://en.cppreference.com/w/cpp/language/implicit_cast // Anyways, we need to make reverse of int16ToUint16(), not represent value directly. if (value > std::numeric_limits::max()) return -static_cast(std::numeric_limits::max() - value) - 1; else return static_cast(value); } } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Services.2/src/cutehmi/services/Service.cpp b/extensions/CuteHMI/Services.2/src/cutehmi/services/Service.cpp index 0218ef21..8f4a473c 100644 --- a/extensions/CuteHMI/Services.2/src/cutehmi/services/Service.cpp +++ b/extensions/CuteHMI/Services.2/src/cutehmi/services/Service.cpp @@ -1,322 +1,322 @@ #include #include #include #include #include namespace cutehmi { namespace services { constexpr int Service::INITIAL_STOP_TIMEOUT; constexpr int Service::INITIAL_START_TIMEOUT; constexpr int Service::INITIAL_REPAIR_TIMEOUT; constexpr const char * Service::INITIAL_NAME; Service::Service(QObject * parent): QObject(parent), m(new Members) { ServiceManager::Instance().add(this); setStatus(DefaultStatus()); } Service::~Service() { stop(); // Wait till either service stops or timeout is reached. if (m->stateInterface) while (!m->stateInterface->stopped().active() && !m->stateInterface->interrupted().active()) { QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents); // Due to repair and start timeouts service may end up in broken state. if (m->stateInterface->broken().active()) stop(); } if (m->serviceable) ServiceManager::Instance().leave(this); ServiceManager::Instance().remove(this); if (m->stateMachine) m->stateMachine->stop(); } int Service::stopTimeout() const { return m->stopTimeout; } void Service::setStopTimeout(int timeout) { if (m->stopTimeout != timeout) { m->stopTimeout = timeout; emit stopTimeoutChanged(); } } int Service::startTimeout() const { return m->startTimeout; } void Service::setStartTimeout(int startTimeout) { if (m->startTimeout != startTimeout) { m->startTimeout = startTimeout; emit startTimeoutChanged(); } } int Service::repairTimeout() const { return m->repairTimeout; } void Service::setRepairTimeout(int repairTimeout) { if (m->repairTimeout != repairTimeout) { m->repairTimeout = repairTimeout; emit repairTimeoutChanged(); } } QString Service::name() const { return m->name; } void Service::setName(const QString & name) { if (m->name != name) { m->name = name; emit nameChanged(); } } QString Service::status() const { return m->status; } void Service::setServiceable(QVariant serviceable) { QObject * qobjectPtr = serviceable.value(); Serviceable * serviceablePtr = dynamic_cast(qobjectPtr); if (qobjectPtr != nullptr && serviceablePtr == nullptr) CUTEHMI_WARNING("Object assigned as serviceable to '" << name() << "' service does not implement 'cutehmi::services::Serviceable' interface."); if (m->serviceable != serviceablePtr) { if (m->serviceable) ServiceManager::Instance().leave(this); destroyStateMachine(); if (serviceablePtr) initializeStateMachine(*serviceablePtr); m->serviceable = serviceablePtr; if (m->serviceable) ServiceManager::Instance().manage(this); else setStatus(DefaultStatus()); emit serviceableChanged(); } } QVariant Service::serviceable() const { return QVariant::fromValue(m->serviceable); } void Service::start() { emit started(); } void Service::stop() { emit stopped(); } internal::StateInterface * Service::stateInterface() { return m->stateInterface; } const internal::StateInterface * Service::stateInterface() const { return m->stateInterface; } void Service::activate() { emit activated(); } void Service::setStatus(const QString & status) { if (m->status != status) { m->status = status; emit statusChanged(); } } QString & Service::DefaultStatus() { static QString name = QObject::tr("Unmanaged"); return name; } void Service::destroyStateMachine() { if (m->stateMachine) { m->stateMachine->stop(); m->stateMachine->deleteLater(); m->stateMachine = nullptr; m->stateInterface = nullptr; } } void Service::initializeStateMachine(Serviceable & serviceable) { try { m->stateMachine = new QStateMachine(this); m->stateInterface = new internal::StateInterface(m->stateMachine); connect(m->stateInterface, & internal::StateInterface::statusChanged, this, [this]() { setStatus(m->stateInterface->status()); } ); connect(& m->stateInterface->interrupted(), & QState::entered, [this]() { Notification::Critical(tr("Interrupting stop sequence of '%1' service, because it took more than %2 [ms].").arg(name()).arg(stopTimeout())); }); connect(& m->stateInterface->started(), & QState::entered, [this]() { Notification::Info(tr("Service '%1' has started.").arg(name())); }); connect(& m->stateInterface->stopped(), & QState::entered, [this]() { Notification::Info(tr("Service '%1' is stopped.").arg(name())); }); connect(& m->stateInterface->broken(), & QState::entered, [this]() { Notification::Warning(tr("Service '%1' broke.").arg(name())); }); // Configure timeouts. connect(& m->stateInterface->stopping(), & QState::entered, [this]() { if (stopTimeout() >= 0) m->timeoutTimer.start(stopTimeout()); }); - // It's safer to stop timout, so that it won't make false shot. + // It's safer to stop timeout, so that it won't make false shot. connect(& m->stateInterface->stopping(), & QState::exited, & m->timeoutTimer, & QTimer::stop); connect(& m->stateInterface->evacuating(), & QState::entered, [this]() { if (stopTimeout() >= 0) m->timeoutTimer.start(stopTimeout()); }); - // It's safer to stop timout, so that it won't make false shot. + // It's safer to stop timeout, so that it won't make false shot. connect(& m->stateInterface->evacuating(), & QState::exited, & m->timeoutTimer, & QTimer::stop); connect(& m->stateInterface->starting(), & QState::entered, [this]() { if (startTimeout() >= 0) m->timeoutTimer.start(startTimeout()); }); - // It's safer to stop timout, so that it won't make false shot. + // It's safer to stop timeout, so that it won't make false shot. connect(& m->stateInterface->starting(), & QState::exited, & m->timeoutTimer, & QTimer::stop); connect(& m->stateInterface->repairing(), & QState::entered, [this]() { if (repairTimeout() >= 0) m->timeoutTimer.start(repairTimeout()); }); - // It's safer to stop timout, so that it won't make false shot. + // It's safer to stop timeout, so that it won't make false shot. connect(& m->stateInterface->repairing(), & QState::exited, & m->timeoutTimer, & QTimer::stop); m->stateInterface->stopped().assignProperty(m->stateInterface, "status", tr("Stopped")); m->stateInterface->interrupted().assignProperty(m->stateInterface, "status", tr("Interrupted")); m->stateInterface->starting().assignProperty(m->stateInterface, "status", tr("Starting")); m->stateInterface->started().assignProperty(m->stateInterface, "status", tr("Started")); m->stateInterface->idling().assignProperty(m->stateInterface, "status", tr("Idling")); m->stateInterface->yielding().assignProperty(m->stateInterface, "status", tr("Yielding")); m->stateInterface->active().assignProperty(m->stateInterface, "status", tr("Active")); m->stateInterface->stopping().assignProperty(m->stateInterface, "status", tr("Stopping")); m->stateInterface->broken().assignProperty(m->stateInterface, "status", tr("Broken")); m->stateInterface->repairing().assignProperty(m->stateInterface, "status", tr("Repairing")); m->stateInterface->evacuating().assignProperty(m->stateInterface, "status", tr("Evacuating")); m->stateMachine->addState(& m->stateInterface->stopped()); m->stateMachine->addState(& m->stateInterface->interrupted()); m->stateMachine->addState(& m->stateInterface->starting()); m->stateMachine->addState(& m->stateInterface->started()); m->stateMachine->addState(& m->stateInterface->stopping()); m->stateMachine->addState(& m->stateInterface->broken()); m->stateMachine->addState(& m->stateInterface->repairing()); m->stateMachine->addState(& m->stateInterface->evacuating()); m->stateMachine->setInitialState(& m->stateInterface->stopped()); m->stateInterface->stopped().addTransition(this, & Service::started, & m->stateInterface->starting()); m->stateInterface->started().addTransition(this, & Service::stopped, & m->stateInterface->stopping()); m->stateInterface->broken().addTransition(this, & Service::started, & m->stateInterface->repairing()); m->stateInterface->broken().addTransition(this, & Service::stopped, & m->stateInterface->evacuating()); m->stateInterface->stopping().addTransition(& m->timeoutTimer, & QTimer::timeout, & m->stateInterface->interrupted()); m->stateInterface->starting().addTransition(& m->timeoutTimer, & QTimer::timeout, & m->stateInterface->broken()); m->stateInterface->repairing().addTransition(& m->timeoutTimer, & QTimer::timeout, & m->stateInterface->broken()); m->stateInterface->yielding().addTransition(this, & Service::activated, & m->stateInterface->active()); m->stateInterface->evacuating().addTransition(& m->timeoutTimer, & QTimer::timeout, & m->stateInterface->interrupted()); addTransition(& m->stateInterface->starting(), & m->stateInterface->started(), serviceable.transitionToStarted()); addTransition(& m->stateInterface->repairing(), & m->stateInterface->started(), serviceable.transitionToStarted()); addTransition(& m->stateInterface->stopping(), & m->stateInterface->stopped(), serviceable.transitionToStopped()); addTransition(& m->stateInterface->starting(), & m->stateInterface->broken(), serviceable.transitionToBroken()); addTransition(& m->stateInterface->started(), & m->stateInterface->broken(), serviceable.transitionToBroken()); addTransition(& m->stateInterface->repairing(), & m->stateInterface->broken(), serviceable.transitionToBroken()); if (serviceable.transitionToIdling()) addTransition(& m->stateInterface->active(), & m->stateInterface->idling(), serviceable.transitionToIdling()); addTransition(& m->stateInterface->idling(), & m->stateInterface->yielding(), serviceable.transitionToYielding()); addTransition(& m->stateInterface->evacuating(), & m->stateInterface->stopped(), serviceable.transitionToStopped()); addStatuses(serviceable.configureBroken(& m->stateInterface->broken())); addStatuses(serviceable.configureStarted(& m->stateInterface->active(), & m->stateInterface->idling(), & m->stateInterface->yielding())); addStatuses(serviceable.configureStarting(& m->stateInterface->starting())); addStatuses(serviceable.configureStopping(& m->stateInterface->stopping())); addStatuses(serviceable.configureRepairing(& m->stateInterface->repairing())); addStatuses(serviceable.configureEvacuating(& m->stateInterface->evacuating())); m->stateMachine->start(); - QCoreApplication::processEvents(); // This is required in order to trully start state machine and prevent it from ignoring incomming events. + QCoreApplication::processEvents(); // This is required in order to truly start state machine and prevent it from ignoring incoming events. } catch (const std::exception & e) { CUTEHMI_CRITICAL("Could not initialize new state machine, because of following exception: " << e.what()); } } void Service::addTransition(QState * source, QState * target, std::unique_ptr transition) { if (transition) { transition->setParent(source); transition->setTargetState(target); source->addTransition(transition.release()); } else source->addTransition(target); } void Service::addStatuses(std::unique_ptr statuses) { if (statuses) for (auto stateStatus = statuses->begin(); stateStatus != statuses->end(); ++stateStatus) stateStatus.key()->assignProperty(m->stateInterface, "status", stateStatus.value()); } } } //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Symbols/HVAC.0/BasicHeater.qml b/extensions/CuteHMI/Symbols/HVAC.0/BasicHeater.qml index 0a8fe00a..c1375c26 100644 --- a/extensions/CuteHMI/Symbols/HVAC.0/BasicHeater.qml +++ b/extensions/CuteHMI/Symbols/HVAC.0/BasicHeater.qml @@ -1,51 +1,51 @@ import QtQuick 2.0 /** Basic heater. */ HeatExchanger { id: root content: Component { SymbolCanvas { element: root onPaint: { var ctx = getContext("2d") ctx.save() ctx.reset() ctx.lineWidth = root.units.strokeWidth ctx.fillStyle = root.color.background ctx.strokeStyle = root.color.foreground // Draw plus symbol with radius r. var r = width * 0.25 var xCenter = width * 0.5 var yCenter = height * 0.5 - // Draw cricle. + // Draw circle. ctx.beginPath() ctx.arc(xCenter, yCenter, r, 0, 2 * Math.PI) ctx.fill() ctx.stroke() // Draw plus. ctx.beginPath() ctx.moveTo(xCenter - r * 0.5, yCenter) ctx.lineTo(xCenter + r * 0.5, yCenter) ctx.moveTo(xCenter, yCenter - r * 0.5) ctx.lineTo(xCenter, yCenter + r * 0.5) ctx.stroke() ctx.restore() } } } } //(c)C: Copyright © 2020, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Symbols/HVAC.0/Cooler.qml b/extensions/CuteHMI/Symbols/HVAC.0/Cooler.qml index d681bfc5..cc1e5564 100644 --- a/extensions/CuteHMI/Symbols/HVAC.0/Cooler.qml +++ b/extensions/CuteHMI/Symbols/HVAC.0/Cooler.qml @@ -1,56 +1,56 @@ import QtQuick 2.0 /** Cooler. */ HeatExchanger { id: root content: Component { SymbolCanvas { property point symbolPos: root.symbolPos() element: root onPaint: { var ctx = getContext("2d") ctx.save() ctx.reset() ctx.lineWidth = root.units.strokeWidth ctx.fillStyle = root.color.background ctx.strokeStyle = root.color.foreground // Draw plus symbol with radius r. var r = width * 0.25 // Draw diagonal line. ctx.moveTo(0, height) ctx.lineTo(width, 0) ctx.stroke() - // Draw cricle. + // Draw circle. ctx.beginPath() ctx.arc(symbolPos.x, symbolPos.y, r, 0, 2 * Math.PI) ctx.fill() ctx.stroke() // Draw minus. ctx.beginPath() ctx.moveTo(symbolPos.x - r * 0.5, symbolPos.y) ctx.lineTo(symbolPos.x + r * 0.5, symbolPos.y) ctx.stroke() ctx.restore() } onSymbolPosChanged: requestPaint() } } } //(c)C: Copyright © 2020, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Symbols/HVAC.0/Heater.qml b/extensions/CuteHMI/Symbols/HVAC.0/Heater.qml index c19f5b45..db74d84d 100644 --- a/extensions/CuteHMI/Symbols/HVAC.0/Heater.qml +++ b/extensions/CuteHMI/Symbols/HVAC.0/Heater.qml @@ -1,58 +1,58 @@ import QtQuick 2.0 /** Heater. */ HeatExchanger { id: root content: Component { SymbolCanvas { element: root property point symbolPos: root.symbolPos() onPaint: { var ctx = getContext("2d") ctx.save() ctx.reset() ctx.lineWidth = root.units.strokeWidth ctx.fillStyle = root.color.background ctx.strokeStyle = root.color.foreground // Draw plus symbol with radius r. var r = width * 0.25 // Draw diagonal line. ctx.moveTo(0, height) ctx.lineTo(width, 0) ctx.stroke() - // Draw cricle. + // Draw circle. ctx.beginPath() ctx.arc(symbolPos.x, symbolPos.y, r, 0, 2 * Math.PI) ctx.fill() ctx.stroke() // Draw plus. ctx.beginPath() ctx.moveTo(symbolPos.x - r * 0.5, symbolPos.y) ctx.lineTo(symbolPos.x + r * 0.5, symbolPos.y) ctx.moveTo(symbolPos.x, symbolPos.y - r * 0.5) ctx.lineTo(symbolPos.x, symbolPos.y + r * 0.5) ctx.stroke() ctx.restore() } onSymbolPosChanged: requestPaint() } } } //(c)C: Copyright © 2020, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Symbols/HVAC.0/MotorActuator.qml b/extensions/CuteHMI/Symbols/HVAC.0/MotorActuator.qml index 4b3ecc13..0edfcf3a 100644 --- a/extensions/CuteHMI/Symbols/HVAC.0/MotorActuator.qml +++ b/extensions/CuteHMI/Symbols/HVAC.0/MotorActuator.qml @@ -1,82 +1,82 @@ import QtQuick 2.0 import CuteHMI.GUI 0.0 /** Motor actuator. */ Element { id: root implicitWidth: units.quadrat * 0.5 implicitHeight: units.quadrat * 0.5 property real start: 0.0 property real value: 0.0 property string symbol: "M" property alias font: symbolText.font SymbolCanvas { id: canvas anchors.fill: parent element: root onPaint: { var ctx = getContext("2d") ctx.save() ctx.reset() ctx.lineWidth = units.strokeWidth ctx.fillStyle = color.background ctx.strokeStyle = color.stroke var r = (width - units.strokeWidth) * 0.5 var xCenter = width * 0.5 var yCenter = height * 0.5 - // Draw cricle. + // Draw circle. ctx.beginPath() ctx.arc(xCenter, yCenter, r, 0, 2 * Math.PI) ctx.fill() ctx.stroke() // Draw progress ctx.beginPath() ctx.strokeStyle = color.background ctx.arc(xCenter, yCenter, r - units.strokeWidth, 0, 2 * Math.PI) ctx.stroke() ctx.beginPath() ctx.strokeStyle = color.foreground ctx.arc(xCenter, yCenter, r - units.strokeWidth, start * 2 * Math.PI - 0.5 * Math.PI, value * 2 * Math.PI - 0.5 * Math.PI) ctx.stroke() ctx.restore() } Connections { target: root onStartChanged: canvas.requestPaint() onValueChanged: canvas.requestPaint() } } Text { id: symbolText anchors.centerIn: parent color: root.color.foreground font.family: CuteApplication.theme.fonts.monospace.family font.pixelSize: units.quadrat * 0.2 text: parent.symbol } } //(c)C: Copyright © 2020, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Test.0/tests/test_random.cpp b/extensions/CuteHMI/Test.0/tests/test_random.cpp index de96539e..76b6dc32 100644 --- a/extensions/CuteHMI/Test.0/tests/test_random.cpp +++ b/extensions/CuteHMI/Test.0/tests/test_random.cpp @@ -1,100 +1,100 @@ #include #include namespace cutehmi { namespace test { class test_random: public QObject { Q_OBJECT private slots: void randQString(); void randQStringList(); void randPickDouble_data(); void randPickDouble(); }; void test_random::randQString() { QVERIFY(!cutehmi::test::rand(cutehmi::test::rand(1, 255)).isEmpty()); } void test_random::randQStringList() { QVERIFY(!cutehmi::test::rand(cutehmi::test::rand(1, 255)).isEmpty()); } void test_random::randPickDouble_data() { QTest::addColumn("min"); QTest::addColumn("max"); QTest::newRow("normalized positive interval") << 0.5 << 0.75; QTest::newRow("normalized negative interval") << -0.75 << -0.5; QTest::newRow("normalized mixed interval") << -0.5 << 0.75; QTest::newRow("degenerated positive interval") << 0.5 << 0.5; QTest::newRow("degenerated negatvie interval") << -0.5 << -0.5; QTest::newRow("degenerated zero interval") << 0.0 << 0.0; QTest::newRow("degenerated negative zero interval") << -0.0 << -0.0; QTest::newRow("degenerated mixed zero interval with negative lower bound") << -0.0 << 0.0; QTest::newRow("degenerated mixed zero interval with negative upper bound") << 0.0 << -0.0; QTest::newRow("positive fractions interval") << 0.0 << 1.0; QTest::newRow("negative fractions interval") << -1.0 << 0.0; QTest::newRow("mixed fractions interval") << -1.0 << 1.0; QTest::newRow("extremely small left interval") << std::nextafter(0.0, -1.0) << 0.0; QTest::newRow("extremely small right interval") << 0.0 << std::nextafter(0.0, 1.0); - QTest::newRow("extremely small symetric interval") << std::nextafter(0.0, -1.0) << std::nextafter(0.0, 1.0); + QTest::newRow("extremely small symmetric interval") << std::nextafter(0.0, -1.0) << std::nextafter(0.0, 1.0); - QTest::newRow("assymetric right dominated interval") << std::nextafter(0.0, -1.0) << 100.0; - QTest::newRow("assymetric left dominated interval") << -100.0 << std::nextafter(0.0, 1.0); + QTest::newRow("asymmetric right dominated interval") << std::nextafter(0.0, -1.0) << 100.0; + QTest::newRow("asymmetric left dominated interval") << -100.0 << std::nextafter(0.0, 1.0); QTest::newRow("positive thousand interval") << 0.0 << 1000.0; QTest::newRow("negative thousand interval") << -1000.0 << 0.0; - QTest::newRow("mixed thaousand interval") << -1000.0 << 1000.0; + QTest::newRow("mixed thousand interval") << -1000.0 << 1000.0; QTest::newRow("positive hundred to thousand interval") << 100.0 << 1000.0; QTest::newRow("negative hundred to thousand interval") << -1000.0 << -100.0; QTest::newRow("mixed hundred to thousand interval") << -100.0 << 1000.0; QTest::newRow("large 10e100 positive interval") << 0.0 << 10.0e100; QTest::newRow("large 10e100 negative interval") << -10.0e100 << 0.0; QTest::newRow("large 10e100 mixed interval") << -10.0e100 << 10.0e100; QTest::newRow("positive interval limits") << 0.0 << std::numeric_limits::max(); QTest::newRow("negative interval limits") << std::numeric_limits::lowest() << 0.0; QTest::newRow("mixed interval limits") << std::numeric_limits::lowest() << std::numeric_limits::max(); QTest::newRow("close to positgive limit") << std::nextafter(std::numeric_limits::max(), -1.0) << std::numeric_limits::max(); QTest::newRow("close to negative limit") << std::numeric_limits::lowest() << std::nextafter(std::numeric_limits::lowest(), 1.0); } void test_random::randPickDouble() { QFETCH(double, min); QFETCH(double, max); double result = cutehmi::test::randPick(min, max); QVERIFY2(result >= min && result <= max, QString("min: %1, max: %2, result: %3").arg(min).arg(max).arg(result).toLocal8Bit().constData()); } } } QTEST_MAIN(cutehmi::test::test_random) #include "test_random.moc" //(c)C: Copyright © 2019, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/extensions/CuteHMI/Workarounds/PuppetBootloader.0/src/cutehmi/workarounds/puppetbootloader/internal/Init_win.cpp b/extensions/CuteHMI/Workarounds/PuppetBootloader.0/src/cutehmi/workarounds/puppetbootloader/internal/Init_win.cpp index 80c425c8..bacbdaeb 100644 --- a/extensions/CuteHMI/Workarounds/PuppetBootloader.0/src/cutehmi/workarounds/puppetbootloader/internal/Init_win.cpp +++ b/extensions/CuteHMI/Workarounds/PuppetBootloader.0/src/cutehmi/workarounds/puppetbootloader/internal/Init_win.cpp @@ -1,79 +1,79 @@ #include "Init.hpp" #include #include #include #include #include #include #include namespace cutehmi { namespace workarounds { namespace puppetbootloader { namespace internal { static constexpr const char * TARGET_APP = "Qml2Puppet"; static HMODULE DllHandle; static std::unique_ptr OldPath; void Init::_Construct() { - // Porceed only if we run from target application. + // Proceed only if we run from target application. if (QCoreApplication::applicationName() != TARGET_APP) return; // Obtain handle to PuppetBootloader library itself. if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(& Init::_Construct), & DllHandle) == 0) { qCritical() << "Function GetModuleHandleExA() failed with error code " << GetLastError() << "."; return; } // Store old DLL path. DWORD oldPathLength = GetDllDirectoryA(0, OldPath.get()); OldPath.reset(new char[oldPathLength]); GetDllDirectoryA(oldPathLength, OldPath.get()); // GetModuleFileName() won't return required length, so for the sake of simplicity stick with MAX_PATH for now and perform critical exit if path is too long. std::unique_ptr dllPath(new char[MAX_PATH]); DWORD dllPathLength = GetModuleFileNameA(DllHandle, dllPath.get(), MAX_PATH); if (dllPathLength == MAX_PATH) { qCritical() << "Runtime path '" << dllPath.get() << "' (truncated) exceeds maximal length of " << MAX_PATH << " characters supported by '" << Q_FUNC_INFO << "'."; return; } // PuppetBootloader resides in directory with other libraries, so its directory should now be added to DLL search paths. QFileInfo dllPathInfo(dllPath.get()); QFileInfo oldPathInfo(OldPath.get()); // Do not override DLL path if it has been already set by something as strange as PuppetBootloader... it would probably break that thing. if ((strlen(OldPath.get()) > 0) && (oldPathInfo.canonicalPath() != dllPathInfo.canonicalPath())) { qCritical() << "Can not override DLL search directory, which has been already set to '" << OldPath.get() << "'."; return; } // Main event of the evening - add directory with libraries to DLL search paths, so that TARGET_APP can load plugins with dependencies. SetDllDirectoryA(dllPathInfo.canonicalPath().toLocal8Bit().constData()); // And people say SetDllDirectory() is useless, huh... } void Init::_Destroy() { if (QCoreApplication::applicationName() != TARGET_APP) return; if (strlen(OldPath.get()) == 0) SetDllDirectoryA(nullptr); else SetDllDirectoryA(OldPath.get()); } } } } } diff --git a/tools/cutehmi.qmlplugindump.0/src/5/12/6/main.cpp b/tools/cutehmi.qmlplugindump.0/src/5/12/6/main.cpp index b83aebb0..37467cf7 100644 --- a/tools/cutehmi.qmlplugindump.0/src/5/12/6/main.cpp +++ b/tools/cutehmi.qmlplugindump.0/src/5/12/6/main.cpp @@ -1,1375 +1,1375 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #ifdef QT_WIDGETS_LIB #include #endif // QT_WIDGETS_LIB #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmltypereader.h" #include "qmlstreamwriter.h" #ifdef QT_SIMULATOR #include #endif #ifdef Q_OS_WIN # if !defined(Q_CC_MINGW) # include # endif #include #endif namespace { const uint qtQmlMajorVersion = 2; const uint qtQmlMinorVersion = QT_VERSION_MINOR; const uint qtQuickMajorVersion = 2; const uint qtQuickMinorVersion = QT_VERSION_MINOR; const QString qtQuickQualifiedName = QString::fromLatin1("QtQuick %1.%2") .arg(qtQuickMajorVersion) .arg(qtQuickMinorVersion); QString pluginImportPath; bool verbose = false; bool creatable = true; QString currentProperty; QString inObjectInstantiation; } static QString enquote(const QString &string) { QString s = string; return QString("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\")) .replace(QLatin1Char('"'),QLatin1String("\\\""))); } void collectReachableMetaObjects(const QMetaObject *meta, QSet *metas, bool extended = false) { if (! meta || metas->contains(meta)) return; // dynamic meta objects can break things badly // but extended types are usually fine const QMetaObjectPrivate *mop = reinterpret_cast(meta->d.data); if (extended || !(mop->flags & DynamicMetaObject)) metas->insert(meta); collectReachableMetaObjects(meta->superClass(), metas); } void collectReachableMetaObjects(QObject *object, QSet *metas) { if (! object) return; const QMetaObject *meta = object->metaObject(); if (verbose) std::cerr << "Processing object " << qPrintable( meta->className() ) << std::endl; collectReachableMetaObjects(meta, metas); for (int index = 0; index < meta->propertyCount(); ++index) { QMetaProperty prop = meta->property(index); if (QQmlMetaType::isQObject(prop.userType())) { if (verbose) std::cerr << " Processing property " << qPrintable( prop.name() ) << std::endl; currentProperty = QString("%1::%2").arg(meta->className(), prop.name()); // if the property was not initialized during construction, // accessing a member of oo is going to cause a segmentation fault QObject *oo = QQmlMetaType::toQObject(prop.read(object)); if (oo && !metas->contains(oo->metaObject())) collectReachableMetaObjects(oo, metas); currentProperty.clear(); } } } void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet *metas) { collectReachableMetaObjects(ty.baseMetaObject(), metas, ty.isExtendedType()); if (ty.attachedPropertiesType(engine)) collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas); } /* We want to add the MetaObject for 'Qt' to the list, this is a simple way to access it. */ class FriendlyQObject: public QObject { public: static const QMetaObject *qtMeta() { return &staticQtMetaObject; } }; /* When we dump a QMetaObject, we want to list all the types it is exported as. To do this, we need to find the QQmlTypes associated with this QMetaObject. */ static QHash > qmlTypesByCppName; static QHash cppToId; /* Takes a C++ type name, such as Qt::LayoutDirection or QString and maps it to how it should appear in the description file. These names need to be unique globally, so we don't change the C++ symbol's name much. It is mostly used to for explicit translations such as QString->string and translations for extended QML objects. */ QByteArray convertToId(const QByteArray &cppName) { return cppToId.value(cppName, cppName); } QByteArray convertToId(const QMetaObject *mo) { QByteArray className(mo->className()); if (!className.isEmpty()) return convertToId(className); // likely a metaobject generated for an extended qml object if (mo->superClass()) { className = convertToId(mo->superClass()); className.append("_extended"); return className; } static QHash generatedNames; className = generatedNames.value(mo); if (!className.isEmpty()) return className; std::cerr << "Found a QMetaObject without a className, generating a random name" << std::endl; className = QByteArray("error-unknown-name-"); className.append(QByteArray::number(generatedNames.size())); generatedNames.insert(mo, className); return className; } // Collect all metaobjects for types registered with qmlRegisterType() without parameters void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet& metas, QMap> &compositeTypes) { const auto qmlAllTypes = QQmlMetaType::qmlAllTypes(); for (const QQmlType &ty : qmlAllTypes) { if (!metas.contains(ty.baseMetaObject())) { if (!ty.isComposite()) { collectReachableMetaObjects(engine, ty, &metas); } else { compositeTypes[ty.elementName()].insert(ty); } } } } QSet collectReachableMetaObjects(QQmlEngine *engine, QSet &noncreatables, QSet &singletons, QMap> &compositeTypes, const QList &skip = QList()) { QSet metas; metas.insert(FriendlyQObject::qtMeta()); const auto qmlTypes = QQmlMetaType::qmlTypes(); for (const QQmlType &ty : qmlTypes) { if (!ty.isCreatable()) noncreatables.insert(ty.baseMetaObject()); if (ty.isSingleton()) singletons.insert(ty.baseMetaObject()); if (!ty.isComposite()) { qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty); collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas); } else { compositeTypes[ty.elementName()].insert(ty); } } if (creatable) { // find even more QMetaObjects by instantiating QML types and running // over the instances for (const QQmlType &ty : qmlTypes) { if (skip.contains(ty)) continue; if (ty.isExtendedType()) continue; if (!ty.isCreatable()) continue; if (ty.typeName() == "QQmlComponent") continue; QString tyName = ty.qmlTypeName(); tyName = tyName.mid(tyName.lastIndexOf(QLatin1Char('/')) + 1); if (tyName.isEmpty()) continue; inObjectInstantiation = tyName; QObject *object = nullptr; if (ty.isSingleton()) { QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo(); if (!siinfo) { std::cerr << "Internal error, " << qPrintable(tyName) << "(" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << " is singleton, but has no singletonInstanceInfo" << std::endl; continue; } if (siinfo->qobjectCallback) { if (verbose) std::cerr << "Trying to get singleton for " << qPrintable(tyName) << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl; siinfo->init(engine); collectReachableMetaObjects(object, &metas); object = siinfo->qobjectApi(engine); } else { inObjectInstantiation.clear(); continue; // we don't handle QJSValue singleton types. } } else { if (verbose) std::cerr << "Trying to create object " << qPrintable( tyName ) << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; object = ty.create(); } inObjectInstantiation.clear(); if (object) { if (verbose) std::cerr << "Got " << qPrintable( tyName ) << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; collectReachableMetaObjects(object, &metas); object->deleteLater(); } else { std::cerr << "Could not create " << qPrintable(tyName) << std::endl; } } } collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate::get(engine), metas, compositeTypes); return metas; } class KnownAttributes { QHash m_properties; QHash > m_methods; public: bool knownMethod(const QByteArray &name, int nArgs, int revision) { if (m_methods.contains(name)) { QHash overloads = m_methods.value(name); if (overloads.contains(nArgs) && overloads.value(nArgs) <= revision) return true; } m_methods[name][nArgs] = revision; return false; } bool knownProperty(const QByteArray &name, int revision) { if (m_properties.contains(name) && m_properties.value(name) <= revision) return true; m_properties[name] = revision; return false; } }; class Dumper { QmlStreamWriter *qml; QString relocatableModuleUri; public: Dumper(QmlStreamWriter *qml) : qml(qml) {} void setRelocatableModuleUri(const QString &uri) { relocatableModuleUri = uri; } const QString getExportString(QString qmlTyName, int majorVersion, int minorVersion) { if (qmlTyName.startsWith(relocatableModuleUri + QLatin1Char('/'))) { qmlTyName.remove(0, relocatableModuleUri.size() + 1); } if (qmlTyName.startsWith("./")) { qmlTyName.remove(0, 2); } if (qmlTyName.startsWith(QLatin1Char('/'))) { qmlTyName.remove(0, 1); } const QString exportString = enquote( QString("%1 %2.%3").arg( qmlTyName, QString::number(majorVersion), QString::number(minorVersion))); return exportString; } void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) { QSet implicitSignals = dumpMetaProperties(meta, 0, knownAttributes); if (meta == &QObject::staticMetaObject) { // for QObject, hide deleteLater() and onDestroyed for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) { QMetaMethod method = meta->method(index); QByteArray signature = method.methodSignature(); if (signature == QByteArrayLiteral("destroyed(QObject*)") || signature == QByteArrayLiteral("destroyed()") || signature == QByteArrayLiteral("deleteLater()")) continue; dump(method, implicitSignals, knownAttributes); } // and add toString(), destroy() and destroy(int) if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("toString"), 0, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString"))); qml->writeEndObject(); } if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 0, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); qml->writeEndObject(); } if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 1, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); qml->writeStartObject(QLatin1String("Parameter")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay"))); qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int"))); qml->writeEndObject(); qml->writeEndObject(); } } else { for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) dump(meta->method(index), implicitSignals, knownAttributes); } } QString getPrototypeNameForCompositeType(const QMetaObject *metaObject, QSet &defaultReachableNames, QList *objectsToMerge) { QString prototypeName; if (!defaultReachableNames.contains(metaObject->className())) { // dynamic meta objects can break things badly // but extended types are usually fine const QMetaObjectPrivate *mop = reinterpret_cast(metaObject->d.data); if (!(mop->flags & DynamicMetaObject) && objectsToMerge && !objectsToMerge->contains(metaObject)) objectsToMerge->append(metaObject); const QMetaObject *superMetaObject = metaObject->superClass(); if (!superMetaObject) prototypeName = "QObject"; else prototypeName = getPrototypeNameForCompositeType( superMetaObject, defaultReachableNames, objectsToMerge); } else { prototypeName = convertToId(metaObject->className()); } return prototypeName; } void dumpComposite(QQmlEngine *engine, const QSet &compositeType, QSet &defaultReachableNames) { for (const QQmlType &type : compositeType) dumpCompositeItem(engine, type, defaultReachableNames); } void dumpCompositeItem(QQmlEngine *engine, const QQmlType &compositeType, QSet &defaultReachableNames) { QQmlComponent e(engine, compositeType.sourceUrl()); if (!e.isReady()) { std::cerr << "WARNING: skipping module " << compositeType.elementName().toStdString() << std::endl << e.errorString().toStdString() << std::endl; return; } QObject *object = e.create(); if (!object) return; qml->writeStartObject("Component"); const QMetaObject *mainMeta = object->metaObject(); QList objectsToMerge; KnownAttributes knownAttributes; // Get C++ base class name for the composite type QString prototypeName = getPrototypeNameForCompositeType(mainMeta, defaultReachableNames, &objectsToMerge); qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName)); QString qmlTyName = compositeType.qmlTypeName(); const QString exportString = getExportString(qmlTyName, compositeType.majorVersion(), compositeType.minorVersion()); qml->writeScriptBinding(QLatin1String("name"), exportString); qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString); qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList() << QString::number(compositeType.minorVersion())); qml->writeBooleanBinding(QLatin1String("isComposite"), true); if (compositeType.isSingleton()) { qml->writeBooleanBinding(QLatin1String("isCreatable"), false); qml->writeBooleanBinding(QLatin1String("isSingleton"), true); } for (int index = mainMeta->classInfoCount() - 1 ; index >= 0 ; --index) { QMetaClassInfo classInfo = mainMeta->classInfo(index); if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value()))); break; } } for (const QMetaObject *meta : qAsConst(objectsToMerge)) { for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) dump(meta->enumerator(index)); writeMetaContent(meta, &knownAttributes); } qml->writeEndObject(); } QString getDefaultProperty(const QMetaObject *meta) { for (int index = meta->classInfoCount() - 1; index >= 0; --index) { QMetaClassInfo classInfo = meta->classInfo(index); if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { return QLatin1String(classInfo.value()); } } return QString(); } struct QmlTypeInfo { QmlTypeInfo() {} QmlTypeInfo(const QString &exportString, int revision, const QMetaObject *extendedObject, QByteArray attachedTypeId) : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {} QString exportString; int revision = 0; const QMetaObject *extendedObject = nullptr; QByteArray attachedTypeId; }; void dump(QQmlEnginePrivate *engine, const QMetaObject *meta, bool isUncreatable, bool isSingleton) { qml->writeStartObject("Component"); QByteArray id = convertToId(meta); qml->writeScriptBinding(QLatin1String("name"), enquote(id)); // collect type information QVector typeInfo; for (QQmlType type : qmlTypesByCppName.value(meta->className())) { const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr; QByteArray attachedTypeId; if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) { // Can happen when a type is registered that returns itself as attachedPropertiesType() // because there is no creatable type to attach to. if (attachedType != meta) attachedTypeId = convertToId(attachedType); } const QString exportString = getExportString(type.qmlTypeName(), type.majorVersion(), type.minorVersion()); int metaObjectRevision = type.metaObjectRevision(); if (extendedObject) { // emulate custom metaobjectrevision out of import metaObjectRevision = type.majorVersion() * 100 + type.minorVersion(); } QmlTypeInfo info = { exportString, metaObjectRevision, extendedObject, attachedTypeId }; typeInfo.append(info); } // sort to ensure stable output std::sort(typeInfo.begin(), typeInfo.end(), [](const QmlTypeInfo &i1, const QmlTypeInfo &i2) { return i1.revision < i2.revision; }); // determine default property // TODO: support revisioning of default property QString defaultProperty = getDefaultProperty(meta); if (defaultProperty.isEmpty()) { for (const QmlTypeInfo &iter : typeInfo) { if (iter.extendedObject) { defaultProperty = getDefaultProperty(iter.extendedObject); if (!defaultProperty.isEmpty()) break; } } } if (!defaultProperty.isEmpty()) qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty)); if (meta->superClass()) qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass()))); if (!typeInfo.isEmpty()) { QMap exports; // sort exports for (const QmlTypeInfo &iter : typeInfo) exports.insert(iter.exportString, QString::number(iter.revision)); QStringList exportStrings = exports.keys(); QStringList metaObjectRevisions = exports.values(); qml->writeArrayBinding(QLatin1String("exports"), exportStrings); if (isUncreatable) qml->writeBooleanBinding(QLatin1String("isCreatable"), false); if (isSingleton) qml->writeBooleanBinding(QLatin1String("isSingleton"), true); qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions); for (const QmlTypeInfo &iter : typeInfo) { if (!iter.attachedTypeId.isEmpty()) { qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId)); break; } } } for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) dump(meta->enumerator(index)); writeMetaContent(meta); // dump properties from extended metaobjects last for (auto iter : typeInfo) { if (iter.extendedObject) dumpMetaProperties(iter.extendedObject, iter.revision); } qml->writeEndObject(); } void writeEasingCurve() { qml->writeStartObject(QLatin1String("Component")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve"))); qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QQmlEasingValueType"))); qml->writeEndObject(); } private: /* Removes pointer and list annotations from a type name, returning what was removed in isList and isPointer */ static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer) { static QByteArray declListPrefix = "QQmlListProperty<"; if (typeName->endsWith('*')) { *isPointer = true; typeName->truncate(typeName->length() - 1); removePointerAndList(typeName, isList, isPointer); } else if (typeName->startsWith(declListPrefix)) { *isList = true; typeName->truncate(typeName->length() - 1); // get rid of the suffix '>' *typeName = typeName->mid(declListPrefix.size()); removePointerAndList(typeName, isList, isPointer); } *typeName = convertToId(*typeName); } void writeTypeProperties(QByteArray typeName, bool isWritable) { bool isList = false, isPointer = false; removePointerAndList(&typeName, &isList, &isPointer); qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); if (isList) qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true")); if (!isWritable) qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true")); if (isPointer) qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); } void dump(const QMetaProperty &prop, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { int revision = metaRevision ? metaRevision : prop.revision(); QByteArray propName = prop.name(); if (knownAttributes && knownAttributes->knownProperty(propName, revision)) return; qml->writeStartObject("Property"); qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name()))); if (revision) qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); writeTypeProperties(prop.typeName(), prop.isWritable()); qml->writeEndObject(); } QSet dumpMetaProperties(const QMetaObject *meta, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { QSet implicitSignals; for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { const QMetaProperty &property = meta->property(index); dump(property, metaRevision, knownAttributes); if (knownAttributes) knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), 0, property.revision()); implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); } return implicitSignals; } void dump(const QMetaMethod &meth, const QSet &implicitSignals, KnownAttributes *knownAttributes = nullptr) { if (meth.methodType() == QMetaMethod::Signal) { if (meth.access() != QMetaMethod::Public) return; // nothing to do. } else if (meth.access() != QMetaMethod::Public) { return; // nothing to do. } QByteArray name = meth.name(); const QString typeName = convertToId(meth.typeName()); if (implicitSignals.contains(name) && !meth.revision() && meth.methodType() == QMetaMethod::Signal && meth.parameterNames().isEmpty() && typeName == QLatin1String("void")) { // don't mention implicit signals return; } int revision = meth.revision(); if (knownAttributes && knownAttributes->knownMethod(name, meth.parameterNames().size(), revision)) return; if (meth.methodType() == QMetaMethod::Signal) qml->writeStartObject(QLatin1String("Signal")); else qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(name)); if (revision) qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); if (typeName != QLatin1String("void")) qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); for (int i = 0; i < meth.parameterTypes().size(); ++i) { QByteArray argName = meth.parameterNames().at(i); qml->writeStartObject(QLatin1String("Parameter")); if (! argName.isEmpty()) qml->writeScriptBinding(QLatin1String("name"), enquote(argName)); writeTypeProperties(meth.parameterTypes().at(i), true); qml->writeEndObject(); } qml->writeEndObject(); } void dump(const QMetaEnum &e) { qml->writeStartObject(QLatin1String("Enum")); qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name()))); QList > namesValues; const int keyCount = e.keyCount(); namesValues.reserve(keyCount); for (int index = 0; index < keyCount; ++index) { namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index)))); } qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues); qml->writeEndObject(); } }; enum ExitCode { EXIT_INVALIDARGUMENTS = 1, EXIT_SEGV = 2, EXIT_IMPORTERROR = 3 }; void printUsage(const QString &appName) { std::cerr << qPrintable(QString( "Usage: %1 [-v] [-qapp] [-noinstantiate] [-defaultplatform] [-[non]relocatable] [-dependencies ] [-merge ] [-output ] [-noforceqtquick] module.uri version [module/import/path]\n" " %1 [-v] [-qapp] [-noinstantiate] -path path/to/qmldir/directory [version]\n" " %1 [-v] -builtins\n" "Example: %1 Qt.labs.folderlistmodel 2.0 /home/user/dev/qt-install/imports").arg( appName)) << std::endl; } static bool readDependenciesData(QString dependenciesFile, const QByteArray &fileData, QStringList *dependencies, const QStringList &urisToSkip, bool forceQtQuickDependency = true) { if (verbose) { std::cerr << "parsing " << qPrintable( dependenciesFile ) << " skipping"; for (const QString &uriToSkip : urisToSkip) std::cerr << ' ' << qPrintable(uriToSkip); std::cerr << std::endl; } QJsonParseError parseError; parseError.error = QJsonParseError::NoError; QJsonDocument doc = QJsonDocument::fromJson(fileData, &parseError); if (parseError.error != QJsonParseError::NoError) { std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() << ":" << parseError.errorString().toStdString() << " at " << parseError.offset << std::endl; return false; } if (doc.isArray()) { const QStringList requiredKeys = QStringList() << QStringLiteral("name") << QStringLiteral("type") << QStringLiteral("version"); const auto deps = doc.array(); for (const QJsonValue &dep : deps) { if (dep.isObject()) { QJsonObject obj = dep.toObject(); for (const QString &requiredKey : requiredKeys) if (!obj.contains(requiredKey) || obj.value(requiredKey).isString()) continue; if (obj.value(QStringLiteral("type")).toString() != QLatin1String("module")) continue; QString name = obj.value((QStringLiteral("name"))).toString(); QString version = obj.value(QStringLiteral("version")).toString(); if (name.isEmpty() || urisToSkip.contains(name) || version.isEmpty()) continue; if (name.contains(QLatin1String("Private"), Qt::CaseInsensitive)) { if (verbose) - std::cerr << "skipping private dependecy " + std::cerr << "skipping private dependency " << qPrintable( name ) << " " << qPrintable(version) << std::endl; continue; } if (verbose) std::cerr << "appending dependency " << qPrintable( name ) << " " << qPrintable(version) << std::endl; dependencies->append(name + QLatin1Char(' ')+version); } } } else { std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() << ": expected an array" << std::endl; return false; } // Workaround for avoiding conflicting types when no dependency has been found. // // qmlplugindump used to import QtQuick, so all types defined in QtQuick used to be skipped when dumping. // Now that it imports only Qt, it is no longer the case: if no dependency is found all the types defined // in QtQuick will be dumped, causing conflicts. if (forceQtQuickDependency && dependencies->isEmpty()) dependencies->push_back(qtQuickQualifiedName); return true; } static bool readDependenciesFile(const QString &dependenciesFile, QStringList *dependencies, const QStringList &urisToSkip) { if (!QFileInfo::exists(dependenciesFile)) { std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() << std::endl; return false; } QFile f(dependenciesFile); if (!f.open(QFileDevice::ReadOnly)) { std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() << ", " << f.errorString().toStdString() << std::endl; return false; } QByteArray fileData = f.readAll(); return readDependenciesData(dependenciesFile, fileData, dependencies, urisToSkip, false); } static bool getDependencies(const QQmlEngine &engine, const QString &pluginImportUri, const QString &pluginImportVersion, QStringList *dependencies, bool forceQtQuickDependency) { QString importScannerExe = QLatin1String("qmlimportscanner"); QFileInfo selfExe(QCoreApplication::applicationFilePath()); if (!selfExe.suffix().isEmpty()) importScannerExe += QLatin1String(".") + selfExe.suffix(); QString command = selfExe.absoluteDir().filePath(importScannerExe); QStringList commandArgs = QStringList() << QLatin1String("-qmlFiles") << QLatin1String("-"); QStringList importPathList = engine.importPathList(); importPathList.removeOne(QStringLiteral("qrc:/qt-project.org/imports")); for (const QString &path : importPathList) commandArgs << QLatin1String("-importPath") << path; QProcess importScanner; importScanner.start(command, commandArgs, QProcess::ReadWrite); if (!importScanner.waitForStarted()) return false; importScanner.write("import "); importScanner.write(pluginImportUri.toUtf8()); importScanner.write(" "); importScanner.write(pluginImportVersion.toUtf8()); importScanner.write("\nQtObject{}\n"); importScanner.closeWriteChannel(); if (!importScanner.waitForFinished()) { std::cerr << "failure to start " << qPrintable(command); for (const QString &arg : qAsConst(commandArgs)) std::cerr << ' ' << qPrintable(arg); std::cerr << std::endl; return false; } QByteArray depencenciesData = importScanner.readAllStandardOutput(); if (!readDependenciesData(QLatin1String(""), depencenciesData, dependencies, QStringList(pluginImportUri), forceQtQuickDependency)) { std::cerr << "failed to process output of qmlimportscanner" << std::endl; if (importScanner.exitCode() != 0) std::cerr << importScanner.readAllStandardError().toStdString(); return false; } QStringList aux; for (const QString &str : qAsConst(*dependencies)) { if (!str.startsWith("Qt.test.qtestroot")) aux += str; } *dependencies = aux; return true; } bool compactDependencies(QStringList *dependencies) { if (dependencies->isEmpty()) return false; dependencies->sort(); QStringList oldDep = dependencies->constFirst().split(QLatin1Char(' ')); Q_ASSERT(oldDep.size() == 2); int oldPos = 0; for (int idep = 1; idep < dependencies->size(); ++idep) { QString depStr = dependencies->at(idep); const QStringList newDep = depStr.split(QLatin1Char(' ')); Q_ASSERT(newDep.size() == 2); if (newDep.constFirst() != oldDep.constFirst()) { if (++oldPos != idep) dependencies->replace(oldPos, depStr); oldDep = newDep; } else { const QStringList v1 = oldDep.constLast().split(QLatin1Char('.')); const QStringList v2 = newDep.constLast().split(QLatin1Char('.')); Q_ASSERT(v1.size() == 2); Q_ASSERT(v2.size() == 2); bool ok; int major1 = v1.first().toInt(&ok); Q_ASSERT(ok); int major2 = v2.first().toInt(&ok); Q_ASSERT(ok); if (major1 != major2) { std::cerr << "Found a dependency on " << qPrintable(oldDep.constFirst()) << " with two major versions:" << qPrintable(oldDep.constLast()) << " and " << qPrintable(newDep.constLast()) << " which is unsupported, discarding smaller version" << std::endl; if (major1 < major2) dependencies->replace(oldPos, depStr); } else { int minor1 = v1.last().toInt(&ok); Q_ASSERT(ok); int minor2 = v2.last().toInt(&ok); Q_ASSERT(ok); if (minor1 < minor2) dependencies->replace(oldPos, depStr); } } } if (++oldPos < dependencies->size()) { *dependencies = dependencies->mid(0, oldPos); return true; } return false; } inline std::wostream &operator<<(std::wostream &str, const QString &s) { #ifdef Q_OS_WIN str << reinterpret_cast(s.utf16()); #else str << s.toStdWString(); #endif return str; } void printDebugMessage(QtMsgType, const QMessageLogContext &, const QString &msg) { std::wcerr << msg << std::endl; // In case of QtFatalMsg the calling code will abort() when appropriate. } int main(int argc, char *argv[]) { #if defined(Q_OS_WIN) && !defined(Q_CC_MINGW) // we do not want windows popping up if the module loaded triggers an assert SetErrorMode(SEM_NOGPFAULTERRORBOX); _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); #endif // Q_OS_WIN && !Q_CC_MINGW // The default message handler might not print to console on some systems. Enforce this. qInstallMessageHandler(printDebugMessage); #ifdef QT_SIMULATOR // Running this application would bring up the Qt Simulator (since it links Qt GUI), avoid that! QtSimulatorPrivate::SimulatorConnection::createStubInstance(); #endif // don't require a window manager even though we're a QGuiApplication bool requireWindowManager = false; for (int index = 1; index < argc; ++index) { if (QString::fromLocal8Bit(argv[index]) == "--defaultplatform" || QString::fromLocal8Bit(argv[index]) == "-defaultplatform") { requireWindowManager = true; break; } } if (!requireWindowManager && qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal")); else QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); // Check which kind of application should be instantiated. bool useQApplication = false; for (int i = 0; i < argc; ++i) { QString arg = QLatin1String(argv[i]); if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) useQApplication = true; } #ifdef QT_WIDGETS_LIB QScopedPointer app(useQApplication ? new QApplication(argc, argv) : new QGuiApplication(argc, argv)); #else Q_UNUSED(useQApplication); QScopedPointer app(new QGuiApplication(argc, argv)); #endif // QT_WIDGETS_LIB QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); QStringList args = app->arguments(); const QString appName = QFileInfo(app->applicationFilePath()).baseName(); if (args.size() < 2) { printUsage(appName); return EXIT_INVALIDARGUMENTS; } QString outputFilename; QString pluginImportUri; QString pluginImportVersion; bool relocatable = true; QString dependenciesFile; QString mergeFile; bool forceQtQuickDependency = true; enum Action { Uri, Path, Builtins }; Action action = Uri; { QStringList positionalArgs; for (int iArg = 0; iArg < args.size(); ++iArg) { const QString &arg = args.at(iArg); if (!arg.startsWith(QLatin1Char('-'))) { positionalArgs.append(arg); continue; } if (arg == QLatin1String("--dependencies") || arg == QLatin1String("-dependencies")) { if (++iArg == args.size()) { std::cerr << "missing dependencies file" << std::endl; return EXIT_INVALIDARGUMENTS; } dependenciesFile = args.at(iArg); // Remove absolute path so that it does not show up in the // printed command line inside the plugins.qmltypes file. args[iArg] = QFileInfo(args.at(iArg)).fileName(); } else if (arg == QLatin1String("--merge") || arg == QLatin1String("-merge")) { if (++iArg == args.size()) { std::cerr << "missing merge file" << std::endl; return EXIT_INVALIDARGUMENTS; } mergeFile = args.at(iArg); } else if (arg == QLatin1String("--notrelocatable") || arg == QLatin1String("-notrelocatable") || arg == QLatin1String("--nonrelocatable") || arg == QLatin1String("-nonrelocatable")) { relocatable = false; } else if (arg == QLatin1String("--relocatable") || arg == QLatin1String("-relocatable")) { relocatable = true; } else if (arg == QLatin1String("--noinstantiate") || arg == QLatin1String("-noinstantiate")) { creatable = false; } else if (arg == QLatin1String("--path") || arg == QLatin1String("-path")) { action = Path; } else if (arg == QLatin1String("--builtins") || arg == QLatin1String("-builtins")) { action = Builtins; } else if (arg == QLatin1String("-v")) { verbose = true; } else if (arg == QLatin1String("--noforceqtquick") || arg == QLatin1String("-noforceqtquick")){ forceQtQuickDependency = false; } else if (arg == QLatin1String("--output") || arg == QLatin1String("-output")) { if (++iArg == args.size()) { std::cerr << "missing output file" << std::endl; return EXIT_INVALIDARGUMENTS; } outputFilename = args.at(iArg); } else if (arg == QLatin1String("--defaultplatform") || arg == QLatin1String("-defaultplatform")) { continue; } else if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) { continue; } else { std::cerr << "Invalid argument: " << qPrintable(arg) << std::endl; return EXIT_INVALIDARGUMENTS; } } if (action == Uri) { if (positionalArgs.size() != 3 && positionalArgs.size() != 4) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } pluginImportUri = positionalArgs.at(1); pluginImportVersion = positionalArgs[2]; if (positionalArgs.size() >= 4) pluginImportPath = positionalArgs.at(3); } else if (action == Path) { if (positionalArgs.size() != 2 && positionalArgs.size() != 3) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } pluginImportPath = QDir::fromNativeSeparators(positionalArgs.at(1)); if (positionalArgs.size() == 3) pluginImportVersion = positionalArgs.at(2); } else if (action == Builtins) { if (positionalArgs.size() != 1) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } } } QQmlEngine engine; if (!pluginImportPath.isEmpty()) { QDir cur = QDir::current(); cur.cd(pluginImportPath); pluginImportPath = cur.canonicalPath(); QDir::setCurrent(pluginImportPath); engine.addImportPath(pluginImportPath); } // Merge file. QStringList mergeDependencies; QString mergeComponents; if (!mergeFile.isEmpty()) { const QStringList merge = readQmlTypes(mergeFile); if (!merge.isEmpty()) { QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)"); QRegularExpressionMatchIterator i = re.globalMatch(merge[1]); while (i.hasNext()) { QRegularExpressionMatch m = i.next(); QString d = m.captured(1); mergeDependencies << m.captured(1); } mergeComponents = merge [2]; } } // Dependencies. bool calculateDependencies = !pluginImportUri.isEmpty() && !pluginImportVersion.isEmpty(); QStringList dependencies; if (!dependenciesFile.isEmpty()) calculateDependencies = !readDependenciesFile(dependenciesFile, &dependencies, QStringList(pluginImportUri)) && calculateDependencies; if (calculateDependencies) getDependencies(engine, pluginImportUri, pluginImportVersion, &dependencies, forceQtQuickDependency); compactDependencies(&dependencies); QString qtQmlImportString = QString::fromLatin1("import QtQml %1.%2") .arg(qtQmlMajorVersion) .arg(qtQmlMinorVersion); // load the QtQml builtins and the dependencies { QByteArray code(qtQmlImportString.toUtf8()); for (const QString &moduleToImport : qAsConst(dependencies)) { code.append("\nimport "); code.append(moduleToImport.toUtf8()); } code.append("\nQtObject {}"); QQmlComponent c(&engine); c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/loaddependencies.qml")); c.create(); const auto errors = c.errors(); if (!errors.isEmpty()) { for (const QQmlError &error : errors) std::cerr << qPrintable( error.toString() ) << std::endl; return EXIT_IMPORTERROR; } } // find all QMetaObjects reachable from the builtin module QSet uncreatableMetas; QSet singletonMetas; QMap> defaultCompositeTypes; QSet defaultReachable = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, defaultCompositeTypes); QList defaultTypes = QQmlMetaType::qmlTypes(); // add some otherwise unreachable QMetaObjects defaultReachable.insert(&QQuickMouseEvent::staticMetaObject); // QQuickKeyEvent, QQuickPinchEvent, QQuickDropEvent are not exported QSet defaultReachableNames; // this will hold the meta objects we want to dump information of QSet metas; // composite types we want to dump information of QMap> compositeTypes; if (action == Builtins) { for (const QMetaObject *m : qAsConst(defaultReachable)) { if (m->className() == QLatin1String("Qt")) { metas.insert(m); break; } } } else if (pluginImportUri == QLatin1String("QtQml")) { bool ok = false; const uint major = pluginImportVersion.splitRef('.').at(0).toUInt(&ok, 10); if (!ok) { std::cerr << "Malformed version string \""<< qPrintable(pluginImportVersion) << "\"." << std::endl; return EXIT_INVALIDARGUMENTS; } if (major != qtQmlMajorVersion) { std::cerr << "Unsupported version \"" << qPrintable(pluginImportVersion) << "\": Major version number must be \"" << qtQmlMajorVersion << "\"." << std::endl; return EXIT_INVALIDARGUMENTS; } metas = defaultReachable; for (const QMetaObject *m : qAsConst(defaultReachable)) { if (m->className() == QLatin1String("Qt")) { metas.remove(m); break; } } } else { // find a valid QtQuick import QByteArray importCode; QQmlType qtObjectType = QQmlMetaType::qmlType(&QObject::staticMetaObject); if (!qtObjectType.isValid()) { std::cerr << "Could not find QtObject type" << std::endl; importCode = qtQmlImportString.toUtf8(); } else { QString module = qtObjectType.qmlTypeName(); module = module.mid(0, module.lastIndexOf(QLatin1Char('/'))); importCode = QString("import %1 %2.%3").arg(module, QString::number(qtObjectType.majorVersion()), QString::number(qtObjectType.minorVersion())).toUtf8(); } // avoid importing dependencies? for (const QString &moduleToImport : qAsConst(dependencies)) { importCode.append("\nimport "); importCode.append(moduleToImport.toUtf8()); } // find all QMetaObjects reachable when the specified module is imported if (action != Path) { importCode += QString("\nimport %0 %1\n").arg(pluginImportUri, pluginImportVersion).toLatin1(); } else { // pluginImportVersion can be empty importCode += QString("\nimport \".\" %2\n").arg(pluginImportVersion).toLatin1(); } // create a component with these imports to make sure the imports are valid // and to populate the declarative meta type system { QByteArray code = importCode; code += "\nQtObject {}"; QQmlComponent c(&engine); c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/typelist.qml")); c.create(); const auto errors = c.errors(); if (!errors.isEmpty()) { for (const QQmlError &error : errors) std::cerr << qPrintable( error.toString() ) << std::endl; return EXIT_IMPORTERROR; } } QSet candidates = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, compositeTypes, defaultTypes); candidates.subtract(defaultReachable); for (QString iter: compositeTypes.keys()) { if (defaultCompositeTypes.contains(iter)) { QSet compositeTypesByName = compositeTypes.value(iter); compositeTypesByName.subtract(defaultCompositeTypes.value(iter)); compositeTypes[iter] = compositeTypesByName; } } // Also eliminate meta objects with the same classname. // This is required because extended objects seem not to share // a single meta object instance. for (const QMetaObject *mo : qAsConst(defaultReachable)) defaultReachableNames.insert(QByteArray(mo->className())); for (const QMetaObject *mo : qAsConst(candidates)) { if (!defaultReachableNames.contains(mo->className())) metas.insert(mo); } } // setup static rewrites of type names cppToId.insert("QString", "string"); cppToId.insert("QQmlEasingValueType::Type", "Type"); // start dumping data QByteArray bytes; QmlStreamWriter qml(&bytes); qml.writeStartDocument(); qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2); qml.write(QString("\n" "// This file describes the plugin-supplied types contained in the library.\n" "// It is used for QML tooling purposes only.\n" "//\n" "// This file was auto-generated by:\n" "// '%1 %2'\n" "\n").arg(QFileInfo(args.at(0)).baseName(), args.mid(1).join(QLatin1Char(' ')))); qml.writeStartObject("Module"); // Insert merge dependencies. if (!mergeDependencies.isEmpty()) { dependencies << mergeDependencies; } compactDependencies(&dependencies); QStringList quotedDependencies; for (const QString &dep : qAsConst(dependencies)) quotedDependencies << enquote(dep); qml.writeArrayBinding("dependencies", quotedDependencies); // put the metaobjects into a map so they are always dumped in the same order QMap nameToMeta; for (const QMetaObject *meta : qAsConst(metas)) nameToMeta.insert(convertToId(meta), meta); Dumper dumper(&qml); if (relocatable) dumper.setRelocatableModuleUri(pluginImportUri); for (const QMetaObject *meta : qAsConst(nameToMeta)) { dumper.dump(QQmlEnginePrivate::get(&engine), meta, uncreatableMetas.contains(meta), singletonMetas.contains(meta)); } QMap >::const_iterator iter = compositeTypes.constBegin(); for (; iter != compositeTypes.constEnd(); ++iter) dumper.dumpComposite(&engine, iter.value(), defaultReachableNames); // define QEasingCurve as an extension of QQmlEasingValueType, this way // properties using the QEasingCurve type get useful type information. if (pluginImportUri.isEmpty()) dumper.writeEasingCurve(); // Insert merge file. qml.write(mergeComponents); qml.writeEndObject(); qml.writeEndDocument(); if (!outputFilename.isEmpty()) { QFile file(outputFilename); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << bytes.constData(); } } else { std::cout << bytes.constData() << std::flush; } // workaround to avoid crashes on exit QTimer timer; timer.setSingleShot(true); timer.setInterval(0); QObject::connect(&timer, SIGNAL(timeout()), app.data(), SLOT(quit())); timer.start(); return app->exec(); } diff --git a/tools/cutehmi.qmlplugindump.0/src/5/13/2/main.cpp b/tools/cutehmi.qmlplugindump.0/src/5/13/2/main.cpp index 0a66aa41..25bbbba9 100644 --- a/tools/cutehmi.qmlplugindump.0/src/5/13/2/main.cpp +++ b/tools/cutehmi.qmlplugindump.0/src/5/13/2/main.cpp @@ -1,1374 +1,1374 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #ifdef QT_WIDGETS_LIB #include #endif // QT_WIDGETS_LIB #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmltypereader.h" #include "qmlstreamwriter.h" #ifdef QT_SIMULATOR #include #endif #ifdef Q_OS_WIN # if !defined(Q_CC_MINGW) # include # endif #include #endif namespace { const uint qtQmlMajorVersion = 2; const uint qtQmlMinorVersion = 0; const uint qtQuickMajorVersion = 2; const uint qtQuickMinorVersion = 0; const QString qtQuickQualifiedName = QString::fromLatin1("QtQuick %1.%2") .arg(qtQuickMajorVersion) .arg(qtQuickMinorVersion); QString pluginImportPath; bool verbose = false; bool creatable = true; QString currentProperty; QString inObjectInstantiation; } static QString enquote(const QString &string) { QString s = string; return QString("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\")) .replace(QLatin1Char('"'),QLatin1String("\\\""))); } void collectReachableMetaObjects(const QMetaObject *meta, QSet *metas, bool extended = false) { if (! meta || metas->contains(meta)) return; // dynamic meta objects can break things badly // but extended types are usually fine const QMetaObjectPrivate *mop = reinterpret_cast(meta->d.data); if (extended || !(mop->flags & DynamicMetaObject)) metas->insert(meta); collectReachableMetaObjects(meta->superClass(), metas); } void collectReachableMetaObjects(QObject *object, QSet *metas) { if (! object) return; const QMetaObject *meta = object->metaObject(); if (verbose) std::cerr << "Processing object " << qPrintable( meta->className() ) << std::endl; collectReachableMetaObjects(meta, metas); for (int index = 0; index < meta->propertyCount(); ++index) { QMetaProperty prop = meta->property(index); if (QQmlMetaType::isQObject(prop.userType())) { if (verbose) std::cerr << " Processing property " << qPrintable( prop.name() ) << std::endl; currentProperty = QString("%1::%2").arg(meta->className(), prop.name()); // if the property was not initialized during construction, // accessing a member of oo is going to cause a segmentation fault QObject *oo = QQmlMetaType::toQObject(prop.read(object)); if (oo && !metas->contains(oo->metaObject())) collectReachableMetaObjects(oo, metas); currentProperty.clear(); } } } void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet *metas) { collectReachableMetaObjects(ty.baseMetaObject(), metas, ty.isExtendedType()); if (ty.attachedPropertiesType(engine)) collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas); } /* We want to add the MetaObject for 'Qt' to the list, this is a simple way to access it. */ class FriendlyQObject: public QObject { public: static const QMetaObject *qtMeta() { return &staticQtMetaObject; } }; /* When we dump a QMetaObject, we want to list all the types it is exported as. To do this, we need to find the QQmlTypes associated with this QMetaObject. */ static QHash > qmlTypesByCppName; static QHash cppToId; /* Takes a C++ type name, such as Qt::LayoutDirection or QString and maps it to how it should appear in the description file. These names need to be unique globally, so we don't change the C++ symbol's name much. It is mostly used to for explicit translations such as QString->string and translations for extended QML objects. */ QByteArray convertToId(const QByteArray &cppName) { return cppToId.value(cppName, cppName); } QByteArray convertToId(const QMetaObject *mo) { QByteArray className(mo->className()); if (!className.isEmpty()) return convertToId(className); // likely a metaobject generated for an extended qml object if (mo->superClass()) { className = convertToId(mo->superClass()); className.append("_extended"); return className; } static QHash generatedNames; className = generatedNames.value(mo); if (!className.isEmpty()) return className; std::cerr << "Found a QMetaObject without a className, generating a random name" << std::endl; className = QByteArray("error-unknown-name-"); className.append(QByteArray::number(generatedNames.size())); generatedNames.insert(mo, className); return className; } // Collect all metaobjects for types registered with qmlRegisterType() without parameters void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet& metas, QMap> &compositeTypes) { const auto qmlAllTypes = QQmlMetaType::qmlAllTypes(); for (const QQmlType &ty : qmlAllTypes) { if (!metas.contains(ty.baseMetaObject())) { if (!ty.isComposite()) { collectReachableMetaObjects(engine, ty, &metas); } else { compositeTypes[ty.elementName()].insert(ty); } } } } QSet collectReachableMetaObjects(QQmlEngine *engine, QSet &noncreatables, QSet &singletons, QMap> &compositeTypes, const QList &skip = QList()) { QSet metas; metas.insert(FriendlyQObject::qtMeta()); const auto qmlTypes = QQmlMetaType::qmlTypes(); for (const QQmlType &ty : qmlTypes) { if (!ty.isCreatable()) noncreatables.insert(ty.baseMetaObject()); if (ty.isSingleton()) singletons.insert(ty.baseMetaObject()); if (!ty.isComposite()) { qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty); collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas); } else { compositeTypes[ty.elementName()].insert(ty); } } if (creatable) { // find even more QMetaObjects by instantiating QML types and running // over the instances for (const QQmlType &ty : qmlTypes) { if (skip.contains(ty)) continue; if (ty.isExtendedType()) continue; if (!ty.isCreatable()) continue; if (ty.typeName() == "QQmlComponent") continue; QString tyName = ty.qmlTypeName(); tyName = tyName.mid(tyName.lastIndexOf(QLatin1Char('/')) + 1); if (tyName.isEmpty()) continue; inObjectInstantiation = tyName; QObject *object = nullptr; if (ty.isSingleton()) { QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo(); if (!siinfo) { std::cerr << "Internal error, " << qPrintable(tyName) << "(" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << " is singleton, but has no singletonInstanceInfo" << std::endl; continue; } if (siinfo->qobjectCallback) { if (verbose) std::cerr << "Trying to get singleton for " << qPrintable(tyName) << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl; siinfo->init(engine); collectReachableMetaObjects(object, &metas); object = siinfo->qobjectApi(engine); } else { inObjectInstantiation.clear(); continue; // we don't handle QJSValue singleton types. } } else { if (verbose) std::cerr << "Trying to create object " << qPrintable( tyName ) << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; object = ty.create(); } inObjectInstantiation.clear(); if (object) { if (verbose) std::cerr << "Got " << qPrintable( tyName ) << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; collectReachableMetaObjects(object, &metas); object->deleteLater(); } else { std::cerr << "Could not create " << qPrintable(tyName) << std::endl; } } } collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate::get(engine), metas, compositeTypes); return metas; } class KnownAttributes { QHash m_properties; QHash > m_methods; public: bool knownMethod(const QByteArray &name, int nArgs, int revision) { if (m_methods.contains(name)) { QHash overloads = m_methods.value(name); if (overloads.contains(nArgs) && overloads.value(nArgs) <= revision) return true; } m_methods[name][nArgs] = revision; return false; } bool knownProperty(const QByteArray &name, int revision) { if (m_properties.contains(name) && m_properties.value(name) <= revision) return true; m_properties[name] = revision; return false; } }; class Dumper { QmlStreamWriter *qml; QString relocatableModuleUri; public: Dumper(QmlStreamWriter *qml) : qml(qml) {} void setRelocatableModuleUri(const QString &uri) { relocatableModuleUri = uri; } const QString getExportString(QString qmlTyName, int majorVersion, int minorVersion) { if (qmlTyName.startsWith(relocatableModuleUri + QLatin1Char('/'))) { qmlTyName.remove(0, relocatableModuleUri.size() + 1); } if (qmlTyName.startsWith("./")) { qmlTyName.remove(0, 2); } if (qmlTyName.startsWith(QLatin1Char('/'))) { qmlTyName.remove(0, 1); } const QString exportString = enquote( QString("%1 %2.%3").arg( qmlTyName, QString::number(majorVersion), QString::number(minorVersion))); return exportString; } void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) { QSet implicitSignals = dumpMetaProperties(meta, 0, knownAttributes); if (meta == &QObject::staticMetaObject) { // for QObject, hide deleteLater() and onDestroyed for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) { QMetaMethod method = meta->method(index); QByteArray signature = method.methodSignature(); if (signature == QByteArrayLiteral("destroyed(QObject*)") || signature == QByteArrayLiteral("destroyed()") || signature == QByteArrayLiteral("deleteLater()")) continue; dump(method, implicitSignals, knownAttributes); } // and add toString(), destroy() and destroy(int) if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("toString"), 0, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString"))); qml->writeEndObject(); } if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 0, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); qml->writeEndObject(); } if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 1, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); qml->writeStartObject(QLatin1String("Parameter")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay"))); qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int"))); qml->writeEndObject(); qml->writeEndObject(); } } else { for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) dump(meta->method(index), implicitSignals, knownAttributes); } } QString getPrototypeNameForCompositeType(const QMetaObject *metaObject, QSet &defaultReachableNames, QList *objectsToMerge) { QString prototypeName; if (!defaultReachableNames.contains(metaObject->className())) { // dynamic meta objects can break things badly // but extended types are usually fine const QMetaObjectPrivate *mop = reinterpret_cast(metaObject->d.data); if (!(mop->flags & DynamicMetaObject) && objectsToMerge && !objectsToMerge->contains(metaObject)) objectsToMerge->append(metaObject); const QMetaObject *superMetaObject = metaObject->superClass(); if (!superMetaObject) prototypeName = "QObject"; else prototypeName = getPrototypeNameForCompositeType( superMetaObject, defaultReachableNames, objectsToMerge); } else { prototypeName = convertToId(metaObject->className()); } return prototypeName; } void dumpComposite(QQmlEngine *engine, const QSet &compositeType, QSet &defaultReachableNames) { for (const QQmlType &type : compositeType) dumpCompositeItem(engine, type, defaultReachableNames); } void dumpCompositeItem(QQmlEngine *engine, const QQmlType &compositeType, QSet &defaultReachableNames) { QQmlComponent e(engine, compositeType.sourceUrl()); if (!e.isReady()) { std::cerr << "WARNING: skipping module " << compositeType.elementName().toStdString() << std::endl << e.errorString().toStdString() << std::endl; return; } QObject *object = e.create(); if (!object) return; qml->writeStartObject("Component"); const QMetaObject *mainMeta = object->metaObject(); QList objectsToMerge; KnownAttributes knownAttributes; // Get C++ base class name for the composite type QString prototypeName = getPrototypeNameForCompositeType(mainMeta, defaultReachableNames, &objectsToMerge); qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName)); QString qmlTyName = compositeType.qmlTypeName(); const QString exportString = getExportString(qmlTyName, compositeType.majorVersion(), compositeType.minorVersion()); qml->writeScriptBinding(QLatin1String("name"), exportString); qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString); qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList() << QString::number(compositeType.minorVersion())); qml->writeBooleanBinding(QLatin1String("isComposite"), true); if (compositeType.isSingleton()) { qml->writeBooleanBinding(QLatin1String("isCreatable"), false); qml->writeBooleanBinding(QLatin1String("isSingleton"), true); } for (int index = mainMeta->classInfoCount() - 1 ; index >= 0 ; --index) { QMetaClassInfo classInfo = mainMeta->classInfo(index); if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value()))); break; } } for (const QMetaObject *meta : qAsConst(objectsToMerge)) { for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) dump(meta->enumerator(index)); writeMetaContent(meta, &knownAttributes); } qml->writeEndObject(); } QString getDefaultProperty(const QMetaObject *meta) { for (int index = meta->classInfoCount() - 1; index >= 0; --index) { QMetaClassInfo classInfo = meta->classInfo(index); if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { return QLatin1String(classInfo.value()); } } return QString(); } struct QmlTypeInfo { QmlTypeInfo() {} QmlTypeInfo(const QString &exportString, int revision, const QMetaObject *extendedObject, QByteArray attachedTypeId) : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {} QString exportString; int revision = 0; const QMetaObject *extendedObject = nullptr; QByteArray attachedTypeId; }; void dump(QQmlEnginePrivate *engine, const QMetaObject *meta, bool isUncreatable, bool isSingleton) { qml->writeStartObject("Component"); QByteArray id = convertToId(meta); qml->writeScriptBinding(QLatin1String("name"), enquote(id)); // collect type information QVector typeInfo; for (QQmlType type : qmlTypesByCppName.value(meta->className())) { const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr; QByteArray attachedTypeId; if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) { // Can happen when a type is registered that returns itself as attachedPropertiesType() // because there is no creatable type to attach to. if (attachedType != meta) attachedTypeId = convertToId(attachedType); } const QString exportString = getExportString(type.qmlTypeName(), type.majorVersion(), type.minorVersion()); int metaObjectRevision = type.metaObjectRevision(); if (extendedObject) { // emulate custom metaobjectrevision out of import metaObjectRevision = type.majorVersion() * 100 + type.minorVersion(); } QmlTypeInfo info = { exportString, metaObjectRevision, extendedObject, attachedTypeId }; typeInfo.append(info); } // sort to ensure stable output std::sort(typeInfo.begin(), typeInfo.end(), [](const QmlTypeInfo &i1, const QmlTypeInfo &i2) { return i1.revision < i2.revision; }); // determine default property // TODO: support revisioning of default property QString defaultProperty = getDefaultProperty(meta); if (defaultProperty.isEmpty()) { for (const QmlTypeInfo &iter : typeInfo) { if (iter.extendedObject) { defaultProperty = getDefaultProperty(iter.extendedObject); if (!defaultProperty.isEmpty()) break; } } } if (!defaultProperty.isEmpty()) qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty)); if (meta->superClass()) qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass()))); if (!typeInfo.isEmpty()) { QMap exports; // sort exports for (const QmlTypeInfo &iter : typeInfo) exports.insert(iter.exportString, QString::number(iter.revision)); QStringList exportStrings = exports.keys(); QStringList metaObjectRevisions = exports.values(); qml->writeArrayBinding(QLatin1String("exports"), exportStrings); if (isUncreatable) qml->writeBooleanBinding(QLatin1String("isCreatable"), false); if (isSingleton) qml->writeBooleanBinding(QLatin1String("isSingleton"), true); qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions); for (const QmlTypeInfo &iter : typeInfo) { if (!iter.attachedTypeId.isEmpty()) { qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId)); break; } } } for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) dump(meta->enumerator(index)); writeMetaContent(meta); // dump properties from extended metaobjects last for (auto iter : typeInfo) { if (iter.extendedObject) dumpMetaProperties(iter.extendedObject, iter.revision); } qml->writeEndObject(); } void writeEasingCurve() { qml->writeStartObject(QLatin1String("Component")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve"))); qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QQmlEasingValueType"))); qml->writeEndObject(); } private: /* Removes pointer and list annotations from a type name, returning what was removed in isList and isPointer */ static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer) { static QByteArray declListPrefix = "QQmlListProperty<"; if (typeName->endsWith('*')) { *isPointer = true; typeName->truncate(typeName->length() - 1); removePointerAndList(typeName, isList, isPointer); } else if (typeName->startsWith(declListPrefix)) { *isList = true; typeName->truncate(typeName->length() - 1); // get rid of the suffix '>' *typeName = typeName->mid(declListPrefix.size()); removePointerAndList(typeName, isList, isPointer); } *typeName = convertToId(*typeName); } void writeTypeProperties(QByteArray typeName, bool isWritable) { bool isList = false, isPointer = false; removePointerAndList(&typeName, &isList, &isPointer); qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); if (isList) qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true")); if (!isWritable) qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true")); if (isPointer) qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); } void dump(const QMetaProperty &prop, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { int revision = metaRevision ? metaRevision : prop.revision(); QByteArray propName = prop.name(); if (knownAttributes && knownAttributes->knownProperty(propName, revision)) return; qml->writeStartObject("Property"); qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name()))); if (revision) qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); writeTypeProperties(prop.typeName(), prop.isWritable()); qml->writeEndObject(); } QSet dumpMetaProperties(const QMetaObject *meta, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { QSet implicitSignals; for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { const QMetaProperty &property = meta->property(index); dump(property, metaRevision, knownAttributes); if (knownAttributes) knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), 0, property.revision()); implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); } return implicitSignals; } void dump(const QMetaMethod &meth, const QSet &implicitSignals, KnownAttributes *knownAttributes = nullptr) { if (meth.methodType() == QMetaMethod::Signal) { if (meth.access() != QMetaMethod::Public) return; // nothing to do. } else if (meth.access() != QMetaMethod::Public) { return; // nothing to do. } QByteArray name = meth.name(); const QString typeName = convertToId(meth.typeName()); if (implicitSignals.contains(name) && !meth.revision() && meth.methodType() == QMetaMethod::Signal && meth.parameterNames().isEmpty() && typeName == QLatin1String("void")) { // don't mention implicit signals return; } int revision = meth.revision(); if (knownAttributes && knownAttributes->knownMethod(name, meth.parameterNames().size(), revision)) return; if (meth.methodType() == QMetaMethod::Signal) qml->writeStartObject(QLatin1String("Signal")); else qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(name)); if (revision) qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); if (typeName != QLatin1String("void")) qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); for (int i = 0; i < meth.parameterTypes().size(); ++i) { QByteArray argName = meth.parameterNames().at(i); qml->writeStartObject(QLatin1String("Parameter")); if (! argName.isEmpty()) qml->writeScriptBinding(QLatin1String("name"), enquote(argName)); writeTypeProperties(meth.parameterTypes().at(i), true); qml->writeEndObject(); } qml->writeEndObject(); } void dump(const QMetaEnum &e) { qml->writeStartObject(QLatin1String("Enum")); qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name()))); QList > namesValues; const int keyCount = e.keyCount(); namesValues.reserve(keyCount); for (int index = 0; index < keyCount; ++index) { namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index)))); } qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues); qml->writeEndObject(); } }; enum ExitCode { EXIT_INVALIDARGUMENTS = 1, EXIT_SEGV = 2, EXIT_IMPORTERROR = 3 }; void printUsage(const QString &appName) { std::cerr << qPrintable(QString( "Usage: %1 [-v] [-qapp] [-noinstantiate] [-defaultplatform] [-[non]relocatable] [-dependencies ] [-merge ] [-output ] [-noforceqtquick] module.uri version [module/import/path]\n" " %1 [-v] [-qapp] [-noinstantiate] -path path/to/qmldir/directory [version]\n" " %1 [-v] -builtins\n" "Example: %1 Qt.labs.folderlistmodel 2.0 /home/user/dev/qt-install/imports").arg( appName)) << std::endl; } static bool readDependenciesData(QString dependenciesFile, const QByteArray &fileData, QStringList *dependencies, const QStringList &urisToSkip, bool forceQtQuickDependency = true) { if (verbose) { std::cerr << "parsing " << qPrintable( dependenciesFile ) << " skipping"; for (const QString &uriToSkip : urisToSkip) std::cerr << ' ' << qPrintable(uriToSkip); std::cerr << std::endl; } QJsonParseError parseError; parseError.error = QJsonParseError::NoError; QJsonDocument doc = QJsonDocument::fromJson(fileData, &parseError); if (parseError.error != QJsonParseError::NoError) { std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() << ":" << parseError.errorString().toStdString() << " at " << parseError.offset << std::endl; return false; } if (doc.isArray()) { const QStringList requiredKeys = QStringList() << QStringLiteral("name") << QStringLiteral("type") << QStringLiteral("version"); const auto deps = doc.array(); for (const QJsonValue &dep : deps) { if (dep.isObject()) { QJsonObject obj = dep.toObject(); for (const QString &requiredKey : requiredKeys) if (!obj.contains(requiredKey) || obj.value(requiredKey).isString()) continue; if (obj.value(QStringLiteral("type")).toString() != QLatin1String("module")) continue; QString name = obj.value((QStringLiteral("name"))).toString(); QString version = obj.value(QStringLiteral("version")).toString(); if (name.isEmpty() || urisToSkip.contains(name) || version.isEmpty()) continue; if (name.contains(QLatin1String("Private"), Qt::CaseInsensitive)) { if (verbose) - std::cerr << "skipping private dependecy " + std::cerr << "skipping private dependency " << qPrintable( name ) << " " << qPrintable(version) << std::endl; continue; } if (verbose) std::cerr << "appending dependency " << qPrintable( name ) << " " << qPrintable(version) << std::endl; dependencies->append(name + QLatin1Char(' ')+version); } } } else { std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() << ": expected an array" << std::endl; return false; } // Workaround for avoiding conflicting types when no dependency has been found. // // qmlplugindump used to import QtQuick, so all types defined in QtQuick used to be skipped when dumping. // Now that it imports only Qt, it is no longer the case: if no dependency is found all the types defined // in QtQuick will be dumped, causing conflicts. if (forceQtQuickDependency && dependencies->isEmpty()) dependencies->push_back(qtQuickQualifiedName); return true; } static bool readDependenciesFile(const QString &dependenciesFile, QStringList *dependencies, const QStringList &urisToSkip) { if (!QFileInfo::exists(dependenciesFile)) { std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() << std::endl; return false; } QFile f(dependenciesFile); if (!f.open(QFileDevice::ReadOnly)) { std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() << ", " << f.errorString().toStdString() << std::endl; return false; } QByteArray fileData = f.readAll(); return readDependenciesData(dependenciesFile, fileData, dependencies, urisToSkip, false); } static bool getDependencies(const QQmlEngine &engine, const QString &pluginImportUri, const QString &pluginImportVersion, QStringList *dependencies, bool forceQtQuickDependency) { QString importScannerExe = QLatin1String("qmlimportscanner"); QFileInfo selfExe(QCoreApplication::applicationFilePath()); if (!selfExe.suffix().isEmpty()) importScannerExe += QLatin1String(".") + selfExe.suffix(); QString command = selfExe.absoluteDir().filePath(importScannerExe); QStringList commandArgs = QStringList() << QLatin1String("-qmlFiles") << QLatin1String("-"); QStringList importPathList = engine.importPathList(); importPathList.removeOne(QStringLiteral("qrc:/qt-project.org/imports")); for (const QString &path : importPathList) commandArgs << QLatin1String("-importPath") << path; QProcess importScanner; importScanner.start(command, commandArgs, QProcess::ReadWrite); if (!importScanner.waitForStarted()) return false; importScanner.write("import "); importScanner.write(pluginImportUri.toUtf8()); importScanner.write(" "); importScanner.write(pluginImportVersion.toUtf8()); importScanner.write("\nQtObject{}\n"); importScanner.closeWriteChannel(); if (!importScanner.waitForFinished()) { std::cerr << "failure to start " << qPrintable(command); for (const QString &arg : qAsConst(commandArgs)) std::cerr << ' ' << qPrintable(arg); std::cerr << std::endl; return false; } QByteArray depencenciesData = importScanner.readAllStandardOutput(); if (!readDependenciesData(QLatin1String(""), depencenciesData, dependencies, QStringList(pluginImportUri), forceQtQuickDependency)) { std::cerr << "failed to process output of qmlimportscanner" << std::endl; if (importScanner.exitCode() != 0) std::cerr << importScanner.readAllStandardError().toStdString(); return false; } QStringList aux; for (const QString &str : qAsConst(*dependencies)) { if (!str.startsWith("Qt.test.qtestroot")) aux += str; } *dependencies = aux; return true; } bool compactDependencies(QStringList *dependencies) { if (dependencies->isEmpty()) return false; dependencies->sort(); QStringList oldDep = dependencies->constFirst().split(QLatin1Char(' ')); Q_ASSERT(oldDep.size() == 2); int oldPos = 0; for (int idep = 1; idep < dependencies->size(); ++idep) { QString depStr = dependencies->at(idep); const QStringList newDep = depStr.split(QLatin1Char(' ')); Q_ASSERT(newDep.size() == 2); if (newDep.constFirst() != oldDep.constFirst()) { if (++oldPos != idep) dependencies->replace(oldPos, depStr); oldDep = newDep; } else { const QStringList v1 = oldDep.constLast().split(QLatin1Char('.')); const QStringList v2 = newDep.constLast().split(QLatin1Char('.')); Q_ASSERT(v1.size() == 2); Q_ASSERT(v2.size() == 2); bool ok; int major1 = v1.first().toInt(&ok); Q_ASSERT(ok); int major2 = v2.first().toInt(&ok); Q_ASSERT(ok); if (major1 != major2) { std::cerr << "Found a dependency on " << qPrintable(oldDep.constFirst()) << " with two major versions:" << qPrintable(oldDep.constLast()) << " and " << qPrintable(newDep.constLast()) << " which is unsupported, discarding smaller version" << std::endl; if (major1 < major2) dependencies->replace(oldPos, depStr); } else { int minor1 = v1.last().toInt(&ok); Q_ASSERT(ok); int minor2 = v2.last().toInt(&ok); Q_ASSERT(ok); if (minor1 < minor2) dependencies->replace(oldPos, depStr); } } } if (++oldPos < dependencies->size()) { *dependencies = dependencies->mid(0, oldPos); return true; } return false; } inline std::wostream &operator<<(std::wostream &str, const QString &s) { #ifdef Q_OS_WIN str << reinterpret_cast(s.utf16()); #else str << s.toStdWString(); #endif return str; } void printDebugMessage(QtMsgType, const QMessageLogContext &, const QString &msg) { std::wcerr << msg << std::endl; // In case of QtFatalMsg the calling code will abort() when appropriate. } int main(int argc, char *argv[]) { #if defined(Q_OS_WIN) && !defined(Q_CC_MINGW) // we do not want windows popping up if the module loaded triggers an assert SetErrorMode(SEM_NOGPFAULTERRORBOX); _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); #endif // Q_OS_WIN && !Q_CC_MINGW // The default message handler might not print to console on some systems. Enforce this. qInstallMessageHandler(printDebugMessage); #ifdef QT_SIMULATOR // Running this application would bring up the Qt Simulator (since it links Qt GUI), avoid that! QtSimulatorPrivate::SimulatorConnection::createStubInstance(); #endif // don't require a window manager even though we're a QGuiApplication bool requireWindowManager = false; for (int index = 1; index < argc; ++index) { if (QString::fromLocal8Bit(argv[index]) == "--defaultplatform" || QString::fromLocal8Bit(argv[index]) == "-defaultplatform") { requireWindowManager = true; break; } } if (!requireWindowManager && qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal")); else QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); // Check which kind of application should be instantiated. bool useQApplication = false; for (int i = 0; i < argc; ++i) { QString arg = QLatin1String(argv[i]); if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) useQApplication = true; } #ifdef QT_WIDGETS_LIB QScopedPointer app(useQApplication ? new QApplication(argc, argv) : new QGuiApplication(argc, argv)); #else Q_UNUSED(useQApplication); QScopedPointer app(new QGuiApplication(argc, argv)); #endif // QT_WIDGETS_LIB QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); QStringList args = app->arguments(); const QString appName = QFileInfo(app->applicationFilePath()).baseName(); if (args.size() < 2) { printUsage(appName); return EXIT_INVALIDARGUMENTS; } QString outputFilename; QString pluginImportUri; QString pluginImportVersion; bool relocatable = true; QString dependenciesFile; QString mergeFile; bool forceQtQuickDependency = true; enum Action { Uri, Path, Builtins }; Action action = Uri; { QStringList positionalArgs; for (int iArg = 0; iArg < args.size(); ++iArg) { const QString &arg = args.at(iArg); if (!arg.startsWith(QLatin1Char('-'))) { positionalArgs.append(arg); continue; } if (arg == QLatin1String("--dependencies") || arg == QLatin1String("-dependencies")) { if (++iArg == args.size()) { std::cerr << "missing dependencies file" << std::endl; return EXIT_INVALIDARGUMENTS; } dependenciesFile = args.at(iArg); // Remove absolute path so that it does not show up in the // printed command line inside the plugins.qmltypes file. args[iArg] = QFileInfo(args.at(iArg)).fileName(); } else if (arg == QLatin1String("--merge") || arg == QLatin1String("-merge")) { if (++iArg == args.size()) { std::cerr << "missing merge file" << std::endl; return EXIT_INVALIDARGUMENTS; } mergeFile = args.at(iArg); } else if (arg == QLatin1String("--notrelocatable") || arg == QLatin1String("-notrelocatable") || arg == QLatin1String("--nonrelocatable") || arg == QLatin1String("-nonrelocatable")) { relocatable = false; } else if (arg == QLatin1String("--relocatable") || arg == QLatin1String("-relocatable")) { relocatable = true; } else if (arg == QLatin1String("--noinstantiate") || arg == QLatin1String("-noinstantiate")) { creatable = false; } else if (arg == QLatin1String("--path") || arg == QLatin1String("-path")) { action = Path; } else if (arg == QLatin1String("--builtins") || arg == QLatin1String("-builtins")) { action = Builtins; } else if (arg == QLatin1String("-v")) { verbose = true; } else if (arg == QLatin1String("--noforceqtquick") || arg == QLatin1String("-noforceqtquick")){ forceQtQuickDependency = false; } else if (arg == QLatin1String("--output") || arg == QLatin1String("-output")) { if (++iArg == args.size()) { std::cerr << "missing output file" << std::endl; return EXIT_INVALIDARGUMENTS; } outputFilename = args.at(iArg); } else if (arg == QLatin1String("--defaultplatform") || arg == QLatin1String("-defaultplatform")) { continue; } else if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) { continue; } else { std::cerr << "Invalid argument: " << qPrintable(arg) << std::endl; return EXIT_INVALIDARGUMENTS; } } if (action == Uri) { if (positionalArgs.size() != 3 && positionalArgs.size() != 4) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } pluginImportUri = positionalArgs.at(1); pluginImportVersion = positionalArgs[2]; if (positionalArgs.size() >= 4) pluginImportPath = positionalArgs.at(3); } else if (action == Path) { if (positionalArgs.size() != 2 && positionalArgs.size() != 3) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } pluginImportPath = QDir::fromNativeSeparators(positionalArgs.at(1)); if (positionalArgs.size() == 3) pluginImportVersion = positionalArgs.at(2); } else if (action == Builtins) { if (positionalArgs.size() != 1) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } } } QQmlEngine engine; if (!pluginImportPath.isEmpty()) { QDir cur = QDir::current(); cur.cd(pluginImportPath); pluginImportPath = cur.canonicalPath(); QDir::setCurrent(pluginImportPath); engine.addImportPath(pluginImportPath); } // Merge file. QStringList mergeDependencies; QString mergeComponents; if (!mergeFile.isEmpty()) { const QStringList merge = readQmlTypes(mergeFile); if (!merge.isEmpty()) { QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)"); QRegularExpressionMatchIterator i = re.globalMatch(merge[1]); while (i.hasNext()) { QRegularExpressionMatch m = i.next(); mergeDependencies << m.captured(1); } mergeComponents = merge [2]; } } // Dependencies. bool calculateDependencies = !pluginImportUri.isEmpty() && !pluginImportVersion.isEmpty(); QStringList dependencies; if (!dependenciesFile.isEmpty()) calculateDependencies = !readDependenciesFile(dependenciesFile, &dependencies, QStringList(pluginImportUri)) && calculateDependencies; if (calculateDependencies) getDependencies(engine, pluginImportUri, pluginImportVersion, &dependencies, forceQtQuickDependency); compactDependencies(&dependencies); QString qtQmlImportString = QString::fromLatin1("import QtQml %1.%2") .arg(qtQmlMajorVersion) .arg(qtQmlMinorVersion); // load the QtQml builtins and the dependencies { QByteArray code(qtQmlImportString.toUtf8()); for (const QString &moduleToImport : qAsConst(dependencies)) { code.append("\nimport "); code.append(moduleToImport.toUtf8()); } code.append("\nQtObject {}"); QQmlComponent c(&engine); c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/loaddependencies.qml")); c.create(); const auto errors = c.errors(); if (!errors.isEmpty()) { for (const QQmlError &error : errors) std::cerr << qPrintable( error.toString() ) << std::endl; return EXIT_IMPORTERROR; } } // find all QMetaObjects reachable from the builtin module QSet uncreatableMetas; QSet singletonMetas; QMap> defaultCompositeTypes; QSet defaultReachable = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, defaultCompositeTypes); QList defaultTypes = QQmlMetaType::qmlTypes(); // add some otherwise unreachable QMetaObjects defaultReachable.insert(&QQuickMouseEvent::staticMetaObject); // QQuickKeyEvent, QQuickPinchEvent, QQuickDropEvent are not exported QSet defaultReachableNames; // this will hold the meta objects we want to dump information of QSet metas; // composite types we want to dump information of QMap> compositeTypes; if (action == Builtins) { for (const QMetaObject *m : qAsConst(defaultReachable)) { if (m->className() == QLatin1String("Qt")) { metas.insert(m); break; } } } else if (pluginImportUri == QLatin1String("QtQml")) { bool ok = false; const uint major = pluginImportVersion.splitRef('.').at(0).toUInt(&ok, 10); if (!ok) { std::cerr << "Malformed version string \""<< qPrintable(pluginImportVersion) << "\"." << std::endl; return EXIT_INVALIDARGUMENTS; } if (major != qtQmlMajorVersion) { std::cerr << "Unsupported version \"" << qPrintable(pluginImportVersion) << "\": Major version number must be \"" << qtQmlMajorVersion << "\"." << std::endl; return EXIT_INVALIDARGUMENTS; } metas = defaultReachable; for (const QMetaObject *m : qAsConst(defaultReachable)) { if (m->className() == QLatin1String("Qt")) { metas.remove(m); break; } } } else { // find a valid QtQuick import QByteArray importCode; QQmlType qtObjectType = QQmlMetaType::qmlType(&QObject::staticMetaObject); if (!qtObjectType.isValid()) { std::cerr << "Could not find QtObject type" << std::endl; importCode = qtQmlImportString.toUtf8(); } else { QString module = qtObjectType.qmlTypeName(); module = module.mid(0, module.lastIndexOf(QLatin1Char('/'))); importCode = QString("import %1 %2.%3").arg(module, QString::number(qtObjectType.majorVersion()), QString::number(qtObjectType.minorVersion())).toUtf8(); } // avoid importing dependencies? for (const QString &moduleToImport : qAsConst(dependencies)) { importCode.append("\nimport "); importCode.append(moduleToImport.toUtf8()); } // find all QMetaObjects reachable when the specified module is imported if (action != Path) { importCode += QString("\nimport %0 %1\n").arg(pluginImportUri, pluginImportVersion).toLatin1(); } else { // pluginImportVersion can be empty importCode += QString("\nimport \".\" %2\n").arg(pluginImportVersion).toLatin1(); } // create a component with these imports to make sure the imports are valid // and to populate the declarative meta type system { QByteArray code = importCode; code += "\nQtObject {}"; QQmlComponent c(&engine); c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/typelist.qml")); c.create(); const auto errors = c.errors(); if (!errors.isEmpty()) { for (const QQmlError &error : errors) std::cerr << qPrintable( error.toString() ) << std::endl; return EXIT_IMPORTERROR; } } QSet candidates = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, compositeTypes, defaultTypes); candidates.subtract(defaultReachable); for (QString iter: compositeTypes.keys()) { if (defaultCompositeTypes.contains(iter)) { QSet compositeTypesByName = compositeTypes.value(iter); compositeTypesByName.subtract(defaultCompositeTypes.value(iter)); compositeTypes[iter] = compositeTypesByName; } } // Also eliminate meta objects with the same classname. // This is required because extended objects seem not to share // a single meta object instance. for (const QMetaObject *mo : qAsConst(defaultReachable)) defaultReachableNames.insert(QByteArray(mo->className())); for (const QMetaObject *mo : qAsConst(candidates)) { if (!defaultReachableNames.contains(mo->className())) metas.insert(mo); } } // setup static rewrites of type names cppToId.insert("QString", "string"); cppToId.insert("QQmlEasingValueType::Type", "Type"); // start dumping data QByteArray bytes; QmlStreamWriter qml(&bytes); qml.writeStartDocument(); qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2); qml.write(QString("\n" "// This file describes the plugin-supplied types contained in the library.\n" "// It is used for QML tooling purposes only.\n" "//\n" "// This file was auto-generated by:\n" "// '%1 %2'\n" "\n").arg(QFileInfo(args.at(0)).baseName(), args.mid(1).join(QLatin1Char(' ')))); qml.writeStartObject("Module"); // Insert merge dependencies. if (!mergeDependencies.isEmpty()) { dependencies << mergeDependencies; } compactDependencies(&dependencies); QStringList quotedDependencies; for (const QString &dep : qAsConst(dependencies)) quotedDependencies << enquote(dep); qml.writeArrayBinding("dependencies", quotedDependencies); // put the metaobjects into a map so they are always dumped in the same order QMap nameToMeta; for (const QMetaObject *meta : qAsConst(metas)) nameToMeta.insert(convertToId(meta), meta); Dumper dumper(&qml); if (relocatable) dumper.setRelocatableModuleUri(pluginImportUri); for (const QMetaObject *meta : qAsConst(nameToMeta)) { dumper.dump(QQmlEnginePrivate::get(&engine), meta, uncreatableMetas.contains(meta), singletonMetas.contains(meta)); } QMap >::const_iterator iter = compositeTypes.constBegin(); for (; iter != compositeTypes.constEnd(); ++iter) dumper.dumpComposite(&engine, iter.value(), defaultReachableNames); // define QEasingCurve as an extension of QQmlEasingValueType, this way // properties using the QEasingCurve type get useful type information. if (pluginImportUri.isEmpty()) dumper.writeEasingCurve(); // Insert merge file. qml.write(mergeComponents); qml.writeEndObject(); qml.writeEndDocument(); if (!outputFilename.isEmpty()) { QFile file(outputFilename); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << bytes.constData(); } } else { std::cout << bytes.constData() << std::flush; } // workaround to avoid crashes on exit QTimer timer; timer.setSingleShot(true); timer.setInterval(0); QObject::connect(&timer, SIGNAL(timeout()), app.data(), SLOT(quit())); timer.start(); return app->exec(); } diff --git a/tools/cutehmi.qmlplugindump.0/src/5/14/0/main.cpp b/tools/cutehmi.qmlplugindump.0/src/5/14/0/main.cpp index 15567184..934f07b6 100644 --- a/tools/cutehmi.qmlplugindump.0/src/5/14/0/main.cpp +++ b/tools/cutehmi.qmlplugindump.0/src/5/14/0/main.cpp @@ -1,1408 +1,1408 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** 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. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #ifdef QT_WIDGETS_LIB #include #endif // QT_WIDGETS_LIB #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmltypereader.h" #include "qmlstreamwriter.h" #ifdef QT_SIMULATOR #include #endif #ifdef Q_OS_WIN # if !defined(Q_CC_MINGW) # include # endif #include #endif namespace { const uint qtQmlMajorVersion = 2; const uint qtQmlMinorVersion = 0; const uint qtQuickMajorVersion = 2; const uint qtQuickMinorVersion = 0; const QString qtQuickQualifiedName = QString::fromLatin1("QtQuick %1.%2") .arg(qtQuickMajorVersion) .arg(qtQuickMinorVersion); QString pluginImportPath; bool verbose = false; bool creatable = true; QString currentProperty; QString inObjectInstantiation; } static QString enquote(const QString &string) { QString s = string; return QString("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\")) .replace(QLatin1Char('"'),QLatin1String("\\\""))); } struct QmlVersionInfo { QString pluginImportUri; int majorVersion; int minorVersion; bool strict; }; static bool matchingImportUri(const QQmlType &ty, const QmlVersionInfo& versionInfo) { if (versionInfo.strict) { return (versionInfo.pluginImportUri == ty.module() && (ty.majorVersion() == versionInfo.majorVersion || ty.majorVersion() == -1)) || ty.module().isEmpty(); } return ty.module().isEmpty() || versionInfo.pluginImportUri == ty.module() || ty.module().startsWith(versionInfo.pluginImportUri + QLatin1Char('.')); } void collectReachableMetaObjects(const QMetaObject *meta, QSet *metas, const QmlVersionInfo &info, bool extended = false, bool alreadyChangedModule = false) { auto ty = QQmlMetaType::qmlType(meta); if (! meta || metas->contains(meta)) return; if (matchingImportUri(ty, info)) { if (!alreadyChangedModule) { // dynamic meta objects can break things badly // but extended types are usually fine const QMetaObjectPrivate *mop = reinterpret_cast(meta->d.data); if (extended || !(mop->flags & DynamicMetaObject)) metas->insert(meta); } else if (!ty.module().isEmpty()) { // empty module (e.g. from an attached property) would cause a (false) match; do not warn about them qWarning() << "Circular module dependency cannot be expressed in plugin.qmltypes file" << "Object was:" << meta->className() << ty.module() << info.pluginImportUri; } } else if (!ty.module().isEmpty()) { alreadyChangedModule = true; } collectReachableMetaObjects(meta->superClass(), metas, info, /*extended=*/ false, alreadyChangedModule); } void collectReachableMetaObjects(QObject *object, QSet *metas, const QmlVersionInfo &info) { if (! object) return; const QMetaObject *meta = object->metaObject(); if (verbose) std::cerr << "Processing object " << qPrintable( meta->className() ) << std::endl; collectReachableMetaObjects(meta, metas, info); for (int index = 0; index < meta->propertyCount(); ++index) { QMetaProperty prop = meta->property(index); if (QQmlMetaType::isQObject(prop.userType())) { if (verbose) std::cerr << " Processing property " << qPrintable( prop.name() ) << std::endl; currentProperty = QString("%1::%2").arg(meta->className(), prop.name()); // if the property was not initialized during construction, // accessing a member of oo is going to cause a segmentation fault QObject *oo = QQmlMetaType::toQObject(prop.read(object)); if (oo && !metas->contains(oo->metaObject())) collectReachableMetaObjects(oo, metas, info); currentProperty.clear(); } } } void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet *metas, const QmlVersionInfo& info) { collectReachableMetaObjects(ty.baseMetaObject(), metas, info, ty.isExtendedType()); if (ty.attachedPropertiesType(engine) && matchingImportUri(ty, info)) { collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas, info); } } /* We want to add the MetaObject for 'Qt' to the list, this is a simple way to access it. */ class FriendlyQObject: public QObject { public: static const QMetaObject *qtMeta() { return &staticQtMetaObject; } }; /* When we dump a QMetaObject, we want to list all the types it is exported as. To do this, we need to find the QQmlTypes associated with this QMetaObject. */ static QHash > qmlTypesByCppName; static QHash cppToId; /* Takes a C++ type name, such as Qt::LayoutDirection or QString and maps it to how it should appear in the description file. These names need to be unique globally, so we don't change the C++ symbol's name much. It is mostly used to for explicit translations such as QString->string and translations for extended QML objects. */ QByteArray convertToId(const QByteArray &cppName) { return cppToId.value(cppName, cppName); } QByteArray convertToId(const QMetaObject *mo) { QByteArray className(mo->className()); if (!className.isEmpty()) return convertToId(className); // likely a metaobject generated for an extended qml object if (mo->superClass()) { className = convertToId(mo->superClass()); className.append("_extended"); return className; } static QHash generatedNames; className = generatedNames.value(mo); if (!className.isEmpty()) return className; std::cerr << "Found a QMetaObject without a className, generating a random name" << std::endl; className = QByteArray("error-unknown-name-"); className.append(QByteArray::number(generatedNames.size())); generatedNames.insert(mo, className); return className; } // Collect all metaobjects for types registered with qmlRegisterType() without parameters void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet& metas, QMap> &compositeTypes, const QmlVersionInfo &info) { const auto qmlAllTypes = QQmlMetaType::qmlAllTypes(); for (const QQmlType &ty : qmlAllTypes) { if (!metas.contains(ty.baseMetaObject())) { if (!ty.isComposite()) { collectReachableMetaObjects(engine, ty, &metas, info); } else if (matchingImportUri(ty, info)) { compositeTypes[ty.elementName()].append(ty); } } } } QSet collectReachableMetaObjects(QQmlEngine *engine, QSet &noncreatables, QSet &singletons, QMap> &compositeTypes, const QmlVersionInfo &info, const QList &skip = QList() ) { QSet metas; metas.insert(FriendlyQObject::qtMeta()); const auto qmlTypes = QQmlMetaType::qmlTypes(); for (const QQmlType &ty : qmlTypes) { if (!matchingImportUri(ty,info)) continue; if (!ty.isCreatable()) noncreatables.insert(ty.baseMetaObject()); if (ty.isSingleton()) singletons.insert(ty.baseMetaObject()); if (!ty.isComposite()) { qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty); collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas, info); } else { compositeTypes[ty.elementName()].append(ty); } } if (creatable) { // find even more QMetaObjects by instantiating QML types and running // over the instances for (const QQmlType &ty : qmlTypes) { if (!matchingImportUri(ty, info)) continue; if (skip.contains(ty)) continue; if (ty.isExtendedType()) continue; if (!ty.isCreatable()) continue; if (ty.typeName() == "QQmlComponent") continue; QString tyName = ty.qmlTypeName(); tyName = tyName.mid(tyName.lastIndexOf(QLatin1Char('/')) + 1); if (tyName.isEmpty()) continue; inObjectInstantiation = tyName; QObject *object = nullptr; if (ty.isSingleton()) { QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo(); if (!siinfo) { std::cerr << "Internal error, " << qPrintable(tyName) << "(" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << " is singleton, but has no singletonInstanceInfo" << std::endl; continue; } if (ty.isQObjectSingleton()) { if (verbose) std::cerr << "Trying to get singleton for " << qPrintable(tyName) << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl; collectReachableMetaObjects(object, &metas, info); object = QQmlEnginePrivate::get(engine)->singletonInstance(ty); } else { inObjectInstantiation.clear(); continue; // we don't handle QJSValue singleton types. } } else { if (verbose) std::cerr << "Trying to create object " << qPrintable( tyName ) << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; object = ty.create(); } inObjectInstantiation.clear(); if (object) { if (verbose) std::cerr << "Got " << qPrintable( tyName ) << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; collectReachableMetaObjects(object, &metas, info); object->deleteLater(); } else { std::cerr << "Could not create " << qPrintable(tyName) << std::endl; } } } collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate::get(engine), metas, compositeTypes, info); return metas; } class KnownAttributes { QHash m_properties; QHash > m_methods; public: bool knownMethod(const QByteArray &name, int nArgs, int revision) { if (m_methods.contains(name)) { QHash overloads = m_methods.value(name); if (overloads.contains(nArgs) && overloads.value(nArgs) <= revision) return true; } m_methods[name][nArgs] = revision; return false; } bool knownProperty(const QByteArray &name, int revision) { if (m_properties.contains(name) && m_properties.value(name) <= revision) return true; m_properties[name] = revision; return false; } }; class Dumper { QmlStreamWriter *qml; QString relocatableModuleUri; public: Dumper(QmlStreamWriter *qml) : qml(qml) {} void setRelocatableModuleUri(const QString &uri) { relocatableModuleUri = uri; } QString getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo) { const QString module = type.module().isEmpty() ? versionInfo.pluginImportUri : type.module(); const int majorVersion = type.majorVersion() >= 0 ? type.majorVersion() : versionInfo.majorVersion; const int minorVersion = type.minorVersion() >= 0 ? type.minorVersion() : versionInfo.minorVersion; const QString versionedElement = type.elementName() + QString::fromLatin1(" %1.%2").arg(majorVersion).arg(minorVersion); return enquote((module == relocatableModuleUri) ? versionedElement : module + QLatin1Char('/') + versionedElement); } void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) { QSet implicitSignals = dumpMetaProperties(meta, 0, knownAttributes); if (meta == &QObject::staticMetaObject) { // for QObject, hide deleteLater() and onDestroyed for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) { QMetaMethod method = meta->method(index); QByteArray signature = method.methodSignature(); if (signature == QByteArrayLiteral("destroyed(QObject*)") || signature == QByteArrayLiteral("destroyed()") || signature == QByteArrayLiteral("deleteLater()")) continue; dump(method, implicitSignals, knownAttributes); } // and add toString(), destroy() and destroy(int) if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("toString"), 0, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString"))); qml->writeEndObject(); } if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 0, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); qml->writeEndObject(); } if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 1, 0)) { qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); qml->writeStartObject(QLatin1String("Parameter")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay"))); qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int"))); qml->writeEndObject(); qml->writeEndObject(); } } else { for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) dump(meta->method(index), implicitSignals, knownAttributes); } } QString getPrototypeNameForCompositeType( const QMetaObject *metaObject, QList *objectsToMerge, const QmlVersionInfo &versionInfo) { auto ty = QQmlMetaType::qmlType(metaObject); QString prototypeName; if (matchingImportUri(ty, versionInfo)) { // dynamic meta objects can break things badly // but extended types are usually fine const QMetaObjectPrivate *mop = reinterpret_cast(metaObject->d.data); if (!(mop->flags & DynamicMetaObject) && objectsToMerge && !objectsToMerge->contains(metaObject)) objectsToMerge->append(metaObject); const QMetaObject *superMetaObject = metaObject->superClass(); if (!superMetaObject) { prototypeName = "QObject"; } else { QQmlType superType = QQmlMetaType::qmlType(superMetaObject); if (superType.isValid() && !superType.isComposite()) return convertToId(superMetaObject->className()); prototypeName = getPrototypeNameForCompositeType( superMetaObject, objectsToMerge, versionInfo); } } else { prototypeName = convertToId(metaObject->className()); } return prototypeName; } void dumpComposite(QQmlEngine *engine, const QList &compositeType, const QmlVersionInfo &versionInfo) { for (const QQmlType &type : compositeType) dumpCompositeItem(engine, type, versionInfo); } void dumpCompositeItem(QQmlEngine *engine, const QQmlType &compositeType, const QmlVersionInfo &versionInfo) { QQmlComponent e(engine, compositeType.sourceUrl()); if (!e.isReady()) { std::cerr << "WARNING: skipping module " << compositeType.elementName().toStdString() << std::endl << e.errorString().toStdString() << std::endl; return; } QObject *object = e.create(); if (!object) return; qml->writeStartObject("Component"); const QMetaObject *mainMeta = object->metaObject(); QList objectsToMerge; KnownAttributes knownAttributes; // Get C++ base class name for the composite type QString prototypeName = getPrototypeNameForCompositeType(mainMeta, &objectsToMerge, versionInfo); qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName)); QString qmlTyName = compositeType.qmlTypeName(); const QString exportString = getExportString(compositeType, versionInfo); // TODO: why don't we simply output the compositeType.elementName() here? // That would make more sense, but it would change the format quite a bit. qml->writeScriptBinding(QLatin1String("name"), exportString); qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString); qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList() << QString::number(compositeType.minorVersion())); qml->writeBooleanBinding(QLatin1String("isComposite"), true); if (compositeType.isSingleton()) { qml->writeBooleanBinding(QLatin1String("isCreatable"), false); qml->writeBooleanBinding(QLatin1String("isSingleton"), true); } for (int index = mainMeta->classInfoCount() - 1 ; index >= 0 ; --index) { QMetaClassInfo classInfo = mainMeta->classInfo(index); if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value()))); break; } } for (const QMetaObject *meta : qAsConst(objectsToMerge)) { for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) dump(meta->enumerator(index)); writeMetaContent(meta, &knownAttributes); } qml->writeEndObject(); } QString getDefaultProperty(const QMetaObject *meta) { for (int index = meta->classInfoCount() - 1; index >= 0; --index) { QMetaClassInfo classInfo = meta->classInfo(index); if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { return QLatin1String(classInfo.value()); } } return QString(); } struct QmlTypeInfo { QmlTypeInfo() {} QmlTypeInfo(const QString &exportString, int revision, const QMetaObject *extendedObject, QByteArray attachedTypeId) : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {} QString exportString; int revision = 0; const QMetaObject *extendedObject = nullptr; QByteArray attachedTypeId; }; void dump(QQmlEnginePrivate *engine, const QMetaObject *meta, bool isUncreatable, bool isSingleton) { qml->writeStartObject("Component"); QByteArray id = convertToId(meta); qml->writeScriptBinding(QLatin1String("name"), enquote(id)); // collect type information QVector typeInfo; for (QQmlType type : qmlTypesByCppName.value(meta->className())) { const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr; QByteArray attachedTypeId; if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) { // Can happen when a type is registered that returns itself as attachedPropertiesType() // because there is no creatable type to attach to. if (attachedType != meta) attachedTypeId = convertToId(attachedType); } const QString exportString = getExportString(type, { QString(), -1, -1, false }); int metaObjectRevision = type.metaObjectRevision(); if (extendedObject) { // emulate custom metaobjectrevision out of import metaObjectRevision = type.majorVersion() * 100 + type.minorVersion(); } QmlTypeInfo info = { exportString, metaObjectRevision, extendedObject, attachedTypeId }; typeInfo.append(info); } // sort to ensure stable output std::sort(typeInfo.begin(), typeInfo.end(), [](const QmlTypeInfo &i1, const QmlTypeInfo &i2) { return i1.revision < i2.revision; }); // determine default property // TODO: support revisioning of default property QString defaultProperty = getDefaultProperty(meta); if (defaultProperty.isEmpty()) { for (const QmlTypeInfo &iter : typeInfo) { if (iter.extendedObject) { defaultProperty = getDefaultProperty(iter.extendedObject); if (!defaultProperty.isEmpty()) break; } } } if (!defaultProperty.isEmpty()) qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty)); if (meta->superClass()) qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass()))); if (!typeInfo.isEmpty()) { QMap exports; // sort exports for (const QmlTypeInfo &iter : typeInfo) exports.insert(iter.exportString, QString::number(iter.revision)); QStringList exportStrings = exports.keys(); QStringList metaObjectRevisions = exports.values(); qml->writeArrayBinding(QLatin1String("exports"), exportStrings); if (isUncreatable) qml->writeBooleanBinding(QLatin1String("isCreatable"), false); if (isSingleton) qml->writeBooleanBinding(QLatin1String("isSingleton"), true); qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions); for (const QmlTypeInfo &iter : typeInfo) { if (!iter.attachedTypeId.isEmpty()) { qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId)); break; } } } for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) dump(meta->enumerator(index)); writeMetaContent(meta); // dump properties from extended metaobjects last for (auto iter : typeInfo) { if (iter.extendedObject) dumpMetaProperties(iter.extendedObject, iter.revision); } qml->writeEndObject(); } void writeEasingCurve() { qml->writeStartObject(QLatin1String("Component")); qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("QEasingCurve"))); qml->writeScriptBinding(QLatin1String("prototype"), enquote(QLatin1String("QQmlEasingValueType"))); qml->writeEndObject(); } private: /* Removes pointer and list annotations from a type name, returning what was removed in isList and isPointer */ static void removePointerAndList(QByteArray *typeName, bool *isList, bool *isPointer) { static QByteArray declListPrefix = "QQmlListProperty<"; if (typeName->endsWith('*')) { *isPointer = true; typeName->truncate(typeName->length() - 1); removePointerAndList(typeName, isList, isPointer); } else if (typeName->startsWith(declListPrefix)) { *isList = true; typeName->truncate(typeName->length() - 1); // get rid of the suffix '>' *typeName = typeName->mid(declListPrefix.size()); removePointerAndList(typeName, isList, isPointer); } *typeName = convertToId(*typeName); } void writeTypeProperties(QByteArray typeName, bool isWritable) { bool isList = false, isPointer = false; removePointerAndList(&typeName, &isList, &isPointer); qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); if (isList) qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true")); if (!isWritable) qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true")); if (isPointer) qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); } void dump(const QMetaProperty &prop, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { int revision = metaRevision ? metaRevision : prop.revision(); QByteArray propName = prop.name(); if (knownAttributes && knownAttributes->knownProperty(propName, revision)) return; qml->writeStartObject("Property"); qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name()))); if (revision) qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); writeTypeProperties(prop.typeName(), prop.isWritable()); qml->writeEndObject(); } QSet dumpMetaProperties(const QMetaObject *meta, int metaRevision = -1, KnownAttributes *knownAttributes = nullptr) { QSet implicitSignals; for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { const QMetaProperty &property = meta->property(index); dump(property, metaRevision, knownAttributes); if (knownAttributes) knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), 0, property.revision()); implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); } return implicitSignals; } void dump(const QMetaMethod &meth, const QSet &implicitSignals, KnownAttributes *knownAttributes = nullptr) { if (meth.methodType() == QMetaMethod::Signal) { if (meth.access() != QMetaMethod::Public) return; // nothing to do. } else if (meth.access() != QMetaMethod::Public) { return; // nothing to do. } QByteArray name = meth.name(); const QString typeName = convertToId(meth.typeName()); if (implicitSignals.contains(name) && !meth.revision() && meth.methodType() == QMetaMethod::Signal && meth.parameterNames().isEmpty() && typeName == QLatin1String("void")) { // don't mention implicit signals return; } int revision = meth.revision(); if (knownAttributes && knownAttributes->knownMethod(name, meth.parameterNames().size(), revision)) return; if (meth.methodType() == QMetaMethod::Signal) qml->writeStartObject(QLatin1String("Signal")); else qml->writeStartObject(QLatin1String("Method")); qml->writeScriptBinding(QLatin1String("name"), enquote(name)); if (revision) qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision)); if (typeName != QLatin1String("void")) qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); for (int i = 0; i < meth.parameterTypes().size(); ++i) { QByteArray argName = meth.parameterNames().at(i); qml->writeStartObject(QLatin1String("Parameter")); if (! argName.isEmpty()) qml->writeScriptBinding(QLatin1String("name"), enquote(argName)); writeTypeProperties(meth.parameterTypes().at(i), true); qml->writeEndObject(); } qml->writeEndObject(); } void dump(const QMetaEnum &e) { qml->writeStartObject(QLatin1String("Enum")); qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name()))); QList > namesValues; const int keyCount = e.keyCount(); namesValues.reserve(keyCount); for (int index = 0; index < keyCount; ++index) { namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index)))); } qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues); qml->writeEndObject(); } }; enum ExitCode { EXIT_INVALIDARGUMENTS = 1, EXIT_SEGV = 2, EXIT_IMPORTERROR = 3 }; void printUsage(const QString &appName) { std::cerr << qPrintable(QString( "Usage: %1 [-v] [-qapp] [-noinstantiate] [-defaultplatform] [-[non]relocatable] [-dependencies ] [-merge ] [-output ] [-noforceqtquick] module.uri version [module/import/path]\n" " %1 [-v] [-qapp] [-noinstantiate] -path path/to/qmldir/directory [version]\n" " %1 [-v] -builtins\n" "Example: %1 Qt.labs.folderlistmodel 2.0 /home/user/dev/qt-install/imports").arg( appName)) << std::endl; } static bool readDependenciesData(QString dependenciesFile, const QByteArray &fileData, QStringList *dependencies, const QStringList &urisToSkip, bool forceQtQuickDependency = true) { if (verbose) { std::cerr << "parsing " << qPrintable( dependenciesFile ) << " skipping"; for (const QString &uriToSkip : urisToSkip) std::cerr << ' ' << qPrintable(uriToSkip); std::cerr << std::endl; } QJsonParseError parseError; parseError.error = QJsonParseError::NoError; QJsonDocument doc = QJsonDocument::fromJson(fileData, &parseError); if (parseError.error != QJsonParseError::NoError) { std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() << ":" << parseError.errorString().toStdString() << " at " << parseError.offset << std::endl; return false; } if (doc.isArray()) { const QStringList requiredKeys = QStringList() << QStringLiteral("name") << QStringLiteral("type") << QStringLiteral("version"); const auto deps = doc.array(); for (const QJsonValue &dep : deps) { if (dep.isObject()) { QJsonObject obj = dep.toObject(); for (const QString &requiredKey : requiredKeys) if (!obj.contains(requiredKey) || obj.value(requiredKey).isString()) continue; if (obj.value(QStringLiteral("type")).toString() != QLatin1String("module")) continue; QString name = obj.value((QStringLiteral("name"))).toString(); QString version = obj.value(QStringLiteral("version")).toString(); if (name.isEmpty() || urisToSkip.contains(name) || version.isEmpty()) continue; if (name.contains(QLatin1String("Private"), Qt::CaseInsensitive)) { if (verbose) - std::cerr << "skipping private dependecy " + std::cerr << "skipping private dependency " << qPrintable( name ) << " " << qPrintable(version) << std::endl; continue; } if (verbose) std::cerr << "appending dependency " << qPrintable( name ) << " " << qPrintable(version) << std::endl; dependencies->append(name + QLatin1Char(' ')+version); } } } else { std::cerr << "Error parsing dependencies file " << dependenciesFile.toStdString() << ": expected an array" << std::endl; return false; } // Workaround for avoiding conflicting types when no dependency has been found. // // qmlplugindump used to import QtQuick, so all types defined in QtQuick used to be skipped when dumping. // Now that it imports only Qt, it is no longer the case: if no dependency is found all the types defined // in QtQuick will be dumped, causing conflicts. if (forceQtQuickDependency && dependencies->isEmpty()) dependencies->push_back(qtQuickQualifiedName); return true; } static bool readDependenciesFile(const QString &dependenciesFile, QStringList *dependencies, const QStringList &urisToSkip) { if (!QFileInfo::exists(dependenciesFile)) { std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() << std::endl; return false; } QFile f(dependenciesFile); if (!f.open(QFileDevice::ReadOnly)) { std::cerr << "non existing dependencies file " << dependenciesFile.toStdString() << ", " << f.errorString().toStdString() << std::endl; return false; } QByteArray fileData = f.readAll(); return readDependenciesData(dependenciesFile, fileData, dependencies, urisToSkip, false); } static bool getDependencies(const QQmlEngine &engine, const QString &pluginImportUri, const QString &pluginImportVersion, QStringList *dependencies, bool forceQtQuickDependency) { QString importScannerExe = QLatin1String("qmlimportscanner"); QFileInfo selfExe(QCoreApplication::applicationFilePath()); if (!selfExe.suffix().isEmpty()) importScannerExe += QLatin1String(".") + selfExe.suffix(); QString command = selfExe.absoluteDir().filePath(importScannerExe); QStringList commandArgs = QStringList() << QLatin1String("-qmlFiles") << QLatin1String("-"); QStringList importPathList = engine.importPathList(); importPathList.removeOne(QStringLiteral("qrc:/qt-project.org/imports")); for (const QString &path : importPathList) commandArgs << QLatin1String("-importPath") << path; QProcess importScanner; importScanner.start(command, commandArgs, QProcess::ReadWrite); if (!importScanner.waitForStarted()) return false; importScanner.write("import "); importScanner.write(pluginImportUri.toUtf8()); importScanner.write(" "); importScanner.write(pluginImportVersion.toUtf8()); importScanner.write("\nQtObject{}\n"); importScanner.closeWriteChannel(); if (!importScanner.waitForFinished()) { std::cerr << "failure to start " << qPrintable(command); for (const QString &arg : qAsConst(commandArgs)) std::cerr << ' ' << qPrintable(arg); std::cerr << std::endl; return false; } QByteArray depencenciesData = importScanner.readAllStandardOutput(); if (!readDependenciesData(QLatin1String(""), depencenciesData, dependencies, QStringList(pluginImportUri), forceQtQuickDependency)) { std::cerr << "failed to process output of qmlimportscanner" << std::endl; if (importScanner.exitCode() != 0) std::cerr << importScanner.readAllStandardError().toStdString(); return false; } QStringList aux; for (const QString &str : qAsConst(*dependencies)) { if (!str.startsWith("Qt.test.qtestroot")) aux += str; } *dependencies = aux; return true; } bool compactDependencies(QStringList *dependencies) { if (dependencies->isEmpty()) return false; dependencies->sort(); QStringList oldDep = dependencies->constFirst().split(QLatin1Char(' ')); Q_ASSERT(oldDep.size() == 2); int oldPos = 0; for (int idep = 1; idep < dependencies->size(); ++idep) { QString depStr = dependencies->at(idep); const QStringList newDep = depStr.split(QLatin1Char(' ')); Q_ASSERT(newDep.size() == 2); if (newDep.constFirst() != oldDep.constFirst()) { if (++oldPos != idep) dependencies->replace(oldPos, depStr); oldDep = newDep; } else { const QStringList v1 = oldDep.constLast().split(QLatin1Char('.')); const QStringList v2 = newDep.constLast().split(QLatin1Char('.')); Q_ASSERT(v1.size() == 2); Q_ASSERT(v2.size() == 2); bool ok; int major1 = v1.first().toInt(&ok); Q_ASSERT(ok); int major2 = v2.first().toInt(&ok); Q_ASSERT(ok); if (major1 != major2) { std::cerr << "Found a dependency on " << qPrintable(oldDep.constFirst()) << " with two major versions:" << qPrintable(oldDep.constLast()) << " and " << qPrintable(newDep.constLast()) << " which is unsupported, discarding smaller version" << std::endl; if (major1 < major2) dependencies->replace(oldPos, depStr); } else { int minor1 = v1.last().toInt(&ok); Q_ASSERT(ok); int minor2 = v2.last().toInt(&ok); Q_ASSERT(ok); if (minor1 < minor2) dependencies->replace(oldPos, depStr); } } } if (++oldPos < dependencies->size()) { *dependencies = dependencies->mid(0, oldPos); return true; } return false; } inline std::wostream &operator<<(std::wostream &str, const QString &s) { #ifdef Q_OS_WIN str << reinterpret_cast(s.utf16()); #else str << s.toStdWString(); #endif return str; } void printDebugMessage(QtMsgType, const QMessageLogContext &, const QString &msg) { std::wcerr << msg << std::endl; // In case of QtFatalMsg the calling code will abort() when appropriate. } QT_BEGIN_NAMESPACE static bool operator<(const QQmlType &a, const QQmlType &b) { return a.qmlTypeName() < b.qmlTypeName() || (a.qmlTypeName() == b.qmlTypeName() && ((a.majorVersion() < b.majorVersion()) || (a.majorVersion() == b.majorVersion() && a.minorVersion() < b.minorVersion()))); } QT_END_NAMESPACE int main(int argc, char *argv[]) { #if defined(Q_OS_WIN) && !defined(Q_CC_MINGW) // we do not want windows popping up if the module loaded triggers an assert SetErrorMode(SEM_NOGPFAULTERRORBOX); _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); #endif // Q_OS_WIN && !Q_CC_MINGW // The default message handler might not print to console on some systems. Enforce this. qInstallMessageHandler(printDebugMessage); #ifdef QT_SIMULATOR // Running this application would bring up the Qt Simulator (since it links Qt GUI), avoid that! QtSimulatorPrivate::SimulatorConnection::createStubInstance(); #endif // don't require a window manager even though we're a QGuiApplication bool requireWindowManager = false; for (int index = 1; index < argc; ++index) { if (QString::fromLocal8Bit(argv[index]) == "--defaultplatform" || QString::fromLocal8Bit(argv[index]) == "-defaultplatform") { requireWindowManager = true; break; } } if (!requireWindowManager && qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal")); else QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); // Check which kind of application should be instantiated. bool useQApplication = false; for (int i = 0; i < argc; ++i) { QString arg = QLatin1String(argv[i]); if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) useQApplication = true; } #ifdef QT_WIDGETS_LIB QScopedPointer app(useQApplication ? new QApplication(argc, argv) : new QGuiApplication(argc, argv)); #else Q_UNUSED(useQApplication); QScopedPointer app(new QGuiApplication(argc, argv)); #endif // QT_WIDGETS_LIB QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); QStringList args = app->arguments(); const QString appName = QFileInfo(app->applicationFilePath()).baseName(); if (args.size() < 2) { printUsage(appName); return EXIT_INVALIDARGUMENTS; } QString outputFilename; QString pluginImportUri; QString pluginImportVersion; bool relocatable = true; QString dependenciesFile; QString mergeFile; bool forceQtQuickDependency = true; bool strict = false; enum Action { Uri, Path, Builtins }; Action action = Uri; { QStringList positionalArgs; for (int iArg = 0; iArg < args.size(); ++iArg) { const QString &arg = args.at(iArg); if (!arg.startsWith(QLatin1Char('-'))) { positionalArgs.append(arg); continue; } if (arg == QLatin1String("--dependencies") || arg == QLatin1String("-dependencies")) { if (++iArg == args.size()) { std::cerr << "missing dependencies file" << std::endl; return EXIT_INVALIDARGUMENTS; } dependenciesFile = args.at(iArg); // Remove absolute path so that it does not show up in the // printed command line inside the plugins.qmltypes file. args[iArg] = QFileInfo(args.at(iArg)).fileName(); } else if (arg == QLatin1String("--merge") || arg == QLatin1String("-merge")) { if (++iArg == args.size()) { std::cerr << "missing merge file" << std::endl; return EXIT_INVALIDARGUMENTS; } mergeFile = args.at(iArg); } else if (arg == QLatin1String("--notrelocatable") || arg == QLatin1String("-notrelocatable") || arg == QLatin1String("--nonrelocatable") || arg == QLatin1String("-nonrelocatable")) { relocatable = false; } else if (arg == QLatin1String("--relocatable") || arg == QLatin1String("-relocatable")) { relocatable = true; } else if (arg == QLatin1String("--noinstantiate") || arg == QLatin1String("-noinstantiate")) { creatable = false; } else if (arg == QLatin1String("--path") || arg == QLatin1String("-path")) { action = Path; } else if (arg == QLatin1String("--builtins") || arg == QLatin1String("-builtins")) { action = Builtins; } else if (arg == QLatin1String("-v")) { verbose = true; } else if (arg == QLatin1String("--noforceqtquick") || arg == QLatin1String("-noforceqtquick")){ forceQtQuickDependency = false; } else if (arg == QLatin1String("--output") || arg == QLatin1String("-output")) { if (++iArg == args.size()) { std::cerr << "missing output file" << std::endl; return EXIT_INVALIDARGUMENTS; } outputFilename = args.at(iArg); } else if (arg == QLatin1String("--defaultplatform") || arg == QLatin1String("-defaultplatform")) { continue; } else if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) { continue; } else if (arg == QLatin1String("--strict") || arg == QLatin1String("-strict")) { strict = true; continue; } else { std::cerr << "Invalid argument: " << qPrintable(arg) << std::endl; return EXIT_INVALIDARGUMENTS; } } if (action == Uri) { if (positionalArgs.size() != 3 && positionalArgs.size() != 4) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } pluginImportUri = positionalArgs.at(1); pluginImportVersion = positionalArgs[2]; if (positionalArgs.size() >= 4) pluginImportPath = positionalArgs.at(3); } else if (action == Path) { if (positionalArgs.size() != 2 && positionalArgs.size() != 3) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } pluginImportPath = QDir::fromNativeSeparators(positionalArgs.at(1)); if (positionalArgs.size() == 3) pluginImportVersion = positionalArgs.at(2); } else if (action == Builtins) { if (positionalArgs.size() != 1) { std::cerr << "Incorrect number of positional arguments" << std::endl; return EXIT_INVALIDARGUMENTS; } } } QQmlEngine engine; if (!pluginImportPath.isEmpty()) { QDir cur = QDir::current(); cur.cd(pluginImportPath); pluginImportPath = cur.canonicalPath(); QDir::setCurrent(pluginImportPath); engine.addImportPath(pluginImportPath); } // Merge file. QStringList mergeDependencies; QString mergeComponents; if (!mergeFile.isEmpty()) { const QStringList merge = readQmlTypes(mergeFile); if (!merge.isEmpty()) { QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)"); QRegularExpressionMatchIterator i = re.globalMatch(merge[1]); while (i.hasNext()) { QRegularExpressionMatch m = i.next(); mergeDependencies << m.captured(1); } mergeComponents = merge [2]; } } // Dependencies. bool calculateDependencies = !pluginImportUri.isEmpty() && !pluginImportVersion.isEmpty(); QStringList dependencies; if (!dependenciesFile.isEmpty()) calculateDependencies = !readDependenciesFile(dependenciesFile, &dependencies, QStringList(pluginImportUri)) && calculateDependencies; if (calculateDependencies) getDependencies(engine, pluginImportUri, pluginImportVersion, &dependencies, forceQtQuickDependency); compactDependencies(&dependencies); QString qtQmlImportString = QString::fromLatin1("import QtQml %1.%2") .arg(qtQmlMajorVersion) .arg(qtQmlMinorVersion); // load the QtQml builtins and the dependencies { QByteArray code(qtQmlImportString.toUtf8()); for (const QString &moduleToImport : qAsConst(dependencies)) { code.append("\nimport "); code.append(moduleToImport.toUtf8()); } code.append("\nQtObject {}"); QQmlComponent c(&engine); c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/loaddependencies.qml")); c.create(); const auto errors = c.errors(); if (!errors.isEmpty()) { for (const QQmlError &error : errors) std::cerr << qPrintable( error.toString() ) << std::endl; return EXIT_IMPORTERROR; } } // find all QMetaObjects reachable from the builtin module QSet uncreatableMetas; QSet singletonMetas; // this will hold the meta objects we want to dump information of QSet metas; // composite types we want to dump information of QMap> compositeTypes; int majorVersion = qtQmlMajorVersion, minorVersion = qtQmlMinorVersion; QmlVersionInfo info; if (action == Builtins) { QMap> defaultCompositeTypes; QSet builtins = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, defaultCompositeTypes, {QLatin1String("Qt"), majorVersion, minorVersion, strict}); Q_ASSERT(builtins.size() == 1); metas.insert(*builtins.begin()); } else { auto versionSplitted = pluginImportVersion.split("."); bool ok = versionSplitted.size() == 2; if (!ok) qCritical("Invalid version number"); else { majorVersion = versionSplitted.at(0).toInt(&ok); if (!ok) qCritical("Invalid major version"); minorVersion = versionSplitted.at(1).toInt(&ok); if (!ok) qCritical("Invalid minor version"); } QList defaultTypes = QQmlMetaType::qmlTypes(); // find a valid QtQuick import QByteArray importCode; QQmlType qtObjectType = QQmlMetaType::qmlType(&QObject::staticMetaObject); if (!qtObjectType.isValid()) { std::cerr << "Could not find QtObject type" << std::endl; importCode = qtQmlImportString.toUtf8(); } else { QString module = qtObjectType.qmlTypeName(); module = module.mid(0, module.lastIndexOf(QLatin1Char('/'))); importCode = QString("import %1 %2.%3").arg(module, QString::number(qtObjectType.majorVersion()), QString::number(qtObjectType.minorVersion())).toUtf8(); } // avoid importing dependencies? for (const QString &moduleToImport : qAsConst(dependencies)) { importCode.append("\nimport "); importCode.append(moduleToImport.toUtf8()); } // find all QMetaObjects reachable when the specified module is imported if (action != Path) { importCode += QString("\nimport %0 %1\n").arg(pluginImportUri, pluginImportVersion).toLatin1(); } else { // pluginImportVersion can be empty importCode += QString("\nimport \".\" %2\n").arg(pluginImportVersion).toLatin1(); } // create a component with these imports to make sure the imports are valid // and to populate the declarative meta type system { QByteArray code = importCode; code += "\nQtObject {}"; QQmlComponent c(&engine); c.setData(code, QUrl::fromLocalFile(pluginImportPath + "/typelist.qml")); c.create(); const auto errors = c.errors(); if (!errors.isEmpty()) { for (const QQmlError &error : errors) std::cerr << qPrintable( error.toString() ) << std::endl; return EXIT_IMPORTERROR; } } info = {pluginImportUri, majorVersion, minorVersion, strict}; QSet candidates = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, compositeTypes, info, defaultTypes); for (auto it = compositeTypes.begin(), end = compositeTypes.end(); it != end; ++it) { std::sort(it->begin(), it->end()); it->erase(std::unique(it->begin(), it->end()), it->end()); } for (const QMetaObject *mo : qAsConst(candidates)) { if (mo->className() != QLatin1String("Qt")) metas.insert(mo); } } // setup static rewrites of type names cppToId.insert("QString", "string"); cppToId.insert("QQmlEasingValueType::Type", "Type"); // start dumping data QByteArray bytes; QmlStreamWriter qml(&bytes); qml.writeStartDocument(); qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2); qml.write(QString("\n" "// This file describes the plugin-supplied types contained in the library.\n" "// It is used for QML tooling purposes only.\n" "//\n" "// This file was auto-generated by:\n" "// '%1 %2'\n" "\n").arg(QFileInfo(args.at(0)).baseName(), args.mid(1).join(QLatin1Char(' ')))); qml.writeStartObject("Module"); // Insert merge dependencies. if (!mergeDependencies.isEmpty()) { dependencies << mergeDependencies; } compactDependencies(&dependencies); QStringList quotedDependencies; for (const QString &dep : qAsConst(dependencies)) quotedDependencies << enquote(dep); qml.writeArrayBinding("dependencies", quotedDependencies); // put the metaobjects into a map so they are always dumped in the same order QMap nameToMeta; for (const QMetaObject *meta : qAsConst(metas)) nameToMeta.insert(convertToId(meta), meta); Dumper dumper(&qml); if (relocatable) dumper.setRelocatableModuleUri(pluginImportUri); for (const QMetaObject *meta : qAsConst(nameToMeta)) { dumper.dump(QQmlEnginePrivate::get(&engine), meta, uncreatableMetas.contains(meta), singletonMetas.contains(meta)); } QMap>::const_iterator iter = compositeTypes.constBegin(); for (; iter != compositeTypes.constEnd(); ++iter) dumper.dumpComposite(&engine, iter.value(), info); // define QEasingCurve as an extension of QQmlEasingValueType, this way // properties using the QEasingCurve type get useful type information. if (pluginImportUri.isEmpty()) dumper.writeEasingCurve(); // Insert merge file. qml.write(mergeComponents); qml.writeEndObject(); qml.writeEndDocument(); if (!outputFilename.isEmpty()) { QFile file(outputFilename); if (file.open(QIODevice::WriteOnly)) { QTextStream stream(&file); stream << bytes.constData(); } } else { std::cout << bytes.constData() << std::flush; } // workaround to avoid crashes on exit QTimer timer; timer.setSingleShot(true); timer.setInterval(0); QObject::connect(&timer, SIGNAL(timeout()), app.data(), SLOT(quit())); timer.start(); return app->exec(); } diff --git a/tools/cutehmi.view.2/dev/cutehmi.view.2-2.unsolved.Qt.bug.txt b/tools/cutehmi.view.2/dev/cutehmi.view.2-2.unsolved.Qt.bug.txt index 1cb2809d..111d55cc 100644 --- a/tools/cutehmi.view.2/dev/cutehmi.view.2-2.unsolved.Qt.bug.txt +++ b/tools/cutehmi.view.2/dev/cutehmi.view.2-2.unsolved.Qt.bug.txt @@ -1,10 +1,10 @@ Problem: -When changing window visiblity in the following manner: +When changing window visibility in the following manner: Window.Windowed -> Window.Maximized -> Window.FullScreen -> Window.Maximized -> Window.Windowed, window does not return to its original size. Investigation: Simply restoring settings when going from Window.FullScreen to Window.Maximized does not help, because explicitly setting x, y, width and height affects the window even if it is maximized. diff --git a/tools/cutehmi.view.2/qml/MainWindow.qml b/tools/cutehmi.view.2/qml/MainWindow.qml index bc0029bd..1301ec78 100644 --- a/tools/cutehmi.view.2/qml/MainWindow.qml +++ b/tools/cutehmi.view.2/qml/MainWindow.qml @@ -1,98 +1,98 @@ import QtQuick 2.3 import QtQuick.Controls 2.1 import QtQuick.Window 2.2 import Qt.labs.settings 1.0 import CuteHMI 2.0 ApplicationWindow { id: mainWindow x: settings.x y: settings.y width: settings.width height: settings.height visibility: settings.visibility visible: true // This is required to avoid "Conflicting properties 'visible' and 'visibility' for Window 'root'" warning and to make Window.AutomaticVisibility visibility work. FocusScope { focus: true Keys.onPressed: { // Toggle full screen. if (event.key === Qt.Key_F11) { if (mainWindow.visibility != Window.FullScreen) { // Store visibility. settings.visibility = mainWindow.visibility mainWindow.visibility = Window.FullScreen } else { // Restore visibility or swap to Window.AutomaticVisibility if application was restored to full screen mode on startup. // - // When changing window visiblity in the following manner: + // When changing window visibility in the following manner: // Window.Windowed -> Window.Maximized -> Window.FullScreen -> Window.Maximized -> Window.Windowed, // window does not return to its original size. if (settings.visibility != Window.FullScreen) mainWindow.visibility = settings.visibility else mainWindow.visibility = Window.AutomaticVisibility // } } } } Settings { id: settings category: "cutehmi.view.2/MainWindow" property int visibility: Window.AutomaticVisibility property int x: 0 property int y: 0 property int width: 1280 property int height: 720 function storeMainWindow() { if (mainWindow.visibility === Window.Hidden) settings.visibility = Window.AutomaticVisibility else settings.visibility = mainWindow.visibility if (mainWindow.visibility == Window.Windowed) { settings.x = mainWindow.x settings.y = mainWindow.y settings.width = mainWindow.width settings.height = mainWindow.height } } } Loader { anchors.fill: parent source: cutehmi_view_initURL } function createDialog(message) { var messageDialogComponent = Qt.createComponent("MessageDialog.qml") var messageDialog = messageDialogComponent.createObject(mainWindow, {"message" : message}) messageDialog.messageChanged.connect(messageDialog.destroy) messageDialog.open() } Component.onCompleted: { for (var i = 1; i < Qt.application.arguments.length; i++) if (Qt.application.arguments[i] === "--fullscreen") mainWindow.visibility = Window.FullScreen Messenger.resetAdvertiser(mainWindow) } onClosing: { settings.storeMainWindow() } } //(c)C: Copyright © 2020, Michał Policht . All rights reserved. //(c)C: This file is a part of CuteHMI. //(c)C: CuteHMI is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. //(c)C: CuteHMI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. //(c)C: You should have received a copy of the GNU Lesser General Public License along with CuteHMI. If not, see . diff --git a/tools/cutehmi.view.2/src/main.cpp b/tools/cutehmi.view.2/src/main.cpp index e110a754..12208d84 100644 --- a/tools/cutehmi.view.2/src/main.cpp +++ b/tools/cutehmi.view.2/src/main.cpp @@ -1,291 +1,291 @@ #include "../cutehmi.metadata.hpp" #include "../cutehmi.dirs.hpp" #include "cutehmi/view/logging.hpp" #include #include #include #include #include // #include // Instead of: // #include // #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace cutehmi::view; /** * Main function. * @param argc number of arguments passed to the program. * @param argv list of arguments passed to the program. * @return return code. */ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationName(CUTEHMI_VIEW_VENDOR); QCoreApplication::setOrganizationDomain(CUTEHMI_VIEW_DOMAIN); QCoreApplication::setApplicationName(CUTEHMI_VIEW_FRIENDLY_NAME); QCoreApplication::setApplicationVersion(QString("%1.%2.%3").arg(CUTEHMI_VIEW_MAJOR).arg(CUTEHMI_VIEW_MINOR).arg(CUTEHMI_VIEW_MICRO)); try { #ifdef CUTEHMI_VIEW_VIRTUAL_KEYBOARD if (qgetenv("QT_IM_MODULE").isEmpty()) qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard")); CUTEHMI_DEBUG("Input method: " << qgetenv("QT_IM_MODULE")); #else CUTEHMI_DEBUG("Support for virtual keyboard is disabled."); #endif if (qgetenv("QT_IM_MODULE") == "qtvirtualkeyboard") { if (qgetenv("QT_VIRTUALKEYBOARD_LAYOUT_PATH").isEmpty()) qputenv("QT_VIRTUALKEYBOARD_LAYOUT_PATH", QByteArray(QDir("../layouts").absolutePath().toLocal8Bit())); CUTEHMI_DEBUG("Qt Virtual Keyboard layouts path: " << qgetenv("QT_VIRTUALKEYBOARD_LAYOUT_PATH")); } // // "In general, creating QObjects before the QApplication is not supported and can lead to weird crashes on exit, depending on the // platform. This means static instances of QObject are also not supported. A properly structured single or multi-threaded application // should make the QApplication be the first created, and last destroyed QObject." // QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); cutehmi::gui::CuteApplication app(argc, argv); // Instead of: // QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); // QGuiApplication app(argc, argv); // app.setWindowIcon(QIcon(":/img/icon.png")); QJsonDocument metadataJson; { QFile metadataFile(":/" CUTEHMI_VIEW_NAME "/cutehmi.metadata.json"); if (metadataFile.open(QFile::ReadOnly)) { metadataJson = QJsonDocument::fromJson(metadataFile.readAll()); } else qFatal("Could not open ':/" CUTEHMI_VIEW_NAME "/cutehmi.metadata.json' file."); } QCommandLineParser cmd; cmd.setApplicationDescription(QCoreApplication::applicationName() + "\n" + metadataJson.object().value("description").toString()); cmd.addHelpOption(); cmd.addVersionOption(); QCommandLineOption fullScreenOption({"f", "fullscreen"}, QCoreApplication::translate("main", "Run application in full screen mode.")); cmd.addOption(fullScreenOption); QCommandLineOption initOption("init", QCoreApplication::translate("main", "Override loader and specify initial QML to load."), QCoreApplication::translate("main", "file")); #ifdef CUTEHMI_VIEW_DEFAULT_INIT initOption.setDefaultValue(CUTEHMI_VIEW_DEFAULT_INIT); #else initOption.setDefaultValue("qrc:/qml/ExtensionLoader.qml"); #endif #ifdef CUTEHMI_VIEW_FORCE_DEFAULT_OPTIONS initOption.setFlags(QCommandLineOption::HiddenFromHelp); #endif cmd.addOption(initOption); QCommandLineOption extensionOption("extension", QCoreApplication::translate("main", "Load extension specified by QML ."), QCoreApplication::translate("main", "import")); #ifdef CUTEHMI_VIEW_DEFAULT_EXTENSION extensionOption.setDefaultValue(CUTEHMI_VIEW_DEFAULT_EXTENSION); #endif #ifdef CUTEHMI_VIEW_FORCE_DEFAULT_OPTIONS extensionOption.setFlags(QCommandLineOption::HiddenFromHelp); #endif cmd.addOption(extensionOption); QCommandLineOption componentOption("component", QCoreApplication::translate("main", "Extension component ."), QCoreApplication::translate("main", "name")); #ifdef CUTEHMI_VIEW_DEFAULT_COMPONENT componentOption.setDefaultValue(CUTEHMI_VIEW_DEFAULT_COMPONENT); #else componentOption.setDefaultValue("Main"); #endif #ifdef CUTEHMI_VIEW_FORCE_DEFAULT_OPTIONS componentOption.setFlags(QCommandLineOption::HiddenFromHelp); #endif cmd.addOption(componentOption); QCommandLineOption hideCursorOption({"t", "touch"}, QCoreApplication::translate("main", "Touch screen (hides mouse cursor).")); cmd.addOption(hideCursorOption); QCommandLineOption styleOption("qstyle", QCoreApplication::translate("main", "Set Qt Quick