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 }