File indexing completed on 2024-05-12 05:12:43
0001 /* 0002 This file is part of Akonadi. 0003 0004 SPDX-FileCopyrightText: 2006 Tobias Koenig <tokoe@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "agentwidget.h" 0010 0011 #include "agentconfigdialog.h" 0012 #include "akonadiconsole_debug.h" 0013 #include <Akonadi/AgentConfigurationDialog> 0014 #include <Akonadi/AgentFilterProxyModel> 0015 #include <Akonadi/AgentInstanceCreateJob> 0016 #include <Akonadi/AgentInstanceWidget> 0017 #include <Akonadi/AgentManager> 0018 #include <Akonadi/AgentTypeDialog> 0019 #include <Akonadi/ControlGui> 0020 0021 #include <KLocalizedString> 0022 #include <KMessageBox> 0023 #include <KStandardGuiItem> 0024 #include <QIcon> 0025 0026 #include <KGuiItem> 0027 #include <QDBusInterface> 0028 #include <QDBusMessage> 0029 #include <QDBusReply> 0030 #include <QDialogButtonBox> 0031 #include <QMenu> 0032 #include <QMetaMethod> 0033 #include <QPlainTextEdit> 0034 #include <QPointer> 0035 #include <QPushButton> 0036 #include <QResizeEvent> 0037 #include <QVBoxLayout> 0038 0039 class TextDialog : public QDialog 0040 { 0041 public: 0042 explicit TextDialog(QWidget *parent = nullptr) 0043 : QDialog(parent) 0044 , mText(new QPlainTextEdit(this)) 0045 { 0046 auto mainLayout = new QVBoxLayout(this); 0047 mText->setReadOnly(true); 0048 0049 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, this); 0050 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); 0051 okButton->setDefault(true); 0052 okButton->setShortcut(Qt::CTRL | Qt::Key_Return); 0053 connect(buttonBox, &QDialogButtonBox::accepted, this, &TextDialog::accept); 0054 connect(buttonBox, &QDialogButtonBox::rejected, this, &TextDialog::reject); 0055 0056 mainLayout->addWidget(mText); 0057 mainLayout->addWidget(buttonBox); 0058 resize(QSize(400, 600)); 0059 } 0060 0061 void setText(const QString &text) 0062 { 0063 mText->setPlainText(text); 0064 } 0065 0066 private: 0067 QPlainTextEdit *const mText; 0068 }; 0069 0070 using namespace Akonadi; 0071 0072 AgentWidget::AgentWidget(QWidget *parent) 0073 : QWidget(parent) 0074 { 0075 ui.setupUi(this); 0076 0077 connect(ui.instanceWidget, &Akonadi::AgentInstanceWidget::doubleClicked, this, &AgentWidget::configureAgent); 0078 connect(ui.instanceWidget, &Akonadi::AgentInstanceWidget::currentChanged, this, &AgentWidget::currentChanged); 0079 connect(ui.instanceWidget, &Akonadi::AgentInstanceWidget::customContextMenuRequested, this, &AgentWidget::showContextMenu); 0080 0081 connect(ui.instanceWidget->view()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AgentWidget::selectionChanged); 0082 connect(ui.instanceWidget->view()->selectionModel(), &QItemSelectionModel::currentChanged, this, &AgentWidget::selectionChanged); 0083 connect(ui.instanceWidget->view()->model(), &QAbstractItemModel::dataChanged, this, &AgentWidget::slotDataChanged); 0084 0085 currentChanged(); 0086 0087 KGuiItem::assign(ui.addButton, KStandardGuiItem::add()); 0088 connect(ui.addButton, &QPushButton::clicked, this, &AgentWidget::addAgent); 0089 0090 KGuiItem::assign(ui.removeButton, KStandardGuiItem::remove()); 0091 connect(ui.removeButton, &QPushButton::clicked, this, &AgentWidget::removeAgent); 0092 0093 mConfigMenu = new QMenu(i18n("Configure"), this); 0094 mConfigMenu->addAction(i18n("Configure Natively..."), this, &AgentWidget::configureAgent); 0095 mConfigMenu->addAction(i18n("Configure Remotely..."), this, &AgentWidget::configureAgentRemote); 0096 mConfigMenu->setIcon(KStandardGuiItem::configure().icon()); 0097 KGuiItem::assign(ui.configButton, KStandardGuiItem::configure()); 0098 ui.configButton->setMenu(mConfigMenu); 0099 connect(ui.configButton, &QPushButton::clicked, this, &AgentWidget::configureAgent); 0100 0101 mSyncMenu = new QMenu(i18n("Synchronize"), this); 0102 mSyncMenu->addAction(i18n("Synchronize All"), this, &AgentWidget::synchronizeAgent); 0103 mSyncMenu->addAction(i18n("Synchronize Collection Tree"), this, &AgentWidget::synchronizeTree); 0104 mSyncMenu->addAction(i18n("Synchronize Tags"), this, [this]() { 0105 const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); 0106 for (auto agent : list) { 0107 agent.synchronizeTags(); 0108 } 0109 }); 0110 mSyncMenu->addAction(i18n("Synchronize Relations"), this, [this]() { 0111 const auto list = ui.instanceWidget->selectedAgentInstances(); 0112 for (auto agent : list) { 0113 agent.synchronizeRelations(); 0114 } 0115 }); 0116 mSyncMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0117 ui.syncButton->setMenu(mSyncMenu); 0118 ui.syncButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0119 connect(ui.syncButton, &QPushButton::clicked, this, &AgentWidget::synchronizeAgent); 0120 0121 ui.abortButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"))); 0122 connect(ui.abortButton, &QPushButton::clicked, this, &AgentWidget::abortAgent); 0123 ui.restartButton->setIcon(QIcon::fromTheme(QStringLiteral("system-reboot"))); // FIXME: Is using system-reboot icon here a good idea? 0124 connect(ui.restartButton, &QPushButton::clicked, this, &AgentWidget::restartAgent); 0125 0126 ui.instanceWidget->agentFilterProxyModel()->setFilterCaseSensitivity(Qt::CaseInsensitive); 0127 connect(ui.mFilterAccount, &QLineEdit::textChanged, this, &AgentWidget::slotSearchAgentType); 0128 ui.mFilterAccount->installEventFilter(this); 0129 ControlGui::widgetNeedsAkonadi(this); 0130 } 0131 0132 void AgentWidget::slotSearchAgentType(const QString &str) 0133 { 0134 ui.instanceWidget->agentFilterProxyModel()->setFilterRegularExpression(str); 0135 } 0136 0137 bool AgentWidget::eventFilter(QObject *obj, QEvent *event) 0138 { 0139 if (event->type() == QEvent::KeyPress && obj == ui.mFilterAccount) { 0140 auto key = static_cast<QKeyEvent *>(event); 0141 if ((key->key() == Qt::Key_Enter) || (key->key() == Qt::Key_Return)) { 0142 event->accept(); 0143 return true; 0144 } 0145 } 0146 return QWidget::eventFilter(obj, event); 0147 } 0148 0149 void AgentWidget::addAgent() 0150 { 0151 QPointer<Akonadi::AgentTypeDialog> dlg = new Akonadi::AgentTypeDialog(this); 0152 if (dlg->exec()) { 0153 const AgentType agentType = dlg->agentType(); 0154 0155 if (agentType.isValid()) { 0156 auto job = new AgentInstanceCreateJob(agentType, this); 0157 job->configure(this); 0158 job->start(); // TODO: check result 0159 } 0160 } 0161 delete dlg; 0162 } 0163 0164 void AgentWidget::selectionChanged() 0165 { 0166 const bool multiSelection = ui.instanceWidget->view()->selectionModel()->selectedRows().size() > 1; 0167 // Only agent removal, sync and restart is possible when multiple items are selected. 0168 ui.configButton->setDisabled(multiSelection); 0169 0170 // Restarting an agent is not possible if it's in Running status... (see AgentProcessInstance::restartWhenIdle) 0171 AgentInstance agent = ui.instanceWidget->currentAgentInstance(); 0172 ui.restartButton->setEnabled(agent.isValid() && agent.status() != 1); 0173 } 0174 0175 void AgentWidget::slotDataChanged(const QModelIndex &topLeft, const QModelIndex & /*bottomRight*/) 0176 { 0177 QList<QModelIndex> selectedRows = ui.instanceWidget->view()->selectionModel()->selectedRows(); 0178 if (selectedRows.isEmpty()) { 0179 selectedRows.append(ui.instanceWidget->view()->selectionModel()->currentIndex()); 0180 } 0181 QList<int> rows; 0182 for (const QModelIndex &index : std::as_const(selectedRows)) { 0183 rows.append(index.row()); 0184 } 0185 std::sort(rows.begin(), rows.end()); 0186 // Assume topLeft.row == bottomRight.row 0187 if (topLeft.row() >= rows.first() && topLeft.row() <= rows.last()) { 0188 selectionChanged(); // depends on status 0189 currentChanged(); 0190 } 0191 } 0192 0193 void AgentWidget::removeAgent() 0194 { 0195 const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); 0196 if (!list.isEmpty()) { 0197 if (KMessageBox::questionTwoActions( 0198 this, 0199 i18np("Do you really want to delete the selected agent instance?", "Do you really want to delete these %1 agent instances?", list.size()), 0200 list.size() == 1 ? i18n("Agent Deletion") : i18n("Multiple Agent Deletion"), 0201 KStandardGuiItem::del(), 0202 KStandardGuiItem::cancel(), 0203 QString(), 0204 KMessageBox::Dangerous) 0205 == KMessageBox::ButtonCode::PrimaryAction) { 0206 for (const AgentInstance &agent : list) { 0207 AgentManager::self()->removeInstance(agent); 0208 } 0209 } 0210 } 0211 } 0212 0213 void AgentWidget::configureAgent() 0214 { 0215 AgentInstance agent = ui.instanceWidget->currentAgentInstance(); 0216 if (agent.isValid()) { 0217 QPointer<AgentConfigurationDialog> dlg = new AgentConfigurationDialog(agent, this); 0218 dlg->exec(); 0219 delete dlg; 0220 } 0221 } 0222 0223 void AgentWidget::configureAgentRemote() 0224 { 0225 AgentInstance agent = ui.instanceWidget->currentAgentInstance(); 0226 if (agent.isValid()) { 0227 QPointer<AgentConfigDialog> dlg = new AgentConfigDialog(this); 0228 dlg->setAgentInstance(agent); 0229 dlg->exec(); 0230 delete dlg; 0231 } 0232 } 0233 0234 void AgentWidget::synchronizeAgent() 0235 { 0236 const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); 0237 if (!list.isEmpty()) { 0238 for (AgentInstance agent : list) { 0239 agent.synchronize(); 0240 } 0241 } 0242 } 0243 0244 void AgentWidget::toggleOnline() 0245 { 0246 AgentInstance agent = ui.instanceWidget->currentAgentInstance(); 0247 if (agent.isValid()) { 0248 agent.setIsOnline(!agent.isOnline()); 0249 } 0250 } 0251 0252 void AgentWidget::showTaskList() 0253 { 0254 AgentInstance agent = ui.instanceWidget->currentAgentInstance(); 0255 if (!agent.isValid()) { 0256 return; 0257 } 0258 0259 QDBusInterface iface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(agent.identifier()), QStringLiteral("/Debug"), QString()); 0260 0261 QDBusReply<QString> reply = iface.call(QStringLiteral("dumpToString")); 0262 QString txt; 0263 if (reply.isValid()) { 0264 txt = reply.value(); 0265 } else { 0266 txt = reply.error().message(); 0267 } 0268 0269 QPointer<TextDialog> dlg = new TextDialog(this); 0270 dlg->setWindowTitle(i18nc("@title:window", "Resource Task List")); 0271 dlg->setText(txt); 0272 dlg->exec(); 0273 delete dlg; 0274 } 0275 0276 void AgentWidget::showChangeNotifications() 0277 { 0278 AgentInstance agent = ui.instanceWidget->currentAgentInstance(); 0279 if (!agent.isValid()) { 0280 return; 0281 } 0282 0283 QDBusInterface iface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(agent.identifier()), QStringLiteral("/Debug"), QString()); 0284 0285 QDBusReply<QString> reply = iface.call(QStringLiteral("dumpNotificationListToString")); 0286 QString txt; 0287 if (reply.isValid()) { 0288 txt = reply.value(); 0289 } else { 0290 txt = reply.error().message(); 0291 } 0292 0293 QPointer<TextDialog> dlg = new TextDialog(this); 0294 dlg->setWindowTitle(i18nc("@title:window", "Change Notification Log")); 0295 dlg->setText(txt); 0296 0297 dlg->exec(); 0298 delete dlg; 0299 } 0300 0301 void AgentWidget::synchronizeTree() 0302 { 0303 const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); 0304 for (AgentInstance agent : list) { 0305 agent.synchronizeCollectionTree(); 0306 } 0307 } 0308 0309 void AgentWidget::abortAgent() 0310 { 0311 const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); 0312 for (const AgentInstance &agent : list) { 0313 agent.abortCurrentTask(); 0314 } 0315 } 0316 0317 void AgentWidget::restartAgent() 0318 { 0319 AgentInstance agent = ui.instanceWidget->currentAgentInstance(); 0320 if (agent.isValid()) { 0321 agent.restart(); 0322 } 0323 } 0324 0325 void AgentWidget::slotCloneAgent() 0326 { 0327 mCloneSource = ui.instanceWidget->currentAgentInstance(); 0328 if (!mCloneSource.isValid()) { 0329 return; 0330 } 0331 const AgentType agentType = mCloneSource.type(); 0332 if (agentType.isValid()) { 0333 auto job = new AgentInstanceCreateJob(agentType, this); 0334 connect(job, &KJob::result, this, &AgentWidget::cloneAgent); 0335 job->start(); 0336 } else { 0337 qCWarning(AKONADICONSOLE_LOG) << "WTF?"; 0338 } 0339 } 0340 0341 void AgentWidget::cloneAgent(KJob *job) 0342 { 0343 if (job->error()) { 0344 KMessageBox::error(this, i18n("Cloning agent failed: %1.", job->errorText())); 0345 return; 0346 } 0347 0348 AgentInstance cloneTarget = static_cast<AgentInstanceCreateJob *>(job)->instance(); 0349 Q_ASSERT(cloneTarget.isValid()); 0350 Q_ASSERT(mCloneSource.isValid()); 0351 0352 QDBusInterface sourceIface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(mCloneSource.identifier()), QStringLiteral("/Settings")); 0353 if (!sourceIface.isValid()) { 0354 qCritical() << "Unable to obtain KConfigXT D-Bus interface of source agent" << mCloneSource.identifier(); 0355 return; 0356 } 0357 0358 QDBusInterface targetIface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(cloneTarget.identifier()), QStringLiteral("/Settings")); 0359 if (!targetIface.isValid()) { 0360 qCritical() << "Unable to obtain KConfigXT D-Bus interface of target agent" << cloneTarget.identifier(); 0361 return; 0362 } 0363 0364 cloneTarget.setName(mCloneSource.name() + QStringLiteral(" (Clone)")); 0365 0366 // iterate over all getter methods in the source interface and call the 0367 // corresponding setter in the target interface 0368 for (int i = 0; i < sourceIface.metaObject()->methodCount(); ++i) { 0369 const QMetaMethod method = sourceIface.metaObject()->method(i); 0370 if (QByteArray(method.typeName()).isEmpty()) { // returns void 0371 continue; 0372 } 0373 const QByteArray signature(method.methodSignature()); 0374 if (signature.isEmpty()) { 0375 continue; 0376 } 0377 if (signature.startsWith("set") || !signature.contains("()")) { // setter or takes parameters // krazy:exclude=strings 0378 continue; 0379 } 0380 if (signature.startsWith("Introspect")) { // D-Bus stuff // krazy:exclude=strings 0381 continue; 0382 } 0383 const QString methodName = QLatin1StringView(signature.left(signature.indexOf('('))); 0384 const QDBusMessage reply = sourceIface.call(methodName); 0385 if (reply.arguments().count() != 1) { 0386 qCritical() << "call to method" << signature << "failed: " << reply.arguments() << reply.errorMessage(); 0387 continue; 0388 } 0389 const QString setterName = QStringLiteral("set") + methodName.at(0).toUpper() + methodName.mid(1); 0390 targetIface.call(setterName, reply.arguments().at(0)); 0391 } 0392 0393 cloneTarget.reconfigure(); 0394 } 0395 0396 void AgentWidget::currentChanged() 0397 { 0398 AgentInstance instance = ui.instanceWidget->currentAgentInstance(); 0399 ui.removeButton->setEnabled(instance.isValid()); 0400 ui.configButton->setEnabled(instance.isValid()); 0401 ui.syncButton->setEnabled(instance.isValid()); 0402 ui.restartButton->setEnabled(instance.isValid()); 0403 0404 if (instance.isValid()) { 0405 ui.identifierLabel->setText(instance.identifier()); 0406 ui.typeLabel->setText(instance.type().name()); 0407 QString onlineStatus = (instance.isOnline() ? i18n("Online") : i18n("Offline")); 0408 QString agentStatus; 0409 switch (instance.status()) { 0410 case AgentInstance::Idle: 0411 agentStatus = i18n("Idle"); 0412 break; 0413 case AgentInstance::Running: 0414 agentStatus = i18n("Running (%1%)", instance.progress()); 0415 break; 0416 case AgentInstance::Broken: 0417 agentStatus = i18n("Broken"); 0418 break; 0419 case AgentInstance::NotConfigured: 0420 agentStatus = i18n("Not Configured"); 0421 break; 0422 } 0423 ui.statusLabel->setText(i18nc("Two statuses, for example \"Online, Running (66%)\" or \"Offline, Broken\"", "%1, %2", onlineStatus, agentStatus)); 0424 ui.statusMessageLabel->setText(instance.statusMessage()); 0425 ui.capabilitiesLabel->setText(instance.type().capabilities().join(QLatin1StringView(", "))); 0426 ui.mimeTypeLabel->setText(instance.type().mimeTypes().join(QLatin1StringView(", "))); 0427 } else { 0428 ui.identifierLabel->setText(QString()); 0429 ui.typeLabel->setText(QString()); 0430 ui.statusLabel->setText(QString()); 0431 ui.capabilitiesLabel->setText(QString()); 0432 ui.mimeTypeLabel->setText(QString()); 0433 } 0434 } 0435 0436 void AgentWidget::showContextMenu(const QPoint &pos) 0437 { 0438 QMenu menu(this); 0439 menu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add Agent..."), this, &AgentWidget::addAgent); 0440 menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Clone Agent"), this, &AgentWidget::slotCloneAgent); 0441 menu.addSeparator(); 0442 menu.addMenu(mSyncMenu); 0443 menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("Abort Activity"), this, &AgentWidget::abortAgent); 0444 menu.addAction(QIcon::fromTheme(QStringLiteral("system-reboot")), 0445 i18n("Restart Agent"), 0446 this, 0447 &AgentWidget::restartAgent); // FIXME: Is using system-reboot icon here a good idea? 0448 menu.addAction(QIcon::fromTheme(QStringLiteral("network-disconnect")), i18n("Toggle Online/Offline"), this, &AgentWidget::toggleOnline); 0449 menu.addAction(i18n("Show task list"), this, &AgentWidget::showTaskList); 0450 menu.addAction(i18n("Show change-notification log"), this, &AgentWidget::showChangeNotifications); 0451 menu.addMenu(mConfigMenu); 0452 menu.addAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("Remove Agent"), this, &AgentWidget::removeAgent); 0453 menu.exec(ui.instanceWidget->mapToGlobal(pos)); 0454 } 0455 0456 void AgentWidget::resizeEvent(QResizeEvent *event) 0457 { 0458 ui.detailsBox->setVisible(event->size().height() > 400); 0459 } 0460 0461 #include "moc_agentwidget.cpp"