File indexing completed on 2024-05-05 17:43:03
0001 /* 0002 * SPDX-FileCopyrightText: 2007 Aaron Seigo <aseigo@kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "engineexplorer.h" 0008 0009 #include <QApplication> 0010 #include <QBitArray> 0011 #include <QBitmap> 0012 #include <QDialogButtonBox> 0013 #include <QMenu> 0014 #include <QStandardItemModel> 0015 #include <QUrl> 0016 0017 #include <KLocalizedString> 0018 #include <KPluginMetaData> 0019 #include <KStandardAction> 0020 #include <KStringHandler> 0021 #include <QAction> 0022 #include <QDateTime> 0023 0024 #include <Plasma/PluginLoader> 0025 0026 Q_DECLARE_METATYPE(Plasma::DataEngine::Data) 0027 0028 #include "modelviewer.h" 0029 #include "serviceviewer.h" 0030 #include "titlecombobox.h" 0031 0032 enum { 0033 ColumnDataSourceAndKey = 0, 0034 ColumnType = 1, 0035 ColumnValue = 2, 0036 0037 ColumnCount, 0038 }; 0039 0040 EngineExplorer::EngineExplorer(QWidget *parent) 0041 : QDialog(parent) 0042 , m_engine(nullptr) 0043 , m_sourceCount(0) 0044 , m_requestingSource(false) 0045 , m_expandButton(new QPushButton(i18n("Expand All"), this)) 0046 , m_collapseButton(new QPushButton(i18n("Collapse All"), this)) 0047 { 0048 setWindowTitle(i18n("Plasma Engine Explorer")); 0049 QWidget *mainWidget = new QWidget(this); 0050 0051 QDialogButtonBox *buttonBox = new QDialogButtonBox(this); 0052 buttonBox->addButton(m_expandButton, QDialogButtonBox::ActionRole); 0053 buttonBox->addButton(m_collapseButton, QDialogButtonBox::ActionRole); 0054 buttonBox->addButton(QDialogButtonBox::Close); 0055 0056 QVBoxLayout *layout = new QVBoxLayout(this); 0057 layout->addWidget(mainWidget); 0058 layout->addWidget(buttonBox); 0059 setLayout(layout); 0060 0061 connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0062 0063 setupUi(mainWidget); 0064 0065 m_engineManager = Plasma::PluginLoader::self(); 0066 m_dataModel = new QStandardItemModel(this); 0067 const int size = m_title->style()->pixelMetric(QStyle::PM_LargeIconSize); 0068 m_title->setIconSize(QSize(size, size)); 0069 m_title->setIcon(QIcon::fromTheme("plasma")); 0070 connect(m_engines, SIGNAL(activated(QString)), this, SLOT(showEngine(QString))); 0071 connect(m_sourceRequesterButton, SIGNAL(clicked(bool)), this, SLOT(requestSource())); 0072 connect(m_serviceRequesterButton, &QAbstractButton::clicked, this, &EngineExplorer::requestServiceForSource); 0073 m_data->setModel(m_dataModel); 0074 m_data->setWordWrap(true); 0075 0076 m_searchLine->setTreeView(m_data); 0077 m_searchLine->setPlaceholderText(i18n("Search")); 0078 0079 listEngines(); 0080 m_engines->setFocus(); 0081 0082 connect(m_collapseButton, &QAbstractButton::clicked, m_data, &QTreeView::collapseAll); 0083 connect(m_expandButton, &QAbstractButton::clicked, m_data, &QTreeView::expandAll); 0084 enableButtons(false); 0085 0086 addAction(KStandardAction::quit(qApp, SLOT(quit()), this)); 0087 0088 connect(m_data, &QWidget::customContextMenuRequested, this, &EngineExplorer::showDataContextMenu); 0089 m_data->setContextMenuPolicy(Qt::CustomContextMenu); 0090 connect(qApp, &QCoreApplication::aboutToQuit, this, &EngineExplorer::cleanUp); 0091 } 0092 0093 EngineExplorer::~EngineExplorer() 0094 { 0095 } 0096 0097 void EngineExplorer::cleanUp() 0098 { 0099 if (!m_engineName.isEmpty()) { 0100 // m_engineManager->unloadEngine(m_engineName); 0101 } 0102 } 0103 0104 void EngineExplorer::setApp(const QString &app) 0105 { 0106 m_app = app; 0107 0108 if (m_engines->count() > 0) { 0109 listEngines(); 0110 } 0111 } 0112 0113 void EngineExplorer::setEngine(const QString &engine) 0114 { 0115 // find the engine in the combo box 0116 const int index = m_engines->findText(engine); 0117 if (index != -1) { 0118 qDebug() << QString("Engine %1 found!").arg(engine); 0119 m_engines->setCurrentIndex(index); 0120 showEngine(engine); 0121 } 0122 } 0123 0124 void EngineExplorer::setInterval(const int interval) 0125 { 0126 m_updateInterval->setValue(interval); 0127 } 0128 0129 void EngineExplorer::removeExtraRows(QStandardItem *parent, int preserve) 0130 { 0131 if (parent->rowCount() > preserve) { 0132 parent->removeRows(preserve, parent->rowCount() - preserve); 0133 } 0134 } 0135 0136 void EngineExplorer::dataUpdated(const QString &source, const Plasma::DataEngine::Data &data) 0137 { 0138 QList<QStandardItem *> items = m_dataModel->findItems(source, Qt::MatchExactly); 0139 0140 if (items.isEmpty()) { 0141 return; 0142 } 0143 0144 QStandardItem *parent = items.first(); 0145 0146 int rows = showData(parent, data); 0147 removeExtraRows(parent, rows); 0148 } 0149 0150 void EngineExplorer::listEngines() 0151 { 0152 m_engines->clear(); 0153 QVector<KPluginMetaData> engines = m_engineManager->listDataEngineMetaData(m_app); 0154 std::sort(engines.begin(), engines.end(), [](auto lhs, auto rhs) { 0155 if (lhs.category() < rhs.category()) { 0156 return true; 0157 } 0158 if (lhs.category() == rhs.category()) { 0159 return lhs.name() < rhs.name(); 0160 } 0161 return false; 0162 }); 0163 0164 for (const KPluginMetaData &engine : qAsConst(engines)) { 0165 m_engines->addItem(QIcon::fromTheme(engine.iconName()), engine.pluginId()); 0166 } 0167 0168 m_engines->setCurrentIndex(-1); 0169 } 0170 0171 void EngineExplorer::showEngine(const QString &name) 0172 { 0173 m_sourceRequester->setEnabled(false); 0174 m_sourceRequesterButton->setEnabled(false); 0175 m_serviceRequester->setEnabled(false); 0176 m_serviceRequesterButton->setEnabled(false); 0177 enableButtons(false); 0178 m_dataModel->clear(); 0179 m_dataModel->setColumnCount(ColumnCount); 0180 QStringList headers{i18n("DataSource/Key"), i18n("Type"), i18n("Value")}; 0181 m_dataModel->setHorizontalHeaderLabels(headers); 0182 m_data->setColumnWidth(ColumnDataSourceAndKey, 200); 0183 m_engine = nullptr; 0184 m_sourceCount = 0; 0185 0186 if (!m_engineName.isEmpty()) { 0187 // m_engineManager->unloadEngine(m_engineName); 0188 } 0189 0190 m_engineName = name; 0191 if (m_engineName.isEmpty()) { 0192 updateTitle(); 0193 return; 0194 } 0195 0196 if (auto res = KPluginFactory::instantiatePlugin<Plasma::DataEngine>(KPluginMetaData::findPluginById(QStringLiteral("plasma/dataengine"), m_engineName))) { 0197 m_engine = res.plugin; 0198 } else { 0199 m_engineName.clear(); 0200 updateTitle(); 0201 return; 0202 } 0203 0204 // qDebug() << "showing engine " << m_engine->objectName(); 0205 // qDebug() << "we have " << sources.count() << " data sources"; 0206 connect(m_engine, &Plasma::DataEngine::sourceAdded, this, &EngineExplorer::addSource); 0207 connect(m_engine, &Plasma::DataEngine::sourceRemoved, this, &EngineExplorer::removeSource); 0208 const QStringList &sources = m_engine->sources(); 0209 for (const QString &source : sources) { 0210 // qDebug() << "adding " << source; 0211 addSource(source); 0212 } 0213 0214 m_sourceRequesterButton->setEnabled(true); 0215 m_updateInterval->setEnabled(true); 0216 m_sourceRequester->setEnabled(true); 0217 m_sourceRequester->setFocus(); 0218 m_serviceRequester->setEnabled(true); 0219 m_serviceRequesterButton->setEnabled(true); 0220 updateTitle(); 0221 } 0222 0223 void EngineExplorer::addSource(const QString &source) 0224 { 0225 // qDebug() << "adding" << source; 0226 QList<QStandardItem *> items = m_dataModel->findItems(source, Qt::MatchExactly); 0227 if (!items.isEmpty()) { 0228 // qDebug() << "er... already there?"; 0229 return; 0230 } 0231 0232 QStandardItem *parent = new QStandardItem(source); 0233 parent->setToolTip(source); 0234 m_dataModel->appendRow(parent); 0235 0236 // qDebug() << "getting data for source " << source; 0237 if (!m_requestingSource || m_sourceRequester->text() != source) { 0238 // qDebug() << "connecting up now"; 0239 m_engine->connectSource(source, this); 0240 } 0241 0242 ++m_sourceCount; 0243 updateTitle(); 0244 0245 enableButtons(true); 0246 } 0247 0248 void EngineExplorer::removeSource(const QString &source) 0249 { 0250 const QList<QStandardItem *> items = m_dataModel->findItems(source, Qt::MatchExactly); 0251 0252 if (items.count() < 1) { 0253 return; 0254 } 0255 0256 for (QStandardItem *item : items) { 0257 m_dataModel->removeRow(item->row()); 0258 } 0259 0260 --m_sourceCount; 0261 m_engine->disconnectSource(source, this); 0262 updateTitle(); 0263 } 0264 0265 void EngineExplorer::requestSource() 0266 { 0267 requestSource(m_sourceRequester->text()); 0268 } 0269 0270 void EngineExplorer::requestServiceForSource() 0271 { 0272 ServiceViewer *viewer = new ServiceViewer(m_engine, m_serviceRequester->text()); 0273 viewer->show(); 0274 } 0275 0276 void EngineExplorer::requestSource(const QString &source) 0277 { 0278 if (!m_engine || source.isEmpty()) { 0279 return; 0280 } 0281 0282 qDebug() << "request source" << source; 0283 m_requestingSource = true; 0284 m_engine->connectSource(source, this, (uint)m_updateInterval->value()); 0285 m_requestingSource = false; 0286 } 0287 0288 void EngineExplorer::showDataContextMenu(const QPoint &point) 0289 { 0290 QModelIndex index = m_data->indexAt(point); 0291 if (index.isValid()) { 0292 while (index.parent().isValid()) { 0293 index = index.parent(); 0294 } 0295 0296 if (index.column() != 0) { 0297 index = m_dataModel->index(index.row(), 0); 0298 } 0299 0300 const QString source = index.data().toString(); 0301 QMenu menu; 0302 menu.addSection(source); 0303 QAction *service = menu.addAction(i18n("Get associated service")); 0304 QAction *model = menu.addAction(i18n("Get associated model")); 0305 QAction *update = menu.addAction(i18n("Update source now")); 0306 QAction *remove = menu.addAction(i18n("Remove source")); 0307 0308 QAction *activated = menu.exec(m_data->viewport()->mapToGlobal(point)); 0309 if (activated == service) { 0310 ServiceViewer *viewer = new ServiceViewer(m_engine, source); 0311 viewer->show(); 0312 } else if (activated == model) { 0313 ModelViewer *viewer = new ModelViewer(m_engine, source); 0314 viewer->show(); 0315 } else if (activated == update) { 0316 m_engine->connectSource(source, this); 0317 // Plasma::DataEngine::Data data = m_engine->query(source); 0318 } else if (activated == remove) { 0319 removeSource(source); 0320 } 0321 } 0322 } 0323 0324 QString EngineExplorer::convertToString(const QVariant &value) 0325 { 0326 switch (value.type()) { 0327 case QVariant::BitArray: { 0328 return i18np("<1 bit>", "<%1 bits>", value.toBitArray().size()); 0329 } 0330 case QVariant::Bitmap: { 0331 QBitmap bitmap = value.value<QBitmap>(); 0332 return QString("<%1x%2px - %3bpp>").arg(bitmap.width()).arg(bitmap.height()).arg(bitmap.depth()); 0333 } 0334 case QVariant::ByteArray: { 0335 // Return the array size if it is not displayable 0336 if (value.toString().isEmpty()) { 0337 return i18np("<1 byte>", "<%1 bytes>", value.toByteArray().size()); 0338 } else { 0339 return value.toString(); 0340 } 0341 } 0342 case QVariant::Image: { 0343 QImage image = value.value<QImage>(); 0344 return QString("<%1x%2px - %3bpp>").arg(image.width()).arg(image.height()).arg(image.depth()); 0345 } 0346 case QVariant::Line: { 0347 QLine line = value.toLine(); 0348 return QString("<x1:%1, y1:%2, x2:%3, y2:%4>").arg(line.x1()).arg(line.y1()).arg(line.x2()).arg(line.y2()); 0349 } 0350 case QVariant::LineF: { 0351 QLineF lineF = value.toLineF(); 0352 return QString("<x1:%1, y1:%2, x2:%3, y2:%4>").arg(lineF.x1()).arg(lineF.y1()).arg(lineF.x2()).arg(lineF.y2()); 0353 } 0354 case QVariant::Locale: { 0355 return QString("%1").arg(value.toLocale().name()); 0356 } 0357 case QVariant::Map: { 0358 QVariantMap map = value.toMap(); 0359 QString str = i18np("<1 item>", "<%1 items>", map.size()); 0360 0361 for (auto it = map.constBegin(); it != map.constEnd(); it++) { 0362 str += "\n" + it.key() + ": " + convertToString(it.value()); 0363 } 0364 0365 return str; 0366 } 0367 case QVariant::Pixmap: { 0368 QPixmap pixmap = value.value<QPixmap>(); 0369 return QString("<%1x%2px - %3bpp>").arg(pixmap.width()).arg(pixmap.height()).arg(pixmap.depth()); 0370 } 0371 case QVariant::Point: { 0372 QPoint point = value.toPoint(); 0373 return QString("<x:%1, y:%2>").arg(point.x()).arg(point.y()); 0374 } 0375 case QVariant::PointF: { 0376 QPointF pointF = value.toPointF(); 0377 return QString("<x:%1, y:%2>").arg(pointF.x()).arg(pointF.y()); 0378 } 0379 case QVariant::Rect: { 0380 QRect rect = value.toRect(); 0381 return QString("<x:%1, y:%2, w:%3, h:%4>").arg(rect.x()).arg(rect.y()).arg(rect.width()).arg(rect.height()); 0382 } 0383 case QVariant::RectF: { 0384 QRectF rectF = value.toRectF(); 0385 return QString("<x:%1, y:%2, w:%3, h:%4>").arg(rectF.x()).arg(rectF.y()).arg(rectF.width()).arg(rectF.height()); 0386 } 0387 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0388 case QVariant::RegExp: { 0389 return QString("%1").arg(value.toRegExp().pattern()); 0390 } 0391 #endif 0392 case QVariant::RegularExpression: { 0393 return value.toRegularExpression().pattern(); 0394 } 0395 case QVariant::Region: { 0396 QRect region = value.value<QRegion>().boundingRect(); 0397 return QString("<x:%1, y:%2, w:%3, h:%4>").arg(region.x()).arg(region.y()).arg(region.width()).arg(region.height()); 0398 } 0399 case QVariant::Size: { 0400 QSize size = value.toSize(); 0401 return QString("<w:%1, h:%2>").arg(size.width()).arg(size.height()); 0402 } 0403 case QVariant::SizeF: { 0404 QSizeF sizeF = value.toSizeF(); 0405 return QString("<w:%1, h:%2>").arg(sizeF.width()).arg(sizeF.height()); 0406 } 0407 case QVariant::Url: { 0408 return QString("%1").arg(value.toUrl().toString()); 0409 } 0410 case QVariant::StringList: { 0411 return QString("%1").arg(value.toStringList().join(", ")); 0412 } 0413 case QVariant::Date: { 0414 return QString("%1").arg(value.toDate().toString()); 0415 } 0416 case QVariant::DateTime: { 0417 return QString("%1").arg(value.toDateTime().toString()); 0418 } 0419 case QVariant::Time: { 0420 return QString("%1").arg(value.toTime().toString()); 0421 } 0422 default: { 0423 if (QLatin1String(value.typeName()) == "QDateTime") { 0424 return QString("%1").arg(value.value<QDateTime>().toString()); 0425 } 0426 0427 Plasma::DataEngine::Data data = value.value<Plasma::DataEngine::Data>(); 0428 if (!data.isEmpty()) { 0429 QStringList result; 0430 0431 for (auto it = data.constBegin(); it != data.constEnd(); it++) { 0432 result << (it.key() + ": " + convertToString(it.value())); 0433 } 0434 0435 return result.join("\n"); 0436 } else if (value.canConvert<QString>()) { 0437 QString str = value.toString(); 0438 if (str.isEmpty()) { 0439 return i18nc("The user did a query to a dataengine and it returned empty data", "<empty>"); 0440 } else { 0441 return str; 0442 } 0443 } 0444 0445 return i18nc("A the dataengine returned something that the humble view on the engineexplorer can't display, like a picture", "<not displayable>"); 0446 } 0447 } 0448 } 0449 0450 int EngineExplorer::showData(QStandardItem *parent, Plasma::DataEngine::Data data) 0451 { 0452 int rowCount = 0; 0453 for (auto it = data.constBegin(); it != data.constEnd(); it++) { 0454 showData(parent, rowCount++, it.key(), it.value()); 0455 } 0456 return rowCount; 0457 } 0458 0459 void EngineExplorer::showData(QStandardItem *parent, int row, const QString &key, const QVariant &value) 0460 { 0461 static_assert(ColumnDataSourceAndKey == 0); 0462 // QTreeView only expands tree for children of column #zero. 0463 QStandardItem *current = new QStandardItem(key); 0464 current->setToolTip(key); 0465 parent->setChild(row, ColumnDataSourceAndKey, current); 0466 0467 const char *typeName = value.typeName(); 0468 int rowCount = 0; 0469 0470 if (value.userType() == qMetaTypeId<QList<QVariantMap>>()) { 0471 // this case is a bit special, and has to be handled before generic QVariantList 0472 const QList<QVariantMap> list = value.value<QList<QVariantMap>>(); 0473 rowCount = showContainerData(parent, current, row, typeName, list); 0474 } else if (value.canConvert<QVariantList>() && value.type() != QVariant::String && value.type() != QVariant::ByteArray) { 0475 const QVariantList list = value.toList(); 0476 rowCount = showContainerData(parent, current, row, typeName, list); 0477 } else if (value.canConvert<QVariantMap>()) { 0478 const QVariantMap map = value.toMap(); 0479 rowCount = showContainerData(parent, current, row, typeName, map); 0480 } else { 0481 parent->setChild(row, ColumnType, new QStandardItem(typeName)); 0482 // clang-format off 0483 QStandardItem *item = value.canConvert<QIcon>() 0484 ? new QStandardItem(value.value<QIcon>(), "") 0485 : new QStandardItem(convertToString(value)); 0486 // clang-format on 0487 item->setToolTip(item->text()); 0488 parent->setChild(row, ColumnValue, item); 0489 // leave rowCount at value 0 0490 } 0491 removeExtraRows(current, rowCount); 0492 } 0493 0494 int EngineExplorer::showContainerData(QStandardItem *parent, QStandardItem *current, int row, const char *typeName, const QList<QVariantMap> &list) 0495 { 0496 QStandardItem *typeItem = new QStandardItem(typeName); 0497 typeItem->setToolTip(typeItem->text()); 0498 parent->setChild(row, ColumnType, typeItem); 0499 parent->setChild(row, ColumnValue, new QStandardItem(ki18ncp("Length of the list", "<%1 item>", "<%1 items>").subs(list.length()).toString())); 0500 0501 int rowCount = 0; 0502 for (const QVariantMap &map : list) { 0503 showData(current, rowCount, QString::number(rowCount), map); 0504 rowCount++; 0505 } 0506 return rowCount; 0507 } 0508 0509 int EngineExplorer::showContainerData(QStandardItem *parent, QStandardItem *current, int row, const char *typeName, const QVariantList &list) 0510 { 0511 QStandardItem *typeItem = new QStandardItem(typeName); 0512 typeItem->setToolTip(typeItem->text()); 0513 parent->setChild(row, ColumnType, typeItem); 0514 parent->setChild(row, ColumnValue, new QStandardItem(ki18ncp("Length of the list", "<%1 item>", "<%1 items>").subs(list.length()).toString())); 0515 0516 int rowCount = 0; 0517 for (const QVariant &var : list) { 0518 showData(current, rowCount, QString::number(rowCount), var); 0519 rowCount++; 0520 } 0521 return rowCount; 0522 } 0523 0524 int EngineExplorer::showContainerData(QStandardItem *parent, QStandardItem *current, int row, const char *typeName, const QVariantMap &map) 0525 { 0526 QStandardItem *typeItem = new QStandardItem(typeName); 0527 typeItem->setToolTip(typeItem->text()); 0528 parent->setChild(row, ColumnType, typeItem); 0529 parent->setChild(row, ColumnValue, new QStandardItem(ki18ncp("Size of the map", "<%1 pair>", "<%1 pairs>").subs(map.size()).toString())); 0530 0531 int rowCount = 0; 0532 for (auto it = map.constBegin(); it != map.constEnd(); it++) { 0533 showData(current, rowCount++, it.key(), it.value()); 0534 } 0535 return rowCount; 0536 } 0537 0538 void EngineExplorer::updateTitle() 0539 { 0540 if (!m_engine || !m_engine->metadata().isValid()) { 0541 m_title->setIcon(QIcon::fromTheme("plasma")); 0542 m_title->setText(i18n("Plasma DataEngine Explorer")); 0543 return; 0544 } 0545 0546 m_title->setText(ki18ncp("The name of the engine followed by the number of data sources", "%1 Engine - 1 data source", "%1 Engine - %2 data sources") 0547 .subs(KStringHandler::capwords(m_engine->metadata().name())) 0548 .subs(m_sourceCount) 0549 .toString()); 0550 0551 if (m_engine->metadata().iconName().isEmpty()) { 0552 m_title->setIcon(QIcon::fromTheme("plasma")); 0553 } else { 0554 m_title->setIcon(QIcon::fromTheme(m_engine->metadata().iconName())); 0555 } 0556 } 0557 0558 void EngineExplorer::enableButtons(bool enable) 0559 { 0560 if (m_expandButton) { 0561 m_expandButton->setEnabled(enable); 0562 } 0563 0564 if (m_collapseButton) { 0565 m_collapseButton->setEnabled(enable); 0566 } 0567 }