File indexing completed on 2024-05-12 15:27:52

0001 /***************************************************************************
0002 File                 : MQTTSubscriptionWidget.cpp
0003 Project              : LabPlot
0004 Description          : Widget for managing topics and subscribing
0005 --------------------------------------------------------------------
0006 Copyright            : (C) 2019 by Kovacs Ferencz (kferike98@gmail.com)
0007 ***************************************************************************/
0008 
0009 /***************************************************************************
0010 *                                                                         *
0011 *  This program is free software; you can redistribute it and/or modify   *
0012 *  it under the terms of the GNU General Public License as published by   *
0013 *  the Free Software Foundation; either version 2 of the License, or      *
0014 *  (at your option) any later version.                                    *
0015 *                                                                         *
0016 *  This program is distributed in the hope that it will be useful,        *
0017 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0018 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0019 *  GNU General Public License for more details.                           *
0020 *                                                                         *
0021 *   You should have received a copy of the GNU General Public License     *
0022 *   along with this program; if not, write to the Free Software           *
0023 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0024 *   Boston, MA  02110-1301  USA                                           *
0025 *                                                                         *
0026 ***************************************************************************/
0027 
0028 #include "MQTTSubscriptionWidget.h"
0029 
0030 #include "backend/datasources/MQTTClient.h"
0031 #include "ImportFileWidget.h"
0032 #include "kdefrontend/dockwidgets/LiveDataDock.h"
0033 
0034 #include <QCompleter>
0035 #include <QMessageBox>
0036 #include <QMqttSubscription>
0037 #include <QTreeWidget>
0038 #include <QTreeWidgetItem>
0039 
0040 #include <KLocalizedString>
0041 
0042 /*!
0043    \class MQTTSubscriptionWidget
0044    \brief Widget for managing topics and subscribing.
0045 
0046    \ingroup kdefrontend
0047 */
0048 MQTTSubscriptionWidget::MQTTSubscriptionWidget(QWidget* parent) : QWidget(parent),
0049     m_searchTimer(new QTimer(this)) {
0050 
0051     ui.setupUi(this);
0052 
0053     m_searchTimer->setInterval(10000);
0054     const int size = ui.leTopics->height();
0055     ui.lTopicSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) );
0056     ui.lSubscriptionSearch->setPixmap( QIcon::fromTheme(QLatin1String("go-next")).pixmap(size, size) );
0057     ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight));
0058     ui.bSubscribe->setToolTip(i18n("Subscribe selected topics"));
0059     ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_ArrowLeft));
0060     ui.bUnsubscribe->setToolTip(i18n("Unsubscribe selected topics"));
0061 
0062     //subscribe/unsubscribe buttons only enabled if something was selected
0063     ui.bSubscribe->setEnabled(false);
0064     ui.bUnsubscribe->setEnabled(false);
0065 
0066     QString info = i18n("Enter the name of the topic to navigate to it.");
0067     QString placeholder = i18n("Enter the name of the topic");
0068     ui.lTopicSearch->setToolTip(info);
0069     ui.leTopics->setToolTip(info);
0070     ui.leTopics->setPlaceholderText(placeholder);
0071     ui.lSubscriptionSearch->setToolTip(info);
0072     ui.leSubscriptions->setToolTip(info);
0073     ui.leSubscriptions->setPlaceholderText(placeholder);
0074 
0075     info = i18n("Set the Quality of Service (QoS) for the subscription to define the guarantee of the message delivery:"
0076                 "<ul>"
0077                 "<li>0 - deliver at most once</li>"
0078                 "<li>1 - deliver at least once</li>"
0079                 "<li>2 - deliver exactly once</li>"
0080                 "</ul>");
0081     ui.cbQos->setToolTip(info);
0082 
0083     auto* importWidget = dynamic_cast<ImportFileWidget*>(parent);
0084     if (importWidget) {
0085         m_parent = MQTTParentWidget::ImportFileWidget;
0086         connect(importWidget, &ImportFileWidget::newTopic, this, &MQTTSubscriptionWidget::setTopicCompleter);
0087         connect(importWidget, &ImportFileWidget::updateSubscriptionTree, this, &MQTTSubscriptionWidget::updateSubscriptionTree);
0088         connect(importWidget, &ImportFileWidget::MQTTClearTopics, this, &MQTTSubscriptionWidget::clearWidgets);
0089     } else {
0090         auto* liveDock = static_cast<LiveDataDock*>(parent);
0091         m_parent = MQTTParentWidget::ImportFileWidget;
0092         connect(liveDock, &LiveDataDock::MQTTClearTopics, this, &MQTTSubscriptionWidget::clearWidgets);
0093         connect(liveDock, &LiveDataDock::newTopic, this, &MQTTSubscriptionWidget::setTopicCompleter);
0094         connect(liveDock, &LiveDataDock::updateSubscriptionTree, this, &MQTTSubscriptionWidget::updateSubscriptionTree);
0095     }
0096 
0097     connect(ui.bSubscribe,  &QPushButton::clicked, this, &MQTTSubscriptionWidget::mqttSubscribe);
0098     connect(ui.bUnsubscribe, &QPushButton::clicked, this,&MQTTSubscriptionWidget::mqttUnsubscribe);
0099 
0100     connect(m_searchTimer, &QTimer::timeout, this, &MQTTSubscriptionWidget::topicTimeout);
0101     connect(ui.leTopics, &QLineEdit::textChanged, this, &MQTTSubscriptionWidget::scrollToTopicTreeItem);
0102     connect(ui.leSubscriptions, &QLineEdit::textChanged, this, &MQTTSubscriptionWidget::scrollToSubsriptionTreeItem);
0103     connect(ui.twTopics, &QTreeWidget::itemDoubleClicked, this, &MQTTSubscriptionWidget::mqttAvailableTopicDoubleClicked);
0104     connect(ui.twSubscriptions, &QTreeWidget::itemDoubleClicked, this, &MQTTSubscriptionWidget::mqttSubscribedTopicDoubleClicked);
0105     connect(ui.twSubscriptions, &QTreeWidget::currentItemChanged, this, &MQTTSubscriptionWidget::subscriptionChanged);
0106 
0107     connect(ui.twTopics, &QTreeWidget::itemSelectionChanged, this, [=]() {
0108         ui.bSubscribe->setEnabled(!ui.twTopics->selectedItems().isEmpty());
0109     });
0110 
0111     connect(ui.twSubscriptions, &QTreeWidget::itemSelectionChanged, this, [=]() {
0112         ui.bUnsubscribe->setEnabled(!ui.twSubscriptions->selectedItems().isEmpty());
0113     });
0114 }
0115 
0116 MQTTSubscriptionWidget::~MQTTSubscriptionWidget() {
0117     m_searchTimer->stop();
0118     delete m_searchTimer;
0119 }
0120 
0121 void MQTTSubscriptionWidget::setTopicList(const QStringList& topicList) {
0122     m_topicList = topicList;
0123 }
0124 
0125 QStringList MQTTSubscriptionWidget::getTopicList() {
0126     return m_topicList;
0127 }
0128 
0129 int MQTTSubscriptionWidget::subscriptionCount() {
0130     return ui.twSubscriptions->topLevelItemCount();
0131 }
0132 
0133 QTreeWidgetItem* MQTTSubscriptionWidget::topLevelTopic(int index) {
0134     return ui.twTopics->topLevelItem(index);
0135 }
0136 
0137 QTreeWidgetItem* MQTTSubscriptionWidget::topLevelSubscription(int index) {
0138     return ui.twSubscriptions->topLevelItem(index);
0139 }
0140 
0141 void MQTTSubscriptionWidget::addTopic(QTreeWidgetItem* item) {
0142     ui.twTopics->addTopLevelItem(item);
0143 }
0144 
0145 int MQTTSubscriptionWidget::topicCount() {
0146     return ui.twTopics->topLevelItemCount();
0147 }
0148 
0149 void MQTTSubscriptionWidget::setTopicTreeText(const QString &text) {
0150     ui.twTopics->headerItem()->setText(0, text);
0151 }
0152 
0153 QTreeWidgetItem* MQTTSubscriptionWidget::currentItem() const {
0154     return ui.twSubscriptions->currentItem();
0155 }
0156 
0157 void MQTTSubscriptionWidget::makeVisible(bool visible) {
0158     ui.cbQos->setVisible(visible);
0159     ui.twTopics->setVisible(visible);
0160     ui.twSubscriptions->setVisible(visible);
0161     ui.leTopics->setVisible(visible);
0162     ui.leSubscriptions->setVisible(visible);
0163     ui.bSubscribe->setVisible(visible);
0164     ui.bUnsubscribe->setVisible(visible);
0165     ui.lTopicSearch->setVisible(visible);
0166     ui.lSubscriptionSearch->setVisible(visible);
0167 }
0168 
0169 void MQTTSubscriptionWidget::testSubscribe(QTreeWidgetItem *item) {
0170     ui.twTopics->setCurrentItem(item);
0171     mqttSubscribe();
0172 }
0173 
0174 void MQTTSubscriptionWidget::testUnsubscribe(QTreeWidgetItem *item) {
0175     ui.twTopics->setCurrentItem(item);
0176     mqttUnsubscribe();
0177 }
0178 
0179 /*!
0180  *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics)
0181  *
0182  * \param children vector of TreeWidgetItem pointers
0183  * \param root pointer to a TreeWidgetItem of twSubscriptions
0184  */
0185 void MQTTSubscriptionWidget::findSubscriptionLeafChildren(QVector<QTreeWidgetItem *>& children, QTreeWidgetItem* root) {
0186     if (root->childCount() == 0)
0187         children.push_back(root);
0188     else
0189         for (int i = 0; i < root->childCount(); ++i)
0190             findSubscriptionLeafChildren(children, root->child(i));
0191 }
0192 
0193 /*!
0194  *\brief Checks if a topic contains another one
0195  *
0196  * \param superior the name of a topic
0197  * \param inferior the name of a topic
0198  * \return  true if superior is equal to or contains(if superior contains wildcards) inferior,
0199  *          false otherwise
0200  */
0201 bool MQTTSubscriptionWidget::checkTopicContains(const QString& superior, const QString& inferior) {
0202     if (superior == inferior)
0203         return true;
0204 
0205     if (!superior.contains('/'))
0206         return false;
0207 
0208     const QStringList& superiorList = superior.split('/', QString::SkipEmptyParts);
0209     const QStringList& inferiorList = inferior.split('/', QString::SkipEmptyParts);
0210 
0211     //a longer topic can't contain a shorter one
0212     if (superiorList.size() > inferiorList.size())
0213         return false;
0214 
0215     bool ok = true;
0216     for (int i = 0; i < superiorList.size(); ++i) {
0217         if (superiorList.at(i) != inferiorList.at(i)) {
0218             if ((superiorList.at(i) != "+") &&
0219                     !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) {
0220                 //if the two topics differ, and the superior's current level isn't + or #(which can be only in the last position)
0221                 //then superior can't contain inferior
0222                 ok = false;
0223                 break;
0224             } else if (i == superiorList.size() - 1 && (superiorList.at(i) == "+" && inferiorList.at(i) == "#") ) {
0225                 //if the two topics differ at the last level
0226                 //and the superior's current level is + while the inferior's is #(which can be only in the last position)
0227                 //then superior can't contain inferior
0228                 ok = false;
0229                 break;
0230             }
0231         }
0232     }
0233     return ok;
0234 }
0235 
0236 /*!
0237  *\brief Starts unsubscribing from the given topic, and signals to ImportFileWidget for further actions
0238  *
0239  * \param topicName the name of a topic we want to unsubscribe from
0240  */
0241 void MQTTSubscriptionWidget::unsubscribeFromTopic(const QString& topicName) {
0242     if (topicName.isEmpty())
0243         return;
0244 
0245     QVector<QTreeWidgetItem*> children;
0246     findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(0));
0247 
0248     //signals for ImportFileWidget
0249     emit MQTTUnsubscribeFromTopic(topicName, children);
0250 
0251     for (int row = 0; row < ui.twSubscriptions->topLevelItemCount(); row++)  {
0252         if (ui.twSubscriptions->topLevelItem(row)->text(0) == topicName) {
0253             ui.twSubscriptions->topLevelItem(row)->takeChildren();
0254             ui.twSubscriptions->takeTopLevelItem(row);
0255         }
0256     }
0257 }
0258 
0259 /*!
0260  *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them.
0261  *       We do this until there are no topics to merge
0262  */
0263 void MQTTSubscriptionWidget::manageCommonLevelSubscriptions() {
0264     bool foundEqual = false;
0265 
0266     do {
0267         foundEqual = false;
0268         QMap<QString, QVector<QString>> equalTopicsMap;
0269         QVector<QString> equalTopics;
0270 
0271         //compare the subscriptions present in the TreeWidget
0272         for (int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) {
0273             for (int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) {
0274                 QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0));
0275 
0276                 //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key)
0277                 if (!commonTopic.isEmpty()) {
0278                     if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0)))
0279                         equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0));
0280 
0281                     if (!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0)))
0282                         equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0));
0283                 }
0284             }
0285         }
0286 
0287         if (!equalTopicsMap.isEmpty()) {
0288             DEBUG("Manage common topics");
0289 
0290             QVector<QString> commonTopics;
0291             QMapIterator<QString, QVector<QString>> topics(equalTopicsMap);
0292 
0293             //check for every map entry, if the found topics can be merged or not
0294             while (topics.hasNext()) {
0295                 topics.next();
0296 
0297                 int level = commonLevelIndex(topics.value().last(), topics.value().first());
0298                 QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts);
0299                 QTreeWidgetItem* currentItem = nullptr;
0300 
0301                 //search the corresponding item to the common topics first level(root)
0302                 for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) {
0303                     if (ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) {
0304                         currentItem = ui.twTopics->topLevelItem(i);
0305                         break;
0306                     }
0307                 }
0308 
0309                 if (!currentItem)
0310                     break;
0311 
0312                 //calculate the number of topics the new + wildcard could replace
0313                 int childCount = checkCommonChildCount(1, level, commonList, currentItem);
0314                 if (childCount > 0) {
0315                     //if the number of topics found and the calculated number of topics is equal, the topics can be merged
0316                     if (topics.value().size() == childCount) {
0317                         QDEBUG("Found common topic to manage: " << topics.key());
0318                         foundEqual = true;
0319                         commonTopics.push_back(topics.key());
0320                     }
0321                 }
0322             }
0323 
0324             if (foundEqual) {
0325                 //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new '+' wildcard
0326                 int lowestLevel = INT_MAX;
0327                 int topicIdx = -1;
0328                 for (int i = 0; i < commonTopics.size(); ++i) {
0329                     int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]);
0330                     if (level < lowestLevel) {
0331                         topicIdx = i;
0332                         lowestLevel = level;
0333                     }
0334                 }
0335                 QDEBUG("Manage: " << commonTopics[topicIdx]);
0336                 if (topicIdx != -1)
0337                     equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]);
0338 
0339                 //Add the common topic ("merging")
0340                 QString commonTopic;
0341                 commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last());
0342                 QStringList nameList;
0343                 nameList.append(commonTopic);
0344                 auto* newTopic = new QTreeWidgetItem(nameList);
0345                 ui.twSubscriptions->addTopLevelItem(newTopic);
0346 
0347                 if (m_parent == MQTTParentWidget::ImportFileWidget)
0348                     emit makeSubscription(commonTopic, static_cast<quint8> (ui.cbQos->currentText().toUInt()));
0349 
0350                 //remove the "merged" topics
0351                 for (const auto& topic : equalTopics) {
0352                     for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) {
0353                         if (ui.twSubscriptions->topLevelItem(j)->text(0) == topic) {
0354                             newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j));
0355 
0356                             if (m_parent == MQTTParentWidget::ImportFileWidget)
0357                                 unsubscribeFromTopic(topic);
0358 
0359                             break;
0360                         }
0361                     }
0362                 }
0363 
0364                 //remove any subscription that the new subscription contains
0365                 for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) {
0366                     if (checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) &&
0367                             commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) {
0368                         if (m_parent == MQTTParentWidget::ImportFileWidget)
0369                             unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0));
0370                         else {
0371                             ui.twSubscriptions->topLevelItem(i)->takeChildren();
0372                             ui.twSubscriptions->takeTopLevelItem(i);
0373                         }
0374                         i--;
0375                     }
0376                 }
0377 
0378                 if (m_parent == MQTTParentWidget::LiveDataDock)
0379                     emit makeSubscription(commonTopic, static_cast<quint8> (ui.cbQos->currentText().toUInt()));
0380             }
0381         }
0382     } while (foundEqual);
0383 }
0384 
0385 /*!
0386  *\brief Fills twSubscriptions with the subscriptions made by the client
0387  */
0388 void MQTTSubscriptionWidget::updateSubscriptionTree(const QVector<QString>& mqttSubscriptions) {
0389     DEBUG("ImportFileWidget::updateSubscriptionTree()");
0390     ui.twSubscriptions->clear();
0391 
0392     for (const auto& sub : mqttSubscriptions) {
0393         QStringList name;
0394         name.append(sub);
0395 
0396         bool found = false;
0397         for (int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j) {
0398             if (ui.twSubscriptions->topLevelItem(j)->text(0) == sub) {
0399                 found = true;
0400                 break;
0401             }
0402         }
0403 
0404         if (!found) {
0405             //Add the subscription to the tree widget
0406             auto* newItem = new QTreeWidgetItem(name);
0407             ui.twSubscriptions->addTopLevelItem(newItem);
0408             name.clear();
0409             name = sub.split('/', QString::SkipEmptyParts);
0410 
0411             //find the corresponding "root" item in twTopics
0412             QTreeWidgetItem* topic = nullptr;
0413             for (int j = 0; j < ui.twTopics->topLevelItemCount(); ++j) {
0414                 if (ui.twTopics->topLevelItem(j)->text(0) == name[0]) {
0415                     topic = ui.twTopics->topLevelItem(j);
0416                     break;
0417                 }
0418             }
0419 
0420             //restore the children of the subscription
0421             if (topic != nullptr && topic->childCount() > 0)
0422                 restoreSubscriptionChildren(topic, newItem, name, 1);
0423         }
0424     }
0425     m_searching = false;
0426 }
0427 
0428 /*!
0429  *\brief Adds to a # wildcard containing topic, every topic present in twTopics that the former topic contains
0430  *
0431  * \param topic pointer to the TreeWidgetItem which was selected before subscribing
0432  * \param subscription pointer to the TreeWidgetItem which represents the new subscirption,
0433  *        we add all of the children to this item
0434  */
0435 void MQTTSubscriptionWidget::addSubscriptionChildren(QTreeWidgetItem* topic, QTreeWidgetItem* subscription) {
0436     //if the topic doesn't have any children we don't do anything
0437     if (topic->childCount() <= 0)
0438         return;
0439 
0440     for (int i = 0; i < topic->childCount(); ++i) {
0441         QTreeWidgetItem* temp = topic->child(i);
0442         QString name;
0443         //if it has children, then we add it as a # wildcrad containing topic
0444         if (topic->child(i)->childCount() > 0) {
0445             name.append(temp->text(0) + "/#");
0446             while (temp->parent() != nullptr) {
0447                 temp = temp->parent();
0448                 name.prepend(temp->text(0) + '/');
0449             }
0450         }
0451 
0452         //if not then we simply add the topic itself
0453         else {
0454             name.append(temp->text(0));
0455             while (temp->parent() != nullptr) {
0456                 temp = temp->parent();
0457                 name.prepend(temp->text(0) + '/');
0458             }
0459         }
0460 
0461         QStringList nameList;
0462         nameList.append(name);
0463         auto* childItem = new QTreeWidgetItem(nameList);
0464         subscription->addChild(childItem);
0465         //we use the function recursively on the given item
0466         addSubscriptionChildren(topic->child(i), childItem);
0467     }
0468 }
0469 
0470 /*!
0471  *\brief Restores the children of a top level item in twSubscriptions if it contains wildcards
0472  *
0473  * \param topic pointer to a top level item in twTopics which represents the root of the subscription topic
0474  * \param subscription pointer to a top level item in twSubscriptions, this is the item whose children will be restored
0475  * \param list QStringList containing the levels of the subscription topic
0476  * \param level the level's number which is being investigated
0477  */
0478 void MQTTSubscriptionWidget::restoreSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription, const QStringList& list, int level) {
0479     if (list[level] != "+" && list[level] != "#" && level < list.size() - 1) {
0480         for (int i = 0; i < topic->childCount(); ++i) {
0481             //if the current level isn't + or # wildcard we recursively continue with the next level
0482             if (topic->child(i)->text(0) == list[level]) {
0483                 restoreSubscriptionChildren(topic->child(i), subscription, list, level + 1);
0484                 break;
0485             }
0486         }
0487     } else if (list[level] == "+") {
0488         for (int i = 0; i < topic->childCount(); ++i) {
0489             //determine the name of the topic, contained by the subscription
0490             QString name;
0491             name.append(topic->child(i)->text(0));
0492             for (int j = level + 1; j < list.size(); ++j)
0493                 name.append('/' + list[j]);
0494 
0495             QTreeWidgetItem* temp = topic->child(i);
0496             while (temp->parent() != nullptr) {
0497                 temp = temp->parent();
0498                 name.prepend(temp->text(0) + '/');
0499             }
0500 
0501             //Add the topic as child of the subscription
0502             QStringList nameList;
0503             nameList.append(name);
0504             auto* newItem = new QTreeWidgetItem(nameList);
0505             subscription->addChild(newItem);
0506             //Continue adding children recursively to the new item
0507             restoreSubscriptionChildren(topic->child(i), newItem, list, level + 1);
0508         }
0509     } else if (list[level] == "#") {
0510         //add the children of the # wildcard containing subscription
0511         addSubscriptionChildren(topic, subscription);
0512     }
0513 }
0514 
0515 /*!
0516  *\brief Returns the amount of topics that the '+' wildcard will replace in the level position
0517  *
0518  * \param levelIdx the level currently being investigated
0519  * \param level the level where the new + wildcard will be placed
0520  * \param commonList the topic name split into levels
0521  * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level
0522  *        represented by levelIdx
0523  * \return returns the childCount, or -1 if some topics already represented by + wildcard have different
0524  *         amount of children
0525  */
0526 int MQTTSubscriptionWidget::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) {
0527     //we recursively check the number of children, until we get to level-1
0528     if (levelIdx < level - 1) {
0529         if (commonList[levelIdx] != "+") {
0530             for (int j = 0; j < currentItem->childCount(); ++j) {
0531                 if (currentItem->child(j)->text(0) == commonList[levelIdx]) {
0532                     //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively
0533                     return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j));
0534                 }
0535             }
0536         } else {
0537             int childCount = -1;
0538             bool ok = true;
0539 
0540             //otherwise we check if every + wildcard represented topic has the same number of children, recursively
0541             for (int j = 0; j < currentItem->childCount(); ++j) {
0542                 int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j));
0543                 if ((j > 0) && (temp != childCount)) {
0544                     ok = false;
0545                     break;
0546                 }
0547                 childCount = temp;
0548             }
0549 
0550             //if yes we return this number, otherwise -1
0551             if (ok)
0552                 return childCount;
0553             else
0554                 return -1;
0555         }
0556     } else if (levelIdx == level - 1) {
0557         if (commonList[levelIdx] != "+") {
0558             for (int j = 0; j < currentItem->childCount(); ++j) {
0559                 if (currentItem->child(j)->text(0) == commonList[levelIdx]) {
0560                     //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item
0561                     return currentItem->child(j)->childCount();
0562                 }
0563             }
0564         } else {
0565             int childCount = -1;
0566             bool ok = true;
0567 
0568             //otherwise we check if every + wildcard represented topic has the same number of children
0569             for (int j = 0; j < currentItem->childCount(); ++j) {
0570                 if ((j > 0) && (currentItem->child(j)->childCount() != childCount)) {
0571                     ok = false;
0572                     break;
0573                 }
0574                 childCount = currentItem->child(j)->childCount();
0575             }
0576 
0577             //if yes we return this number, otherwise -1
0578             if (ok)
0579                 return childCount;
0580             else
0581                 return -1;
0582         }
0583 
0584     } else if (level == 1 && levelIdx == 1)
0585         return currentItem->childCount();
0586 
0587     return -1;
0588 }
0589 
0590 
0591 /*!
0592  *\brief Returns the index of level where the two topic names differ, if there is a common topic for them
0593  *
0594  * \param first the name of a topic
0595  * \param second the name of a topic
0596  * \return The index of the unequal level, if there is a common topic, otherwise -1
0597  */
0598 int MQTTSubscriptionWidget::commonLevelIndex(const QString& first, const QString& second) {
0599     QStringList firstList = first.split('/', QString::SkipEmptyParts);
0600     QStringList secondtList = second.split('/', QString::SkipEmptyParts);
0601     QString commonTopic;
0602     int differIndex = -1;
0603 
0604     if (!firstList.isEmpty()) {
0605         //the two topics have to be the same size and can't be identic
0606         if (firstList.size() == secondtList.size() && (first != second))    {
0607 
0608             //the index where they differ
0609             for (int i = 0; i < firstList.size(); ++i) {
0610                 if (firstList.at(i) != secondtList.at(i)) {
0611                     differIndex = i;
0612                     break;
0613                 }
0614             }
0615 
0616             //they can differ at only one level
0617             bool differ = false;
0618             if (differIndex > 0) {
0619                 for (int j = differIndex + 1; j < firstList.size(); ++j) {
0620                     if (firstList.at(j) != secondtList.at(j)) {
0621                         differ = true;
0622                         break;
0623                     }
0624                 }
0625             } else
0626                 differ = true;
0627 
0628             if (!differ) {
0629                 for (int i = 0; i < firstList.size(); ++i) {
0630                     if (i != differIndex)
0631                         commonTopic.append(firstList.at(i));
0632                     else
0633                         commonTopic.append('+');
0634 
0635                     if (i != firstList.size() - 1)
0636                         commonTopic.append('/');
0637                 }
0638             }
0639         }
0640     }
0641 
0642     //if there is a common topic we return the differIndex
0643     if (!commonTopic.isEmpty())
0644         return differIndex;
0645     else
0646         return -1;
0647 }
0648 
0649 /*!
0650  *\brief Returns the '+' wildcard containing topic name, which includes the given topic names
0651  *
0652  * \param first the name of a topic
0653  * \param second the name of a topic
0654  * \return The name of the common topic, if it exists, otherwise ""
0655  */
0656 QString MQTTSubscriptionWidget::checkCommonLevel(const QString& first, const QString& second) {
0657     const QStringList& firstList = first.split('/', QString::SkipEmptyParts);
0658     if (firstList.isEmpty())
0659         return QString();
0660 
0661     const QStringList& secondtList = second.split('/', QString::SkipEmptyParts);
0662     QString commonTopic;
0663 
0664     //the two topics have to be the same size and can't be identic
0665     if (firstList.size() == secondtList.size() && (first != second))    {
0666 
0667         //the index where they differ
0668         int differIndex = -1;
0669         for (int i = 0; i < firstList.size(); ++i) {
0670             if (firstList.at(i) != secondtList.at(i)) {
0671                 differIndex = i;
0672                 break;
0673             }
0674         }
0675 
0676         //they can differ at only one level
0677         bool differ = false;
0678         if (differIndex > 0) {
0679             for (int j = differIndex + 1; j < firstList.size(); ++j) {
0680                 if (firstList.at(j) != secondtList.at(j)) {
0681                     differ = true;
0682                     break;
0683                 }
0684             }
0685         } else
0686             differ = true;
0687 
0688         if (!differ) {
0689             for (int i = 0; i < firstList.size(); ++i) {
0690                 if (i != differIndex)
0691                     commonTopic.append(firstList.at(i));
0692                 else {
0693                     //we put '+' wildcard at the level where they differ
0694                     commonTopic.append('+');
0695                 }
0696 
0697                 if (i != firstList.size() - 1)
0698                     commonTopic.append('/');
0699             }
0700         }
0701     }
0702 
0703 //  qDebug() << "Common topic for " << first << " and " << second << " is: " << commonTopic;
0704     return commonTopic;
0705 }
0706 
0707 /************** SLOTS **************************************************************/
0708 
0709 /*!
0710  *\brief When a leaf topic is double clicked in the topics tree widget we subscribe on that
0711  */
0712 void MQTTSubscriptionWidget::mqttAvailableTopicDoubleClicked(QTreeWidgetItem* item, int column) {
0713     Q_UNUSED(column)
0714     // Only for leaf topics
0715     if (item->childCount() == 0)
0716         mqttSubscribe();
0717 }
0718 
0719 /*!
0720  *\brief When a leaf subscription is double clicked in the topics tree widget we unsubscribe
0721  */
0722 void MQTTSubscriptionWidget::mqttSubscribedTopicDoubleClicked(QTreeWidgetItem* item, int column) {
0723     Q_UNUSED(column)
0724     // Only for leaf subscriptions
0725     if (item->childCount() == 0)
0726         mqttUnsubscribe();
0727 }
0728 
0729 /*!
0730  *\brief called when the subscribe button is pressed
0731  * subscribes to the topic represented by the current item of twTopics
0732  */
0733 void MQTTSubscriptionWidget::mqttSubscribe() {
0734     QTreeWidgetItem* item = ui.twTopics->currentItem();
0735     if (!item)
0736         return; //should never happen
0737 
0738     //determine the topic name that the current item represents
0739     QTreeWidgetItem* tempItem = item;
0740     QString name = item->text(0);
0741     if (item->childCount() != 0)
0742         name.append("/#");
0743 
0744     while (tempItem->parent()) {
0745         tempItem = tempItem->parent();
0746         name.prepend(tempItem->text(0) + '/');
0747     }
0748 
0749     //check if the subscription already exists
0750     const QList<QTreeWidgetItem*>& topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly);
0751     if (topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) {
0752         QDEBUG("Subscribe to: " << name);
0753         bool foundSuperior = false;
0754 
0755         for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) {
0756             //if the new subscirptions contains an already existing one, we remove the inferior one
0757             if (checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0))
0758                     && name != ui.twSubscriptions->topLevelItem(i)->text(0)) {
0759                 if (m_parent == MQTTParentWidget::ImportFileWidget)
0760                     unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0));
0761                 else {
0762                     ui.twSubscriptions->topLevelItem(i)->takeChildren();
0763                     ui.twSubscriptions->takeTopLevelItem(i);
0764                 }
0765                 --i;
0766                 continue;
0767             }
0768 
0769             //if there is a subscription containing the new one we set foundSuperior true
0770             if (checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name)
0771                     && name != ui.twSubscriptions->topLevelItem(i)->text(0)) {
0772                 foundSuperior = true;
0773                 QDEBUG("Can't continue subscribing. Found superior for " << name << " : " << ui.twSubscriptions->topLevelItem(i)->text(0));
0774                 break;
0775             }
0776         }
0777 
0778         //if there wasn't a superior subscription we can subscribe to the new topic
0779         if (!foundSuperior) {
0780             QStringList toplevelName;
0781             toplevelName.push_back(name);
0782             auto* newTopLevelItem = new QTreeWidgetItem(toplevelName);
0783             ui.twSubscriptions->addTopLevelItem(newTopLevelItem);
0784 
0785             if (name.endsWith('#')) {
0786                 //adding every topic that the subscription contains to twSubscriptions
0787                 addSubscriptionChildren(item, newTopLevelItem);
0788             }
0789 
0790             emit makeSubscription(name, static_cast<quint8>(ui.cbQos->currentText().toUInt()));
0791 
0792             if (name.endsWith('#')) {
0793                 //if an already existing subscription contains a topic that the new subscription also contains
0794                 //we decompose the already existing subscription
0795                 //by unsubscribing from its topics, that are present in the new subscription as well
0796                 const QStringList nameList = name.split('/', QString::SkipEmptyParts);
0797                 const QString& root = nameList.first();
0798                 QVector<QTreeWidgetItem*> children;
0799                 for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) {
0800                     if (ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root)
0801                             && name != ui.twSubscriptions->topLevelItem(i)->text(0)) {
0802                         children.clear();
0803                         //get the "leaf" children of the inspected subscription
0804                         findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i));
0805                         for (const auto& child : children) {
0806                             if (checkTopicContains(name, child->text(0))) {
0807                                 //if the new subscription contains a topic, we unsubscribe from it
0808                                 if (m_parent == MQTTParentWidget::ImportFileWidget) {
0809                                     ui.twSubscriptions->setCurrentItem(child);
0810                                     mqttUnsubscribe();
0811                                     --i;
0812                                 } else {
0813                                     QTreeWidgetItem* unsubscribeItem = child;
0814                                     while (unsubscribeItem->parent() != nullptr) {
0815                                         for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) {
0816                                             const QString& childText = unsubscribeItem->parent()->child(i)->text(0);
0817                                             if (unsubscribeItem->text(0) != childText) {
0818                                                 //add topic as subscription
0819                                                 quint8 qos = static_cast<quint8>(ui.cbQos->currentText().toUInt());
0820                                                 emit addBeforeRemoveSubscription(childText, qos);
0821                                                 //also add it to twSubscriptions
0822                                                 ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i));
0823                                                 --i;
0824                                             } else {
0825                                                 //before we remove the topic, we reparent it to the new subscription
0826                                                 //so no data is lost
0827                                                 emit reparentTopic(unsubscribeItem->text(0), name);
0828                                             }
0829                                         }
0830                                         unsubscribeItem = unsubscribeItem->parent();
0831                                     }
0832 
0833                                     qDebug() << "Remove: " << unsubscribeItem->text(0);
0834                                     emit removeMQTTSubscription(unsubscribeItem->text(0));
0835 
0836                                     ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem));
0837                                 }
0838                             }
0839                         }
0840                     }
0841                 }
0842             }
0843 
0844 
0845             //implementalj es ird at liveDataDock addsubscription!!!!!
0846             manageCommonLevelSubscriptions();
0847             updateSubscriptionCompleter();
0848 
0849             emit enableWill(true);
0850         } else
0851             QMessageBox::warning(this, i18n("Warning"), i18n("You already subscribed to a topic containing this one"));
0852     } else
0853         QMessageBox::warning(this, i18n("Warning"), i18n("You already subscribed to this topic"));
0854 }
0855 
0856 /*!
0857  *\brief Updates the completer for leSubscriptions
0858  */
0859 void MQTTSubscriptionWidget::updateSubscriptionCompleter() {
0860     QStringList subscriptionList;
0861     for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i)
0862         subscriptionList.append(ui.twSubscriptions->topLevelItem(i)->text(0));
0863 
0864     if (!subscriptionList.isEmpty()) {
0865         m_subscriptionCompleter = new QCompleter(subscriptionList, this);
0866         m_subscriptionCompleter->setCompletionMode(QCompleter::PopupCompletion);
0867         m_subscriptionCompleter->setCaseSensitivity(Qt::CaseSensitive);
0868         ui.leSubscriptions->setCompleter(m_subscriptionCompleter);
0869     } else
0870         ui.leSubscriptions->setCompleter(nullptr);
0871 }
0872 
0873 
0874 /*!
0875  *\brief called when the unsubscribe button is pressed
0876  * unsubscribes from the topic represented by the current item of twSubscription
0877  */
0878 void MQTTSubscriptionWidget::mqttUnsubscribe() {
0879     QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem();
0880     if (!unsubscribeItem)
0881         return; //should never happen
0882 
0883     QDEBUG("Unsubscribe from: " << unsubscribeItem->text(0));
0884     //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription)
0885     //we can simply unsubscribe from it
0886     if (unsubscribeItem->parent() == nullptr) {
0887         if (m_parent == MQTTParentWidget::ImportFileWidget)
0888             unsubscribeFromTopic(unsubscribeItem->text(0));
0889         else {
0890             emit removeMQTTSubscription(unsubscribeItem->text(0));
0891             ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem));
0892         }
0893     }
0894 
0895     //otherwise we remove the selected item, but subscribe to every other topic, that was contained by
0896     //the selected item's parent subscription(top level item of twSubscriptions)
0897     else {
0898         while (unsubscribeItem->parent() != nullptr) {
0899             for (int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) {
0900                 const QString& childText = unsubscribeItem->parent()->child(i)->text(0);
0901                 if (unsubscribeItem->text(0) != childText) {
0902                     quint8 qos = static_cast<quint8>(ui.cbQos->currentText().toUInt());
0903                     if (m_parent == MQTTParentWidget::ImportFileWidget)
0904                         emit makeSubscription(childText, qos);
0905                     else
0906                         emit addBeforeRemoveSubscription(childText, qos);
0907 
0908                     ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i));
0909                     --i;
0910                 }
0911             }
0912             unsubscribeItem = unsubscribeItem->parent();
0913         }
0914 
0915         if (m_parent == MQTTParentWidget::ImportFileWidget)
0916             unsubscribeFromTopic(unsubscribeItem->text(0));
0917         else {
0918             emit removeMQTTSubscription(unsubscribeItem->text(0));
0919             ui.twSubscriptions->takeTopLevelItem(ui.twSubscriptions->indexOfTopLevelItem(unsubscribeItem));
0920         }
0921 
0922         //check if any common topics were subscribed, if possible merge them
0923         manageCommonLevelSubscriptions();
0924     }
0925     updateSubscriptionCompleter();
0926 
0927     if (ui.twSubscriptions->topLevelItemCount() <= 0)
0928         emit enableWill(false);
0929 }
0930 
0931 /*!
0932  *\brief called when a new topic is added to the tree(twTopics)
0933  * appends the topic's root to the topicList if it isn't in the list already
0934  * then sets the completer for leTopics
0935  */
0936 void MQTTSubscriptionWidget::setTopicCompleter(const QString& topic) {
0937     if (!m_searching) {
0938         const QStringList& list = topic.split('/', QString::SkipEmptyParts);
0939         QString tempTopic;
0940         if (!list.isEmpty())
0941             tempTopic = list.at(0);
0942         else
0943             tempTopic = topic;
0944 
0945         if (!m_topicList.contains(tempTopic)) {
0946             m_topicList.append(tempTopic);
0947             m_topicCompleter = new QCompleter(m_topicList, this);
0948             m_topicCompleter->setCompletionMode(QCompleter::PopupCompletion);
0949             m_topicCompleter->setCaseSensitivity(Qt::CaseSensitive);
0950             ui.leTopics->setCompleter(m_topicCompleter);
0951         }
0952     }
0953 }
0954 
0955 /*!
0956  *\brief called when leTopics' text is changed
0957  *       if the rootName can be found in twTopics, then we scroll it to the top of the tree widget
0958  *
0959  * \param rootName the current text of leTopics
0960  */
0961 void MQTTSubscriptionWidget::scrollToTopicTreeItem(const QString& rootName) {
0962     m_searching = true;
0963     m_searchTimer->start();
0964 
0965     int topItemIdx = -1;
0966     for (int i = 0; i < ui.twTopics->topLevelItemCount(); ++i)
0967         if (ui.twTopics->topLevelItem(i)->text(0) == rootName) {
0968             topItemIdx = i;
0969             break;
0970         }
0971 
0972     if (topItemIdx >= 0)
0973         ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx),
0974                                   QAbstractItemView::ScrollHint::PositionAtTop);
0975 }
0976 
0977 /*!
0978  *\brief called when leSubscriptions' text is changed
0979  *       if the rootName can be found in twSubscriptions, then we scroll it to the top of the tree widget
0980  *
0981  * \param rootName the current text of leSubscriptions
0982  */
0983 void MQTTSubscriptionWidget::scrollToSubsriptionTreeItem(const QString& rootName) {
0984     int topItemIdx = -1;
0985     for (int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i)
0986         if (ui.twSubscriptions->topLevelItem(i)->text(0) == rootName) {
0987             topItemIdx = i;
0988             break;
0989         }
0990 
0991     if (topItemIdx >= 0)
0992         ui.twSubscriptions->scrollToItem(ui.twSubscriptions->topLevelItem(topItemIdx),
0993                                         QAbstractItemView::ScrollHint::PositionAtTop);
0994 }
0995 
0996 /*!
0997  *\brief called when 10 seconds passed since the last time the user searched for a certain root in twTopics
0998  * enables updating the completer for le
0999  */
1000 void MQTTSubscriptionWidget::topicTimeout() {
1001     m_searching = false;
1002     m_searchTimer->stop();
1003 }
1004 
1005 void MQTTSubscriptionWidget::clearWidgets() {
1006     ui.twTopics->clear();
1007     ui.twSubscriptions->clear();
1008     ui.twTopics->headerItem()->setText(0, i18n("Available"));
1009 }
1010 
1011 void MQTTSubscriptionWidget::onDisconnect() {
1012     m_searchTimer->stop();
1013     m_searching = false;
1014     delete m_topicCompleter;
1015     delete m_subscriptionCompleter;
1016 }