2 This file is part of Akonadi.
4 Copyright (c) 2006 Tobias Koenig <tokoe@kde.org>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 #include "agentwidget.h"
23 #include "agentconfigdialog.h"
24 #include "akonadiconsole_debug.h"
25 #include "kpimtextedit/plaintexteditorwidget.h"
26 #include <AkonadiWidgets/agenttypedialog.h>
27 #include <AkonadiWidgets/agentinstancewidget.h>
28 #include <AkonadiCore/agentmanager.h>
29 #include <AkonadiCore/AgentFilterProxyModel>
30 #include <AkonadiCore/agentinstancecreatejob.h>
31 #include <AkonadiWidgets/controlgui.h>
33 #include <KLocalizedString>
34 #include <KMessageBox>
35 #include <KStandardGuiItem>
43 #include <QPushButton>
44 #include <QDBusInterface>
45 #include <QDBusMessage>
47 #include <QMetaMethod>
48 #include <QResizeEvent>
50 #include <QDialogButtonBox>
51 #include <KConfigGroup>
52 #include <QVBoxLayout>
54 class TextDialog
: public QDialog
57 TextDialog(QWidget
*parent
= Q_NULLPTR
)
60 QDialogButtonBox
*buttonBox
= new QDialogButtonBox(QDialogButtonBox::Ok
);
61 QVBoxLayout
*mainLayout
= new QVBoxLayout
;
62 setLayout(mainLayout
);
63 QPushButton
*okButton
= buttonBox
->button(QDialogButtonBox::Ok
);
64 okButton
->setDefault(true);
65 okButton
->setShortcut(Qt::CTRL
| Qt::Key_Return
);
66 connect(buttonBox
, &QDialogButtonBox::accepted
, this, &TextDialog::accept
);
67 connect(buttonBox
, &QDialogButtonBox::rejected
, this, &TextDialog::reject
);
69 mText
= new KPIMTextEdit::PlainTextEditorWidget
;
70 mainLayout
->addWidget(mText
);
71 mainLayout
->addWidget(buttonBox
);
72 mText
->setReadOnly(true);
73 resize(QSize(400, 600));
76 void setText(const QString
&text
)
78 mText
->setPlainText(text
);
82 KPIMTextEdit::PlainTextEditorWidget
*mText
;
85 using namespace Akonadi
;
87 AgentWidget::AgentWidget(QWidget
*parent
)
92 connect(ui
.instanceWidget
, &Akonadi::AgentInstanceWidget::doubleClicked
, this, &AgentWidget::configureAgent
);
93 connect(ui
.instanceWidget
, &Akonadi::AgentInstanceWidget::currentChanged
, this, &AgentWidget::currentChanged
);
94 connect(ui
.instanceWidget
, &Akonadi::AgentInstanceWidget::customContextMenuRequested
, this, &AgentWidget::showContextMenu
);
96 connect(ui
.instanceWidget
->view()->selectionModel(), &QItemSelectionModel::selectionChanged
, this, &AgentWidget::selectionChanged
);
97 connect(ui
.instanceWidget
->view()->selectionModel(), &QItemSelectionModel::currentChanged
, this, &AgentWidget::selectionChanged
);
98 connect(ui
.instanceWidget
->view()->model(), &QAbstractItemModel::dataChanged
, this, &AgentWidget::slotDataChanged
);
102 KGuiItem::assign(ui
.addButton
, KStandardGuiItem::add());
103 connect(ui
.addButton
, &QPushButton::clicked
, this, &AgentWidget::addAgent
);
105 KGuiItem::assign(ui
.removeButton
, KStandardGuiItem::remove());
106 connect(ui
.removeButton
, &QPushButton::clicked
, this, &AgentWidget::removeAgent
);
108 mConfigMenu
= new QMenu(QStringLiteral("Configure"), this);
109 mConfigMenu
->addAction(QStringLiteral("Configure Natively..."), this, SLOT(configureAgent()));
110 mConfigMenu
->addAction(QStringLiteral("Configure Remotely..."), this, SLOT(configureAgentRemote()));
111 mConfigMenu
->setIcon(KStandardGuiItem::configure().icon());
112 KGuiItem::assign(ui
.configButton
, KStandardGuiItem::configure());
113 ui
.configButton
->setMenu(mConfigMenu
);
114 connect(ui
.configButton
, &QPushButton::clicked
, this, &AgentWidget::configureAgent
);
116 mSyncMenu
= new QMenu(QStringLiteral("Synchronize"), this);
117 mSyncMenu
->addAction(QStringLiteral("Synchronize All"), this, SLOT(synchronizeAgent()));
118 mSyncMenu
->addAction(QStringLiteral("Synchronize Collection Tree"), this, SLOT(synchronizeTree()));
119 mSyncMenu
->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
120 ui
.syncButton
->setMenu(mSyncMenu
);
121 ui
.syncButton
->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
122 connect(ui
.syncButton
, &QPushButton::clicked
, this, &AgentWidget::synchronizeAgent
);
124 ui
.abortButton
->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel")));
125 connect(ui
.abortButton
, &QPushButton::clicked
, this, &AgentWidget::abortAgent
);
126 ui
.restartButton
->setIcon(QIcon::fromTheme(QStringLiteral("system-reboot"))); //FIXME: Is using system-reboot icon here a good idea?
127 connect(ui
.restartButton
, &QPushButton::clicked
, this, &AgentWidget::restartAgent
);
129 ui
.mFilterAccount
->setProxy(ui
.instanceWidget
->agentFilterProxyModel());
130 ui
.mFilterAccount
->installEventFilter(this);
131 ControlGui::widgetNeedsAkonadi(this);
134 bool AgentWidget::eventFilter(QObject
*obj
, QEvent
*event
)
136 if (event
->type() == QEvent::KeyPress
&& obj
== ui
.mFilterAccount
) {
137 QKeyEvent
*key
= static_cast<QKeyEvent
*>(event
);
138 if ((key
->key() == Qt::Key_Enter
) || (key
->key() == Qt::Key_Return
)) {
143 return QWidget::eventFilter(obj
, event
);
146 void AgentWidget::addAgent()
148 QPointer
<Akonadi::AgentTypeDialog
> dlg
= new Akonadi::AgentTypeDialog(this);
150 const AgentType agentType
= dlg
->agentType();
152 if (agentType
.isValid()) {
153 AgentInstanceCreateJob
*job
= new AgentInstanceCreateJob(agentType
, this);
154 job
->configure(this);
155 job
->start(); // TODO: check result
161 void AgentWidget::selectionChanged()
163 const bool multiSelection
= ui
.instanceWidget
->view()->selectionModel()->selectedRows().size() > 1;
164 // Only agent removal, sync and restart is possible when multiple items are selected.
165 ui
.configButton
->setDisabled(multiSelection
);
167 // Restarting an agent is not possible if it's in Running status... (see AgentProcessInstance::restartWhenIdle)
168 AgentInstance agent
= ui
.instanceWidget
->currentAgentInstance();
169 ui
.restartButton
->setEnabled(agent
.isValid() && agent
.status() != 1);
172 void AgentWidget::slotDataChanged(const QModelIndex
&topLeft
, const QModelIndex
& /*bottomRight*/)
174 QList
<QModelIndex
> selectedRows
= ui
.instanceWidget
->view()->selectionModel()->selectedRows();
175 if (selectedRows
.isEmpty()) {
176 selectedRows
.append(ui
.instanceWidget
->view()->selectionModel()->currentIndex());
179 Q_FOREACH (const QModelIndex
&index
, selectedRows
) {
180 rows
.append(index
.row());
183 // Assume topLeft.row == bottomRight.row
184 if (topLeft
.row() >= rows
.first() && topLeft
.row() <= rows
.last()) {
185 selectionChanged(); // depends on status
190 void AgentWidget::removeAgent()
192 AgentInstance::List list
= ui
.instanceWidget
->selectedAgentInstances();
193 if (!list
.isEmpty()) {
194 if (KMessageBox::questionYesNo(this,
195 i18np("Do you really want to delete the selected agent instance?",
196 "Do you really want to delete these %1 agent instances?",
198 list
.size() == 1 ? QStringLiteral("Agent Deletion") : QStringLiteral("Multiple Agent Deletion"),
199 KStandardGuiItem::del(),
200 KStandardGuiItem::cancel(),
202 KMessageBox::Dangerous
)
203 == KMessageBox::Yes
) {
204 foreach (const AgentInstance
&agent
, list
) {
205 AgentManager::self()->removeInstance(agent
);
211 void AgentWidget::configureAgent()
213 AgentInstance agent
= ui
.instanceWidget
->currentAgentInstance();
214 if (agent
.isValid()) {
215 agent
.configure(this);
219 void AgentWidget::configureAgentRemote()
221 AgentInstance agent
= ui
.instanceWidget
->currentAgentInstance();
222 if (agent
.isValid()) {
223 QPointer
<AgentConfigDialog
> dlg
= new AgentConfigDialog(this);
224 dlg
->setAgentInstance(agent
);
230 void AgentWidget::synchronizeAgent()
232 AgentInstance::List list
= ui
.instanceWidget
->selectedAgentInstances();
234 foreach (AgentInstance agent
, list
) {
239 void AgentWidget::toggleOnline()
241 AgentInstance agent
= ui
.instanceWidget
->currentAgentInstance();
242 if (agent
.isValid()) {
243 agent
.setIsOnline(!agent
.isOnline());
247 void AgentWidget::showTaskList()
249 AgentInstance agent
= ui
.instanceWidget
->currentAgentInstance();
250 if (!agent
.isValid()) {
254 QDBusInterface
iface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(agent
.identifier()),
255 QStringLiteral("/Debug"), QString());
257 QDBusReply
<QString
> reply
= iface
.call(QStringLiteral("dumpToString"));
259 if (reply
.isValid()) {
262 txt
= reply
.error().message();
265 QPointer
<TextDialog
> dlg
= new TextDialog(this);
266 dlg
->setWindowTitle(QStringLiteral("Resource Task List"));
272 void AgentWidget::showChangeNotifications()
274 AgentInstance agent
= ui
.instanceWidget
->currentAgentInstance();
275 if (!agent
.isValid()) {
279 QDBusInterface
iface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(agent
.identifier()),
280 QStringLiteral("/Debug"), QString());
282 QDBusReply
<QString
> reply
= iface
.call(QStringLiteral("dumpNotificationListToString"));
284 if (reply
.isValid()) {
287 txt
= reply
.error().message();
290 QPointer
<TextDialog
> dlg
= new TextDialog(this);
291 dlg
->setWindowTitle(QStringLiteral("Change Notification Log"));
298 void AgentWidget::synchronizeTree()
300 AgentInstance::List list
= ui
.instanceWidget
->selectedAgentInstances();
302 foreach (AgentInstance agent
, list
) {
303 agent
.synchronizeCollectionTree();
307 void AgentWidget::abortAgent()
309 AgentInstance::List list
= ui
.instanceWidget
->selectedAgentInstances();
311 foreach (const AgentInstance
&agent
, list
) {
312 agent
.abortCurrentTask();
316 void AgentWidget::restartAgent()
318 AgentInstance agent
= ui
.instanceWidget
->currentAgentInstance();
319 if (agent
.isValid()) {
324 void AgentWidget::cloneAgent()
326 mCloneSource
= ui
.instanceWidget
->currentAgentInstance();
327 if (!mCloneSource
.isValid()) {
330 const AgentType agentType
= mCloneSource
.type();
331 if (agentType
.isValid()) {
332 AgentInstanceCreateJob
*job
= new AgentInstanceCreateJob(agentType
, this);
333 connect(job
, SIGNAL(result(KJob
*)), SLOT(cloneAgent(KJob
*)));
336 qCWarning(AKONADICONSOLE_LOG
) << "WTF?";
340 void AgentWidget::cloneAgent(KJob
*job
)
343 KMessageBox::error(this, QStringLiteral("Cloning agent failed: %1.").arg(job
->errorText()));
347 AgentInstance cloneTarget
= static_cast<AgentInstanceCreateJob
*>(job
)->instance();
348 Q_ASSERT(cloneTarget
.isValid());
349 Q_ASSERT(mCloneSource
.isValid());
351 QDBusInterface
sourceIface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(mCloneSource
.identifier()),
352 QStringLiteral("/Settings"));
353 if (!sourceIface
.isValid()) {
354 qCritical() << "Unable to obtain KConfigXT D-Bus interface of source agent" << mCloneSource
.identifier();
358 QDBusInterface
targetIface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(cloneTarget
.identifier()),
359 QStringLiteral("/Settings"));
360 if (!targetIface
.isValid()) {
361 qCritical() << "Unable to obtain KConfigXT D-Bus interface of target agent" << cloneTarget
.identifier();
365 cloneTarget
.setName(mCloneSource
.name() + QStringLiteral(" (Clone)"));
367 // iterate over all getter methods in the source interface and call the
368 // corresponding setter in the target interface
369 for (int i
= 0; i
< sourceIface
.metaObject()->methodCount(); ++i
) {
370 const QMetaMethod method
= sourceIface
.metaObject()->method(i
);
371 if (QByteArray(method
.typeName()).isEmpty()) { // returns void
374 const QByteArray
signature(method
.methodSignature());
375 if (signature
.isEmpty()) {
378 if (signature
.startsWith("set") || !signature
.contains("()")) { // setter or takes parameters // krazy:exclude=strings
381 if (signature
.startsWith("Introspect")) { // D-Bus stuff // krazy:exclude=strings
384 const QString methodName
= QLatin1String(signature
.left(signature
.indexOf('(')));
385 const QDBusMessage reply
= sourceIface
.call(methodName
);
386 if (reply
.arguments().count() != 1) {
387 qCritical() << "call to method" << signature
<< "failed: " << reply
.arguments() << reply
.errorMessage();
390 const QString setterName
= QStringLiteral("set") + methodName
.at(0).toUpper() + methodName
.mid(1);
391 targetIface
.call(setterName
, reply
.arguments().at(0));
394 cloneTarget
.reconfigure();
397 void AgentWidget::currentChanged()
399 AgentInstance instance
= ui
.instanceWidget
->currentAgentInstance();
400 ui
.removeButton
->setEnabled(instance
.isValid());
401 ui
.configButton
->setEnabled(instance
.isValid());
402 ui
.syncButton
->setEnabled(instance
.isValid());
403 ui
.restartButton
->setEnabled(instance
.isValid());
405 if (instance
.isValid()) {
406 ui
.identifierLabel
->setText(instance
.identifier());
407 ui
.typeLabel
->setText(instance
.type().name());
408 QString onlineStatus
= (instance
.isOnline() ? QStringLiteral("Online") : QStringLiteral("Offline"));
410 switch (instance
.status()) {
411 case AgentInstance::Idle
: agentStatus
=
412 i18nc("agent is in an idle state", "Idle");
414 case AgentInstance::Running
: agentStatus
=
415 i18nc("agent is running", "Running (%1%)", instance
.progress());
417 case AgentInstance::Broken
: agentStatus
=
418 i18nc("agent is broken somehow", "Broken");
420 case AgentInstance::NotConfigured
: agentStatus
=
421 i18nc("agent is not yet configured", "Not Configured");
424 ui
.statusLabel
->setText(
425 i18nc("Two statuses, for example \"Online, Running (66%)\" or \"Offline, Broken\"",
426 "%1, %2", onlineStatus
, agentStatus
));
427 ui
.statusMessageLabel
->setText(instance
.statusMessage());
428 ui
.capabilitiesLabel
->setText(instance
.type().capabilities().join(QStringLiteral(", ")));
429 ui
.mimeTypeLabel
->setText(instance
.type().mimeTypes().join(QStringLiteral(", ")));
431 ui
.identifierLabel
->setText(QString());
432 ui
.typeLabel
->setText(QString());
433 ui
.statusLabel
->setText(QString());
434 ui
.capabilitiesLabel
->setText(QString());
435 ui
.mimeTypeLabel
->setText(QString());
439 void AgentWidget::showContextMenu(const QPoint
&pos
)
442 menu
.addAction(QIcon::fromTheme(QStringLiteral("list-add")), QStringLiteral("Add Agent..."), this, SLOT(addAgent()));
443 menu
.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), QStringLiteral("Clone Agent"), this, SLOT(cloneAgent()));
445 menu
.addMenu(mSyncMenu
);
446 menu
.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), QStringLiteral("Abort Activity"), this, SLOT(abortAgent()));
447 menu
.addAction(QIcon::fromTheme(QStringLiteral("system-reboot")), QStringLiteral("Restart Agent"), this, SLOT(restartAgent())); //FIXME: Is using system-reboot icon here a good idea?
448 menu
.addAction(QIcon::fromTheme(QStringLiteral("network-disconnect")), QStringLiteral("Toggle Online/Offline"), this, SLOT(toggleOnline()));
449 menu
.addAction(QStringLiteral("Show task list"), this, SLOT(showTaskList()));
450 menu
.addAction(QStringLiteral("Show change-notification log"), this, SLOT(showChangeNotifications()));
451 menu
.addMenu(mConfigMenu
);
452 menu
.addAction(QIcon::fromTheme(QStringLiteral("list-remove")), QStringLiteral("Remove Agent"), this, SLOT(removeAgent()));
453 menu
.exec(ui
.instanceWidget
->mapToGlobal(pos
));
456 void AgentWidget::resizeEvent(QResizeEvent
*event
)
458 ui
.detailsBox
->setVisible(event
->size().height() > 400);