diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.cpp b/Libs/DICOM/Core/ctkDICOMScheduler.cpp index 72a5af5e23..35f35b26f2 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.cpp +++ b/Libs/DICOM/Core/ctkDICOMScheduler.cpp @@ -673,6 +673,134 @@ void ctkDICOMScheduler::stopJobsByUIDs(const QStringList& patientIDs, } } +//---------------------------------------------------------------------------- +void ctkDICOMScheduler::stopJobsByJobUIDs(const QStringList &jobUIDs) +{ + Q_D(ctkDICOMScheduler); + + if (jobUIDs.count() == 0) + { + return; + } + + QMutexLocker ml(&d->mMutex); + + // Stops jobs without a worker (in waiting) + foreach (QSharedPointer job, d->JobsQueue) + { + if (!job) + { + continue; + } + + if (job->isPersistent()) + { + continue; + } + + if (job->status() != ctkAbstractJob::JobStatus::Initialized) + { + continue; + } + + ctkDICOMJob* dicomJob = qobject_cast(job.data()); + if (!dicomJob) + { + qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + continue; + } + + if ((!dicomJob->jobUID().isEmpty() && jobUIDs.contains(dicomJob->jobUID()))) + { + job->setStatus(ctkAbstractJob::JobStatus::Stopped); + this->deleteJob(job->jobUID()); + } + } + + // Stops queued and running jobs + foreach (QSharedPointer worker, d->Workers) + { + QSharedPointer job = worker->jobShared(); + if (!job) + { + continue; + } + + if (job->isPersistent()) + { + continue; + } + + ctkDICOMJob* dicomJob = qobject_cast(job.data()); + if (!dicomJob) + { + qCritical() << Q_FUNC_INFO << " failed: unexpected type of job"; + continue; + } + + if ((!dicomJob->jobUID().isEmpty() && jobUIDs.contains(dicomJob->jobUID()))) + { + job->setStatus(ctkAbstractJob::JobStatus::Stopped); + worker->cancel(); + } + } +} + +//---------------------------------------------------------------------------- +void ctkDICOMScheduler::retryJobsByJobUIDs(const QMap &jobUIDs) +{ + Q_D(ctkDICOMScheduler); + for(QString jobUID : jobUIDs.keys()) + { + ctkDICOMJobDetail jd = jobUIDs.value(jobUID); + if (jd.JobClass == "ctkDICOMQueryJob") + { + switch (jd.DICOMLevel) + { + case ctkDICOMJob::DICOMLevels::Patients: + this->queryPatients(); + break; + case ctkDICOMJob::DICOMLevels::Studies: + this->queryStudies(jd.PatientID); + break; + case ctkDICOMJob::DICOMLevels::Series: + this->querySeries(jd.PatientID, + jd.StudyInstanceUID); + break; + case ctkDICOMJob::DICOMLevels::Instances: + this->queryInstances(jd.PatientID, + jd.StudyInstanceUID, + jd.SeriesInstanceUID); + break; + } + } + else if (jd.JobClass == "ctkDICOMRetrieveJob") + { + switch (jd.DICOMLevel) + { + case ctkDICOMJob::DICOMLevels::Patients: + logger.warn("Retrieve Patient is not implemented"); + break; + case ctkDICOMJob::DICOMLevels::Studies: + this->retrieveStudy(jd.PatientID, + jd.StudyInstanceUID); + break; + case ctkDICOMJob::DICOMLevels::Series: + this->retrieveSeries(jd.PatientID, + jd.StudyInstanceUID, + jd.SeriesInstanceUID); + break; + case ctkDICOMJob::DICOMLevels::Instances: + this->retrieveSOPInstance(jd.PatientID, + jd.StudyInstanceUID, + jd.SeriesInstanceUID, + jd.SOPInstanceUID); + break; + } + } + } +} + //---------------------------------------------------------------------------- void ctkDICOMScheduler::raiseJobsPriorityForSeries(const QStringList& selectedSeriesInstanceUIDs, QThread::Priority priority) diff --git a/Libs/DICOM/Core/ctkDICOMScheduler.h b/Libs/DICOM/Core/ctkDICOMScheduler.h index 43b50a99d0..3af7e7f2ce 100644 --- a/Libs/DICOM/Core/ctkDICOMScheduler.h +++ b/Libs/DICOM/Core/ctkDICOMScheduler.h @@ -36,6 +36,7 @@ class ctkAbstractJob; #include "ctkDICOMCoreExport.h" #include "ctkDICOMDatabase.h" class ctkDICOMJob; +class ctkDICOMJobDetail; class ctkDICOMIndexer; class ctkDICOMSchedulerPrivate; class ctkDICOMServer; @@ -170,6 +171,8 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMScheduler : public ctkAbstractScheduler const QStringList& studyInstanceUIDs = {}, const QStringList& seriesInstanceUIDs = {}, const QStringList& sopInstanceUIDs = {}); + Q_INVOKABLE void stopJobsByJobUIDs(const QStringList& jobUIDs); + Q_INVOKABLE void retryJobsByJobUIDs(const QMap& jobUIDs); Q_INVOKABLE void raiseJobsPriorityForSeries(const QStringList& selectedSeriesInstanceUIDs, QThread::Priority priority = QThread::HighestPriority); ///@} diff --git a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui index 8163986c08..466b2bd9c9 100644 --- a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui +++ b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMJobListWidget.ui @@ -89,7 +89,7 @@ - All the completed jobs will be hidden when untoggled. + All the completed jobs will be hidden if unchecked. Show all diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp index b94529ab2b..5c0e273eea 100644 --- a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp @@ -83,12 +83,12 @@ class QCenteredQStandardItemModel : public QStandardItemModel this->setData(this->index(0, Columns::JobClass), td.JobClass, Qt::ToolTipRole); QIcon statusIcon = QIcon(":/Icons/pending.svg"); - QString status = QObject::tr("queued"); + QString statusText = QObject::tr("queued"); QStandardItem *statusItem = new QStandardItem(QString("statusItem")); statusItem->setIcon(statusIcon); - statusItem->setText(status); - statusItem->setToolTip(status); this->setItem(row, Columns::Status, statusItem); + this->setData(this->index(row, Columns::Status), statusText); + this->setData(this->index(row, Columns::Status), statusText, Qt::ToolTipRole); QString DICOMLevel; if (td.JobClass != "ctkDICOMInserterJob") @@ -149,9 +149,18 @@ class QCenteredQStandardItemModel : public QStandardItemModel QStandardItem *statusItem = new QStandardItem(QString("statusItem")); statusItem->setIcon(statusIcon); - statusItem->setText(statusText); - statusItem->setToolTip(statusText); this->setItem(row, Columns::Status, statusItem); + this->setData(this->index(row, Columns::Status), statusText); + this->setData(this->index(row, Columns::Status), statusText, Qt::ToolTipRole); + } + } + + void clearCompletedJobs() + { + QList list = this->findItems(tr("completed|canceled|failed"), Qt::MatchRegularExpression, Columns::Status); + foreach (QStandardItem* item, list) + { + this->removeRow(item->row()); } } }; @@ -174,6 +183,7 @@ class ctkDICOMJobListWidgetPrivate: public Ui_ctkDICOMJobListWidget QSharedPointer Scheduler; QSharedPointer proxyModel; + QSharedPointer showAllProxyModel; QSharedPointer dataModel; }; @@ -186,6 +196,7 @@ ctkDICOMJobListWidgetPrivate::ctkDICOMJobListWidgetPrivate(ctkDICOMJobListWidget { this->Scheduler = nullptr; this->proxyModel = nullptr; + this->showAllProxyModel = nullptr; this->dataModel = nullptr; } @@ -219,13 +230,32 @@ void ctkDICOMJobListWidgetPrivate::init() this->proxyModel = QSharedPointer(new QSortFilterProxyModel); this->proxyModel->setSourceModel(this->dataModel.data()); + this->showAllProxyModel = QSharedPointer(new QSortFilterProxyModel); + this->showAllProxyModel->setSourceModel(this->proxyModel.data()); + this->showAllProxyModel->setFilterKeyColumn(QCenteredQStandardItemModel::Columns::Status); + this->JobsView->setAlternatingRowColors(false); - this->JobsView->setModel(this->proxyModel.data()); + this->JobsView->setModel(this->showAllProxyModel.data()); + + QObject::connect(this->JobsView->selectionModel(), &QItemSelectionModel::selectionChanged, + q, &ctkDICOMJobListWidget::onJobsViewSelectionChanged); QObject::connect(this->FilterLineEdit, SIGNAL(textChanged(QString)), q, SLOT(onFilterTextChanged(QString))); QObject::connect(this->ColumnComboBox, SIGNAL(currentIndexChanged(int)), q, SLOT(onFilterColumnChanged(int))); + + this->StopButton->setEnabled(false); + this->RetryButton->setEnabled(false); + + QObject::connect(this->StopButton, SIGNAL(clicked()), + q, SLOT(onStopButtonClicked())); + QObject::connect(this->RetryButton, SIGNAL(clicked()), + q, SLOT(onRetryButtonClicked())); + QObject::connect(this->ShowCompletedButton, SIGNAL(toggled(bool)), + q, SLOT(onShowCompletedButtonToggled(bool))); + QObject::connect(this->ClearCompletedButton, SIGNAL(clicked()), + q, SLOT(onClearCompletedButtonClicked())); } //---------------------------------------------------------------------------- @@ -411,3 +441,93 @@ void ctkDICOMJobListWidget::onFilterColumnChanged(int index) d->proxyModel->setFilterKeyColumn(index); } +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onJobsViewSelectionChanged() +{ + Q_D(ctkDICOMJobListWidget); + QItemSelectionModel *select = d->JobsView->selectionModel(); + bool selectionOn = select->hasSelection(); + d->StopButton->setEnabled(selectionOn); + d->RetryButton->setEnabled(selectionOn); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onStopButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + QStringList jobsUIDsToStop; + QItemSelectionModel *select = d->JobsView->selectionModel(); + QModelIndexList selectedRows = select->selectedRows(); + foreach (QModelIndex rowIndex, selectedRows) + { + int row = rowIndex.row(); + QString status = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::Status).data().toString(); + QString jobUID = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::JobUID).data().toString(); + if (status == tr("in-progress") || status == tr("queued")) + { + jobsUIDsToStop.append(jobUID); + } + } + + d->Scheduler->stopJobsByJobUIDs(jobsUIDsToStop); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onRetryButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + QMap jobsUIDsToRetry; + QItemSelectionModel *select = d->JobsView->selectionModel(); + QModelIndexList selectedRows = select->selectedRows(); + foreach (QModelIndex rowIndex, selectedRows) + { + int row = rowIndex.row(); + QString status = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::Status).data().toString(); + QString jobUID = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::JobUID).data().toString(); + if (status == tr("failed")) + { + ctkDICOMJobDetail jobDetail; + jobDetail.JobClass = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::JobClass).data().toString(); + + QString DICOMLevelString = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::DICOMLevel).data().toString(); + ctkDICOMJob::DICOMLevels DICOMLevel = ctkDICOMJob::DICOMLevels::Patients; + if (DICOMLevelString == "Studies") + { + DICOMLevel = ctkDICOMJob::DICOMLevels::Studies; + } + else if (DICOMLevelString == "Series") + { + DICOMLevel = ctkDICOMJob::DICOMLevels::Series; + } + else if (DICOMLevelString == "Instances") + { + DICOMLevel = ctkDICOMJob::DICOMLevels::Instances; + } + + jobDetail.DICOMLevel = DICOMLevel; + jobDetail.PatientID = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::PatientID).data().toString(); + jobDetail.StudyInstanceUID = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::StudyInstanceUID).data().toString(); + jobDetail.SeriesInstanceUID = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::SeriesInstanceUID).data().toString(); + jobDetail.SOPInstanceUID = d->dataModel->index(row, QCenteredQStandardItemModel::Columns::SOPInstanceUID).data().toString(); + jobsUIDsToRetry.insert(jobUID, jobDetail); + } + } + + d->Scheduler->retryJobsByJobUIDs(jobsUIDsToRetry); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onShowCompletedButtonToggled(bool toggled) +{ + Q_D(ctkDICOMJobListWidget); + QString text = toggled ? "" : tr("queued|in-progress"); + d->showAllProxyModel->setFilterRegExp(text); +} + +//---------------------------------------------------------------------------- +void ctkDICOMJobListWidget::onClearCompletedButtonClicked() +{ + Q_D(ctkDICOMJobListWidget); + d->dataModel->clearCompletedJobs(); +} + diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h index a93d11d6c9..7eefdee28b 100644 --- a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h @@ -64,6 +64,12 @@ public Q_SLOTS: void onFilterTextChanged(QString); void onFilterColumnChanged(int); + void onJobsViewSelectionChanged(); + void onStopButtonClicked(); + void onRetryButtonClicked(); + void onShowCompletedButtonToggled(bool); + void onClearCompletedButtonClicked(); + protected: QScopedPointer d_ptr;