diff --git a/src/agentwidget.cpp b/src/agentwidget.cpp index d251239..5bcca55 100644 --- a/src/agentwidget.cpp +++ b/src/agentwidget.cpp @@ -1,459 +1,459 @@ /* This file is part of Akonadi. Copyright (c) 2006 Tobias Koenig This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "agentwidget.h" #include "agentconfigdialog.h" #include "akonadiconsole_debug.h" #include "kpimtextedit/plaintexteditorwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class TextDialog : public QDialog { public: - TextDialog(QWidget *parent = nullptr) + explicit TextDialog(QWidget *parent = nullptr) : QDialog(parent) { QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); QVBoxLayout *mainLayout = new QVBoxLayout(this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &TextDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &TextDialog::reject); mText = new KPIMTextEdit::PlainTextEditorWidget; mainLayout->addWidget(mText); mainLayout->addWidget(buttonBox); mText->setReadOnly(true); resize(QSize(400, 600)); } void setText(const QString &text) { mText->setPlainText(text); } private: KPIMTextEdit::PlainTextEditorWidget *mText; }; using namespace Akonadi; AgentWidget::AgentWidget(QWidget *parent) : QWidget(parent) { ui.setupUi(this); connect(ui.instanceWidget, &Akonadi::AgentInstanceWidget::doubleClicked, this, &AgentWidget::configureAgent); connect(ui.instanceWidget, &Akonadi::AgentInstanceWidget::currentChanged, this, &AgentWidget::currentChanged); connect(ui.instanceWidget, &Akonadi::AgentInstanceWidget::customContextMenuRequested, this, &AgentWidget::showContextMenu); connect(ui.instanceWidget->view()->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AgentWidget::selectionChanged); connect(ui.instanceWidget->view()->selectionModel(), &QItemSelectionModel::currentChanged, this, &AgentWidget::selectionChanged); connect(ui.instanceWidget->view()->model(), &QAbstractItemModel::dataChanged, this, &AgentWidget::slotDataChanged); currentChanged(); KGuiItem::assign(ui.addButton, KStandardGuiItem::add()); connect(ui.addButton, &QPushButton::clicked, this, &AgentWidget::addAgent); KGuiItem::assign(ui.removeButton, KStandardGuiItem::remove()); connect(ui.removeButton, &QPushButton::clicked, this, &AgentWidget::removeAgent); mConfigMenu = new QMenu(QStringLiteral("Configure"), this); mConfigMenu->addAction(QStringLiteral("Configure Natively..."), this, &AgentWidget::configureAgent); mConfigMenu->addAction(QStringLiteral("Configure Remotely..."), this, &AgentWidget::configureAgentRemote); mConfigMenu->setIcon(KStandardGuiItem::configure().icon()); KGuiItem::assign(ui.configButton, KStandardGuiItem::configure()); ui.configButton->setMenu(mConfigMenu); connect(ui.configButton, &QPushButton::clicked, this, &AgentWidget::configureAgent); mSyncMenu = new QMenu(QStringLiteral("Synchronize"), this); mSyncMenu->addAction(QStringLiteral("Synchronize All"), this, &AgentWidget::synchronizeAgent); mSyncMenu->addAction(QStringLiteral("Synchronize Collection Tree"), this, &AgentWidget::synchronizeTree); mSyncMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); ui.syncButton->setMenu(mSyncMenu); ui.syncButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(ui.syncButton, &QPushButton::clicked, this, &AgentWidget::synchronizeAgent); ui.abortButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"))); connect(ui.abortButton, &QPushButton::clicked, this, &AgentWidget::abortAgent); ui.restartButton->setIcon(QIcon::fromTheme(QStringLiteral("system-reboot"))); //FIXME: Is using system-reboot icon here a good idea? connect(ui.restartButton, &QPushButton::clicked, this, &AgentWidget::restartAgent); ui.mFilterAccount->setProxy(ui.instanceWidget->agentFilterProxyModel()); ui.mFilterAccount->installEventFilter(this); ControlGui::widgetNeedsAkonadi(this); } bool AgentWidget::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress && obj == ui.mFilterAccount) { QKeyEvent *key = static_cast(event); if ((key->key() == Qt::Key_Enter) || (key->key() == Qt::Key_Return)) { event->accept(); return true; } } return QWidget::eventFilter(obj, event); } void AgentWidget::addAgent() { QPointer dlg = new Akonadi::AgentTypeDialog(this); if (dlg->exec()) { const AgentType agentType = dlg->agentType(); if (agentType.isValid()) { AgentInstanceCreateJob *job = new AgentInstanceCreateJob(agentType, this); job->configure(this); job->start(); // TODO: check result } } delete dlg; } void AgentWidget::selectionChanged() { const bool multiSelection = ui.instanceWidget->view()->selectionModel()->selectedRows().size() > 1; // Only agent removal, sync and restart is possible when multiple items are selected. ui.configButton->setDisabled(multiSelection); // Restarting an agent is not possible if it's in Running status... (see AgentProcessInstance::restartWhenIdle) AgentInstance agent = ui.instanceWidget->currentAgentInstance(); ui.restartButton->setEnabled(agent.isValid() && agent.status() != 1); } void AgentWidget::slotDataChanged(const QModelIndex &topLeft, const QModelIndex & /*bottomRight*/) { QList selectedRows = ui.instanceWidget->view()->selectionModel()->selectedRows(); if (selectedRows.isEmpty()) { selectedRows.append(ui.instanceWidget->view()->selectionModel()->currentIndex()); } QList rows; foreach (const QModelIndex &index, selectedRows) { rows.append(index.row()); } std::sort(rows.begin(), rows.end()); // Assume topLeft.row == bottomRight.row if (topLeft.row() >= rows.first() && topLeft.row() <= rows.last()) { selectionChanged(); // depends on status currentChanged(); } } void AgentWidget::removeAgent() { const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); if (!list.isEmpty()) { if (KMessageBox::questionYesNo(this, i18np("Do you really want to delete the selected agent instance?", "Do you really want to delete these %1 agent instances?", list.size()), list.size() == 1 ? QStringLiteral("Agent Deletion") : QStringLiteral("Multiple Agent Deletion"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QString(), KMessageBox::Dangerous) == KMessageBox::Yes) { for (const AgentInstance &agent : list) { AgentManager::self()->removeInstance(agent); } } } } void AgentWidget::configureAgent() { AgentInstance agent = ui.instanceWidget->currentAgentInstance(); if (agent.isValid()) { agent.configure(this); } } void AgentWidget::configureAgentRemote() { AgentInstance agent = ui.instanceWidget->currentAgentInstance(); if (agent.isValid()) { QPointer dlg = new AgentConfigDialog(this); dlg->setAgentInstance(agent); dlg->exec(); delete dlg; } } void AgentWidget::synchronizeAgent() { const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); if (!list.isEmpty()) for (AgentInstance agent : list) { agent.synchronize(); } } void AgentWidget::toggleOnline() { AgentInstance agent = ui.instanceWidget->currentAgentInstance(); if (agent.isValid()) { agent.setIsOnline(!agent.isOnline()); } } void AgentWidget::showTaskList() { AgentInstance agent = ui.instanceWidget->currentAgentInstance(); if (!agent.isValid()) { return; } QDBusInterface iface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(agent.identifier()), QStringLiteral("/Debug"), QString()); QDBusReply reply = iface.call(QStringLiteral("dumpToString")); QString txt; if (reply.isValid()) { txt = reply.value(); } else { txt = reply.error().message(); } QPointer dlg = new TextDialog(this); dlg->setWindowTitle(QStringLiteral("Resource Task List")); dlg->setText(txt); dlg->exec(); delete dlg; } void AgentWidget::showChangeNotifications() { AgentInstance agent = ui.instanceWidget->currentAgentInstance(); if (!agent.isValid()) { return; } QDBusInterface iface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(agent.identifier()), QStringLiteral("/Debug"), QString()); QDBusReply reply = iface.call(QStringLiteral("dumpNotificationListToString")); QString txt; if (reply.isValid()) { txt = reply.value(); } else { txt = reply.error().message(); } QPointer dlg = new TextDialog(this); dlg->setWindowTitle(QStringLiteral("Change Notification Log")); dlg->setText(txt); dlg->exec(); delete dlg; } void AgentWidget::synchronizeTree() { const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); if (!list.isEmpty()) for (AgentInstance agent : list) { agent.synchronizeCollectionTree(); } } void AgentWidget::abortAgent() { const AgentInstance::List list = ui.instanceWidget->selectedAgentInstances(); if (!list.isEmpty()) for (const AgentInstance &agent : list) { agent.abortCurrentTask(); } } void AgentWidget::restartAgent() { AgentInstance agent = ui.instanceWidget->currentAgentInstance(); if (agent.isValid()) { agent.restart(); } } void AgentWidget::slotCloneAgent() { mCloneSource = ui.instanceWidget->currentAgentInstance(); if (!mCloneSource.isValid()) { return; } const AgentType agentType = mCloneSource.type(); if (agentType.isValid()) { AgentInstanceCreateJob *job = new AgentInstanceCreateJob(agentType, this); connect(job, &KJob::result, this, &AgentWidget::cloneAgent); job->start(); } else { qCWarning(AKONADICONSOLE_LOG) << "WTF?"; } } void AgentWidget::cloneAgent(KJob *job) { if (job->error()) { KMessageBox::error(this, QStringLiteral("Cloning agent failed: %1.").arg(job->errorText())); return; } AgentInstance cloneTarget = static_cast(job)->instance(); Q_ASSERT(cloneTarget.isValid()); Q_ASSERT(mCloneSource.isValid()); QDBusInterface sourceIface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(mCloneSource.identifier()), QStringLiteral("/Settings")); if (!sourceIface.isValid()) { qCritical() << "Unable to obtain KConfigXT D-Bus interface of source agent" << mCloneSource.identifier(); return; } QDBusInterface targetIface(QStringLiteral("org.freedesktop.Akonadi.Agent.%1").arg(cloneTarget.identifier()), QStringLiteral("/Settings")); if (!targetIface.isValid()) { qCritical() << "Unable to obtain KConfigXT D-Bus interface of target agent" << cloneTarget.identifier(); return; } cloneTarget.setName(mCloneSource.name() + QStringLiteral(" (Clone)")); // iterate over all getter methods in the source interface and call the // corresponding setter in the target interface for (int i = 0; i < sourceIface.metaObject()->methodCount(); ++i) { const QMetaMethod method = sourceIface.metaObject()->method(i); if (QByteArray(method.typeName()).isEmpty()) { // returns void continue; } const QByteArray signature(method.methodSignature()); if (signature.isEmpty()) { continue; } if (signature.startsWith("set") || !signature.contains("()")) { // setter or takes parameters // krazy:exclude=strings continue; } if (signature.startsWith("Introspect")) { // D-Bus stuff // krazy:exclude=strings continue; } const QString methodName = QLatin1String(signature.left(signature.indexOf('('))); const QDBusMessage reply = sourceIface.call(methodName); if (reply.arguments().count() != 1) { qCritical() << "call to method" << signature << "failed: " << reply.arguments() << reply.errorMessage(); continue; } const QString setterName = QStringLiteral("set") + methodName.at(0).toUpper() + methodName.mid(1); targetIface.call(setterName, reply.arguments().at(0)); } cloneTarget.reconfigure(); } void AgentWidget::currentChanged() { AgentInstance instance = ui.instanceWidget->currentAgentInstance(); ui.removeButton->setEnabled(instance.isValid()); ui.configButton->setEnabled(instance.isValid()); ui.syncButton->setEnabled(instance.isValid()); ui.restartButton->setEnabled(instance.isValid()); if (instance.isValid()) { ui.identifierLabel->setText(instance.identifier()); ui.typeLabel->setText(instance.type().name()); QString onlineStatus = (instance.isOnline() ? QStringLiteral("Online") : QStringLiteral("Offline")); QString agentStatus; switch (instance.status()) { case AgentInstance::Idle: agentStatus = i18nc("agent is in an idle state", "Idle"); break; case AgentInstance::Running: agentStatus = i18nc("agent is running", "Running (%1%)", instance.progress()); break; case AgentInstance::Broken: agentStatus = i18nc("agent is broken somehow", "Broken"); break; case AgentInstance::NotConfigured: agentStatus = i18nc("agent is not yet configured", "Not Configured"); break; } ui.statusLabel->setText( i18nc("Two statuses, for example \"Online, Running (66%)\" or \"Offline, Broken\"", "%1, %2", onlineStatus, agentStatus)); ui.statusMessageLabel->setText(instance.statusMessage()); ui.capabilitiesLabel->setText(instance.type().capabilities().join(QStringLiteral(", "))); ui.mimeTypeLabel->setText(instance.type().mimeTypes().join(QStringLiteral(", "))); } else { ui.identifierLabel->setText(QString()); ui.typeLabel->setText(QString()); ui.statusLabel->setText(QString()); ui.capabilitiesLabel->setText(QString()); ui.mimeTypeLabel->setText(QString()); } } void AgentWidget::showContextMenu(const QPoint &pos) { QMenu menu(this); menu.addAction(QIcon::fromTheme(QStringLiteral("list-add")), QStringLiteral("Add Agent..."), this, &AgentWidget::addAgent); menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), QStringLiteral("Clone Agent"), this, &AgentWidget::slotCloneAgent); menu.addSeparator(); menu.addMenu(mSyncMenu); menu.addAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), QStringLiteral("Abort Activity"), this, &AgentWidget::abortAgent); menu.addAction(QIcon::fromTheme(QStringLiteral("system-reboot")), QStringLiteral("Restart Agent"), this, &AgentWidget::restartAgent); //FIXME: Is using system-reboot icon here a good idea? menu.addAction(QIcon::fromTheme(QStringLiteral("network-disconnect")), QStringLiteral("Toggle Online/Offline"), this, &AgentWidget::toggleOnline); menu.addAction(QStringLiteral("Show task list"), this, &AgentWidget::showTaskList); menu.addAction(QStringLiteral("Show change-notification log"), this, &AgentWidget::showChangeNotifications); menu.addMenu(mConfigMenu); menu.addAction(QIcon::fromTheme(QStringLiteral("list-remove")), QStringLiteral("Remove Agent"), this, &AgentWidget::removeAgent); menu.exec(ui.instanceWidget->mapToGlobal(pos)); } void AgentWidget::resizeEvent(QResizeEvent *event) { ui.detailsBox->setVisible(event->size().height() > 400); } diff --git a/src/jobtrackermodel.cpp b/src/jobtrackermodel.cpp index de9e1fe..2e22677 100644 --- a/src/jobtrackermodel.cpp +++ b/src/jobtrackermodel.cpp @@ -1,296 +1,296 @@ /* This file is part of Akonadi. Copyright (c) 2009 KDAB Author: Till Adam This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "jobtrackermodel.h" #include "jobtracker.h" #include #include #include #include #include #include #include #include class JobTrackerModel::Private { public: Private(const char *name, JobTrackerModel *_q) : q(_q), tracker(name) { } - int rowForParentId(int parentid) + int rowForParentId(int parentid) const { const int grandparentid = tracker.parentId(parentid); int row = -1; if (grandparentid == -1) { const QString session = tracker.sessionForId(parentid); if (!session.isEmpty()) { row = tracker.sessions().indexOf(session); } } else { // offset of the parent in the list of children of the grandparent row = tracker.rowForJob(parentid, grandparentid); } return row; } private: JobTrackerModel *const q; public: JobTracker tracker; }; JobTrackerModel::JobTrackerModel(const char *name, QObject *parent) : QAbstractItemModel(parent), d(new Private(name, this)) { connect(&d->tracker, &JobTracker::aboutToAdd, this, &JobTrackerModel::jobAboutToBeAdded); connect(&d->tracker, &JobTracker::added, this, &JobTrackerModel::jobAdded); connect(&d->tracker, &JobTracker::updated, this, &JobTrackerModel::jobsUpdated); } JobTrackerModel::~JobTrackerModel() { delete d; } JobTracker &JobTrackerModel::jobTracker() { return d->tracker; } QModelIndex JobTrackerModel::index(int row, int column, const QModelIndex &parent) const { if (column < 0 || column >= NumColumns) { return QModelIndex(); } if (!parent.isValid()) { // session, at top level if (row < 0 || row >= d->tracker.sessions().size()) { return QModelIndex(); } return createIndex(row, column, d->tracker.idForSession(d->tracker.sessions().at(row))); } if (parent.column() != 0) { return QModelIndex(); } // job, i.e. non-toplevel const int jobCount = d->tracker.jobCount(parent.internalId()); if (row < 0 || row >= jobCount) { return QModelIndex(); } return createIndex(row, column, d->tracker.jobIdAt(row, parent.internalId())); } QModelIndex JobTrackerModel::parent(const QModelIndex &idx) const { if (!idx.isValid()) { return QModelIndex(); } const int parentid = d->tracker.parentId(idx.internalId()); if (parentid == -1) { return QModelIndex(); // top level session } const int row = d->rowForParentId(parentid); if (row >= 0) { return createIndex(row, 0, parentid); } else { return QModelIndex(); } } int JobTrackerModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return d->tracker.sessions().size(); } else { if (parent.column() != 0) { return 0; } return d->tracker.jobCount(parent.internalId()); } } int JobTrackerModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return NumColumns; } static QString formatTimeWithMsec(const QTime &time) { return QString(QLocale().toString(time) + QStringLiteral(".%1").arg(time.msec(), 3, 10, QLatin1Char('0'))); } static QString formatDurationWithMsec(qint64 msecs) { QTime time(0, 0, 0); time = time.addMSecs(msecs); return time.toString(QStringLiteral("hh:mm:ss.zzz")); } QVariant JobTrackerModel::data(const QModelIndex &idx, int role) const { // top level items are sessions if (!idx.parent().isValid()) { if (role == Qt::DisplayRole) { const QStringList sessions = d->tracker.sessions(); if (idx.column() == 0 && idx.row() <= sessions.size()) { return sessions.at(idx.row()); } } } else { // not top level, so a job or subjob const int id = idx.internalId(); if (role != Qt::DisplayRole && role != Qt::ForegroundRole && role != Qt::FontRole && role != Qt::ToolTipRole && role != FailedIdRole) { // Avoid the QHash lookup for all other roles return QVariant(); } const JobInfo info = d->tracker.info(id); if (role == Qt::DisplayRole) { switch (idx.column()) { case ColumnJobId: return info.name; case ColumnCreated: return formatTimeWithMsec(info.timestamp.time()); case ColumnWaitTime: if (info.startedTimestamp.isNull() || info.timestamp.isNull()) { return QString(); } return formatDurationWithMsec(info.timestamp.msecsTo(info.startedTimestamp)); case ColumnJobDuration: if (info.endedTimestamp.isNull() || info.startedTimestamp.isNull()) { return QString(); } return formatDurationWithMsec(info.startedTimestamp.msecsTo(info.endedTimestamp)); case ColumnJobType: return info.type; case ColumnState: return info.stateAsString(); case ColumnInfo: return info.debugString; } } else if (role == Qt::ForegroundRole) { if (info.state == JobInfo::Failed) { return QColor(Qt::red); } } else if (role == Qt::FontRole) { if (info.state == JobInfo::Running) { QFont f; f.setBold(true); return f; } } else if (role == Qt::ToolTipRole) { if (info.state == JobInfo::Failed) { return info.error; } } else if (role == FailedIdRole) { return (info.state == JobInfo::Failed); } } return QVariant(); } QVariant JobTrackerModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) { switch (section) { case ColumnJobId: return QStringLiteral("Job ID"); case ColumnCreated: return QStringLiteral("Created"); case ColumnWaitTime: return QStringLiteral("Wait time"); // duration (time started - time created) case ColumnJobDuration: return QStringLiteral("Job duration"); // duration (time ended - time started) case ColumnJobType: return QStringLiteral("Job Type"); case ColumnState: return QStringLiteral("State"); case ColumnInfo: return QStringLiteral("Info"); } } } return QVariant(); } void JobTrackerModel::resetTracker() { beginResetModel(); d->tracker.clear(); endResetModel(); } bool JobTrackerModel::isEnabled() const { return d->tracker.isEnabled(); } void JobTrackerModel::setEnabled(bool on) { d->tracker.setEnabled(on); } void JobTrackerModel::jobAboutToBeAdded(int pos, int parentId) { QModelIndex parentIdx; if (parentId != -1) { const int row = d->rowForParentId(parentId); if (row >= 0) { parentIdx = createIndex(row, 0, parentId); } } beginInsertRows(parentIdx, pos, pos); } void JobTrackerModel::jobAdded() { endInsertRows(); } void JobTrackerModel::jobsUpdated(const QList< QPair< int, int > > &jobs) { // TODO group them by parent? It's likely that multiple jobs for the same // parent will come in in the same batch, isn't it? for (const auto &job : jobs) { const int pos = job.first; const int parentId = job.second; QModelIndex parentIdx; if (parentId != -1) { const int row = d->rowForParentId(parentId); if (row >= 0) { parentIdx = createIndex(row, 0, parentId); } } dataChanged(index(pos, 0, parentIdx), index(pos, 3, parentIdx)); } }