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 }