diff --git a/Libs/DICOM/Widgets/Resources/UI/Icons/more_vert.svg b/Libs/DICOM/Widgets/Resources/UI/Icons/more_vert.svg
index e172f878a6..6d94a46582 100644
--- a/Libs/DICOM/Widgets/Resources/UI/Icons/more_vert.svg
+++ b/Libs/DICOM/Widgets/Resources/UI/Icons/more_vert.svg
@@ -1 +1,38 @@
-
\ No newline at end of file
+
+
diff --git a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui
index 88805e26fb..07820eb749 100644
--- a/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui
+++ b/Libs/DICOM/Widgets/Resources/UI/ctkDICOMVisualBrowserWidget.ui
@@ -36,6 +36,52 @@
2
+ -
+
+
+ color: rgb(0, 0, 0);background-color: rgb(245, 245, 170);
+
+
+ QFrame::Box
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Warning
+
+
+
+ -
+
+
+ Update database
+
+
+
+ -
+
+
+ Create new database
+
+
+
+ -
+
+
+ Select database folder
+
+
+
+
+
+
-
-
@@ -733,8 +779,8 @@ Please set at least one filter to query the servers
- 32
- 32
+ 24
+ 24
diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp
index f26cdddf28..b5735efa3f 100644
--- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp
+++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.cpp
@@ -29,12 +29,14 @@
#include
#include
#include
+#include
#include
#include
#include
// CTK includes
#include
+#include
#include
#include
#include
@@ -136,6 +138,7 @@ class ctkDICOMVisualBrowserWidgetPrivate: public Ui_ctkDICOMVisualBrowserWidget
void importDirectory(QString directory, ctkDICOMVisualBrowserWidget::ImportDirectoryMode mode);
void importFiles(const QStringList& files, ctkDICOMVisualBrowserWidget::ImportDirectoryMode mode);
void importOldSettings();
+ void showUpdateSchemaDialog();
void updateModalityCheckableComboBox();
void createPatients();
void updateFiltersWarnings();
@@ -228,6 +231,8 @@ class ctkDICOMVisualBrowserWidgetPrivate: public Ui_ctkDICOMVisualBrowserWidget
bool IsLoading;
ctkDICOMServerNodeWidget2* ServerNodeWidget;
+ QProgressDialog *UpdateSchemaProgress;
+ QProgressDialog *ExportProgress;
};
CTK_GET_CPP(ctkDICOMVisualBrowserWidget, QString, databaseDirectoryBase, DatabaseDirectoryBase);
@@ -289,6 +294,9 @@ ctkDICOMVisualBrowserWidgetPrivate::ctkDICOMVisualBrowserWidgetPrivate(ctkDICOMV
this->ServerNodeWidget = new ctkDICOMServerNodeWidget2();
this->ServerNodeWidget->setScheduler(this->Scheduler);
this->connectScheduler();
+
+ this->ExportProgress = nullptr;
+ this->UpdateSchemaProgress = nullptr;
}
//----------------------------------------------------------------------------
@@ -303,6 +311,14 @@ void ctkDICOMVisualBrowserWidgetPrivate::init()
Q_Q(ctkDICOMVisualBrowserWidget);
this->setupUi(q);
+ this->DatabaseDirectoryProblemFrame->hide();
+ QObject::connect(this->SelectDatabaseDirectoryButton, SIGNAL(clicked()),
+ q, SLOT(selectDatabaseDirectory()));
+ QObject::connect(this->CreateNewDatabaseButton, SIGNAL(clicked()),
+ q, SLOT(createNewDatabaseDirectory()));
+ QObject::connect(this->UpdateDatabaseButton, SIGNAL(clicked()),
+ q, SLOT(updateDatabase()));
+
this->WarningPushButton->hide();
QObject::connect(this->FilteringPatientIDSearchBox, SIGNAL(textChanged(QString)),
q, SLOT(onFilteringPatientIDChanged()));
@@ -327,20 +343,11 @@ void ctkDICOMVisualBrowserWidgetPrivate::init()
q, SLOT(onQueryPatients()));
this->ServersSettingsCollapsibleGroupBox->layout()->addWidget(this->ServerNodeWidget);
-
- QSize iconSize(28, 28);
- this->PatientsTabWidget->setIconSize(iconSize);
this->PatientsTabWidget->clear();
- // setup patients menu on a tool button on the tab bar
- QTabBar* tabWidget = this->PatientsTabWidget->tabBar();
- tabWidget->setDocumentMode(true);
- tabWidget->setExpanding(true);
-
+ // setup patients menu
this->patientsTabMenuToolButton = new QToolButton(q);
this->patientsTabMenuToolButton->setObjectName("patientsTabMenuToolButton");
- this->patientsTabMenuToolButton->setIconSize(iconSize);
- this->patientsTabMenuToolButton->setFixedHeight(40);
this->patientsTabMenuToolButton->setCheckable(false);
this->patientsTabMenuToolButton->setChecked(false);
this->patientsTabMenuToolButton->setIcon(QIcon(":/Icons/more_vert.svg"));
@@ -349,8 +356,7 @@ void ctkDICOMVisualBrowserWidgetPrivate::init()
QObject::connect(this->patientsTabMenuToolButton, SIGNAL(clicked()),
q, SLOT(onPatientsTabMenuToolButtonClicked()));
- this->PatientsTabWidget->setCornerWidget(this->patientsTabMenuToolButton, Qt::TopRightCorner);
-
+ this->PatientsTabWidget->setCornerWidget(this->patientsTabMenuToolButton, Qt::TopLeftCorner);
QObject::connect(this->PatientsTabWidget, SIGNAL(currentChanged(int)),
q, SLOT(onPatientItemChanged(int)));
@@ -485,12 +491,45 @@ void ctkDICOMVisualBrowserWidgetPrivate::importOldSettings()
QSettings settings;
int dontConfirmCopyOnImport = settings.value("MainWindow/DontConfirmCopyOnImport", static_cast(QMessageBox::InvalidRole)).toInt();
if (dontConfirmCopyOnImport == QMessageBox::AcceptRole)
- {
+ {
settings.setValue("DICOM/ImportDirectoryMode", static_cast(ctkDICOMVisualBrowserWidget::ImportDirectoryCopy));
- }
+ }
settings.remove("MainWindow/DontConfirmCopyOnImport");
}
+//----------------------------------------------------------------------------
+void ctkDICOMVisualBrowserWidgetPrivate::showUpdateSchemaDialog()
+{
+ Q_Q(ctkDICOMVisualBrowserWidget);
+ if (this->UpdateSchemaProgress == 0)
+ {
+ //
+ // Set up the Update Schema Progress Dialog
+ //
+ this->UpdateSchemaProgress = new QProgressDialog(
+ ctkDICOMVisualBrowserWidget::tr("DICOM Schema Update"), ctkDICOMVisualBrowserWidget::tr("Cancel"), 0, 100, q, Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
+
+ // We don't want the progress dialog to resize itself, so we bypass the label by creating our own
+ QLabel* progressLabel = new QLabel(ctkDICOMVisualBrowserWidget::tr("Initialization..."));
+ this->UpdateSchemaProgress->setLabel(progressLabel);
+ this->UpdateSchemaProgress->setWindowModality(Qt::ApplicationModal);
+ this->UpdateSchemaProgress->setMinimumDuration(0);
+ this->UpdateSchemaProgress->setValue(0);
+
+ q->connect(DicomDatabase.data(), SIGNAL(schemaUpdateStarted(int)),
+ this->UpdateSchemaProgress, SLOT(setMaximum(int)));
+ q->connect(DicomDatabase.data(), SIGNAL(schemaUpdateProgress(int)),
+ this->UpdateSchemaProgress, SLOT(setValue(int)));
+ q->connect(DicomDatabase.data(), SIGNAL(schemaUpdateProgress(QString)),
+ progressLabel, SLOT(setText(QString)));
+
+ // close the dialog
+ q->connect(this->DicomDatabase.data(), SIGNAL(schemaUpdated()),
+ this->UpdateSchemaProgress, SLOT(close()));
+ }
+ this->UpdateSchemaProgress->show();
+}
+
//----------------------------------------------------------------------------
void ctkDICOMVisualBrowserWidgetPrivate::updateModalityCheckableComboBox()
{
@@ -1931,9 +1970,17 @@ void ctkDICOMVisualBrowserWidget::setDatabaseDirectory(const QString &directory)
bool success = true;
if (!QDir(absDirectory).exists()
- || (!QDir(absDirectory).isEmpty() && !QFile(databaseFileName).exists()))
+ || (!ctk::isDirEmpty(QDir(absDirectory)) && !QFile(databaseFileName).exists()))
{
logger.warn(tr("Database folder does not contain ctkDICOM.sql file: ") + absDirectory + "\n");
+ d->DatabaseDirectoryProblemFrame->show();
+ d->DatabaseDirectoryProblemLabel->setText(
+ //: %1 is the folder path
+ tr("No valid DICOM database found in folder %1.").arg(absDirectory)
+ );
+ d->UpdateDatabaseButton->hide();
+ d->CreateNewDatabaseButton->show();
+ d->SelectDatabaseDirectoryButton->show();
success = false;
}
@@ -1947,12 +1994,21 @@ void ctkDICOMVisualBrowserWidget::setDatabaseDirectory(const QString &directory)
}
catch (std::exception e)
{
+ Q_UNUSED(e);
databaseOpenSuccess = false;
}
if (!databaseOpenSuccess || d->DicomDatabase->schemaVersionLoaded().isEmpty())
{
logger.warn(tr("Database error: %1 \n").arg(d->DicomDatabase->lastError()));
d->DicomDatabase->closeDatabase();
+ d->DatabaseDirectoryProblemFrame->show();
+ d->DatabaseDirectoryProblemLabel->setText(
+ //: %1 is the folder path
+ tr("No valid DICOM database found in folder %1.").arg(absDirectory)
+ );
+ d->UpdateDatabaseButton->hide();
+ d->CreateNewDatabaseButton->show();
+ d->SelectDatabaseDirectoryButton->show();
success = false;
}
}
@@ -1964,10 +2020,23 @@ void ctkDICOMVisualBrowserWidget::setDatabaseDirectory(const QString &directory)
logger.warn(tr("Database version mismatch: version of selected database = %1, version required = %2 \n")
.arg(d->DicomDatabase->schemaVersionLoaded()).arg(d->DicomDatabase->schemaVersion()));
d->DicomDatabase->closeDatabase();
+ d->DatabaseDirectoryProblemFrame->show();
+ d->DatabaseDirectoryProblemLabel->setText(
+ //: %1 is the folder path
+ tr("Incompatible DICOM database version found in folder %1.").arg(absDirectory)
+ );
+ d->UpdateDatabaseButton->show();
+ d->CreateNewDatabaseButton->show();
+ d->SelectDatabaseDirectoryButton->show();
success = false;
}
}
+ if (success)
+ {
+ d->DatabaseDirectoryProblemFrame->hide();
+ }
+
// Save new database directory in this object and in application settings.
d->DatabaseDirectory = absDirectory;
if (!d->DatabaseDirectorySettingsKey.isEmpty())
@@ -1977,6 +2046,8 @@ void ctkDICOMVisualBrowserWidget::setDatabaseDirectory(const QString &directory)
settings.sync();
}
+ this->onShowPatients();
+
// pass DICOM database instance to Import widget
emit databaseDirectoryChanged(absDirectory);
}
@@ -2084,6 +2155,123 @@ void ctkDICOMVisualBrowserWidget::onIndexingComplete(int patientsAdded, int stud
d->createPatients();
}
+//------------------------------------------------------------------------------
+void ctkDICOMVisualBrowserWidget::selectDatabaseDirectory()
+{
+ Q_D(const ctkDICOMVisualBrowserWidget);
+ d->DatabaseDirectoryProblemFrame->hide();
+ ctkDirectoryButton directoryButton(this);
+ directoryButton.setDirectory(d->DatabaseDirectory);
+ QString dir = directoryButton.browse();
+ this->setDatabaseDirectory(dir);
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMVisualBrowserWidget::createNewDatabaseDirectory()
+{
+ Q_D(ctkDICOMVisualBrowserWidget);
+
+ // Use the current database folder as a basis for the new name
+ QString baseFolder = this->databaseDirectory();
+ if (baseFolder.isEmpty())
+ {
+ baseFolder = d->DefaultDatabaseDirectory;
+ }
+ else
+ {
+ // only use existing folder name as a basis if it is empty or
+ // a valid database
+ if (!ctk::isDirEmpty(QDir(baseFolder)))
+ {
+ QString databaseFileName = QDir(baseFolder).filePath("ctkDICOM.sql");
+ if (!QFile(databaseFileName).exists())
+ {
+ // current folder is a non-empty and not a DICOM database folder
+ // create a subfolder for the new DICOM database based on the name
+ // of default database path
+ QFileInfo defaultFolderInfo(d->DefaultDatabaseDirectory);
+ QString defaultSubfolderName = defaultFolderInfo.fileName();
+ if (defaultSubfolderName.isEmpty())
+ {
+ defaultSubfolderName = defaultFolderInfo.dir().dirName();
+ }
+ baseFolder += "/" + defaultSubfolderName;
+ }
+ }
+ }
+ // Remove existing numerical suffix
+ QString separator = "_";
+ bool isSuffixValid = false;
+ QString suffixStr = baseFolder.split(separator).last();
+ int suffixStart = suffixStr.toInt(&isSuffixValid);
+ if (isSuffixValid)
+ {
+ QStringList baseFolderComponents = baseFolder.split(separator);
+ baseFolderComponents.removeLast();
+ baseFolder = baseFolderComponents.join(separator);
+ }
+ // Try folder names, starting with the current one,
+ // incrementing the original numerical suffix.
+ int attemptsCount = 100;
+ for (int attempt=0; attemptDatabaseDirectoryProblemFrame->show();
+ d->DatabaseDirectoryProblemLabel->setText(
+ //: %1 is the folder path
+ tr("Failed to create new database in folder %1.").arg(QDir(baseFolder).absolutePath())
+ );
+ d->UpdateDatabaseButton->hide();
+ d->CreateNewDatabaseButton->show();
+ d->SelectDatabaseDirectoryButton->show();
+}
+
+//------------------------------------------------------------------------------
+void ctkDICOMVisualBrowserWidget::updateDatabase()
+{
+ Q_D(ctkDICOMVisualBrowserWidget);
+ d->DatabaseDirectoryProblemFrame->hide();
+ d->showUpdateSchemaDialog();
+ QString dir = this->databaseDirectory();
+ // open DICOM database on the directory
+ QString databaseFileName = QDir(dir).filePath("ctkDICOM.sql");
+ try
+ {
+ d->DicomDatabase->openDatabase(databaseFileName);
+ }
+ catch (const std::exception& e)
+ {
+ Q_UNUSED(e);
+ std::cerr << "Database error: " << qPrintable(d->DicomDatabase->lastError()) << "\n";
+ d->DicomDatabase->closeDatabase();
+ return;
+ }
+ d->DicomDatabase->updateSchema();
+ // Update GUI
+ this->setDatabaseDirectory(dir);
+}
+
//------------------------------------------------------------------------------
QStringList ctkDICOMVisualBrowserWidget::fileListForCurrentSelection(ctkDICOMModel::IndexType level,
QList selectedWidgets)
@@ -2905,6 +3093,7 @@ void ctkDICOMVisualBrowserWidget::exportSeries(QString dirPath, QStringList uids
}
destinationDir += sep;
+
// create the destination directory if necessary
if (!QDir().exists(destinationDir))
{
@@ -2922,7 +3111,23 @@ void ctkDICOMVisualBrowserWidget::exportSeries(QString dirPath, QStringList uids
}
}
+ // show progress
+ if (d->ExportProgress == 0)
+ {
+ d->ExportProgress = new QProgressDialog(tr("DICOM Export"), tr("Close"), 0, 100, this, Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
+ d->ExportProgress->setWindowModality(Qt::ApplicationModal);
+ d->ExportProgress->setMinimumDuration(0);
+ }
+ QLabel *exportLabel = new QLabel(
+ //: %1 is the series number
+ tr("Exporting series %1").arg(seriesNumber)
+ );
+ d->ExportProgress->setLabel(exportLabel);
+ d->ExportProgress->setValue(0);
+
int fileNumber = 0;
+ int numFiles = filesForSeries.size();
+ d->ExportProgress->setMaximum(numFiles);
foreach (const QString& filePath, filesForSeries)
{
// File name example: my/destination/folder/000001.dcm
@@ -2930,11 +3135,12 @@ void ctkDICOMVisualBrowserWidget::exportSeries(QString dirPath, QStringList uids
if (!QFile::exists(filePath))
{
+ d->ExportProgress->setValue(numFiles);
//: %1 is the file path
QString errorString = tr("Export source file not found:\n\n%1"
"\n\nHalting export.\n\nError may be fixed via Repair.")
.arg(filePath);
- ctkMessageBox copyErrorMessageBox(this);
+ ctkMessageBox copyErrorMessageBox;
copyErrorMessageBox.setText(errorString);
copyErrorMessageBox.setIcon(QMessageBox::Warning);
copyErrorMessageBox.exec();
@@ -2942,6 +3148,7 @@ void ctkDICOMVisualBrowserWidget::exportSeries(QString dirPath, QStringList uids
}
if (QFile::exists(destinationFileName))
{
+ d->ExportProgress->setValue(numFiles);
//: %1 is the destination file name
QString errorString = tr("Export destination file already exists:\n\n%1"
"\n\nHalting export.")
@@ -2956,6 +3163,7 @@ void ctkDICOMVisualBrowserWidget::exportSeries(QString dirPath, QStringList uids
bool copyResult = QFile::copy(filePath, destinationFileName);
if (!copyResult)
{
+ d->ExportProgress->setValue(numFiles);
//: %1 and %2 refers to source and destination file paths
QString errorString = tr("Failed to copy\n\n%1\n\nto\n\n%2"
"\n\nHalting export.")
@@ -2969,8 +3177,10 @@ void ctkDICOMVisualBrowserWidget::exportSeries(QString dirPath, QStringList uids
}
fileNumber++;
+ d->ExportProgress->setValue(fileNumber);
}
- }
+ d->ExportProgress->setValue(numFiles);
+ }
}
//------------------------------------------------------------------------------
diff --git a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h
index 313ed5bcf6..08d7ec944d 100644
--- a/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h
+++ b/Libs/DICOM/Widgets/ctkDICOMVisualBrowserWidget.h
@@ -299,6 +299,16 @@ public Q_SLOTS:
void onIndexingProgressDetail(const QString&);
void onIndexingComplete(int patientsAdded, int studiesAdded, int seriesAdded, int imagesAdded);
+ /// Show pop-up window for the user to select database directory
+ void selectDatabaseDirectory();
+
+ /// Create new database directory.
+ /// Current database directory used as a basis.
+ void createNewDatabaseDirectory();
+
+ /// Update database in-place to required schema version
+ void updateDatabase();
+
void onFilteringPatientIDChanged();
void onFilteringPatientNameChanged();
void onFilteringStudyDescriptionChanged();
diff --git a/Libs/Widgets/ctkDirectoryButton.cpp b/Libs/Widgets/ctkDirectoryButton.cpp
index 85ebc1135b..672b0c3db9 100644
--- a/Libs/Widgets/ctkDirectoryButton.cpp
+++ b/Libs/Widgets/ctkDirectoryButton.cpp
@@ -250,7 +250,7 @@ void ctkDirectoryButton::setAcceptMode(QFileDialog::AcceptMode mode)
}
//-----------------------------------------------------------------------------
-void ctkDirectoryButton::browse()
+QString ctkDirectoryButton::browse()
{
// See https://bugreports.qt-project.org/browse/QTBUG-10244
class ExcludeReadOnlyFilterProxyModel : public QSortFilterProxyModel
@@ -308,9 +308,10 @@ void ctkDirectoryButton::browse()
// An empty directory means either that the user cancelled the dialog or the selected directory is readonly
if (dir.isEmpty())
{
- return;
+ return "";
}
this->setDirectory(dir);
+ return dir;
}
//-----------------------------------------------------------------------------
diff --git a/Libs/Widgets/ctkDirectoryButton.h b/Libs/Widgets/ctkDirectoryButton.h
index ae53a0f03e..2d92dfb36d 100644
--- a/Libs/Widgets/ctkDirectoryButton.h
+++ b/Libs/Widgets/ctkDirectoryButton.h
@@ -161,12 +161,12 @@ class CTK_WIDGETS_EXPORT ctkDirectoryButton: public QWidget
/// set horizontal policy to QSizePolicy::Ignored and set elideMode to
/// Qt::ElideMiddle (or anything else than Qt::ElideNone).
void setElideMode(Qt::TextElideMode newMode);
- Qt::TextElideMode elideMode()const;
+ Qt::TextElideMode elideMode()const;
public Q_SLOTS:
/// browse() opens a pop up where the user can select a new directory for the
/// button. browse() is automatically called when the button is clicked.
- void browse();
+ QString browse();
Q_SIGNALS:
/// directoryChanged is emitted when the current directory changes.