diff --git a/src/backend/datasources/MQTTClient.cpp b/src/backend/datasources/MQTTClient.cpp index 20472b74f..1ccdd402c 100644 --- a/src/backend/datasources/MQTTClient.cpp +++ b/src/backend/datasources/MQTTClient.cpp @@ -1,1382 +1,1382 @@ /*************************************************************************** File : MQTTClient.cpp Project : LabPlot Description : Represents a MQTT Client -------------------------------------------------------------------- Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/MQTTClient.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTSubscription.h" #include "backend/datasources/MQTTTopic.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/core/Project.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "kdefrontend/datasources/MQTTErrorWidget.h" #include #include #include #include #include #include #include #include #include #include #include /*! \class MQTTClient \brief The MQTT Client connects to the broker set in ImportFileWidget. It manages the MQTTSubscriptions, and the MQTTTopics. \ingroup datasources */ MQTTClient::MQTTClient(const QString& name) : Folder(name), m_paused(false), m_prepared(false), m_sampleSize(1), m_keepNValues(0), m_updateInterval(1000), m_filter(nullptr), m_updateTimer(new QTimer(this)), m_client(new QMqttClient(this)), m_MQTTTest(false), m_willTimer(new QTimer(this)), m_MQTTFirstConnectEstablished(false), m_MQTTRetain(false), m_MQTTUseID(false), m_MQTTUseAuthentication(false), m_disconnectForWill(false), m_loaded(false), m_subscriptionsLoaded(0), m_subscriptionCountToLoad(0) { qDebug() << "MQTTClient constructor: " << m_client->hostname(); m_MQTTWill.MQTTUseWill = false; m_MQTTWill.willRetain = false; m_MQTTWill.willStatistics.fill(false, 15); connect(m_updateTimer, &QTimer::timeout, this, &MQTTClient::read); connect(m_client, &QMqttClient::connected, this, &MQTTClient::onMQTTConnect); connect(m_willTimer, &QTimer::timeout, this, &MQTTClient::updateWillMessage); connect(m_client, &QMqttClient::errorChanged, this, &MQTTClient::MQTTErrorChanged); } MQTTClient::~MQTTClient() { emit clientAboutToBeDeleted(m_client->hostname()); //stop reading before deleting the objects pauseReading(); qDebug()<<"Delete MQTTClient: " << m_client->hostname(); if (m_filter) delete m_filter; delete m_updateTimer; delete m_willTimer; m_client->disconnectFromHost(); delete m_client; } /*! * depending on the update type, periodically or on data changes, starts the timer. */ void MQTTClient::ready() { if (m_updateType == TimeInterval) m_updateTimer->start(m_updateInterval); } /*! * \brief Updates the MQTTTopics of the client */ void MQTTClient::updateNow() { m_updateTimer->stop(); read(); if ((m_updateType == TimeInterval) && !m_paused) m_updateTimer->start(m_updateInterval); } /*! * \brief Continue reading from messages after it was paused. */ void MQTTClient::continueReading() { m_paused = false; if (m_updateType == TimeInterval) m_updateTimer->start(m_updateInterval); } /*! * \brief Pause the reading from messages. */ void MQTTClient::pauseReading() { m_paused = true; if (m_updateType == TimeInterval) m_updateTimer->stop(); } /*! * \brief Sets the filter of the MQTTClient. * * \param f a pointer to the new filter */ void MQTTClient::setFilter(AbstractFileFilter* f) { m_filter = f; } /*! * \brief Returns the filter of the MQTTClient. */ AbstractFileFilter* MQTTClient::filter() const { return m_filter; } /*! * \brief Sets the MQTTclient's update interval to \c interval * \param interval */ void MQTTClient::setUpdateInterval(int interval) { m_updateInterval = interval; if (!m_paused) m_updateTimer->start(m_updateInterval); } /*! * \brief Returns the MQTTClient's update interval to \c interval * \param interval */ int MQTTClient::updateInterval() const { return m_updateInterval; } /*! * \brief Sets how many values we should store * \param keepNValues */ void MQTTClient::setKeepNValues(int keepNValues) { m_keepNValues = keepNValues; } /*! * \brief Returns how many values we should store */ int MQTTClient::keepNValues() const { return m_keepNValues; } /*! * \brief Provides information about whether the reading is paused or not * * \return true if the reading is paused * \return false otherwise */ bool MQTTClient::isPaused() const { return m_paused; } /*! * \brief Sets the size rate to sampleSize * \param sampleSize */ void MQTTClient::setSampleSize(int sampleSize) { m_sampleSize = sampleSize; } /*! * \brief Returns the size rate */ int MQTTClient::sampleSize() const { return m_sampleSize; } /*! * \brief Sets the MQTTClient's reading type to readingType * \param readingType */ void MQTTClient::setReadingType(ReadingType readingType) { m_readingType = readingType; } /*! * \brief Returns the MQTTClient's reading type */ MQTTClient::ReadingType MQTTClient::readingType() const { return m_readingType; } /*! * \brief Sets the MQTTClient's update type to updatetype and handles this change * \param updatetype */ void MQTTClient::setUpdateType(UpdateType updateType) { if (updateType == NewData) { m_updateTimer->stop(); } m_updateType = updateType; } /*! * \brief Returns the MQTTClient's update type */ MQTTClient::UpdateType MQTTClient::updateType() const { return m_updateType; } /*! * \brief Returns the MQTTClient's icon */ QIcon MQTTClient::icon() const { return QIcon::fromTheme("labplot-MQTT"); } /*! * \brief Sets the host and port for the client. * * \param host the hostname of the broker we want to connect to * \param port the port used by the broker */ void MQTTClient::setMQTTClientHostPort(const QString& host, const quint16& port) { m_client->setHostname(host); m_client->setPort(port); } /*! * \brief Returns hostname of the broker the client is connected to. */ QString MQTTClient::clientHostName() const{ return m_client->hostname(); } /*! * \brief Returns the port used by the broker. */ quint16 MQTTClient::clientPort() const { return m_client->port(); } /*! * \brief Sets the flag on the given value. * If set true it means that the broker requires authentication, otherwise it doesn't. * * \param use */ void MQTTClient::setMQTTUseAuthentication(bool use) { m_MQTTUseAuthentication = use; } /*! * \brief Returns whether the broker requires authentication or not. */ bool MQTTClient::MQTTUseAuthentication() const { return m_MQTTUseAuthentication; } /*! * \brief Sets the username and password for the client. * * \param username the username used for authentication * \param password the password used for authentication */ void MQTTClient::setMQTTClientAuthentication(const QString& username, const QString& password) { m_client->setUsername(username); m_client->setPassword(password); } /*! * \brief Returns the username used for authentication. */ QString MQTTClient::clientUserName() const{ return m_client->username(); } /*! * \brief Returns the password used for authentication. */ QString MQTTClient::clientPassword() const{ return m_client->password(); } /*! * \brief Sets the flag on the given value. * If set true it means that user wants to set the client ID, otherwise it's not the case. * * \param use */ void MQTTClient::setMQTTUseID(bool use) { m_MQTTUseID = use; } /*! * \brief Returns whether the user wants to set the client ID or not. */ bool MQTTClient::MQTTUseID() const { return m_MQTTUseID; } /*! * \brief Sets the ID of the client * * \param id */ -void MQTTClient::setMQTTClientId(const QString &id){ +void MQTTClient::setMQTTClientId(const QString &id) { m_client->setClientId(id); } /*! * \brief Returns the ID of the client */ QString MQTTClient::clientID () const{ return m_client->clientId(); } /*! * \brief Sets the flag on the given value. * If retain is true we interpret retain messages, otherwise we do not * * \param retain */ void MQTTClient::setMQTTRetain(bool retain) { m_MQTTRetain = retain; } /*! * \brief Returns the flag, which set to true means that interpret retain messages, otherwise we do not */ bool MQTTClient::MQTTRetain() const { return m_MQTTRetain; } /*! * \brief Returns the name of every MQTTTopics which already received a message, and is child of the MQTTClient */ QVector MQTTClient::topicNames() const { return m_topicNames; } /*! * \brief Adds the initial subscriptions that were set in ImportFileWidget * * \param filter the name of the subscribed topic * \param qos the qos level of the subscription */ void MQTTClient::addInitialMQTTSubscriptions(const QMqttTopicFilter& filter, const quint8& qos) { m_subscribedTopicNameQoS[filter] = qos; } /*! * \brief Returns the name of every MQTTSubscription of the MQTTClient */ QVector MQTTClient::MQTTSubscriptions() const { return m_subscriptions; } /*! * \brief Adds a new MQTTSubscription to the MQTTClient * * \param topic, the name of the topic * \param QoS */ void MQTTClient::addMQTTSubscription(const QString& topicName, quint8 QoS) { //Check whether the subscription already exists, if not we can add it if (!m_subscriptions.contains(topicName)) { const QMqttTopicFilter filter {topicName}; QMqttSubscription* temp = m_client->subscribe(filter, QoS); if (temp) { qDebug()<<"Subscribe to: "<< temp->topic() << " " << temp->qos(); m_subscriptions.push_back(temp->topic().filter()); m_subscribedTopicNameQoS[temp->topic().filter()] = temp->qos(); MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); addChild(newSubscription); m_MQTTSubscriptions.push_back(newSubscription); //Search for inferior subscriptions, that the new subscription contains bool found = false; QVector inferiorSubscriptions; for (int i = 0; i < m_MQTTSubscriptions.size(); ++i) { if (checkTopicContains(topicName, m_MQTTSubscriptions[i]->subscriptionName()) && topicName != m_MQTTSubscriptions[i]->subscriptionName()) { found = true; inferiorSubscriptions.push_back(m_MQTTSubscriptions[i]); } } //If there are some inferior subscriptions, we have to deal with them if (found) { for (int sub = 0; sub < inferiorSubscriptions.size(); ++sub) { qDebug()<<"Reparent topics of inferior subscription: "<subscriptionName(); //We have to reparent every topic of the inferior subscription, so no data is lost QVector topics = inferiorSubscriptions[sub]->topics(); - for(auto* topic : topics) { + for (auto* topic : topics) { topic->reparent(newSubscription); } //Then remove the subscription and every connected information QMqttTopicFilter unsubscribeFilter {inferiorSubscriptions[sub]->subscriptionName()}; m_client->unsubscribe(unsubscribeFilter); for (int j = 0; j < m_MQTTSubscriptions.size(); ++j) { if (m_MQTTSubscriptions[j]->subscriptionName() == inferiorSubscriptions[sub]->subscriptionName()) { m_MQTTSubscriptions.remove(j); } } m_subscriptions.removeAll(inferiorSubscriptions[sub]->subscriptionName()); m_subscribedTopicNameQoS.remove(inferiorSubscriptions[sub]->subscriptionName()); removeChild(inferiorSubscriptions[sub]); } } connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); emit MQTTTopicsChanged(); } } } /*! * \brief Removes a MQTTSubscription from the MQTTClient * * \param name, the name of the subscription to remove */ void MQTTClient::removeMQTTSubscription(const QString& subscriptionName) { //We can only remove the subscription if it exists if (m_subscriptions.contains(subscriptionName)) { //unsubscribe from the topic const QMqttTopicFilter filter{subscriptionName}; m_client->unsubscribe(filter); qDebug()<<"Unsubscribe from: " << subscriptionName; //Remove every connected information m_subscriptions.removeAll(subscriptionName); for (int i = 0; i < m_MQTTSubscriptions.size(); ++i) { if (m_MQTTSubscriptions[i]->subscriptionName() == subscriptionName) { MQTTSubscription* removeSubscription = m_MQTTSubscriptions[i]; m_MQTTSubscriptions.remove(i); //Remove every topic of the subscription as well QVector topics = removeSubscription->topics(); for (int j = 0; j < topics.size(); ++j) { m_topicNames.removeAll(topics[j]->topicName()); } //Remove the MQTTSubscription removeChild(removeSubscription); break; } } QMapIterator j(m_subscribedTopicNameQoS); - while(j.hasNext()) { + while (j.hasNext()) { j.next(); if (j.key().filter() == subscriptionName) { m_subscribedTopicNameQoS.remove(j.key()); break; } } //Signal that there was a change among the topics emit MQTTTopicsChanged(); } } /*! * \brief Adds a MQTTSubscription to the MQTTClient *Used when the user unsubscribes from a topic of a MQTTSubscription * * \param topic, the name of the topic * \param QoS */ void MQTTClient::addBeforeRemoveSubscription(const QString& topicName, quint8 QoS) { //We can't add the subscription if it already exists if (!m_subscriptions.contains(topicName)) { //Subscribe to the topic QMqttTopicFilter filter {topicName}; QMqttSubscription* temp = m_client->subscribe(filter, QoS); if (temp) { //Add the MQTTSubscription and other connected data qDebug()<<"Add subscription before remove: " << temp->topic() << " " << temp->qos(); m_subscriptions.push_back(temp->topic().filter()); m_subscribedTopicNameQoS[temp->topic().filter()] = temp->qos(); MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); addChild(newSubscription); m_MQTTSubscriptions.push_back(newSubscription); //Search for the subscription the topic belonged to bool found = false; MQTTSubscription* superiorSubscription = nullptr; for (auto* subscription : m_MQTTSubscriptions) { if (checkTopicContains(subscription->subscriptionName(), topicName) && topicName != subscription->subscriptionName()) { found = true; superiorSubscription = subscription; break; } } if (found) { //Search for topics belonging to the superior(old) subscription //which are also contained by the new subscription QVector topics = superiorSubscription->topics(); qDebug()<< topics.size(); QVector inferiorTopics; for (auto* topic : topics) { if (checkTopicContains(topicName, topic->topicName())) { inferiorTopics.push_back(topic); } } //Reparent these topics, in order to avoid data loss for (auto* inferiorTopic : inferiorTopics) { inferiorTopic->reparent(newSubscription); } } connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); } } } /*! * \brief Reparents the given MQTTTopic to the given MQTTSubscription * * \param topic, the name of the MQTTTopic * \param parent, the name of the MQTTSubscription */ void MQTTClient::reparentTopic(const QString& topicName, const QString& parentTopicName) { //We can only reparent if the parent containd the topic if (m_subscriptions.contains(parentTopicName) && m_topicNames.contains(topicName)) { qDebug() << "Reparent " << topicName << " to " << parentTopicName; //search for the parent MQTTSubscription bool found = false; MQTTSubscription* superiorSubscription = nullptr; for (auto* subscription : m_MQTTSubscriptions) { if (subscription->subscriptionName() == parentTopicName) { found = true; superiorSubscription = subscription; break; } } if (found) { //get every topic of the MQTTClient QVector topics = children(AbstractAspect::Recursive); //Search for the given topic among the MQTTTopics for (auto* topic : topics) { if (topicName == topic->topicName()) { //if found, it is reparented to the parent MQTTSubscription topic->reparent(superiorSubscription); break; } } } } } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ bool MQTTClient::checkTopicContains(const QString& superior, const QString& inferior) { if (superior == inferior) return true; else { if (superior.contains(QLatin1String("/"))) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one if (superiorList.size() > inferiorList.size()) return false; bool ok = true; for (int i = 0; i < superiorList.size(); ++i) { if (superiorList.at(i) != inferiorList.at(i)) { if ((superiorList.at(i) != '+') && !(superiorList.at(i) == '#' && i == superiorList.size() - 1)) { //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) //then superior can't contain inferior ok = false; break; } else if (i == superiorList.size() - 1 && (superiorList.at(i) == '+' && inferiorList.at(i) == '#') ) { //if the two topics differ at the last level //and the superior's current level is + while the inferior's is #(which can be only in the last position) //then superior can't contain inferior ok = false; break; } } } return ok; } return false; } } /*! *\brief Returns the '+' wildcard containing topic name, which includes the given topic names * * \param first the name of a topic * \param second the name of a topic * \return The name of the common topic, if it exists, otherwise "" */ QString MQTTClient::checkCommonLevel(const QString& first, const QString& second) { QStringList firstList = first.split('/', QString::SkipEmptyParts); QStringList secondtList = second.split('/', QString::SkipEmptyParts); QString commonTopic = ""; if (!firstList.isEmpty()) { //the two topics have to be the same size and can't be identic if ((firstList.size() == secondtList.size()) && (first != second)) { //the index where they differ int differIndex = -1; for (int i = 0; i < firstList.size(); ++i) { if (firstList.at(i) != secondtList.at(i)) { differIndex = i; break; } } //they can differ at only one level and that can't be the first bool differ = false; if (differIndex > 0) { for (int j = differIndex +1; j < firstList.size(); ++j) { if (firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; if (!differ) { for (int i = 0; i < firstList.size(); ++i) { if (i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put '+' wildcard at the level where they differ commonTopic.append('+'); } if (i != firstList.size() - 1) commonTopic.append("/"); } } } } qDebug() << first << " " << second << " common topic: "<stop(); } /*! * \brief Returns whether the user wants to use will message or not */ bool MQTTClient::MQTTWillUse() const{ return m_MQTTWill.MQTTUseWill; } /*! * \brief Sets the will topic of the client * * \param topic */ void MQTTClient::setWillTopic(const QString& topic) { qDebug() << "Set will topic:" << topic; m_MQTTWill.willTopic = topic; } /*! * \brief Returns the will topic of the client */ QString MQTTClient::willTopic() const{ return m_MQTTWill.willTopic; } /*! * \brief Sets the retain flag of the client's will message * * \param retain */ void MQTTClient::setWillRetain(bool retain) { m_MQTTWill.willRetain = retain; } /*! * \brief Returns the retain flag of the client's will message */ bool MQTTClient::willRetain() const { return m_MQTTWill.willRetain; } /*! * \brief Sets the QoS level of the client's will message * * \param QoS */ void MQTTClient::setWillQoS(quint8 QoS) { m_MQTTWill.willQoS = QoS; } /*! * \brief Returns the QoS level of the client's will message */ quint8 MQTTClient::willQoS() const { return m_MQTTWill.willQoS; } /*! * \brief Sets the will message type of the client * * \param messageType */ void MQTTClient::setWillMessageType(WillMessageType messageType) { m_MQTTWill.willMessageType = messageType; } /*! * \brief Returns the will message type of the client */ MQTTClient::WillMessageType MQTTClient::willMessageType() const { return m_MQTTWill.willMessageType; } /*! * \brief Sets the own will message of the user * * \param ownMessage */ void MQTTClient::setWillOwnMessage(const QString& ownMessage) { m_MQTTWill.willOwnMessage = ownMessage; } /*! * \brief Returns the own will message of the user */ QString MQTTClient::willOwnMessage() const { return m_MQTTWill.willOwnMessage; } /*! * \brief Updates the will message of the client */ void MQTTClient::updateWillMessage() { QVector topics = children(AbstractAspect::Recursive); const AsciiFilter* asciiFilter = nullptr; const MQTTTopic* willTopic = nullptr; //Search for the will topic for (const auto* topic : topics) { if (topic->topicName() == m_MQTTWill.willTopic) { willTopic = topic; break; } } //if the will topic is found we can update the will message if (willTopic != nullptr) { //To update the will message we have to disconnect first, then after setting everything connect again if (m_MQTTWill.MQTTUseWill && (m_client->state() == QMqttClient::ClientState::Connected) ) { //Disconnect only once (disconnecting may take a while) if (!m_disconnectForWill) { qDebug() << "Disconnecting from host in order to update will message"; m_client->disconnectFromHost(); m_disconnectForWill = true; } //Try to update again updateWillMessage(); } //If client is disconnected we can update the settings else if (m_MQTTWill.MQTTUseWill && (m_client->state() == QMqttClient::ClientState::Disconnected) && m_disconnectForWill) { m_client->setWillQoS(m_MQTTWill.willQoS); qDebug()<<"Will QoS" << m_MQTTWill.willQoS; m_client->setWillRetain(m_MQTTWill.willRetain); qDebug()<<"Will retain" << m_MQTTWill.willRetain; m_client->setWillTopic(m_MQTTWill.willTopic); qDebug()<<"Will Topic" << m_MQTTWill.willTopic; //Set the will message according to m_willMessageType switch (m_MQTTWill.willMessageType) { case WillMessageType::OwnMessage: m_client->setWillMessage(m_MQTTWill.willOwnMessage.toUtf8()); qDebug()<<"Will own message" << m_MQTTWill.willOwnMessage; break; case WillMessageType::Statistics: { asciiFilter = dynamic_cast(willTopic->filter()); //If the topic's asciiFilter was found, get the needed statistics if (asciiFilter != nullptr) { //Statistics is only possible if the data stored in the MQTTTopic is of type integer or numeric if ((asciiFilter->MQTTColumnMode() == AbstractColumn::ColumnMode::Integer) || (asciiFilter->MQTTColumnMode() == AbstractColumn::ColumnMode::Numeric)) { m_client->setWillMessage(asciiFilter->MQTTColumnStatistics(willTopic).toUtf8()); } //Otherwise set empty message else { m_client->setWillMessage(QString("").toUtf8()); } qDebug() << "Will statistics message: "<< QString(m_client->willMessage()); } break; } case WillMessageType::LastMessage: m_client->setWillMessage(m_MQTTWill.willLastMessage.toUtf8()); qDebug()<<"Will last message:\n" << m_MQTTWill.willLastMessage; break; default: break; } m_disconnectForWill = false; //Reconnect with the updated message m_client->connectToHost(); qDebug()<< "Reconnect to host after updating will message"; } } } /*! * \brief Returns the MQTTClient's will update type */ MQTTClient::WillUpdateType MQTTClient::willUpdateType() const{ return m_MQTTWill.willUpdateType; } /*! * \brief Sets the MQTTClient's will update type * * \param willUpdateType */ void MQTTClient::setWillUpdateType(WillUpdateType willUpdateType) { m_MQTTWill.willUpdateType = willUpdateType; } /*! * \brief Returns the time interval of updating the MQTTClient's will message */ int MQTTClient::willTimeInterval() const{ return m_MQTTWill.willTimeInterval; } /*! * \brief Sets the time interval of updating the MQTTClient's will message, if update type is TimePeriod * * \param interval */ void MQTTClient::setWillTimeInterval(int interval) { m_MQTTWill.willTimeInterval = interval; } /*! * \brief Clear the lastly received message by the will topic * Called when the will topic is changed */ void MQTTClient::clearLastMessage() { m_MQTTWill.willLastMessage.clear(); } /*! * \brief Sets true the corresponding flag of the statistic type, * what means that the given statistic type will be added to the will message * * \param statistics */ -void MQTTClient::addWillStatistics(WillStatistics statistic){ +void MQTTClient::addWillStatistics(WillStatistics statistic) { m_MQTTWill.willStatistics[static_cast(statistic)] = true; } /*! * \brief Sets false the corresponding flag of the statistic type, * what means that the given statistic will no longer be added to the will message * * \param statistics */ void MQTTClient::removeWillStatistics(WillStatistics statistic) { m_MQTTWill.willStatistics[static_cast(statistic)] = false; } /*! * \brief Returns a bool vector, meaning which statistic types are included in the will message * If the corresponding value is true, the statistic type is included, otherwise it isn't */ QVector MQTTClient::willStatistics() const{ return m_MQTTWill.willStatistics; } /*! * \brief Starts the will timer, which will update the will message */ void MQTTClient::startWillTimer() const{ if (m_MQTTWill.willUpdateType == WillUpdateType::TimePeriod) m_willTimer->start(m_MQTTWill.willTimeInterval); } /*! * \brief Stops the will timer */ void MQTTClient::stopWillTimer() const{ m_willTimer->stop(); } //############################################################################## //################################# SLOTS #################################### //############################################################################## /*! *\brief called periodically when update type is TimeInterval */ void MQTTClient::read() { if (m_filter == nullptr) return; if (!m_prepared) { qDebug()<<"Connect"; //connect to the broker m_client->connectToHost(); m_prepared = true; } if ((m_client->state() == QMqttClient::ClientState::Connected) && m_MQTTFirstConnectEstablished) { qDebug()<<"Read"; //Signal for every MQTTTopic that they can read emit readFromTopics(); } } /*! *\brief called when the client successfully connected to the broker */ void MQTTClient::onMQTTConnect() { if (m_client->error() == QMqttClient::NoError) { //if this is the first connection (after setting the options in ImportFileWidget or loading saved project) if (!m_MQTTFirstConnectEstablished) { qDebug()<<"connection made in MQTTClient"; //Subscribe to initial or loaded topics QMapIterator i(m_subscribedTopicNameQoS); - while(i.hasNext()) { + while (i.hasNext()) { i.next(); qDebug()<subscribe(i.key(), i.value()); if (temp) { //If we didn't load the MQTTClient from xml we have to add the MQTTSubscriptions if (!m_loaded) { m_subscriptions.push_back(temp->topic().filter()); MQTTSubscription* newSubscription = new MQTTSubscription(temp->topic().filter()); newSubscription->setMQTTClient(this); addChild(newSubscription); m_MQTTSubscriptions.push_back(newSubscription); } connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); } } m_MQTTFirstConnectEstablished = true; //Signal that the initial subscriptions were made emit MQTTSubscribed(); } //if there was already a connection made(happens after updating will message) else { qDebug() << "Start resubscribing after will message update"; //Only the client has to make the subscriptions again, every other connected data is still available QMapIterator i(m_subscribedTopicNameQoS); - while(i.hasNext()) { + while (i.hasNext()) { i.next(); QMqttSubscription* temp = m_client->subscribe(i.key(), i.value()); if (temp) { qDebug()<topic()<<" "<qos(); connect(temp, &QMqttSubscription::messageReceived, this, &MQTTClient::MQTTSubscriptionMessageReceived); } else qDebug()<<"Couldn't subscribe after will update"; } } } } /*! *\brief called when a message is received by a topic belonging to one of subscriptions of the client. * It passes the message to the appropriate MQTTSubscription which will pass it to the appropriate MQTTTopic */ void MQTTClient::MQTTSubscriptionMessageReceived(const QMqttMessage& msg) { //Decide to interpret retain message or not if (!msg.retain() || (msg.retain() && m_MQTTRetain) ) { //If this is the first message from the topic, save its name if (!m_topicNames.contains(msg.topic().name())) { m_topicNames.push_back(msg.topic().name()); //Signal that a new topic is found emit MQTTTopicsChanged(); } //Pass the message and the topic name to the MQTTSubscription which contains the topic for (auto* subscription : m_MQTTSubscriptions) { if (checkTopicContains(subscription->subscriptionName(), msg.topic().name())) { subscription->messageArrived(msg.payload(), msg.topic().name()); break; } } //if the message was received by the will topic, update the last message received by it if (msg.topic().name() == m_MQTTWill.willTopic) m_MQTTWill.willLastMessage = QString(msg.payload()); } } /*! *\brief Handles some of the possible errors of the client, using MQTTErrorWidget */ void MQTTClient::MQTTErrorChanged(QMqttClient::ClientError clientError) { if (clientError != QMqttClient::ClientError::NoError) { MQTTErrorWidget* errorWidget = new MQTTErrorWidget(clientError, this); errorWidget->show(); } } /*! *\brief Called when a subscription is loaded. * Checks whether every saved subscription was loaded or not. * If everything is loaded, it makes the connection and starts the reading * * \param name, the name of the subscription */ void MQTTClient::subscriptionLoaded(const QString &name) { if (!name.isEmpty()) { qDebug() << "Finished loading: " << name; //Save information about the subscription m_subscriptionsLoaded++; m_subscriptions.push_back(name); QMqttTopicFilter filter {name}; m_subscribedTopicNameQoS[filter] = 0; //Save the topics belonging to the subscription for (const auto* subscription : m_MQTTSubscriptions) { if (subscription->subscriptionName() == name) { const auto& topics = subscription->topics(); for (auto* topic : topics) { m_topicNames.push_back(topic->topicName()); } break; } } //Check whether every subscription was loaded or not if (m_subscriptionsLoaded == m_subscriptionCountToLoad) { //if everything was loaded we can start reading m_loaded = true; read(); } } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void MQTTClient::save(QXmlStreamWriter* writer) const { writer->writeStartElement("MQTTClient"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); writer->writeAttribute("subscriptionCount", QString::number(m_MQTTSubscriptions.size())); writer->writeAttribute("updateType", QString::number(m_updateType)); writer->writeAttribute("readingType", QString::number(m_readingType)); writer->writeAttribute("keepValues", QString::number(m_keepNValues)); if (m_updateType == TimeInterval) writer->writeAttribute("updateInterval", QString::number(m_updateInterval)); if (m_readingType != TillEnd) writer->writeAttribute("sampleSize", QString::number(m_sampleSize)); writer->writeAttribute("host", m_client->hostname()); writer->writeAttribute("port", QString::number(m_client->port())); writer->writeAttribute("username", m_client->username()); writer->writeAttribute("password", m_client->password()); writer->writeAttribute("clientId", m_client->clientId()); writer->writeAttribute("useRetain", QString::number(m_MQTTRetain)); writer->writeAttribute("useWill", QString::number(m_MQTTWill.MQTTUseWill)); writer->writeAttribute("willTopic", m_MQTTWill.willTopic); writer->writeAttribute("willOwnMessage", m_MQTTWill.willOwnMessage); writer->writeAttribute("willQoS", QString::number(m_MQTTWill.willQoS)); writer->writeAttribute("willRetain", QString::number(m_MQTTWill.willRetain)); writer->writeAttribute("willMessageType", QString::number(static_cast(m_MQTTWill.willMessageType))); writer->writeAttribute("willUpdateType", QString::number(static_cast(m_MQTTWill.willUpdateType))); writer->writeAttribute("willTimeInterval", QString::number(m_MQTTWill.willTimeInterval)); for (int i = 0; i < m_MQTTWill.willStatistics.count(); ++i) writer->writeAttribute("willStatistics"+QString::number(i), QString::number(m_MQTTWill.willStatistics[i])); writer->writeAttribute("useID", QString::number(m_MQTTUseID)); writer->writeAttribute("useAuthentication", QString::number(m_MQTTUseAuthentication)); writer->writeEndElement(); //filter m_filter->save(writer); //MQTTSubscription for (auto* sub : children(IncludeHidden)) sub->save(writer); writer->writeEndElement(); // "MQTTClient" } /*! Loads from XML. */ bool MQTTClient::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "MQTTClient") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("subscriptionCount").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'subscriptionCount'")); else m_subscriptionCountToLoad = str.toInt(); str = attribs.value("keepValues").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'keepValues'")); else m_keepNValues = str.toInt(); str = attribs.value("updateType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'updateType'")); else m_updateType = static_cast(str.toInt()); str = attribs.value("readingType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'readingType'")); else m_readingType = static_cast(str.toInt()); if (m_updateType == TimeInterval) { str = attribs.value("updateInterval").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'updateInterval'")); else m_updateInterval = str.toInt(); } if (m_readingType != TillEnd) { str = attribs.value("sampleSize").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'sampleSize'")); else m_sampleSize = str.toInt(); } str = attribs.value("host").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'host'")); else m_client->setHostname(str); str = attribs.value("port").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'port'")); else m_client->setPort(str.toUInt()); str = attribs.value("useAuthentication").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useAuthentication'")); else m_MQTTUseAuthentication = str.toInt(); if (m_MQTTUseAuthentication) { str = attribs.value("username").toString(); if (!str.isEmpty()) m_client->setUsername(str); str = attribs.value("password").toString(); if (!str.isEmpty()) m_client->setPassword(str); } str = attribs.value("useID").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useID'")); else m_MQTTUseID = str.toInt(); if (m_MQTTUseID) { str = attribs.value("clientId").toString(); if (!str.isEmpty()) m_client->setClientId(str); } str = attribs.value("useRetain").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useRetain'")); else m_MQTTRetain = str.toInt(); str = attribs.value("useWill").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useWill'")); else m_MQTTWill.MQTTUseWill = str.toInt(); if (m_MQTTWill.MQTTUseWill) { str = attribs.value("willTopic").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willTopic'")); else m_MQTTWill.willTopic = str; str = attribs.value("willOwnMessage").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willOwnMessage'")); else m_MQTTWill.willOwnMessage = str; str = attribs.value("willQoS").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willQoS'")); else m_MQTTWill.willQoS = str.toUInt(); str = attribs.value("willRetain").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willRetain'")); else m_MQTTWill.willRetain = str.toInt(); str = attribs.value("willMessageType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willMessageType'")); else m_MQTTWill.willMessageType = static_cast(str.toInt()); str = attribs.value("willUpdateType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willUpdateType'")); else m_MQTTWill.willUpdateType = static_cast(str.toInt()); str = attribs.value("willTimeInterval").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willTimeInterval'")); else m_MQTTWill.willTimeInterval = str.toInt(); for (int i = 0; i < m_MQTTWill.willStatistics.count(); ++i) { str = attribs.value("willStatistics"+QString::number(i)).toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'willTimeInterval'")); else m_MQTTWill.willStatistics[i] = str.toInt(); } } } else if (reader->name() == "asciiFilter") { m_filter = new AsciiFilter(); if (!m_filter->load(reader)) return false; } else if (reader->name() == "MQTTSubscription") { MQTTSubscription* subscription = new MQTTSubscription(""); subscription->setMQTTClient(this); m_MQTTSubscriptions.push_back(subscription); connect(subscription, &MQTTSubscription::loaded, this, &MQTTClient::subscriptionLoaded); if (!subscription->load(reader, preview)) { delete subscription; return false; } addChildFast(subscription); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return !reader->hasError(); } #endif diff --git a/src/backend/datasources/MQTTSubscription.cpp b/src/backend/datasources/MQTTSubscription.cpp index 3ce4de7ab..88389a501 100644 --- a/src/backend/datasources/MQTTSubscription.cpp +++ b/src/backend/datasources/MQTTSubscription.cpp @@ -1,225 +1,225 @@ /*************************************************************************** File : MQTTSubscription.cpp Project : LabPlot Description : Represents a subscription made in MQTTClient -------------------------------------------------------------------- Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/MQTTSubscription.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTTopic.h" #include "backend/datasources/MQTTClient.h" #include #include /*! \class MQTTSubscription \brief Represents a subscription made in a MQTTClient object. It plays a role in managing MQTTTopic objects and makes possible representing the subscriptions and topics in a tree like structure \ingroup datasources */ MQTTSubscription::MQTTSubscription(const QString& name) : Folder(name), m_subscriptionName(name) { qDebug() << "New MQTTSubscription: " << name; } MQTTSubscription::~MQTTSubscription() { qDebug() << "Delete MQTTSubscription: " << m_subscriptionName; } /*! *\brief Adds an MQTTTopic as a child * * \param topicName the name of the topic, which will be added to the tree widget */ void MQTTSubscription::addTopic(const QString& topicName) { MQTTTopic* newTopic = new MQTTTopic(topicName, this, false); addChild(newTopic); } /*! *\brief Returns the object's MQTTTopic children * * \return a vector of pointers to the children of the MQTTSubscription */ const QVector MQTTSubscription::topics() const { return children(); } /*! *\brief Returns the object's parent * * \return a pointer to the parent MQTTTopic of the object */ MQTTClient* MQTTSubscription::mqttClient() const { return m_MQTTClient; } /*! *\brief Called when a message arrived to a topic contained by the MQTTSubscription * If the topic can't be found among the children, a new MQTTTopic is instantiated * Passes the messages to the appropriate MQTTTopic * * \param message the message to pass * \param topicName the name of the topic the message was sent to */ -void MQTTSubscription::messageArrived(const QString& message, const QString& topicName){ +void MQTTSubscription::messageArrived(const QString& message, const QString& topicName) { bool found = false; QVector topics = children(); //search for the topic among the MQTTTopic children for (auto* topic: topics) { if (topicName == topic->topicName()) { //pass the message to the topic topic->newMessage(message); //read the message if needed if ((m_MQTTClient->updateType() == MQTTClient::UpdateType::NewData) && !m_MQTTClient->isPaused()) topic->read(); found = true; break; } } //if the topic can't be found, we add it as a new MQTTTopic, and read from it if needed if (!found) { addTopic(topicName); topics = children(); MQTTTopic* newTopic = nullptr; for (auto* topic: topics) { if (topicName == topic->topicName()) { newTopic = topic; break; } } if (newTopic != nullptr) { newTopic->newMessage(message); if ((m_MQTTClient->updateType() == MQTTClient::UpdateType::NewData) && !m_MQTTClient->isPaused()) newTopic->read(); } } } /*! *\brief Returns the subscription's name * * \return m_subscriptionName */ QString MQTTSubscription::subscriptionName() const { return m_subscriptionName; } /*! *\brief Sets the MQTTClient the subscription belongs to * * \param client */ void MQTTSubscription::setMQTTClient(MQTTClient* client) { m_MQTTClient = client; } /*! *\brief Returns the icon of MQTTSubscription */ QIcon MQTTSubscription::icon() const { return QIcon::fromTheme("labplot-MQTT"); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void MQTTSubscription::save(QXmlStreamWriter* writer) const { writer->writeStartElement("MQTTSubscription"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); writer->writeAttribute("subscriptionName", m_subscriptionName); writer->writeEndElement(); //MQTTTopics for (auto* topic : children(IncludeHidden)) topic->save(writer); writer->writeEndElement(); // "MQTTSubscription" } /*! Loads from XML. */ bool MQTTSubscription::load(XmlStreamReader* reader, bool preview) { if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "MQTTSubscription") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("subscriptionName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'subscriptionName'")); else { m_subscriptionName = str; setName(str); } } else if ( reader->name() == QLatin1String("MQTTTopic")) { MQTTTopic* topic = new MQTTTopic("", this, false); if (!topic->load(reader, preview)) { delete topic; return false; } addChildFast(topic); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } emit loaded(this->subscriptionName()); return !reader->hasError(); } #endif diff --git a/src/backend/datasources/MQTTTopic.cpp b/src/backend/datasources/MQTTTopic.cpp index 87fc3b101..c6d814f72 100644 --- a/src/backend/datasources/MQTTTopic.cpp +++ b/src/backend/datasources/MQTTTopic.cpp @@ -1,353 +1,353 @@ /*************************************************************************** File : MQTTTopic.cpp Project : LabPlot Description : Represents a topic of a MQTTSubscription -------------------------------------------------------------------- Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/datasources/MQTTTopic.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTSubscription.h" #include "backend/datasources/MQTTClient.h" #include "backend/core/Project.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "backend/datasources/filters/AsciiFilter.h" #include #include #include #include #include #include #include #include #include #include /*! \class MQTTTopic \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters. Represents a topic of a subscription made in MQTTClient \ingroup datasources */ MQTTTopic::MQTTTopic(const QString& name, MQTTSubscription* subscription, bool loading) : Spreadsheet(name, loading), m_topicName(name), m_MQTTClient(subscription->mqttClient()), m_filter(new AsciiFilter()) { AsciiFilter* mainFilter = dynamic_cast(m_MQTTClient->filter()); AsciiFilter* myFilter = dynamic_cast(m_filter); myFilter->setAutoModeEnabled(mainFilter->isAutoModeEnabled()); if (!mainFilter->isAutoModeEnabled()) { myFilter->setCommentCharacter(mainFilter->commentCharacter()); myFilter->setSeparatingCharacter(mainFilter->separatingCharacter()); myFilter->setDateTimeFormat(mainFilter->dateTimeFormat()); myFilter->setCreateIndexEnabled(mainFilter->createIndexEnabled()); myFilter->setSimplifyWhitespacesEnabled(mainFilter->simplifyWhitespacesEnabled()); myFilter->setNaNValueToZero(mainFilter->NaNValueToZeroEnabled()); myFilter->setRemoveQuotesEnabled(mainFilter->removeQuotesEnabled()); myFilter->setSkipEmptyParts(mainFilter->skipEmptyParts()); myFilter->setHeaderEnabled(mainFilter->isHeaderEnabled()); QString vectorNames; const QStringList& filterVectorNames = mainFilter->vectorNames(); for (int i = 0; i < filterVectorNames.size(); ++i) { vectorNames.append(filterVectorNames.at(i)); if (i != vectorNames.size() - 1) vectorNames.append(QLatin1String(" ")); } myFilter->setVectorNames(vectorNames); myFilter->setStartRow(mainFilter->startRow()); myFilter->setEndRow(mainFilter->endRow()); myFilter->setStartColumn(mainFilter->startColumn()); myFilter->setEndColumn(mainFilter->endColumn()); } connect(m_MQTTClient, &MQTTClient::readFromTopics, this, &MQTTTopic::read); qDebug()<<"New MqttTopic: " << m_topicName; initActions(); } MQTTTopic::~MQTTTopic() { qDebug()<<"MqttTopic destructor:"<actions().size()>1) firstAction = menu->actions().at(1); menu->insertAction(firstAction, m_plotDataAction); menu->insertSeparator(firstAction); return menu; } QWidget* MQTTTopic::view() const { if (!m_partView) m_partView = new SpreadsheetView(const_cast(this), true); return m_partView; } /*! *\brief Returns the reading type of the MQTTClient to which the MQTTTopic belongs */ int MQTTTopic::readingType() const { return static_cast (m_MQTTClient->readingType()); } /*! *\brief Returns sampleSize of the MQTTClient to which the MQTTTopic belongs */ int MQTTTopic::sampleSize() const { return m_MQTTClient->sampleSize(); } /*! *\brief Returns whether reading is paused or not in the MQTTClient to which the MQTTTopic belongs */ bool MQTTTopic::isPaused() const { return m_MQTTClient->isPaused(); } /*! *\brief Returns update interval of the MQTTClient to which the MQTTTopic belongs */ int MQTTTopic::updateInterval() const { return m_MQTTClient->updateInterval(); } /*! *\brief Returns the keepNValues (how many values we should keep) of the MQTTClient to which the MQTTTopic belongs */ int MQTTTopic::keepNValues() const { return m_MQTTClient->keepNValues(); } /*! *\brief Adds a message received by the topic to the message puffer */ void MQTTTopic::newMessage(const QString& message) { m_messagePuffer.push_back(message); } /*! *\brief Returns the name of the MQTTTopic */ QString MQTTTopic::topicName() const{ return m_topicName; } /*! *\brief Initializes the actions of MQTTTopic */ void MQTTTopic::initActions() { m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this); connect(m_plotDataAction, &QAction::triggered, this, &MQTTTopic::plotData); } /*! *\brief Returns the MQTTClient the topic belongs to */ MQTTClient *MQTTTopic::mqttClient() const{ return m_MQTTClient; } //############################################################################## //################################# SLOTS #################################### //############################################################################## /*! *\brief Plots the data stored in MQTTTopic */ void MQTTTopic::plotData() { PlotDataDialog* dlg = new PlotDataDialog(this); dlg->exec(); } /*! *\brief Reads every message from the message puffer */ void MQTTTopic::read() { - while(!m_messagePuffer.isEmpty()) { + while (!m_messagePuffer.isEmpty()) { qDebug()<< "Reading from topic " << m_topicName; const QString tempMessage = m_messagePuffer.takeFirst(); dynamic_cast(m_filter)->readMQTTTopic(tempMessage, m_topicName, this); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void MQTTTopic::save(QXmlStreamWriter* writer) const { writer->writeStartElement("MQTTTopic"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement("general"); writer->writeAttribute("topicName", m_topicName); writer->writeAttribute("filterPrepared", QString::number(dynamic_cast(m_filter)->isPrepared())); writer->writeAttribute("filterSeparator", dynamic_cast(m_filter)->separator()); writer->writeAttribute("messagePufferSize", QString::number(m_messagePuffer.size())); for (int i = 0; i < m_messagePuffer.count(); ++i) writer->writeAttribute("message"+QString::number(i), m_messagePuffer[i]); writer->writeEndElement(); //filter m_filter->save(writer); //Columns for (auto* col : children(IncludeHidden)) col->save(writer); writer->writeEndElement(); //MQTTTopic } /*! Loads from XML. */ bool MQTTTopic::load(XmlStreamReader* reader, bool preview) { removeColumns(0, columnCount()); if (!readBasicAttributes(reader)) return false; bool isFilterPrepared = false; QString separator = ""; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "MQTTTopic") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("topicName").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'topicName'")); else { m_topicName = str; setName(str); } str = attribs.value("filterPrepared").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'filterPrepared'")); else { isFilterPrepared = str.toInt(); } str = attribs.value("filterSeparator").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'filterSeparator'")); else { separator = str; } int pufferSize = 0; str = attribs.value("messagePufferSize").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'messagePufferSize'")); else pufferSize = str.toInt(); for (int i = 0; i < pufferSize; ++i) { str = attribs.value("message"+QString::number(i)).toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'message"+QString::number(i)+"'")); else m_messagePuffer.push_back(str); } } else if (reader->name() == "asciiFilter") { if (!m_filter->load(reader)) return false; } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Text); if (!column->load(reader, preview)) { delete column; setColumnCount(0); return false; } addChild(column); } else {// unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } //prepare filter for reading dynamic_cast(m_filter)->setPreparedForMQTT(isFilterPrepared, this, separator); return !reader->hasError(); } #endif diff --git a/src/kdefrontend/datasources/MQTTErrorWidget.cpp b/src/kdefrontend/datasources/MQTTErrorWidget.cpp index 1e03837f3..ce0e030ae 100644 --- a/src/kdefrontend/datasources/MQTTErrorWidget.cpp +++ b/src/kdefrontend/datasources/MQTTErrorWidget.cpp @@ -1,130 +1,130 @@ /*************************************************************************** File : MQTTErrorWidget.cpp Project : LabPlot Description : Widget for informing about an MQTT error, and for trying to solve it -------------------------------------------------------------------- Copyright : (C) 2018 by Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "src/kdefrontend/datasources/MQTTErrorWidget.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include #include #include MQTTErrorWidget::MQTTErrorWidget(QMqttClient::ClientError error, MQTTClient* client, QWidget *parent) : QWidget(parent), m_error(error), m_client(client) { ui.setupUi(this); bool close = false; //showing the appropriate options according to the error type switch (m_error) { case QMqttClient::ClientError::IdRejected: ui.lePassword->hide(); ui.lPassword->hide(); ui.leUserName->hide(); ui.lUserName->hide(); ui.lErrorType->setText("The client ID is malformed. This might be related to its length.\nSet new ID!"); break; case QMqttClient::ClientError::BadUsernameOrPassword: ui.lId->hide(); ui.leId->hide(); ui.lErrorType->setText("The data in the username or password is malformed.\nSet new username and password!"); break; case QMqttClient::ClientError::NotAuthorized: ui.lId->hide(); ui.leId->hide(); ui.lErrorType->setText("The client is not authorized to connect."); break; case QMqttClient::ClientError::ServerUnavailable: ui.lePassword->hide(); ui.lPassword->hide(); ui.leUserName->hide(); ui.lUserName->hide(); ui.lErrorType->setText("The network connection has been established, but the service is unavailable on the broker side."); break; case QMqttClient::ClientError::UnknownError: ui.lePassword->hide(); ui.lPassword->hide(); ui.leUserName->hide(); ui.lUserName->hide(); ui.lErrorType->setText("An unknown error occurred."); break; case QMqttClient::NoError: case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ProtocolViolation: close = true; break; default: close = true; break; } connect(ui.bChange, &QPushButton::clicked, this, &MQTTErrorWidget::tryToReconnect); setAttribute(Qt::WA_DeleteOnClose); if (close) this->close(); } /*! *\brief Try to reconnect in MQTTClient after reseting options that might cause the error */ -void MQTTErrorWidget::tryToReconnect(){ +void MQTTErrorWidget::tryToReconnect() { bool ok = false; switch (m_error) { case QMqttClient::ClientError::IdRejected: if (!ui.leId->text().isEmpty()) { m_client->setMQTTClientId(ui.leId->text()); m_client->read(); ok = true; } break; case QMqttClient::ClientError::BadUsernameOrPassword: if (!ui.lePassword->text().isEmpty() && !ui.leUserName->text().isEmpty()) { m_client->setMQTTClientAuthentication(ui.leUserName->text(), ui.lePassword->text()); m_client->read(); ok = true; } break; case QMqttClient::ClientError::NotAuthorized: if (!ui.lePassword->text().isEmpty() && !ui.leUserName->text().isEmpty()) { m_client->setMQTTClientAuthentication(ui.leUserName->text(), ui.lePassword->text()); m_client->read(); ok = true; } break; case QMqttClient::NoError: case QMqttClient::InvalidProtocolVersion: case QMqttClient::TransportInvalid: case QMqttClient::ServerUnavailable: case QMqttClient::UnknownError: case QMqttClient::ProtocolViolation: break; default: break; } if (ok) this->close(); } #endif diff --git a/src/kdefrontend/dockwidgets/LiveDataDock.cpp b/src/kdefrontend/dockwidgets/LiveDataDock.cpp index a3478b5aa..bb33b66f2 100644 --- a/src/kdefrontend/dockwidgets/LiveDataDock.cpp +++ b/src/kdefrontend/dockwidgets/LiveDataDock.cpp @@ -1,1693 +1,1693 @@ /*************************************************************************** File : LiveDataDock.cpp Project : LabPlot Description : Dock widget for live data properties -------------------------------------------------------------------- Copyright : (C) 2017 by Fabian Kristof (fkristofszabolcs@gmail.com) Copyright : (C) 2018 by Kovacs Ferencz (kferike98@gmail.com) Copyright : (C) 2018 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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 General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "LiveDataDock.h" #include #include #include #include #include #include #ifdef HAVE_MQTT #include "kdefrontend/widgets/MQTTWillSettingsWidget.h" #include #include #include #endif LiveDataDock::LiveDataDock(QWidget* parent) : QWidget(parent), m_paused(false) #ifdef HAVE_MQTT , m_searching(true), m_searchTimer(new QTimer(this)), m_interpretMessage(true), m_previousMQTTClient(nullptr) #endif { ui.setupUi(this); ui.bUpdateNow->setIcon(QIcon::fromTheme(QLatin1String("view-refresh"))); connect(ui.bPausePlayReading, &QPushButton::clicked, this, &LiveDataDock::pauseContinueReading); connect(ui.bUpdateNow, &QPushButton::clicked, this, &LiveDataDock::updateNow); connect(ui.sbUpdateInterval, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::updateIntervalChanged); connect(ui.sbKeepNValues, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::keepNValuesChanged); connect(ui.sbSampleSize, static_cast(&QSpinBox::valueChanged), this, &LiveDataDock::sampleSizeChanged); connect(ui.cbUpdateType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::updateTypeChanged); connect(ui.cbReadingType, static_cast(&QComboBox::currentIndexChanged), this, &LiveDataDock::readingTypeChanged); #ifdef HAVE_MQTT m_searchTimer->setInterval(10000); connect(this, &LiveDataDock::newTopic, this, &LiveDataDock::setTopicCompleter); connect(m_searchTimer, &QTimer::timeout, this, &LiveDataDock::topicTimeout); connect(ui.bSubscribe, &QPushButton::clicked, this, &LiveDataDock::addSubscription); connect(ui.bUnsubscribe, &QPushButton::clicked, this, &LiveDataDock::removeSubscription); connect(ui.bWillUpdateNow, &QPushButton::clicked, this, &LiveDataDock::willUpdateNow); connect(ui.leTopics, &QLineEdit::textChanged, this, &LiveDataDock::scrollToTopicTreeItem); connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &LiveDataDock::scrollToSubsriptionTreeItem); connect(ui.bWillSettings, &QPushButton::clicked, this, &LiveDataDock::showWillSettings); ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); ui.bSubscribe->setToolTip(i18n("Subscribe selected topics")); ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_ArrowLeft)); ui.bUnsubscribe->setToolTip(i18n("Unsubscribe selected topics")); ui.bWillSettings->setToolTip(i18n("Manage MQTT connection's will settings")); ui.bWillSettings->setIcon(ui.bWillSettings->style()->standardIcon(QStyle::SP_FileDialogDetailedView)); #endif } LiveDataDock::~LiveDataDock() { #ifdef HAVE_MQTT delete m_searchTimer; QMapIterator clients(m_clients); - while(clients.hasNext()) { + while (clients.hasNext()) { clients.next(); delete clients.value(); } #endif } #ifdef HAVE_MQTT /*! * \brief Sets the MQTTClients of this dock widget * \param clients */ void LiveDataDock::setMQTTClients(const QList& clients) { m_liveDataSources.clear(); m_mqttClients.clear(); m_mqttClients = clients; const MQTTClient* const fmc = clients.at(0); ui.sbUpdateInterval->setValue(fmc->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(fmc->updateType())); ui.cbReadingType->setCurrentIndex(static_cast(fmc->readingType())); if (fmc->updateType() == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fmc->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fmc->keepNValues()); ui.sbKeepNValues->setEnabled(true); if (fmc->readingType() == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else ui.sbSampleSize->setValue(fmc->sampleSize()); // disable "whole file" option const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); //show MQTT connected options ui.gbManageSubscriptions->show(); ui.bSubscribe->show(); ui.bUnsubscribe->show(); ui.twTopics->show(); ui.leTopics->show(); ui.lTopicSearch->show(); ui.twSubscriptions->show(); ui.lQoS->show(); ui.cbQoS->show(); ui.lWillSettings->show(); ui.bWillSettings->show(); //if there isn't a client with this hostname we instantiate a new one - if(m_clients[fmc->clientHostName()] == nullptr) { + if (m_clients[fmc->clientHostName()] == nullptr) { m_clients[fmc->clientHostName()] = new QMqttClient(); connect(fmc, &MQTTClient::clientAboutToBeDeleted, this, &LiveDataDock::removeClient); connect(m_clients[fmc->clientHostName()], &QMqttClient::connected, this, &LiveDataDock::onMQTTConnect); connect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); m_clients[fmc->clientHostName()]->setHostname(fmc->clientHostName()); m_clients[fmc->clientHostName()]->setPort(fmc->clientPort()); - if(fmc->MQTTUseAuthentication()) { + if (fmc->MQTTUseAuthentication()) { m_clients[fmc->clientHostName()]->setUsername(fmc->clientUserName()); m_clients[fmc->clientHostName()]->setPassword(fmc->clientPassword()); } - if(fmc->MQTTUseID()) { + if (fmc->MQTTUseID()) { m_clients[fmc->clientHostName()]->setClientId(fmc->clientID()); } m_clients[fmc->clientHostName()]->connectToHost(); } - if(m_previousMQTTClient == nullptr) { + if (m_previousMQTTClient == nullptr) { connect(fmc, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); //Fill the subscription tree(useful if the MQTTClient was loaded) QVector topics = fmc->topicNames(); for (const auto& topic : topics) { addTopicToTree(topic); } fillSubscriptions(); } //if the previous MQTTClient's host name was different from the current one we have to disconnect some slots //and clear the tree widgets - else if(m_previousMQTTClient->clientHostName() != fmc->clientHostName()) { + else if (m_previousMQTTClient->clientHostName() != fmc->clientHostName()) { disconnect(m_previousMQTTClient, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); connect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); disconnect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); ui.twTopics->clear(); //repopulating the tree widget with the already known topics of the client - for(int i = 0; i < m_addedTopics[fmc->clientHostName()].size(); ++i) { + for (int i = 0; i < m_addedTopics[fmc->clientHostName()].size(); ++i) { addTopicToTree(m_addedTopics[fmc->clientHostName()].at(i)); } //fill subscriptions tree widget ui.twSubscriptions->clear(); fillSubscriptions(); connect(fmc, &MQTTClient::MQTTSubscribed, this, &LiveDataDock::fillSubscriptions); connect(m_clients[fmc->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceived); } - if(fmc->willUpdateType() == MQTTClient::WillUpdateType::OnClick && fmc->MQTTWillUse()) + if (fmc->willUpdateType() == MQTTClient::WillUpdateType::OnClick && fmc->MQTTWillUse()) ui.bWillUpdateNow->show(); m_previousMQTTClient = fmc; } #endif /*! * \brief Sets the live data sources of this dock widget * \param sources */ void LiveDataDock::setLiveDataSources(const QList& sources) { #ifdef HAVE_MQTT m_mqttClients.clear(); #endif m_liveDataSources = sources; const LiveDataSource* const fds = sources.at(0); const LiveDataSource::SourceType sourceType = fds->sourceType(); const LiveDataSource::ReadingType readingType = fds->readingType(); const LiveDataSource::UpdateType updateType = fds->updateType(); ui.sbUpdateInterval->setValue(fds->updateInterval()); ui.cbUpdateType->setCurrentIndex(static_cast(updateType)); ui.cbReadingType->setCurrentIndex(static_cast(readingType)); if (updateType == LiveDataSource::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); } if (fds->isPaused()) { ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } ui.sbKeepNValues->setValue(fds->keepNValues()); // disable "whole file" when having no file (i.e. socket or port) auto* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); if (sourceType == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || readingType == LiveDataSource::ReadingType::TillEnd || readingType == LiveDataSource::ReadingType::WholeFile) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.sbSampleSize->setValue(fds->sampleSize()); } // disable "on new data"-option if not available model = qobject_cast(ui.cbUpdateType->model()); item = model->item(LiveDataSource::UpdateType::NewData); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::NetworkUdpSocket || sourceType == LiveDataSource::SourceType::SerialPort) { item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); } else { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } ui.bWillSettings->hide(); ui.lWillSettings->hide(); ui.bWillUpdateNow->hide(); ui.bSubscribe->hide(); ui.bUnsubscribe->hide(); ui.twTopics->hide(); ui.leTopics->hide(); ui.lTopicSearch->hide(); ui.twSubscriptions->hide(); ui.gbManageSubscriptions->hide(); ui.lQoS->hide(); ui.cbQoS->hide(); } /*! * \brief Modifies the sample size of the live data sources or MQTTClient objects * \param sampleSize */ void LiveDataDock::sampleSizeChanged(int sampleSize) { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setSampleSize(sampleSize); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setSampleSize(sampleSize); } #endif } /*! * \brief Updates the live data sources now */ void LiveDataDock::updateNow() { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->updateNow(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->updateNow(); } #endif } /*! * \brief LiveDataDock::updateTypeChanged * \param idx */ void LiveDataDock::updateTypeChanged(int idx) { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); const auto type = static_cast(idx); switch (type) { case LiveDataSource::UpdateType::TimeInterval: ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); ui.lSampleSize->show(); ui.sbSampleSize->show(); for (auto* source : m_liveDataSources) { source->setUpdateType(type); source->setUpdateInterval(ui.sbUpdateInterval->value()); source->setFileWatched(false); } break; case LiveDataSource::UpdateType::NewData: ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); ui.lSampleSize->hide(); ui.sbSampleSize->hide(); for (auto* source : m_liveDataSources) { source->setFileWatched(true); source->setUpdateType(type); } } } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { DEBUG("LiveDataDock::updateTypeChanged()"); const MQTTClient::UpdateType type = static_cast(idx); if (type == MQTTClient::UpdateType::TimeInterval) { ui.lUpdateInterval->show(); ui.sbUpdateInterval->show(); for (auto* client : m_mqttClients) { client->setUpdateType(type); client->setUpdateInterval(ui.sbUpdateInterval->value()); } } else if (type == MQTTClient::UpdateType::NewData) { ui.lUpdateInterval->hide(); ui.sbUpdateInterval->hide(); for (auto* client : m_mqttClients) { client->setUpdateType(type); } } } #endif } /*! * \brief Handles the change of the reading type in the dock widget * \param idx */ void LiveDataDock::readingTypeChanged(int idx) { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { const auto type = static_cast(idx); const LiveDataSource* const fds = m_liveDataSources.at(0); const LiveDataSource::SourceType sourceType = fds->sourceType(); const LiveDataSource::UpdateType updateType = fds->updateType(); if (sourceType == LiveDataSource::SourceType::NetworkTcpSocket || sourceType == LiveDataSource::SourceType::LocalSocket || sourceType == LiveDataSource::SourceType::SerialPort || type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile || updateType == LiveDataSource::UpdateType::NewData) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* source : m_liveDataSources) source->setReadingType(type); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { MQTTClient::ReadingType type = static_cast(idx); if (type == MQTTClient::ReadingType::TillEnd) { ui.lSampleSize->hide(); ui.sbSampleSize->hide(); } else { ui.lSampleSize->show(); ui.sbSampleSize->show(); } for (auto* client : m_mqttClients) client->setReadingType(type); } #endif } /*! * \brief Modifies the update interval of the live data sources * \param updateInterval */ void LiveDataDock::updateIntervalChanged(int updateInterval) { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setUpdateInterval(updateInterval); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setUpdateInterval(updateInterval); } #endif } /*! * \brief Modifies the number of samples to keep in each of the live data sources * \param keepNValues */ void LiveDataDock::keepNValuesChanged(const int keepNValues) { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->setKeepNValues(keepNValues); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->setKeepNValues(keepNValues); } #endif } /*! * \brief Pauses the reading of the live data source */ void LiveDataDock::pauseReading() { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { for (auto* source: m_liveDataSources) source->pauseReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->pauseReading(); } #endif } /*! * \brief Continues the reading of the live data source */ void LiveDataDock::continueReading() { - if(!m_liveDataSources.isEmpty()) { + if (!m_liveDataSources.isEmpty()) { for (auto* source : m_liveDataSources) source->continueReading(); } #ifdef HAVE_MQTT else if (!m_mqttClients.isEmpty()) { for (auto* client : m_mqttClients) client->continueReading(); } #endif } /*! * \brief Handles the pausing/continuing of reading of the live data source */ void LiveDataDock::pauseContinueReading() { m_paused = !m_paused; if (m_paused) { pauseReading(); ui.bPausePlayReading->setText(i18n("Continue Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-record"))); } else { continueReading(); ui.bPausePlayReading->setText(i18n("Pause Reading")); ui.bPausePlayReading->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); } } #ifdef HAVE_MQTT /*! *\brief called when use will message checkbox's state is changed in the will settings widget, * Sets the mqttUseWill according to state for every client in m_mqttClients * * \param state the state of the checbox */ void LiveDataDock::useWillMessage(int state) { qDebug()<<"Use will message: " <setMQTTWillUse(true); if (m_mqttClients.first()->willUpdateType() == MQTTClient::WillUpdateType::OnClick) ui.bWillUpdateNow->show(); } else if (state == Qt::Unchecked) { for (auto* source : m_mqttClients) source->setMQTTWillUse(false); ui.bWillUpdateNow->hide(); } } /*! *\brief called when will message's QoS is changed in the will settings widget * sets the will QoS level for every client in m_mqttClients * * \param QoS the QoS level of the will message */ void LiveDataDock::willQoSChanged(int QoS) { for (auto* source : m_mqttClients) source->setWillQoS(QoS); } /*! *\brief called when will message's retain flag is changed in the will settings widget * sets the retain flag for the will message in every client in m_mqttClients * * \param state the state of the will retain chechbox */ void LiveDataDock::willRetainChanged(int state) { - if(state == Qt::Checked) { + if (state == Qt::Checked) { for (auto* source : m_mqttClients) source->setWillRetain(true); } else if (state == Qt::Unchecked) { for (auto* source : m_mqttClients) source->setWillRetain(false); } } /*! *\brief called when will topic combobox's current item is changed in the will settings widget * sets the will topic for every client in m_mqttClients * * \param topic the current text of cbWillTopic */ void LiveDataDock::willTopicChanged(const QString& topic) { for (auto* source : m_mqttClients) { - if(source->willTopic() != topic) + if (source->willTopic() != topic) source->clearLastMessage(); source->setWillTopic(topic); } } /*! *\brief called when the selected will message type is changed in the will settings widget * sets the will message type for every client in m_mqttClients * * \param type the selected will message type */ void LiveDataDock::willMessageTypeChanged(int type) { for (auto* source : m_mqttClients) source->setWillMessageType(static_cast (type)); } /*! *\brief called when the will own message is changed in the will settings widget * sets the will own message for every client in m_mqttClients * * \param message the will message given by the user */ void LiveDataDock::willOwnMessageChanged(const QString& message) { for (auto* source : m_mqttClients) source->setWillOwnMessage(message); } /*! *\brief called when the selected update type for the will message is changed in the will settings widget * sets the will update type for every client in m_mqttClients * * \param type the selected will update type */ void LiveDataDock::willUpdateTypeChanged(int updateType) { for (auto* source : m_mqttClients) source->setWillUpdateType(static_cast(updateType)); - if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { + if (static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { ui.bWillUpdateNow->hide(); for (auto* source : m_mqttClients) source->startWillTimer(); } else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { ui.bWillUpdateNow->show(); //if update type is on click we stop the will timer for (auto* source : m_mqttClients) source->stopWillTimer(); } } /*! *\brief called when the will update now button is pressed * updates the will message of every client in m_mqttClients */ void LiveDataDock::willUpdateNow() { for (auto* source : m_mqttClients) source->updateWillMessage(); } /*! *\brief called when the update interval for will message is changed in the will settings widget * sets the will update interval for every client in m_mqttClients, then starts the will timer for each one * * \param interval the new will update interval */ void LiveDataDock::willUpdateIntervalChanged(int interval) { for (auto* source : m_mqttClients) { source->setWillTimeInterval(interval); source->startWillTimer(); } } /*! *\brief called when the will statistics are changed in the will settings widget * adds or removes the statistic represented by the index from every client in m_mqttClients */ void LiveDataDock::statisticsChanged(int index) { const bool useStatistic = m_mqttClients.first()->willStatistics()[index]; //if it's checked we add it - if(!useStatistic) { - if(index >= 0) { + if (!useStatistic) { + if (index >= 0) { for (auto* source : m_mqttClients) source->addWillStatistics(static_cast(index) ); } } //otherwise remove it else { - if(index >= 0){ + if (index >= 0) { for (auto* source : m_mqttClients) source->removeWillStatistics(static_cast(index) ); } } } /*! *\brief called when the client connects to the broker successfully, it subscribes to every topic (# wildcard) * in order to later list every available topic */ void LiveDataDock::onMQTTConnect() { const QMqttTopicFilter globalFilter{"#"}; QMqttSubscription* subscription { m_clients[m_mqttClients.first()->clientHostName()]->subscribe(globalFilter, 1) }; - if(!subscription) + if (!subscription) qDebug()<<"Couldn't make global subscription in LiveDataDock"; } /*! *\brief called when the client receives a message * if the message arrived from a new topic, the topic is put in twTopics */ void LiveDataDock::mqttMessageReceived(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) - if(!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { + if (!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); addTopicToTree(topic.name()); } } /*! *\brief called when the subscribe button is pressed * subscribes to the topic represented by the current item of twTopics in every client from m_mqttClients */ void LiveDataDock::addSubscription() { QString name; QTreeWidgetItem* item = ui.twTopics->currentItem(); - if(item != nullptr) { + if (item != nullptr) { QTreeWidgetItem* tempItem = item; //determine the topic name that the current item represents name.prepend(item->text(0)); - if(item->childCount() != 0) + if (item->childCount() != 0) name.append("/#"); - while(tempItem->parent() != nullptr) { + while (tempItem->parent() != nullptr) { tempItem = tempItem->parent(); name.prepend(tempItem->text(0) + '/'); } //check if the subscription already exists const QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); - if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { + if (topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { qDebug() << "LiveDataDock: start to add new subscription: " << name; bool foundSuperior = false; - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { //if the new subscriptions contains an already existing one, we remove the inferior one - if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) + if (checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); --i; continue; } //if there is a subscription containing the new one we set foundSuperior true - if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) + if (checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { foundSuperior = true; qDebug()<<"Can't add "<topLevelItem(i)->text(0); break; } } //if there wasn't a superior subscription we can subscribe to the new topic - if(!foundSuperior) { + if (!foundSuperior) { QStringList toplevelName; toplevelName.push_back(name); QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); ui.twSubscriptions->addTopLevelItem(newTopLevelItem); - if(name.endsWith('#')) { + if (name.endsWith('#')) { //adding every topic that the subscription contains to twSubscriptions addSubscriptionChildren(item, newTopLevelItem); } //subscribe in every MQTTClient for (auto* source : m_mqttClients) { source->addMQTTSubscription(name, ui.cbQoS->currentIndex()); } - if(name.endsWith('#')) { + if (name.endsWith('#')) { //if an already existing subscription contains a topic that the new subscription also contains //we decompose the already existing subscription //by unsubscribing from its topics, that are present in the new subscription as well const QStringList& nameList = name.split('/', QString::SkipEmptyParts); const QString root = nameList.first(); QVector children; - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { children.clear(); //get the "leaf" children of the inspected subscription findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); - for(int j = 0; j < children.size(); ++j) { - if(checkTopicContains(name, children[j]->text(0))) { + for (int j = 0; j < children.size(); ++j) { + if (checkTopicContains(name, children[j]->text(0))) { //if the new subscription contains a topic, we unsubscribe from it QTreeWidgetItem* unsubscribeItem = children[j]; - while(unsubscribeItem->parent() != nullptr) { - for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { + while (unsubscribeItem->parent() != nullptr) { + for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { - if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { + if (unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source : m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } //also add it to twSubscriptions ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); --i; } else { //before we remove the topic, we reparent it to the new subscription //so no data is lost for (auto* source : m_mqttClients) { source->reparentTopic(unsubscribeItem->text(0), name); } } } unsubscribeItem = unsubscribeItem->parent(); } qDebug()<<"Remove: "<text(0); //Remove topic/subscription for (auto* source : m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } } } } } manageCommonLevelSubscriptions(); updateSubscriptionCompleter(); - if(!ui.bWillSettings->isEnabled()) + if (!ui.bWillSettings->isEnabled()) ui.bWillSettings->setEnabled(true); } else { QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); } } else QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when the unsubscribe button is pressed * unsubscribes from the topic represented by the current item of twSubscription in every client from m_mqttClients */ void LiveDataDock::removeSubscription() { QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); - if(unsubscribeItem != nullptr) { + if (unsubscribeItem != nullptr) { qDebug() << "LiveDataDock: unsubscribe from " << unsubscribeItem->text(0); //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) //we can simply unsubscribe from it - if(unsubscribeItem->parent() == nullptr) { + if (unsubscribeItem->parent() == nullptr) { for (auto* source : m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); } //otherwise we remove the selected item, but subscribe to every other topic, that was contained by //the selected item's parent subscription(top level item of twSubscriptions) else{ - while(unsubscribeItem->parent() != nullptr) { - for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { - if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { + while (unsubscribeItem->parent() != nullptr) { + for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { + if (unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { //add topic as subscription to every client for (auto* source : m_mqttClients) { source->addBeforeRemoveSubscription(unsubscribeItem->parent()->child(i)->text(0), ui.cbQoS->currentIndex()); } ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); --i; } } unsubscribeItem = unsubscribeItem->parent(); } //remove topic/subscription from every client for (auto* source : m_mqttClients) { source->removeMQTTSubscription(unsubscribeItem->text(0)); } ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem)); //check if any common topics were subscribed, if possible merge them manageCommonLevelSubscriptions(); } - if(ui.twSubscriptions->topLevelItemCount() <= 0) + if (ui.twSubscriptions->topLevelItemCount() <= 0) ui.bWillSettings->setEnabled(false); updateSubscriptionCompleter(); } else QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } /*! *\brief called when a new topic is added to the tree(twTopics) * appends the topic's root to the topicList if it isn't in the list already * then sets the completer for leTopics */ void LiveDataDock::setTopicCompleter(const QString& topicName) { - if(!m_searching) { + if (!m_searching) { const QStringList& list = topicName.split('/', QString::SkipEmptyParts); QString topic; - if(!list.isEmpty()) { + if (!list.isEmpty()) { topic = list.at(0); } else topic = topicName; - if(!m_topicList[m_mqttClients.first()->clientHostName()].contains(topic)) { + if (!m_topicList[m_mqttClients.first()->clientHostName()].contains(topic)) { m_topicList[m_mqttClients.first()->clientHostName()].append(topic); m_topicCompleter = new QCompleter(m_topicList[m_mqttClients.first()->clientHostName()], this); m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion); m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leTopics->setCompleter(m_topicCompleter); } } } /*! *\brief Updates the completer for leSubscriptions */ void LiveDataDock::updateSubscriptionCompleter() { QStringList subscriptionList; const QVector& subscriptions = m_mqttClients.first()->MQTTSubscriptions(); - if(!subscriptions.isEmpty()) { + if (!subscriptions.isEmpty()) { for (const auto& subscription : subscriptions) { subscriptionList << subscription; } m_subscriptionCompleter = new QCompleter(subscriptionList, this); m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion); m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive); ui.leSubscriptions->setCompleter(m_subscriptionCompleter); } else { ui.leSubscriptions->setCompleter(nullptr); } } /*! *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics * enables updating the completer for le */ void LiveDataDock::topicTimeout() { m_searching = false; m_searchTimer->stop(); } /*! *\brief called when a new the host name of the MQTTClients from m _mqttClients changes * or when the MQTTClients initialize their subscriptions * Fills twSubscriptions with the subscriptions of the MQTTClient */ void LiveDataDock::fillSubscriptions() { const MQTTClient* const fmc = m_mqttClients.at(0); ui.twSubscriptions->clear(); QVector subscriptions = fmc->MQTTSubscriptions(); for (int i = 0; i < subscriptions.count(); ++i) { QStringList name; name.append(subscriptions[i]); bool found = false; - for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { - if(ui.twSubscriptions->topLevelItem(j)->text(0) == subscriptions[i]) { + for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + if (ui.twSubscriptions->topLevelItem(j)->text(0) == subscriptions[i]) { found = true; break; } } - if(!found) { + if (!found) { //Add the subscription to the tree widget QTreeWidgetItem* newItem = new QTreeWidgetItem(name); ui.twSubscriptions->addTopLevelItem(newItem); name.clear(); name = subscriptions[i].split('/', QString::SkipEmptyParts); //find the corresponding "root" item in twTopics QTreeWidgetItem* topic = nullptr; - for(int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { - if(ui.twTopics->topLevelItem(j)->text(0) == name[0]) { + for (int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) { + if (ui.twTopics->topLevelItem(j)->text(0) == name[0]) { topic = ui.twTopics->topLevelItem(j); break; } } //restore the children of the subscription - if(topic != nullptr && topic->childCount() > 0) { + if (topic != nullptr && topic->childCount() > 0) { restoreSubscriptionChildren(topic, newItem, name, 1); } } } m_searching = false; } /*! *\brief Checks if a topic contains another one * * \param superior the name of a topic * \param inferior the name of a topic * \return true if superior is equal to or contains(if superior contains wildcards) inferior, * false otherwise */ bool LiveDataDock::checkTopicContains(const QString& superior, const QString& inferior) { if (superior == inferior) return true; else { - if(superior.contains('/')) { + if (superior.contains('/')) { QStringList superiorList = superior.split('/', QString::SkipEmptyParts); QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); //a longer topic can't contain a shorter one - if(superiorList.size() > inferiorList.size()) + if (superiorList.size() > inferiorList.size()) return false; bool ok = true; - for(int i = 0; i < superiorList.size(); ++i) { - if(superiorList.at(i) != inferiorList.at(i)) { - if((superiorList.at(i) != "+") && + for (int i = 0; i < superiorList.size(); ++i) { + if (superiorList.at(i) != inferiorList.at(i)) { + if ((superiorList.at(i) != "+") && !(superiorList.at(i) == '#' && i == superiorList.size() - 1)) { //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position) //then superior can't contain inferior ok = false; break; - } else if(i == superiorList.size() - 1 && (superiorList.at(i) == "+" && inferiorList.at(i) == '#') ) { + } else if (i == superiorList.size() - 1 && (superiorList.at(i) == "+" && inferiorList.at(i) == '#') ) { //if the two topics differ at the last level //and the superior's current level is + while the inferior's is #(which can be only in the last position) //then superior can't contain inferior ok = false; break; } } } return ok; } return false; } } /*! *\brief called when leTopics' text is changed * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget * * \param rootName the current text of leTopics */ void LiveDataDock::scrollToTopicTreeItem(const QString& rootName) { m_searching = true; m_searchTimer->start(); int topItemIdx = -1; - for(int i = 0; i< ui.twTopics->topLevelItemCount(); ++i) - if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) + if (ui.twTopics->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } - if(topItemIdx >= 0) { + if (topItemIdx >= 0) { ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief called when leSubscriptions' text is changed * if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget * * \param rootName the current text of leSubscriptions */ void LiveDataDock::scrollToSubsriptionTreeItem(const QString& rootName) { int topItemIdx = -1; - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) - if(ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) + if (ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) { topItemIdx = i; break; } - if(topItemIdx >= 0) { + if (topItemIdx >= 0) { ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } /*! *\brief Returns the "+" wildcard containing topic name, which includes the given topic names * * \param first the name of a topic * \param second the name of a topic * \return The name of the common topic, if it exists, otherwise "" */ QString LiveDataDock::checkCommonLevel(const QString& first, const QString& second) { const QStringList firstList = first.split('/', QString::SkipEmptyParts); const QStringList secondtList = second.split('/', QString::SkipEmptyParts); QString commonTopic = ""; - if(!firstList.isEmpty()) { + if (!firstList.isEmpty()) { //the two topics have to be the same size and can't be identic - if(firstList.size() == secondtList.size() && (first != second)) { + if (firstList.size() == secondtList.size() && (first != second)) { //the index where they differ int differIndex = -1; - for(int i = 0; i < firstList.size(); ++i) { - if(firstList.at(i) != secondtList.at(i)) { + for (int i = 0; i < firstList.size(); ++i) { + if (firstList.at(i) != secondtList.at(i)) { differIndex = i; break; } } //they can differ at only one level bool differ = false; - if(differIndex > 0 && differIndex < firstList.size() - 1) { - for(int j = differIndex +1; j < firstList.size(); ++j) { - if(firstList.at(j) != secondtList.at(j)) { + if (differIndex > 0 && differIndex < firstList.size() - 1) { + for (int j = differIndex + 1; j < firstList.size(); ++j) { + if (firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; - if(!differ) + if (!differ) { - for(int i = 0; i < firstList.size(); ++i) { - if(i != differIndex) { + for (int i = 0; i < firstList.size(); ++i) { + if (i != differIndex) { commonTopic.append(firstList.at(i)); } else { //we put "+" wildcard at the level where they differ commonTopic.append('+'); } - if(i != firstList.size() - 1) + if (i != firstList.size() - 1) commonTopic.append('/'); } } } } qDebug() << "Common topic for " << first << " and "<childCount() > 0) { - for(int i = 0; i < topic->childCount(); ++i) { + if (topic->childCount() > 0) { + for (int i = 0; i < topic->childCount(); ++i) { QTreeWidgetItem* temp = topic->child(i); QString name; //if it has children, then we add it as a # wildcrad containing topic - if(topic->child(i)->childCount() > 0) { + if (topic->child(i)->childCount() > 0) { name.append(temp->text(0) + QLatin1String("/#")); - while(temp->parent() != nullptr) { + while (temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + '/'); } } //if not then we simply add the topic itself else { name.append(temp->text(0)); - while(temp->parent() != nullptr) { + while (temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + '/'); } } QStringList nameList { QStringList() << name}; QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); subscription->addChild(childItem); //we use the function recursively on the given item addSubscriptionChildren(topic->child(i), childItem); } } } /*! *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards * * \param topic pointer to a top level item in twTopics which represents the root of the subscription topic * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored * \param list QStringList containing the levels of the subscription topic * \param level the level's number which is being investigated */ void LiveDataDock::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) { - if(level < list.size()) { - if((level < list.size() - 1) && (list[level] != "+") && (list[level] != '#')) { - for(int i = 0; i < topic->childCount(); ++i) { + if (level < list.size()) { + if ((level < list.size() - 1) && (list[level] != "+") && (list[level] != '#')) { + for (int i = 0; i < topic->childCount(); ++i) { //if the current level isn't + or # wildcard we recursively continue with the next level - if(topic->child(i)->text(0) == list[level]) { + if (topic->child(i)->text(0) == list[level]) { restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1); break; } } } else if (list[level] == "+") { - for(int i = 0; i < topic->childCount(); ++i) { + for (int i = 0; i < topic->childCount(); ++i) { //determine the name of the topic, contained by the subscription QString name; name.append(topic->child(i)->text(0)); - for(int j = level + 1; j < list.size(); ++j) { + for (int j = level + 1; j < list.size(); ++j) { name.append('/' + list[j]); } QTreeWidgetItem* temp = topic->child(i); - while(temp->parent() != nullptr) { + while (temp->parent() != nullptr) { temp = temp->parent(); name.prepend(temp->text(0) + '/'); } //Add the topic as child of the subscription QStringList nameList; nameList.append(name); QTreeWidgetItem* newItem = new QTreeWidgetItem(nameList); subscription->addChild(newItem); //Continue adding children recursively to the new item restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1); } } else if (list[level] == '#') { //add the children of the # wildcard containing subscription addSubscriptionChildren(topic, subscription); } } } /*! *\brief Returns the index of level where the two topic names differ, if there is a common topic for them * * \param first the name of a topic * \param second the name of a topic * \return The index of the unequal level, if there is a common topic, otherwise -1 */ int LiveDataDock::commonLevelIndex(const QString& first, const QString& second) { QStringList firstList = first.split('/', QString::SkipEmptyParts); QStringList secondtList = second.split('/', QString::SkipEmptyParts); QString commonTopic = ""; int differIndex = -1; - if(!firstList.isEmpty()) { + if (!firstList.isEmpty()) { //the two topics have to be the same size and can't be identic - if(firstList.size() == secondtList.size() && (first != second)) { + if (firstList.size() == secondtList.size() && (first != second)) { //the index where they differ - for(int i = 0; i < firstList.size(); ++i) { - if(firstList.at(i) != secondtList.at(i)) { + for (int i = 0; i < firstList.size(); ++i) { + if (firstList.at(i) != secondtList.at(i)) { differIndex = i; break; } } //they can differ at only one level bool differ = false; - if(differIndex > 0) { - for(int j = differIndex +1; j < firstList.size(); ++j) { - if(firstList.at(j) != secondtList.at(j)) { + if (differIndex > 0) { + for (int j = differIndex + 1; j < firstList.size(); ++j) { + if (firstList.at(j) != secondtList.at(j)) { differ = true; break; } } } else differ = true; - if(!differ) + if (!differ) { - for(int i = 0; i < firstList.size(); ++i) { - if(i != differIndex) + for (int i = 0; i < firstList.size(); ++i) { + if (i != differIndex) commonTopic.append(firstList.at(i)); else commonTopic.append('+'); - if(i != firstList.size() - 1) + if (i != firstList.size() - 1) commonTopic.append('/'); } } } } //if there is a common topic we return the differIndex - if(!commonTopic.isEmpty()) + if (!commonTopic.isEmpty()) return differIndex; else return -1; } /*! *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) * * \param children vector of TreeWidgetItem pointers * \param root pointer to a TreeWidgetItem of twSubscriptions */ void LiveDataDock::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { - if(root->childCount() == 0) { + if (root->childCount() == 0) { children.push_back(root); } else { - for(int i = 0; i < root->childCount(); ++i) { + for (int i = 0; i < root->childCount(); ++i) { findSubscriptionLeafChildren(children, root->child(i)); } } } /*! *\brief Returns the amount of topics that the "+" wildcard will replace in the level position * * \param levelIdx the level currently being investigated * \param level the level where the new + wildcard will be placed * \param commonList the topic name split into levels * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level * represented by levelIdx * \return returns the childCount, or -1 if some topics already represented by + wildcard have different * amount of children */ int LiveDataDock::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { //we recursively check the number of children, until we get to level-1 - if(levelIdx < level - 1) { - if(commonList[levelIdx] != "+") { - for(int j = 0; j < currentItem->childCount(); ++j) { - if(currentItem->child(j)->text(0) == commonList[levelIdx]) { + if (levelIdx < level - 1) { + if (commonList[levelIdx] != "+") { + for (int j = 0; j < currentItem->childCount(); ++j) { + if (currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children, recursively - for(int j = 0; j < currentItem->childCount(); ++j) { + for (int j = 0; j < currentItem->childCount(); ++j) { int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); - if((j > 0) && (temp != childCount)) { + if ((j > 0) && (temp != childCount)) { ok = false; break; } childCount = temp; } //if yes we return this number, otherwise -1 - if(ok) + if (ok) return childCount; else return -1; } } else if (levelIdx == level - 1) { - if(commonList[levelIdx] != "+") { - for(int j = 0; j < currentItem->childCount(); ++j) { - if(currentItem->child(j)->text(0) == commonList[levelIdx]) { + if (commonList[levelIdx] != "+") { + for (int j = 0; j < currentItem->childCount(); ++j) { + if (currentItem->child(j)->text(0) == commonList[levelIdx]) { //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item return currentItem->child(j)->childCount(); } } } else { int childCount = -1; bool ok = true; //otherwise we check if every + wildcard represented topic has the same number of children - for(int j = 0; j < currentItem->childCount(); ++j) { - if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { + for (int j = 0; j < currentItem->childCount(); ++j) { + if ((j > 0) && (currentItem->child(j)->childCount() != childCount)) { ok = false; break; } childCount = currentItem->child(j)->childCount(); } //if yes we return this number, otherwise -1 - if(ok) + if (ok) return childCount; else return -1; } } else if (level == 1 && levelIdx == 1) return currentItem->childCount(); return -1; } /*! *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. * We do this until there are no topics to merge */ void LiveDataDock::manageCommonLevelSubscriptions() { bool foundEqual = false; - do{ + do { foundEqual = false; QMap> equalTopicsMap; QVector equalTopics; //compare the subscriptions present in the TreeWidget - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { - for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { + for (int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) - if(!commonTopic.isEmpty()) { - if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { + if (!commonTopic.isEmpty()) { + if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); } - if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { + if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); } } } } - if(!equalTopicsMap.isEmpty()) { + if (!equalTopicsMap.isEmpty()) { qDebug()<<"Manage equal topics"; QVector commonTopics; QMapIterator> topics(equalTopicsMap); //check for every map entry, if the found topics can be merged or not - while(topics.hasNext()) { + while (topics.hasNext()) { topics.next(); int level = commonLevelIndex(topics.value().last(), topics.value().first()); QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; //search the corresponding item to the common topics first level(root) - for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { - if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { currentItem = ui.twTopics->topLevelItem(i); break; } } //calculate the number of topics the new + wildcard could replace int childCount = checkCommonChildCount(1, level, commonList, currentItem); - if(childCount > 0) { + if (childCount > 0) { //if the number of topics found and the calculated number of topics is equal, the topics can be merged - if(topics.value().size() == childCount) { + if (topics.value().size() == childCount) { qDebug() << "Found common topic to manage: " << topics.key(); foundEqual = true; commonTopics.push_back(topics.key()); } } } - if(foundEqual) { + if (foundEqual) { //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new "+" wildcard int highestLevel = INT_MAX; int topicIdx = -1; - for(int i = 0; i < commonTopics.size(); ++i) { + for (int i = 0; i < commonTopics.size(); ++i) { int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); - if(level < highestLevel) { + if (level < highestLevel) { topicIdx = i; highestLevel = level; } } qDebug() << "Start to manage: " << commonTopics[topicIdx]; equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); //Add the common topic ("merging") QString commonTopic; commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); QStringList nameList; nameList.append(commonTopic); QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); ui.twSubscriptions->addTopLevelItem(newTopic); //remove the "merged" topics - for(int i = 0; i < equalTopics.size(); ++i) { - for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ - if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { + for (int i = 0; i < equalTopics.size(); ++i) { + for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + if (ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); break; } } } //remove any subscription that the new subscription contains - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { ui.twSubscriptions->topLevelItem(i)->takeChildren(); ui.twSubscriptions->takeTopLevelItem(i); i--; } } //make the subscription on commonTopic in every MQTTClient from m_mqttClients for (auto* source : m_mqttClients) { source->addMQTTSubscription(commonTopic, ui.cbQoS->currentIndex()); } } } - } while(foundEqual); + } while (foundEqual); } /*! *\brief Adds topicName to twTopics * * \param topicName the name of the topic, which will be added to the tree widget */ void LiveDataDock::addTopicToTree(const QString &topicName) { QStringList name; QChar sep = '/'; QString rootName; - if(topicName.contains(sep)) { + if (topicName.contains(sep)) { QStringList list = topicName.split(sep, QString::SkipEmptyParts); - if(!list.isEmpty()) { + if (!list.isEmpty()) { rootName = list.at(0); name.append(list.at(0)); QTreeWidgetItem* currentItem; //check whether the first level of the topic can be found in twTopics int topItemIdx = -1; - for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { - if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { topItemIdx = i; break; } } //if not we simply add every level of the topic to the tree - if( topItemIdx < 0) { + if ( topItemIdx < 0) { currentItem = new QTreeWidgetItem(name); ui.twTopics->addTopLevelItem(currentItem); - for(int i = 1; i < list.size(); ++i) { + for (int i = 1; i < list.size(); ++i) { name.clear(); name.append(list.at(i)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(0); } } //otherwise we search for the first level that isn't part of the tree, //then add every level of the topic to the tree from that certain level else { currentItem = ui.twTopics->topLevelItem(topItemIdx); int listIdx = 1; - for(; listIdx < list.size(); ++listIdx) { + for (; listIdx < list.size(); ++listIdx) { QTreeWidgetItem* childItem = nullptr; bool found = false; - for(int j = 0; j < currentItem->childCount(); ++j) { + for (int j = 0; j < currentItem->childCount(); ++j) { childItem = currentItem->child(j); - if(childItem->text(0) == list.at(listIdx)) { + if (childItem->text(0) == list.at(listIdx)) { found = true; currentItem = childItem; break; } } - if(!found) { + if (!found) { //this is the level that isn't present in the tree break; } } //add every level to the tree starting with the first level that isn't part of the tree - for(; listIdx < list.size(); ++listIdx) { + for (; listIdx < list.size(); ++listIdx) { name.clear(); name.append(list.at(listIdx)); currentItem->addChild(new QTreeWidgetItem(name)); currentItem = currentItem->child(currentItem->childCount() - 1); } } } } else { rootName = topicName; name.append(topicName); ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); } //if a subscribed topic contains the new topic, we have to update twSubscriptions - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { QStringList subscriptionName = ui.twSubscriptions->topLevelItem(i)->text(0).split('/', QString::SkipEmptyParts); if (rootName == subscriptionName[0]) { fillSubscriptions(); break; } } //signals that a newTopic was added, in order to fill the completer of leTopics //we have to pass the whole topic name, not just the root name, for testing purposes emit newTopic(topicName); } /*! *\brief called when a client receives a message, if the clients hostname isn't identic with the host name of MQTTClients * if the message arrived from a new topic, the topic is put in m_addedTopics */ void LiveDataDock::mqttMessageReceivedInBackground(const QByteArray& message, const QMqttTopicName& topic) { Q_UNUSED(message) - if(!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { + if (!m_addedTopics[m_mqttClients.first()->clientHostName()].contains(topic.name())) { m_addedTopics[m_mqttClients.first()->clientHostName()].push_back(topic.name()); } } /*! *\brief called when an MQTTClient is about to be deleted * removes every data connected to the MQTTClient, and disconnects the corresponding client from m_clients * * \param name the host name of the MQTTClient that will be deleted */ void LiveDataDock::removeClient(const QString& name) { m_clients[name]->disconnectFromHost(); m_addedTopics.remove(name); m_topicList.remove(name); - if(m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == name) { + if (m_previousMQTTClient != nullptr && m_previousMQTTClient->clientHostName() == name) { disconnect(m_clients[m_previousMQTTClient->clientHostName()], &QMqttClient::messageReceived, this, &LiveDataDock::mqttMessageReceivedInBackground); m_previousMQTTClient = nullptr; } - if(m_mqttClients.first()->clientHostName() == name) { + if (m_mqttClients.first()->clientHostName() == name) { ui.twSubscriptions->clear(); ui.twTopics->clear(); m_mqttClients.clear(); } delete m_clients[name]; m_clients.remove(name); } /*! * \brief Used for testing the MQTT related features * \param topic */ -bool LiveDataDock::testSubscribe(const QString& topic){ +bool LiveDataDock::testSubscribe(const QString& topic) { QStringList topicList = topic.split('/', QString::SkipEmptyParts); QTreeWidgetItem* currentItem = nullptr; - for(int i = 0; i topLevelItemCount(); ++i) { - if(ui.twTopics->topLevelItem(i)->text(0) == topicList[0]) { + for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if (ui.twTopics->topLevelItem(i)->text(0) == topicList[0]) { currentItem = ui.twTopics->topLevelItem(i); break; } } if (currentItem) { - for(int i = 1 ; i < topicList.size(); ++i) { - if(topicList[i] == '#') + for (int i = 1 ; i < topicList.size(); ++i) { + if (topicList[i] == '#') break; - for(int j = 0; j < currentItem->childCount(); ++j) { - if(currentItem->child(j)->text(0) == topicList[i]) { + for (int j = 0; j < currentItem->childCount(); ++j) { + if (currentItem->child(j)->text(0) == topicList[i]) { currentItem = currentItem->child(j); break; } else if (j == currentItem->childCount() - 1) return false; } } } else return false; ui.twTopics->setCurrentItem(currentItem); addSubscription(); return true; } /*! * \brief Used for testing the MQTT related features * \param topic */ bool LiveDataDock::testUnsubscribe(const QString& topic) { QTreeWidgetItem* currentItem = nullptr; - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), topic)) { + for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if (checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), topic)) { currentItem = ui.twSubscriptions->topLevelItem(i); break; } } - if(currentItem) { + if (currentItem) { do { - if(topic == currentItem->text(0)) { + if (topic == currentItem->text(0)) { ui.twSubscriptions->setCurrentItem(currentItem); removeSubscription(); return true; } else { - for(int i = 0; i < currentItem->childCount(); ++i) { + for (int i = 0; i < currentItem->childCount(); ++i) { qDebug()<child(i)->text(0)<<" "<child(i)->text(0), topic)) { + if (checkTopicContains(currentItem->child(i)->text(0), topic)) { currentItem = currentItem->child(i); break; } else if (i == currentItem->childCount() - 1) return false; } } - } while(currentItem); + } while (currentItem); } else return false; return false; } void LiveDataDock::showWillSettings() { QMenu menu; const MQTTClient* const fmc = m_mqttClients.at(0); QVector topics = fmc->topicNames(); MQTTWillSettingsWidget willSettings(&menu, fmc->willSettings(), topics); connect(&willSettings, &MQTTWillSettingsWidget::useChanged, this, &LiveDataDock::useWillMessage); connect(&willSettings, &MQTTWillSettingsWidget::messageTypeChanged, this, &LiveDataDock::willMessageTypeChanged); connect(&willSettings, &MQTTWillSettingsWidget::updateTypeChanged, this, &LiveDataDock::willUpdateTypeChanged); connect(&willSettings, &MQTTWillSettingsWidget::retainChanged, this, &LiveDataDock::willRetainChanged); connect(&willSettings, &MQTTWillSettingsWidget::intervalChanged, this, &LiveDataDock::willUpdateIntervalChanged); connect(&willSettings, &MQTTWillSettingsWidget::ownMessageChanged, this, &LiveDataDock::willOwnMessageChanged); connect(&willSettings, &MQTTWillSettingsWidget::topicChanged, this, &LiveDataDock::willTopicChanged); connect(&willSettings, &MQTTWillSettingsWidget::statisticsChanged, this, &LiveDataDock::statisticsChanged); connect(&willSettings, &MQTTWillSettingsWidget::QoSChanged, this, &LiveDataDock::willQoSChanged); connect(&willSettings, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&willSettings); menu.addAction(widgetAction); QPoint pos(ui.bWillSettings->sizeHint().width(),ui.bWillSettings->sizeHint().height()); menu.exec(ui.bWillSettings->mapToGlobal(pos)); } #endif