File indexing completed on 2024-05-05 05:37:09

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