From b7ffedd21a2265d1819d23a3d8573d5bded5737e Mon Sep 17 00:00:00 2001 From: Davide Punzo Date: Thu, 23 May 2024 18:00:17 +0200 Subject: [PATCH] ENH: Optimize observer pattern in ctkDICOMScheduler This commit addresses an efficiency issue in the implementation of the Observer pattern within the ctkDICOMScheduler and its associated UI widgets. The system had the potential to emit a large number of signals: The ctkDICOMScheduler functions as an intermediary between the UI and the underlying logic, tunneling all processing signals from the logic through itself. The UI components monitor the ctkDICOMScheduler to react to these signals. This leads to an O(N^2) complexity problem when dealing with many patients/studies/series. To mitigate this, several strategies have been implemented: 1. **Batching and Throttling**: Changes are now batched together and a throttling mechanism has been introduced. This mechanism delays the processing of changes, reducing the number of signals by waiting a certain amount of time since the last signal before sending a new one. This is particularly effective when changes often occur in bursts. 2. **Filtering**: A filtering mechanism has been added to the signals, allowing only relevant changes to be signaled to each observer. This is achieved by adding a parameter to the signals that specifies the type of changes the signal represents. 3. **Hierarchical Observers**: The hierarchical relationship of the observers has been leveraged to reduce the number of signals. Now, each object observes its nearest ancestor that has changed, rather than observing the ctkDICOMScheduler directly. --- Libs/Core/ctkJobScheduler.cpp | 104 +++++-- Libs/Core/ctkJobScheduler.h | 28 +- Libs/Core/ctkJobScheduler_p.h | 9 + Libs/DICOM/Core/ctkDICOMJob.h | 1 + Libs/DICOM/Core/ctkDICOMStorageListener.cpp | 3 + .../Cpp/ctkDICOMPatientItemWidgetTest1.cpp | 6 +- .../Cpp/ctkDICOMVisualBrowserWidgetTest1.cpp | 6 +- Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp | 154 +++++----- Libs/DICOM/Widgets/ctkDICOMJobListWidget.h | 12 +- .../Widgets/ctkDICOMPatientItemWidget.cpp | 220 +++++++++++--- .../DICOM/Widgets/ctkDICOMPatientItemWidget.h | 18 +- .../Widgets/ctkDICOMSeriesItemWidget.cpp | 247 +++++++-------- Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h | 13 +- .../Widgets/ctkDICOMServerNodeWidget2.cpp | 68 +++-- .../DICOM/Widgets/ctkDICOMServerNodeWidget2.h | 8 +- .../DICOM/Widgets/ctkDICOMStudyItemWidget.cpp | 189 +++++++----- Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h | 11 +- .../Widgets/ctkDICOMVisualBrowserWidget.cpp | 287 ++++++++++-------- .../Widgets/ctkDICOMVisualBrowserWidget.h | 16 +- 19 files changed, 867 insertions(+), 533 deletions(-) diff --git a/Libs/Core/ctkJobScheduler.cpp b/Libs/Core/ctkJobScheduler.cpp index 9080fee7ee..34ee9192d1 100644 --- a/Libs/Core/ctkJobScheduler.cpp +++ b/Libs/Core/ctkJobScheduler.cpp @@ -53,11 +53,17 @@ ctkJobSchedulerPrivate::~ctkJobSchedulerPrivate() = default; //--------------------------------------------------------------------------- void ctkJobSchedulerPrivate::init() { + Q_Q(ctkJobScheduler); QObject::connect(this, SIGNAL(queueJobsInThreadPool()), this, SLOT(onQueueJobsInThreadPool())); - this->ThreadPool = QSharedPointer(new QThreadPool()); + this->ThreadPool = QSharedPointer(new QThreadPool(this)); this->ThreadPool->setMaxThreadCount(20); + this->ThrottleTimer = QSharedPointer(new QTimer(this)); + this->ThrottleTimer->setSingleShot(true); + + QObject::connect(this->ThrottleTimer.data(), SIGNAL(timeout()), + q, SLOT(emitThrottledSignals())); } //------------------------------------------------------------------------------ @@ -144,15 +150,19 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) QMetaObject::Connection failedConnection = QObject::connect(job.data(), &ctkAbstractJob::failed, q, [q, job](){ q->onJobFailed(job.data()); }); - QMap connections = { + QMetaObject::Connection progressConnection = + QObject::connect(job.data(), SIGNAL(progressJobDetail(QVariant)), + q, SLOT(onProgressJobDetail(QVariant))); + + QMap connections = + { {"started", startedConnection}, {"userStopped", userStoppedConnection}, {"finished", finishedConnection}, {"attemptFailed", attemptFailedConnection}, - {"failed", failedConnection} + {"failed", failedConnection}, + {"progress", progressConnection}, }; - QObject::connect(job.data(), SIGNAL(progressJobDetail(QVariant)), - q, SIGNAL(progressJobDetail(QVariant))); { QMutexLocker locker(&this->QueueMutex); @@ -179,8 +189,6 @@ bool ctkJobSchedulerPrivate::insertJob(QSharedPointer job) //------------------------------------------------------------------------------ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) { - Q_Q(ctkJobScheduler); - logger.debug(QString("ctkJobScheduler: deleting job object %1 in thread %2.\n") .arg(jobUID) .arg(QString::number(reinterpret_cast(QThread::currentThreadId()), 16))); @@ -199,8 +207,7 @@ bool ctkJobSchedulerPrivate::removeJob(const QString& jobUID) QObject::disconnect(connections.value("finished")); QObject::disconnect(connections.value("attemptFailed")); QObject::disconnect(connections.value("failed")); - QObject::disconnect(job.data(), SIGNAL(progressJobDetail(QVariant)), - q, SIGNAL(progressJobDetail(QVariant))); + QObject::disconnect(connections.value("progress")); this->JobsConnections.remove(jobUID); this->JobsQueue.remove(jobUID); @@ -237,24 +244,19 @@ void ctkJobSchedulerPrivate::removeJobs(const QStringList &jobUIDs) QObject::disconnect(connections.value("finished")); QObject::disconnect(connections.value("attemptFailed")); QObject::disconnect(connections.value("failed")); - QObject::disconnect(job.data(), SIGNAL(progressJobDetail(QVariant)), q, SIGNAL(progressJobDetail(QVariant))); + QObject::disconnect(connections.value("progress")); this->JobsConnections.remove(jobUID); this->JobsQueue.remove(jobUID); } } - foreach (QVariant data, datas) - { - emit q->jobUserStopped(data); - } + emit q->jobUserStopped(datas); } //------------------------------------------------------------------------------ void ctkJobSchedulerPrivate::removeAllJobs() { - Q_Q(ctkJobScheduler); - { // The QMutexLocker is enclosed within brackets to restrict its scope and // prevent conflicts with other QMutexLockers within the scheduler's methods. @@ -278,7 +280,7 @@ void ctkJobSchedulerPrivate::removeAllJobs() QObject::disconnect(connections.value("finished")); QObject::disconnect(connections.value("attemptFailed")); QObject::disconnect(connections.value("failed")); - QObject::disconnect(job.data(), SIGNAL(progressJobDetail(QVariant)), q, SIGNAL(progressJobDetail(QVariant))); + QObject::disconnect(connections.value("progress")); this->JobsConnections.remove(jobUID); this->JobsQueue.remove(jobUID); @@ -722,18 +724,25 @@ QSharedPointer ctkJobScheduler::threadPoolShared() const //---------------------------------------------------------------------------- void ctkJobScheduler::onJobStarted(ctkAbstractJob* job) { + Q_D(ctkJobScheduler); if (!job) { return; } logger.debug(job->loggerReport(tr("started"))); - emit this->jobStarted(job->toVariant()); + + d->BatchedJobStarted.append(job->toVariant()); + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } } //---------------------------------------------------------------------------- void ctkJobScheduler::onJobUserStopped(ctkAbstractJob* job) { + Q_D(ctkJobScheduler); if (!job) { return; @@ -746,12 +755,17 @@ void ctkJobScheduler::onJobUserStopped(ctkAbstractJob* job) this->deleteWorker(jobUID); this->deleteJob(jobUID); - emit this->jobUserStopped(data); + d->BatchedJobUserStopped.append(job->toVariant()); + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } } //---------------------------------------------------------------------------- void ctkJobScheduler::onJobFinished(ctkAbstractJob* job) { + Q_D(ctkJobScheduler); if (!job) { return; @@ -764,12 +778,17 @@ void ctkJobScheduler::onJobFinished(ctkAbstractJob* job) this->deleteWorker(jobUID); this->deleteJob(jobUID); - emit this->jobFinished(data); + d->BatchedJobFinished.append(job->toVariant()); + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } } //---------------------------------------------------------------------------- void ctkJobScheduler::onJobAttemptFailed(ctkAbstractJob* job) { + Q_D(ctkJobScheduler); if (!job) { return; @@ -782,12 +801,17 @@ void ctkJobScheduler::onJobAttemptFailed(ctkAbstractJob* job) this->deleteWorker(jobUID); this->deleteJob(jobUID); - emit this->jobAttemptFailed(data); + d->BatchedJobAttemptFailed.append(job->toVariant()); + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } } //---------------------------------------------------------------------------- void ctkJobScheduler::onJobFailed(ctkAbstractJob* job) { + Q_D(ctkJobScheduler); if (!job) { return; @@ -800,5 +824,41 @@ void ctkJobScheduler::onJobFailed(ctkAbstractJob* job) this->deleteWorker(jobUID); this->deleteJob(jobUID); - emit this->jobFailed(data); + d->BatchedJobFailed.append(job->toVariant()); + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } +} + +//---------------------------------------------------------------------------- +void ctkJobScheduler::onProgressJobDetail(QVariant data) +{ + Q_D(ctkJobScheduler); + + d->BatchedJobProgress.append(data); + if (!d->ThrottleTimer->isActive()) + { + d->ThrottleTimer->start(d->ThrottleTimeInterval); + } +} + +//---------------------------------------------------------------------------- +void ctkJobScheduler::emitThrottledSignals() +{ + Q_D(ctkJobScheduler); + + emit this->jobStarted(d->BatchedJobStarted); + emit this->jobUserStopped(d->BatchedJobUserStopped); + emit this->jobFinished(d->BatchedJobFinished); + emit this->jobAttemptFailed(d->BatchedJobAttemptFailed); + emit this->jobFailed(d->BatchedJobFailed); + emit this->progressJobDetail(d->BatchedJobProgress); + + d->BatchedJobStarted.clear(); + d->BatchedJobUserStopped.clear(); + d->BatchedJobFinished.clear(); + d->BatchedJobAttemptFailed.clear(); + d->BatchedJobFailed.clear(); + d->BatchedJobProgress.clear(); } diff --git a/Libs/Core/ctkJobScheduler.h b/Libs/Core/ctkJobScheduler.h index 76241893d9..4ff945c054 100644 --- a/Libs/Core/ctkJobScheduler.h +++ b/Libs/Core/ctkJobScheduler.h @@ -106,21 +106,23 @@ class CTK_CORE_EXPORT ctkJobScheduler : public QObject QSharedPointer threadPoolShared() const; Q_SIGNALS: - void jobInitialized(QVariant data); - void jobQueued(QVariant data); - void jobStarted(QVariant data); - void jobUserStopped(QVariant data); - void jobFinished(QVariant data); - void jobAttemptFailed(QVariant data); - void jobFailed(QVariant data); - void progressJobDetail(QVariant data); + void jobInitialized(QVariant); + void jobQueued(QVariant); + void jobStarted(QList); + void jobUserStopped(QList); + void jobFinished(QList); + void jobAttemptFailed(QList); + void jobFailed(QList); + void progressJobDetail(QList); public Q_SLOTS: - virtual void onJobStarted(ctkAbstractJob* job); - virtual void onJobUserStopped(ctkAbstractJob* job); - virtual void onJobFinished(ctkAbstractJob* job); - virtual void onJobAttemptFailed(ctkAbstractJob* job); - virtual void onJobFailed(ctkAbstractJob* job); + virtual void onJobStarted(ctkAbstractJob*); + virtual void onJobUserStopped(ctkAbstractJob*); + virtual void onJobFinished(ctkAbstractJob*); + virtual void onJobAttemptFailed(ctkAbstractJob*); + virtual void onJobFailed(ctkAbstractJob*); + virtual void onProgressJobDetail(QVariant); + virtual void emitThrottledSignals(); protected: QScopedPointer d_ptr; diff --git a/Libs/Core/ctkJobScheduler_p.h b/Libs/Core/ctkJobScheduler_p.h index 53285cbcd7..0cbe0d7d03 100644 --- a/Libs/Core/ctkJobScheduler_p.h +++ b/Libs/Core/ctkJobScheduler_p.h @@ -24,6 +24,7 @@ // Qt includes #include #include +#include class QThreadPool; // ctkCore includes @@ -73,6 +74,14 @@ public Q_SLOTS: QMap> JobsQueue; QMap> JobsConnections; QMap> Workers; + QList BatchedJobStarted; + QList BatchedJobUserStopped; + QList BatchedJobFinished; + QList BatchedJobAttemptFailed; + QList BatchedJobFailed; + QList BatchedJobProgress; + QSharedPointer ThrottleTimer; + int ThrottleTimeInterval{300}; }; #endif diff --git a/Libs/DICOM/Core/ctkDICOMJob.h b/Libs/DICOM/Core/ctkDICOMJob.h index 821a1ad9f8..47aa796aa9 100644 --- a/Libs/DICOM/Core/ctkDICOMJob.h +++ b/Libs/DICOM/Core/ctkDICOMJob.h @@ -41,6 +41,7 @@ class CTK_DICOM_CORE_EXPORT ctkDICOMJob : public ctkAbstractJob { Q_OBJECT Q_ENUMS(DICOMLevel) + Q_PROPERTY(QString patientID READ patientID WRITE setPatientID); Q_PROPERTY(QString studyInstanceUID READ studyInstanceUID WRITE setStudyInstanceUID); Q_PROPERTY(QString seriesInstanceUID READ seriesInstanceUID WRITE setSeriesInstanceUID); Q_PROPERTY(QString sopInstanceUID READ sopInstanceUID WRITE setSOPInstanceUID); diff --git a/Libs/DICOM/Core/ctkDICOMStorageListener.cpp b/Libs/DICOM/Core/ctkDICOMStorageListener.cpp index e113273443..46b5481aa9 100644 --- a/Libs/DICOM/Core/ctkDICOMStorageListener.cpp +++ b/Libs/DICOM/Core/ctkDICOMStorageListener.cpp @@ -123,6 +123,8 @@ OFCondition ctkDICOMStorageListenerSCUPrivate::handleIncomingCommand(T_DIMSE_Mes reqDataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesUID); OFString studyUID; reqDataset->findAndGetOFString(DCM_StudyInstanceUID, studyUID); + OFString patientID; + reqDataset->findAndGetOFString(DCM_PatientID, patientID); emit this->listener->progress( ctkDICOMStorageListener::tr("Got STORE request for %1").arg(instanceUID.c_str())); emit this->listener->progress(0); @@ -131,6 +133,7 @@ OFCondition ctkDICOMStorageListenerSCUPrivate::handleIncomingCommand(T_DIMSE_Mes QSharedPointer jobResponseSet = QSharedPointer(new ctkDICOMJobResponseSet); jobResponseSet->setJobType(ctkDICOMJobResponseSet::JobType::StoreSOPInstance); + jobResponseSet->setPatientID(patientID.c_str()); jobResponseSet->setStudyInstanceUID(studyUID.c_str()); jobResponseSet->setSeriesInstanceUID(seriesUID.c_str()); jobResponseSet->setSOPInstanceUID(instanceUID.c_str()); diff --git a/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMPatientItemWidgetTest1.cpp b/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMPatientItemWidgetTest1.cpp index 159c2e5a35..5e87546d02 100644 --- a/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMPatientItemWidgetTest1.cpp +++ b/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMPatientItemWidgetTest1.cpp @@ -52,7 +52,7 @@ int ctkDICOMPatientItemWidgetTest1(int argc, char* argv[]) CHECK_QSTRING(widget.filteringStudyDescription(), ""); CHECK_QSTRING(widget.filteringSeriesDescription(), ""); CHECK_INT(widget.filteringDate(), ctkDICOMPatientItemWidget::DateType::Any); - CHECK_INT(widget.numberOfStudiesPerPatient(), 2); + CHECK_INT(widget.numberOfOpenedStudiesPerPatient(), 2); CHECK_INT(widget.thumbnailSize(), ctkDICOMStudyItemWidget::ThumbnailSizeOption::Medium); // Test setting and getting @@ -66,8 +66,8 @@ int ctkDICOMPatientItemWidgetTest1(int argc, char* argv[]) CHECK_QSTRING(widget.filteringSeriesDescription(), "series"); widget.setFilteringDate(ctkDICOMPatientItemWidget::DateType::LastYear); CHECK_INT(widget.filteringDate(), ctkDICOMPatientItemWidget::DateType::LastYear); - widget.setNumberOfStudiesPerPatient(6); - CHECK_INT(widget.numberOfStudiesPerPatient(), 6); + widget.setNumberOfOpenedStudiesPerPatient(6); + CHECK_INT(widget.numberOfOpenedStudiesPerPatient(), 6); widget.setThumbnailSize(ctkDICOMStudyItemWidget::ThumbnailSizeOption::Small); CHECK_INT(widget.thumbnailSize(), ctkDICOMStudyItemWidget::ThumbnailSizeOption::Small); diff --git a/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMVisualBrowserWidgetTest1.cpp b/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMVisualBrowserWidgetTest1.cpp index ad58c9c7d8..017256b0d9 100644 --- a/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMVisualBrowserWidgetTest1.cpp +++ b/Libs/DICOM/Widgets/Testing/Cpp/ctkDICOMVisualBrowserWidgetTest1.cpp @@ -63,7 +63,7 @@ int ctkDICOMVisualBrowserWidgetTest1(int argc, char* argv[]) CHECK_QSTRING(browser.filteringSeriesDescription(), ""); CHECK_QSTRING(browser.filteringModalities().at(0), "Any"); CHECK_INT(browser.filteringDate(), ctkDICOMPatientItemWidget::DateType::Any); - CHECK_INT(browser.numberOfStudiesPerPatient(), 2); + CHECK_INT(browser.numberOfOpenedStudiesPerPatient(), 2); CHECK_INT(browser.thumbnailSize(), ctkDICOMStudyItemWidget::ThumbnailSizeOption::Medium); CHECK_BOOL(browser.isSendActionVisible(), false); CHECK_BOOL(browser.isDeleteActionVisible(), true); @@ -145,8 +145,8 @@ int ctkDICOMVisualBrowserWidgetTest1(int argc, char* argv[]) CHECK_QSTRING(browser.filteringModalities().at(0), "CT"); browser.setFilteringDate(ctkDICOMPatientItemWidget::DateType::LastYear); CHECK_INT(browser.filteringDate(), ctkDICOMPatientItemWidget::DateType::LastYear); - browser.setNumberOfStudiesPerPatient(6); - CHECK_INT(browser.numberOfStudiesPerPatient(), 6); + browser.setNumberOfOpenedStudiesPerPatient(6); + CHECK_INT(browser.numberOfOpenedStudiesPerPatient(), 6); browser.setThumbnailSize(ctkDICOMStudyItemWidget::ThumbnailSizeOption::Small); CHECK_INT(browser.thumbnailSize(), ctkDICOMStudyItemWidget::ThumbnailSizeOption::Small); browser.setSendActionVisible(true); diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp index 6258077acc..66e97c9cd8 100644 --- a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.cpp @@ -720,18 +720,18 @@ void ctkDICOMJobListWidgetPrivate::disconnectScheduler() q, SLOT(onJobInitialized(QVariant))); ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobQueued(QVariant)), q, SLOT(onJobQueued(QVariant))); - ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); - ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobAttemptFailed(QVariant)), - q, SLOT(onJobAttemptFailed(QVariant))); - ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(onProgressJobDetail(QVariant))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobAttemptFailed(QList)), + q, SLOT(onJobAttemptFailed(QList))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); + ctkDICOMJobListWidget::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), + q, SLOT(onProgressJobDetail(QList))); } //---------------------------------------------------------------------------- @@ -747,18 +747,18 @@ void ctkDICOMJobListWidgetPrivate::connectScheduler() q, SLOT(onJobInitialized(QVariant))); ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobQueued(QVariant)), q, SLOT(onJobQueued(QVariant))); - ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); - ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobAttemptFailed(QVariant)), - q, SLOT(onJobAttemptFailed(QVariant))); - ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(onProgressJobDetail(QVariant))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobAttemptFailed(QList)), + q, SLOT(onJobAttemptFailed(QList))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); + ctkDICOMJobListWidget::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), + q, SLOT(onProgressJobDetail(QList))); } //---------------------------------------------------------------------------- @@ -1108,95 +1108,113 @@ void ctkDICOMJobListWidget::onJobQueued(QVariant data) ctkDICOMJobDetail td = data.value(); if(td.JobClass.isEmpty()) - { + { return; - } + } d->dataModel->updateJobStatus(td, QCenteredItemModel::Queued); } //---------------------------------------------------------------------------- -void ctkDICOMJobListWidget::onJobStarted(QVariant data) +void ctkDICOMJobListWidget::onJobStarted(QList datas) { Q_D(ctkDICOMJobListWidget); - ctkDICOMJobDetail td = data.value(); - - if(td.JobClass.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + continue; + } - d->dataModel->updateJobStatus(td, QCenteredItemModel::Running); + d->dataModel->updateJobStatus(td, QCenteredItemModel::Running); + } } //---------------------------------------------------------------------------- -void ctkDICOMJobListWidget::onJobFinished(QVariant data) +void ctkDICOMJobListWidget::onJobFinished(QList datas) { Q_D(ctkDICOMJobListWidget); - ctkDICOMJobDetail td = data.value(); - - if(td.JobClass.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + continue; + } - d->dataModel->updateJobStatus(td, QCenteredItemModel::Completed); + d->dataModel->updateJobStatus(td, QCenteredItemModel::Completed); + } } //---------------------------------------------------------------------------- -void ctkDICOMJobListWidget::onProgressJobDetail(QVariant data) +void ctkDICOMJobListWidget::onProgressJobDetail(QList datas) { Q_D(ctkDICOMJobListWidget); - ctkDICOMJobDetail td = data.value(); - - if(td.JobType == ctkDICOMJobResponseSet::JobType::None) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + + if(td.JobType == ctkDICOMJobResponseSet::JobType::None) + { + continue; + } - d->dataModel->updateProgressBar(td, d->Scheduler->dicomDatabase()); + d->dataModel->updateProgressBar(td, d->Scheduler->dicomDatabase()); + } } //---------------------------------------------------------------------------- -void ctkDICOMJobListWidget::onJobAttemptFailed(QVariant data) +void ctkDICOMJobListWidget::onJobAttemptFailed(QList datas) { Q_D(ctkDICOMJobListWidget); - ctkDICOMJobDetail td = data.value(); - - if(td.JobClass.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + + if(td.JobClass.isEmpty()) + { + continue; + } - d->dataModel->updateJobStatus(td, QCenteredItemModel::AttemptFailed); + d->dataModel->updateJobStatus(td, QCenteredItemModel::AttemptFailed); + } } //---------------------------------------------------------------------------- -void ctkDICOMJobListWidget::onJobFailed(QVariant data) +void ctkDICOMJobListWidget::onJobFailed(QList datas) { Q_D(ctkDICOMJobListWidget); - ctkDICOMJobDetail td = data.value(); - - if(td.JobClass.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); - d->dataModel->updateJobStatus(td, QCenteredItemModel::Failed); + if(td.JobClass.isEmpty()) + { + continue; + } + + d->dataModel->updateJobStatus(td, QCenteredItemModel::Failed); + } } //---------------------------------------------------------------------------- -void ctkDICOMJobListWidget::onJobUserStopped(QVariant data) +void ctkDICOMJobListWidget::onJobUserStopped(QList datas) { Q_D(ctkDICOMJobListWidget); - ctkDICOMJobDetail td = data.value(); - - if(td.JobClass.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); - d->dataModel->updateJobStatus(td, QCenteredItemModel::UserStopped); + if(td.JobClass.isEmpty()) + { + continue; + } + + d->dataModel->updateJobStatus(td, QCenteredItemModel::UserStopped); + } } //---------------------------------------------------------------------------- diff --git a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h index 613e91d119..33cb6cc517 100644 --- a/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMJobListWidget.h @@ -57,12 +57,12 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMJobListWidget : public QWidget public Q_SLOTS: void onJobInitialized(QVariant); void onJobQueued(QVariant); - void onJobStarted(QVariant); - void onJobAttemptFailed(QVariant); - void onJobFailed(QVariant); - void onJobUserStopped(QVariant); - void onJobFinished(QVariant); - void onProgressJobDetail(QVariant); + void onJobStarted(QList); + void onJobAttemptFailed(QList); + void onJobFailed(QList); + void onJobUserStopped(QList); + void onJobFinished(QList); + void onProgressJobDetail(QList); void onFilterTextChanged(QString); void onFilterColumnChanged(QString); diff --git a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp index 3879a024a0..810ce15808 100644 --- a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.cpp @@ -65,7 +65,8 @@ class ctkDICOMPatientItemWidgetPrivate : public Ui_ctkDICOMPatientItemWidget ~ctkDICOMPatientItemWidgetPrivate(); void init(QWidget* parentWidget); - + void connectScheduler(); + void disconnectScheduler(); QString getPatientItemFromPatientID(const QString& patientID); QString formatDate(const QString&); bool isStudyItemAlreadyAdded(const QString& studyItem); @@ -80,7 +81,7 @@ class ctkDICOMPatientItemWidgetPrivate : public Ui_ctkDICOMPatientItemWidget QSharedPointer Scheduler; QSharedPointer VisualDICOMBrowser; - int NumberOfStudiesPerPatient; + int NumberOfOpenedStudiesPerPatient; ctkDICOMStudyItemWidget::ThumbnailSizeOption ThumbnailSize; QString PatientItem; @@ -115,7 +116,7 @@ ctkDICOMPatientItemWidgetPrivate::ctkDICOMPatientItemWidgetPrivate(ctkDICOMPatie : q_ptr(&obj) { this->FilteringDate = ctkDICOMPatientItemWidget::DateType::Any; - this->NumberOfStudiesPerPatient = 2; + this->NumberOfOpenedStudiesPerPatient = 2; this->ThumbnailSize = ctkDICOMStudyItemWidget::ThumbnailSizeOption::Medium; this->PatientItem = ""; this->PatientID = ""; @@ -164,6 +165,48 @@ void ctkDICOMPatientItemWidgetPrivate::init(QWidget* parentWidget) q, SLOT(onPatientServersCheckableComboBoxChanged())); } +//---------------------------------------------------------------------------- +void ctkDICOMPatientItemWidgetPrivate::connectScheduler() +{ + Q_Q(ctkDICOMPatientItemWidget); + if (!this->Scheduler) + { + return; + } + + QObject::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), + q, SLOT(updateGUIFromScheduler(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); +} + +//---------------------------------------------------------------------------- +void ctkDICOMPatientItemWidgetPrivate::disconnectScheduler() +{ + Q_Q(ctkDICOMPatientItemWidget); + if (!this->Scheduler) + { + return; + } + + QObject::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), + q, SLOT(updateGUIFromScheduler(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); +} + //---------------------------------------------------------------------------- QString ctkDICOMPatientItemWidgetPrivate::getPatientItemFromPatientID(const QString& patientID) { @@ -382,7 +425,7 @@ void ctkDICOMPatientItemWidgetPrivate::createStudies() foreach (ctkDICOMStudyItemWidget* studyItemWidget, studiesMap) { studiesListWidgetLayout->addWidget(studyItemWidget); - if (cont < this->NumberOfStudiesPerPatient) + if (cont < this->NumberOfOpenedStudiesPerPatient) { studyItemWidget->setCollapsed(false); studyItemWidget->generateSeries(this->QueryOn, this->RetrieveOn); @@ -603,8 +646,8 @@ CTK_SET_CPP(ctkDICOMPatientItemWidget, const QString&, setFilteringSeriesDescrip CTK_GET_CPP(ctkDICOMPatientItemWidget, QString, filteringSeriesDescription, FilteringSeriesDescription); CTK_SET_CPP(ctkDICOMPatientItemWidget, const QStringList&, setFilteringModalities, FilteringModalities); CTK_GET_CPP(ctkDICOMPatientItemWidget, QStringList, filteringModalities, FilteringModalities); -CTK_SET_CPP(ctkDICOMPatientItemWidget, int, setNumberOfStudiesPerPatient, NumberOfStudiesPerPatient); -CTK_GET_CPP(ctkDICOMPatientItemWidget, int, numberOfStudiesPerPatient, NumberOfStudiesPerPatient); +CTK_SET_CPP(ctkDICOMPatientItemWidget, int, setNumberOfOpenedStudiesPerPatient, NumberOfOpenedStudiesPerPatient); +CTK_GET_CPP(ctkDICOMPatientItemWidget, int, numberOfOpenedStudiesPerPatient, NumberOfOpenedStudiesPerPatient); CTK_SET_CPP(ctkDICOMPatientItemWidget, const ctkDICOMStudyItemWidget::ThumbnailSizeOption&, setThumbnailSize, ThumbnailSize); CTK_GET_CPP(ctkDICOMPatientItemWidget, ctkDICOMStudyItemWidget::ThumbnailSizeOption, thumbnailSize, ThumbnailSize); @@ -634,38 +677,18 @@ QSharedPointer ctkDICOMPatientItemWidget::schedulerShared() c void ctkDICOMPatientItemWidget::setScheduler(ctkDICOMScheduler& scheduler) { Q_D(ctkDICOMPatientItemWidget); - if (d->Scheduler) - { - QObject::disconnect(d->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - this, SLOT(updateGUIFromScheduler(QVariant))); - } - + d->disconnectScheduler(); d->Scheduler = QSharedPointer(&scheduler, skipDelete); - - if (d->Scheduler) - { - QObject::connect(d->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - this, SLOT(updateGUIFromScheduler(QVariant))); - } + d->connectScheduler(); } //---------------------------------------------------------------------------- void ctkDICOMPatientItemWidget::setScheduler(QSharedPointer scheduler) { Q_D(ctkDICOMPatientItemWidget); - if (d->Scheduler) - { - QObject::disconnect(d->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - this, SLOT(updateGUIFromScheduler(QVariant))); - } - + d->disconnectScheduler(); d->Scheduler = scheduler; - - if (d->Scheduler) - { - QObject::connect(d->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - this, SLOT(updateGUIFromScheduler(QVariant))); - } + d->connectScheduler(); } //---------------------------------------------------------------------------- @@ -752,7 +775,8 @@ void ctkDICOMPatientItemWidget::addStudyItemWidget(const QString& studyItem) { studyDescription = this->tr("UNDEFINED"); } - ctkDICOMStudyItemWidget* studyItemWidget = new ctkDICOMStudyItemWidget(d->VisualDICOMBrowser.data()); + ctkDICOMStudyItemWidget* studyItemWidget = + new ctkDICOMStudyItemWidget(this, d->VisualDICOMBrowser.data()); studyItemWidget->setStudyItem(studyItem); studyItemWidget->setPatientID(d->PatientID); studyItemWidget->setStudyInstanceUID(studyInstanceUID); @@ -896,24 +920,142 @@ void ctkDICOMPatientItemWidget::generateSeriesAtToggle(bool toggled, const QStri } //------------------------------------------------------------------------------ -void ctkDICOMPatientItemWidget::updateGUIFromScheduler(const QVariant& data) +void ctkDICOMPatientItemWidget::updateGUIFromScheduler(QList datas) { Q_D(ctkDICOMPatientItemWidget); - ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) + bool updateStudies = false; + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty()) + { + d->createStudies(); + continue; + } + + if (td.PatientID != d->PatientID) + { + continue; + } + + emit this->progressJobDetail(data); + + if (td.JobType != ctkDICOMJobResponseSet::JobType::QueryStudies) + { + continue; + } + + updateStudies = true; + } + + if (updateStudies) { d->createStudies(); } +} - if (td.JobUID.isEmpty() || - td.JobType != ctkDICOMJobResponseSet::JobType::QueryStudies || - td.PatientID != d->PatientID) +//------------------------------------------------------------------------------ +void ctkDICOMPatientItemWidget::onJobStarted(QList datas) +{ + Q_D(ctkDICOMPatientItemWidget); + foreach (QVariant data, datas) { - return; + ctkDICOMJobDetail td = data.value(); + + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) + { + continue; + } + + emit this->jobStarted(data); } +} - d->createStudies(); +//------------------------------------------------------------------------------ +void ctkDICOMPatientItemWidget::onJobUserStopped(QList datas) +{ + Q_D(ctkDICOMPatientItemWidget); + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) + { + continue; + } + + emit this->jobUserStopped(data); + } +} + +//------------------------------------------------------------------------------ +void ctkDICOMPatientItemWidget::onJobFailed(QList datas) +{ + Q_D(ctkDICOMPatientItemWidget); + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) + { + continue; + } + + emit this->jobFailed(data); + } +} + +//------------------------------------------------------------------------------ +void ctkDICOMPatientItemWidget::onJobFinished(QList datas) +{ + Q_D(ctkDICOMPatientItemWidget); + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + if (td.JobType == ctkDICOMJobResponseSet::JobType::Inserter) + { + foreach (ctkDICOMStudyItemWidget* studyItemWidget, d->StudyItemWidgetsList) + { + if (!studyItemWidget) + { + continue; + } + + QTableWidget* seriesListTableWidget = studyItemWidget->seriesListTableWidget(); + for (int row = 0; row < seriesListTableWidget->rowCount(); row++) + { + for (int column = 0; column < seriesListTableWidget->columnCount(); column++) + { + ctkDICOMSeriesItemWidget* seriesItemWidget = + qobject_cast(seriesListTableWidget->cellWidget(row, column)); + if (!seriesItemWidget) + { + continue; + } + if (seriesItemWidget->referenceSeriesInserterJobUID() == td.JobUID || + seriesItemWidget->referenceInstanceInserterJobUID() == td.JobUID || + seriesItemWidget->referenceSeriesInserterJobUID() == "StorageListener" || + seriesItemWidget->referenceInstanceInserterJobUID() == "StorageListener") + { + seriesItemWidget->onJobFinished(data); + } + } + } + } + } + + if (td.JobUID.isEmpty() || + td.PatientID != d->PatientID) + { + continue; + } + + emit this->jobFinished(data); + } } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h index cdfad76798..b95fd308ba 100644 --- a/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMPatientItemWidget.h @@ -47,7 +47,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMPatientItemWidget : public QWidget Q_PROPERTY(QString patientItem READ patientItem WRITE setPatientItem); Q_PROPERTY(QString patientID READ patientID WRITE setPatientID); Q_PROPERTY(QString patientName READ patientName WRITE setPatientName); - Q_PROPERTY(int numberOfStudiesPerPatient READ numberOfStudiesPerPatient WRITE setNumberOfStudiesPerPatient); + Q_PROPERTY(int numberOfOpenedStudiesPerPatient READ numberOfOpenedStudiesPerPatient WRITE setNumberOfOpenedStudiesPerPatient); Q_PROPERTY(ctkDICOMStudyItemWidget::ThumbnailSizeOption thumbnailSize READ thumbnailSize WRITE setThumbnailSize); Q_PROPERTY(QStringList allowedServers READ allowedServers WRITE setAllowedServers); Q_PROPERTY(OperationStatus operationStatus READ operationStatus WRITE setOperationStatus); @@ -133,8 +133,8 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMPatientItemWidget : public QWidget ///@{ /// Number of non collapsed studies per patient /// 2 by default - void setNumberOfStudiesPerPatient(int numberOfStudiesPerPatient); - int numberOfStudiesPerPatient() const; + void setNumberOfOpenedStudiesPerPatient(int numberOfOpenedStudiesPerPatient); + int numberOfOpenedStudiesPerPatient() const; ///@} ///@{ @@ -208,7 +208,11 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMPatientItemWidget : public QWidget public Q_SLOTS: void generateStudies(bool query = true, bool retrieve = true); void generateSeriesAtToggle(bool toggled = true, const QString& studyItem = ""); - void updateGUIFromScheduler(const QVariant& data); + void updateGUIFromScheduler(QList); + void onJobStarted(QList); + void onJobUserStopped(QList); + void onJobFailed(QList); + void onJobFinished(QList); void onSeriesItemClicked(); void raiseSelectedSeriesJobsPriority(); void onPatientServersCheckableComboBoxChanged(); @@ -216,6 +220,12 @@ public Q_SLOTS: Q_SIGNALS: /// Emitted when the GUI finished to update after a studies query. void updateGUIFinished(); + /// Propagate jobs signals to the tree + void jobStarted(QVariant); + void jobUserStopped(QVariant); + void jobFinished(QVariant); + void jobFailed(QVariant); + void progressJobDetail(QVariant); protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp index 77ae9ca404..0db063c5cb 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.cpp @@ -40,6 +40,7 @@ #include "ctkDICOMJob.h" #include "ctkDICOMJobResponseSet.h" #include "ctkDICOMScheduler.h" +#include "ctkDICOMStudyItemWidget.h" #include "ctkDICOMThumbnailGenerator.h" // ctkDICOMWidgets includes @@ -48,6 +49,14 @@ static ctkLogger logger("org.commontk.DICOM.Widgets.DICOMSeriesItemWidget"); +//---------------------------------------------------------------------------- +static void skipDelete(QObject* obj) +{ + Q_UNUSED(obj); + // this deleter does not delete the object from memory + // useful if the pointer is not owned by the smart pointer +} + //---------------------------------------------------------------------------- class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget { @@ -60,9 +69,9 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesItemWidget& obj); ~ctkDICOMSeriesItemWidgetPrivate(); - void init(); - void connectScheduler(); - void disconnectScheduler(); + void init(ctkDICOMStudyItemWidget* top); + void connectToTop(); + void disconnectFromTop(); QString getDICOMCenterFrameFromInstances(QStringList instancesList); void createThumbnail(ctkDICOMJobDetail td); void drawModalityThumbnail(); @@ -81,6 +90,8 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget QSharedPointer DicomDatabase; QSharedPointer Scheduler; + QSharedPointer StudyWidget; + QMap Connections; QStringList AllowedServers; QString PatientID; @@ -96,6 +107,7 @@ class ctkDICOMSeriesItemWidgetPrivate : public Ui_ctkDICOMSeriesItemWidget bool RaiseJobsPriority; bool IsCloud; bool RetrieveFailed; + bool RetrieveSeries; bool IsLoaded; bool IsVisible; int ThumbnailSizePixel; @@ -126,6 +138,7 @@ ctkDICOMSeriesItemWidgetPrivate::ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesI this->IsCloud = false; this->RetrieveFailed = false; + this->RetrieveSeries = false; this->IsLoaded = false; this->IsVisible = false; this->StopJobs = false; @@ -146,17 +159,74 @@ ctkDICOMSeriesItemWidgetPrivate::ctkDICOMSeriesItemWidgetPrivate(ctkDICOMSeriesI //---------------------------------------------------------------------------- ctkDICOMSeriesItemWidgetPrivate::~ctkDICOMSeriesItemWidgetPrivate() { + this->disconnectFromTop(); } //---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::init() +void ctkDICOMSeriesItemWidgetPrivate::init(ctkDICOMStudyItemWidget* top) { Q_Q(ctkDICOMSeriesItemWidget); this->setupUi(q); + this->StudyWidget = QSharedPointer(top, skipDelete); + this->connectToTop(); + this->SeriesThumbnail->setTransformationMode(Qt::TransformationMode::SmoothTransformation); this->SeriesThumbnail->textPushButton()->setElideMode(Qt::ElideRight); this->SeriesThumbnail->setSelectedColor(QColor::Invalid); + + QObject::connect(this->SeriesThumbnail, SIGNAL(statusPushButtonClicked(bool)), + q, SLOT(onStatusPushButtonClicked(bool))); +} + +//---------------------------------------------------------------------------- +void ctkDICOMSeriesItemWidgetPrivate::connectToTop() +{ + Q_Q(ctkDICOMSeriesItemWidget); + if (!this->StudyWidget) + { + return; + } + + QMetaObject::Connection progressConnection = + QObject::connect(this->StudyWidget.data(), SIGNAL(progressJobDetail(QVariant)), + q, SLOT(updateGUIFromScheduler(QVariant))); + QMetaObject::Connection progressBarConnection = + QObject::connect(this->StudyWidget.data(), SIGNAL(progressJobDetail(QVariant)), + q, SLOT(updateSeriesProgressBar(QVariant))); + QMetaObject::Connection startedConnection = + QObject::connect(this->StudyWidget.data(), SIGNAL(jobStarted(QVariant)), + q, SLOT(onJobStarted(QVariant))); + QMetaObject::Connection userStoppedConnection = + QObject::connect(this->StudyWidget.data(), SIGNAL(jobUserStopped(QVariant)), + q, SLOT(onJobUserStopped(QVariant))); + QMetaObject::Connection failedConnection = + QObject::connect(this->StudyWidget.data(), SIGNAL(jobFailed(QVariant)), + q, SLOT(onJobFailed(QVariant))); + QMetaObject::Connection finishedConnection = + QObject::connect(this->StudyWidget.data(), SIGNAL(jobFinished(QVariant)), + q, SLOT(onJobFinished(QVariant))); + + this->Connections = + { + {"progress", progressConnection}, + {"progressBar", progressBarConnection}, + {"started", startedConnection}, + {"userStopped", userStoppedConnection}, + {"finished", finishedConnection}, + {"failed", failedConnection} + }; +} + +//---------------------------------------------------------------------------- +void ctkDICOMSeriesItemWidgetPrivate::disconnectFromTop() +{ + QObject::disconnect(this->Connections.value("progress")); + QObject::disconnect(this->Connections.value("progressBar")); + QObject::disconnect(this->Connections.value("started")); + QObject::disconnect(this->Connections.value("userStopped")); + QObject::disconnect(this->Connections.value("finished")); + QObject::disconnect(this->Connections.value("failed")); } //---------------------------------------------------------------------------- @@ -307,41 +377,31 @@ void ctkDICOMSeriesItemWidgetPrivate::createThumbnail(ctkDICOMJobDetail td) if (file.isEmpty() && (this->IsCloud || this->RetrieveFailed) && (jobType == ctkDICOMJobResponseSet::JobType::None || - jobType == ctkDICOMJobResponseSet::JobType::QueryInstances)) + jobType == ctkDICOMJobResponseSet::JobType::QueryInstances) && + this->RetrieveOn) { - if (this->RetrieveOn) - { - this->Scheduler->retrieveSOPInstance(this->PatientID, - this->StudyInstanceUID, - this->SeriesInstanceUID, - this->CentralFrameSOPInstanceUID, - this->RaiseJobsPriority ? QThread::HighestPriority : QThread::HighPriority, - this->AllowedServers); - } + this->Scheduler->retrieveSOPInstance(this->PatientID, + this->StudyInstanceUID, + this->SeriesInstanceUID, + this->CentralFrameSOPInstanceUID, + this->RaiseJobsPriority ? QThread::HighestPriority : QThread::HighPriority, + this->AllowedServers); + this->RetrieveSeries = true; return; } // Get series if (numberOfFrames > 1 && - (this->IsCloud || this->RetrieveFailed) && - ((jobSopInstanceUID == this->CentralFrameSOPInstanceUID && - (jobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || - jobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance)) || - (jobType == ctkDICOMJobResponseSet::JobType::None || - jobType == ctkDICOMJobResponseSet::JobType::QueryInstances))) - { - QList> jobs = - this->Scheduler->getJobsByDICOMUIDs({}, - {}, - {this->SeriesInstanceUID}); - if (jobs.count() == 0 && this->RetrieveOn) + this->IsCloud && + this->RetrieveSeries && + this->RetrieveOn) { - this->Scheduler->retrieveSeries(this->PatientID, - this->StudyInstanceUID, - this->SeriesInstanceUID, - this->RaiseJobsPriority ? QThread::HighestPriority : QThread::LowPriority, - this->AllowedServers); - } + this->RetrieveSeries = false; + this->Scheduler->retrieveSeries(this->PatientID, + this->StudyInstanceUID, + this->SeriesInstanceUID, + this->RaiseJobsPriority ? QThread::HighestPriority : QThread::LowPriority, + this->AllowedServers); } } @@ -680,69 +740,25 @@ void ctkDICOMSeriesItemWidgetPrivate::updateRetrieveUIOnFinished() this->SeriesThumbnail->setStatusIcon(QIcon(":/Icons/accept.svg")); } -//---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::connectScheduler() -{ - Q_Q(ctkDICOMSeriesItemWidget); - if (!this->Scheduler) - { - return; - } - - QObject::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateGUIFromScheduler(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateSeriesProgressBar(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); - QObject::connect(this->SeriesThumbnail, SIGNAL(statusPushButtonClicked(bool)), - q, SLOT(onStatusPushButtonClicked(bool))); -} - -//---------------------------------------------------------------------------- -void ctkDICOMSeriesItemWidgetPrivate::disconnectScheduler() -{ - Q_Q(ctkDICOMSeriesItemWidget); - if (!this->Scheduler) - { - return; - } - - QObject::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateGUIFromScheduler(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateSeriesProgressBar(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); -} - //---------------------------------------------------------------------------- // ctkDICOMSeriesItemWidget methods //---------------------------------------------------------------------------- -ctkDICOMSeriesItemWidget::ctkDICOMSeriesItemWidget(QWidget* parentWidget) - : Superclass(parentWidget) +ctkDICOMSeriesItemWidget::ctkDICOMSeriesItemWidget(ctkDICOMStudyItemWidget* top, QWidget* parent) + : Superclass(parent) , d_ptr(new ctkDICOMSeriesItemWidgetPrivate(*this)) { Q_D(ctkDICOMSeriesItemWidget); - d->init(); + d->init(top); } //---------------------------------------------------------------------------- ctkDICOMSeriesItemWidget::~ctkDICOMSeriesItemWidget() { + Q_D(ctkDICOMSeriesItemWidget); + + QObject::disconnect(d->SeriesThumbnail, SIGNAL(statusPushButtonClicked(bool)), + this, SLOT(onStatusPushButtonClicked(bool))); } //------------------------------------------------------------------------------ @@ -769,6 +785,8 @@ CTK_GET_CPP(ctkDICOMSeriesItemWidget, bool, isLoaded, IsLoaded); CTK_GET_CPP(ctkDICOMSeriesItemWidget, bool, isVisible, IsVisible); CTK_SET_CPP(ctkDICOMSeriesItemWidget, bool, setRetrieveFailed, RetrieveFailed); CTK_GET_CPP(ctkDICOMSeriesItemWidget, bool, retrieveFailed, RetrieveFailed); +CTK_GET_CPP(ctkDICOMSeriesItemWidget, QString, referenceSeriesInserterJobUID, ReferenceSeriesInserterJobUID); +CTK_GET_CPP(ctkDICOMSeriesItemWidget, QString, referenceInstanceInserterJobUID, ReferenceInstanceInserterJobUID); CTK_SET_CPP(ctkDICOMSeriesItemWidget, int, setThumbnailSizePixel, ThumbnailSizePixel); CTK_GET_CPP(ctkDICOMSeriesItemWidget, int, thumbnailSizePixel, ThumbnailSizePixel); @@ -799,14 +817,6 @@ void ctkDICOMSeriesItemWidget::forceRetrieve() this->generateInstances(true); } -//---------------------------------------------------------------------------- -static void skipDelete(QObject* obj) -{ - Q_UNUSED(obj); - // this deleter does not delete the object from memory - // useful if the pointer is not owned by the smart pointer -} - //---------------------------------------------------------------------------- ctkDICOMScheduler* ctkDICOMSeriesItemWidget::scheduler() const { @@ -825,18 +835,14 @@ QSharedPointer ctkDICOMSeriesItemWidget::schedulerShared() co void ctkDICOMSeriesItemWidget::setScheduler(ctkDICOMScheduler& scheduler) { Q_D(ctkDICOMSeriesItemWidget); - d->disconnectScheduler(); d->Scheduler = QSharedPointer(&scheduler, skipDelete); - d->connectScheduler(); } //---------------------------------------------------------------------------- void ctkDICOMSeriesItemWidget::setScheduler(QSharedPointer scheduler) { Q_D(ctkDICOMSeriesItemWidget); - d->disconnectScheduler(); d->Scheduler = scheduler; - d->connectScheduler(); } //---------------------------------------------------------------------------- @@ -903,12 +909,14 @@ void ctkDICOMSeriesItemWidget::updateGUIFromScheduler(const QVariant& data) Q_D(ctkDICOMSeriesItemWidget); ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty() || - (td.JobType != ctkDICOMJobResponseSet::JobType::QueryInstances && - td.JobType != ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance&& - td.JobType != ctkDICOMJobResponseSet::JobType::StoreSOPInstance) || - td.PatientID != d->PatientID || - td.StudyInstanceUID != d->StudyInstanceUID || + if (td.JobUID.isEmpty()) + { + return; + } + + if ((td.JobType != ctkDICOMJobResponseSet::JobType::QueryInstances && + td.JobType != ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance && + td.JobType != ctkDICOMJobResponseSet::JobType::StoreSOPInstance ) || td.SeriesInstanceUID != d->SeriesInstanceUID) { return; @@ -923,23 +931,19 @@ void ctkDICOMSeriesItemWidget::updateSeriesProgressBar(const QVariant& data) Q_D(ctkDICOMSeriesItemWidget); ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty() || - (td.JobType != ctkDICOMJobResponseSet::JobType::RetrieveSeries && + if (td.JobUID.isEmpty()) + { + return; + } + + if ((td.JobType != ctkDICOMJobResponseSet::JobType::RetrieveSeries && td.JobType != ctkDICOMJobResponseSet::JobType::StoreSOPInstance) || - td.PatientID != d->PatientID || - td.StudyInstanceUID != d->StudyInstanceUID || td.SeriesInstanceUID != d->SeriesInstanceUID) { return; } d->updateThumbnailProgressBar(); - - if (td.JobType == ctkDICOMJobResponseSet::JobType::StoreSOPInstance && - d->ReferenceSeriesInserterJobUID == "StorageListener") - { - d->updateRetrieveUIOnFinished(); - } } //---------------------------------------------------------------------------- @@ -952,8 +956,6 @@ void ctkDICOMSeriesItemWidget::onJobStarted(const QVariant &data) (td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID && td.SeriesInstanceUID == d->SeriesInstanceUID) { d->SeriesThumbnail->setOperationStatus(ctkThumbnailLabel::InProgress); @@ -981,8 +983,6 @@ void ctkDICOMSeriesItemWidget::onJobUserStopped(const QVariant &data) (td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID && td.SeriesInstanceUID == d->SeriesInstanceUID) { d->SeriesThumbnail->setOperationStatus(ctkThumbnailLabel::Failed); @@ -1010,8 +1010,6 @@ void ctkDICOMSeriesItemWidget::onJobFailed(const QVariant &data) (td.JobType == ctkDICOMJobResponseSet::JobType::QueryInstances || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries) && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID && td.SeriesInstanceUID == d->SeriesInstanceUID) { d->SeriesThumbnail->setOperationStatus(ctkThumbnailLabel::Failed); @@ -1041,8 +1039,6 @@ void ctkDICOMSeriesItemWidget::onJobFinished(const QVariant &data) if ((td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSeries || td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance) && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID && td.SeriesInstanceUID == d->SeriesInstanceUID) { if (td.JobType == ctkDICOMJobResponseSet::JobType::RetrieveSOPInstance) @@ -1071,19 +1067,24 @@ void ctkDICOMSeriesItemWidget::onJobFinished(const QVariant &data) return; } + if (td.JobType != ctkDICOMJobResponseSet::JobType::Inserter) + { + return; + } + QStringList instancesList = d->DicomDatabase->instancesForSeries(d->SeriesInstanceUID); int numberOfFrames = instancesList.count(); if (!d->ReferenceInstanceInserterJobUID.isEmpty() && - td.JobType == ctkDICOMJobResponseSet::JobType::Inserter && - td.JobUID == d->ReferenceInstanceInserterJobUID && + (td.JobUID == d->ReferenceInstanceInserterJobUID || + d->ReferenceInstanceInserterJobUID == "StorageListener") && numberOfFrames == 1) { d->SeriesThumbnail->setOperationStatus(ctkThumbnailLabel::Completed); d->SeriesThumbnail->setStatusIcon(QIcon(":/Icons/accept.svg")); } else if (!d->ReferenceSeriesInserterJobUID.isEmpty() && - td.JobType == ctkDICOMJobResponseSet::JobType::Inserter && - td.JobUID == d->ReferenceSeriesInserterJobUID) + (td.JobUID == d->ReferenceSeriesInserterJobUID || + d->ReferenceSeriesInserterJobUID == "StorageListener")) { d->ReferenceSeriesInserterJobUID = ""; d->updateRetrieveUIOnFinished(); diff --git a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h index 596bb77074..f57fe7bbf4 100644 --- a/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMSeriesItemWidget.h @@ -34,7 +34,9 @@ class ctkDICOMScheduler; // ctkDICOMWidgets includes #include "ctkDICOMWidgetsExport.h" + class ctkDICOMSeriesItemWidgetPrivate; +class ctkDICOMStudyItemWidget; /// \ingroup DICOM_Widgets class CTK_DICOM_WIDGETS_EXPORT ctkDICOMSeriesItemWidget : public QWidget @@ -49,6 +51,8 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMSeriesItemWidget : public QWidget Q_PROPERTY(QString seriesDescription READ seriesDescription WRITE setSeriesDescription); Q_PROPERTY(bool isCloud READ isCloud); Q_PROPERTY(bool retrieveFailed READ retrieveFailed WRITE setRetrieveFailed); + Q_PROPERTY(QString referenceSeriesInserterJobUID READ referenceSeriesInserterJobUID); + Q_PROPERTY(QString referenceInstanceInserterJobUID READ referenceInstanceInserterJobUID); Q_PROPERTY(int thumbnailSizePixel READ thumbnailSizePixel WRITE setThumbnailSizePixel); Q_PROPERTY(bool stopJobs READ stopJobs WRITE setStopJobs); Q_PROPERTY(bool raiseJobsPriority READ raiseJobsPriority WRITE setRaiseJobsPriority); @@ -56,7 +60,8 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMSeriesItemWidget : public QWidget public: typedef QWidget Superclass; - explicit ctkDICOMSeriesItemWidget(QWidget* parent = nullptr); + explicit ctkDICOMSeriesItemWidget(ctkDICOMStudyItemWidget* top = nullptr, + QWidget* parent = nullptr); virtual ~ctkDICOMSeriesItemWidget(); ///@{ @@ -126,6 +131,12 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMSeriesItemWidget : public QWidget bool retrieveFailed() const; ///@} + ///@{ + /// Return the referenceInserterJobUID + QString referenceSeriesInserterJobUID() const; + QString referenceInstanceInserterJobUID() const; + ///@} + /// Series has been loaded by the parent widget bool isLoaded() const; diff --git a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp index a950a2b966..4237b9ae18 100644 --- a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.cpp @@ -397,14 +397,14 @@ void ctkDICOMServerNodeWidget2Private::disconnectScheduler() return; } - ctkDICOMServerNodeWidget2::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - ctkDICOMServerNodeWidget2::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - ctkDICOMServerNodeWidget2::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); - ctkDICOMServerNodeWidget2::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); } //---------------------------------------------------------------------------- @@ -416,14 +416,14 @@ void ctkDICOMServerNodeWidget2Private::connectScheduler() return; } - ctkDICOMServerNodeWidget2::connect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - ctkDICOMServerNodeWidget2::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - ctkDICOMServerNodeWidget2::connect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); - ctkDICOMServerNodeWidget2::connect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); } //---------------------------------------------------------------------------- @@ -1204,38 +1204,50 @@ void ctkDICOMServerNodeWidget2::onVerifyCurrentServerNode() //---------------------------------------------------------------------------- -void ctkDICOMServerNodeWidget2::onJobStarted(QVariant data) +void ctkDICOMServerNodeWidget2::onJobStarted(QList datas) { Q_D(ctkDICOMServerNodeWidget2); - ctkDICOMJobDetail td = data.value(); - d->updateServerVerification(td, QString(tr("in-progress"))); + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + d->updateServerVerification(td, QString(tr("in-progress"))); + } this->updateGUIState(); } //---------------------------------------------------------------------------- -void ctkDICOMServerNodeWidget2::onJobUserStopped(QVariant data) +void ctkDICOMServerNodeWidget2::onJobUserStopped(QList datas) { Q_D(ctkDICOMServerNodeWidget2); - ctkDICOMJobDetail td = data.value(); - d->updateServerVerification(td, QString(tr("user-stopped"))); + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + d->updateServerVerification(td, QString(tr("user-stopped"))); + } this->updateGUIState(); } //---------------------------------------------------------------------------- -void ctkDICOMServerNodeWidget2::onJobFailed(QVariant data) +void ctkDICOMServerNodeWidget2::onJobFailed(QList datas) { Q_D(ctkDICOMServerNodeWidget2); - ctkDICOMJobDetail td = data.value(); - d->updateServerVerification(td, QString(tr("failed"))); + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + d->updateServerVerification(td, QString(tr("failed"))); + } this->updateGUIState(); } //---------------------------------------------------------------------------- -void ctkDICOMServerNodeWidget2::onJobFinished(QVariant data) +void ctkDICOMServerNodeWidget2::onJobFinished(QList datas) { Q_D(ctkDICOMServerNodeWidget2); - ctkDICOMJobDetail td = data.value(); - d->updateServerVerification(td, QString(tr("success"))); + foreach (QVariant data, datas) + { + ctkDICOMJobDetail td = data.value(); + d->updateServerVerification(td, QString(tr("success"))); + } this->updateGUIState(); } diff --git a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h index ed5c5c3406..e766b65420 100644 --- a/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h +++ b/Libs/DICOM/Widgets/ctkDICOMServerNodeWidget2.h @@ -110,10 +110,10 @@ public Q_SLOTS: /// Verify the current selected row (different from the checked rows) void onVerifyCurrentServerNode(); - void onJobStarted(QVariant); - void onJobUserStopped(QVariant); - void onJobFailed(QVariant); - void onJobFinished(QVariant); + void onJobStarted(QList); + void onJobUserStopped(QList); + void onJobFailed(QList); + void onJobFinished(QList); void readSettings(); void saveSettings(); diff --git a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp index bfd984d90c..d5d9e497ab 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.cpp @@ -37,6 +37,7 @@ #include "ctkDICOMScheduler.h" // ctkDICOMWidgets includes +#include "ctkDICOMPatientItemWidget.h" #include "ctkDICOMStudyItemWidget.h" #include "ui_ctkDICOMStudyItemWidget.h" @@ -64,9 +65,9 @@ class ctkDICOMStudyItemWidgetPrivate : public Ui_ctkDICOMStudyItemWidget ctkDICOMStudyItemWidgetPrivate(ctkDICOMStudyItemWidget& obj); ~ctkDICOMStudyItemWidgetPrivate(); - void init(QWidget* parentWidget); - void connectScheduler(); - void disconnectScheduler(); + void init(ctkDICOMPatientItemWidget* parent, QWidget* root); + void connectToTop(); + void disconnectFromTop(); void updateColumnsWidths(); void createSeries(); int getScreenWidth(); @@ -83,7 +84,9 @@ class ctkDICOMStudyItemWidgetPrivate : public Ui_ctkDICOMStudyItemWidget QSharedPointer DicomDatabase; QSharedPointer Scheduler; + QSharedPointer PatientWidget; QSharedPointer VisualDICOMBrowser; + QMap Connections; ctkDICOMStudyItemWidget::ThumbnailSizeOption ThumbnailSize; int ThumbnailSizePixel; @@ -127,8 +130,7 @@ ctkDICOMStudyItemWidgetPrivate::ctkDICOMStudyItemWidgetPrivate(ctkDICOMStudyItem //---------------------------------------------------------------------------- ctkDICOMStudyItemWidgetPrivate::~ctkDICOMStudyItemWidgetPrivate() { - Q_Q(ctkDICOMStudyItemWidget); - + this->disconnectFromTop(); for (int row = 0; row < this->SeriesListTableWidget->rowCount(); row++) { for (int column = 0; column < this->SeriesListTableWidget->columnCount(); column++) @@ -140,19 +142,21 @@ ctkDICOMStudyItemWidgetPrivate::~ctkDICOMStudyItemWidgetPrivate() continue; } - q->disconnect(seriesItemWidget, SIGNAL(customContextMenuRequested(const QPoint&)), - this->VisualDICOMBrowser.data(), SLOT(showSeriesContextMenu(const QPoint&))); + QObject::disconnect(seriesItemWidget, SIGNAL(customContextMenuRequested(const QPoint&)), + this->VisualDICOMBrowser.data(), SLOT(showSeriesContextMenu(const QPoint&))); } } } //---------------------------------------------------------------------------- -void ctkDICOMStudyItemWidgetPrivate::init(QWidget* parentWidget) +void ctkDICOMStudyItemWidgetPrivate::init(ctkDICOMPatientItemWidget* top, QWidget* parent) { Q_Q(ctkDICOMStudyItemWidget); this->setupUi(q); - this->VisualDICOMBrowser = QSharedPointer(parentWidget, skipDelete); + this->PatientWidget = QSharedPointer(top, skipDelete); + this->connectToTop(); + this->VisualDICOMBrowser = QSharedPointer(parent, skipDelete); this->StudyDescriptionTextBrowser->hide(); this->StudyDescriptionTextBrowser->setReadOnly(true); @@ -160,52 +164,54 @@ void ctkDICOMStudyItemWidgetPrivate::init(QWidget* parentWidget) this->OperationStatusPushButton->hide(); - q->connect(this->StudySelectionCheckBox, SIGNAL(clicked(bool)), - q, SLOT(onStudySelectionClicked(bool))); - q->connect(this->OperationStatusPushButton, SIGNAL(clicked(bool)), - q, SLOT(onOperationStatusClicked(bool))); + QObject::connect(this->StudySelectionCheckBox, SIGNAL(clicked(bool)), + q, SLOT(onStudySelectionClicked(bool))); + QObject::connect(this->OperationStatusPushButton, SIGNAL(clicked(bool)), + q, SLOT(onOperationStatusClicked(bool))); } //------------------------------------------------------------------------------ -void ctkDICOMStudyItemWidgetPrivate::connectScheduler() +void ctkDICOMStudyItemWidgetPrivate::connectToTop() { Q_Q(ctkDICOMStudyItemWidget); - if (!this->Scheduler) + if (!this->PatientWidget) { return; } - QObject::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), + QMetaObject::Connection progressConnection = + QObject::connect(this->PatientWidget.data(), SIGNAL(progressJobDetail(QVariant)), q, SLOT(updateGUIFromScheduler(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), + QMetaObject::Connection startedConnection = + QObject::connect(this->PatientWidget.data(), SIGNAL(jobStarted(QVariant)), + q, SLOT(onJobStarted(QVariant))); + QMetaObject::Connection userStoppedConnection = + QObject::connect(this->PatientWidget.data(), SIGNAL(jobUserStopped(QVariant)), q, SLOT(onJobUserStopped(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); + QMetaObject::Connection failedConnection = + QObject::connect(this->PatientWidget.data(), SIGNAL(jobFailed(QVariant)), + q, SLOT(onJobFailed(QVariant))); + QMetaObject::Connection finishedConnection = + QObject::connect(this->PatientWidget.data(), SIGNAL(jobFinished(QVariant)), + q, SLOT(onJobFinished(QVariant))); + this->Connections = + { + {"progress", progressConnection}, + {"started", startedConnection}, + {"userStopped", userStoppedConnection}, + {"finished", finishedConnection}, + {"failed", failedConnection} + }; } //------------------------------------------------------------------------------ -void ctkDICOMStudyItemWidgetPrivate::disconnectScheduler() +void ctkDICOMStudyItemWidgetPrivate::disconnectFromTop() { - Q_Q(ctkDICOMStudyItemWidget); - if (!this->Scheduler) - { - return; - } - - QObject::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateGUIFromScheduler(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); + QObject::disconnect(this->Connections.value("progress")); + QObject::disconnect(this->Connections.value("started")); + QObject::disconnect(this->Connections.value("userStopped")); + QObject::disconnect(this->Connections.value("finished")); + QObject::disconnect(this->Connections.value("failed")); } //------------------------------------------------------------------------------ @@ -446,12 +452,12 @@ ctkDICOMSeriesItemWidget* ctkDICOMStudyItemWidgetPrivate::isSeriesItemAlreadyAdd // ctkDICOMStudyItemWidget methods //---------------------------------------------------------------------------- -ctkDICOMStudyItemWidget::ctkDICOMStudyItemWidget(QWidget* parentWidget) - : Superclass(parentWidget) +ctkDICOMStudyItemWidget::ctkDICOMStudyItemWidget(ctkDICOMPatientItemWidget* top, QWidget* parent) + : Superclass(parent) , d_ptr(new ctkDICOMStudyItemWidgetPrivate(*this)) { Q_D(ctkDICOMStudyItemWidget); - d->init(parentWidget); + d->init(top, parent); } //---------------------------------------------------------------------------- @@ -587,18 +593,14 @@ QSharedPointer ctkDICOMStudyItemWidget::schedulerShared() con void ctkDICOMStudyItemWidget::setScheduler(ctkDICOMScheduler& scheduler) { Q_D(ctkDICOMStudyItemWidget); - d->disconnectScheduler(); d->Scheduler = QSharedPointer(&scheduler, skipDelete); - d->connectScheduler(); } //---------------------------------------------------------------------------- void ctkDICOMStudyItemWidget::setScheduler(QSharedPointer scheduler) { Q_D(ctkDICOMStudyItemWidget); - d->disconnectScheduler(); d->Scheduler = scheduler; - d->connectScheduler(); } //---------------------------------------------------------------------------- @@ -675,7 +677,7 @@ ctkDICOMSeriesItemWidget* ctkDICOMStudyItemWidget::addSeriesItemWidget(int table } QString seriesNumber = d->DicomDatabase->fieldForSeries("SeriesNumber", seriesItem); - ctkDICOMSeriesItemWidget* seriesItemWidget = new ctkDICOMSeriesItemWidget; + ctkDICOMSeriesItemWidget* seriesItemWidget = new ctkDICOMSeriesItemWidget(this); seriesItemWidget->setSeriesItem(seriesItem); seriesItemWidget->setPatientID(d->PatientID); seriesItemWidget->setStudyInstanceUID(d->StudyInstanceUID); @@ -771,12 +773,17 @@ void ctkDICOMStudyItemWidget::updateGUIFromScheduler(const QVariant& data) if (td.JobUID.isEmpty()) { d->createSeries(); + return; } - if (td.JobUID.isEmpty() || - td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries || - td.PatientID != d->PatientID || - td.StudyInstanceUID != d->StudyInstanceUID) + if (td.StudyInstanceUID != d->StudyInstanceUID) + { + return; + } + + emit this->progressJobDetail(data); + + if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) { return; } @@ -790,15 +797,22 @@ void ctkDICOMStudyItemWidget::onJobStarted(const QVariant &data) Q_D(ctkDICOMStudyItemWidget); ctkDICOMJobDetail td = data.value(); - if (!td.JobUID.isEmpty() && - td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID) + if (td.JobUID.isEmpty() || + td.StudyInstanceUID != d->StudyInstanceUID) { - d->Status = ctkDICOMStudyItemWidget::InProgress; - d->OperationStatusPushButton->show(); - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/pending.svg")); + return; } + + emit this->jobStarted(data); + + if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + { + return; + } + + d->Status = ctkDICOMStudyItemWidget::InProgress; + d->OperationStatusPushButton->show(); + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/pending.svg")); } //------------------------------------------------------------------------------ @@ -807,14 +821,21 @@ void ctkDICOMStudyItemWidget::onJobUserStopped(const QVariant &data) Q_D(ctkDICOMStudyItemWidget); ctkDICOMJobDetail td = data.value(); - if (!td.JobUID.isEmpty() && - td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID) + if (td.JobUID.isEmpty() || + td.StudyInstanceUID != d->StudyInstanceUID) { - d->Status = ctkDICOMStudyItemWidget::Failed; - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); + return; + } + + emit this->jobUserStopped(data); + + if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + { + return; } + + d->Status = ctkDICOMStudyItemWidget::Failed; + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); } //------------------------------------------------------------------------------ @@ -823,14 +844,21 @@ void ctkDICOMStudyItemWidget::onJobFailed(const QVariant &data) Q_D(ctkDICOMStudyItemWidget); ctkDICOMJobDetail td = data.value(); - if (!td.JobUID.isEmpty() && - td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID) + if (td.JobUID.isEmpty() || + td.StudyInstanceUID != d->StudyInstanceUID) + { + return; + } + + emit this->jobFailed(data); + + if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) { - d->Status = ctkDICOMStudyItemWidget::Failed; - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); + return; } + + d->Status = ctkDICOMStudyItemWidget::Failed; + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/error_red.svg")); } //------------------------------------------------------------------------------ @@ -839,14 +867,21 @@ void ctkDICOMStudyItemWidget::onJobFinished(const QVariant &data) Q_D(ctkDICOMStudyItemWidget); ctkDICOMJobDetail td = data.value(); - if (!td.JobUID.isEmpty() && - td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries && - td.PatientID == d->PatientID && - td.StudyInstanceUID == d->StudyInstanceUID) + if (td.JobUID.isEmpty() || + td.StudyInstanceUID != d->StudyInstanceUID) { - d->Status = ctkDICOMStudyItemWidget::Completed; - d->OperationStatusPushButton->setIcon(QIcon(":/Icons/accept.svg")); + return; } + + emit this->jobFinished(data); + + if (td.JobType != ctkDICOMJobResponseSet::JobType::QuerySeries) + { + return; + } + + d->Status = ctkDICOMStudyItemWidget::Completed; + d->OperationStatusPushButton->setIcon(QIcon(":/Icons/accept.svg")); } //------------------------------------------------------------------------------ diff --git a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h index 4b22c4e96c..86755c8196 100644 --- a/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMStudyItemWidget.h @@ -40,6 +40,8 @@ class ctkDICOMScheduler; // ctkDICOMWidgets includes #include "ctkDICOMSeriesItemWidget.h" + +class ctkDICOMPatientItemWidget; class ctkDICOMSeriesItemWidget; class ctkDICOMStudyItemWidgetPrivate; @@ -66,7 +68,8 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMStudyItemWidget : public QWidget public: typedef QWidget Superclass; - explicit ctkDICOMStudyItemWidget(QWidget* parent = nullptr); + explicit ctkDICOMStudyItemWidget(ctkDICOMPatientItemWidget* top = nullptr, + QWidget* parent = nullptr); virtual ~ctkDICOMStudyItemWidget(); ///@{ @@ -222,6 +225,12 @@ public Q_SLOTS: Q_SIGNALS: /// Emitted when the GUI finished to update after a series query. void updateGUIFinished(); + /// Propagate jobs signals to the tree + void jobStarted(QVariant); + void jobUserStopped(QVariant); + void jobFinished(QVariant); + void jobFailed(QVariant); + void progressJobDetail(QVariant); protected: QScopedPointer d_ptr; diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp index 98c54274c8..a10a870770 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp @@ -144,7 +144,7 @@ class ctkDICOMVisualBrowserWidgetPrivate : public Ui_ctkDICOMVisualBrowserWidget void importDirectory(QString directory, ctkDICOMVisualBrowserWidget::ImportDirectoryMode mode); void importFiles(const QStringList& files, ctkDICOMVisualBrowserWidget::ImportDirectoryMode mode); void importOldSettings(); - void restoreSearchIcon(); + void configureSearchIcon(); void showUpdateSchemaDialog(); void updateModalityCheckableComboBox(); void createPatients(bool queryRetrieve = true, @@ -240,7 +240,7 @@ class ctkDICOMVisualBrowserWidgetPrivate : public Ui_ctkDICOMVisualBrowserWidget QStringList PreviousFilteringModalities; QStringList FilteringModalities; - int NumberOfStudiesPerPatient; + int NumberOfOpenedStudiesPerPatient; ctkDICOMStudyItemWidget::ThumbnailSizeOption ThumbnailSize; bool SendActionVisible; bool DeleteActionVisible; @@ -280,7 +280,7 @@ ctkDICOMVisualBrowserWidgetPrivate::ctkDICOMVisualBrowserWidgetPrivate(ctkDICOMV this->DefaultDatabaseDirectory = ""; this->DatabaseDirectory = ""; - this->NumberOfStudiesPerPatient = 2; + this->NumberOfOpenedStudiesPerPatient = 2; this->ThumbnailSize = ctkDICOMStudyItemWidget::ThumbnailSizeOption::Medium; this->SendActionVisible = false; this->DeleteActionVisible = true; @@ -394,6 +394,7 @@ void ctkDICOMVisualBrowserWidgetPrivate::init() } toggleQueryRetrieveAction->setChecked(settings.value("DICOM/QueryRetrieveEnabled", "").toBool()); queryRetrieveButtonMenu->addAction(toggleQueryRetrieveAction); + this->configureSearchIcon(); QObject::connect(toggleQueryRetrieveAction, SIGNAL(toggled(bool)), q, SLOT(onQueryRetrieveOptionToggled(bool))); @@ -490,16 +491,16 @@ void ctkDICOMVisualBrowserWidgetPrivate::disconnectScheduler() return; } - QObject::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateGUIFromScheduler(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), + q, SLOT(updateGUIFromScheduler(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); + QObject::disconnect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); } //---------------------------------------------------------------------------- @@ -511,16 +512,16 @@ void ctkDICOMVisualBrowserWidgetPrivate::connectScheduler() return; } - q->connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QVariant)), - q, SLOT(updateGUIFromScheduler(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobStarted(QVariant)), - q, SLOT(onJobStarted(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QVariant)), - q, SLOT(onJobUserStopped(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QVariant)), - q, SLOT(onJobFailed(QVariant))); - QObject::connect(this->Scheduler.data(), SIGNAL(jobFinished(QVariant)), - q, SLOT(onJobFinished(QVariant))); + QObject::connect(this->Scheduler.data(), SIGNAL(progressJobDetail(QList)), + q, SLOT(updateGUIFromScheduler(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobStarted(QList)), + q, SLOT(onJobStarted(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobUserStopped(QList)), + q, SLOT(onJobUserStopped(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobFailed(QList)), + q, SLOT(onJobFailed(QList))); + QObject::connect(this->Scheduler.data(), SIGNAL(jobFinished(QList)), + q, SLOT(onJobFinished(QList))); } //---------------------------------------------------------------------------- @@ -580,7 +581,7 @@ void ctkDICOMVisualBrowserWidgetPrivate::importOldSettings() } //---------------------------------------------------------------------------- -void ctkDICOMVisualBrowserWidgetPrivate::restoreSearchIcon() +void ctkDICOMVisualBrowserWidgetPrivate::configureSearchIcon() { QSettings settings; bool queryRetrieveEnabled = settings.value("DICOM/QueryRetrieveEnabled", "").toBool(); @@ -1079,7 +1080,7 @@ void ctkDICOMVisualBrowserWidgetPrivate::retrieveSeries() } this->updateFiltersWarnings(); - this->restoreSearchIcon(); + this->configureSearchIcon(); foreach (ctkDICOMSeriesItemWidget* seriesItemWidget, seriesWidgetsList) { @@ -1713,8 +1714,8 @@ CTK_GET_CPP(ctkDICOMVisualBrowserWidget, QString, filteringStudyDescription, Fil CTK_GET_CPP(ctkDICOMVisualBrowserWidget, ctkDICOMPatientItemWidget::DateType, filteringDate, FilteringDate); CTK_GET_CPP(ctkDICOMVisualBrowserWidget, QString, filteringSeriesDescription, FilteringSeriesDescription); CTK_GET_CPP(ctkDICOMVisualBrowserWidget, QStringList, filteringModalities, FilteringModalities); -CTK_SET_CPP(ctkDICOMVisualBrowserWidget, int, setNumberOfStudiesPerPatient, NumberOfStudiesPerPatient); -CTK_GET_CPP(ctkDICOMVisualBrowserWidget, int, numberOfStudiesPerPatient, NumberOfStudiesPerPatient); +CTK_SET_CPP(ctkDICOMVisualBrowserWidget, int, setNumberOfOpenedStudiesPerPatient, NumberOfOpenedStudiesPerPatient); +CTK_GET_CPP(ctkDICOMVisualBrowserWidget, int, numberOfOpenedStudiesPerPatient, NumberOfOpenedStudiesPerPatient); CTK_SET_CPP(ctkDICOMVisualBrowserWidget, const ctkDICOMStudyItemWidget::ThumbnailSizeOption&, setThumbnailSize, ThumbnailSize); CTK_GET_CPP(ctkDICOMVisualBrowserWidget, ctkDICOMStudyItemWidget::ThumbnailSizeOption, thumbnailSize, ThumbnailSize); CTK_SET_CPP(ctkDICOMVisualBrowserWidget, bool, setSendActionVisible, SendActionVisible); @@ -2022,7 +2023,7 @@ int ctkDICOMVisualBrowserWidget::addPatientItemWidget(const QString& patientItem patientItemWidget->setFilteringSeriesDescription(d->FilteringSeriesDescription); patientItemWidget->setFilteringModalities(d->FilteringModalities); patientItemWidget->setThumbnailSize(d->ThumbnailSize); - patientItemWidget->setNumberOfStudiesPerPatient(d->NumberOfStudiesPerPatient); + patientItemWidget->setNumberOfOpenedStudiesPerPatient(d->NumberOfOpenedStudiesPerPatient); patientItemWidget->setContextMenuPolicy(Qt::CustomContextMenu); this->connect(patientItemWidget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showPatientContextMenu(const QPoint&))); @@ -2360,7 +2361,7 @@ void ctkDICOMVisualBrowserWidget::onIndexingComplete(int patientsAdded, int stud d->ProgressFrame->hide(); d->ProgressDetailLineEdit->hide(); - d->restoreSearchIcon(); + d->configureSearchIcon(); // allow users of this widget to know that the process has finished emit directoryImported(); @@ -2915,165 +2916,185 @@ void ctkDICOMVisualBrowserWidget::onQueryRetrieveOptionToggled(bool toggled) Q_D(ctkDICOMVisualBrowserWidget); QSettings settings; settings.setValue("DICOM/QueryRetrieveEnabled", toggled); - d->restoreSearchIcon(); + d->configureSearchIcon(); } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::updateGUIFromScheduler(const QVariant& data) +void ctkDICOMVisualBrowserWidget::updateGUIFromScheduler(QList datas) { Q_D(ctkDICOMVisualBrowserWidget); - ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) - { - d->updateFiltersWarnings(); - return; - } - else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies || - td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) + bool updatePatients = false; + foreach (QVariant data, datas) { + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty()) + { + d->updateFiltersWarnings(); + continue; + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies || + td.JobType == ctkDICOMJobResponseSet::JobType::QuerySeries) + { + d->updateFiltersWarnings(); + continue; + } + else if (td.JobType != ctkDICOMJobResponseSet::JobType::QueryPatients) + { + continue; + } + d->updateFiltersWarnings(); - return; - } - else if (td.JobType != ctkDICOMJobResponseSet::JobType::QueryPatients) - { - return; + if (td.NumberOfDataSets == 0) + { + d->WarningPushButton->setText(tr("The patients query provided no results. Please refine your filters.")); + d->WarningPushButton->show(); + d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); + } + else + { + d->WarningPushButton->hide(); + updatePatients = true; + } } - d->updateFiltersWarnings(); - if (td.NumberOfDataSets == 0) - { - d->WarningPushButton->setText(tr("The patients query provided no results. Please refine your filters.")); - d->WarningPushButton->show(); - d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); - } - else + if (updatePatients) { - d->WarningPushButton->hide(); + d->createPatients(); } - - d->createPatients(); } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::onJobStarted(const QVariant& data) +void ctkDICOMVisualBrowserWidget::onJobStarted(QList datas) { Q_D(ctkDICOMVisualBrowserWidget); - ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty()) + { + continue; + } - if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) - { - d->updateFiltersWarnings(); - d->SearchMenuButton->setIcon(QIcon(":/Icons/wait.svg")); - } - else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) - { - QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); - int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); - ctkDICOMPatientItemWidget* patientItemWidget = - qobject_cast(d->PatientsTabWidget->widget(patientIndex)); - if (patientItemWidget) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) { - patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::InProgress); + d->updateFiltersWarnings(); + d->SearchMenuButton->setIcon(QIcon(":/Icons/wait.svg")); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); + int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); + ctkDICOMPatientItemWidget* patientItemWidget = + qobject_cast(d->PatientsTabWidget->widget(patientIndex)); + if (patientItemWidget) + { + patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::InProgress); + } + d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_pending.svg")); } - d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_pending.svg")); } } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::onJobUserStopped(const QVariant& data) +void ctkDICOMVisualBrowserWidget::onJobUserStopped(QList datas) { Q_D(ctkDICOMVisualBrowserWidget); - ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty()) + { + continue; + } - if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) - { - d->updateFiltersWarnings(); - d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); - } - else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) - { - QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); - int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); - ctkDICOMPatientItemWidget* patientItemWidget = - qobject_cast(d->PatientsTabWidget->widget(patientIndex)); - if (patientItemWidget) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) { - patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::Failed); + d->updateFiltersWarnings(); + d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); + int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); + ctkDICOMPatientItemWidget* patientItemWidget = + qobject_cast(d->PatientsTabWidget->widget(patientIndex)); + if (patientItemWidget) + { + patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::Failed); + } + d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_failed.svg")); } - d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_failed.svg")); } } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::onJobFailed(const QVariant& data) +void ctkDICOMVisualBrowserWidget::onJobFailed(QList datas) { Q_D(ctkDICOMVisualBrowserWidget); - ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty()) + { + continue; + } - if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) - { - d->updateFiltersWarnings(); - d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); - d->WarningPushButton->setText(tr("The patients query failed. Please check the servers settings.")); - d->WarningPushButton->show(); - } - else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) - { - QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); - int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); - ctkDICOMPatientItemWidget* patientItemWidget = - qobject_cast(d->PatientsTabWidget->widget(patientIndex)); - if (patientItemWidget) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) + { + d->updateFiltersWarnings(); + d->SearchMenuButton->setIcon(QIcon(":/Icons/query_failed.svg")); + d->WarningPushButton->setText(tr("The patients query failed. Please check the servers settings.")); + d->WarningPushButton->show(); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) { - patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::Failed); + QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); + int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); + ctkDICOMPatientItemWidget* patientItemWidget = + qobject_cast(d->PatientsTabWidget->widget(patientIndex)); + if (patientItemWidget) + { + patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::Failed); + } + d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_failed.svg")); } - d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_failed.svg")); } } //------------------------------------------------------------------------------ -void ctkDICOMVisualBrowserWidget::onJobFinished(const QVariant& data) +void ctkDICOMVisualBrowserWidget::onJobFinished(QList datas) { Q_D(ctkDICOMVisualBrowserWidget); - ctkDICOMJobDetail td = data.value(); - if (td.JobUID.isEmpty()) + foreach (QVariant data, datas) { - return; - } + ctkDICOMJobDetail td = data.value(); + if (td.JobUID.isEmpty()) + { + continue; + } - if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) - { - d->updateFiltersWarnings(); - d->SearchMenuButton->setIcon(QIcon(":/Icons/query_success.svg")); - } - else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) - { - QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); - int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); - ctkDICOMPatientItemWidget* patientItemWidget = - qobject_cast(d->PatientsTabWidget->widget(patientIndex)); - if (patientItemWidget) + if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryPatients) { - patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::Completed); + d->updateFiltersWarnings(); + d->SearchMenuButton->setIcon(QIcon(":/Icons/query_success.svg")); + } + else if (td.JobType == ctkDICOMJobResponseSet::JobType::QueryStudies) + { + QString patientItem = d->findPatientItemFromPatientInfo(td.PatientID); + int patientIndex = d->findPatientTabIndexFromPatientItem(patientItem); + ctkDICOMPatientItemWidget* patientItemWidget = + qobject_cast(d->PatientsTabWidget->widget(patientIndex)); + if (patientItemWidget) + { + patientItemWidget->setOperationStatus(ctkDICOMPatientItemWidget::Completed); + } + d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_success.svg")); } - d->PatientsTabWidget->setTabIcon(patientIndex, QIcon(":/Icons/patient_success.svg")); } } diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h index a3b0fc8a0c..4e6f32f4f2 100644 --- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h +++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h @@ -79,7 +79,7 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMVisualBrowserWidget : public QWidget Q_PROPERTY(QString databaseDirectoryBase READ databaseDirectoryBase WRITE setDatabaseDirectoryBase) Q_PROPERTY(QString filteringPatientID READ filteringPatientID WRITE setFilteringPatientID); Q_PROPERTY(QString filteringPatientName READ filteringPatientName WRITE setFilteringPatientName); - Q_PROPERTY(int numberOfStudiesPerPatient READ numberOfStudiesPerPatient WRITE setNumberOfStudiesPerPatient); + Q_PROPERTY(int numberOfOpenedStudiesPerPatient READ numberOfOpenedStudiesPerPatient WRITE setNumberOfOpenedStudiesPerPatient); Q_PROPERTY(ctkDICOMStudyItemWidget::ThumbnailSizeOption thumbnailSize READ thumbnailSize WRITE setThumbnailSize); Q_PROPERTY(ctkDICOMVisualBrowserWidget::ImportDirectoryMode ImportDirectoryMode READ importDirectoryMode WRITE setImportDirectoryMode) Q_PROPERTY(bool sendActionVisible READ isSendActionVisible WRITE setSendActionVisible) @@ -220,8 +220,8 @@ class CTK_DICOM_WIDGETS_EXPORT ctkDICOMVisualBrowserWidget : public QWidget ///@{ /// Number of non collapsed studies per patient /// 2 by default - void setNumberOfStudiesPerPatient(int numberOfStudiesPerPatient); - int numberOfStudiesPerPatient() const; + void setNumberOfOpenedStudiesPerPatient(int numberOfOpenedStudiesPerPatient); + int numberOfOpenedStudiesPerPatient() const; ///@} ///@{ @@ -368,11 +368,11 @@ public Q_SLOTS: ///@{ /// update GUI after query/retrieve operations - void updateGUIFromScheduler(const QVariant&); - void onJobStarted(const QVariant&); - void onJobUserStopped(const QVariant&); - void onJobFailed(const QVariant&); - void onJobFinished(const QVariant&); + void updateGUIFromScheduler(QList); + void onJobStarted(QList); + void onJobUserStopped(QList); + void onJobFailed(QList); + void onJobFinished(QList); ///@} /// stops all the operations